package checker import "../ast" import "../hir" import "../source" import "../symbol" import "../target" import "../types" 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, comptime_values: []Comptime_Value, result: types.Type, hir_id: hir.Function_Id, } Infer_Local :: struct { name: symbol.Id, type: types.Type, declared: types.Type, statement: ast.Stmt_Id, mutable: bool, // open_const/open_float mark a local whose initializer is an unannotated numeric // constant: like an open-constant global, it can adopt a backward demand from use. open_const: bool, open_float: bool, const_value: i128, demanded: bool, } Build_Local :: struct { name: symbol.Id, type: types.Type, mutable: bool, id: hir.Local_Id, } Static_Binding :: struct { name: symbol.Id, type: types.Type, value: Ct_Value_Id, } Expand_Expansion :: struct { statement: ast.Stmt_Id, index: u32, } Entry_Point_Kind :: enum u8 { Invalid, Plain, Process, } // Build_Ctx threads the per-function accumulators through build_block so that // nested control-flow blocks (if/else) can be built recursively. `locals` is a // scope stack: each block records its entry length and truncates back to it on // exit, while `hir_locals` keeps every allocated slot for the function. // A labeled value-loop currently being built. A `yield :label x` inside the loop // body assigns `x` to the loop's result `slot` (typed `slot_type`) and `break`s. // Pushed by `build_value_loop` while its body is built; innermost is last. Yield_Target :: struct { label: symbol.Id, slot: hir.Local_Id, slot_type: types.Type, // True when the loop also yields `none` (a `{T, none}` set → `?T`); set from a // pure-AST scan, used to pick the slot's element type on the first concrete yield. result_optional: bool, // `len(defers)` when this target's body began; a `yield :label` flushes defers down // to here before breaking, so an outer-loop / value-block yield runs inner defers too. defer_floor: int, } Defer_Entry :: struct { body: []hir.Stmt_Id, error_only: bool, capture: hir.Local_Id, } Build_Ctx :: struct { checker: ^Checker, pkg: ast.Package_Id, file: ast.File_Id, result: types.Type, local_types: []types.Type, locals: ^[dynamic]Build_Local, hir_locals: ^[dynamic]hir.Local, local_spans: ^[dynamic]source.Span, local_used: ^[dynamic]bool, local_warnable: ^[dynamic]bool, global_reads: ^[dynamic]hir.Global_Id, calls: ^[dynamic]hir.Function_Id, problematic: ^bool, // Stack of labeled value-loops being built (innermost last); see Yield_Target. yield_targets: ^[dynamic]Yield_Target, // `defer` lowering. Deferred statements are built once at the `defer` site and // their hir stmt ids stored here as a flat stack across scopes (one entry per // deferred statement); they are replayed (appended) at each scope exit in LIFO // order. `loop_defer_starts` records `len(defers)` at each enclosing loop body // entry: `break`/`continue` flush down to that mark (and need `len > loop_floor` // to be valid). `defer_depth`/`loop_floor` guard control flow inside a deferred // statement: `return` is rejected while `defer_depth > 0`, and `break`/`continue` // only see loops opened within the defer (those past `loop_floor`). defers: ^[dynamic]Defer_Entry, loop_defer_starts: ^[dynamic]int, // Parallel to `loop_defer_starts`: the label of each enclosing break target (INVALID // when unlabeled), so a `break :L` / `continue :L` can target an outer one. A labeled // block statement is a break target too; `loop_is_loop` distinguishes loops (which // `continue` and unlabeled `break`/`continue` target) from value/labeled blocks. loop_labels: ^[dynamic]symbol.Id, loop_is_loop: ^[dynamic]bool, defer_depth: int, loop_floor: int, } Function_Index_Entry :: struct { scope: ast.Package_Id, file: ast.File_Id, hidden: bool, name: symbol.Id, id: ast.Function_Id, } Global_Index_Entry :: struct { scope: ast.Package_Id, file: ast.File_Id, hidden: bool, name: symbol.Id, id: ast.Global_Id, } Import_Index_Entry :: struct { scope: ast.File_Id, name: symbol.Id, id: ast.Import_Id, } Type_Factory_Entry :: struct { template: ast.Function_Id, values: []Comptime_Value, result: types.Type, resolving: bool, } Generated_Type_Entry :: struct { expr: ast.Expr_Id, values: []Comptime_Value, result: types.Type, pkg: ast.Package_Id, file: ast.File_Id, defaults: []Ct_Value_Id, } Resolved_Field_Default :: struct { expr: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id, static_value: Ct_Value_Id, span: source.Span, } Type_Factory_Origin :: struct { result: types.Type, template: ast.Function_Id, values: []Comptime_Value, } Call_Resolution :: struct { expr: ast.Expr_Id, ctx: []Comptime_Value, expand_ctx: []Expand_Expansion, mapping: []int, comptime_values: []Comptime_Value, runtime_types: []types.Type, } 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, // Backward type-demand state for open-constant globals (milestone 14). global_demands // accumulates demands reachable from any use (other globals' initializers and function // bodies); global_demands_dirty lets a demand pushed from a function body re-trigger the // inference fixpoint. global_demands: []types.Type, global_open_const: []bool, global_open_float: []bool, global_const_value: []i128, global_demands_dirty: bool, // Program-wide inference for direct constraint fields in named native records. // The arrays use Store.fields' existing dense indices; resolved concrete types // live directly in module.types.fields so layout and lowering need no side table. record_field_constraints: []types.Type, record_field_defaults: []types.Type, record_field_conflicts: []types.Type, record_field_conflict_spans: []source.Span, record_field_demands_dirty: bool, poisoned_packages: []bool, external_global_canonical: []ast.Global_Id, external_global_diagnostics: []source.Diagnostic_Id, 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, // Anonymous globals synthesized for `&` (Zig's `&.{...}`). Staged // here during global/function building and flushed into module.globals AFTER // build_globals, so the 1:1 module.globals <-> ast.globals index identity holds. anon_globals: [dynamic]hir.Global, main_symbol: symbol.Id, entry_point: Entry_Point_Kind, io_provider_template: ast.Function_Id, sink_symbol: symbol.Id, type_symbol: symbol.Id, current_result: types.Type, inferred_test_error: ^types.Type, current_build_ctx: ^Build_Ctx, current_comptime_values: []Comptime_Value, static_state: Ct_State, static_bindings: [dynamic]Static_Binding, comptime_keys: [dynamic]string, comptime_static_values: [dynamic]Ct_Value_Id, expand_context: [dynamic]Expand_Expansion, type_factories: [dynamic]Type_Factory_Entry, generated_types: [dynamic]Generated_Type_Entry, type_factory_origins: [dynamic]Type_Factory_Origin, call_resolutions: [dynamic]Call_Resolution, target: target.Target, allocator: mem.Allocator, } symbol_text :: proc(checker: ^Checker, id: symbol.Id) -> string { return symbol.resolve(checker.symbols, id) } append_tracked_local :: proc( locals: ^[dynamic]hir.Local, spans: ^[dynamic]source.Span, used: ^[dynamic]bool, warnable: ^[dynamic]bool, local: hir.Local, span: source.Span, ) -> hir.Local_Id { id := hir.local_id(len(locals^)) append(locals, local) append(spans, span) append(used, false) append(warnable, true) return id } append_build_local :: proc( ctx: ^Build_Ctx, name: symbol.Id, type: types.Type, mutable: bool, span: source.Span, ) -> hir.Local_Id { id := append_tracked_local( ctx.hir_locals, ctx.local_spans, ctx.local_used, ctx.local_warnable, hir.Local{name=name, type=type, mutable=mutable}, span, ) append(ctx.locals, Build_Local{name=name, type=type, mutable=mutable, id=id}) return id } ignore_tracked_locals :: proc(ctx: ^Build_Ctx, start: int) { for i := start; i < len(ctx.local_warnable^); i += 1 { ctx.local_warnable^[i] = false } } mark_local_used :: proc(checker: ^Checker, id: hir.Local_Id) { ctx := checker.current_build_ctx if ctx == nil || id == hir.INVALID_LOCAL { return } index := int(id) if index >= 0 && index < len(ctx.local_used^) { ctx.local_used^[index] = true } } build_local_expr :: proc(checker: ^Checker, local: Build_Local, span: source.Span) -> hir.Expr_Id { mark_local_used(checker, local.id) return add_hir_expr(checker, hir.Expr{ kind=.Local, span=span, type=local.type, target=hir.local_ref(local.id), left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } block_reads_name :: proc(checker: ^Checker, statements: []ast.Stmt_Id, name: symbol.Id) -> bool { // Imported C expressions can be deeply nested, so walk the source graph iteratively. statement_stack: [dynamic]ast.Stmt_Id statement_stack.allocator = checker.allocator defer delete(statement_stack) expr_stack: [dynamic]ast.Expr_Id expr_stack.allocator = checker.allocator defer delete(expr_stack) append(&statement_stack, ..statements) for len(statement_stack) > 0 || len(expr_stack) > 0 { if len(statement_stack) > 0 { statement_id := pop(&statement_stack) if statement_id == ast.INVALID_STMT || int(statement_id) >= len(checker.ast_module.statements) { continue } statement := checker.ast_module.statements[statement_id] #partial switch statement.kind { case .Declaration, .Assignment, .Return, .Expression, .Yield: append(&expr_stack, statement.expr, statement.target) append(&statement_stack, ..statement.body) case .If: append(&expr_stack, statement.expr, statement.guard) append(&statement_stack, ..statement.body) append(&statement_stack, ..statement.else_body) case .While: append(&expr_stack, statement.expr) append(&statement_stack, ..statement.body) if statement.update != ast.INVALID_STMT { append(&statement_stack, statement.update) } case .For: append(&expr_stack, statement.expr) append(&statement_stack, ..statement.body) case .Block: append(&statement_stack, ..statement.body) case .Defer: if statement.update != ast.INVALID_STMT { append(&statement_stack, statement.update) } case .Match, .Match_Arm: append(&expr_stack, statement.expr) append(&expr_stack, ..statement.patterns) append(&statement_stack, ..statement.body) case .Break, .Continue, .Invalid: } continue } expr_id := pop(&expr_stack) if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { continue } expr := checker.ast_module.exprs[expr_id] if expr.kind == .Name || expr.kind == .Call { if !symbol.is_valid(expr.qualifier) && expr.name == name || expr.qualifier == name { return true } } switch expr.kind { case .Call, .Array, .Struct_Literal, .Slice: append(&expr_stack, ..expr.args) append(&expr_stack, expr.left) case .Negate, .Not, .Bit_Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast: append(&expr_stack, expr.left) case .Comptime: append(&expr_stack, expr.left) append(&statement_stack, ..expr.body) case .Catch: append(&expr_stack, expr.left, expr.right) append(&statement_stack, ..expr.body) case .Add, .Sub, .Mul, .Div, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left, .Shift_Right, .Shift_Left_Saturating, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range: append(&expr_stack, expr.left, expr.right) case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Inference_Hole, .Type, .Name, .Function_Literal, .Anonymous_Struct_Type: } } return false } record_unused_locals :: proc( checker: ^Checker, locals: []hir.Local, spans: []source.Span, used: []bool, warnable: []bool, ) { for local, index in locals { if local.name == checker.sink_symbol || !symbol.is_valid(local.name) || index >= len(warnable) || !warnable[index] || index >= len(used) || used[index] { continue } span := source.Span{} if index < len(spans) { span = spans[index] } if local.parameter { source.addf_warning(checker.diagnostics, span, "unused parameter '%s'", symbol_text(checker, local.name)) } else { source.addf_warning(checker.diagnostics, span, "unused local '%s'", symbol_text(checker, local.name)) } } } write_type_label :: proc(checker: ^Checker, builder: ^strings.Builder, value: types.Type) { store := &checker.module.types item, ok := types.node(store, value) if !ok { strings.write_string(builder, types.name(value)) return } if item.name != 0 { strings.write_string(builder, symbol_text(checker, symbol.Id(item.name))) return } switch item.kind { case .Array: strings.write_byte(builder, '[') if item.inferred_count { strings.write_byte(builder, '_') } else { fmt.sbprintf(builder, "%d", item.count) } if item.has_sentinel { fmt.sbprintf(builder, ";%d", item.sentinel) } strings.write_byte(builder, ']') if item.mutable { strings.write_string(builder, "mut ") } write_type_label(checker, builder, item.child) case .Pointer: if item.has_sentinel { fmt.sbprintf(builder, "[*;%d]", item.sentinel) } else { strings.write_byte(builder, '*' if item.many else '@') } if item.mutable { strings.write_string(builder, "mut ") } write_type_label(checker, builder, item.child) case .Slice: if item.has_sentinel { fmt.sbprintf(builder, "[;%d]", item.sentinel) } else { strings.write_string(builder, "[]") } if item.mutable { strings.write_string(builder, "mut ") } write_type_label(checker, builder, item.child) case .Range: strings.write_string(builder, "range(") write_type_label(checker, builder, item.child) strings.write_byte(builder, ')') case .Optional: strings.write_byte(builder, '?') write_type_label(checker, builder, item.child) case .Function: strings.write_string(builder, "c_func(" if item.c_abi else "func(") for param, index in types.params_for(store, value) { if index > 0 { strings.write_string(builder, ", ") } write_type_label(checker, builder, param.type) } if item.variadic { if item.field_count > 0 { strings.write_string(builder, ", ") } strings.write_string(builder, "...") } strings.write_string(builder, ") ") write_type_label(checker, builder, item.child) case .Fallible: write_type_label(checker, builder, item.child) strings.write_string(builder, " ! ") write_type_label(checker, builder, item.extra) case .Sum: write_type_label(checker, builder, item.child) strings.write_string(builder, " | ") write_type_label(checker, builder, item.extra) case .Type_Call: strings.write_string(builder, "") case .Struct: strings.write_string(builder, "struct") case .Union: strings.write_string(builder, "union") case .Enum: strings.write_string(builder, "enum") case .Alias, .Distinct, .Named: write_type_label(checker, builder, item.child) case .Invalid, .Void, .Anyopaque, .Int_Constraint, .Uint_Constraint, .Float_Constraint, .Range_Constraint, .Scalar: strings.write_string(builder, types.name(value)) } } // Render dynamic types using source syntax so diagnostics never expose internal // type-store ids such as ``. type_label :: proc(checker: ^Checker, value: types.Type) -> string { builder := strings.builder_make(context.temp_allocator) write_type_label(checker, &builder, value) return strings.to_string(builder) } is_ptrcast_call :: proc(checker: ^Checker, expr: ast.Expr) -> bool { return expr.intrinsic && expr.left == ast.INVALID_EXPR && !symbol.is_valid(expr.qualifier) && symbol_text(checker, expr.name) == "ptrcast" } is_intrinsic_call :: proc(checker: ^Checker, expr: ast.Expr, name: string) -> bool { return expr.intrinsic && expr.kind == .Call && expr.left == ast.INVALID_EXPR && !symbol.is_valid(expr.qualifier) && symbol_text(checker, expr.name) == name } static_field_value :: proc(checker: ^Checker, base, name: symbol.Id) -> (Ct_Value, bool) { binding, ok := current_static_binding(checker, base) if !ok || binding.value == INVALID_CT_VALUE || int(binding.value) >= len(checker.static_state.values) { return {}, false } return persistent_field_value(checker, binding.value, name) } persistent_field_value :: proc(checker: ^Checker, root: Ct_Value_Id, name: symbol.Id) -> (Ct_Value, bool) { if root == INVALID_CT_VALUE || int(root) >= len(checker.static_state.values) { return {}, false } value := checker.static_state.values[root] index, _, found := find_struct_field(checker, value.type, name) children := ct_child_slice(&checker.static_state, value) if !found || index < 0 || index >= len(children) || children[index] == INVALID_CT_VALUE || int(children[index]) >= len(checker.static_state.values) { return {}, false } return checker.static_state.values[children[index]], true } build_static_value :: proc(checker: ^Checker, value: Ct_Value, span: source.Span, expected: types.Type) -> hir.Expr_Id { if value.kind == .Void { return add_hir_expr(checker, hir.Expr{ kind=.Void, span=span, type=types.VOID, target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } if value.kind == .Integer { if types.is_enum(value.type, &checker.module.types) { return add_hir_expr(checker, hir.Expr{ kind=.Integer, span=span, type=value.type, integer=i64(value.integer), target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } expr := ast.Expr{ kind=.Integer, span=span, integer=u64(value.integer), left=ast.INVALID_EXPR, right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, } return build_constant_expr(checker, expr, Constant{kind=.Value, value=value.integer}, value.type) } if value.kind == .Float { bits := transmute(i64)value.float if types.bits(value.type, checker.target) == 32 { bits = i64(transmute(u32)f32(value.float)) } return add_hir_expr(checker, hir.Expr{ kind=.Float, span=span, type=value.type, integer=bits, target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } if value.kind == .Bool { return add_hir_expr(checker, hir.Expr{ kind=.Bool, span=span, type=types.BOOL, integer=i64(value.integer), target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } if value.kind == .String && value.index < u64(len(checker.ast_module.strings)) { return add_hir_expr(checker, hir.Expr{ kind=.String, span=span, type=string_literal_type(checker, value.index), integer=i64(value.index), target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } if value.kind == .Function { value_expected := expected if types.is_valid(expected) else value.type return build_function_value(checker, ast.Function_Id(u32(value.index)), span, value_expected) } if value.kind == .Array || value.kind == .Struct || value.kind == .Range { children := ct_child_slice(&checker.static_state, value) args := make([]hir.Expr_Id, len(children), checker.allocator) for child, index in children { if child == INVALID_CT_VALUE || int(child) >= len(checker.static_state.values) { delete(args, checker.allocator) id := source.add(checker.diagnostics, span, "invalid persistent compile-time aggregate") return invalid_hir_expr(checker, span, id, expected) } args[index] = build_static_value(checker, checker.static_state.values[child], span, types.INVALID) } kind := hir.Expr_Kind.Array if value.kind == .Struct { kind = .Struct } else if value.kind == .Range { kind = .Range } return add_hir_expr(checker, hir.Expr{ kind=kind, span=span, type=value.type, integer=value.active, args=args, target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } if value.kind == .None { return add_hir_expr(checker, hir.Expr{ kind=.None, span=span, type=value.type, target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } if value.kind == .Optional_Some { children := ct_child_slice(&checker.static_state, value) if len(children) == 1 && children[0] != INVALID_CT_VALUE && int(children[0]) < len(checker.static_state.values) { child_type := types.child_type(value.type, &checker.module.types) child := build_static_value(checker, checker.static_state.values[children[0]], span, child_type) return add_hir_expr(checker, hir.Expr{ kind=.Optional_Some, span=span, type=value.type, left=child, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } } id := source.add(checker.diagnostics, span, "compile-time-only value cannot be used at runtime") return invalid_hir_expr(checker, span, id, expected) } push_static_integer_binding :: proc(checker: ^Checker, name: symbol.Id, value_type: types.Type, value: i128) -> int { start := len(checker.static_bindings) if symbol.is_valid(name) && name != checker.sink_symbol { id := ct_add_value(&checker.static_state, Ct_Value{kind=.Integer, type=value_type, integer=value}) append(&checker.static_bindings, Static_Binding{name=name, type=value_type, value=id}) } return start } push_static_void_binding :: proc(checker: ^Checker, name: symbol.Id) -> int { start := len(checker.static_bindings) if symbol.is_valid(name) && name != checker.sink_symbol { id := ct_add_value(&checker.static_state, Ct_Value{kind=.Void, type=types.VOID}) append(&checker.static_bindings, Static_Binding{name=name, type=types.VOID, value=id}) } return start } pop_static_bindings :: proc(checker: ^Checker, start: int) { resize(&checker.static_bindings, start) } comptime_string_argument :: proc( checker: ^Checker, id: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id, ) -> (string, bool) { if id == ast.INVALID_EXPR || int(id) >= len(checker.ast_module.exprs) { return "", false } expr := checker.ast_module.exprs[id] if expr.kind == .String && expr.integer < u64(len(checker.ast_module.strings)) { return checker.ast_module.strings[expr.integer], true } if expr.kind == .Name && !symbol.is_valid(expr.qualifier) { value, ok := current_comptime_value(checker, expr.name) if ok && value.kind == .String { return value.text, true } } if expr.kind == .Name && symbol.is_valid(expr.qualifier) && symbol_text(checker, expr.name) == "name" { if value, ok := static_field_value(checker, expr.qualifier, expr.name); ok && value.kind == .String && value.index < u64(len(checker.ast_module.strings)) { return checker.ast_module.strings[value.index], true } } state := ct_state_make(checker, pkg, file, diagnose=false) defer ct_state_destroy(&state) value, flow, ok := ct_eval_expr(&state, id, types.INVALID, 0) if ok && flow.kind == .Normal { return ct_value_bytes(&state, value) } return "", false } comptime_slice_bytes :: proc( checker: ^Checker, expr: ast.Expr, pkg: ast.Package_Id, file: ast.File_Id, ) -> (string, bool) { state := ct_state_make(checker, pkg, file, diagnose=false) defer ct_state_destroy(&state) value, flow, ok := ct_eval_slice_expr(&state, expr, 0) if !ok || flow.kind != .Normal { return "", false } return ct_value_bytes(&state, value) } canonical_decimal_index :: proc(text: string) -> (u64, bool) { if len(text) == 0 || len(text) > 1 && text[0] == '0' { return 0, false } value: u64 for byte in text { if byte < '0' || byte > '9' { return 0, false } digit := u64(byte-'0') if value > (0xffff_ffff_ffff_ffff-digit)/10 { return 0, false } value = value*10+digit } return value, true } field_intrinsic_expr :: proc( checker: ^Checker, expr: ast.Expr, pkg: ast.Package_Id, file: ast.File_Id, ) -> (ast.Expr, bool) { if !is_intrinsic_call(checker, expr, "field") || len(expr.args) != 2 { return {}, false } name, ok := comptime_string_argument(checker, expr.args[1], pkg, file) if !ok { return {}, false } result := ast.Expr{ kind=.Field, span=expr.span, left=expr.args[0], right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, } if index, numeric := canonical_decimal_index(name); numeric { result.integer = index result.name = symbol.INVALID } else { result.name = symbol.intern(checker.symbols, name) } return result, true } std_named_type :: proc(checker: ^Checker, path, name: string) -> types.Type { name_id := symbol.intern(checker.symbols, name) result := types.INVALID for &import_item in checker.ast_module.imports { if import_item.valid && import_item.path == path { import_item.used = true if !types.is_valid(result) { result = types.find_named(&checker.module.types, u32(import_item.target), u32(name_id)) } } } return result } Type_Builtin :: enum u8 { None, Size_Of, Align_Of, Min_Value, Max_Value, } Division_Builtin :: enum u8 { None, Trunc, Floor, Exact, Ceil, Rem, Mod, } division_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Division_Builtin { if !expr.intrinsic || expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) { return .None } switch symbol_text(checker, expr.name) { case "divtrunc": return .Trunc case "divfloor": return .Floor case "divexact": return .Exact case "divceil": return .Ceil case "rem": return .Rem case "mod": return .Mod } return .None } type_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Type_Builtin { if !expr.intrinsic || expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) { return .None } name := symbol_text(checker, expr.name) if name == "sizeof" { return .Size_Of } if name == "alignof" { return .Align_Of } if name == "minval" { return .Min_Value } if name == "maxval" { return .Max_Value } return .None } valid_ptrcast_child :: proc(checker: ^Checker, value: types.Type) -> bool { return types.is_valid(value) && !types.is_void(value) && !types.is_anyopaque(value) && !types.is_function(value, &checker.module.types) && (types.is_runtime_value(value, &checker.module.types) || types.is_opaque_struct(value, &checker.module.types)) } valid_layout_type :: proc(checker: ^Checker, value: types.Type) -> bool { return types.is_runtime_value(value, &checker.module.types) } type_builtin_value :: proc(checker: ^Checker, kind: Type_Builtin, value: types.Type) -> i128 { #partial switch kind { case .Size_Of: return i128(types.size(value, &checker.module.types, checker.target)) case .Align_Of: return i128(types.alignment_of(value, &checker.module.types, checker.target)) case .Min_Value: if types.is_unsigned(value, checker.target) { return 0 } return -(i128(1) << u32(types.bits(value, checker.target)-1)) case .Max_Value: bit_count := types.bits(value, checker.target) sign_bit_count := 1 if types.is_signed(value, checker.target) else 0 return (i128(1) << u32(bit_count-sign_bit_count))-1 case: return 0 } } build_type_builtin :: proc( checker: ^Checker, expr: ast.Expr, kind: Type_Builtin, pkg: ast.Package_Id, file: ast.File_Id, ) -> hir.Expr_Id { if len(expr.args) != 1 { id := source.addf(checker.diagnostics, expr.span, "%s! expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args)) return invalid_hir_expr(checker, expr.span, id, types.USIZE) } target, target_ok := resolve_type_argument(checker, expr.args[0], pkg, file) if !target_ok { label := "layout" if kind == .Size_Of || kind == .Align_Of else "integer bound" id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "%s target must be a type", label) return invalid_hir_expr(checker, expr.span, id, types.USIZE) } if (kind == .Size_Of || kind == .Align_Of) && !valid_layout_type(checker, target) { id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "layout target must be a sized runtime value type, got %s", type_label(checker, target)) return invalid_hir_expr(checker, expr.span, id, types.USIZE) } if (kind == .Min_Value || kind == .Max_Value) && !types.is_concrete_integer(target) { id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "integer bound target must be a concrete integer type, got %s", type_label(checker, target)) return invalid_hir_expr(checker, expr.span, id, types.USIZE) } result_type := types.USIZE if kind == .Size_Of || kind == .Align_Of else target return build_constant_expr( checker, expr, Constant{kind=.Value, value=type_builtin_value(checker, kind, target)}, result_type, ) } tag_result_type :: proc(checker: ^Checker, value: types.Type) -> (types.Type, bool) { if !types.is_tagged_union(value, &checker.module.types) { return types.INVALID, false } return types.union_tag_enum(value, &checker.module.types), true } enum_member_name_from_value :: proc(checker: ^Checker, enum_type: types.Type, value: i128) -> (string, bool) { if !types.is_enum(enum_type, &checker.module.types) { return "", false } for member in types.enum_members_for(&checker.module.types, enum_type) { if member.value == value { return symbol_text(checker, symbol.Id(member.name)), true } } return "", false } build_tag_intrinsic :: proc( checker: ^Checker, expr: ast.Expr, locals: []Build_Local, global_reads: ^[dynamic]hir.Global_Id, calls: ^[dynamic]hir.Function_Id, pkg: ast.Package_Id, file: ast.File_Id, ) -> hir.Expr_Id { if len(expr.args) != 1 { id := source.addf(checker.diagnostics, expr.span, "tag! expects 1 argument, got %d", len(expr.args)) return invalid_hir_expr(checker, expr.span, id) } state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values, diagnose=false) value_id, flow, comptime_ok := ct_eval_expr(&state, expr.args[0], types.INVALID, 0) if comptime_ok && flow.kind == .Normal && value_id != INVALID_CT_VALUE && int(value_id) < len(state.values) { value := state.values[value_id] if tag_type, tagged := tag_result_type(checker, value.type); tagged && value.kind == .Struct && value.active >= 0 { fields := types.fields_for(&checker.module.types, value.type) if int(value.active) < len(fields) { if member, found := find_enum_member(checker, tag_type, symbol.Id(fields[value.active].name)); found { ct_state_destroy(&state) return add_hir_expr(checker, hir.Expr{ kind=.Integer, span=expr.span, type=tag_type, integer=i64(member.value), target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } } } } ct_state_destroy(&state) value := build_nested_expr(checker, expr.args[0], locals, global_reads, calls, types.INVALID, pkg, file) if checker.module.exprs[value].kind == .Invalid { return value } tag_type, ok := tag_result_type(checker, checker.module.exprs[value].type) if !ok { id := source.add(checker.diagnostics, expr.span, "tag! requires a tagged-union value") return invalid_hir_expr(checker, expr.span, id) } return add_hir_expr(checker, hir.Expr{ kind=.Union_Tag, span=expr.span, type=tag_type, left=value, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } build_tagname_intrinsic :: proc( checker: ^Checker, expr: ast.Expr, pkg: ast.Package_Id, file: ast.File_Id, ) -> hir.Expr_Id { if len(expr.args) != 1 { id := source.addf(checker.diagnostics, expr.span, "tagname! expects 1 argument, got %d", len(expr.args)) return invalid_hir_expr(checker, expr.span, id) } state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values, diagnose=false) defer ct_state_destroy(&state) value_id, flow, ok := ct_eval_expr(&state, expr.args[0], types.INVALID, 0) if !ok || flow.kind != .Normal || value_id == INVALID_CT_VALUE || int(value_id) >= len(state.values) { id := source.add(checker.diagnostics, expr.span, "tagname! requires a comptime-known enum value") return invalid_hir_expr(checker, expr.span, id) } value := state.values[value_id] name, name_ok := enum_member_name_from_value(checker, value.type, value.integer) if value.kind != .Integer || !name_ok { id := source.add(checker.diagnostics, expr.span, "tagname! requires a comptime-known enum value") return invalid_hir_expr(checker, expr.span, id) } string_id := intern_comptime_string(checker, name) return add_hir_expr(checker, hir.Expr{ kind=.String, span=expr.span, type=string_literal_type(checker, string_id), integer=i64(string_id), target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } is_type_metatype_syntax :: proc(checker: ^Checker, value: ast.Type_Syntax) -> bool { item, ok := types.node(&checker.module.types, value) return ok && item.name == u32(checker.type_symbol) && item.qualifier == 0 } is_comptime_type_param :: proc(checker: ^Checker, param: ast.Param) -> bool { return param.comptime_value && is_type_metatype_syntax(checker, param.type) } function_has_comptime_params :: proc(function: ast.Function) -> bool { for param in function.params { if param.comptime_value { return true } } return false } runtime_param_count :: proc(function: ast.Function) -> int { count := 0 for param in function.params { if !param.comptime_value { count += 1 } } return count } comptime_param_count :: proc(function: ast.Function) -> int { count := 0 for param in function.params { if param.comptime_value { count += 1 } } return count } 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) } fits_u64 :: proc(value: i128) -> bool { return value >= 0 && value <= i128(0xffff_ffff_ffff_ffff) } constraint_integer_literal_type :: proc(constraint: types.Type, value: i128) -> types.Type { if constraint == types.UINT { return types.smallest_unsigned_for_literal(u64(value)) if fits_u64(value) else types.INVALID } return types.smallest_signed_for_literal(i64(value)) if fits_i64(value) else types.INVALID } constraint_recovery_type :: proc(checker: ^Checker, constraint: types.Type) -> types.Type { switch constraint { case types.UINT: return types.U64 case types.FLOAT: return types.F64 case types.RANGE: return types.range(&checker.module.types, types.I64) case: return types.I64 } } type_from_syntax :: proc( checker: ^Checker, value: ast.Type_Syntax, pkg := ast.Package_Id(0), file := ast.File_Id(0), depth := 0, active_state: ^Ct_State = nil, ) -> types.Type { if depth > 64 { return types.INVALID } item, ok := types.node(&checker.module.types, value) if !ok { return value } if item.qualifier == 0 && item.name != 0 { if actual, ok := current_comptime_type(checker, symbol.Id(item.name)); ok { return actual } } store := &checker.module.types changed := false #partial switch item.kind { case .Alias: return type_from_syntax(checker, item.child, pkg, file, depth+1, active_state) case .Array: child := type_from_syntax(checker, item.child, pkg, file, depth+1, active_state) changed = changed || child != item.child item.child = child if item.unresolved_count { expr_id := ast.Expr_Id(item.count_expr) span := source.Span{} if expr_id != ast.INVALID_EXPR && int(expr_id) < len(checker.ast_module.exprs) { span = checker.ast_module.exprs[expr_id].span } constant := eval_integer_constant_in_state(active_state, expr_id) if active_state != nil else eval_integer_constant_in_context(checker, expr_id, pkg, file) if constant.kind == .Value { switch { case constant.value < 0: source.add(checker.diagnostics, span, "array count must be non-negative") return types.INVALID case constant.value > i128(0xffff_ffff_ffff_ffff): source.add(checker.diagnostics, span, "array count does not fit in u64") return types.INVALID case: item.count = u64(constant.value) item.unresolved_count = false item.count_expr = 0 changed = true } } else { if constant.kind == .Integer_Division { source.add(checker.diagnostics, span, "integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!") return types.INVALID } source.add(checker.diagnostics, span, "array count must be a compile-time integer expression") return types.INVALID } } return types.intern(store, item) case .Pointer, .Slice, .Optional, .Range, .Fallible: child := type_from_syntax(checker, item.child, pkg, file, depth+1, active_state) extra := type_from_syntax(checker, item.extra, pkg, file, depth+1, active_state) item.child = child item.extra = extra return types.intern(store, item) case .Distinct, .Enum: child := type_from_syntax(checker, item.child, pkg, file, depth+1, active_state) extra := type_from_syntax(checker, item.extra, pkg, file, depth+1, active_state) changed = child != item.child || extra != item.extra item.child = child item.extra = extra case .Function: params := types.params_for(store, value) resolved_params := make([]types.Type, len(params), checker.allocator) defer delete(resolved_params, checker.allocator) for param, index in params { resolved_params[index] = type_from_syntax(checker, param.type, pkg, file, depth+1, active_state) } result := type_from_syntax(checker, item.child, pkg, file, depth+1, active_state) return types.function(store, resolved_params, result, item.c_abi, item.variadic) case .Sum: left := type_from_syntax(checker, item.child, pkg, file, depth+1, active_state) right := type_from_syntax(checker, item.extra, pkg, file, depth+1, active_state) composed, compose_error := types.compose_sum(store, left, right) if compose_error == .Unsupported { source.add(checker.diagnostics, source.Span{}, "only native unbacked enums and tagged unions can be composed with '|'") return types.INVALID } if compose_error == .Conflict { source.add(checker.diagnostics, source.Span{}, "sum composition contains the same variant name with different payload types") return types.INVALID } return composed case .Type_Call: expr_id := ast.Expr_Id(item.count_expr) call_file := file if expr_id != ast.INVALID_EXPR && int(expr_id) < len(checker.ast_module.exprs) { call_file = ast.File_Id(checker.ast_module.exprs[expr_id].span.file) } return resolve_type_factory_call(checker, expr_id, pkg, call_file) } if changed { return types.intern(store, item) } return value } function_channel_type :: proc(checker: ^Checker, function: ast.Function) -> types.Type { result := type_from_syntax(checker, function.result, function.pkg, function.file) if types.is_valid(function.error) { return types.fallible(&checker.module.types, result, type_from_syntax(checker, function.error, function.pkg, function.file)) } return result } is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool { return types.is_runtime_value(value, &checker.module.types) } type_contains_unresolved_named :: proc(checker: ^Checker, value: types.Type, depth := 0) -> bool { if depth > 64 { return true } item, ok := types.node(&checker.module.types, value) if !ok { return false } if item.kind == .Named && !item.declared { return true } if type_contains_unresolved_named(checker, item.child, depth+1) || type_contains_unresolved_named(checker, item.extra, depth+1) { return true } if item.kind == .Function { for param in types.params_for(&checker.module.types, value) { if type_contains_unresolved_named(checker, param.type, depth+1) { return true } } } return false } is_comptime_value_type :: proc(checker: ^Checker, value: types.Type, depth := 0) -> bool { if depth > 256 || !types.is_valid(value) { return false } if is_type_metatype_syntax(checker, value) { return true } if is_runtime_type(checker, value) { return true } item, ok := types.node(&checker.module.types, value) if !ok { return false } if item.kind == .Function { return true } if item.kind == .Array || item.kind == .Optional || item.kind == .Alias || item.kind == .Distinct { return is_comptime_value_type(checker, item.child, depth+1) } if item.kind == .Struct || item.kind == .Union { if !item.declared || item.opaque || item.c_layout || (item.kind == .Union && !types.is_tagged_union(value, &checker.module.types)) { return false } for field in types.fields_for(&checker.module.types, value) { if !types.is_void(field.type) && !is_comptime_value_type(checker, field.type, depth+1) { return false } } return true } return false } is_undefined_expr :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> bool { if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { return false } return checker.ast_module.exprs[expr_id].kind == .Undefined } is_float_constant_expr :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> bool { if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { return false } expr := checker.ast_module.exprs[expr_id] if expr.kind == .Float { return true } return expr.kind == .Negate && is_float_constant_expr(checker, expr.left) } is_numeric_arithmetic_expr :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> bool { if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { return false } #partial switch checker.ast_module.exprs[expr_id].kind { case .Add, .Sub, .Mul, .Div, .Negate: return true } return false } is_numeric_constant_expr :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> bool { if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { return false } return eval_constant(checker, expr_id).kind == .Value || is_float_constant_expr(checker, expr_id) } is_typed_integer_fold_candidate :: proc(checker: ^Checker, expr_id: ast.Expr_Id, depth := 0) -> bool { if depth > 64 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { return false } expr := checker.ast_module.exprs[expr_id] #partial switch expr.kind { case .Integer: return true case .Negate, .Bit_Not, .Cast: return is_typed_integer_fold_candidate(checker, expr.left, depth+1) case .Add, .Sub, .Mul, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left, .Shift_Right, .Shift_Left_Saturating: return is_typed_integer_fold_candidate(checker, expr.left, depth+1) && is_typed_integer_fold_candidate(checker, expr.right, depth+1) } return false } is_numeric_demand :: proc(value: types.Type, selected := target.DEFAULT) -> bool { return types.is_concrete_scalar(value) && !types.is_bool(value) || types.is_float(value, selected) } string_literal_type :: proc(checker: ^Checker, string_id: u64) -> types.Type { length: u64 if string_id < u64(len(checker.ast_module.strings)) { length = u64(len(checker.ast_module.strings[string_id])) } array := types.array(&checker.module.types, types.U8, length, false, true, 0) return types.pointer(&checker.module.types, array, false, false) } container_pointer_type :: proc(store: ^types.Store, item: types.Node) -> types.Type { return types.pointer(store, item.child, item.mutable, true, item.has_sentinel, item.sentinel) } 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))) } has_inferred_array_count :: proc(checker: ^Checker, value: types.Type) -> bool { item, ok := types.node(&checker.module.types, value) return ok && item.kind == .Array && item.inferred_count } resolve_inferred_array_from_type :: proc(checker: ^Checker, value, inferred: types.Type) -> types.Type { item, ok := types.node(&checker.module.types, value) actual, actual_ok := types.node(&checker.module.types, inferred) if !ok || !actual_ok || item.kind != .Array || actual.kind != .Array || !item.inferred_count { return value } if item.child != actual.child || item.mutable != actual.mutable || item.has_sentinel != actual.has_sentinel || (item.has_sentinel && item.sentinel != actual.sentinel) { return value } return types.with_array_count(&checker.module.types, value, actual.count) } 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, file := ast.INVALID_FILE) -> 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 } } visible := ast.INVALID_FUNCTION for low < len(index) && index[low].scope == scope && index[low].name == name { entry := index[low] if entry.hidden { if entry.file == file { return entry.id } } else { visible = entry.id } low += 1 } return visible } find_global_symbol :: proc(index: []Global_Index_Entry, scope: ast.Package_Id, name: symbol.Id, file := ast.INVALID_FILE) -> 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 } } visible := ast.INVALID_GLOBAL for low < len(index) && index[low].scope == scope && index[low].name == name { entry := index[low] if entry.hidden { if entry.file == file { return entry.id } } else { visible = entry.id } low += 1 } return visible } 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) { function_count := 0 for function in checker.ast_module.functions { if !function.generated { function_count += 1 } } for alias in checker.ast_module.aliases { if alias.valid && alias.kind == .Function { function_count += 1 } } checker.function_index = make([]Function_Index_Entry, function_count, checker.allocator) function_index := 0 for function, id in checker.ast_module.functions { if function.generated { continue } checker.function_index[function_index] = Function_Index_Entry{scope=function.pkg, file=function.file, hidden=function.file_hidden, name=function.name, id=ast.function_id(id)} function_index += 1 } for alias in checker.ast_module.aliases { if alias.valid && alias.kind == .Function { checker.function_index[function_index] = Function_Index_Entry{scope=alias.pkg, file=alias.file, hidden=alias.file_hidden, name=alias.name, id=ast.Function_Id(alias.target)} function_index += 1 } } slice.sort_by(checker.function_index, function_index_less) global_count := len(checker.ast_module.globals) for alias in checker.ast_module.aliases { if alias.valid && alias.kind == .Global { global_count += 1 } } checker.global_index = make([]Global_Index_Entry, global_count, checker.allocator) for global, id in checker.ast_module.globals { checker.global_index[id] = Global_Index_Entry{scope=global.pkg, file=global.file, hidden=global.file_hidden, name=global.name, id=ast.global_id(id)} } global_index := len(checker.ast_module.globals) for alias in checker.ast_module.aliases { if alias.valid && alias.kind == .Global { checker.global_index[global_index] = Global_Index_Entry{scope=alias.pkg, file=alias.file, hidden=alias.file_hidden, name=alias.name, id=ast.Global_Id(alias.target)} global_index += 1 } } slice.sort_by(checker.global_index, global_index_less) import_count := 0 for import_item in checker.ast_module.imports { if !import_item.test_only { import_count += 1 } } checker.import_index = make([]Import_Index_Entry, import_count, checker.allocator) import_index := 0 for import_item, id in checker.ast_module.imports { if import_item.test_only { continue } checker.import_index[import_index] = Import_Index_Entry{scope=import_item.file, name=import_item.alias, id=ast.import_id(id)} import_index += 1 } slice.sort_by(checker.import_index, import_index_less) } find_template :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0), file := ast.INVALID_FILE) -> ast.Function_Id { return find_function_symbol(checker.function_index, pkg, name, file) } configure_entry_point :: proc(checker: ^Checker) { main_template := find_template(checker, checker.main_symbol, 0) if main_template == ast.INVALID_FUNCTION { return } main := checker.ast_module.functions[main_template] if len(main.params) == 0 { checker.entry_point = .Plain return } if len(main.params) != 1 || main.params[0].comptime_value { return } parameter_type := types.resolve_alias( type_from_syntax(checker, main.params[0].type, main.pkg, main.file), &checker.module.types, ) init_name := symbol.intern(checker.symbols, "Init") process_package := ast.INVALID_PACKAGE for import_item in checker.ast_module.imports { if import_item.valid && import_item.path == "@std/process" { candidate := types.find_named(&checker.module.types, u32(import_item.target), u32(init_name)) if types.equal(parameter_type, candidate) { process_package = import_item.target break } } } if process_package == ast.INVALID_PACKAGE { return } io_name := symbol.intern(checker.symbols, "Io") io_package := ast.INVALID_PACKAGE io_type := types.INVALID for import_item in checker.ast_module.imports { if import_item.valid && import_item.path == "@std/io" { candidate := types.find_named(&checker.module.types, u32(import_item.target), u32(io_name)) io_package = import_item.target io_type = candidate break } } if io_package == ast.INVALID_PACKAGE { return } init_item, init_ok := types.node(&checker.module.types, parameter_type) init_fields := types.fields_for(&checker.module.types, parameter_type) io_field_name := symbol.intern(checker.symbols, "io") if !init_ok || init_item.kind != .Struct || init_item.tuple || init_item.c_layout || len(init_fields) != 1 || init_fields[0].name != u32(io_field_name) || !types.equal(types.resolve_alias(init_fields[0].type, &checker.module.types), io_type) { checker.template_diagnostics[main_template] = source.add( checker.diagnostics, main.span, "@std/process Init must be an auto-layout record containing exactly 'io io.Io'", ) return } provider_name := symbol.intern(checker.symbols, "system") provider := ast.INVALID_FUNCTION provider_count := 0 for function, function_id in checker.ast_module.functions { if function.pkg != io_package || function.name != provider_name { continue } provider_count += 1 result := types.resolve_alias( type_from_syntax(checker, function.result, function.pkg, function.file), &checker.module.types, ) if function.file_hidden && function.has_body && !function.c_abi && len(function.params) == 0 && !types.is_valid(function.error) && types.equal(result, io_type) { provider = ast.function_id(function_id) } } if provider_count != 1 || provider == ast.INVALID_FUNCTION { checker.template_diagnostics[main_template] = source.add( checker.diagnostics, main.span, "@std/io does not provide the required 'hide system func() Io' startup implementation", ) return } checker.entry_point = .Process checker.io_provider_template = provider } find_global :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0), file := ast.INVALID_FILE) -> ast.Global_Id { return find_global_symbol(checker.global_index, pkg, name, file) } 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 } declared_type_named :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id, file := ast.INVALID_FILE) -> bool { id := types.find_named(&checker.module.types, u32(pkg), u32(name), file=u32(file)) item, ok := types.node(&checker.module.types, id) return ok && item.declared } declarations_conflict :: proc(left_file: ast.File_Id, left_hidden: bool, right_file: ast.File_Id, right_hidden: bool) -> bool { return left_file == right_file if left_hidden && right_hidden else true } type_declaration_conflicts :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id, file: ast.File_Id, hidden: bool) -> bool { for item in checker.module.types.nodes { if item.declared && item.pkg == u32(pkg) && item.name == u32(name) && declarations_conflict(file, hidden, ast.File_Id(item.file), item.file_hidden) { return true } } return false } visible_name_kind :: proc( checker: ^Checker, name: symbol.Id, pkg: ast.Package_Id, file: ast.File_Id, locals: []Build_Local = nil, labels: []symbol.Id = nil, yield_targets: []Yield_Target = nil, ) -> string { if !symbol.is_valid(name) || name == checker.sink_symbol { return "" } if _, ok := current_comptime_value(checker, name); ok { return "comptime parameter" } if _, ok := find_build_local(locals, name); ok { return "local" } for label in labels { if label == name { return "label" } } for target in yield_targets { if target.label == name { return "label" } } if find_import(checker, file, name) != ast.INVALID_IMPORT { return "import" } if find_global(checker, name, pkg, file) != ast.INVALID_GLOBAL { return "global" } if find_template(checker, name, pkg, file) != ast.INVALID_FUNCTION { return "function" } if declared_type_named(checker, pkg, name, file) { return "type" } return "" } add_shadow_diagnostic :: proc( checker: ^Checker, span: source.Span, name: symbol.Id, decl_kind: string, pkg: ast.Package_Id, file: ast.File_Id, locals: []Build_Local = nil, labels: []symbol.Id = nil, yield_targets: []Yield_Target = nil, ) -> source.Diagnostic_Id { kind := visible_name_kind(checker, name, pkg, file, locals, labels, yield_targets) if len(kind) == 0 { return source.INVALID_DIAGNOSTIC } return source.addf( checker.diagnostics, span, "%s '%s' shadows visible %s", decl_kind, symbol_text(checker, name), kind, ) } add_label_shadow_diagnostic :: proc(ctx: ^Build_Ctx, span: source.Span, label: symbol.Id) -> source.Diagnostic_Id { if !symbol.is_valid(label) { return source.INVALID_DIAGNOSTIC } yield_targets := ctx.yield_targets^[:] if len(yield_targets) > 0 && yield_targets[len(yield_targets) - 1].label == label { label_is_active_loop := false for loop_label in ctx.loop_labels^[:] { if loop_label == label { label_is_active_loop = true break } } if !label_is_active_loop { yield_targets = yield_targets[:len(yield_targets) - 1] } } return add_shadow_diagnostic( ctx.checker, span, label, "label", ctx.pkg, ctx.file, ctx.locals^[:], ctx.loop_labels^[:], yield_targets, ) } 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 } expr_lookup_file :: proc(expr: ast.Expr, file: ast.File_Id) -> ast.File_Id { return ast.INVALID_FILE if symbol.is_valid(expr.qualifier) else file } expr_symbol_span :: proc(checker: ^Checker, expr: ast.Expr, name: symbol.Id) -> source.Span { span := expr.span span.end = span.start+source.Offset(len(symbol_text(checker, name))) return span } 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 { span := expr_symbol_span(checker, expr, expr.qualifier) id := source.addf(checker.diagnostics, span, "unknown symbol '%s'", symbol_text(checker, expr.qualifier)) source.set_primary_label(checker.diagnostics, id, "unknown symbol") return id } 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, file: ast.File_Id) -> source.Diagnostic_Id { if find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file)) != ast.INVALID_FUNCTION { template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file)) id := source.addf(checker.diagnostics, expr_symbol_span(checker, expr, expr.name), "'%s' is a function, not a global value", symbol_text(checker, expr.name)) if !checker.ast_module.functions[template].imported { source.add_secondary_label(checker.diagnostics, id, checker.ast_module.functions[template].span, "function declared here") } return id } 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), ) } span := expr_symbol_span(checker, expr, expr.name) id := source.addf(checker.diagnostics, span, "unknown symbol '%s'", symbol_text(checker, expr.name)) source.set_primary_label(checker.diagnostics, id, "unknown symbol") return id } add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id, file: ast.File_Id) -> source.Diagnostic_Id { if find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file)) != ast.INVALID_GLOBAL { global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file)) id := source.addf(checker.diagnostics, expr_symbol_span(checker, expr, expr.name), "'%s' is a global, not a function", symbol_text(checker, expr.name)) if !checker.ast_module.globals[global].external { source.add_secondary_label(checker.diagnostics, id, checker.ast_module.globals[global].span, "global declared here") } return id } 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), ) } span := expr_symbol_span(checker, expr, expr.name) id := source.addf(checker.diagnostics, span, "unknown symbol '%s'", symbol_text(checker, expr.name)) source.set_primary_label(checker.diagnostics, id, "unknown symbol") return id } find_unsupported :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id) -> (ast.Unsupported, bool) { for item in checker.ast_module.unsupported { if item.pkg == pkg && item.name == name { return item, true } } return {}, false } add_unsupported_diagnostic :: proc(checker: ^Checker, span: source.Span, pkg: ast.Package_Id, name: symbol.Id) -> source.Diagnostic_Id { if item, ok := find_unsupported(checker, pkg, name); ok { return source.addf( checker.diagnostics, span, "C declaration '%s' is unavailable: %s", symbol_text(checker, name), item.reason, ) } return source.INVALID_DIAGNOSTIC } add_unsupported_type_diagnostic :: proc( checker: ^Checker, span: source.Span, value: types.Type, depth := 0, ) -> source.Diagnostic_Id { if depth > 64 { return source.INVALID_DIAGNOSTIC } item, ok := types.node(&checker.module.types, value) if !ok { return source.INVALID_DIAGNOSTIC } if item.kind == .Alias { return add_unsupported_diagnostic(checker, span, ast.Package_Id(item.pkg), symbol.Id(item.name)) } if types.is_valid(item.child) { return add_unsupported_type_diagnostic(checker, span, item.child, depth+1) } if types.is_valid(item.extra) { return add_unsupported_type_diagnostic(checker, span, item.extra, depth+1) } return source.INVALID_DIAGNOSTIC } function_signatures_equal :: proc(left, right: ast.Function) -> bool { if left.result != right.result || left.error != right.error || left.variadic != right.variadic || len(left.params) != len(right.params) { return false } for param, index in left.params { if param.type != right.params[index].type || param.comptime_value != right.params[index].comptime_value { return false } } return true } valid_call_arity :: proc(function: ast.Function, count: int) -> bool { return count >= len(function.params) if function.variadic else count == len(function.params) } Call_Argument_Mode :: enum u8 { Invalid, Explicit, Inferred, } call_param_index :: proc(mapping: []int, source_index: int) -> int { if source_index < 0 || source_index >= len(mapping) { return -1 } return mapping[source_index] } next_runtime_call_arg :: proc(function: ast.Function, mapping: []int, start, source_count: int) -> int { index := start for index < source_count { param_index := call_param_index(mapping, index) if param_index >= len(function.params) || !function.params[param_index].comptime_value { break } index += 1 } return index } call_mapping_mode :: proc(function: ast.Function, mapping: []int) -> Call_Argument_Mode { if len(mapping) != len(function.params) { return .Inferred } for value, index in mapping { if value != index { return .Inferred } } return .Explicit } comptime_binding_index :: proc(function: ast.Function, prefix: int, name: symbol.Id) -> (int, bool) { ordinal := 0 for param in function.params { if !param.comptime_value { continue } if ordinal >= prefix { return -1, false } if param.name == name { return ordinal, true } ordinal += 1 } return -1, false } comptime_param_for_name :: proc(function: ast.Function, name: symbol.Id) -> (ast.Param, bool) { for param in function.params { if param.comptime_value && param.name == name { return param, true } } return {}, false } Call_Mapping_Search :: struct { checker: ^Checker, function: ^ast.Function, args: []ast.Expr_Id, current: []int, candidates: ^[dynamic][]int, } is_comptime_string_param :: proc(checker: ^Checker, param: ast.Param, function: ast.Function) -> bool { declared := types.resolve_alias(type_from_syntax(checker, param.type, function.pkg, function.file), &checker.module.types) item, ok := types.container(declared, &checker.module.types) return ok && item.kind == .Slice && !item.mutable && item.child == types.U8 } explicit_comptime_argument_valid :: proc( checker: ^Checker, function: ast.Function, param: ast.Param, arg: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id, ) -> bool { if arg == ast.INVALID_EXPR || int(arg) >= len(checker.ast_module.exprs) { return false } expr := checker.ast_module.exprs[arg] if expr.kind == .Inference_Hole { return true } if is_comptime_type_param(checker, param) { // Avoid asking the ordinary type resolver to diagnose while candidates are // being probed. A call can be a type argument only when its declaration is // a type factory. if expr.kind == .Call { target_pkg, available := expr_package(checker, expr, pkg, file, false) if !available { return false } template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file)) if template == ast.INVALID_FUNCTION || !is_type_metatype_syntax(checker, checker.ast_module.functions[template].result) { return false } } _, ok := resolve_type_argument(checker, arg, pkg, file) return ok } if is_comptime_string_param(checker, param, function) { _, ok := comptime_string_argument(checker, arg, pkg, file) return ok } if types.is_concrete_integer(type_from_syntax(checker, param.type, function.pkg, function.file)) { return eval_integer_constant_in_context(checker, arg, pkg, file).kind == .Value } if type_pattern_mentions_comptime(checker, function, comptime_param_count(function), param.type) { return true } declared := type_from_syntax(checker, param.type, function.pkg, function.file) _, ok := eval_static_comptime_value(checker, param.name, arg, declared, pkg, file) return ok } search_call_mappings :: proc(search: ^Call_Mapping_Search, param_index, source_index: int) { if len(search.candidates^) >= COMPTIME_EVAL_QUOTA { return } function := search.function^ if param_index == len(function.params) { if source_index != len(search.args) { return } candidate := make([]int, len(search.current), search.checker.allocator) copy(candidate, search.current) append(search.candidates, candidate) return } param := function.params[param_index] if param.comptime_value { if source_index < len(search.args) { search.current[source_index] = param_index search_call_mappings(search, param_index+1, source_index+1) search.current[source_index] = -1 } search_call_mappings(search, param_index+1, source_index) return } if source_index >= len(search.args) { return } search.current[source_index] = param_index search_call_mappings(search, param_index+1, source_index+1) search.current[source_index] = -1 } call_mapping_semantically_valid :: proc( checker: ^Checker, function: ast.Function, mapping: []int, args: []ast.Expr_Id, expected: types.Type, locals: []Infer_Local, pkg: ast.Package_Id, file: ast.File_Id, ) -> (bool, string) { actual_args := make([]types.Type, len(function.params), checker.allocator) defer delete(actual_args, checker.allocator) for source_index in 0..= len(function.params) { continue } if function.params[param_index].comptime_value { if !explicit_comptime_argument_valid( checker, function, function.params[param_index], args[source_index], pkg, file, ) { return false, fmt.aprintf( "argument %d is not a valid comptime value for parameter '%s'", source_index+1, symbol_text(checker, function.params[param_index].name), allocator=checker.allocator, ) } continue } actual_args[param_index] = infer_expr(checker, args[source_index], locals, pkg, file) } inference_failure := "" values, ok := infer_call_comptime_values( checker, function, comptime_param_count(function), mapping, args, actual_args, expected, pkg, file, diagnose=false, failure=&inference_failure, ) defer delete(values, checker.allocator) if !ok { if len(inference_failure) > 0 { return false, inference_failure } return false, fmt.aprintf("could not infer or evaluate every comptime parameter", allocator=checker.allocator) } delete(inference_failure, checker.allocator) previous := checker.current_comptime_values checker.current_comptime_values = values defer checker.current_comptime_values = previous for param, index in function.params { if param.comptime_value { continue } actual := actual_args[index] declared := type_from_syntax(checker, param.type, function.pkg, function.file) source_index := -1 for mapped_param, candidate_source in mapping { if mapped_param == index { source_index = candidate_source break } } if !is_runtime_type(checker, actual) && !can_implicitly_convert_type(checker, actual, declared) { return false, fmt.aprintf( "argument %d is not a runtime value", source_index+1, allocator=checker.allocator, ) } if types.is_constraint(declared) { if !types.is_valid(types.constraint_target(declared, actual, &checker.module.types)) { return false, fmt.aprintf( "argument %d of type %s does not satisfy %s", source_index+1, type_label(checker, actual), type_label(checker, declared), allocator=checker.allocator, ) } } else if !can_implicitly_convert_type(checker, actual, declared) { if source_index < 0 || !is_numeric_constant_expr(checker, args[source_index]) || !expr_accepts_numeric_demand(checker, args[source_index], declared, locals, pkg, file) { return false, fmt.aprintf( "argument %d of type %s cannot convert to %s", source_index+1, type_label(checker, actual), type_label(checker, declared), allocator=checker.allocator, ) } } } if is_runtime_type(checker, expected) { result := function_channel_type(checker, function) if !is_runtime_type(checker, result) || !can_implicitly_convert_type(checker, result, expected) { return false, fmt.aprintf( "result type %s cannot convert to expected type %s", type_label(checker, result), type_label(checker, expected), allocator=checker.allocator, ) } } return true, "" } call_argument_mapping :: proc( checker: ^Checker, function: ^ast.Function, args: []ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id, expected := types.INVALID, locals: []Infer_Local = nil, ) -> (mapping: []int, mode: Call_Argument_Mode, comptime_count: int, failure: string) { if function.variadic || function.c_abi { if !valid_call_arity(function^, len(args)) { return nil, .Invalid, 0, "" } mapping = make([]int, len(args), checker.allocator) for &value, index in mapping { value = index } return mapping, .Explicit, 0, "" } current := make([]int, len(args), checker.allocator) defer delete(current, checker.allocator) for &value in current { value = -1 } candidates: [dynamic][]int candidates.allocator = checker.allocator defer { for candidate in candidates { delete(candidate, checker.allocator) } delete(candidates) } search := Call_Mapping_Search{ checker=checker, function=function, args=args, current=current, candidates=&candidates, } search_call_mappings(&search, 0, 0) selected_index := -1 failures: [dynamic]string failures.allocator = checker.allocator defer { for item in failures { delete(item, checker.allocator) } delete(failures) } if len(candidates) == 1 { selected_index = 0 } if selected_index < 0 && len(candidates) > 1 { for candidate, index in candidates { valid, reason := call_mapping_semantically_valid( checker, function^, candidate, args, expected, locals, pkg, file, ) if valid { if selected_index >= 0 { return nil, .Invalid, comptime_param_count(function^), fmt.aprintf( "multiple complete argument mappings satisfy the call", allocator=checker.allocator, ) } selected_index = index } else { append(&failures, reason) } } } if selected_index < 0 { if len(failures) > 0 { builder := strings.builder_make(checker.allocator) defer strings.builder_destroy(&builder) for reason, index in failures { if index > 0 { strings.write_string(&builder, "; ") } fmt.sbprintf(&builder, "candidate %d: %s", index+1, reason) } return nil, .Invalid, comptime_param_count(function^), fmt.aprintf( "%s", strings.to_string(builder), allocator=checker.allocator, ) } return nil, .Invalid, comptime_param_count(function^), "" } selected := make([]int, len(args), checker.allocator) copy(selected, candidates[selected_index]) identity := len(args) == len(function.params) if identity { for value, index in selected { if value != index { identity = false break } } } return selected, .Explicit if identity else .Inferred, comptime_param_count(function^), "" } bind_inferred_comptime :: proc( checker: ^Checker, function: ast.Function, prefix: int, values: []Comptime_Value, bound: []bool, name: symbol.Id, candidate: Comptime_Value, span: source.Span, diagnose: bool, ) -> bool { index, ok := comptime_binding_index(function, prefix, name) if !ok { return false } value := candidate value.name = name if !bound[index] { values[index] = value bound[index] = true return true } existing := values[index] matches := existing.kind == value.kind if matches { if existing.kind == .Type { matches = types.equal(types.resolve_alias(existing.type, &checker.module.types), types.resolve_alias(value.type, &checker.module.types)) } else if existing.kind == .Static { matches = existing.fingerprint == value.fingerprint && existing.key == value.key && types.equal(existing.type, value.type) } else { matches = existing.value == value.value && types.equal(existing.type, value.type) } } if !matches && diagnose { left := type_label(checker, existing.type) if existing.kind == .Type else fmt.aprintf("", allocator=checker.allocator) if existing.kind == .Static else fmt.aprintf("%d", existing.value, allocator=checker.allocator) right := type_label(checker, value.type) if value.kind == .Type else fmt.aprintf("", allocator=checker.allocator) if value.kind == .Static else fmt.aprintf("%d", value.value, allocator=checker.allocator) source.addf(checker.diagnostics, span, "conflicting inference for comptime parameter '%s': %s and %s", symbol_text(checker, name), left, right) if existing.kind != .Type { delete(left, checker.allocator) } if value.kind != .Type { delete(right, checker.allocator) } } return matches } type_pattern_mentions_comptime :: proc( checker: ^Checker, function: ast.Function, prefix: int, pattern: types.Type, depth := 0, type_params_only := false, ) -> bool { if depth > 64 { return false } item, ok := types.node(&checker.module.types, pattern) if !ok { return false } if item.qualifier == 0 && item.name != 0 { if _, found := comptime_binding_index(function, prefix, symbol.Id(item.name)); found { if !type_params_only { return true } param, param_ok := comptime_param_for_name(function, symbol.Id(item.name)) if param_ok && is_comptime_type_param(checker, param) { return true } } } if item.kind == .Array && item.unresolved_count { expr_id := ast.Expr_Id(item.count_expr) if expr_id != ast.INVALID_EXPR && int(expr_id) < len(checker.ast_module.exprs) { expr := checker.ast_module.exprs[expr_id] if expr.kind == .Name && !symbol.is_valid(expr.qualifier) { if _, found := comptime_binding_index(function, prefix, expr.name); found { if !type_params_only { return true } param, param_ok := comptime_param_for_name(function, expr.name) if param_ok && is_comptime_type_param(checker, param) { return true } } } } } if item.kind == .Type_Call { expr_id := ast.Expr_Id(item.count_expr) if expr_id != ast.INVALID_EXPR && int(expr_id) < len(checker.ast_module.exprs) { for arg_id in checker.ast_module.exprs[expr_id].args { if arg_id == ast.INVALID_EXPR || int(arg_id) >= len(checker.ast_module.exprs) { continue } arg := checker.ast_module.exprs[arg_id] if arg.kind == .Name && !symbol.is_valid(arg.qualifier) { if _, found := comptime_binding_index(function, prefix, arg.name); found { if !type_params_only { return true } param, param_ok := comptime_param_for_name(function, arg.name) if param_ok && is_comptime_type_param(checker, param) { return true } } } } } } if types.is_valid(item.child) && type_pattern_mentions_comptime(checker, function, prefix, item.child, depth+1, type_params_only) { return true } if types.is_valid(item.extra) && type_pattern_mentions_comptime(checker, function, prefix, item.extra, depth+1, type_params_only) { return true } if item.kind == .Function { for field in types.params_for(&checker.module.types, pattern) { if type_pattern_mentions_comptime(checker, function, prefix, field.type, depth+1, type_params_only) { return true } } } return false } match_inferred_type_pattern :: proc( checker: ^Checker, function: ast.Function, prefix: int, pattern, actual: types.Type, values: []Comptime_Value, bound: []bool, span: source.Span, diagnose: bool, depth := 0, ) -> bool { if depth > 64 || !types.is_valid(actual) { return false } store := &checker.module.types actual_type := types.resolve_alias(actual, store) pattern_item, pattern_ok := types.node(store, pattern) if pattern_ok && pattern_item.qualifier == 0 && pattern_item.name != 0 { name := symbol.Id(pattern_item.name) if binding_index, is_binding := comptime_binding_index(function, prefix, name); is_binding { param, param_ok := comptime_param_for_name(function, name) if !param_ok || !is_comptime_type_param(checker, param) { return false } if bound[binding_index] && can_implicitly_convert_type(checker, actual_type, values[binding_index].type) { return true } return bind_inferred_comptime( checker, function, prefix, values, bound, name, Comptime_Value{type=actual_type, kind=.Type}, span, diagnose, ) } } if !pattern_ok { return types.equal(pattern, actual_type) } if pattern_item.kind == .Type_Call { expr_id := ast.Expr_Id(pattern_item.count_expr) if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { return false } expr := checker.ast_module.exprs[expr_id] target_pkg, available := expr_package(checker, expr, function.pkg, function.file, true) if !available { return false } template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, function.file)) origin: Type_Factory_Origin found := false for candidate in checker.type_factory_origins { if candidate.template == template && types.equal(candidate.result, actual_type) { origin = candidate found = true break } } if !found || len(expr.args) != len(origin.values) { return false } matched := true for arg_id, index in expr.args { if arg_id == ast.INVALID_EXPR || int(arg_id) >= len(checker.ast_module.exprs) { matched = false continue } arg := checker.ast_module.exprs[arg_id] if arg.kind == .Name && !symbol.is_valid(arg.qualifier) { if _, is_binding := comptime_binding_index(function, prefix, arg.name); is_binding { matched = bind_inferred_comptime( checker, function, prefix, values, bound, arg.name, origin.values[index], span, diagnose, ) && matched } } } return matched } actual_item, actual_ok := types.node(store, actual_type) if pattern_item.kind == .Slice { actual_pointer, actual_array, array_pointer_ok := types.array_pointer(actual_type, store) if array_pointer_ok { mutable := actual_pointer.mutable && actual_array.mutable if pattern_item.mutable && !mutable || pattern_item.has_sentinel && (!actual_array.has_sentinel || pattern_item.sentinel != actual_array.sentinel) { return false } return match_inferred_type_pattern( checker, function, prefix, pattern_item.child, actual_array.child, values, bound, span, diagnose, depth+1, ) } } if !actual_ok || pattern_item.kind != actual_item.kind { resolved := type_from_syntax(checker, pattern, function.pkg, function.file) return types.is_valid(resolved) && types.equal(types.resolve_alias(resolved, store), actual_type) } if pattern_item.many != actual_item.many || pattern_item.has_sentinel != actual_item.has_sentinel || pattern_item.has_sentinel && pattern_item.sentinel != actual_item.sentinel { return false } matched := true if pattern_item.kind == .Array { if pattern_item.unresolved_count { count_expr := ast.Expr_Id(pattern_item.count_expr) if count_expr != ast.INVALID_EXPR && int(count_expr) < len(checker.ast_module.exprs) { expr := checker.ast_module.exprs[count_expr] if expr.kind == .Name && !symbol.is_valid(expr.qualifier) { if binding_index, is_binding := comptime_binding_index(function, prefix, expr.name); is_binding { param, param_ok := comptime_param_for_name(function, expr.name) if param_ok && !is_comptime_type_param(checker, param) { matched = bind_inferred_comptime( checker, function, prefix, values, bound, expr.name, Comptime_Value{type=values[binding_index].type, value=i128(actual_item.count), kind=.Integer}, span, diagnose, ) && matched } } } } } else if pattern_item.count != actual_item.count { matched = false } } if types.is_valid(pattern_item.child) { matched = match_inferred_type_pattern( checker, function, prefix, pattern_item.child, actual_item.child, values, bound, span, diagnose, depth+1, ) && matched } if types.is_valid(pattern_item.extra) { matched = match_inferred_type_pattern( checker, function, prefix, pattern_item.extra, actual_item.extra, values, bound, span, diagnose, depth+1, ) && matched } if pattern_item.kind == .Function { pattern_params := types.params_for(store, pattern) actual_params := types.params_for(store, actual_type) if len(pattern_params) != len(actual_params) || pattern_item.c_abi != actual_item.c_abi || pattern_item.variadic != actual_item.variadic { return false } for field, index in pattern_params { matched = match_inferred_type_pattern( checker, function, prefix, field.type, actual_params[index].type, values, bound, span, diagnose, depth+1, ) && matched } } return matched } infer_call_comptime_values :: proc( checker: ^Checker, function: ast.Function, prefix: int, mapping: []int, args: []ast.Expr_Id, actual_args: []types.Type, expected: types.Type, pkg: ast.Package_Id, file: ast.File_Id, diagnose := false, failure: ^string = nil, ) -> ([]Comptime_Value, bool) { values := make([]Comptime_Value, prefix, checker.allocator) bound := make([]bool, prefix, checker.allocator) defer delete(bound, checker.allocator) ordinal := 0 for param in function.params { if !param.comptime_value { continue } values[ordinal].name = param.name if is_comptime_type_param(checker, param) { values[ordinal].kind = .Type } else if is_comptime_string_param(checker, param, function) { values[ordinal].kind = .String values[ordinal].type = type_from_syntax(checker, param.type, function.pkg, function.file) } else if types.is_concrete_integer(type_from_syntax(checker, param.type, function.pkg, function.file)) { values[ordinal].kind = .Integer values[ordinal].type = type_from_syntax(checker, param.type, function.pkg, function.file) } else { values[ordinal].kind = .Static values[ordinal].type = type_from_syntax(checker, param.type, function.pkg, function.file) } ordinal += 1 } matched := true for arg_id, source_index in args { param_index := call_param_index(mapping, source_index) if param_index < 0 || param_index >= len(function.params) || !function.params[param_index].comptime_value || arg_id == ast.INVALID_EXPR || int(arg_id) >= len(checker.ast_module.exprs) { continue } expr := checker.ast_module.exprs[arg_id] if expr.kind == .Inference_Hole { continue } param := function.params[param_index] binding_index, binding_ok := comptime_binding_index(function, prefix, param.name) if !binding_ok { matched = false continue } if is_comptime_type_param(checker, param) { actual, ok := resolve_type_argument(checker, arg_id, pkg, file) if !ok { if failure != nil && len(failure^) == 0 { failure^ = fmt.aprintf( "argument %d for comptime type parameter '%s' is not a type", source_index+1, symbol_text(checker, param.name), allocator=checker.allocator, ) } if diagnose { source.addf(checker.diagnostics, expr.span, "argument for comptime type parameter '%s' must be a type", symbol_text(checker, param.name)) } matched = false continue } values[binding_index] = Comptime_Value{name=param.name, type=actual, kind=.Type} bound[binding_index] = true } else if is_comptime_string_param(checker, param, function) { text, text_ok := comptime_string_argument(checker, arg_id, pkg, file) if !text_ok { if failure != nil && len(failure^) == 0 { failure^ = fmt.aprintf( "argument %d for comptime string parameter '%s' does not evaluate to immutable bytes", source_index+1, symbol_text(checker, param.name), allocator=checker.allocator, ) } if diagnose { source.addf(checker.diagnostics, expr.span, "argument for comptime string parameter '%s' must evaluate to immutable bytes", symbol_text(checker, param.name)) } matched = false continue } values[binding_index] = Comptime_Value{ name=param.name, type=type_from_syntax(checker, param.type, function.pkg, function.file), text=text, kind=.String, } bound[binding_index] = true } else if types.is_concrete_integer(type_from_syntax(checker, param.type, function.pkg, function.file)) { declared := type_from_syntax(checker, param.type, function.pkg, function.file) constant := eval_integer_constant_in_context(checker, arg_id, pkg, file) if constant.kind != .Value { if failure != nil && len(failure^) == 0 { failure^ = fmt.aprintf( "argument %d for comptime parameter '%s' is not a compile-time integer expression", source_index+1, symbol_text(checker, param.name), allocator=checker.allocator, ) } if diagnose { source.addf(checker.diagnostics, expr.span, "argument for comptime parameter '%s' must be a compile-time integer expression", symbol_text(checker, param.name)) } matched = false continue } if !fits_integer_type(constant.value, declared, checker.target) { if failure != nil && len(failure^) == 0 { failure^ = fmt.aprintf( "argument %d integer constant %d does not fit in %s", source_index+1, constant.value, types.name(declared), allocator=checker.allocator, ) } if diagnose { source.addf(checker.diagnostics, expr.span, "integer constant %d does not fit in %s", constant.value, types.name(declared)) } matched = false continue } values[binding_index] = Comptime_Value{name=param.name, type=declared, value=constant.value, kind=.Integer} bound[binding_index] = true } else { available: [dynamic]Comptime_Value available.allocator = checker.allocator append(&available, ..checker.current_comptime_values) for prior, prior_index in values[:binding_index] { if bound[prior_index] { append(&available, prior) } } previous := checker.current_comptime_values checker.current_comptime_values = available[:] declared := type_from_syntax(checker, param.type, function.pkg, function.file) checker.current_comptime_values = previous value, value_ok := eval_static_comptime_value( checker, param.name, arg_id, declared, pkg, file, available[:], diagnose, ) delete(available) if !value_ok { if failure != nil && len(failure^) == 0 { failure^ = fmt.aprintf( "argument %d for comptime parameter '%s' has no stable comptime identity", source_index+1, symbol_text(checker, param.name), allocator=checker.allocator, ) } matched = false continue } values[binding_index] = value bound[binding_index] = true } } all_bound := true for value_bound in bound { all_bound = all_bound && value_bound } if !all_bound && is_runtime_type(checker, expected) { if types.is_valid(function.error) && types.kind(expected, &checker.module.types) == .Fallible { matched = match_inferred_type_pattern( checker, function, prefix, function.result, types.fallible_success(expected, &checker.module.types), values, bound, source.Span{}, diagnose, ) && matched matched = match_inferred_type_pattern( checker, function, prefix, function.error, types.fallible_error(expected, &checker.module.types), values, bound, source.Span{}, diagnose, ) && matched } else if type_pattern_mentions_comptime(checker, function, prefix, function.result) { matched = match_inferred_type_pattern( checker, function, prefix, function.result, expected, values, bound, source.Span{}, diagnose, ) && matched } } all_bound = true for value_bound in bound { all_bound = all_bound && value_bound } // Concrete arguments bind first. Numeric constants and `none` are contextual and // therefore only contribute after stronger evidence has had a chance to bind the // parameter type. weak_passes := [2]bool{false, true} for weak in weak_passes { for arg_id, source_index in args { param_index := call_param_index(mapping, source_index) if param_index >= len(function.params) || param_index >= len(actual_args) { continue } if param_index < 0 || function.params[param_index].comptime_value { continue } arg_expr := checker.ast_module.exprs[arg_id] is_none := arg_expr.kind == .None is_weak := is_numeric_constant_expr(checker, arg_id) || is_none if is_weak != weak { continue } if !type_pattern_mentions_comptime(checker, function, prefix, function.params[param_index].type) { continue } // Once the result type has fixed every comptime parameter, an anonymous // keyed record must be checked against the specialized parameter type. // Its provisional structural type intentionally contains only the supplied // fields, so comparing that type here would reject omitted defaulted fields. if all_bound && arg_expr.kind == .Struct_Literal && !arg_expr.tuple && !symbol.is_valid(arg_expr.name) { continue } if weak { if item, ok := types.node(&checker.module.types, function.params[param_index].type); ok && item.qualifier == 0 && item.name != 0 { if binding_index, is_binding := comptime_binding_index(function, prefix, symbol.Id(item.name)); is_binding && bound[binding_index] { continue } } } actual := actual_args[param_index] if is_none { previous := checker.current_comptime_values checker.current_comptime_values = values contextual := type_from_syntax( checker, function.params[param_index].type, function.pkg, function.file, ) checker.current_comptime_values = previous if types.is_optional(contextual, &checker.module.types) { actual = contextual actual_args[param_index] = contextual } } matched = match_inferred_type_pattern( checker, function, prefix, function.params[param_index].type, actual, values, bound, checker.ast_module.exprs[arg_id].span, diagnose, ) && matched } } ordinal = 0 for param in function.params { if !param.comptime_value { continue } if bound[ordinal] { ordinal += 1 continue } matched = false if failure != nil && len(failure^) == 0 { failure^ = fmt.aprintf( "cannot infer comptime parameter '%s'", symbol_text(checker, param.name), allocator=checker.allocator, ) } if diagnose { source.addf( checker.diagnostics, param.span, "cannot infer comptime parameter '%s'; pass it explicitly", symbol_text(checker, param.name), ) } ordinal += 1 } if !matched { if failure != nil && len(failure^) == 0 { failure^ = fmt.aprintf("comptime inference produced conflicting bindings", allocator=checker.allocator) } delete(values, checker.allocator) return nil, false } return values, true } call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int) -> types.Type { if index < 0 || index >= len(function.params) { return types.INVALID } if is_comptime_type_param(checker, function.params[index]) { return types.INVALID } for param in function.params { if !param.comptime_value { continue } if _, ok := current_comptime_value(checker, param.name); !ok { return types.INVALID } } declared := type_from_syntax(checker, function.params[index].type, function.pkg, function.file) // A `float` param defaults to f64 so an integer-literal argument builds as a // float constant (e.g. `f(3)` -> 3.0), mirroring `pi float = 3` for locals. // Integer/range constraints keep building naturally from their literals. if declared == types.FLOAT { return types.F64 } return declared } resolve_type_argument :: proc( checker: ^Checker, expr_id: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id, ) -> (types.Type, bool) { if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { return types.INVALID, false } expr := checker.ast_module.exprs[expr_id] #partial switch expr.kind { case .Type: resolved := type_from_syntax(checker, expr.type, pkg, file) return resolved, types.is_valid(resolved) case .Name: if symbol.is_valid(expr.qualifier) && symbol_text(checker, expr.name) == "type" { if value, ok := static_field_value(checker, expr.qualifier, expr.name); ok && value.kind == .Type { return types.Type(value.index), true } } if !symbol.is_valid(expr.qualifier) { if actual, ok := current_comptime_type(checker, expr.name); ok { return actual, true } } target_pkg, available := expr_package(checker, expr, pkg, file) if !available { return types.INVALID, false } value := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file))) value = types.resolve_alias(value, &checker.module.types) return value, types.is_valid(value) case .Call: value := resolve_type_factory_call(checker, expr_id, pkg, file) return value, types.is_valid(value) } return types.INVALID, false } clone_comptime_values :: proc(values: []Comptime_Value, allocator: mem.Allocator) -> []Comptime_Value { result := make([]Comptime_Value, len(values), allocator) copy(result, values) return result } expand_expansions_equal :: proc(left, right: []Expand_Expansion) -> bool { if len(left) != len(right) { return false } for value, index in left { if value != right[index] { return false } } return true } find_call_resolution :: proc( checker: ^Checker, expr: ast.Expr_Id, ) -> (int, bool) { for index := len(checker.call_resolutions)-1; index >= 0; index -= 1 { entry := checker.call_resolutions[index] if entry.expr == expr && comptime_values_equal(entry.ctx, checker.current_comptime_values) && expand_expansions_equal(entry.expand_ctx, checker.expand_context[:]) { return index, true } } return -1, false } store_call_resolution :: proc( checker: ^Checker, expr: ast.Expr_Id, mapping: []int, comptime_values: []Comptime_Value, runtime_types: []types.Type, ) { entry := Call_Resolution{ expr=expr, ctx=clone_comptime_values(checker.current_comptime_values, checker.allocator), expand_ctx=slice.clone(checker.expand_context[:], checker.allocator), mapping=slice.clone(mapping, checker.allocator), comptime_values=clone_comptime_values(comptime_values, checker.allocator), runtime_types=slice.clone(runtime_types, checker.allocator), } if index, ok := find_call_resolution(checker, expr); ok { previous := checker.call_resolutions[index] delete(previous.ctx, checker.allocator) delete(previous.expand_ctx, checker.allocator) delete(previous.mapping, checker.allocator) delete(previous.comptime_values, checker.allocator) delete(previous.runtime_types, checker.allocator) checker.call_resolutions[index] = entry return } append(&checker.call_resolutions, entry) } resolved_call_arg_expected :: proc( checker: ^Checker, function: ast.Function, param_index: int, resolution_index: int, ) -> types.Type { if resolution_index < 0 || resolution_index >= len(checker.call_resolutions) { return call_arg_expected(checker, function, param_index) } previous := checker.current_comptime_values checker.current_comptime_values = checker.call_resolutions[resolution_index].comptime_values result := call_arg_expected(checker, function, param_index) checker.current_comptime_values = previous return result } resolve_generated_struct_type :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id, active_state: ^Ct_State) -> types.Type { for entry in checker.generated_types { if entry.expr == expr_id && comptime_values_equal(entry.values, checker.current_comptime_values) { return entry.result } } if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { return types.INVALID } expr := checker.ast_module.exprs[expr_id] field_start := int(u32(expr.integer>>32)) field_count := int(u32(expr.integer)) if field_start < 0 || field_count < 0 || field_start+field_count > len(checker.ast_module.type_fields) { return types.INVALID } template_fields := checker.ast_module.type_fields[field_start:field_start+field_count] fields := make([]types.Field, len(template_fields), checker.allocator) defer delete(fields, checker.allocator) for field, index in template_fields { resolved := type_from_syntax(checker, field.type, pkg, file, active_state=active_state) if (!is_runtime_type(checker, resolved) && !is_comptime_value_type(checker, resolved)) || types.is_void(resolved) { if expr.tuple { source.addf(checker.diagnostics, expr.span, "tuple element %d requires a concrete runtime type, got %s", index, type_label(checker, resolved)) } else { source.addf(checker.diagnostics, expr.span, "anonymous struct field '%s' requires a concrete runtime type, got %s", symbol_text(checker, symbol.Id(field.name)), type_label(checker, resolved)) } return types.INVALID } fields[index] = types.Field{name=field.name, type=resolved} } result := types.struct_generated(&checker.module.types, fields, expr.tuple) append(&checker.generated_types, Generated_Type_Entry{ expr=expr_id, values=clone_comptime_values(checker.current_comptime_values, checker.allocator), result=result, pkg=pkg, file=file, }) return result } resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id) -> types.Type { if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { return types.INVALID } expr := checker.ast_module.exprs[expr_id] if expr.kind != .Call || expr.left != ast.INVALID_EXPR { source.add(checker.diagnostics, expr.span, "type position requires a direct type-factory call") return types.INVALID } if expr.intrinsic { if !is_intrinsic_call(checker, expr, "struct_type") { return types.INVALID } state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values) value, flow, ok := ct_eval_call_expr(&state, expr, types.INVALID, 0, expr_id) result := types.INVALID if ok && flow.kind == .Normal && value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Type { result = types.Type(state.values[value].index) } ct_state_destroy(&state) return result } target_pkg, available := expr_package(checker, expr, pkg, file, true) if !available { _ = add_package_resolution_diagnostic(checker, expr, file) return types.INVALID } template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file)) if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) { source.addf(checker.diagnostics, expr.span, "unknown type factory '%s'", symbol_text(checker, expr.name)) return types.INVALID } function := checker.ast_module.functions[template] if !is_type_metatype_syntax(checker, function.result) || types.is_valid(function.error) { source.addf(checker.diagnostics, expr.span, "function '%s' does not return a type", symbol_text(checker, expr.name)) return types.INVALID } for param in function.params { if !param.comptime_value { source.addf(checker.diagnostics, param.span, "type-factory parameter '%s' must be comptime", symbol_text(checker, param.name)) return types.INVALID } } if !valid_call_arity(function, len(expr.args)) { source.addf(checker.diagnostics, expr.span, "type factory '%s' expects %d arguments, got %d", symbol_text(checker, expr.name), len(function.params), len(expr.args)) return types.INVALID } values, ok := collect_comptime_values(checker, function, expr.args, pkg, file, true, checker.current_comptime_values) defer delete(values, checker.allocator) if !ok { return types.INVALID } // A generic function's declaration is validated before it has a specialization. // Leave calls containing its unresolved type parameters pending until then. for value in values { if value.kind != .Type { continue } if item, item_ok := types.node(&checker.module.types, value.type); item_ok && item.kind == .Named && !item.declared { return types.INVALID } } for &entry in checker.type_factories { if entry.template != template || !comptime_values_equal(entry.values, values) { continue } if entry.resolving { source.addf(checker.diagnostics, expr.span, "recursive type-factory specialization of '%s'", symbol_text(checker, expr.name)) return types.INVALID } return entry.result } entry_index := len(checker.type_factories) append(&checker.type_factories, Type_Factory_Entry{ template=template, values=clone_comptime_values(values, checker.allocator), result=types.INVALID, resolving=true, }) state := ct_state_make(checker, pkg, file) value, flow, eval_ok := ct_eval_call_expr(&state, expr, function.result, 0, expr_id) result := types.INVALID if eval_ok && flow.kind == .Normal && value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Type { result = types.Type(state.values[value].index) } else if state.diagnostic == source.INVALID_DIAGNOSTIC { source.addf(checker.diagnostics, expr.span, "type factory '%s' did not return a type", symbol_text(checker, expr.name)) } ct_state_destroy(&state) checker.type_factories[entry_index].result = result checker.type_factories[entry_index].resolving = false record_type_factory_origin(checker, result, template, values) return result } record_type_factory_origin :: proc( checker: ^Checker, result: types.Type, template: ast.Function_Id, values: []Comptime_Value, ) { if !types.is_valid(result) { return } generated := false for entry in checker.generated_types { if types.equal(entry.result, result) { generated = true break } } if !generated { return } for origin in checker.type_factory_origins { if origin.template == template && types.equal(origin.result, result) && comptime_values_equal(origin.values, values) { return } } append(&checker.type_factory_origins, Type_Factory_Origin{ result=result, template=template, values=clone_comptime_values(values, checker.allocator), }) } collect_comptime_values :: proc( checker: ^Checker, function: ast.Function, args: []ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id, diagnose := false, extra_values: []Comptime_Value = nil, ) -> ([]Comptime_Value, bool) { if !function_has_comptime_params(function) { return nil, true } values: [dynamic]Comptime_Value values.allocator = checker.allocator ok := true for param, index in function.params { if !param.comptime_value { continue } span := param.span if index < len(args) && args[index] != ast.INVALID_EXPR && int(args[index]) < len(checker.ast_module.exprs) { span = checker.ast_module.exprs[args[index]].span } if is_comptime_type_param(checker, param) { actual, actual_ok := types.INVALID, false if index < len(args) { actual, actual_ok = resolve_type_argument(checker, args[index], pkg, file) } if !actual_ok { if diagnose { source.addf( checker.diagnostics, span, "argument for comptime type parameter '%s' must be a type", symbol_text(checker, param.name), ) } ok = false continue } append(&values, Comptime_Value{name=param.name, type=actual, kind=.Type}) continue } if is_comptime_string_param(checker, param, function) { text, text_ok := "", false if index < len(args) { text, text_ok = comptime_string_argument(checker, args[index], pkg, file) } if !text_ok { if diagnose { source.addf( checker.diagnostics, span, "argument for comptime string parameter '%s' must evaluate to immutable bytes", symbol_text(checker, param.name), ) } ok = false continue } append(&values, Comptime_Value{ name=param.name, type=type_from_syntax(checker, param.type, function.pkg, function.file), text=text, kind=.String, }) continue } resolution_values: [dynamic]Comptime_Value resolution_values.allocator = checker.allocator append(&resolution_values, ..extra_values) append(&resolution_values, ..values[:]) previous := checker.current_comptime_values checker.current_comptime_values = resolution_values[:] declared := type_from_syntax(checker, param.type, function.pkg, function.file) checker.current_comptime_values = previous delete(resolution_values) if types.is_concrete_integer(declared) { constant := Constant{kind = .Not_Constant} if index < len(args) { constant = eval_integer_constant_in_context(checker, args[index], pkg, file, values=extra_values) } if constant.kind != .Value { if diagnose { source.addf( checker.diagnostics, span, "argument for comptime parameter '%s' must be a compile-time integer expression", symbol_text(checker, param.name), ) } ok = false continue } if !fits_integer_type(constant.value, declared, checker.target) { if diagnose { source.addf( checker.diagnostics, span, "integer constant %d does not fit in %s", constant.value, types.name(declared), ) } ok = false continue } append(&values, Comptime_Value{name=param.name, type=declared, value=constant.value}) continue } if index >= len(args) || args[index] == ast.INVALID_EXPR { ok = false continue } available: [dynamic]Comptime_Value available.allocator = checker.allocator append(&available, ..extra_values) append(&available, ..values[:]) value, value_ok := eval_static_comptime_value( checker, param.name, args[index], declared, pkg, file, available[:], diagnose, ) delete(available) if !value_ok { ok = false continue } append(&values, value) } if !ok { delete(values) return nil, false } return values[:], true } callable_arg_expected :: proc(function_type: types.Type, function_item: types.Node, store: ^types.Store, index: int) -> types.Type { if index < 0 || index >= int(function_item.field_count) { return types.INVALID } params := types.params_for(store, function_type) if index >= len(params) { return types.INVALID } return params[index].type } valid_callable_arity :: proc(function_item: types.Node, count: int) -> bool { return count >= int(function_item.field_count) if function_item.variadic else count == int(function_item.field_count) } function_value_signature :: proc( checker: ^Checker, template: ast.Function_Id, ) -> (params: []types.Type, result: types.Type, ok: bool) { if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) { return nil, types.INVALID, false } function := checker.ast_module.functions[template] if function_has_comptime_params(function) { return nil, types.INVALID, false } if function.c_abi && types.is_valid(function.error) { return nil, types.INVALID, false } if !function.c_abi && (!function.has_body || function.variadic) { return nil, types.INVALID, false } result = function_channel_type(checker, function) if !types.is_void(result) && !is_runtime_type(checker, result) { return nil, types.INVALID, false } params = make([]types.Type, len(function.params), checker.allocator) for param, index in function.params { param_type := type_from_syntax(checker, param.type, function.pkg, function.file) if !is_runtime_type(checker, param_type) { delete(params, checker.allocator) return nil, types.INVALID, false } params[index] = param_type } return params, result, true } function_type_for_template :: proc( checker: ^Checker, template: ast.Function_Id, demanded: ^[dynamic]Spec_Id = nil, demand_spec := true, ) -> (types.Type, Spec_Id, bool) { params, result, ok := function_value_signature(checker, template) if !ok { return types.INVALID, INVALID_SPEC, false } defer delete(params, checker.allocator) function := checker.ast_module.functions[template] function_type := types.function(&checker.module.types, params, result, function.c_abi, function.variadic) spec := INVALID_SPEC if demanded == nil { if demand_spec { spec = ensure_spec(checker, template, params) } else { spec = find_spec(checker, template, params) } } else { spec = find_spec(checker, template, params) mark_spec_demanded(checker, spec, demanded) } return function_type, spec, spec != INVALID_SPEC || !demand_spec } function_pointer_type_for_template :: proc( checker: ^Checker, template: ast.Function_Id, demanded: ^[dynamic]Spec_Id = nil, demand_spec := true, ) -> (types.Type, Spec_Id, bool) { function_type, spec, ok := function_type_for_template(checker, template, demanded, demand_spec) if !ok { return types.INVALID, spec, false } return types.pointer(&checker.module.types, function_type, false, false), spec, true } function_expr_type_for_template :: proc( checker: ^Checker, template: ast.Function_Id, expected: types.Type, demanded: ^[dynamic]Spec_Id = nil, ) -> (types.Type, bool) { function_type, _, ok := function_type_for_template(checker, template, demanded) if !ok { return types.INVALID, false } pointer_expected := expected if types.is_optional(pointer_expected, &checker.module.types) { pointer_expected = types.child_type(pointer_expected, &checker.module.types) } if types.can_coerce_function_pointer(function_type, pointer_expected, &checker.module.types) { return pointer_expected, true } return function_type, true } 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 is_intrinsic_call(checker, expr, "typeinfo") { for &import_item in checker.ast_module.imports { if import_item.valid && import_item.path == "@std/meta" { import_item.used = true } } } 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) if expr.left != ast.INVALID_EXPR { append(&stack, expr.left) } case .Array, .Struct_Literal, .Slice: append(&stack, ..expr.args) if expr.left != ast.INVALID_EXPR { append(&stack, expr.left) } case .Negate, .Not, .Bit_Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast: append(&stack, expr.left) case .Comptime: if expr.left != ast.INVALID_EXPR { append(&stack, expr.left) } mark_block_imports_used(checker, expr.body, file) case .Catch: append(&stack, expr.left) if expr.right != ast.INVALID_EXPR { append(&stack, expr.right) } mark_block_imports_used(checker, expr.body, file) case .Function_Literal: function_id := ast.Function_Id(u32(expr.integer)) if function_id != ast.INVALID_FUNCTION && int(function_id) < len(checker.ast_module.functions) { function := checker.ast_module.functions[function_id] mark_block_imports_used(checker, function.body, function.file) } case .Add, .Sub, .Mul, .Div, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left, .Shift_Right, .Shift_Left_Saturating, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range: append(&stack, expr.left, expr.right) case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Inference_Hole, .Type, .Name, .Anonymous_Struct_Type: } } } mark_block_imports_used :: proc(checker: ^Checker, statements: []ast.Stmt_Id, file: ast.File_Id) { for statement_id in statements { statement := checker.ast_module.statements[statement_id] #partial switch statement.kind { case .Declaration, .Assignment, .Return, .Expression, .Yield: mark_expr_imports_used(checker, statement.expr, file) if statement.target != ast.INVALID_EXPR { mark_expr_imports_used(checker, statement.target, file) } // A value-block declaration/assignment carries its block in `body`. mark_block_imports_used(checker, statement.body, file) case .If: mark_expr_imports_used(checker, statement.expr, file) if statement.guard != ast.INVALID_EXPR { mark_expr_imports_used(checker, statement.guard, file) } mark_block_imports_used(checker, statement.body, file) mark_block_imports_used(checker, statement.else_body, file) case .While: mark_expr_imports_used(checker, statement.expr, file) mark_block_imports_used(checker, statement.body, file) if statement.update != ast.INVALID_STMT { update := [1]ast.Stmt_Id{statement.update} mark_block_imports_used(checker, update[:], file) } case .For: mark_expr_imports_used(checker, statement.expr, file) mark_block_imports_used(checker, statement.body, file) case .Block: mark_block_imports_used(checker, statement.body, file) case .Defer: deferred := [1]ast.Stmt_Id{statement.update} mark_block_imports_used(checker, deferred[:], file) case .Match, .Match_Arm: // `Match` carries the subject in `expr` and arms in `body`; each `Match_Arm` // carries its patterns in `patterns` and the arm body in `body`. mark_expr_imports_used(checker, statement.expr, file) for pattern in statement.patterns { mark_expr_imports_used(checker, pattern, file) } mark_block_imports_used(checker, statement.body, file) case .Break, .Continue: case .Invalid: } } } validate_external_globals :: proc(checker: ^Checker) { for global, global_index in checker.ast_module.globals { checker.external_global_canonical[global_index] = ast.global_id(global_index) if !global.external { continue } switch global.link_name { case "main": checker.external_global_diagnostics[global_index] = source.add( checker.diagnostics, global.span, "external C variable 'main' conflicts with the program entry point", ) case "write": checker.external_global_diagnostics[global_index] = source.add( checker.diagnostics, global.span, "external C variable 'write' conflicts with the compiler runtime", ) } for previous, previous_index in checker.ast_module.globals[:global_index] { if !previous.external || previous.link_name != global.link_name { continue } canonical := checker.external_global_canonical[previous_index] if canonical == ast.INVALID_GLOBAL { canonical = ast.global_id(previous_index) } canonical_index := int(canonical) if canonical_index < 0 || canonical_index >= len(checker.ast_module.globals) { canonical = ast.global_id(previous_index) canonical_index = previous_index } checker.external_global_canonical[global_index] = canonical canonical_global := checker.ast_module.globals[canonical_index] canonical_type := checker.global_types[canonical_index] if !types.equal(checker.global_types[global_index], canonical_type) || global.writable != canonical_global.writable { checker.external_global_diagnostics[global_index] = source.addf( checker.diagnostics, global.span, "conflicting external C variable declarations for '%s'", global.link_name, ) } checker.global_types[global_index] = canonical_type break } for function in checker.ast_module.functions { if !function.c_abi || function.has_body || len(function.unsupported_reason) > 0 || symbol_text(checker, function.name) != global.link_name { continue } if checker.external_global_diagnostics[global_index] == source.INVALID_DIAGNOSTIC { checker.external_global_diagnostics[global_index] = source.addf( checker.diagnostics, global.span, "external C variable '%s' conflicts with a C function declaration", global.link_name, ) } break } } } runtime_write_declaration_matches :: proc(checker: ^Checker, function: ast.Function) -> bool { if function.variadic || len(function.params) != 3 || types.is_valid(function.error) { return false } store := &checker.module.types buffer := types.optional(store, types.pointer(store, types.ANYOPAQUE, false, true)) return type_from_syntax(checker, function.params[0].type, function.pkg, function.file) == types.C_INT && type_from_syntax(checker, function.params[1].type, function.pkg, function.file) == buffer && type_from_syntax(checker, function.params[2].type, function.pkg, function.file) == types.C_ULONG && type_from_syntax(checker, function.result, function.pkg, function.file) == types.C_LONG } validate_declarations :: proc(checker: ^Checker) { for function, function_id in checker.ast_module.functions { if len(function.unsupported_reason) > 0 { continue } has_comptime := function_has_comptime_params(function) signature_poisoned := function.diagnostic != source.INVALID_DIAGNOSTIC locals: [dynamic]symbol.Id locals.allocator = checker.allocator comptime_prefix := 0 for param in function.params { param_type := types.INVALID if param.comptime_value || !has_comptime { param_type = type_from_syntax(checker, param.type, function.pkg, function.file) } if param.comptime_value && !signature_poisoned { dependent := type_pattern_mentions_comptime( checker, function, comptime_prefix, param.type, type_params_only=true, ) if function.c_abi { checker.template_diagnostics[function_id] = source.add( checker.diagnostics, param.span, "comptime parameters require 'func', not 'c_func'", ) } if !is_type_metatype_syntax(checker, param.type) && !dependent && !is_comptime_value_type(checker, param_type) && param_type != types.RANGE { checker.template_diagnostics[function_id] = source.addf( checker.diagnostics, param.span, "comptime parameter '%s' requires type or a concrete value type", symbol_text(checker, param.name), ) } } else if !has_comptime && !signature_poisoned && types.is_comptime_only(param_type, &checker.module.types) { checker.template_diagnostics[function_id] = source.addf( checker.diagnostics, param.span, "parameter '%s' has a comptime-only type; prefix it with '$'", symbol_text(checker, param.name), ) } else if !has_comptime && !signature_poisoned { if diagnostic := add_unsupported_type_diagnostic(checker, param.span, param_type); diagnostic != source.INVALID_DIAGNOSTIC { checker.template_diagnostics[function_id] = diagnostic continue } } if param.comptime_value { comptime_prefix += 1 } if param.type == types.VOID { source.add( checker.diagnostics, param.span, "void is only valid as a function result type", ) } if param.name != checker.sink_symbol && contains_name(locals[:], param.name) { source.addf( checker.diagnostics, param.span, "duplicate parameter '%s'", symbol_text(checker, param.name), ) } else { _ = add_shadow_diagnostic( checker, param.span, param.name, "parameter", function.pkg, function.file, ) } append(&locals, param.name) if !has_comptime && !signature_poisoned && types.contains_c_struct_by_value(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 !has_comptime && !signature_poisoned { result_type := type_from_syntax(checker, function.result, function.pkg, function.file) if diagnostic := add_unsupported_type_diagnostic(checker, function.span, result_type); diagnostic != source.INVALID_DIAGNOSTIC { checker.template_diagnostics[function_id] = diagnostic } if types.contains_c_struct_by_value(result_type, &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 types.is_valid(function.error) && !signature_poisoned { error_type := type_from_syntax(checker, function.error, function.pkg, function.file) error_sum := types.is_enum(error_type, &checker.module.types) || types.is_tagged_union(error_type, &checker.module.types) if function.c_abi { checker.template_diagnostics[function_id] = source.add( checker.diagnostics, function.span, "fallible functions must use 'func', not 'c_func'", ) } else if !error_sum { checker.template_diagnostics[function_id] = source.add( checker.diagnostics, function.span, "fallible function error type must be a native enum or tagged union", ) } } if !function.has_body && !function.c_abi && !signature_poisoned { 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.variadic && (!function.c_abi || function.has_body) && !signature_poisoned { checker.template_diagnostics[function_id] = source.addf( checker.diagnostics, function.span, "variadic function '%s' must be a bodyless 'c_func' declaration", symbol_text(checker, function.name), ) } if !function.has_body && function.c_abi && !signature_poisoned { for param in function.params { param_type := type_from_syntax(checker, param.type, function.pkg, function.file) if add_unsupported_type_diagnostic(checker, param.span, param_type) != source.INVALID_DIAGNOSTIC { continue } if types.contains_c_struct_by_value(param_type, &checker.module.types) { continue } if !types.is_c_signature_type(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(checker, function.result, function.pkg, function.file) if add_unsupported_type_diagnostic(checker, function.span, result) == source.INVALID_DIAGNOSTIC && !types.contains_c_struct_by_value(result, &checker.module.types) && !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", ) } external_name := function.link_name if len(function.link_name) > 0 else symbol_text(checker, function.name) if external_name == "write" && !runtime_write_declaration_matches(checker, function) { checker.template_diagnostics[function_id] = source.add( checker.diagnostics, function.span, "external C function 'write' conflicts with the compiler runtime declaration", ) } } mark_block_imports_used(checker, function.body, function.file) 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 } if function.imported && other.imported && function_signatures_equal(function, other) { continue } checker.template_diagnostics[function_id] = source.addf( checker.diagnostics, function.span, "duplicate foreign symbol '%s'", symbol_text(checker, function.name), ) break } } } init_record_field_inference :: proc(checker: ^Checker) { store := &checker.module.types for item in store.nodes { if !(item.declared && (item.kind == .Struct || item.kind == .Union) && !item.c_layout && symbol.is_valid(symbol.Id(item.name))) { continue } start := int(item.field_start) end := start+int(item.field_count) if start < 0 || end > len(store.fields) { continue } for slot in start.. bool { return slot >= 0 && slot < len(checker.record_field_constraints) && types.is_constraint(checker.record_field_constraints[slot]) } record_field_owner :: proc(checker: ^Checker, slot: int) -> (symbol.Id, symbol.Id, ast.Package_Id, bool) { for item in checker.module.types.nodes { if !(item.declared && (item.kind == .Struct || item.kind == .Union) && !item.c_layout && symbol.is_valid(symbol.Id(item.name))) { continue } start := int(item.field_start) if slot >= start && slot < start+int(item.field_count) && slot >= 0 && slot < len(checker.module.types.fields) { return symbol.Id(item.name), symbol.Id(checker.module.types.fields[slot].name), ast.Package_Id(item.pkg), true } } return symbol.INVALID, symbol.INVALID, ast.INVALID_PACKAGE, false } record_field_conflict :: proc(checker: ^Checker, slot: int, actual: types.Type, span: source.Span) { if !is_inferred_record_field(checker, slot) || types.is_valid(checker.record_field_conflicts[slot]) { return } checker.record_field_conflicts[slot] = actual checker.record_field_conflict_spans[slot] = span } merge_record_field_default :: proc(checker: ^Checker, slot: int, candidate: types.Type, span: source.Span) -> bool { if !is_inferred_record_field(checker, slot) || !is_runtime_type(checker, candidate) { return false } constraint := checker.record_field_constraints[slot] if !types.constraint_accepts(constraint, candidate, &checker.module.types) { record_field_conflict(checker, slot, candidate, span) return false } current := checker.record_field_defaults[slot] if !is_runtime_type(checker, current) { checker.record_field_defaults[slot] = candidate checker.record_field_demands_dirty = true return true } if types.equal(current, candidate) { return false } merged := types.widest(current, candidate) if types.is_concrete_scalar(merged) { if !types.equal(current, merged) { checker.record_field_defaults[slot] = merged checker.record_field_demands_dirty = true return true } return false } record_field_conflict(checker, slot, candidate, span) return false } merge_record_field_demand :: proc(checker: ^Checker, slot: int, demand: types.Type, span: source.Span) -> bool { if !is_inferred_record_field(checker, slot) || !is_runtime_type(checker, demand) { return false } constraint := checker.record_field_constraints[slot] if !types.constraint_accepts(constraint, demand, &checker.module.types) { record_field_conflict(checker, slot, demand, span) return false } current := checker.module.types.fields[slot].type if types.is_constraint(current) { checker.module.types.fields[slot].type = demand checker.record_field_demands_dirty = true return true } if types.equal(current, demand) { return false } merged := types.widest(current, demand) if types.is_concrete_scalar(merged) { if !types.equal(current, merged) { checker.module.types.fields[slot].type = merged checker.record_field_demands_dirty = true return true } return false } record_field_conflict(checker, slot, demand, span) return false } record_field_expr_candidate :: proc( checker: ^Checker, slot: int, expr_id: ast.Expr_Id, inferred: types.Type, locals: []Infer_Local, pkg: ast.Package_Id, file: ast.File_Id, ) -> bool { if !is_inferred_record_field(checker, slot) || expr_id == ast.INVALID_EXPR { return false } expr := checker.ast_module.exprs[expr_id] constraint := checker.record_field_constraints[slot] if !numeric_operand_is_open(checker, expr_id, locals, pkg, file) { concrete := types.constraint_target(constraint, inferred, &checker.module.types) if is_runtime_type(checker, concrete) { return merge_record_field_demand(checker, slot, concrete, expr.span) } } if constant := eval_integer_constant_in_context(checker, expr_id, pkg, file); constant.kind == .Value { candidate := constraint_integer_literal_type(constraint, constant.value) if constraint == types.FLOAT { candidate = types.F64 if fits_i64(constant.value) else types.INVALID } if types.is_valid(candidate) { return merge_record_field_default(checker, slot, candidate, expr.span) } } if is_float_constant_expr(checker, expr_id) { return merge_record_field_default(checker, slot, types.F64, expr.span) } if numeric_operand_is_open(checker, expr_id, locals, pkg, file) { candidate := inferred if constraint == types.FLOAT && types.is_concrete_integer(candidate) { candidate = types.F64 } return merge_record_field_default(checker, slot, candidate, expr.span) } concrete := types.constraint_target(constraint, inferred, &checker.module.types) if !is_runtime_type(checker, concrete) { if is_runtime_type(checker, inferred) { record_field_conflict(checker, slot, inferred, expr.span) } return false } return merge_record_field_demand(checker, slot, concrete, expr.span) } finalize_record_field_inference :: proc(checker: ^Checker) { for constraint, slot in checker.record_field_constraints { if !types.is_constraint(constraint) { continue } record_name, field_name, record_pkg, ok := record_field_owner(checker, slot) if !ok { continue } current := checker.module.types.fields[slot].type conflict := checker.record_field_conflicts[slot] if types.is_valid(conflict) { if is_runtime_type(checker, current) { source.addf( checker.diagnostics, checker.record_field_conflict_spans[slot], "conflicting types %s and %s for field '%s.%s' declared as '%s'", types.name(current), types.name(conflict), symbol_text(checker, record_name), symbol_text(checker, field_name), types.name(constraint), ) } else { source.addf( checker.diagnostics, checker.record_field_conflict_spans[slot], "type %s does not satisfy the '%s' constraint for field '%s.%s'", types.name(conflict), types.name(constraint), symbol_text(checker, record_name), symbol_text(checker, field_name), ) } } else if types.is_constraint(current) && !(int(record_pkg) < len(checker.poisoned_packages) && checker.poisoned_packages[record_pkg]) { source.addf( checker.diagnostics, source.Span{}, "could not resolve the '%s' constraint for field '%s.%s'", types.name(constraint), symbol_text(checker, record_name), symbol_text(checker, field_name), ) } if types.is_constraint(current) { checker.module.types.fields[slot].type = constraint_recovery_type(checker, constraint) } } } is_immutable_u8_slice :: proc(checker: ^Checker, value: types.Type) -> bool { item, ok := types.node(&checker.module.types, value) return ok && item.kind == .Slice && !item.mutable && item.child == types.U8 } validate_meta_schema :: proc(checker: ^Checker) { meta_package := ast.INVALID_PACKAGE for import_item in checker.ast_module.imports { if import_item.valid && import_item.path == "@std/meta" { meta_package = import_item.target break } } if meta_package == ast.INVALID_PACKAGE { return } find := proc(checker: ^Checker, pkg: ast.Package_Id, name: string) -> types.Type { return types.find_named(&checker.module.types, u32(pkg), u32(symbol.intern(checker.symbols, name))) } field_info := find(checker, meta_package, "FieldInfo") record_info := find(checker, meta_package, "RecordInfo") enum_info := find(checker, meta_package, "EnumInfo") type_info := find(checker, meta_package, "TypeInfo") layout := find(checker, meta_package, "Layout") valid := types.is_valid(field_info) && types.is_valid(record_info) && types.is_valid(enum_info) && types.is_valid(type_info) && types.is_valid(layout) layout_item, layout_ok := types.node(&checker.module.types, layout) layout_members := types.enum_members_for(&checker.module.types, layout) valid = valid && layout_ok && layout_item.kind == .Enum && len(layout_members) == 2 if valid { valid = symbol_text(checker, symbol.Id(layout_members[0].name)) == "auto" && symbol_text(checker, symbol.Id(layout_members[1].name)) == "c" } field_item, field_ok := types.node(&checker.module.types, field_info) record_item, record_ok := types.node(&checker.module.types, record_info) enum_item, enum_ok := types.node(&checker.module.types, enum_info) valid = valid && field_ok && field_item.kind == .Struct && !field_item.tuple && !field_item.c_layout && record_ok && record_item.kind == .Struct && !record_item.tuple && !record_item.c_layout && enum_ok && enum_item.kind == .Struct && !enum_item.tuple && !enum_item.c_layout field_fields := types.fields_for(&checker.module.types, field_info) valid = valid && len(field_fields) == 3 if valid { valid = symbol_text(checker, symbol.Id(field_fields[0].name)) == "name" && is_immutable_u8_slice(checker, field_fields[0].type) && symbol_text(checker, symbol.Id(field_fields[1].name)) == "type" && is_type_metatype_syntax(checker, field_fields[1].type) && symbol_text(checker, symbol.Id(field_fields[2].name)) == "index" && types.equal(field_fields[2].type, types.USIZE) } enum_fields := types.fields_for(&checker.module.types, enum_info) valid = valid && len(enum_fields) == 1 if valid { fields_item, fields_ok := types.node(&checker.module.types, enum_fields[0].type) valid = symbol_text(checker, symbol.Id(enum_fields[0].name)) == "fields" && fields_ok && fields_item.kind == .Slice && !fields_item.mutable && types.equal(fields_item.child, field_info) } record_fields := types.fields_for(&checker.module.types, record_info) valid = valid && len(record_fields) == 4 if valid { name_type := types.child_type(record_fields[0].type, &checker.module.types) fields_item, fields_ok := types.node(&checker.module.types, record_fields[1].type) valid = symbol_text(checker, symbol.Id(record_fields[0].name)) == "name" && types.is_optional(record_fields[0].type, &checker.module.types) && is_immutable_u8_slice(checker, name_type) && symbol_text(checker, symbol.Id(record_fields[1].name)) == "fields" && fields_ok && fields_item.kind == .Slice && !fields_item.mutable && types.equal(fields_item.child, field_info) && symbol_text(checker, symbol.Id(record_fields[2].name)) == "is_tuple" && types.is_bool(record_fields[2].type) && symbol_text(checker, symbol.Id(record_fields[3].name)) == "layout" && types.equal(record_fields[3].type, layout) } type_item, type_ok := types.node(&checker.module.types, type_info) type_fields := types.fields_for(&checker.module.types, type_info) expected_tags := []string{ "invalid", "void", "anyopaque", "bool", "integer", "float", "array", "pointer", "slice", "range", "optional", "function", "enum", "record", "union", "fallible", "distinct", } valid = valid && type_ok && type_item.kind == .Union && types.is_enum(type_item.child, &checker.module.types) && len(type_fields) == len(expected_tags) if valid { for tag, index in expected_tags { field := type_fields[index] if symbol_text(checker, symbol.Id(field.name)) != tag || (tag == "record" && !types.equal(field.type, record_info)) || (tag == "enum" && !types.equal(field.type, enum_info)) || (tag != "record" && tag != "enum" && !types.is_void(field.type)) { valid = false break } } } if !valid { source.add(checker.diagnostics, source.Span{}, "@std/meta declarations do not match the compiler reflection ABI") } } validate_type_nodes :: proc(checker: ^Checker) { node_count := len(checker.module.types.nodes) for index in 0.. len(checker.module.types.fields) { continue } for slot in start..