Files
brolang/compiler/checker/comptime.odin
T

3241 lines
122 KiB
Odin

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