harden compiler parsing, recovery, and deep-expression handling
This commit is contained in:
+490
-319
@@ -60,6 +60,11 @@ Checker :: struct {
|
||||
global_types: []types.Type,
|
||||
constants: []Constant,
|
||||
template_diagnostics: []int,
|
||||
constant_stack: [dynamic]Constant_Frame,
|
||||
expr_stack: [dynamic]int,
|
||||
infer_stack: [dynamic]Infer_Frame,
|
||||
build_stack: [dynamic]Build_Expr_Frame,
|
||||
cycle_stack: [dynamic]Cycle_Frame,
|
||||
main_symbol: symbol.Id,
|
||||
sink_symbol: symbol.Id,
|
||||
allocator: mem.Allocator,
|
||||
@@ -69,38 +74,75 @@ symbol_text :: proc(checker: ^Checker, id: symbol.Id) -> string {
|
||||
return symbol.resolve(checker.symbols, id)
|
||||
}
|
||||
|
||||
Constant_Frame :: struct {
|
||||
expr: int,
|
||||
stage: u8,
|
||||
}
|
||||
|
||||
eval_constant :: proc(checker: ^Checker, expr_id: int) -> Constant {
|
||||
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
||||
return Constant{kind = .Not_Constant}
|
||||
}
|
||||
if checker.constants[expr_id].kind != .Unknown {
|
||||
return checker.constants[expr_id]
|
||||
stack := checker.constant_stack
|
||||
clear_dynamic_array(&stack)
|
||||
defer {
|
||||
clear_dynamic_array(&stack)
|
||||
checker.constant_stack = stack
|
||||
}
|
||||
expr := checker.ast_module.exprs[expr_id]
|
||||
result: Constant
|
||||
switch expr.kind {
|
||||
case .Integer:
|
||||
result = Constant{kind = .Value, value = i128(expr.integer)}
|
||||
case .Add:
|
||||
left := eval_constant(checker, expr.left)
|
||||
right := eval_constant(checker, expr.right)
|
||||
append(&stack, Constant_Frame{expr=expr_id})
|
||||
|
||||
for len(stack) > 0 {
|
||||
frame_index := len(stack)-1
|
||||
frame := stack[frame_index]
|
||||
if checker.constants[frame.expr].kind != .Unknown {
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
expr := checker.ast_module.exprs[frame.expr]
|
||||
if expr.kind != .Add {
|
||||
result := Constant{kind = .Not_Constant}
|
||||
if expr.kind == .Integer {
|
||||
result = Constant{kind = .Value, value = i128(expr.integer)}
|
||||
}
|
||||
checker.constants[frame.expr] = result
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if frame.stage == 0 {
|
||||
stack[frame_index].stage = 1
|
||||
if expr.left >= 0 && expr.left < len(checker.ast_module.exprs) &&
|
||||
checker.constants[expr.left].kind == .Unknown {
|
||||
append(&stack, Constant_Frame{expr=expr.left})
|
||||
}
|
||||
continue
|
||||
}
|
||||
if frame.stage == 1 {
|
||||
stack[frame_index].stage = 2
|
||||
if expr.right >= 0 && expr.right < len(checker.ast_module.exprs) &&
|
||||
checker.constants[expr.right].kind == .Unknown {
|
||||
append(&stack, Constant_Frame{expr=expr.right})
|
||||
}
|
||||
continue
|
||||
}
|
||||
left := Constant{kind = .Not_Constant}
|
||||
right := Constant{kind = .Not_Constant}
|
||||
if expr.left >= 0 && expr.left < len(checker.constants) {
|
||||
left = checker.constants[expr.left]
|
||||
}
|
||||
if expr.right >= 0 && expr.right < len(checker.constants) {
|
||||
right = checker.constants[expr.right]
|
||||
}
|
||||
result := Constant{kind = .Not_Constant}
|
||||
if left.kind == .Overflow || right.kind == .Overflow {
|
||||
result = Constant{kind = .Overflow}
|
||||
} else if left.kind != .Value || right.kind != .Value {
|
||||
result = Constant{kind = .Not_Constant}
|
||||
} else {
|
||||
} else if left.kind == .Value && right.kind == .Value {
|
||||
value, overflow := intrinsics.overflow_add(left.value, right.value)
|
||||
if overflow {
|
||||
result = Constant{kind = .Overflow}
|
||||
} else {
|
||||
result = Constant{kind = .Value, value = value}
|
||||
}
|
||||
result = Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
|
||||
}
|
||||
case .Invalid, .Name, .Call:
|
||||
result = Constant{kind = .Not_Constant}
|
||||
checker.constants[frame.expr] = result
|
||||
_ = pop(&stack)
|
||||
}
|
||||
checker.constants[expr_id] = result
|
||||
return result
|
||||
return checker.constants[expr_id]
|
||||
}
|
||||
|
||||
fits_signed_type :: proc(value: i128, target: types.Type) -> bool {
|
||||
@@ -264,26 +306,29 @@ contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool {
|
||||
}
|
||||
|
||||
mark_expr_imports_used :: proc(checker: ^Checker, expr_id, file: int) {
|
||||
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
||||
return
|
||||
stack := checker.expr_stack
|
||||
clear_dynamic_array(&stack)
|
||||
defer {
|
||||
clear_dynamic_array(&stack)
|
||||
checker.expr_stack = stack
|
||||
}
|
||||
expr := checker.ast_module.exprs[expr_id]
|
||||
switch expr.kind {
|
||||
case .Name:
|
||||
if symbol.is_valid(expr.qualifier) {
|
||||
append(&stack, expr_id)
|
||||
for len(stack) > 0 {
|
||||
id := pop(&stack)
|
||||
if id < 0 || id >= len(checker.ast_module.exprs) {
|
||||
continue
|
||||
}
|
||||
expr := checker.ast_module.exprs[id]
|
||||
if (expr.kind == .Name || expr.kind == .Call) && symbol.is_valid(expr.qualifier) {
|
||||
_ = find_import(checker, file, expr.qualifier, true)
|
||||
}
|
||||
case .Call:
|
||||
if symbol.is_valid(expr.qualifier) {
|
||||
_ = find_import(checker, file, expr.qualifier, true)
|
||||
switch expr.kind {
|
||||
case .Call:
|
||||
append(&stack, ..expr.args)
|
||||
case .Add:
|
||||
append(&stack, expr.left, expr.right)
|
||||
case .Invalid, .Integer, .Name:
|
||||
}
|
||||
for arg in expr.args {
|
||||
mark_expr_imports_used(checker, arg, file)
|
||||
}
|
||||
case .Add:
|
||||
mark_expr_imports_used(checker, expr.left, file)
|
||||
mark_expr_imports_used(checker, expr.right, file)
|
||||
case .Invalid, .Integer:
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,80 +490,138 @@ ensure_spec :: proc(checker: ^Checker, template: int, actual_args: []types.Type)
|
||||
return index
|
||||
}
|
||||
|
||||
Infer_Frame :: struct {
|
||||
expr: int,
|
||||
stage: u8,
|
||||
left: types.Type,
|
||||
arg_index: int,
|
||||
args: []types.Type,
|
||||
template: int,
|
||||
}
|
||||
|
||||
infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local, pkg := 0, file := 0) -> types.Type {
|
||||
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
||||
return types.INVALID
|
||||
stack := checker.infer_stack
|
||||
clear_dynamic_array(&stack)
|
||||
defer {
|
||||
for frame in stack {
|
||||
delete(frame.args, checker.allocator)
|
||||
}
|
||||
clear_dynamic_array(&stack)
|
||||
checker.infer_stack = stack
|
||||
}
|
||||
constant := eval_constant(checker, expr_id)
|
||||
if constant.kind == .Overflow || (constant.kind == .Value && !fits_i64(constant.value)) {
|
||||
return types.I64
|
||||
append(&stack, Infer_Frame{expr=expr_id, template=-1})
|
||||
last := types.INVALID
|
||||
|
||||
for len(stack) > 0 {
|
||||
frame_index := len(stack)-1
|
||||
frame := stack[frame_index]
|
||||
if frame.expr < 0 || frame.expr >= len(checker.ast_module.exprs) {
|
||||
last = types.INVALID
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
expr := checker.ast_module.exprs[frame.expr]
|
||||
if frame.stage == 0 {
|
||||
constant := eval_constant(checker, frame.expr)
|
||||
if constant.kind == .Overflow || (constant.kind == .Value && !fits_i64(constant.value)) {
|
||||
last = types.I64
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if constant.kind == .Value {
|
||||
last = types.smallest_signed_for_literal(i64(constant.value))
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
switch expr.kind {
|
||||
case .Invalid:
|
||||
last = types.INVALID
|
||||
_ = pop(&stack)
|
||||
case .Integer:
|
||||
last = types.smallest_signed_for_literal(expr.integer)
|
||||
_ = pop(&stack)
|
||||
case .Name:
|
||||
last = types.INVALID
|
||||
if !symbol.is_valid(expr.qualifier) {
|
||||
last = find_infer_local(locals, expr.name)
|
||||
}
|
||||
if !types.is_valid(last) {
|
||||
target_pkg, available := expr_package(checker, expr, pkg, file)
|
||||
if available {
|
||||
global := find_global(checker, expr.name, target_pkg)
|
||||
if global >= 0 {
|
||||
last = checker.global_types[global]
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = pop(&stack)
|
||||
case .Add:
|
||||
stack[frame_index].stage = 1
|
||||
append(&stack, Infer_Frame{expr=expr.left, template=-1})
|
||||
case .Call:
|
||||
target_pkg, available := expr_package(checker, expr, pkg, file)
|
||||
template := -1
|
||||
if available {
|
||||
template = find_template(checker, expr.name, target_pkg)
|
||||
}
|
||||
if template < 0 {
|
||||
last = types.INVALID
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if checker.template_diagnostics[template] >= 0 {
|
||||
declared := type_from_syntax(checker.ast_module.functions[template].result)
|
||||
last = declared if declared.kind == .Concrete || declared.kind == .Void else types.INVALID
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
stack[frame_index].template = template
|
||||
stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator)
|
||||
stack[frame_index].stage = 3
|
||||
if len(expr.args) > 0 {
|
||||
append(&stack, Infer_Frame{expr=expr.args[0], template=-1})
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if frame.stage == 1 {
|
||||
stack[frame_index].left = last
|
||||
stack[frame_index].stage = 2
|
||||
append(&stack, Infer_Frame{expr=expr.right, template=-1})
|
||||
continue
|
||||
}
|
||||
if frame.stage == 2 {
|
||||
last = types.widest(frame.left, last)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if frame.stage == 3 {
|
||||
if frame.arg_index < len(expr.args) {
|
||||
stack[frame_index].args[frame.arg_index] = last
|
||||
stack[frame_index].arg_index += 1
|
||||
if frame.arg_index+1 < len(expr.args) {
|
||||
append(&stack, Infer_Frame{expr=expr.args[frame.arg_index+1], template=-1})
|
||||
continue
|
||||
}
|
||||
}
|
||||
function := checker.ast_module.functions[frame.template]
|
||||
if can_specialize(function, stack[frame_index].args) {
|
||||
spec := ensure_spec(checker, frame.template, stack[frame_index].args)
|
||||
last = checker.specs[spec].result
|
||||
} else {
|
||||
declared := type_from_syntax(function.result)
|
||||
if function.pkg == 0 && function.name == checker.main_symbol && function.result == .Int {
|
||||
last = types.I32
|
||||
} else {
|
||||
last = declared if declared.kind == .Concrete || declared.kind == .Void else types.INVALID
|
||||
}
|
||||
}
|
||||
delete(stack[frame_index].args, checker.allocator)
|
||||
stack[frame_index].args = nil
|
||||
_ = pop(&stack)
|
||||
}
|
||||
}
|
||||
if constant.kind == .Value {
|
||||
return types.smallest_signed_for_literal(i64(constant.value))
|
||||
}
|
||||
expr := checker.ast_module.exprs[expr_id]
|
||||
switch expr.kind {
|
||||
case .Invalid:
|
||||
return types.INVALID
|
||||
case .Integer:
|
||||
return types.smallest_signed_for_literal(expr.integer)
|
||||
case .Name:
|
||||
if !symbol.is_valid(expr.qualifier) {
|
||||
local_type := find_infer_local(locals, expr.name)
|
||||
if types.is_valid(local_type) {
|
||||
return local_type
|
||||
}
|
||||
}
|
||||
target_pkg, available := expr_package(checker, expr, pkg, file)
|
||||
if !available {
|
||||
return types.INVALID
|
||||
}
|
||||
global := find_global(checker, expr.name, target_pkg)
|
||||
if global >= 0 {
|
||||
return checker.global_types[global]
|
||||
}
|
||||
return types.INVALID
|
||||
case .Add:
|
||||
left := infer_expr(checker, expr.left, locals, pkg, file)
|
||||
right := infer_expr(checker, expr.right, locals, pkg, file)
|
||||
return types.widest(left, right)
|
||||
case .Call:
|
||||
target_pkg, available := expr_package(checker, expr, pkg, file)
|
||||
if !available {
|
||||
return types.INVALID
|
||||
}
|
||||
template := find_template(checker, expr.name, target_pkg)
|
||||
if template < 0 {
|
||||
return types.INVALID
|
||||
}
|
||||
if checker.template_diagnostics[template] >= 0 {
|
||||
declared := type_from_syntax(checker.ast_module.functions[template].result)
|
||||
if declared.kind == .Concrete || declared.kind == .Void {
|
||||
return declared
|
||||
}
|
||||
return types.INVALID
|
||||
}
|
||||
args := make([]types.Type, len(expr.args), checker.allocator)
|
||||
for arg, index in expr.args {
|
||||
args[index] = infer_expr(checker, arg, locals, pkg, file)
|
||||
}
|
||||
function := checker.ast_module.functions[template]
|
||||
if !can_specialize(function, args) {
|
||||
delete(args, checker.allocator)
|
||||
declared := type_from_syntax(function.result)
|
||||
if function.pkg == 0 && function.name == checker.main_symbol && function.result == .Int {
|
||||
return types.I32
|
||||
}
|
||||
if declared.kind == .Concrete || declared.kind == .Void {
|
||||
return declared
|
||||
}
|
||||
return types.INVALID
|
||||
}
|
||||
spec := ensure_spec(checker, template, args)
|
||||
delete(args, checker.allocator)
|
||||
return checker.specs[spec].result
|
||||
}
|
||||
return types.INVALID
|
||||
return last
|
||||
}
|
||||
|
||||
infer_spec_result :: proc(checker: ^Checker, spec_id: int) -> types.Type {
|
||||
@@ -752,6 +855,17 @@ build_constant_expr :: proc(
|
||||
)
|
||||
}
|
||||
|
||||
Build_Expr_Frame :: struct {
|
||||
expr: int,
|
||||
expected: types.Type,
|
||||
stage: u8,
|
||||
left: int,
|
||||
arg_index: int,
|
||||
built_args: []int,
|
||||
arg_types: []types.Type,
|
||||
template: int,
|
||||
}
|
||||
|
||||
build_expr :: proc(
|
||||
checker: ^Checker,
|
||||
expr_id: int,
|
||||
@@ -762,173 +876,190 @@ build_expr :: proc(
|
||||
pkg := 0,
|
||||
file := 0,
|
||||
) -> int {
|
||||
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
||||
id := source.add(checker.diagnostics, source.Span{}, "missing expression")
|
||||
return invalid_hir_expr(checker, source.Span{}, id)
|
||||
stack := checker.build_stack
|
||||
clear_dynamic_array(&stack)
|
||||
defer {
|
||||
for frame in stack {
|
||||
delete(frame.built_args, checker.allocator)
|
||||
delete(frame.arg_types, checker.allocator)
|
||||
}
|
||||
clear_dynamic_array(&stack)
|
||||
checker.build_stack = stack
|
||||
}
|
||||
expr := checker.ast_module.exprs[expr_id]
|
||||
constant := eval_constant(checker, expr_id)
|
||||
if constant.kind == .Value || constant.kind == .Overflow {
|
||||
return build_constant_expr(checker, expr, constant, expected)
|
||||
}
|
||||
switch expr.kind {
|
||||
case .Invalid:
|
||||
return invalid_hir_expr(checker, expr.span, expr.diagnostic)
|
||||
case .Integer:
|
||||
unreachable()
|
||||
case .Name:
|
||||
if !symbol.is_valid(expr.qualifier) {
|
||||
if local, ok := find_build_local(locals, expr.name); ok {
|
||||
return add_hir_expr(
|
||||
append(&stack, Build_Expr_Frame{expr=expr_id, expected=expected, template=-1})
|
||||
last := -1
|
||||
|
||||
for len(stack) > 0 {
|
||||
frame_index := len(stack)-1
|
||||
frame := stack[frame_index]
|
||||
if frame.expr < 0 || frame.expr >= len(checker.ast_module.exprs) {
|
||||
id := source.add(checker.diagnostics, source.Span{}, "missing expression")
|
||||
last = invalid_hir_expr(checker, source.Span{}, id)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
expr := checker.ast_module.exprs[frame.expr]
|
||||
if frame.stage == 0 {
|
||||
constant := eval_constant(checker, frame.expr)
|
||||
if constant.kind == .Value || constant.kind == .Overflow {
|
||||
last = build_constant_expr(checker, expr, constant, frame.expected)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
switch expr.kind {
|
||||
case .Invalid, .Integer:
|
||||
last = invalid_hir_expr(checker, expr.span, expr.diagnostic)
|
||||
_ = pop(&stack)
|
||||
case .Name:
|
||||
last = -1
|
||||
if !symbol.is_valid(expr.qualifier) {
|
||||
if local, ok := find_build_local(locals, expr.name); ok {
|
||||
last = add_hir_expr(checker, hir.Expr{
|
||||
kind=.Local, span=expr.span, type=local.type, target=local.id,
|
||||
left=-1, right=-1, diagnostic=-1,
|
||||
})
|
||||
}
|
||||
}
|
||||
if last < 0 {
|
||||
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
||||
if !available {
|
||||
id := add_package_resolution_diagnostic(checker, expr, file)
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
} else if global := find_global(checker, expr.name, target_pkg); global >= 0 {
|
||||
add_unique(global_reads, global)
|
||||
last = add_hir_expr(checker, hir.Expr{
|
||||
kind=.Global, span=expr.span, type=checker.global_types[global],
|
||||
target=global, left=-1, right=-1, diagnostic=-1,
|
||||
})
|
||||
} else {
|
||||
id := add_name_resolution_diagnostic(checker, expr, target_pkg)
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
}
|
||||
_ = pop(&stack)
|
||||
case .Add:
|
||||
stack[frame_index].stage = 1
|
||||
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=-1})
|
||||
case .Call:
|
||||
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
||||
if !available {
|
||||
id := add_package_resolution_diagnostic(checker, expr, file)
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
template := find_template(checker, expr.name, target_pkg)
|
||||
if template < 0 {
|
||||
id := add_call_resolution_diagnostic(checker, expr, target_pkg)
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if checker.template_diagnostics[template] >= 0 {
|
||||
last = invalid_hir_expr(checker, expr.span, checker.template_diagnostics[template])
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if len(expr.args) != len(checker.ast_module.functions[template].params) {
|
||||
id := source.addf(
|
||||
checker.diagnostics,
|
||||
expr.span,
|
||||
"function '%s' expects %d arguments, got %d",
|
||||
symbol_text(checker, expr.name),
|
||||
len(checker.ast_module.functions[template].params),
|
||||
len(expr.args),
|
||||
)
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
stack[frame_index].template = template
|
||||
stack[frame_index].built_args = make([]int, len(expr.args), checker.allocator)
|
||||
stack[frame_index].arg_types = make([]types.Type, len(expr.args), checker.allocator)
|
||||
stack[frame_index].stage = 3
|
||||
if len(expr.args) > 0 {
|
||||
arg_expected := type_from_syntax(checker.ast_module.functions[template].params[0].type)
|
||||
if arg_expected.kind != .Concrete {
|
||||
arg_expected = types.INVALID
|
||||
}
|
||||
append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=-1})
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if frame.stage == 1 {
|
||||
stack[frame_index].left = last
|
||||
stack[frame_index].stage = 2
|
||||
append(&stack, Build_Expr_Frame{expr=expr.right, expected=types.INVALID, template=-1})
|
||||
continue
|
||||
}
|
||||
if frame.stage == 2 {
|
||||
left := frame.left
|
||||
right := last
|
||||
result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
|
||||
if !types.is_signed(result) {
|
||||
id := source.add(checker.diagnostics, expr.span, "addition requires compatible signed integers")
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
} else {
|
||||
left = coerce_expr(checker, left, result, checker.module.exprs[left].span)
|
||||
right = coerce_expr(checker, right, result, checker.module.exprs[right].span)
|
||||
last = add_hir_expr(checker, hir.Expr{
|
||||
kind=.Add, span=expr.span, type=result, left=left, right=right,
|
||||
target=-1, diagnostic=-1,
|
||||
})
|
||||
}
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if frame.stage == 3 {
|
||||
if frame.arg_index < len(expr.args) {
|
||||
stack[frame_index].built_args[frame.arg_index] = last
|
||||
stack[frame_index].arg_types[frame.arg_index] = checker.module.exprs[last].type
|
||||
stack[frame_index].arg_index += 1
|
||||
if frame.arg_index+1 < len(expr.args) {
|
||||
next := frame.arg_index+1
|
||||
arg_expected := type_from_syntax(checker.ast_module.functions[frame.template].params[next].type)
|
||||
if arg_expected.kind != .Concrete {
|
||||
arg_expected = types.INVALID
|
||||
}
|
||||
append(&stack, Build_Expr_Frame{expr=expr.args[next], expected=arg_expected, template=-1})
|
||||
continue
|
||||
}
|
||||
}
|
||||
spec := ensure_spec(checker, frame.template, stack[frame_index].arg_types)
|
||||
delete(stack[frame_index].arg_types, checker.allocator)
|
||||
stack[frame_index].arg_types = nil
|
||||
for _, index in stack[frame_index].built_args {
|
||||
stack[frame_index].built_args[index] = coerce_expr(
|
||||
checker,
|
||||
hir.Expr {
|
||||
kind = .Local,
|
||||
span = expr.span,
|
||||
type = local.type,
|
||||
target = local.id,
|
||||
left = -1,
|
||||
right = -1,
|
||||
diagnostic = -1,
|
||||
},
|
||||
stack[frame_index].built_args[index],
|
||||
checker.specs[spec].args[index],
|
||||
checker.module.exprs[stack[frame_index].built_args[index]].span,
|
||||
)
|
||||
}
|
||||
}
|
||||
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
||||
if !available {
|
||||
id := add_package_resolution_diagnostic(checker, expr, file)
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
global := find_global(checker, expr.name, target_pkg)
|
||||
if global >= 0 {
|
||||
add_unique(global_reads, global)
|
||||
return add_hir_expr(
|
||||
checker,
|
||||
hir.Expr {
|
||||
kind = .Global,
|
||||
span = expr.span,
|
||||
type = checker.global_types[global],
|
||||
target = global,
|
||||
left = -1,
|
||||
right = -1,
|
||||
diagnostic = -1,
|
||||
},
|
||||
)
|
||||
}
|
||||
id := add_name_resolution_diagnostic(checker, expr, target_pkg)
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
case .Add:
|
||||
left := build_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
right := build_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
|
||||
result := types.widest(left_type, right_type)
|
||||
if !types.is_signed(result) {
|
||||
id := source.add(
|
||||
checker.diagnostics,
|
||||
expr.span,
|
||||
"addition requires compatible signed integers",
|
||||
)
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
left = coerce_expr(checker, left, result, checker.module.exprs[left].span)
|
||||
right = coerce_expr(checker, right, result, checker.module.exprs[right].span)
|
||||
return add_hir_expr(
|
||||
checker,
|
||||
hir.Expr {
|
||||
kind = .Add,
|
||||
span = expr.span,
|
||||
type = result,
|
||||
left = left,
|
||||
right = right,
|
||||
target = -1,
|
||||
diagnostic = -1,
|
||||
},
|
||||
)
|
||||
case .Call:
|
||||
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
||||
if !available {
|
||||
id := add_package_resolution_diagnostic(checker, expr, file)
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
template := find_template(checker, expr.name, target_pkg)
|
||||
if template < 0 {
|
||||
id := add_call_resolution_diagnostic(checker, expr, target_pkg)
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
if checker.template_diagnostics[template] >= 0 {
|
||||
return invalid_hir_expr(checker, expr.span, checker.template_diagnostics[template])
|
||||
}
|
||||
if len(expr.args) != len(checker.ast_module.functions[template].params) {
|
||||
id := source.addf(
|
||||
checker.diagnostics,
|
||||
expr.span,
|
||||
"function '%s' expects %d arguments, got %d",
|
||||
symbol_text(checker, expr.name),
|
||||
len(checker.ast_module.functions[template].params),
|
||||
len(expr.args),
|
||||
)
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
built_args := make([]int, len(expr.args), checker.allocator)
|
||||
arg_types := make([]types.Type, len(expr.args), checker.allocator)
|
||||
for arg, index in expr.args {
|
||||
arg_expected := type_from_syntax(checker.ast_module.functions[template].params[index].type)
|
||||
if arg_expected.kind != .Concrete {
|
||||
arg_expected = types.INVALID
|
||||
add_unique(calls, spec)
|
||||
result := checker.specs[spec].result
|
||||
if !types.is_valid(result) {
|
||||
id := source.addf(
|
||||
checker.diagnostics,
|
||||
expr.span,
|
||||
"could not resolve result type for specialization of '%s'",
|
||||
symbol_text(checker, expr.name),
|
||||
)
|
||||
delete(stack[frame_index].built_args, checker.allocator)
|
||||
stack[frame_index].built_args = nil
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
} else {
|
||||
last = add_hir_expr(checker, hir.Expr{
|
||||
kind=.Call, span=expr.span, type=result, target=spec,
|
||||
left=-1, right=-1, args=stack[frame_index].built_args, diagnostic=-1,
|
||||
})
|
||||
stack[frame_index].built_args = nil
|
||||
}
|
||||
built_args[index] = build_expr(
|
||||
checker,
|
||||
arg,
|
||||
locals,
|
||||
global_reads,
|
||||
calls,
|
||||
arg_expected,
|
||||
pkg,
|
||||
file,
|
||||
)
|
||||
arg_types[index] = checker.module.exprs[built_args[index]].type
|
||||
_ = pop(&stack)
|
||||
}
|
||||
spec := ensure_spec(checker, template, arg_types)
|
||||
delete(arg_types, checker.allocator)
|
||||
for _, index in built_args {
|
||||
built_args[index] = coerce_expr(
|
||||
checker,
|
||||
built_args[index],
|
||||
checker.specs[spec].args[index],
|
||||
checker.module.exprs[built_args[index]].span,
|
||||
)
|
||||
}
|
||||
add_unique(calls, spec)
|
||||
result := checker.specs[spec].result
|
||||
if !types.is_valid(result) {
|
||||
id := source.addf(
|
||||
checker.diagnostics,
|
||||
expr.span,
|
||||
"could not resolve result type for specialization of '%s'",
|
||||
symbol_text(checker, expr.name),
|
||||
)
|
||||
delete(built_args, checker.allocator)
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
return add_hir_expr(
|
||||
checker,
|
||||
hir.Expr {
|
||||
kind = .Call,
|
||||
span = expr.span,
|
||||
type = result,
|
||||
target = spec,
|
||||
left = -1,
|
||||
right = -1,
|
||||
args = built_args,
|
||||
diagnostic = -1,
|
||||
},
|
||||
)
|
||||
}
|
||||
return invalid_hir_expr(
|
||||
checker,
|
||||
expr.span,
|
||||
source.add(checker.diagnostics, expr.span, "invalid expression"),
|
||||
)
|
||||
return last
|
||||
}
|
||||
|
||||
make_link_name :: proc(checker: ^Checker, spec_id: int) -> string {
|
||||
@@ -1022,7 +1153,7 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
|
||||
result = spec.result,
|
||||
locals = hir_locals[:],
|
||||
body = body[:],
|
||||
direct_global_reads = global_reads[:],
|
||||
direct_global_reads = global_reads,
|
||||
calls = calls[:],
|
||||
problematic = problematic,
|
||||
diagnostic = checker.template_diagnostics[spec.template],
|
||||
@@ -1387,7 +1518,7 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
|
||||
result = spec.result,
|
||||
locals = hir_locals[:],
|
||||
body = body[:],
|
||||
direct_global_reads = global_reads[:],
|
||||
direct_global_reads = global_reads,
|
||||
calls = calls[:],
|
||||
problematic = problematic,
|
||||
diagnostic = -1,
|
||||
@@ -1396,24 +1527,31 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
|
||||
delete(locals)
|
||||
}
|
||||
|
||||
expr_problematic :: proc(module: ^hir.Module, expr_id: int) -> bool {
|
||||
if expr_id < 0 || expr_id >= len(module.exprs) {
|
||||
return true
|
||||
expr_problematic :: proc(checker: ^Checker, expr_id: int) -> bool {
|
||||
module := &checker.module
|
||||
stack := checker.expr_stack
|
||||
clear_dynamic_array(&stack)
|
||||
defer {
|
||||
clear_dynamic_array(&stack)
|
||||
checker.expr_stack = stack
|
||||
}
|
||||
expr := module.exprs[expr_id]
|
||||
if expr.kind == .Invalid {
|
||||
return true
|
||||
}
|
||||
if expr.left >= 0 && expr_problematic(module, expr.left) {
|
||||
return true
|
||||
}
|
||||
if expr.right >= 0 && expr_problematic(module, expr.right) {
|
||||
return true
|
||||
}
|
||||
for arg in expr.args {
|
||||
if expr_problematic(module, arg) {
|
||||
append(&stack, expr_id)
|
||||
for len(stack) > 0 {
|
||||
id := pop(&stack)
|
||||
if id < 0 || id >= len(module.exprs) {
|
||||
return true
|
||||
}
|
||||
expr := module.exprs[id]
|
||||
if expr.kind == .Invalid {
|
||||
return true
|
||||
}
|
||||
if expr.left >= 0 {
|
||||
append(&stack, expr.left)
|
||||
}
|
||||
if expr.right >= 0 {
|
||||
append(&stack, expr.right)
|
||||
}
|
||||
append(&stack, ..expr.args)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1477,10 +1615,10 @@ build_globals :: proc(checker: ^Checker) {
|
||||
expr = expr,
|
||||
static_value = static_value,
|
||||
is_static = is_static,
|
||||
dependencies = dependencies[:],
|
||||
dependencies = dependencies,
|
||||
calls = calls[:],
|
||||
direct_problem = expr_problematic(&checker.module, expr),
|
||||
problematic = expr_problematic(&checker.module, expr),
|
||||
direct_problem = expr_problematic(checker, expr),
|
||||
problematic = expr_problematic(checker, expr),
|
||||
diagnostic = diagnostic,
|
||||
},
|
||||
)
|
||||
@@ -1544,17 +1682,13 @@ resolve_call_targets :: proc(checker: ^Checker) {
|
||||
}
|
||||
}
|
||||
|
||||
append_unique_slice :: proc(values: ^[]int, value: int, allocator: mem.Allocator) -> bool {
|
||||
append_unique_slice :: proc(values: ^[dynamic]int, value: int) -> bool {
|
||||
for existing in values^ {
|
||||
if existing == value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
replacement := make([]int, len(values^) + 1, allocator)
|
||||
copy(replacement, values^)
|
||||
replacement[len(values^)] = value
|
||||
delete(values^, allocator)
|
||||
values^ = replacement
|
||||
append(values, value)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1572,11 +1706,7 @@ propagate_global_reads :: proc(checker: ^Checker) {
|
||||
continue
|
||||
}
|
||||
for global_id in checker.module.functions[callee].direct_global_reads {
|
||||
if append_unique_slice(
|
||||
&function.direct_global_reads,
|
||||
global_id,
|
||||
checker.allocator,
|
||||
) {
|
||||
if append_unique_slice(&function.direct_global_reads, global_id) {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
@@ -1593,37 +1723,68 @@ propagate_global_reads :: proc(checker: ^Checker) {
|
||||
continue
|
||||
}
|
||||
for dependency in checker.module.functions[function_id].direct_global_reads {
|
||||
_ = append_unique_slice(&global.dependencies, dependency, checker.allocator)
|
||||
_ = append_unique_slice(&global.dependencies, dependency)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cycle_Frame :: struct {
|
||||
global: int,
|
||||
next_dependency: int,
|
||||
}
|
||||
|
||||
detect_global_cycles_visit :: proc(checker: ^Checker, global_id: int, states: []u8) {
|
||||
if states[global_id] == 2 {
|
||||
return
|
||||
}
|
||||
if states[global_id] == 1 {
|
||||
id := source.addf(
|
||||
checker.diagnostics,
|
||||
checker.ast_module.globals[global_id].span,
|
||||
"global initialization cycle involving '%s'",
|
||||
symbol_text(checker, checker.module.globals[global_id].name),
|
||||
)
|
||||
checker.module.globals[global_id].diagnostic = id
|
||||
checker.module.globals[global_id].problematic = true
|
||||
return
|
||||
stack := checker.cycle_stack
|
||||
clear_dynamic_array(&stack)
|
||||
defer {
|
||||
clear_dynamic_array(&stack)
|
||||
checker.cycle_stack = stack
|
||||
}
|
||||
states[global_id] = 1
|
||||
for dependency in checker.module.globals[global_id].dependencies {
|
||||
if dependency >= 0 && dependency < len(states) {
|
||||
detect_global_cycles_visit(checker, dependency, states)
|
||||
if checker.module.globals[dependency].problematic {
|
||||
checker.module.globals[global_id].problematic = true
|
||||
}
|
||||
append(&stack, Cycle_Frame{global=global_id})
|
||||
for len(stack) > 0 {
|
||||
frame_index := len(stack)-1
|
||||
frame := &stack[frame_index]
|
||||
if states[frame.global] == 0 {
|
||||
states[frame.global] = 1
|
||||
}
|
||||
dependencies := checker.module.globals[frame.global].dependencies
|
||||
if frame.next_dependency >= len(dependencies) {
|
||||
states[frame.global] = 2
|
||||
if checker.module.globals[frame.global].problematic && frame_index > 0 {
|
||||
checker.module.globals[stack[frame_index-1].global].problematic = true
|
||||
}
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
dependency := dependencies[frame.next_dependency]
|
||||
frame.next_dependency += 1
|
||||
if dependency < 0 || dependency >= len(states) {
|
||||
continue
|
||||
}
|
||||
if states[dependency] == 1 {
|
||||
id := source.addf(
|
||||
checker.diagnostics,
|
||||
checker.ast_module.globals[dependency].span,
|
||||
"global initialization cycle involving '%s'",
|
||||
symbol_text(checker, checker.module.globals[dependency].name),
|
||||
)
|
||||
checker.module.globals[dependency].diagnostic = id
|
||||
checker.module.globals[dependency].problematic = true
|
||||
checker.module.globals[frame.global].problematic = true
|
||||
continue
|
||||
}
|
||||
if states[dependency] == 2 {
|
||||
if checker.module.globals[dependency].problematic {
|
||||
checker.module.globals[frame.global].problematic = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
append(&stack, Cycle_Frame{global=dependency})
|
||||
}
|
||||
states[global_id] = 2
|
||||
}
|
||||
|
||||
synthesize_trap_main :: proc(checker: ^Checker) {
|
||||
@@ -1700,6 +1861,11 @@ check :: proc(
|
||||
allocator = allocator,
|
||||
}
|
||||
checker.specs.allocator = allocator
|
||||
checker.constant_stack.allocator = allocator
|
||||
checker.expr_stack.allocator = allocator
|
||||
checker.infer_stack.allocator = allocator
|
||||
checker.build_stack.allocator = allocator
|
||||
checker.cycle_stack.allocator = allocator
|
||||
build_symbol_indexes(&checker)
|
||||
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
||||
checker.constants = make([]Constant, len(ast_module.exprs), allocator)
|
||||
@@ -1718,6 +1884,11 @@ check :: proc(
|
||||
delete(checker.global_types, allocator)
|
||||
delete(checker.constants, allocator)
|
||||
delete(checker.template_diagnostics, allocator)
|
||||
delete(checker.constant_stack)
|
||||
delete(checker.expr_stack)
|
||||
delete(checker.infer_stack)
|
||||
delete(checker.build_stack)
|
||||
delete(checker.cycle_stack)
|
||||
}
|
||||
|
||||
for function, index in ast_module.functions {
|
||||
|
||||
Reference in New Issue
Block a user