package checker import "../ast" import "../hir" import "../source" import "../symbol" import "../types" import "base:intrinsics" import "core:math" import "core:mem" COMPTIME_EVAL_QUOTA :: 100_000 Comptime_Value_Kind :: enum u8 { Integer, Type, } Comptime_Value :: struct { name: symbol.Id, type: types.Type, value: i128, 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_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) { 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, 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) } } 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_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 == .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 .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_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_binding_value(state, index), ct_flow(.Normal), true } 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 } 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) return value, ct_flow(.Normal), value_ok } 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) return value, ct_flow(.Normal), value_ok } 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) return value, ct_flow(.Normal), value_ok } } } } 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 value, ct_flow(.Normal), true case .Slice: return ct_eval_slice_expr(state, expr, depth+1) case .Undefined, .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 { struct_type = types.resolve_alias(expected, store) } 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 value, ct_flow(.Normal), true } } } 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 children[0], ct_flow(.Normal), true } 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 children[index], ct_flow(.Normal), true } 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 children[index], ct_flow(.Normal), true } 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 div_trunc, div_floor, div_exact, or div_ceil", ) } 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") } 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 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) } 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 } 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_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_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_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 } subject_value := state.values[subject] 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") } } for arm_id in statement.body { arm := checker.ast_module.statements[arm_id] if arm.kind != .Match_Arm { continue } matched := len(arm.patterns) == 0 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 { continue } if field_index, field, found := find_struct_field(checker, subject_value.type, pattern.name); found && field_index == int(subject_value.active) { matched = true payload_field = field_index payload_type = field.type children := ct_child_slice(state, subject_value) if len(children) > 0 { payload = children[0] } break } } else if pattern.kind == .Range { probe, range_flow, range_ok := ct_eval_expr(state, pattern_id, subject_value.type, depth+1) if range_ok && range_flow.kind == .Normal && 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+1) if pattern_ok && pattern_flow.kind == .Normal && ct_values_equal(state, subject, probe) { matched = true break } } } } if !matched { continue } scope_start := len(state.bindings) if len(arm.captures) > 0 && payload != INVALID_CT_VALUE { capture := arm.captures[0] if arm.pointer_capture { if subject_place == INVALID_CT_PLACE || payload_field < 0 || !types.is_valid(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(payload_field)}, payload_type, subject_writable, ) pointer_type := types.pointer(&checker.module.types, 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[payload].type, 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) }