booleans, comparisons, and if/else

This commit is contained in:
2026-06-21 20:20:55 +02:00
parent 19e9fbdd4b
commit c90ada608e
13 changed files with 1064 additions and 518 deletions
+421 -383
View File
@@ -44,6 +44,23 @@ Build_Local :: struct {
id: hir.Local_Id,
}
// Build_Ctx threads the per-function accumulators through build_block so that
// nested control-flow blocks (if/else) can be built recursively. `locals` is a
// scope stack: each block records its entry length and truncates back to it on
// exit, while `hir_locals` keeps every allocated slot for the function.
Build_Ctx :: struct {
checker: ^Checker,
pkg: ast.Package_Id,
file: ast.File_Id,
result: types.Type,
locals: ^[dynamic]Build_Local,
hir_locals: ^[dynamic]hir.Local,
global_reads: ^[dynamic]hir.Global_Id,
calls: ^[dynamic]hir.Function_Id,
problematic: ^bool,
has_return: ^bool,
}
Constant_Kind :: enum {
Unknown,
Not_Constant,
@@ -593,11 +610,29 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
if expr.left != ast.INVALID_EXPR {
append(&stack, expr.left)
}
case .Negate, .Address, .Deref, .Field, .Unwrap, .Keyed:
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Keyed:
append(&stack, expr.left)
case .Add, .Index, .Orelse:
case .Add, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
append(&stack, expr.left, expr.right)
case .Invalid, .Integer, .Float, .String, .None, .Name:
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Name:
}
}
}
mark_block_imports_used :: proc(checker: ^Checker, statements: []ast.Stmt_Id, file: ast.File_Id) {
for statement_id in statements {
statement := checker.ast_module.statements[statement_id]
switch statement.kind {
case .Declaration, .Assignment, .Return, .Expression:
mark_expr_imports_used(checker, statement.expr, file)
if statement.target != ast.INVALID_EXPR {
mark_expr_imports_used(checker, statement.target, file)
}
case .If:
mark_expr_imports_used(checker, statement.expr, file)
mark_block_imports_used(checker, statement.body, file)
mark_block_imports_used(checker, statement.else_body, file)
case .Invalid:
}
}
}
@@ -772,17 +807,7 @@ validate_declarations :: proc(checker: ^Checker) {
)
}
}
for statement_id in function.body {
statement := checker.ast_module.statements[statement_id]
switch statement.kind {
case .Declaration, .Assignment, .Return, .Expression:
mark_expr_imports_used(checker, statement.expr, function.file)
if statement.target != ast.INVALID_EXPR {
mark_expr_imports_used(checker, statement.target, function.file)
}
case .Invalid:
}
}
mark_block_imports_used(checker, function.body, function.file)
delete(locals)
}
for function, function_id in checker.ast_module.functions {
@@ -988,6 +1013,15 @@ infer_compound_expr :: proc(
) -> types.Type {
store := &checker.module.types
#partial switch expr.kind {
case .Bool:
return types.BOOL
case .Not:
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
return types.BOOL
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded)
return types.BOOL
case .String:
return string_literal_type(checker, expr.integer)
case .Array:
@@ -1124,7 +1158,8 @@ infer_expr :: proc(
last = types.F64
_ = pop(&stack)
case .String, .Array, .None, .Address, .Deref, .Index, .Slice,
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed:
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed,
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded)
_ = pop(&stack)
case .Name:
@@ -1342,6 +1377,49 @@ infer_expr :: proc(
return last
}
infer_statements :: proc(
checker: ^Checker,
statements: []ast.Stmt_Id,
locals: ^[dynamic]Infer_Local,
pkg: ast.Package_Id,
file: ast.File_Id,
demanded: ^[dynamic]Spec_Id,
result: ^types.Type,
) {
scope_start := len(locals^)
for statement_id in statements {
statement := checker.ast_module.statements[statement_id]
#partial switch statement.kind {
case .Declaration:
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
declared_local := type_from_syntax(statement.type)
if is_runtime_type(checker, declared_local) {
value_type = declared_local
}
append(locals, Infer_Local{name = statement.name, type = value_type})
case .Assignment, .Expression:
if statement.target != ast.INVALID_EXPR {
_ = infer_expr(checker, statement.target, locals^[:], pkg, file, demanded)
}
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
case .Return:
if statement.expr != ast.INVALID_EXPR {
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
if !types.is_valid(result^) {
result^ = returned
} else {
result^ = types.widest(result^, returned)
}
}
case .If:
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
infer_statements(checker, statement.else_body, locals, pkg, file, demanded, result)
}
}
resize(locals, scope_start)
}
infer_spec_result :: proc(checker: ^Checker, id: Spec_Id, demanded: ^[dynamic]Spec_Id = nil) -> types.Type {
spec := checker.specs[id]
function := checker.ast_module.functions[spec.template]
@@ -1362,32 +1440,7 @@ infer_spec_result :: proc(checker: ^Checker, id: Spec_Id, demanded: ^[dynamic]Sp
}
result := types.INVALID
for statement_id in function.body {
statement := checker.ast_module.statements[statement_id]
#partial switch statement.kind {
case .Declaration:
value_type := infer_expr(checker, statement.expr, locals[:], function.pkg, function.file, demanded)
declared_local := type_from_syntax(statement.type)
if is_runtime_type(checker, declared_local) {
value_type = declared_local
}
append(&locals, Infer_Local{name = statement.name, type = value_type})
case .Assignment, .Expression:
if statement.target != ast.INVALID_EXPR {
_ = infer_expr(checker, statement.target, locals[:], function.pkg, function.file, demanded)
}
_ = infer_expr(checker, statement.expr, locals[:], function.pkg, function.file, demanded)
case .Return:
if statement.expr != ast.INVALID_EXPR {
returned := infer_expr(checker, statement.expr, locals[:], function.pkg, function.file, demanded)
if !types.is_valid(result) {
result = returned
} else {
result = types.widest(result, returned)
}
}
}
}
infer_statements(checker, function.body, &locals, function.pkg, function.file, demanded, &result)
if types.is_constraint(declared) {
return result
}
@@ -2116,6 +2169,90 @@ build_compound_expr :: proc(
kind=.Orelse, span=expr.span, type=child, left=optional, right=fallback,
target=hir.INVALID_REF, 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),
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Not:
operand := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
operand_type := checker.module.exprs[operand].type
if checker.module.exprs[operand].kind != .Invalid && !types.is_bool(operand_type) {
id := source.add(checker.diagnostics, expr.span, "'!' requires a bool operand")
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
}
return add_hir_expr(checker, hir.Expr{
kind=.Not, span=expr.span, type=types.BOOL, left=operand,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .And, .Or:
left := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
right := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.BOOL, pkg, file)
left_type := checker.module.exprs[left].type
right_type := checker.module.exprs[right].type
left_ok := checker.module.exprs[left].kind == .Invalid || types.is_bool(left_type)
right_ok := checker.module.exprs[right].kind == .Invalid || types.is_bool(right_type)
if !left_ok || !right_ok {
id := source.add(checker.diagnostics, expr.span, "'and'/'or' require bool operands")
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
}
return add_hir_expr(checker, hir.Expr{
kind=.And if expr.kind == .And else .Or, span=expr.span, type=types.BOOL,
left=left, right=right, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
// Contextualize a bare integer-literal operand to the other operand's type
// so comparisons like `count > 0` or `0 < count` type-check.
left_const := eval_constant(checker, expr.left)
right_const := eval_constant(checker, expr.right)
left, right: hir.Expr_Id
if right_const.kind == .Value && left_const.kind != .Value {
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
hint := checker.module.exprs[left].type
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, hint, 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)
hint := checker.module.exprs[right].type
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, 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)
}
left_type := checker.module.exprs[left].type
right_type := checker.module.exprs[right].type
if checker.module.exprs[left].kind == .Invalid || checker.module.exprs[right].kind == .Invalid {
return invalid_hir_expr(checker, expr.span, expr.diagnostic, types.BOOL)
}
operand_type := types.INVALID
if types.is_bool(left_type) && types.is_bool(right_type) {
if expr.kind != .Eq && expr.kind != .Ne {
id := source.add(checker.diagnostics, expr.span, "bool values only support '==' and '!='")
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
}
operand_type = types.BOOL
} else {
operand_type = types.widest(left_type, right_type)
if !types.is_concrete_scalar(operand_type) || types.is_bool(operand_type) {
id := source.add(checker.diagnostics, expr.span, "comparison requires compatible numeric operands")
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
}
}
left = coerce_expr(checker, left, operand_type, checker.module.exprs[left].span)
right = coerce_expr(checker, right, operand_type, checker.module.exprs[right].span)
compare_kind := hir.Expr_Kind.Eq
#partial switch expr.kind {
case .Eq: compare_kind = .Eq
case .Ne: compare_kind = .Ne
case .Lt: compare_kind = .Lt
case .Le: compare_kind = .Le
case .Gt: compare_kind = .Gt
case .Ge: compare_kind = .Ge
}
return add_hir_expr(checker, hir.Expr{
kind=compare_kind, span=expr.span, type=types.BOOL, left=left, right=right,
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Struct_Literal:
target_pkg, available := expr_package(checker, expr, pkg, file, true)
struct_type := types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
@@ -2230,7 +2367,8 @@ build_expr :: proc(
}
switch expr.kind {
case .String, .Array, .None, .Address, .Deref, .Index, .Slice,
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed:
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed,
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
last = build_compound_expr(
checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
)
@@ -2676,6 +2814,231 @@ 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 {
checker := ctx.checker
body: [dynamic]hir.Stmt_Id
body.allocator = checker.allocator
scope_start := len(ctx.locals^)
for statement_id in statements {
statement := checker.ast_module.statements[statement_id]
switch statement.kind {
case .Declaration:
declared := resolve_inferred_array(checker, type_from_syntax(statement.type), statement.expr)
expected := types.INVALID
if is_runtime_type(checker, declared) {
expected = declared
}
value := build_expr(
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
expected, ctx.pkg, ctx.file,
)
value_type := checker.module.exprs[value].type
if is_runtime_type(checker, declared) {
value = coerce_expr(checker, value, declared, statement.span)
value_type = checker.module.exprs[value].type
} else if types.is_void(declared) {
id := source.add(checker.diagnostics, statement.span, "locals cannot have type void")
value = invalid_hir_expr(checker, statement.span, id)
value_type = types.INVALID
}
if _, found := find_build_local(ctx.locals^[scope_start:], statement.name); found {
id := source.addf(
checker.diagnostics, statement.span,
"duplicate local '%s'", symbol_text(checker, statement.name),
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
continue
}
local_id := hir.local_id(len(ctx.hir_locals^))
append(ctx.hir_locals, hir.Local{
name = statement.name, type = value_type, mutable = !statement.immutable,
})
append(ctx.locals, Build_Local{
name = statement.name, type = value_type, mutable = !statement.immutable, id = local_id,
})
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Declaration, span = statement.span, local = local_id, expr = value,
diagnostic = source.INVALID_DIAGNOSTIC,
})
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
case .Assignment:
if statement.target != ast.INVALID_EXPR {
target_expr := build_expr(
checker, statement.target, ctx.locals^[:], ctx.global_reads, ctx.calls,
types.INVALID, ctx.pkg, ctx.file,
)
target_type := checker.module.exprs[target_expr].type
if !hir_location_writable(checker, target_expr, ctx.locals^[:]) {
id := source.add(checker.diagnostics, statement.span, "assignment target is not writable")
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind=.Trap, span=statement.span, local=hir.INVALID_LOCAL,
target=hir.INVALID_EXPR, expr=hir.INVALID_EXPR, diagnostic=id,
})
ctx.problematic^ = true
continue
}
value := build_expr(
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
target_type, ctx.pkg, ctx.file,
)
value = coerce_expr(checker, value, target_type, statement.span)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind=.Assignment, span=statement.span, local=hir.INVALID_LOCAL,
target=target_expr, expr=value, diagnostic=source.INVALID_DIAGNOSTIC,
})
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
continue
}
if statement.name == checker.sink_symbol {
value := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
if types.is_void(checker.module.exprs[value].type) {
id := source.add(checker.diagnostics, statement.span, "cannot assign a void expression to '_'")
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
} else {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Sink, span = statement.span, expr = value,
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
})
}
continue
}
local, found := find_build_local(ctx.locals^[:], statement.name)
if !found {
id := source.addf(checker.diagnostics, statement.span, "cannot assign unresolved local '%s'", symbol_text(checker, statement.name))
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
continue
}
if !local.mutable {
id := source.addf(checker.diagnostics, statement.span, "cannot assign immutable local '%s'", symbol_text(checker, statement.name))
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
continue
}
value := build_expr(
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
local.type, ctx.pkg, ctx.file,
)
value = coerce_expr(checker, value, local.type, statement.span)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Assignment, span = statement.span, expr = value, local = local.id,
target = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
})
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
case .Return:
ctx.has_return^ = true
if statement.expr == ast.INVALID_EXPR {
if !types.is_void(ctx.result) {
id := source.add(checker.diagnostics, statement.span, "'return _' is only valid in a void function")
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
} else {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Return, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
})
}
continue
}
if types.is_void(ctx.result) {
id := source.add(checker.diagnostics, statement.span, "void function cannot return a value")
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
continue
}
value := build_expr(
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
ctx.result, ctx.pkg, ctx.file,
)
value = coerce_expr(checker, value, ctx.result, statement.span)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Return, span = statement.span, expr = value,
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
})
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
case .Expression:
value := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
if !types.is_void(checker.module.exprs[value].type) {
id := source.add(checker.diagnostics, statement.span, "non-void expression result must be consumed or assigned to '_'")
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
} else {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Expression, span = statement.span, expr = value,
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
})
}
case .If:
condition := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.BOOL, ctx.pkg, ctx.file)
if checker.module.exprs[condition].kind != .Invalid && !types.is_bool(checker.module.exprs[condition].type) {
id := source.add(checker.diagnostics, statement.span, "'if' condition must be a bool")
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
ctx.problematic^ = true
}
then_body := build_block(ctx, statement.body)
else_body: []hir.Stmt_Id = nil
if statement.else_body != nil {
else_body = build_block(ctx, statement.else_body)
}
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .If, span = statement.span, expr = condition,
then_body = then_body, else_body = else_body,
local = hir.INVALID_LOCAL, target = hir.INVALID_EXPR,
diagnostic = source.INVALID_DIAGNOSTIC,
})
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
case .Invalid:
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = statement.diagnostic,
})
ctx.problematic^ = true
}
}
resize(ctx.locals, scope_start)
return body[:]
}
build_function :: proc(checker: ^Checker, id: Spec_Id) {
spec := checker.specs[id]
function := checker.ast_module.functions[spec.template]
@@ -2769,348 +3132,23 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
},
)
}
for statement_id in function.body {
statement := checker.ast_module.statements[statement_id]
switch statement.kind {
case .Declaration:
declared := resolve_inferred_array(checker, type_from_syntax(statement.type), statement.expr)
expected := types.INVALID
if is_runtime_type(checker, declared) {
expected = declared
}
value := build_expr(
checker,
statement.expr,
locals[:],
&global_reads,
&calls,
expected,
function.pkg,
function.file,
)
value_type := checker.module.exprs[value].type
if is_runtime_type(checker, declared) {
value = coerce_expr(checker, value, declared, statement.span)
value_type = checker.module.exprs[value].type
} else if types.is_void(declared) {
id := source.add(
checker.diagnostics,
statement.span,
"locals cannot have type void",
)
value = invalid_hir_expr(checker, statement.span, id)
value_type = types.INVALID
}
if _, found := find_build_local(locals[:], statement.name); found {
id := source.addf(
checker.diagnostics,
statement.span,
"duplicate local '%s'",
symbol_text(checker, statement.name),
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = id,
},
)
problematic = true
continue
}
local_id := hir.local_id(len(hir_locals))
append(
&hir_locals,
hir.Local {
name = statement.name,
type = value_type,
mutable = !statement.immutable,
},
)
append(
&locals,
Build_Local {
name = statement.name,
type = value_type,
mutable = !statement.immutable,
id = local_id,
},
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Declaration,
span = statement.span,
local = local_id,
expr = value,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
problematic = problematic || checker.module.exprs[value].kind == .Invalid
case .Assignment:
if statement.target != ast.INVALID_EXPR {
target_expr := build_expr(
checker, statement.target, locals[:], &global_reads, &calls,
types.INVALID, function.pkg, function.file,
)
target_type := checker.module.exprs[target_expr].type
if !hir_location_writable(checker, target_expr, locals[:]) {
id := source.add(checker.diagnostics, statement.span, "assignment target is not writable")
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind=.Trap, span=statement.span, local=hir.INVALID_LOCAL,
target=hir.INVALID_EXPR, expr=hir.INVALID_EXPR, diagnostic=id,
})
problematic = true
continue
}
value := build_expr(
checker, statement.expr, locals[:], &global_reads, &calls,
target_type, function.pkg, function.file,
)
value = coerce_expr(checker, value, target_type, statement.span)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind=.Assignment, span=statement.span, local=hir.INVALID_LOCAL,
target=target_expr, expr=value, diagnostic=source.INVALID_DIAGNOSTIC,
})
problematic = problematic || checker.module.exprs[value].kind == .Invalid
continue
}
if statement.name == checker.sink_symbol {
value := build_expr(checker, statement.expr, locals[:], &global_reads, &calls, types.INVALID, function.pkg, function.file)
if types.is_void(checker.module.exprs[value].type) {
id := source.add(
checker.diagnostics,
statement.span,
"cannot assign a void expression to '_'",
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = id,
},
)
problematic = true
} else {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Sink,
span = statement.span,
expr = value,
local = hir.INVALID_LOCAL,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
}
continue
}
local, found := find_build_local(locals[:], statement.name)
if !found {
id := source.addf(
checker.diagnostics,
statement.span,
"cannot assign unresolved local '%s'",
symbol_text(checker, statement.name),
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = id,
},
)
problematic = true
continue
}
if !local.mutable {
id := source.addf(
checker.diagnostics,
statement.span,
"cannot assign immutable local '%s'",
symbol_text(checker, statement.name),
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = id,
},
)
problematic = true
continue
}
value := build_expr(
checker,
statement.expr,
locals[:],
&global_reads,
&calls,
local.type,
function.pkg,
function.file,
)
value = coerce_expr(checker, value, local.type, statement.span)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Assignment,
span = statement.span,
expr = value,
local = local.id,
target = hir.INVALID_EXPR,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
problematic = problematic || checker.module.exprs[value].kind == .Invalid
case .Return:
has_return = true
if statement.expr == ast.INVALID_EXPR {
if !types.is_void(spec.result) {
id := source.add(
checker.diagnostics,
statement.span,
"'return _' is only valid in a void function",
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = id,
},
)
problematic = true
} else {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Return,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
}
continue
}
if types.is_void(spec.result) {
id := source.add(
checker.diagnostics,
statement.span,
"void function cannot return a value",
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = id,
},
)
problematic = true
continue
}
value := build_expr(
checker,
statement.expr,
locals[:],
&global_reads,
&calls,
spec.result,
function.pkg,
function.file,
)
value = coerce_expr(checker, value, spec.result, statement.span)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Return,
span = statement.span,
expr = value,
local = hir.INVALID_LOCAL,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
problematic = problematic || checker.module.exprs[value].kind == .Invalid
case .Expression:
value := build_expr(checker, statement.expr, locals[:], &global_reads, &calls, types.INVALID, function.pkg, function.file)
if !types.is_void(checker.module.exprs[value].type) {
id := source.add(
checker.diagnostics,
statement.span,
"non-void expression result must be consumed or assigned to '_'",
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = id,
},
)
problematic = true
} else {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Expression,
span = statement.span,
expr = value,
local = hir.INVALID_LOCAL,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
}
case .Invalid:
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = statement.diagnostic,
},
)
problematic = true
}
ctx := Build_Ctx{
checker = checker,
pkg = function.pkg,
file = function.file,
result = spec.result,
locals = &locals,
hir_locals = &hir_locals,
global_reads = &global_reads,
calls = &calls,
problematic = &problematic,
has_return = &has_return,
}
block := build_block(&ctx, function.body)
for block_stmt in block {
append(&body, block_stmt)
}
delete(block, checker.allocator)
if !types.is_void(spec.result) && !has_return {
id := source.addf(