cc39c96fc0
Snapshot full union storage rather than choosing an overlapping field. Preserve structural union source syntax independently of nominal identity so direct literal arguments materialize and share normalized specialization keys. Reject uninitialized bytes and address-bearing member layouts.
5899 lines
279 KiB
Plaintext
5899 lines
279 KiB
Plaintext
import "@std/mem"
|
|
import "@std/arraylist"
|
|
import "@std/hashmap"
|
|
import "@std/enummap"
|
|
import "@source/ast"
|
|
import "@source/diagnostics"
|
|
import "@source/strpool"
|
|
import "@source/lower"
|
|
import "@source/interpreter"
|
|
import "@source/ir"
|
|
|
|
sema :: import "@source/sema"
|
|
typecheck :: import "@source/sema/check"
|
|
|
|
@hide:file no_node :: maxval!(ast.NodeId)
|
|
@hide:file no_index :: maxval!(usize)
|
|
|
|
@hide:file
|
|
Error :: alias mem.AllocError | diagnostics.CapacityError | strpool.InternError
|
|
|
|
@hide:file
|
|
ValueKind :: enum {
|
|
invalid
|
|
type
|
|
integer
|
|
boolean
|
|
string
|
|
function
|
|
null
|
|
real
|
|
aggregate
|
|
}
|
|
|
|
@hide:file
|
|
Value :: struct {
|
|
kind ValueKind = .invalid
|
|
node ast.NodeId = no_node
|
|
magnitude u64 = 0
|
|
negative bool = false
|
|
boolean bool = false
|
|
annotation ast.NodeId = no_node
|
|
}
|
|
|
|
@hide:file
|
|
Instance :: struct {
|
|
function ast.NodeId
|
|
arguments []mut Value
|
|
declaration ast.NodeId = no_node
|
|
type ast.NodeId = no_node
|
|
next usize = no_index
|
|
diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
|
|
signature ast.NodeId = no_node
|
|
value ast.NodeId = no_node
|
|
}
|
|
|
|
@hide:file
|
|
Rewrite :: struct { target ast.NodeId, saved ast.NodeId }
|
|
|
|
#! owns an undo log and retained checking obligations; deinit releases them with their retained allocator.
|
|
#! borrows the caller's AST builder; call deinit before finishing or deinitializing that builder.
|
|
#! generated nodes remain arena-owned and are released with the AST.
|
|
Result :: struct {
|
|
builder @mut ast.Builder
|
|
changes arraylist.ArrayList(Rewrite)
|
|
constraints arraylist.ArrayList(ast.NodeId)
|
|
}
|
|
|
|
#! restores rewritten source nodes in the borrowed builder and releases the undo log and checking obligations.
|
|
#! does not remove generated arena nodes or restore resolution bindings.
|
|
deinit proc(result @mut Result) void {
|
|
index := result.changes.items.len
|
|
while (index > 0) {
|
|
index -= 1
|
|
change :: result.changes.items[index]
|
|
ast.replace_node_from_source(result.builder, change.target, change.saved)
|
|
}
|
|
arraylist.deinit(&result.changes)
|
|
arraylist.deinit(&result.constraints)
|
|
}
|
|
|
|
@hide:file
|
|
State :: struct {
|
|
allocator mem.Allocator
|
|
builder @mut ast.Builder
|
|
tree ast.Tree
|
|
strings @mut strpool.StringPool
|
|
bindings arraylist.ArrayList(sema.Binding)
|
|
declarations arraylist.ArrayList(ast.NodeId)
|
|
instances arraylist.ArrayList(Instance)
|
|
cache hashmap.AutoHashMap(usize, usize)
|
|
expanded arraylist.ArrayList(u8)
|
|
capture_types hashmap.AutoHashMap(ast.NodeId, ast.NodeId)
|
|
diagnostics @mut diagnostics.Store
|
|
depth usize = 0
|
|
original_count usize
|
|
rewrites @mut Result
|
|
mapping []mut ast.NodeId
|
|
resolution @mut sema.Resolution
|
|
evaluation_diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
|
|
type_execution_depth usize = 0
|
|
type_execution_remaining usize = 0
|
|
value_expansion_depth usize = 0
|
|
return_type ast.NodeId = no_node
|
|
yield_type ast.NodeId = no_node
|
|
}
|
|
|
|
#! elaborates templates into cached concrete declarations in the caller-owned `builder` arena.
|
|
#! extends `resolution` to cover generated nodes; no template reaches checking or lowering.
|
|
#! `allocator` supplies returned logs, temporary storage released on every exit, and replacement resolution bindings.
|
|
#! use the allocator retained by `resolution`; the returned Result borrows `builder` until the caller calls deinit.
|
|
#! on failure, restores rewritten source nodes and releases logs; generated nodes remain arena-owned.
|
|
elaborate proc(
|
|
allocator mem.Allocator,
|
|
builder @mut ast.Builder,
|
|
root ast.NodeId,
|
|
resolution @mut sema.Resolution,
|
|
strings @mut strpool.StringPool,
|
|
diagnostic_store @mut diagnostics.Store,
|
|
) Result ! Error {
|
|
result := Result{ builder = builder, changes = arraylist.init(allocator), constraints = arraylist.init(allocator) }
|
|
errdefer deinit(&result)
|
|
# explicit evaluation is useful even in packages without generic declarations.
|
|
original :: ast.view(builder, root)
|
|
needed := false
|
|
for (0..ast.node_count(&original)) |i| {
|
|
id :: ast.NodeId(i)
|
|
class :: ast.node_class(&original, id)
|
|
if (class == .expression
|
|
and (ast.expr_kind(&original, id) == .comptime_expr or ast.expr_kind(&original, id) == .block_expr))
|
|
needed = true
|
|
if (class == .expression and ast.expr_kind(&original, id) == .binary
|
|
and ast.binary_expr(&original, id).operator == .bit_or) needed = true
|
|
if (class == .statement and ast.stmt_kind(&original, id) == .for_stmt
|
|
and ast.for_stmt(&original, id).is_inline) needed = true
|
|
if (class == .match_arm and ast.match_arm_view(&original, id).is_inline) needed = true
|
|
if (class == .expression and ast.expr_kind(&original, id) == .builtin_call) {
|
|
name :: ast.builtin_call(&original, id).name
|
|
text :: ast.source_text(&original, ast.node_span(&original, name))
|
|
if (mem.eql(text, "field") or mem.eql(text, "struct_type") or mem.eql(text, "typeinfo")
|
|
or mem.eql(text, "compile_error")) needed = true
|
|
}
|
|
if (class == .function and ast.function_generic(&original, id)) needed = true
|
|
if (class == .function) {
|
|
if (type_storage_annotation(&original, resolution.storage.bindings,
|
|
ast.function_decl(&original, id).return_type, 0)) needed = true
|
|
}
|
|
if (class == .statement and ast.stmt_kind(&original, id) == .declaration
|
|
and type_storage_annotation(&original, resolution.storage.bindings,
|
|
ast.statement_decl(&original, id).initializer, 0)) needed = true
|
|
if (needed) break
|
|
}
|
|
if (!needed) return result
|
|
mapping :: try mem.alloc(ast.NodeId, allocator, ast.node_count(&original))
|
|
defer mem.free(allocator, mapping)
|
|
state := State{
|
|
allocator = allocator, builder = builder, tree = ast.view(builder, root), strings = strings,
|
|
bindings = arraylist.init(allocator), declarations = arraylist.init(allocator),
|
|
instances = arraylist.init(allocator), cache = hashmap.init(allocator),
|
|
capture_types = hashmap.init(allocator),
|
|
expanded = arraylist.init(allocator), diagnostics = diagnostic_store,
|
|
original_count = ast.node_count(&original),
|
|
rewrites = &result,
|
|
mapping = mapping,
|
|
resolution = resolution,
|
|
}
|
|
defer arraylist.deinit(&state.bindings)
|
|
defer arraylist.deinit(&state.declarations)
|
|
defer arraylist.deinit(&state.expanded)
|
|
defer hashmap.deinit(&state.cache)
|
|
defer hashmap.deinit(&state.capture_types)
|
|
defer {
|
|
for (state.instances.items) |instance| mem.free(allocator, instance.arguments)
|
|
arraylist.deinit(&state.instances)
|
|
}
|
|
count :: ast.node_count(&state.tree)
|
|
for (0..count) |i| try arraylist.append(&state.bindings, sema.binding(resolution, ast.NodeId(i)))
|
|
for (0..ast.package_decl_count(&state.tree)) |i| {
|
|
id :: ast.package_decl(&state.tree, i)
|
|
if (ast.decl_kind(&state.tree, id) == .function and template(&state, id)) continue
|
|
try arraylist.append(&state.declarations, id)
|
|
}
|
|
index := 0
|
|
while (index < state.declarations.items.len) {
|
|
try expand(&state, state.declarations.items[index])
|
|
index += 1
|
|
}
|
|
# metadata definitions have no runtime layout. retain their AST and bindings:
|
|
# surviving runtime annotations still resolve the definition and are rejected.
|
|
retained := usize(0)
|
|
index = 0
|
|
while (index < state.declarations.items.len) {
|
|
declaration :: state.declarations.items[index]
|
|
index += 1
|
|
if (ast.node_class(&state.tree, declaration) == .statement
|
|
and ast.stmt_kind(&state.tree, declaration) == .declaration) {
|
|
view :: ast.statement_decl(&state.tree, declaration)
|
|
if (view.mutability == .constant and type_record_storage(&state, view.initializer, 0)) {
|
|
state.type_execution_remaining = 4096
|
|
state.type_execution_depth += 1
|
|
defer state.type_execution_depth -= 1
|
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
|
if (try type_record_check(&state, view.initializer)) continue
|
|
try reject_evaluation(&state, view.initializer)
|
|
}
|
|
}
|
|
state.declarations.items[retained] = declaration
|
|
retained += 1
|
|
}
|
|
state.declarations.items = state.declarations.items[..retained]
|
|
try remember(&state, root)
|
|
try ast.set_package_declarations(builder, root, state.declarations.items)
|
|
try refresh(&state)
|
|
owned :: try mem.alloc(sema.Binding, allocator, state.bindings.items.len)
|
|
memcopy!(owned, state.bindings.items)
|
|
sema.replace_bindings(resolution, owned)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
refresh proc(state @mut State) void ! mem.AllocError {
|
|
state.tree = ast.view(state.builder, state.tree.root)
|
|
count :: ast.node_count(&state.tree)
|
|
while (state.bindings.items.len < count) try arraylist.append(&state.bindings, sema.Binding{})
|
|
while (state.expanded.items.len < count) try arraylist.append(&state.expanded, u8(0))
|
|
}
|
|
|
|
@hide:file
|
|
binding proc(state @State, id ast.NodeId) sema.Binding {
|
|
if (usize(id) >= state.bindings.items.len) return sema.Binding{}
|
|
return state.bindings.items[usize(id)]
|
|
}
|
|
|
|
@hide:file
|
|
reject proc(state @mut State, id ast.NodeId, code diagnostics.Code) void ! Error {
|
|
span :: ast.node_span(&state.tree, id)
|
|
diagnostic :: try diagnostics.add(state.diagnostics, code, span)
|
|
invalid :: try ast.add_invalid_expr(state.builder, span, diagnostic)
|
|
try replace(state, id, invalid)
|
|
try refresh(state)
|
|
state.bindings.items[usize(id)] = sema.Binding{ kind = .invalid, data = sema.BindingData{ diagnostic = diagnostic } }
|
|
}
|
|
|
|
@hide:file
|
|
expand_compile_error proc(state @mut State, id ast.NodeId) void ! Error {
|
|
if (ast.call_arg_count(&state.tree, id) != 1) {
|
|
try reject(state, id, .invalid_compile_error)
|
|
return
|
|
}
|
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
|
value :: try evaluate(state, ast.call_arg(&state.tree, id, 0))
|
|
if (value.kind != .string) {
|
|
if (state.evaluation_diagnostic != diagnostics.no_diagnostic) try reject_evaluation(state, id)
|
|
else try reject(state, id, .invalid_compile_error)
|
|
return
|
|
}
|
|
text :: ast.literal_text(&state.tree, value.node)
|
|
bytes := arraylist.init(u8, state.allocator)
|
|
defer arraylist.deinit(&bytes)
|
|
cursor := usize(1)
|
|
while (cursor < text.len - 1) {
|
|
byte :: ast.string_next(text, &cursor) catch {
|
|
try reject(state, id, .invalid_escape)
|
|
return
|
|
}
|
|
try arraylist.append(&bytes, byte)
|
|
}
|
|
state.evaluation_diagnostic = try diagnostics.add_message(state.diagnostics,
|
|
.compile_error, ast.node_span(&state.tree, id), bytes.items)
|
|
try reject_evaluation(state, id)
|
|
}
|
|
|
|
@hide:file
|
|
expand proc(state @mut State, id ast.NodeId) void ! Error {
|
|
try expand_expected(state, id, no_node)
|
|
}
|
|
|
|
@hide:file
|
|
expand_expected proc(state @mut State, id, expected ast.NodeId) void ! Error {
|
|
try refresh(state)
|
|
if (state.expanded.items[usize(id)] != 0) return
|
|
state.expanded.items[usize(id)] = 1
|
|
class :: ast.node_class(&state.tree, id)
|
|
saved_return :: state.return_type
|
|
saved_yield :: state.yield_type
|
|
defer {
|
|
state.return_type = saved_return
|
|
state.yield_type = saved_yield
|
|
}
|
|
if (class == .function) {
|
|
state.return_type = ast.function_decl(&state.tree, id).return_type
|
|
state.yield_type = no_node
|
|
}
|
|
if (class == .statement) {
|
|
match ast.stmt_kind(&state.tree, id) {
|
|
.declaration: {
|
|
view :: ast.statement_decl(&state.tree, id)
|
|
annotation := no_node
|
|
if (view.type) |type| {
|
|
try expand(state, type)
|
|
annotation = type
|
|
}
|
|
try expand_expected(state, view.initializer, annotation)
|
|
return
|
|
}
|
|
.assignment_stmt: {
|
|
view :: ast.assignment_stmt(&state.tree, id)
|
|
try expand(state, view.target)
|
|
try expand_expected(state, view.value, try expression_type(state, view.target, 0))
|
|
return
|
|
}
|
|
.return_stmt, .yield_stmt: {
|
|
yielding :: ast.stmt_kind(&state.tree, id) == .yield_stmt
|
|
value :: if (yielding) ast.yield_stmt(&state.tree, id).value else ast.return_stmt(&state.tree, id).value
|
|
annotation :: if (yielding) state.yield_type else state.return_type
|
|
if (value) |operand| try expand_expected(state, operand, annotation)
|
|
return
|
|
}
|
|
else: {}
|
|
}
|
|
}
|
|
if (class == .field) {
|
|
view :: ast.field(&state.tree, id)
|
|
annotation := no_node
|
|
if (view.type) |type| {
|
|
try expand(state, type)
|
|
annotation = type
|
|
}
|
|
if (view.value) |value| try expand_expected(state, value, annotation)
|
|
return
|
|
}
|
|
if (class == .expression and ast.expr_kind(&state.tree, id) == .block_expr) {
|
|
body :: ast.block_expr(&state.tree, id)
|
|
annotation :: if (expected != no_node) expected else try value_block_type(state, body, 0)
|
|
if (is_type(state, annotation) or type_record_storage(state, annotation, 0)) {
|
|
selected :: try evaluate_type_value_block(state, id, annotation)
|
|
if (selected == no_node) {
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
try replace(state, id, selected)
|
|
try refresh(state)
|
|
state.bindings.items[usize(id)] = binding(state, selected)
|
|
return
|
|
}
|
|
state.value_expansion_depth += 1
|
|
defer state.value_expansion_depth -= 1
|
|
state.yield_type = annotation
|
|
try expand(state, body)
|
|
return
|
|
}
|
|
if (class == .block and state.value_expansion_depth > 0) {
|
|
statements := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&statements)
|
|
count :: ast.block_statement_count(&state.tree, id)
|
|
for (0..count) |i| {
|
|
statement :: ast.block_statement(&state.tree, id, i)
|
|
try expand(state, statement)
|
|
try arraylist.append(&statements, statement)
|
|
if (value_block_terminal(state, statement)) break
|
|
}
|
|
if (statements.items.len != count) {
|
|
replacement :: try ast.add_block(state.builder, ast.node_span(&state.tree, id), statements.items)
|
|
try replace(state, id, replacement)
|
|
try refresh(state)
|
|
}
|
|
return
|
|
}
|
|
if (class == .statement and ast.stmt_kind(&state.tree, id) == .for_stmt
|
|
and ast.for_stmt(&state.tree, id).is_inline) {
|
|
try expand_inline_for(state, id)
|
|
return
|
|
}
|
|
if (class == .statement and ast.stmt_kind(&state.tree, id) == .for_stmt) {
|
|
loop :: ast.for_stmt(&state.tree, id)
|
|
try expand(state, loop.iterable)
|
|
if (loop.index) |capture| {
|
|
annotation :: try builtin(state, id, .usize)
|
|
hashmap.put(&state.capture_types, capture, annotation) catch |err| {
|
|
match err {
|
|
.key_exists: {}
|
|
else: return err
|
|
}
|
|
}
|
|
}
|
|
if (!loop.pointer_capture and loop.capture != null) {
|
|
iterable :: try expression_type(state, loop.iterable, 0)
|
|
element := no_node
|
|
if (ast.expr_kind(&state.tree, loop.iterable) == .range) {
|
|
element = try expression_type(state, ast.range_expr(&state.tree, loop.iterable).start, 0)
|
|
} else if (iterable != no_node) {
|
|
kind :: ast.expr_kind(&state.tree, iterable)
|
|
if (kind == .array_type) element = ast.array_type(&state.tree, iterable).element
|
|
if (kind == .slice_type) element = ast.slice_type(&state.tree, iterable).element
|
|
if (kind == .pointer_type) {
|
|
array :: try canonical_type(state, ast.pointer_type(&state.tree, iterable).element)
|
|
if (array != no_node and ast.expr_kind(&state.tree, array) == .array_type)
|
|
element = ast.array_type(&state.tree, array).element
|
|
}
|
|
if (binding(state, iterable).kind == .builtin
|
|
and binding(state, iterable).data.builtin == .string)
|
|
element = try builtin(state, id, .u8)
|
|
}
|
|
if (element != no_node) {
|
|
hashmap.put(&state.capture_types, loop.capture?, element) catch |err| {
|
|
match err {
|
|
.key_exists: {}
|
|
else: return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (class == .statement
|
|
and (ast.stmt_kind(&state.tree, id) == .if_stmt or ast.stmt_kind(&state.tree, id) == .while_stmt)) {
|
|
looping :: ast.stmt_kind(&state.tree, id) == .while_stmt
|
|
capture :: if (looping) ast.while_stmt(&state.tree, id).capture else ast.if_stmt(&state.tree, id).capture
|
|
condition :: if (looping) ast.while_stmt(&state.tree, id).condition else ast.if_stmt(&state.tree, id).condition
|
|
if (capture) |name| {
|
|
annotation :: try expression_type(state, condition, 0)
|
|
if (annotation != no_node and ast.expr_kind(&state.tree, annotation) == .optional_type) {
|
|
child :: ast.type_operand(&state.tree, annotation)
|
|
hashmap.put(&state.capture_types, name, child) catch |err| {
|
|
match err {
|
|
.key_exists: {}
|
|
else: return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (class == .expression and ast.expr_kind(&state.tree, id) == .catch_expr) {
|
|
expression :: ast.catch_expr(&state.tree, id)
|
|
try expand_expected(state, expression.operand, expected)
|
|
if (expression.capture) |capture| {
|
|
annotation :: try expression_type(state, expression.operand, 0)
|
|
if (annotation != no_node and ast.expr_kind(&state.tree, annotation) == .fallible_type) {
|
|
hashmap.put(&state.capture_types, capture, ast.binary_expr(&state.tree, annotation).right) catch |err| {
|
|
match err {
|
|
.key_exists: {}
|
|
else: return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
annotation :: if (expected != no_node) expected else try expression_type(state, id, 0)
|
|
state.yield_type = annotation
|
|
try expand_expected(state, expression.handler, annotation)
|
|
return
|
|
}
|
|
if (class == .statement and ast.stmt_kind(&state.tree, id) == .expression) {
|
|
expression :: ast.statement_expr(&state.tree, id)
|
|
if (
|
|
ast.expr_kind(&state.tree, expression) == .match_expr
|
|
and try specialize_match(state, expression, id, no_node)
|
|
) return
|
|
}
|
|
if (
|
|
class == .expression and ast.expr_kind(&state.tree, id) == .match_expr
|
|
and try specialize_match(state, id, no_node, expected)
|
|
) return
|
|
if (class == .statement and ast.stmt_kind(&state.tree, id) == .if_stmt) {
|
|
conditional :: ast.if_stmt(&state.tree, id)
|
|
explicit :: ast.expr_kind(&state.tree, conditional.condition) == .comptime_expr
|
|
if (conditional.capture == null and (explicit or usize(id) >= state.original_count)) {
|
|
condition :: if (explicit) ast.comptime_operand(&state.tree, conditional.condition) else conditional.condition
|
|
try expand(state, condition)
|
|
if (explicit or branch_constant(state, condition, 0)) {
|
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
|
boolean :: try builtin(state, condition, .bool)
|
|
value :: try evaluate_ir(state, condition, boolean)
|
|
if (explicit and value.kind == .invalid and state.evaluation_diagnostic == diagnostics.no_diagnostic)
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.comptime_argument_required, ast.node_span(&state.tree, condition))
|
|
if (state.evaluation_diagnostic != diagnostics.no_diagnostic) {
|
|
invalid :: try ast.add_invalid_stmt(state.builder, ast.node_span(&state.tree, id),
|
|
state.evaluation_diagnostic)
|
|
try replace(state, id, invalid)
|
|
try refresh(state)
|
|
return
|
|
}
|
|
if (value.kind == .boolean) {
|
|
branch := conditional.then_block
|
|
if (!value.boolean) {
|
|
if (conditional.else_block) |other| branch = other
|
|
else branch = try ast.add_block(state.builder, ast.node_span(&state.tree, id), &[])
|
|
}
|
|
replacement :: try ast.add_block_stmt(state.builder, branch)
|
|
try replace(state, id, replacement)
|
|
try refresh(state)
|
|
try expand(state, branch)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (class == .expression and ast.expr_kind(&state.tree, id) == .comptime_expr) {
|
|
operand :: ast.comptime_operand(&state.tree, id)
|
|
try expand_expected(state, operand, expected)
|
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
|
value :: try evaluate(state, operand)
|
|
if (value.annotation == no_node or !type_record_storage(state, value.annotation, 0))
|
|
try arraylist.append(&state.rewrites.constraints, operand)
|
|
if (value.kind == .invalid) {
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
concrete :: try materialize(state, value)
|
|
replacement := concrete.node
|
|
if (value.annotation != no_node and value.kind != .type) {
|
|
replacement = try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, id),
|
|
value.annotation, replacement)
|
|
try refresh(state)
|
|
}
|
|
try replace(state, id, replacement)
|
|
try refresh(state)
|
|
return
|
|
}
|
|
if (class == .expression and ast.expr_kind(&state.tree, id) == .builtin_call) {
|
|
name :: ast.builtin_call(&state.tree, id).name
|
|
if (
|
|
mem.eql(ast.source_text(&state.tree, ast.node_span(&state.tree, name)), "some")
|
|
and expected != no_node and ast.call_arg_count(&state.tree, id) == 1
|
|
) {
|
|
annotation := try canonical_type(state, expected)
|
|
if (annotation != no_node and ast.expr_kind(&state.tree, annotation) == .fallible_type)
|
|
annotation = try canonical_type(state, ast.binary_expr(&state.tree, annotation).left)
|
|
if (annotation != no_node and ast.expr_kind(&state.tree, annotation) == .optional_type) {
|
|
try expand_expected(state, ast.call_arg(&state.tree, id, 0), ast.type_operand(&state.tree, annotation))
|
|
return
|
|
}
|
|
}
|
|
if (mem.eql(ast.source_text(&state.tree, ast.node_span(&state.tree, name)), "compile_error")) {
|
|
try expand_compile_error(state, id)
|
|
return
|
|
}
|
|
if (mem.eql(ast.source_text(&state.tree, ast.node_span(&state.tree, name)), "field")) {
|
|
try expand_field(state, id)
|
|
return
|
|
}
|
|
if (mem.eql(ast.source_text(&state.tree, ast.node_span(&state.tree, name)), "struct_type")) {
|
|
try expand_struct_type(state, id)
|
|
return
|
|
}
|
|
if (mem.eql(ast.source_text(&state.tree, ast.node_span(&state.tree, name)), "typeinfo")) {
|
|
try expand_typeinfo(state, id)
|
|
return
|
|
}
|
|
}
|
|
if (class == .expression and ast.expr_kind(&state.tree, id) == .call) {
|
|
callee :: ast.call_expr(&state.tree, id).callee
|
|
try expand(state, callee)
|
|
resolved :: binding(state, callee)
|
|
if (
|
|
resolved.kind == .declaration
|
|
and ast.node_class(&state.tree, resolved.data.declaration) == .function
|
|
and template(state, resolved.data.declaration)
|
|
) {
|
|
try specialize_call(state, id, resolved.data.declaration, expected)
|
|
return
|
|
}
|
|
signature := no_node
|
|
if (resolved.kind == .declaration and ast.node_class(&state.tree, resolved.data.declaration) == .function)
|
|
signature = resolved.data.declaration
|
|
else signature = try expression_type(state, callee, 0)
|
|
if (
|
|
signature != no_node
|
|
and (ast.node_class(&state.tree, signature) == .function
|
|
or ast.expr_kind(&state.tree, signature) == .function_type)
|
|
and ast.function_param_count(&state.tree, signature) == ast.call_arg_count(&state.tree, id)
|
|
) {
|
|
for (0..ast.call_arg_count(&state.tree, id)) |i| {
|
|
annotation :: ast.param(&state.tree, ast.function_param(&state.tree, signature, i)).type
|
|
try expand_expected(state, ast.call_arg(&state.tree, id, i), annotation)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
if (class == .expression and expected != no_node) {
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
if (kind == .unary) {
|
|
view :: ast.unary_expr(&state.tree, id)
|
|
if (view.operator == .negate or view.operator == .bit_not or view.operator == .logical_not) {
|
|
try expand_expected(state, view.operand, expected)
|
|
return
|
|
}
|
|
}
|
|
if (kind == .binary and !is_type(state, expected)) {
|
|
view :: ast.binary_expr(&state.tree, id)
|
|
match view.operator {
|
|
.add, .subtract, .multiply, .divide, .bit_and, .bit_or, .bit_xor,
|
|
.logical_and, .logical_or: {
|
|
try expand_expected(state, view.left, expected)
|
|
try expand_expected(state, view.right, expected)
|
|
return
|
|
}
|
|
.shift_left, .shift_right, .shift_left_saturating: {
|
|
try expand_expected(state, view.left, expected)
|
|
try expand(state, view.right)
|
|
return
|
|
}
|
|
else: {}
|
|
}
|
|
}
|
|
if (kind == .try_expr) {
|
|
try expand_expected(state, ast.type_operand(&state.tree, id), expected)
|
|
return
|
|
}
|
|
if (kind == .match_expr) {
|
|
try expand(state, ast.match_expr(&state.tree, id).subject)
|
|
for (0..ast.match_arm_count(&state.tree, id)) |i| {
|
|
arm_id :: ast.match_arm(&state.tree, id, i)
|
|
arm :: ast.match_arm_view(&state.tree, arm_id)
|
|
for (0..ast.match_pattern_count(&state.tree, arm_id)) |j|
|
|
try expand(state, ast.match_pattern(&state.tree, arm_id, j))
|
|
state.yield_type = expected
|
|
annotation :: if (arm.expression) expected else no_node
|
|
try expand_expected(state, arm.body, annotation)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
if (class == .expression) {
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
if (kind == .record_literal or kind == .array_literal) {
|
|
annotation := expected
|
|
if (kind == .record_literal) {
|
|
if (ast.record_literal(&state.tree, id).type) |type| {
|
|
try expand(state, type)
|
|
annotation = type
|
|
}
|
|
}
|
|
shape := if (annotation != no_node) try canonical_type(state, annotation) else no_node
|
|
if (shape != no_node and ast.expr_kind(&state.tree, shape) == .fallible_type)
|
|
shape = try canonical_type(state, ast.binary_expr(&state.tree, shape).left)
|
|
if (shape != no_node and ast.expr_kind(&state.tree, shape) == .optional_type)
|
|
shape = try canonical_type(state, ast.type_operand(&state.tree, shape))
|
|
if (
|
|
kind == .array_literal and shape != no_node
|
|
and (ast.expr_kind(&state.tree, shape) == .array_type or ast.expr_kind(&state.tree, shape) == .slice_type)
|
|
) {
|
|
element :: if (ast.expr_kind(&state.tree, shape) == .array_type)
|
|
ast.array_type(&state.tree, shape).element else ast.slice_type(&state.tree, shape).element
|
|
for (0..ast.array_element_count(&state.tree, id)) |i|
|
|
try expand_expected(state, ast.array_element(&state.tree, id, i), element)
|
|
return
|
|
}
|
|
if (
|
|
kind == .record_literal and shape != no_node
|
|
and (ast.expr_kind(&state.tree, shape) == .record_type or ast.expr_kind(&state.tree, shape) == .union_type)
|
|
) {
|
|
for (0..ast.record_field_count(&state.tree, id)) |i| {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, id, i))
|
|
slot :: if (field.name != null) type_record_slot(state, shape, field.name?) else i
|
|
field_type := no_node
|
|
if (slot < ast.record_field_count(&state.tree, shape)) {
|
|
if (ast.field(&state.tree, ast.record_field(&state.tree, shape, slot)).type) |type|
|
|
field_type = type
|
|
}
|
|
if (field.value) |value| try expand_expected(state, value, field_type)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
count :: ast.child_count(&state.tree, id)
|
|
for (0..count) |i| {
|
|
child :: ast.child(&state.tree, id, i)
|
|
try expand(state, child)
|
|
}
|
|
if (!try type_array_projection(state, id)) {
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
if (class == .expression and ast.expr_kind(&state.tree, id) == .binary) {
|
|
view :: ast.binary_expr(&state.tree, id)
|
|
if (view.operator == .bit_or
|
|
and (is_type(state, try expression_type(state, view.left, 0))
|
|
or is_type(state, try expression_type(state, view.right, 0)))) {
|
|
composed :: try ast.add_binary_type(state.builder, view.span, view.left, view.right, false)
|
|
try replace(state, id, composed)
|
|
try refresh(state)
|
|
return
|
|
}
|
|
if (
|
|
(view.operator == .equal or view.operator == .not_equal)
|
|
and branch_constant(state, view.left, 0) and branch_constant(state, view.right, 0)
|
|
) {
|
|
left :: try evaluate(state, view.left)
|
|
right :: try evaluate(state, view.right)
|
|
if (left.kind == .type and right.kind == .type) _ = try evaluate_binary(state, id)
|
|
}
|
|
}
|
|
if (usize(id) >= state.original_count and class == .expression and ast.expr_kind(&state.tree, id) == .array_type) {
|
|
view :: ast.array_type(&state.tree, id)
|
|
if (view.count) |count_node| {
|
|
try arraylist.append(&state.rewrites.constraints, count_node)
|
|
value :: try evaluate(state, count_node)
|
|
if (value.kind == .integer) {
|
|
folded :: try materialize(state, value)
|
|
replacement :: try ast.add_array_type(state.builder, ast.node_span(&state.tree, id),
|
|
folded.node, view.element, view.mutable)
|
|
try replace(state, id, replacement)
|
|
try refresh(state)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
value_block_terminal proc(state @State, id ast.NodeId) bool {
|
|
class :: ast.node_class(&state.tree, id)
|
|
if (class == .block) {
|
|
count :: ast.block_statement_count(&state.tree, id)
|
|
return count > 0 and value_block_terminal(state, ast.block_statement(&state.tree, id, count - 1))
|
|
}
|
|
if (class != .statement) return false
|
|
match ast.stmt_kind(&state.tree, id) {
|
|
.yield_stmt, .return_stmt, .break_stmt, .continue_stmt, .unreachable_stmt: return true
|
|
.block_stmt: return value_block_terminal(state, ast.statement_block(&state.tree, id))
|
|
.if_stmt: {
|
|
view :: ast.if_stmt(&state.tree, id)
|
|
return (
|
|
view.else_block != null
|
|
and value_block_terminal(state, view.then_block)
|
|
and value_block_terminal(state, view.else_block?)
|
|
)
|
|
}
|
|
else: return false
|
|
}
|
|
}
|
|
|
|
# reflection values are owned AST snapshots. only ordinary leaves and selected
|
|
# runtime bodies enter IR; metadata slices never retain interpreter addresses.
|
|
@hide:file
|
|
metadata_member proc(state @mut State, at ast.NodeId, text []u8) ast.NodeId ! Error {
|
|
result :: try ast.add_generated_identifier(state.builder, ast.node_span(&state.tree, at),
|
|
try strpool.intern(state.strings, text), text)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
metadata_package_contains proc(state @State, package ast.NodeId, source diagnostics.SourceId) bool {
|
|
for (0..ast.package_decl_count_at(&state.tree, package)) |i| {
|
|
if (ast.node_span(&state.tree, ast.package_decl_at(&state.tree, package, i)).source == source) return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
metadata_tags proc(state @mut State, type ast.NodeId) ast.NodeId ! Error {
|
|
if (ast.expr_kind(&state.tree, type) == .enum_type) return type
|
|
if (ast.union_type(&state.tree, type).tag) |tag| return try canonical_type(state, tag)
|
|
members := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&members)
|
|
for (0..ast.record_field_count(&state.tree, type)) |i| {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, type, i))
|
|
member :: try ast.add_field(state.builder, field.span, field.name, null, null)
|
|
try arraylist.append(&members, member)
|
|
try refresh(state)
|
|
}
|
|
result :: try ast.add_enum_type(state.builder, ast.node_span(&state.tree, type), null, members.items)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
metadata_import proc(state @State, at ast.NodeId) ast.NodeId {
|
|
source :: ast.node_span(&state.tree, at).source
|
|
for (0..state.original_count) |i| {
|
|
imported :: ast.NodeId(i)
|
|
resolved :: binding(state, imported)
|
|
if (ast.node_class(&state.tree, imported) != .import or resolved.kind != .namespace) continue
|
|
if (!mem.eql(ast.source_text(&state.tree, ast.import_decl(&state.tree, imported).path), "@std/meta")) continue
|
|
other :: ast.node_span(&state.tree, imported).source
|
|
if (source == other) return resolved.data.namespace
|
|
if (metadata_package_contains(state, resolved.data.namespace, source)) return resolved.data.namespace
|
|
for (0..state.original_count) |j| {
|
|
package :: ast.NodeId(j)
|
|
if (package == state.tree.root or ast.node_class(&state.tree, package) != .package) continue
|
|
if (
|
|
metadata_package_contains(state, package, source)
|
|
and metadata_package_contains(state, package, other)
|
|
) return resolved.data.namespace
|
|
}
|
|
}
|
|
return no_node
|
|
}
|
|
|
|
@hide:file
|
|
metadata_type proc(state @mut State, package ast.NodeId, name []u8) ast.NodeId ! Error {
|
|
for (0..ast.package_decl_count_at(&state.tree, package)) |i| {
|
|
declaration :: ast.package_decl_at(&state.tree, package, i)
|
|
if (ast.node_class(&state.tree, declaration) != .statement) continue
|
|
if (mem.eql(strpool.get_str(state.strings, ast.name_id(&state.tree, declaration))?, name))
|
|
return try canonical_type(state, ast.statement_decl(&state.tree, declaration).initializer)
|
|
}
|
|
return no_node
|
|
}
|
|
|
|
@hide:file
|
|
metadata_record proc(state @mut State, at, type ast.NodeId, values []ast.NodeId) ast.NodeId ! Error {
|
|
try refresh(state)
|
|
fields := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&fields)
|
|
span :: ast.node_span(&state.tree, at)
|
|
for (values) |value, i| {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, type, i))
|
|
owned := value
|
|
if (!is_type(state, field.type?))
|
|
owned = try ast.add_typed_constant(state.builder, span, field.type?, value)
|
|
member :: try ast.add_field(state.builder, span, field.name, null, owned)
|
|
try arraylist.append(&fields, member)
|
|
try refresh(state)
|
|
}
|
|
literal :: try ast.add_record_literal(state.builder, span, null, fields.items)
|
|
result :: try ast.add_typed_constant(state.builder, span, type, literal)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
metadata_schema proc(state @mut State, type ast.NodeId, names [][]u8, types []ast.NodeId) bool ! Error {
|
|
if (
|
|
type == no_node or ast.expr_kind(&state.tree, type) != .record_type
|
|
or ast.record_field_count(&state.tree, type) != names.len
|
|
) return false
|
|
for (names) |name, i| {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, type, i))
|
|
if (
|
|
field.name == null or field.type == null
|
|
or !mem.eql(ast.literal_text(&state.tree, field.name?), name)
|
|
or !try same_type(state, field.type?, types[i])
|
|
) return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
@hide:file
|
|
metadata_abi proc(state @mut State, at ast.NodeId, types []ast.NodeId) bool ! Error {
|
|
info :: types[0]
|
|
if (
|
|
info == no_node or ast.expr_kind(&state.tree, info) != .union_type
|
|
or !ast.union_type(&state.tree, info).auto_tag
|
|
) return false
|
|
span :: ast.node_span(&state.tree, at)
|
|
byte_string :: try ast.add_slice_type(state.builder, span, try builtin(state, at, .u8), false, null)
|
|
optional_name :: try ast.add_optional_type(state.builder, span, byte_string)
|
|
fields :: try ast.add_slice_type(state.builder, span, types[2], false, null)
|
|
usize_type :: try builtin(state, at, .usize)
|
|
type_type :: try builtin(state, at, .type)
|
|
bool_type :: try builtin(state, at, .bool)
|
|
try refresh(state)
|
|
array_names [2][]u8 :: ["child", "len"]
|
|
array_types [2]ast.NodeId :: [type_type, usize_type]
|
|
field_names [3][]u8 :: ["name", "type", "index"]
|
|
field_types [3]ast.NodeId :: [byte_string, type_type, usize_type]
|
|
record_names [4][]u8 :: ["name", "fields", "is_tuple", "layout"]
|
|
record_types [4]ast.NodeId :: [optional_name, fields, bool_type, types[5]]
|
|
enum_names [1][]u8 :: ["fields"]
|
|
enum_types [1]ast.NodeId :: [fields]
|
|
if (
|
|
!try metadata_schema(state, types[1], array_names[..], array_types[..])
|
|
or !try metadata_schema(state, types[2], field_names[..], field_types[..])
|
|
or !try metadata_schema(state, types[3], record_names[..], record_types[..])
|
|
or !try metadata_schema(state, types[4], enum_names[..], enum_types[..])
|
|
) return false
|
|
layout :: types[5]
|
|
if (
|
|
layout == no_node or ast.expr_kind(&state.tree, layout) != .enum_type
|
|
or ast.enum_member_count(&state.tree, layout) != 2
|
|
) return false
|
|
for (0..usize(2)) |i| {
|
|
name :: ast.field(&state.tree, ast.enum_member(&state.tree, layout, i)).name
|
|
expected :: if (i == 0) "auto" else "c"
|
|
if (name == null or !mem.eql(ast.literal_text(&state.tree, name?), expected)) return false
|
|
}
|
|
names [18][]u8 :: ["invalid", "void", "noreturn", "anyopaque", "bool", "integer", "float",
|
|
"array", "pointer", "slice", "range", "optional", "function", "enum", "record", "union", "fallible", "distinct"]
|
|
if (ast.record_field_count(&state.tree, info) != names.len) return false
|
|
void_type :: try builtin(state, at, .void)
|
|
for (names) |name, i| {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, info, i))
|
|
expected := void_type
|
|
if (i == 7) expected = types[1]
|
|
if (i == 13) expected = types[4]
|
|
if (i == 14) expected = types[3]
|
|
if (i == 17) expected = type_type
|
|
if (
|
|
field.name == null or field.type == null
|
|
or !mem.eql(ast.literal_text(&state.tree, field.name?), name)
|
|
or !try same_type(state, field.type?, expected)
|
|
) return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
@hide:file
|
|
expand_typeinfo proc(state @mut State, id ast.NodeId) void ! Error {
|
|
if (ast.call_arg_count(&state.tree, id) != 1) {
|
|
try reject(state, id, .argument_count_mismatch)
|
|
return
|
|
}
|
|
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
|
state.type_execution_depth += 1
|
|
defer state.type_execution_depth -= 1
|
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
|
argument :: ast.call_arg(&state.tree, id, 0)
|
|
value :: try evaluate(state, argument)
|
|
if (value.kind != .type) {
|
|
if (state.evaluation_diagnostic != diagnostics.no_diagnostic) try reject_evaluation(state, id)
|
|
else try reject(state, id, .expected_type)
|
|
return
|
|
}
|
|
package :: metadata_import(state, id)
|
|
if (package == no_node) {
|
|
try reject(state, id, .missing_metadata_import)
|
|
return
|
|
}
|
|
types [6]mut ast.NodeId := undefined
|
|
names [6][]u8 :: ["TypeInfo", "ArrayInfo", "FieldInfo", "RecordInfo", "EnumInfo", "Layout"]
|
|
for (names) |name, i| {
|
|
types[i] = try metadata_type(state, package, name)
|
|
if (types[i] == no_node) {
|
|
try reject(state, id, .invalid_metadata_abi)
|
|
return
|
|
}
|
|
}
|
|
if (!try metadata_abi(state, id, types[..])) {
|
|
try reject(state, id, .invalid_metadata_abi)
|
|
return
|
|
}
|
|
type :: value.node
|
|
if (type_record_storage(state, type, 0)) {
|
|
if (!try type_record_check(state, type)) {
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
} else if (binding(state, type).kind != .builtin) {
|
|
constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, argument), argument, true)
|
|
try refresh(state)
|
|
try arraylist.append(&state.rewrites.constraints, constraint)
|
|
}
|
|
kind :: ast.expr_kind(&state.tree, type)
|
|
tag []u8 := "invalid"
|
|
payload := no_node
|
|
shape := type
|
|
resolved :: binding(state, type)
|
|
if (resolved.kind == .builtin) {
|
|
builtin_kind :: builtin_identity(resolved.data.builtin)
|
|
if (try integer_type(state, type)) tag = "integer"
|
|
else if (builtin_kind == .float) tag = "float"
|
|
else if (builtin_kind == .bool) tag = "bool"
|
|
else if (builtin_kind == .void) tag = "void"
|
|
else if (builtin_kind == .anyopaque) tag = "anyopaque"
|
|
else if (builtin_kind == .string) tag = "pointer"
|
|
} else if (kind == .array_type) {
|
|
tag = "array"
|
|
array :: ast.array_type(&state.tree, type)
|
|
length := Value{}
|
|
if (array.count) |count| length = try evaluate(state, count)
|
|
if (length.kind != .integer or length.negative) {
|
|
try reject(state, id, .invalid_array_length)
|
|
return
|
|
}
|
|
child :: (try materialize(state, try evaluate(state, array.element))).node
|
|
count :: try ast.add_integer(state.builder, ast.node_span(&state.tree, id), length.magnitude)
|
|
values [2]ast.NodeId :: [child, count]
|
|
payload = try metadata_record(state, id, types[1], values[..])
|
|
} else if (kind == .distinct_type) {
|
|
tag = "distinct"
|
|
payload = (try materialize(state, try evaluate(state, ast.type_operand(&state.tree, type)))).node
|
|
} else if (kind == .pointer_type) tag = "pointer"
|
|
else if (kind == .slice_type) tag = "slice"
|
|
else if (kind == .optional_type) tag = "optional"
|
|
else if (kind == .function_type) tag = "function"
|
|
else if (kind == .fallible_type) tag = "fallible"
|
|
else if (kind == .union_type) tag = "union"
|
|
else if (kind == .record_type) tag = "record"
|
|
else if (kind == .enum_type or kind == .union_tag_type) {
|
|
tag = "enum"
|
|
if (kind == .union_tag_type) {
|
|
variants :: try inline_match_shape(state, try canonical_type(state, ast.type_operand(&state.tree, type)))
|
|
shape = try metadata_tags(state, variants)
|
|
}
|
|
} else if (kind == .sum_type) {
|
|
shape = try inline_match_shape(state, type)
|
|
if (shape == no_node) {
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
tag = if (ast.expr_kind(&state.tree, shape) == .enum_type) "enum" else "union"
|
|
}
|
|
span :: ast.node_span(&state.tree, id)
|
|
if (mem.eql(tag, "record") or mem.eql(tag, "enum")) {
|
|
enumeration :: mem.eql(tag, "enum")
|
|
fields := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&fields)
|
|
count :: if (enumeration) ast.enum_member_count(&state.tree, shape) else ast.record_field_count(&state.tree, shape)
|
|
tuple := !enumeration
|
|
for (0..count) |i| {
|
|
if (!try type_step(state, id)) {
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
field_id :: if (enumeration) ast.enum_member(&state.tree, shape, i)
|
|
else ast.record_field(&state.tree, shape, i)
|
|
field :: ast.field(&state.tree, field_id)
|
|
ordinal :: try ast.add_integer(state.builder, span, u64(i))
|
|
try refresh(state)
|
|
name := ordinal
|
|
if (field.name) |member| {
|
|
name = member
|
|
tuple = false
|
|
}
|
|
text :: try ast.add_string(state.builder, span, ast.literal_text(&state.tree, name))
|
|
child_type :: if (enumeration) type else field.type?
|
|
child :: (try materialize(state, try evaluate(state, child_type))).node
|
|
values [3]ast.NodeId :: [text, child, ordinal]
|
|
try arraylist.append(&fields, try metadata_record(state, id, types[2], values[..]))
|
|
}
|
|
collection :: try ast.add_array_literal(state.builder, span, fields.items)
|
|
if (enumeration) {
|
|
values [1]ast.NodeId :: [collection]
|
|
payload = try metadata_record(state, id, types[4], values[..])
|
|
} else {
|
|
name := try ast.add_literal(state.builder, .null, span)
|
|
try refresh(state)
|
|
for (state.declarations.items) |declaration| {
|
|
if (ast.node_class(&state.tree, declaration) == .statement
|
|
and ast.statement_decl(&state.tree, declaration).initializer == type) {
|
|
name = try ast.add_string(state.builder, span, strpool.get_str(state.strings, ast.name_id(&state.tree, declaration))?)
|
|
break
|
|
}
|
|
}
|
|
tuple_value :: try ast.add_boolean(state.builder, span, tuple)
|
|
try refresh(state)
|
|
layout_text :: if (ast.record_c_layout(&state.tree, type)) "c" else "auto"
|
|
layout_name :: try metadata_member(state, id, layout_text)
|
|
layout :: try ast.add_enum_literal(state.builder, span, layout_name)
|
|
values [4]ast.NodeId :: [name, collection, tuple_value, layout]
|
|
payload = try metadata_record(state, id, types[3], values[..])
|
|
}
|
|
}
|
|
if (payload == no_node) payload = try ast.add_literal(state.builder, .void, span)
|
|
member :: try metadata_member(state, id, tag)
|
|
field :: try ast.add_field(state.builder, span, member, null, payload)
|
|
fields [1]ast.NodeId :: [field]
|
|
literal :: try ast.add_record_literal(state.builder, span, null, fields[..])
|
|
result :: try ast.add_typed_constant(state.builder, span, types[0], literal)
|
|
try replace(state, id, result)
|
|
try refresh(state)
|
|
}
|
|
|
|
# erase only the name: ordinary selection checks the base and retains its place,
|
|
# evaluation order, mutability and active-union checks through both backends.
|
|
@hide:file
|
|
expand_field proc(state @mut State, id ast.NodeId) void ! Error {
|
|
if (ast.call_arg_count(&state.tree, id) != 2) {
|
|
try reject(state, id, .argument_count_mismatch)
|
|
return
|
|
}
|
|
base :: ast.call_arg(&state.tree, id, 0)
|
|
argument :: ast.call_arg(&state.tree, id, 1)
|
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
|
value :: try evaluate(state, argument)
|
|
if (value.kind != .string) {
|
|
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.comptime_argument_required, ast.node_span(&state.tree, argument))
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
try arraylist.append(&state.rewrites.constraints, argument)
|
|
text :: ast.literal_text(&state.tree, value.node)
|
|
bytes := arraylist.init(u8, state.allocator)
|
|
defer arraylist.deinit(&bytes)
|
|
cursor := usize(1)
|
|
while (cursor < text.len - 1) {
|
|
byte :: ast.string_next(text, &cursor) catch {
|
|
try reject(state, id, .invalid_escape)
|
|
return
|
|
}
|
|
try arraylist.append(&bytes, byte)
|
|
}
|
|
# only canonical decimal names denote tuple ordinals; "01" is a name.
|
|
numeric := bytes.items.len > 0 and (bytes.items.len == 1 or bytes.items[0] != '0')
|
|
ordinal := u64(0)
|
|
for (bytes.items) |byte| {
|
|
if (byte < '0' or byte > '9') {
|
|
numeric = false
|
|
break
|
|
}
|
|
digit :: u64(byte - '0')
|
|
if (ordinal > divtrunc!(maxval!(u64) - digit, u64(10))) {
|
|
numeric = false
|
|
break
|
|
}
|
|
ordinal = ordinal * 10 + digit
|
|
}
|
|
span :: ast.node_span(&state.tree, argument)
|
|
member := no_node
|
|
if (numeric) member = try ast.add_integer(state.builder, span, ordinal)
|
|
else {
|
|
interned :: try strpool.intern(state.strings, bytes.items)
|
|
member = try ast.add_generated_identifier(state.builder, span, interned, bytes.items)
|
|
}
|
|
selection :: try ast.add_selection(state.builder, ast.node_span(&state.tree, id), base, member)
|
|
try replace(state, id, selection)
|
|
try refresh(state)
|
|
state.expanded.items[usize(id)] = 0
|
|
try expand(state, id)
|
|
}
|
|
|
|
# literal collections deliberately retain per-field context: generated records
|
|
# may pair heterogeneous types and optional defaults in arrays as well as tuples.
|
|
@hide:file
|
|
struct_collection proc(state @mut State, input ast.NodeId) ast.NodeId ! Error {
|
|
if (!try type_step(state, input)) return no_node
|
|
try expand(state, input)
|
|
resolved :: binding(state, input)
|
|
if (resolved.kind == .invalid) {
|
|
state.evaluation_diagnostic = resolved.data.diagnostic
|
|
return no_node
|
|
}
|
|
if (resolved.kind == .declaration
|
|
and ast.node_class(&state.tree, resolved.data.declaration) == .statement) {
|
|
declaration :: ast.statement_decl(&state.tree, resolved.data.declaration)
|
|
if (declaration.mutability != .constant) return no_node
|
|
if (declaration.type) |annotation| {
|
|
value :: try type_execution_value(state, declaration.initializer, annotation)
|
|
if (value == no_node) return no_node
|
|
return try struct_collection(state, value)
|
|
}
|
|
return try struct_collection(state, declaration.initializer)
|
|
}
|
|
kind :: ast.expr_kind(&state.tree, input)
|
|
if (kind == .array_literal) return input
|
|
if (kind == .record_literal) {
|
|
if (ast.record_literal(&state.tree, input).type != null) {
|
|
annotation :: try expression_type(state, input, 0)
|
|
value :: try type_execution_value(state, input, annotation)
|
|
if (value == no_node) return no_node
|
|
return try struct_collection(state, value)
|
|
}
|
|
for (0..ast.record_field_count(&state.tree, input)) |i| {
|
|
if (ast.field(&state.tree, ast.record_field(&state.tree, input, i)).name != null) return no_node
|
|
}
|
|
return input
|
|
}
|
|
if (kind == .typed_constant) {
|
|
value :: ast.typed_constant(&state.tree, input)
|
|
annotation :: try canonical_type(state, value.type)
|
|
if (annotation == no_node) return no_node
|
|
if (ast.expr_kind(&state.tree, annotation) != .array_type) {
|
|
if (ast.expr_kind(&state.tree, annotation) != .record_type) return no_node
|
|
for (0..ast.record_field_count(&state.tree, annotation)) |i| {
|
|
if (ast.field(&state.tree, ast.record_field(&state.tree, annotation, i)).name != null) return no_node
|
|
}
|
|
}
|
|
if (!try type_array_initialized(state, input, input)) return no_node
|
|
return input
|
|
}
|
|
value :: try evaluate(state, input)
|
|
if (value.kind != .aggregate or value.annotation == no_node) return no_node
|
|
owned :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, input),
|
|
value.annotation, value.node)
|
|
try refresh(state)
|
|
return try struct_collection(state, owned)
|
|
}
|
|
|
|
@hide:file
|
|
struct_collection_count proc(state @State, input ast.NodeId) usize {
|
|
value :: if (ast.expr_kind(&state.tree, input) == .typed_constant)
|
|
ast.typed_constant(&state.tree, input).value else input
|
|
return if (ast.expr_kind(&state.tree, value) == .array_literal)
|
|
ast.array_element_count(&state.tree, value) else ast.record_field_count(&state.tree, value)
|
|
}
|
|
|
|
@hide:file
|
|
struct_collection_item proc(state @mut State, input ast.NodeId, index usize) ast.NodeId ! Error {
|
|
value := input
|
|
annotation := no_node
|
|
if (ast.expr_kind(&state.tree, input) == .typed_constant) {
|
|
view :: ast.typed_constant(&state.tree, input)
|
|
value = view.value
|
|
type :: try canonical_type(state, view.type)
|
|
if (ast.expr_kind(&state.tree, type) == .array_type) annotation = ast.array_type(&state.tree, type).element
|
|
else annotation = ast.field(&state.tree, ast.record_field(&state.tree, type, index)).type?
|
|
}
|
|
element :: if (ast.expr_kind(&state.tree, value) == .array_literal)
|
|
ast.array_element(&state.tree, value, index)
|
|
else ast.field(&state.tree, ast.record_field(&state.tree, value, index)).value?
|
|
if (annotation == no_node or is_type(state, annotation)
|
|
or ast.expr_kind(&state.tree, element) == .typed_constant) return element
|
|
result :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, element), annotation, element)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
expand_struct_type proc(state @mut State, id ast.NodeId) void ! Error {
|
|
if (ast.call_arg_count(&state.tree, id) != 4) {
|
|
try reject(state, id, .argument_count_mismatch)
|
|
return
|
|
}
|
|
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
|
state.type_execution_depth += 1
|
|
defer state.type_execution_depth -= 1
|
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
|
layout_input :: ast.call_arg(&state.tree, id, 0)
|
|
try expand(state, layout_input)
|
|
layout := layout_input
|
|
if (ast.expr_kind(&state.tree, layout) != .enum_literal) {
|
|
value :: try evaluate_ir(state, layout, try expression_type(state, layout, 0))
|
|
layout = value.node
|
|
}
|
|
if (layout == no_node or ast.expr_kind(&state.tree, layout) != .enum_literal) {
|
|
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_struct_layout, ast.node_span(&state.tree, layout_input))
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
layout_name :: ast.literal_text(&state.tree, ast.enum_literal(&state.tree, layout))
|
|
c_layout :: mem.eql(layout_name, "c")
|
|
if (!c_layout and !mem.eql(layout_name, "auto")) {
|
|
try reject(state, id, .invalid_struct_layout)
|
|
return
|
|
}
|
|
collections [3]mut ast.NodeId := undefined
|
|
for (0..usize(3)) |i| {
|
|
collections[i] = try struct_collection(state, ast.call_arg(&state.tree, id, i + 1))
|
|
if (collections[i] == no_node) {
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
}
|
|
count :: struct_collection_count(state, collections[0])
|
|
if (count != struct_collection_count(state, collections[1])
|
|
or count != struct_collection_count(state, collections[2])) {
|
|
try reject(state, id, .array_length_mismatch)
|
|
return
|
|
}
|
|
if (c_layout and count == 0) {
|
|
try reject(state, id, .invalid_struct_layout)
|
|
return
|
|
}
|
|
if (count > state.type_execution_remaining) {
|
|
try reject(state, id, .comptime_execution_limit)
|
|
return
|
|
}
|
|
fields := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&fields)
|
|
bytes := arraylist.init(u8, state.allocator)
|
|
defer arraylist.deinit(&bytes)
|
|
for (0..count) |i| {
|
|
if (!try type_step(state, id)) {
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
name_input :: try struct_collection_item(state, collections[0], i)
|
|
name_value :: try evaluate(state, name_input)
|
|
if (name_value.kind != .string) {
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
bytes.items = bytes.items[..0]
|
|
text :: ast.literal_text(&state.tree, name_value.node)
|
|
cursor := usize(1)
|
|
while (cursor < text.len - 1) {
|
|
byte :: ast.string_next(text, &cursor) catch {
|
|
try reject(state, id, .invalid_escape)
|
|
return
|
|
}
|
|
valid :: (
|
|
byte == '_' or (byte >= 'a' and byte <= 'z') or (byte >= 'A' and byte <= 'Z')
|
|
or (bytes.items.len > 0 and byte >= '0' and byte <= '9')
|
|
)
|
|
if (!valid) {
|
|
try reject(state, id, .invalid_generated_field_name)
|
|
return
|
|
}
|
|
try arraylist.append(&bytes, byte)
|
|
}
|
|
if (bytes.items.len == 0) {
|
|
try reject(state, id, .invalid_generated_field_name)
|
|
return
|
|
}
|
|
span :: ast.node_span(&state.tree, name_input)
|
|
name :: try ast.add_generated_identifier(state.builder, span,
|
|
try strpool.intern(state.strings, bytes.items), bytes.items)
|
|
try refresh(state)
|
|
for (fields.items) |previous| {
|
|
if (ast.name_id(&state.tree, ast.field(&state.tree, previous).name?) == ast.name_id(&state.tree, name)) {
|
|
try reject(state, id, .duplicate_field)
|
|
return
|
|
}
|
|
}
|
|
type_input :: try struct_collection_item(state, collections[1], i)
|
|
type_value :: try evaluate(state, type_input)
|
|
if (type_value.kind != .type) {
|
|
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_record_field_type, ast.node_span(&state.tree, type_input))
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
field_type :: (try materialize(state, type_value)).node
|
|
default_input :: try struct_collection_item(state, collections[2], i)
|
|
optional :: try ast.add_optional_type(state.builder, span, field_type)
|
|
contextual :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, default_input), optional, default_input)
|
|
capture :: try ast.add_identifier(state.builder, span, strpool.no_str)
|
|
try refresh(state)
|
|
present :: try type_capture_guard(state, contextual, capture)
|
|
if (present.kind != .boolean) {
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
default ?ast.NodeId := null
|
|
if (present.boolean) {
|
|
unwrapped :: try ast.add_unary(state.builder,
|
|
ast.node_span(&state.tree, default_input), .unwrap, contextual)
|
|
try refresh(state)
|
|
value :: try type_execution_value(state, unwrapped, field_type)
|
|
if (value == no_node) {
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
default = value
|
|
}
|
|
field :: try ast.add_field(state.builder, span, name, field_type, default)
|
|
try refresh(state)
|
|
try arraylist.append(&fields, field)
|
|
}
|
|
record :: try ast.add_record_type(state.builder, ast.node_span(&state.tree, id), fields.items)
|
|
if (c_layout) ast.set_record_c_layout(state.builder, record)
|
|
try replace(state, id, record)
|
|
try refresh(state)
|
|
state.expanded.items[usize(id)] = 0
|
|
try expand(state, id)
|
|
}
|
|
|
|
# composed struct variants use declaration IDs internally, but generated syntax
|
|
# must not embed those declarations and make dependency discovery treat them as owned.
|
|
@hide:file
|
|
member_name proc(state @mut State, name ast.NodeId) ast.NodeId ! Error {
|
|
if (ast.node_class(&state.tree, name) != .statement) return name
|
|
result :: try ast.add_identifier(state.builder, ast.node_span(&state.tree, name), ast.name_id(&state.tree, name))
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
# flatten source member names and payload annotations only; ordinary checking still
|
|
# validates composition, variant identity and collisions on the original sum type.
|
|
@hide:file
|
|
inline_sum_fields proc(state @mut State, input ast.NodeId, fields @mut arraylist.ArrayList(ast.NodeId)) bool ! Error {
|
|
if (!try type_step(state, input)) return false
|
|
type :: try canonical_type(state, input)
|
|
if (type == no_node) return false
|
|
kind :: ast.expr_kind(&state.tree, type)
|
|
if (kind == .sum_type) {
|
|
sum :: ast.binary_expr(&state.tree, type)
|
|
if (!try inline_sum_fields(state, sum.left, fields)) return false
|
|
return try inline_sum_fields(state, sum.right, fields)
|
|
}
|
|
if (kind == .record_type) {
|
|
for (0..ast.node_count(&state.tree)) |i| {
|
|
declaration :: ast.NodeId(i)
|
|
if (ast.node_class(&state.tree, declaration) != .statement
|
|
or ast.stmt_kind(&state.tree, declaration) != .declaration) continue
|
|
if (ast.statement_decl(&state.tree, declaration).initializer != type) continue
|
|
name :: try member_name(state, declaration)
|
|
field :: try ast.add_field(state.builder, ast.node_span(&state.tree, type), name, type, null)
|
|
try refresh(state)
|
|
try arraylist.append(fields, field)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
if (kind != .enum_type and kind != .union_type) return false
|
|
count :: if (kind == .enum_type) ast.enum_member_count(&state.tree, type)
|
|
else ast.record_field_count(&state.tree, type)
|
|
for (0..count) |i| {
|
|
if (!try type_step(state, input)) return false
|
|
member :: if (kind == .enum_type) ast.enum_member(&state.tree, type, i)
|
|
else ast.record_field(&state.tree, type, i)
|
|
field :: ast.field(&state.tree, member)
|
|
if (field.name == null) return false
|
|
if (kind == .union_type and field.type == null) return false
|
|
duplicate := false
|
|
for (fields.items) |previous| {
|
|
if (ast.name_id(&state.tree, ast.field(&state.tree, previous).name?)
|
|
== ast.name_id(&state.tree, field.name?)) duplicate = true
|
|
}
|
|
if (duplicate) continue
|
|
payload :: if (kind == .enum_type) try builtin(state, member, .void) else field.type?
|
|
generated :: try ast.add_field(state.builder, field.span, field.name, payload, null)
|
|
try refresh(state)
|
|
try arraylist.append(fields, generated)
|
|
}
|
|
return true
|
|
}
|
|
|
|
@hide:file
|
|
inline_match_shape proc(state @mut State, annotation ast.NodeId) ast.NodeId ! Error {
|
|
if (ast.expr_kind(&state.tree, annotation) != .sum_type) return annotation
|
|
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
|
state.type_execution_depth += 1
|
|
defer state.type_execution_depth -= 1
|
|
fields := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&fields)
|
|
if (!try inline_sum_fields(state, annotation, &fields)) return no_node
|
|
tagged := false
|
|
for (fields.items) |member| {
|
|
payload :: try canonical_type(state, ast.field(&state.tree, member).type?)
|
|
if (payload == no_node) return no_node
|
|
resolved :: binding(state, payload)
|
|
if (resolved.kind != .builtin or resolved.data.builtin != .void) tagged = true
|
|
}
|
|
span :: ast.node_span(&state.tree, annotation)
|
|
result :: if (tagged) try ast.add_union_type(state.builder, span, null, true, fields.items)
|
|
else try ast.add_enum_type(state.builder, span, null, fields.items)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
# expand dispatch, not the subject: ordinary checking/lowering retains its single
|
|
# evaluation and by-value payload captures. only tags are compile-time constants.
|
|
@hide:file
|
|
expand_inline_match proc(state @mut State, id, subject_type ast.NodeId) void ! Error {
|
|
inline_arm := no_node
|
|
for (0..ast.match_arm_count(&state.tree, id)) |i| {
|
|
candidate :: ast.match_arm(&state.tree, id, i)
|
|
if (ast.match_arm_view(&state.tree, candidate).is_inline) {
|
|
inline_arm = candidate
|
|
break
|
|
}
|
|
}
|
|
if (inline_arm == no_node) return
|
|
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
|
state.type_execution_depth += 1
|
|
defer state.type_execution_depth -= 1
|
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
|
arm :: ast.match_arm_view(&state.tree, inline_arm)
|
|
for (0..usize(2)) |i| {
|
|
capture :: if (i == 0) arm.capture else arm.tag_capture
|
|
if (capture) |name| {
|
|
resolved :: binding(state, name)
|
|
if (resolved.kind == .invalid) {
|
|
state.evaluation_diagnostic = resolved.data.diagnostic
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
count :: ast.match_arm_count(&state.tree, id)
|
|
if (ast.match_arm(&state.tree, id, count - 1) != inline_arm) {
|
|
try reject(state, id, .unreachable_match_arm)
|
|
return
|
|
}
|
|
subject :: ast.match_expr(&state.tree, id).subject
|
|
try expand(state, subject)
|
|
annotation := subject_type
|
|
if (annotation == no_node) annotation = try expression_type(state, subject, 0)
|
|
if (annotation == no_node) {
|
|
try reject(state, id, .invalid_inline_match_subject)
|
|
return
|
|
}
|
|
kind :: ast.expr_kind(&state.tree, annotation)
|
|
tag_only :: kind == .union_tag_type
|
|
members_type := annotation
|
|
if (tag_only) members_type = try canonical_type(state, ast.type_operand(&state.tree, annotation))
|
|
if (members_type != no_node) members_type = try inline_match_shape(state, members_type)
|
|
if (members_type == no_node) {
|
|
if (state.evaluation_diagnostic != diagnostics.no_diagnostic) try reject_evaluation(state, id)
|
|
else try reject(state, id, .invalid_sum_type)
|
|
return
|
|
}
|
|
tagged := false
|
|
if (!tag_only and ast.expr_kind(&state.tree, members_type) == .union_type) {
|
|
view :: ast.union_type(&state.tree, members_type)
|
|
tagged = view.auto_tag or view.tag != null
|
|
}
|
|
if (ast.expr_kind(&state.tree, members_type) != .enum_type and !tag_only and !tagged) {
|
|
try reject(state, id, .invalid_inline_match_subject)
|
|
return
|
|
}
|
|
if (!tagged and arm.tag_capture_present) {
|
|
try reject(state, id, .invalid_inline_match_capture)
|
|
return
|
|
}
|
|
arms := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&arms)
|
|
for (0..(count - 1)) |i| {
|
|
prior :: ast.match_arm(&state.tree, id, i)
|
|
if (ast.match_arm_view(&state.tree, prior).catch_all) {
|
|
try reject(state, id, .unreachable_match_arm)
|
|
return
|
|
}
|
|
try arraylist.append(&arms, prior)
|
|
}
|
|
execution := TypeExecution{
|
|
locals = arraylist.init(state.allocator),
|
|
mapping = try mem.alloc(ast.NodeId, state.allocator, ast.node_count(&state.tree)),
|
|
first_owned = ast.NodeId(0), result_type = no_node,
|
|
}
|
|
defer arraylist.deinit(&execution.locals)
|
|
defer mem.free(state.allocator, execution.mapping)
|
|
span :: ast.node_span(&state.tree, inline_arm)
|
|
static_capture :: if (tagged) arm.tag_capture else arm.capture
|
|
tag_type := annotation
|
|
if (static_capture != null) {
|
|
if (tagged) {
|
|
union_view :: ast.union_type(&state.tree, members_type)
|
|
if (kind != .sum_type and union_view.tag != null)
|
|
tag_type = try canonical_type(state, union_view.tag?)
|
|
else if (type_record_storage(state, annotation, 0)) tag_type = try metadata_tags(state, annotation)
|
|
else {
|
|
concrete :: try materialize(state, Value{ kind = .type, node = annotation })
|
|
tag_type = try ast.add_union_tag_type(state.builder, span, concrete.node)
|
|
try refresh(state)
|
|
}
|
|
}
|
|
concrete_tag :: try materialize(state, Value{ kind = .type, node = tag_type })
|
|
tag_type = concrete_tag.node
|
|
}
|
|
union_members :: tagged or tag_only
|
|
members :: if (union_members) ast.record_field_count(&state.tree, members_type)
|
|
else ast.enum_member_count(&state.tree, members_type)
|
|
for (0..members) |i| {
|
|
member :: if (union_members) ast.record_field(&state.tree, members_type, i)
|
|
else ast.enum_member(&state.tree, members_type, i)
|
|
field :: ast.field(&state.tree, member)
|
|
if (field.name == null or (tagged and field.type == null)) {
|
|
try reject(state, id, .invalid_record_field_type)
|
|
return
|
|
}
|
|
covered := false
|
|
for (arms.items) |prior| {
|
|
for (0..ast.match_pattern_count(&state.tree, prior)) |j| {
|
|
pattern :: ast.match_pattern(&state.tree, prior, j)
|
|
if (ast.expr_kind(&state.tree, pattern) == .enum_literal
|
|
and ast.name_id(&state.tree, ast.enum_literal(&state.tree, pattern))
|
|
== ast.name_id(&state.tree, field.name?)) covered = true
|
|
}
|
|
}
|
|
if (covered) continue
|
|
if (!try type_step(state, inline_arm)) {
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
execution.locals.items = execution.locals.items[..0]
|
|
mem.set(execution.mapping, no_node)
|
|
literal :: try ast.add_enum_literal(state.builder, span, field.name?)
|
|
if (static_capture) |capture| {
|
|
# runtime patterns use the union's dispatch enum, not its declared tag enum.
|
|
tag_literal :: try ast.add_enum_literal(state.builder, span, field.name?)
|
|
tag :: try ast.add_typed_constant(state.builder, span, tag_type, tag_literal)
|
|
try refresh(state)
|
|
try arraylist.append(&execution.locals, TypeLocal{
|
|
declaration = capture, value = tag, annotation = tag_type,
|
|
})
|
|
}
|
|
payload_capture ?ast.NodeId := null
|
|
if (tagged and arm.capture != null) {
|
|
payload_type :: if (is_type(state, field.type?)) field.type? else try canonical_type(state, field.type?)
|
|
if (payload_type == no_node) {
|
|
try reject(state, id, .invalid_record_field_type)
|
|
return
|
|
}
|
|
if (binding(state, payload_type).kind == .builtin
|
|
and binding(state, payload_type).data.builtin == .void) {
|
|
value :: try ast.add_literal(state.builder, .void, span)
|
|
try refresh(state)
|
|
try arraylist.append(&execution.locals, TypeLocal{
|
|
declaration = arm.capture?, value = value, annotation = payload_type,
|
|
})
|
|
} else {
|
|
name :: arm.capture?
|
|
payload_capture = try ast.add_identifier(state.builder,
|
|
ast.node_span(&state.tree, name), ast.name_id(&state.tree, name))
|
|
try refresh(state)
|
|
state.bindings.items[usize(payload_capture?)] = sema.Binding{
|
|
kind = .declaration, data = sema.BindingData{ declaration = payload_capture? },
|
|
}
|
|
execution.mapping[usize(name)] = payload_capture?
|
|
hashmap.put(&state.capture_types, payload_capture?, payload_type) catch |err| {
|
|
match err {
|
|
.key_exists: unreachable
|
|
else: return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
body :: try type_snapshot(state, &execution, arm.body)
|
|
patterns [1]ast.NodeId :: [literal]
|
|
generated :: try ast.add_match_arm(state.builder, span, patterns[..],
|
|
payload_capture, body, arm.expression, false)
|
|
try refresh(state)
|
|
try arraylist.append(&arms, generated)
|
|
}
|
|
if (arms.items.len == count - 1) {
|
|
try reject(state, id, .unreachable_match_arm)
|
|
return
|
|
}
|
|
replacement :: try ast.add_match(state.builder, ast.node_span(&state.tree, id), subject, arms.items)
|
|
try replace(state, id, replacement)
|
|
try refresh(state)
|
|
}
|
|
|
|
# runtime bodies share the type-producer selector, but retain ordinary runtime
|
|
# parameters and control transfers in the selected arm.
|
|
@hide:file
|
|
specialize_match proc(state @mut State, id, statement, expected ast.NodeId) bool ! Error {
|
|
explicit :: ast.expr_kind(&state.tree, ast.match_expr(&state.tree, id).subject) == .comptime_expr
|
|
try expand_inline_match(state, id, no_node)
|
|
if (ast.expr_kind(&state.tree, id) != .match_expr) return true
|
|
subject :: ast.match_expr(&state.tree, id).subject
|
|
annotation := no_node
|
|
for (0..ast.match_arm_count(&state.tree, id)) |i| {
|
|
arm_id :: ast.match_arm(&state.tree, id, i)
|
|
arm :: ast.match_arm_view(&state.tree, arm_id)
|
|
if (explicit or arm.capture == null or ast.match_pattern_count(&state.tree, arm_id) == 0) continue
|
|
if (hashmap.get(&state.capture_types, arm.capture?) != null) continue
|
|
if (annotation == no_node) {
|
|
annotation = try expression_type(state, subject, 0)
|
|
if (annotation != no_node) annotation = try inline_match_shape(state, annotation)
|
|
}
|
|
if (annotation == no_node) break
|
|
payload := no_node
|
|
if (ast.match_expr(&state.tree, id).optional_capture) {
|
|
if (ast.expr_kind(&state.tree, annotation) != .optional_type) break
|
|
payload = ast.type_operand(&state.tree, annotation)
|
|
} else {
|
|
if (ast.expr_kind(&state.tree, annotation) != .union_type) break
|
|
pattern :: ast.match_pattern(&state.tree, arm_id, 0)
|
|
if (ast.expr_kind(&state.tree, pattern) != .enum_literal) continue
|
|
slot :: type_record_slot(state, annotation, ast.enum_literal(&state.tree, pattern))
|
|
if (slot == no_index) continue
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, annotation, slot))
|
|
if (field.type) |type| payload = type
|
|
}
|
|
if (payload != no_node) {
|
|
hashmap.put(&state.capture_types, arm.capture?, payload) catch |err| {
|
|
match err {
|
|
.key_exists: unreachable
|
|
else: return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
try expand(state, subject)
|
|
metadata :: type_record_storage(state, try expression_type(state, subject, 0), 0)
|
|
if (!explicit and usize(id) < state.original_count and !metadata) return false
|
|
if (!explicit and !metadata and !branch_constant(state, subject, 0)) return false
|
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
|
index :: try type_match_arm(state, id)
|
|
if (index == no_index) {
|
|
try reject_evaluation(state, id)
|
|
return true
|
|
}
|
|
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, id, index))
|
|
selected := try type_match_body(state, id, arm)
|
|
if (selected != no_node and !arm.expression) {
|
|
selected = try ast.add_block_expr(state.builder, ast.node_span(&state.tree, selected), selected)
|
|
try refresh(state)
|
|
}
|
|
if (selected == no_node) {
|
|
try reject_evaluation(state, id)
|
|
return true
|
|
}
|
|
try expand_expected(state, selected, expected)
|
|
if (statement != no_node) {
|
|
replacement :: try ast.add_expression_stmt(state.builder, selected)
|
|
try replace(state, statement, replacement)
|
|
} else {
|
|
resolved :: binding(state, selected)
|
|
try remember(state, id)
|
|
ast.replace_node_from_source(state.builder, id, selected)
|
|
try refresh(state)
|
|
state.bindings.items[usize(id)] = resolved
|
|
}
|
|
try refresh(state)
|
|
return true
|
|
}
|
|
|
|
# only probe side-effect-free expressions. ordinary calls stay runtime unless the
|
|
# source requests evaluation with $(); probing must not execute FFI or emit traps.
|
|
@hide:file
|
|
branch_constant proc(state @State, id ast.NodeId, depth usize) bool {
|
|
if (depth >= 128) return false
|
|
resolved :: binding(state, id)
|
|
if (resolved.kind == .builtin) return true
|
|
if (resolved.kind == .declaration) {
|
|
target :: resolved.data.declaration
|
|
if (ast.node_class(&state.tree, target) == .function) return true
|
|
if (ast.node_class(&state.tree, target) != .statement) return false
|
|
declaration :: ast.statement_decl(&state.tree, target)
|
|
initializer :: ast.expr_kind(&state.tree, declaration.initializer)
|
|
# contextual aggregates may omit defaults with runtime calls.
|
|
if (declaration.type != null and (initializer == .record_literal or initializer == .array_literal)) return false
|
|
return (
|
|
declaration.mutability == .constant
|
|
and branch_constant(state, declaration.initializer, depth + 1)
|
|
)
|
|
}
|
|
match ast.expr_kind(&state.tree, id) {
|
|
.literal_int, .literal_float, .literal_bool, .literal_string, .literal_null, .literal_void,
|
|
.union_constant: return true
|
|
.typed_constant: return branch_constant(state, ast.typed_constant(&state.tree, id).value, depth + 1)
|
|
.enum_literal: return true
|
|
.record_literal: {
|
|
# typed literals may omit defaults; materialized constants are complete
|
|
# contextual literals inside a typed_constant wrapper.
|
|
for (0..ast.record_field_count(&state.tree, id)) |i| {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, id, i))
|
|
if (field.value == null or !branch_constant(state, field.value?, depth + 1)) return false
|
|
}
|
|
return ast.record_literal(&state.tree, id).type == null
|
|
}
|
|
.array_literal: {
|
|
for (0..ast.array_element_count(&state.tree, id)) |i| {
|
|
if (!branch_constant(state, ast.array_element(&state.tree, id, i), depth + 1)) return false
|
|
}
|
|
return true
|
|
}
|
|
.selection: return branch_constant(state, ast.selection_expr(&state.tree, id).base, depth + 1)
|
|
.index: {
|
|
view :: ast.index_expr(&state.tree, id)
|
|
return branch_constant(state, view.base, depth + 1) and branch_constant(state, view.index, depth + 1)
|
|
}
|
|
.unary: {
|
|
view :: ast.unary_expr(&state.tree, id)
|
|
return (
|
|
(view.operator == .negate or view.operator == .logical_not or view.operator == .bit_not)
|
|
and branch_constant(state, view.operand, depth + 1)
|
|
)
|
|
}
|
|
.binary: {
|
|
view :: ast.binary_expr(&state.tree, id)
|
|
return (
|
|
branch_constant(state, view.left, depth + 1)
|
|
and branch_constant(state, view.right, depth + 1)
|
|
)
|
|
}
|
|
.pointer_type: return branch_constant(state, ast.pointer_type(&state.tree, id).element, depth + 1)
|
|
.optional_type, .alias_expr: return branch_constant(state, ast.type_operand(&state.tree, id), depth + 1)
|
|
.record_type, .union_type, .enum_type, .distinct_type, .array_type, .slice_type, .function_type,
|
|
.fallible_type, .sum_type, .union_tag_type: return true
|
|
else: return false
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
builtin proc(state @mut State, at ast.NodeId, kind sema.BuiltinType) ast.NodeId ! Error {
|
|
id :: try ast.add_identifier(state.builder, ast.node_span(&state.tree, at), strpool.no_str)
|
|
try refresh(state)
|
|
state.bindings.items[usize(id)] = sema.Binding{ kind = .builtin, data = sema.BindingData{ builtin = kind } }
|
|
return id
|
|
}
|
|
|
|
@hide:file
|
|
reference proc(state @mut State, at, target ast.NodeId) ast.NodeId ! Error {
|
|
id :: try ast.add_identifier(state.builder, ast.node_span(&state.tree, at), strpool.no_str)
|
|
try refresh(state)
|
|
state.bindings.items[usize(id)] = sema.Binding{ kind = .declaration, data = sema.BindingData{ declaration = target } }
|
|
return id
|
|
}
|
|
|
|
@hide:file
|
|
is_type proc(state @State, id ast.NodeId) bool {
|
|
value :: binding(state, id)
|
|
return value.kind == .builtin and value.data.builtin == .type
|
|
}
|
|
|
|
# recognize erased storage without evaluating dependent lengths or elaborating
|
|
# bodies. pointers retain the runtime materialization boundary.
|
|
@hide:file
|
|
type_storage_annotation proc(tree @ast.Tree, bindings []sema.Binding, input ast.NodeId, depth usize) bool {
|
|
if (depth >= 128 or usize(input) >= bindings.len) return false
|
|
resolved :: bindings[usize(input)]
|
|
if (resolved.kind == .builtin) return resolved.data.builtin == .type
|
|
if (resolved.kind == .declaration) {
|
|
declaration :: resolved.data.declaration
|
|
if (ast.node_class(tree, declaration) != .statement) return false
|
|
view :: ast.statement_decl(tree, declaration)
|
|
return (view.mutability == .constant
|
|
and type_storage_annotation(tree, bindings, view.initializer, depth + 1))
|
|
}
|
|
kind :: ast.expr_kind(tree, input)
|
|
if (kind == .alias_expr)
|
|
return type_storage_annotation(tree, bindings, ast.type_operand(tree, input), depth + 1)
|
|
if (kind == .array_type)
|
|
return type_storage_annotation(tree, bindings, ast.array_type(tree, input).element, depth + 1)
|
|
if (kind == .slice_type)
|
|
return type_storage_annotation(tree, bindings, ast.slice_type(tree, input).element, depth + 1)
|
|
if (kind != .record_type and kind != .union_type) return false
|
|
for (0..ast.record_field_count(tree, input)) |i| {
|
|
field :: ast.field(tree, ast.record_field(tree, input, i))
|
|
if (field.type != null and type_storage_annotation(tree, bindings, field.type?, depth + 1)) return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
compiletime_result proc(state @State, annotation ast.NodeId) bool {
|
|
return type_record_storage(state, annotation, 0)
|
|
}
|
|
|
|
@hide:file
|
|
inference_contextual proc(state @State, id ast.NodeId) bool {
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
if (kind == .literal_null or kind == .literal_string) return true
|
|
if (ast.expr_kind(&state.tree, id) == .record_literal) {
|
|
annotation :: ast.record_literal(&state.tree, id).type
|
|
return annotation == null or ast.expr_kind(&state.tree, annotation?) == .enum_literal
|
|
}
|
|
return flexible(state, id) or contextual_literal(state, id)
|
|
}
|
|
|
|
@hide:file
|
|
inference_phase proc(state @State, id ast.NodeId) usize {
|
|
if (inference_contextual(state, id)) return 2
|
|
if (ast.expr_kind(&state.tree, id) == .call) {
|
|
callee :: binding(state, ast.call_expr(&state.tree, id).callee)
|
|
if (
|
|
callee.kind == .declaration and ast.node_class(&state.tree, callee.data.declaration) == .function
|
|
and template(state, callee.data.declaration)
|
|
and ast.call_arg_count(&state.tree, id) != ast.function_param_count(&state.tree, callee.data.declaration)
|
|
) return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
@hide:file
|
|
annotation_ready proc(
|
|
state @State, function, id ast.NodeId, arguments []Value, depth usize,
|
|
) bool {
|
|
if (depth >= 128) return false
|
|
resolved :: binding(state, id)
|
|
if (resolved.kind == .declaration) {
|
|
index :: parameter_index(state, function, resolved.data.declaration)
|
|
if (index != no_index) return arguments[index].kind != .invalid
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i| {
|
|
if (!annotation_ready(state, function, ast.child(&state.tree, id, i), arguments, depth + 1)) return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
@hide:file
|
|
specialize_call proc(state @mut State, call, function, expected ast.NodeId) void ! Error {
|
|
if (state.depth >= 128 or state.instances.items.len >= 4096) {
|
|
try reject(state, call, .specialization_limit)
|
|
return
|
|
}
|
|
state.depth += 1
|
|
defer state.depth -= 1
|
|
count :: ast.function_param_count(&state.tree, function)
|
|
# type-containing results have no runtime call: every input is specialized.
|
|
type_result :: compiletime_result(state, ast.function_decl(&state.tree, function).return_type)
|
|
runtime := usize(0)
|
|
for (0..count) |i| {
|
|
if (!type_result and !ast.param(&state.tree, ast.function_param(&state.tree, function, i)).comptime) runtime += 1
|
|
}
|
|
supplied :: ast.call_arg_count(&state.tree, call)
|
|
explicit :: supplied == count
|
|
if (!explicit and (type_result or supplied != runtime)) {
|
|
try reject(state, call, .argument_count_mismatch)
|
|
return
|
|
}
|
|
arguments :: try mem.alloc(Value, state.allocator, count)
|
|
defer mem.free(state.allocator, arguments)
|
|
mem.set(arguments, Value{})
|
|
runtime_arguments := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&runtime_arguments)
|
|
position := 0
|
|
for (0..count) |i| {
|
|
parameter :: ast.function_param(&state.tree, function, i)
|
|
view :: ast.param(&state.tree, parameter)
|
|
if (view.comptime and !explicit) continue
|
|
argument :: ast.call_arg(&state.tree, call, position)
|
|
position += 1
|
|
if (type_result or view.comptime) {
|
|
context_annotation :: try argument_annotation(state, function, view.type, arguments)
|
|
try expand_expected(state, argument, context_annotation)
|
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
|
annotation := view.type
|
|
contextual := contextual_literal(state, argument)
|
|
if (!contextual and !is_type(state, annotation)) {
|
|
type :: try canonical_type(state, annotation)
|
|
contextual = type != no_node and ast.expr_kind(&state.tree, type) == .fallible_type
|
|
}
|
|
if (contextual or type_record_storage(state, annotation, 0))
|
|
annotation = try argument_annotation(state, function, view.type, arguments)
|
|
arguments[i] = try evaluate_expected(state, argument, annotation)
|
|
if (arguments[i].kind == .invalid) {
|
|
try reject_evaluation(state, call)
|
|
return
|
|
}
|
|
if (is_type(state, view.type) != (arguments[i].kind == .type)) {
|
|
try reject(state, call, .type_mismatch)
|
|
return
|
|
}
|
|
} else {
|
|
try arraylist.append(&runtime_arguments, argument)
|
|
}
|
|
}
|
|
if (!explicit) {
|
|
# rigid arguments constrain first; result context then types nested generic
|
|
# calls before flexible literals choose their default widths.
|
|
for (0..usize(3)) |phase| {
|
|
if (phase == 1 and expected != no_node) {
|
|
result :: ast.function_decl(&state.tree, function).return_type
|
|
if (!try infer_result(state, function, result, expected, arguments)) {
|
|
try reject(state, call, .cannot_infer_generic)
|
|
return
|
|
}
|
|
}
|
|
position = 0
|
|
for (0..count) |i| {
|
|
parameter :: ast.function_param(&state.tree, function, i)
|
|
view :: ast.param(&state.tree, parameter)
|
|
if (view.comptime) continue
|
|
argument :: runtime_arguments.items[position]
|
|
position += 1
|
|
contextual :: inference_contextual(state, argument)
|
|
if (inference_phase(state, argument) != phase) continue
|
|
if (annotation_ready(state, function, view.type, arguments, 0)) {
|
|
annotation :: try argument_annotation(state, function, view.type, arguments)
|
|
try expand_expected(state, argument, annotation)
|
|
}
|
|
actual :: try expression_type(state, argument, 0)
|
|
# contextual literals need not have an independent type; the rewritten
|
|
# call checks them against the substituted signature after inference.
|
|
if (
|
|
(actual == no_node or !try infer(state, function, view.type, actual, arguments, contextual))
|
|
and !contextual
|
|
) {
|
|
try reject(state, call, .cannot_infer_generic)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (0..count) |i| {
|
|
parameter :: ast.function_param(&state.tree, function, i)
|
|
if (ast.param(&state.tree, parameter).comptime and arguments[i].kind == .invalid) {
|
|
try reject(state, call, .cannot_infer_generic)
|
|
return
|
|
}
|
|
}
|
|
position = 0
|
|
for (0..count) |i| {
|
|
view :: ast.param(&state.tree, ast.function_param(&state.tree, function, i))
|
|
if (view.comptime or type_result) continue
|
|
argument :: runtime_arguments.items[position]
|
|
position += 1
|
|
if (state.expanded.items[usize(argument)] != 0) continue
|
|
annotation :: try argument_annotation(state, function, view.type, arguments)
|
|
try expand_expected(state, argument, annotation)
|
|
}
|
|
key := usize(function)
|
|
for arguments |argument| {
|
|
if (argument.kind != .invalid) key = mix(key, try value_hash(state, argument))
|
|
}
|
|
head := no_index
|
|
if (hashmap.get(&state.cache, key)) |found| head = found
|
|
current := head
|
|
instance := no_index
|
|
while (current != no_index) {
|
|
entry :: state.instances.items[current]
|
|
equal := entry.function == function and entry.arguments.len == count
|
|
if (equal) {
|
|
for (0..count) |i| {
|
|
if (!try value_equal(state, entry.arguments[i], arguments[i])) {
|
|
equal = false
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if (equal) {
|
|
instance = current
|
|
break
|
|
}
|
|
current = entry.next
|
|
}
|
|
if (instance == no_index) {
|
|
for (0..count) |i| {
|
|
if (arguments[i].kind != .invalid) arguments[i] = try materialize(state, arguments[i])
|
|
}
|
|
values :: try mem.alloc(Value, state.allocator, count)
|
|
memcopy!(values, arguments)
|
|
instance = state.instances.items.len
|
|
arraylist.append(&state.instances, Instance{ function = function, arguments = values, next = head }) catch |err| {
|
|
mem.free(state.allocator, values)
|
|
return err
|
|
}
|
|
# cache heads are replaced without reallocating a second entry for the same hash.
|
|
if (head != no_index) {
|
|
for (state.cache.entries) |@entry| {
|
|
if (entry.hash != 0 and entry.key == key) {
|
|
entry.value = instance
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
hashmap.put(&state.cache, key, instance) catch |err| {
|
|
match err {
|
|
.key_exists: unreachable
|
|
else: return err
|
|
}
|
|
}
|
|
}
|
|
try instantiate(state, instance, call)
|
|
if (binding(state, call).kind == .invalid) {
|
|
state.instances.items[instance].diagnostic = binding(state, call).data.diagnostic
|
|
return
|
|
}
|
|
}
|
|
diagnostic :: state.instances.items[instance].diagnostic
|
|
if (diagnostic != diagnostics.no_diagnostic) {
|
|
invalid :: try ast.add_invalid_expr(state.builder, ast.node_span(&state.tree, call), diagnostic)
|
|
try replace(state, call, invalid)
|
|
try refresh(state)
|
|
state.bindings.items[usize(call)] = sema.Binding{ kind = .invalid, data = sema.BindingData{ diagnostic = diagnostic } }
|
|
return
|
|
}
|
|
target :: state.instances.items[instance].declaration
|
|
if (target == no_node and state.instances.items[instance].value == no_node) {
|
|
try reject(state, call, .specialization_cycle)
|
|
return
|
|
}
|
|
if (explicit) {
|
|
signature :: state.instances.items[instance].signature
|
|
for (0..count) |i| {
|
|
parameter :: ast.function_param(&state.tree, signature, i)
|
|
view :: ast.param(&state.tree, parameter)
|
|
if (
|
|
(!type_result and !view.comptime) or is_type(state, view.type)
|
|
or type_record_storage(state, view.type, 0)
|
|
) continue
|
|
original :: ast.call_arg(&state.tree, call, i)
|
|
constraint :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, original), view.type, original)
|
|
try refresh(state)
|
|
try arraylist.append(&state.rewrites.constraints, constraint)
|
|
}
|
|
}
|
|
replacement := state.instances.items[instance].value
|
|
if (replacement == no_node) {
|
|
callee :: try reference(state, call, target)
|
|
if (state.instances.items[instance].type != no_node)
|
|
replacement = try ast.add_type_operand(state.builder, ast.node_span(&state.tree, call), callee, true)
|
|
else replacement = try ast.add_call(state.builder, ast.node_span(&state.tree, call), callee, runtime_arguments.items)
|
|
}
|
|
try replace(state, call, replacement)
|
|
try refresh(state)
|
|
}
|
|
|
|
@hide:file
|
|
instantiate proc(state @mut State, index usize, call ast.NodeId) void ! Error {
|
|
function :: state.instances.items[index].function
|
|
type_result :: compiletime_result(state, ast.function_decl(&state.tree, function).return_type)
|
|
count :: state.original_count
|
|
clone_start :: ast.node_count(&state.tree)
|
|
mapping :: state.mapping
|
|
mem.set(mapping, no_node)
|
|
parameters :: ast.function_param_count(&state.tree, function)
|
|
for (0..parameters) |i| {
|
|
parameter :: ast.function_param(&state.tree, function, i)
|
|
if (!type_result and !ast.param(&state.tree, parameter).comptime) continue
|
|
value :: state.instances.items[index].arguments[i]
|
|
replacement := value.node
|
|
annotation :: ast.param(&state.tree, parameter).type
|
|
if (!is_type(state, annotation)) {
|
|
concrete_type :: try ast.clone_subtree(state.builder, annotation, mapping)
|
|
replacement = try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, parameter), concrete_type, value.node)
|
|
try refresh(state)
|
|
}
|
|
for (0..count) |n| {
|
|
resolved :: binding(state, ast.NodeId(n))
|
|
if (
|
|
resolved.kind == .declaration and resolved.data.declaration == parameter
|
|
and ast.node_class(&state.tree, ast.NodeId(n)) == .expression
|
|
) mapping[n] = replacement
|
|
}
|
|
}
|
|
clone :: try ast.clone_subtree(state.builder, function, mapping)
|
|
try refresh(state)
|
|
# copy original resolution, remapping references to cloned locals and runtime parameters.
|
|
for (0..count) |i| {
|
|
target :: mapping[i]
|
|
if (target == no_node or usize(target) < clone_start) continue
|
|
if (ast.node_class(&state.tree, target) == .expression and ast.expr_kind(&state.tree, target) == .typed_constant) continue
|
|
value := state.bindings.items[i]
|
|
if (value.kind == .declaration) {
|
|
original :: value.data.declaration
|
|
if (original != function and usize(original) < count and mapping[usize(original)] != no_node)
|
|
value.data.declaration = mapping[usize(original)]
|
|
}
|
|
state.bindings.items[usize(target)] = value
|
|
}
|
|
state.instances.items[index].signature = try ast.copy_node(state.builder, clone)
|
|
try refresh(state)
|
|
view :: ast.function_decl(&state.tree, clone)
|
|
for (0..parameters) |i| {
|
|
parameter :: ast.function_param(&state.tree, clone, i)
|
|
annotation :: ast.param(&state.tree, parameter).type
|
|
if ((type_result or ast.param(&state.tree, parameter).comptime) and !is_type(state, annotation)) {
|
|
if (!try valid_value(state, annotation, state.instances.items[index].arguments[i])) {
|
|
value :: state.instances.items[index].arguments[i]
|
|
code :: if (value.kind == .integer and try integer_type(state, annotation))
|
|
diagnostics.Code.literal_out_of_range else diagnostics.Code.type_mismatch
|
|
try reject(state, call, code)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
if (type_result) {
|
|
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
|
state.type_execution_depth += 1
|
|
defer state.type_execution_depth -= 1
|
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
|
constraints_start :: state.rewrites.constraints.items.len
|
|
returned :: try return_type_value(state, view.body, ast.NodeId(clone_start), view.return_type)
|
|
if (returned == no_node) {
|
|
try reject_type_result(state, call, returned, constraints_start)
|
|
return
|
|
}
|
|
if (!is_type(state, view.return_type)) {
|
|
state.instances.items[index].value = returned
|
|
constraint_count :: state.rewrites.constraints.items.len
|
|
for (constraints_start..constraint_count) |i| {
|
|
constraint :: state.rewrites.constraints.items[i]
|
|
if (!try expand_type_matches(state, constraint)) {
|
|
try reject_type_result(state, call, returned, constraints_start)
|
|
return
|
|
}
|
|
try expand(state, constraint)
|
|
}
|
|
return
|
|
}
|
|
result := returned
|
|
kind :: ast.expr_kind(&state.tree, returned)
|
|
if (kind != .record_type and kind != .union_type and kind != .enum_type and kind != .distinct_type) {
|
|
result = try ast.add_type_operand(state.builder, ast.node_span(&state.tree, returned), returned, true)
|
|
try refresh(state)
|
|
}
|
|
declaration :: try ast.add_statement_decl(state.builder, view.span, view.name,
|
|
ast.name_id(&state.tree, function), null, .constant, result)
|
|
try refresh(state)
|
|
state.instances.items[index].declaration = declaration
|
|
state.instances.items[index].type = returned
|
|
state.bindings.items[usize(declaration)] = sema.Binding{ kind = .declaration, data = sema.BindingData{ declaration = declaration } }
|
|
try arraylist.append(&state.declarations, declaration)
|
|
if (!try expand_type_matches(state, declaration)) {
|
|
try reject_type_result(state, call, result, constraints_start)
|
|
return
|
|
}
|
|
try expand(state, declaration)
|
|
constraint_count :: state.rewrites.constraints.items.len
|
|
for (constraints_start..constraint_count) |i| {
|
|
constraint :: state.rewrites.constraints.items[i]
|
|
if (!try expand_type_matches(state, constraint)) {
|
|
try reject_type_result(state, call, result, constraints_start)
|
|
return
|
|
}
|
|
try expand(state, constraint)
|
|
}
|
|
} else {
|
|
runtime := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&runtime)
|
|
for (0..parameters) |i| {
|
|
parameter :: ast.function_param(&state.tree, clone, i)
|
|
if (!ast.param(&state.tree, parameter).comptime) try arraylist.append(&runtime, parameter)
|
|
}
|
|
concrete :: try ast.add_function_decl(state.builder, view.span, view.name,
|
|
ast.name_id(&state.tree, function), view.return_type, runtime.items, view.body)
|
|
ast.replace_node(state.builder, clone, concrete)
|
|
try refresh(state)
|
|
state.instances.items[index].declaration = clone
|
|
state.bindings.items[usize(clone)] = sema.Binding{ kind = .declaration, data = sema.BindingData{ declaration = clone } }
|
|
try arraylist.append(&state.declarations, clone)
|
|
try expand(state, clone)
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
reject_evaluation proc(state @mut State, call ast.NodeId) void ! Error {
|
|
diagnostic :: state.evaluation_diagnostic
|
|
if (diagnostic == diagnostics.no_diagnostic) {
|
|
try reject(state, call, .comptime_argument_required)
|
|
return
|
|
}
|
|
invalid :: try ast.add_invalid_expr(state.builder, ast.node_span(&state.tree, call), diagnostic)
|
|
try replace(state, call, invalid)
|
|
try refresh(state)
|
|
state.bindings.items[usize(call)] = sema.Binding{ kind = .invalid, data = sema.BindingData{ diagnostic = diagnostic } }
|
|
}
|
|
|
|
# failed deferred elaboration must not leave unchecked type expressions in the package.
|
|
@hide:file
|
|
reject_type_result proc(state @mut State, call, result ast.NodeId, constraints_start usize) void ! Error {
|
|
try reject_evaluation(state, call)
|
|
state.evaluation_diagnostic = binding(state, call).data.diagnostic
|
|
if (result != no_node) try reject_evaluation(state, result)
|
|
for (constraints_start..(state.rewrites.constraints.items.len)) |i|
|
|
try reject_evaluation(state, state.rewrites.constraints.items[i])
|
|
}
|
|
|
|
@hide:file
|
|
evaluation_dependencies proc(
|
|
state @mut State, id ast.NodeId, flags []mut u8, declarations @mut arraylist.ArrayList(ast.NodeId),
|
|
) bool ! Error {
|
|
if ((flags[usize(id)] & 4) != 0) return true
|
|
flags[usize(id)] = flags[usize(id)] | 4
|
|
if (ast.node_class(&state.tree, id) == .expression and ast.expr_kind(&state.tree, id) == .typed_constant) {
|
|
if (!try type_array_initialized(state, id, id)) return false
|
|
}
|
|
resolved :: binding(state, id)
|
|
if (resolved.kind == .declaration) {
|
|
target :: resolved.data.declaration
|
|
if ((flags[usize(target)] & 8) == 0 and (flags[usize(target)] & 2) == 0) {
|
|
class :: ast.node_class(&state.tree, target)
|
|
if (class == .parameter) return false
|
|
if (class == .statement and ast.statement_decl(&state.tree, target).mutability == .variable) return false
|
|
flags[usize(target)] = flags[usize(target)] | 2
|
|
try arraylist.append(declarations, target)
|
|
}
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i| {
|
|
if (!try evaluation_dependencies(state, ast.child(&state.tree, id, i), flags, declarations)) return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
@hide:file
|
|
evaluation_owned proc(state @State, id ast.NodeId, flags []mut u8) void {
|
|
if ((flags[usize(id)] & 8) != 0) return
|
|
flags[usize(id)] = flags[usize(id)] | 8
|
|
for (0..ast.child_count(&state.tree, id)) |i| evaluation_owned(state, ast.child(&state.tree, id, i), flags)
|
|
}
|
|
|
|
@hide:file
|
|
contextual_literal proc(state @State, id ast.NodeId) bool {
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
if (kind == .builtin_call)
|
|
return mem.eql(ast.literal_text(&state.tree, ast.builtin_call(&state.tree, id).name), "some")
|
|
if (kind == .unary) return ast.unary_expr(&state.tree, id).operator == .bit_not and flexible(state, id)
|
|
if (kind == .binary) {
|
|
operator :: ast.binary_expr(&state.tree, id).operator
|
|
return (
|
|
(operator == .bit_and or operator == .bit_or or operator == .bit_xor
|
|
or operator == .shift_left or operator == .shift_right or operator == .shift_left_saturating)
|
|
and flexible(state, id)
|
|
)
|
|
}
|
|
return kind == .record_literal or kind == .array_literal or kind == .enum_literal or kind == .block_expr
|
|
}
|
|
|
|
@hide:file
|
|
annotation_generic proc(state @State, function, id ast.NodeId, depth usize) bool {
|
|
if (depth >= 128) return true
|
|
resolved :: binding(state, id)
|
|
if (
|
|
resolved.kind == .declaration
|
|
and parameter_index(state, function, resolved.data.declaration) != no_index
|
|
) return true
|
|
for (0..ast.child_count(&state.tree, id)) |i| {
|
|
if (annotation_generic(state, function, ast.child(&state.tree, id, i), depth + 1)) return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
argument_annotation proc(
|
|
state @mut State, function, annotation ast.NodeId, arguments []Value,
|
|
) ast.NodeId ! Error {
|
|
if (!annotation_generic(state, function, annotation, 0)) return annotation
|
|
mapping :: try mem.alloc(ast.NodeId, state.allocator, state.original_count)
|
|
defer mem.free(state.allocator, mapping)
|
|
mem.set(mapping, no_node)
|
|
for (0..ast.function_param_count(&state.tree, function)) |i| {
|
|
if (arguments[i].kind == .invalid) continue
|
|
parameter :: ast.function_param(&state.tree, function, i)
|
|
value := try materialize(state, arguments[i])
|
|
if (value.kind != .type and value.annotation != no_node) {
|
|
value.node = try ast.add_typed_constant(state.builder,
|
|
ast.node_span(&state.tree, value.node), value.annotation, value.node)
|
|
try refresh(state)
|
|
}
|
|
for (0..mapping.len) |n| {
|
|
resolved :: binding(state, ast.NodeId(n))
|
|
if (
|
|
resolved.kind == .declaration and resolved.data.declaration == parameter
|
|
and ast.node_class(&state.tree, ast.NodeId(n)) == .expression
|
|
) mapping[n] = value.node
|
|
}
|
|
}
|
|
start :: ast.node_count(&state.tree)
|
|
result :: try ast.clone_subtree(state.builder, annotation, mapping)
|
|
try refresh(state)
|
|
for (0..mapping.len) |i| {
|
|
target :: mapping[i]
|
|
if (target != no_node and usize(target) >= start) state.bindings.items[usize(target)] = state.bindings.items[i]
|
|
}
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
evaluate_expected proc(state @mut State, id, annotation ast.NodeId) Value ! Error {
|
|
try expand_expected(state, id, annotation)
|
|
if (!is_type(state, annotation) and type_record_storage(state, annotation, 0)) {
|
|
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
|
state.type_execution_depth += 1
|
|
defer state.type_execution_depth -= 1
|
|
constant :: try type_execution_value(state, id, annotation)
|
|
if (constant == no_node or !try type_array_initialized(state, constant, id)) return Value{}
|
|
value :: ast.typed_constant(&state.tree, constant)
|
|
return Value{ kind = .aggregate, node = value.value, annotation = value.type }
|
|
}
|
|
if (!is_type(state, annotation)) {
|
|
type :: try canonical_type(state, annotation)
|
|
if (type != no_node and ast.expr_kind(&state.tree, type) == .array_type
|
|
and !try type_array_initialized(state, id, id)) return Value{}
|
|
if (type != no_node and (contextual_literal(state, id)
|
|
or ast.expr_kind(&state.tree, id) == .block_expr
|
|
or ast.expr_kind(&state.tree, id) == .slice
|
|
or ast.expr_kind(&state.tree, type) == .fallible_type))
|
|
return try evaluate_ir(state, id, type)
|
|
}
|
|
return try evaluate(state, id)
|
|
}
|
|
|
|
# raw union snapshots cannot distinguish addresses from overlapping integer bits.
|
|
# require every view to be address-free before retaining the complete representation.
|
|
@hide:file
|
|
union_bytes_materializable proc(checked @typecheck.Result, type typecheck.Type, depth usize) bool {
|
|
if (depth >= 128) return false
|
|
match type.kind {
|
|
.int, .i8, .i16, .i32, .isize, .u8, .u16, .u32, .u64, .usize,
|
|
.float, .bool, .void, .enumeration: return true
|
|
.array, .optional: {
|
|
return union_bytes_materializable(checked, typecheck.array_type(checked, type.array_type).element, depth + 1)
|
|
}
|
|
.fallible: {
|
|
channels :: typecheck.array_type(checked, type.array_type)
|
|
return (union_bytes_materializable(checked, channels.element, depth + 1)
|
|
and union_bytes_materializable(checked, channels.error_type, depth + 1))
|
|
}
|
|
.record: {
|
|
for (typecheck.record_type(checked, type.record_type).fields) |field| {
|
|
if (!union_bytes_materializable(checked, field.type, depth + 1)) return false
|
|
}
|
|
return true
|
|
}
|
|
else: return false
|
|
}
|
|
}
|
|
|
|
# values leaving temporary evaluation must own no interpreter addresses or module IDs.
|
|
# snapshot readonly bytes and rebuild source literals/references before releasing their backing.
|
|
@hide:file
|
|
serialize_value proc(
|
|
state @mut State,
|
|
module @ir.Module,
|
|
checked @typecheck.Result,
|
|
outcome @interpreter.Outcome,
|
|
functions []ast.NodeId,
|
|
type typecheck.Type,
|
|
value ir.Value,
|
|
span ast.Span,
|
|
) ast.NodeId ! Error {
|
|
uninitialized := value.uninitialized
|
|
if (ir.aggregate(value.type)) {
|
|
if (module.arrays.items[usize(value.array_type)].is_union)
|
|
uninitialized = ir.union_tag(module, value).uninitialized
|
|
else if (type.kind == .optional)
|
|
uninitialized = ir.aggregate_item(module, value, 0).uninitialized
|
|
}
|
|
if (uninitialized) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .read_before_initialization, span)
|
|
return no_node
|
|
}
|
|
result := no_node
|
|
if (type.kind == .slice) {
|
|
slice :: typecheck.array_type(checked, type.array_type)
|
|
if (slice.mutable or slice.element.kind != .u8 or slice.element.nominal != no_node) return no_node
|
|
pointer :: ir.aggregate_item(module, value, 0)
|
|
length :: ir.aggregate_item(module, value, 1)
|
|
if (pointer.uninitialized or length.uninitialized) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .read_before_initialization, span)
|
|
return no_node
|
|
}
|
|
if (
|
|
length.data.unsigned != 0
|
|
and interpreter.evaluation_byte(outcome, module, pointer, usize(length.data.unsigned - 1)) == null
|
|
) return no_node
|
|
nonzero_sentinel :: slice.sentinel != null and slice.sentinel? != 0
|
|
length_bytes :: usize(length.data.unsigned)
|
|
if (nonzero_sentinel and length_bytes == maxval!(usize)) return no_node
|
|
extra :: if (nonzero_sentinel) usize(1) else usize(0)
|
|
bytes :: try mem.alloc(u8, state.allocator, length_bytes + extra)
|
|
defer mem.free(state.allocator, bytes)
|
|
for (0..length_bytes) |index| {
|
|
byte :: interpreter.evaluation_byte(outcome, module, pointer, index)
|
|
if (byte == null) return no_node
|
|
if (byte?.uninitialized) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .read_before_initialization, span)
|
|
return no_node
|
|
}
|
|
bytes[index] = u8(byte?.data.unsigned)
|
|
}
|
|
if (slice.sentinel != null) {
|
|
sentinel :: interpreter.evaluation_byte(outcome, module, pointer, length_bytes)
|
|
if (sentinel == null) return no_node
|
|
if (sentinel?.uninitialized) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .read_before_initialization, span)
|
|
return no_node
|
|
}
|
|
if (sentinel?.data.unsigned != u64(slice.sentinel?)) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .sentinel_mismatch, span)
|
|
return no_node
|
|
}
|
|
if (nonzero_sentinel) bytes[length_bytes] = u8(slice.sentinel?)
|
|
}
|
|
result = try ast.add_string(state.builder, span, bytes)
|
|
if (nonzero_sentinel) {
|
|
end :: try ast.add_integer(state.builder, span, u64(length_bytes))
|
|
sentinel :: try ast.add_integer(state.builder, span, u64(slice.sentinel?))
|
|
result = try ast.add_slice(state.builder, span, result, null, end, sentinel)
|
|
}
|
|
} else if (type.kind == .array) {
|
|
array :: typecheck.array_type(checked, type.array_type)
|
|
elements := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&elements)
|
|
for (0..array.len) |i| {
|
|
child :: ir.aggregate_item(module, value, i)
|
|
node :: try serialize_value(state, module, checked, outcome, functions, array.element, child, span)
|
|
if (node == no_node) return no_node
|
|
try arraylist.append(&elements, node)
|
|
}
|
|
result = try ast.add_array_literal(state.builder, span, elements.items)
|
|
} else if (type.kind == .record) {
|
|
record :: typecheck.record_type(checked, type.record_type)
|
|
if (record.is_union and record.tag_type.kind != .enumeration) {
|
|
if (!union_bytes_materializable(checked, type, 0)) return no_node
|
|
bytes :: try mem.alloc(u8, state.allocator, value.data.elements.len)
|
|
defer mem.free(state.allocator, bytes)
|
|
for (value.data.elements) |byte, index| {
|
|
if (byte.uninitialized) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .read_before_initialization, span)
|
|
return no_node
|
|
}
|
|
bytes[index] = u8(byte.data.unsigned)
|
|
}
|
|
annotation :: if (type.nominal != no_node) type.nominal
|
|
else if (record.declaration != no_node) record.declaration else record.source
|
|
concrete :: try materialize(state, Value{ kind = .type, node = annotation })
|
|
literal :: try ast.add_string(state.builder, span, bytes)
|
|
result = try ast.add_union_constant(state.builder, span, concrete.node, literal)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
fields := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&fields)
|
|
for (record.fields) |field, i| {
|
|
if (record.is_union and !ir.union_active(module, value, i)) continue
|
|
if (record.is_union and field.type.kind == .void) {
|
|
name :: try member_name(state, field.name?)
|
|
result = try ast.add_enum_literal(state.builder, span, name)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
child :: ir.aggregate_item(module, value, i)
|
|
node :: try serialize_value(state, module, checked, outcome, functions, field.type, child, span)
|
|
if (node == no_node) return no_node
|
|
name := field.name
|
|
if (name != null) name = try member_name(state, name?)
|
|
generated :: try ast.add_field(state.builder, span, name, null, node)
|
|
try refresh(state)
|
|
try arraylist.append(&fields, generated)
|
|
}
|
|
result = try ast.add_record_literal(state.builder, span, null, fields.items)
|
|
} else if (type.kind == .fallible) {
|
|
container :: typecheck.array_type(checked, type.array_type)
|
|
failed :: ir.union_tag(module, value).data.integer == 1
|
|
index :: if (failed) usize(1) else usize(0)
|
|
channel :: if (failed) container.error_type else container.element
|
|
child :: ir.aggregate_item(module, value, index)
|
|
result = try serialize_value(state, module, checked, outcome, functions, channel, child, span)
|
|
if (result == no_node) return no_node
|
|
# keep named success/error types distinct even when their members overlap.
|
|
annotation := no_node
|
|
if (channel.nominal != no_node) annotation = channel.nominal
|
|
else if (channel.kind == .enumeration) annotation = typecheck.enum_type(checked, channel.enum_type).declaration
|
|
else if (channel.kind == .record) annotation = typecheck.record_type(checked, channel.record_type).declaration
|
|
if (annotation != no_node) {
|
|
concrete :: try materialize(state, Value{ kind = .type, node = annotation })
|
|
result = try ast.add_typed_constant(state.builder, span, concrete.node, result)
|
|
}
|
|
} else if (type.kind == .enumeration) {
|
|
enumeration :: typecheck.enum_type(checked, type.enum_type)
|
|
negative :: !ir.unsigned_integer(value.type) and value.data.integer < 0
|
|
magnitude :: if (negative) u64(-(value.data.integer + 1)) + 1 else value.data.unsigned
|
|
for (enumeration.members) |member| {
|
|
if (member.magnitude == magnitude and member.negative == negative) {
|
|
name :: try member_name(state, member.name)
|
|
result = try ast.add_enum_literal(state.builder, span, name)
|
|
break
|
|
}
|
|
}
|
|
} else if (type.kind == .optional) {
|
|
container :: typecheck.array_type(checked, type.array_type)
|
|
if (container.element.kind == .pointer or container.element.kind == .function) {
|
|
absent :: if (container.element.kind == .function)
|
|
value.data.function == maxval!(ir.FunctionId)
|
|
else value.data.unsigned == maxval!(u64)
|
|
if (absent) result = try ast.add_literal(state.builder, .null, span)
|
|
else result = try serialize_value(state, module, checked, outcome, functions, container.element, value, span)
|
|
} else if (!ir.aggregate_item(module, value, 0).data.boolean) {
|
|
result = try ast.add_literal(state.builder, .null, span)
|
|
} else {
|
|
child :: ir.aggregate_item(module, value, 1)
|
|
payload :: try serialize_value(state, module, checked, outcome, functions, container.element, child, span)
|
|
if (payload == no_node) return no_node
|
|
name :: try ast.add_generated_identifier(state.builder, span,
|
|
try strpool.intern(state.strings, "some"), "some")
|
|
arguments [1]ast.NodeId :: [payload]
|
|
result = try ast.add_builtin_call(state.builder, span, name, arguments[..])
|
|
}
|
|
} else if (type.kind == .function) {
|
|
index :: usize(value.data.function)
|
|
if (index >= functions.len or functions[index] == no_node) return no_node
|
|
result = try reference(state, functions[index], functions[index])
|
|
} else if (ir.integer_bits(value.type) != 0 and type.kind != .pointer) {
|
|
negative :: !ir.unsigned_integer(value.type) and value.data.integer < 0
|
|
magnitude :: if (negative) u64(-(value.data.integer + 1)) + 1 else value.data.unsigned
|
|
result = try ast.add_integer(state.builder, span, magnitude)
|
|
if (negative) result = try ast.add_unary(state.builder, span, .negate, result)
|
|
} else if (type.kind == .float) {
|
|
result = try ast.add_float(state.builder, span, value.data.real)
|
|
} else if (type.kind == .bool) {
|
|
result = try ast.add_boolean(state.builder, span, value.data.boolean)
|
|
} else if (type.kind == .string) {
|
|
result = try ast.add_string(state.builder, span, ir.string_bytes(module, value.data.string))
|
|
} else if (type.kind == .void) {
|
|
result = try ast.add_literal(state.builder, .void, span)
|
|
}
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
constant_equal proc(state @mut State, left, right ast.NodeId) bool ! Error {
|
|
kind :: ast.expr_kind(&state.tree, left)
|
|
if (kind != ast.expr_kind(&state.tree, right)) return false
|
|
if (kind == .literal_void) return true
|
|
if (kind == .typed_constant or kind == .union_constant) {
|
|
a :: if (kind == .union_constant) ast.union_constant(&state.tree, left) else ast.typed_constant(&state.tree, left)
|
|
b :: if (kind == .union_constant) ast.union_constant(&state.tree, right) else ast.typed_constant(&state.tree, right)
|
|
return try same_type_context(state, a.type, b.type, true) and try constant_equal(state, a.value, b.value)
|
|
}
|
|
if (kind == .slice) {
|
|
a :: ast.slice_expr(&state.tree, left)
|
|
b :: ast.slice_expr(&state.tree, right)
|
|
# serialized nonzero-sentinel views have a canonical owned string and explicit end.
|
|
return (
|
|
try constant_equal(state, a.base, b.base)
|
|
and try constant_equal(state, a.end?, b.end?)
|
|
and try constant_equal(state, a.sentinel?, b.sentinel?)
|
|
)
|
|
}
|
|
if (kind == .array_literal) {
|
|
count :: ast.array_element_count(&state.tree, left)
|
|
if (count != ast.array_element_count(&state.tree, right)) return false
|
|
for (0..count) |i| {
|
|
if (!try constant_equal(state, ast.array_element(&state.tree, left, i), ast.array_element(&state.tree, right, i))) return false
|
|
}
|
|
return true
|
|
}
|
|
if (kind == .record_literal) {
|
|
count :: ast.record_field_count(&state.tree, left)
|
|
if (count != ast.record_field_count(&state.tree, right)) return false
|
|
for (0..count) |i| {
|
|
a :: ast.field(&state.tree, ast.record_field(&state.tree, left, i))
|
|
b :: ast.field(&state.tree, ast.record_field(&state.tree, right, i))
|
|
if ((a.name == null) != (b.name == null)) return false
|
|
if (a.name != null and ast.name_id(&state.tree, a.name?) != ast.name_id(&state.tree, b.name?)) return false
|
|
if (!try constant_equal(state, a.value?, b.value?)) return false
|
|
}
|
|
return true
|
|
}
|
|
if (kind == .builtin_call and contextual_literal(state, left) and contextual_literal(state, right)) {
|
|
return try constant_equal(state, ast.call_arg(&state.tree, left, 0), ast.call_arg(&state.tree, right, 0))
|
|
}
|
|
if (kind == .enum_literal)
|
|
return ast.name_id(&state.tree, ast.enum_literal(&state.tree, left)) == ast.name_id(&state.tree, ast.enum_literal(&state.tree, right))
|
|
return try value_equal(state, try evaluate(state, left), try evaluate(state, right))
|
|
}
|
|
|
|
@hide:file
|
|
constant_hash proc(state @mut State, id ast.NodeId) usize ! Error {
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
hash :: enum_index(kind)
|
|
if (kind == .literal_void) return hash
|
|
if (kind == .typed_constant or kind == .union_constant) {
|
|
value :: if (kind == .union_constant) ast.union_constant(&state.tree, id) else ast.typed_constant(&state.tree, id)
|
|
return mix(mix(hash, try type_hash_context(state, value.type, true)), try constant_hash(state, value.value))
|
|
}
|
|
if (kind == .slice) {
|
|
view :: ast.slice_expr(&state.tree, id)
|
|
return mix(mix(mix(hash, try constant_hash(state, view.base)),
|
|
try constant_hash(state, view.end?)), try constant_hash(state, view.sentinel?))
|
|
}
|
|
if (kind == .array_literal) {
|
|
result := hash
|
|
for (0..ast.array_element_count(&state.tree, id)) |i|
|
|
result = mix(result, try constant_hash(state, ast.array_element(&state.tree, id, i)))
|
|
return result
|
|
}
|
|
if (kind == .record_literal) {
|
|
result := hash
|
|
for (0..ast.record_field_count(&state.tree, id)) |i| {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, id, i))
|
|
if (field.name) |name| result = mix(result, usize(ast.name_id(&state.tree, name)))
|
|
result = mix(result, try constant_hash(state, field.value?))
|
|
}
|
|
return result
|
|
}
|
|
if (kind == .builtin_call and contextual_literal(state, id))
|
|
return mix(hash, try constant_hash(state, ast.call_arg(&state.tree, id, 0)))
|
|
if (kind == .enum_literal) return mix(hash, usize(ast.name_id(&state.tree, ast.enum_literal(&state.tree, id))))
|
|
return try value_hash(state, try evaluate(state, id))
|
|
}
|
|
|
|
@hide:file
|
|
evaluate_call proc(state @mut State, call ast.NodeId) Value ! Error {
|
|
callee :: try evaluate(state, ast.call_expr(&state.tree, call).callee)
|
|
if (callee.kind != .function and callee.kind != .type) return Value{}
|
|
result_type := callee.node
|
|
function := no_node
|
|
if (callee.kind == .function) {
|
|
resolved :: binding(state, callee.node)
|
|
if (resolved.kind != .declaration) return Value{}
|
|
function = resolved.data.declaration
|
|
result_type = ast.function_decl(&state.tree, resolved.data.declaration).return_type
|
|
}
|
|
arguments := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&arguments)
|
|
for (0..ast.call_arg_count(&state.tree, call)) |i| {
|
|
argument :: ast.call_arg(&state.tree, call, i)
|
|
value := Value{}
|
|
if (function != no_node and i < ast.function_param_count(&state.tree, function)) {
|
|
annotation :: ast.param(&state.tree, ast.function_param(&state.tree, function, i)).type
|
|
value = try evaluate_expected(state, argument, annotation)
|
|
} else value = try evaluate(state, argument)
|
|
if (value.kind == .invalid) return Value{}
|
|
concrete := try materialize(state, value)
|
|
if (value.annotation != no_node) {
|
|
concrete.node = try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, value.node),
|
|
value.annotation, concrete.node)
|
|
try refresh(state)
|
|
}
|
|
try arraylist.append(&arguments, concrete.node)
|
|
}
|
|
# reuse checking, typed IR and the interpreter: compile-time arithmetic and local
|
|
# inference must have exactly the same semantics as an ordinary runtime call.
|
|
span :: ast.node_span(&state.tree, call)
|
|
invocation :: try ast.add_call(state.builder, span, callee.node, arguments.items)
|
|
try refresh(state)
|
|
return try evaluate_ir(state, invocation, result_type)
|
|
}
|
|
|
|
@hide:file
|
|
evaluate_ir proc(state @mut State, expression, result_type ast.NodeId) Value ! Error {
|
|
if (result_type == no_node) return Value{}
|
|
span :: ast.node_span(&state.tree, expression)
|
|
returned :: try ast.add_return_stmt(state.builder, span, expression)
|
|
statements [1]ast.NodeId :: [returned]
|
|
body :: try ast.add_block(state.builder, span, statements[..])
|
|
try refresh(state)
|
|
return try evaluate_ir_body(state, expression, body, result_type)
|
|
}
|
|
|
|
@hide:file
|
|
evaluate_ir_body proc(state @mut State, at, body, result_type ast.NodeId) Value ! Error {
|
|
span :: ast.node_span(&state.tree, at)
|
|
wrapper :: try ast.add_function_decl(state.builder, span, span, strpool.no_str, result_type, &[], body)
|
|
try refresh(state)
|
|
declarations := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&declarations)
|
|
try arraylist.append(&declarations, wrapper)
|
|
flags := arraylist.init(u8, state.allocator)
|
|
defer arraylist.deinit(&flags)
|
|
index := usize(0)
|
|
while (index < declarations.items.len) {
|
|
declaration :: declarations.items[index]
|
|
try expand(state, declaration)
|
|
while (flags.items.len < ast.node_count(&state.tree)) try arraylist.append(&flags, u8(0))
|
|
flags.items[usize(declaration)] = flags.items[usize(declaration)] | 2
|
|
evaluation_owned(state, declaration, flags.items)
|
|
if (!try evaluation_dependencies(state, declaration, flags.items, &declarations)) return Value{}
|
|
index += 1
|
|
}
|
|
root :: try ast.add_package(state.builder, span, declarations.items)
|
|
try refresh(state)
|
|
bindings :: try mem.alloc(sema.Binding, state.allocator, state.bindings.items.len)
|
|
memcopy!(bindings, state.bindings.items)
|
|
sema.replace_bindings(state.resolution, bindings)
|
|
tree :: ast.view(state.builder, root)
|
|
diagnostic_store := diagnostics.init(state.allocator)
|
|
defer diagnostics.deinit(&diagnostic_store)
|
|
# Resolution's existing diagnostic IDs remain valid in this temporary store.
|
|
for (state.diagnostics.list.items) |diagnostic| {
|
|
if (diagnostic.custom_message) |text| {
|
|
_ = try diagnostics.add_message(&diagnostic_store, diagnostic.code, diagnostic.span, text)
|
|
} else _ = try diagnostics.add(&diagnostic_store, diagnostic.code, diagnostic.span)
|
|
}
|
|
original_diagnostics :: diagnostics.count(&diagnostic_store)
|
|
checked := try typecheck.check(state.allocator, &tree, state.resolution, &diagnostic_store)
|
|
defer typecheck.deinit(&checked)
|
|
if (diagnostics.count(&diagnostic_store) != original_diagnostics) {
|
|
diagnostic :: diagnostic_store.list.items[original_diagnostics]
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, diagnostic.code, diagnostic.span)
|
|
return Value{}
|
|
}
|
|
functions := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&functions)
|
|
module := try lower.lower_evaluation(state.allocator, &tree, state.resolution, &checked,
|
|
wrapper, &functions, &diagnostic_store)
|
|
defer ir.deinit(&module)
|
|
outcome := try interpreter.run_evaluation(state.allocator, &module, &diagnostic_store)
|
|
defer interpreter.deinit(&outcome)
|
|
if (outcome.trap) |trap| {
|
|
if (usize(trap) < original_diagnostics) state.evaluation_diagnostic = trap
|
|
else {
|
|
diagnostic :: diagnostics.get(&diagnostic_store, trap)?
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, diagnostic.code, diagnostic.span)
|
|
}
|
|
return Value{}
|
|
}
|
|
value :: outcome.value
|
|
source_type :: typecheck.type_of(&checked, result_type)
|
|
node :: try serialize_value(state, &module, &checked, &outcome, functions.items, source_type, value, span)
|
|
if (node == no_node) {
|
|
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_value_escape, span)
|
|
return Value{}
|
|
}
|
|
if (
|
|
(ir.aggregate(value.type) and source_type.kind != .slice)
|
|
or (source_type.kind == .slice and ast.expr_kind(&state.tree, node) == .slice)
|
|
or source_type.kind == .enumeration or source_type.kind == .void
|
|
)
|
|
return Value{ kind = .aggregate, node = node, annotation = result_type }
|
|
result := try evaluate(state, node)
|
|
result.annotation = result_type
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
TypeLocal :: struct {
|
|
declaration ast.NodeId
|
|
value ast.NodeId
|
|
annotation ast.NodeId
|
|
}
|
|
|
|
@hide:file
|
|
TypeExecution :: struct {
|
|
locals arraylist.ArrayList(TypeLocal)
|
|
mapping []mut ast.NodeId
|
|
first_owned ast.NodeId
|
|
returned ast.NodeId = no_node
|
|
result_type ast.NodeId
|
|
yielded ast.NodeId = no_node
|
|
yield_type ast.NodeId = no_node
|
|
yield_depth usize = 0
|
|
cleanup_yield_depth usize = 0
|
|
cleanup_depth usize = 0
|
|
cleanup_loop_depth usize = 0
|
|
evaluate_values bool = false
|
|
pending_flow TypeFlow = .next
|
|
loop_depth usize = 0
|
|
inline_body ast.NodeId = no_node
|
|
inline_blocks ?@mut arraylist.ArrayList(ast.NodeId) = null
|
|
}
|
|
|
|
@hide:file
|
|
TypeFlow :: enum { next, returned, yielded, break, continue, invalid }
|
|
|
|
# type values cannot enter runtime IR. execute their control flow here, substituting
|
|
# owned constants into snapshots; ordinary value operations still use checked IR.
|
|
@hide:file
|
|
return_type_value proc(state @mut State, block, first_owned, result_type ast.NodeId) ast.NodeId ! Error {
|
|
# ponytail: snapshots recheck scalar IR; cap type-control work at 4096 steps
|
|
# until type values can share one persistent checked execution frame.
|
|
execution := TypeExecution{
|
|
locals = arraylist.init(state.allocator),
|
|
mapping = try mem.alloc(ast.NodeId, state.allocator, ast.node_count(&state.tree)),
|
|
first_owned = first_owned,
|
|
result_type = result_type,
|
|
evaluate_values = true,
|
|
}
|
|
defer arraylist.deinit(&execution.locals)
|
|
defer mem.free(state.allocator, execution.mapping)
|
|
mem.set(execution.mapping, no_node)
|
|
if (try execute_type_block(state, &execution, block) != .returned) return no_node
|
|
return execution.returned
|
|
}
|
|
|
|
@hide:file
|
|
evaluate_type_value_block proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
|
|
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
|
state.type_execution_depth += 1
|
|
defer state.type_execution_depth -= 1
|
|
execution := TypeExecution{
|
|
locals = arraylist.init(state.allocator),
|
|
mapping = try mem.alloc(ast.NodeId, state.allocator, ast.node_count(&state.tree)),
|
|
first_owned = ast.NodeId(0),
|
|
result_type = no_node,
|
|
evaluate_values = true,
|
|
}
|
|
defer arraylist.deinit(&execution.locals)
|
|
defer mem.free(state.allocator, execution.mapping)
|
|
mem.set(execution.mapping, no_node)
|
|
return try execute_type_value_block(state, &execution, ast.block_expr(&state.tree, expression), annotation)
|
|
}
|
|
|
|
@hide:file
|
|
execute_type_value_block proc(
|
|
state @mut State, execution @mut TypeExecution, block, annotation ast.NodeId,
|
|
) ast.NodeId ! Error {
|
|
saved_value :: execution.yielded
|
|
saved_type :: execution.yield_type
|
|
execution.yielded = no_node
|
|
execution.yield_type = annotation
|
|
execution.yield_depth += 1
|
|
defer {
|
|
execution.yielded = saved_value
|
|
execution.yield_type = saved_type
|
|
execution.yield_depth -= 1
|
|
}
|
|
flow :: try execute_type_block(state, execution, block)
|
|
if (flow == .yielded) return execution.yielded
|
|
if (flow != .next) execution.pending_flow = flow
|
|
else {
|
|
inferred :: if (annotation == no_node) try value_block_type(state, block, 0) else annotation
|
|
result :: binding(state, inferred)
|
|
if (result.kind == .builtin and result.data.builtin == .void) {
|
|
value :: try ast.add_literal(state.builder, .void, ast.node_span(&state.tree, block))
|
|
try refresh(state)
|
|
return value
|
|
}
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.missing_yield, ast.node_span(&state.tree, block))
|
|
execution.pending_flow = .invalid
|
|
}
|
|
return no_node
|
|
}
|
|
|
|
@hide:file
|
|
type_step proc(state @mut State, at ast.NodeId) bool ! Error {
|
|
if (state.type_execution_remaining == 0) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.comptime_execution_limit, ast.node_span(&state.tree, at))
|
|
return false
|
|
}
|
|
state.type_execution_remaining -= 1
|
|
return true
|
|
}
|
|
|
|
@hide:file
|
|
prepare_type_snapshot proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) void ! Error {
|
|
if (id == no_node or usize(id) >= execution.mapping.len or execution.pending_flow != .next) return
|
|
# substituted arguments belong to the caller, including nominal type identity.
|
|
if (id < execution.first_owned) {
|
|
execution.mapping[usize(id)] = id
|
|
return
|
|
}
|
|
execution.mapping[usize(id)] = no_node
|
|
resolved :: binding(state, id)
|
|
if (resolved.kind == .declaration) {
|
|
for (execution.locals.items) |local| {
|
|
if (local.declaration == resolved.data.declaration) {
|
|
execution.mapping[usize(id)] = local.value
|
|
return
|
|
}
|
|
}
|
|
}
|
|
if (execution.evaluate_values and ast.node_class(&state.tree, id) == .field) {
|
|
field :: ast.field(&state.tree, id)
|
|
if (field.type != null and field.value != null) {
|
|
annotation :: try type_snapshot(state, execution, field.type?)
|
|
if (annotation == no_node) return
|
|
value :: try type_snapshot_expected(state, execution, field.value?, annotation)
|
|
execution.mapping[usize(field.value?)] = value
|
|
if (value == no_node and execution.pending_flow == .next) execution.pending_flow = .invalid
|
|
return
|
|
}
|
|
}
|
|
if (execution.evaluate_values and ast.node_class(&state.tree, id) == .expression) {
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
if (kind == .block_expr) {
|
|
execution.mapping[usize(id)] = try execute_type_value_block(state, execution,
|
|
ast.block_expr(&state.tree, id), no_node)
|
|
if (execution.mapping[usize(id)] == no_node and execution.pending_flow == .next)
|
|
execution.pending_flow = .invalid
|
|
return
|
|
}
|
|
if (kind == .match_expr) {
|
|
execution.mapping[usize(id)] = try type_snapshot_match(state, execution, id, no_node)
|
|
if (execution.mapping[usize(id)] == no_node and execution.pending_flow == .next)
|
|
execution.pending_flow = .invalid
|
|
return
|
|
}
|
|
if (kind == .binary) {
|
|
view :: ast.binary_expr(&state.tree, id)
|
|
if (view.operator == .logical_and or view.operator == .logical_or) {
|
|
boolean :: try builtin(state, id, .bool)
|
|
left :: try type_snapshot_expected(state, execution, view.left, boolean)
|
|
if (left == no_node) return
|
|
value :: try evaluate_ir(state, left, boolean)
|
|
if (value.kind != .boolean) {
|
|
execution.pending_flow = .invalid
|
|
return
|
|
}
|
|
right := no_node
|
|
if (value.boolean == (view.operator == .logical_or)) {
|
|
saved :: execution.evaluate_values
|
|
execution.evaluate_values = false
|
|
right = try type_snapshot(state, execution, view.right)
|
|
if (right != no_node and !try skip_type_value_blocks(state, right)) {
|
|
execution.pending_flow = .invalid
|
|
return
|
|
}
|
|
execution.evaluate_values = saved
|
|
} else right = try type_snapshot_expected(state, execution, view.right, boolean)
|
|
if (right == no_node and execution.pending_flow == .next) {
|
|
execution.pending_flow = .invalid
|
|
return
|
|
}
|
|
# retain the operator and skipped operand for ordinary type validation.
|
|
execution.mapping[usize(view.left)] = left
|
|
execution.mapping[usize(view.right)] = right
|
|
return
|
|
}
|
|
}
|
|
if (kind == .catch_expr) {
|
|
execution.mapping[usize(id)] = try type_snapshot_catch(state, execution, id, no_node)
|
|
if (execution.mapping[usize(id)] == no_node and execution.pending_flow == .next)
|
|
execution.pending_flow = .invalid
|
|
return
|
|
}
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i|
|
|
try prepare_type_snapshot(state, execution, ast.child(&state.tree, id, i))
|
|
}
|
|
|
|
@hide:file
|
|
bind_type_snapshot proc(state @mut State, execution @TypeExecution, id, start ast.NodeId) void ! Error {
|
|
if (usize(id) >= execution.mapping.len) return
|
|
target :: execution.mapping[usize(id)]
|
|
if (target == no_node or target < start) return
|
|
resolved := binding(state, id)
|
|
if (resolved.kind == .declaration) {
|
|
declaration :: resolved.data.declaration
|
|
if (usize(declaration) < execution.mapping.len and execution.mapping[usize(declaration)] != no_node)
|
|
resolved.data.declaration = execution.mapping[usize(declaration)]
|
|
}
|
|
state.bindings.items[usize(target)] = resolved
|
|
if (hashmap.get(&state.capture_types, id)) |annotation| {
|
|
hashmap.put(&state.capture_types, target, annotation) catch |err| {
|
|
match err {
|
|
.key_exists: {}
|
|
else: return err
|
|
}
|
|
}
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i|
|
|
try bind_type_snapshot(state, execution, ast.child(&state.tree, id, i), start)
|
|
}
|
|
|
|
@hide:file
|
|
skip_type_value_blocks proc(state @mut State, id ast.NodeId) bool ! Error {
|
|
if (ast.node_class(&state.tree, id) == .expression and ast.expr_kind(&state.tree, id) == .block_expr) {
|
|
annotation :: try expression_type(state, id, 0)
|
|
if (annotation == no_node) return false
|
|
span :: ast.node_span(&state.tree, id)
|
|
# skipped type-control bodies retain only their result type for checked IR.
|
|
placeholder :: try ast.add_literal(state.builder, .undefined, span)
|
|
local :: try ast.add_statement_decl(state.builder, span, span, strpool.no_str,
|
|
annotation, .variable, placeholder)
|
|
try refresh(state)
|
|
yielded :: try ast.add_yield_stmt(state.builder, span, try reference(state, id, local))
|
|
statements [2]ast.NodeId :: [local, yielded]
|
|
body :: try ast.add_block(state.builder, span, statements[..])
|
|
replacement :: try ast.add_block_expr(state.builder, span, body)
|
|
ast.replace_node_from_source(state.builder, id, replacement)
|
|
try refresh(state)
|
|
return true
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i|
|
|
if (!try skip_type_value_blocks(state, ast.child(&state.tree, id, i))) return false
|
|
return true
|
|
}
|
|
|
|
@hide:file
|
|
type_snapshot proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) ast.NodeId ! Error {
|
|
if (id == no_node or execution.pending_flow != .next) return no_node
|
|
if (execution.mapping.len < ast.node_count(&state.tree)) {
|
|
previous :: execution.mapping.len
|
|
execution.mapping = try mem.realloc(state.allocator, execution.mapping, ast.node_count(&state.tree))
|
|
mem.set(execution.mapping[previous..], no_node)
|
|
}
|
|
try prepare_type_snapshot(state, execution, id)
|
|
if (execution.pending_flow != .next) return no_node
|
|
start :: ast.NodeId(ast.node_count(&state.tree))
|
|
result :: try ast.clone_subtree(state.builder, id, execution.mapping)
|
|
try refresh(state)
|
|
try bind_type_snapshot(state, execution, id, start)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
type_snapshot_expected proc(
|
|
state @mut State, execution @mut TypeExecution, id, annotation ast.NodeId,
|
|
) ast.NodeId ! Error {
|
|
if (id == no_node or execution.pending_flow != .next) return no_node
|
|
if (execution.evaluate_values and ast.expr_kind(&state.tree, id) == .block_expr)
|
|
return try execute_type_value_block(state, execution, ast.block_expr(&state.tree, id), annotation)
|
|
if (execution.evaluate_values and ast.expr_kind(&state.tree, id) == .match_expr)
|
|
return try type_snapshot_match(state, execution, id, annotation)
|
|
if (execution.evaluate_values and ast.expr_kind(&state.tree, id) == .catch_expr)
|
|
return try type_snapshot_catch(state, execution, id, annotation)
|
|
return try type_snapshot(state, execution, id)
|
|
}
|
|
|
|
@hide:file
|
|
type_snapshot_catch proc(
|
|
state @mut State, execution @mut TypeExecution, id, annotation ast.NodeId,
|
|
) ast.NodeId ! Error {
|
|
view :: ast.catch_expr(&state.tree, id)
|
|
operand :: try type_snapshot(state, execution, view.operand)
|
|
if (operand == no_node) return no_node
|
|
try expand_expected(state, operand, annotation)
|
|
operand_type :: try expression_type(state, operand, 0)
|
|
if (operand_type == no_node or ast.expr_kind(&state.tree, operand_type) != .fallible_type) return no_node
|
|
channels :: ast.binary_expr(&state.tree, operand_type)
|
|
owned :: try type_execution_value(state, operand, operand_type)
|
|
if (owned == no_node) return no_node
|
|
span :: ast.node_span(&state.tree, id)
|
|
boolean :: try builtin(state, id, .bool)
|
|
yes :: try ast.add_boolean(state.builder, span, true)
|
|
no :: try ast.add_boolean(state.builder, span, false)
|
|
failed :: try ast.add_return_stmt(state.builder, span, yes)
|
|
failure_statements [1]ast.NodeId :: [failed]
|
|
handler :: try ast.add_block(state.builder, span, failure_statements[..])
|
|
caught :: try ast.add_catch_expr(state.builder, span, owned, null, handler, true)
|
|
discarded :: try ast.add_expression_stmt(state.builder, caught)
|
|
success :: try ast.add_return_stmt(state.builder, span, no)
|
|
statements [2]ast.NodeId :: [discarded, success]
|
|
body :: try ast.add_block(state.builder, span, statements[..])
|
|
try refresh(state)
|
|
guard :: try evaluate_ir_body(state, id, body, boolean)
|
|
if (guard.kind != .boolean) return no_node
|
|
payload :: ast.typed_constant(&state.tree, owned).value
|
|
if (!guard.boolean) return try type_execution_value(state, payload, channels.left)
|
|
saved :: execution.locals.items.len
|
|
defer execution.locals.items = execution.locals.items[..saved]
|
|
if (view.capture) |capture| {
|
|
error :: try type_execution_value(state, payload, channels.right)
|
|
if (error == no_node) return no_node
|
|
try arraylist.append(&execution.locals, TypeLocal{
|
|
declaration = capture, value = error, annotation = channels.right,
|
|
})
|
|
}
|
|
result_type := channels.left
|
|
if (annotation != no_node and !is_type(state, annotation)) {
|
|
contextual :: try canonical_type(state, annotation)
|
|
if (contextual != no_node) {
|
|
kind :: ast.expr_kind(&state.tree, contextual)
|
|
if (kind == .optional_type
|
|
and try same_type(state, ast.type_operand(&state.tree, contextual), channels.left))
|
|
result_type = annotation
|
|
if (kind == .fallible_type
|
|
and try same_type(state, ast.binary_expr(&state.tree, contextual).left, channels.left))
|
|
result_type = annotation
|
|
}
|
|
}
|
|
if (view.handler_is_block)
|
|
return try execute_type_value_block(state, execution, view.handler, result_type)
|
|
selected :: try type_snapshot_expected(state, execution, view.handler, result_type)
|
|
return try type_execution_value(state, selected, result_type)
|
|
}
|
|
|
|
@hide:file
|
|
type_dispatch_snapshot proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) ast.NodeId ! Error {
|
|
subject :: try type_snapshot(state, execution, ast.match_expr(&state.tree, id).subject)
|
|
if (subject == no_node) return no_node
|
|
saved :: execution.evaluate_values
|
|
execution.evaluate_values = false
|
|
defer execution.evaluate_values = saved
|
|
snapshot :: try type_snapshot(state, execution, id)
|
|
if (snapshot == no_node) return no_node
|
|
arms := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&arms)
|
|
for (0..ast.match_arm_count(&state.tree, snapshot)) |i|
|
|
try arraylist.append(&arms, ast.match_arm(&state.tree, snapshot, i))
|
|
optional_capture :: ast.match_expr(&state.tree, id).optional_capture
|
|
result :: try ast.add_match(state.builder, ast.node_span(&state.tree, id), subject, arms.items)
|
|
if (optional_capture) ast.set_match_optional(state.builder, result)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
type_snapshot_match proc(
|
|
state @mut State, execution @mut TypeExecution, id, annotation ast.NodeId,
|
|
) ast.NodeId ! Error {
|
|
snapshot :: try type_dispatch_snapshot(state, execution, id)
|
|
if (snapshot == no_node) return no_node
|
|
index :: try type_match_arm(state, snapshot)
|
|
if (index == no_index) return no_node
|
|
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, id, index))
|
|
saved :: execution.locals.items.len
|
|
defer execution.locals.items = execution.locals.items[..saved]
|
|
if (arm.capture) |capture| {
|
|
value :: try type_match_capture(state, snapshot)
|
|
if (value == no_node) return no_node
|
|
constant :: ast.typed_constant(&state.tree, value)
|
|
payload :: if (is_type(state, constant.type)) constant.value else value
|
|
try arraylist.append(&execution.locals, TypeLocal{
|
|
declaration = capture,
|
|
value = payload,
|
|
annotation = constant.type,
|
|
})
|
|
}
|
|
if (!arm.expression) return try execute_type_value_block(state, execution, arm.body, annotation)
|
|
return try type_snapshot_expected(state, execution, arm.body, annotation)
|
|
}
|
|
|
|
@hide:file
|
|
nominal_type_value proc(state @State, id ast.NodeId) bool {
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
return kind == .record_type or kind == .union_type or kind == .enum_type or kind == .distinct_type
|
|
}
|
|
|
|
# transfers targeting an inline loop must be selected before checking runtime
|
|
# bodies. nested loops own their transfers and are elaborated independently.
|
|
@hide:file
|
|
inline_has_transfer proc(state @State, id ast.NodeId) bool {
|
|
class :: ast.node_class(&state.tree, id)
|
|
if (class == .function) return false
|
|
if (class == .statement) {
|
|
kind :: ast.stmt_kind(&state.tree, id)
|
|
if (kind == .for_stmt or kind == .while_stmt) return false
|
|
if (kind == .break_stmt or kind == .continue_stmt) return true
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i| {
|
|
if (inline_has_transfer(state, ast.child(&state.tree, id, i))) return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
inline_statement proc(state @mut State, id ast.NodeId) TypeFlow ! Error {
|
|
kind :: ast.stmt_kind(&state.tree, id)
|
|
if (kind == .break_stmt) return .break
|
|
if (kind == .continue_stmt) return .continue
|
|
if (kind == .unreachable_stmt) return .returned
|
|
if (kind == .yield_stmt or kind == .return_stmt) {
|
|
try expand(state, id)
|
|
return if (kind == .yield_stmt) TypeFlow.yielded else TypeFlow.returned
|
|
}
|
|
if (kind == .block_stmt)
|
|
return try inline_block(state, ast.statement_block(&state.tree, id))
|
|
selected := no_node
|
|
value_source := false
|
|
if (kind == .if_stmt) {
|
|
conditional :: ast.if_stmt(&state.tree, id)
|
|
explicit :: ast.expr_kind(&state.tree, conditional.condition) == .comptime_expr
|
|
condition :: if (explicit) ast.comptime_operand(&state.tree, conditional.condition) else conditional.condition
|
|
try expand(state, condition)
|
|
if (conditional.capture == null and (explicit or branch_constant(state, condition, 0))) {
|
|
value :: try evaluate_ir(state, condition, try builtin(state, condition, .bool))
|
|
if (value.kind != .boolean) return .invalid
|
|
if (value.boolean) selected = conditional.then_block
|
|
else if (conditional.else_block) |other| selected = other
|
|
else selected = try ast.add_block(state.builder, conditional.span, &[])
|
|
}
|
|
}
|
|
if (kind == .expression) {
|
|
expression :: ast.statement_expr(&state.tree, id)
|
|
if (ast.expr_kind(&state.tree, expression) == .match_expr) {
|
|
subject :: ast.match_expr(&state.tree, expression).subject
|
|
explicit :: ast.expr_kind(&state.tree, subject) == .comptime_expr
|
|
try expand(state, subject)
|
|
if (explicit or branch_constant(state, subject, 0)) {
|
|
index :: try type_match_arm(state, expression)
|
|
if (index == no_index) return .invalid
|
|
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, expression, index))
|
|
body :: try type_match_body(state, expression, arm)
|
|
if (body == no_node) return .invalid
|
|
if (!arm.expression) {
|
|
selected = body
|
|
value_source = true
|
|
} else {
|
|
statement :: try ast.add_expression_stmt(state.builder, body)
|
|
statements [1]ast.NodeId :: [statement]
|
|
selected = try ast.add_block(state.builder, ast.node_span(&state.tree, id), statements[..])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (selected != no_node) {
|
|
try refresh(state)
|
|
flow := try inline_block(state, selected)
|
|
replacement := no_node
|
|
if (value_source) {
|
|
value :: try ast.add_block_expr(state.builder, ast.node_span(&state.tree, selected), selected)
|
|
replacement = try ast.add_expression_stmt(state.builder, value)
|
|
if (flow == .yielded) flow = .next
|
|
} else replacement = try ast.add_block_stmt(state.builder, selected)
|
|
try replace(state, id, replacement)
|
|
try refresh(state)
|
|
return flow
|
|
}
|
|
if (inline_has_transfer(state, id)) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.inline_loop_control, ast.node_span(&state.tree, id))
|
|
return .invalid
|
|
}
|
|
try expand(state, id)
|
|
return .next
|
|
}
|
|
|
|
@hide:file
|
|
inline_block proc(state @mut State, block ast.NodeId) TypeFlow ! Error {
|
|
statements := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&statements)
|
|
flow := TypeFlow.next
|
|
for (0..ast.block_statement_count(&state.tree, block)) |i| {
|
|
statement :: ast.block_statement(&state.tree, block, i)
|
|
if (!try type_step(state, statement)) return .invalid
|
|
flow = try inline_statement(state, statement)
|
|
if (flow == .invalid) return flow
|
|
kind :: ast.stmt_kind(&state.tree, statement)
|
|
if (kind != .break_stmt and kind != .continue_stmt) try arraylist.append(&statements, statement)
|
|
if (flow != .next) break
|
|
}
|
|
replacement :: try ast.add_block(state.builder, ast.node_span(&state.tree, block), statements.items)
|
|
try replace(state, block, replacement)
|
|
try refresh(state)
|
|
return flow
|
|
}
|
|
|
|
@hide:file
|
|
expand_inline_for proc(state @mut State, id ast.NodeId) void ! Error {
|
|
loop :: ast.for_stmt(&state.tree, id)
|
|
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
|
state.type_execution_depth += 1
|
|
defer state.type_execution_depth -= 1
|
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
|
blocks := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&blocks)
|
|
execution := TypeExecution{
|
|
locals = arraylist.init(state.allocator),
|
|
mapping = try mem.alloc(ast.NodeId, state.allocator, ast.node_count(&state.tree)),
|
|
first_owned = ast.NodeId(0), result_type = no_node,
|
|
inline_body = loop.body, inline_blocks = &blocks,
|
|
}
|
|
defer arraylist.deinit(&execution.locals)
|
|
defer mem.free(state.allocator, execution.mapping)
|
|
mem.set(execution.mapping, no_node)
|
|
for (0..usize(2)) |i| {
|
|
capture :: if (i == 0) loop.capture else loop.index
|
|
if (capture) |node| {
|
|
resolved :: binding(state, node)
|
|
if (resolved.kind == .invalid) state.evaluation_diagnostic = resolved.data.diagnostic
|
|
}
|
|
}
|
|
if (loop.pointer_capture)
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_value_escape, loop.span)
|
|
flow := TypeFlow.invalid
|
|
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
|
|
flow = try execute_type_for(state, &execution, id)
|
|
if ((flow != .next and flow != .yielded and flow != .returned)
|
|
or state.evaluation_diagnostic != diagnostics.no_diagnostic) {
|
|
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_argument_required, loop.span)
|
|
invalid :: try ast.add_invalid_stmt(state.builder, loop.span, state.evaluation_diagnostic)
|
|
try replace(state, id, invalid)
|
|
try refresh(state)
|
|
return
|
|
}
|
|
statements := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&statements)
|
|
for (blocks.items) |block| {
|
|
statement :: try ast.add_block_stmt(state.builder, block)
|
|
try arraylist.append(&statements, statement)
|
|
}
|
|
block :: try ast.add_block(state.builder, loop.span, statements.items)
|
|
replacement :: try ast.add_block_stmt(state.builder, block)
|
|
try replace(state, id, replacement)
|
|
try refresh(state)
|
|
}
|
|
|
|
# select with ordinary checked IR so patterns, overlap and exhaustiveness have
|
|
# the same rules as runtime matching, without checking unexecuted type bodies.
|
|
@hide:file
|
|
type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
|
|
try expand_inline_match(state, id, no_node)
|
|
if (ast.expr_kind(&state.tree, id) != .match_expr) return no_index
|
|
optional_capture :: ast.match_expr(&state.tree, id).optional_capture
|
|
if (!try expand_type_matches(state, ast.match_expr(&state.tree, id).subject)) return no_index
|
|
try expand(state, ast.match_expr(&state.tree, id).subject)
|
|
arms := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&arms)
|
|
patterns := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&patterns)
|
|
subject :: ast.match_expr(&state.tree, id).subject
|
|
annotation :: try expression_type(state, subject, 0)
|
|
metadata :: type_record_storage(state, annotation, 0)
|
|
dispatch := subject
|
|
if (metadata) {
|
|
value :: try evaluate_expected(state, subject, annotation)
|
|
if (value.kind != .aggregate or ast.expr_kind(&state.tree, value.node) != .record_literal) return no_index
|
|
active :: ast.field(&state.tree, ast.record_field(&state.tree, value.node, 0))
|
|
span :: ast.node_span(&state.tree, subject)
|
|
tags :: try metadata_tags(state, annotation)
|
|
tag :: try ast.add_enum_literal(state.builder, span, active.name?)
|
|
dispatch = try ast.add_typed_constant(state.builder, span, tags, tag)
|
|
try refresh(state)
|
|
}
|
|
for (0..ast.match_arm_count(&state.tree, id)) |i| {
|
|
arm_id :: ast.match_arm(&state.tree, id, i)
|
|
arm :: ast.match_arm_view(&state.tree, arm_id)
|
|
# selector bodies omit source capture uses, but must retain their resolver failures.
|
|
if (arm.capture) |capture| {
|
|
resolved :: binding(state, capture)
|
|
if (resolved.kind == .invalid) {
|
|
state.evaluation_diagnostic = resolved.data.diagnostic
|
|
return no_index
|
|
}
|
|
}
|
|
patterns.items = patterns.items[..0]
|
|
for (0..ast.match_pattern_count(&state.tree, arm_id)) |j| {
|
|
pattern :: ast.match_pattern(&state.tree, arm_id, j)
|
|
if (!try expand_type_matches(state, pattern)) return no_index
|
|
try expand(state, pattern)
|
|
try arraylist.append(&patterns, pattern)
|
|
}
|
|
if (metadata and arm.capture != null) {
|
|
expected := no_node
|
|
valid := !arm.catch_all and patterns.items.len > 0
|
|
for (patterns.items) |pattern| {
|
|
if (ast.expr_kind(&state.tree, pattern) != .enum_literal) {
|
|
valid = false
|
|
continue
|
|
}
|
|
slot :: type_record_slot(state, annotation, ast.enum_literal(&state.tree, pattern))
|
|
if (slot == no_index) {
|
|
valid = false
|
|
continue
|
|
}
|
|
payload :: ast.field(&state.tree, ast.record_field(&state.tree, annotation, slot)).type?
|
|
if (binding(state, payload).kind == .builtin and binding(state, payload).data.builtin == .void)
|
|
valid = false
|
|
if (expected != no_node and !try same_type(state, expected, payload)) valid = false
|
|
expected = payload
|
|
}
|
|
if (!valid) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_match_capture, ast.node_span(&state.tree, arm_id))
|
|
return no_index
|
|
}
|
|
}
|
|
span :: ast.node_span(&state.tree, arm_id)
|
|
index :: try ast.add_integer(state.builder, span, u64(i))
|
|
capture :: if (metadata) null else arm.capture
|
|
selector :: try ast.add_match_arm(state.builder, span, patterns.items,
|
|
capture, index, true, arm.catch_all)
|
|
try refresh(state)
|
|
try arraylist.append(&arms, selector)
|
|
}
|
|
selector :: try ast.add_match(state.builder, ast.node_span(&state.tree, id),
|
|
dispatch, arms.items)
|
|
if (optional_capture)
|
|
ast.set_match_optional(state.builder, selector)
|
|
try refresh(state)
|
|
# selector arms contain only indices. do not specialize this synthetic match
|
|
# again while its checked evaluation expands the wrapper.
|
|
state.expanded.items[usize(selector)] = 1
|
|
selected :: try evaluate_ir(state, selector, try builtin(state, id, .usize))
|
|
if (selected.kind != .integer) return no_index
|
|
return usize(selected.magnitude)
|
|
}
|
|
|
|
# match checking has already validated every capture. copy the active payload
|
|
# out of owned constant storage, never an address in the temporary interpreter.
|
|
@hide:file
|
|
type_match_capture proc(state @mut State, id ast.NodeId) ast.NodeId ! Error {
|
|
subject :: ast.match_expr(&state.tree, id).subject
|
|
if (ast.match_expr(&state.tree, id).optional_capture) {
|
|
unwrapped :: try ast.add_unary(state.builder,
|
|
ast.node_span(&state.tree, subject), .unwrap, subject)
|
|
try refresh(state)
|
|
child :: try expression_type(state, unwrapped, 0)
|
|
return try type_execution_value(state, unwrapped, child)
|
|
}
|
|
annotation :: try expression_type(state, subject, 0)
|
|
value :: if (type_record_storage(state, annotation, 0))
|
|
try evaluate_expected(state, subject, annotation) else try evaluate_ir(state, subject, annotation)
|
|
if (value.kind != .aggregate or ast.expr_kind(&state.tree, value.node) != .record_literal) return no_node
|
|
payload :: ast.field(&state.tree, ast.record_field(&state.tree, value.node, 0))
|
|
shape :: try inline_match_shape(state, annotation)
|
|
if (shape == no_node) return no_node
|
|
for (0..ast.record_field_count(&state.tree, shape)) |i| {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, shape, i))
|
|
if (
|
|
field.name != null and payload.name != null
|
|
and ast.name_id(&state.tree, field.name?) == ast.name_id(&state.tree, payload.name?)
|
|
) {
|
|
if (
|
|
ast.expr_kind(&state.tree, payload.value?) == .typed_constant
|
|
and try same_type(state, field.type?, ast.typed_constant(&state.tree, payload.value?).type)
|
|
) return payload.value?
|
|
result :: try ast.add_typed_constant(state.builder,
|
|
ast.node_span(&state.tree, subject), field.type?, payload.value?)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
}
|
|
return no_node
|
|
}
|
|
|
|
@hide:file
|
|
type_match_body proc(state @mut State, id ast.NodeId, arm ast.MatchArmView) ast.NodeId ! Error {
|
|
if (arm.capture == null) return arm.body
|
|
value :: try type_match_capture(state, id)
|
|
if (value == no_node) return no_node
|
|
snapshot :: ast.typed_constant(&state.tree, value)
|
|
execution := TypeExecution{
|
|
locals = arraylist.init(state.allocator),
|
|
mapping = try mem.alloc(ast.NodeId, state.allocator, ast.node_count(&state.tree)),
|
|
first_owned = ast.NodeId(0),
|
|
result_type = no_node,
|
|
}
|
|
defer arraylist.deinit(&execution.locals)
|
|
defer mem.free(state.allocator, execution.mapping)
|
|
mem.set(execution.mapping, no_node)
|
|
captured :: if (is_type(state, snapshot.type)) snapshot.value else value
|
|
try arraylist.append(&execution.locals, TypeLocal{
|
|
declaration = arm.capture?, value = captured,
|
|
annotation = snapshot.type,
|
|
})
|
|
return try type_snapshot(state, &execution, arm.body)
|
|
}
|
|
|
|
@hide:file
|
|
type_value_expression proc(state @mut State, input ast.NodeId) ast.NodeId ! Error {
|
|
if (input == no_node) return no_node
|
|
expression := input
|
|
while (ast.expr_kind(&state.tree, expression) == .match_expr) {
|
|
if (!try type_step(state, expression)) return no_node
|
|
index :: try type_match_arm(state, expression)
|
|
if (index == no_index) return no_node
|
|
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, expression, index))
|
|
expression = try type_match_body(state, expression, arm)
|
|
if (!arm.expression and expression != no_node) {
|
|
expression = try ast.add_block_expr(state.builder, ast.node_span(&state.tree, expression), expression)
|
|
try refresh(state)
|
|
}
|
|
if (expression == no_node) return no_node
|
|
}
|
|
if (expression != no_node and ast.expr_kind(&state.tree, expression) == .block_expr) {
|
|
annotation :: try value_block_type(state, ast.block_expr(&state.tree, expression), 0)
|
|
if (annotation != no_node and (is_type(state, annotation) or type_record_storage(state, annotation, 0)))
|
|
return try evaluate_type_value_block(state, expression, annotation)
|
|
}
|
|
return expression
|
|
}
|
|
|
|
# normalize selected value sources before ordinary expansion can visit their bodies.
|
|
# nominal results enter here after their instance declaration is cached.
|
|
@hide:file
|
|
expand_type_matches proc(state @mut State, id ast.NodeId) bool ! Error {
|
|
if (id == no_node) return false
|
|
if (ast.node_class(&state.tree, id) == .expression and ast.expr_kind(&state.tree, id) == .match_expr) {
|
|
selected :: try type_value_expression(state, id)
|
|
if (selected == no_node) return false
|
|
resolved :: binding(state, selected)
|
|
try remember(state, id)
|
|
ast.replace_node_from_source(state.builder, id, selected)
|
|
try refresh(state)
|
|
state.bindings.items[usize(id)] = resolved
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i| {
|
|
if (!try expand_type_matches(state, ast.child(&state.tree, id, i))) return false
|
|
}
|
|
return try type_array_projection(state, id)
|
|
}
|
|
|
|
@hide:file
|
|
type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error {
|
|
if (ast.node_class(&state.tree, id) != .expression) return true
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
if (kind == .index or kind == .selection) {
|
|
base :: if (kind == .index) ast.index_expr(&state.tree, id).base else ast.selection_expr(&state.tree, id).base
|
|
base_binding :: binding(state, base)
|
|
if (base_binding.kind == .invalid) {
|
|
state.evaluation_diagnostic = base_binding.data.diagnostic
|
|
return false
|
|
}
|
|
if (ast.expr_kind(&state.tree, base) != .typed_constant) return true
|
|
array :: ast.typed_constant(&state.tree, base)
|
|
type :: try canonical_type(state, array.type)
|
|
record :: (type != no_node and type_record_storage(state, type, 0)
|
|
and (ast.expr_kind(&state.tree, type) == .record_type
|
|
or ast.expr_kind(&state.tree, type) == .union_type))
|
|
if (
|
|
!record and !try execution_array_storage(state, array.type)
|
|
and !try metadata_sequence(state, array.type)
|
|
) return true
|
|
selected := no_node
|
|
if (record) {
|
|
if (kind != .selection) return true
|
|
member :: ast.selection_expr(&state.tree, id).member
|
|
slot :: type_record_slot(state, type, member)
|
|
if (slot == no_index) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.unknown_field, ast.node_span(&state.tree, member))
|
|
return false
|
|
}
|
|
if (ast.expr_kind(&state.tree, type) == .union_type) {
|
|
active :: ast.field(&state.tree, ast.record_field(&state.tree, array.value, 0))
|
|
if (active.name == null or ast.name_id(&state.tree, active.name?) != ast.name_id(&state.tree, member)) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.inactive_union_field, ast.node_span(&state.tree, id))
|
|
return false
|
|
}
|
|
selected = active.value?
|
|
} else selected = ast.field(&state.tree, ast.record_field(&state.tree, array.value, slot)).value?
|
|
} else if (kind == .index) {
|
|
index :: try type_array_index(state, ast.index_expr(&state.tree, id).index,
|
|
ast.array_element_count(&state.tree, array.value))
|
|
if (index == no_index) return false
|
|
selected = ast.array_element(&state.tree, array.value, index)
|
|
if (ast.expr_kind(&state.tree, selected) == .literal_undefined) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.read_before_initialization, ast.node_span(&state.tree, id))
|
|
return false
|
|
}
|
|
if (!type_record_storage(state, array.type, 0)
|
|
and ast.expr_kind(&state.tree, selected) != .typed_constant) {
|
|
descriptor :: ast.array_type(&state.tree, try canonical_type(state, array.type))
|
|
selected = try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, id),
|
|
descriptor.element, selected)
|
|
try refresh(state)
|
|
}
|
|
} else {
|
|
member :: ast.selection_expr(&state.tree, id).member
|
|
if (!type_record_storage(state, array.type, 0)
|
|
and !mem.eql(ast.literal_text(&state.tree, member), "len")) return true
|
|
if (!mem.eql(ast.literal_text(&state.tree, member), "len")) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.unknown_field, ast.node_span(&state.tree, member))
|
|
return false
|
|
}
|
|
count :: try ast.add_integer(state.builder, ast.node_span(&state.tree, id),
|
|
u64(ast.array_element_count(&state.tree, array.value)))
|
|
selected = try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, id),
|
|
try builtin(state, id, .usize), count)
|
|
try refresh(state)
|
|
}
|
|
# copying a nominal definition into the projection would create a new
|
|
# identity on every read. retain its original node behind a type alias.
|
|
if (nominal_type_value(state, selected)) {
|
|
selected = try ast.add_type_operand(state.builder, ast.node_span(&state.tree, id), selected, true)
|
|
try refresh(state)
|
|
}
|
|
resolved :: binding(state, selected)
|
|
try remember(state, id)
|
|
ast.replace_node_from_source(state.builder, id, selected)
|
|
try refresh(state)
|
|
state.bindings.items[usize(id)] = resolved
|
|
}
|
|
return true
|
|
}
|
|
|
|
# type-control arrays are persistent AST snapshots, including uninitialized leaves.
|
|
# only fully initialized ordinary values may enter checked runtime IR.
|
|
@hide:file
|
|
execution_array_storage proc(state @mut State, annotation ast.NodeId) bool ! Error {
|
|
current :: try canonical_type(state, annotation)
|
|
return current != no_node and ast.expr_kind(&state.tree, current) == .array_type
|
|
}
|
|
|
|
@hide:file
|
|
metadata_sequence proc(state @mut State, annotation ast.NodeId) bool ! Error {
|
|
type :: try canonical_type(state, annotation)
|
|
return (type != no_node and ast.expr_kind(&state.tree, type) == .slice_type
|
|
and type_record_storage(state, type, 0))
|
|
}
|
|
|
|
@hide:file
|
|
type_record_storage proc(state @State, annotation ast.NodeId, depth usize) bool {
|
|
return type_storage_annotation(&state.tree, state.bindings.items, annotation, depth)
|
|
}
|
|
|
|
@hide:file
|
|
type_record_slot proc(state @State, record, member ast.NodeId) usize {
|
|
if (ast.expr_kind(&state.tree, member) != .literal_int) {
|
|
for (0..ast.record_field_count(&state.tree, record)) |i| {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, record, i))
|
|
if (field.name != null and ast.name_id(&state.tree, field.name?) == ast.name_id(&state.tree, member))
|
|
return i
|
|
}
|
|
return no_index
|
|
}
|
|
ordinal := usize(0)
|
|
for (ast.literal_text(&state.tree, member)) |byte| {
|
|
if (byte < '0' or byte > '9') return no_index
|
|
digit :: usize(byte - '0')
|
|
if (ordinal > divtrunc!(maxval!(usize) - digit, usize(10))) return no_index
|
|
ordinal = ordinal * 10 + digit
|
|
}
|
|
if (ordinal >= ast.record_field_count(&state.tree, record)) return no_index
|
|
if (ast.field(&state.tree, ast.record_field(&state.tree, record, ordinal)).name != null) return no_index
|
|
return ordinal
|
|
}
|
|
|
|
@hide:file
|
|
type_union_value proc(state @mut State, expression, type ast.NodeId) ast.NodeId ! Error {
|
|
if (ast.expr_kind(&state.tree, expression) == .typed_constant) {
|
|
value :: ast.typed_constant(&state.tree, expression)
|
|
if (try same_type(state, type, value.type)) return expression
|
|
}
|
|
name := no_node
|
|
input := no_node
|
|
kind :: ast.expr_kind(&state.tree, expression)
|
|
if (kind == .enum_literal) name = ast.enum_literal(&state.tree, expression)
|
|
else if (kind == .record_literal and ast.record_field_count(&state.tree, expression) == 1) {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, expression, 0))
|
|
if (field.name) |member| name = member
|
|
if (field.value) |value| input = value
|
|
}
|
|
slot :: if (name != no_node) type_record_slot(state, type, name) else no_index
|
|
if (slot == no_index) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.union_initializer_count, ast.node_span(&state.tree, expression))
|
|
return no_node
|
|
}
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, type, slot))
|
|
payload_type :: try canonical_type(state, field.type?)
|
|
empty :: (binding(state, payload_type).kind == .builtin
|
|
and binding(state, payload_type).data.builtin == .void)
|
|
span :: ast.node_span(&state.tree, expression)
|
|
if (input == no_node and empty) input = try ast.add_literal(state.builder, .void, span)
|
|
if (input == no_node) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .missing_field, span)
|
|
return no_node
|
|
}
|
|
try refresh(state)
|
|
value :: try type_execution_value(state, input, field.type?)
|
|
if (value == no_node) return no_node
|
|
member :: try ast.add_field(state.builder, span, field.name, null, value)
|
|
fields [1]ast.NodeId :: [member]
|
|
literal :: try ast.add_record_literal(state.builder, span, null, fields[..])
|
|
result :: try ast.add_typed_constant(state.builder, span, type, literal)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
# keep ordinary field/default diagnostics even though the enclosing metadata record
|
|
# has no runtime layout. type leaves are checked here; value leaves use the checker.
|
|
@hide:file
|
|
type_record_check proc(state @mut State, annotation ast.NodeId) bool ! Error {
|
|
if (is_type(state, annotation)) return true
|
|
type :: try canonical_type(state, annotation)
|
|
if (type == no_node) return false
|
|
if (ast.expr_kind(&state.tree, type) == .slice_type)
|
|
return try type_record_check(state, ast.slice_type(&state.tree, type).element)
|
|
if (ast.expr_kind(&state.tree, type) == .array_type) {
|
|
if (!try type_step(state, type)) return false
|
|
descriptor :: ast.array_type(&state.tree, type)
|
|
if (descriptor.count) |count| {
|
|
value :: try evaluate(state, count)
|
|
if (value.kind != .integer or value.negative) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_array_length, ast.node_span(&state.tree, count))
|
|
return false
|
|
}
|
|
}
|
|
return try type_record_check(state, descriptor.element)
|
|
}
|
|
for (0..ast.record_field_count(&state.tree, type)) |i| {
|
|
field_id :: ast.record_field(&state.tree, type, i)
|
|
field :: ast.field(&state.tree, field_id)
|
|
if (!try type_step(state, field_id)) return false
|
|
if (field.name != null and type_record_slot(state, type, field.name?) != i) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .duplicate_field, field.span)
|
|
return false
|
|
}
|
|
if (ast.expr_kind(&state.tree, type) == .union_type) {
|
|
payload :: try canonical_type(state, field.type?)
|
|
if (
|
|
payload != no_node and binding(state, payload).kind == .builtin
|
|
and binding(state, payload).data.builtin == .void
|
|
) continue
|
|
}
|
|
if (type_record_storage(state, field.type?, 0)) {
|
|
if (!try type_record_check(state, field.type?)) return false
|
|
if (field.value != null and try type_execution_value(state, field.value?, field.type?) == no_node) return false
|
|
} else {
|
|
fields [1]ast.NodeId :: [field_id]
|
|
record :: try ast.add_record_type(state.builder, field.span, fields[..])
|
|
constraint :: try ast.add_type_operand(state.builder, field.span, record, true)
|
|
try refresh(state)
|
|
try arraylist.append(&state.rewrites.constraints, constraint)
|
|
if (field.value != null and try type_execution_value(state, field.value?, field.type?) == no_node) return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
# normalize complete record snapshots in declaration order, evaluating every leaf
|
|
# through its declared context. no record containing type values enters runtime IR.
|
|
@hide:file
|
|
type_record_value proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
|
|
type :: try canonical_type(state, annotation)
|
|
if (ast.expr_kind(&state.tree, type) == .union_type)
|
|
return try type_union_value(state, expression, type)
|
|
if (ast.expr_kind(&state.tree, expression) == .typed_constant) {
|
|
actual :: ast.typed_constant(&state.tree, expression)
|
|
if (try same_type(state, type, actual.type)) return expression
|
|
} else if (ast.expr_kind(&state.tree, expression) == .record_literal) {
|
|
if (!try type_record_check(state, type)) return no_node
|
|
literal :: ast.record_literal(&state.tree, expression)
|
|
if (literal.type != null and !try same_type(state, type, literal.type?)) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.type_mismatch, ast.node_span(&state.tree, expression))
|
|
return no_node
|
|
}
|
|
count :: ast.record_field_count(&state.tree, type)
|
|
values :: try mem.alloc(ast.NodeId, state.allocator, count)
|
|
defer mem.free(state.allocator, values)
|
|
mem.set(values, no_node)
|
|
for (0..ast.record_field_count(&state.tree, expression)) |i| {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, expression, i))
|
|
slot := i
|
|
if (field.name) |name| slot = type_record_slot(state, type, name)
|
|
if (slot >= count or values[slot] != no_node) {
|
|
code :: if (slot < count) diagnostics.Code.duplicate_field
|
|
else if (field.name != null) diagnostics.Code.unknown_field else diagnostics.Code.tuple_arity_mismatch
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, code, field.span)
|
|
return no_node
|
|
}
|
|
values[slot] = field.value?
|
|
}
|
|
fields := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&fields)
|
|
for (0..count) |i| {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, type, i))
|
|
if (field.name) |name| {
|
|
if (type_record_slot(state, type, name) != i) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .duplicate_field, field.span)
|
|
return no_node
|
|
}
|
|
}
|
|
input := values[i]
|
|
if (input == no_node and field.value != null) input = field.value?
|
|
if (input == no_node) {
|
|
code :: if (field.name == null) diagnostics.Code.tuple_arity_mismatch else diagnostics.Code.missing_field
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, code, ast.node_span(&state.tree, expression))
|
|
return no_node
|
|
}
|
|
if (!try type_step(state, input)) return no_node
|
|
value :: try type_execution_value(state, input, field.type?)
|
|
if (value == no_node) return no_node
|
|
generated :: try ast.add_field(state.builder, field.span, field.name, null, value)
|
|
try refresh(state)
|
|
try arraylist.append(&fields, generated)
|
|
}
|
|
span :: ast.node_span(&state.tree, expression)
|
|
normalized :: try ast.add_record_literal(state.builder, span, null, fields.items)
|
|
result :: try ast.add_typed_constant(state.builder, span, type, normalized)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.type_mismatch, ast.node_span(&state.tree, expression))
|
|
return no_node
|
|
}
|
|
|
|
@hide:file
|
|
type_array_index proc(state @mut State, expression ast.NodeId, count usize) usize ! Error {
|
|
usize_type :: try builtin(state, expression, .usize)
|
|
actual :: try expression_type(state, expression, 0)
|
|
if (!flexible(state, expression) and (actual == no_node or !try same_type(state, actual, usize_type))) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_index_type, ast.node_span(&state.tree, expression))
|
|
return no_index
|
|
}
|
|
value :: try evaluate_ir(state, expression, usize_type)
|
|
if (value.kind != .integer) return no_index
|
|
if (value.magnitude >= u64(count)) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.index_out_of_bounds, ast.node_span(&state.tree, expression))
|
|
return no_index
|
|
}
|
|
return usize(value.magnitude)
|
|
}
|
|
|
|
@hide:file
|
|
type_array_compatible proc(state @mut State, expected, actual ast.NodeId) bool ! Error {
|
|
if (is_type(state, expected) or is_type(state, actual))
|
|
return is_type(state, expected) and is_type(state, actual)
|
|
left :: try canonical_type(state, expected)
|
|
right :: try canonical_type(state, actual)
|
|
if (left == no_node or right == no_node) return false
|
|
if (ast.expr_kind(&state.tree, left) != .array_type or ast.expr_kind(&state.tree, right) != .array_type)
|
|
return try same_type(state, left, right)
|
|
a :: ast.array_type(&state.tree, left)
|
|
b :: ast.array_type(&state.tree, right)
|
|
if (a.mutable != b.mutable or b.count == null) return false
|
|
if (a.count) |count| {
|
|
if (!try value_equal(state, try evaluate(state, count), try evaluate(state, b.count?))) return false
|
|
}
|
|
return try type_array_compatible(state, a.element, b.element)
|
|
}
|
|
|
|
# undefined leaves are initialization state, not type values. snapshots may copy
|
|
# that state locally; procedure boundaries require every leaf to be initialized.
|
|
@hide:file
|
|
type_array_initialized proc(state @mut State, value, at ast.NodeId) bool ! Error {
|
|
kind :: ast.expr_kind(&state.tree, value)
|
|
if (kind == .literal_undefined) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.read_before_initialization, ast.node_span(&state.tree, at))
|
|
return false
|
|
}
|
|
if (kind == .typed_constant)
|
|
return try type_array_initialized(state, ast.typed_constant(&state.tree, value).value, at)
|
|
if (kind == .array_literal) {
|
|
for (0..ast.array_element_count(&state.tree, value)) |i| {
|
|
if (!try type_array_initialized(state, ast.array_element(&state.tree, value, i), at)) return false
|
|
}
|
|
}
|
|
if (kind == .record_literal) {
|
|
for (0..ast.record_field_count(&state.tree, value)) |i| {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, value, i))
|
|
if (field.value != null and !try type_array_initialized(state, field.value?, at)) return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
# bound the expanded snapshot, not just its dimensions, so later reads and
|
|
# initialization checks cannot traverse an exponentially shared empty shape.
|
|
@hide:file
|
|
type_array_undefined proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
|
|
if (is_type(state, annotation)) return expression
|
|
type :: try canonical_type(state, annotation)
|
|
if (type == no_node or ast.expr_kind(&state.tree, type) != .array_type) return expression
|
|
descriptor :: ast.array_type(&state.tree, type)
|
|
length := Value{}
|
|
if (descriptor.count) |count| length = try evaluate(state, count)
|
|
if (length.kind != .integer or length.negative) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_array_length, ast.node_span(&state.tree, annotation))
|
|
return no_node
|
|
}
|
|
# validate child dimensions even when this dimension is empty.
|
|
child :: try type_array_undefined(state, expression, descriptor.element)
|
|
if (child == no_node) return no_node
|
|
elements := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&elements)
|
|
for (0..length.magnitude) |i| {
|
|
if (!try type_step(state, expression)) return no_node
|
|
element := child
|
|
if (i != 0) element = try type_array_undefined(state, expression, descriptor.element)
|
|
if (element == no_node) return no_node
|
|
try arraylist.append(&elements, element)
|
|
}
|
|
span :: ast.node_span(&state.tree, expression)
|
|
type_span :: ast.node_span(&state.tree, annotation)
|
|
element_type :: if (ast.expr_kind(&state.tree, child) == .typed_constant)
|
|
ast.typed_constant(&state.tree, child).type else descriptor.element
|
|
count :: try ast.add_integer(state.builder, span, length.magnitude)
|
|
concrete :: try ast.add_array_type(state.builder, type_span, count, element_type, descriptor.mutable)
|
|
literal :: try ast.add_array_literal(state.builder, span, elements.items)
|
|
result :: try ast.add_typed_constant(state.builder, span, concrete, literal)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
type_array_value proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
|
|
if (ast.expr_kind(&state.tree, expression) == .literal_undefined)
|
|
return try type_array_undefined(state, expression, annotation)
|
|
type :: try canonical_type(state, annotation)
|
|
array :: ast.array_type(&state.tree, type)
|
|
if (ast.expr_kind(&state.tree, expression) == .typed_constant) {
|
|
actual :: ast.typed_constant(&state.tree, expression)
|
|
if (try type_array_compatible(state, type, actual.type)) return expression
|
|
if (!try type_array_initialized(state, expression, expression)) return no_node
|
|
} else if (ast.expr_kind(&state.tree, expression) == .array_literal) {
|
|
count :: ast.array_element_count(&state.tree, expression)
|
|
if (array.count) |length| {
|
|
value :: try evaluate(state, length)
|
|
if (value.kind != .integer or value.negative) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_array_length, ast.node_span(&state.tree, length))
|
|
return no_node
|
|
}
|
|
if (value.magnitude != u64(count)) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.array_length_mismatch, ast.node_span(&state.tree, expression))
|
|
return no_node
|
|
}
|
|
}
|
|
elements := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&elements)
|
|
element_type := array.element
|
|
for (0..count) |i| {
|
|
element :: try type_execution_value(state,
|
|
ast.array_element(&state.tree, expression, i), element_type)
|
|
if (element == no_node) return no_node
|
|
if (ast.expr_kind(&state.tree, element) == .typed_constant)
|
|
element_type = ast.typed_constant(&state.tree, element).type
|
|
try arraylist.append(&elements, element)
|
|
}
|
|
# empty outer arrays still need concrete, valid child dimensions.
|
|
if (count == 0) {
|
|
child := element_type
|
|
while (!is_type(state, child)) {
|
|
child = try canonical_type(state, child)
|
|
if (child == no_node or ast.expr_kind(&state.tree, child) != .array_type) break
|
|
descriptor :: ast.array_type(&state.tree, child)
|
|
value := Value{}
|
|
if (descriptor.count) |length| value = try evaluate(state, length)
|
|
if (value.kind != .integer or value.negative) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_array_length, ast.node_span(&state.tree, child))
|
|
return no_node
|
|
}
|
|
child = descriptor.element
|
|
}
|
|
if (type_record_storage(state, child, 0) and !try type_record_check(state, child)) return no_node
|
|
}
|
|
span :: ast.node_span(&state.tree, expression)
|
|
length :: try ast.add_integer(state.builder, span, u64(count))
|
|
concrete :: try ast.add_array_type(state.builder, ast.node_span(&state.tree, annotation),
|
|
length, element_type, array.mutable)
|
|
literal :: try ast.add_array_literal(state.builder, span, elements.items)
|
|
result :: try ast.add_typed_constant(state.builder, span, concrete, literal)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
if (type_record_storage(state, annotation, 0)) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.type_mismatch, ast.node_span(&state.tree, expression))
|
|
return no_node
|
|
}
|
|
value :: try evaluate_ir(state, expression, annotation)
|
|
if (value.kind == .invalid) return no_node
|
|
result :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, expression), annotation, value.node)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
# rebuild only the field/index path so previous snapshots retain their owned values.
|
|
@hide:file
|
|
type_array_update proc(
|
|
state @mut State, execution @mut TypeExecution, target, root, current, input ast.NodeId,
|
|
) ast.NodeId ! Error {
|
|
# find the outermost projection; rebuild its child before copying this parent.
|
|
first := target
|
|
while (true) {
|
|
kind :: ast.expr_kind(&state.tree, first)
|
|
if (kind != .index and kind != .selection) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_assignment_target, ast.node_span(&state.tree, target))
|
|
return no_node
|
|
}
|
|
base :: if (kind == .index) ast.index_expr(&state.tree, first).base
|
|
else ast.selection_expr(&state.tree, first).base
|
|
if (base == root) break
|
|
first = base
|
|
}
|
|
array :: ast.typed_constant(&state.tree, current)
|
|
type :: try canonical_type(state, array.type)
|
|
if (
|
|
type == no_node
|
|
or (ast.expr_kind(&state.tree, type) != .record_type and ast.expr_kind(&state.tree, type) != .array_type)
|
|
) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_index_assignment, ast.node_span(&state.tree, target))
|
|
return no_node
|
|
}
|
|
record :: ast.expr_kind(&state.tree, type) == .record_type
|
|
index := no_index
|
|
element_type := no_node
|
|
count := usize(0)
|
|
previous := no_node
|
|
if (record) {
|
|
if (ast.expr_kind(&state.tree, first) == .selection)
|
|
index = type_record_slot(state, type, ast.selection_expr(&state.tree, first).member)
|
|
if (index == no_index) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.unknown_field, ast.node_span(&state.tree, first))
|
|
return no_node
|
|
}
|
|
element_type = ast.field(&state.tree, ast.record_field(&state.tree, type, index)).type?
|
|
count = ast.record_field_count(&state.tree, array.value)
|
|
previous = ast.field(&state.tree, ast.record_field(&state.tree, array.value, index)).value?
|
|
} else {
|
|
descriptor :: ast.array_type(&state.tree, type)
|
|
if (!descriptor.mutable or ast.expr_kind(&state.tree, first) != .index) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_index_assignment, ast.node_span(&state.tree, target))
|
|
return no_node
|
|
}
|
|
expression :: try type_snapshot(state, execution, ast.index_expr(&state.tree, first).index)
|
|
if (!try expand_type_matches(state, expression)) return no_node
|
|
count = ast.array_element_count(&state.tree, array.value)
|
|
index = try type_array_index(state, expression, count)
|
|
if (index == no_index) return no_node
|
|
element_type = descriptor.element
|
|
previous = ast.array_element(&state.tree, array.value, index)
|
|
}
|
|
updated := no_node
|
|
if (first == target) updated = try type_execution_value(state, input, element_type)
|
|
else {
|
|
if (ast.expr_kind(&state.tree, previous) != .typed_constant) {
|
|
previous = try type_execution_value(state, previous, element_type)
|
|
if (previous == no_node) return no_node
|
|
}
|
|
updated = try type_array_update(state, execution, target, first, previous, input)
|
|
}
|
|
if (updated == no_node) return no_node
|
|
elements := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&elements)
|
|
for (0..count) |i| {
|
|
element := no_node
|
|
if (record) {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, array.value, i))
|
|
value :: if (i == index) updated else field.value?
|
|
element = try ast.add_field(state.builder, field.span, field.name, null, value)
|
|
try refresh(state)
|
|
} else element = if (i == index) updated else ast.array_element(&state.tree, array.value, i)
|
|
try arraylist.append(&elements, element)
|
|
}
|
|
span :: ast.node_span(&state.tree, target)
|
|
literal := no_node
|
|
if (record) literal = try ast.add_record_literal(state.builder, span, null, elements.items)
|
|
else literal = try ast.add_array_literal(state.builder, span, elements.items)
|
|
result :: try ast.add_typed_constant(state.builder, span, array.type, literal)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.NodeId ! Error {
|
|
if (input == no_node or annotation == no_node) return no_node
|
|
if (ast.expr_kind(&state.tree, input) == .block_expr
|
|
and (is_type(state, annotation) or type_record_storage(state, annotation, 0)))
|
|
return try evaluate_type_value_block(state, input, annotation)
|
|
expression :: try type_value_expression(state, input)
|
|
if (expression == no_node) return no_node
|
|
if (!try expand_type_matches(state, annotation)) return no_node
|
|
# a recursive record needs its cached declaration before expanding its fields.
|
|
if (is_type(state, annotation) and nominal_type_value(state, expression)) {
|
|
concrete :: try materialize(state, Value{ kind = .type, node = expression })
|
|
if (type_record_storage(state, expression, 0)) {
|
|
if (!try type_record_check(state, expression)) return no_node
|
|
return concrete.node
|
|
}
|
|
constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, expression), concrete.node, true)
|
|
try refresh(state)
|
|
try arraylist.append(&state.rewrites.constraints, constraint)
|
|
return concrete.node
|
|
}
|
|
if (!try expand_type_matches(state, expression)) return no_node
|
|
try expand_expected(state, expression, annotation)
|
|
resolved :: binding(state, expression)
|
|
if (resolved.kind == .invalid) {
|
|
state.evaluation_diagnostic = resolved.data.diagnostic
|
|
return no_node
|
|
}
|
|
if (!is_type(state, annotation) and type_record_storage(state, annotation, 0)
|
|
and resolved.kind == .declaration
|
|
and ast.node_class(&state.tree, resolved.data.declaration) == .statement) {
|
|
declaration :: ast.statement_decl(&state.tree, resolved.data.declaration)
|
|
if (declaration.mutability != .constant or !try type_step(state, expression)) return no_node
|
|
if (declaration.type != null and !try same_type(state, annotation, declaration.type?)) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.type_mismatch, ast.node_span(&state.tree, expression))
|
|
return no_node
|
|
}
|
|
return try type_execution_value(state, declaration.initializer, annotation)
|
|
}
|
|
if (try execution_array_storage(state, annotation)) {
|
|
value :: try type_array_value(state, expression, annotation)
|
|
if (value != no_node and !type_record_storage(state, annotation, 0)) {
|
|
# erased snapshots still owe ordinary array-element/layout checking.
|
|
constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, expression),
|
|
ast.typed_constant(&state.tree, value).type, true)
|
|
try refresh(state)
|
|
try arraylist.append(&state.rewrites.constraints, constraint)
|
|
}
|
|
return value
|
|
}
|
|
if (
|
|
!is_type(state, annotation) and type_record_storage(state, annotation, 0)
|
|
and ast.expr_kind(&state.tree, try canonical_type(state, annotation)) == .slice_type
|
|
) {
|
|
if (ast.expr_kind(&state.tree, expression) == .typed_constant) {
|
|
actual :: ast.typed_constant(&state.tree, expression)
|
|
if (try same_type(state, annotation, actual.type)) return expression
|
|
}
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.type_mismatch, ast.node_span(&state.tree, expression))
|
|
return no_node
|
|
}
|
|
if (!is_type(state, annotation) and type_record_storage(state, annotation, 0))
|
|
return try type_record_value(state, expression, annotation)
|
|
if (is_type(state, annotation)) {
|
|
if (ast.expr_kind(&state.tree, expression) == .literal_undefined) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.read_before_initialization, ast.node_span(&state.tree, expression))
|
|
return no_node
|
|
}
|
|
value :: try evaluate(state, expression)
|
|
if (value.kind != .type) {
|
|
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.type_mismatch, ast.node_span(&state.tree, expression))
|
|
return no_node
|
|
}
|
|
concrete :: try materialize(state, value)
|
|
if (type_record_storage(state, concrete.node, 0)) {
|
|
if (!try type_record_check(state, concrete.node)) return no_node
|
|
return concrete.node
|
|
}
|
|
constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, expression), concrete.node, true)
|
|
try refresh(state)
|
|
try arraylist.append(&state.rewrites.constraints, constraint)
|
|
return concrete.node
|
|
}
|
|
kind :: ast.expr_kind(&state.tree, expression)
|
|
if (kind == .literal_string or kind == .typed_constant) {
|
|
value :: try evaluate(state, expression)
|
|
if (
|
|
value.kind == .string and try valid_value(state, annotation, value)
|
|
and (value.annotation == no_node or try same_type(state, annotation, value.annotation))
|
|
) {
|
|
result :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, expression),
|
|
annotation, value.node)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
}
|
|
# check the leaf as an assignment, not as the evaluation wrapper's return:
|
|
# exact value-type failures belong to the source leaf and use type_mismatch.
|
|
contextual :: try ast.add_typed_constant(state.builder,
|
|
ast.node_span(&state.tree, expression), annotation, expression)
|
|
try refresh(state)
|
|
value :: try evaluate_ir(state, contextual, annotation)
|
|
if (value.kind == .invalid) return no_node
|
|
concrete :: try materialize(state, value)
|
|
result :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, expression), annotation, concrete.node)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
# check capture guards with ordinary IR without elaborating either source body.
|
|
@hide:file
|
|
type_capture_guard proc(state @mut State, expression, capture ast.NodeId) Value ! Error {
|
|
resolved :: binding(state, capture)
|
|
if (resolved.kind == .invalid) {
|
|
state.evaluation_diagnostic = resolved.data.diagnostic
|
|
return Value{}
|
|
}
|
|
span :: ast.node_span(&state.tree, expression)
|
|
yes :: try ast.add_boolean(state.builder, span, true)
|
|
no :: try ast.add_boolean(state.builder, span, false)
|
|
taken :: try ast.add_return_stmt(state.builder, span, yes)
|
|
skipped :: try ast.add_return_stmt(state.builder, span, no)
|
|
taken_statements [1]ast.NodeId :: [taken]
|
|
skipped_statements [1]ast.NodeId :: [skipped]
|
|
then_block :: try ast.add_block(state.builder, span, taken_statements[..])
|
|
else_block :: try ast.add_block(state.builder, span, skipped_statements[..])
|
|
selector :: try ast.add_if_stmt(state.builder, span, expression, then_block, else_block)
|
|
ast.set_capture(state.builder, selector, capture)
|
|
statements [1]ast.NodeId :: [selector]
|
|
body :: try ast.add_block(state.builder, span, statements[..])
|
|
try refresh(state)
|
|
return try evaluate_ir_body(state, expression, body, try builtin(state, expression, .bool))
|
|
}
|
|
|
|
# update one owned aggregate through ordinary assignment checking and execution.
|
|
# only the target's root becomes a temporary local; indices and RHS retain their
|
|
# pre-assignment snapshots, and no interpreter storage survives the evaluation.
|
|
@hide:file
|
|
type_assignment_value proc(
|
|
state @mut State, execution @mut TypeExecution, statement ast.NodeId, index usize,
|
|
) ast.NodeId ! Error {
|
|
assignment :: ast.assignment_stmt(&state.tree, statement)
|
|
local :: execution.locals.items[index]
|
|
span :: ast.node_span(&state.tree, statement)
|
|
value :: try type_snapshot(state, execution, assignment.value)
|
|
if (value == no_node) return no_node
|
|
mutability :: if (ast.node_class(&state.tree, local.declaration) == .statement)
|
|
ast.statement_decl(&state.tree, local.declaration).mutability else ast.Mutability.constant
|
|
temporary :: try ast.add_statement_decl(state.builder, span, span, strpool.no_str,
|
|
local.annotation, mutability, local.value)
|
|
try refresh(state)
|
|
target := no_node
|
|
{
|
|
execution.locals.items[index].value = try reference(state, assignment.target, temporary)
|
|
defer execution.locals.items[index].value = local.value
|
|
target = try type_snapshot(state, execution, assignment.target)
|
|
}
|
|
if (!try expand_type_matches(state, target) or !try expand_type_matches(state, value)) return no_node
|
|
update :: try ast.add_assignment_stmt(state.builder, span, target, value)
|
|
returned :: try ast.add_return_stmt(state.builder, span, try reference(state, statement, temporary))
|
|
statements [3]ast.NodeId :: [temporary, update, returned]
|
|
body :: try ast.add_block(state.builder, span, statements[..])
|
|
try refresh(state)
|
|
result :: try evaluate_ir_body(state, statement, body, local.annotation)
|
|
if (result.kind == .invalid) return no_node
|
|
concrete :: try materialize(state, result)
|
|
owned :: try ast.add_typed_constant(state.builder, span, local.annotation, concrete.node)
|
|
try refresh(state)
|
|
return owned
|
|
}
|
|
|
|
@hide:file
|
|
execute_type_block proc(state @mut State, execution @mut TypeExecution, block ast.NodeId) TypeFlow ! Error {
|
|
if (block == execution.inline_body) {
|
|
snapshot :: try type_snapshot(state, execution, block)
|
|
if (snapshot == no_node) return .invalid
|
|
flow :: try inline_block(state, snapshot)
|
|
if (flow == .invalid) return flow
|
|
try arraylist.append(execution.inline_blocks?, snapshot)
|
|
return flow
|
|
}
|
|
saved :: execution.locals.items.len
|
|
defer execution.locals.items = execution.locals.items[..saved]
|
|
reached := usize(0)
|
|
flow := TypeFlow.next
|
|
for (0..ast.block_statement_count(&state.tree, block)) |i| {
|
|
statement :: ast.block_statement(&state.tree, block, i)
|
|
if (!try type_step(state, statement)) return .invalid
|
|
reached = i + 1
|
|
flow = try execute_type_statement(state, execution, statement)
|
|
if (flow != .next) break
|
|
}
|
|
if (flow == .invalid) return flow
|
|
# yielded/returned values already own their snapshots before scope cleanup runs.
|
|
while (reached > 0) {
|
|
reached -= 1
|
|
statement :: ast.block_statement(&state.tree, block, reached)
|
|
if (ast.stmt_kind(&state.tree, statement) != .defer_stmt) continue
|
|
saved_depth :: execution.cleanup_yield_depth
|
|
saved_loop :: execution.cleanup_loop_depth
|
|
execution.cleanup_loop_depth = execution.loop_depth
|
|
execution.cleanup_yield_depth = execution.yield_depth
|
|
execution.cleanup_depth += 1
|
|
cleanup :: try execute_type_block(state, execution, ast.cleanup_stmt(&state.tree, statement).body)
|
|
execution.cleanup_depth -= 1
|
|
execution.cleanup_yield_depth = saved_depth
|
|
execution.cleanup_loop_depth = saved_loop
|
|
if (cleanup != .next) return .invalid
|
|
}
|
|
return flow
|
|
}
|
|
|
|
# substitute selector names without snapshotting away the assignment's root place.
|
|
@hide:file
|
|
type_assignment_target proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) ast.NodeId ! Error {
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
if (kind == .builtin_call) {
|
|
name :: ast.builtin_call(&state.tree, id).name
|
|
if (!mem.eql(ast.source_text(&state.tree, ast.node_span(&state.tree, name)), "field")) return id
|
|
if (ast.call_arg_count(&state.tree, id) != 2) {
|
|
try reject(state, id, .argument_count_mismatch)
|
|
return no_node
|
|
}
|
|
base :: try type_assignment_target(state, execution, ast.call_arg(&state.tree, id, 0))
|
|
if (base == no_node) return no_node
|
|
argument :: try type_snapshot(state, execution, ast.call_arg(&state.tree, id, 1))
|
|
if (argument == no_node) return no_node
|
|
arguments [2]ast.NodeId :: [base, argument]
|
|
result :: try ast.add_builtin_call(state.builder, ast.node_span(&state.tree, id), name, arguments[..])
|
|
try refresh(state)
|
|
try expand(state, result)
|
|
if (binding(state, result).kind == .invalid) return no_node
|
|
return result
|
|
}
|
|
if (kind != .selection and kind != .index) return id
|
|
base :: if (kind == .selection) ast.selection_expr(&state.tree, id).base else ast.index_expr(&state.tree, id).base
|
|
selected :: try type_assignment_target(state, execution, base)
|
|
if (selected == no_node or selected == base) return if (selected == no_node) no_node else id
|
|
result := no_node
|
|
if (kind == .selection)
|
|
result = try ast.add_selection(state.builder, ast.node_span(&state.tree, id),
|
|
selected, ast.selection_expr(&state.tree, id).member)
|
|
else result = try ast.add_index(state.builder, ast.node_span(&state.tree, id),
|
|
selected, ast.index_expr(&state.tree, id).index)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
execute_type_statement proc(state @mut State, execution @mut TypeExecution, statement ast.NodeId) TypeFlow ! Error {
|
|
if (execution.cleanup_depth > 0) {
|
|
kind :: ast.stmt_kind(&state.tree, statement)
|
|
if (kind == .return_stmt
|
|
or ((kind == .break_stmt or kind == .continue_stmt) and execution.loop_depth <= execution.cleanup_loop_depth)) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.cleanup_control_flow, ast.node_span(&state.tree, statement))
|
|
return .invalid
|
|
}
|
|
}
|
|
flow :: try execute_type_statement_body(state, execution, statement)
|
|
if (execution.pending_flow == .next) return flow
|
|
pending :: execution.pending_flow
|
|
execution.pending_flow = .next
|
|
return pending
|
|
}
|
|
|
|
@hide:file
|
|
execute_type_statement_body proc(state @mut State, execution @mut TypeExecution, statement ast.NodeId) TypeFlow ! Error {
|
|
match ast.stmt_kind(&state.tree, statement) {
|
|
.unreachable_stmt: {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.unreachable_executed, ast.node_span(&state.tree, statement))
|
|
return .invalid
|
|
}
|
|
.defer_stmt: {}
|
|
.errdefer_stmt: {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.errdefer_outside_fallible, ast.node_span(&state.tree, statement))
|
|
return .invalid
|
|
}
|
|
.yield_stmt: {
|
|
if (execution.yield_depth == 0) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.yield_outside_block, ast.node_span(&state.tree, statement))
|
|
return .invalid
|
|
}
|
|
if (execution.cleanup_depth > 0 and execution.yield_depth <= execution.cleanup_yield_depth) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.cleanup_control_flow, ast.node_span(&state.tree, statement))
|
|
return .invalid
|
|
}
|
|
value :: ast.yield_stmt(&state.tree, statement).value?
|
|
expression :: try type_snapshot_expected(state, execution, value, execution.yield_type)
|
|
if (expression == no_node) return .invalid
|
|
annotation := execution.yield_type
|
|
if (annotation == no_node) annotation = try expression_type(state, expression, 0)
|
|
execution.yielded = try type_execution_value(state, expression, annotation)
|
|
if (execution.yielded == no_node
|
|
or !try type_array_initialized(state, execution.yielded, value)) return .invalid
|
|
return .yielded
|
|
}
|
|
.return_stmt: {
|
|
if (ast.return_stmt(&state.tree, statement).value) |value| {
|
|
expression :: try type_snapshot_expected(state, execution, value, execution.result_type)
|
|
if (expression == no_node) return .invalid
|
|
execution.returned = try type_execution_value(state, expression, execution.result_type)
|
|
if (execution.returned != no_node
|
|
and !try type_array_initialized(state, execution.returned, value)) return .invalid
|
|
if (execution.returned != no_node) return .returned
|
|
}
|
|
return .invalid
|
|
}
|
|
.declaration: {
|
|
declaration :: ast.statement_decl(&state.tree, statement)
|
|
annotation := no_node
|
|
if (declaration.type) |type| annotation = try type_snapshot(state, execution, type)
|
|
snapshot :: try type_snapshot_expected(state, execution, declaration.initializer, annotation)
|
|
expression :: try type_value_expression(state, snapshot)
|
|
if (expression == no_node) return .invalid
|
|
if (annotation == no_node and nominal_type_value(state, expression))
|
|
annotation = try builtin(state, expression, .type)
|
|
else if (annotation == no_node) {
|
|
if (!try expand_type_matches(state, expression)) return .invalid
|
|
try expand(state, expression)
|
|
annotation = try expression_type(state, expression, 0)
|
|
if (annotation == no_node) {
|
|
value :: try evaluate(state, expression)
|
|
if (value.kind == .type) annotation = try builtin(state, expression, .type)
|
|
}
|
|
}
|
|
if (annotation == no_node) return .invalid
|
|
value :: try type_execution_value(state, expression, annotation)
|
|
if (value == no_node) return .invalid
|
|
if (ast.expr_kind(&state.tree, value) == .typed_constant)
|
|
annotation = ast.typed_constant(&state.tree, value).type
|
|
try arraylist.append(&execution.locals, TypeLocal{
|
|
declaration = statement, value = value, annotation = annotation,
|
|
})
|
|
}
|
|
.assignment_stmt: {
|
|
assignment :: ast.assignment_stmt(&state.tree, statement)
|
|
target :: try type_assignment_target(state, execution, assignment.target)
|
|
if (target == no_node) return .invalid
|
|
if (target != assignment.target) {
|
|
normalized :: try ast.add_assignment_stmt(state.builder,
|
|
ast.node_span(&state.tree, statement), target, assignment.value)
|
|
if (assignment.compound) ast.set_assignment_compound(state.builder, normalized)
|
|
try refresh(state)
|
|
# synthesized places still need local substitution in snapshots.
|
|
previous :: execution.mapping.len
|
|
execution.mapping = try mem.realloc(state.allocator, execution.mapping, ast.node_count(&state.tree))
|
|
mem.set(execution.mapping[previous..], no_node)
|
|
return try execute_type_statement(state, execution, normalized)
|
|
}
|
|
root := assignment.target
|
|
indexed := true
|
|
while (true) {
|
|
kind :: ast.expr_kind(&state.tree, root)
|
|
if (kind == .selection and binding(state, root).kind == .none) {
|
|
indexed = false
|
|
root = ast.selection_expr(&state.tree, root).base
|
|
}
|
|
else if (kind == .index) root = ast.index_expr(&state.tree, root).base
|
|
else break
|
|
}
|
|
resolved :: binding(state, root)
|
|
index := no_index
|
|
if (resolved.kind == .declaration) {
|
|
for (execution.locals.items) |local, i| {
|
|
if (local.declaration == resolved.data.declaration) index = i
|
|
}
|
|
}
|
|
if (
|
|
index == no_index
|
|
or (root == assignment.target
|
|
and (ast.node_class(&state.tree, execution.locals.items[index].declaration) != .statement
|
|
or ast.statement_decl(&state.tree, execution.locals.items[index].declaration).mutability != .variable))
|
|
) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_assignment_target, ast.node_span(&state.tree, assignment.target))
|
|
return .invalid
|
|
}
|
|
if (root != assignment.target) {
|
|
if ((indexed and try execution_array_storage(state, execution.locals.items[index].annotation))
|
|
or type_record_storage(state, execution.locals.items[index].annotation, 0)) {
|
|
local :: execution.locals.items[index]
|
|
if (
|
|
ast.node_class(&state.tree, local.declaration) != .statement
|
|
or ast.statement_decl(&state.tree, local.declaration).mutability != .variable
|
|
) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_index_assignment, ast.node_span(&state.tree, assignment.target))
|
|
return .invalid
|
|
}
|
|
input :: try type_snapshot(state, execution, assignment.value)
|
|
if (!try expand_type_matches(state, input)) return .invalid
|
|
value :: try type_array_update(state, execution, assignment.target, root, local.value, input)
|
|
if (value == no_node) return .invalid
|
|
execution.locals.items[index].value = value
|
|
return .next
|
|
}
|
|
value :: try type_assignment_value(state, execution, statement, index)
|
|
if (value == no_node) return .invalid
|
|
execution.locals.items[index].value = value
|
|
return .next
|
|
}
|
|
expression :: try type_snapshot_expected(state, execution,
|
|
assignment.value, execution.locals.items[index].annotation)
|
|
value :: try type_execution_value(state, expression, execution.locals.items[index].annotation)
|
|
if (value == no_node) return .invalid
|
|
execution.locals.items[index].value = value
|
|
}
|
|
.if_stmt, .while_stmt: {
|
|
looping :: ast.stmt_kind(&state.tree, statement) == .while_stmt
|
|
condition := no_node
|
|
body := no_node
|
|
other := no_node
|
|
capture ?ast.NodeId := null
|
|
if (looping) {
|
|
view :: ast.while_stmt(&state.tree, statement)
|
|
capture = view.capture
|
|
condition = view.condition
|
|
body = view.body
|
|
} else {
|
|
view :: ast.if_stmt(&state.tree, statement)
|
|
capture = view.capture
|
|
condition = view.condition
|
|
body = view.then_block
|
|
if (view.else_block) |branch| other = branch
|
|
}
|
|
if (looping) execution.loop_depth += 1
|
|
defer if (looping) execution.loop_depth -= 1
|
|
saved :: execution.locals.items.len
|
|
defer execution.locals.items = execution.locals.items[..saved]
|
|
invariant :: branch_constant(state, condition, 0)
|
|
value := Value{}
|
|
while (true) {
|
|
if (!try type_step(state, condition)) return .invalid
|
|
if (!invariant or value.kind == .invalid) {
|
|
expression :: try type_snapshot(state, execution, condition)
|
|
if (expression == no_node) return .invalid
|
|
if (capture) |name| value = try type_capture_guard(state, expression, name)
|
|
else {
|
|
boolean :: try builtin(state, expression, .bool)
|
|
value = try evaluate_ir(state, expression, boolean)
|
|
}
|
|
}
|
|
if (value.kind != .boolean) return .invalid
|
|
if (!value.boolean) {
|
|
if (!looping and other != no_node) return try execute_type_block(state, execution, other)
|
|
return .next
|
|
}
|
|
execution.locals.items = execution.locals.items[..saved]
|
|
if (capture) |name| {
|
|
expression :: try type_snapshot(state, execution, condition)
|
|
if (expression == no_node) return .invalid
|
|
unwrapped :: try ast.add_unary(state.builder,
|
|
ast.node_span(&state.tree, condition), .unwrap, expression)
|
|
try refresh(state)
|
|
annotation :: try expression_type(state, unwrapped, 0)
|
|
payload :: try type_execution_value(state, unwrapped, annotation)
|
|
if (payload == no_node) return .invalid
|
|
try arraylist.append(&execution.locals, TypeLocal{
|
|
declaration = name, value = payload, annotation = annotation,
|
|
})
|
|
}
|
|
flow :: try execute_type_block(state, execution, body)
|
|
if (!looping or flow == .returned or flow == .yielded or flow == .invalid) return flow
|
|
if (flow == .break) return .next
|
|
}
|
|
}
|
|
.expression: {
|
|
expression :: ast.statement_expr(&state.tree, statement)
|
|
if (ast.expr_kind(&state.tree, expression) != .match_expr) {
|
|
snapshot :: try type_snapshot(state, execution, expression)
|
|
if (snapshot == no_node) return .invalid
|
|
annotation :: try expression_type(state, snapshot, 0)
|
|
if (try type_execution_value(state, snapshot, annotation) == no_node) return .invalid
|
|
return .next
|
|
}
|
|
subject_type := no_node
|
|
arms :: ast.match_arm_count(&state.tree, expression)
|
|
if (arms > 0
|
|
and ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, expression, arms - 1)).is_inline) {
|
|
subject :: try type_snapshot(state, execution, ast.match_expr(&state.tree, expression).subject)
|
|
if (subject == no_node) return .invalid
|
|
subject_type = try expression_type(state, subject, 0)
|
|
}
|
|
before :: ast.node_count(&state.tree)
|
|
try expand_inline_match(state, expression, subject_type)
|
|
if (ast.expr_kind(&state.tree, expression) != .match_expr) return .invalid
|
|
if (ast.node_count(&state.tree) != before) {
|
|
previous :: execution.mapping.len
|
|
execution.mapping = try mem.realloc(state.allocator, execution.mapping, ast.node_count(&state.tree))
|
|
mem.set(execution.mapping[previous..], no_node)
|
|
}
|
|
snapshot :: try type_dispatch_snapshot(state, execution, expression)
|
|
if (snapshot == no_node) return .invalid
|
|
index :: try type_match_arm(state, snapshot)
|
|
if (index == no_index) return .invalid
|
|
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, expression, index))
|
|
saved :: execution.locals.items.len
|
|
defer execution.locals.items = execution.locals.items[..saved]
|
|
if (arm.capture) |capture| {
|
|
value :: try type_match_capture(state, snapshot)
|
|
if (value == no_node) return .invalid
|
|
constant :: ast.typed_constant(&state.tree, value)
|
|
captured :: if (is_type(state, constant.type)) constant.value else value
|
|
try arraylist.append(&execution.locals, TypeLocal{
|
|
declaration = capture, value = captured,
|
|
annotation = constant.type,
|
|
})
|
|
}
|
|
if (!arm.expression) {
|
|
if (try execute_type_value_block(state, execution, arm.body, no_node) == no_node) return .invalid
|
|
return .next
|
|
}
|
|
body :: try type_snapshot(state, execution, arm.body)
|
|
value :: try type_value_expression(state, body)
|
|
if (value == no_node) return .invalid
|
|
annotation :: try expression_type(state, value, 0)
|
|
if (try type_execution_value(state, value, annotation) == no_node) return .invalid
|
|
}
|
|
.for_stmt: return try execute_type_for(state, execution, statement)
|
|
.block_stmt: return try execute_type_block(state, execution, ast.statement_block(&state.tree, statement))
|
|
.break_stmt, .continue_stmt: {
|
|
if (execution.loop_depth == 0) {
|
|
code :: if (ast.stmt_kind(&state.tree, statement) == .break_stmt)
|
|
diagnostics.Code.break_outside_loop else diagnostics.Code.continue_outside_loop
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, code, ast.node_span(&state.tree, statement))
|
|
return .invalid
|
|
}
|
|
return if (ast.stmt_kind(&state.tree, statement) == .break_stmt) TypeFlow.break else TypeFlow.continue
|
|
}
|
|
else: return .invalid
|
|
}
|
|
return .next
|
|
}
|
|
|
|
# retain a collection place, but freeze its indices at loop entry. snapshots then read
|
|
# the current owned root after body assignments without re-evaluating the place.
|
|
@hide:file
|
|
type_collection_place proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) ast.NodeId ! Error {
|
|
resolved :: binding(state, id)
|
|
if (resolved.kind == .declaration) {
|
|
for (execution.locals.items) |local| {
|
|
if (local.declaration == resolved.data.declaration) return id
|
|
}
|
|
return no_node
|
|
}
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
if (kind != .selection and kind != .index) return no_node
|
|
base :: if (kind == .selection) ast.selection_expr(&state.tree, id).base else ast.index_expr(&state.tree, id).base
|
|
place :: try type_collection_place(state, execution, base)
|
|
if (place == no_node) return no_node
|
|
result := no_node
|
|
if (kind == .selection) {
|
|
result = try ast.add_selection(state.builder, ast.node_span(&state.tree, id),
|
|
place, ast.selection_expr(&state.tree, id).member)
|
|
} else {
|
|
index :: try type_snapshot(state, execution, ast.index_expr(&state.tree, id).index)
|
|
if (index == no_node) return no_node
|
|
value :: try type_execution_value(state, index, try builtin(state, index, .usize))
|
|
if (value == no_node) return no_node
|
|
result = try ast.add_index(state.builder, ast.node_span(&state.tree, id), place, value)
|
|
}
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
type_collection_read proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) ast.NodeId ! Error {
|
|
if (binding(state, id).kind == .declaration) return try type_snapshot(state, execution, id)
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
base :: if (kind == .selection) ast.selection_expr(&state.tree, id).base else ast.index_expr(&state.tree, id).base
|
|
value :: try type_collection_read(state, execution, base)
|
|
result := no_node
|
|
if (kind == .selection)
|
|
result = try ast.add_selection(state.builder, ast.node_span(&state.tree, id),
|
|
value, ast.selection_expr(&state.tree, id).member)
|
|
else result = try ast.add_index(state.builder, ast.node_span(&state.tree, id),
|
|
value, ast.index_expr(&state.tree, id).index)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
execute_type_collection proc(state @mut State, execution @mut TypeExecution, statement ast.NodeId) TypeFlow ! Error {
|
|
loop :: ast.for_stmt(&state.tree, statement)
|
|
span :: ast.node_span(&state.tree, statement)
|
|
if (loop.capture) |capture| {
|
|
if (binding(state, capture).kind == .invalid) {
|
|
state.evaluation_diagnostic = binding(state, capture).data.diagnostic
|
|
return .invalid
|
|
}
|
|
}
|
|
if (loop.index) |index| {
|
|
if (binding(state, index).kind == .invalid) {
|
|
state.evaluation_diagnostic = binding(state, index).data.diagnostic
|
|
return .invalid
|
|
}
|
|
}
|
|
snapshot :: try type_snapshot(state, execution, loop.iterable)
|
|
if (!try expand_type_matches(state, snapshot)) return .invalid
|
|
try expand(state, snapshot)
|
|
annotation :: try expression_type(state, snapshot, 0)
|
|
if (annotation == no_node) return .invalid
|
|
tuple :: loop.is_inline and ast.expr_kind(&state.tree, annotation) == .record_type
|
|
if tuple {
|
|
for (0..ast.record_field_count(&state.tree, annotation)) |i| {
|
|
if (ast.field(&state.tree, ast.record_field(&state.tree, annotation, i)).name != null) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .invalid_for_iterable, span)
|
|
return .invalid
|
|
}
|
|
}
|
|
}
|
|
collection :: try type_execution_value(state, snapshot, annotation)
|
|
if (collection == no_node) return .invalid
|
|
count := Value{}
|
|
if tuple {
|
|
count = Value{ kind = .integer, magnitude = u64(ast.record_field_count(&state.tree, annotation)) }
|
|
} else if (try execution_array_storage(state, annotation) or try metadata_sequence(state, annotation)) {
|
|
count = Value{ kind = .integer,
|
|
magnitude = u64(ast.array_element_count(&state.tree, ast.typed_constant(&state.tree, collection).value)) }
|
|
} else {
|
|
# validate the loop contract even for empty collections, without executing
|
|
# its type-valued body. the synthetic body exits before reading an element.
|
|
stop :: try ast.add_break_stmt(state.builder, span)
|
|
stops [1]ast.NodeId :: [stop]
|
|
body :: try ast.add_block(state.builder, span, stops[..])
|
|
selector :: try ast.add_for_stmt(state.builder, span, collection, loop.capture, loop.index,
|
|
loop.pointer_capture, loop.index_capture, body)
|
|
try refresh(state)
|
|
length := no_node
|
|
if (ast.expr_kind(&state.tree, annotation) == .array_type) length = ast.array_type(&state.tree, annotation).count?
|
|
else {
|
|
value :: try evaluate(state, collection)
|
|
if (value.kind == .string) {
|
|
text :: ast.literal_text(&state.tree, value.node)
|
|
cursor := usize(1)
|
|
size := u64(0)
|
|
while (cursor < text.len - 1) {
|
|
_ = ast.string_next(text, &cursor) catch { return .invalid }
|
|
size += 1
|
|
}
|
|
length = try ast.add_integer(state.builder, span, size)
|
|
} else length = try ast.add_integer(state.builder, span, 0)
|
|
}
|
|
returned :: try ast.add_return_stmt(state.builder, span, length)
|
|
statements [2]ast.NodeId :: [selector, returned]
|
|
wrapper :: try ast.add_block(state.builder, span, statements[..])
|
|
try refresh(state)
|
|
count = try evaluate_ir_body(state, statement, wrapper, try builtin(state, statement, .usize))
|
|
if (count.kind != .integer) return .invalid
|
|
}
|
|
if (loop.pointer_capture and loop.capture != null) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_value_escape, span)
|
|
return .invalid
|
|
}
|
|
place := no_node
|
|
if (tuple or ast.expr_kind(&state.tree, annotation) == .array_type)
|
|
place = try type_collection_place(state, execution, loop.iterable)
|
|
if (state.evaluation_diagnostic != diagnostics.no_diagnostic) return .invalid
|
|
saved :: execution.locals.items.len
|
|
defer execution.locals.items = execution.locals.items[..saved]
|
|
execution.loop_depth += 1
|
|
defer execution.loop_depth -= 1
|
|
for (0..usize(count.magnitude)) |i| {
|
|
if (!try type_step(state, statement)) return .invalid
|
|
execution.locals.items = execution.locals.items[..saved]
|
|
index :: try ast.add_integer(state.builder, span, u64(i))
|
|
try refresh(state)
|
|
if loop.capture |capture| {
|
|
current := collection
|
|
if (place != no_node) current = try type_collection_read(state, execution, place)
|
|
element :: if (tuple) try ast.add_selection(state.builder, span, current, index)
|
|
else try ast.add_index(state.builder, span, current, index)
|
|
try refresh(state)
|
|
element_type :: if (tuple)
|
|
ast.field(&state.tree, ast.record_field(&state.tree, annotation, i)).type?
|
|
else try expression_type(state, element, 0)
|
|
value :: try type_execution_value(state, element, element_type)
|
|
if (value == no_node) return .invalid
|
|
try arraylist.append(&execution.locals, TypeLocal{
|
|
declaration = capture, value = value, annotation = element_type,
|
|
})
|
|
}
|
|
if (loop.index) |capture| {
|
|
type :: try builtin(state, capture, .usize)
|
|
value :: try type_execution_value(state, index, type)
|
|
if (value == no_node) return .invalid
|
|
try arraylist.append(&execution.locals, TypeLocal{
|
|
declaration = capture, value = value, annotation = type,
|
|
})
|
|
}
|
|
flow :: try execute_type_block(state, execution, loop.body)
|
|
if (flow == .returned or flow == .yielded or flow == .invalid) return flow
|
|
if (flow == .break) return .next
|
|
}
|
|
return .next
|
|
}
|
|
|
|
@hide:file
|
|
execute_type_for proc(state @mut State, execution @mut TypeExecution, statement ast.NodeId) TypeFlow ! Error {
|
|
loop :: ast.for_stmt(&state.tree, statement)
|
|
if (ast.expr_kind(&state.tree, loop.iterable) != .range)
|
|
return try execute_type_collection(state, execution, statement)
|
|
if (loop.pointer_capture or loop.index_capture) return .invalid
|
|
bounds :: ast.range_expr(&state.tree, loop.iterable)
|
|
start :: try type_snapshot(state, execution, bounds.start)
|
|
end :: try type_snapshot(state, execution, bounds.end)
|
|
if (start == no_node or end == no_node) return .invalid
|
|
typed_bound :: if (flexible(state, start)) end else start
|
|
annotation :: try expression_type(state, typed_bound, 0)
|
|
if (annotation == no_node or !try integer_type(state, annotation)) return .invalid
|
|
current := try type_execution_value(state, start, annotation)
|
|
limit :: try type_execution_value(state, end, annotation)
|
|
if (current == no_node or limit == no_node) return .invalid
|
|
saved :: execution.locals.items.len
|
|
defer execution.locals.items = execution.locals.items[..saved]
|
|
if (loop.capture) |capture| try arraylist.append(&execution.locals, TypeLocal{
|
|
declaration = capture, value = current, annotation = annotation,
|
|
})
|
|
execution.loop_depth += 1
|
|
defer execution.loop_depth -= 1
|
|
while (true) {
|
|
if (!try type_step(state, statement)) return .invalid
|
|
span :: ast.node_span(&state.tree, statement)
|
|
operator ast.BinaryOperator :: if (bounds.inclusive) .less_equal else .less
|
|
comparison :: try ast.add_binary(state.builder, span, operator, current, limit)
|
|
try refresh(state)
|
|
keep :: try evaluate(state, comparison)
|
|
if (keep.kind != .boolean) return .invalid
|
|
if (!keep.boolean) return .next
|
|
if (loop.capture != null) execution.locals.items[saved].value = current
|
|
flow :: try execute_type_block(state, execution, loop.body)
|
|
if (flow == .returned or flow == .yielded or flow == .invalid) return flow
|
|
if (flow == .break) return .next
|
|
if (try value_equal(state, try evaluate(state, current), try evaluate(state, limit))) return .next
|
|
one :: try ast.add_integer(state.builder, span, 1)
|
|
increment :: try ast.add_binary(state.builder, span, .add, current, one)
|
|
try refresh(state)
|
|
current = try type_execution_value(state, increment, annotation)
|
|
if (current == no_node) return .invalid
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
mix proc(left, right usize) usize {
|
|
return (left << 7) xor (left >> (sizeof!(usize) * 8 - 7)) xor right
|
|
}
|
|
|
|
@hide:file
|
|
evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
|
|
if (id == no_node) return Value{}
|
|
if (state.depth >= 128) return Value{}
|
|
state.depth += 1
|
|
defer state.depth -= 1
|
|
try expand(state, id)
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
resolved :: binding(state, id)
|
|
if (resolved.kind == .builtin and resolved.data.builtin != .type) return Value{ kind = .type, node = id }
|
|
if (resolved.kind == .declaration) {
|
|
target :: resolved.data.declaration
|
|
class :: ast.node_class(&state.tree, target)
|
|
if (class == .function) {
|
|
if (template(state, target)) return Value{}
|
|
return Value{ kind = .function, node = id }
|
|
}
|
|
if (class == .statement) {
|
|
declaration :: ast.statement_decl(&state.tree, target)
|
|
if (declaration.mutability != .constant) return Value{}
|
|
value := try evaluate(state, declaration.initializer)
|
|
if (declaration.type) |annotation| value.annotation = annotation
|
|
return value
|
|
}
|
|
return Value{}
|
|
}
|
|
match kind {
|
|
.union_constant: {
|
|
value :: ast.union_constant(&state.tree, id)
|
|
return Value{ kind = .aggregate, node = id, annotation = value.type }
|
|
}
|
|
.typed_constant: {
|
|
view :: ast.typed_constant(&state.tree, id)
|
|
if (!is_type(state, view.type) and type_record_storage(state, view.type, 0)) {
|
|
if (!try type_array_initialized(state, id, id)) return Value{}
|
|
return Value{ kind = .aggregate, node = view.value, annotation = view.type }
|
|
}
|
|
value := try evaluate_expected(state, view.value, view.type)
|
|
value.annotation = view.type
|
|
return value
|
|
}
|
|
.literal_int: {
|
|
value := u64(0)
|
|
for ast.literal_text(&state.tree, id) |byte| {
|
|
if (byte < '0' or byte > '9') return Value{}
|
|
digit :: u64(byte - '0')
|
|
if (value > divtrunc!(maxval!(u64) - digit, u64(10))) return Value{}
|
|
value = value * 10 + digit
|
|
}
|
|
return Value{ kind = .integer, node = id, magnitude = value }
|
|
}
|
|
.literal_float: {
|
|
if (ast.float_value(&state.tree, id)) |value| return Value{
|
|
kind = .real, node = id, magnitude = ptrcast!(@u64, &value)^,
|
|
}
|
|
return try evaluate_ir(state, id, try builtin(state, id, .float))
|
|
}
|
|
.literal_bool: return Value{ kind = .boolean, node = id,
|
|
boolean = mem.eql(ast.literal_text(&state.tree, id), "true") }
|
|
.literal_string: {
|
|
text :: ast.literal_text(&state.tree, id)
|
|
cursor := usize(1)
|
|
while (cursor < text.len - 1) {
|
|
_ = ast.string_next(text, &cursor) catch {
|
|
try reject(state, id, .invalid_escape)
|
|
return Value{}
|
|
}
|
|
}
|
|
return Value{ kind = .string, node = id }
|
|
}
|
|
.literal_null: return Value{ kind = .null, node = id }
|
|
.literal_void: return Value{ kind = .aggregate, node = id, annotation = try builtin(state, id, .void) }
|
|
.record_type, .array_type, .slice_type, .pointer_type, .function_type, .optional_type,
|
|
.enum_type, .union_type, .union_tag_type, .distinct_type, .fallible_type, .sum_type:
|
|
return Value{ kind = .type, node = id }
|
|
.alias_expr: return try evaluate(state, ast.type_operand(&state.tree, id))
|
|
.unary: {
|
|
view :: ast.unary_expr(&state.tree, id)
|
|
if (view.operator == .bit_not)
|
|
return try evaluate_ir(state, id, try expression_type(state, id, 0))
|
|
if (view.operator == .unwrap) {
|
|
# source-known callbacks already own declaration identities, not interpreter handles.
|
|
value := try evaluate(state, view.operand)
|
|
annotation :: try expression_type(state, view.operand, 0)
|
|
if (value.kind == .function and annotation != no_node
|
|
and ast.expr_kind(&state.tree, annotation) == .optional_type) {
|
|
try arraylist.append(&state.rewrites.constraints, id)
|
|
value.annotation = ast.type_operand(&state.tree, annotation)
|
|
return value
|
|
}
|
|
return try evaluate_ir(state, id, try expression_type(state, id, 0))
|
|
}
|
|
value := try evaluate(state, view.operand)
|
|
if (view.operator == .negate and value.kind == .integer) value.negative = value.magnitude != 0 and !value.negative
|
|
else if (view.operator == .negate and value.kind == .real) value.magnitude = value.magnitude xor (u64(1) << 63)
|
|
else if (view.operator == .logical_not and value.kind == .boolean) value.boolean = !value.boolean
|
|
else return Value{}
|
|
value.node = id
|
|
return value
|
|
}
|
|
.binary: return try evaluate_binary(state, id)
|
|
.call: return try evaluate_call(state, id)
|
|
.block_expr: {
|
|
annotation :: try expression_type(state, id, 0)
|
|
if (annotation == no_node) return Value{}
|
|
if (is_type(state, annotation) or type_record_storage(state, annotation, 0)) {
|
|
result :: try evaluate_type_value_block(state, id, annotation)
|
|
if (result == no_node) return Value{}
|
|
return try evaluate_expected(state, result, annotation)
|
|
}
|
|
return try evaluate_ir(state, id, annotation)
|
|
}
|
|
.match_expr: {
|
|
annotation :: try expression_type(state, id, 0)
|
|
if (annotation != no_node and (is_type(state, annotation) or type_record_storage(state, annotation, 0))) {
|
|
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
|
state.type_execution_depth += 1
|
|
defer state.type_execution_depth -= 1
|
|
selected :: try type_value_expression(state, id)
|
|
if (selected == no_node or selected == id) return Value{}
|
|
return try evaluate_expected(state, selected, annotation)
|
|
}
|
|
return try evaluate_ir(state, id, annotation)
|
|
}
|
|
.record_literal, .array_literal, .selection, .index, .builtin_call, .catch_expr:
|
|
return try evaluate_ir(state, id, try expression_type(state, id, 0))
|
|
else: return Value{}
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
evaluate_binary proc(state @mut State, id ast.NodeId) Value ! Error {
|
|
view :: ast.binary_expr(&state.tree, id)
|
|
if (view.operator == .divide) return try evaluate_ir(state, id, try builtin(state, id, .float))
|
|
if (view.operator == .bit_and or view.operator == .bit_or or view.operator == .bit_xor
|
|
or view.operator == .shift_left or view.operator == .shift_right or view.operator == .shift_left_saturating)
|
|
return try evaluate_ir(state, id, try expression_type(state, id, 0))
|
|
left :: try evaluate(state, view.left)
|
|
if (left.kind == .invalid) return Value{}
|
|
if (left.kind == .boolean) {
|
|
if (view.operator == .logical_and and !left.boolean) return Value{ kind = .boolean, node = id, boolean = false }
|
|
if (view.operator == .logical_or and left.boolean) return Value{ kind = .boolean, node = id, boolean = true }
|
|
}
|
|
right := try evaluate(state, view.right)
|
|
if (left.kind == .invalid or right.kind == .invalid) return Value{}
|
|
if (left.kind == .real or right.kind == .real) {
|
|
boolean :: (
|
|
view.operator == .equal or view.operator == .not_equal or view.operator == .less
|
|
or view.operator == .less_equal or view.operator == .greater or view.operator == .greater_equal
|
|
)
|
|
kind sema.BuiltinType :: if (boolean) .bool else .float
|
|
result_type :: try builtin(state, id, kind)
|
|
return try evaluate_ir(state, id, result_type)
|
|
}
|
|
if (
|
|
left.annotation != no_node and right.annotation != no_node
|
|
and left.kind == .integer and right.kind == .integer
|
|
and !try same_type(state, left.annotation, right.annotation)
|
|
) return Value{}
|
|
if (view.operator == .equal or view.operator == .not_equal) {
|
|
if (left.kind != right.kind) return Value{}
|
|
equal :: try value_equal(state, left, right)
|
|
if (left.kind == .type) {
|
|
operands [2]ast.NodeId :: [view.left, view.right]
|
|
for (operands) |operand| {
|
|
if (type_record_storage(state, operand, 0)) continue
|
|
constraint :: try ast.add_type_operand(state.builder,
|
|
ast.node_span(&state.tree, operand), operand, true)
|
|
try refresh(state)
|
|
try arraylist.append(&state.rewrites.constraints, constraint)
|
|
}
|
|
folded :: try ast.add_boolean(state.builder, ast.node_span(&state.tree, id),
|
|
equal == (view.operator == .equal))
|
|
try replace(state, id, folded)
|
|
try refresh(state)
|
|
}
|
|
return Value{ kind = .boolean, node = id, boolean = equal == (view.operator == .equal) }
|
|
}
|
|
if (left.kind == .boolean and right.kind == .boolean) {
|
|
if (view.operator == .logical_and) return Value{ kind = .boolean, node = id, boolean = left.boolean and right.boolean }
|
|
if (view.operator == .logical_or) return Value{ kind = .boolean, node = id, boolean = left.boolean or right.boolean }
|
|
return Value{}
|
|
}
|
|
if (left.kind != .integer or right.kind != .integer) return Value{}
|
|
annotation :: if (left.annotation != no_node) left.annotation else right.annotation
|
|
less :: if (left.negative != right.negative) left.negative
|
|
else if (left.negative) left.magnitude > right.magnitude else left.magnitude < right.magnitude
|
|
equal :: left.magnitude == right.magnitude and left.negative == right.negative
|
|
match view.operator {
|
|
.less: return Value{ kind = .boolean, node = id, boolean = less }
|
|
.less_equal: return Value{ kind = .boolean, node = id, boolean = less or equal }
|
|
.greater: return Value{ kind = .boolean, node = id, boolean = !less and !equal }
|
|
.greater_equal: return Value{ kind = .boolean, node = id, boolean = !less }
|
|
.multiply: {
|
|
if (right.magnitude != 0 and left.magnitude > divtrunc!(maxval!(u64), right.magnitude)) return Value{}
|
|
magnitude :: left.magnitude * right.magnitude
|
|
return try integer_value(state, id, magnitude, magnitude != 0 and left.negative != right.negative, annotation)
|
|
}
|
|
.add, .subtract: {
|
|
if (view.operator == .subtract and right.magnitude != 0) right.negative = !right.negative
|
|
if (left.negative == right.negative) {
|
|
if (left.magnitude > maxval!(u64) - right.magnitude) return Value{}
|
|
return try integer_value(state, id, left.magnitude + right.magnitude, left.negative, annotation)
|
|
}
|
|
if (left.magnitude < right.magnitude) return try integer_value(state, id, right.magnitude - left.magnitude, right.negative, annotation)
|
|
return try integer_value(state, id, left.magnitude - right.magnitude,
|
|
left.magnitude != right.magnitude and left.negative, annotation)
|
|
}
|
|
else: return Value{}
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
canonical_type proc(state @mut State, id ast.NodeId) ast.NodeId ! Error {
|
|
value :: try evaluate(state, id)
|
|
return if (value.kind == .type) value.node else no_node
|
|
}
|
|
|
|
# metadata storage compares anonymous annotation shapes, including ordinary nested
|
|
# fields. named definitions and nominal type results retain their original identity.
|
|
@hide:file
|
|
# ponytail: scan declaration syntax for nominal identity; index record origins if metadata-heavy builds make this hot.
|
|
structural_type_record proc(state @State, id ast.NodeId) bool {
|
|
for (state.instances.items) |instance| {
|
|
if (instance.type == id) return false
|
|
}
|
|
for (0..ast.node_count(&state.tree)) |i| {
|
|
declaration :: ast.NodeId(i)
|
|
if (ast.node_class(&state.tree, declaration) != .statement
|
|
or ast.stmt_kind(&state.tree, declaration) != .declaration) continue
|
|
if (ast.statement_decl(&state.tree, declaration).initializer == id) return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
@hide:file
|
|
same_type proc(state @mut State, left, right ast.NodeId) bool ! Error {
|
|
return try same_type_context(state, left, right, false)
|
|
}
|
|
|
|
@hide:file
|
|
same_type_context proc(state @mut State, left, right ast.NodeId, metadata bool) bool ! Error {
|
|
if (is_type(state, left) or is_type(state, right))
|
|
return is_type(state, left) and is_type(state, right)
|
|
a :: try canonical_type(state, left)
|
|
b :: try canonical_type(state, right)
|
|
if (a == no_node or b == no_node) return false
|
|
if (a == b) return true
|
|
x :: binding(state, a)
|
|
y :: binding(state, b)
|
|
if (x.kind == .builtin or y.kind == .builtin) return x.kind == .builtin and y.kind == .builtin and builtin_identity(x.data.builtin) == builtin_identity(y.data.builtin)
|
|
kind :: ast.expr_kind(&state.tree, a)
|
|
if (kind == .sum_type or ast.expr_kind(&state.tree, b) == .sum_type)
|
|
return try type_subset(state, a, b) and try type_subset(state, b, a)
|
|
if (kind != ast.expr_kind(&state.tree, b)) return false
|
|
match kind {
|
|
.union_tag_type: return try same_type_context(state,
|
|
ast.type_operand(&state.tree, a), ast.type_operand(&state.tree, b), metadata)
|
|
.record_type: {
|
|
if (!metadata and (!type_record_storage(state, a, 0) or !type_record_storage(state, b, 0))) return false
|
|
if (!structural_type_record(state, a) or !structural_type_record(state, b)) return false
|
|
count :: ast.record_field_count(&state.tree, a)
|
|
if (count != ast.record_field_count(&state.tree, b)) return false
|
|
for (0..count) |i| {
|
|
p :: ast.field(&state.tree, ast.record_field(&state.tree, a, i))
|
|
q :: ast.field(&state.tree, ast.record_field(&state.tree, b, i))
|
|
if ((p.name == null) != (q.name == null)) return false
|
|
if (p.name != null and ast.name_id(&state.tree, p.name?) != ast.name_id(&state.tree, q.name?)) return false
|
|
if (p.type == null or q.type == null or !try same_type_context(state, p.type?, q.type?, true)) return false
|
|
}
|
|
return true
|
|
}
|
|
.pointer_type: {
|
|
p :: ast.pointer_type(&state.tree, a)
|
|
q :: ast.pointer_type(&state.tree, b)
|
|
return p.mutable == q.mutable and p.many == q.many and try same_type_context(state, p.element, q.element, metadata)
|
|
}
|
|
.array_type: {
|
|
p :: ast.array_type(&state.tree, a)
|
|
q :: ast.array_type(&state.tree, b)
|
|
if (p.mutable != q.mutable or p.count == null or q.count == null) return false
|
|
pv :: try evaluate(state, p.count?)
|
|
qv :: try evaluate(state, q.count?)
|
|
return try value_equal(state, pv, qv) and try same_type_context(state, p.element, q.element, metadata)
|
|
}
|
|
.slice_type: {
|
|
p :: ast.slice_type(&state.tree, a)
|
|
q :: ast.slice_type(&state.tree, b)
|
|
if (p.mutable != q.mutable or (p.sentinel == null) != (q.sentinel == null)) return false
|
|
if (p.sentinel) |sentinel| {
|
|
if (!try value_equal(state, try evaluate(state, sentinel), try evaluate(state, q.sentinel?))) return false
|
|
}
|
|
return try same_type_context(state, p.element, q.element, metadata)
|
|
}
|
|
.optional_type: return try same_type_context(state, ast.type_operand(&state.tree, a), ast.type_operand(&state.tree, b), metadata)
|
|
.fallible_type: {
|
|
p :: ast.binary_expr(&state.tree, a)
|
|
q :: ast.binary_expr(&state.tree, b)
|
|
return try same_type_context(state, p.left, q.left, metadata) and try same_type_context(state, p.right, q.right, metadata)
|
|
}
|
|
.function_type: {
|
|
count :: ast.function_param_count(&state.tree, a)
|
|
if (count != ast.function_param_count(&state.tree, b)) return false
|
|
if (!try same_type_context(state, ast.callable_result(&state.tree, a), ast.callable_result(&state.tree, b), metadata)) return false
|
|
for (0..count) |i| {
|
|
p :: ast.param(&state.tree, ast.function_param(&state.tree, a, i)).type
|
|
q :: ast.param(&state.tree, ast.function_param(&state.tree, b, i)).type
|
|
if (!try same_type_context(state, p, q, metadata)) return false
|
|
}
|
|
return true
|
|
}
|
|
else: return false
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
type_subset proc(state @mut State, left, right ast.NodeId) bool ! Error {
|
|
a :: try canonical_type(state, left)
|
|
b :: try canonical_type(state, right)
|
|
if (a == no_node or b == no_node) return false
|
|
if (ast.expr_kind(&state.tree, a) == .sum_type) {
|
|
view :: ast.binary_expr(&state.tree, a)
|
|
return try type_subset(state, view.left, b) and try type_subset(state, view.right, b)
|
|
}
|
|
if (ast.expr_kind(&state.tree, b) == .sum_type) {
|
|
view :: ast.binary_expr(&state.tree, b)
|
|
return try type_subset(state, a, view.left) or try type_subset(state, a, view.right)
|
|
}
|
|
return try same_type(state, a, b)
|
|
}
|
|
|
|
@hide:file
|
|
value_equal proc(state @mut State, left, right Value) bool ! Error {
|
|
if (left.kind != right.kind) return false
|
|
return match left.kind {
|
|
.invalid, .null: true
|
|
.type: try same_type(state, left.node, right.node)
|
|
.integer: left.magnitude == right.magnitude and left.negative == right.negative
|
|
.boolean: left.boolean == right.boolean
|
|
.function: binding(state, left.node).data.declaration == binding(state, right.node).data.declaration
|
|
.real: left.magnitude == right.magnitude
|
|
.aggregate: (
|
|
try same_type(state, left.annotation, right.annotation)
|
|
and try constant_equal(state, left.node, right.node)
|
|
)
|
|
.string: try same_string(state, left.node, right.node)
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
value_hash proc(state @mut State, value Value) usize ! Error {
|
|
hash :: enum_index(value.kind)
|
|
negative :: if (value.negative) usize(1) else usize(0)
|
|
boolean :: if (value.boolean) usize(1) else usize(0)
|
|
return match value.kind {
|
|
.type: mix(hash, try type_hash(state, value.node))
|
|
.integer: mix(mix(hash, usize(value.magnitude)), negative)
|
|
.boolean: mix(hash, boolean)
|
|
.function: mix(hash, usize(binding(state, value.node).data.declaration))
|
|
.string: string_hash(hash, ast.literal_text(&state.tree, value.node))
|
|
.real: mix(hash, usize(value.magnitude))
|
|
.aggregate: mix(mix(hash, try type_hash(state, value.annotation)), try constant_hash(state, value.node))
|
|
.invalid, .null: hash
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
string_hash proc(seed usize, text []u8) usize {
|
|
result := seed
|
|
cursor := usize(1)
|
|
while (cursor < text.len - 1) {
|
|
byte :: ast.string_next(text, &cursor) catch { return 0 }
|
|
result = mix(result, usize(byte))
|
|
}
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
type_hash proc(state @mut State, id ast.NodeId) usize ! Error {
|
|
return try type_hash_context(state, id, false)
|
|
}
|
|
|
|
@hide:file
|
|
type_hash_context proc(state @mut State, id ast.NodeId, metadata bool) usize ! Error {
|
|
node :: try canonical_type(state, id)
|
|
if (node == no_node) return 0
|
|
resolved :: binding(state, node)
|
|
if (resolved.kind == .builtin) return mix(1, enum_index(builtin_identity(resolved.data.builtin)))
|
|
kind :: ast.expr_kind(&state.tree, node)
|
|
hash := enum_index(kind)
|
|
match kind {
|
|
.union_tag_type: return mix(hash,
|
|
try type_hash_context(state, ast.type_operand(&state.tree, node), metadata))
|
|
.record_type: {
|
|
if ((!metadata and !type_record_storage(state, node, 0))
|
|
or !structural_type_record(state, node)) return mix(hash, usize(node))
|
|
for (0..ast.record_field_count(&state.tree, node)) |i| {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, node, i))
|
|
if (field.name) |name| hash = mix(hash, usize(ast.name_id(&state.tree, name)))
|
|
if (field.type) |annotation| hash = mix(hash, try type_hash_context(state, annotation, true))
|
|
}
|
|
return hash
|
|
}
|
|
.pointer_type: {
|
|
view :: ast.pointer_type(&state.tree, node)
|
|
mutable :: if (view.mutable) usize(1) else usize(0)
|
|
many :: if (view.many) usize(1) else usize(0)
|
|
hash = mix(hash, mutable)
|
|
hash = mix(hash, many)
|
|
return mix(hash, try type_hash_context(state, view.element, metadata))
|
|
}
|
|
.array_type: {
|
|
view :: ast.array_type(&state.tree, node)
|
|
mutable :: if (view.mutable) usize(1) else usize(0)
|
|
hash = mix(hash, mutable)
|
|
if (view.count) |count| hash = mix(hash, try value_hash(state, try evaluate(state, count)))
|
|
return mix(hash, try type_hash_context(state, view.element, metadata))
|
|
}
|
|
.slice_type: {
|
|
view :: ast.slice_type(&state.tree, node)
|
|
mutable :: if (view.mutable) usize(1) else usize(0)
|
|
hash = mix(hash, mutable)
|
|
if (view.sentinel) |sentinel| hash = mix(hash, try value_hash(state, try evaluate(state, sentinel)))
|
|
return mix(hash, try type_hash_context(state, view.element, metadata))
|
|
}
|
|
.optional_type: return mix(hash, try type_hash_context(state, ast.type_operand(&state.tree, node), metadata))
|
|
.sum_type: {
|
|
view :: ast.binary_expr(&state.tree, node)
|
|
# set fingerprints must ignore ordering, grouping and repeated members.
|
|
# colliding fingerprints still use full type equality in the cache chain.
|
|
return try type_hash_context(state, view.left, metadata) | try type_hash_context(state, view.right, metadata)
|
|
}
|
|
.fallible_type: {
|
|
view :: ast.binary_expr(&state.tree, node)
|
|
return mix(mix(hash, try type_hash_context(state, view.left, metadata)), try type_hash_context(state, view.right, metadata))
|
|
}
|
|
.function_type: {
|
|
hash = mix(hash, try type_hash_context(state, ast.callable_result(&state.tree, node), metadata))
|
|
for (0..ast.function_param_count(&state.tree, node)) |i| {
|
|
parameter :: ast.function_param(&state.tree, node, i)
|
|
hash = mix(hash, try type_hash_context(state, ast.param(&state.tree, parameter).type, metadata))
|
|
}
|
|
return hash
|
|
}
|
|
else: return mix(hash, usize(node))
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
value_block_yield proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId ! Error {
|
|
if (depth >= 128) return no_node
|
|
class :: ast.node_class(&state.tree, id)
|
|
if (class != .block and class != .statement) return no_node
|
|
if (class == .statement) {
|
|
kind :: ast.stmt_kind(&state.tree, id)
|
|
if (kind == .yield_stmt)
|
|
return ast.yield_stmt(&state.tree, id).value?
|
|
if (kind != .block_stmt and kind != .if_stmt and kind != .while_stmt and kind != .for_stmt)
|
|
return no_node
|
|
}
|
|
result := no_node
|
|
for (0..ast.child_count(&state.tree, id)) |i| {
|
|
child :: ast.child(&state.tree, id, i)
|
|
value :: try value_block_yield(state, child, depth + 1)
|
|
if (value == no_node or try expression_type(state, value, depth + 1) == no_node) continue
|
|
result = value
|
|
if (!flexible(state, value)) return result
|
|
}
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
value_block_type proc(state @mut State, block ast.NodeId, depth usize) ast.NodeId ! Error {
|
|
value :: try value_block_yield(state, block, depth)
|
|
return if (value != no_node) try expression_type(state, value, depth + 1)
|
|
else try builtin(state, block, .void)
|
|
}
|
|
|
|
@hide:file
|
|
expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId ! Error {
|
|
if (id == no_node) return no_node
|
|
if (depth >= 128) return no_node
|
|
try expand(state, id)
|
|
resolved :: binding(state, id)
|
|
if (resolved.kind == .builtin and resolved.data.builtin != .type) return try builtin(state, id, .type)
|
|
if (resolved.kind == .declaration) {
|
|
target :: resolved.data.declaration
|
|
if (hashmap.get(&state.capture_types, target)) |annotation| return try canonical_type(state, annotation)
|
|
class :: ast.node_class(&state.tree, target)
|
|
if (class == .parameter) {
|
|
annotation :: ast.param(&state.tree, target).type
|
|
return try canonical_type(state, annotation)
|
|
}
|
|
if (class == .statement) {
|
|
declaration :: ast.statement_decl(&state.tree, target)
|
|
if (declaration.type) |annotation| return try canonical_type(state, annotation)
|
|
return try expression_type(state, declaration.initializer, depth + 1)
|
|
}
|
|
if (class == .function) {
|
|
params := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(¶ms)
|
|
for (0..ast.function_param_count(&state.tree, target)) |i|
|
|
try arraylist.append(¶ms, ast.function_param(&state.tree, target, i))
|
|
result :: try ast.add_function_type(state.builder, ast.node_span(&state.tree, id),
|
|
ast.function_decl(&state.tree, target).return_type, params.items)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
return no_node
|
|
}
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
match kind {
|
|
.record_type, .array_type, .slice_type, .pointer_type, .function_type, .optional_type,
|
|
.enum_type, .union_type, .union_tag_type, .distinct_type, .fallible_type, .sum_type, .alias_expr:
|
|
return try builtin(state, id, .type)
|
|
.typed_constant, .union_constant: {
|
|
annotation :: if (kind == .union_constant) ast.union_constant(&state.tree, id).type
|
|
else ast.typed_constant(&state.tree, id).type
|
|
return if (is_type(state, annotation)) annotation else try canonical_type(state, annotation)
|
|
}
|
|
.literal_int: return try builtin(state, id, .i64)
|
|
.literal_float: return try builtin(state, id, .f64)
|
|
.literal_bool: return try builtin(state, id, .bool)
|
|
.literal_string: return try builtin(state, id, .string)
|
|
.literal_void: return try builtin(state, id, .void)
|
|
.block_expr: return try value_block_type(state, ast.block_expr(&state.tree, id), depth + 1)
|
|
.match_expr: {
|
|
result := no_node
|
|
for (0..ast.match_arm_count(&state.tree, id)) |i| {
|
|
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, id, i))
|
|
annotation :: if (arm.expression) try expression_type(state, arm.body, depth + 1)
|
|
else try value_block_type(state, arm.body, depth + 1)
|
|
if (annotation == no_node) continue
|
|
result = annotation
|
|
if (!arm.expression or !flexible(state, arm.body)) return result
|
|
}
|
|
return result
|
|
}
|
|
.builtin_call: {
|
|
name :: ast.literal_text(&state.tree, ast.builtin_call(&state.tree, id).name)
|
|
count :: ast.call_arg_count(&state.tree, id)
|
|
if ((mem.eql(name, "sizeof") or mem.eql(name, "alignof")) and count == 1)
|
|
return try builtin(state, id, .usize)
|
|
if ((mem.eql(name, "maxval") or mem.eql(name, "minval")) and count == 1)
|
|
return try canonical_type(state, ast.call_arg(&state.tree, id, 0))
|
|
if ((mem.eql(name, "ptrcast") or mem.eql(name, "constcast")) and count == 2)
|
|
return try canonical_type(state, ast.call_arg(&state.tree, id, 0))
|
|
if (mem.eql(name, "divtrunc") and count == 2) {
|
|
left :: ast.call_arg(&state.tree, id, 0)
|
|
right :: ast.call_arg(&state.tree, id, 1)
|
|
operand :: if (flexible(state, left)) right else left
|
|
return try expression_type(state, operand, depth + 1)
|
|
}
|
|
return no_node
|
|
}
|
|
.catch_expr, .try_expr: {
|
|
operand :: if (kind == .catch_expr) ast.catch_expr(&state.tree, id).operand
|
|
else ast.type_operand(&state.tree, id)
|
|
type :: try expression_type(state, operand, depth + 1)
|
|
if (type == no_node or ast.expr_kind(&state.tree, type) != .fallible_type) return no_node
|
|
return try canonical_type(state, ast.binary_expr(&state.tree, type).left)
|
|
}
|
|
.record_literal: {
|
|
view :: ast.record_literal(&state.tree, id)
|
|
if (view.type) |annotation| return try canonical_type(state, annotation)
|
|
fields := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&fields)
|
|
for (0..ast.record_field_count(&state.tree, id)) |i| {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, id, i))
|
|
if (field.value == null) return no_node
|
|
type :: try expression_type(state, field.value?, depth + 1)
|
|
if (type == no_node) return no_node
|
|
generated :: try ast.add_field(state.builder, field.span, field.name, type, null)
|
|
try refresh(state)
|
|
try arraylist.append(&fields, generated)
|
|
}
|
|
result :: try ast.add_record_type(state.builder, ast.node_span(&state.tree, id), fields.items)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
.array_literal: {
|
|
count :: ast.array_element_count(&state.tree, id)
|
|
if (count == 0) return no_node
|
|
element := try expression_type(state, ast.array_element(&state.tree, id, 0), depth + 1)
|
|
if (element == no_node) return no_node
|
|
# inferred values have concrete default widths even when a cast or
|
|
# procedure result used the int/float annotation shorthand.
|
|
scalar :: binding(state, element)
|
|
if (scalar.kind == .builtin) {
|
|
if (scalar.data.builtin == .int) element = try builtin(state, element, .i64)
|
|
if (scalar.data.builtin == .float) element = try builtin(state, element, .f64)
|
|
}
|
|
length :: try ast.add_integer(state.builder, ast.node_span(&state.tree, id), u64(count))
|
|
try refresh(state)
|
|
result :: try ast.add_array_type(state.builder, ast.node_span(&state.tree, id), length, element, false)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
.call: {
|
|
callee :: ast.call_expr(&state.tree, id).callee
|
|
destination :: binding(state, callee)
|
|
if (destination.kind == .builtin) return callee
|
|
if (destination.kind == .declaration and ast.node_class(&state.tree, destination.data.declaration) == .function) return try canonical_type(state, ast.function_decl(&state.tree, destination.data.declaration).return_type)
|
|
signature :: try expression_type(state, callee, depth + 1)
|
|
if (signature != no_node and is_type(state, signature)) return try canonical_type(state, callee)
|
|
if (signature != no_node and ast.expr_kind(&state.tree, signature) == .function_type)
|
|
return try canonical_type(state, ast.callable_result(&state.tree, signature))
|
|
return no_node
|
|
}
|
|
.unary: {
|
|
view :: ast.unary_expr(&state.tree, id)
|
|
operand :: try expression_type(state, view.operand, depth + 1)
|
|
if (operand == no_node) return no_node
|
|
if (view.operator == .address_of) {
|
|
mutable := false
|
|
target :: binding(state, view.operand)
|
|
if (target.kind == .declaration and ast.node_class(&state.tree, target.data.declaration) == .statement)
|
|
mutable = ast.statement_decl(&state.tree, target.data.declaration).mutability == .variable
|
|
result :: try ast.add_pointer_type(state.builder, view.span, operand, mutable, false)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
if (view.operator == .dereference and ast.expr_kind(&state.tree, operand) == .pointer_type)
|
|
return try canonical_type(state, ast.pointer_type(&state.tree, operand).element)
|
|
if (view.operator == .unwrap and ast.expr_kind(&state.tree, operand) == .optional_type)
|
|
return try canonical_type(state, ast.type_operand(&state.tree, operand))
|
|
return operand
|
|
}
|
|
.binary: {
|
|
view :: ast.binary_expr(&state.tree, id)
|
|
if (
|
|
view.operator == .equal or view.operator == .not_equal or view.operator == .less
|
|
or view.operator == .less_equal or view.operator == .greater or view.operator == .greater_equal
|
|
or view.operator == .logical_and or view.operator == .logical_or
|
|
) return try builtin(state, id, .bool)
|
|
if ((view.operator == .bit_and or view.operator == .bit_or or view.operator == .bit_xor)
|
|
and flexible(state, view.left)) return try expression_type(state, view.right, depth + 1)
|
|
return try expression_type(state, view.left, depth + 1)
|
|
}
|
|
.selection: {
|
|
view :: ast.selection_expr(&state.tree, id)
|
|
base := try expression_type(state, view.base, depth + 1)
|
|
if (base == no_node) return no_node
|
|
if (is_type(state, base)) {
|
|
selected :: try canonical_type(state, view.base)
|
|
if (selected != no_node and ast.expr_kind(&state.tree, selected) == .enum_type) return selected
|
|
}
|
|
if (ast.expr_kind(&state.tree, base) == .pointer_type) base = try canonical_type(state, ast.pointer_type(&state.tree, base).element)
|
|
if (base == no_node) return no_node
|
|
member :: ast.literal_text(&state.tree, view.member)
|
|
base_kind :: ast.expr_kind(&state.tree, base)
|
|
if (
|
|
mem.eql(member, "len") and (
|
|
base_kind == .array_type or base_kind == .slice_type
|
|
or (binding(state, base).kind == .builtin and binding(state, base).data.builtin == .string)
|
|
)
|
|
)
|
|
return try builtin(state, id, .usize)
|
|
if (mem.eql(member, "ptr")) {
|
|
element := no_node
|
|
mutable := false
|
|
if (base_kind == .array_type) {
|
|
element = ast.array_type(&state.tree, base).element
|
|
mutable = ast.array_type(&state.tree, base).mutable
|
|
}
|
|
if (base_kind == .slice_type) {
|
|
element = ast.slice_type(&state.tree, base).element
|
|
mutable = ast.slice_type(&state.tree, base).mutable
|
|
}
|
|
if (binding(state, base).kind == .builtin and binding(state, base).data.builtin == .string)
|
|
element = try builtin(state, id, .u8)
|
|
if (element != no_node) {
|
|
result :: try ast.add_pointer_type(state.builder, view.span, element, mutable, true)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
}
|
|
if (base_kind != .record_type and base_kind != .union_type) return no_node
|
|
slot :: type_record_slot(state, base, view.member)
|
|
if (slot == no_index) return no_node
|
|
return try canonical_type(state, ast.field(&state.tree, ast.record_field(&state.tree, base, slot)).type?)
|
|
}
|
|
.index: {
|
|
view :: ast.index_expr(&state.tree, id)
|
|
base :: try expression_type(state, view.base, depth + 1)
|
|
if (base == no_node) return no_node
|
|
base_kind :: ast.expr_kind(&state.tree, base)
|
|
if (base_kind == .array_type) {
|
|
element :: ast.array_type(&state.tree, base).element
|
|
return if (is_type(state, element)) element else try canonical_type(state, element)
|
|
}
|
|
if (base_kind == .slice_type) return try canonical_type(state, ast.slice_type(&state.tree, base).element)
|
|
if (base_kind == .pointer_type) return try canonical_type(state, ast.pointer_type(&state.tree, base).element)
|
|
if (binding(state, base).kind == .builtin and binding(state, base).data.builtin == .string) return try builtin(state, id, .u8)
|
|
return no_node
|
|
}
|
|
.slice: {
|
|
view :: ast.slice_expr(&state.tree, id)
|
|
base :: try expression_type(state, view.base, depth + 1)
|
|
if (base == no_node) return no_node
|
|
base_kind :: ast.expr_kind(&state.tree, base)
|
|
element := no_node
|
|
mutable := false
|
|
if (base_kind == .array_type) {
|
|
element = ast.array_type(&state.tree, base).element
|
|
mutable = ast.array_type(&state.tree, base).mutable
|
|
}
|
|
if (base_kind == .pointer_type) {
|
|
element = ast.pointer_type(&state.tree, base).element
|
|
mutable = ast.pointer_type(&state.tree, base).mutable
|
|
}
|
|
if (base_kind == .slice_type) {
|
|
element = ast.slice_type(&state.tree, base).element
|
|
mutable = ast.slice_type(&state.tree, base).mutable
|
|
}
|
|
if (element == no_node) return no_node
|
|
result :: try ast.add_slice_type(state.builder, ast.node_span(&state.tree, id), element, mutable, null)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
else: return no_node
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
parameter_index proc(state @State, function, id ast.NodeId) usize {
|
|
for (0..ast.function_param_count(&state.tree, function)) |i| {
|
|
if (ast.function_param(&state.tree, function, i) == id and ast.param(&state.tree, id).comptime) return i
|
|
}
|
|
return no_index
|
|
}
|
|
|
|
@hide:file
|
|
infer_value proc(state @mut State, function, pattern ast.NodeId, actual Value, arguments []mut Value, contextual bool) bool ! Error {
|
|
resolved :: binding(state, pattern)
|
|
if (resolved.kind == .declaration) {
|
|
index :: parameter_index(state, function, resolved.data.declaration)
|
|
if (index != no_index) {
|
|
if (arguments[index].kind == .invalid) {
|
|
arguments[index] = actual
|
|
return true
|
|
}
|
|
if (contextual and arguments[index].kind == .type and actual.kind == .type) return true
|
|
return try value_equal(state, arguments[index], actual)
|
|
}
|
|
}
|
|
return try value_equal(state, try evaluate(state, pattern), actual)
|
|
}
|
|
|
|
@hide:file
|
|
infer proc(state @mut State, function, pattern, actual ast.NodeId, arguments []mut Value, contextual bool) bool ! Error {
|
|
resolved :: binding(state, pattern)
|
|
if (resolved.kind == .declaration) {
|
|
index :: parameter_index(state, function, resolved.data.declaration)
|
|
if (index != no_index) {
|
|
if (arguments[index].kind == .type) {
|
|
expected :: try canonical_type(state, arguments[index].node)
|
|
# a fixed slice type supplies borrowing/string context; ordinary
|
|
# argument checking owns element, mutability and sentinel validation.
|
|
if (expected != no_node and ast.expr_kind(&state.tree, expected) == .slice_type) return true
|
|
}
|
|
return try infer_value(state, function, pattern, Value{ kind = .type, node = actual }, arguments, contextual)
|
|
}
|
|
}
|
|
# only normalize the actual type: the pattern may still contain unbound
|
|
# parameters or a type-generator call whose arguments must be inferred.
|
|
concrete :: try canonical_type(state, actual)
|
|
if (concrete == no_node) return false
|
|
kind :: ast.expr_kind(&state.tree, pattern)
|
|
actual_kind :: ast.expr_kind(&state.tree, concrete)
|
|
match kind {
|
|
.pointer_type: {
|
|
if (actual_kind != .pointer_type) return false
|
|
return try infer(state, function, ast.pointer_type(&state.tree, pattern).element,
|
|
ast.pointer_type(&state.tree, concrete).element, arguments, contextual)
|
|
}
|
|
.slice_type: {
|
|
element := no_node
|
|
if (actual_kind == .slice_type) element = ast.slice_type(&state.tree, concrete).element
|
|
if (actual_kind == .array_type) element = ast.array_type(&state.tree, concrete).element
|
|
if (element == no_node) return false
|
|
return try infer(state, function, ast.slice_type(&state.tree, pattern).element, element, arguments, contextual)
|
|
}
|
|
.array_type: {
|
|
if (actual_kind != .array_type) return false
|
|
p :: ast.array_type(&state.tree, pattern)
|
|
a :: ast.array_type(&state.tree, concrete)
|
|
if (p.count) |count| {
|
|
if (a.count == null or !try infer_value(state, function, count, try evaluate(state, a.count?), arguments, false)) return false
|
|
}
|
|
return try infer(state, function, p.element, a.element, arguments, contextual)
|
|
}
|
|
.optional_type: {
|
|
element :: if (actual_kind == .optional_type) ast.type_operand(&state.tree, concrete) else concrete
|
|
return try infer(state, function, ast.type_operand(&state.tree, pattern), element, arguments, contextual)
|
|
}
|
|
.fallible_type: {
|
|
if (actual_kind != .fallible_type) return false
|
|
p :: ast.binary_expr(&state.tree, pattern)
|
|
a :: ast.binary_expr(&state.tree, concrete)
|
|
return (try infer(state, function, p.left, a.left, arguments, contextual)
|
|
and try infer(state, function, p.right, a.right, arguments, contextual))
|
|
}
|
|
.function_type: {
|
|
if (actual_kind != .function_type) return false
|
|
count :: ast.function_param_count(&state.tree, pattern)
|
|
if (count != ast.function_param_count(&state.tree, concrete)) return false
|
|
for (0..count) |i| {
|
|
p :: ast.param(&state.tree, ast.function_param(&state.tree, pattern, i)).type
|
|
a :: ast.param(&state.tree, ast.function_param(&state.tree, concrete, i)).type
|
|
if (!try infer(state, function, p, a, arguments, contextual)) return false
|
|
}
|
|
return try infer(state, function, ast.callable_result(&state.tree, pattern),
|
|
ast.callable_result(&state.tree, concrete), arguments, contextual)
|
|
}
|
|
.call: {
|
|
callee :: binding(state, ast.call_expr(&state.tree, pattern).callee)
|
|
if (callee.kind != .declaration) return false
|
|
for (0..(state.instances.items.len)) |i| {
|
|
instance :: state.instances.items[i]
|
|
if (instance.type == no_node or instance.function != callee.data.declaration) continue
|
|
if (try canonical_type(state, instance.type) != concrete) continue
|
|
count :: ast.call_arg_count(&state.tree, pattern)
|
|
if (count != instance.arguments.len) return false
|
|
for (0..count) |j| {
|
|
if (!try infer_value(state, function, ast.call_arg(&state.tree, pattern, j), instance.arguments[j], arguments, contextual)) return false
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
else: return true
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
infer_result proc(
|
|
state @mut State,
|
|
function, pattern, actual ast.NodeId,
|
|
arguments []mut Value,
|
|
) bool ! Error {
|
|
concrete :: try canonical_type(state, actual)
|
|
if (concrete == no_node) return true
|
|
if (
|
|
ast.expr_kind(&state.tree, concrete) == .optional_type
|
|
and ast.expr_kind(&state.tree, pattern) != .optional_type
|
|
) {
|
|
resolved :: binding(state, pattern)
|
|
if (resolved.kind == .declaration) {
|
|
index :: parameter_index(state, function, resolved.data.declaration)
|
|
if (index != no_index) {
|
|
if (arguments[index].kind == .invalid)
|
|
return try infer(state, function, pattern, concrete, arguments, false)
|
|
if (arguments[index].kind == .type and try same_type(state, arguments[index].node, concrete)) return true
|
|
}
|
|
}
|
|
return try infer_result(state, function, pattern, ast.type_operand(&state.tree, concrete), arguments)
|
|
}
|
|
if (
|
|
ast.expr_kind(&state.tree, concrete) == .fallible_type
|
|
and ast.expr_kind(&state.tree, pattern) != .fallible_type
|
|
) return try infer_result(state, function, pattern, ast.binary_expr(&state.tree, concrete).left, arguments)
|
|
if (ast.expr_kind(&state.tree, pattern) == .fallible_type) {
|
|
view :: ast.binary_expr(&state.tree, pattern)
|
|
if (ast.expr_kind(&state.tree, concrete) != .fallible_type)
|
|
return try infer_result(state, function, view.left, concrete, arguments)
|
|
channels :: ast.binary_expr(&state.tree, concrete)
|
|
return (try infer_result(state, function, view.left, channels.left, arguments)
|
|
and try infer_result(state, function, view.right, channels.right, arguments))
|
|
}
|
|
return try infer(state, function, pattern, concrete, arguments, false)
|
|
}
|
|
|
|
@hide:file
|
|
materialize proc(state @mut State, value Value) Value ! Error {
|
|
result := value
|
|
span :: ast.node_span(&state.tree, value.node)
|
|
if (value.kind == .type and nominal_type_value(state, value.node)) {
|
|
# refer to declared nominal types, rather than reusing their definition as
|
|
# an annotation and turning its value-role checker cell into an annotation.
|
|
for (state.declarations.items) |declaration| {
|
|
if (
|
|
ast.node_class(&state.tree, declaration) == .statement
|
|
and ast.statement_decl(&state.tree, declaration).initializer == value.node
|
|
) {
|
|
result.node = try reference(state, value.node, declaration)
|
|
return result
|
|
}
|
|
}
|
|
}
|
|
if (value.kind == .integer) {
|
|
result.node = try ast.add_integer(state.builder, span, value.magnitude)
|
|
if (value.negative) result.node = try ast.add_unary(state.builder, span, .negate, result.node)
|
|
} else if (value.kind == .boolean) {
|
|
result.node = try ast.add_boolean(state.builder, span, value.boolean)
|
|
} else if (value.kind == .real) {
|
|
result.node = try ast.add_float(state.builder, span, ptrcast!(@f64, &value.magnitude)^)
|
|
}
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
valid_value proc(state @mut State, annotation ast.NodeId, value Value) bool ! Error {
|
|
type :: try canonical_type(state, annotation)
|
|
if (type == no_node) return false
|
|
resolved :: binding(state, type)
|
|
if (resolved.kind == .builtin) {
|
|
kind :: resolved.data.builtin
|
|
if (kind == .bool) return value.kind == .boolean
|
|
if (kind == .string) return value.kind == .string
|
|
if (kind == .float or kind == .f64) return value.kind == .real
|
|
if (kind == .void) return (
|
|
value.kind == .aggregate and value.annotation != no_node
|
|
and try same_type(state, type, value.annotation)
|
|
)
|
|
if (value.kind != .integer) return false
|
|
bits := u32(64)
|
|
signed := false
|
|
match kind {
|
|
.i8, .c_char: {
|
|
bits = 8
|
|
signed = true
|
|
}
|
|
.i16, .c_short: {
|
|
bits = 16
|
|
signed = true
|
|
}
|
|
.i32, .c_int: {
|
|
bits = 32
|
|
signed = true
|
|
}
|
|
.int, .i64, .isize, .c_long, .c_longlong: signed = true
|
|
.u8, .c_uchar: bits = 8
|
|
.u16, .c_ushort: bits = 16
|
|
.u32, .c_uint: bits = 32
|
|
.u64, .uint, .usize, .c_ulong, .c_ulonglong: {}
|
|
else: return false
|
|
}
|
|
limit := maxval!(u64)
|
|
if (signed) {
|
|
limit = u64(1) << (bits - 1)
|
|
if (!value.negative) limit -= 1
|
|
} else {
|
|
if (value.negative) return false
|
|
if (bits != 64) limit = (u64(1) << bits) - 1
|
|
}
|
|
return value.magnitude <= limit
|
|
}
|
|
kind :: ast.expr_kind(&state.tree, type)
|
|
if (value.kind == .aggregate) return value.annotation != no_node and try same_type(state, type, value.annotation)
|
|
if (kind == .optional_type) return value.kind == .null or try valid_value(state, ast.type_operand(&state.tree, type), value)
|
|
if (kind == .slice_type and value.kind == .string) {
|
|
view :: ast.slice_type(&state.tree, type)
|
|
child :: try canonical_type(state, view.element)
|
|
return (
|
|
!view.mutable and child != no_node and binding(state, child).kind == .builtin
|
|
and binding(state, child).data.builtin == .u8
|
|
)
|
|
}
|
|
if (kind == .function_type and value.kind == .function) {
|
|
actual :: try expression_type(state, value.node, 0)
|
|
return actual != no_node and try same_type(state, type, actual)
|
|
}
|
|
if (kind == .distinct_type) return try valid_value(state, ast.type_operand(&state.tree, type), value)
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
enum_index proc($T type, value T) usize {
|
|
match typeinfo!(T) {
|
|
.enum |info|: {
|
|
inline for (info.fields) |field| {
|
|
if (value == field!(T, field.name)) return field.index
|
|
}
|
|
unreachable
|
|
}
|
|
else: unreachable
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
remember proc(state @mut State, target ast.NodeId) void ! mem.AllocError {
|
|
if (usize(target) >= state.original_count) return
|
|
saved :: try ast.copy_node(state.builder, target)
|
|
try arraylist.append(&state.rewrites.changes, Rewrite{ target = target, saved = saved })
|
|
}
|
|
|
|
@hide:file
|
|
replace proc(state @mut State, target, source ast.NodeId) void ! mem.AllocError {
|
|
try remember(state, target)
|
|
ast.replace_node(state.builder, target, source)
|
|
}
|
|
|
|
@hide:file
|
|
builtin_identities :: $enummap.init_default(
|
|
sema.BuiltinType,
|
|
sema.BuiltinType,
|
|
sema.BuiltinType.type, {
|
|
i64 = .int, c_long = .int, c_longlong = .int,
|
|
f64 = .float,
|
|
uint = .u64, c_ulong = .u64, c_ulonglong = .u64,
|
|
c_char = .i8,
|
|
c_uchar = .u8,
|
|
c_short = .i16,
|
|
c_ushort = .u16,
|
|
c_int = .i32,
|
|
c_uint = .u32,
|
|
})
|
|
|
|
@hide:file
|
|
builtin_identity proc(kind sema.BuiltinType) sema.BuiltinType {
|
|
if (enummap.get(&builtin_identities, kind)) |identity| return identity
|
|
return kind
|
|
}
|
|
|
|
@hide:file
|
|
integer_type proc(state @mut State, annotation ast.NodeId) bool ! Error {
|
|
type :: try canonical_type(state, annotation)
|
|
if (type == no_node) return false
|
|
resolved :: binding(state, type)
|
|
if (resolved.kind == .builtin) {
|
|
return match builtin_identity(resolved.data.builtin) {
|
|
.int, .i8, .i16, .i32, .isize, .u8, .u16, .u32, .u64, .usize: true
|
|
else: false
|
|
}
|
|
}
|
|
if (ast.expr_kind(&state.tree, type) == .distinct_type) return try integer_type(state, ast.type_operand(&state.tree, type))
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
integer_value proc(state @mut State, id ast.NodeId, magnitude u64, negative bool, annotation ast.NodeId) Value ! Error {
|
|
result :: Value{ kind = .integer, node = id, magnitude = magnitude, negative = negative, annotation = annotation }
|
|
if (annotation != no_node and !try valid_value(state, annotation, result)) return Value{}
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
same_string proc(state @State, left, right ast.NodeId) bool ! Error {
|
|
a :: ast.literal_text(&state.tree, left)
|
|
b :: ast.literal_text(&state.tree, right)
|
|
ac := usize(1)
|
|
bc := usize(1)
|
|
while (ac < a.len - 1 and bc < b.len - 1) {
|
|
av :: ast.string_next(a, &ac) catch { return false }
|
|
bv :: ast.string_next(b, &bc) catch { return false }
|
|
if (av != bv) return false
|
|
}
|
|
return ac == a.len - 1 and bc == b.len - 1
|
|
}
|
|
|
|
@hide:file
|
|
flexible proc(state @State, id ast.NodeId) bool {
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
if (kind == .literal_int or kind == .literal_float) return true
|
|
if (kind == .unary) {
|
|
view :: ast.unary_expr(&state.tree, id)
|
|
return (view.operator == .negate or view.operator == .bit_not) and flexible(state, view.operand)
|
|
}
|
|
if (kind == .binary) {
|
|
view :: ast.binary_expr(&state.tree, id)
|
|
return (
|
|
(view.operator == .add or view.operator == .subtract or view.operator == .multiply or view.operator == .divide
|
|
or view.operator == .bit_and or view.operator == .bit_or or view.operator == .bit_xor
|
|
or view.operator == .shift_left or view.operator == .shift_right or view.operator == .shift_left_saturating)
|
|
and flexible(state, view.left) and flexible(state, view.right)
|
|
)
|
|
}
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
template proc(state @State, id ast.NodeId) bool {
|
|
return ast.function_generic(&state.tree, id) or compiletime_result(state, ast.function_decl(&state.tree, id).return_type)
|
|
}
|