undefined assignment
This commit is contained in:
+226
-44
@@ -33,8 +33,11 @@ Spec :: struct {
|
||||
}
|
||||
|
||||
Infer_Local :: struct {
|
||||
name: symbol.Id,
|
||||
type: types.Type,
|
||||
name: symbol.Id,
|
||||
type: types.Type,
|
||||
declared: types.Type,
|
||||
statement: ast.Stmt_Id,
|
||||
mutable: bool,
|
||||
}
|
||||
|
||||
Build_Local :: struct {
|
||||
@@ -53,6 +56,7 @@ Build_Ctx :: struct {
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
result: types.Type,
|
||||
local_types: []types.Type,
|
||||
locals: ^[dynamic]Build_Local,
|
||||
hir_locals: ^[dynamic]hir.Local,
|
||||
global_reads: ^[dynamic]hir.Global_Id,
|
||||
@@ -262,6 +266,13 @@ is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool {
|
||||
return types.is_runtime_value(value, &checker.module.types)
|
||||
}
|
||||
|
||||
is_undefined_expr :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> bool {
|
||||
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
||||
return false
|
||||
}
|
||||
return checker.ast_module.exprs[expr_id].kind == .Undefined
|
||||
}
|
||||
|
||||
string_literal_type :: proc(checker: ^Checker, string_id: u64) -> types.Type {
|
||||
length: u64
|
||||
if string_id < u64(len(checker.ast_module.strings)) {
|
||||
@@ -636,7 +647,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
|
||||
append(&stack, expr.left)
|
||||
case .Add, .Sub, .Mul, .Div, .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, .Enum_Literal:
|
||||
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Name, .Enum_Literal:
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -956,14 +967,21 @@ validate_type_nodes :: proc(checker: ^Checker) {
|
||||
}
|
||||
|
||||
find_infer_local :: proc(locals: []Infer_Local, name: symbol.Id) -> types.Type {
|
||||
for index := len(locals) - 1; index >= 0; index -= 1 {
|
||||
if locals[index].name == name {
|
||||
return locals[index].type
|
||||
}
|
||||
if index, ok := find_infer_local_index(locals, name); ok {
|
||||
return locals[index].type
|
||||
}
|
||||
return types.INVALID
|
||||
}
|
||||
|
||||
find_infer_local_index :: proc(locals: []Infer_Local, name: symbol.Id) -> (int, bool) {
|
||||
for index := len(locals) - 1; index >= 0; index -= 1 {
|
||||
if locals[index].name == name {
|
||||
return index, true
|
||||
}
|
||||
}
|
||||
return -1, false
|
||||
}
|
||||
|
||||
find_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []types.Type) -> Spec_Id {
|
||||
function := checker.ast_module.functions[template]
|
||||
for spec, index in checker.specs {
|
||||
@@ -1123,6 +1141,8 @@ infer_compound_expr :: proc(
|
||||
return types.array(store, element, u64(len(expr.args)), false)
|
||||
case .None:
|
||||
return types.INVALID
|
||||
case .Undefined:
|
||||
return types.INVALID
|
||||
case .Enum_Literal:
|
||||
return types.INVALID
|
||||
case .Address:
|
||||
@@ -1247,7 +1267,7 @@ infer_expr :: proc(
|
||||
case .Float:
|
||||
last = types.F64
|
||||
_ = pop(&stack)
|
||||
case .String, .Array, .None, .Address, .Deref, .Index, .Slice,
|
||||
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
||||
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed, .Enum_Literal,
|
||||
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded)
|
||||
@@ -1505,34 +1525,127 @@ flatten_conditional_unwrap_operands :: proc(
|
||||
append(operands, expr_id)
|
||||
}
|
||||
|
||||
record_infer_local_type :: proc(local: Infer_Local, local_types: []types.Type) {
|
||||
if local.statement != ast.INVALID_STMT && int(local.statement) < len(local_types) {
|
||||
local_types[local.statement] = local.type
|
||||
}
|
||||
}
|
||||
|
||||
merge_infer_local_type :: proc(
|
||||
checker: ^Checker,
|
||||
local: ^Infer_Local,
|
||||
inferred: types.Type,
|
||||
local_types: []types.Type,
|
||||
) -> bool {
|
||||
if !is_runtime_type(checker, inferred) {
|
||||
return false
|
||||
}
|
||||
if types.is_constraint(local.declared) {
|
||||
if !types.is_concrete_integer(inferred) {
|
||||
return false
|
||||
}
|
||||
if !is_runtime_type(checker, local.type) {
|
||||
local.type = inferred
|
||||
record_infer_local_type(local^, local_types)
|
||||
return true
|
||||
}
|
||||
if types.equal(local.type, inferred) {
|
||||
return false
|
||||
}
|
||||
merged := types.widest(local.type, inferred)
|
||||
if types.is_concrete_integer(merged) {
|
||||
local.type = merged
|
||||
record_infer_local_type(local^, local_types)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
if is_runtime_type(checker, local.declared) {
|
||||
local.type = local.declared
|
||||
record_infer_local_type(local^, local_types)
|
||||
return false
|
||||
}
|
||||
if !is_runtime_type(checker, local.type) {
|
||||
local.type = inferred
|
||||
record_infer_local_type(local^, local_types)
|
||||
return true
|
||||
}
|
||||
if types.equal(local.type, inferred) {
|
||||
return false
|
||||
}
|
||||
merged := types.widest(local.type, inferred)
|
||||
if types.is_concrete_scalar(merged) {
|
||||
local.type = merged
|
||||
record_infer_local_type(local^, local_types)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
infer_statements :: proc(
|
||||
checker: ^Checker,
|
||||
statements: []ast.Stmt_Id,
|
||||
locals: ^[dynamic]Infer_Local,
|
||||
local_types: []types.Type,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
demanded: ^[dynamic]Spec_Id,
|
||||
result: ^types.Type,
|
||||
result_hint := types.INVALID,
|
||||
) {
|
||||
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)
|
||||
declared_local := resolve_inferred_array(checker, type_from_syntax(statement.type), statement.expr)
|
||||
value_type := types.INVALID
|
||||
if !is_undefined_expr(checker, statement.expr) {
|
||||
value_type = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
||||
}
|
||||
if is_runtime_type(checker, declared_local) {
|
||||
value_type = declared_local
|
||||
}
|
||||
append(locals, Infer_Local{name = statement.name, type = value_type})
|
||||
case .Assignment, .Expression:
|
||||
local := Infer_Local{
|
||||
name=statement.name,
|
||||
type=value_type,
|
||||
declared=declared_local,
|
||||
statement=statement_id,
|
||||
mutable=!statement.immutable,
|
||||
}
|
||||
append(locals, local)
|
||||
record_infer_local_type(local, local_types)
|
||||
case .Assignment:
|
||||
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
||||
if statement.target != ast.INVALID_EXPR {
|
||||
_ = infer_expr(checker, statement.target, locals^[:], pkg, file, demanded)
|
||||
target_expr := checker.ast_module.exprs[statement.target]
|
||||
if target_expr.kind == .Name && !symbol.is_valid(target_expr.qualifier) {
|
||||
if local_index, ok := find_infer_local_index(locals^[:], target_expr.name); ok &&
|
||||
locals^[local_index].mutable {
|
||||
_ = merge_infer_local_type(checker, &locals^[local_index], value_type, local_types)
|
||||
}
|
||||
}
|
||||
} else if statement.name != checker.sink_symbol {
|
||||
if local_index, ok := find_infer_local_index(locals^[:], statement.name); ok &&
|
||||
locals^[local_index].mutable {
|
||||
_ = merge_infer_local_type(checker, &locals^[local_index], value_type, local_types)
|
||||
}
|
||||
}
|
||||
case .Expression:
|
||||
_ = 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 is_runtime_type(checker, result_hint) {
|
||||
expr := checker.ast_module.exprs[statement.expr]
|
||||
if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
|
||||
if local_index, ok := find_infer_local_index(locals^[:], expr.name); ok {
|
||||
_ = merge_infer_local_type(checker, &locals^[local_index], result_hint, local_types)
|
||||
returned = result_hint
|
||||
}
|
||||
}
|
||||
}
|
||||
if !types.is_valid(result^) {
|
||||
result^ = returned
|
||||
} else if !types.equal(result^, returned) {
|
||||
@@ -1558,27 +1671,27 @@ infer_statements :: proc(
|
||||
types.is_optional(operand_types[index], &checker.module.types) {
|
||||
capture_type = types.child_type(operand_types[index], &checker.module.types)
|
||||
}
|
||||
append(locals, Infer_Local{name=capture, type=capture_type})
|
||||
append(locals, Infer_Local{name=capture, type=capture_type, declared=capture_type, statement=ast.INVALID_STMT})
|
||||
}
|
||||
if statement.guard != ast.INVALID_EXPR {
|
||||
_ = infer_expr(checker, statement.guard, locals^[:], pkg, file, demanded)
|
||||
}
|
||||
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
|
||||
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
resize(locals, capture_start)
|
||||
infer_statements(checker, statement.else_body, locals, pkg, file, demanded, result)
|
||||
infer_statements(checker, statement.else_body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
delete(operand_types, checker.allocator)
|
||||
delete(operands)
|
||||
} else {
|
||||
_ = 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)
|
||||
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
infer_statements(checker, statement.else_body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
}
|
||||
case .While:
|
||||
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
||||
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
|
||||
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
if statement.update != ast.INVALID_STMT {
|
||||
update := [1]ast.Stmt_Id{statement.update}
|
||||
infer_statements(checker, update[:], locals, pkg, file, demanded, result)
|
||||
infer_statements(checker, update[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
}
|
||||
case .For:
|
||||
iterable_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
||||
@@ -1596,43 +1709,55 @@ infer_statements :: proc(
|
||||
}
|
||||
}
|
||||
if symbol.is_valid(statement.name) {
|
||||
append(locals, Infer_Local{name=statement.name, type=capture_type})
|
||||
append(locals, Infer_Local{name=statement.name, type=capture_type, declared=capture_type, statement=ast.INVALID_STMT})
|
||||
}
|
||||
if symbol.is_valid(statement.index_name) {
|
||||
append(locals, Infer_Local{name=statement.index_name, type=types.USIZE})
|
||||
append(locals, Infer_Local{name=statement.index_name, type=types.USIZE, declared=types.USIZE, statement=ast.INVALID_STMT})
|
||||
}
|
||||
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
|
||||
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
resize(locals, capture_start)
|
||||
}
|
||||
}
|
||||
resize(locals, scope_start)
|
||||
}
|
||||
|
||||
infer_spec_result :: proc(checker: ^Checker, id: Spec_Id, demanded: ^[dynamic]Spec_Id = nil) -> types.Type {
|
||||
infer_spec_locals_and_result :: proc(
|
||||
checker: ^Checker,
|
||||
id: Spec_Id,
|
||||
demanded: ^[dynamic]Spec_Id = nil,
|
||||
) -> ([]types.Type, types.Type) {
|
||||
spec := checker.specs[id]
|
||||
function := checker.ast_module.functions[spec.template]
|
||||
declared := type_from_syntax(function.result)
|
||||
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
|
||||
declared = types.I32
|
||||
}
|
||||
result_hint := declared if is_runtime_type(checker, declared) else types.INVALID
|
||||
|
||||
locals: [dynamic]Infer_Local
|
||||
locals.allocator = checker.allocator
|
||||
defer delete(locals)
|
||||
local_types := make([]types.Type, len(checker.ast_module.statements), checker.allocator)
|
||||
for param, index in function.params {
|
||||
param_type := types.INVALID
|
||||
if index < len(spec.args) {
|
||||
param_type = spec.args[index]
|
||||
}
|
||||
append(&locals, Infer_Local{name = param.name, type = param_type})
|
||||
append(&locals, Infer_Local{name=param.name, type=param_type, declared=param_type, statement=ast.INVALID_STMT})
|
||||
}
|
||||
|
||||
result := types.INVALID
|
||||
infer_statements(checker, function.body, &locals, function.pkg, function.file, demanded, &result)
|
||||
infer_statements(checker, function.body, &locals, local_types, function.pkg, function.file, demanded, &result, result_hint)
|
||||
if types.is_constraint(declared) {
|
||||
return result
|
||||
return local_types, result
|
||||
}
|
||||
return declared
|
||||
return local_types, declared
|
||||
}
|
||||
|
||||
infer_spec_result :: proc(checker: ^Checker, id: Spec_Id, demanded: ^[dynamic]Spec_Id = nil) -> types.Type {
|
||||
local_types, result := infer_spec_locals_and_result(checker, id, demanded)
|
||||
delete(local_types, checker.allocator)
|
||||
return result
|
||||
}
|
||||
|
||||
merge_inferred_type :: proc(store: ^types.Store, current: ^types.Type, inferred: types.Type) -> bool {
|
||||
@@ -2296,7 +2421,7 @@ build_compound_expr :: proc(
|
||||
infer_locals := make([]Infer_Local, len(locals), checker.allocator)
|
||||
defer delete(infer_locals, checker.allocator)
|
||||
for local, index in locals {
|
||||
infer_locals[index] = Infer_Local{name=local.name, type=local.type}
|
||||
infer_locals[index] = Infer_Local{name=local.name, type=local.type, declared=local.type, statement=ast.INVALID_STMT}
|
||||
}
|
||||
for arg in expr.args {
|
||||
actual := infer_nested_expr(checker, arg, infer_locals, pkg, file, nil)
|
||||
@@ -2332,6 +2457,13 @@ build_compound_expr :: proc(
|
||||
kind=.None, span=expr.span, type=expected, target=hir.INVALID_REF,
|
||||
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Undefined:
|
||||
id := source.add(
|
||||
checker.diagnostics,
|
||||
expr.span,
|
||||
"'undefined' is only valid as a mutable local declaration initializer",
|
||||
)
|
||||
return invalid_hir_expr(checker, expr.span, id, expected)
|
||||
case .Enum_Literal:
|
||||
if !types.is_enum(expected, store) {
|
||||
id := source.addf(
|
||||
@@ -2762,7 +2894,7 @@ build_expr :: proc(
|
||||
continue
|
||||
}
|
||||
switch expr.kind {
|
||||
case .String, .Array, .None, .Address, .Deref, .Index, .Slice,
|
||||
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
||||
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed,
|
||||
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range,
|
||||
.Enum_Literal:
|
||||
@@ -3290,22 +3422,62 @@ build_block :: proc(
|
||||
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
|
||||
if statement_id != ast.INVALID_STMT && int(statement_id) < len(ctx.local_types) &&
|
||||
is_runtime_type(checker, ctx.local_types[statement_id]) &&
|
||||
(types.is_constraint(declared) || is_undefined_expr(checker, statement.expr)) {
|
||||
declared = ctx.local_types[statement_id]
|
||||
}
|
||||
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)
|
||||
expected := types.INVALID
|
||||
value := hir.INVALID_EXPR
|
||||
value_type := types.INVALID
|
||||
if is_undefined_expr(checker, statement.expr) {
|
||||
if statement.immutable {
|
||||
id := source.add(
|
||||
checker.diagnostics,
|
||||
statement.span,
|
||||
"'undefined' requires a mutable local declaration",
|
||||
)
|
||||
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 !is_runtime_type(checker, declared) {
|
||||
id := source.addf(
|
||||
checker.diagnostics,
|
||||
statement.span,
|
||||
"could not infer a concrete type for 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_type = declared
|
||||
} else {
|
||||
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
|
||||
} 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 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^[duplicate_start:], statement.name); found {
|
||||
id := source.addf(
|
||||
@@ -3332,7 +3504,9 @@ build_block :: proc(
|
||||
kind = .Declaration, span = statement.span, local = local_id, expr = value,
|
||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
||||
if value != hir.INVALID_EXPR {
|
||||
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
||||
}
|
||||
case .Assignment:
|
||||
if statement.target != ast.INVALID_EXPR {
|
||||
target_expr := build_expr(
|
||||
@@ -3888,6 +4062,13 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
||||
global_reads.allocator = checker.allocator
|
||||
calls: [dynamic]hir.Function_Id
|
||||
calls.allocator = checker.allocator
|
||||
demanded: [dynamic]Spec_Id
|
||||
demanded.allocator = checker.allocator
|
||||
local_types, _ := infer_spec_locals_and_result(checker, id, &demanded)
|
||||
defer {
|
||||
delete(local_types, checker.allocator)
|
||||
delete(demanded)
|
||||
}
|
||||
|
||||
for param, index in function.params {
|
||||
local_id := hir.local_id(len(hir_locals))
|
||||
@@ -3946,6 +4127,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
||||
pkg = function.pkg,
|
||||
file = function.file,
|
||||
result = spec.result,
|
||||
local_types = local_types,
|
||||
locals = &locals,
|
||||
hir_locals = &hir_locals,
|
||||
global_reads = &global_reads,
|
||||
|
||||
Reference in New Issue
Block a user