5839 lines
217 KiB
Odin
5839 lines
217 KiB
Odin
package checker
|
|
|
|
import "../ast"
|
|
import "../hir"
|
|
import "../source"
|
|
import "../symbol"
|
|
import "../types"
|
|
import "base:intrinsics"
|
|
|
|
import "core:fmt"
|
|
import "core:hash"
|
|
import "core:math"
|
|
import "core:mem"
|
|
import "core:strings"
|
|
|
|
COMPTIME_EVAL_QUOTA :: 100_000
|
|
|
|
Comptime_Value_Kind :: enum u8 {
|
|
Integer,
|
|
Type,
|
|
String,
|
|
Static,
|
|
}
|
|
|
|
Comptime_Value :: struct {
|
|
name: symbol.Id,
|
|
type: types.Type,
|
|
value: i128,
|
|
text: string,
|
|
static_value: Ct_Value_Id,
|
|
key: string,
|
|
fingerprint: u64,
|
|
kind: Comptime_Value_Kind,
|
|
}
|
|
|
|
Constant_Kind :: enum {
|
|
Unknown,
|
|
Not_Constant,
|
|
Value,
|
|
Overflow,
|
|
Div_By_Zero,
|
|
Non_Exact,
|
|
Integer_Division,
|
|
}
|
|
|
|
Constant :: struct {
|
|
kind: Constant_Kind,
|
|
value: i128,
|
|
}
|
|
|
|
Constant_Frame :: struct {
|
|
expr: ast.Expr_Id,
|
|
stage: u8,
|
|
}
|
|
|
|
find_comptime_value :: proc(values: []Comptime_Value, name: symbol.Id) -> (Comptime_Value, bool) {
|
|
for index := len(values) - 1; index >= 0; index -= 1 {
|
|
if values[index].name == name {
|
|
return values[index], true
|
|
}
|
|
}
|
|
return {}, false
|
|
}
|
|
|
|
current_comptime_value :: proc(checker: ^Checker, name: symbol.Id) -> (Comptime_Value, bool) {
|
|
return find_comptime_value(checker.current_comptime_values, name)
|
|
}
|
|
|
|
current_static_binding :: proc(checker: ^Checker, name: symbol.Id) -> (Static_Binding, bool) {
|
|
for index := len(checker.static_bindings)-1; index >= 0; index -= 1 {
|
|
if checker.static_bindings[index].name == name {
|
|
return checker.static_bindings[index], true
|
|
}
|
|
}
|
|
return {}, false
|
|
}
|
|
|
|
current_comptime_type :: proc(checker: ^Checker, name: symbol.Id) -> (types.Type, bool) {
|
|
if value, ok := current_comptime_value(checker, name); ok && value.kind == .Type {
|
|
return value.type, true
|
|
}
|
|
return types.INVALID, false
|
|
}
|
|
|
|
comptime_values_equal :: proc(left, right: []Comptime_Value) -> bool {
|
|
if len(left) != len(right) {
|
|
return false
|
|
}
|
|
for value, index in left {
|
|
other := right[index]
|
|
if value.name != other.name || value.kind != other.kind || !types.equal(value.type, other.type) ||
|
|
(value.kind == .Integer && value.value != other.value) ||
|
|
(value.kind == .String && value.text != other.text) ||
|
|
(value.kind == .Static && (value.fingerprint != other.fingerprint || value.key != other.key)) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
|
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return Constant{kind = .Not_Constant}
|
|
}
|
|
stack := checker.constant_stack
|
|
clear_dynamic_array(&stack)
|
|
defer {
|
|
clear_dynamic_array(&stack)
|
|
checker.constant_stack = stack
|
|
}
|
|
append(&stack, Constant_Frame{expr=expr_id})
|
|
|
|
for len(stack) > 0 {
|
|
frame_index := len(stack)-1
|
|
frame := stack[frame_index]
|
|
if checker.constants[frame.expr].kind != .Unknown {
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
expr := checker.ast_module.exprs[frame.expr]
|
|
if expr.kind != .Add && expr.kind != .Sub && expr.kind != .Mul && expr.kind != .Negate {
|
|
result := Constant{kind = .Not_Constant}
|
|
if expr.kind == .Integer {
|
|
result = Constant{kind = .Value, value = i128(expr.integer)}
|
|
}
|
|
checker.constants[frame.expr] = result
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if frame.stage == 0 {
|
|
stack[frame_index].stage = 1
|
|
if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.ast_module.exprs) &&
|
|
checker.constants[expr.left].kind == .Unknown {
|
|
append(&stack, Constant_Frame{expr=expr.left})
|
|
}
|
|
continue
|
|
}
|
|
if frame.stage == 1 && expr.kind == .Negate {
|
|
operand := Constant{kind = .Not_Constant}
|
|
if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.constants) {
|
|
operand = checker.constants[expr.left]
|
|
}
|
|
result := Constant{kind = .Not_Constant}
|
|
if operand.kind == .Overflow {
|
|
result = Constant{kind = .Overflow}
|
|
} else if operand.kind == .Value {
|
|
value, overflow := intrinsics.overflow_sub(i128(0), operand.value)
|
|
result = Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
|
|
}
|
|
checker.constants[frame.expr] = result
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if frame.stage == 1 {
|
|
stack[frame_index].stage = 2
|
|
if expr.right != ast.INVALID_EXPR && int(expr.right) < len(checker.ast_module.exprs) &&
|
|
checker.constants[expr.right].kind == .Unknown {
|
|
append(&stack, Constant_Frame{expr=expr.right})
|
|
}
|
|
continue
|
|
}
|
|
left := Constant{kind = .Not_Constant}
|
|
right := Constant{kind = .Not_Constant}
|
|
if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.constants) {
|
|
left = checker.constants[expr.left]
|
|
}
|
|
if expr.right != ast.INVALID_EXPR && int(expr.right) < len(checker.constants) {
|
|
right = checker.constants[expr.right]
|
|
}
|
|
result := Constant{kind = .Not_Constant}
|
|
if left.kind == .Overflow || right.kind == .Overflow {
|
|
result = Constant{kind = .Overflow}
|
|
} else if left.kind == .Value && right.kind == .Value {
|
|
value: i128
|
|
overflow: bool
|
|
#partial switch expr.kind {
|
|
case .Sub: value, overflow = intrinsics.overflow_sub(left.value, right.value)
|
|
case .Mul: value, overflow = intrinsics.overflow_mul(left.value, right.value)
|
|
case: value, overflow = intrinsics.overflow_add(left.value, right.value)
|
|
}
|
|
switch {
|
|
case overflow: result = Constant{kind = .Overflow}
|
|
case: result = Constant{kind = .Value, value = value}
|
|
}
|
|
}
|
|
checker.constants[frame.expr] = result
|
|
_ = pop(&stack)
|
|
}
|
|
return checker.constants[expr_id]
|
|
}
|
|
|
|
Ct_Value_Id :: distinct u32
|
|
INVALID_CT_VALUE :: Ct_Value_Id(0xffff_ffff)
|
|
|
|
ct_value_id :: proc(index: int) -> Ct_Value_Id {
|
|
assert(index >= 0 && u64(index) < u64(INVALID_CT_VALUE))
|
|
return Ct_Value_Id(index)
|
|
}
|
|
|
|
Ct_Cell_Id :: distinct u32
|
|
INVALID_CT_CELL :: Ct_Cell_Id(0xffff_ffff)
|
|
|
|
ct_cell_id :: proc(index: int) -> Ct_Cell_Id {
|
|
assert(index >= 0 && u64(index) < u64(INVALID_CT_CELL))
|
|
return Ct_Cell_Id(index)
|
|
}
|
|
|
|
Ct_Place_Id :: distinct u32
|
|
INVALID_CT_PLACE :: Ct_Place_Id(0xffff_ffff)
|
|
|
|
ct_place_id :: proc(index: int) -> Ct_Place_Id {
|
|
assert(index >= 0 && u64(index) < u64(INVALID_CT_PLACE))
|
|
return Ct_Place_Id(index)
|
|
}
|
|
|
|
Ct_Value_Kind :: enum u8 {
|
|
Invalid,
|
|
Void,
|
|
Undefined,
|
|
Integer,
|
|
Float,
|
|
Bool,
|
|
String,
|
|
Range,
|
|
Array,
|
|
Struct,
|
|
Pointer,
|
|
Slice,
|
|
Function,
|
|
Type,
|
|
Null,
|
|
Optional_Some,
|
|
Fallible,
|
|
}
|
|
|
|
Ct_Error_Kind :: enum u8 {
|
|
None,
|
|
Not_Comptime,
|
|
Compile_Error,
|
|
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_Error_Refinement :: struct {
|
|
cell: Ct_Cell_Id,
|
|
variants: []u32,
|
|
}
|
|
|
|
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,
|
|
error_refinements: [dynamic]Ct_Error_Refinement,
|
|
defers: [dynamic]Ct_Defer,
|
|
yield_targets: [dynamic]symbol.Id,
|
|
defer_depth: int,
|
|
value_return_depth: int,
|
|
steps: int,
|
|
error: Ct_Error_Kind,
|
|
diagnostic: source.Diagnostic_Id,
|
|
silent: bool,
|
|
foldable: bool,
|
|
demanded: ^[dynamic]Spec_Id,
|
|
promoted_cells: [dynamic]Ct_Cell_Id,
|
|
promoted_globals: [dynamic]hir.Global_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.foldable = true
|
|
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.error_refinements.allocator = checker.allocator
|
|
state.defers.allocator = checker.allocator
|
|
state.yield_targets.allocator = checker.allocator
|
|
state.promoted_cells.allocator = checker.allocator
|
|
state.promoted_globals.allocator = checker.allocator
|
|
for value in values {
|
|
if value.kind == .Integer {
|
|
id := ct_add_value(&state, Ct_Value{kind=.Integer, type=value.type, integer=value.value})
|
|
ct_bind_value(&state, value.name, value.type, id, false)
|
|
} else if value.kind == .Type {
|
|
id := ct_add_value(&state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(value.type)})
|
|
ct_bind_value(&state, value.name, types.INVALID, id, false)
|
|
} else if value.kind == .String {
|
|
string_id := u64(0)
|
|
for text, index in checker.ast_module.strings {
|
|
if text == value.text {
|
|
string_id = u64(index)
|
|
break
|
|
}
|
|
}
|
|
id := ct_add_value(&state, Ct_Value{kind=.String, type=value.type, index=string_id})
|
|
ct_bind_value(&state, value.name, value.type, id, false)
|
|
} else if value.kind == .Static {
|
|
id := ct_clone_graph(&state, &checker.static_state, value.static_value)
|
|
ct_bind_value(&state, value.name, value.type, id, false)
|
|
}
|
|
}
|
|
for binding in checker.static_bindings {
|
|
id := ct_clone_graph(&state, &checker.static_state, binding.value)
|
|
ct_bind_value(&state, binding.name, binding.type, id, false)
|
|
}
|
|
return state
|
|
}
|
|
|
|
ct_state_destroy :: proc(state: ^Ct_State) {
|
|
delete(state.values)
|
|
delete(state.children)
|
|
delete(state.cells)
|
|
delete(state.places)
|
|
delete(state.paths)
|
|
delete(state.bindings)
|
|
delete(state.error_refinements)
|
|
delete(state.defers)
|
|
delete(state.yield_targets)
|
|
delete(state.promoted_cells)
|
|
delete(state.promoted_globals)
|
|
}
|
|
|
|
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_value_has_children :: proc(kind: Ct_Value_Kind) -> bool {
|
|
return kind == .Range || kind == .Array || kind == .Struct ||
|
|
kind == .Optional_Some || kind == .Fallible
|
|
}
|
|
|
|
ct_child_slice :: proc(state: ^Ct_State, value: Ct_Value) -> []Ct_Value_Id {
|
|
if !ct_value_has_children(value.kind) {
|
|
return nil
|
|
}
|
|
start := int(value.start)
|
|
end := start+int(value.count)
|
|
if start < 0 || end > len(state.children) {
|
|
return nil
|
|
}
|
|
return state.children[start:end]
|
|
}
|
|
|
|
ct_value_contains_undefined :: proc(state: ^Ct_State, id: Ct_Value_Id, depth := 0) -> bool {
|
|
if depth > 64 || id == INVALID_CT_VALUE || int(id) >= len(state.values) {
|
|
return false
|
|
}
|
|
value := state.values[id]
|
|
if value.kind == .Undefined {
|
|
return true
|
|
}
|
|
for child in ct_child_slice(state, value) {
|
|
if ct_value_contains_undefined(state, child, depth+1) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
ct_observe_value :: proc(state: ^Ct_State, id: Ct_Value_Id, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
if state.values[id].kind == .Undefined {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
|
state, .Not_Comptime, span, "cannot read an undefined value at comptime",
|
|
)
|
|
}
|
|
return id, ct_flow(.Normal), true
|
|
}
|
|
|
|
ct_fail :: proc(state: ^Ct_State, kind: Ct_Error_Kind, span: source.Span, message: string) -> bool {
|
|
if state.error == .None {
|
|
state.error = kind
|
|
if !state.silent && len(message) > 0 {
|
|
state.diagnostic = source.add(state.checker.diagnostics, span, message)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
ct_failf :: proc(state: ^Ct_State, kind: Ct_Error_Kind, span: source.Span, fmt: string, args: ..any) -> bool {
|
|
if state.error == .None {
|
|
state.error = kind
|
|
if !state.silent {
|
|
state.diagnostic = source.addf(state.checker.diagnostics, span, fmt, ..args)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
ct_step :: proc(state: ^Ct_State, span: source.Span) -> bool {
|
|
state.steps += 1
|
|
if state.steps > COMPTIME_EVAL_QUOTA {
|
|
return ct_fail(state, .Quota, span, "comptime evaluation exceeded the step quota")
|
|
}
|
|
return true
|
|
}
|
|
|
|
ct_find_binding_index :: proc(state: ^Ct_State, name: symbol.Id) -> (int, bool) {
|
|
for index := len(state.bindings) - 1; index >= 0; index -= 1 {
|
|
if state.bindings[index].name == name {
|
|
return index, true
|
|
}
|
|
}
|
|
return -1, false
|
|
}
|
|
|
|
ct_binding_value :: proc(state: ^Ct_State, index: int) -> Ct_Value_Id {
|
|
if index < 0 || index >= len(state.bindings) {
|
|
return INVALID_CT_VALUE
|
|
}
|
|
binding := state.bindings[index]
|
|
if binding.cell != INVALID_CT_CELL && int(binding.cell) < len(state.cells) && state.cells[binding.cell].live {
|
|
return state.cells[binding.cell].value
|
|
}
|
|
return binding.value
|
|
}
|
|
|
|
ct_binding_place :: proc(state: ^Ct_State, index: int) -> Ct_Place_Id {
|
|
if index < 0 || index >= len(state.bindings) {
|
|
return INVALID_CT_PLACE
|
|
}
|
|
binding := state.bindings[index]
|
|
if binding.cell == INVALID_CT_CELL || int(binding.cell) >= len(state.cells) || !state.cells[binding.cell].live {
|
|
return INVALID_CT_PLACE
|
|
}
|
|
return ct_add_place(state, binding.cell, binding.type, binding.mutable)
|
|
}
|
|
|
|
ct_flow :: proc(kind: Ct_Flow_Kind, value := INVALID_CT_VALUE, label := symbol.INVALID) -> Ct_Flow {
|
|
return Ct_Flow{kind=kind, value=value, label=label}
|
|
}
|
|
|
|
ct_bool_value :: proc(state: ^Ct_State, id: Ct_Value_Id) -> (bool, bool) {
|
|
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
|
|
return false, false
|
|
}
|
|
value := state.values[id]
|
|
if value.kind == .Bool {
|
|
return value.integer != 0, true
|
|
}
|
|
return false, false
|
|
}
|
|
|
|
ct_integer_value :: proc(state: ^Ct_State, id: Ct_Value_Id) -> (i128, bool) {
|
|
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
|
|
return 0, false
|
|
}
|
|
value := state.values[id]
|
|
if value.kind == .Integer || value.kind == .Bool {
|
|
return value.integer, true
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
ct_is_integer_like :: proc(value: Ct_Value) -> bool {
|
|
return value.kind == .Integer || value.kind == .Bool
|
|
}
|
|
|
|
ct_default_integer_type :: proc(value: i128) -> types.Type {
|
|
if fits_i64(value) {
|
|
return types.smallest_signed_for_literal(i64(value))
|
|
}
|
|
return types.I64
|
|
}
|
|
|
|
ct_coerce_value :: proc(state: ^Ct_State, id: Ct_Value_Id, expected: types.Type, span: source.Span) -> (Ct_Value_Id, bool) {
|
|
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
|
|
return INVALID_CT_VALUE, false
|
|
}
|
|
if !types.is_valid(expected) || types.is_constraint(expected) {
|
|
return id, true
|
|
}
|
|
value := state.values[id]
|
|
if value.kind == .Type && is_type_metatype_syntax(state.checker, expected) {
|
|
return id, true
|
|
}
|
|
if types.equal(value.type, expected) {
|
|
return id, true
|
|
}
|
|
store := &state.checker.module.types
|
|
if value.kind == .String {
|
|
if item, ok := types.node(store, expected); ok && item.kind == .Slice && !item.mutable && item.child == types.U8 {
|
|
value.type = expected
|
|
return ct_add_value(state, value), true
|
|
}
|
|
}
|
|
if value.kind == .Pointer && types.can_weaken_pointer(value.type, expected, store) {
|
|
value.type = expected
|
|
return ct_add_value(state, value), true
|
|
}
|
|
if value.kind == .Slice && types.can_weaken_slice(value.type, expected, store) {
|
|
value.type = expected
|
|
return ct_add_value(state, value), true
|
|
}
|
|
if value.kind == .Function {
|
|
actual_item, actual_ok := types.node(store, value.type)
|
|
_, _, expected_function, expected_pointer := types.function_pointer(expected, store)
|
|
expected_ok := expected_pointer
|
|
if expected_item, ok := types.node(store, expected); ok && expected_item.kind == .Function {
|
|
expected_function = expected
|
|
expected_ok = true
|
|
}
|
|
if actual_ok && actual_item.kind == .Function && expected_ok &&
|
|
types.equal(value.type, expected_function) {
|
|
value.type = expected
|
|
return ct_add_value(state, value), true
|
|
}
|
|
_, _, actual_function, actual_pointer := types.function_pointer(value.type, store)
|
|
if actual_pointer && expected_pointer && types.equal(actual_function, expected_function) {
|
|
value.type = expected
|
|
return ct_add_value(state, value), true
|
|
}
|
|
}
|
|
if value.kind == .Array {
|
|
expected_item, expected_ok := types.node(store, expected)
|
|
value_item, value_ok := types.node(store, value.type)
|
|
if expected_ok && value_ok && expected_item.kind == .Array && value_item.kind == .Array &&
|
|
expected_item.inferred_count && types.equal(expected_item.child, value_item.child) {
|
|
value.type = types.with_array_count(store, expected, value_item.count)
|
|
return ct_add_value(state, value), true
|
|
}
|
|
}
|
|
if value.kind == .Null {
|
|
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, "'null' 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",
|
|
type_label(state.checker, value.type), type_label(state.checker, 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_retain_value_storage :: proc(state: ^Ct_State, id: Ct_Value_Id, depth := 0) {
|
|
if depth > 64 || id == INVALID_CT_VALUE || int(id) >= len(state.values) {
|
|
return
|
|
}
|
|
value := state.values[id]
|
|
if value.kind == .Pointer || value.kind == .Slice {
|
|
place_id := Ct_Place_Id(value.index)
|
|
if place_id != INVALID_CT_PLACE && int(place_id) < len(state.places) {
|
|
cell := state.places[place_id].cell
|
|
if cell != INVALID_CT_CELL && int(cell) < len(state.cells) {
|
|
state.cells[cell].live = true
|
|
}
|
|
}
|
|
}
|
|
for child in ct_child_slice(state, value) {
|
|
ct_retain_value_storage(state, child, depth+1)
|
|
}
|
|
}
|
|
|
|
ct_value_can_materialize :: proc(state: ^Ct_State, id: Ct_Value_Id, depth := 0) -> bool {
|
|
if depth > 64 || id == INVALID_CT_VALUE || int(id) >= len(state.values) {
|
|
return false
|
|
}
|
|
value := state.values[id]
|
|
#partial switch value.kind {
|
|
case .Invalid, .Undefined, .Function, .Type:
|
|
return false
|
|
case .Void, .Integer, .Float, .Bool, .String, .Null:
|
|
return true
|
|
case .Pointer:
|
|
store := &state.checker.module.types
|
|
pointer, pointer_ok := types.node(store, value.type)
|
|
place_id := Ct_Place_Id(value.index)
|
|
if !pointer_ok || pointer.kind != .Pointer || pointer.mutable ||
|
|
place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) {
|
|
return false
|
|
}
|
|
place := state.places[place_id]
|
|
if place.cell == INVALID_CT_CELL || int(place.cell) >= len(state.cells) || len(ct_place_path(state, place)) != 0 {
|
|
return false
|
|
}
|
|
root_id := state.cells[place.cell].value
|
|
if root_id == INVALID_CT_VALUE || int(root_id) >= len(state.values) {
|
|
return false
|
|
}
|
|
root := state.values[root_id]
|
|
array, array_ok := types.node(store, root.type)
|
|
return array_ok && array.kind == .Array &&
|
|
((pointer.many && value.active == 0 && types.equal(pointer.child, array.child)) ||
|
|
(!pointer.many && value.active == -1 && types.equal(pointer.child, root.type))) &&
|
|
ct_value_can_materialize(state, root_id, depth+1)
|
|
case .Slice:
|
|
store := &state.checker.module.types
|
|
slice, slice_ok := types.node(store, value.type)
|
|
place_id := Ct_Place_Id(value.index)
|
|
if !slice_ok || slice.kind != .Slice || slice.mutable || value.start != 0 ||
|
|
place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) {
|
|
return false
|
|
}
|
|
place := state.places[place_id]
|
|
if place.cell == INVALID_CT_CELL || int(place.cell) >= len(state.cells) || len(ct_place_path(state, place)) != 0 {
|
|
return false
|
|
}
|
|
root_id := state.cells[place.cell].value
|
|
if root_id == INVALID_CT_VALUE || int(root_id) >= len(state.values) {
|
|
return false
|
|
}
|
|
root := state.values[root_id]
|
|
array, array_ok := types.node(store, root.type)
|
|
return array_ok && array.kind == .Array && u64(value.count) == array.count &&
|
|
types.equal(slice.child, array.child) && ct_value_can_materialize(state, root_id, depth+1)
|
|
case .Range, .Array, .Struct, .Optional_Some, .Fallible:
|
|
for child in ct_child_slice(state, value) {
|
|
if child != INVALID_CT_VALUE && !ct_value_can_materialize(state, child, depth+1) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
ct_materialize_array_pointer :: proc(
|
|
state: ^Ct_State,
|
|
value: Ct_Value,
|
|
span: source.Span,
|
|
) -> (hir.Expr_Id, bool) {
|
|
checker := state.checker
|
|
store := &checker.module.types
|
|
pointer, pointer_ok := types.node(store, value.type)
|
|
place_id := Ct_Place_Id(value.index)
|
|
if !pointer_ok || pointer.kind != .Pointer || pointer.mutable ||
|
|
place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
place := state.places[place_id]
|
|
if place.cell == INVALID_CT_CELL || int(place.cell) >= len(state.cells) || len(ct_place_path(state, place)) != 0 {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
root_id := state.cells[place.cell].value
|
|
if root_id == INVALID_CT_VALUE || int(root_id) >= len(state.values) {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
root := state.values[root_id]
|
|
array, array_ok := types.node(store, root.type)
|
|
if !array_ok || array.kind != .Array ||
|
|
(pointer.many && (value.active != 0 || !types.equal(pointer.child, array.child))) ||
|
|
(!pointer.many && (value.active != -1 || !types.equal(pointer.child, root.type))) {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
global_id := hir.INVALID_GLOBAL
|
|
for cell, index in state.promoted_cells {
|
|
if cell == place.cell {
|
|
global_id = state.promoted_globals[index]
|
|
break
|
|
}
|
|
}
|
|
if global_id == hir.INVALID_GLOBAL {
|
|
root_expr := ct_materialize_value(state, root_id, span, root.type)
|
|
if root_expr == hir.INVALID_EXPR || expr_problematic(checker, root_expr) {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
global_id = hir.Global_Id(len(checker.ast_module.globals)+len(checker.anon_globals))
|
|
append(&checker.anon_globals, hir.Global{
|
|
name=symbol.intern(checker.symbols, "__comptime.array"),
|
|
type=root.type,
|
|
expr=root_expr,
|
|
eager=true,
|
|
writable=false,
|
|
external=false,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append(&state.promoted_cells, place.cell)
|
|
append(&state.promoted_globals, global_id)
|
|
}
|
|
if checker.current_build_ctx != nil {
|
|
add_unique_global(checker.current_build_ctx.global_reads, global_id)
|
|
}
|
|
global := add_hir_expr(checker, hir.Expr{
|
|
kind=.Global, span=span, type=root.type, target=hir.global_ref(global_id),
|
|
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
address := add_hir_expr(checker, hir.Expr{
|
|
kind=.Address, span=span, type=types.pointer(store, root.type, false, false), left=global,
|
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
return coerce_expr(checker, address, value.type, span), true
|
|
}
|
|
|
|
ct_materialize_array_slice :: proc(
|
|
state: ^Ct_State,
|
|
value: Ct_Value,
|
|
span: source.Span,
|
|
) -> (hir.Expr_Id, bool) {
|
|
store := &state.checker.module.types
|
|
slice, slice_ok := types.node(store, value.type)
|
|
place_id := Ct_Place_Id(value.index)
|
|
if !slice_ok || slice.kind != .Slice || slice.mutable || value.start != 0 ||
|
|
place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
place := state.places[place_id]
|
|
if place.cell == INVALID_CT_CELL || int(place.cell) >= len(state.cells) || len(ct_place_path(state, place)) != 0 {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
root_id := state.cells[place.cell].value
|
|
if root_id == INVALID_CT_VALUE || int(root_id) >= len(state.values) {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
root := state.values[root_id]
|
|
array, array_ok := types.node(store, root.type)
|
|
if !array_ok || array.kind != .Array || u64(value.count) != array.count ||
|
|
!types.equal(slice.child, array.child) {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
pointer := Ct_Value{
|
|
kind=.Pointer,
|
|
type=types.pointer(store, root.type, false, false),
|
|
index=value.index,
|
|
active=-1,
|
|
}
|
|
address, ok := ct_materialize_array_pointer(state, pointer, span)
|
|
if !ok {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
return coerce_expr(state.checker, address, value.type, span), true
|
|
}
|
|
|
|
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 .Void:
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Void, span=span, type=types.VOID,
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Undefined:
|
|
if state.diagnostic == source.INVALID_DIAGNOSTIC {
|
|
state.diagnostic = source.add(checker.diagnostics, span, "cannot materialize an undefined comptime value")
|
|
}
|
|
return invalid_hir_expr(checker, span, state.diagnostic, value.type)
|
|
case .Integer:
|
|
if types.is_enum(value.type, &checker.module.types) {
|
|
int_value := i64(value.integer) if value.integer < 0 else transmute(i64)u64(value.integer)
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Integer, span=span, type=value.type, integer=int_value,
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
return build_constant_expr(checker, ast.Expr{span=span}, Constant{kind=.Value, value=value.integer}, value.type)
|
|
case .Bool:
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Bool, span=span, type=types.BOOL, integer=i64(value.integer),
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Float:
|
|
bits := transmute(i64)value.float
|
|
if types.bits(value.type, checker.target) == 32 {
|
|
bits = i64(transmute(u32)f32(value.float))
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Float, span=span, type=value.type, integer=bits,
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .String:
|
|
literal_type := string_literal_type(checker, value.index)
|
|
literal := add_hir_expr(checker, hir.Expr{
|
|
kind=.String, span=span, type=literal_type, integer=i64(value.index),
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
return coerce_expr(checker, literal, value.type, span)
|
|
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:
|
|
if result, ok := ct_materialize_array_pointer(state, value, span); ok {
|
|
return result
|
|
}
|
|
if state.diagnostic == source.INVALID_DIAGNOSTIC {
|
|
state.diagnostic = source.add(checker.diagnostics, span, "only immutable pointers to whole comptime arrays can materialize as runtime memory")
|
|
}
|
|
return invalid_hir_expr(checker, span, state.diagnostic, value.type)
|
|
case .Slice:
|
|
if result, ok := ct_materialize_array_slice(state, value, span); ok {
|
|
return result
|
|
}
|
|
if state.diagnostic == source.INVALID_DIAGNOSTIC {
|
|
state.diagnostic = source.add(checker.diagnostics, span, "only immutable full-array comptime slices can materialize as runtime memory")
|
|
}
|
|
return invalid_hir_expr(checker, span, state.diagnostic, value.type)
|
|
case .Function:
|
|
value_expected := expected if types.is_valid(expected) else value.type
|
|
return build_function_value(checker, ast.Function_Id(u32(value.index)), span, value_expected)
|
|
case .Null:
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Null, span=span, type=value.type,
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Optional_Some:
|
|
children := ct_child_slice(state, value)
|
|
child := hir.INVALID_EXPR
|
|
if len(children) > 0 {
|
|
child_type := types.child_type(value.type, &checker.module.types)
|
|
child = ct_materialize_value(state, children[0], span, child_type)
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Optional_Some, span=span, type=value.type, left=child,
|
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Fallible:
|
|
children := ct_child_slice(state, value)
|
|
args := make([]hir.Expr_Id, 1, checker.allocator)
|
|
args[0] = hir.INVALID_EXPR
|
|
if len(children) > 0 && children[0] != INVALID_CT_VALUE {
|
|
fallible_item, _ := types.node(&checker.module.types, value.type)
|
|
payload_type := fallible_item.extra if value.active != 0 else fallible_item.child
|
|
if !types.is_void(payload_type) {
|
|
args[0] = ct_materialize_value(state, children[0], span, payload_type)
|
|
}
|
|
}
|
|
return add_hir_expr(checker, hir.Expr{
|
|
kind=.Struct, span=span, type=value.type, args=args, integer=value.active,
|
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
return invalid_hir_expr(checker, span, source.INVALID_DIAGNOSTIC, value.type)
|
|
}
|
|
|
|
ct_undefined_value :: proc(state: ^Ct_State, value_type: types.Type, depth := 0) -> (Ct_Value_Id, bool) {
|
|
if depth > 64 || !types.is_valid(value_type) {
|
|
return INVALID_CT_VALUE, false
|
|
}
|
|
store := &state.checker.module.types
|
|
item, ok := types.node(store, value_type)
|
|
if ok && item.kind == .Array {
|
|
children := make([]Ct_Value_Id, int(item.count), state.checker.allocator)
|
|
defer delete(children, state.checker.allocator)
|
|
for &child in children {
|
|
child, ok = ct_undefined_value(state, item.child, depth+1)
|
|
if !ok { return INVALID_CT_VALUE, false }
|
|
}
|
|
start := u32(len(state.children))
|
|
append(&state.children, ..children)
|
|
return ct_add_value(state, Ct_Value{kind=.Array, type=value_type, start=start, count=u32(len(children))}), true
|
|
}
|
|
if ok && item.kind == .Struct && !item.opaque {
|
|
fields := types.fields_for(store, value_type)
|
|
children := make([]Ct_Value_Id, len(fields), state.checker.allocator)
|
|
defer delete(children, state.checker.allocator)
|
|
for field, index in fields {
|
|
children[index], ok = ct_undefined_value(state, field.type, depth+1)
|
|
if !ok { return INVALID_CT_VALUE, false }
|
|
}
|
|
start := u32(len(state.children))
|
|
append(&state.children, ..children)
|
|
return ct_add_value(state, Ct_Value{kind=.Struct, type=value_type, start=start, count=u32(len(children))}), true
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Undefined, type=value_type}), true
|
|
}
|
|
|
|
ct_eval_expr :: proc(
|
|
state: ^Ct_State,
|
|
expr_id: ast.Expr_Id,
|
|
expected := types.INVALID,
|
|
depth := 0,
|
|
) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
checker := state.checker
|
|
if depth > 128 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, source.Span{}, "expression cannot be evaluated at comptime")
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
if !ct_step(state, expr.span) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
store := &checker.module.types
|
|
#partial switch expr.kind {
|
|
case .Integer:
|
|
value_type := expected if types.is_concrete_integer(expected) || types.is_enum(expected, store) else ct_default_integer_type(i128(expr.integer))
|
|
id := ct_add_value(state, Ct_Value{kind=.Integer, type=value_type, integer=i128(expr.integer)})
|
|
if types.is_valid(expected) {
|
|
return ct_coerce_expr_value(state, id, expected, expr.span)
|
|
}
|
|
return id, ct_flow(.Normal), true
|
|
case .Bool:
|
|
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=i128(expr.integer)}), ct_flow(.Normal), true
|
|
case .Float:
|
|
value_type := expected if types.is_float(expected, checker.target) else types.F64
|
|
return ct_add_value(state, Ct_Value{kind=.Float, type=value_type, float=transmute(f64)expr.integer}), ct_flow(.Normal), true
|
|
case .String:
|
|
return ct_add_value(state, Ct_Value{kind=.String, type=string_literal_type(checker, expr.integer), index=expr.integer}), ct_flow(.Normal), true
|
|
case .Name:
|
|
if !symbol.is_valid(expr.qualifier) {
|
|
if index, ok := ct_find_binding_index(state, expr.name); ok {
|
|
return ct_observe_value(state, ct_binding_value(state, index), expr.span)
|
|
}
|
|
if value, ok := current_comptime_value(checker, expr.name); ok {
|
|
if value.kind == .Integer {
|
|
id := ct_add_value(state, Ct_Value{kind=.Integer, type=value.type, integer=value.value})
|
|
return id, ct_flow(.Normal), true
|
|
}
|
|
if value.kind == .String {
|
|
string_id := u64(0)
|
|
for text, index in checker.ast_module.strings {
|
|
if text == value.text {
|
|
string_id = u64(index)
|
|
break
|
|
}
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.String, type=value.type, index=string_id}), ct_flow(.Normal), true
|
|
}
|
|
if value.kind == .Static && value.static_value != INVALID_CT_VALUE &&
|
|
int(value.static_value) < len(checker.static_state.values) {
|
|
id := ct_clone_graph(state, &checker.static_state, value.static_value)
|
|
return ct_observe_value(state, id, expr.span)
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(value.type)}), ct_flow(.Normal), true
|
|
}
|
|
} else if find_import(checker, state.file, expr.qualifier) == ast.INVALID_IMPORT {
|
|
if index, ok := ct_find_binding_index(state, expr.qualifier); ok {
|
|
base := ct_binding_value(state, index)
|
|
return ct_eval_field_value(state, base, expr.name, expr.span)
|
|
}
|
|
}
|
|
if enum_type, enum_ok := enum_type_from_name_expr(checker, expr, state.pkg, state.file); enum_ok {
|
|
member, ok := find_enum_member(checker, enum_type, expr.name)
|
|
if !ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown enum member '%s'", symbol_text(checker, expr.name))
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=enum_type, integer=member.value}), ct_flow(.Normal), true
|
|
}
|
|
target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false)
|
|
if !available {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "unavailable imported package")
|
|
}
|
|
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, state.file))
|
|
if global == ast.INVALID_GLOBAL || int(global) >= len(checker.ast_module.globals) {
|
|
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, state.file))
|
|
if template != ast.INVALID_FUNCTION {
|
|
function_type, _, function_ok := function_type_for_template(
|
|
checker,
|
|
template,
|
|
state.demanded,
|
|
state.demanded != nil,
|
|
)
|
|
if function_ok {
|
|
id := ct_add_value(state, Ct_Value{
|
|
kind=.Function, type=function_type, index=u64(template),
|
|
})
|
|
if types.is_valid(expected) {
|
|
return ct_coerce_expr_value(state, id, expected, expr.span)
|
|
}
|
|
return id, ct_flow(.Normal), true
|
|
}
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "function '%s' is not comptime-callable as a value", symbol_text(checker, expr.name))
|
|
}
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unresolved comptime value '%s'", symbol_text(checker, expr.name))
|
|
}
|
|
g := checker.ast_module.globals[global]
|
|
if g.external || !g.immutable || g.writable {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "global '%s' is not comptime-known", symbol_text(checker, expr.name))
|
|
}
|
|
global_expected := type_from_syntax(checker, g.type, g.pkg, g.file)
|
|
return ct_eval_expr(state, g.expr, global_expected, depth+1)
|
|
case .Function_Literal:
|
|
template := ast.Function_Id(u32(expr.integer))
|
|
function_type, _, ok := function_type_for_template(
|
|
checker,
|
|
template,
|
|
state.demanded,
|
|
state.demanded != nil,
|
|
)
|
|
if !ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
|
state, .Not_Comptime, expr.span, "function literal is not comptime-callable as a value",
|
|
)
|
|
}
|
|
id := ct_add_value(state, Ct_Value{
|
|
kind=.Function, type=function_type, index=u64(template),
|
|
})
|
|
if types.is_valid(expected) {
|
|
return ct_coerce_expr_value(state, id, expected, expr.span)
|
|
}
|
|
return id, ct_flow(.Normal), true
|
|
case .Comptime:
|
|
if expr.left != ast.INVALID_EXPR {
|
|
return ct_eval_expr(state, expr.left, expected, depth+1)
|
|
}
|
|
flow, ok := ct_exec_value_source(state, expr.body, symbol.INVALID, false, false, depth+1)
|
|
if ok && flow.kind == .Yield {
|
|
return flow.value, ct_flow(.Normal), true
|
|
}
|
|
return INVALID_CT_VALUE, flow, ct_fail(state, .Not_Comptime, expr.span, "comptime block must yield a value")
|
|
case .Array:
|
|
return ct_eval_array_expr(state, expr, expected, depth+1)
|
|
case .Struct_Literal:
|
|
return ct_eval_struct_expr(state, expr, expected, depth+1)
|
|
case .Type:
|
|
resolved := type_from_syntax(checker, expr.type, state.pkg, state.file)
|
|
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(resolved)}), ct_flow(.Normal), types.is_valid(resolved)
|
|
case .Anonymous_Struct_Type:
|
|
resolved := resolve_generated_struct_type(checker, expr_id, state.pkg, state.file, state)
|
|
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(resolved)}), ct_flow(.Normal), types.is_valid(resolved)
|
|
case .Enum_Literal:
|
|
return ct_eval_enum_literal(state, expr, expected, depth+1)
|
|
case .Null:
|
|
if !types.is_optional(expected, store) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "'null' requires an optional context")
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Null, type=expected}), ct_flow(.Normal), true
|
|
case .Unreachable:
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
|
state, .Not_Comptime, expr.span, "reached unreachable code during comptime evaluation",
|
|
)
|
|
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
|
|
}
|
|
if !symbol.is_valid(expr.name) {
|
|
return ct_eval_tuple_field_value(state, base_id, expr.integer, expr.span)
|
|
}
|
|
return ct_eval_field_value(state, base_id, expr.name, expr.span)
|
|
case .Index:
|
|
index_id, index_flow, index_ok := ct_eval_expr(state, expr.right, types.USIZE, depth+1)
|
|
if !index_ok || index_flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, index_flow, index_ok
|
|
}
|
|
index_value, index_is_int := ct_integer_value(state, index_id)
|
|
if !index_is_int || index_value < 0 || index_value > i128(0x7fff_ffff) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime index must be a non-negative integer")
|
|
}
|
|
base_id, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
if base_id != INVALID_CT_VALUE && int(base_id) < len(state.values) {
|
|
base := state.values[base_id]
|
|
if base.kind == .Slice {
|
|
place, _, _ := ct_slice_element_place(state, base, int(index_value))
|
|
if place == INVALID_CT_PLACE {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime slice index out of bounds")
|
|
}
|
|
value, value_ok := ct_place_get(state, place)
|
|
if !value_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
return ct_observe_value(state, value, expr.span)
|
|
}
|
|
if base.kind == .Pointer {
|
|
pointer_item, pointer_ok := types.node(store, base.type)
|
|
if pointer_ok && pointer_item.kind == .Pointer {
|
|
if pointer_item.many {
|
|
place, _, _ := ct_pointer_place(state, base, int(index_value))
|
|
if place == INVALID_CT_PLACE {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime pointer index out of bounds")
|
|
}
|
|
value, value_ok := ct_place_get(state, place)
|
|
if !value_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
return ct_observe_value(state, value, expr.span)
|
|
}
|
|
if array_item, array_ok := types.node(store, pointer_item.child); array_ok && array_item.kind == .Array {
|
|
base_place, _, _ := ct_pointer_place(state, base)
|
|
if base_place == INVALID_CT_PLACE || int(index_value) >= int(array_item.count) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime array index out of bounds")
|
|
}
|
|
place := ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Index, index=u32(index_value)}, array_item.child, pointer_item.mutable && array_item.mutable)
|
|
value, value_ok := ct_place_get(state, place)
|
|
if !value_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
return ct_observe_value(state, value, expr.span)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ct_eval_index_value(state, base_id, int(index_value), expr.span)
|
|
case .Unwrap:
|
|
value, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
return ct_unwrap_optional(state, value, expr.span)
|
|
case .Orelse:
|
|
value, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
v := state.values[value]
|
|
if v.kind == .Optional_Some {
|
|
children := ct_child_slice(state, v)
|
|
if len(children) > 0 {
|
|
return children[0], ct_flow(.Normal), true
|
|
}
|
|
}
|
|
if v.kind == .Null {
|
|
child := types.child_type(v.type, store)
|
|
return ct_eval_expr(state, expr.right, child, depth+1)
|
|
}
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "'orelse' requires an optional left operand")
|
|
case .Try:
|
|
return ct_eval_try_expr(state, expr, depth+1)
|
|
case .Catch:
|
|
return ct_eval_catch_expr(state, expr, expected, depth+1)
|
|
case .Range:
|
|
left, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
right, right_flow, right_ok := ct_eval_expr(state, expr.right, state.values[left].type, depth+1)
|
|
if !right_ok || right_flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, right_flow, right_ok
|
|
}
|
|
child_type := types.widest(state.values[left].type, state.values[right].type)
|
|
if !types.is_concrete_integer(child_type) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "range bounds must be compatible concrete integers")
|
|
}
|
|
left, _ = ct_coerce_value(state, left, child_type, expr.span)
|
|
right, _ = ct_coerce_value(state, right, child_type, expr.span)
|
|
start := u32(len(state.children))
|
|
append(&state.children, left, right)
|
|
return ct_add_value(state, Ct_Value{
|
|
kind=.Range, type=types.range(store, child_type), start=start, count=2, active=i64(expr.integer),
|
|
}), ct_flow(.Normal), true
|
|
case .Negate, .Not, .Bit_Not:
|
|
value, flow, ok := ct_eval_expr(state, expr.left, expected, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
return ct_eval_unary(state, expr.kind, value, expr.span)
|
|
case .Add, .Sub, .Mul, .Div, .Bit_And, .Bit_Or, .Bit_Xor, .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
|
|
left_expected := expected if expr.kind == .Div && types.is_float(expected, checker.target) else types.INVALID
|
|
if (expr.kind == .Bit_And || expr.kind == .Bit_Or || expr.kind == .Bit_Xor) &&
|
|
types.is_concrete_integer(expected) {
|
|
left_expected = expected
|
|
}
|
|
left_expr := checker.ast_module.exprs[expr.left]
|
|
right_expr := checker.ast_module.exprs[expr.right]
|
|
if left_expr.kind == .Null && right_expr.kind != .Null &&
|
|
(expr.kind == .Eq || expr.kind == .Ne) {
|
|
right, right_flow, right_ok := ct_eval_expr(state, expr.right, types.INVALID, depth+1)
|
|
if !right_ok || right_flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, right_flow, right_ok
|
|
}
|
|
left, flow, ok := ct_eval_expr(state, expr.left, state.values[right].type, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
return ct_eval_binary(state, expr.kind, left, right, expr.span)
|
|
}
|
|
left, flow, ok := ct_eval_expr(state, expr.left, left_expected, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
right, right_flow, right_ok := ct_eval_expr(state, expr.right, state.values[left].type, depth+1)
|
|
if !right_ok || right_flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, right_flow, right_ok
|
|
}
|
|
return ct_eval_binary(state, expr.kind, left, right, expr.span)
|
|
case .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
|
|
left, flow, ok := ct_eval_expr(state, expr.left, expected, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
right, right_flow, right_ok := ct_eval_expr(state, expr.right, types.U64, depth+1)
|
|
if !right_ok || right_flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, right_flow, right_ok
|
|
}
|
|
return ct_eval_binary(state, expr.kind, left, right, expr.span)
|
|
case .And, .Or:
|
|
left, flow, ok := ct_eval_expr(state, expr.left, types.BOOL, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
left_bool, bool_ok := ct_bool_value(state, left)
|
|
if !bool_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "'and'/'or' require bool operands")
|
|
}
|
|
if expr.kind == .And && !left_bool {
|
|
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=0}), ct_flow(.Normal), true
|
|
}
|
|
if expr.kind == .Or && left_bool {
|
|
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1}), ct_flow(.Normal), true
|
|
}
|
|
return ct_eval_expr(state, expr.right, types.BOOL, depth+1)
|
|
case .Call:
|
|
return ct_eval_call_expr(state, expr, expected, depth+1, expr_id)
|
|
case .Cast:
|
|
target := type_from_syntax(checker, expr.type, state.pkg, state.file)
|
|
value, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
return ct_scalar_cast(state, value, target, expr.span)
|
|
case .Address:
|
|
place, place_type, writable, flow, ok := ct_eval_place(state, expr.left, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
result_type := types.pointer(store, place_type, writable, false)
|
|
if types.is_pointer(expected, store) &&
|
|
types.equal(types.child_type(expected, store), place_type) &&
|
|
(!types.is_mutable(expected, store) || writable) {
|
|
result_type = expected
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Pointer, type=result_type, index=u64(place), active=-1}), ct_flow(.Normal), true
|
|
case .Deref:
|
|
place, _, _, flow, ok := ct_eval_place(state, expr_id, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
value, value_ok := ct_place_get(state, place)
|
|
if !value_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime pointer no longer points to live storage")
|
|
}
|
|
return ct_observe_value(state, value, expr.span)
|
|
case .Slice:
|
|
return ct_eval_slice_expr(state, expr, depth+1)
|
|
case .Undefined:
|
|
if value, ok := ct_undefined_value(state, expected); ok {
|
|
return value, ct_flow(.Normal), true
|
|
}
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "undefined comptime value requires a concrete scalar or aggregate type")
|
|
case .Keyed:
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "expression cannot be evaluated at comptime")
|
|
}
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "expression cannot be evaluated at comptime")
|
|
}
|
|
|
|
ct_coerce_expr_value :: proc(state: ^Ct_State, id: Ct_Value_Id, expected: types.Type, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
coerced, ok := ct_coerce_value(state, id, expected, span)
|
|
return coerced, ct_flow(.Normal), ok
|
|
}
|
|
|
|
ct_eval_array_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
checker := state.checker
|
|
store := &checker.module.types
|
|
element_type := types.INVALID
|
|
result_type := expected
|
|
expected_node, has_expected := types.node(store, expected)
|
|
if has_expected && expected_node.kind == .Array {
|
|
element_type = expected_node.child
|
|
if !expected_node.inferred_count && expected_node.count != u64(len(expr.args)) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
|
state, .Not_Comptime, expr.span, "array literal expects %d elements, got %d",
|
|
expected_node.count, len(expr.args),
|
|
)
|
|
}
|
|
if expected_node.inferred_count {
|
|
result_type = types.with_array_count(store, expected, u64(len(expr.args)))
|
|
}
|
|
} else {
|
|
has_expected = false
|
|
result_type = types.INVALID
|
|
}
|
|
values := make([]Ct_Value_Id, len(expr.args), checker.allocator)
|
|
defer delete(values, checker.allocator)
|
|
for arg, index 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)
|
|
}
|
|
values[index] = 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)
|
|
}
|
|
for &child in values {
|
|
coerced, ok := ct_coerce_value(state, child, element_type, expr.span)
|
|
if !ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
child = coerced
|
|
}
|
|
start := u32(len(state.children))
|
|
append(&state.children, ..values)
|
|
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 expr.tuple {
|
|
resolved := types.is_valid(struct_type)
|
|
fields := types.fields_for(store, struct_type) if resolved else nil
|
|
if resolved {
|
|
item, item_ok := types.node(store, struct_type)
|
|
if !item_ok || item.kind != .Struct || !item.tuple || len(fields) != len(expr.args) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "invalid tuple construction")
|
|
}
|
|
}
|
|
values := make([]Ct_Value_Id, len(expr.args), checker.allocator)
|
|
inferred_fields := make([]types.Field, len(expr.args), checker.allocator)
|
|
defer {
|
|
delete(values, checker.allocator)
|
|
delete(inferred_fields, checker.allocator)
|
|
}
|
|
for arg, index in expr.args {
|
|
expected_field := fields[index].type if resolved else types.INVALID
|
|
value, flow, ok := ct_eval_expr(state, arg, expected_field, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
if resolved {
|
|
value, ok = ct_coerce_value(state, value, expected_field, checker.ast_module.exprs[arg].span)
|
|
if !ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
} else {
|
|
inferred_fields[index].type = state.values[value].type
|
|
}
|
|
values[index] = value
|
|
}
|
|
if !resolved {
|
|
struct_type = types.struct_anonymous(store, inferred_fields, true)
|
|
}
|
|
start := u32(len(state.children))
|
|
append(&state.children, ..values)
|
|
return ct_add_value(state, Ct_Value{kind=.Struct, type=struct_type, start=start, count=u32(len(values))}), ct_flow(.Normal), true
|
|
}
|
|
if !types.is_record(struct_type, store) || types.is_opaque_struct(struct_type, store) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown or opaque record type '%s'", symbol_text(checker, expr.name))
|
|
}
|
|
fields := types.fields_for(store, struct_type)
|
|
union_record := types.is_union(struct_type, store)
|
|
if union_record && len(expr.args) != 1 {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "union literal requires exactly one field initializer")
|
|
}
|
|
values := make([]Ct_Value_Id, 1 if union_record else len(fields), checker.allocator)
|
|
initialized := make([]bool, len(fields), checker.allocator)
|
|
defer {
|
|
delete(values, checker.allocator)
|
|
delete(initialized, checker.allocator)
|
|
}
|
|
for &value in values {
|
|
value = INVALID_CT_VALUE
|
|
}
|
|
active_field: i64
|
|
for keyed in expr.args {
|
|
keyed_expr := checker.ast_module.exprs[keyed]
|
|
index, field, field_ok := find_struct_field(checker, struct_type, keyed_expr.name)
|
|
if !field_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, keyed_expr.span, "unknown struct field '%s'", symbol_text(checker, keyed_expr.name))
|
|
}
|
|
if initialized[index] {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, keyed_expr.span, "duplicate initializer for struct field '%s'", symbol_text(checker, keyed_expr.name))
|
|
}
|
|
initialized[index] = true
|
|
value_index := 0 if union_record else index
|
|
active_field = i64(index)
|
|
if keyed_expr.left == ast.INVALID_EXPR {
|
|
if !types.is_void(field.type) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, keyed_expr.span, "field '%s' requires a value", symbol_text(checker, keyed_expr.name))
|
|
}
|
|
continue
|
|
}
|
|
value, flow, value_ok := ct_eval_expr(state, keyed_expr.left, field.type, depth+1)
|
|
if !value_ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, value_ok
|
|
}
|
|
value, value_ok = ct_coerce_value(state, value, field.type, keyed_expr.span)
|
|
if !value_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
values[value_index] = value
|
|
}
|
|
if !union_record {
|
|
for field, index in fields {
|
|
if values[index] != INVALID_CT_VALUE {
|
|
continue
|
|
}
|
|
field_default, default_values, has_default := find_struct_field_default(checker, struct_type, symbol.Id(field.name))
|
|
if !has_default {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "missing initializer for struct field '%s'", symbol_text(checker, symbol.Id(field.name)))
|
|
}
|
|
if field_default.static_value != INVALID_CT_VALUE {
|
|
value := ct_clone_graph(state, &checker.static_state, field_default.static_value)
|
|
if value == INVALID_CT_VALUE {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
values[index] = value
|
|
continue
|
|
}
|
|
previous_pkg, previous_file := state.pkg, state.file
|
|
previous_comptime := checker.current_comptime_values
|
|
state.pkg, state.file = field_default.pkg, field_default.file
|
|
checker.current_comptime_values = default_values
|
|
value, flow, ok := ct_eval_expr(state, field_default.expr, field.type, depth+1)
|
|
state.pkg, state.file = previous_pkg, previous_file
|
|
checker.current_comptime_values = previous_comptime
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
value, ok = ct_coerce_value(state, value, field.type, field_default.span)
|
|
if !ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
values[index] = value
|
|
}
|
|
}
|
|
start := u32(len(state.children))
|
|
append(&state.children, ..values)
|
|
return ct_add_value(state, Ct_Value{kind=.Struct, type=struct_type, start=start, count=u32(len(values)), active=active_field}), ct_flow(.Normal), true
|
|
}
|
|
|
|
ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
checker := state.checker
|
|
store := &checker.module.types
|
|
base_value, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
container_place := INVALID_CT_PLACE
|
|
container_start := 0
|
|
container_len := 0
|
|
item: types.Node
|
|
item_ok := false
|
|
if base_value != INVALID_CT_VALUE && int(base_value) < len(state.values) {
|
|
base := state.values[base_value]
|
|
if base.kind == .Slice {
|
|
container_place = Ct_Place_Id(base.index)
|
|
container_start = int(base.start)
|
|
container_len = int(base.count)
|
|
item, item_ok = types.node(store, base.type)
|
|
} else if base.kind == .Pointer {
|
|
pointer_item, pointer_ok := types.node(store, base.type)
|
|
array_item: types.Node
|
|
array_ok := false
|
|
if pointer_ok {
|
|
array_item, array_ok = types.node(store, pointer_item.child)
|
|
}
|
|
if pointer_ok && !pointer_item.many && array_ok && array_item.kind == .Array {
|
|
container_place, _, _ = ct_pointer_place(state, base)
|
|
container_len = int(array_item.count)
|
|
item = array_item
|
|
item.mutable = pointer_item.mutable && array_item.mutable
|
|
item_ok = true
|
|
}
|
|
}
|
|
}
|
|
if container_place == INVALID_CT_PLACE {
|
|
place, place_type, writable, place_flow, place_ok := ct_eval_place(state, expr.left, depth+1)
|
|
if !place_ok || place_flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, place_flow, place_ok
|
|
}
|
|
array_item, array_ok := types.node(store, place_type)
|
|
if !array_ok || array_item.kind != .Array {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "slicing requires a comptime array, slice, or pointer-to-array")
|
|
}
|
|
container_place = place
|
|
container_len = int(array_item.count)
|
|
item = array_item
|
|
item.mutable = writable && array_item.mutable
|
|
item_ok = true
|
|
}
|
|
if !item_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "slicing requires a comptime array, slice, or pointer-to-array")
|
|
}
|
|
start := 0
|
|
end := container_len
|
|
for bound, index in expr.args {
|
|
if bound == ast.INVALID_EXPR {
|
|
continue
|
|
}
|
|
value, bound_flow, bound_ok := ct_eval_expr(state, bound, types.USIZE, depth+1)
|
|
if !bound_ok || bound_flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, bound_flow, bound_ok
|
|
}
|
|
integer, integer_ok := ct_integer_value(state, value)
|
|
if !integer_ok || integer < 0 || integer > i128(0x7fff_ffff) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "slice bounds must be non-negative integers")
|
|
}
|
|
if index == 0 {
|
|
start = int(integer)
|
|
} else {
|
|
end = int(integer)
|
|
}
|
|
}
|
|
if start < 0 || end < start || end > container_len {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime slice bounds out of range")
|
|
}
|
|
preserve_sentinel := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
|
|
result_type := types.slice(store, item.child, item.mutable, preserve_sentinel, item.sentinel)
|
|
return ct_add_value(state, Ct_Value{
|
|
kind=.Slice, type=result_type, index=u64(container_place),
|
|
start=u32(container_start+start), count=u32(end-start),
|
|
}), ct_flow(.Normal), true
|
|
}
|
|
|
|
ct_eval_enum_literal :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
checker := state.checker
|
|
store := &checker.module.types
|
|
if types.is_tagged_union(expected, store) {
|
|
index, field, found := find_struct_field(checker, expected, expr.name)
|
|
if !found {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown variant '.%s' on '%s'", symbol_text(checker, expr.name), type_label(checker, expected))
|
|
}
|
|
payload := INVALID_CT_VALUE
|
|
if expr.left != ast.INVALID_EXPR {
|
|
value, flow, ok := ct_eval_expr(state, expr.left, field.type, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
payload, ok = ct_coerce_value(state, value, field.type, expr.span)
|
|
if !ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
} else if !types.is_void(field.type) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "variant '.%s' on '%s' needs a payload", symbol_text(checker, expr.name), type_label(checker, expected))
|
|
}
|
|
start := u32(len(state.children))
|
|
append(&state.children, payload)
|
|
return ct_add_value(state, Ct_Value{kind=.Struct, type=expected, start=start, count=1, active=i64(index)}), ct_flow(.Normal), true
|
|
}
|
|
if expr.left != ast.INVALID_EXPR {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "'.%s{...}' requires a tagged-union context", symbol_text(checker, expr.name))
|
|
}
|
|
if !types.is_enum(expected, store) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "'.%s' requires an enum context", symbol_text(checker, expr.name))
|
|
}
|
|
member, ok := find_enum_member(checker, expected, expr.name)
|
|
if !ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown enum member '%s'", symbol_text(checker, expr.name))
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=expected, integer=member.value}), ct_flow(.Normal), true
|
|
}
|
|
|
|
ct_eval_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, name: symbol.Id, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
checker := state.checker
|
|
if base_id == INVALID_CT_VALUE || int(base_id) >= len(state.values) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
base := state.values[base_id]
|
|
field_name := symbol_text(checker, name)
|
|
if base.kind == .Array && field_name == "len" {
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(base.count)}), ct_flow(.Normal), true
|
|
}
|
|
if base.kind == .String && field_name == "len" {
|
|
length := i128(0)
|
|
if base.index < u64(len(checker.ast_module.strings)) {
|
|
length = i128(len(checker.ast_module.strings[base.index]))
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=length}), ct_flow(.Normal), true
|
|
}
|
|
if base.kind == .Slice && field_name == "len" {
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(base.count)}), ct_flow(.Normal), true
|
|
}
|
|
if base.kind == .Pointer && field_name == "len" {
|
|
if length, ok := ct_sequence_length(state, base); ok {
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(length)}), ct_flow(.Normal), true
|
|
}
|
|
}
|
|
if (base.kind == .Slice || base.kind == .Pointer) && field_name == "ptr" {
|
|
item, ok := types.container(base.type, &checker.module.types)
|
|
if !ok || item.kind == .Pointer {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime value does not expose '.ptr'")
|
|
}
|
|
container_place := INVALID_CT_PLACE
|
|
offset := 0
|
|
if base.kind == .Slice {
|
|
container_place = Ct_Place_Id(base.index)
|
|
offset = int(base.start)
|
|
} else {
|
|
container_place, _, _ = ct_pointer_place(state, base)
|
|
}
|
|
if container_place == INVALID_CT_PLACE {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime pointer no longer points to live storage")
|
|
}
|
|
return ct_add_value(state, Ct_Value{
|
|
kind=.Pointer, type=container_pointer_type(&checker.module.types, item),
|
|
index=u64(container_place), active=i64(offset),
|
|
}), ct_flow(.Normal), true
|
|
}
|
|
if base.kind == .Pointer {
|
|
place, child, _ := ct_pointer_place(state, base)
|
|
if place == INVALID_CT_PLACE {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime pointer no longer points to live storage")
|
|
}
|
|
if types.is_record(child, &checker.module.types) {
|
|
index, field, ok := find_struct_field(checker, child, name)
|
|
if !ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "unknown struct field '%s'", field_name)
|
|
}
|
|
field_place := ct_extend_place(state, place, Ct_Path_Elem{kind=.Field, index=u32(index)}, field.type, false)
|
|
if value, value_ok := ct_place_get(state, field_place); value_ok {
|
|
return ct_observe_value(state, value, span)
|
|
}
|
|
}
|
|
}
|
|
if base.kind != .Struct {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "unknown comptime field '%s'", field_name)
|
|
}
|
|
index, field, ok := find_struct_field(checker, base.type, name)
|
|
if !ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "unknown struct field '%s'", field_name)
|
|
}
|
|
children := ct_child_slice(state, base)
|
|
if types.is_union(base.type, &checker.module.types) {
|
|
if int(base.active) != index || len(children) == 0 || children[0] == INVALID_CT_VALUE {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "variant '%s' has no payload to read", field_name)
|
|
}
|
|
return ct_observe_value(state, children[0], span)
|
|
}
|
|
if index < 0 || index >= len(children) || types.is_void(field.type) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "field '%s' has no value", field_name)
|
|
}
|
|
return ct_observe_value(state, children[index], span)
|
|
}
|
|
|
|
ct_eval_tuple_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, index: u64, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
if base_id == INVALID_CT_VALUE || int(base_id) >= len(state.values) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
base := state.values[base_id]
|
|
base_type := base.type
|
|
if base.kind == .Pointer {
|
|
place, child, _ := ct_pointer_place(state, base)
|
|
field_index, field, ok := find_tuple_field(state.checker, child, index)
|
|
if place == INVALID_CT_PLACE || !ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "tuple field index is out of bounds")
|
|
}
|
|
field_place := ct_extend_place(state, place, Ct_Path_Elem{kind=.Field, index=u32(field_index)}, field.type, false)
|
|
value, value_ok := ct_place_get(state, field_place)
|
|
if !value_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
return ct_observe_value(state, value, span)
|
|
}
|
|
field_index, _, ok := find_tuple_field(state.checker, base_type, index)
|
|
children := ct_child_slice(state, base)
|
|
if !ok || field_index < 0 || field_index >= len(children) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "tuple field index is out of bounds")
|
|
}
|
|
return ct_observe_value(state, children[field_index], span)
|
|
}
|
|
|
|
ct_eval_index_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, index: int, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
checker := state.checker
|
|
if base_id == INVALID_CT_VALUE || int(base_id) >= len(state.values) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
base := state.values[base_id]
|
|
if base.kind == .Array {
|
|
children := ct_child_slice(state, base)
|
|
if index < 0 || index >= len(children) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime array index out of bounds")
|
|
}
|
|
return ct_observe_value(state, children[index], span)
|
|
}
|
|
if base.kind == .String {
|
|
if base.index >= u64(len(checker.ast_module.strings)) || index < 0 || index >= len(checker.ast_module.strings[base.index]) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime string index out of bounds")
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=types.U8, integer=i128(checker.ast_module.strings[base.index][index])}), ct_flow(.Normal), true
|
|
}
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "indexing requires a comptime array or string")
|
|
}
|
|
|
|
ct_sequence_length :: proc(state: ^Ct_State, value: Ct_Value) -> (int, bool) {
|
|
checker := state.checker
|
|
#partial switch value.kind {
|
|
case .Array:
|
|
return int(value.count), true
|
|
case .String:
|
|
if value.index < u64(len(checker.ast_module.strings)) {
|
|
return len(checker.ast_module.strings[value.index]), true
|
|
}
|
|
case .Slice:
|
|
return int(value.count), true
|
|
case .Pointer:
|
|
child := types.child_type(value.type, &checker.module.types)
|
|
item, ok := types.node(&checker.module.types, child)
|
|
if ok && item.kind == .Array {
|
|
return int(item.count), true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
ct_pointer_place :: proc(state: ^Ct_State, value: Ct_Value, extra_index := 0) -> (Ct_Place_Id, types.Type, bool) {
|
|
if value.kind != .Pointer {
|
|
return INVALID_CT_PLACE, types.INVALID, false
|
|
}
|
|
store := &state.checker.module.types
|
|
pointer_item, ok := types.node(store, value.type)
|
|
if !ok || pointer_item.kind != .Pointer {
|
|
return INVALID_CT_PLACE, types.INVALID, false
|
|
}
|
|
base := Ct_Place_Id(value.index)
|
|
child := pointer_item.child
|
|
if base == INVALID_CT_PLACE || int(base) >= len(state.places) || !ct_place_live(state, base) {
|
|
return INVALID_CT_PLACE, types.INVALID, false
|
|
}
|
|
if pointer_item.many {
|
|
index := int(value.active)+extra_index
|
|
if index < 0 {
|
|
return INVALID_CT_PLACE, types.INVALID, false
|
|
}
|
|
place := state.places[base]
|
|
item, item_ok := types.node(store, place.type)
|
|
if !item_ok || item.kind != .Array || index >= int(item.count) {
|
|
return INVALID_CT_PLACE, types.INVALID, false
|
|
}
|
|
return ct_extend_place(
|
|
state, base, Ct_Path_Elem{kind=.Index, index=u32(index)},
|
|
child, place.writable && item.mutable,
|
|
), child, pointer_item.mutable
|
|
}
|
|
return base, child, pointer_item.mutable
|
|
}
|
|
|
|
ct_slice_element_place :: proc(state: ^Ct_State, value: Ct_Value, index: int) -> (Ct_Place_Id, types.Type, bool) {
|
|
if value.kind != .Slice || index < 0 || index >= int(value.count) {
|
|
return INVALID_CT_PLACE, types.INVALID, false
|
|
}
|
|
store := &state.checker.module.types
|
|
item, ok := types.node(store, value.type)
|
|
if !ok || item.kind != .Slice {
|
|
return INVALID_CT_PLACE, types.INVALID, false
|
|
}
|
|
base := Ct_Place_Id(value.index)
|
|
return ct_extend_place(
|
|
state, base, Ct_Path_Elem{kind=.Index, index=u32(int(value.start)+index)},
|
|
item.child, item.mutable,
|
|
), item.child, item.mutable
|
|
}
|
|
|
|
ct_eval_place :: proc(
|
|
state: ^Ct_State,
|
|
expr_id: ast.Expr_Id,
|
|
depth: int,
|
|
) -> (Ct_Place_Id, types.Type, bool, Ct_Flow, bool) {
|
|
checker := state.checker
|
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), false
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
store := &checker.module.types
|
|
#partial switch expr.kind {
|
|
case .Name:
|
|
if !symbol.is_valid(expr.qualifier) {
|
|
if index, ok := ct_find_binding_index(state, expr.name); ok {
|
|
place := ct_binding_place(state, index)
|
|
if place != INVALID_CT_PLACE {
|
|
return place, state.bindings[index].type, state.bindings[index].mutable, ct_flow(.Normal), true
|
|
}
|
|
}
|
|
} else if find_import(checker, state.file, expr.qualifier) == ast.INVALID_IMPORT {
|
|
if index, ok := ct_find_binding_index(state, expr.qualifier); ok {
|
|
base_value := ct_binding_value(state, index)
|
|
base_place := INVALID_CT_PLACE
|
|
base_type := state.bindings[index].type
|
|
base_writable := state.bindings[index].mutable
|
|
if base_value != INVALID_CT_VALUE && int(base_value) < len(state.values) &&
|
|
state.values[base_value].kind == .Pointer {
|
|
base_place, base_type, base_writable = ct_pointer_place(state, state.values[base_value])
|
|
} else {
|
|
base_place = ct_binding_place(state, index)
|
|
}
|
|
if base_place != INVALID_CT_PLACE && types.is_record(base_type, store) {
|
|
field_index, field, field_ok := find_struct_field(checker, base_type, expr.name)
|
|
if !field_ok {
|
|
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown struct field '%s'", symbol_text(checker, expr.name))
|
|
}
|
|
return ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Field, index=u32(field_index)}, field.type, base_writable),
|
|
field.type, base_writable, ct_flow(.Normal), true
|
|
}
|
|
}
|
|
}
|
|
target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false)
|
|
if !available {
|
|
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "unavailable imported package")
|
|
}
|
|
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, state.file))
|
|
if global == ast.INVALID_GLOBAL || int(global) >= len(checker.ast_module.globals) {
|
|
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unresolved comptime value '%s'", symbol_text(checker, expr.name))
|
|
}
|
|
g := checker.ast_module.globals[global]
|
|
if g.external || !g.immutable || g.writable {
|
|
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "global '%s' is not comptime-known", symbol_text(checker, expr.name))
|
|
}
|
|
global_expected := type_from_syntax(checker, g.type, g.pkg, g.file)
|
|
value, flow, ok := ct_eval_expr(state, g.expr, global_expected, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_PLACE, types.INVALID, false, flow, ok
|
|
}
|
|
cell := ct_add_cell(state, value, false)
|
|
place := ct_add_place(state, cell, state.values[value].type, false)
|
|
return place, state.values[value].type, false, ct_flow(.Normal), true
|
|
case .Deref:
|
|
pointer, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_PLACE, types.INVALID, false, flow, ok
|
|
}
|
|
if pointer == INVALID_CT_VALUE || int(pointer) >= len(state.values) || state.values[pointer].kind != .Pointer {
|
|
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "postfix '^' requires a comptime pointer")
|
|
}
|
|
place, child, writable := ct_pointer_place(state, state.values[pointer])
|
|
if place == INVALID_CT_PLACE {
|
|
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime pointer no longer points to live storage")
|
|
}
|
|
return place, child, writable, ct_flow(.Normal), true
|
|
case .Field:
|
|
base_value, flow, value_ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
|
if value_ok && flow.kind == .Normal && base_value != INVALID_CT_VALUE && int(base_value) < len(state.values) &&
|
|
state.values[base_value].kind == .Pointer {
|
|
base_place, base_type, base_writable := ct_pointer_place(state, state.values[base_value])
|
|
if base_place != INVALID_CT_PLACE && types.is_record(base_type, store) {
|
|
index, field, field_ok := find_struct_field(checker, base_type, expr.name)
|
|
if !field_ok {
|
|
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown struct field '%s'", symbol_text(checker, expr.name))
|
|
}
|
|
return ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Field, index=u32(index)}, field.type, base_writable),
|
|
field.type, base_writable, ct_flow(.Normal), true
|
|
}
|
|
}
|
|
if !value_ok || flow.kind != .Normal {
|
|
return INVALID_CT_PLACE, types.INVALID, false, flow, value_ok
|
|
}
|
|
base_place, base_type, base_writable, place_flow, place_ok := ct_eval_place(state, expr.left, depth+1)
|
|
if !place_ok || place_flow.kind != .Normal {
|
|
return INVALID_CT_PLACE, types.INVALID, false, place_flow, place_ok
|
|
}
|
|
index, field, field_ok := find_struct_field(checker, base_type, expr.name)
|
|
if !field_ok {
|
|
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown struct field '%s'", symbol_text(checker, expr.name))
|
|
}
|
|
return ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Field, index=u32(index)}, field.type, base_writable),
|
|
field.type, base_writable, ct_flow(.Normal), true
|
|
case .Index:
|
|
index_value, index_flow, index_ok := ct_eval_expr(state, expr.right, types.USIZE, depth+1)
|
|
if !index_ok || index_flow.kind != .Normal {
|
|
return INVALID_CT_PLACE, types.INVALID, false, index_flow, index_ok
|
|
}
|
|
index_int, int_ok := ct_integer_value(state, index_value)
|
|
if !int_ok || index_int < 0 || index_int > i128(0x7fff_ffff) {
|
|
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime index must be a non-negative integer")
|
|
}
|
|
base_value, flow, value_ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
|
if !value_ok || flow.kind != .Normal {
|
|
return INVALID_CT_PLACE, types.INVALID, false, flow, value_ok
|
|
}
|
|
if base_value != INVALID_CT_VALUE && int(base_value) < len(state.values) {
|
|
base := state.values[base_value]
|
|
if base.kind == .Slice {
|
|
place, child, writable := ct_slice_element_place(state, base, int(index_int))
|
|
if place == INVALID_CT_PLACE {
|
|
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime slice index out of bounds")
|
|
}
|
|
return place, child, writable, ct_flow(.Normal), true
|
|
}
|
|
if base.kind == .Pointer {
|
|
pointer_item, pointer_ok := types.node(store, base.type)
|
|
if pointer_ok && pointer_item.kind == .Pointer {
|
|
if pointer_item.many {
|
|
place, child, writable := ct_pointer_place(state, base, int(index_int))
|
|
if place == INVALID_CT_PLACE {
|
|
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime pointer index out of bounds")
|
|
}
|
|
return place, child, writable, ct_flow(.Normal), true
|
|
}
|
|
array_item, array_ok := types.node(store, pointer_item.child)
|
|
if array_ok && array_item.kind == .Array {
|
|
base_place, _, writable := ct_pointer_place(state, base)
|
|
if int(index_int) >= int(array_item.count) {
|
|
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime array index out of bounds")
|
|
}
|
|
return ct_extend_place(
|
|
state, base_place, Ct_Path_Elem{kind=.Index, index=u32(index_int)},
|
|
array_item.child, writable && array_item.mutable,
|
|
), array_item.child, writable && array_item.mutable, ct_flow(.Normal), true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
base_place, base_type, base_writable, place_flow, place_ok := ct_eval_place(state, expr.left, depth+1)
|
|
if !place_ok || place_flow.kind != .Normal {
|
|
return INVALID_CT_PLACE, types.INVALID, false, place_flow, place_ok
|
|
}
|
|
item, item_ok := types.node(store, base_type)
|
|
if !item_ok || item.kind != .Array || int(index_int) >= int(item.count) {
|
|
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime array index out of bounds")
|
|
}
|
|
writable := base_writable && item.mutable
|
|
return ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Index, index=u32(index_int)}, item.child, writable),
|
|
item.child, writable, ct_flow(.Normal), true
|
|
}
|
|
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "expression is not addressable at comptime")
|
|
}
|
|
|
|
ct_unwrap_optional :: proc(state: ^Ct_State, id: Ct_Value_Id, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
value := state.values[id]
|
|
if value.kind == .Optional_Some {
|
|
children := ct_child_slice(state, value)
|
|
if len(children) > 0 {
|
|
return children[0], ct_flow(.Normal), true
|
|
}
|
|
}
|
|
if value.kind == .Null {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime optional unwrap of null")
|
|
}
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "postfix '?' requires an optional")
|
|
}
|
|
|
|
ct_normalize_integer :: proc(state: ^Ct_State, value: i128, type: types.Type) -> i128 {
|
|
bits := types.bits(type, state.checker.target)
|
|
mask := (i128(1) << u32(bits))-1
|
|
raw := value & mask
|
|
if types.is_signed(type, state.checker.target) {
|
|
sign := i128(1) << u32(bits-1)
|
|
if raw & sign != 0 {
|
|
return raw-(i128(1) << u32(bits))
|
|
}
|
|
}
|
|
return raw
|
|
}
|
|
|
|
ct_eval_unary :: proc(state: ^Ct_State, op: ast.Expr_Kind, id: Ct_Value_Id, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
value := state.values[id]
|
|
if op == .Not {
|
|
if value.kind != .Bool {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "'!' requires a bool operand")
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if value.integer == 0 else 0}), ct_flow(.Normal), true
|
|
}
|
|
if op == .Bit_Not {
|
|
if value.kind != .Integer || !types.is_concrete_integer(value.type) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "'~' requires a concrete integer operand")
|
|
}
|
|
value.integer = ct_normalize_integer(state, ~value.integer, value.type)
|
|
return ct_add_value(state, value), ct_flow(.Normal), true
|
|
}
|
|
if value.kind == .Integer {
|
|
result, overflow := intrinsics.overflow_sub(i128(0), value.integer)
|
|
if overflow {
|
|
state.error = .Overflow
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
value.integer = result
|
|
return ct_add_value(state, value), ct_flow(.Normal), true
|
|
}
|
|
if value.kind == .Float {
|
|
value.float = -value.float
|
|
return ct_add_value(state, value), ct_flow(.Normal), true
|
|
}
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "negation requires a signed integer or float")
|
|
}
|
|
|
|
ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: Ct_Value_Id, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
if left_id == INVALID_CT_VALUE || right_id == INVALID_CT_VALUE ||
|
|
int(left_id) >= len(state.values) || int(right_id) >= len(state.values) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
left := state.values[left_id]
|
|
right := state.values[right_id]
|
|
is_compare := op == .Eq || op == .Ne || op == .Lt || op == .Le || op == .Gt || op == .Ge
|
|
if left.kind == .Null || right.kind == .Null {
|
|
if (op != .Eq && op != .Ne) ||
|
|
!types.is_optional(left.type, &state.checker.module.types) ||
|
|
!types.equal(left.type, right.type) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "'null' only supports '==' and '!=' with an optional value")
|
|
}
|
|
equal := left.kind == .Null && right.kind == .Null
|
|
if op == .Ne {
|
|
equal = !equal
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if equal else 0}), ct_flow(.Normal), true
|
|
}
|
|
if left.kind == .Type || right.kind == .Type {
|
|
if left.kind != .Type || right.kind != .Type || (op != .Eq && op != .Ne) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "type values only support '==' and '!=' with another type")
|
|
}
|
|
ok := types.equal(types.Type(left.index), types.Type(right.index))
|
|
if op == .Ne {
|
|
ok = !ok
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true
|
|
}
|
|
if left.kind == .Bool && right.kind == .Bool {
|
|
if op != .Eq && op != .Ne {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "bool values only support '==' and '!='")
|
|
}
|
|
ok := left.integer == right.integer
|
|
if op == .Ne {
|
|
ok = !ok
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true
|
|
}
|
|
if left.kind == .Float || right.kind == .Float {
|
|
lf := left.float if left.kind == .Float else f64(left.integer)
|
|
rf := right.float if right.kind == .Float else f64(right.integer)
|
|
if is_compare {
|
|
ok := false
|
|
#partial switch op {
|
|
case .Eq: ok = lf == rf
|
|
case .Ne: ok = lf != rf
|
|
case .Lt: ok = lf < rf
|
|
case .Le: ok = lf <= rf
|
|
case .Gt: ok = lf > rf
|
|
case .Ge: ok = lf >= rf
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true
|
|
}
|
|
result := lf
|
|
#partial switch op {
|
|
case .Add: result = lf + rf
|
|
case .Sub: result = lf - rf
|
|
case .Mul: result = lf * rf
|
|
case .Div: result = lf / rf
|
|
}
|
|
result_type := types.widest(left.type, right.type)
|
|
if !types.is_float(result_type, state.checker.target) {
|
|
result_type = types.F64
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Float, type=result_type, float=result}), ct_flow(.Normal), true
|
|
}
|
|
if ct_is_integer_like(left) && ct_is_integer_like(right) {
|
|
if is_compare {
|
|
ok := false
|
|
#partial switch op {
|
|
case .Eq: ok = left.integer == right.integer
|
|
case .Ne: ok = left.integer != right.integer
|
|
case .Lt: ok = left.integer < right.integer
|
|
case .Le: ok = left.integer <= right.integer
|
|
case .Gt: ok = left.integer > right.integer
|
|
case .Ge: ok = left.integer >= right.integer
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true
|
|
}
|
|
if op == .Div {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
|
state, .Integer_Division, span,
|
|
"integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!",
|
|
)
|
|
}
|
|
if op == .Bit_And || op == .Bit_Or || op == .Bit_Xor {
|
|
result_type := types.widest(left.type, right.type)
|
|
if !types.is_concrete_integer(result_type) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "bitwise operation requires compatible concrete integer operands")
|
|
}
|
|
value := left.integer & right.integer
|
|
#partial switch op {
|
|
case .Bit_Or: value = left.integer | right.integer
|
|
case .Bit_Xor: value = left.integer ~ right.integer
|
|
}
|
|
value = ct_normalize_integer(state, value, result_type)
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=value}), ct_flow(.Normal), true
|
|
}
|
|
if op == .Shift_Left || op == .Shift_Right || op == .Shift_Left_Saturating {
|
|
if !types.is_concrete_integer(left.type) || !types.is_unsigned(right.type, state.checker.target) || right.integer < 0 {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "shift requires a concrete integer value and unsigned integer count")
|
|
}
|
|
bits := types.bits(left.type, state.checker.target)
|
|
if right.integer >= i128(bits) {
|
|
if op != .Shift_Left_Saturating {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "shift count exceeds integer width")
|
|
}
|
|
endpoint := i128(0)
|
|
if left.integer != 0 {
|
|
if types.is_signed(left.type, state.checker.target) {
|
|
endpoint = -(i128(1) << u32(bits-1)) if left.integer < 0 else (i128(1) << u32(bits-1))-1
|
|
} else {
|
|
endpoint = (i128(1) << u32(bits))-1
|
|
}
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=left.type, integer=endpoint}), ct_flow(.Normal), true
|
|
}
|
|
count := u32(right.integer)
|
|
if op == .Shift_Right {
|
|
value := left.integer >> count
|
|
if !types.is_signed(left.type, state.checker.target) {
|
|
value = ct_normalize_integer(state, left.integer, left.type) >> count
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=left.type, integer=value}), ct_flow(.Normal), true
|
|
}
|
|
if op == .Shift_Left_Saturating {
|
|
factor := i128(1) << count
|
|
value := left.integer*factor
|
|
if types.is_signed(left.type, state.checker.target) {
|
|
minimum := -(i128(1) << u32(bits-1))
|
|
maximum := (i128(1) << u32(bits-1))-1
|
|
value = max(minimum, min(maximum, value))
|
|
} else {
|
|
maximum := (i128(1) << u32(bits))-1
|
|
value = min(maximum, ct_normalize_integer(state, left.integer, left.type)*factor)
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=left.type, integer=value}), ct_flow(.Normal), true
|
|
}
|
|
value := ct_normalize_integer(state, ct_normalize_integer(state, left.integer, left.type) << count, left.type)
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=left.type, integer=value}), ct_flow(.Normal), true
|
|
}
|
|
value: i128
|
|
overflow := false
|
|
#partial switch op {
|
|
case .Sub:
|
|
value, overflow = intrinsics.overflow_sub(left.integer, right.integer)
|
|
case .Mul:
|
|
value, overflow = intrinsics.overflow_mul(left.integer, right.integer)
|
|
case:
|
|
value, overflow = intrinsics.overflow_add(left.integer, right.integer)
|
|
}
|
|
if overflow {
|
|
state.error = .Overflow
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
result_type := types.widest(left.type, right.type)
|
|
if !types.is_concrete_integer(result_type) && !types.is_enum(result_type, &state.checker.module.types) {
|
|
result_type = ct_default_integer_type(value)
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=value}), ct_flow(.Normal), true
|
|
}
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
|
|
}
|
|
|
|
ct_eval_division_builtin :: proc(
|
|
state: ^Ct_State,
|
|
kind: Division_Builtin,
|
|
left_id, right_id: Ct_Value_Id,
|
|
span: source.Span,
|
|
) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
if left_id == INVALID_CT_VALUE || right_id == INVALID_CT_VALUE ||
|
|
int(left_id) >= len(state.values) || int(right_id) >= len(state.values) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
left := state.values[left_id]
|
|
right := state.values[right_id]
|
|
result_type := types.widest(left.type, right.type)
|
|
if !types.is_concrete_scalar(result_type) || types.is_bool(result_type) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
|
state, .Not_Comptime, span, "division builtins require compatible numeric operands",
|
|
)
|
|
}
|
|
left_id, left_ok := ct_coerce_value(state, left_id, result_type, span)
|
|
right_id, right_ok := ct_coerce_value(state, right_id, result_type, span)
|
|
if !left_ok || !right_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
left = state.values[left_id]
|
|
right = state.values[right_id]
|
|
if left.kind == .Float && right.kind == .Float {
|
|
if right.float == 0 {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Div_By_Zero, span, "division builtin denominator is zero")
|
|
}
|
|
quotient := left.float / right.float
|
|
result := quotient
|
|
#partial switch kind {
|
|
case .Trunc: result = math.trunc(quotient)
|
|
case .Floor: result = math.floor(quotient)
|
|
case .Ceil: result = math.ceil(quotient)
|
|
case .Exact:
|
|
result = math.trunc(quotient)
|
|
if result * right.float != left.float {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Non_Exact, span, "exact division has a remainder")
|
|
}
|
|
case .Rem, .Mod:
|
|
result = left.float - math.trunc(quotient) * right.float
|
|
if kind == .Mod && result != 0 && (result < 0) != (right.float < 0) {
|
|
result += right.float
|
|
}
|
|
}
|
|
if types.bits(result_type, state.checker.target) == 32 {
|
|
result = f64(f32(result))
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Float, type=result_type, float=result}), ct_flow(.Normal), true
|
|
}
|
|
if left.kind != .Integer || right.kind != .Integer {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "division builtins require compatible numeric operands")
|
|
}
|
|
if right.integer == 0 {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Div_By_Zero, span, "division builtin denominator is zero")
|
|
}
|
|
is_quotient := kind == .Trunc || kind == .Floor || kind == .Exact || kind == .Ceil
|
|
if is_quotient && types.is_signed(result_type, state.checker.target) {
|
|
minimum := -(i128(1) << u32(types.bits(result_type, state.checker.target)-1))
|
|
if left.integer == minimum && right.integer == -1 {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Overflow, span, "signed integer division overflow")
|
|
}
|
|
}
|
|
quotient := left.integer / right.integer
|
|
remainder := left.integer % right.integer
|
|
result := quotient
|
|
#partial switch kind {
|
|
case .Floor:
|
|
if remainder != 0 && (left.integer < 0) != (right.integer < 0) {
|
|
result -= 1
|
|
}
|
|
case .Ceil:
|
|
if remainder != 0 && (left.integer < 0) == (right.integer < 0) {
|
|
result += 1
|
|
}
|
|
case .Exact:
|
|
if remainder != 0 {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Non_Exact, span, "exact division has a remainder")
|
|
}
|
|
case .Rem: result = remainder
|
|
case .Mod:
|
|
result = remainder
|
|
if result != 0 && (result < 0) != (right.integer < 0) {
|
|
result += right.integer
|
|
}
|
|
case:
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=result}), ct_flow(.Normal), true
|
|
}
|
|
|
|
ct_eval_division_call :: proc(
|
|
state: ^Ct_State,
|
|
expr: ast.Expr,
|
|
kind: Division_Builtin,
|
|
expected: types.Type,
|
|
depth: int,
|
|
) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
checker := state.checker
|
|
if len(expr.args) != 2 {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
|
state, .Not_Comptime, expr.span, "%s expects 2 arguments, got %d",
|
|
symbol_text(checker, expr.name), len(expr.args),
|
|
)
|
|
}
|
|
hint := expected if types.is_concrete_scalar(expected) && !types.is_bool(expected) else types.INVALID
|
|
left_const := is_numeric_constant_expr(checker, expr.args[0])
|
|
right_const := is_numeric_constant_expr(checker, expr.args[1])
|
|
left, right := INVALID_CT_VALUE, INVALID_CT_VALUE
|
|
flow := ct_flow(.Normal)
|
|
ok := false
|
|
if left_const && !right_const && !types.is_valid(hint) {
|
|
right, flow, ok = ct_eval_expr(state, expr.args[1], types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
left, flow, ok = ct_eval_expr(state, expr.args[0], state.values[right].type, depth+1)
|
|
} else {
|
|
left, flow, ok = ct_eval_expr(state, expr.args[0], hint, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
right_hint := hint if types.is_valid(hint) else state.values[left].type
|
|
right, flow, ok = ct_eval_expr(state, expr.args[1], right_hint, depth+1)
|
|
}
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
return ct_eval_division_builtin(state, kind, left, right, expr.span)
|
|
}
|
|
|
|
ct_memory_region_info :: proc(state: ^Ct_State, value: Ct_Value) -> (child: types.Type, count: int, mutable: bool, ok: bool) {
|
|
store := &state.checker.module.types
|
|
if value.kind == .Slice {
|
|
item, item_ok := types.node(store, types.resolve_alias(value.type, store))
|
|
if item_ok && item.kind == .Slice {
|
|
return item.child, int(value.count), item.mutable, true
|
|
}
|
|
}
|
|
if value.kind == .Pointer {
|
|
pointer, pointer_ok := types.node(store, types.resolve_alias(value.type, store))
|
|
if pointer_ok && pointer.kind == .Pointer && !pointer.many {
|
|
array, array_ok := types.node(store, types.resolve_alias(pointer.child, store))
|
|
if array_ok && array.kind == .Array {
|
|
return array.child, int(array.count), pointer.mutable && array.mutable, true
|
|
}
|
|
}
|
|
}
|
|
if value.kind == .String && value.index < u64(len(state.checker.ast_module.strings)) {
|
|
return types.U8, len(state.checker.ast_module.strings[value.index]), false, true
|
|
}
|
|
return types.INVALID, 0, false, false
|
|
}
|
|
|
|
ct_memory_element_place :: proc(state: ^Ct_State, value: Ct_Value, index: int) -> Ct_Place_Id {
|
|
if value.kind == .Slice {
|
|
place, _, _ := ct_slice_element_place(state, value, index)
|
|
return place
|
|
}
|
|
if value.kind == .Pointer {
|
|
base, array_type, writable := ct_pointer_place(state, value)
|
|
array, ok := types.node(&state.checker.module.types, types.resolve_alias(array_type, &state.checker.module.types))
|
|
if base != INVALID_CT_PLACE && ok && array.kind == .Array && index >= 0 && index < int(array.count) {
|
|
return ct_extend_place(
|
|
state, base, Ct_Path_Elem{kind=.Index, index=u32(index)}, array.child, writable && array.mutable,
|
|
)
|
|
}
|
|
}
|
|
return INVALID_CT_PLACE
|
|
}
|
|
|
|
ct_places_equal :: proc(state: ^Ct_State, left_id, right_id: Ct_Place_Id) -> bool {
|
|
if left_id == INVALID_CT_PLACE || right_id == INVALID_CT_PLACE ||
|
|
int(left_id) >= len(state.places) || int(right_id) >= len(state.places) {
|
|
return false
|
|
}
|
|
left, right := state.places[left_id], state.places[right_id]
|
|
if left.cell != right.cell || left.count != right.count {
|
|
return false
|
|
}
|
|
left_path, right_path := ct_place_path(state, left), ct_place_path(state, right)
|
|
for elem, index in left_path {
|
|
if elem != right_path[index] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
ct_eval_memory_call :: proc(
|
|
state: ^Ct_State,
|
|
expr: ast.Expr,
|
|
kind: Memory_Builtin,
|
|
depth: int,
|
|
) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
name := symbol_text(state.checker, expr.name)
|
|
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", name, len(expr.args))
|
|
}
|
|
destination_id, destination_flow, destination_ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
|
|
if !destination_ok || destination_flow.kind != .Normal || destination_id == INVALID_CT_VALUE || int(destination_id) >= len(state.values) {
|
|
return INVALID_CT_VALUE, destination_flow, destination_ok
|
|
}
|
|
destination := state.values[destination_id]
|
|
destination_child, destination_count, destination_mutable, region_ok := ct_memory_region_info(state, destination)
|
|
if !region_ok || !destination_mutable {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
|
state, .Not_Comptime, state.checker.ast_module.exprs[expr.args[0]].span,
|
|
"%s! destination must be a mutable slice or mutable pointer-to-array", name,
|
|
)
|
|
}
|
|
destination_places := make([]Ct_Place_Id, destination_count, state.checker.allocator)
|
|
defer delete(destination_places, state.checker.allocator)
|
|
for &place, index in destination_places {
|
|
place = ct_memory_element_place(state, destination, index)
|
|
if place == INVALID_CT_PLACE || !state.places[place].writable {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime memory destination no longer points to writable storage")
|
|
}
|
|
}
|
|
|
|
if kind == .Set {
|
|
value, value_flow, value_ok := ct_eval_expr(state, expr.args[1], destination_child, depth+1)
|
|
if !value_ok || value_flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, value_flow, value_ok
|
|
}
|
|
value, value_ok = ct_coerce_value(state, value, destination_child, state.checker.ast_module.exprs[expr.args[1]].span)
|
|
if !value_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
for place in destination_places {
|
|
if !ct_place_set(state, place, value) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Void, type=types.VOID}), ct_flow(.Normal), true
|
|
}
|
|
|
|
source_id, source_flow, source_ok := ct_eval_expr(state, expr.args[1], types.INVALID, depth+1)
|
|
if !source_ok || source_flow.kind != .Normal || source_id == INVALID_CT_VALUE || int(source_id) >= len(state.values) {
|
|
return INVALID_CT_VALUE, source_flow, source_ok
|
|
}
|
|
source := state.values[source_id]
|
|
source_child, source_count, _, source_region_ok := ct_memory_region_info(state, source)
|
|
if !source_region_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, state.checker.ast_module.exprs[expr.args[1]].span, "memcopy! source must be a slice or pointer-to-array")
|
|
}
|
|
if !types.equal(
|
|
types.resolve_alias(destination_child, &state.checker.module.types),
|
|
types.resolve_alias(source_child, &state.checker.module.types),
|
|
) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "memcopy! source and destination element types must match")
|
|
}
|
|
if destination_count != source_count {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "memcopy! source and destination lengths differ")
|
|
}
|
|
|
|
source_places := make([]Ct_Place_Id, source_count, state.checker.allocator)
|
|
values := make([]Ct_Value_Id, source_count, state.checker.allocator)
|
|
defer delete(source_places, state.checker.allocator)
|
|
defer delete(values, state.checker.allocator)
|
|
for index in 0..<source_count {
|
|
if source.kind == .String {
|
|
values[index] = ct_add_value(state, Ct_Value{
|
|
kind=.Integer, type=types.U8, integer=i128(state.checker.ast_module.strings[source.index][index]),
|
|
})
|
|
continue
|
|
}
|
|
source_places[index] = ct_memory_element_place(state, source, index)
|
|
if source_places[index] == INVALID_CT_PLACE {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime memory source no longer points to live storage")
|
|
}
|
|
// ponytail: O(n^2) is simplest here; use canonical intervals if large comptime copies become common.
|
|
for destination_place in destination_places {
|
|
if ct_places_equal(state, source_places[index], destination_place) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "memcopy! source and destination overlap")
|
|
}
|
|
}
|
|
value, value_ok := ct_place_get(state, source_places[index])
|
|
if !value_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
values[index] = value
|
|
}
|
|
for place, index in destination_places {
|
|
if !ct_place_set(state, place, values[index]) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Void, type=types.VOID}), ct_flow(.Normal), true
|
|
}
|
|
|
|
ct_scalar_cast :: proc(state: ^Ct_State, id: Ct_Value_Id, target: types.Type, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
value := state.values[id]
|
|
if !types.is_concrete_scalar(target) || types.is_bool(target) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "scalar cast requires numeric scalar types")
|
|
}
|
|
if value.kind == .Float {
|
|
if types.is_float(target, state.checker.target) {
|
|
value.type = target
|
|
return ct_add_value(state, value), ct_flow(.Normal), true
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=target, integer=i128(value.float)}), ct_flow(.Normal), true
|
|
}
|
|
if ct_is_integer_like(value) {
|
|
if types.is_float(target, state.checker.target) {
|
|
return ct_add_value(state, Ct_Value{kind=.Float, type=target, float=f64(value.integer)}), ct_flow(.Normal), true
|
|
}
|
|
value.kind = .Integer
|
|
value.type = target
|
|
return ct_add_value(state, value), ct_flow(.Normal), true
|
|
}
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "scalar cast requires numeric scalar types")
|
|
}
|
|
|
|
intern_comptime_string :: proc(checker: ^Checker, text: string) -> u64 {
|
|
string_id := u64(len(checker.ast_module.strings))
|
|
for value, index in checker.ast_module.strings {
|
|
if value == text {
|
|
string_id = u64(index)
|
|
break
|
|
}
|
|
}
|
|
if string_id == u64(len(checker.ast_module.strings)) {
|
|
append(&checker.ast_module.strings, strings.clone(text, checker.ast_module.allocator))
|
|
append(&checker.module.strings, strings.clone(text, checker.allocator))
|
|
}
|
|
return string_id
|
|
}
|
|
|
|
ct_reflection_string :: proc(state: ^Ct_State, text: string) -> Ct_Value_Id {
|
|
checker := state.checker
|
|
string_id := intern_comptime_string(checker, text)
|
|
return ct_add_value(state, Ct_Value{
|
|
kind=.String,
|
|
type=types.slice(&checker.module.types, types.U8, false),
|
|
index=string_id,
|
|
})
|
|
}
|
|
|
|
ct_value_bytes :: proc(state: ^Ct_State, id: Ct_Value_Id) -> (string, bool) {
|
|
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
|
|
return "", false
|
|
}
|
|
value := state.values[id]
|
|
if value.kind == .String {
|
|
if value.index < u64(len(state.checker.ast_module.strings)) {
|
|
return state.checker.ast_module.strings[value.index], true
|
|
}
|
|
return "", false
|
|
}
|
|
item, ok := types.container(value.type, &state.checker.module.types)
|
|
if !ok || item.child != types.U8 || item.mutable ||
|
|
(value.kind != .Array && value.kind != .Slice) {
|
|
return "", false
|
|
}
|
|
count := int(value.count)
|
|
bytes := make([]u8, count, state.checker.allocator)
|
|
defer delete(bytes, state.checker.allocator)
|
|
for index in 0..<count {
|
|
child := INVALID_CT_VALUE
|
|
if value.kind == .Array {
|
|
children := ct_child_slice(state, value)
|
|
if index >= len(children) { return "", false }
|
|
child = children[index]
|
|
} else {
|
|
place, _, place_ok := ct_slice_element_place(state, value, index)
|
|
if !place_ok { return "", false }
|
|
child, place_ok = ct_place_get(state, place)
|
|
if !place_ok { return "", false }
|
|
}
|
|
integer, integer_ok := ct_integer_value(state, child)
|
|
if !integer_ok || integer < 0 || integer > 255 { return "", false }
|
|
bytes[index] = u8(integer)
|
|
}
|
|
string_id := intern_comptime_string(state.checker, string(bytes))
|
|
return state.checker.ast_module.strings[string_id], true
|
|
}
|
|
|
|
ct_struct_value :: proc(state: ^Ct_State, value_type: types.Type, children: []Ct_Value_Id) -> Ct_Value_Id {
|
|
start := u32(len(state.children))
|
|
append(&state.children, ..children)
|
|
return ct_add_value(state, Ct_Value{kind=.Struct, type=value_type, start=start, count=u32(len(children))})
|
|
}
|
|
|
|
ct_typeinfo_value :: proc(state: ^Ct_State, target: types.Type, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
checker := state.checker
|
|
store := &checker.module.types
|
|
typeinfo_type := std_named_type(checker, "@std/meta", "TypeInfo")
|
|
arrayinfo_type := std_named_type(checker, "@std/meta", "ArrayInfo")
|
|
fieldinfo_type := std_named_type(checker, "@std/meta", "FieldInfo")
|
|
recordinfo_type := std_named_type(checker, "@std/meta", "RecordInfo")
|
|
enuminfo_type := std_named_type(checker, "@std/meta", "EnumInfo")
|
|
layout_type := std_named_type(checker, "@std/meta", "Layout")
|
|
if !types.is_valid(typeinfo_type) || !types.is_valid(arrayinfo_type) || !types.is_valid(fieldinfo_type) ||
|
|
!types.is_valid(recordinfo_type) || !types.is_valid(enuminfo_type) || !types.is_valid(layout_type) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
|
state, .Not_Comptime, span,
|
|
"typeinfo! requires importing @std/meta",
|
|
)
|
|
}
|
|
resolved := types.resolve_alias(target, store)
|
|
item, item_ok := types.node(store, resolved)
|
|
tag := "invalid"
|
|
if !item_ok {
|
|
if types.is_void(resolved) { tag = "void" }
|
|
else if types.is_noreturn(resolved) { tag = "noreturn" }
|
|
else if types.is_anyopaque(resolved) { tag = "anyopaque" }
|
|
else if types.is_bool(resolved) { tag = "bool" }
|
|
else if types.is_concrete_integer(resolved) { tag = "integer" }
|
|
else if types.is_float(resolved, checker.target) { tag = "float" }
|
|
} else {
|
|
#partial switch item.kind {
|
|
case .Array: tag = "array"
|
|
case .Pointer: tag = "pointer"
|
|
case .Slice: tag = "slice"
|
|
case .Range: tag = "range"
|
|
case .Optional: tag = "optional"
|
|
case .Function: tag = "function"
|
|
case .Enum: tag = "enum"
|
|
case .Struct: tag = "record"
|
|
case .Union: tag = "union"
|
|
case .Fallible: tag = "fallible"
|
|
case .Distinct: tag = "distinct"
|
|
case .Alias:
|
|
tag = "invalid"
|
|
case:
|
|
tag = "invalid"
|
|
}
|
|
}
|
|
variant_index, _, variant_ok := find_struct_field(checker, typeinfo_type, symbol.intern(checker.symbols, tag))
|
|
if !variant_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "@std/meta TypeInfo is malformed")
|
|
}
|
|
if tag == "array" {
|
|
child_value := ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(item.child)})
|
|
len_value := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(item.count)})
|
|
array_value := ct_struct_value(state, arrayinfo_type, []Ct_Value_Id{child_value, len_value})
|
|
payload_start := u32(len(state.children))
|
|
append(&state.children, array_value)
|
|
return ct_add_value(state, Ct_Value{
|
|
kind=.Struct, type=typeinfo_type, start=payload_start, count=1, active=i64(variant_index),
|
|
}), ct_flow(.Normal), true
|
|
}
|
|
if tag == "enum" {
|
|
members := types.enum_members_for(store, resolved)
|
|
field_values := make([]Ct_Value_Id, len(members), checker.allocator)
|
|
defer delete(field_values, checker.allocator)
|
|
for member, index in members {
|
|
name_value := ct_reflection_string(state, symbol_text(checker, symbol.Id(member.name)))
|
|
type_value := ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(resolved)})
|
|
index_value := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)})
|
|
field_values[index] = ct_struct_value(
|
|
state, fieldinfo_type, []Ct_Value_Id{name_value, type_value, index_value},
|
|
)
|
|
}
|
|
fields_start := u32(len(state.children))
|
|
append(&state.children, ..field_values)
|
|
fields_type := types.array(store, fieldinfo_type, u64(len(field_values)), false)
|
|
fields_value := ct_add_value(state, Ct_Value{
|
|
kind=.Array, type=fields_type, start=fields_start, count=u32(len(field_values)),
|
|
})
|
|
enum_value := ct_struct_value(state, enuminfo_type, []Ct_Value_Id{fields_value})
|
|
payload_start := u32(len(state.children))
|
|
append(&state.children, enum_value)
|
|
return ct_add_value(state, Ct_Value{
|
|
kind=.Struct, type=typeinfo_type, start=payload_start, count=1, active=i64(variant_index),
|
|
}), ct_flow(.Normal), true
|
|
}
|
|
if tag != "record" {
|
|
start := u32(len(state.children))
|
|
return ct_add_value(state, Ct_Value{
|
|
kind=.Struct, type=typeinfo_type, start=start, count=0, active=i64(variant_index),
|
|
}), ct_flow(.Normal), true
|
|
}
|
|
fields := types.fields_for(store, resolved)
|
|
field_values := make([]Ct_Value_Id, len(fields), checker.allocator)
|
|
defer delete(field_values, checker.allocator)
|
|
for field, index in fields {
|
|
name := ""
|
|
if item.tuple {
|
|
name = fmt.aprintf("%d", index, allocator=checker.allocator)
|
|
} else {
|
|
name = symbol_text(checker, symbol.Id(field.name))
|
|
}
|
|
name_value := ct_reflection_string(state, name)
|
|
if item.tuple {
|
|
delete(name, checker.allocator)
|
|
}
|
|
type_value := ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(field.type)})
|
|
index_value := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)})
|
|
children := []Ct_Value_Id{name_value, type_value, index_value}
|
|
field_values[index] = ct_struct_value(state, fieldinfo_type, children)
|
|
}
|
|
fields_start := u32(len(state.children))
|
|
append(&state.children, ..field_values)
|
|
fields_type := types.array(store, fieldinfo_type, u64(len(field_values)), false)
|
|
fields_value := ct_add_value(state, Ct_Value{
|
|
kind=.Array, type=fields_type, start=fields_start, count=u32(len(field_values)),
|
|
})
|
|
name_optional_type := types.optional(store, types.slice(store, types.U8, false))
|
|
record_name := ct_add_value(state, Ct_Value{kind=.Null, type=name_optional_type})
|
|
if item.name != 0 {
|
|
name_value := ct_reflection_string(state, symbol_text(checker, symbol.Id(item.name)))
|
|
name_start := u32(len(state.children))
|
|
append(&state.children, name_value)
|
|
record_name = ct_add_value(state, Ct_Value{
|
|
kind=.Optional_Some, type=name_optional_type, start=name_start, count=1,
|
|
})
|
|
}
|
|
tuple_value := ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if item.tuple else 0})
|
|
layout_name := symbol.intern(checker.symbols, "c" if item.c_layout else "auto")
|
|
layout_member, layout_ok := find_enum_member(checker, layout_type, layout_name)
|
|
if !layout_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "@std/meta Layout is malformed")
|
|
}
|
|
layout_value := ct_add_value(state, Ct_Value{kind=.Integer, type=layout_type, integer=layout_member.value})
|
|
record_children := []Ct_Value_Id{record_name, fields_value, tuple_value, layout_value}
|
|
record_value := ct_struct_value(state, recordinfo_type, record_children)
|
|
payload_start := u32(len(state.children))
|
|
append(&state.children, record_value)
|
|
return ct_add_value(state, Ct_Value{
|
|
kind=.Struct, type=typeinfo_type, start=payload_start, count=1, active=i64(variant_index),
|
|
}), ct_flow(.Normal), true
|
|
}
|
|
|
|
valid_generated_field_name :: proc(name: string) -> bool {
|
|
if len(name) == 0 {
|
|
return false
|
|
}
|
|
is_start := proc(value: byte) -> bool {
|
|
return value == '_' || value >= 'a' && value <= 'z' || value >= 'A' && value <= 'Z'
|
|
}
|
|
if !is_start(name[0]) {
|
|
return false
|
|
}
|
|
for value in transmute([]byte)name[1:] {
|
|
if !is_start(value) && !(value >= '0' && value <= '9') {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
ct_collection_children :: proc(
|
|
state: ^Ct_State,
|
|
expr_id: ast.Expr_Id,
|
|
depth: int,
|
|
label: string,
|
|
) -> ([]Ct_Value_Id, Ct_Flow, bool) {
|
|
value, flow, ok := ct_eval_expr(state, expr_id, types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return nil, flow, ok
|
|
}
|
|
if value == INVALID_CT_VALUE || int(value) >= len(state.values) {
|
|
return nil, ct_flow(.Normal), false
|
|
}
|
|
item := state.values[value]
|
|
if item.kind != .Array && item.kind != .Struct {
|
|
return nil, ct_flow(.Normal), ct_failf(
|
|
state, .Not_Comptime, state.checker.ast_module.exprs[expr_id].span,
|
|
"struct_type! %s must be a fixed array or tuple", label,
|
|
)
|
|
}
|
|
if item.kind == .Struct {
|
|
node, node_ok := types.node(&state.checker.module.types, item.type)
|
|
if !node_ok || !node.tuple {
|
|
return nil, ct_flow(.Normal), ct_failf(
|
|
state, .Not_Comptime, state.checker.ast_module.exprs[expr_id].span,
|
|
"struct_type! %s must be a fixed array or tuple", label,
|
|
)
|
|
}
|
|
}
|
|
children := ct_child_slice(state, item)
|
|
result := make([]Ct_Value_Id, len(children), state.checker.allocator)
|
|
copy(result, children)
|
|
return result, ct_flow(.Normal), true
|
|
}
|
|
|
|
ct_literal_collection :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> ([]ast.Expr_Id, bool) {
|
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return nil, false
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
if expr.kind == .Array || expr.kind == .Struct_Literal && expr.tuple {
|
|
return expr.args, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
ct_struct_type :: proc(
|
|
state: ^Ct_State,
|
|
expr_id: ast.Expr_Id,
|
|
expr: ast.Expr,
|
|
depth: int,
|
|
) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
checker := state.checker
|
|
if len(expr.args) != 4 {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
|
state, .Not_Comptime, expr.span,
|
|
"struct_type! expects 4 arguments, got %d", len(expr.args),
|
|
)
|
|
}
|
|
for entry in checker.generated_types {
|
|
if entry.expr == expr_id && comptime_values_equal(entry.values, checker.current_comptime_values) {
|
|
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(entry.result)}), ct_flow(.Normal), true
|
|
}
|
|
}
|
|
|
|
layout_expr := checker.ast_module.exprs[expr.args[0]]
|
|
c_layout := false
|
|
layout_ok := false
|
|
if layout_expr.kind == .Enum_Literal {
|
|
name := symbol_text(checker, layout_expr.name)
|
|
c_layout = name == "c"
|
|
layout_ok = name == "auto" || name == "c"
|
|
} else {
|
|
layout_value, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
if layout_value != INVALID_CT_VALUE && int(layout_value) < len(state.values) {
|
|
value := state.values[layout_value]
|
|
if value.kind == .Integer && types.is_enum(value.type, &checker.module.types) {
|
|
if name, found := enum_member_name_from_value(checker, value.type, value.integer); found {
|
|
c_layout = name == "c"
|
|
layout_ok = name == "auto" || name == "c"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if !layout_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
|
state, .Not_Comptime, layout_expr.span,
|
|
"struct_type! layout must be .auto or .c",
|
|
)
|
|
}
|
|
|
|
name_values, name_flow, names_ok := ct_collection_children(state, expr.args[1], depth+1, "field names")
|
|
defer delete(name_values, checker.allocator)
|
|
if !names_ok || name_flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, name_flow, names_ok
|
|
}
|
|
names := make([]string, len(name_values), checker.allocator)
|
|
defer delete(names, checker.allocator)
|
|
for value, index in name_values {
|
|
name, ok := ct_value_bytes(state, value)
|
|
if !ok || !valid_generated_field_name(name) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
|
state, .Not_Comptime, checker.ast_module.exprs[expr.args[1]].span,
|
|
"struct_type! field names must be valid comptime immutable byte strings",
|
|
)
|
|
}
|
|
for previous in names[:index] {
|
|
if previous == name {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
|
state, .Not_Comptime, checker.ast_module.exprs[expr.args[1]].span,
|
|
"duplicate struct_type! field name '%s'", name,
|
|
)
|
|
}
|
|
}
|
|
names[index] = name
|
|
}
|
|
|
|
field_types: []types.Type
|
|
if type_exprs, literal := ct_literal_collection(checker, expr.args[2]); literal {
|
|
field_types = make([]types.Type, len(type_exprs), checker.allocator)
|
|
for item, index in type_exprs {
|
|
field_types[index], _ = resolve_type_argument(checker, item, state.pkg, state.file)
|
|
}
|
|
} else {
|
|
type_values, flow, ok := ct_collection_children(state, expr.args[2], depth+1, "field types")
|
|
defer delete(type_values, checker.allocator)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
field_types = make([]types.Type, len(type_values), checker.allocator)
|
|
for value, index in type_values {
|
|
if value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Type {
|
|
field_types[index] = types.Type(state.values[value].index)
|
|
}
|
|
}
|
|
}
|
|
defer delete(field_types, checker.allocator)
|
|
if len(names) != len(field_types) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
|
state, .Not_Comptime, expr.span,
|
|
"struct_type! collection lengths differ: %d names and %d types", len(names), len(field_types),
|
|
)
|
|
}
|
|
if c_layout && len(names) == 0 {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "c struct types require at least one field")
|
|
}
|
|
fields := make([]types.Field, len(names), checker.allocator)
|
|
defer delete(fields, checker.allocator)
|
|
for field_type, index in field_types {
|
|
valid := types.is_valid(field_type) && !types.is_void(field_type)
|
|
if c_layout {
|
|
valid = valid && types.is_runtime_value(field_type, &checker.module.types) &&
|
|
types.is_c_record_field_type(field_type, &checker.module.types)
|
|
} else {
|
|
valid = valid && is_runtime_type(checker, field_type)
|
|
}
|
|
if !valid {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
|
state, .Not_Comptime, checker.ast_module.exprs[expr.args[2]].span,
|
|
"struct_type! field '%s' has invalid %s type %s",
|
|
names[index], "C-layout" if c_layout else "value", type_label(checker, field_type),
|
|
)
|
|
}
|
|
fields[index] = types.Field{name=u32(symbol.intern(checker.symbols, names[index])), type=field_type}
|
|
}
|
|
|
|
default_exprs, literal_defaults := ct_literal_collection(checker, expr.args[3])
|
|
default_values: []Ct_Value_Id
|
|
if !literal_defaults {
|
|
flow: Ct_Flow
|
|
ok: bool
|
|
default_values, flow, ok = ct_collection_children(state, expr.args[3], depth+1, "field defaults")
|
|
if !ok || flow.kind != .Normal {
|
|
delete(default_values, checker.allocator)
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
}
|
|
defer delete(default_values, checker.allocator)
|
|
default_count := len(default_exprs) if literal_defaults else len(default_values)
|
|
if default_count != len(fields) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
|
state, .Not_Comptime, expr.span,
|
|
"struct_type! collection lengths differ: %d fields and %d defaults", len(fields), default_count,
|
|
)
|
|
}
|
|
persistent_defaults := make([]Ct_Value_Id, len(fields), checker.allocator)
|
|
for &value in persistent_defaults {
|
|
value = INVALID_CT_VALUE
|
|
}
|
|
for field, index in fields {
|
|
expected := types.optional(&checker.module.types, field.type)
|
|
value := INVALID_CT_VALUE
|
|
if literal_defaults {
|
|
flow: Ct_Flow
|
|
ok: bool
|
|
value, flow, ok = ct_eval_expr(state, default_exprs[index], expected, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
delete(persistent_defaults, checker.allocator)
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
} else {
|
|
value = default_values[index]
|
|
}
|
|
if value == INVALID_CT_VALUE {
|
|
delete(persistent_defaults, checker.allocator)
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
|
state, .Not_Comptime, expr.span,
|
|
"struct_type! field defaults contain an invalid comptime value",
|
|
)
|
|
}
|
|
coerced, ok := ct_coerce_value(state, value, expected, expr.span)
|
|
value = coerced
|
|
key := strings.builder_make(checker.allocator)
|
|
undefined := ct_value_contains_undefined(state, value)
|
|
key_ok := ok && ct_write_comptime_key(state, value, &key)
|
|
stable := ok && !undefined && key_ok
|
|
strings.builder_destroy(&key)
|
|
if !stable {
|
|
delete(persistent_defaults, checker.allocator)
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
|
state, .Not_Comptime, expr.span,
|
|
"struct_type! default for field '%s' must have a stable comptime identity coercible to ?%s",
|
|
names[index], type_label(checker, field.type),
|
|
)
|
|
}
|
|
default := state.values[value]
|
|
if default.kind == .Null {
|
|
continue
|
|
}
|
|
children := ct_child_slice(state, default)
|
|
if default.kind != .Optional_Some || len(children) != 1 {
|
|
delete(persistent_defaults, checker.allocator)
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
persistent_defaults[index] = ct_clone_graph(&checker.static_state, state, children[0])
|
|
}
|
|
|
|
result := types.struct_generated(&checker.module.types, fields, c_layout=c_layout)
|
|
append(&checker.generated_types, Generated_Type_Entry{
|
|
expr=expr_id,
|
|
values=clone_comptime_values(checker.current_comptime_values, checker.allocator),
|
|
result=result,
|
|
pkg=state.pkg,
|
|
file=state.file,
|
|
defaults=persistent_defaults,
|
|
})
|
|
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(result)}), ct_flow(.Normal), true
|
|
}
|
|
|
|
ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int, expr_id := ast.INVALID_EXPR) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
checker := state.checker
|
|
resolution := -1
|
|
if expr_id != ast.INVALID_EXPR {
|
|
if index, ok := find_call_resolution(checker, expr_id); ok {
|
|
resolution = index
|
|
}
|
|
}
|
|
if expr.left != ast.INVALID_EXPR {
|
|
callee, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
if callee == INVALID_CT_VALUE || int(callee) >= len(state.values) || state.values[callee].kind != .Function {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime function-pointer call requires a known function value")
|
|
}
|
|
return ct_eval_template_call(state, ast.Function_Id(u32(state.values[callee].index)), expr.args, expr.span, expected, depth+1)
|
|
}
|
|
if is_intrinsic_call(checker, expr, "compile_error") {
|
|
message := "compile_error! requires one comptime string argument"
|
|
if len(expr.args) == 1 {
|
|
value, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
|
|
if ok && flow.kind == .Normal {
|
|
if text, text_ok := ct_value_bytes(state, value); text_ok {
|
|
message = text
|
|
}
|
|
}
|
|
}
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Compile_Error, expr.span, message)
|
|
}
|
|
if is_intrinsic_call(checker, expr, "some") {
|
|
if len(expr.args) != 1 {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "some! expects 1 argument, got %d", len(expr.args))
|
|
}
|
|
if !types.is_optional(expected, &checker.module.types) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "some! requires an optional context")
|
|
}
|
|
child_type := types.child_type(expected, &checker.module.types)
|
|
child, flow, ok := ct_eval_expr(state, expr.args[0], child_type, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
child, ok = ct_coerce_value(state, child, child_type, checker.ast_module.exprs[expr.args[0]].span)
|
|
if !ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
start := u32(len(state.children))
|
|
append(&state.children, child)
|
|
return ct_add_value(state, Ct_Value{kind=.Optional_Some, type=expected, start=start, count=1}), ct_flow(.Normal), true
|
|
}
|
|
if is_intrinsic_call(checker, expr, "struct_type") {
|
|
return ct_struct_type(state, expr_id, expr, depth+1)
|
|
}
|
|
if is_intrinsic_call(checker, expr, "field") {
|
|
if len(expr.args) != 2 {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "field! expects 2 arguments, got %d", len(expr.args))
|
|
}
|
|
if enum_type, ok := resolve_type_argument(checker, expr.args[0], state.pkg, state.file);
|
|
ok && types.is_enum(enum_type, &checker.module.types) {
|
|
name_value, name_flow, name_ok := ct_eval_expr(state, expr.args[1], types.INVALID, depth+1)
|
|
if !name_ok || name_flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, name_flow, name_ok
|
|
}
|
|
name, text_ok := ct_value_bytes(state, name_value)
|
|
if !text_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "field! name must be a comptime immutable byte string")
|
|
}
|
|
member, member_ok := find_enum_member(checker, enum_type, symbol.intern(checker.symbols, name))
|
|
if !member_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown enum member '%s'", name)
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=enum_type, integer=member.value}), ct_flow(.Normal), true
|
|
}
|
|
base, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
name_value, name_flow, name_ok := ct_eval_expr(state, expr.args[1], types.INVALID, depth+1)
|
|
if !name_ok || name_flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, name_flow, name_ok
|
|
}
|
|
name, text_ok := ct_value_bytes(state, name_value)
|
|
if !text_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "field! name must be a comptime immutable byte string")
|
|
}
|
|
if index, numeric := canonical_decimal_index(name); numeric {
|
|
return ct_eval_tuple_field_value(state, base, index, expr.span)
|
|
}
|
|
return ct_eval_field_value(state, base, symbol.intern(checker.symbols, name), expr.span)
|
|
}
|
|
if is_intrinsic_call(checker, expr, "typeinfo") {
|
|
if len(expr.args) != 1 {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "typeinfo! expects 1 argument, got %d", len(expr.args))
|
|
}
|
|
target, ok := resolve_type_argument(checker, expr.args[0], state.pkg, state.file)
|
|
if !ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "typeinfo! argument must be a type")
|
|
}
|
|
return ct_typeinfo_value(state, target, expr.span)
|
|
}
|
|
if is_intrinsic_call(checker, expr, "tag") {
|
|
if len(expr.args) != 1 {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "tag! expects 1 argument, got %d", len(expr.args))
|
|
}
|
|
value_id, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
if value_id == INVALID_CT_VALUE || int(value_id) >= len(state.values) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
value := state.values[value_id]
|
|
tag_type, tag_ok := tag_result_type(checker, value.type)
|
|
if !tag_ok || value.kind != .Struct || value.active < 0 {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "tag! requires a tagged-union value")
|
|
}
|
|
fields := types.fields_for(&checker.module.types, value.type)
|
|
if int(value.active) >= len(fields) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
member, found := find_enum_member(checker, tag_type, symbol.Id(fields[value.active].name))
|
|
if !found {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=tag_type, integer=member.value}), ct_flow(.Normal), true
|
|
}
|
|
if is_intrinsic_call(checker, expr, "tagname") {
|
|
if len(expr.args) != 1 {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "tagname! expects 1 argument, got %d", len(expr.args))
|
|
}
|
|
value_id, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
if value_id == INVALID_CT_VALUE || int(value_id) >= len(state.values) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
value := state.values[value_id]
|
|
name, found := enum_member_name_from_value(checker, value.type, value.integer)
|
|
if value.kind != .Integer || !found {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "tagname! requires a comptime-known enum value")
|
|
}
|
|
return ct_reflection_string(state, name), ct_flow(.Normal), true
|
|
}
|
|
if builtin := type_builtin_call(checker, expr); builtin != .None {
|
|
if len(expr.args) != 1 {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "%s! expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args))
|
|
}
|
|
target, target_ok := resolve_type_argument(checker, expr.args[0], state.pkg, state.file)
|
|
if !target_ok {
|
|
label := "layout" if builtin == .Size_Of || builtin == .Align_Of else "integer bound"
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, checker.ast_module.exprs[expr.args[0]].span, "%s target must be a type", label)
|
|
}
|
|
if (builtin == .Size_Of || builtin == .Align_Of) && !valid_layout_type(checker, target) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, checker.ast_module.exprs[expr.args[0]].span, "layout target must be a sized runtime value type, got %s", type_label(checker, target))
|
|
}
|
|
if (builtin == .Min_Value || builtin == .Max_Value) && !types.is_concrete_integer(target) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, checker.ast_module.exprs[expr.args[0]].span, "integer bound target must be a concrete integer type, got %s", type_label(checker, target))
|
|
}
|
|
result_type := types.USIZE if builtin == .Size_Of || builtin == .Align_Of else target
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=type_builtin_value(checker, builtin, target)}), ct_flow(.Normal), true
|
|
}
|
|
if builtin := division_builtin_call(checker, expr); builtin != .None {
|
|
return ct_eval_division_call(state, expr, builtin, expected, depth+1)
|
|
}
|
|
if builtin := memory_builtin_call(checker, expr); builtin != .None {
|
|
return ct_eval_memory_call(state, expr, builtin, depth+1)
|
|
}
|
|
if is_intrinsic_call(checker, expr, "constcast") {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "constcast! is not available during comptime evaluation")
|
|
}
|
|
if expr.intrinsic {
|
|
if symbol.is_valid(expr.qualifier) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "intrinsic calls must be unqualified")
|
|
}
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown intrinsic '%s!'", symbol_text(checker, expr.name))
|
|
}
|
|
if symbol.is_valid(expr.qualifier) && find_import(checker, state.file, expr.qualifier) == ast.INVALID_IMPORT {
|
|
if index, ok := ct_find_binding_index(state, expr.qualifier); ok {
|
|
base := ct_binding_value(state, index)
|
|
callee, flow, field_ok := ct_eval_field_value(state, base, expr.name, expr.span)
|
|
if !field_ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, field_ok
|
|
}
|
|
if callee != INVALID_CT_VALUE && int(callee) < len(state.values) && state.values[callee].kind == .Function {
|
|
return ct_eval_template_call(
|
|
state, ast.Function_Id(u32(state.values[callee].index)), expr.args, expr.span, expected, depth+1,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false)
|
|
if !available {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "unavailable function package")
|
|
}
|
|
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, state.file))
|
|
if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) {
|
|
if !symbol.is_valid(expr.qualifier) {
|
|
if index, ok := ct_find_binding_index(state, expr.name); ok {
|
|
value := ct_binding_value(state, index)
|
|
if value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Function {
|
|
return ct_eval_template_call(state, ast.Function_Id(u32(state.values[value].index)), expr.args, expr.span, expected, depth+1)
|
|
}
|
|
}
|
|
}
|
|
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, state.file))
|
|
if global != ast.INVALID_GLOBAL && int(global) < len(checker.ast_module.globals) {
|
|
g := checker.ast_module.globals[global]
|
|
if !g.external && g.immutable && !g.writable {
|
|
global_expected := type_from_syntax(checker, g.type, g.pkg, g.file)
|
|
value, flow, ok := ct_eval_expr(state, g.expr, global_expected, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
if value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Function {
|
|
return ct_eval_template_call(state, ast.Function_Id(u32(state.values[value].index)), expr.args, expr.span, expected, depth+1)
|
|
}
|
|
}
|
|
}
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unresolved function '%s'", symbol_text(checker, expr.name))
|
|
}
|
|
if runtime_param_count(checker.ast_module.functions[template]) != 0 {
|
|
resolution = -1
|
|
}
|
|
return ct_eval_template_call(state, template, expr.args, expr.span, expected, depth+1, resolution)
|
|
}
|
|
|
|
ct_eval_template_call :: proc(
|
|
state: ^Ct_State,
|
|
template: ast.Function_Id,
|
|
args: []ast.Expr_Id,
|
|
span: source.Span,
|
|
expected: types.Type,
|
|
depth: int,
|
|
resolution := -1,
|
|
) -> (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))
|
|
}
|
|
resolved := resolution >= 0 && resolution < len(checker.call_resolutions)
|
|
if !resolved && !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_Value
|
|
comptime_ok := false
|
|
if resolved {
|
|
comptime_values = clone_comptime_values(
|
|
checker.call_resolutions[resolution].comptime_values, checker.allocator,
|
|
)
|
|
comptime_ok = true
|
|
} else {
|
|
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)
|
|
if types.is_void(result_type) || types.kind(result_type, &checker.module.types) == .Fallible {
|
|
state.foldable = false
|
|
}
|
|
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
|
|
}
|
|
if index >= len(args) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
append(&runtime_types, type_from_syntax(checker, param.type, function.pkg, function.file))
|
|
append(&runtime_names, param.name)
|
|
}
|
|
checker.current_comptime_values = previous_comptime
|
|
runtime_index := 0
|
|
for param, index in function.params {
|
|
if param.comptime_value {
|
|
continue
|
|
}
|
|
param_type := runtime_types[runtime_index]
|
|
value, flow, ok := ct_eval_expr(state, args[index], param_type, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
value, ok = ct_coerce_value(state, value, param_type, checker.ast_module.exprs[args[index]].span)
|
|
if !ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
if ct_value_contains_undefined(state, value) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
|
state, .Not_Comptime, checker.ast_module.exprs[args[index]].span,
|
|
"cannot pass an undefined value at comptime",
|
|
)
|
|
}
|
|
append(&runtime_values, value)
|
|
runtime_index += 1
|
|
}
|
|
checker.current_comptime_values = comptime_values
|
|
previous_pkg := state.pkg
|
|
previous_file := state.file
|
|
previous_result := state.result
|
|
state.pkg = function.pkg
|
|
state.file = function.file
|
|
state.result = result_type
|
|
defer {
|
|
state.pkg = previous_pkg
|
|
state.file = previous_file
|
|
state.result = previous_result
|
|
}
|
|
param_start := len(state.bindings)
|
|
for comptime_value in comptime_values {
|
|
if comptime_value.kind == .Static && comptime_value.static_value != INVALID_CT_VALUE &&
|
|
int(comptime_value.static_value) < len(checker.static_state.values) {
|
|
value := ct_clone_graph(state, &checker.static_state, comptime_value.static_value)
|
|
ct_bind_value(state, comptime_value.name, comptime_value.type, value, false)
|
|
}
|
|
}
|
|
for value, index in runtime_values {
|
|
ct_bind_value(state, runtime_names[index], runtime_types[index], value, false)
|
|
}
|
|
flow, ok := ct_exec_statements(state, function.body, false, depth+1)
|
|
ct_pop_bindings(state, param_start)
|
|
if !ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
if flow.kind != .Return {
|
|
if flow.kind == .Normal && types.is_void(result_type) {
|
|
result := ct_add_value(state, Ct_Value{kind=.Void, type=types.VOID})
|
|
return result, ct_flow(.Normal), true
|
|
}
|
|
if flow.kind == .Normal && types.kind(result_type, &checker.module.types) == .Fallible &&
|
|
types.is_void(types.fallible_success(result_type, &checker.module.types)) {
|
|
result := ct_make_fallible(state, result_type, INVALID_CT_VALUE, false)
|
|
return result, ct_flow(.Normal), true
|
|
}
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "comptime function '%s' did not return a value", symbol_text(checker, function.name))
|
|
}
|
|
result := flow.value
|
|
if ct_value_contains_undefined(state, result) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
|
state, .Not_Comptime, span, "comptime function returned an undefined value",
|
|
)
|
|
}
|
|
if runtime_param_count(function) == 0 {
|
|
ct_retain_value_storage(state, result)
|
|
}
|
|
if ct_value_references_dead_storage(state, result) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime function returned a pointer to expired storage")
|
|
}
|
|
if is_type_metatype_syntax(checker, function.result) && result != INVALID_CT_VALUE &&
|
|
int(result) < len(state.values) && state.values[result].kind == .Type {
|
|
record_type_factory_origin(
|
|
checker, types.Type(state.values[result].index), template, comptime_values,
|
|
)
|
|
}
|
|
if types.is_valid(expected) {
|
|
return ct_coerce_expr_value(state, result, expected, span)
|
|
}
|
|
return result, ct_flow(.Normal), true
|
|
}
|
|
|
|
ct_clone_value :: proc(dst, src: ^Ct_State, id: Ct_Value_Id) -> Ct_Value_Id {
|
|
if id == INVALID_CT_VALUE || int(id) >= len(src.values) {
|
|
return INVALID_CT_VALUE
|
|
}
|
|
value := src.values[id]
|
|
children := ct_child_slice(src, value)
|
|
if len(children) > 0 {
|
|
value.start = u32(len(dst.children))
|
|
for child in children {
|
|
append(&dst.children, ct_clone_value(dst, src, child))
|
|
}
|
|
}
|
|
return ct_add_value(dst, value)
|
|
}
|
|
|
|
Ct_Clone_Context :: struct {
|
|
dst, src: ^Ct_State,
|
|
values: []Ct_Value_Id,
|
|
cells: []Ct_Cell_Id,
|
|
places: []Ct_Place_Id,
|
|
}
|
|
|
|
ct_clone_graph_value :: proc(ctx: ^Ct_Clone_Context, id: Ct_Value_Id) -> Ct_Value_Id {
|
|
if id == INVALID_CT_VALUE || int(id) >= len(ctx.src.values) {
|
|
return INVALID_CT_VALUE
|
|
}
|
|
if ctx.values[id] != INVALID_CT_VALUE {
|
|
return ctx.values[id]
|
|
}
|
|
value := ctx.src.values[id]
|
|
children := ct_child_slice(ctx.src, value)
|
|
if ct_value_has_children(value.kind) {
|
|
value.start = 0
|
|
value.count = 0
|
|
}
|
|
dst_id := ct_add_value(ctx.dst, value)
|
|
ctx.values[id] = dst_id
|
|
if len(children) > 0 {
|
|
cloned := make([]Ct_Value_Id, len(children), ctx.dst.checker.allocator)
|
|
defer delete(cloned, ctx.dst.checker.allocator)
|
|
for child, index in children {
|
|
cloned[index] = ct_clone_graph_value(ctx, child)
|
|
}
|
|
start := u32(len(ctx.dst.children))
|
|
append(&ctx.dst.children, ..cloned)
|
|
ctx.dst.values[dst_id].start = start
|
|
ctx.dst.values[dst_id].count = u32(len(children))
|
|
}
|
|
if value.kind == .Pointer || value.kind == .Slice {
|
|
cloned_place := ct_clone_graph_place(ctx, Ct_Place_Id(value.index))
|
|
ctx.dst.values[dst_id].index = u64(cloned_place)
|
|
}
|
|
return dst_id
|
|
}
|
|
|
|
ct_clone_graph_cell :: proc(ctx: ^Ct_Clone_Context, id: Ct_Cell_Id) -> Ct_Cell_Id {
|
|
if id == INVALID_CT_CELL || int(id) >= len(ctx.src.cells) {
|
|
return INVALID_CT_CELL
|
|
}
|
|
if ctx.cells[id] != INVALID_CT_CELL {
|
|
return ctx.cells[id]
|
|
}
|
|
cell := ctx.src.cells[id]
|
|
cell.value = INVALID_CT_VALUE
|
|
dst_id := ct_cell_id(len(ctx.dst.cells))
|
|
append(&ctx.dst.cells, cell)
|
|
ctx.cells[id] = dst_id
|
|
ctx.dst.cells[dst_id].value = ct_clone_graph_value(ctx, ctx.src.cells[id].value)
|
|
return dst_id
|
|
}
|
|
|
|
ct_clone_graph_place :: proc(ctx: ^Ct_Clone_Context, id: Ct_Place_Id) -> Ct_Place_Id {
|
|
if id == INVALID_CT_PLACE || int(id) >= len(ctx.src.places) {
|
|
return INVALID_CT_PLACE
|
|
}
|
|
if ctx.places[id] != INVALID_CT_PLACE {
|
|
return ctx.places[id]
|
|
}
|
|
place := ctx.src.places[id]
|
|
path := ct_place_path(ctx.src, place)
|
|
place.start = u32(len(ctx.dst.paths))
|
|
place.count = u32(len(path))
|
|
append(&ctx.dst.paths, ..path)
|
|
place.cell = INVALID_CT_CELL
|
|
dst_id := Ct_Place_Id(len(ctx.dst.places))
|
|
append(&ctx.dst.places, place)
|
|
ctx.places[id] = dst_id
|
|
ctx.dst.places[dst_id].cell = ct_clone_graph_cell(ctx, ctx.src.places[id].cell)
|
|
return dst_id
|
|
}
|
|
|
|
ct_clone_graph :: proc(dst, src: ^Ct_State, id: Ct_Value_Id) -> Ct_Value_Id {
|
|
ctx := Ct_Clone_Context{
|
|
dst=dst,
|
|
src=src,
|
|
values=make([]Ct_Value_Id, len(src.values), dst.checker.allocator),
|
|
cells=make([]Ct_Cell_Id, len(src.cells), dst.checker.allocator),
|
|
places=make([]Ct_Place_Id, len(src.places), dst.checker.allocator),
|
|
}
|
|
defer {
|
|
delete(ctx.values, dst.checker.allocator)
|
|
delete(ctx.cells, dst.checker.allocator)
|
|
delete(ctx.places, dst.checker.allocator)
|
|
}
|
|
for &value in ctx.values { value = INVALID_CT_VALUE }
|
|
for &cell in ctx.cells { cell = INVALID_CT_CELL }
|
|
for &place in ctx.places { place = INVALID_CT_PLACE }
|
|
return ct_clone_graph_value(&ctx, id)
|
|
}
|
|
|
|
cache_zero_runtime_call :: proc(
|
|
checker: ^Checker,
|
|
resolution: int,
|
|
expr: ast.Expr_Id,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> Call_Fold {
|
|
if resolution < 0 || resolution >= len(checker.call_resolutions) {
|
|
return .Runtime
|
|
}
|
|
if checker.call_resolutions[resolution].fold != .Unknown {
|
|
return checker.call_resolutions[resolution].fold
|
|
}
|
|
state := ct_state_make(
|
|
checker, pkg, file, values=checker.current_comptime_values, diagnose=false,
|
|
)
|
|
value, flow, ok := ct_eval_expr(&state, expr)
|
|
if ok && flow.kind == .Normal && state.foldable && ct_value_can_materialize(&state, value) {
|
|
checker.call_resolutions[resolution].fold = .Value
|
|
checker.call_resolutions[resolution].folded_value = ct_clone_graph(
|
|
&checker.static_state, &state, value,
|
|
)
|
|
ct_state_destroy(&state)
|
|
return .Value
|
|
}
|
|
error := state.error
|
|
ct_state_destroy(&state)
|
|
if error == .Compile_Error {
|
|
diagnosed := ct_state_make(
|
|
checker, pkg, file, values=checker.current_comptime_values,
|
|
)
|
|
_, _, _ = ct_eval_expr(&diagnosed, expr)
|
|
checker.call_resolutions[resolution].fold = .Compile_Error
|
|
checker.call_resolutions[resolution].diagnostic = diagnosed.diagnostic
|
|
ct_state_destroy(&diagnosed)
|
|
return .Compile_Error
|
|
}
|
|
checker.call_resolutions[resolution].fold = .Runtime
|
|
return .Runtime
|
|
}
|
|
|
|
store_static_binding :: proc(checker: ^Checker, source: ^Ct_State, id: Ct_Value_Id, name: symbol.Id) -> Static_Binding {
|
|
value := ct_clone_graph(&checker.static_state, source, id)
|
|
value_type := types.INVALID
|
|
if value != INVALID_CT_VALUE && int(value) < len(checker.static_state.values) {
|
|
value_type = checker.static_state.values[value].type
|
|
}
|
|
return Static_Binding{name=name, type=value_type, value=value}
|
|
}
|
|
|
|
ct_write_comptime_key :: proc(state: ^Ct_State, id: Ct_Value_Id, builder: ^strings.Builder, depth := 0) -> bool {
|
|
if depth > 256 || id == INVALID_CT_VALUE || int(id) >= len(state.values) {
|
|
return false
|
|
}
|
|
value := state.values[id]
|
|
fmt.sbprintf(builder, "t%d:", value.type)
|
|
#partial switch value.kind {
|
|
case .Integer:
|
|
fmt.sbprintf(builder, "i%d;", value.integer)
|
|
return true
|
|
case .Float:
|
|
if types.bits(value.type, state.checker.target) == 32 {
|
|
fmt.sbprintf(builder, "f%08x;", transmute(u32)f32(value.float))
|
|
} else {
|
|
fmt.sbprintf(builder, "f%016x;", transmute(u64)value.float)
|
|
}
|
|
return true
|
|
case .Bool:
|
|
fmt.sbprintf(builder, "b%d;", value.integer)
|
|
return true
|
|
case .Type:
|
|
fmt.sbprintf(builder, "y%d;", value.index)
|
|
return true
|
|
case .String:
|
|
if value.index >= u64(len(state.checker.ast_module.strings)) {
|
|
return false
|
|
}
|
|
text := state.checker.ast_module.strings[value.index]
|
|
fmt.sbprintf(builder, "s%d:", len(text))
|
|
hex := "0123456789abcdef"
|
|
for byte in transmute([]byte)text {
|
|
strings.write_byte(builder, hex[byte>>4])
|
|
strings.write_byte(builder, hex[byte&0xf])
|
|
}
|
|
strings.write_byte(builder, ';')
|
|
return true
|
|
case .Function:
|
|
if value.index >= u64(len(state.checker.ast_module.functions)) {
|
|
return false
|
|
}
|
|
fmt.sbprintf(builder, "fn%d;", value.index)
|
|
return true
|
|
case .Slice:
|
|
item, item_ok := types.container(value.type, &state.checker.module.types)
|
|
if !item_ok || item.kind != .Slice || item.mutable || item.child != types.U8 {
|
|
return false
|
|
}
|
|
text, text_ok := ct_value_bytes(state, id)
|
|
if !text_ok {
|
|
return false
|
|
}
|
|
fmt.sbprintf(builder, "s%d:", len(text))
|
|
hex := "0123456789abcdef"
|
|
for byte in transmute([]byte)text {
|
|
strings.write_byte(builder, hex[byte>>4])
|
|
strings.write_byte(builder, hex[byte&0xf])
|
|
}
|
|
strings.write_byte(builder, ';')
|
|
return true
|
|
case .Array:
|
|
children := ct_child_slice(state, value)
|
|
fmt.sbprintf(builder, "a%d[", len(children))
|
|
for child in children {
|
|
if !ct_write_comptime_key(state, child, builder, depth+1) {
|
|
return false
|
|
}
|
|
}
|
|
strings.write_string(builder, "];")
|
|
return true
|
|
case .Struct:
|
|
if types.is_union(value.type, &state.checker.module.types) &&
|
|
!types.is_tagged_union(value.type, &state.checker.module.types) {
|
|
return false
|
|
}
|
|
children := ct_child_slice(state, value)
|
|
fmt.sbprintf(builder, "r%d:%d[", value.active, len(children))
|
|
for child in children {
|
|
if child != INVALID_CT_VALUE && !ct_write_comptime_key(state, child, builder, depth+1) {
|
|
return false
|
|
}
|
|
}
|
|
strings.write_string(builder, "];")
|
|
return true
|
|
case .Null:
|
|
strings.write_string(builder, "n;")
|
|
return true
|
|
case .Optional_Some:
|
|
children := ct_child_slice(state, value)
|
|
if len(children) != 1 || !ct_write_comptime_key(state, children[0], builder, depth+1) {
|
|
return false
|
|
}
|
|
strings.write_string(builder, "o;")
|
|
return true
|
|
case .Invalid, .Void, .Undefined, .Range, .Pointer, .Fallible:
|
|
return false
|
|
}
|
|
return false
|
|
}
|
|
|
|
eval_static_comptime_value :: proc(
|
|
checker: ^Checker,
|
|
name: symbol.Id,
|
|
expr: ast.Expr_Id,
|
|
declared: types.Type,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
values: []Comptime_Value = nil,
|
|
diagnose := false,
|
|
) -> (Comptime_Value, bool) {
|
|
state := ct_state_make(checker, pkg, file, values=values, diagnose=diagnose)
|
|
defer ct_state_destroy(&state)
|
|
id, flow, ok := ct_eval_expr(&state, expr, declared, 0)
|
|
if ok && flow.kind == .Normal {
|
|
id, ok = ct_coerce_value(&state, id, declared, checker.ast_module.exprs[expr].span)
|
|
}
|
|
if !ok || flow.kind != .Normal || ct_value_contains_undefined(&state, id) {
|
|
if diagnose {
|
|
source.add(
|
|
checker.diagnostics,
|
|
checker.ast_module.exprs[expr].span,
|
|
"comptime argument has no stable comptime identity",
|
|
)
|
|
}
|
|
return {}, false
|
|
}
|
|
builder := strings.builder_make(checker.allocator)
|
|
defer strings.builder_destroy(&builder)
|
|
if !ct_write_comptime_key(&state, id, &builder) {
|
|
if diagnose {
|
|
source.add(
|
|
checker.diagnostics,
|
|
checker.ast_module.exprs[expr].span,
|
|
"comptime argument has no stable comptime identity",
|
|
)
|
|
}
|
|
return {}, false
|
|
}
|
|
key_view := strings.to_string(builder)
|
|
static_value := INVALID_CT_VALUE
|
|
key := ""
|
|
for existing, index in checker.comptime_keys {
|
|
if existing == key_view {
|
|
key = existing
|
|
static_value = checker.comptime_static_values[index]
|
|
break
|
|
}
|
|
}
|
|
if static_value == INVALID_CT_VALUE {
|
|
key = strings.clone(key_view, checker.allocator)
|
|
static_value = ct_clone_graph(&checker.static_state, &state, id)
|
|
append(&checker.comptime_keys, key)
|
|
append(&checker.comptime_static_values, static_value)
|
|
}
|
|
return Comptime_Value{
|
|
name=name,
|
|
type=declared,
|
|
static_value=static_value,
|
|
key=key,
|
|
fingerprint=hash.fnv64a(transmute([]byte)key),
|
|
kind=.Static,
|
|
}, true
|
|
}
|
|
|
|
ct_eval_try_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
if state.defer_depth > 0 {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "cannot 'try' inside a 'defer'")
|
|
}
|
|
checker := state.checker
|
|
channel, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
if channel == INVALID_CT_VALUE || int(channel) >= len(state.values) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
value := state.values[channel]
|
|
if value.kind != .Fallible {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "'try' requires a fallible expression")
|
|
}
|
|
children := ct_child_slice(state, value)
|
|
payload := INVALID_CT_VALUE
|
|
if len(children) > 0 {
|
|
payload = children[0]
|
|
}
|
|
if value.active == 0 {
|
|
return payload, ct_flow(.Normal), true
|
|
}
|
|
enclosing_success := types.fallible_success(state.result, &checker.module.types)
|
|
enclosing_error := types.fallible_error(state.result, &checker.module.types)
|
|
if !types.is_valid(enclosing_success) || !types.is_valid(enclosing_error) {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "'try' requires an enclosing fallible function")
|
|
}
|
|
error_payload := payload
|
|
if payload != INVALID_CT_VALUE {
|
|
coerce_ok: bool
|
|
error_payload, coerce_ok = ct_coerce_value(state, payload, enclosing_error, expr.span)
|
|
if !coerce_ok {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
|
}
|
|
}
|
|
start := u32(len(state.children))
|
|
append(&state.children, error_payload)
|
|
result := ct_add_value(state, Ct_Value{kind=.Fallible, type=state.result, start=start, count=1, active=1})
|
|
return INVALID_CT_VALUE, ct_flow(.Return, result), true
|
|
}
|
|
|
|
ct_eval_catch_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
|
|
checker := state.checker
|
|
channel, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return INVALID_CT_VALUE, flow, ok
|
|
}
|
|
value := state.values[channel]
|
|
if value.kind != .Fallible {
|
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "'catch' requires a fallible expression")
|
|
}
|
|
success := types.fallible_success(value.type, &checker.module.types)
|
|
children := ct_child_slice(state, value)
|
|
payload := INVALID_CT_VALUE
|
|
if len(children) > 0 {
|
|
payload = children[0]
|
|
}
|
|
if value.active == 0 {
|
|
return payload, ct_flow(.Normal), true
|
|
}
|
|
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)
|
|
}
|
|
if expr.right != ast.INVALID_EXPR {
|
|
result, result_flow, result_ok := ct_eval_expr(state, expr.right, success, depth+1)
|
|
ct_pop_bindings(state, scope_start)
|
|
return result, result_flow, result_ok
|
|
}
|
|
state.value_return_depth += 1
|
|
handler, handler_ok := ct_exec_value_source(
|
|
state, expr.body, symbol.INVALID, expr.integer != 0, true, depth+1,
|
|
)
|
|
state.value_return_depth -= 1
|
|
ct_pop_bindings(state, scope_start)
|
|
if !handler_ok {
|
|
return INVALID_CT_VALUE, handler, false
|
|
}
|
|
if handler.kind == .Yield {
|
|
if types.is_valid(expected) {
|
|
return ct_coerce_expr_value(state, handler.value, expected, expr.span)
|
|
}
|
|
return handler.value, ct_flow(.Normal), true
|
|
}
|
|
if handler.kind == .Normal && types.is_void(success) {
|
|
value := ct_add_value(state, Ct_Value{kind=.Void, type=types.VOID})
|
|
return value, ct_flow(.Normal), true
|
|
}
|
|
if handler.kind == .Return {
|
|
return INVALID_CT_VALUE, handler, 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_refined_error_return :: proc(state: ^Ct_State, expr_id: ast.Expr_Id, target: types.Type) -> bool {
|
|
checker := state.checker
|
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return false
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
if expr.kind != .Name || symbol.is_valid(expr.qualifier) {
|
|
return false
|
|
}
|
|
index, found := ct_find_binding_index(state, expr.name)
|
|
if !found || state.bindings[index].mutable {
|
|
return false
|
|
}
|
|
binding := state.bindings[index]
|
|
candidates: []u32
|
|
for refinement_index := len(state.error_refinements) - 1; refinement_index >= 0; refinement_index -= 1 {
|
|
refinement := state.error_refinements[refinement_index]
|
|
if refinement.cell == binding.cell {
|
|
candidates = refinement.variants
|
|
break
|
|
}
|
|
}
|
|
if len(candidates) == 0 {
|
|
return false
|
|
}
|
|
selected: [dynamic]u32
|
|
selected.allocator = checker.allocator
|
|
defer delete(selected)
|
|
for candidate in candidates {
|
|
allowed := true
|
|
for refinement in state.error_refinements {
|
|
if refinement.cell != binding.cell {
|
|
continue
|
|
}
|
|
found_variant := false
|
|
for variant in refinement.variants {
|
|
found_variant = found_variant || variant == candidate
|
|
}
|
|
allowed = allowed && found_variant
|
|
}
|
|
if allowed {
|
|
append(&selected, candidate)
|
|
}
|
|
}
|
|
return types.selected_sum_fits(binding.type, target, selected[:], &checker.module.types)
|
|
}
|
|
|
|
ct_project_sum_value :: proc(state: ^Ct_State, value_id: Ct_Value_Id, target: types.Type, span: source.Span) -> (Ct_Value_Id, bool) {
|
|
checker := state.checker
|
|
if value_id == INVALID_CT_VALUE || int(value_id) >= len(state.values) {
|
|
return INVALID_CT_VALUE, false
|
|
}
|
|
value := state.values[value_id]
|
|
name := symbol.INVALID
|
|
if types.is_enum(value.type, &checker.module.types) && value.kind == .Integer {
|
|
for member in types.enum_members_for(&checker.module.types, value.type) {
|
|
if member.value == value.integer {
|
|
name = symbol.Id(member.name)
|
|
break
|
|
}
|
|
}
|
|
} else if types.is_tagged_union(value.type, &checker.module.types) && value.kind == .Struct {
|
|
fields := types.fields_for(&checker.module.types, value.type)
|
|
if value.active >= 0 && int(value.active) < len(fields) {
|
|
name = symbol.Id(fields[value.active].name)
|
|
}
|
|
}
|
|
if !symbol.is_valid(name) {
|
|
return INVALID_CT_VALUE, ct_fail(state, .Not_Comptime, span, "invalid refined error value")
|
|
}
|
|
if types.is_enum(target, &checker.module.types) {
|
|
member, found := find_enum_member(checker, target, name)
|
|
if !found {
|
|
return INVALID_CT_VALUE, ct_fail(state, .Not_Comptime, span, "refined error is not in the declared error type")
|
|
}
|
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=target, integer=member.value}), true
|
|
}
|
|
if !types.is_tagged_union(target, &checker.module.types) {
|
|
return INVALID_CT_VALUE, ct_fail(state, .Not_Comptime, span, "declared error type is not a sum")
|
|
}
|
|
field_index, _, found := find_struct_field(checker, target, name)
|
|
if !found {
|
|
return INVALID_CT_VALUE, ct_fail(state, .Not_Comptime, span, "refined error is not in the declared error type")
|
|
}
|
|
start := len(state.children)
|
|
if value.kind == .Struct {
|
|
append(&state.children, ..ct_child_slice(state, value))
|
|
}
|
|
return ct_add_value(state, Ct_Value{
|
|
kind=.Struct, type=target, active=i64(field_index), start=u32(start), count=u32(len(state.children)-start),
|
|
}), true
|
|
}
|
|
|
|
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
|
|
project_error := false
|
|
if expr.kind == .Enum_Literal && types.sum_has_name(&checker.module.types, error_type, u32(expr.name)) {
|
|
error_path = true
|
|
expected = error_type
|
|
} else if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
|
|
if index, found := ct_find_binding_index(state, expr.name); found {
|
|
actual := state.bindings[index].type
|
|
if can_implicitly_convert_type(checker, actual, error_type) &&
|
|
!can_implicitly_convert_type(checker, actual, success) {
|
|
error_path = true
|
|
expected = error_type
|
|
} else if ct_refined_error_return(state, expr_id, error_type) {
|
|
error_path = true
|
|
project_error = true
|
|
expected = types.INVALID
|
|
}
|
|
}
|
|
}
|
|
value, flow, ok := ct_eval_expr(state, expr_id, expected, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return flow, ok
|
|
}
|
|
if project_error {
|
|
value, ok = ct_project_sum_value(state, value, error_type, span)
|
|
} else {
|
|
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_has_yield_target :: proc(state: ^Ct_State, label: symbol.Id) -> bool {
|
|
for index := len(state.yield_targets) - 1; index >= 0; index -= 1 {
|
|
if state.yield_targets[index] == label {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
ct_statements_exit_value_source :: proc(state: ^Ct_State, statements: []ast.Stmt_Id) -> bool {
|
|
checker := state.checker
|
|
for statement_id in statements {
|
|
if statement_id == ast.INVALID_STMT || int(statement_id) >= len(checker.ast_module.statements) {
|
|
return false
|
|
}
|
|
statement := checker.ast_module.statements[statement_id]
|
|
#partial switch statement.kind {
|
|
case .Return, .Break, .Continue:
|
|
return true
|
|
case .Yield:
|
|
if symbol.is_valid(statement.label) && ct_has_yield_target(state, statement.label) {
|
|
return true
|
|
}
|
|
case .If:
|
|
if statement.else_body != nil &&
|
|
ct_statements_exit_value_source(state, statement.body) &&
|
|
ct_statements_exit_value_source(state, statement.else_body) {
|
|
return true
|
|
}
|
|
case .Block:
|
|
if ct_statements_exit_value_source(state, statement.body) {
|
|
return true
|
|
}
|
|
case .Match:
|
|
exits := len(statement.body) > 0
|
|
for arm_id in statement.body {
|
|
arm := checker.ast_module.statements[arm_id]
|
|
exits = exits && arm.kind == .Match_Arm && ct_statements_exit_value_source(state, arm.body)
|
|
}
|
|
if exits {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
ct_validate_ordinary_statements :: proc(state: ^Ct_State, statements: []ast.Stmt_Id) -> bool {
|
|
checker := state.checker
|
|
for statement_id in statements {
|
|
if statement_id == ast.INVALID_STMT || int(statement_id) >= len(checker.ast_module.statements) {
|
|
return false
|
|
}
|
|
statement := checker.ast_module.statements[statement_id]
|
|
#partial switch statement.kind {
|
|
case .Declaration, .Assignment:
|
|
if statement.expr == ast.INVALID_EXPR && !ct_validate_value_source_structure(
|
|
state, statement.body, statement.label, statement.value_control_flow, false,
|
|
) {
|
|
return false
|
|
}
|
|
case .Return:
|
|
if statement.value_control_flow && !ct_validate_value_source_structure(
|
|
state, statement.body, symbol.INVALID, true, false,
|
|
) {
|
|
return false
|
|
}
|
|
case .Yield:
|
|
if !symbol.is_valid(statement.label) {
|
|
return ct_fail(
|
|
state, .Not_Comptime, statement.span,
|
|
"'yield' is only valid as the final statement of a value block",
|
|
)
|
|
}
|
|
if !ct_has_yield_target(state, statement.label) {
|
|
return ct_failf(
|
|
state, .Not_Comptime, statement.span,
|
|
"no enclosing value loop or block is labeled '%s'",
|
|
symbol_text(checker, statement.label),
|
|
)
|
|
}
|
|
if statement.value_control_flow && !ct_validate_value_source_structure(
|
|
state, statement.body, symbol.INVALID, true, false,
|
|
) {
|
|
return false
|
|
}
|
|
case .If:
|
|
if !ct_validate_ordinary_statements(state, statement.body) ||
|
|
!ct_validate_ordinary_statements(state, statement.else_body) {
|
|
return false
|
|
}
|
|
case .While, .For:
|
|
if !ct_validate_ordinary_statements(state, statement.body) {
|
|
return false
|
|
}
|
|
if statement.update != ast.INVALID_STMT {
|
|
update := [1]ast.Stmt_Id{statement.update}
|
|
if !ct_validate_ordinary_statements(state, update[:]) {
|
|
return false
|
|
}
|
|
}
|
|
case .Block:
|
|
if !ct_validate_ordinary_statements(state, statement.body) {
|
|
return false
|
|
}
|
|
case .Defer:
|
|
if statement.update != ast.INVALID_STMT {
|
|
deferred := [1]ast.Stmt_Id{statement.update}
|
|
if !ct_validate_ordinary_statements(state, deferred[:]) {
|
|
return false
|
|
}
|
|
}
|
|
case .Match:
|
|
for arm_id in statement.body {
|
|
arm := checker.ast_module.statements[arm_id]
|
|
if arm.kind == .Match_Arm && !ct_validate_ordinary_statements(state, arm.body) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
ct_validate_value_branch_structure :: proc(state: ^Ct_State, statements: []ast.Stmt_Id) -> bool {
|
|
checker := state.checker
|
|
if len(statements) == 1 && checker.ast_module.statements[statements[0]].kind == .Expression {
|
|
return true
|
|
}
|
|
n := len(statements)
|
|
if n > 0 {
|
|
last := checker.ast_module.statements[statements[n-1]]
|
|
if last.kind == .Yield && !symbol.is_valid(last.label) {
|
|
if !ct_validate_ordinary_statements(state, statements[:n-1]) {
|
|
return false
|
|
}
|
|
return !last.value_control_flow || ct_validate_value_source_structure(
|
|
state, last.body, symbol.INVALID, true, false,
|
|
)
|
|
}
|
|
}
|
|
if ct_statements_exit_value_source(state, statements) {
|
|
return ct_validate_ordinary_statements(state, statements)
|
|
}
|
|
return ct_fail(
|
|
state, .Not_Comptime, source.Span{},
|
|
"a value branch must end with 'yield' or exit on every path (return/break/continue)",
|
|
)
|
|
}
|
|
|
|
ct_validate_value_if_structure :: proc(state: ^Ct_State, statement: ast.Stmt) -> bool {
|
|
checker := state.checker
|
|
if statement.else_body == nil {
|
|
return ct_fail(
|
|
state, .Not_Comptime, statement.span,
|
|
"an 'if' used as a value must have an 'else' so every path yields",
|
|
)
|
|
}
|
|
if !ct_validate_value_branch_structure(state, statement.body) {
|
|
return false
|
|
}
|
|
if len(statement.else_body) == 1 {
|
|
else_statement := checker.ast_module.statements[statement.else_body[0]]
|
|
if else_statement.kind == .If {
|
|
return ct_validate_value_if_structure(state, else_statement)
|
|
}
|
|
}
|
|
return ct_validate_value_branch_structure(state, statement.else_body)
|
|
}
|
|
|
|
ct_validate_value_source_structure :: proc(
|
|
state: ^Ct_State,
|
|
statements: []ast.Stmt_Id,
|
|
label: symbol.Id,
|
|
value_control_flow: bool,
|
|
allow_exit: bool,
|
|
) -> bool {
|
|
checker := state.checker
|
|
if symbol.is_valid(label) {
|
|
target_start := len(state.yield_targets)
|
|
append(&state.yield_targets, label)
|
|
valid := ct_validate_ordinary_statements(state, statements) &&
|
|
ct_statements_exit_value_source(state, statements)
|
|
resize(&state.yield_targets, target_start)
|
|
if valid {
|
|
return true
|
|
}
|
|
if state.error == .None {
|
|
return ct_fail(state, .Not_Comptime, source.Span{}, "a labeled value block must 'yield' on every path")
|
|
}
|
|
return false
|
|
}
|
|
if value_control_flow && len(statements) == 1 {
|
|
statement := checker.ast_module.statements[statements[0]]
|
|
#partial switch statement.kind {
|
|
case .If:
|
|
return ct_validate_value_if_structure(state, statement)
|
|
case .For, .While:
|
|
n := len(statement.body)
|
|
if !symbol.is_valid(statement.label) {
|
|
return ct_fail(
|
|
state, .Not_Comptime, statement.span,
|
|
"a value loop must label its body (e.g. 'blk:') so a 'yield :blk' can exit it",
|
|
)
|
|
}
|
|
if n == 0 {
|
|
return ct_fail(
|
|
state, .Not_Comptime, statement.span,
|
|
"a value loop's body must end with a 'yield' for when the loop completes",
|
|
)
|
|
}
|
|
last := checker.ast_module.statements[statement.body[n-1]]
|
|
if last.kind != .Yield || symbol.is_valid(last.label) {
|
|
return ct_fail(
|
|
state, .Not_Comptime, statement.span,
|
|
"a value loop's body must end with a 'yield' for when the loop completes",
|
|
)
|
|
}
|
|
target_start := len(state.yield_targets)
|
|
append(&state.yield_targets, statement.label)
|
|
valid := ct_validate_ordinary_statements(state, statement.body[:n-1])
|
|
if valid && statement.update != ast.INVALID_STMT {
|
|
update := [1]ast.Stmt_Id{statement.update}
|
|
valid = ct_validate_ordinary_statements(state, update[:])
|
|
}
|
|
resize(&state.yield_targets, target_start)
|
|
if valid && last.value_control_flow {
|
|
valid = ct_validate_value_source_structure(
|
|
state, last.body, symbol.INVALID, true, false,
|
|
)
|
|
}
|
|
return valid
|
|
case .Match:
|
|
for arm_id in statement.body {
|
|
arm := checker.ast_module.statements[arm_id]
|
|
if arm.kind == .Match_Arm && !ct_validate_value_branch_structure(state, arm.body) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
n := len(statements)
|
|
if n > 0 {
|
|
last := checker.ast_module.statements[statements[n-1]]
|
|
if last.kind == .Yield && !symbol.is_valid(last.label) {
|
|
if !ct_validate_ordinary_statements(state, statements[:n-1]) {
|
|
return false
|
|
}
|
|
return !last.value_control_flow || ct_validate_value_source_structure(
|
|
state, last.body, symbol.INVALID, true, false,
|
|
)
|
|
}
|
|
}
|
|
if allow_exit {
|
|
return ct_validate_ordinary_statements(state, statements)
|
|
}
|
|
return ct_fail(state, .Not_Comptime, source.Span{}, "a value block must end with an explicit 'yield'")
|
|
}
|
|
|
|
ct_exec_value_branch :: proc(state: ^Ct_State, statements: []ast.Stmt_Id, depth: int) -> (Ct_Flow, bool) {
|
|
checker := state.checker
|
|
if len(statements) == 1 {
|
|
statement := checker.ast_module.statements[statements[0]]
|
|
if statement.kind == .Expression {
|
|
value, flow, ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return flow, ok
|
|
}
|
|
return ct_flow(.Yield, value), true
|
|
}
|
|
}
|
|
flow, ok := ct_exec_statements(state, statements, true, depth+1)
|
|
if ok && flow.kind == .Normal {
|
|
return flow, ct_fail(
|
|
state, .Not_Comptime, source.Span{},
|
|
"a value branch must end with 'yield' or exit on every path (return/break/continue)",
|
|
)
|
|
}
|
|
return flow, ok
|
|
}
|
|
|
|
ct_exec_value_loop :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) -> (Ct_Flow, bool) {
|
|
checker := state.checker
|
|
n := len(statement.body)
|
|
if !symbol.is_valid(statement.label) {
|
|
return ct_flow(.Normal), ct_fail(
|
|
state, .Not_Comptime, statement.span,
|
|
"a value loop must label its body (e.g. 'blk:') so a 'yield :blk' can exit it",
|
|
)
|
|
}
|
|
if n == 0 {
|
|
return ct_flow(.Normal), ct_fail(
|
|
state, .Not_Comptime, statement.span,
|
|
"a value loop's body must end with a 'yield' for when the loop completes",
|
|
)
|
|
}
|
|
fall_stmt := checker.ast_module.statements[statement.body[n-1]]
|
|
if fall_stmt.kind != .Yield || symbol.is_valid(fall_stmt.label) {
|
|
return ct_flow(.Normal), ct_fail(
|
|
state, .Not_Comptime, statement.span,
|
|
"a value loop's body must end with a 'yield' for when the loop completes",
|
|
)
|
|
}
|
|
fallback := ct_flow(.Normal)
|
|
fallback_ok := true
|
|
if fall_stmt.value_control_flow {
|
|
fallback, fallback_ok = ct_exec_value_source(
|
|
state, fall_stmt.body, symbol.INVALID, true, false, depth+1,
|
|
)
|
|
} else {
|
|
value, flow, ok := ct_eval_expr(state, fall_stmt.expr, types.INVALID, depth+1)
|
|
fallback_ok = ok
|
|
fallback = flow
|
|
if ok && flow.kind == .Normal {
|
|
fallback = ct_flow(.Yield, value)
|
|
}
|
|
}
|
|
if !fallback_ok || fallback.kind != .Yield {
|
|
return fallback, fallback_ok
|
|
}
|
|
peeled := statement
|
|
peeled.body = statement.body[:n-1]
|
|
target_start := len(state.yield_targets)
|
|
append(&state.yield_targets, statement.label)
|
|
defer resize(&state.yield_targets, target_start)
|
|
flow := ct_flow(.Normal)
|
|
ok := false
|
|
if statement.kind == .For {
|
|
flow, ok = ct_exec_for(state, peeled, false, depth+1)
|
|
} else {
|
|
flow, ok = ct_exec_while(state, peeled, false, depth+1)
|
|
}
|
|
if !ok {
|
|
return flow, false
|
|
}
|
|
if flow.kind == .Yield && flow.label == statement.label {
|
|
return ct_flow(.Yield, flow.value), true
|
|
}
|
|
if flow.kind == .Normal {
|
|
return fallback, true
|
|
}
|
|
return flow, true
|
|
}
|
|
|
|
ct_exec_value_source :: proc(
|
|
state: ^Ct_State,
|
|
statements: []ast.Stmt_Id,
|
|
label: symbol.Id,
|
|
value_control_flow: bool,
|
|
allow_exit: bool,
|
|
depth: int,
|
|
) -> (Ct_Flow, bool) {
|
|
checker := state.checker
|
|
if !ct_validate_value_source_structure(state, statements, label, value_control_flow, allow_exit) {
|
|
return ct_flow(.Normal), false
|
|
}
|
|
if symbol.is_valid(label) {
|
|
target_start := len(state.yield_targets)
|
|
append(&state.yield_targets, label)
|
|
flow, ok := ct_exec_statements(state, statements, false, depth+1)
|
|
resize(&state.yield_targets, target_start)
|
|
if !ok {
|
|
return flow, false
|
|
}
|
|
if flow.kind == .Yield && flow.label == label {
|
|
return ct_flow(.Yield, flow.value), true
|
|
}
|
|
if flow.kind == .Normal {
|
|
return flow, ct_fail(state, .Not_Comptime, source.Span{}, "a labeled value block must 'yield' on every path")
|
|
}
|
|
return flow, true
|
|
}
|
|
if value_control_flow && len(statements) == 1 {
|
|
statement := checker.ast_module.statements[statements[0]]
|
|
#partial switch statement.kind {
|
|
case .If:
|
|
return ct_exec_if(state, statement, true, depth+1)
|
|
case .For, .While:
|
|
return ct_exec_value_loop(state, statement, depth+1)
|
|
case .Match:
|
|
return ct_exec_match(state, statement, true, depth+1)
|
|
}
|
|
}
|
|
flow, ok := ct_exec_statements(state, statements, true, depth+1)
|
|
if ok && flow.kind == .Normal && !allow_exit {
|
|
return flow, ct_fail(state, .Not_Comptime, source.Span{}, "a value block must end with an explicit 'yield'")
|
|
}
|
|
return flow, ok
|
|
}
|
|
|
|
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, statement_index 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_value_source(
|
|
state, statement.body, statement.label, statement.value_control_flow, false, 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, active_state=state)
|
|
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 && value_flow.kind != .Normal {
|
|
flow = value_flow
|
|
} 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, active_state=state)
|
|
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 && state.value_return_depth == 0 {
|
|
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_value_source(
|
|
state, statement.body, symbol.INVALID, true, false, 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 if ok {
|
|
flow = value_flow
|
|
}
|
|
} else {
|
|
flow, ok = ct_return_value(state, statement.expr, statement.span, depth+1)
|
|
}
|
|
case .Yield:
|
|
if symbol.is_valid(statement.label) {
|
|
if !ct_has_yield_target(state, statement.label) {
|
|
ok = ct_failf(
|
|
state, .Not_Comptime, statement.span,
|
|
"no enclosing value loop or block is labeled '%s'",
|
|
symbol_text(checker, statement.label),
|
|
)
|
|
}
|
|
} else if !yield_returns || statement_index != len(statements)-1 {
|
|
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' is only valid as the final statement of a value block")
|
|
}
|
|
if ok {
|
|
if statement.value_control_flow {
|
|
value_flow, value_ok := ct_exec_value_source(
|
|
state, statement.body, symbol.INVALID, true, false, depth+1,
|
|
)
|
|
ok = value_ok
|
|
flow = value_flow
|
|
if ok && value_flow.kind == .Yield {
|
|
flow.label = statement.label
|
|
if types.is_void(state.values[flow.value].type) {
|
|
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' expression must produce a non-void value")
|
|
}
|
|
}
|
|
} else {
|
|
value, expr_flow, expr_ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1)
|
|
ok = expr_ok
|
|
flow = expr_flow
|
|
if ok && flow.kind == .Normal {
|
|
if types.is_void(state.values[value].type) {
|
|
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' expression must produce a non-void value")
|
|
} else {
|
|
flow = ct_flow(.Yield, value, statement.label)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case .If:
|
|
flow, ok = ct_exec_if(state, statement, false, depth+1)
|
|
case .While:
|
|
flow, ok = ct_exec_while(state, statement, false, depth+1)
|
|
case .For:
|
|
flow, ok = ct_exec_for(state, statement, false, 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, false, 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, false, 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_value_source(
|
|
state, statement.body, statement.label, statement.value_control_flow, false, depth+1,
|
|
)
|
|
if ok && flow.kind == .Yield {
|
|
return ct_flow(.Normal), true
|
|
}
|
|
return flow, ok
|
|
}
|
|
_, 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_value_source(
|
|
state, statement.body, statement.label, statement.value_control_flow, false, depth+1,
|
|
)
|
|
if ok && flow.kind == .Yield {
|
|
value = flow.value
|
|
flow = ct_flow(.Normal)
|
|
}
|
|
} else {
|
|
value_expected := expected
|
|
if statement.assignment_op == .Shift_Left || statement.assignment_op == .Shift_Right ||
|
|
statement.assignment_op == .Shift_Left_Saturating {
|
|
value_expected = types.U64
|
|
}
|
|
value, flow, ok = ct_eval_expr(state, statement.expr, value_expected, depth+1)
|
|
}
|
|
if !ok || flow.kind != .Normal {
|
|
return flow, ok
|
|
}
|
|
if statement.assignment_op != .Set {
|
|
current, current_ok := ct_place_get(state, place)
|
|
if !current_ok {
|
|
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime assignment target no longer points to live storage")
|
|
}
|
|
op := ast.Expr_Kind.Add
|
|
#partial switch statement.assignment_op {
|
|
case .Sub: op = .Sub
|
|
case .Mul: op = .Mul
|
|
case .Div: op = .Div
|
|
case .Bit_And: op = .Bit_And
|
|
case .Bit_Or: op = .Bit_Or
|
|
case .Bit_Xor: op = .Bit_Xor
|
|
case .Shift_Left: op = .Shift_Left
|
|
case .Shift_Right: op = .Shift_Right
|
|
case .Shift_Left_Saturating: op = .Shift_Left_Saturating
|
|
case: op = .Add
|
|
}
|
|
bin_flow: Ct_Flow
|
|
value, bin_flow, ok = ct_eval_binary(state, op, current, value, statement.span)
|
|
if !ok || bin_flow.kind != .Normal {
|
|
return bin_flow, ok
|
|
}
|
|
}
|
|
value, ok = ct_coerce_value(state, value, expected, statement.span)
|
|
if !ok {
|
|
return ct_flow(.Normal), false
|
|
}
|
|
if !ct_place_set(state, place, value) {
|
|
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime assignment target is not writable")
|
|
}
|
|
return ct_flow(.Normal), true
|
|
}
|
|
|
|
ct_exec_if :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) {
|
|
checker := state.checker
|
|
if yield_returns && statement.else_body == nil {
|
|
return ct_flow(.Normal), ct_fail(
|
|
state, .Not_Comptime, statement.span,
|
|
"an 'if' used as a value must have an 'else' so every path yields",
|
|
)
|
|
}
|
|
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
|
|
if yield_returns {
|
|
if !value && len(body) == 1 && checker.ast_module.statements[body[0]].kind == .If {
|
|
return ct_exec_if(state, checker.ast_module.statements[body[0]], true, depth+1)
|
|
}
|
|
return ct_exec_value_branch(state, body, depth+1)
|
|
}
|
|
return ct_exec_statements(state, body, false, 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 == .Null {
|
|
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 := ct_flow(.Normal)
|
|
ok := false
|
|
if yield_returns {
|
|
if !matched && len(body) == 1 && checker.ast_module.statements[body[0]].kind == .If {
|
|
flow, ok = ct_exec_if(state, checker.ast_module.statements[body[0]], true, depth+1)
|
|
} else {
|
|
flow, ok = ct_exec_value_branch(state, body, depth+1)
|
|
}
|
|
} else {
|
|
flow, ok = ct_exec_statements(state, body, false, depth+1)
|
|
}
|
|
ct_pop_bindings(state, scope_start)
|
|
return flow, ok
|
|
}
|
|
|
|
ct_loop_consumes_flow :: proc(flow: Ct_Flow, label: symbol.Id, want_continue: bool) -> bool {
|
|
if want_continue && flow.kind != .Continue {
|
|
return false
|
|
}
|
|
if !want_continue && flow.kind != .Break {
|
|
return false
|
|
}
|
|
return !symbol.is_valid(flow.label) || flow.label == label
|
|
}
|
|
|
|
ct_exec_while :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) {
|
|
for {
|
|
if !ct_step(state, statement.span) {
|
|
return ct_flow(.Normal), false
|
|
}
|
|
condition, flow, ok := ct_eval_expr(state, statement.expr, types.BOOL, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return flow, ok
|
|
}
|
|
value, bool_ok := ct_bool_value(state, condition)
|
|
if !bool_ok {
|
|
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'while' condition must be a bool")
|
|
}
|
|
if !value {
|
|
return ct_flow(.Normal), true
|
|
}
|
|
body_flow, body_ok := ct_exec_statements(state, statement.body, yield_returns, depth+1)
|
|
if !body_ok {
|
|
return body_flow, false
|
|
}
|
|
if ct_loop_consumes_flow(body_flow, statement.label, false) {
|
|
return ct_flow(.Normal), true
|
|
}
|
|
if body_flow.kind != .Normal && !ct_loop_consumes_flow(body_flow, statement.label, true) {
|
|
return body_flow, true
|
|
}
|
|
if statement.update != ast.INVALID_STMT {
|
|
update := [1]ast.Stmt_Id{statement.update}
|
|
update_flow, update_ok := ct_exec_statements(state, update[:], false, depth+1)
|
|
if !update_ok || update_flow.kind != .Normal {
|
|
return update_flow, update_ok
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ct_exec_for :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) {
|
|
checker := state.checker
|
|
store := &checker.module.types
|
|
iterable, flow, ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return flow, ok
|
|
}
|
|
value := state.values[iterable]
|
|
if value.kind == .Range {
|
|
if statement.pointer_capture {
|
|
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "range loops do not support pointer captures")
|
|
}
|
|
children := ct_child_slice(state, value)
|
|
if len(children) < 2 {
|
|
return ct_flow(.Normal), false
|
|
}
|
|
start, start_ok := ct_integer_value(state, children[0])
|
|
end, end_ok := ct_integer_value(state, children[1])
|
|
if !start_ok || !end_ok {
|
|
return ct_flow(.Normal), false
|
|
}
|
|
index := i128(0)
|
|
for current := start; current < end || (value.active != 0 && current == end); current += 1 {
|
|
scope_start := len(state.bindings)
|
|
item := ct_add_value(state, Ct_Value{kind=.Integer, type=types.child_type(value.type, &checker.module.types), integer=current})
|
|
ct_bind_value(state, statement.name, state.values[item].type, item, false)
|
|
if symbol.is_valid(statement.index_name) {
|
|
idx := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=index})
|
|
ct_bind_value(state, statement.index_name, types.USIZE, idx, false)
|
|
}
|
|
body_flow, body_ok := ct_exec_statements(state, statement.body, yield_returns, depth+1)
|
|
ct_pop_bindings(state, scope_start)
|
|
if !body_ok {
|
|
return body_flow, false
|
|
}
|
|
if ct_loop_consumes_flow(body_flow, statement.label, false) {
|
|
return ct_flow(.Normal), true
|
|
}
|
|
if body_flow.kind != .Normal && !ct_loop_consumes_flow(body_flow, statement.label, true) {
|
|
return body_flow, true
|
|
}
|
|
index += 1
|
|
}
|
|
return ct_flow(.Normal), true
|
|
}
|
|
if value.kind == .Array {
|
|
children := ct_child_slice(state, value)
|
|
if statement.pointer_capture {
|
|
base_place, _, base_writable, place_flow, place_ok := ct_eval_place(state, statement.expr, depth+1)
|
|
if !place_ok || place_flow.kind != .Normal {
|
|
return place_flow, place_ok
|
|
}
|
|
item, item_ok := types.node(store, value.type)
|
|
if !item_ok || item.kind != .Array {
|
|
return ct_flow(.Normal), false
|
|
}
|
|
for _, index in children {
|
|
scope_start := len(state.bindings)
|
|
elem_writable := base_writable && item.mutable
|
|
elem_place := ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Index, index=u32(index)}, item.child, elem_writable)
|
|
pointer_type := types.pointer(store, item.child, elem_writable, false)
|
|
pointer := ct_add_value(state, Ct_Value{kind=.Pointer, type=pointer_type, index=u64(elem_place), active=-1})
|
|
ct_bind_value(state, statement.name, pointer_type, pointer, false)
|
|
if symbol.is_valid(statement.index_name) {
|
|
idx := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)})
|
|
ct_bind_value(state, statement.index_name, types.USIZE, idx, false)
|
|
}
|
|
body_flow, body_ok := ct_exec_statements(state, statement.body, yield_returns, depth+1)
|
|
ct_pop_bindings(state, scope_start)
|
|
if !body_ok {
|
|
return body_flow, false
|
|
}
|
|
if ct_loop_consumes_flow(body_flow, statement.label, false) {
|
|
return ct_flow(.Normal), true
|
|
}
|
|
if body_flow.kind != .Normal && !ct_loop_consumes_flow(body_flow, statement.label, true) {
|
|
return body_flow, true
|
|
}
|
|
}
|
|
return ct_flow(.Normal), true
|
|
}
|
|
for child, index in children {
|
|
scope_start := len(state.bindings)
|
|
ct_bind_value(state, statement.name, state.values[child].type, child, false)
|
|
if symbol.is_valid(statement.index_name) {
|
|
idx := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)})
|
|
ct_bind_value(state, statement.index_name, types.USIZE, idx, false)
|
|
}
|
|
body_flow, body_ok := ct_exec_statements(state, statement.body, yield_returns, depth+1)
|
|
ct_pop_bindings(state, scope_start)
|
|
if !body_ok {
|
|
return body_flow, false
|
|
}
|
|
if ct_loop_consumes_flow(body_flow, statement.label, false) {
|
|
return ct_flow(.Normal), true
|
|
}
|
|
if body_flow.kind != .Normal && !ct_loop_consumes_flow(body_flow, statement.label, true) {
|
|
return body_flow, true
|
|
}
|
|
}
|
|
return ct_flow(.Normal), true
|
|
}
|
|
if value.kind == .Slice || value.kind == .Pointer {
|
|
count := 0
|
|
array_item: types.Node
|
|
is_pointer_array := false
|
|
if value.kind == .Slice {
|
|
count = int(value.count)
|
|
} else {
|
|
pointer_item, pointer_ok := types.node(store, value.type)
|
|
if pointer_ok && !pointer_item.many {
|
|
array_item, is_pointer_array = types.node(store, pointer_item.child)
|
|
is_pointer_array = is_pointer_array && array_item.kind == .Array
|
|
if is_pointer_array {
|
|
count = int(array_item.count)
|
|
}
|
|
}
|
|
}
|
|
if value.kind == .Pointer && !is_pointer_array {
|
|
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime for-loop pointer iterable must point to an array")
|
|
}
|
|
for index in 0..<count {
|
|
elem_place := INVALID_CT_PLACE
|
|
elem_type := types.INVALID
|
|
elem_writable := false
|
|
if value.kind == .Slice {
|
|
elem_place, elem_type, elem_writable = ct_slice_element_place(state, value, index)
|
|
} else {
|
|
base_place, _, pointer_writable := ct_pointer_place(state, value)
|
|
elem_writable = pointer_writable && array_item.mutable
|
|
elem_type = array_item.child
|
|
elem_place = ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Index, index=u32(index)}, elem_type, elem_writable)
|
|
}
|
|
if elem_place == INVALID_CT_PLACE {
|
|
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime for-loop iterable no longer points to live storage")
|
|
}
|
|
scope_start := len(state.bindings)
|
|
if statement.pointer_capture {
|
|
pointer_type := types.pointer(store, elem_type, elem_writable, false)
|
|
pointer := ct_add_value(state, Ct_Value{kind=.Pointer, type=pointer_type, index=u64(elem_place), active=-1})
|
|
ct_bind_value(state, statement.name, pointer_type, pointer, false)
|
|
} else {
|
|
child, child_ok := ct_place_get(state, elem_place)
|
|
if !child_ok {
|
|
return ct_flow(.Normal), false
|
|
}
|
|
ct_bind_value(state, statement.name, state.values[child].type, child, false)
|
|
}
|
|
if symbol.is_valid(statement.index_name) {
|
|
idx := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)})
|
|
ct_bind_value(state, statement.index_name, types.USIZE, idx, false)
|
|
}
|
|
body_flow, body_ok := ct_exec_statements(state, statement.body, yield_returns, depth+1)
|
|
ct_pop_bindings(state, scope_start)
|
|
if !body_ok {
|
|
return body_flow, false
|
|
}
|
|
if ct_loop_consumes_flow(body_flow, statement.label, false) {
|
|
return ct_flow(.Normal), true
|
|
}
|
|
if body_flow.kind != .Normal && !ct_loop_consumes_flow(body_flow, statement.label, true) {
|
|
return body_flow, true
|
|
}
|
|
}
|
|
return ct_flow(.Normal), true
|
|
}
|
|
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime for-loop iterable must be a range, array, slice, or pointer-to-array")
|
|
}
|
|
|
|
Ct_Match_Selection :: struct {
|
|
arm: ast.Stmt_Id,
|
|
payload: Ct_Value_Id,
|
|
tag: Ct_Value_Id,
|
|
payload_field: int,
|
|
payload_type: types.Type,
|
|
}
|
|
|
|
ct_select_match_arm :: proc(
|
|
state: ^Ct_State,
|
|
statement: ast.Stmt,
|
|
subject: Ct_Value_Id,
|
|
depth: int,
|
|
) -> (Ct_Match_Selection, bool) {
|
|
checker := state.checker
|
|
if subject == INVALID_CT_VALUE || int(subject) >= len(state.values) {
|
|
return {}, false
|
|
}
|
|
subject_value := state.values[subject]
|
|
for arm_id in statement.body {
|
|
arm := checker.ast_module.statements[arm_id]
|
|
if arm.kind != .Match_Arm {
|
|
continue
|
|
}
|
|
matched := len(arm.patterns) == 0
|
|
selection := Ct_Match_Selection{
|
|
arm=arm_id,
|
|
payload=INVALID_CT_VALUE,
|
|
tag=INVALID_CT_VALUE,
|
|
payload_field=-1,
|
|
payload_type=types.INVALID,
|
|
}
|
|
if arm.expand {
|
|
if subject_value.kind == .Struct && types.is_tagged_union(subject_value.type, &checker.module.types) {
|
|
fields := types.fields_for(&checker.module.types, subject_value.type)
|
|
if subject_value.active < 0 || int(subject_value.active) >= len(fields) {
|
|
return {}, false
|
|
}
|
|
field_index := int(subject_value.active)
|
|
field := fields[field_index]
|
|
selection.payload_field = field_index
|
|
selection.payload_type = field.type
|
|
children := ct_child_slice(state, subject_value)
|
|
if len(children) > 0 {
|
|
selection.payload = children[0]
|
|
} else if types.is_void(field.type) {
|
|
selection.payload = ct_add_value(state, Ct_Value{kind=.Void, type=types.VOID})
|
|
}
|
|
tag_type := types.union_tag_enum(subject_value.type, &checker.module.types)
|
|
member, found := find_enum_member(checker, tag_type, symbol.Id(field.name))
|
|
if !found {
|
|
return {}, false
|
|
}
|
|
selection.tag = ct_add_value(state, Ct_Value{kind=.Integer, type=tag_type, integer=member.value})
|
|
return selection, true
|
|
}
|
|
if subject_value.kind == .Integer && types.is_enum(subject_value.type, &checker.module.types) {
|
|
selection.tag = subject
|
|
return selection, true
|
|
}
|
|
return {}, false
|
|
}
|
|
if !matched {
|
|
for pattern_id in arm.patterns {
|
|
pattern := checker.ast_module.exprs[pattern_id]
|
|
if subject_value.kind == .Struct && types.is_tagged_union(subject_value.type, &checker.module.types) {
|
|
if pattern.kind != .Enum_Literal {
|
|
return {}, false
|
|
}
|
|
field_index, field, found := find_struct_field(checker, subject_value.type, pattern.name)
|
|
if !found {
|
|
return {}, false
|
|
}
|
|
if field_index == int(subject_value.active) {
|
|
matched = true
|
|
selection.payload_field = field_index
|
|
selection.payload_type = field.type
|
|
children := ct_child_slice(state, subject_value)
|
|
if len(children) > 0 {
|
|
selection.payload = children[0]
|
|
}
|
|
break
|
|
}
|
|
} else if pattern.kind == .Range {
|
|
probe, range_flow, range_ok := ct_eval_expr(state, pattern_id, subject_value.type, depth)
|
|
if !range_ok || range_flow.kind != .Normal {
|
|
return {}, false
|
|
}
|
|
if ct_range_contains(state, probe, subject) {
|
|
matched = true
|
|
break
|
|
}
|
|
} else {
|
|
probe, pattern_flow, pattern_ok := ct_eval_expr(state, pattern_id, subject_value.type, depth)
|
|
if !pattern_ok || pattern_flow.kind != .Normal {
|
|
return {}, false
|
|
}
|
|
if ct_values_equal(state, subject, probe) {
|
|
matched = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if matched {
|
|
return selection, true
|
|
}
|
|
}
|
|
return {}, false
|
|
}
|
|
|
|
ct_exec_match :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) {
|
|
checker := state.checker
|
|
refinement_cell := INVALID_CT_CELL
|
|
subject_expr := checker.ast_module.exprs[statement.expr]
|
|
if subject_expr.kind == .Name && !symbol.is_valid(subject_expr.qualifier) {
|
|
if index, found := ct_find_binding_index(state, subject_expr.name); found && !state.bindings[index].mutable {
|
|
refinement_cell = state.bindings[index].cell
|
|
}
|
|
}
|
|
subject, flow, ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1)
|
|
if !ok || flow.kind != .Normal {
|
|
return flow, ok
|
|
}
|
|
wants_pointer := false
|
|
for arm_id in statement.body {
|
|
arm := checker.ast_module.statements[arm_id]
|
|
wants_pointer = wants_pointer || (arm.kind == .Match_Arm && arm.pointer_capture)
|
|
}
|
|
subject_place := INVALID_CT_PLACE
|
|
subject_writable := false
|
|
if wants_pointer {
|
|
place_type: types.Type
|
|
place_flow: Ct_Flow
|
|
place_ok := false
|
|
subject_place, place_type, subject_writable, place_flow, place_ok = ct_eval_place(state, statement.expr, depth+1)
|
|
if !place_ok || place_flow.kind != .Normal {
|
|
return place_flow, place_ok
|
|
}
|
|
if !types.is_tagged_union(place_type, &checker.module.types) {
|
|
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime match pointer captures require an addressable tagged-union subject")
|
|
}
|
|
}
|
|
selection, selected := ct_select_match_arm(state, statement, subject, depth+1)
|
|
if selected {
|
|
arm := checker.ast_module.statements[selection.arm]
|
|
refinement_variants: [dynamic]u32
|
|
refinement_variants.allocator = checker.allocator
|
|
defer delete(refinement_variants)
|
|
subject_type := state.values[subject].type
|
|
if refinement_cell != INVALID_CT_CELL &&
|
|
(types.is_enum(subject_type, &checker.module.types) || types.is_tagged_union(subject_type, &checker.module.types)) {
|
|
if arm.expand {
|
|
if types.is_enum(subject_type, &checker.module.types) {
|
|
for member in types.enum_members_for(&checker.module.types, subject_type) {
|
|
if member.value == state.values[subject].integer {
|
|
append(&refinement_variants, member.name)
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
fields := types.fields_for(&checker.module.types, subject_type)
|
|
active := state.values[subject].active
|
|
if active >= 0 && int(active) < len(fields) {
|
|
append(&refinement_variants, fields[active].name)
|
|
}
|
|
}
|
|
} else if len(arm.patterns) > 0 {
|
|
for pattern_id in arm.patterns {
|
|
pattern := checker.ast_module.exprs[pattern_id]
|
|
if pattern.kind == .Enum_Literal {
|
|
append(&refinement_variants, u32(pattern.name))
|
|
}
|
|
}
|
|
} else {
|
|
covered: [dynamic]symbol.Id
|
|
covered.allocator = checker.allocator
|
|
defer delete(covered)
|
|
for candidate_id in statement.body {
|
|
if candidate_id == selection.arm {
|
|
break
|
|
}
|
|
candidate := checker.ast_module.statements[candidate_id]
|
|
for pattern_id in candidate.patterns {
|
|
pattern := checker.ast_module.exprs[pattern_id]
|
|
if pattern.kind == .Enum_Literal {
|
|
append(&covered, pattern.name)
|
|
}
|
|
}
|
|
}
|
|
member_enum := types.union_tag_enum(subject_type, &checker.module.types) if types.is_tagged_union(subject_type, &checker.module.types) else subject_type
|
|
for member in types.enum_members_for(&checker.module.types, member_enum) {
|
|
if !contains_name(covered[:], symbol.Id(member.name)) {
|
|
append(&refinement_variants, member.name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
refinement_start := len(state.error_refinements)
|
|
if refinement_cell != INVALID_CT_CELL && len(refinement_variants) > 0 {
|
|
append(&state.error_refinements, Ct_Error_Refinement{cell=refinement_cell, variants=refinement_variants[:]})
|
|
}
|
|
defer resize(&state.error_refinements, refinement_start)
|
|
scope_start := len(state.bindings)
|
|
if arm.expand && types.is_enum(state.values[subject].type, &checker.module.types) {
|
|
if len(arm.captures) > 0 && arm.captures[0] != checker.sink_symbol {
|
|
ct_bind_value(state, arm.captures[0], state.values[subject].type, subject, false)
|
|
}
|
|
} else if len(arm.captures) > 0 && selection.payload != INVALID_CT_VALUE {
|
|
capture := arm.captures[0]
|
|
if arm.pointer_capture {
|
|
if subject_place == INVALID_CT_PLACE || selection.payload_field < 0 || !types.is_valid(selection.payload_type) {
|
|
ct_pop_bindings(state, scope_start)
|
|
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, arm.span, "comptime match pointer capture requires a tagged-union payload")
|
|
}
|
|
payload_place := ct_extend_place(
|
|
state, subject_place, Ct_Path_Elem{kind=.Field, index=u32(selection.payload_field)},
|
|
selection.payload_type, subject_writable,
|
|
)
|
|
pointer_type := types.pointer(&checker.module.types, selection.payload_type, subject_writable, false)
|
|
pointer := ct_add_value(state, Ct_Value{kind=.Pointer, type=pointer_type, index=u64(payload_place), active=-1})
|
|
if capture != checker.sink_symbol {
|
|
ct_bind_value(state, capture, pointer_type, pointer, false)
|
|
}
|
|
} else if capture != checker.sink_symbol {
|
|
ct_bind_value(state, capture, state.values[selection.payload].type, selection.payload, false)
|
|
}
|
|
}
|
|
if arm.expand && len(arm.captures) > 1 && arm.captures[1] != checker.sink_symbol &&
|
|
selection.tag != INVALID_CT_VALUE {
|
|
ct_bind_value(state, arm.captures[1], state.values[selection.tag].type, selection.tag, false)
|
|
}
|
|
arm_flow := ct_flow(.Normal)
|
|
arm_ok := false
|
|
if yield_returns {
|
|
arm_flow, arm_ok = ct_exec_value_branch(state, arm.body, depth+1)
|
|
} else {
|
|
arm_flow, arm_ok = ct_exec_statements(state, arm.body, false, depth+1)
|
|
}
|
|
ct_pop_bindings(state, scope_start)
|
|
return arm_flow, arm_ok
|
|
}
|
|
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "comptime match did not select an arm")
|
|
}
|
|
|
|
ct_values_equal :: proc(state: ^Ct_State, left_id, right_id: Ct_Value_Id) -> bool {
|
|
if left_id == INVALID_CT_VALUE || right_id == INVALID_CT_VALUE ||
|
|
int(left_id) >= len(state.values) || int(right_id) >= len(state.values) {
|
|
return false
|
|
}
|
|
left := state.values[left_id]
|
|
right := state.values[right_id]
|
|
if ct_is_integer_like(left) && ct_is_integer_like(right) {
|
|
return left.integer == right.integer
|
|
}
|
|
if left.kind == .Float && right.kind == .Float {
|
|
return left.float == right.float
|
|
}
|
|
if left.kind == .Bool && right.kind == .Bool {
|
|
return left.integer == right.integer
|
|
}
|
|
return false
|
|
}
|
|
|
|
ct_range_contains :: proc(state: ^Ct_State, range_id, value_id: Ct_Value_Id) -> bool {
|
|
if range_id == INVALID_CT_VALUE || int(range_id) >= len(state.values) {
|
|
return false
|
|
}
|
|
range_value := state.values[range_id]
|
|
if range_value.kind != .Range {
|
|
return false
|
|
}
|
|
children := ct_child_slice(state, range_value)
|
|
if len(children) < 2 {
|
|
return false
|
|
}
|
|
lo, lo_ok := ct_integer_value(state, children[0])
|
|
hi, hi_ok := ct_integer_value(state, children[1])
|
|
value, value_ok := ct_integer_value(state, value_id)
|
|
if !lo_ok || !hi_ok || !value_ok {
|
|
return false
|
|
}
|
|
return value >= lo && (value <= hi if range_value.active != 0 else value < hi)
|
|
}
|
|
|
|
eval_integer_constant_in_context :: proc(
|
|
checker: ^Checker,
|
|
expr_id: ast.Expr_Id,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
depth := 0,
|
|
values: []Comptime_Value = nil,
|
|
) -> Constant {
|
|
if depth > 64 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return Constant{kind=.Not_Constant}
|
|
}
|
|
state := ct_state_make(checker, pkg, file, types.INVALID, values, diagnose=false)
|
|
defer ct_state_destroy(&state)
|
|
return eval_integer_constant_in_state(&state, expr_id, depth)
|
|
}
|
|
|
|
eval_integer_constant_in_state :: proc(state: ^Ct_State, expr_id: ast.Expr_Id, depth := 0) -> Constant {
|
|
if depth > 64 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(state.checker.ast_module.exprs) {
|
|
return Constant{kind=.Not_Constant}
|
|
}
|
|
previous_silent := state.silent
|
|
state.silent = true
|
|
defer state.silent = previous_silent
|
|
value, flow, ok := ct_eval_expr(state, expr_id, types.INVALID, depth)
|
|
if !ok || flow.kind != .Normal {
|
|
#partial switch state.error {
|
|
case .Overflow:
|
|
return Constant{kind=.Overflow}
|
|
case .Div_By_Zero:
|
|
return Constant{kind=.Div_By_Zero}
|
|
case .Non_Exact:
|
|
return Constant{kind=.Non_Exact}
|
|
case .Integer_Division:
|
|
return Constant{kind=.Integer_Division}
|
|
}
|
|
return Constant{kind=.Not_Constant}
|
|
}
|
|
integer, integer_ok := ct_integer_value(state, value)
|
|
if !integer_ok {
|
|
return Constant{kind=.Not_Constant}
|
|
}
|
|
return Constant{kind=.Value, value=integer}
|
|
}
|
|
|
|
try_fold_typed_integer_expr :: proc(
|
|
checker: ^Checker,
|
|
expr_id: ast.Expr_Id,
|
|
expected: types.Type,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> (hir.Expr_Id, bool) {
|
|
if !is_typed_integer_fold_candidate(checker, expr_id) {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
state := ct_state_make(checker, pkg, file, diagnose=false)
|
|
defer ct_state_destroy(&state)
|
|
value, flow, ok := ct_eval_expr(&state, expr_id, expected)
|
|
if !ok || flow.kind != .Normal || value == INVALID_CT_VALUE || int(value) >= len(state.values) ||
|
|
state.values[value].kind != .Integer {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
return ct_materialize_value(&state, value, checker.ast_module.exprs[expr_id].span, expected), true
|
|
}
|
|
|
|
eval_comptime_statements :: proc(
|
|
checker: ^Checker,
|
|
statements: []ast.Stmt_Id,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
depth: int,
|
|
values: []Comptime_Value,
|
|
yield_returns: bool,
|
|
) -> (Constant, bool, bool) {
|
|
state := ct_state_make(checker, pkg, file, types.INVALID, values, diagnose=false)
|
|
defer ct_state_destroy(&state)
|
|
flow := ct_flow(.Normal)
|
|
ok := false
|
|
if yield_returns {
|
|
flow, ok = ct_exec_value_source(&state, statements, symbol.INVALID, false, false, depth)
|
|
} else {
|
|
flow, ok = ct_exec_statements(&state, statements, false, 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_value_source(&state, expr.body, symbol.INVALID, false, false, 0)
|
|
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_value_source(&state, expr.body, symbol.INVALID, false, false, 0)
|
|
if ok && flow.kind == .Yield {
|
|
value = flow.value
|
|
flow = ct_flow(.Normal)
|
|
} else if ok {
|
|
state.error = .Not_Comptime
|
|
state.diagnostic = source.add(checker.diagnostics, expr.span, "comptime block must yield a value")
|
|
ok = false
|
|
}
|
|
}
|
|
if ok && flow.kind == .Normal && value != INVALID_CT_VALUE {
|
|
return ct_materialize_value(&state, value, expr.span, expected)
|
|
}
|
|
if state.error == .Div_By_Zero {
|
|
return build_constant_expr(checker, expr, Constant{kind=.Div_By_Zero}, expected)
|
|
}
|
|
if state.error == .Overflow {
|
|
return build_constant_expr(checker, expr, Constant{kind=.Overflow}, expected)
|
|
}
|
|
if state.error == .Non_Exact {
|
|
return build_constant_expr(checker, expr, Constant{kind=.Non_Exact}, expected)
|
|
}
|
|
if state.error == .Integer_Division {
|
|
return build_constant_expr(checker, expr, Constant{kind=.Integer_Division}, expected)
|
|
}
|
|
diagnostic := state.diagnostic
|
|
if diagnostic == source.INVALID_DIAGNOSTIC {
|
|
diagnostic = source.add(checker.diagnostics, expr.span, "expression cannot be evaluated at comptime")
|
|
}
|
|
return invalid_hir_expr(checker, expr.span, diagnostic, expected)
|
|
}
|
|
|
|
try_build_specialization_expr :: proc(
|
|
checker: ^Checker,
|
|
expr_id: ast.Expr_Id,
|
|
expected: types.Type,
|
|
pkg: ast.Package_Id,
|
|
file: ast.File_Id,
|
|
) -> (hir.Expr_Id, bool) {
|
|
if len(checker.current_comptime_values) == 0 && len(checker.static_bindings) == 0 ||
|
|
expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values, diagnose=false)
|
|
defer ct_state_destroy(&state)
|
|
value, flow, ok := ct_eval_expr(&state, expr_id, expected, 0)
|
|
if !ok || flow.kind != .Normal || value == INVALID_CT_VALUE || int(value) >= len(state.values) {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
static := state.values[value]
|
|
if static.kind != .Function {
|
|
return hir.INVALID_EXPR, false
|
|
}
|
|
return ct_materialize_value(&state, value, checker.ast_module.exprs[expr_id].span, expected), true
|
|
}
|