for loops

This commit is contained in:
2026-06-22 20:11:18 +02:00
parent 380b5943b3
commit 27f42dd253
15 changed files with 1369 additions and 22 deletions
+208 -6
View File
@@ -611,7 +611,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
}
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Keyed:
append(&stack, expr.left)
case .Add, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
case .Add, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
append(&stack, expr.left, expr.right)
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Name:
}
@@ -638,6 +638,9 @@ mark_block_imports_used :: proc(checker: ^Checker, statements: []ast.Stmt_Id, fi
update := [1]ast.Stmt_Id{statement.update}
mark_block_imports_used(checker, update[:], file)
}
case .For:
mark_expr_imports_used(checker, statement.expr, file)
mark_block_imports_used(checker, statement.body, file)
case .Invalid:
}
}
@@ -1028,6 +1031,23 @@ infer_compound_expr :: proc(
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded)
return types.BOOL
case .Range:
left := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
right := infer_nested_expr(checker, expr.right, locals, pkg, file, demanded)
left_const := eval_constant(checker, expr.left)
right_const := eval_constant(checker, expr.right)
child := types.INVALID
if left_const.kind == .Value && right_const.kind != .Value {
child = right
} else if right_const.kind == .Value && left_const.kind != .Value {
child = left
} else {
child = types.widest(left, right)
}
if !types.is_concrete_integer(child) {
return types.INVALID
}
return types.range(store, child)
case .String:
return string_literal_type(checker, expr.integer)
case .Array:
@@ -1165,7 +1185,7 @@ infer_expr :: proc(
_ = pop(&stack)
case .String, .Array, .None, .Address, .Deref, .Index, .Slice,
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed,
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded)
_ = pop(&stack)
case .Name:
@@ -1413,7 +1433,7 @@ infer_statements :: proc(
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
if !types.is_valid(result^) {
result^ = returned
} else {
} else if !types.equal(result^, returned) {
result^ = types.widest(result^, returned)
}
}
@@ -1438,6 +1458,29 @@ infer_statements :: proc(
update := [1]ast.Stmt_Id{statement.update}
infer_statements(checker, update[:], locals, pkg, file, demanded, result)
}
case .For:
iterable_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
capture_start := len(locals^)
capture_type := types.INVALID
if types.is_range(iterable_type, &checker.module.types) {
capture_type = types.child_type(iterable_type, &checker.module.types)
} else {
item, ok := sequence_item(iterable_type, &checker.module.types)
if ok {
capture_type = item.child
if statement.pointer_capture {
capture_type = types.pointer(&checker.module.types, item.child, item.mutable, false)
}
}
}
if symbol.is_valid(statement.name) {
append(locals, Infer_Local{name=statement.name, type=capture_type})
}
if symbol.is_valid(statement.index_name) {
append(locals, Infer_Local{name=statement.index_name, type=types.USIZE})
}
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
resize(locals, capture_start)
}
}
resize(locals, scope_start)
@@ -1899,6 +1942,19 @@ hir_is_location :: proc(checker: ^Checker, expr_id: hir.Expr_Id) -> bool {
return false
}
sequence_item :: proc(value: types.Type, store: ^types.Store) -> (types.Node, bool) {
item, ok := types.node(store, value)
if ok && (item.kind == .Array || item.kind == .Slice) {
return item, true
}
pointer, array, pointer_ok := types.array_pointer(value, store)
if pointer_ok {
array.mutable = pointer.mutable && array.mutable
return array, true
}
return {}, false
}
find_struct_field :: proc(checker: ^Checker, struct_type: types.Type, name: symbol.Id) -> (int, types.Field, bool) {
for field, index in types.fields_for(&checker.module.types, struct_type) {
if field.name == u32(name) {
@@ -2192,6 +2248,46 @@ build_compound_expr :: proc(
kind=.Orelse, span=expr.span, type=child, left=optional, right=fallback,
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Range:
expected_child := types.INVALID
if types.is_range(expected, store) {
expected_child = types.child_type(expected, store)
}
left_const := eval_constant(checker, expr.left)
right_const := eval_constant(checker, expr.right)
left, right: hir.Expr_Id
if types.is_valid(expected_child) {
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, expected_child, pkg, file)
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, expected_child, pkg, file)
} else if right_const.kind == .Value && left_const.kind != .Value {
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, checker.module.exprs[left].type, pkg, file)
} else if left_const.kind == .Value && right_const.kind != .Value {
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, checker.module.exprs[right].type, pkg, file)
} else {
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
}
child := expected_child
if !types.is_valid(child) {
child = types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
}
if !types.is_concrete_integer(child) {
id := source.add(checker.diagnostics, expr.span, "range bounds must be compatible concrete integers")
return invalid_hir_expr(checker, expr.span, id)
}
left = coerce_expr(checker, left, child, checker.module.exprs[left].span)
right = coerce_expr(checker, right, child, checker.module.exprs[right].span)
args := make([]hir.Expr_Id, 2, checker.allocator)
args[0] = left
args[1] = right
return add_hir_expr(checker, hir.Expr{
kind=.Range, span=expr.span, type=types.range(store, child),
integer=i64(expr.integer), args=args,
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Bool:
return add_hir_expr(checker, hir.Expr{
kind=.Bool, span=expr.span, type=types.BOOL, integer=i64(expr.integer),
@@ -2391,7 +2487,7 @@ build_expr :: proc(
switch expr.kind {
case .String, .Array, .None, .Address, .Deref, .Index, .Slice,
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed,
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
last = build_compound_expr(
checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
)
@@ -2849,11 +2945,16 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator)
}
build_block :: proc(ctx: ^Build_Ctx, statements: []ast.Stmt_Id) -> []hir.Stmt_Id {
build_block :: proc(
ctx: ^Build_Ctx,
statements: []ast.Stmt_Id,
duplicate_scope_start := -1,
) -> []hir.Stmt_Id {
checker := ctx.checker
body: [dynamic]hir.Stmt_Id
body.allocator = checker.allocator
scope_start := len(ctx.locals^)
duplicate_start := scope_start if duplicate_scope_start < 0 else duplicate_scope_start
for statement_id in statements {
statement := checker.ast_module.statements[statement_id]
switch statement.kind {
@@ -2876,7 +2977,7 @@ build_block :: proc(ctx: ^Build_Ctx, statements: []ast.Stmt_Id) -> []hir.Stmt_Id
value = invalid_hir_expr(checker, statement.span, id)
value_type = types.INVALID
}
if _, found := find_build_local(ctx.locals^[scope_start:], statement.name); found {
if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); found {
id := source.addf(
checker.diagnostics, statement.span,
"duplicate local '%s'", symbol_text(checker, statement.name),
@@ -3126,6 +3227,107 @@ build_block :: proc(ctx: ^Build_Ctx, statements: []ast.Stmt_Id) -> []hir.Stmt_Id
diagnostic=source.INVALID_DIAGNOSTIC,
})
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
case .For:
iterable := build_expr(
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
types.INVALID, ctx.pkg, ctx.file,
)
iterable_type := checker.module.exprs[iterable].type
capture_type := types.I64
iterator_type := types.INVALID
valid_loop := checker.module.exprs[iterable].kind != .Invalid
diagnostic := source.INVALID_DIAGNOSTIC
is_range := types.is_range(iterable_type, &checker.module.types)
if is_range {
capture_type = types.child_type(iterable_type, &checker.module.types)
if statement.pointer_capture {
diagnostic = source.add(checker.diagnostics, statement.span, "range loops do not support pointer captures")
valid_loop = false
}
if symbol.is_valid(statement.index_name) {
diagnostic = source.add(checker.diagnostics, statement.span, "range loops do not support index captures")
valid_loop = false
}
} else {
item, ok := sequence_item(iterable_type, &checker.module.types)
if !ok {
diagnostic = source.add(
checker.diagnostics,
statement.span,
"for-loop iterable must be a range, array, slice, or pointer-to-array",
)
valid_loop = false
} else {
iterator_type = types.pointer(
&checker.module.types,
item.child,
item.mutable,
true,
item.has_sentinel,
item.sentinel,
)
capture_type = item.child
if statement.pointer_capture {
_, _, array_pointer_ok := types.array_pointer(iterable_type, &checker.module.types)
if !types.is_slice(iterable_type, &checker.module.types) && !array_pointer_ok {
diagnostic = source.add(
checker.diagnostics,
statement.span,
"pointer capture over an array requires a pointer-to-array such as '&items'",
)
valid_loop = false
}
capture_type = types.pointer(&checker.module.types, item.child, item.mutable, false)
}
}
}
capture_start := len(ctx.locals^)
item_local := hir.local_id(len(ctx.hir_locals^))
append(ctx.hir_locals, hir.Local{name=statement.name, type=capture_type, mutable=false})
append(ctx.locals, Build_Local{name=statement.name, type=capture_type, mutable=false, id=item_local})
index_local := hir.INVALID_LOCAL
if symbol.is_valid(statement.index_name) {
if statement.index_name == statement.name {
diagnostic = source.add(checker.diagnostics, statement.span, "for-loop captures must have distinct names")
valid_loop = false
} else {
index_local = hir.local_id(len(ctx.hir_locals^))
append(ctx.hir_locals, hir.Local{name=statement.index_name, type=types.USIZE, mutable=false})
append(ctx.locals, Build_Local{name=statement.index_name, type=types.USIZE, mutable=false, id=index_local})
}
}
loop_body := build_block(ctx, statement.body, capture_start)
resize(ctx.locals, capture_start)
append(&body, hir.stmt_id(len(checker.module.statements)))
if valid_loop {
append(&checker.module.statements, hir.Stmt{
kind=.For,
span=statement.span,
local=item_local,
index_local=index_local,
expr=iterable,
iterator_type=iterator_type,
pointer_capture=statement.pointer_capture,
then_body=loop_body,
update=hir.INVALID_STMT,
target=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
} else {
delete(loop_body, checker.allocator)
append(&checker.module.statements, hir.Stmt{
kind=.Trap,
span=statement.span,
local=hir.INVALID_LOCAL,
index_local=hir.INVALID_LOCAL,
expr=hir.INVALID_EXPR,
target=hir.INVALID_EXPR,
diagnostic=diagnostic,
})
ctx.problematic^ = true
}
case .Invalid:
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{