for loops
This commit is contained in:
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user