booleans, comparisons, and if/else
This commit is contained in:
+421
-383
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user