package checker import "../ast" import "../hir" import "../source" import "../symbol" import "../target" import "../types" import "base:intrinsics" import "core:fmt" import "core:mem" import "core:slice" import "core:strings" Spec_Id :: distinct u32 INVALID_SPEC :: Spec_Id(0xffff_ffff) spec_id :: proc(index: int) -> Spec_Id { assert(index >= 0 && u64(index) < u64(INVALID_SPEC)) return Spec_Id(index) } spec_index :: proc(id: Spec_Id, count: int) -> (int, bool) { index := int(id) return index, id != INVALID_SPEC && index < count } Spec :: struct { template: ast.Function_Id, args: []types.Type, result: types.Type, hir_id: hir.Function_Id, } Infer_Local :: struct { name: symbol.Id, type: types.Type, } Build_Local :: struct { name: symbol.Id, type: types.Type, mutable: bool, id: hir.Local_Id, } Constant_Kind :: enum { Unknown, Not_Constant, Value, Overflow, } Constant :: struct { kind: Constant_Kind, value: i128, } Function_Index_Entry :: struct { scope: ast.Package_Id, name: symbol.Id, id: ast.Function_Id, } Global_Index_Entry :: struct { scope: ast.Package_Id, name: symbol.Id, id: ast.Global_Id, } Import_Index_Entry :: struct { scope: ast.File_Id, name: symbol.Id, id: ast.Import_Id, } Checker :: struct { ast_module: ^ast.Module, diagnostics: ^source.Diagnostics, symbols: ^symbol.Table, module: hir.Module, specs: [dynamic]Spec, function_index: []Function_Index_Entry, global_index: []Global_Index_Entry, import_index: []Import_Index_Entry, global_types: []types.Type, constants: []Constant, template_diagnostics: []source.Diagnostic_Id, constant_stack: [dynamic]Constant_Frame, ast_expr_stack: [dynamic]ast.Expr_Id, hir_expr_stack: [dynamic]hir.Expr_Id, infer_stack: [dynamic]Infer_Frame, build_stack: [dynamic]Build_Expr_Frame, cycle_stack: [dynamic]Cycle_Frame, main_symbol: symbol.Id, sink_symbol: symbol.Id, target: target.Target, allocator: mem.Allocator, } symbol_text :: proc(checker: ^Checker, id: symbol.Id) -> string { return symbol.resolve(checker.symbols, id) } Constant_Frame :: struct { expr: ast.Expr_Id, stage: u8, } eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant { if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { return Constant{kind = .Not_Constant} } stack := checker.constant_stack clear_dynamic_array(&stack) defer { clear_dynamic_array(&stack) checker.constant_stack = stack } 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 && expr.kind != .Negate { 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 != ast.INVALID_EXPR && int(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 && expr.kind == .Negate { operand := Constant{kind = .Not_Constant} if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.constants) { operand = checker.constants[expr.left] } result := Constant{kind = .Not_Constant} if operand.kind == .Overflow { result = Constant{kind = .Overflow} } else if operand.kind == .Value { value, overflow := intrinsics.overflow_sub(i128(0), operand.value) result = Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value} } checker.constants[frame.expr] = result _ = pop(&stack) continue } if frame.stage == 1 { stack[frame_index].stage = 2 if expr.right != ast.INVALID_EXPR && int(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 != ast.INVALID_EXPR && int(expr.left) < len(checker.constants) { left = checker.constants[expr.left] } if expr.right != ast.INVALID_EXPR && int(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 { value, overflow := intrinsics.overflow_add(left.value, right.value) result = Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value} } checker.constants[frame.expr] = result _ = pop(&stack) } return checker.constants[expr_id] } fits_signed_type :: proc(value: i128, value_type: types.Type, selected := target.DEFAULT) -> bool { if !types.is_signed(value_type, selected) { return false } limit := i128(1) << u32(types.bits(value_type, selected) - 1) return value >= -limit && value < limit } fits_unsigned_type :: proc(value: i128, value_type: types.Type, selected := target.DEFAULT) -> bool { if !types.is_unsigned(value_type, selected) || value < 0 { return false } limit := i128(1) << u32(types.bits(value_type, selected)) return value < limit } fits_integer_type :: proc(value: i128, value_type: types.Type, selected := target.DEFAULT) -> bool { return fits_signed_type(value, value_type, selected) || fits_unsigned_type(value, value_type, selected) } fits_i64 :: proc(value: i128) -> bool { return fits_signed_type(value, types.I64) } type_from_syntax :: proc(value: ast.Type_Syntax) -> types.Type { return value } is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool { return types.is_runtime_value(value, &checker.module.types) } resolve_inferred_array :: proc(checker: ^Checker, value: types.Type, expr_id: ast.Expr_Id) -> types.Type { item, ok := types.node(&checker.module.types, value) if !ok || item.kind != .Array || !item.inferred_count || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { return value } expr := checker.ast_module.exprs[expr_id] if expr.kind != .Array { return value } return types.with_array_count(&checker.module.types, value, u64(len(expr.args))) } function_index_less :: proc(left, right: Function_Index_Entry) -> bool { if left.scope != right.scope { return left.scope < right.scope } if left.name != right.name { return int(left.name) < int(right.name) } return left.id < right.id } global_index_less :: proc(left, right: Global_Index_Entry) -> bool { if left.scope != right.scope { return left.scope < right.scope } if left.name != right.name { return int(left.name) < int(right.name) } return left.id < right.id } import_index_less :: proc(left, right: Import_Index_Entry) -> bool { if left.scope != right.scope { return left.scope < right.scope } if left.name != right.name { return int(left.name) < int(right.name) } return left.id < right.id } find_function_symbol :: proc(index: []Function_Index_Entry, scope: ast.Package_Id, name: symbol.Id) -> ast.Function_Id { low := 0 high := len(index) for low < high { middle := low + (high-low)/2 entry := index[middle] if entry.scope < scope || entry.scope == scope && int(entry.name) < int(name) { low = middle + 1 } else { high = middle } } if low < len(index) && index[low].scope == scope && index[low].name == name { return index[low].id } return ast.INVALID_FUNCTION } find_global_symbol :: proc(index: []Global_Index_Entry, scope: ast.Package_Id, name: symbol.Id) -> ast.Global_Id { low := 0 high := len(index) for low < high { middle := low + (high-low)/2 entry := index[middle] if entry.scope < scope || entry.scope == scope && int(entry.name) < int(name) { low = middle + 1 } else { high = middle } } if low < len(index) && index[low].scope == scope && index[low].name == name { return index[low].id } return ast.INVALID_GLOBAL } find_import_symbol :: proc(index: []Import_Index_Entry, scope: ast.File_Id, name: symbol.Id) -> ast.Import_Id { low := 0 high := len(index) for low < high { middle := low + (high-low)/2 entry := index[middle] if entry.scope < scope || entry.scope == scope && int(entry.name) < int(name) { low = middle + 1 } else { high = middle } } if low < len(index) && index[low].scope == scope && index[low].name == name { return index[low].id } return ast.INVALID_IMPORT } build_symbol_indexes :: proc(checker: ^Checker) { checker.function_index = make([]Function_Index_Entry, len(checker.ast_module.functions), checker.allocator) for function, id in checker.ast_module.functions { checker.function_index[id] = Function_Index_Entry{scope=function.pkg, name=function.name, id=ast.function_id(id)} } slice.sort_by(checker.function_index, function_index_less) checker.global_index = make([]Global_Index_Entry, len(checker.ast_module.globals), checker.allocator) for global, id in checker.ast_module.globals { checker.global_index[id] = Global_Index_Entry{scope=global.pkg, name=global.name, id=ast.global_id(id)} } slice.sort_by(checker.global_index, global_index_less) checker.import_index = make([]Import_Index_Entry, len(checker.ast_module.imports), checker.allocator) for import_item, id in checker.ast_module.imports { checker.import_index[id] = Import_Index_Entry{scope=import_item.file, name=import_item.alias, id=ast.import_id(id)} } slice.sort_by(checker.import_index, import_index_less) } find_template :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0)) -> ast.Function_Id { return find_function_symbol(checker.function_index, pkg, name) } find_global :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0)) -> ast.Global_Id { return find_global_symbol(checker.global_index, pkg, name) } find_import :: proc(checker: ^Checker, file: ast.File_Id, alias: symbol.Id, mark_used := false) -> ast.Import_Id { id := find_import_symbol(checker.import_index, file, alias) if id != ast.INVALID_IMPORT && mark_used { checker.ast_module.imports[id].used = true } return id } expr_package :: proc(checker: ^Checker, expr: ast.Expr, pkg: ast.Package_Id, file: ast.File_Id, mark_used := false) -> (ast.Package_Id, bool) { if !symbol.is_valid(expr.qualifier) { return pkg, true } import_id := find_import(checker, file, expr.qualifier, mark_used) if import_id == ast.INVALID_IMPORT { return ast.INVALID_PACKAGE, false } import_item := checker.ast_module.imports[import_id] if import_item.target == ast.INVALID_PACKAGE || int(import_item.target) >= len(checker.ast_module.packages) || !checker.ast_module.packages[import_item.target].available { return import_item.target, false } return import_item.target, true } add_package_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, file: ast.File_Id) -> source.Diagnostic_Id { if find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT { return source.addf(checker.diagnostics, expr.span, "unknown package alias '%s'", symbol_text(checker, expr.qualifier)) } return source.addf(checker.diagnostics, expr.span, "unavailable imported package '%s'", symbol_text(checker, expr.qualifier)) } add_name_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id) -> source.Diagnostic_Id { if find_template(checker, expr.name, target_pkg) != ast.INVALID_FUNCTION { return source.addf(checker.diagnostics, expr.span, "'%s' is a function, not a global value", symbol_text(checker, expr.name)) } if symbol.is_valid(expr.qualifier) { return source.addf( checker.diagnostics, expr.span, "package '%s' has no member '%s'", symbol_text(checker, expr.qualifier), symbol_text(checker, expr.name), ) } return source.addf(checker.diagnostics, expr.span, "unresolved global '%s'", symbol_text(checker, expr.name)) } add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id) -> source.Diagnostic_Id { if find_global(checker, expr.name, target_pkg) != ast.INVALID_GLOBAL { return source.addf(checker.diagnostics, expr.span, "'%s' is a global, not a function", symbol_text(checker, expr.name)) } if symbol.is_valid(expr.qualifier) { return source.addf( checker.diagnostics, expr.span, "package '%s' has no member '%s'", symbol_text(checker, expr.qualifier), symbol_text(checker, expr.name), ) } return source.addf(checker.diagnostics, expr.span, "unresolved function '%s'", symbol_text(checker, expr.name)) } contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool { for existing in names { if existing == name { return true } } return false } mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: ast.File_Id) { stack := checker.ast_expr_stack clear_dynamic_array(&stack) defer { clear_dynamic_array(&stack) checker.ast_expr_stack = stack } append(&stack, expr_id) for len(stack) > 0 { id := pop(&stack) if id == ast.INVALID_EXPR || int(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) } switch expr.kind { case .Call: append(&stack, ..expr.args) case .Array, .Struct_Literal, .Slice: append(&stack, ..expr.args) if expr.left != ast.INVALID_EXPR { append(&stack, expr.left) } case .Negate, .Address, .Deref, .Field, .Unwrap, .Keyed: append(&stack, expr.left) case .Add, .Index, .Orelse: append(&stack, expr.left, expr.right) case .Invalid, .Integer, .Float, .String, .None, .Name: } } } validate_declarations :: proc(checker: ^Checker) { for function, function_id in checker.ast_module.functions { locals: [dynamic]symbol.Id locals.allocator = checker.allocator for param in function.params { if param.type == types.VOID { source.add( checker.diagnostics, param.span, "void is only valid as a function result type", ) } if contains_name(locals[:], param.name) { source.addf( checker.diagnostics, param.span, "duplicate parameter '%s'", symbol_text(checker, param.name), ) } append(&locals, param.name) if types.contains_c_struct_by_value(type_from_syntax(param.type), &checker.module.types) { checker.template_diagnostics[function_id] = source.addf( checker.diagnostics, param.span, "C records cannot be passed by value to '%s'", symbol_text(checker, function.name), ) } } if types.contains_c_struct_by_value(type_from_syntax(function.result), &checker.module.types) { checker.template_diagnostics[function_id] = source.addf( checker.diagnostics, function.span, "C records cannot be returned by value from '%s'", symbol_text(checker, function.name), ) } if !function.has_body && !function.c_abi { checker.template_diagnostics[function_id] = source.addf( checker.diagnostics, function.span, "bodyless function '%s' must use 'c_func'", symbol_text(checker, function.name), ) } if !function.has_body && function.c_abi { for param in function.params { if !types.is_c_signature_type(type_from_syntax(param.type), &checker.module.types) { checker.template_diagnostics[function_id] = source.addf( checker.diagnostics, param.span, "foreign function '%s' requires concrete parameter types", symbol_text(checker, function.name), ) } } result := type_from_syntax(function.result) if !types.is_c_signature_type(result, &checker.module.types, true) { checker.template_diagnostics[function_id] = source.addf( checker.diagnostics, function.span, "foreign function '%s' requires a concrete or void result type", symbol_text(checker, function.name), ) } if function.pkg == 0 && function.name == checker.main_symbol { checker.template_diagnostics[function_id] = source.add( checker.diagnostics, function.span, "main must have a body", ) } } 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: } } delete(locals) } for function, function_id in checker.ast_module.functions { if function.has_body || !function.c_abi { continue } for other, other_id in checker.ast_module.functions { if other_id == function_id || other.has_body || !other.c_abi || other.name != function.name { continue } checker.template_diagnostics[function_id] = source.addf( checker.diagnostics, function.span, "duplicate foreign symbol '%s'", symbol_text(checker, function.name), ) break } } } validate_type_nodes :: proc(checker: ^Checker) { for item, index in checker.module.types.nodes { id := types.DYNAMIC_START+types.Type(index) if item.has_sentinel { value := i128(item.sentinel) if types.is_signed(item.child, checker.target) { value = i128(i64(item.sentinel)) } if !types.is_concrete_integer(item.child) || !fits_integer_type(value, item.child, checker.target) { source.addf( checker.diagnostics, source.Span{}, "sentinel value does not fit array or slice element type %s", types.name(item.child), ) } } if item.kind == .Struct { for field in types.fields_for(&checker.module.types, id) { if types.contains_c_struct_by_value(field.type, &checker.module.types) { source.add( checker.diagnostics, source.Span{}, "C records may only appear behind pointers", ) } } } } } 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 } } return types.INVALID } 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 { if spec.template != template || len(spec.args) != len(function.params) { continue } matches := true for param, param_index in function.params { actual := types.INVALID if param_index < len(actual_args) { actual = actual_args[param_index] } if !types.equal(spec.args[param_index], specialized_param_type(param.type, actual)) { matches = false break } } if matches { return spec_id(index) } } return INVALID_SPEC } specialized_param_type :: proc(syntax: ast.Type_Syntax, actual: types.Type) -> types.Type { declared := type_from_syntax(syntax) if types.is_constraint(declared) { return actual } return declared } can_specialize :: proc(checker: ^Checker, function: ast.Function, actual_args: []types.Type) -> bool { for param, index in function.params { actual := types.INVALID if index < len(actual_args) { actual = actual_args[index] } if !is_runtime_type(checker, specialized_param_type(param.type, actual)) { return false } } return true } ensure_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []types.Type) -> Spec_Id { if existing := find_spec(checker, template, actual_args); existing != INVALID_SPEC { return existing } function := checker.ast_module.functions[template] signature: [dynamic]types.Type signature.allocator = checker.allocator for param, index in function.params { actual := types.INVALID if index < len(actual_args) { actual = actual_args[index] } append(&signature, specialized_param_type(param.type, actual)) } result := type_from_syntax(function.result) if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT { result = types.I32 } index := spec_id(len(checker.specs)) append( &checker.specs, Spec{template = template, args = signature[:], result = result, hir_id = hir.INVALID_FUNCTION}, ) return index } mark_spec_demanded :: proc(checker: ^Checker, id: Spec_Id, stack: ^[dynamic]Spec_Id) { if id == INVALID_SPEC || checker.specs[id].hir_id != hir.INVALID_FUNCTION { return } checker.specs[id].hir_id = hir.Function_Id(0) append(stack, id) } Infer_Frame :: struct { expr: ast.Expr_Id, stage: u8, left: types.Type, arg_index: int, args: []types.Type, template: ast.Function_Id, } infer_nested_expr :: proc( checker: ^Checker, expr_id: ast.Expr_Id, locals: []Infer_Local, pkg: ast.Package_Id, file: ast.File_Id, demanded: ^[dynamic]Spec_Id, ) -> types.Type { outer := checker.infer_stack checker.infer_stack = nil checker.infer_stack.allocator = checker.allocator result := infer_expr(checker, expr_id, locals, pkg, file, demanded) delete(checker.infer_stack) checker.infer_stack = outer return result } infer_compound_expr :: proc( checker: ^Checker, expr: ast.Expr, locals: []Infer_Local, pkg: ast.Package_Id, file: ast.File_Id, demanded: ^[dynamic]Spec_Id, ) -> types.Type { store := &checker.module.types #partial switch expr.kind { case .String: return types.slice(store, types.U8, false, true, 0) case .Array: element := types.INVALID for arg in expr.args { actual := infer_nested_expr(checker, arg, locals, pkg, file, demanded) if !types.is_valid(element) { element = actual } else if !types.equal(element, actual) { element = types.widest(element, actual) } } if !types.is_valid(element) { element = types.I64 } return types.array(store, element, u64(len(expr.args)), false) case .None: return types.INVALID case .Address: child := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) return types.pointer(store, child, false, false) case .Deref: value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) return types.child_type(value, store) if types.is_pointer(value, store) else types.INVALID case .Index: value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) _ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded) return types.child_type(value, store) case .Slice: value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) item, ok := types.node(store, value) if !ok || (item.kind != .Array && item.kind != .Slice) { return types.INVALID } for bound in expr.args { if bound != ast.INVALID_EXPR { _ = infer_nested_expr(checker, bound, locals, pkg, file, demanded) } } preserve := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR return types.slice(store, item.child, item.mutable, preserve, item.sentinel) case .Field: value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) item, has_item := types.node(store, value) field_name := symbol_text(checker, expr.name) if has_item && (item.kind == .Array || item.kind == .Slice) { if field_name == "len" { return types.USIZE } if field_name == "ptr" { return types.pointer(store, item.child, item.mutable, true) } } if types.is_pointer(value, store) { value = types.child_type(value, store) } _, field, ok := find_struct_field(checker, value, expr.name) return field.type if ok else types.INVALID case .Unwrap: value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) return types.child_type(value, store) if types.is_optional(value, store) else types.INVALID case .Orelse: value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) _ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded) return types.child_type(value, store) if types.is_optional(value, store) else types.INVALID case .Struct_Literal: for keyed in expr.args { _ = infer_nested_expr(checker, checker.ast_module.exprs[keyed].left, locals, pkg, file, demanded) } target_pkg, available := expr_package(checker, expr, pkg, file) return types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID case .Keyed: return infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) case: return types.INVALID } } infer_expr :: proc( checker: ^Checker, expr_id: ast.Expr_Id, locals: []Infer_Local, pkg := ast.Package_Id(0), file := ast.File_Id(0), demanded: ^[dynamic]Spec_Id = nil, ) -> types.Type { 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 } append(&stack, Infer_Frame{expr=expr_id, template=ast.INVALID_FUNCTION}) last := types.INVALID for len(stack) > 0 { frame_index := len(stack)-1 frame := stack[frame_index] if frame.expr == ast.INVALID_EXPR || int(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.I64 if expr.integer <= 0x7fff_ffff_ffff_ffff { last = types.smallest_signed_for_literal(i64(expr.integer)) } _ = pop(&stack) case .Float: last = types.F64 _ = pop(&stack) case .String, .Array, .None, .Address, .Deref, .Index, .Slice, .Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed: last = infer_compound_expr(checker, expr, locals, pkg, file, demanded) _ = pop(&stack) case .Name: last = types.INVALID if !symbol.is_valid(expr.qualifier) { last = find_infer_local(locals, expr.name) } else { base_type := find_infer_local(locals, expr.qualifier) item, has_item := types.node(&checker.module.types, base_type) field_name := symbol_text(checker, expr.name) if has_item && (item.kind == .Array || item.kind == .Slice) { if field_name == "len" { last = types.USIZE } else if field_name == "ptr" { last = types.pointer(&checker.module.types, item.child, item.mutable, true) } } if types.is_pointer(base_type, &checker.module.types) { base_type = types.child_type(base_type, &checker.module.types) } if !types.is_valid(last) { _, field, ok := find_struct_field(checker, base_type, expr.name) if ok { last = field.type } } } 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 != ast.INVALID_GLOBAL { last = checker.global_types[global] } } } _ = pop(&stack) case .Negate: stack[frame_index].stage = 5 append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION}) case .Add: stack[frame_index].stage = 1 append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION}) case .Call: target_pkg, available := expr_package(checker, expr, pkg, file) template := ast.INVALID_FUNCTION if available { template = find_template(checker, expr.name, target_pkg) } if template == ast.INVALID_FUNCTION { last = types.INVALID _ = pop(&stack) continue } if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC { declared := type_from_syntax(checker.ast_module.functions[template].result) last = declared if is_runtime_type(checker, declared) || types.is_void(declared) 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=ast.INVALID_FUNCTION}) } } continue } if frame.stage == 5 { if !types.is_signed(last, checker.target) && !types.is_float(last, checker.target) { last = types.INVALID } _ = pop(&stack) continue } if frame.stage == 1 { stack[frame_index].left = last stack[frame_index].stage = 2 append(&stack, Infer_Frame{expr=expr.right, template=ast.INVALID_FUNCTION}) continue } if frame.stage == 2 { if types.is_many_pointer(frame.left, &checker.module.types) && types.is_concrete_integer(last) { last = frame.left } else { 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=ast.INVALID_FUNCTION}) continue } } function := checker.ast_module.functions[frame.template] if can_specialize(checker, function, stack[frame_index].args) { spec := INVALID_SPEC if demanded == nil { spec = ensure_spec(checker, frame.template, stack[frame_index].args) } else if len(expr.args) == len(function.params) { spec = find_spec(checker, frame.template, stack[frame_index].args) mark_spec_demanded(checker, spec, demanded) } if spec != INVALID_SPEC { last = checker.specs[spec].result } else { declared := type_from_syntax(function.result) last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID } } else { declared := type_from_syntax(function.result) if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT { last = types.I32 } else { last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID } } delete(stack[frame_index].args, checker.allocator) stack[frame_index].args = nil _ = pop(&stack) } } return last } 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] declared := type_from_syntax(function.result) if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT { declared = types.I32 } locals: [dynamic]Infer_Local locals.allocator = checker.allocator defer delete(locals) 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}) } 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) } } } } if types.is_constraint(declared) { return result } return declared } merge_inferred_type :: proc(store: ^types.Store, current: ^types.Type, inferred: types.Type) -> bool { if !types.is_runtime_value(inferred, store) { return false } if !types.is_runtime_value(current^, store) { current^ = inferred return true } if types.equal(current^, inferred) { return false } merged := types.widest(current^, inferred) if types.is_concrete_scalar(merged) && !types.equal(current^, merged) { current^ = merged return true } return false } infer_all :: proc(checker: ^Checker) { for global, index in checker.ast_module.globals { declared := type_from_syntax(global.type) if is_runtime_type(checker, declared) { checker.global_types[index] = declared } } main_template := find_template(checker, checker.main_symbol, 0) if main_template != ast.INVALID_FUNCTION { ensure_spec(checker, main_template, nil) } for { changed := false spec_count := len(checker.specs) for global, index in checker.ast_module.globals { inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file) if is_runtime_type(checker, type_from_syntax(global.type)) { continue } changed = merge_inferred_type(&checker.module.types, &checker.global_types[index], inferred) || changed } for index := 0; index < len(checker.specs); index += 1 { id := spec_id(index) inferred := infer_spec_result(checker, id) changed = merge_inferred_type(&checker.module.types, &checker.specs[id].result, inferred) || changed } if len(checker.specs) != spec_count { changed = true } if !changed { break } } } prune_specs :: proc(checker: ^Checker) { stack: [dynamic]Spec_Id stack.allocator = checker.allocator defer delete(stack) main_template := find_template(checker, checker.main_symbol, 0) if main_template != ast.INVALID_FUNCTION { mark_spec_demanded(checker, find_spec(checker, main_template, nil), &stack) } for global in checker.ast_module.globals { _ = infer_expr(checker, global.expr, nil, global.pkg, global.file, &stack) } for len(stack) > 0 { id := pop(&stack) _ = infer_spec_result(checker, id, &stack) } retained := 0 for spec in checker.specs { if spec.hir_id == hir.INVALID_FUNCTION { delete(spec.args, checker.allocator) continue } checker.specs[retained] = spec checker.specs[retained].hir_id = hir.function_id(retained) retained += 1 } for len(checker.specs) > retained { _ = pop(&checker.specs) } } add_hir_expr :: proc(checker: ^Checker, expr: hir.Expr) -> hir.Expr_Id { id := hir.expr_id(len(checker.module.exprs)) append(&checker.module.exprs, expr) return id } invalid_hir_expr :: proc( checker: ^Checker, span: source.Span, diagnostic: source.Diagnostic_Id, recovery_type := types.INVALID, ) -> hir.Expr_Id { return add_hir_expr( checker, hir.Expr { kind = .Invalid, span = span, type = recovery_type, target = hir.INVALID_REF, left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, diagnostic = diagnostic, }, ) } add_unique_global :: proc(values: ^[dynamic]hir.Global_Id, value: hir.Global_Id) { for existing in values { if existing == value { return } } append(values, value) } add_unique_function :: proc(values: ^[dynamic]hir.Function_Id, value: hir.Function_Id) { for existing in values { if existing == value { return } } append(values, value) } find_build_local :: proc(locals: []Build_Local, name: symbol.Id) -> (Build_Local, bool) { for index := len(locals) - 1; index >= 0; index -= 1 { if locals[index].name == name { return locals[index], true } } return Build_Local{}, false } coerce_expr :: proc( checker: ^Checker, expr_id: hir.Expr_Id, expected: types.Type, span: source.Span, ) -> hir.Expr_Id { if expr_id == hir.INVALID_EXPR { return expr_id } actual := checker.module.exprs[expr_id].type if types.equal(actual, expected) { return expr_id } if types.can_weaken_pointer(actual, expected, &checker.module.types) { checker.module.exprs[expr_id].type = expected return expr_id } if types.is_optional(expected, &checker.module.types) { child := types.child_type(expected, &checker.module.types) if types.equal(actual, child) || types.can_widen(actual, child) || types.can_weaken_pointer(actual, child, &checker.module.types) { value := coerce_expr(checker, expr_id, child, span) return add_hir_expr(checker, hir.Expr{ kind=.Optional_Some, span=span, type=expected, left=value, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } } if types.can_widen(actual, expected) { return add_hir_expr( checker, hir.Expr { kind = .Widen, span = span, type = expected, left = expr_id, target = hir.INVALID_REF, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC, }, ) } id := source.addf( checker.diagnostics, span, "cannot implicitly convert %s to %s", types.name(actual), types.name(expected), ) return invalid_hir_expr(checker, span, id, expected) } build_constant_expr :: proc( checker: ^Checker, expr: ast.Expr, constant: Constant, expected: types.Type, ) -> hir.Expr_Id { recovery_type := types.I64 if types.is_concrete_integer(expected) { recovery_type = expected } if constant.kind == .Overflow || (!types.is_concrete_integer(expected) && !fits_i64(constant.value)) { id := source.add( checker.diagnostics, expr.span, "integer constant expression exceeds signed i64 range", ) return invalid_hir_expr(checker, expr.span, id, recovery_type) } value := i64(constant.value) if constant.value >= 0 && constant.value <= i128(0xffff_ffff_ffff_ffff) { value = transmute(i64)u64(constant.value) } result_type := types.smallest_signed_for_literal(value) if types.is_concrete_integer(expected) { if !fits_integer_type(constant.value, expected, checker.target) { id := source.addf( checker.diagnostics, expr.span, "integer constant %d does not fit in %s", constant.value, types.name(expected), ) return invalid_hir_expr(checker, expr.span, id, expected) } result_type = expected } return add_hir_expr( checker, hir.Expr { kind = .Integer, span = expr.span, type = result_type, integer = value, target = hir.INVALID_REF, left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC, }, ) } build_float_expr :: proc(checker: ^Checker, expr: ast.Expr, expected: types.Type) -> hir.Expr_Id { result_type := types.F64 if types.is_float(expected, checker.target) { result_type = expected } else if types.is_valid(expected) { id := source.addf( checker.diagnostics, expr.span, "cannot implicitly convert f64 to %s", types.name(expected), ) return invalid_hir_expr(checker, expr.span, id, expected) } value := transmute(f64)expr.integer bits := transmute(i64)value if types.bits(result_type, checker.target) == 32 { bits = i64(transmute(u32)f32(value)) } return add_hir_expr(checker, hir.Expr{ kind=.Float, span=expr.span, type=result_type, integer=bits, target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } Build_Expr_Frame :: struct { expr: ast.Expr_Id, expected: types.Type, stage: u8, left: hir.Expr_Id, arg_index: int, built_args: []hir.Expr_Id, arg_types: []types.Type, template: ast.Function_Id, } hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: []Build_Local) -> bool { if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(checker.module.exprs) { return false } expr := checker.module.exprs[expr_id] #partial switch expr.kind { case .Local: id := hir.as_local(expr.target) for local in locals { if local.id == id { return local.mutable } } case .Deref: pointer_type := checker.module.exprs[expr.left].type return types.is_mutable(pointer_type, &checker.module.types) case .Index: container_type := checker.module.exprs[expr.left].type return types.is_mutable(container_type, &checker.module.types) case .Field: base_type := checker.module.exprs[expr.left].type if types.is_pointer(base_type, &checker.module.types) { return types.is_mutable(base_type, &checker.module.types) } return hir_location_writable(checker, expr.left, locals) case: } return false } hir_is_location :: proc(checker: ^Checker, expr_id: hir.Expr_Id) -> bool { if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(checker.module.exprs) { return false } #partial switch checker.module.exprs[expr_id].kind { case .Local, .Global, .Deref, .Index, .Field: return true } return false } find_struct_field :: proc(checker: ^Checker, struct_type: types.Type, name: symbol.Id) -> (int, types.Field, bool) { for field, index in types.fields_for(&checker.module.types, struct_type) { if field.name == u32(name) { return index, field, true } } return 0, {}, false } build_nested_expr :: proc( checker: ^Checker, expr_id: ast.Expr_Id, locals: []Build_Local, global_reads: ^[dynamic]hir.Global_Id, calls: ^[dynamic]hir.Function_Id, expected: types.Type, pkg: ast.Package_Id, file: ast.File_Id, ) -> hir.Expr_Id { outer := checker.build_stack checker.build_stack = nil checker.build_stack.allocator = checker.allocator result := build_expr(checker, expr_id, locals, global_reads, calls, expected, pkg, file) delete(checker.build_stack) checker.build_stack = outer return result } build_compound_expr :: proc( checker: ^Checker, expr: ast.Expr, locals: []Build_Local, global_reads: ^[dynamic]hir.Global_Id, calls: ^[dynamic]hir.Function_Id, expected: types.Type, pkg: ast.Package_Id, file: ast.File_Id, ) -> hir.Expr_Id { store := &checker.module.types #partial switch expr.kind { case .String: string_type := types.slice(store, types.U8, false, true, 0) return add_hir_expr(checker, hir.Expr{ kind=.String, span=expr.span, type=string_type, integer=i64(expr.integer), target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) case .Array: element_type := types.INVALID result_type := expected expected_node, has_expected := types.node(store, expected) if !has_expected || expected_node.kind != .Array { has_expected = false result_type = types.INVALID } else { element_type = expected_node.child if !expected_node.inferred_count && expected_node.count != u64(len(expr.args)) { id := source.addf( checker.diagnostics, expr.span, "array literal expects %d elements, got %d", expected_node.count, len(expr.args), ) return invalid_hir_expr(checker, expr.span, id, expected) } if expected_node.inferred_count { result_type = types.with_array_count(store, expected, u64(len(expr.args))) } } if !has_expected { 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} } for arg in expr.args { actual := infer_nested_expr(checker, arg, infer_locals, pkg, file, nil) if !types.is_valid(element_type) { element_type = actual } else { element_type = types.widest(element_type, actual) } } if !types.is_valid(element_type) { element_type = types.I64 } result_type = types.array(store, element_type, u64(len(expr.args)), false) } args := make([]hir.Expr_Id, len(expr.args), checker.allocator) for arg, index in expr.args { args[index] = build_nested_expr( checker, arg, locals, global_reads, calls, element_type, pkg, file, ) args[index] = coerce_expr(checker, args[index], element_type, checker.module.exprs[args[index]].span) } return add_hir_expr(checker, hir.Expr{ kind=.Array, span=expr.span, type=result_type, args=args, target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) case .None: if !types.is_optional(expected, store) { id := source.add(checker.diagnostics, expr.span, "'none' requires an optional context") return invalid_hir_expr(checker, expr.span, id, expected) } return add_hir_expr(checker, hir.Expr{ kind=.None, span=expr.span, type=expected, target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) case .Address: value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) if !hir_is_location(checker, value) { id := source.add(checker.diagnostics, expr.span, "'&' requires an addressable location") return invalid_hir_expr(checker, expr.span, id) } value_type := checker.module.exprs[value].type writable := hir_location_writable(checker, value, locals) result_type := types.pointer(store, value_type, writable, false) if types.is_pointer(expected, store) && types.equal(types.child_type(expected, store), value_type) && (!types.is_mutable(expected, store) || writable) { result_type = expected } return add_hir_expr(checker, hir.Expr{ kind=.Address, span=expr.span, type=result_type, left=value, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) case .Deref: pointer := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) pointer_type := checker.module.exprs[pointer].type if !types.is_pointer(pointer_type, store) { id := source.add(checker.diagnostics, expr.span, "postfix '^' requires a pointer") return invalid_hir_expr(checker, expr.span, id) } return add_hir_expr(checker, hir.Expr{ kind=.Deref, span=expr.span, type=types.child_type(pointer_type, store), left=pointer, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) case .Index: container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) index := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.USIZE, pkg, file) container_type := checker.module.exprs[container].type item, ok := types.node(store, container_type) if !ok || (item.kind != .Array && item.kind != .Slice && !(item.kind == .Pointer && item.many)) { id := source.add(checker.diagnostics, expr.span, "indexing requires an array, slice, or many-item pointer") return invalid_hir_expr(checker, expr.span, id) } return add_hir_expr(checker, hir.Expr{ kind=.Index, span=expr.span, type=item.child, left=container, right=index, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC, }) case .Slice: container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) container_type := checker.module.exprs[container].type item, ok := types.node(store, container_type) if !ok || (item.kind != .Array && item.kind != .Slice) { id := source.add(checker.diagnostics, expr.span, "slicing requires an array or slice") return invalid_hir_expr(checker, expr.span, id) } bounds := make([]hir.Expr_Id, 2, checker.allocator) bounds[0] = hir.INVALID_EXPR bounds[1] = hir.INVALID_EXPR for bound, index in expr.args { if bound != ast.INVALID_EXPR { bounds[index] = build_nested_expr(checker, bound, locals, global_reads, calls, types.USIZE, pkg, file) } } preserve_sentinel := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR result_type := types.slice(store, item.child, item.mutable, preserve_sentinel, item.sentinel) return add_hir_expr(checker, hir.Expr{ kind=.Slice, span=expr.span, type=result_type, args=bounds, left=container, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) case .Field: base := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) base_type := checker.module.exprs[base].type item, has_item := types.node(store, base_type) field_name := symbol_text(checker, expr.name) if has_item && (item.kind == .Array || item.kind == .Slice) { if field_name == "len" { return add_hir_expr(checker, hir.Expr{ kind=.Length, span=expr.span, type=types.USIZE, left=base, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } if field_name == "ptr" { return add_hir_expr(checker, hir.Expr{ kind=.Slice_Ptr, span=expr.span, type=types.pointer(store, item.child, item.mutable, true), left=base, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } } if types.is_pointer(base_type, store) { base_type = types.child_type(base_type, store) } index, field, ok := find_struct_field(checker, base_type, expr.name) if !ok { id := source.addf(checker.diagnostics, expr.span, "unknown struct field '%s'", symbol_text(checker, expr.name)) return invalid_hir_expr(checker, expr.span, id) } return add_hir_expr(checker, hir.Expr{ kind=.Field, span=expr.span, type=field.type, integer=i64(index), left=base, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) case .Unwrap: optional := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) optional_type := checker.module.exprs[optional].type if !types.is_optional(optional_type, store) { id := source.add(checker.diagnostics, expr.span, "postfix '?' requires an optional") return invalid_hir_expr(checker, expr.span, id) } return add_hir_expr(checker, hir.Expr{ kind=.Unwrap, span=expr.span, type=types.child_type(optional_type, store), left=optional, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) case .Orelse: optional := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) optional_type := checker.module.exprs[optional].type if !types.is_optional(optional_type, store) { id := source.add(checker.diagnostics, expr.span, "'orelse' requires an optional left operand") return invalid_hir_expr(checker, expr.span, id) } child := types.child_type(optional_type, store) fallback := build_nested_expr(checker, expr.right, locals, global_reads, calls, child, pkg, file) fallback = coerce_expr(checker, fallback, child, checker.module.exprs[fallback].span) return add_hir_expr(checker, hir.Expr{ kind=.Orelse, span=expr.span, type=child, left=optional, right=fallback, 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 if !types.is_struct(struct_type, store) || types.is_opaque_struct(struct_type, store) { id := source.addf(checker.diagnostics, expr.span, "unknown or opaque struct type '%s'", symbol_text(checker, expr.name)) return invalid_hir_expr(checker, expr.span, id) } if types.is_c_struct(struct_type, store) { id := source.add(checker.diagnostics, expr.span, "c_struct values cannot be constructed by value") return invalid_hir_expr(checker, expr.span, id) } fields := types.fields_for(store, struct_type) values := make([]hir.Expr_Id, len(fields), checker.allocator) initialized := make([]bool, len(fields), checker.allocator) defer delete(initialized, checker.allocator) for &value in values { value = hir.INVALID_EXPR } for keyed in expr.args { keyed_expr := checker.ast_module.exprs[keyed] index, field, ok := find_struct_field(checker, struct_type, keyed_expr.name) if !ok { source.addf(checker.diagnostics, keyed_expr.span, "unknown struct field '%s'", symbol_text(checker, keyed_expr.name)) continue } if initialized[index] { source.addf(checker.diagnostics, keyed_expr.span, "duplicate initializer for struct field '%s'", symbol_text(checker, keyed_expr.name)) continue } initialized[index] = true values[index] = build_nested_expr(checker, keyed_expr.left, locals, global_reads, calls, field.type, pkg, file) values[index] = coerce_expr(checker, values[index], field.type, keyed_expr.span) } for field, index in fields { if values[index] == hir.INVALID_EXPR { id := source.addf(checker.diagnostics, expr.span, "missing initializer for struct field '%s'", symbol_text(checker, symbol.Id(field.name))) delete(values, checker.allocator) return invalid_hir_expr(checker, expr.span, id, struct_type) } } return add_hir_expr(checker, hir.Expr{ kind=.Struct, span=expr.span, type=struct_type, args=values, target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) case .Keyed: id := source.add(checker.diagnostics, expr.span, "keyed initializer is only valid inside a struct literal") return invalid_hir_expr(checker, expr.span, id) case: return invalid_hir_expr(checker, expr.span, expr.diagnostic) } } build_expr :: proc( checker: ^Checker, expr_id: ast.Expr_Id, locals: []Build_Local, global_reads: ^[dynamic]hir.Global_Id, calls: ^[dynamic]hir.Function_Id, expected := types.INVALID, pkg := ast.Package_Id(0), file := ast.File_Id(0), ) -> hir.Expr_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 } append(&stack, Build_Expr_Frame{expr=expr_id, expected=expected, template=ast.INVALID_FUNCTION}) last := hir.INVALID_EXPR for len(stack) > 0 { frame_index := len(stack)-1 frame := stack[frame_index] if frame.expr == ast.INVALID_EXPR || int(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 .String, .Array, .None, .Address, .Deref, .Index, .Slice, .Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed: last = build_compound_expr( checker, expr, locals, global_reads, calls, frame.expected, pkg, file, ) _ = pop(&stack) case .Invalid, .Integer: last = invalid_hir_expr(checker, expr.span, expr.diagnostic) _ = pop(&stack) case .Float: last = build_float_expr(checker, expr, frame.expected) _ = pop(&stack) case .Name: last = hir.INVALID_EXPR 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=hir.local_ref(local.id), left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC, }) } } else if local, ok := find_build_local(locals, expr.qualifier); ok { base := add_hir_expr(checker, hir.Expr{ kind=.Local, span=expr.span, type=local.type, target=hir.local_ref(local.id), left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) base_type := local.type item, has_item := types.node(&checker.module.types, base_type) field_name := symbol_text(checker, expr.name) if has_item && (item.kind == .Array || item.kind == .Slice) { if field_name == "len" { last = add_hir_expr(checker, hir.Expr{ kind=.Length, span=expr.span, type=types.USIZE, left=base, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } else if field_name == "ptr" { last = add_hir_expr(checker, hir.Expr{ kind=.Slice_Ptr, span=expr.span, type=types.pointer(&checker.module.types, item.child, item.mutable, true), left=base, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } } if types.is_pointer(base_type, &checker.module.types) { base_type = types.child_type(base_type, &checker.module.types) } index, field, found := find_struct_field(checker, base_type, expr.name) if last == hir.INVALID_EXPR && found { last = add_hir_expr(checker, hir.Expr{ kind=.Field, span=expr.span, type=field.type, integer=i64(index), left=base, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } } if last == hir.INVALID_EXPR { 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 != ast.INVALID_GLOBAL { hir_global := hir.Global_Id(global) add_unique_global(global_reads, hir_global) last = add_hir_expr(checker, hir.Expr{ kind=.Global, span=expr.span, type=checker.global_types[global], target=hir.global_ref(hir_global), left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC, }) } else { id := add_name_resolution_diagnostic(checker, expr, target_pkg) last = invalid_hir_expr(checker, expr.span, id) } } _ = pop(&stack) case .Negate: stack[frame_index].stage = 5 append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION}) case .Add: stack[frame_index].stage = 1 append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION}) 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 == ast.INVALID_FUNCTION { 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] != source.INVALID_DIAGNOSTIC { 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([]hir.Expr_Id, 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 !is_runtime_type(checker, arg_expected) { arg_expected = types.INVALID } append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=ast.INVALID_FUNCTION}) } } continue } if frame.stage == 5 { operand := last operand_type := checker.module.exprs[operand].type if !types.is_signed(operand_type, checker.target) && !types.is_float(operand_type, checker.target) { id := source.add(checker.diagnostics, expr.span, "negation requires a signed integer or float") last = invalid_hir_expr(checker, expr.span, id) } else { last = add_hir_expr(checker, hir.Expr{ kind=.Negate, span=expr.span, type=operand_type, left=operand, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) if types.is_signed(frame.expected, checker.target) || types.is_float(frame.expected, checker.target) { last = coerce_expr(checker, last, frame.expected, expr.span) } } _ = pop(&stack) continue } if frame.stage == 1 { stack[frame_index].left = last stack[frame_index].stage = 2 right_expected := types.INVALID if types.is_many_pointer(checker.module.exprs[last].type, &checker.module.types) { right_expected = types.USIZE } append(&stack, Build_Expr_Frame{expr=expr.right, expected=right_expected, template=ast.INVALID_FUNCTION}) continue } if frame.stage == 2 { left := frame.left right := last if types.is_many_pointer(checker.module.exprs[left].type, &checker.module.types) && types.equal(checker.module.exprs[right].type, types.USIZE) { last = add_hir_expr(checker, hir.Expr{ kind=.Pointer_Add, span=expr.span, type=checker.module.exprs[left].type, left=left, right=right, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC, }) _ = pop(&stack) continue } result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type) if !types.is_concrete_scalar(result) { id := source.add(checker.diagnostics, expr.span, "addition requires compatible numeric operands") 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 = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC, }) } _ = 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 !is_runtime_type(checker, arg_expected) { arg_expected = types.INVALID } append(&stack, Build_Expr_Frame{expr=expr.args[next], expected=arg_expected, template=ast.INVALID_FUNCTION}) continue } } spec := find_spec(checker, frame.template, stack[frame_index].arg_types) delete(stack[frame_index].arg_types, checker.allocator) stack[frame_index].arg_types = nil if spec == INVALID_SPEC { id := source.addf( checker.diagnostics, expr.span, "could not resolve 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) _ = pop(&stack) continue } for _, index in stack[frame_index].built_args { stack[frame_index].built_args[index] = coerce_expr( checker, stack[frame_index].built_args[index], checker.specs[spec].args[index], checker.module.exprs[stack[frame_index].built_args[index]].span, ) } function_id := checker.specs[spec].hir_id assert(function_id != hir.INVALID_FUNCTION) add_unique_function(calls, function_id) 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=hir.function_ref(function_id), left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, args=stack[frame_index].built_args, diagnostic = source.INVALID_DIAGNOSTIC, }) stack[frame_index].built_args = nil } _ = pop(&stack) } } return last } make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string { spec := checker.specs[id] function := checker.ast_module.functions[spec.template] if function.pkg == 0 && function.name == checker.main_symbol { return fmt.aprintf("main", allocator = checker.allocator) } if !function.has_body && function.c_abi { return fmt.aprintf("%s", symbol_text(checker, function.name), allocator = checker.allocator) } builder := strings.builder_make(checker.allocator) defer strings.builder_destroy(&builder) strings.write_string(&builder, "bro_c__" if function.c_abi else "bro__") fmt.sbprintf(&builder, "p%d__", function.pkg) strings.write_string(&builder, symbol_text(checker, function.name)) for arg in spec.args { strings.write_string(&builder, "__") if arg >= types.DYNAMIC_START { fmt.sbprintf(&builder, "t%d", arg) } else { strings.write_string(&builder, types.name(arg)) } } return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator) } build_function :: proc(checker: ^Checker, id: Spec_Id) { spec := checker.specs[id] function := checker.ast_module.functions[spec.template] signature_diagnostic := source.INVALID_DIAGNOSTIC if !types.is_void(spec.result) && !is_runtime_type(checker, spec.result) { checker.specs[id].result = types.I64 spec.result = types.I64 signature_diagnostic = source.addf( checker.diagnostics, function.span, "could not resolve a concrete result type for '%s'", symbol_text(checker, function.name), ) } for arg in spec.args { if !is_runtime_type(checker, arg) { signature_diagnostic = source.addf( checker.diagnostics, function.span, "could not resolve a concrete parameter type for '%s'", symbol_text(checker, function.name), ) break } } assert(spec.hir_id == hir.function_id(len(checker.module.functions))) locals: [dynamic]Build_Local locals.allocator = checker.allocator hir_locals: [dynamic]hir.Local hir_locals.allocator = checker.allocator params: [dynamic]hir.Local_Id params.allocator = checker.allocator body: [dynamic]hir.Stmt_Id body.allocator = checker.allocator global_reads: [dynamic]hir.Global_Id global_reads.allocator = checker.allocator calls: [dynamic]hir.Function_Id calls.allocator = checker.allocator for param, index in function.params { local_id := hir.local_id(len(hir_locals)) param_type := types.INVALID if index < len(spec.args) { param_type = spec.args[index] } append(&hir_locals, hir.Local{name = param.name, type = param_type, parameter = true}) append(&locals, Build_Local{name = param.name, type = param_type, id = local_id}) append(¶ms, local_id) } problematic := signature_diagnostic != source.INVALID_DIAGNOSTIC || checker.template_diagnostics[spec.template] != source.INVALID_DIAGNOSTIC if !function.has_body { assert(spec.hir_id == hir.function_id(len(checker.module.functions))) append( &checker.module.functions, hir.Function { name = function.name, link_name = make_link_name(checker, id), calling_convention = .C if function.c_abi else .Brolang, implementation = .Declaration, linkage = .External if function.c_abi else .Internal, is_main = function.pkg == 0 && function.name == checker.main_symbol, params = params[:], result = spec.result, locals = hir_locals[:], body = body[:], direct_global_reads = global_reads, calls = calls[:], problematic = problematic, diagnostic = checker.template_diagnostics[spec.template], }, ) delete(locals) return } has_return := false if signature_diagnostic != source.INVALID_DIAGNOSTIC { append(&body, hir.stmt_id(len(checker.module.statements))) append( &checker.module.statements, hir.Stmt { kind = .Trap, span = function.span, expr = hir.INVALID_EXPR, local = hir.INVALID_LOCAL, diagnostic = signature_diagnostic, }, ) } 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 } } if !types.is_void(spec.result) && !has_return { id := source.addf( checker.diagnostics, function.span, "function '%s' does not return a value", symbol_text(checker, function.name), ) append(&body, hir.stmt_id(len(checker.module.statements))) append( &checker.module.statements, hir.Stmt{kind = .Trap, span = function.span, expr = hir.INVALID_EXPR, local = hir.INVALID_LOCAL, diagnostic = id}, ) problematic = true } assert(spec.hir_id == hir.function_id(len(checker.module.functions))) append( &checker.module.functions, hir.Function { name = function.name, link_name = make_link_name(checker, id), calling_convention = .C if function.c_abi || (function.pkg == 0 && function.name == checker.main_symbol) else .Brolang, implementation = .Definition, linkage = .External if function.c_abi || (function.pkg == 0 && function.name == checker.main_symbol) else .Internal, is_main = function.pkg == 0 && function.name == checker.main_symbol, params = params[:], result = spec.result, locals = hir_locals[:], body = body[:], direct_global_reads = global_reads, calls = calls[:], problematic = problematic, diagnostic = source.INVALID_DIAGNOSTIC, }, ) delete(locals) } expr_problematic :: proc(checker: ^Checker, expr_id: hir.Expr_Id) -> bool { module := &checker.module stack := checker.hir_expr_stack clear_dynamic_array(&stack) defer { clear_dynamic_array(&stack) checker.hir_expr_stack = stack } append(&stack, expr_id) for len(stack) > 0 { id := pop(&stack) if id == hir.INVALID_EXPR || int(id) >= len(module.exprs) { return true } expr := module.exprs[id] if expr.kind == .Invalid { return true } if expr.left != hir.INVALID_EXPR { append(&stack, expr.left) } if expr.right != hir.INVALID_EXPR { append(&stack, expr.right) } append(&stack, ..expr.args) } return false } build_globals :: proc(checker: ^Checker) { for global, global_index in checker.ast_module.globals { dependencies: [dynamic]hir.Global_Id dependencies.allocator = checker.allocator calls: [dynamic]hir.Function_Id calls.allocator = checker.allocator declared := resolve_inferred_array(checker, type_from_syntax(global.type), global.expr) expected := types.INVALID if is_runtime_type(checker, declared) { expected = declared } expr := build_expr(checker, global.expr, nil, &dependencies, &calls, expected, global.pkg, global.file) global_type := checker.global_types[global_index] if is_runtime_type(checker, declared) { expr = coerce_expr(checker, expr, declared, global.span) global_type = checker.module.exprs[expr].type } else if is_runtime_type(checker, checker.module.exprs[expr].type) { global_type = checker.module.exprs[expr].type } diagnostic := source.INVALID_DIAGNOSTIC if !is_runtime_type(checker, global_type) { diagnostic = source.addf( checker.diagnostics, global.span, "could not resolve a concrete type for global '%s'", symbol_text(checker, global.name), ) global_type = types.I64 expr = invalid_hir_expr(checker, global.span, diagnostic, global_type) } if global.type == types.VOID { diagnostic = source.add( checker.diagnostics, global.span, "void is only valid as a function result type", ) expr = invalid_hir_expr(checker, global.span, diagnostic) } if !global.immutable { diagnostic = source.add( checker.diagnostics, global.span, "mutable declarations are only valid inside functions", ) expr = invalid_hir_expr(checker, global.span, diagnostic) } is_static := checker.module.exprs[expr].kind == .Integer && diagnostic == source.INVALID_DIAGNOSTIC static_value: i64 if is_static { static_value = checker.module.exprs[expr].integer } _ = hir.global_id(len(checker.module.globals)) append( &checker.module.globals, hir.Global { name = global.name, type = global_type, expr = expr, static_value = static_value, is_static = is_static, dependencies = dependencies, calls = calls[:], direct_problem = expr_problematic(checker, expr), problematic = expr_problematic(checker, expr), diagnostic = diagnostic, }, ) } } propagate_problems :: proc(checker: ^Checker) { changed := true for changed { changed = false for &function in checker.module.functions { if function.problematic { continue } for call in function.calls { if call != hir.INVALID_FUNCTION && int(call) < len(checker.module.functions) && checker.module.functions[call].problematic { function.problematic = true changed = true break } } } for &global in checker.module.globals { if global.problematic { continue } for dependency in global.dependencies { if dependency != hir.INVALID_GLOBAL && int(dependency) < len(checker.module.globals) && checker.module.globals[dependency].problematic { global.problematic = true changed = true break } } if global.problematic { continue } for call in global.calls { if call != hir.INVALID_FUNCTION && int(call) < len(checker.module.functions) && checker.module.functions[call].problematic { global.problematic = true changed = true break } } } } } append_unique_global :: proc(values: ^[dynamic]hir.Global_Id, value: hir.Global_Id) -> bool { for existing in values^ { if existing == value { return false } } append(values, value) return true } propagate_global_reads :: proc(checker: ^Checker) { changed := true for changed { changed = false for &function in checker.module.functions { for call in function.calls { if call == hir.INVALID_FUNCTION || int(call) >= len(checker.module.functions) { continue } for global_id in checker.module.functions[call].direct_global_reads { if append_unique_global(&function.direct_global_reads, global_id) { changed = true } } } } } for &global in checker.module.globals { for call in global.calls { if call == hir.INVALID_FUNCTION || int(call) >= len(checker.module.functions) { continue } for dependency in checker.module.functions[call].direct_global_reads { _ = append_unique_global(&global.dependencies, dependency) } } } } Cycle_Frame :: struct { global: hir.Global_Id, next_dependency: int, } detect_global_cycles_visit :: proc(checker: ^Checker, global_id: hir.Global_Id, states: []u8) { if states[global_id] == 2 { return } stack := checker.cycle_stack clear_dynamic_array(&stack) defer { clear_dynamic_array(&stack) checker.cycle_stack = stack } 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 == hir.INVALID_GLOBAL || int(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}) } } synthesize_trap_main :: proc(checker: ^Checker) { id := source.add(checker.diagnostics, source.Span{}, "missing or unusable main function") statement_id := hir.stmt_id(len(checker.module.statements)) _ = hir.function_id(len(checker.module.functions)) append( &checker.module.statements, hir.Stmt{kind = .Trap, span = source.Span{}, expr = hir.INVALID_EXPR, local = hir.INVALID_LOCAL, diagnostic = id}, ) body := make([]hir.Stmt_Id, 1, checker.allocator) body[0] = statement_id append( &checker.module.functions, hir.Function { name = checker.main_symbol, link_name = fmt.aprintf("main", allocator = checker.allocator), calling_convention = .C, implementation = .Definition, linkage = .External, is_main = true, result = types.VOID, body = body, problematic = true, diagnostic = id, }, ) } replace_main_with_trap :: proc(checker: ^Checker, diagnostic: source.Diagnostic_Id) { for &function in checker.module.functions { if !function.is_main { continue } delete(function.params, checker.allocator) delete(function.body, checker.allocator) function.params = nil function.result = types.VOID function.calling_convention = .C function.implementation = .Definition function.linkage = .External function.problematic = true function.diagnostic = diagnostic statement_id := hir.stmt_id(len(checker.module.statements)) append( &checker.module.statements, hir.Stmt { kind = .Trap, span = source.Span{}, expr = hir.INVALID_EXPR, local = hir.INVALID_LOCAL, diagnostic = diagnostic, }, ) function.body = make([]hir.Stmt_Id, 1, checker.allocator) function.body[0] = statement_id return } synthesize_trap_main(checker) } check :: proc( ast_module: ^ast.Module, diagnostics: ^source.Diagnostics, symbols: ^symbol.Table, selected := target.DEFAULT, allocator := context.allocator, ) -> hir.Module { checker := Checker { ast_module = ast_module, diagnostics = diagnostics, symbols = symbols, module = hir.init_module(selected, allocator), main_symbol = symbol.intern(symbols, "main"), sink_symbol = symbol.intern(symbols, "_"), target = selected, allocator = allocator, } checker.specs.allocator = allocator types.destroy_store(&checker.module.types) checker.module.types = types.clone_store(&ast_module.type_store, allocator) checker.module.types.selected = selected for value in ast_module.strings { append(&checker.module.strings, strings.clone(value, allocator)) } checker.constant_stack.allocator = allocator checker.ast_expr_stack.allocator = allocator checker.hir_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) checker.template_diagnostics = make([]source.Diagnostic_Id, len(ast_module.functions), allocator) for &diagnostic in checker.template_diagnostics { diagnostic = source.INVALID_DIAGNOSTIC } defer { for spec in checker.specs { delete(spec.args, allocator) } delete(checker.specs) delete(checker.function_index, allocator) delete(checker.global_index, allocator) delete(checker.import_index, allocator) delete(checker.global_types, allocator) delete(checker.constants, allocator) delete(checker.template_diagnostics, allocator) delete(checker.constant_stack) delete(checker.ast_expr_stack) delete(checker.hir_expr_stack) delete(checker.infer_stack) delete(checker.build_stack) delete(checker.cycle_stack) } for function, index in ast_module.functions { for previous in ast_module.functions[:index] { if previous.pkg == function.pkg && previous.name == function.name { source.addf(diagnostics, function.span, "duplicate function '%s'", symbol_text(&checker, function.name)) } } for global in ast_module.globals { if global.pkg == function.pkg && global.name == function.name { source.addf(diagnostics, function.span, "package declaration '%s' conflicts with a global", symbol_text(&checker, function.name)) } } } for global, index in ast_module.globals { for previous in ast_module.globals[:index] { if previous.pkg == global.pkg && previous.name == global.name { source.addf(diagnostics, global.span, "duplicate global '%s'", symbol_text(&checker, global.name)) } } } validate_type_nodes(&checker) validate_declarations(&checker) infer_all(&checker) prune_specs(&checker) build_globals(&checker) for index := 0; index < len(checker.specs); index += 1 { build_function(&checker, spec_id(index)) } propagate_global_reads(&checker) main_template := find_template(&checker, checker.main_symbol, 0) main_declarations := 0 for function in ast_module.functions { if function.pkg == 0 && function.name == checker.main_symbol { main_declarations += 1 } } if main_declarations == 0 { synthesize_trap_main(&checker) } else { template := ast_module.functions[main_template] if main_declarations != 1 || !template.has_body || len(template.params) != 0 || !(template.result == types.VOID || template.result == types.I32 || template.result == types.INT) { id := checker.template_diagnostics[main_template] if id == source.INVALID_DIAGNOSTIC { id = source.add( diagnostics, template.span, "main must be unique, have a body, take no parameters, and return void, i32, or int", ) } replace_main_with_trap(&checker, id) } } propagate_problems(&checker) states := make([]u8, len(checker.module.globals), allocator) for index in 0 ..< len(checker.module.globals) { detect_global_cycles_visit(&checker, hir.global_id(index), states) } delete(states, allocator) propagate_problems(&checker) for import_item in ast_module.imports { if import_item.valid && !import_item.used { source.addf(diagnostics, import_item.span, "unused import '%s'", symbol_text(&checker, import_item.alias)) } } return checker.module }