Files
brolang/compiler/checker/comptime.odin
T

4704 lines
177 KiB
Odin

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