package checker import "../ast" import "../hir" import "../source" import "../symbol" import "../types" import "base:intrinsics" import "core:fmt" import "core:hash" import "core:math" import "core:mem" import "core:strings" COMPTIME_EVAL_QUOTA :: 100_000 Comptime_Value_Kind :: enum u8 { Integer, Type, String, Static, } Comptime_Value :: struct { name: symbol.Id, type: types.Type, value: i128, text: string, static_value: Ct_Value_Id, key: string, fingerprint: u64, kind: Comptime_Value_Kind, } Constant_Kind :: enum { Unknown, Not_Constant, Value, Overflow, Div_By_Zero, Non_Exact, Integer_Division, } Constant :: struct { kind: Constant_Kind, value: i128, } Constant_Frame :: struct { expr: ast.Expr_Id, stage: u8, } find_comptime_value :: proc(values: []Comptime_Value, name: symbol.Id) -> (Comptime_Value, bool) { for index := len(values) - 1; index >= 0; index -= 1 { if values[index].name == name { return values[index], true } } return {}, false } current_comptime_value :: proc(checker: ^Checker, name: symbol.Id) -> (Comptime_Value, bool) { return find_comptime_value(checker.current_comptime_values, name) } current_static_binding :: proc(checker: ^Checker, name: symbol.Id) -> (Static_Binding, bool) { for index := len(checker.static_bindings)-1; index >= 0; index -= 1 { if checker.static_bindings[index].name == name { return checker.static_bindings[index], true } } return {}, false } current_comptime_type :: proc(checker: ^Checker, name: symbol.Id) -> (types.Type, bool) { if value, ok := current_comptime_value(checker, name); ok && value.kind == .Type { return value.type, true } return types.INVALID, false } comptime_values_equal :: proc(left, right: []Comptime_Value) -> bool { if len(left) != len(right) { return false } for value, index in left { other := right[index] if value.name != other.name || value.kind != other.kind || !types.equal(value.type, other.type) || (value.kind == .Integer && value.value != other.value) || (value.kind == .String && value.text != other.text) || (value.kind == .Static && (value.fingerprint != other.fingerprint || value.key != other.key)) { return false } } return true } eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant { if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { return Constant{kind = .Not_Constant} } stack := checker.constant_stack clear_dynamic_array(&stack) defer { clear_dynamic_array(&stack) checker.constant_stack = stack } append(&stack, Constant_Frame{expr=expr_id}) for len(stack) > 0 { frame_index := len(stack)-1 frame := stack[frame_index] if checker.constants[frame.expr].kind != .Unknown { _ = pop(&stack) continue } expr := checker.ast_module.exprs[frame.expr] if expr.kind != .Add && expr.kind != .Sub && expr.kind != .Mul && expr.kind != .Negate { result := Constant{kind = .Not_Constant} if expr.kind == .Integer { result = Constant{kind = .Value, value = i128(expr.integer)} } checker.constants[frame.expr] = result _ = pop(&stack) continue } if frame.stage == 0 { stack[frame_index].stage = 1 if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.ast_module.exprs) && checker.constants[expr.left].kind == .Unknown { append(&stack, Constant_Frame{expr=expr.left}) } continue } if frame.stage == 1 && expr.kind == .Negate { operand := Constant{kind = .Not_Constant} if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.constants) { operand = checker.constants[expr.left] } result := Constant{kind = .Not_Constant} if operand.kind == .Overflow { result = Constant{kind = .Overflow} } else if operand.kind == .Value { value, overflow := intrinsics.overflow_sub(i128(0), operand.value) result = Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value} } checker.constants[frame.expr] = result _ = pop(&stack) continue } if frame.stage == 1 { stack[frame_index].stage = 2 if expr.right != ast.INVALID_EXPR && int(expr.right) < len(checker.ast_module.exprs) && checker.constants[expr.right].kind == .Unknown { append(&stack, Constant_Frame{expr=expr.right}) } continue } left := Constant{kind = .Not_Constant} right := Constant{kind = .Not_Constant} if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.constants) { left = checker.constants[expr.left] } if expr.right != ast.INVALID_EXPR && int(expr.right) < len(checker.constants) { right = checker.constants[expr.right] } result := Constant{kind = .Not_Constant} if left.kind == .Overflow || right.kind == .Overflow { result = Constant{kind = .Overflow} } else if left.kind == .Value && right.kind == .Value { value: i128 overflow: bool #partial switch expr.kind { case .Sub: value, overflow = intrinsics.overflow_sub(left.value, right.value) case .Mul: value, overflow = intrinsics.overflow_mul(left.value, right.value) case: value, overflow = intrinsics.overflow_add(left.value, right.value) } switch { case overflow: result = Constant{kind = .Overflow} case: result = Constant{kind = .Value, value = value} } } checker.constants[frame.expr] = result _ = pop(&stack) } return checker.constants[expr_id] } Ct_Value_Id :: distinct u32 INVALID_CT_VALUE :: Ct_Value_Id(0xffff_ffff) ct_value_id :: proc(index: int) -> Ct_Value_Id { assert(index >= 0 && u64(index) < u64(INVALID_CT_VALUE)) return Ct_Value_Id(index) } Ct_Cell_Id :: distinct u32 INVALID_CT_CELL :: Ct_Cell_Id(0xffff_ffff) ct_cell_id :: proc(index: int) -> Ct_Cell_Id { assert(index >= 0 && u64(index) < u64(INVALID_CT_CELL)) return Ct_Cell_Id(index) } Ct_Place_Id :: distinct u32 INVALID_CT_PLACE :: Ct_Place_Id(0xffff_ffff) ct_place_id :: proc(index: int) -> Ct_Place_Id { assert(index >= 0 && u64(index) < u64(INVALID_CT_PLACE)) return Ct_Place_Id(index) } Ct_Value_Kind :: enum u8 { Invalid, Void, Undefined, Integer, Float, Bool, String, Range, Array, Struct, Pointer, Slice, Function, Type, None, Optional_Some, Fallible, } Ct_Error_Kind :: enum u8 { None, Not_Comptime, Overflow, Div_By_Zero, Non_Exact, Integer_Division, Quota, } Ct_Value :: struct { kind: Ct_Value_Kind, type: types.Type, integer: i128, float: f64, index: u64, start: u32, count: u32, active: i64, } Ct_Path_Kind :: enum u8 { Index, Field, } Ct_Path_Elem :: struct { kind: Ct_Path_Kind, index: u32, } Ct_Cell :: struct { value: Ct_Value_Id, mutable: bool, live: bool, } Ct_Place :: struct { cell: Ct_Cell_Id, type: types.Type, start: u32, count: u32, writable: bool, } Ct_Binding :: struct { name: symbol.Id, type: types.Type, value: Ct_Value_Id, cell: Ct_Cell_Id, mutable: bool, } Ct_Flow_Kind :: enum u8 { Normal, Return, Yield, Break, Continue, } Ct_Flow :: struct { kind: Ct_Flow_Kind, value: Ct_Value_Id, label: symbol.Id, } Ct_State :: struct { checker: ^Checker, pkg: ast.Package_Id, file: ast.File_Id, result: types.Type, values: [dynamic]Ct_Value, children: [dynamic]Ct_Value_Id, cells: [dynamic]Ct_Cell, places: [dynamic]Ct_Place, paths: [dynamic]Ct_Path_Elem, bindings: [dynamic]Ct_Binding, defers: [dynamic]Ct_Defer, defer_depth: int, steps: int, error: Ct_Error_Kind, diagnostic: source.Diagnostic_Id, silent: bool, demanded: ^[dynamic]Spec_Id, } Ct_Defer :: struct { statement: ast.Stmt_Id, error_only: bool, capture: symbol.Id, } ct_state_make :: proc( checker: ^Checker, pkg: ast.Package_Id, file: ast.File_Id, result := types.INVALID, values: []Comptime_Value = nil, diagnose := true, demanded: ^[dynamic]Spec_Id = nil, ) -> Ct_State { state: Ct_State state.checker = checker state.pkg = pkg state.file = file state.result = result state.error = .None state.diagnostic = source.INVALID_DIAGNOSTIC state.silent = !diagnose state.demanded = demanded state.values.allocator = checker.allocator state.children.allocator = checker.allocator state.cells.allocator = checker.allocator state.places.allocator = checker.allocator state.paths.allocator = checker.allocator state.bindings.allocator = checker.allocator state.defers.allocator = checker.allocator for value in values { if value.kind == .Integer { id := ct_add_value(&state, Ct_Value{kind=.Integer, type=value.type, integer=value.value}) ct_bind_value(&state, value.name, value.type, id, false) } else if value.kind == .Type { id := ct_add_value(&state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(value.type)}) ct_bind_value(&state, value.name, types.INVALID, id, false) } else if value.kind == .String { string_id := u64(0) for text, index in checker.ast_module.strings { if text == value.text { string_id = u64(index) break } } id := ct_add_value(&state, Ct_Value{kind=.String, type=value.type, index=string_id}) ct_bind_value(&state, value.name, value.type, id, false) } else if value.kind == .Static { id := ct_clone_graph(&state, &checker.static_state, value.static_value) ct_bind_value(&state, value.name, value.type, id, false) } } for binding in checker.static_bindings { id := ct_clone_graph(&state, &checker.static_state, binding.value) ct_bind_value(&state, binding.name, binding.type, id, false) } return state } ct_state_destroy :: proc(state: ^Ct_State) { delete(state.values) delete(state.children) delete(state.cells) delete(state.places) delete(state.paths) delete(state.bindings) delete(state.defers) } ct_add_value :: proc(state: ^Ct_State, value: Ct_Value) -> Ct_Value_Id { id := ct_value_id(len(state.values)) append(&state.values, value) return id } ct_add_cell :: proc(state: ^Ct_State, value: Ct_Value_Id, mutable: bool) -> Ct_Cell_Id { id := ct_cell_id(len(state.cells)) append(&state.cells, Ct_Cell{value=value, mutable=mutable, live=true}) return id } ct_add_place :: proc( state: ^Ct_State, cell: Ct_Cell_Id, value_type: types.Type, writable: bool, path: []Ct_Path_Elem = nil, ) -> Ct_Place_Id { id := ct_place_id(len(state.places)) start := u32(len(state.paths)) append(&state.paths, ..path) append(&state.places, Ct_Place{ cell=cell, type=value_type, start=start, count=u32(len(path)), writable=writable, }) return id } ct_place_path :: proc(state: ^Ct_State, place: Ct_Place) -> []Ct_Path_Elem { start := int(place.start) end := start+int(place.count) if start < 0 || end > len(state.paths) { return nil } return state.paths[start:end] } ct_extend_place :: proc( state: ^Ct_State, base_id: Ct_Place_Id, elem: Ct_Path_Elem, value_type: types.Type, writable: bool, ) -> Ct_Place_Id { if base_id == INVALID_CT_PLACE || int(base_id) >= len(state.places) { return INVALID_CT_PLACE } base := state.places[base_id] path := ct_place_path(state, base) extended := make([]Ct_Path_Elem, len(path)+1, state.checker.allocator) defer delete(extended, state.checker.allocator) copy(extended, path) extended[len(path)] = elem return ct_add_place(state, base.cell, value_type, writable, extended[:]) } ct_bind_value :: proc(state: ^Ct_State, name: symbol.Id, value_type: types.Type, value: Ct_Value_Id, mutable: bool) { cell := ct_add_cell(state, value, mutable) append(&state.bindings, Ct_Binding{name=name, type=value_type, value=value, cell=cell, mutable=mutable}) } ct_pop_bindings :: proc(state: ^Ct_State, start: int) { for index := start; index < len(state.bindings); index += 1 { cell := state.bindings[index].cell if cell != INVALID_CT_CELL && int(cell) < len(state.cells) { state.cells[cell].live = false } } resize(&state.bindings, start) } ct_child_slice :: proc(state: ^Ct_State, value: Ct_Value) -> []Ct_Value_Id { start := int(value.start) end := start+int(value.count) if start < 0 || end > len(state.children) { return nil } return state.children[start:end] } ct_value_contains_undefined :: proc(state: ^Ct_State, id: Ct_Value_Id, depth := 0) -> bool { if depth > 64 || id == INVALID_CT_VALUE || int(id) >= len(state.values) { return false } value := state.values[id] if value.kind == .Undefined { return true } for child in ct_child_slice(state, value) { if ct_value_contains_undefined(state, child, depth+1) { return true } } return false } ct_observe_value :: proc(state: ^Ct_State, id: Ct_Value_Id, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) { if id == INVALID_CT_VALUE || int(id) >= len(state.values) { return INVALID_CT_VALUE, ct_flow(.Normal), false } if state.values[id].kind == .Undefined { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail( state, .Not_Comptime, span, "cannot read an undefined value at comptime", ) } return id, ct_flow(.Normal), true } ct_fail :: proc(state: ^Ct_State, kind: Ct_Error_Kind, span: source.Span, message: string) -> bool { if state.error == .None { state.error = kind if !state.silent && len(message) > 0 { state.diagnostic = source.add(state.checker.diagnostics, span, message) } } return false } ct_failf :: proc(state: ^Ct_State, kind: Ct_Error_Kind, span: source.Span, fmt: string, args: ..any) -> bool { if state.error == .None { state.error = kind if !state.silent { state.diagnostic = source.addf(state.checker.diagnostics, span, fmt, ..args) } } return false } ct_step :: proc(state: ^Ct_State, span: source.Span) -> bool { state.steps += 1 if state.steps > COMPTIME_EVAL_QUOTA { return ct_fail(state, .Quota, span, "comptime evaluation exceeded the step quota") } return true } ct_find_binding_index :: proc(state: ^Ct_State, name: symbol.Id) -> (int, bool) { for index := len(state.bindings) - 1; index >= 0; index -= 1 { if state.bindings[index].name == name { return index, true } } return -1, false } ct_binding_value :: proc(state: ^Ct_State, index: int) -> Ct_Value_Id { if index < 0 || index >= len(state.bindings) { return INVALID_CT_VALUE } binding := state.bindings[index] if binding.cell != INVALID_CT_CELL && int(binding.cell) < len(state.cells) && state.cells[binding.cell].live { return state.cells[binding.cell].value } return binding.value } ct_binding_place :: proc(state: ^Ct_State, index: int) -> Ct_Place_Id { if index < 0 || index >= len(state.bindings) { return INVALID_CT_PLACE } binding := state.bindings[index] if binding.cell == INVALID_CT_CELL || int(binding.cell) >= len(state.cells) || !state.cells[binding.cell].live { return INVALID_CT_PLACE } return ct_add_place(state, binding.cell, binding.type, binding.mutable) } ct_flow :: proc(kind: Ct_Flow_Kind, value := INVALID_CT_VALUE, label := symbol.INVALID) -> Ct_Flow { return Ct_Flow{kind=kind, value=value, label=label} } ct_bool_value :: proc(state: ^Ct_State, id: Ct_Value_Id) -> (bool, bool) { if id == INVALID_CT_VALUE || int(id) >= len(state.values) { return false, false } value := state.values[id] if value.kind == .Bool { return value.integer != 0, true } return false, false } ct_integer_value :: proc(state: ^Ct_State, id: Ct_Value_Id) -> (i128, bool) { if id == INVALID_CT_VALUE || int(id) >= len(state.values) { return 0, false } value := state.values[id] if value.kind == .Integer || value.kind == .Bool { return value.integer, true } return 0, false } ct_is_integer_like :: proc(value: Ct_Value) -> bool { return value.kind == .Integer || value.kind == .Bool } ct_default_integer_type :: proc(value: i128) -> types.Type { if fits_i64(value) { return types.smallest_signed_for_literal(i64(value)) } return types.I64 } ct_coerce_value :: proc(state: ^Ct_State, id: Ct_Value_Id, expected: types.Type, span: source.Span) -> (Ct_Value_Id, bool) { if id == INVALID_CT_VALUE || int(id) >= len(state.values) { return INVALID_CT_VALUE, false } if !types.is_valid(expected) || types.is_constraint(expected) { return id, true } value := state.values[id] if value.kind == .Type && is_type_metatype_syntax(state.checker, expected) { return id, true } if types.equal(value.type, expected) { return id, true } store := &state.checker.module.types if value.kind == .String { if item, ok := types.node(store, expected); ok && item.kind == .Slice && !item.mutable && item.child == types.U8 { value.type = expected return ct_add_value(state, value), true } } if value.kind == .Pointer && types.can_weaken_pointer(value.type, expected, store) { value.type = expected return ct_add_value(state, value), true } if value.kind == .Slice && types.can_weaken_slice(value.type, expected, store) { value.type = expected return ct_add_value(state, value), true } if value.kind == .Array { expected_item, expected_ok := types.node(store, expected) value_item, value_ok := types.node(store, value.type) if expected_ok && value_ok && expected_item.kind == .Array && value_item.kind == .Array && expected_item.inferred_count && types.equal(expected_item.child, value_item.child) { value.type = types.with_array_count(store, expected, value_item.count) return ct_add_value(state, value), true } } if value.kind == .None { if types.is_optional(expected, store) { value.type = expected return ct_add_value(state, value), true } return INVALID_CT_VALUE, ct_fail(state, .Not_Comptime, span, "'none' requires an optional context") } if types.is_optional(expected, store) { child := types.child_type(expected, store) coerced, ok := ct_coerce_value(state, id, child, span) if ok { start := u32(len(state.children)) append(&state.children, coerced) return ct_add_value(state, Ct_Value{kind=.Optional_Some, type=expected, start=start, count=1}), true } return INVALID_CT_VALUE, false } if ct_is_integer_like(value) { if types.is_concrete_integer(expected) || types.is_enum(expected, store) { backing := expected if item, ok := types.node(store, expected); ok && item.kind == .Enum { backing = item.child } if !fits_integer_type(value.integer, backing, state.checker.target) { return INVALID_CT_VALUE, ct_failf( state, .Not_Comptime, span, "integer constant %d does not fit in %s", value.integer, types.name(expected), ) } value.type = expected value.kind = .Integer return ct_add_value(state, value), true } if types.is_float(expected, state.checker.target) { return ct_add_value(state, Ct_Value{kind=.Float, type=expected, float=f64(value.integer)}), true } } if value.kind == .Float && types.is_float(expected, state.checker.target) { value.type = expected return ct_add_value(state, value), true } return INVALID_CT_VALUE, ct_failf( state, .Not_Comptime, span, "cannot implicitly convert %s to %s at comptime", types.name(value.type), types.name(expected), ) } ct_place_get :: proc(state: ^Ct_State, place_id: Ct_Place_Id) -> (Ct_Value_Id, bool) { if place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) { return INVALID_CT_VALUE, false } place := state.places[place_id] if place.cell == INVALID_CT_CELL || int(place.cell) >= len(state.cells) || !state.cells[place.cell].live { return INVALID_CT_VALUE, false } current := state.cells[place.cell].value for elem in ct_place_path(state, place) { if current == INVALID_CT_VALUE || int(current) >= len(state.values) { return INVALID_CT_VALUE, false } value := state.values[current] children := ct_child_slice(state, value) #partial switch elem.kind { case .Index: index := int(elem.index) if value.kind != .Array || index < 0 || index >= len(children) { return INVALID_CT_VALUE, false } current = children[index] case .Field: index := int(elem.index) if value.kind != .Struct || index < 0 { return INVALID_CT_VALUE, false } if types.is_union(value.type, &state.checker.module.types) { if value.active != i64(index) || len(children) == 0 { return INVALID_CT_VALUE, false } current = children[0] } else { if index >= len(children) { return INVALID_CT_VALUE, false } current = children[index] } } } return current, true } ct_update_path :: proc(state: ^Ct_State, current: Ct_Value_Id, path: []Ct_Path_Elem, replacement: Ct_Value_Id) -> (Ct_Value_Id, bool) { if len(path) == 0 { return replacement, true } if current == INVALID_CT_VALUE || int(current) >= len(state.values) { return INVALID_CT_VALUE, false } value := state.values[current] children := ct_child_slice(state, value) if len(children) == 0 { return INVALID_CT_VALUE, false } next_index := -1 elem := path[0] #partial switch elem.kind { case .Index: if value.kind != .Array { return INVALID_CT_VALUE, false } next_index = int(elem.index) case .Field: if value.kind != .Struct { return INVALID_CT_VALUE, false } if types.is_union(value.type, &state.checker.module.types) { if value.active != i64(elem.index) { return INVALID_CT_VALUE, false } next_index = 0 } else { next_index = int(elem.index) } } if next_index < 0 || next_index >= len(children) { return INVALID_CT_VALUE, false } updated_child, ok := ct_update_path(state, children[next_index], path[1:], replacement) if !ok { return INVALID_CT_VALUE, false } copied := make([]Ct_Value_Id, len(children), state.checker.allocator) defer delete(copied, state.checker.allocator) copy(copied, children) copied[next_index] = updated_child value.start = u32(len(state.children)) append(&state.children, ..copied) return ct_add_value(state, value), true } ct_place_set :: proc(state: ^Ct_State, place_id: Ct_Place_Id, replacement: Ct_Value_Id) -> bool { if place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) { return false } place := state.places[place_id] if !place.writable || place.cell == INVALID_CT_CELL || int(place.cell) >= len(state.cells) || !state.cells[place.cell].live { return false } path := ct_place_path(state, place) root, ok := ct_update_path(state, state.cells[place.cell].value, path, replacement) if !ok { return false } state.cells[place.cell].value = root return true } ct_place_live :: proc(state: ^Ct_State, place_id: Ct_Place_Id) -> bool { if place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) { return false } cell := state.places[place_id].cell return cell != INVALID_CT_CELL && int(cell) < len(state.cells) && state.cells[cell].live } ct_value_references_dead_storage :: proc(state: ^Ct_State, id: Ct_Value_Id) -> bool { if id == INVALID_CT_VALUE || int(id) >= len(state.values) { return false } value := state.values[id] if value.kind == .Pointer || value.kind == .Slice { return !ct_place_live(state, Ct_Place_Id(value.index)) } for child in ct_child_slice(state, value) { if child != INVALID_CT_VALUE && ct_value_references_dead_storage(state, child) { return true } } return false } ct_materialize_value :: proc( state: ^Ct_State, id: Ct_Value_Id, span: source.Span, expected := types.INVALID, ) -> hir.Expr_Id { checker := state.checker if id == INVALID_CT_VALUE || int(id) >= len(state.values) { return invalid_hir_expr(checker, span, source.INVALID_DIAGNOSTIC) } materialized := id if types.is_valid(expected) { if coerced, ok := ct_coerce_value(state, id, expected, span); ok { materialized = coerced } else { return invalid_hir_expr(checker, span, state.diagnostic, expected) } } value := state.values[materialized] #partial switch value.kind { case .Undefined: if state.diagnostic == source.INVALID_DIAGNOSTIC { state.diagnostic = source.add(checker.diagnostics, span, "cannot materialize an undefined comptime value") } return invalid_hir_expr(checker, span, state.diagnostic, value.type) case .Integer: if types.is_enum(value.type, &checker.module.types) { int_value := i64(value.integer) if value.integer < 0 else transmute(i64)u64(value.integer) return add_hir_expr(checker, hir.Expr{ kind=.Integer, span=span, type=value.type, integer=int_value, target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } return build_constant_expr(checker, ast.Expr{span=span}, Constant{kind=.Value, value=value.integer}, value.type) case .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, }) case .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, }) case .String: return add_hir_expr(checker, hir.Expr{ kind=.String, span=span, type=value.type, integer=i64(value.index), target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) case .Range: children := ct_child_slice(state, value) args := make([]hir.Expr_Id, 2, checker.allocator) for child, index in children[:min(2, len(children))] { args[index] = ct_materialize_value(state, child, span) } return add_hir_expr(checker, hir.Expr{ kind=.Range, 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, }) case .Array, .Struct: children := ct_child_slice(state, value) args := make([]hir.Expr_Id, len(children), checker.allocator) if value.kind == .Array { item, _ := types.node(&checker.module.types, value.type) for child, index in children { args[index] = ct_materialize_value(state, child, span, item.child) } } else if types.is_union(value.type, &checker.module.types) { fields := types.fields_for(&checker.module.types, value.type) payload_type := types.INVALID if value.active >= 0 && value.active < i64(len(fields)) { payload_type = fields[value.active].type } for child, index in children { args[index] = hir.INVALID_EXPR if child != INVALID_CT_VALUE && types.is_valid(payload_type) && !types.is_void(payload_type) { args[index] = ct_materialize_value(state, child, span, payload_type) } } } else { fields := types.fields_for(&checker.module.types, value.type) for child, index in children { field_type := fields[index].type if index < len(fields) else types.INVALID args[index] = ct_materialize_value(state, child, span, field_type) } } kind := hir.Expr_Kind.Array if value.kind == .Array else hir.Expr_Kind.Struct return add_hir_expr(checker, hir.Expr{ kind=kind, span=span, type=value.type, args=args, integer=value.active, target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) case .Pointer, .Slice: if state.diagnostic == source.INVALID_DIAGNOSTIC { state.diagnostic = source.add(checker.diagnostics, span, "comptime storage pointers and slices cannot materialize as runtime memory") } return invalid_hir_expr(checker, span, state.diagnostic, value.type) case .Function: return build_function_value(checker, ast.Function_Id(u32(value.index)), span, expected) case .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, }) case .Optional_Some: children := ct_child_slice(state, value) child := hir.INVALID_EXPR if len(children) > 0 { child_type := types.child_type(value.type, &checker.module.types) child = ct_materialize_value(state, 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, }) case .Fallible: children := ct_child_slice(state, value) args := make([]hir.Expr_Id, 1, checker.allocator) args[0] = hir.INVALID_EXPR if len(children) > 0 && children[0] != INVALID_CT_VALUE { fallible_item, _ := types.node(&checker.module.types, value.type) payload_type := fallible_item.extra if value.active != 0 else fallible_item.child if !types.is_void(payload_type) { args[0] = ct_materialize_value(state, children[0], span, payload_type) } } return add_hir_expr(checker, hir.Expr{ kind=.Struct, span=span, type=value.type, args=args, integer=value.active, target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } return invalid_hir_expr(checker, span, source.INVALID_DIAGNOSTIC, value.type) } ct_undefined_value :: proc(state: ^Ct_State, value_type: types.Type, depth := 0) -> (Ct_Value_Id, bool) { if depth > 64 || !types.is_valid(value_type) { return INVALID_CT_VALUE, false } store := &state.checker.module.types item, ok := types.node(store, value_type) if ok && item.kind == .Array { children := make([]Ct_Value_Id, int(item.count), state.checker.allocator) defer delete(children, state.checker.allocator) for &child in children { child, ok = ct_undefined_value(state, item.child, depth+1) if !ok { return INVALID_CT_VALUE, false } } start := u32(len(state.children)) append(&state.children, ..children) return ct_add_value(state, Ct_Value{kind=.Array, type=value_type, start=start, count=u32(len(children))}), true } if ok && item.kind == .Struct && !item.opaque { fields := types.fields_for(store, value_type) children := make([]Ct_Value_Id, len(fields), state.checker.allocator) defer delete(children, state.checker.allocator) for field, index in fields { children[index], ok = ct_undefined_value(state, field.type, depth+1) if !ok { return INVALID_CT_VALUE, false } } start := u32(len(state.children)) append(&state.children, ..children) return ct_add_value(state, Ct_Value{kind=.Struct, type=value_type, start=start, count=u32(len(children))}), true } return ct_add_value(state, Ct_Value{kind=.Undefined, type=value_type}), true } ct_eval_expr :: proc( state: ^Ct_State, expr_id: ast.Expr_Id, expected := types.INVALID, depth := 0, ) -> (Ct_Value_Id, Ct_Flow, bool) { checker := state.checker if depth > 128 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, source.Span{}, "expression cannot be evaluated at comptime") } expr := checker.ast_module.exprs[expr_id] if !ct_step(state, expr.span) { return INVALID_CT_VALUE, ct_flow(.Normal), false } store := &checker.module.types #partial switch expr.kind { case .Integer: value_type := expected if types.is_concrete_integer(expected) || types.is_enum(expected, store) else ct_default_integer_type(i128(expr.integer)) id := ct_add_value(state, Ct_Value{kind=.Integer, type=value_type, integer=i128(expr.integer)}) if types.is_valid(expected) { return ct_coerce_expr_value(state, id, expected, expr.span) } return id, ct_flow(.Normal), true case .Bool: return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=i128(expr.integer)}), ct_flow(.Normal), true case .Float: value_type := expected if types.is_float(expected, checker.target) else types.F64 return ct_add_value(state, Ct_Value{kind=.Float, type=value_type, float=transmute(f64)expr.integer}), ct_flow(.Normal), true case .String: return ct_add_value(state, Ct_Value{kind=.String, type=string_literal_type(checker, expr.integer), index=expr.integer}), ct_flow(.Normal), true case .Name: if !symbol.is_valid(expr.qualifier) { if index, ok := ct_find_binding_index(state, expr.name); ok { return ct_observe_value(state, ct_binding_value(state, index), expr.span) } if value, ok := current_comptime_value(checker, expr.name); ok { if value.kind == .Integer { id := ct_add_value(state, Ct_Value{kind=.Integer, type=value.type, integer=value.value}) return id, ct_flow(.Normal), true } if value.kind == .String { string_id := u64(0) for text, index in checker.ast_module.strings { if text == value.text { string_id = u64(index) break } } return ct_add_value(state, Ct_Value{kind=.String, type=value.type, index=string_id}), ct_flow(.Normal), true } return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(value.type)}), ct_flow(.Normal), true } } else if find_import(checker, state.file, expr.qualifier) == ast.INVALID_IMPORT { if index, ok := ct_find_binding_index(state, expr.qualifier); ok { base := ct_binding_value(state, index) return ct_eval_field_value(state, base, expr.name, expr.span) } } if enum_type, enum_ok := enum_type_from_name_expr(checker, expr, state.pkg, state.file); enum_ok { member, ok := find_enum_member(checker, enum_type, expr.name) if !ok { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown enum member '%s'", symbol_text(checker, expr.name)) } return ct_add_value(state, Ct_Value{kind=.Integer, type=enum_type, integer=member.value}), ct_flow(.Normal), true } target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false) if !available { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "unavailable imported package") } global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, state.file)) if global == ast.INVALID_GLOBAL || int(global) >= len(checker.ast_module.globals) { template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, state.file)) if template != ast.INVALID_FUNCTION { pointer_type, _, function_ok := function_pointer_type_for_template( checker, template, state.demanded, state.demanded != nil, ) if function_ok { return ct_add_value(state, Ct_Value{ kind=.Function, type=pointer_type, index=u64(template), }), ct_flow(.Normal), true } return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "function '%s' is not comptime-callable as a value", symbol_text(checker, expr.name)) } return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unresolved comptime value '%s'", symbol_text(checker, expr.name)) } g := checker.ast_module.globals[global] if g.external || !g.immutable || g.writable { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "global '%s' is not comptime-known", symbol_text(checker, expr.name)) } global_expected := type_from_syntax(checker, g.type, g.pkg, g.file) return ct_eval_expr(state, g.expr, global_expected, depth+1) case .Comptime: if expr.left != ast.INVALID_EXPR { return ct_eval_expr(state, expr.left, expected, depth+1) } flow, ok := ct_exec_statements(state, expr.body, true, depth+1) if ok && flow.kind == .Yield { return flow.value, ct_flow(.Normal), true } return INVALID_CT_VALUE, flow, ct_fail(state, .Not_Comptime, expr.span, "comptime block must yield a value") case .Array: return ct_eval_array_expr(state, expr, expected, depth+1) case .Struct_Literal: return ct_eval_struct_expr(state, expr, expected, depth+1) case .Type: resolved := type_from_syntax(checker, expr.type, state.pkg, state.file) return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(resolved)}), ct_flow(.Normal), types.is_valid(resolved) case .Anonymous_Struct_Type: resolved := resolve_generated_struct_type(checker, expr_id, state.pkg, state.file) return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(resolved)}), ct_flow(.Normal), types.is_valid(resolved) case .Enum_Literal: return ct_eval_enum_literal(state, expr, expected, depth+1) case .None: if !types.is_optional(expected, store) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "'none' requires an optional context") } return ct_add_value(state, Ct_Value{kind=.None, type=expected}), ct_flow(.Normal), true case .Field: if enum_type, enum_ok := enum_type_from_field_expr(checker, expr, state.pkg, state.file); enum_ok { member, ok := find_enum_member(checker, enum_type, expr.name) if !ok { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown enum member '%s'", symbol_text(checker, expr.name)) } return ct_add_value(state, Ct_Value{kind=.Integer, type=enum_type, integer=member.value}), ct_flow(.Normal), true } base_id, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } return ct_eval_field_value(state, base_id, expr.name, expr.span) case .Index: index_id, index_flow, index_ok := ct_eval_expr(state, expr.right, types.USIZE, depth+1) if !index_ok || index_flow.kind != .Normal { return INVALID_CT_VALUE, index_flow, index_ok } index_value, index_is_int := ct_integer_value(state, index_id) if !index_is_int || index_value < 0 || index_value > i128(0x7fff_ffff) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime index must be a non-negative integer") } base_id, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } if base_id != INVALID_CT_VALUE && int(base_id) < len(state.values) { base := state.values[base_id] if base.kind == .Slice { place, _, _ := ct_slice_element_place(state, base, int(index_value)) if place == INVALID_CT_PLACE { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime slice index out of bounds") } value, value_ok := ct_place_get(state, place) if !value_ok { return INVALID_CT_VALUE, ct_flow(.Normal), false } return ct_observe_value(state, value, expr.span) } if base.kind == .Pointer { pointer_item, pointer_ok := types.node(store, base.type) if pointer_ok && pointer_item.kind == .Pointer { if pointer_item.many { place, _, _ := ct_pointer_place(state, base, int(index_value)) if place == INVALID_CT_PLACE { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime pointer index out of bounds") } value, value_ok := ct_place_get(state, place) if !value_ok { return INVALID_CT_VALUE, ct_flow(.Normal), false } return ct_observe_value(state, value, expr.span) } if array_item, array_ok := types.node(store, pointer_item.child); array_ok && array_item.kind == .Array { base_place, _, _ := ct_pointer_place(state, base) if base_place == INVALID_CT_PLACE || int(index_value) >= int(array_item.count) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime array index out of bounds") } place := ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Index, index=u32(index_value)}, array_item.child, pointer_item.mutable && array_item.mutable) value, value_ok := ct_place_get(state, place) if !value_ok { return INVALID_CT_VALUE, ct_flow(.Normal), false } return ct_observe_value(state, value, expr.span) } } } } return ct_eval_index_value(state, base_id, int(index_value), expr.span) case .Unwrap: value, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } return ct_unwrap_optional(state, value, expr.span) case .Orelse: value, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } v := state.values[value] if v.kind == .Optional_Some { children := ct_child_slice(state, v) if len(children) > 0 { return children[0], ct_flow(.Normal), true } } if v.kind == .None { child := types.child_type(v.type, store) return ct_eval_expr(state, expr.right, child, depth+1) } return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "'orelse' requires an optional left operand") case .Try: return ct_eval_try_expr(state, expr, depth+1) case .Catch: return ct_eval_catch_expr(state, expr, expected, depth+1) case .Range: left, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } right, right_flow, right_ok := ct_eval_expr(state, expr.right, state.values[left].type, depth+1) if !right_ok || right_flow.kind != .Normal { return INVALID_CT_VALUE, right_flow, right_ok } child_type := types.widest(state.values[left].type, state.values[right].type) if !types.is_concrete_integer(child_type) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "range bounds must be compatible concrete integers") } left, _ = ct_coerce_value(state, left, child_type, expr.span) right, _ = ct_coerce_value(state, right, child_type, expr.span) start := u32(len(state.children)) append(&state.children, left, right) return ct_add_value(state, Ct_Value{ kind=.Range, type=types.range(store, child_type), start=start, count=2, active=i64(expr.integer), }), ct_flow(.Normal), true case .Negate, .Not: value, flow, ok := ct_eval_expr(state, expr.left, expected, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } return ct_eval_unary(state, expr.kind, value, expr.span) case .Add, .Sub, .Mul, .Div, .Eq, .Ne, .Lt, .Le, .Gt, .Ge: left_expected := expected if expr.kind == .Div && types.is_float(expected, checker.target) else types.INVALID left, flow, ok := ct_eval_expr(state, expr.left, left_expected, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } right, right_flow, right_ok := ct_eval_expr(state, expr.right, state.values[left].type, depth+1) if !right_ok || right_flow.kind != .Normal { return INVALID_CT_VALUE, right_flow, right_ok } return ct_eval_binary(state, expr.kind, left, right, expr.span) case .And, .Or: left, flow, ok := ct_eval_expr(state, expr.left, types.BOOL, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } left_bool, bool_ok := ct_bool_value(state, left) if !bool_ok { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "'and'/'or' require bool operands") } if expr.kind == .And && !left_bool { return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=0}), ct_flow(.Normal), true } if expr.kind == .Or && left_bool { return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1}), ct_flow(.Normal), true } return ct_eval_expr(state, expr.right, types.BOOL, depth+1) case .Call: return ct_eval_call_expr(state, expr, expected, depth+1) case .Cast: target := type_from_syntax(checker, expr.type, state.pkg, state.file) value, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } return ct_scalar_cast(state, value, target, expr.span) case .Address: place, place_type, writable, flow, ok := ct_eval_place(state, expr.left, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } result_type := types.pointer(store, place_type, writable, false) if types.is_pointer(expected, store) && types.equal(types.child_type(expected, store), place_type) && (!types.is_mutable(expected, store) || writable) { result_type = expected } return ct_add_value(state, Ct_Value{kind=.Pointer, type=result_type, index=u64(place), active=-1}), ct_flow(.Normal), true case .Deref: place, _, _, flow, ok := ct_eval_place(state, expr_id, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } value, value_ok := ct_place_get(state, place) if !value_ok { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime pointer no longer points to live storage") } return ct_observe_value(state, value, expr.span) case .Slice: return ct_eval_slice_expr(state, expr, depth+1) case .Undefined: if value, ok := ct_undefined_value(state, expected); ok { return value, ct_flow(.Normal), true } return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "undefined comptime value requires a concrete scalar or aggregate type") case .Keyed: return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "expression cannot be evaluated at comptime") } return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "expression cannot be evaluated at comptime") } ct_coerce_expr_value :: proc(state: ^Ct_State, id: Ct_Value_Id, expected: types.Type, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) { coerced, ok := ct_coerce_value(state, id, expected, span) return coerced, ct_flow(.Normal), ok } ct_eval_array_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) { checker := state.checker store := &checker.module.types element_type := types.INVALID result_type := expected expected_node, has_expected := types.node(store, expected) if has_expected && expected_node.kind == .Array { element_type = expected_node.child if !expected_node.inferred_count && expected_node.count != u64(len(expr.args)) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf( state, .Not_Comptime, expr.span, "array literal expects %d elements, got %d", expected_node.count, len(expr.args), ) } if expected_node.inferred_count { result_type = types.with_array_count(store, expected, u64(len(expr.args))) } } else { has_expected = false result_type = types.INVALID } start := u32(len(state.children)) for arg in expr.args { value, flow, ok := ct_eval_expr(state, arg, element_type, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } if !types.is_valid(element_type) { element_type = state.values[value].type } else if !types.equal(element_type, state.values[value].type) { element_type = types.widest(element_type, state.values[value].type) } append(&state.children, value) } if !types.is_valid(element_type) { element_type = types.I64 } if !has_expected { result_type = types.array(store, element_type, u64(len(expr.args)), false) } children := state.children[int(start):int(start)+len(expr.args)] for &child in children { coerced, ok := ct_coerce_value(state, child, element_type, expr.span) if !ok { return INVALID_CT_VALUE, ct_flow(.Normal), false } child = coerced } return ct_add_value(state, Ct_Value{kind=.Array, type=result_type, start=start, count=u32(len(expr.args))}), ct_flow(.Normal), true } ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) { checker := state.checker store := &checker.module.types struct_type := types.INVALID if expr.left != ast.INVALID_EXPR { struct_type, _ = resolve_type_argument(checker, expr.left, state.pkg, state.file) struct_type = types.resolve_alias(struct_type, store) } else if symbol.is_valid(expr.name) { target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false) struct_type = types.find_named(store, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, state.file))) if available else types.INVALID struct_type = types.resolve_alias(struct_type, store) } else if expr.tuple { struct_type = types.INVALID } else { struct_type = types.resolve_alias(expected, store) } if expr.tuple { resolved := types.is_valid(struct_type) fields := types.fields_for(store, struct_type) if resolved else nil if resolved { item, item_ok := types.node(store, struct_type) if !item_ok || item.kind != .Struct || !item.tuple || len(fields) != len(expr.args) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "invalid tuple construction") } } values := make([]Ct_Value_Id, len(expr.args), checker.allocator) inferred_fields := make([]types.Field, len(expr.args), checker.allocator) defer { delete(values, checker.allocator) delete(inferred_fields, checker.allocator) } for arg, index in expr.args { expected_field := fields[index].type if resolved else types.INVALID value, flow, ok := ct_eval_expr(state, arg, expected_field, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } if resolved { value, ok = ct_coerce_value(state, value, expected_field, checker.ast_module.exprs[arg].span) if !ok { return INVALID_CT_VALUE, ct_flow(.Normal), false } } else { inferred_fields[index].type = state.values[value].type } values[index] = value } if !resolved { struct_type = types.struct_anonymous(store, inferred_fields, true) } start := u32(len(state.children)) append(&state.children, ..values) return ct_add_value(state, Ct_Value{kind=.Struct, type=struct_type, start=start, count=u32(len(values))}), ct_flow(.Normal), true } if !types.is_record(struct_type, store) || types.is_opaque_struct(struct_type, store) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown or opaque record type '%s'", symbol_text(checker, expr.name)) } fields := types.fields_for(store, struct_type) union_record := types.is_union(struct_type, store) if union_record && len(expr.args) != 1 { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "union literal requires exactly one field initializer") } values := make([]Ct_Value_Id, 1 if union_record else len(fields), checker.allocator) initialized := make([]bool, len(fields), checker.allocator) defer { delete(values, checker.allocator) delete(initialized, checker.allocator) } for &value in values { value = INVALID_CT_VALUE } active_field: i64 for keyed in expr.args { keyed_expr := checker.ast_module.exprs[keyed] index, field, field_ok := find_struct_field(checker, struct_type, keyed_expr.name) if !field_ok { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, keyed_expr.span, "unknown struct field '%s'", symbol_text(checker, keyed_expr.name)) } if initialized[index] { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, keyed_expr.span, "duplicate initializer for struct field '%s'", symbol_text(checker, keyed_expr.name)) } initialized[index] = true value_index := 0 if union_record else index active_field = i64(index) if keyed_expr.left == ast.INVALID_EXPR { if !types.is_void(field.type) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, keyed_expr.span, "field '%s' requires a value", symbol_text(checker, keyed_expr.name)) } continue } value, flow, value_ok := ct_eval_expr(state, keyed_expr.left, field.type, depth+1) if !value_ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, value_ok } value, value_ok = ct_coerce_value(state, value, field.type, keyed_expr.span) if !value_ok { return INVALID_CT_VALUE, ct_flow(.Normal), false } values[value_index] = value } if !union_record { for field, index in fields { if values[index] == INVALID_CT_VALUE { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "missing initializer for struct field '%s'", symbol_text(checker, symbol.Id(field.name))) } } } start := u32(len(state.children)) append(&state.children, ..values) return ct_add_value(state, Ct_Value{kind=.Struct, type=struct_type, start=start, count=u32(len(values)), active=active_field}), ct_flow(.Normal), true } ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) { checker := state.checker store := &checker.module.types base_value, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } container_place := INVALID_CT_PLACE container_start := 0 container_len := 0 item: types.Node item_ok := false if base_value != INVALID_CT_VALUE && int(base_value) < len(state.values) { base := state.values[base_value] if base.kind == .Slice { container_place = Ct_Place_Id(base.index) container_start = int(base.start) container_len = int(base.count) item, item_ok = types.node(store, base.type) } else if base.kind == .Pointer { pointer_item, pointer_ok := types.node(store, base.type) array_item: types.Node array_ok := false if pointer_ok { array_item, array_ok = types.node(store, pointer_item.child) } if pointer_ok && !pointer_item.many && array_ok && array_item.kind == .Array { container_place, _, _ = ct_pointer_place(state, base) container_len = int(array_item.count) item = array_item item.mutable = pointer_item.mutable && array_item.mutable item_ok = true } } } if container_place == INVALID_CT_PLACE { place, place_type, writable, place_flow, place_ok := ct_eval_place(state, expr.left, depth+1) if !place_ok || place_flow.kind != .Normal { return INVALID_CT_VALUE, place_flow, place_ok } array_item, array_ok := types.node(store, place_type) if !array_ok || array_item.kind != .Array { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "slicing requires a comptime array, slice, or pointer-to-array") } container_place = place container_len = int(array_item.count) item = array_item item.mutable = writable && array_item.mutable item_ok = true } if !item_ok { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "slicing requires a comptime array, slice, or pointer-to-array") } start := 0 end := container_len for bound, index in expr.args { if bound == ast.INVALID_EXPR { continue } value, bound_flow, bound_ok := ct_eval_expr(state, bound, types.USIZE, depth+1) if !bound_ok || bound_flow.kind != .Normal { return INVALID_CT_VALUE, bound_flow, bound_ok } integer, integer_ok := ct_integer_value(state, value) if !integer_ok || integer < 0 || integer > i128(0x7fff_ffff) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "slice bounds must be non-negative integers") } if index == 0 { start = int(integer) } else { end = int(integer) } } if start < 0 || end < start || end > container_len { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime slice bounds out of range") } preserve_sentinel := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR result_type := types.slice(store, item.child, item.mutable, preserve_sentinel, item.sentinel) return ct_add_value(state, Ct_Value{ kind=.Slice, type=result_type, index=u64(container_place), start=u32(container_start+start), count=u32(end-start), }), ct_flow(.Normal), true } ct_eval_enum_literal :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) { checker := state.checker store := &checker.module.types if types.is_tagged_union(expected, store) { index, field, found := find_struct_field(checker, expected, expr.name) if !found { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown variant '.%s' on '%s'", symbol_text(checker, expr.name), type_label(checker, expected)) } payload := INVALID_CT_VALUE if expr.left != ast.INVALID_EXPR { value, flow, ok := ct_eval_expr(state, expr.left, field.type, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } payload, ok = ct_coerce_value(state, value, field.type, expr.span) if !ok { return INVALID_CT_VALUE, ct_flow(.Normal), false } } else if !types.is_void(field.type) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "variant '.%s' on '%s' needs a payload", symbol_text(checker, expr.name), type_label(checker, expected)) } start := u32(len(state.children)) append(&state.children, payload) return ct_add_value(state, Ct_Value{kind=.Struct, type=expected, start=start, count=1, active=i64(index)}), ct_flow(.Normal), true } if expr.left != ast.INVALID_EXPR { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "'.%s{...}' requires a tagged-union context", symbol_text(checker, expr.name)) } if !types.is_enum(expected, store) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "'.%s' requires an enum context", symbol_text(checker, expr.name)) } member, ok := find_enum_member(checker, expected, expr.name) if !ok { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown enum member '%s'", symbol_text(checker, expr.name)) } return ct_add_value(state, Ct_Value{kind=.Integer, type=expected, integer=member.value}), ct_flow(.Normal), true } ct_eval_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, name: symbol.Id, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) { checker := state.checker if base_id == INVALID_CT_VALUE || int(base_id) >= len(state.values) { return INVALID_CT_VALUE, ct_flow(.Normal), false } base := state.values[base_id] field_name := symbol_text(checker, name) if base.kind == .Array && field_name == "len" { return ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(base.count)}), ct_flow(.Normal), true } if base.kind == .String && field_name == "len" { length := i128(0) if base.index < u64(len(checker.ast_module.strings)) { length = i128(len(checker.ast_module.strings[base.index])) } return ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=length}), ct_flow(.Normal), true } if base.kind == .Slice && field_name == "len" { return ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(base.count)}), ct_flow(.Normal), true } if base.kind == .Pointer && field_name == "len" { if length, ok := ct_sequence_length(state, base); ok { return ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(length)}), ct_flow(.Normal), true } } if (base.kind == .Slice || base.kind == .Pointer) && field_name == "ptr" { item, ok := types.container(base.type, &checker.module.types) if !ok || item.kind == .Pointer { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime value does not expose '.ptr'") } container_place := INVALID_CT_PLACE offset := 0 if base.kind == .Slice { container_place = Ct_Place_Id(base.index) offset = int(base.start) } else { container_place, _, _ = ct_pointer_place(state, base) } if container_place == INVALID_CT_PLACE { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime pointer no longer points to live storage") } return ct_add_value(state, Ct_Value{ kind=.Pointer, type=container_pointer_type(&checker.module.types, item), index=u64(container_place), active=i64(offset), }), ct_flow(.Normal), true } if base.kind == .Pointer { place, child, _ := ct_pointer_place(state, base) if place == INVALID_CT_PLACE { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime pointer no longer points to live storage") } if types.is_record(child, &checker.module.types) { index, field, ok := find_struct_field(checker, child, name) if !ok { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "unknown struct field '%s'", field_name) } field_place := ct_extend_place(state, place, Ct_Path_Elem{kind=.Field, index=u32(index)}, field.type, false) if value, value_ok := ct_place_get(state, field_place); value_ok { return ct_observe_value(state, value, span) } } } if base.kind != .Struct { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "unknown comptime field '%s'", field_name) } index, field, ok := find_struct_field(checker, base.type, name) if !ok { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "unknown struct field '%s'", field_name) } children := ct_child_slice(state, base) if types.is_union(base.type, &checker.module.types) { if int(base.active) != index || len(children) == 0 || children[0] == INVALID_CT_VALUE { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "variant '%s' has no payload to read", field_name) } return ct_observe_value(state, children[0], span) } if index < 0 || index >= len(children) || types.is_void(field.type) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "field '%s' has no value", field_name) } return ct_observe_value(state, children[index], span) } ct_eval_tuple_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, index: u64, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) { if base_id == INVALID_CT_VALUE || int(base_id) >= len(state.values) { return INVALID_CT_VALUE, ct_flow(.Normal), false } base := state.values[base_id] base_type := base.type if base.kind == .Pointer { place, child, _ := ct_pointer_place(state, base) field_index, field, ok := find_tuple_field(state.checker, child, index) if place == INVALID_CT_PLACE || !ok { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "tuple field index is out of bounds") } field_place := ct_extend_place(state, place, Ct_Path_Elem{kind=.Field, index=u32(field_index)}, field.type, false) value, value_ok := ct_place_get(state, field_place) if !value_ok { return INVALID_CT_VALUE, ct_flow(.Normal), false } return ct_observe_value(state, value, span) } field_index, _, ok := find_tuple_field(state.checker, base_type, index) children := ct_child_slice(state, base) if !ok || field_index < 0 || field_index >= len(children) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "tuple field index is out of bounds") } return ct_observe_value(state, children[field_index], span) } ct_eval_index_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, index: int, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) { checker := state.checker if base_id == INVALID_CT_VALUE || int(base_id) >= len(state.values) { return INVALID_CT_VALUE, ct_flow(.Normal), false } base := state.values[base_id] if base.kind == .Array { children := ct_child_slice(state, base) if index < 0 || index >= len(children) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime array index out of bounds") } return ct_observe_value(state, children[index], span) } if base.kind == .String { if base.index >= u64(len(checker.ast_module.strings)) || index < 0 || index >= len(checker.ast_module.strings[base.index]) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime string index out of bounds") } return ct_add_value(state, Ct_Value{kind=.Integer, type=types.U8, integer=i128(checker.ast_module.strings[base.index][index])}), ct_flow(.Normal), true } return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "indexing requires a comptime array or string") } ct_sequence_length :: proc(state: ^Ct_State, value: Ct_Value) -> (int, bool) { checker := state.checker #partial switch value.kind { case .Array: return int(value.count), true case .String: if value.index < u64(len(checker.ast_module.strings)) { return len(checker.ast_module.strings[value.index]), true } case .Slice: return int(value.count), true case .Pointer: child := types.child_type(value.type, &checker.module.types) item, ok := types.node(&checker.module.types, child) if ok && item.kind == .Array { return int(item.count), true } } return 0, false } ct_pointer_place :: proc(state: ^Ct_State, value: Ct_Value, extra_index := 0) -> (Ct_Place_Id, types.Type, bool) { if value.kind != .Pointer { return INVALID_CT_PLACE, types.INVALID, false } store := &state.checker.module.types pointer_item, ok := types.node(store, value.type) if !ok || pointer_item.kind != .Pointer { return INVALID_CT_PLACE, types.INVALID, false } base := Ct_Place_Id(value.index) child := pointer_item.child if base == INVALID_CT_PLACE || int(base) >= len(state.places) || !ct_place_live(state, base) { return INVALID_CT_PLACE, types.INVALID, false } if pointer_item.many { index := int(value.active)+extra_index if index < 0 { return INVALID_CT_PLACE, types.INVALID, false } place := state.places[base] item, item_ok := types.node(store, place.type) if !item_ok || item.kind != .Array || index >= int(item.count) { return INVALID_CT_PLACE, types.INVALID, false } return ct_extend_place( state, base, Ct_Path_Elem{kind=.Index, index=u32(index)}, child, place.writable && item.mutable, ), child, pointer_item.mutable } return base, child, pointer_item.mutable } ct_slice_element_place :: proc(state: ^Ct_State, value: Ct_Value, index: int) -> (Ct_Place_Id, types.Type, bool) { if value.kind != .Slice || index < 0 || index >= int(value.count) { return INVALID_CT_PLACE, types.INVALID, false } store := &state.checker.module.types item, ok := types.node(store, value.type) if !ok || item.kind != .Slice { return INVALID_CT_PLACE, types.INVALID, false } base := Ct_Place_Id(value.index) return ct_extend_place( state, base, Ct_Path_Elem{kind=.Index, index=u32(int(value.start)+index)}, item.child, item.mutable, ), item.child, item.mutable } ct_eval_place :: proc( state: ^Ct_State, expr_id: ast.Expr_Id, depth: int, ) -> (Ct_Place_Id, types.Type, bool, Ct_Flow, bool) { checker := state.checker if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), false } expr := checker.ast_module.exprs[expr_id] store := &checker.module.types #partial switch expr.kind { case .Name: if !symbol.is_valid(expr.qualifier) { if index, ok := ct_find_binding_index(state, expr.name); ok { place := ct_binding_place(state, index) if place != INVALID_CT_PLACE { return place, state.bindings[index].type, state.bindings[index].mutable, ct_flow(.Normal), true } } } else if find_import(checker, state.file, expr.qualifier) == ast.INVALID_IMPORT { if index, ok := ct_find_binding_index(state, expr.qualifier); ok { base_value := ct_binding_value(state, index) base_place := INVALID_CT_PLACE base_type := state.bindings[index].type base_writable := state.bindings[index].mutable if base_value != INVALID_CT_VALUE && int(base_value) < len(state.values) && state.values[base_value].kind == .Pointer { base_place, base_type, base_writable = ct_pointer_place(state, state.values[base_value]) } else { base_place = ct_binding_place(state, index) } if base_place != INVALID_CT_PLACE && types.is_record(base_type, store) { field_index, field, field_ok := find_struct_field(checker, base_type, expr.name) if !field_ok { return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown struct field '%s'", symbol_text(checker, expr.name)) } return ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Field, index=u32(field_index)}, field.type, base_writable), field.type, base_writable, ct_flow(.Normal), true } } } target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false) if !available { return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "unavailable imported package") } global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, state.file)) if global == ast.INVALID_GLOBAL || int(global) >= len(checker.ast_module.globals) { return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unresolved comptime value '%s'", symbol_text(checker, expr.name)) } g := checker.ast_module.globals[global] if g.external || !g.immutable || g.writable { return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "global '%s' is not comptime-known", symbol_text(checker, expr.name)) } global_expected := type_from_syntax(checker, g.type, g.pkg, g.file) value, flow, ok := ct_eval_expr(state, g.expr, global_expected, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_PLACE, types.INVALID, false, flow, ok } cell := ct_add_cell(state, value, false) place := ct_add_place(state, cell, state.values[value].type, false) return place, state.values[value].type, false, ct_flow(.Normal), true case .Deref: pointer, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_PLACE, types.INVALID, false, flow, ok } if pointer == INVALID_CT_VALUE || int(pointer) >= len(state.values) || state.values[pointer].kind != .Pointer { return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "postfix '^' requires a comptime pointer") } place, child, writable := ct_pointer_place(state, state.values[pointer]) if place == INVALID_CT_PLACE { return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime pointer no longer points to live storage") } return place, child, writable, ct_flow(.Normal), true case .Field: base_value, flow, value_ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) if value_ok && flow.kind == .Normal && base_value != INVALID_CT_VALUE && int(base_value) < len(state.values) && state.values[base_value].kind == .Pointer { base_place, base_type, base_writable := ct_pointer_place(state, state.values[base_value]) if base_place != INVALID_CT_PLACE && types.is_record(base_type, store) { index, field, field_ok := find_struct_field(checker, base_type, expr.name) if !field_ok { return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown struct field '%s'", symbol_text(checker, expr.name)) } return ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Field, index=u32(index)}, field.type, base_writable), field.type, base_writable, ct_flow(.Normal), true } } if !value_ok || flow.kind != .Normal { return INVALID_CT_PLACE, types.INVALID, false, flow, value_ok } base_place, base_type, base_writable, place_flow, place_ok := ct_eval_place(state, expr.left, depth+1) if !place_ok || place_flow.kind != .Normal { return INVALID_CT_PLACE, types.INVALID, false, place_flow, place_ok } index, field, field_ok := find_struct_field(checker, base_type, expr.name) if !field_ok { return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown struct field '%s'", symbol_text(checker, expr.name)) } return ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Field, index=u32(index)}, field.type, base_writable), field.type, base_writable, ct_flow(.Normal), true case .Index: index_value, index_flow, index_ok := ct_eval_expr(state, expr.right, types.USIZE, depth+1) if !index_ok || index_flow.kind != .Normal { return INVALID_CT_PLACE, types.INVALID, false, index_flow, index_ok } index_int, int_ok := ct_integer_value(state, index_value) if !int_ok || index_int < 0 || index_int > i128(0x7fff_ffff) { return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime index must be a non-negative integer") } base_value, flow, value_ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) if !value_ok || flow.kind != .Normal { return INVALID_CT_PLACE, types.INVALID, false, flow, value_ok } if base_value != INVALID_CT_VALUE && int(base_value) < len(state.values) { base := state.values[base_value] if base.kind == .Slice { place, child, writable := ct_slice_element_place(state, base, int(index_int)) if place == INVALID_CT_PLACE { return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime slice index out of bounds") } return place, child, writable, ct_flow(.Normal), true } if base.kind == .Pointer { pointer_item, pointer_ok := types.node(store, base.type) if pointer_ok && pointer_item.kind == .Pointer { if pointer_item.many { place, child, writable := ct_pointer_place(state, base, int(index_int)) if place == INVALID_CT_PLACE { return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime pointer index out of bounds") } return place, child, writable, ct_flow(.Normal), true } array_item, array_ok := types.node(store, pointer_item.child) if array_ok && array_item.kind == .Array { base_place, _, writable := ct_pointer_place(state, base) if int(index_int) >= int(array_item.count) { return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime array index out of bounds") } return ct_extend_place( state, base_place, Ct_Path_Elem{kind=.Index, index=u32(index_int)}, array_item.child, writable && array_item.mutable, ), array_item.child, writable && array_item.mutable, ct_flow(.Normal), true } } } } base_place, base_type, base_writable, place_flow, place_ok := ct_eval_place(state, expr.left, depth+1) if !place_ok || place_flow.kind != .Normal { return INVALID_CT_PLACE, types.INVALID, false, place_flow, place_ok } item, item_ok := types.node(store, base_type) if !item_ok || item.kind != .Array || int(index_int) >= int(item.count) { return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime array index out of bounds") } writable := base_writable && item.mutable return ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Index, index=u32(index_int)}, item.child, writable), item.child, writable, ct_flow(.Normal), true } return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "expression is not addressable at comptime") } ct_unwrap_optional :: proc(state: ^Ct_State, id: Ct_Value_Id, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) { if id == INVALID_CT_VALUE || int(id) >= len(state.values) { return INVALID_CT_VALUE, ct_flow(.Normal), false } value := state.values[id] if value.kind == .Optional_Some { children := ct_child_slice(state, value) if len(children) > 0 { return children[0], ct_flow(.Normal), true } } if value.kind == .None { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime optional unwrap of none") } return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "postfix '?' requires an optional") } ct_eval_unary :: proc(state: ^Ct_State, op: ast.Expr_Kind, id: Ct_Value_Id, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) { if id == INVALID_CT_VALUE || int(id) >= len(state.values) { return INVALID_CT_VALUE, ct_flow(.Normal), false } value := state.values[id] if op == .Not { if value.kind != .Bool { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "'!' requires a bool operand") } return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if value.integer == 0 else 0}), ct_flow(.Normal), true } if value.kind == .Integer { result, overflow := intrinsics.overflow_sub(i128(0), value.integer) if overflow { state.error = .Overflow return INVALID_CT_VALUE, ct_flow(.Normal), false } value.integer = result return ct_add_value(state, value), ct_flow(.Normal), true } if value.kind == .Float { value.float = -value.float return ct_add_value(state, value), ct_flow(.Normal), true } return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "negation requires a signed integer or float") } ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: Ct_Value_Id, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) { if left_id == INVALID_CT_VALUE || right_id == INVALID_CT_VALUE || int(left_id) >= len(state.values) || int(right_id) >= len(state.values) { return INVALID_CT_VALUE, ct_flow(.Normal), false } left := state.values[left_id] right := state.values[right_id] is_compare := op == .Eq || op == .Ne || op == .Lt || op == .Le || op == .Gt || op == .Ge if left.kind == .Bool && right.kind == .Bool { if op != .Eq && op != .Ne { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "bool values only support '==' and '!='") } ok := left.integer == right.integer if op == .Ne { ok = !ok } return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true } if left.kind == .Float || right.kind == .Float { lf := left.float if left.kind == .Float else f64(left.integer) rf := right.float if right.kind == .Float else f64(right.integer) if is_compare { ok := false #partial switch op { case .Eq: ok = lf == rf case .Ne: ok = lf != rf case .Lt: ok = lf < rf case .Le: ok = lf <= rf case .Gt: ok = lf > rf case .Ge: ok = lf >= rf } return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true } result := lf #partial switch op { case .Add: result = lf + rf case .Sub: result = lf - rf case .Mul: result = lf * rf case .Div: result = lf / rf } result_type := types.widest(left.type, right.type) if !types.is_float(result_type, state.checker.target) { result_type = types.F64 } return ct_add_value(state, Ct_Value{kind=.Float, type=result_type, float=result}), ct_flow(.Normal), true } if ct_is_integer_like(left) && ct_is_integer_like(right) { if is_compare { ok := false #partial switch op { case .Eq: ok = left.integer == right.integer case .Ne: ok = left.integer != right.integer case .Lt: ok = left.integer < right.integer case .Le: ok = left.integer <= right.integer case .Gt: ok = left.integer > right.integer case .Ge: ok = left.integer >= right.integer } return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true } if op == .Div { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail( state, .Integer_Division, span, "integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!", ) } value: i128 overflow := false #partial switch op { case .Sub: value, overflow = intrinsics.overflow_sub(left.integer, right.integer) case .Mul: value, overflow = intrinsics.overflow_mul(left.integer, right.integer) case: value, overflow = intrinsics.overflow_add(left.integer, right.integer) } if overflow { state.error = .Overflow return INVALID_CT_VALUE, ct_flow(.Normal), false } result_type := types.widest(left.type, right.type) if !types.is_concrete_integer(result_type) && !types.is_enum(result_type, &state.checker.module.types) { result_type = ct_default_integer_type(value) } return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=value}), ct_flow(.Normal), true } return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands") } ct_eval_division_builtin :: proc( state: ^Ct_State, kind: Division_Builtin, left_id, right_id: Ct_Value_Id, span: source.Span, ) -> (Ct_Value_Id, Ct_Flow, bool) { if left_id == INVALID_CT_VALUE || right_id == INVALID_CT_VALUE || int(left_id) >= len(state.values) || int(right_id) >= len(state.values) { return INVALID_CT_VALUE, ct_flow(.Normal), false } left := state.values[left_id] right := state.values[right_id] result_type := types.widest(left.type, right.type) if !types.is_concrete_scalar(result_type) || types.is_bool(result_type) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail( state, .Not_Comptime, span, "division builtins require compatible numeric operands", ) } left_id, left_ok := ct_coerce_value(state, left_id, result_type, span) right_id, right_ok := ct_coerce_value(state, right_id, result_type, span) if !left_ok || !right_ok { return INVALID_CT_VALUE, ct_flow(.Normal), false } left = state.values[left_id] right = state.values[right_id] if left.kind == .Float && right.kind == .Float { if right.float == 0 { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Div_By_Zero, span, "division builtin denominator is zero") } quotient := left.float / right.float result := quotient #partial switch kind { case .Trunc: result = math.trunc(quotient) case .Floor: result = math.floor(quotient) case .Ceil: result = math.ceil(quotient) case .Exact: result = math.trunc(quotient) if result * right.float != left.float { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Non_Exact, span, "exact division has a remainder") } case .Rem, .Mod: result = left.float - math.trunc(quotient) * right.float if kind == .Mod && result != 0 && (result < 0) != (right.float < 0) { result += right.float } } if types.bits(result_type, state.checker.target) == 32 { result = f64(f32(result)) } return ct_add_value(state, Ct_Value{kind=.Float, type=result_type, float=result}), ct_flow(.Normal), true } if left.kind != .Integer || right.kind != .Integer { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "division builtins require compatible numeric operands") } if right.integer == 0 { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Div_By_Zero, span, "division builtin denominator is zero") } is_quotient := kind == .Trunc || kind == .Floor || kind == .Exact || kind == .Ceil if is_quotient && types.is_signed(result_type, state.checker.target) { minimum := -(i128(1) << u32(types.bits(result_type, state.checker.target)-1)) if left.integer == minimum && right.integer == -1 { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Overflow, span, "signed integer division overflow") } } quotient := left.integer / right.integer remainder := left.integer % right.integer result := quotient #partial switch kind { case .Floor: if remainder != 0 && (left.integer < 0) != (right.integer < 0) { result -= 1 } case .Ceil: if remainder != 0 && (left.integer < 0) == (right.integer < 0) { result += 1 } case .Exact: if remainder != 0 { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Non_Exact, span, "exact division has a remainder") } case .Rem: result = remainder case .Mod: result = remainder if result != 0 && (result < 0) != (right.integer < 0) { result += right.integer } case: } return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=result}), ct_flow(.Normal), true } ct_eval_division_call :: proc( state: ^Ct_State, expr: ast.Expr, kind: Division_Builtin, expected: types.Type, depth: int, ) -> (Ct_Value_Id, Ct_Flow, bool) { checker := state.checker if len(expr.args) != 2 { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf( state, .Not_Comptime, expr.span, "%s expects 2 arguments, got %d", symbol_text(checker, expr.name), len(expr.args), ) } hint := expected if types.is_concrete_scalar(expected) && !types.is_bool(expected) else types.INVALID left_const := is_numeric_constant_expr(checker, expr.args[0]) right_const := is_numeric_constant_expr(checker, expr.args[1]) left, right := INVALID_CT_VALUE, INVALID_CT_VALUE flow := ct_flow(.Normal) ok := false if left_const && !right_const && !types.is_valid(hint) { right, flow, ok = ct_eval_expr(state, expr.args[1], types.INVALID, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } left, flow, ok = ct_eval_expr(state, expr.args[0], state.values[right].type, depth+1) } else { left, flow, ok = ct_eval_expr(state, expr.args[0], hint, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } right_hint := hint if types.is_valid(hint) else state.values[left].type right, flow, ok = ct_eval_expr(state, expr.args[1], right_hint, depth+1) } if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } return ct_eval_division_builtin(state, kind, left, right, expr.span) } ct_scalar_cast :: proc(state: ^Ct_State, id: Ct_Value_Id, target: types.Type, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) { if id == INVALID_CT_VALUE || int(id) >= len(state.values) { return INVALID_CT_VALUE, ct_flow(.Normal), false } value := state.values[id] if !types.is_concrete_scalar(target) || types.is_bool(target) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "scalar cast requires numeric scalar types") } if value.kind == .Float { if types.is_float(target, state.checker.target) { value.type = target return ct_add_value(state, value), ct_flow(.Normal), true } return ct_add_value(state, Ct_Value{kind=.Integer, type=target, integer=i128(value.float)}), ct_flow(.Normal), true } if ct_is_integer_like(value) { if types.is_float(target, state.checker.target) { return ct_add_value(state, Ct_Value{kind=.Float, type=target, float=f64(value.integer)}), ct_flow(.Normal), true } value.kind = .Integer value.type = target return ct_add_value(state, value), ct_flow(.Normal), true } return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "scalar cast requires numeric scalar types") } intern_comptime_string :: proc(checker: ^Checker, text: string) -> u64 { string_id := u64(len(checker.ast_module.strings)) for value, index in checker.ast_module.strings { if value == text { string_id = u64(index) break } } if string_id == u64(len(checker.ast_module.strings)) { append(&checker.ast_module.strings, strings.clone(text, checker.ast_module.allocator)) append(&checker.module.strings, strings.clone(text, checker.allocator)) } return string_id } ct_reflection_string :: proc(state: ^Ct_State, text: string) -> Ct_Value_Id { checker := state.checker string_id := intern_comptime_string(checker, text) return ct_add_value(state, Ct_Value{ kind=.String, type=types.slice(&checker.module.types, types.U8, false), index=string_id, }) } ct_value_bytes :: proc(state: ^Ct_State, id: Ct_Value_Id) -> (string, bool) { if id == INVALID_CT_VALUE || int(id) >= len(state.values) { return "", false } value := state.values[id] if value.kind == .String { if value.index < u64(len(state.checker.ast_module.strings)) { return state.checker.ast_module.strings[value.index], true } return "", false } item, ok := types.container(value.type, &state.checker.module.types) if !ok || item.child != types.U8 || item.mutable || (value.kind != .Array && value.kind != .Slice) { return "", false } count := int(value.count) bytes := make([]u8, count, state.checker.allocator) defer delete(bytes, state.checker.allocator) for index in 0..= len(children) { return "", false } child = children[index] } else { place, _, place_ok := ct_slice_element_place(state, value, index) if !place_ok { return "", false } child, place_ok = ct_place_get(state, place) if !place_ok { return "", false } } integer, integer_ok := ct_integer_value(state, child) if !integer_ok || integer < 0 || integer > 255 { return "", false } bytes[index] = u8(integer) } string_id := intern_comptime_string(state.checker, string(bytes)) return state.checker.ast_module.strings[string_id], true } ct_struct_value :: proc(state: ^Ct_State, value_type: types.Type, children: []Ct_Value_Id) -> Ct_Value_Id { start := u32(len(state.children)) append(&state.children, ..children) return ct_add_value(state, Ct_Value{kind=.Struct, type=value_type, start=start, count=u32(len(children))}) } ct_typeinfo_value :: proc(state: ^Ct_State, target: types.Type, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) { checker := state.checker store := &checker.module.types typeinfo_type := std_named_type(checker, "@std/meta", "TypeInfo") fieldinfo_type := std_named_type(checker, "@std/meta", "FieldInfo") recordinfo_type := std_named_type(checker, "@std/meta", "RecordInfo") enuminfo_type := std_named_type(checker, "@std/meta", "EnumInfo") layout_type := std_named_type(checker, "@std/meta", "Layout") if !types.is_valid(typeinfo_type) || !types.is_valid(fieldinfo_type) || !types.is_valid(recordinfo_type) || !types.is_valid(enuminfo_type) || !types.is_valid(layout_type) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail( state, .Not_Comptime, span, "typeinfo! requires importing @std/meta", ) } resolved := types.resolve_alias(target, store) item, item_ok := types.node(store, resolved) tag := "invalid" if !item_ok { if types.is_void(resolved) { tag = "void" } else if types.is_anyopaque(resolved) { tag = "anyopaque" } else if types.is_bool(resolved) { tag = "bool" } else if types.is_concrete_integer(resolved) { tag = "integer" } else if types.is_float(resolved, checker.target) { tag = "float" } } else { #partial switch item.kind { case .Array: tag = "array" case .Pointer: tag = "pointer" case .Slice: tag = "slice" case .Range: tag = "range" case .Optional: tag = "optional" case .Function: tag = "function" case .Enum: tag = "enum" case .Struct: tag = "record" case .Union: tag = "union" case .Fallible: tag = "fallible" case .Distinct: tag = "distinct" case .Alias: tag = "invalid" case: tag = "invalid" } } variant_index, _, variant_ok := find_struct_field(checker, typeinfo_type, symbol.intern(checker.symbols, tag)) if !variant_ok { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "@std/meta TypeInfo is malformed") } if tag == "enum" { members := types.enum_members_for(store, resolved) field_values := make([]Ct_Value_Id, len(members), checker.allocator) defer delete(field_values, checker.allocator) for member, index in members { name_value := ct_reflection_string(state, symbol_text(checker, symbol.Id(member.name))) type_value := ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(resolved)}) index_value := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)}) field_values[index] = ct_struct_value( state, fieldinfo_type, []Ct_Value_Id{name_value, type_value, index_value}, ) } fields_start := u32(len(state.children)) append(&state.children, ..field_values) fields_type := types.array(store, fieldinfo_type, u64(len(field_values)), false) fields_value := ct_add_value(state, Ct_Value{ kind=.Array, type=fields_type, start=fields_start, count=u32(len(field_values)), }) enum_value := ct_struct_value(state, enuminfo_type, []Ct_Value_Id{fields_value}) payload_start := u32(len(state.children)) append(&state.children, enum_value) return ct_add_value(state, Ct_Value{ kind=.Struct, type=typeinfo_type, start=payload_start, count=1, active=i64(variant_index), }), ct_flow(.Normal), true } if tag != "record" { start := u32(len(state.children)) return ct_add_value(state, Ct_Value{ kind=.Struct, type=typeinfo_type, start=start, count=0, active=i64(variant_index), }), ct_flow(.Normal), true } fields := types.fields_for(store, resolved) field_values := make([]Ct_Value_Id, len(fields), checker.allocator) defer delete(field_values, checker.allocator) for field, index in fields { name := "" if item.tuple { name = fmt.aprintf("%d", index, allocator=checker.allocator) } else { name = symbol_text(checker, symbol.Id(field.name)) } name_value := ct_reflection_string(state, name) if item.tuple { delete(name, checker.allocator) } type_value := ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(field.type)}) index_value := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)}) children := []Ct_Value_Id{name_value, type_value, index_value} field_values[index] = ct_struct_value(state, fieldinfo_type, children) } fields_start := u32(len(state.children)) append(&state.children, ..field_values) fields_type := types.array(store, fieldinfo_type, u64(len(field_values)), false) fields_value := ct_add_value(state, Ct_Value{ kind=.Array, type=fields_type, start=fields_start, count=u32(len(field_values)), }) name_optional_type := types.optional(store, types.slice(store, types.U8, false)) record_name := ct_add_value(state, Ct_Value{kind=.None, type=name_optional_type}) if item.name != 0 { name_value := ct_reflection_string(state, symbol_text(checker, symbol.Id(item.name))) name_start := u32(len(state.children)) append(&state.children, name_value) record_name = ct_add_value(state, Ct_Value{ kind=.Optional_Some, type=name_optional_type, start=name_start, count=1, }) } tuple_value := ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if item.tuple else 0}) layout_name := symbol.intern(checker.symbols, "c" if item.c_layout else "auto") layout_member, layout_ok := find_enum_member(checker, layout_type, layout_name) if !layout_ok { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "@std/meta Layout is malformed") } layout_value := ct_add_value(state, Ct_Value{kind=.Integer, type=layout_type, integer=layout_member.value}) record_children := []Ct_Value_Id{record_name, fields_value, tuple_value, layout_value} record_value := ct_struct_value(state, recordinfo_type, record_children) payload_start := u32(len(state.children)) append(&state.children, record_value) return ct_add_value(state, Ct_Value{ kind=.Struct, type=typeinfo_type, start=payload_start, count=1, active=i64(variant_index), }), ct_flow(.Normal), true } ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) { checker := state.checker if expr.left != ast.INVALID_EXPR { callee, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } if callee == INVALID_CT_VALUE || int(callee) >= len(state.values) || state.values[callee].kind != .Function { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime function-pointer call requires a known function value") } return ct_eval_template_call(state, ast.Function_Id(u32(state.values[callee].index)), expr.args, expr.span, expected, depth+1) } if is_intrinsic_call(checker, expr, "compile_error") { message := "compile_error! requires one comptime string argument" if len(expr.args) == 1 { value, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1) if ok && flow.kind == .Normal { if text, text_ok := ct_value_bytes(state, value); text_ok { message = text } } } return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, message) } if is_intrinsic_call(checker, expr, "field") { if len(expr.args) != 2 { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "field! expects 2 arguments, got %d", len(expr.args)) } if enum_type, ok := resolve_type_argument(checker, expr.args[0], state.pkg, state.file); ok && types.is_enum(enum_type, &checker.module.types) { name_value, name_flow, name_ok := ct_eval_expr(state, expr.args[1], types.INVALID, depth+1) if !name_ok || name_flow.kind != .Normal { return INVALID_CT_VALUE, name_flow, name_ok } name, text_ok := ct_value_bytes(state, name_value) if !text_ok { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "field! name must be a comptime immutable byte string") } member, member_ok := find_enum_member(checker, enum_type, symbol.intern(checker.symbols, name)) if !member_ok { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown enum member '%s'", name) } return ct_add_value(state, Ct_Value{kind=.Integer, type=enum_type, integer=member.value}), ct_flow(.Normal), true } base, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } name_value, name_flow, name_ok := ct_eval_expr(state, expr.args[1], types.INVALID, depth+1) if !name_ok || name_flow.kind != .Normal { return INVALID_CT_VALUE, name_flow, name_ok } name, text_ok := ct_value_bytes(state, name_value) if !text_ok { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "field! name must be a comptime immutable byte string") } if index, numeric := canonical_decimal_index(name); numeric { return ct_eval_tuple_field_value(state, base, index, expr.span) } return ct_eval_field_value(state, base, symbol.intern(checker.symbols, name), expr.span) } if is_intrinsic_call(checker, expr, "typeinfo") { if len(expr.args) != 1 { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "typeinfo! expects 1 argument, got %d", len(expr.args)) } target, ok := resolve_type_argument(checker, expr.args[0], state.pkg, state.file) if !ok { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "typeinfo! argument must be a type") } return ct_typeinfo_value(state, target, expr.span) } if builtin := type_builtin_call(checker, expr); builtin != .None { if len(expr.args) != 1 { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "%s! expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args)) } target, target_ok := resolve_type_argument(checker, expr.args[0], state.pkg, state.file) if !target_ok { label := "layout" if builtin == .Size_Of || builtin == .Align_Of else "integer bound" return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, checker.ast_module.exprs[expr.args[0]].span, "%s target must be a type", label) } if (builtin == .Size_Of || builtin == .Align_Of) && !valid_layout_type(checker, target) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, checker.ast_module.exprs[expr.args[0]].span, "layout target must be a sized runtime value type, got %s", type_label(checker, target)) } if (builtin == .Min_Value || builtin == .Max_Value) && !types.is_concrete_integer(target) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, checker.ast_module.exprs[expr.args[0]].span, "integer bound target must be a concrete integer type, got %s", type_label(checker, target)) } result_type := types.USIZE if builtin == .Size_Of || builtin == .Align_Of else target return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=type_builtin_value(checker, builtin, target)}), ct_flow(.Normal), true } if builtin := division_builtin_call(checker, expr); builtin != .None { return ct_eval_division_call(state, expr, builtin, expected, depth+1) } if expr.intrinsic { if symbol.is_valid(expr.qualifier) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "intrinsic calls must be unqualified") } return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown intrinsic '%s!'", symbol_text(checker, expr.name)) } target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false) if !available { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "unavailable function package") } template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, state.file)) if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) { if !symbol.is_valid(expr.qualifier) { if index, ok := ct_find_binding_index(state, expr.name); ok { value := ct_binding_value(state, index) if value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Function { return ct_eval_template_call(state, ast.Function_Id(u32(state.values[value].index)), expr.args, expr.span, expected, depth+1) } } } global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, state.file)) if global != ast.INVALID_GLOBAL && int(global) < len(checker.ast_module.globals) { g := checker.ast_module.globals[global] if !g.external && g.immutable && !g.writable { global_expected := type_from_syntax(checker, g.type, g.pkg, g.file) value, flow, ok := ct_eval_expr(state, g.expr, global_expected, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } if value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Function { return ct_eval_template_call(state, ast.Function_Id(u32(state.values[value].index)), expr.args, expr.span, expected, depth+1) } } } return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unresolved function '%s'", symbol_text(checker, expr.name)) } return ct_eval_template_call(state, template, expr.args, expr.span, expected, depth+1) } ct_eval_template_call :: proc( state: ^Ct_State, template: ast.Function_Id, args: []ast.Expr_Id, span: source.Span, expected: types.Type, depth: int, ) -> (Ct_Value_Id, Ct_Flow, bool) { checker := state.checker if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) { return INVALID_CT_VALUE, ct_flow(.Normal), false } function := checker.ast_module.functions[template] if !function.has_body || len(function.unsupported_reason) > 0 { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "function '%s' is runtime-only", symbol_text(checker, function.name)) } if !valid_call_arity(function, len(args)) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "function '%s' arity mismatch", symbol_text(checker, function.name)) } comptime_values, comptime_ok := collect_comptime_values(checker, function, args, state.pkg, state.file, false, checker.current_comptime_values) defer delete(comptime_values, checker.allocator) if !comptime_ok { return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "invalid comptime argument for '%s'", symbol_text(checker, function.name)) } previous_comptime := checker.current_comptime_values checker.current_comptime_values = comptime_values defer checker.current_comptime_values = previous_comptime result_type := function_channel_type(checker, function) runtime_values: [dynamic]Ct_Value_Id runtime_values.allocator = checker.allocator runtime_types: [dynamic]types.Type runtime_types.allocator = checker.allocator runtime_names: [dynamic]symbol.Id runtime_names.allocator = checker.allocator defer { delete(runtime_values) delete(runtime_types) delete(runtime_names) } for param, index in function.params { if param.comptime_value { continue } param_type := type_from_syntax(checker, param.type, function.pkg, function.file) if index >= len(args) { return INVALID_CT_VALUE, ct_flow(.Normal), false } value, flow, ok := ct_eval_expr(state, args[index], param_type, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } value, ok = ct_coerce_value(state, value, param_type, checker.ast_module.exprs[args[index]].span) if !ok { return INVALID_CT_VALUE, ct_flow(.Normal), false } if ct_value_contains_undefined(state, value) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail( state, .Not_Comptime, checker.ast_module.exprs[args[index]].span, "cannot pass an undefined value at comptime", ) } append(&runtime_values, value) append(&runtime_types, param_type) append(&runtime_names, param.name) } previous_pkg := state.pkg previous_file := state.file previous_result := state.result state.pkg = function.pkg state.file = function.file state.result = result_type defer { state.pkg = previous_pkg state.file = previous_file state.result = previous_result } param_start := len(state.bindings) for value, index in runtime_values { ct_bind_value(state, runtime_names[index], runtime_types[index], value, false) } flow, ok := ct_exec_statements(state, function.body, false, depth+1) ct_pop_bindings(state, param_start) if !ok { return INVALID_CT_VALUE, ct_flow(.Normal), false } if flow.kind != .Return { if flow.kind == .Normal && types.is_void(result_type) { result := ct_add_value(state, Ct_Value{kind=.Void, type=types.VOID}) return result, ct_flow(.Normal), true } if flow.kind == .Normal && types.kind(result_type, &checker.module.types) == .Fallible && types.is_void(types.fallible_success(result_type, &checker.module.types)) { result := ct_make_fallible(state, result_type, INVALID_CT_VALUE, false) return result, ct_flow(.Normal), true } return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "comptime function '%s' did not return a value", symbol_text(checker, function.name)) } result := flow.value if ct_value_contains_undefined(state, result) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail( state, .Not_Comptime, span, "comptime function returned an undefined value", ) } if ct_value_references_dead_storage(state, result) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime function returned a pointer to expired storage") } if types.is_valid(expected) { return ct_coerce_expr_value(state, result, expected, span) } return result, ct_flow(.Normal), true } ct_clone_value :: proc(dst, src: ^Ct_State, id: Ct_Value_Id) -> Ct_Value_Id { if id == INVALID_CT_VALUE || int(id) >= len(src.values) { return INVALID_CT_VALUE } value := src.values[id] children := ct_child_slice(src, value) if len(children) > 0 { value.start = u32(len(dst.children)) for child in children { append(&dst.children, ct_clone_value(dst, src, child)) } } return ct_add_value(dst, value) } Ct_Clone_Context :: struct { dst, src: ^Ct_State, values: []Ct_Value_Id, cells: []Ct_Cell_Id, places: []Ct_Place_Id, } ct_clone_graph_value :: proc(ctx: ^Ct_Clone_Context, id: Ct_Value_Id) -> Ct_Value_Id { if id == INVALID_CT_VALUE || int(id) >= len(ctx.src.values) { return INVALID_CT_VALUE } if ctx.values[id] != INVALID_CT_VALUE { return ctx.values[id] } value := ctx.src.values[id] children := ct_child_slice(ctx.src, value) value.start = 0 value.count = 0 dst_id := ct_add_value(ctx.dst, value) ctx.values[id] = dst_id if len(children) > 0 { cloned := make([]Ct_Value_Id, len(children), ctx.dst.checker.allocator) defer delete(cloned, ctx.dst.checker.allocator) for child, index in children { cloned[index] = ct_clone_graph_value(ctx, child) } start := u32(len(ctx.dst.children)) append(&ctx.dst.children, ..cloned) ctx.dst.values[dst_id].start = start ctx.dst.values[dst_id].count = u32(len(children)) } if value.kind == .Pointer || value.kind == .Slice { ctx.dst.values[dst_id].index = u64(ct_clone_graph_place(ctx, Ct_Place_Id(value.index))) } return dst_id } ct_clone_graph_cell :: proc(ctx: ^Ct_Clone_Context, id: Ct_Cell_Id) -> Ct_Cell_Id { if id == INVALID_CT_CELL || int(id) >= len(ctx.src.cells) { return INVALID_CT_CELL } if ctx.cells[id] != INVALID_CT_CELL { return ctx.cells[id] } cell := ctx.src.cells[id] cell.value = INVALID_CT_VALUE dst_id := ct_cell_id(len(ctx.dst.cells)) append(&ctx.dst.cells, cell) ctx.cells[id] = dst_id ctx.dst.cells[dst_id].value = ct_clone_graph_value(ctx, ctx.src.cells[id].value) return dst_id } ct_clone_graph_place :: proc(ctx: ^Ct_Clone_Context, id: Ct_Place_Id) -> Ct_Place_Id { if id == INVALID_CT_PLACE || int(id) >= len(ctx.src.places) { return INVALID_CT_PLACE } if ctx.places[id] != INVALID_CT_PLACE { return ctx.places[id] } place := ctx.src.places[id] path := ct_place_path(ctx.src, place) place.start = u32(len(ctx.dst.paths)) place.count = u32(len(path)) append(&ctx.dst.paths, ..path) place.cell = INVALID_CT_CELL dst_id := Ct_Place_Id(len(ctx.dst.places)) append(&ctx.dst.places, place) ctx.places[id] = dst_id ctx.dst.places[dst_id].cell = ct_clone_graph_cell(ctx, ctx.src.places[id].cell) return dst_id } ct_clone_graph :: proc(dst, src: ^Ct_State, id: Ct_Value_Id) -> Ct_Value_Id { ctx := Ct_Clone_Context{ dst=dst, src=src, values=make([]Ct_Value_Id, len(src.values), dst.checker.allocator), cells=make([]Ct_Cell_Id, len(src.cells), dst.checker.allocator), places=make([]Ct_Place_Id, len(src.places), dst.checker.allocator), } defer { delete(ctx.values, dst.checker.allocator) delete(ctx.cells, dst.checker.allocator) delete(ctx.places, dst.checker.allocator) } for &value in ctx.values { value = INVALID_CT_VALUE } for &cell in ctx.cells { cell = INVALID_CT_CELL } for &place in ctx.places { place = INVALID_CT_PLACE } return ct_clone_graph_value(&ctx, id) } store_static_binding :: proc(checker: ^Checker, source: ^Ct_State, id: Ct_Value_Id, name: symbol.Id) -> Static_Binding { value := ct_clone_graph(&checker.static_state, source, id) value_type := types.INVALID if value != INVALID_CT_VALUE && int(value) < len(checker.static_state.values) { value_type = checker.static_state.values[value].type } return Static_Binding{name=name, type=value_type, value=value} } ct_write_comptime_key :: proc(state: ^Ct_State, id: Ct_Value_Id, builder: ^strings.Builder, depth := 0) -> bool { if depth > 256 || id == INVALID_CT_VALUE || int(id) >= len(state.values) { return false } value := state.values[id] fmt.sbprintf(builder, "t%d:", value.type) #partial switch value.kind { case .Integer: fmt.sbprintf(builder, "i%d;", value.integer) return true case .Float: if types.bits(value.type, state.checker.target) == 32 { fmt.sbprintf(builder, "f%08x;", transmute(u32)f32(value.float)) } else { fmt.sbprintf(builder, "f%016x;", transmute(u64)value.float) } return true case .Bool: fmt.sbprintf(builder, "b%d;", value.integer) return true case .Type: fmt.sbprintf(builder, "y%d;", value.index) return true case .String: if value.index >= u64(len(state.checker.ast_module.strings)) { return false } text := state.checker.ast_module.strings[value.index] fmt.sbprintf(builder, "s%d:", len(text)) hex := "0123456789abcdef" for byte in transmute([]byte)text { strings.write_byte(builder, hex[byte>>4]) strings.write_byte(builder, hex[byte&0xf]) } strings.write_byte(builder, ';') return true case .Slice: item, item_ok := types.container(value.type, &state.checker.module.types) if !item_ok || item.kind != .Slice || item.mutable || item.child != types.U8 { return false } text, text_ok := ct_value_bytes(state, id) if !text_ok { return false } fmt.sbprintf(builder, "s%d:", len(text)) hex := "0123456789abcdef" for byte in transmute([]byte)text { strings.write_byte(builder, hex[byte>>4]) strings.write_byte(builder, hex[byte&0xf]) } strings.write_byte(builder, ';') return true case .Array: children := ct_child_slice(state, value) fmt.sbprintf(builder, "a%d[", len(children)) for child in children { if !ct_write_comptime_key(state, child, builder, depth+1) { return false } } strings.write_string(builder, "];") return true case .Struct: if types.is_union(value.type, &state.checker.module.types) && !types.is_tagged_union(value.type, &state.checker.module.types) { return false } children := ct_child_slice(state, value) fmt.sbprintf(builder, "r%d:%d[", value.active, len(children)) for child in children { if child != INVALID_CT_VALUE && !ct_write_comptime_key(state, child, builder, depth+1) { return false } } strings.write_string(builder, "];") return true case .None: strings.write_string(builder, "n;") return true case .Optional_Some: children := ct_child_slice(state, value) if len(children) != 1 || !ct_write_comptime_key(state, children[0], builder, depth+1) { return false } strings.write_string(builder, "o;") return true case .Invalid, .Void, .Undefined, .Range, .Pointer, .Function, .Fallible: return false } return false } eval_static_comptime_value :: proc( checker: ^Checker, name: symbol.Id, expr: ast.Expr_Id, declared: types.Type, pkg: ast.Package_Id, file: ast.File_Id, values: []Comptime_Value = nil, diagnose := false, ) -> (Comptime_Value, bool) { state := ct_state_make(checker, pkg, file, values=values, diagnose=false) defer ct_state_destroy(&state) id, flow, ok := ct_eval_expr(&state, expr, declared, 0) if ok && flow.kind == .Normal { id, ok = ct_coerce_value(&state, id, declared, checker.ast_module.exprs[expr].span) } if !ok || flow.kind != .Normal || ct_value_contains_undefined(&state, id) { if diagnose { source.add( checker.diagnostics, checker.ast_module.exprs[expr].span, "comptime argument has no stable comptime identity", ) } return {}, false } builder := strings.builder_make(checker.allocator) defer strings.builder_destroy(&builder) if !ct_write_comptime_key(&state, id, &builder) { if diagnose { source.add( checker.diagnostics, checker.ast_module.exprs[expr].span, "comptime argument has no stable comptime identity", ) } return {}, false } key_view := strings.to_string(builder) static_value := INVALID_CT_VALUE key := "" for existing, index in checker.comptime_keys { if existing == key_view { key = existing static_value = checker.comptime_static_values[index] break } } if static_value == INVALID_CT_VALUE { key = strings.clone(key_view, checker.allocator) static_value = ct_clone_graph(&checker.static_state, &state, id) append(&checker.comptime_keys, key) append(&checker.comptime_static_values, static_value) } return Comptime_Value{ name=name, type=declared, static_value=static_value, key=key, fingerprint=hash.fnv64a(transmute([]byte)key), kind=.Static, }, true } ct_eval_try_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) { if state.defer_depth > 0 { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "cannot 'try' inside a 'defer'") } checker := state.checker channel, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } if channel == INVALID_CT_VALUE || int(channel) >= len(state.values) { return INVALID_CT_VALUE, ct_flow(.Normal), false } value := state.values[channel] if value.kind != .Fallible { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "'try' requires a fallible expression") } children := ct_child_slice(state, value) payload := INVALID_CT_VALUE if len(children) > 0 { payload = children[0] } if value.active == 0 { return payload, ct_flow(.Normal), true } enclosing_success := types.fallible_success(state.result, &checker.module.types) enclosing_error := types.fallible_error(state.result, &checker.module.types) if !types.is_valid(enclosing_success) || !types.is_valid(enclosing_error) { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "'try' requires an enclosing fallible function") } error_payload := payload if payload != INVALID_CT_VALUE { coerce_ok: bool error_payload, coerce_ok = ct_coerce_value(state, payload, enclosing_error, expr.span) if !coerce_ok { return INVALID_CT_VALUE, ct_flow(.Normal), false } } start := u32(len(state.children)) append(&state.children, error_payload) result := ct_add_value(state, Ct_Value{kind=.Fallible, type=state.result, start=start, count=1, active=1}) return INVALID_CT_VALUE, ct_flow(.Return, result), true } ct_eval_catch_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) { checker := state.checker channel, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) if !ok || flow.kind != .Normal { return INVALID_CT_VALUE, flow, ok } value := state.values[channel] if value.kind != .Fallible { return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "'catch' requires a fallible expression") } success := types.fallible_success(value.type, &checker.module.types) children := ct_child_slice(state, value) payload := INVALID_CT_VALUE if len(children) > 0 { payload = children[0] } if value.active == 0 { return payload, ct_flow(.Normal), true } if expr.right != ast.INVALID_EXPR { return ct_eval_expr(state, expr.right, success, depth+1) } scope_start := len(state.bindings) if symbol.is_valid(expr.name) && expr.name != checker.sink_symbol && payload != INVALID_CT_VALUE { error_type := types.fallible_error(value.type, &checker.module.types) ct_bind_value(state, expr.name, error_type, payload, false) } handler, handler_ok := ct_exec_statements(state, expr.body, true, depth+1) ct_pop_bindings(state, scope_start) if !handler_ok { return INVALID_CT_VALUE, handler, false } if handler.kind == .Yield { if types.is_valid(expected) { return ct_coerce_expr_value(state, handler.value, expected, expr.span) } return handler.value, ct_flow(.Normal), true } if handler.kind == .Normal && types.is_void(success) { value := ct_add_value(state, Ct_Value{kind=.Void, type=types.VOID}) return value, ct_flow(.Normal), true } return INVALID_CT_VALUE, handler, ct_fail(state, .Not_Comptime, expr.span, "catch block must yield a value") } ct_make_fallible :: proc(state: ^Ct_State, result_type: types.Type, payload: Ct_Value_Id, error_path: bool) -> Ct_Value_Id { start := u32(len(state.children)) append(&state.children, payload) return ct_add_value(state, Ct_Value{ kind=.Fallible, type=result_type, start=start, count=1, active=1 if error_path else 0, }) } ct_return_value :: proc(state: ^Ct_State, expr_id: ast.Expr_Id, span: source.Span, depth: int) -> (Ct_Flow, bool) { checker := state.checker if types.is_void(state.result) { if expr_id != ast.INVALID_EXPR { return ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "void function cannot return a value") } return ct_flow(.Return, ct_add_value(state, Ct_Value{kind=.Void, type=types.VOID})), true } if types.kind(state.result, &checker.module.types) == .Fallible { success := types.fallible_success(state.result, &checker.module.types) error_type := types.fallible_error(state.result, &checker.module.types) if expr_id == ast.INVALID_EXPR { if types.is_void(success) { return ct_flow(.Return, ct_make_fallible(state, state.result, INVALID_CT_VALUE, false)), true } return ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "fallible return requires a value") } expr := checker.ast_module.exprs[expr_id] error_path := false expected := success if expr.kind == .Enum_Literal && types.sum_has_name(&checker.module.types, error_type, u32(expr.name)) { error_path = true expected = error_type } value, flow, ok := ct_eval_expr(state, expr_id, expected, depth+1) if !ok || flow.kind != .Normal { return flow, ok } value, ok = ct_coerce_value(state, value, expected, span) if !ok { return ct_flow(.Normal), false } return ct_flow(.Return, ct_make_fallible(state, state.result, value, error_path)), true } if expr_id == ast.INVALID_EXPR { return ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "non-void function must return a value") } value, flow, ok := ct_eval_expr(state, expr_id, state.result, depth+1) if !ok || flow.kind != .Normal { return flow, ok } value, ok = ct_coerce_value(state, value, state.result, span) if !ok { return ct_flow(.Normal), false } return ct_flow(.Return, value), true } ct_exec_statements :: proc( state: ^Ct_State, statements: []ast.Stmt_Id, yield_returns: bool, depth := 0, ) -> (Ct_Flow, bool) { checker := state.checker if depth > 128 { return ct_flow(.Normal), ct_fail(state, .Not_Comptime, source.Span{}, "comptime evaluation exceeded recursion depth") } scope_start := len(state.bindings) defer_start := len(state.defers) defer { ct_pop_bindings(state, scope_start) resize(&state.defers, defer_start) } for statement_id in statements { if statement_id == ast.INVALID_STMT || int(statement_id) >= len(checker.ast_module.statements) { return ct_flow(.Normal), false } statement := checker.ast_module.statements[statement_id] if !ct_step(state, statement.span) { return ct_flow(.Normal), false } flow := ct_flow(.Normal) ok := true #partial switch statement.kind { case .Declaration: if statement.expr == ast.INVALID_EXPR { value_flow, value_ok := ct_exec_statements(state, statement.body, true, depth+1) ok = value_ok if ok && value_flow.kind == .Yield { value := value_flow.value declared := type_from_syntax(checker, statement.type, state.pkg, state.file) if types.is_valid(declared) && !types.is_void(declared) { value, ok = ct_coerce_value(state, value, declared, statement.span) } if ok && statement.name != checker.sink_symbol { ct_bind_value(state, statement.name, state.values[value].type, value, !statement.immutable) } } else if ok { ok = ct_fail(state, .Not_Comptime, statement.span, "comptime value block must yield") } } else { declared := type_from_syntax(checker, statement.type, state.pkg, state.file) expected := declared if types.is_valid(declared) && !types.is_void(declared) else types.INVALID value, expr_flow, expr_ok := ct_eval_expr(state, statement.expr, expected, depth+1) ok = expr_ok flow = expr_flow if ok && flow.kind == .Normal { if types.is_valid(expected) { value, ok = ct_coerce_value(state, value, expected, statement.span) } if ok && statement.name != checker.sink_symbol { ct_bind_value(state, statement.name, state.values[value].type, value, !statement.immutable) } } } case .Assignment: flow, ok = ct_exec_assignment(state, statement, depth+1) case .Expression: _, flow, ok = ct_eval_expr(state, statement.expr, types.INVALID, depth+1) case .Return: if yield_returns { ok = ct_fail(state, .Not_Comptime, statement.span, "'return' is not valid in this comptime block") } else if statement.value_control_flow { value_flow, value_ok := ct_exec_statements(state, statement.body, true, depth+1) ok = value_ok if ok && value_flow.kind == .Yield { value := value_flow.value if types.kind(state.result, &checker.module.types) == .Fallible { success := types.fallible_success(state.result, &checker.module.types) value, ok = ct_coerce_value(state, value, success, statement.span) if ok { flow = ct_flow(.Return, ct_make_fallible(state, state.result, value, false)) } } else { value, ok = ct_coerce_value(state, value, state.result, statement.span) if ok { flow = ct_flow(.Return, value) } } } } else { flow, ok = ct_return_value(state, statement.expr, statement.span, depth+1) } case .Yield: if !yield_returns { ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' is only valid in a comptime value block") } else if statement.value_control_flow { flow, ok = ct_exec_statements(state, statement.body, true, depth+1) if ok && flow.kind == .Yield && types.is_void(state.values[flow.value].type) { ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' expression must produce a non-void value") } } else { value, expr_flow, expr_ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1) ok = expr_ok flow = expr_flow if ok && flow.kind == .Normal { if types.is_void(state.values[value].type) { ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' expression must produce a non-void value") } else { flow = ct_flow(.Yield, value, statement.label) } } } case .If: flow, ok = ct_exec_if(state, statement, yield_returns, depth+1) case .While: flow, ok = ct_exec_while(state, statement, yield_returns, depth+1) case .For: flow, ok = ct_exec_for(state, statement, yield_returns, depth+1) case .Break: flow = ct_flow(.Break, INVALID_CT_VALUE, statement.label) case .Continue: flow = ct_flow(.Continue, INVALID_CT_VALUE, statement.label) case .Block: flow, ok = ct_exec_statements(state, statement.body, yield_returns, depth+1) case .Defer: if statement.error_only && types.kind(state.result, &checker.module.types) != .Fallible { ok = ct_fail(state, .Not_Comptime, statement.span, "'errdefer' requires an enclosing fallible function") } else { capture := symbol.INVALID if len(statement.captures) > 0 { capture = statement.captures[0] } append(&state.defers, Ct_Defer{ statement=statement.update, error_only=statement.error_only, capture=capture, }) } case .Match: flow, ok = ct_exec_match(state, statement, yield_returns, depth+1) case .Match_Arm: ok = ct_fail(state, .Not_Comptime, statement.span, "unexpected match arm outside 'match'") case .Invalid: ok = false } if !ok { return flow, false } if flow.kind != .Normal { if !ct_flush_defers(state, defer_start, flow, depth+1) { return flow, false } return flow, true } } if !ct_flush_defers(state, defer_start, ct_flow(.Normal), depth+1) { return ct_flow(.Normal), false } return ct_flow(.Normal), true } ct_flush_defers :: proc(state: ^Ct_State, start: int, exit: Ct_Flow, depth: int) -> bool { error_exit := false error_value := INVALID_CT_VALUE if exit.kind == .Return && exit.value != INVALID_CT_VALUE && int(exit.value) < len(state.values) { returned := state.values[exit.value] if returned.kind == .Fallible && returned.active != 0 { error_exit = true children := ct_child_slice(state, returned) if len(children) > 0 { error_value = children[0] } } } for index := len(state.defers) - 1; index >= start; index -= 1 { entry := state.defers[index] if entry.error_only && !error_exit { continue } binding_start := len(state.bindings) bound_capture := false if entry.error_only && symbol.is_valid(entry.capture) && entry.capture != state.checker.sink_symbol && error_value != INVALID_CT_VALUE { ct_bind_value(state, entry.capture, state.values[error_value].type, error_value, false) bound_capture = true } stmt := [1]ast.Stmt_Id{entry.statement} state.defer_depth += 1 flow, ok := ct_exec_statements(state, stmt[:], false, depth+1) state.defer_depth -= 1 if bound_capture { ct_pop_bindings(state, binding_start) } if !ok || flow.kind != .Normal { return false } } return true } ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) -> (Ct_Flow, bool) { checker := state.checker name := statement.name target_expr := statement.target if statement.target != ast.INVALID_EXPR { target := checker.ast_module.exprs[statement.target] if target.kind == .Name && !symbol.is_valid(target.qualifier) { name = target.name } } if name == checker.sink_symbol { if statement.expr == ast.INVALID_EXPR { flow, ok := ct_exec_statements(state, statement.body, true, depth+1) return ct_flow(.Normal), ok && (flow.kind == .Yield || flow.kind == .Normal) } _, flow, ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1) return flow, ok } place := INVALID_CT_PLACE expected := types.INVALID writable := false if target_expr != ast.INVALID_EXPR { place_flow: Ct_Flow place_ok := false place, expected, writable, place_flow, place_ok = ct_eval_place(state, target_expr, depth+1) if !place_ok || place_flow.kind != .Normal { return place_flow, place_ok } } else { index, found := ct_find_binding_index(state, name) if !found { return ct_flow(.Normal), ct_failf(state, .Not_Comptime, statement.span, "cannot assign unresolved comptime local '%s'", symbol_text(checker, name)) } place = ct_binding_place(state, index) expected = state.bindings[index].type writable = state.bindings[index].mutable } if !writable { return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime assignment target is not writable") } value := INVALID_CT_VALUE flow := ct_flow(.Normal) ok := true if statement.expr == ast.INVALID_EXPR { flow, ok = ct_exec_statements(state, statement.body, true, depth+1) if ok && flow.kind == .Yield { value = flow.value flow = ct_flow(.Normal) } } else { value, flow, ok = ct_eval_expr(state, statement.expr, expected, depth+1) } if !ok || flow.kind != .Normal { return flow, ok } if statement.assignment_op != .Set { current, current_ok := ct_place_get(state, place) if !current_ok { return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime assignment target no longer points to live storage") } op := ast.Expr_Kind.Add #partial switch statement.assignment_op { case .Sub: op = .Sub case .Mul: op = .Mul case .Div: op = .Div case: op = .Add } bin_flow: Ct_Flow value, bin_flow, ok = ct_eval_binary(state, op, current, value, statement.span) if !ok || bin_flow.kind != .Normal { return bin_flow, ok } } value, ok = ct_coerce_value(state, value, expected, statement.span) if !ok { return ct_flow(.Normal), false } if !ct_place_set(state, place, value) { return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime assignment target is not writable") } return ct_flow(.Normal), true } ct_exec_if :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) { checker := state.checker if len(statement.captures) == 0 { condition, flow, ok := ct_eval_expr(state, statement.expr, types.BOOL, depth+1) if !ok || flow.kind != .Normal { return flow, ok } value, bool_ok := ct_bool_value(state, condition) if !bool_ok { return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'if' condition must be a bool") } body := statement.body if value else statement.else_body return ct_exec_statements(state, body, yield_returns, depth+1) } operands: [dynamic]ast.Expr_Id operands.allocator = checker.allocator defer delete(operands) flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &operands) if len(operands) != len(statement.captures) { return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'if' unwrap capture count mismatch") } scope_start := len(state.bindings) matched := true for operand, index in operands { value, flow, ok := ct_eval_expr(state, operand, types.INVALID, depth+1) if !ok || flow.kind != .Normal { ct_pop_bindings(state, scope_start) return flow, ok } v := state.values[value] if v.kind == .None { matched = false break } if v.kind != .Optional_Some { ct_pop_bindings(state, scope_start) return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'if' unwrap requires an optional value") } children := ct_child_slice(state, v) if len(children) > 0 && statement.captures[index] != checker.sink_symbol { ct_bind_value(state, statement.captures[index], state.values[children[0]].type, children[0], false) } } if matched && statement.guard != ast.INVALID_EXPR { guard, flow, ok := ct_eval_expr(state, statement.guard, types.BOOL, depth+1) if !ok || flow.kind != .Normal { ct_pop_bindings(state, scope_start) return flow, ok } guard_value, guard_ok := ct_bool_value(state, guard) if !guard_ok { ct_pop_bindings(state, scope_start) return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'if' unwrap guard must be a bool") } matched = guard_value } body := statement.body if matched else statement.else_body flow, ok := ct_exec_statements(state, body, yield_returns, depth+1) ct_pop_bindings(state, scope_start) return flow, ok } ct_loop_consumes_flow :: proc(flow: Ct_Flow, label: symbol.Id, want_continue: bool) -> bool { if want_continue && flow.kind != .Continue { return false } if !want_continue && flow.kind != .Break { return false } return !symbol.is_valid(flow.label) || flow.label == label } ct_exec_while :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) { for { if !ct_step(state, statement.span) { return ct_flow(.Normal), false } condition, flow, ok := ct_eval_expr(state, statement.expr, types.BOOL, depth+1) if !ok || flow.kind != .Normal { return flow, ok } value, bool_ok := ct_bool_value(state, condition) if !bool_ok { return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'while' condition must be a bool") } if !value { return ct_flow(.Normal), true } body_flow, body_ok := ct_exec_statements(state, statement.body, yield_returns, depth+1) if !body_ok { return body_flow, false } if ct_loop_consumes_flow(body_flow, statement.label, false) { return ct_flow(.Normal), true } if body_flow.kind != .Normal && !ct_loop_consumes_flow(body_flow, statement.label, true) { return body_flow, true } if statement.update != ast.INVALID_STMT { update := [1]ast.Stmt_Id{statement.update} update_flow, update_ok := ct_exec_statements(state, update[:], false, depth+1) if !update_ok || update_flow.kind != .Normal { return update_flow, update_ok } } } } ct_exec_for :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) { checker := state.checker store := &checker.module.types iterable, flow, ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1) if !ok || flow.kind != .Normal { return flow, ok } value := state.values[iterable] if value.kind == .Range { if statement.pointer_capture { return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "range loops do not support pointer captures") } children := ct_child_slice(state, value) if len(children) < 2 { return ct_flow(.Normal), false } start, start_ok := ct_integer_value(state, children[0]) end, end_ok := ct_integer_value(state, children[1]) if !start_ok || !end_ok { return ct_flow(.Normal), false } index := i128(0) for current := start; current < end || (value.active != 0 && current == end); current += 1 { scope_start := len(state.bindings) item := ct_add_value(state, Ct_Value{kind=.Integer, type=types.child_type(value.type, &checker.module.types), integer=current}) ct_bind_value(state, statement.name, state.values[item].type, item, false) if symbol.is_valid(statement.index_name) { idx := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=index}) ct_bind_value(state, statement.index_name, types.USIZE, idx, false) } body_flow, body_ok := ct_exec_statements(state, statement.body, yield_returns, depth+1) ct_pop_bindings(state, scope_start) if !body_ok { return body_flow, false } if ct_loop_consumes_flow(body_flow, statement.label, false) { return ct_flow(.Normal), true } if body_flow.kind != .Normal && !ct_loop_consumes_flow(body_flow, statement.label, true) { return body_flow, true } index += 1 } return ct_flow(.Normal), true } if value.kind == .Array { children := ct_child_slice(state, value) if statement.pointer_capture { base_place, _, base_writable, place_flow, place_ok := ct_eval_place(state, statement.expr, depth+1) if !place_ok || place_flow.kind != .Normal { return place_flow, place_ok } item, item_ok := types.node(store, value.type) if !item_ok || item.kind != .Array { return ct_flow(.Normal), false } for _, index in children { scope_start := len(state.bindings) elem_writable := base_writable && item.mutable elem_place := ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Index, index=u32(index)}, item.child, elem_writable) pointer_type := types.pointer(store, item.child, elem_writable, false) pointer := ct_add_value(state, Ct_Value{kind=.Pointer, type=pointer_type, index=u64(elem_place), active=-1}) ct_bind_value(state, statement.name, pointer_type, pointer, false) if symbol.is_valid(statement.index_name) { idx := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)}) ct_bind_value(state, statement.index_name, types.USIZE, idx, false) } body_flow, body_ok := ct_exec_statements(state, statement.body, yield_returns, depth+1) ct_pop_bindings(state, scope_start) if !body_ok { return body_flow, false } if ct_loop_consumes_flow(body_flow, statement.label, false) { return ct_flow(.Normal), true } if body_flow.kind != .Normal && !ct_loop_consumes_flow(body_flow, statement.label, true) { return body_flow, true } } return ct_flow(.Normal), true } for child, index in children { scope_start := len(state.bindings) ct_bind_value(state, statement.name, state.values[child].type, child, false) if symbol.is_valid(statement.index_name) { idx := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)}) ct_bind_value(state, statement.index_name, types.USIZE, idx, false) } body_flow, body_ok := ct_exec_statements(state, statement.body, yield_returns, depth+1) ct_pop_bindings(state, scope_start) if !body_ok { return body_flow, false } if ct_loop_consumes_flow(body_flow, statement.label, false) { return ct_flow(.Normal), true } if body_flow.kind != .Normal && !ct_loop_consumes_flow(body_flow, statement.label, true) { return body_flow, true } } return ct_flow(.Normal), true } if value.kind == .Slice || value.kind == .Pointer { count := 0 array_item: types.Node is_pointer_array := false if value.kind == .Slice { count = int(value.count) } else { pointer_item, pointer_ok := types.node(store, value.type) if pointer_ok && !pointer_item.many { array_item, is_pointer_array = types.node(store, pointer_item.child) is_pointer_array = is_pointer_array && array_item.kind == .Array if is_pointer_array { count = int(array_item.count) } } } if value.kind == .Pointer && !is_pointer_array { return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime for-loop pointer iterable must point to an array") } for index in 0.. (Ct_Match_Selection, bool) { checker := state.checker if subject == INVALID_CT_VALUE || int(subject) >= len(state.values) { return {}, false } subject_value := state.values[subject] for arm_id in statement.body { arm := checker.ast_module.statements[arm_id] if arm.kind != .Match_Arm { continue } matched := len(arm.patterns) == 0 selection := Ct_Match_Selection{ arm=arm_id, payload=INVALID_CT_VALUE, payload_field=-1, payload_type=types.INVALID, } if !matched { for pattern_id in arm.patterns { pattern := checker.ast_module.exprs[pattern_id] if subject_value.kind == .Struct && types.is_tagged_union(subject_value.type, &checker.module.types) { if pattern.kind != .Enum_Literal { return {}, false } field_index, field, found := find_struct_field(checker, subject_value.type, pattern.name) if !found { return {}, false } if field_index == int(subject_value.active) { matched = true selection.payload_field = field_index selection.payload_type = field.type children := ct_child_slice(state, subject_value) if len(children) > 0 { selection.payload = children[0] } break } } else if pattern.kind == .Range { probe, range_flow, range_ok := ct_eval_expr(state, pattern_id, subject_value.type, depth) if !range_ok || range_flow.kind != .Normal { return {}, false } if ct_range_contains(state, probe, subject) { matched = true break } } else { probe, pattern_flow, pattern_ok := ct_eval_expr(state, pattern_id, subject_value.type, depth) if !pattern_ok || pattern_flow.kind != .Normal { return {}, false } if ct_values_equal(state, subject, probe) { matched = true break } } } } if matched { return selection, true } } return {}, false } ct_exec_match :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) { checker := state.checker subject, flow, ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1) if !ok || flow.kind != .Normal { return flow, ok } wants_pointer := false for arm_id in statement.body { arm := checker.ast_module.statements[arm_id] wants_pointer = wants_pointer || (arm.kind == .Match_Arm && arm.pointer_capture) } subject_place := INVALID_CT_PLACE subject_writable := false if wants_pointer { place_type: types.Type place_flow: Ct_Flow place_ok := false subject_place, place_type, subject_writable, place_flow, place_ok = ct_eval_place(state, statement.expr, depth+1) if !place_ok || place_flow.kind != .Normal { return place_flow, place_ok } if !types.is_tagged_union(place_type, &checker.module.types) { return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime match pointer captures require an addressable tagged-union subject") } } selection, selected := ct_select_match_arm(state, statement, subject, depth+1) if selected { arm := checker.ast_module.statements[selection.arm] scope_start := len(state.bindings) if len(arm.captures) > 0 && selection.payload != INVALID_CT_VALUE { capture := arm.captures[0] if arm.pointer_capture { if subject_place == INVALID_CT_PLACE || selection.payload_field < 0 || !types.is_valid(selection.payload_type) { ct_pop_bindings(state, scope_start) return ct_flow(.Normal), ct_fail(state, .Not_Comptime, arm.span, "comptime match pointer capture requires a tagged-union payload") } payload_place := ct_extend_place( state, subject_place, Ct_Path_Elem{kind=.Field, index=u32(selection.payload_field)}, selection.payload_type, subject_writable, ) pointer_type := types.pointer(&checker.module.types, selection.payload_type, subject_writable, false) pointer := ct_add_value(state, Ct_Value{kind=.Pointer, type=pointer_type, index=u64(payload_place), active=-1}) if capture != checker.sink_symbol { ct_bind_value(state, capture, pointer_type, pointer, false) } } else if capture != checker.sink_symbol { ct_bind_value(state, capture, state.values[selection.payload].type, selection.payload, false) } } if yield_returns && len(arm.body) == 1 && checker.ast_module.statements[arm.body[0]].kind == .Expression { expr_stmt := checker.ast_module.statements[arm.body[0]] value, expr_flow, expr_ok := ct_eval_expr(state, expr_stmt.expr, types.INVALID, depth+1) ct_pop_bindings(state, scope_start) if !expr_ok || expr_flow.kind != .Normal { return expr_flow, expr_ok } return ct_flow(.Yield, value), true } arm_flow, arm_ok := ct_exec_statements(state, arm.body, yield_returns, depth+1) ct_pop_bindings(state, scope_start) return arm_flow, arm_ok } return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime match did not select an arm") } ct_values_equal :: proc(state: ^Ct_State, left_id, right_id: Ct_Value_Id) -> bool { if left_id == INVALID_CT_VALUE || right_id == INVALID_CT_VALUE || int(left_id) >= len(state.values) || int(right_id) >= len(state.values) { return false } left := state.values[left_id] right := state.values[right_id] if ct_is_integer_like(left) && ct_is_integer_like(right) { return left.integer == right.integer } if left.kind == .Float && right.kind == .Float { return left.float == right.float } if left.kind == .Bool && right.kind == .Bool { return left.integer == right.integer } return false } ct_range_contains :: proc(state: ^Ct_State, range_id, value_id: Ct_Value_Id) -> bool { if range_id == INVALID_CT_VALUE || int(range_id) >= len(state.values) { return false } range_value := state.values[range_id] if range_value.kind != .Range { return false } children := ct_child_slice(state, range_value) if len(children) < 2 { return false } lo, lo_ok := ct_integer_value(state, children[0]) hi, hi_ok := ct_integer_value(state, children[1]) value, value_ok := ct_integer_value(state, value_id) if !lo_ok || !hi_ok || !value_ok { return false } return value >= lo && (value <= hi if range_value.active != 0 else value < hi) } eval_integer_constant_in_context :: proc( checker: ^Checker, expr_id: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id, depth := 0, values: []Comptime_Value = nil, ) -> Constant { if depth > 64 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { return Constant{kind=.Not_Constant} } state := ct_state_make(checker, pkg, file, types.INVALID, values, diagnose=false) defer ct_state_destroy(&state) value, flow, ok := ct_eval_expr(&state, expr_id, types.INVALID, depth) if !ok || flow.kind != .Normal { #partial switch state.error { case .Overflow: return Constant{kind=.Overflow} case .Div_By_Zero: return Constant{kind=.Div_By_Zero} case .Non_Exact: return Constant{kind=.Non_Exact} case .Integer_Division: return Constant{kind=.Integer_Division} } return Constant{kind=.Not_Constant} } integer, integer_ok := ct_integer_value(&state, value) if !integer_ok { return Constant{kind=.Not_Constant} } return Constant{kind=.Value, value=integer} } eval_comptime_statements :: proc( checker: ^Checker, statements: []ast.Stmt_Id, pkg: ast.Package_Id, file: ast.File_Id, depth: int, values: []Comptime_Value, yield_returns: bool, ) -> (Constant, bool, bool) { state := ct_state_make(checker, pkg, file, types.INVALID, values, diagnose=false) defer ct_state_destroy(&state) flow, ok := ct_exec_statements(&state, statements, yield_returns, depth) if !ok { #partial switch state.error { case .Overflow: return Constant{kind=.Overflow}, false, false case .Div_By_Zero: return Constant{kind=.Div_By_Zero}, false, false case .Non_Exact: return Constant{kind=.Non_Exact}, false, false case .Integer_Division: return Constant{kind=.Integer_Division}, false, false } return Constant{kind=.Not_Constant}, false, false } wanted := Ct_Flow_Kind.Yield if yield_returns else Ct_Flow_Kind.Return if flow.kind != wanted { return Constant{kind=.Not_Constant}, false, true } integer, integer_ok := ct_integer_value(&state, flow.value) if !integer_ok { return Constant{kind=.Not_Constant}, false, false } return Constant{kind=.Value, value=integer}, true, true } eval_comptime_call :: proc( checker: ^Checker, expr: ast.Expr, pkg: ast.Package_Id, file: ast.File_Id, depth: int, values: []Comptime_Value, ) -> Constant { state := ct_state_make(checker, pkg, file, types.INVALID, values, diagnose=false) defer ct_state_destroy(&state) value, flow, ok := ct_eval_call_expr(&state, expr, types.INVALID, depth) if !ok || flow.kind != .Normal { #partial switch state.error { case .Overflow: return Constant{kind=.Overflow} case .Div_By_Zero: return Constant{kind=.Div_By_Zero} case .Non_Exact: return Constant{kind=.Non_Exact} case .Integer_Division: return Constant{kind=.Integer_Division} } return Constant{kind=.Not_Constant} } integer, integer_ok := ct_integer_value(&state, value) if !integer_ok { return Constant{kind=.Not_Constant} } return Constant{kind=.Value, value=integer} } infer_comptime_expr_type :: proc( checker: ^Checker, expr: ast.Expr, pkg: ast.Package_Id, file: ast.File_Id, demanded: ^[dynamic]Spec_Id = nil, ) -> types.Type { state := ct_state_make(checker, pkg, file, diagnose=false, demanded=demanded) defer ct_state_destroy(&state) value := INVALID_CT_VALUE flow := ct_flow(.Normal) ok := false if expr.left != ast.INVALID_EXPR { value, flow, ok = ct_eval_expr(&state, expr.left, types.INVALID) } else { flow, ok = ct_exec_statements(&state, expr.body, true) if ok && flow.kind == .Yield { value = flow.value flow = ct_flow(.Normal) } else if ok { state.error = .Not_Comptime if !state.silent { state.diagnostic = source.add(checker.diagnostics, expr.span, "comptime block must yield a value") } ok = false } } if !ok || flow.kind != .Normal || value == INVALID_CT_VALUE || int(value) >= len(state.values) { if state.error == .Overflow || state.error == .Div_By_Zero || state.error == .Non_Exact || state.error == .Integer_Division { return types.I64 } return types.INVALID } return state.values[value].type } build_comptime_expr :: proc( checker: ^Checker, expr: ast.Expr, expected: types.Type, pkg: ast.Package_Id, file: ast.File_Id, ) -> hir.Expr_Id { state := ct_state_make(checker, pkg, file) defer ct_state_destroy(&state) value := INVALID_CT_VALUE flow := ct_flow(.Normal) ok := false if expr.left != ast.INVALID_EXPR { value, flow, ok = ct_eval_expr(&state, expr.left, expected) } else { flow, ok = ct_exec_statements(&state, expr.body, true) if ok && flow.kind == .Yield { value = flow.value flow = ct_flow(.Normal) } else if ok { state.error = .Not_Comptime state.diagnostic = source.add(checker.diagnostics, expr.span, "comptime block must yield a value") ok = false } } if ok && flow.kind == .Normal && value != INVALID_CT_VALUE { return ct_materialize_value(&state, value, expr.span, expected) } if state.error == .Div_By_Zero { return build_constant_expr(checker, expr, Constant{kind=.Div_By_Zero}, expected) } if state.error == .Overflow { return build_constant_expr(checker, expr, Constant{kind=.Overflow}, expected) } if state.error == .Non_Exact { return build_constant_expr(checker, expr, Constant{kind=.Non_Exact}, expected) } if state.error == .Integer_Division { return build_constant_expr(checker, expr, Constant{kind=.Integer_Division}, expected) } diagnostic := state.diagnostic if diagnostic == source.INVALID_DIAGNOSTIC { diagnostic = source.add(checker.diagnostics, expr.span, "expression cannot be evaluated at comptime") } return invalid_hir_expr(checker, expr.span, diagnostic, expected) }