7985 lines
383 KiB
Plaintext
7985 lines
383 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
|
|
demanded bool = false
|
|
}
|
|
|
|
@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
|
|
demand_depth usize = 0
|
|
snapshot_uninitialized bool = false
|
|
demanded_functions []mut u8
|
|
execution_functions []mut u8
|
|
execution_visited arraylist.ArrayList(ast.NodeId)
|
|
}
|
|
|
|
#! 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
|
|
for (0..ast.function_param_count(&original, id)) |position| {
|
|
parameter :: ast.param(&original, ast.function_param(&original, id, position))
|
|
if (!parameter.comptime and type_storage_annotation(&original,
|
|
resolution.storage.bindings, parameter.type, 0)) needed = true
|
|
}
|
|
if (!ast.function_external(&original, id)
|
|
and type_local_storage(&original, resolution.storage.bindings,
|
|
ast.function_decl(&original, id).body, 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)
|
|
demanded_functions :: try mem.alloc(u8, allocator, ast.node_count(&original))
|
|
defer mem.free(allocator, demanded_functions)
|
|
mem.set(demanded_functions, 0)
|
|
execution_functions :: try mem.alloc(u8, allocator, ast.node_count(&original))
|
|
defer mem.free(allocator, execution_functions)
|
|
mem.set(execution_functions, 0)
|
|
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,
|
|
demanded_functions = demanded_functions,
|
|
execution_functions = execution_functions,
|
|
execution_visited = arraylist.init(allocator),
|
|
}
|
|
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 arraylist.deinit(&state.execution_visited)
|
|
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)))
|
|
try collect_demanded_functions(&state, root, false)
|
|
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) or state.demanded_functions[usize(id)] == 1)) 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)
|
|
}
|
|
|
|
# ordinary constant calls retain one interpreter frame and its aliases. only
|
|
# bodies whose type-control operations need value-dependent execution use snapshots.
|
|
@hide:file
|
|
metadata_capture proc(state @State, id, capture ast.NodeId) bool {
|
|
if (ast.node_class(&state.tree, id) == .expression and ast.expr_kind(&state.tree, id) == .match_expr) {
|
|
subject :: ast.match_expr(&state.tree, id).subject
|
|
if (ast.expr_kind(&state.tree, subject) == .builtin_call
|
|
and mem.eql(ast.literal_text(&state.tree, ast.builtin_call(&state.tree, subject).name), "typeinfo")) {
|
|
for (0..ast.match_arm_count(&state.tree, id)) |i| {
|
|
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, id, i))
|
|
if (arm.capture != null and arm.capture? == capture) return true
|
|
}
|
|
}
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i|
|
|
if (metadata_capture(state, ast.child(&state.tree, id, i), capture)) return true
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
runtime_dependency proc(state @State, id, body ast.NodeId, depth usize) bool {
|
|
if (depth >= 128) return true
|
|
resolved :: binding(state, id)
|
|
if (resolved.kind == .declaration) {
|
|
declaration :: resolved.data.declaration
|
|
class :: ast.node_class(&state.tree, declaration)
|
|
if (class == .parameter) return !ast.param(&state.tree, declaration).comptime
|
|
if (class == .statement) {
|
|
view :: ast.statement_decl(&state.tree, declaration)
|
|
return view.mutability == .variable or runtime_dependency(state, view.initializer, body, depth + 1)
|
|
}
|
|
if (class == .expression) return !metadata_capture(state, body, declaration)
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i|
|
|
if (runtime_dependency(state, ast.child(&state.tree, id, i), body, depth + 1)) return true
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
scan_demand_execution proc(state @mut State, id, body ast.NodeId, static_control bool) bool ! mem.AllocError {
|
|
class :: ast.node_class(&state.tree, id)
|
|
if (static_control and class == .statement and ast.stmt_kind(&state.tree, id) == .if_stmt) {
|
|
conditional :: ast.if_stmt(&state.tree, id)
|
|
if (try scan_demand_execution(state, conditional.condition, body, true)) return true
|
|
selected_statically :: !runtime_dependency(state, conditional.condition, body, 0)
|
|
if (try scan_demand_execution(state, conditional.then_block, body, selected_statically)) return true
|
|
if (conditional.else_block) |other|
|
|
if (try scan_demand_execution(state, other, body, selected_statically)) return true
|
|
return false
|
|
}
|
|
if (class == .expression) {
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
if (!static_control and kind == .builtin_call
|
|
and mem.eql(ast.literal_text(&state.tree, ast.builtin_call(&state.tree, id).name), "compile_error")) return true
|
|
if (kind == .match_expr) {
|
|
subject :: ast.match_expr(&state.tree, id).subject
|
|
if (ast.expr_kind(&state.tree, subject) == .builtin_call
|
|
and mem.eql(ast.literal_text(&state.tree, ast.builtin_call(&state.tree, subject).name), "typeinfo")) {
|
|
for (0..ast.match_arm_count(&state.tree, id)) |i| {
|
|
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, id, i))
|
|
if (try scan_demand_execution(state, arm.body, body, true)) return true
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
if (kind == .index) {
|
|
view :: ast.index_expr(&state.tree, id)
|
|
root := view.base
|
|
while (ast.expr_kind(&state.tree, root) == .selection)
|
|
root = ast.selection_expr(&state.tree, root).base
|
|
resolved :: binding(state, root)
|
|
if (resolved.kind == .declaration and runtime_dependency(state, view.index, body, 0)
|
|
and metadata_capture(state, body, resolved.data.declaration)) return true
|
|
}
|
|
if (kind == .call) {
|
|
callee :: binding(state, ast.call_expr(&state.tree, id).callee)
|
|
if (callee.kind == .declaration and usize(callee.data.declaration) < state.original_count
|
|
and ast.node_class(&state.tree, callee.data.declaration) == .function
|
|
and !ast.function_external(&state.tree, callee.data.declaration)
|
|
and !compiletime_call(state, callee.data.declaration)) {
|
|
function :: callee.data.declaration
|
|
flag :: state.execution_functions[usize(function)]
|
|
if (flag == 2) return true
|
|
if (flag == 0) {
|
|
try arraylist.append(&state.execution_visited, function)
|
|
state.execution_functions[usize(function)] = 3
|
|
called_body :: ast.function_decl(&state.tree, function).body
|
|
if (try scan_demand_execution(state, called_body, called_body, false)) return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i|
|
|
if (try scan_demand_execution(state, ast.child(&state.tree, id, i), body, static_control)) return true
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
needs_demand_execution proc(state @mut State, function ast.NodeId) bool ! mem.AllocError {
|
|
if (usize(function) >= state.original_count) return false
|
|
flag :: state.execution_functions[usize(function)]
|
|
if (flag == 1) return false
|
|
if (flag == 2) return true
|
|
defer {
|
|
for (state.execution_visited.items) |visited| {
|
|
if (state.execution_functions[usize(visited)] == 3) state.execution_functions[usize(visited)] = 0
|
|
}
|
|
state.execution_visited.items = state.execution_visited.items[..0]
|
|
}
|
|
try arraylist.append(&state.execution_visited, function)
|
|
state.execution_functions[usize(function)] = 3
|
|
body :: ast.function_decl(&state.tree, function).body
|
|
required :: try scan_demand_execution(state, body, body, false)
|
|
if (!required) {
|
|
for (state.execution_visited.items) |visited| state.execution_functions[usize(visited)] = 1
|
|
} else state.execution_functions[usize(function)] = 2
|
|
return required
|
|
}
|
|
|
|
# find demanded callees before walking runtime declarations, independent of source
|
|
# order. a later runtime reference retains the original declaration as usual.
|
|
@hide:file
|
|
collect_demanded_functions proc(state @mut State, id ast.NodeId, demanded bool) void ! mem.AllocError {
|
|
class :: ast.node_class(&state.tree, id)
|
|
demand := demanded
|
|
if (class == .statement and ast.stmt_kind(&state.tree, id) == .for_stmt
|
|
and ast.for_stmt(&state.tree, id).is_inline) {
|
|
loop :: ast.for_stmt(&state.tree, id)
|
|
try collect_demanded_functions(state, loop.iterable, true)
|
|
try collect_demanded_functions(state, loop.body, demanded)
|
|
return
|
|
}
|
|
if (class == .expression) {
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
if (kind == .comptime_expr) demand = true
|
|
if (kind == .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) {
|
|
function :: callee.data.declaration
|
|
if (demand and !ast.function_external(&state.tree, function)
|
|
and state.demanded_functions[usize(function)] == 0
|
|
and try needs_demand_execution(state, function)) {
|
|
state.demanded_functions[usize(function)] = 1
|
|
try collect_demanded_functions(state, ast.function_decl(&state.tree, function).body, true)
|
|
}
|
|
if (ast.call_arg_count(&state.tree, id) == ast.function_param_count(&state.tree, function)) {
|
|
for (0..ast.call_arg_count(&state.tree, id)) |i| {
|
|
parameter :: ast.param(&state.tree, ast.function_param(&state.tree, function, i))
|
|
try collect_demanded_functions(state, ast.call_arg(&state.tree, id, i), demand or parameter.comptime)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i|
|
|
try collect_demanded_functions(state, ast.child(&state.tree, id, i), demand)
|
|
}
|
|
|
|
@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.demand_depth == 0) {
|
|
resolved :: binding(state, id)
|
|
if (resolved.kind == .declaration and usize(resolved.data.declaration) < state.original_count) {
|
|
declaration :: resolved.data.declaration
|
|
if (state.demanded_functions[usize(declaration)] == 1 and !template(state, declaration)) {
|
|
state.demanded_functions[usize(declaration)] = 2
|
|
try arraylist.append(&state.declarations, declaration)
|
|
}
|
|
}
|
|
}
|
|
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 (!ast.function_external(&state.tree, id)
|
|
and type_local_storage(&state.tree, state.bindings.items,
|
|
ast.function_decl(&state.tree, id).body, 0)
|
|
and !compiletime_call(state, id)) {
|
|
try expand_runtime_metadata(state, id)
|
|
return
|
|
}
|
|
}
|
|
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)
|
|
operand_type :: try expression_type(state, expression.operand, 0)
|
|
if (type_record_storage(state, operand_type, 0)
|
|
and ast.expr_kind(&state.tree, operand_type) == .fallible_type) {
|
|
try specialize_type_catch(state, id, operand_type, expected)
|
|
return
|
|
}
|
|
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) {
|
|
state.demand_depth += 1
|
|
defer state.demand_depth -= 1
|
|
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)
|
|
or (state.demand_depth > 0 and usize(resolved.data.declaration) < state.original_count
|
|
and !ast.function_external(&state.tree, resolved.data.declaration)
|
|
and try needs_demand_execution(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.tree, 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) {
|
|
operand := no_node
|
|
if (ast.expr_kind(&state.tree, view.left) == .literal_null) operand = view.right
|
|
else if (ast.expr_kind(&state.tree, view.right) == .literal_null) operand = view.left
|
|
if (operand != no_node) {
|
|
annotation :: try expression_type(state, operand, 0)
|
|
if (type_record_storage(state, annotation, 0)
|
|
and ast.expr_kind(&state.tree, annotation) == .optional_type) {
|
|
value :: try evaluate_expected(state, operand, annotation)
|
|
if (value.kind != .aggregate) {
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
absent :: ast.expr_kind(&state.tree, value.node) == .literal_null
|
|
folded :: try ast.add_boolean(state.builder, view.span,
|
|
absent == (view.operator == .equal))
|
|
try replace(state, id, folded)
|
|
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)
|
|
return try metadata_variant_tags(state, type)
|
|
}
|
|
|
|
# dispatch covers union variants, not unused members of an explicit tag enum.
|
|
@hide:file
|
|
metadata_variant_tags proc(state @mut State, type ast.NodeId) ast.NodeId ! Error {
|
|
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)
|
|
for (fields.items) |previous| {
|
|
field :: ast.field(&state.tree, previous)
|
|
if (ast.name_id(&state.tree, field.name?) != ast.name_id(&state.tree, name)) continue
|
|
return try same_type(state, field.type?, type)
|
|
}
|
|
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
|
|
if (kind == .enum_type and ast.enum_type(&state.tree, type).backing != null) return false
|
|
if (kind == .union_type and !ast.union_type(&state.tree, type).auto_tag
|
|
and ast.union_type(&state.tree, type).tag == null) 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| {
|
|
name :: ast.field(&state.tree, previous).name?
|
|
if (ast.name_id(&state.tree, name) != ast.name_id(&state.tree, field.name?)) continue
|
|
if (name != field.name?) return false
|
|
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
|
|
annotation := subject_type
|
|
if (annotation == no_node) {
|
|
try expand(state, subject)
|
|
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, members_type)
|
|
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.tree, 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
|
|
}
|
|
|
|
# implicit probing is limited to constants and effect-free, fully bound type
|
|
# helpers. ordinary runtime calls still require an explicit evaluation request.
|
|
@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, .slice_constant: return true
|
|
.typed_constant: return branch_constant(state, ast.typed_constant(&state.tree, id).value, depth + 1)
|
|
.enum_literal: return true
|
|
.call: return branch_type_call(state, id, depth + 1)
|
|
.block_expr: return branch_body_constant(state, ast.block_expr(&state.tree, id), depth + 1)
|
|
.builtin_call: {
|
|
name :: ast.literal_text(&state.tree, ast.builtin_call(&state.tree, id).name)
|
|
if (ast.call_arg_count(&state.tree, id) != 1
|
|
or (!mem.eql(name, "minval") and !mem.eql(name, "maxval")
|
|
and !mem.eql(name, "sizeof") and !mem.eql(name, "alignof"))) return false
|
|
return branch_constant(state, ast.call_arg(&state.tree, id, 0), depth + 1)
|
|
}
|
|
.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
|
|
branch_type_call proc(state @State, id ast.NodeId, depth usize) bool {
|
|
if (depth >= 128 or ast.call_arg_count(&state.tree, id) != 0) return false
|
|
callee :: binding(state, ast.call_expr(&state.tree, id).callee)
|
|
if (callee.kind != .declaration) return false
|
|
function :: callee.data.declaration
|
|
if (ast.node_class(&state.tree, function) != .function
|
|
or ast.function_external(&state.tree, function)
|
|
or ast.function_param_count(&state.tree, function) != 0) return false
|
|
for (state.instances.items) |instance| {
|
|
if (instance.declaration != function) continue
|
|
for (0..ast.function_param_count(&state.tree, instance.function)) |i| {
|
|
parameter :: ast.param(&state.tree, ast.function_param(&state.tree, instance.function, i))
|
|
if (parameter.comptime and is_type(state, parameter.type))
|
|
return branch_body_constant(state, ast.function_decl(&state.tree, function).body, depth + 1)
|
|
}
|
|
return false
|
|
}
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
branch_body_constant proc(state @State, id ast.NodeId, depth usize) bool {
|
|
if (depth >= 128) return false
|
|
if (ast.node_class(&state.tree, id) == .block) {
|
|
for (0..ast.block_statement_count(&state.tree, id)) |i| {
|
|
if (!branch_body_constant(state, ast.block_statement(&state.tree, id, i), depth + 1)) return false
|
|
}
|
|
return true
|
|
}
|
|
if (ast.node_class(&state.tree, id) != .statement) return false
|
|
match ast.stmt_kind(&state.tree, id) {
|
|
.return_stmt, .yield_stmt: {
|
|
value :: if (ast.stmt_kind(&state.tree, id) == .yield_stmt)
|
|
ast.yield_stmt(&state.tree, id).value else ast.return_stmt(&state.tree, id).value
|
|
return value == null or branch_constant(state, value?, depth + 1)
|
|
}
|
|
.block_stmt: return branch_body_constant(state, ast.statement_block(&state.tree, id), depth + 1)
|
|
.expression: return branch_constant(state, ast.statement_expr(&state.tree, id), depth + 1)
|
|
.declaration: {
|
|
declaration :: ast.statement_decl(&state.tree, id)
|
|
initializer :: ast.expr_kind(&state.tree, declaration.initializer)
|
|
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)
|
|
)
|
|
}
|
|
.if_stmt: {
|
|
conditional :: ast.if_stmt(&state.tree, id)
|
|
return (
|
|
conditional.capture == null
|
|
and branch_constant(state, conditional.condition, depth + 1)
|
|
and branch_body_constant(state, conditional.then_block, depth + 1)
|
|
and (conditional.else_block == null
|
|
or branch_body_constant(state, conditional.else_block?, depth + 1))
|
|
)
|
|
}
|
|
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 or kind == .optional_type)
|
|
return type_storage_annotation(tree, bindings, ast.type_operand(tree, input), depth + 1)
|
|
if (kind == .fallible_type or kind == .sum_type) {
|
|
channels :: ast.binary_expr(tree, input)
|
|
return (type_storage_annotation(tree, bindings, channels.left, depth + 1)
|
|
or type_storage_annotation(tree, bindings, channels.right, 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
|
|
storage_schema proc(tree @ast.Tree, bindings []sema.Binding, input ast.NodeId, depth usize) ast.NodeId {
|
|
if (depth >= 128 or usize(input) >= bindings.len) return no_node
|
|
resolved :: bindings[usize(input)]
|
|
if (resolved.kind == .declaration) {
|
|
target :: resolved.data.declaration
|
|
if (ast.node_class(tree, target) == .function) return target
|
|
if (ast.node_class(tree, target) == .statement)
|
|
return storage_schema(tree, bindings, ast.statement_decl(tree, target).initializer, depth + 1)
|
|
}
|
|
if (ast.expr_kind(tree, input) == .alias_expr)
|
|
return storage_schema(tree, bindings, ast.type_operand(tree, input), depth + 1)
|
|
return input
|
|
}
|
|
|
|
# read only existing annotations: discovery must not specialize recursive calls.
|
|
@hide:file
|
|
storage_expression_annotation proc(
|
|
tree @ast.Tree,
|
|
bindings []sema.Binding,
|
|
input ast.NodeId,
|
|
depth usize,
|
|
) ast.NodeId {
|
|
if (depth >= 128 or usize(input) >= bindings.len) return no_node
|
|
resolved :: bindings[usize(input)]
|
|
if (resolved.kind == .declaration) {
|
|
target :: resolved.data.declaration
|
|
class :: ast.node_class(tree, target)
|
|
if (class == .parameter) return ast.param(tree, target).type
|
|
if (class == .statement) {
|
|
declaration :: ast.statement_decl(tree, target)
|
|
if (declaration.type) |annotation| return annotation
|
|
return storage_expression_annotation(tree, bindings, declaration.initializer, depth + 1)
|
|
}
|
|
return no_node
|
|
}
|
|
kind :: ast.expr_kind(tree, input)
|
|
if (kind == .typed_constant) return ast.typed_constant(tree, input).type
|
|
if (kind == .union_constant) return ast.union_constant(tree, input).type
|
|
if (kind == .slice_constant) return ast.slice_constant(tree, input).type
|
|
if (kind == .record_literal) {
|
|
if (ast.record_literal(tree, input).type) |type| return type
|
|
return no_node
|
|
}
|
|
if (kind == .call) {
|
|
callee :: storage_schema(tree, bindings, ast.call_expr(tree, input).callee, depth + 1)
|
|
if (callee != no_node and ast.node_class(tree, callee) == .function)
|
|
return ast.function_decl(tree, callee).return_type
|
|
return no_node
|
|
}
|
|
base := no_node
|
|
if (kind == .selection) base = ast.selection_expr(tree, input).base
|
|
else if (kind == .index) base = ast.index_expr(tree, input).base
|
|
else if (kind == .slice) base = ast.slice_expr(tree, input).base
|
|
else if (kind == .unary) base = ast.unary_expr(tree, input).operand
|
|
else if (kind == .try_expr) base = ast.type_operand(tree, input)
|
|
else if (kind == .catch_expr) base = ast.catch_expr(tree, input).operand
|
|
else if (kind == .comptime_expr) return storage_expression_annotation(tree, bindings,
|
|
ast.comptime_operand(tree, input), depth + 1)
|
|
if (base == no_node) return no_node
|
|
annotation :: storage_schema(tree, bindings,
|
|
storage_expression_annotation(tree, bindings, base, depth + 1), depth + 1)
|
|
if (annotation == no_node) return no_node
|
|
shape :: ast.expr_kind(tree, annotation)
|
|
if (kind == .slice) return annotation
|
|
if (kind == .try_expr or kind == .catch_expr)
|
|
return if (shape == .fallible_type) ast.binary_expr(tree, annotation).left else no_node
|
|
if (kind == .unary)
|
|
return if (shape == .optional_type and ast.unary_expr(tree, input).operator == .unwrap)
|
|
ast.type_operand(tree, annotation) else no_node
|
|
if (kind == .index) {
|
|
if (shape == .array_type) return ast.array_type(tree, annotation).element
|
|
if (shape == .slice_type) return ast.slice_type(tree, annotation).element
|
|
return no_node
|
|
}
|
|
if (shape != .record_type and shape != .union_type) return no_node
|
|
member :: ast.selection_expr(tree, input).member
|
|
slot :: type_record_slot(tree, annotation, member)
|
|
if (slot == no_index) return no_node
|
|
if (ast.field(tree, ast.record_field(tree, annotation, slot)).type) |type| return type
|
|
return no_node
|
|
}
|
|
|
|
@hide:file
|
|
type_storage_yields proc(
|
|
tree @ast.Tree,
|
|
bindings []sema.Binding,
|
|
id ast.NodeId,
|
|
scalar_type bool,
|
|
depth usize,
|
|
) bool {
|
|
if (depth >= 128) return false
|
|
class :: ast.node_class(tree, id)
|
|
if (class != .block and class != .statement) return false
|
|
if (class == .statement and ast.stmt_kind(tree, id) == .yield_stmt) {
|
|
value :: ast.yield_stmt(tree, id).value
|
|
return value != null and type_storage_initializer(tree, bindings, value?, scalar_type, depth + 1)
|
|
}
|
|
for (0..ast.child_count(tree, id)) |i| {
|
|
if (type_storage_yields(tree, bindings, ast.child(tree, id, i), scalar_type, depth + 1)) return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
# discover inferred storage without elaborating calls or executing initializers.
|
|
# immutable scalar type bindings remain aliases in ordinary runtime procedures.
|
|
@hide:file
|
|
type_storage_initializer proc(
|
|
tree @ast.Tree, bindings []sema.Binding, input ast.NodeId, scalar_type bool, depth usize,
|
|
) bool {
|
|
if (depth >= 128 or usize(input) >= bindings.len) return false
|
|
annotation :: storage_schema(tree, bindings,
|
|
storage_expression_annotation(tree, bindings, input, depth + 1), depth + 1)
|
|
if (annotation != no_node) {
|
|
if (!scalar_type and bindings[usize(annotation)].kind == .builtin
|
|
and bindings[usize(annotation)].data.builtin == .type) return false
|
|
return type_storage_annotation(tree, bindings, annotation, depth + 1)
|
|
}
|
|
resolved :: bindings[usize(input)]
|
|
if (resolved.kind == .builtin) return scalar_type
|
|
if (resolved.kind == .declaration) {
|
|
target :: resolved.data.declaration
|
|
class :: ast.node_class(tree, target)
|
|
if (class == .statement) {
|
|
declaration :: ast.statement_decl(tree, target)
|
|
if (declaration.type != null) return false
|
|
return type_storage_initializer(tree, bindings, declaration.initializer, scalar_type, depth + 1)
|
|
}
|
|
return false
|
|
}
|
|
kind :: ast.expr_kind(tree, input)
|
|
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 scalar_type
|
|
.record_literal: {
|
|
for (0..ast.record_field_count(tree, input)) |i| {
|
|
field :: ast.field(tree, ast.record_field(tree, input, i))
|
|
if (field.value != null
|
|
and type_storage_initializer(tree, bindings, field.value?, true, depth + 1)) return true
|
|
}
|
|
return false
|
|
}
|
|
.array_literal: {
|
|
for (0..ast.array_element_count(tree, input)) |i| {
|
|
if (type_storage_initializer(tree, bindings, ast.array_element(tree, input, i), true, depth + 1))
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
.comptime_expr:
|
|
return type_storage_initializer(tree, bindings, ast.comptime_operand(tree, input), scalar_type, depth + 1)
|
|
.match_expr: {
|
|
for (0..ast.match_arm_count(tree, input)) |i| {
|
|
arm :: ast.match_arm_view(tree, ast.match_arm(tree, input, i))
|
|
if (arm.expression) {
|
|
if (type_storage_initializer(tree, bindings, arm.body, scalar_type, depth + 1)) return true
|
|
} else if (type_storage_yields(tree, bindings, arm.body, scalar_type, depth + 1)) return true
|
|
}
|
|
return false
|
|
}
|
|
.block_expr:
|
|
return type_storage_yields(tree, bindings, ast.block_expr(tree, input), scalar_type, depth + 1)
|
|
.builtin_call: {
|
|
name :: ast.literal_text(tree, ast.builtin_call(tree, input).name)
|
|
if (mem.eql(name, "typeinfo")) return true
|
|
if (mem.eql(name, "struct_type")) return scalar_type
|
|
if (mem.eql(name, "some") and ast.call_arg_count(tree, input) == 1)
|
|
return type_storage_initializer(tree, bindings, ast.call_arg(tree, input, 0), true, depth + 1)
|
|
return false
|
|
}
|
|
else: return false
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
type_local_storage proc(tree @ast.Tree, bindings []sema.Binding, id ast.NodeId, depth usize) bool {
|
|
if (depth >= 128) return false
|
|
if (ast.node_class(tree, id) == .statement and ast.stmt_kind(tree, id) == .declaration) {
|
|
declaration :: ast.statement_decl(tree, id)
|
|
if (declaration.type) |annotation| {
|
|
if (type_storage_annotation(tree, bindings, annotation, 0)) return true
|
|
}
|
|
else if (type_storage_initializer(tree, bindings, declaration.initializer,
|
|
declaration.mutability == .variable, 0)) return true
|
|
}
|
|
for (0..ast.child_count(tree, id)) |i| {
|
|
if (type_local_storage(tree, bindings, ast.child(tree, id, i), depth + 1)) return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
# local metadata does not make unrelated parameters constant. retain constant-input
|
|
# execution only when a parameter participates in constructing or updating metadata.
|
|
@hide:file
|
|
metadata_local proc(state @State, id ast.NodeId) bool {
|
|
if (ast.node_class(&state.tree, id) != .statement
|
|
or ast.stmt_kind(&state.tree, id) != .declaration) return false
|
|
declaration :: ast.statement_decl(&state.tree, id)
|
|
if (declaration.type) |annotation| return type_record_storage(state, annotation, 0)
|
|
return type_storage_initializer(&state.tree, state.bindings.items, declaration.initializer,
|
|
declaration.mutability == .variable, 0)
|
|
}
|
|
|
|
@hide:file
|
|
assignment_root proc(state @State, id ast.NodeId) ast.NodeId {
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
if (kind == .selection) return assignment_root(state, ast.selection_expr(&state.tree, id).base)
|
|
if (kind == .index) return assignment_root(state, ast.index_expr(&state.tree, id).base)
|
|
if (kind == .builtin_call
|
|
and mem.eql(ast.literal_text(&state.tree, ast.builtin_call(&state.tree, id).name), "field")
|
|
and ast.call_arg_count(&state.tree, id) == 2)
|
|
return assignment_root(state, ast.call_arg(&state.tree, id, 0))
|
|
resolved :: binding(state, id)
|
|
return if (resolved.kind == .declaration) resolved.data.declaration else no_node
|
|
}
|
|
|
|
@hide:file
|
|
DependencyPath :: struct { declaration ast.NodeId, parent ?@DependencyPath }
|
|
|
|
@hide:file
|
|
parameter_dependency proc(state @State, id, body ast.NodeId, path ?@DependencyPath, depth usize) bool {
|
|
if (depth >= 128) return true
|
|
resolved :: binding(state, id)
|
|
if (resolved.kind == .declaration) {
|
|
declaration :: resolved.data.declaration
|
|
class :: ast.node_class(&state.tree, declaration)
|
|
if (class == .parameter and ast.param(&state.tree, declaration).type != no_node)
|
|
return !ast.param(&state.tree, declaration).comptime
|
|
if (class == .statement or class == .expression or class == .parameter) {
|
|
current := path
|
|
while (current) |entry| {
|
|
if (entry.declaration == declaration) return false
|
|
current = entry.parent
|
|
}
|
|
entry :: DependencyPath{ declaration = declaration, parent = path }
|
|
if (class == .statement
|
|
and parameter_dependency(state, ast.statement_decl(&state.tree, declaration).initializer,
|
|
body, &entry, depth + 1)) return true
|
|
if (class == .statement and ast.statement_decl(&state.tree, declaration).mutability == .constant)
|
|
return false
|
|
return declaration_parameter_dependency(state, body, declaration, body, &entry, false, depth + 1)
|
|
}
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i|
|
|
if (parameter_dependency(state, ast.child(&state.tree, id, i), body, path, depth + 1)) return true
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
subtree_has proc(state @State, root, node ast.NodeId) bool {
|
|
if (root == node) return true
|
|
for (0..ast.child_count(&state.tree, root)) |i|
|
|
if (subtree_has(state, ast.child(&state.tree, root, i), node)) return true
|
|
return false
|
|
}
|
|
|
|
# follow writes as well as initializers; the declaration path breaks assignment cycles.
|
|
@hide:file
|
|
declaration_parameter_dependency proc(
|
|
state @State, id, declaration, body ast.NodeId, path @DependencyPath, dynamic bool, depth usize,
|
|
) bool {
|
|
if (depth >= 128) return true
|
|
selected := dynamic
|
|
class :: ast.node_class(&state.tree, id)
|
|
if (class == .statement) {
|
|
kind :: ast.stmt_kind(&state.tree, id)
|
|
if (kind == .assignment_stmt) {
|
|
assignment :: ast.assignment_stmt(&state.tree, id)
|
|
if (assignment_root(state, assignment.target) == declaration)
|
|
return (dynamic or parameter_dependency(state, assignment.target, body, path, depth + 1)
|
|
or parameter_dependency(state, assignment.value, body, path, depth + 1))
|
|
}
|
|
if (kind == .if_stmt or kind == .while_stmt or kind == .for_stmt) {
|
|
condition := no_node
|
|
captured := false
|
|
if (kind == .if_stmt) {
|
|
view :: ast.if_stmt(&state.tree, id)
|
|
condition = view.condition
|
|
captured = view.capture != null and view.capture? == declaration
|
|
} else if (kind == .while_stmt) {
|
|
view :: ast.while_stmt(&state.tree, id)
|
|
condition = view.condition
|
|
captured = view.capture != null and view.capture? == declaration
|
|
} else {
|
|
view :: ast.for_stmt(&state.tree, id)
|
|
condition = view.iterable
|
|
captured = ((view.capture != null and view.capture? == declaration)
|
|
or (view.index != null and view.index? == declaration))
|
|
}
|
|
if (captured) return parameter_dependency(state, condition, body, path, depth + 1)
|
|
if (!subtree_has(state, id, declaration))
|
|
selected = dynamic or parameter_dependency(state, condition, body, path, depth + 1)
|
|
}
|
|
}
|
|
if (class == .expression and ast.expr_kind(&state.tree, id) == .match_expr) {
|
|
subject :: ast.match_expr(&state.tree, id).subject
|
|
for (0..ast.match_arm_count(&state.tree, id)) |i| {
|
|
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, id, i))
|
|
if (arm.capture != null and arm.capture? == declaration)
|
|
return parameter_dependency(state, subject, body, path, depth + 1)
|
|
}
|
|
if (!subtree_has(state, id, declaration))
|
|
selected = dynamic or parameter_dependency(state, subject, body, path, depth + 1)
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i|
|
|
if (declaration_parameter_dependency(state, ast.child(&state.tree, id, i),
|
|
declaration, body, path, selected, depth + 1)) return true
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
metadata_parameter_dependency proc(state @State, id, body, dynamic ast.NodeId) bool {
|
|
class :: ast.node_class(&state.tree, id)
|
|
if (metadata_local(state, id) and parameter_dependency(state, id, body, null, 0)) return true
|
|
if (class == .statement) {
|
|
kind :: ast.stmt_kind(&state.tree, id)
|
|
if (kind == .assignment_stmt) {
|
|
assignment :: ast.assignment_stmt(&state.tree, id)
|
|
root :: assignment_root(state, assignment.target)
|
|
if (root != no_node and metadata_local(state, root))
|
|
return ((dynamic != no_node and !subtree_has(state, dynamic, root))
|
|
or parameter_dependency(state, assignment.target, body, null, 0)
|
|
or parameter_dependency(state, assignment.value, body, null, 0))
|
|
}
|
|
if (kind == .if_stmt or kind == .while_stmt or kind == .for_stmt) {
|
|
condition :: if (kind == .if_stmt) ast.if_stmt(&state.tree, id).condition
|
|
else if (kind == .while_stmt) ast.while_stmt(&state.tree, id).condition
|
|
else ast.for_stmt(&state.tree, id).iterable
|
|
selected :: if (parameter_dependency(state, condition, body, null, 0)) id else dynamic
|
|
for (0..ast.child_count(&state.tree, id)) |i|
|
|
if (metadata_parameter_dependency(state, ast.child(&state.tree, id, i), body, selected)) return true
|
|
return false
|
|
}
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i|
|
|
if (metadata_parameter_dependency(state, ast.child(&state.tree, id, i), body, dynamic)) return true
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
compiletime_call proc(state @State, function ast.NodeId) bool {
|
|
if (type_record_storage(state, ast.function_decl(&state.tree, function).return_type, 0)) return true
|
|
runtime_input := false
|
|
for (0..ast.function_param_count(&state.tree, function)) |i| {
|
|
parameter :: ast.param(&state.tree, ast.function_param(&state.tree, function, i))
|
|
if (!parameter.comptime and type_record_storage(state, parameter.type, 0)) return true
|
|
if (!parameter.comptime) runtime_input = true
|
|
}
|
|
if (!runtime_input or ast.function_external(&state.tree, function)) return false
|
|
body :: ast.function_decl(&state.tree, function).body
|
|
if (!type_local_storage(&state.tree, state.bindings.items, body, 0)) return false
|
|
return metadata_parameter_dependency(state, body, body, no_node)
|
|
}
|
|
|
|
@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)
|
|
# demand is scoped to explicit evaluation, never inferred from constant arguments.
|
|
metadata_result :: compiletime_call(state, function)
|
|
demanded :: !metadata_result and state.demand_depth > 0 and try needs_demand_execution(state, function)
|
|
type_result :: metadata_result or demanded
|
|
runtime := usize(0)
|
|
for (0..count) |i| {
|
|
if (!metadata_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 (metadata_result or supplied < runtime or supplied > count)) {
|
|
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)
|
|
# supplied compile-time arguments form a declaration-order prefix; infer the rest.
|
|
supplied_comptime :: if (metadata_result) count else supplied - runtime
|
|
comptime_position := usize(0)
|
|
position := 0
|
|
for (0..count) |i| {
|
|
parameter :: ast.function_param(&state.tree, function, i)
|
|
view :: ast.param(&state.tree, parameter)
|
|
if (view.comptime and !metadata_result) {
|
|
if (comptime_position >= supplied_comptime) continue
|
|
comptime_position += 1
|
|
}
|
|
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
|
|
}
|
|
}
|
|
if (!view.comptime and !metadata_result) 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)
|
|
}
|
|
demand_key :: if (demanded) usize(1) else usize(0)
|
|
key := mix(usize(function), demand_key)
|
|
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 and entry.demanded == demanded
|
|
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, demanded = demanded,
|
|
}) 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 (type_result or supplied_comptime != 0) {
|
|
signature :: state.instances.items[instance].signature
|
|
position = 0
|
|
comptime_position = 0
|
|
for (0..count) |i| {
|
|
parameter :: ast.function_param(&state.tree, signature, i)
|
|
view :: ast.param(&state.tree, parameter)
|
|
if (view.comptime and !metadata_result) {
|
|
if (comptime_position >= supplied_comptime) continue
|
|
comptime_position += 1
|
|
}
|
|
original :: ast.call_arg(&state.tree, call, position)
|
|
position += 1
|
|
if (
|
|
(!type_result and !view.comptime) or is_type(state, view.type)
|
|
or type_record_storage(state, view.type, 0)
|
|
) continue
|
|
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_call(state, function) or state.instances.items[index].demanded
|
|
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) {
|
|
state.demand_depth += 1
|
|
defer state.demand_depth -= 1
|
|
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 {
|
|
state.demand_depth += 1
|
|
defer state.demand_depth -= 1
|
|
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
|
|
}
|
|
}
|
|
|
|
# rebuild exact slice backing annotations without retaining temporary checker IDs.
|
|
@hide:file
|
|
serialized_type proc(
|
|
state @mut State,
|
|
checked @typecheck.Result,
|
|
type typecheck.Type,
|
|
span ast.Span,
|
|
depth usize,
|
|
) ast.NodeId ! Error {
|
|
if (depth >= 128) return no_node
|
|
source := type.nominal
|
|
if (source == no_node and type.kind == .record) {
|
|
record :: typecheck.record_type(checked, type.record_type)
|
|
source = if (record.declaration != no_node) record.declaration else record.source
|
|
}
|
|
if (source == no_node and type.kind == .enumeration)
|
|
source = typecheck.enum_type(checked, type.enum_type).declaration
|
|
if (source == no_node and type.kind == .function) source = type.function
|
|
if (source != no_node) {
|
|
if (ast.node_class(&state.tree, source) == .function) {
|
|
params := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(¶ms)
|
|
for (0..ast.function_param_count(&state.tree, source)) |i|
|
|
try arraylist.append(¶ms, ast.function_param(&state.tree, source, i))
|
|
source = try ast.add_function_type(state.builder, span,
|
|
ast.callable_result(&state.tree, source), params.items)
|
|
try refresh(state)
|
|
}
|
|
return (try materialize(state, Value{ kind = .type, node = source })).node
|
|
}
|
|
result := no_node
|
|
if (type.kind == .array or type.kind == .slice or type.kind == .optional or type.kind == .fallible) {
|
|
descriptor :: typecheck.array_type(checked, type.array_type)
|
|
child :: try serialized_type(state, checked, descriptor.element, span, depth + 1)
|
|
if (child == no_node) return no_node
|
|
if (type.kind == .array) {
|
|
count :: try ast.add_integer(state.builder, span, u64(descriptor.len))
|
|
result = try ast.add_array_type(state.builder, span, count, child, descriptor.mutable)
|
|
} else if (type.kind == .slice) {
|
|
sentinel ?ast.NodeId := null
|
|
if (descriptor.sentinel) |value| {
|
|
unsigned :: (descriptor.element.kind == .u8 or descriptor.element.kind == .u16
|
|
or descriptor.element.kind == .u32 or descriptor.element.kind == .u64 or descriptor.element.kind == .usize)
|
|
negative :: value < 0 and !unsigned
|
|
magnitude :: if (negative) u64(-(value + 1)) + 1 else ptrcast!(@u64, &value)^
|
|
literal := try ast.add_integer(state.builder, span, magnitude)
|
|
if (negative) literal = try ast.add_unary(state.builder, span, .negate, literal)
|
|
sentinel = literal
|
|
}
|
|
result = try ast.add_slice_type(state.builder, span, child, descriptor.mutable, sentinel)
|
|
} else if (type.kind == .optional) result = try ast.add_optional_type(state.builder, span, child)
|
|
else {
|
|
error_type :: try serialized_type(state, checked, descriptor.error_type, span, depth + 1)
|
|
if (error_type == no_node) return no_node
|
|
result = try ast.add_binary_type(state.builder, span, child, error_type, true)
|
|
}
|
|
} else if (type.kind == .pointer) {
|
|
descriptor :: typecheck.pointer_type(checked, type.pointer_type)
|
|
child :: try serialized_type(state, checked, descriptor.element, span, depth + 1)
|
|
if (child == no_node) return no_node
|
|
result = try ast.add_pointer_type(state.builder, span, child, descriptor.mutable, descriptor.many)
|
|
} else {
|
|
kind sema.BuiltinType :: match type.kind {
|
|
.int: .i64
|
|
.i8: .i8
|
|
.i16: .i16
|
|
.i32: .i32
|
|
.isize: .isize
|
|
.u8: .u8
|
|
.u16: .u16
|
|
.u32: .u32
|
|
.u64: .u64
|
|
.usize: .usize
|
|
.float: .f64
|
|
.bool: .bool
|
|
.string: .string
|
|
.void: .void
|
|
.anyopaque: .anyopaque
|
|
else: return no_node
|
|
}
|
|
result = try ast.add_identifier(state.builder, span, strpool.no_str)
|
|
try refresh(state)
|
|
state.bindings.items[usize(result)] = sema.Binding{ kind = .builtin, data = sema.BindingData{ builtin = kind } }
|
|
}
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
# values leaving temporary evaluation must own no interpreter addresses or module IDs.
|
|
# snapshot readonly storage 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,
|
|
remaining @mut usize,
|
|
) ast.NodeId ! Error {
|
|
if (remaining^ == 0 or state.depth >= 128) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_execution_limit, span)
|
|
return no_node
|
|
}
|
|
remaining^ -= 1
|
|
state.depth += 1
|
|
defer state.depth -= 1
|
|
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) {
|
|
if (state.snapshot_uninitialized) return try ast.add_literal(state.builder, .undefined, span)
|
|
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) 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 (slice.element.kind != .u8 or slice.element.nominal != no_node) {
|
|
shape :: module.arrays.items[usize(value.array_type)]
|
|
count :: usize(length.data.unsigned)
|
|
extra :: if (slice.sentinel != null) usize(1) else usize(0)
|
|
if (count > remaining^ or extra > remaining^ - count) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_execution_limit, span)
|
|
return no_node
|
|
}
|
|
if (count != 0 and interpreter.evaluation_element(outcome, module, pointer,
|
|
count - 1, shape.element, shape.element_array) == null) return no_node
|
|
elements := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&elements)
|
|
for (0..(count + extra)) |index| {
|
|
borrowed :: interpreter.evaluation_element(outcome, module, pointer,
|
|
index, shape.element, shape.element_array)
|
|
if (borrowed == null) return no_node
|
|
child := borrowed?
|
|
# recursive slice reads may overwrite the accessor's raw-view scratch.
|
|
scratch_backed :: ir.aggregate(child.type) and child.raw
|
|
owned []mut ir.Value := &[]
|
|
if (scratch_backed) {
|
|
owned = try mem.alloc(ir.Value, state.allocator, child.data.elements.len)
|
|
memcopy!(owned, child.data.elements)
|
|
child.data = ir.ValueData{ elements = owned }
|
|
}
|
|
defer if (scratch_backed) mem.free(state.allocator, owned)
|
|
node :: try serialize_value(state, module, checked, outcome, functions,
|
|
slice.element, child, span, remaining)
|
|
if (node == no_node) return no_node
|
|
if (index == count and slice.sentinel != null) {
|
|
# checker sentinels retain full-width integer bits, including u64 maxima.
|
|
if (child.data.integer != slice.sentinel?) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .sentinel_mismatch, span)
|
|
return no_node
|
|
}
|
|
}
|
|
try arraylist.append(&elements, node)
|
|
}
|
|
annotation :: try serialized_type(state, checked, type, span, 0)
|
|
if (annotation == no_node) return no_node
|
|
child_type :: if (type.nominal == no_node) ast.slice_type(&state.tree, annotation).element
|
|
else try serialized_type(state, checked, slice.element, span, 0)
|
|
if (child_type == no_node) return no_node
|
|
length_node :: try ast.add_integer(state.builder, span, u64(count + extra))
|
|
array_type :: try ast.add_array_type(state.builder, span, length_node, child_type, false)
|
|
literal :: try ast.add_array_literal(state.builder, span, elements.items)
|
|
backing :: try ast.add_typed_constant(state.builder, span, array_type, literal)
|
|
result = try ast.add_slice_constant(state.builder, span, annotation, backing)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
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,
|
|
remaining,
|
|
)
|
|
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, remaining)
|
|
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, remaining)
|
|
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, remaining)
|
|
} 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, remaining)
|
|
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 or kind == .slice_constant) {
|
|
a :: if (kind == .union_constant) ast.union_constant(&state.tree, left)
|
|
else if (kind == .slice_constant) ast.slice_constant(&state.tree, left) else ast.typed_constant(&state.tree, left)
|
|
b :: if (kind == .union_constant) ast.union_constant(&state.tree, right)
|
|
else if (kind == .slice_constant) ast.slice_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 or kind == .slice_constant) {
|
|
value :: if (kind == .union_constant) ast.union_constant(&state.tree, id)
|
|
else if (kind == .slice_constant) ast.slice_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)
|
|
remaining := usize(100000)
|
|
node :: try serialize_value(state, &module, &checked, &outcome, functions.items, source_type, value, span, &remaining)
|
|
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 ast.expr_kind(&state.tree, node) == .slice_constant))
|
|
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
|
|
# unannotated numeric literal origins stay contextual while executing constants;
|
|
# annotations, casts and rigid assignment sources retain ordinary exact types.
|
|
contextual bool = false
|
|
}
|
|
|
|
@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
|
|
failed bool = false
|
|
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
|
|
residual ?@mut arraylist.ArrayList(ast.NodeId) = null
|
|
residual_boundary usize = 0
|
|
residual_body ast.NodeId = no_node
|
|
residual_flow TypeFlow = .next
|
|
}
|
|
|
|
@hide:file
|
|
TypeFlow :: enum { next, returned, yielded, break, continue, invalid }
|
|
|
|
@hide:file
|
|
ResidualCleanup :: struct { position usize, statement ast.NodeId }
|
|
|
|
@hide:file
|
|
metadata_reads proc(state @State, id, declaration ast.NodeId, depth usize) bool {
|
|
if (depth >= 128) return false
|
|
resolved :: binding(state, id)
|
|
if (resolved.kind == .declaration) {
|
|
if (resolved.data.declaration == declaration) return true
|
|
if (ast.node_class(&state.tree, resolved.data.declaration) == .statement)
|
|
return metadata_reads(state, ast.statement_decl(&state.tree, resolved.data.declaration).initializer,
|
|
declaration, depth + 1)
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i|
|
|
if (metadata_reads(state, ast.child(&state.tree, id, i), declaration, depth + 1)) return true
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
metadata_updates_outer proc(state @State, id, scope ast.NodeId) bool {
|
|
if (ast.node_class(&state.tree, id) == .statement and ast.stmt_kind(&state.tree, id) == .assignment_stmt) {
|
|
root :: assignment_root(state, ast.assignment_stmt(&state.tree, id).target)
|
|
if (root != no_node and metadata_local(state, root) and !subtree_has(state, scope, root)) return true
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i|
|
|
if (metadata_updates_outer(state, ast.child(&state.tree, id, i), scope)) return true
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
metadata_updates proc(state @State, id ast.NodeId) bool {
|
|
return metadata_updates_outer(state, id, id)
|
|
}
|
|
|
|
@hide:file
|
|
metadata_needs proc(state @State, id, declaration ast.NodeId) bool {
|
|
if (metadata_local(state, id) and metadata_reads(state, id, declaration, 0)) return true
|
|
if (ast.node_class(&state.tree, id) == .statement) {
|
|
kind :: ast.stmt_kind(&state.tree, id)
|
|
if (kind == .assignment_stmt) {
|
|
root :: assignment_root(state, ast.assignment_stmt(&state.tree, id).target)
|
|
if (root != no_node and metadata_local(state, root)
|
|
and metadata_reads(state, id, declaration, 0)) return true
|
|
}
|
|
if ((kind == .while_stmt or kind == .for_stmt or kind == .if_stmt) and metadata_updates(state, id)) {
|
|
condition :: if (kind == .while_stmt) ast.while_stmt(&state.tree, id).condition
|
|
else if (kind == .for_stmt) ast.for_stmt(&state.tree, id).iterable
|
|
else ast.if_stmt(&state.tree, id).condition
|
|
if (metadata_reads(state, condition, declaration, 0)) return true
|
|
}
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i|
|
|
if (metadata_needs(state, ast.child(&state.tree, id, i), declaration)) return true
|
|
return false
|
|
}
|
|
|
|
# erase metadata storage before ordinary checking. runtime scopes may create their
|
|
# own metadata, but cannot change a snapshot belonging to an enclosing runtime path.
|
|
@hide:file
|
|
residual_block proc(state @mut State, execution @mut TypeExecution, block ast.NodeId, boundary usize) ast.NodeId ! Error {
|
|
saved :: execution.locals.items.len
|
|
saved_output :: execution.residual
|
|
saved_boundary :: execution.residual_boundary
|
|
statements := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&statements)
|
|
cleanups := arraylist.init(ResidualCleanup, state.allocator)
|
|
defer arraylist.deinit(&cleanups)
|
|
execution.residual = &statements
|
|
execution.residual_boundary = boundary
|
|
execution.residual_flow = .next
|
|
defer {
|
|
execution.locals.items = execution.locals.items[..saved]
|
|
execution.residual = saved_output
|
|
execution.residual_boundary = saved_boundary
|
|
}
|
|
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 no_node
|
|
kind :: ast.stmt_kind(&state.tree, statement)
|
|
if ((kind == .break_stmt or kind == .continue_stmt) and execution.loop_depth > 0) {
|
|
execution.residual_flow = if (kind == .break_stmt) TypeFlow.break else TypeFlow.continue
|
|
break
|
|
}
|
|
if (kind == .defer_stmt) {
|
|
try arraylist.append(&cleanups, ResidualCleanup{ position = statements.items.len, statement = statement })
|
|
try arraylist.append(&statements, statement)
|
|
continue
|
|
}
|
|
constant := metadata_local(state, statement)
|
|
if (kind == .declaration and !constant) {
|
|
declaration :: ast.statement_decl(&state.tree, statement)
|
|
constant = (type_storage_initializer(&state.tree, state.bindings.items,
|
|
declaration.initializer, true, 0)
|
|
or metadata_needs(state, execution.residual_body, statement))
|
|
}
|
|
if (kind == .assignment_stmt) {
|
|
root :: assignment_root(state, ast.assignment_stmt(&state.tree, statement).target)
|
|
for (execution.locals.items) |local, index| {
|
|
if (root != local.declaration) continue
|
|
if (index < boundary) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.comptime_argument_required, ast.node_span(&state.tree, statement))
|
|
return no_node
|
|
}
|
|
constant = true
|
|
}
|
|
}
|
|
if (constant) {
|
|
saved_values :: execution.evaluate_values
|
|
execution.evaluate_values = true
|
|
flow :: execute_type_statement(state, execution, statement) catch |err| {
|
|
execution.evaluate_values = saved_values
|
|
return err
|
|
}
|
|
execution.evaluate_values = saved_values
|
|
if (flow != .next) return no_node
|
|
continue
|
|
}
|
|
if (kind == .block_stmt) {
|
|
child :: try residual_block(state, execution, ast.statement_block(&state.tree, statement), boundary)
|
|
if (child == no_node) return no_node
|
|
try arraylist.append(&statements, try ast.add_block_stmt(state.builder, child))
|
|
try refresh(state)
|
|
if (execution.residual_flow != .next) break
|
|
continue
|
|
}
|
|
if (kind == .expression
|
|
and ast.expr_kind(&state.tree, ast.statement_expr(&state.tree, statement)) == .match_expr) {
|
|
expression :: ast.statement_expr(&state.tree, statement)
|
|
subject :: try type_snapshot(state, execution, ast.match_expr(&state.tree, expression).subject)
|
|
if (subject == no_node) return no_node
|
|
try expand(state, subject)
|
|
if (type_record_storage(state, try expression_type(state, subject, 0), 0)
|
|
or branch_constant(state, subject, 0)) {
|
|
arms := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&arms)
|
|
for (0..ast.match_arm_count(&state.tree, expression)) |position|
|
|
try arraylist.append(&arms, ast.match_arm(&state.tree, expression, position))
|
|
dispatch :: try ast.add_match(state.builder, ast.node_span(&state.tree, expression), subject, arms.items)
|
|
try refresh(state)
|
|
try expand_inline_match(state, dispatch, try expression_type(state, subject, 0))
|
|
index :: try type_match_arm(state, dispatch)
|
|
if (index == no_index) return no_node
|
|
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, dispatch, index))
|
|
selected :: try type_match_body(state, dispatch, arm)
|
|
if (selected == no_node) return no_node
|
|
if (arm.expression) {
|
|
snapshot :: try type_snapshot(state, execution, selected)
|
|
if (snapshot == no_node) return no_node
|
|
try arraylist.append(&statements, try ast.add_expression_stmt(state.builder, snapshot))
|
|
} else {
|
|
body :: try residual_block(state, execution, selected, boundary)
|
|
if (body == no_node) return no_node
|
|
value :: try ast.add_block_expr(state.builder, ast.node_span(&state.tree, expression), body)
|
|
try arraylist.append(&statements, try ast.add_expression_stmt(state.builder, value))
|
|
if (execution.residual_flow == .yielded) execution.residual_flow = .next
|
|
}
|
|
try refresh(state)
|
|
if (execution.residual_flow != .next) break
|
|
continue
|
|
}
|
|
}
|
|
if (kind == .if_stmt and ast.if_stmt(&state.tree, statement).capture != null) {
|
|
condition :: try type_snapshot(state, execution, ast.if_stmt(&state.tree, statement).condition)
|
|
if (condition == no_node) return no_node
|
|
try expand(state, condition)
|
|
if (type_record_storage(state, try expression_type(state, condition, 0), 0)) {
|
|
flow :: try execute_type_statement(state, execution, statement)
|
|
if (flow == .invalid) return no_node
|
|
execution.residual_flow = flow
|
|
if (flow != .next) break
|
|
continue
|
|
}
|
|
}
|
|
if (kind == .if_stmt and ast.if_stmt(&state.tree, statement).capture == null) {
|
|
condition :: try type_snapshot(state, execution, ast.if_stmt(&state.tree, statement).condition)
|
|
if (condition == no_node) return no_node
|
|
try expand(state, condition)
|
|
if (branch_constant(state, condition, 0)) {
|
|
value :: try evaluate_ir(state, condition, try builtin(state, condition, .bool))
|
|
if (value.kind != .boolean) return no_node
|
|
view :: ast.if_stmt(&state.tree, statement)
|
|
selected := if (value.boolean) view.then_block else no_node
|
|
if (!value.boolean and view.else_block != null) selected = view.else_block?
|
|
if (selected != no_node) {
|
|
child :: try residual_block(state, execution, selected, boundary)
|
|
if (child == no_node) return no_node
|
|
try arraylist.append(&statements, try ast.add_block_stmt(state.builder, child))
|
|
try refresh(state)
|
|
if (execution.residual_flow != .next) break
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
if ((kind == .for_stmt or kind == .while_stmt) and metadata_updates(state, statement)) {
|
|
flow :: try execute_type_statement(state, execution, statement)
|
|
if (flow == .invalid) return no_node
|
|
execution.residual_flow = flow
|
|
if (flow != .next) break
|
|
continue
|
|
}
|
|
snapshot :: try type_snapshot(state, execution, statement)
|
|
if (snapshot == no_node) return no_node
|
|
try arraylist.append(&statements, snapshot)
|
|
if (kind == .return_stmt or kind == .yield_stmt) {
|
|
execution.residual_flow = if (kind == .return_stmt) TypeFlow.returned else TypeFlow.yielded
|
|
break
|
|
}
|
|
}
|
|
remaining := cleanups.items.len
|
|
while (remaining > 0) {
|
|
remaining -= 1
|
|
cleanup :: cleanups.items[remaining]
|
|
saved_flow :: execution.residual_flow
|
|
body :: try residual_block(state, execution, ast.cleanup_stmt(&state.tree, cleanup.statement).body, boundary)
|
|
if (body == no_node) return no_node
|
|
statements.items[cleanup.position] = try ast.add_cleanup_stmt(state.builder,
|
|
ast.node_span(&state.tree, cleanup.statement), body, null, false)
|
|
try refresh(state)
|
|
execution.residual_flow = saved_flow
|
|
}
|
|
result :: try ast.add_block(state.builder, ast.node_span(&state.tree, block), statements.items)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
expand_runtime_metadata proc(state @mut State, function ast.NodeId) void ! Error {
|
|
view :: ast.function_decl(&state.tree, function)
|
|
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
|
|
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 = view.return_type, residual_body = view.body,
|
|
}
|
|
defer arraylist.deinit(&execution.locals)
|
|
defer mem.free(state.allocator, execution.mapping)
|
|
mem.set(execution.mapping, no_node)
|
|
body := try residual_block(state, &execution, view.body, 0)
|
|
if (body == no_node) {
|
|
diagnostic := state.evaluation_diagnostic
|
|
if (diagnostic == diagnostics.no_diagnostic)
|
|
diagnostic = try diagnostics.add(state.diagnostics, .comptime_argument_required, view.span)
|
|
invalid :: try ast.add_invalid_stmt(state.builder, view.span, diagnostic)
|
|
statements [1]ast.NodeId :: [invalid]
|
|
body = try ast.add_block(state.builder, view.span, statements[..])
|
|
}
|
|
try replace(state, view.body, body)
|
|
try refresh(state)
|
|
try expand(state, view.return_type)
|
|
for (0..ast.function_param_count(&state.tree, function)) |i|
|
|
try expand(state, ast.function_param(&state.tree, function, i))
|
|
try expand(state, view.body)
|
|
}
|
|
|
|
# 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)
|
|
flow :: try execute_type_block(state, &execution, block)
|
|
if (flow == .returned) return execution.returned
|
|
if (flow == .next) return try type_void_completion(state, result_type, block)
|
|
return no_node
|
|
}
|
|
|
|
@hide:file
|
|
type_void_completion proc(state @mut State, annotation, at ast.NodeId) ast.NodeId ! Error {
|
|
type :: try canonical_type(state, annotation)
|
|
if (type == no_node) return no_node
|
|
child :: if (ast.expr_kind(&state.tree, type) == .fallible_type)
|
|
try canonical_type(state, ast.binary_expr(&state.tree, type).left) else type
|
|
resolved :: binding(state, child)
|
|
if (resolved.kind != .builtin or resolved.data.builtin != .void) return no_node
|
|
value :: try ast.add_literal(state.builder, .void, ast.node_span(&state.tree, at))
|
|
try refresh(state)
|
|
return try type_execution_value(state, value, annotation)
|
|
}
|
|
|
|
@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
|
|
resolved_capture :: binding(state, id)
|
|
if (execution.residual != null and !execution.evaluate_values
|
|
and (ast.node_class(&state.tree, id) == .expression or ast.node_class(&state.tree, id) == .parameter)
|
|
and resolved_capture.kind == .declaration and resolved_capture.data.declaration == id) return
|
|
if (execution.residual != null and !execution.evaluate_values) {
|
|
# captures precede their bodies semantically, regardless of AST child order.
|
|
for (0..ast.child_count(&state.tree, id)) |i| {
|
|
child :: ast.child(&state.tree, id, i)
|
|
resolved :: binding(state, child)
|
|
if ((ast.node_class(&state.tree, child) != .expression and ast.node_class(&state.tree, child) != .parameter)
|
|
or resolved.kind != .declaration or resolved.data.declaration != child) continue
|
|
capture :: try ast.copy_node(state.builder, child)
|
|
try refresh(state)
|
|
execution.mapping[usize(child)] = capture
|
|
state.bindings.items[usize(capture)] = sema.Binding{
|
|
kind = .declaration, data = sema.BindingData{ declaration = capture },
|
|
}
|
|
}
|
|
}
|
|
if (execution.residual != null and !execution.evaluate_values and ast.node_class(&state.tree, id) == .block) {
|
|
saved_flow :: execution.residual_flow
|
|
saved_loop :: execution.loop_depth
|
|
execution.loop_depth = 0
|
|
defer {
|
|
execution.residual_flow = saved_flow
|
|
execution.loop_depth = saved_loop
|
|
}
|
|
execution.mapping[usize(id)] = try residual_block(state, execution, id, execution.locals.items.len)
|
|
if (execution.mapping[usize(id)] == no_node) execution.pending_flow = .invalid
|
|
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) {
|
|
value := local.value
|
|
if (local.contextual and ast.expr_kind(&state.tree, value) == .typed_constant)
|
|
value = ast.typed_constant(&state.tree, value).value
|
|
execution.mapping[usize(id)] = 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 == .call and type_call_borrows(state, execution, id)) {
|
|
execution.mapping[usize(id)] = try type_borrow_call(state, execution, id)
|
|
if (execution.mapping[usize(id)] == no_node) execution.pending_flow = .invalid
|
|
return
|
|
}
|
|
if (kind == .slice) {
|
|
base :: ast.slice_expr(&state.tree, id).base
|
|
root := base
|
|
while (true) {
|
|
root_kind :: ast.expr_kind(&state.tree, root)
|
|
if (root_kind == .selection) root = ast.selection_expr(&state.tree, root).base
|
|
else if (root_kind == .index) root = ast.index_expr(&state.tree, root).base
|
|
else if (root_kind == .unary and ast.unary_expr(&state.tree, root).operator == .unwrap)
|
|
root = ast.unary_expr(&state.tree, root).operand
|
|
else if (root_kind == .builtin_call
|
|
and mem.eql(ast.literal_text(&state.tree, ast.builtin_call(&state.tree, root).name), "field")
|
|
and ast.call_arg_count(&state.tree, root) == 2)
|
|
root = ast.call_arg(&state.tree, root, 0)
|
|
else break
|
|
}
|
|
place :: binding(state, root)
|
|
if (place.kind == .declaration
|
|
and ast.node_class(&state.tree, place.data.declaration) == .statement
|
|
and ast.statement_decl(&state.tree, place.data.declaration).mutability == .variable) {
|
|
annotation :: try expression_type(state, base, 0)
|
|
if (type_record_storage(state, annotation, 0)
|
|
and ast.expr_kind(&state.tree, annotation) == .array_type) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.comptime_value_escape, ast.node_span(&state.tree, id))
|
|
execution.pending_flow = .invalid
|
|
return
|
|
}
|
|
}
|
|
}
|
|
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 == .try_expr) {
|
|
execution.mapping[usize(id)] = try type_snapshot_try(state, execution, id)
|
|
if (execution.mapping[usize(id)] == no_node and execution.pending_flow == .next)
|
|
execution.pending_flow = .invalid
|
|
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_fallible_failed proc(
|
|
state @mut State,
|
|
owned, type, at ast.NodeId,
|
|
) ?bool ! Error {
|
|
missing ?bool :: null
|
|
if (type_record_storage(state, type, 0)) {
|
|
if (!try type_array_initialized(state, owned, at)) return missing
|
|
return !try same_type(state,
|
|
ast.typed_constant(&state.tree, ast.typed_constant(&state.tree, owned).value).type,
|
|
ast.binary_expr(&state.tree, type).left)
|
|
}
|
|
span :: ast.node_span(&state.tree, at)
|
|
boolean :: try builtin(state, at, .bool)
|
|
yes :: try ast.add_boolean(state.builder, span, true)
|
|
no :: try ast.add_boolean(state.builder, span, false)
|
|
returned :: try ast.add_return_stmt(state.builder, span, yes)
|
|
failure_statements [1]ast.NodeId :: [returned]
|
|
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, at, body, boolean)
|
|
if (guard.kind != .boolean) return missing
|
|
return guard.boolean
|
|
}
|
|
|
|
@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
|
|
failure :: try type_fallible_failed(state, owned, operand_type, id)
|
|
if (failure == null) return no_node
|
|
failed :: failure?
|
|
payload :: ast.typed_constant(&state.tree, owned).value
|
|
if (!failed) 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
|
|
specialize_type_catch proc(state @mut State, id, annotation, expected ast.NodeId) void ! Error {
|
|
view :: ast.catch_expr(&state.tree, id)
|
|
value :: try evaluate_expected(state, view.operand, annotation)
|
|
if (value.kind != .aggregate) {
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
channels :: ast.binary_expr(&state.tree, annotation)
|
|
payload :: value.node
|
|
success :: try same_type(state, ast.typed_constant(&state.tree, payload).type, channels.left)
|
|
selected := no_node
|
|
if (success) selected = try type_execution_value(state, payload, channels.left)
|
|
else {
|
|
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)
|
|
if (view.capture) |capture| {
|
|
resolved :: binding(state, capture)
|
|
if (resolved.kind == .invalid) {
|
|
state.evaluation_diagnostic = resolved.data.diagnostic
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
error :: try type_execution_value(state, payload, channels.right)
|
|
if (error == no_node) {
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
try arraylist.append(&execution.locals, TypeLocal{
|
|
declaration = capture, value = error, annotation = channels.right,
|
|
})
|
|
}
|
|
selected = try type_snapshot(state, &execution, view.handler)
|
|
if (view.handler_is_block and selected != no_node) {
|
|
selected = try ast.add_block_expr(state.builder, ast.node_span(&state.tree, id), selected)
|
|
try refresh(state)
|
|
}
|
|
}
|
|
if (selected == no_node) {
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
context :: if (expected != no_node) expected else channels.left
|
|
try expand_expected(state, selected, context)
|
|
if (is_type(state, channels.left) or type_record_storage(state, channels.left, 0)) {
|
|
selected = try type_execution_value(state, selected, channels.left)
|
|
if (selected == no_node) {
|
|
try reject_evaluation(state, id)
|
|
return
|
|
}
|
|
} else {
|
|
result_type := channels.left
|
|
contextual :: try canonical_type(state, context)
|
|
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 = context
|
|
if (kind == .fallible_type
|
|
and try same_type(state, ast.binary_expr(&state.tree, contextual).left, channels.left))
|
|
result_type = context
|
|
}
|
|
selected = try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, id), result_type, selected)
|
|
try refresh(state)
|
|
}
|
|
if (is_type(state, try expression_type(state, selected, 0))) {
|
|
selected = try ast.add_type_operand(state.builder, ast.node_span(&state.tree, id), selected, true)
|
|
try refresh(state)
|
|
}
|
|
try replace(state, id, selected)
|
|
try refresh(state)
|
|
state.bindings.items[usize(id)] = binding(state, selected)
|
|
}
|
|
|
|
@hide:file
|
|
type_snapshot_try proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) ast.NodeId ! Error {
|
|
operand :: try type_snapshot(state, execution, ast.type_operand(&state.tree, id))
|
|
if (operand == no_node) return no_node
|
|
try expand(state, operand)
|
|
annotation :: try expression_type(state, operand, 0)
|
|
if (annotation == no_node or ast.expr_kind(&state.tree, annotation) != .fallible_type) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_fallible_operand, ast.node_span(&state.tree, id))
|
|
return no_node
|
|
}
|
|
result :: try canonical_type(state, execution.result_type)
|
|
if (execution.cleanup_depth > 0 or result == no_node
|
|
or ast.expr_kind(&state.tree, result) != .fallible_type) {
|
|
code diagnostics.Code :: if (execution.cleanup_depth > 0)
|
|
.cleanup_control_flow else .try_outside_fallible
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, code, ast.node_span(&state.tree, id))
|
|
return no_node
|
|
}
|
|
channels :: ast.binary_expr(&state.tree, annotation)
|
|
if (!try type_subset(state, channels.right, ast.binary_expr(&state.tree, result).right)) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.incompatible_propagated_error, ast.node_span(&state.tree, id))
|
|
return no_node
|
|
}
|
|
# propagation is a catch whose error branch returns from the enclosing frame.
|
|
span :: ast.node_span(&state.tree, id)
|
|
capture :: try ast.add_identifier(state.builder, span, strpool.no_str)
|
|
try refresh(state)
|
|
value :: try reference(state, id, capture)
|
|
returned :: try ast.add_return_stmt(state.builder, span, value)
|
|
statements [1]ast.NodeId :: [returned]
|
|
handler :: try ast.add_block(state.builder, span, statements[..])
|
|
caught :: try ast.add_catch_expr(state.builder, span, operand, capture, handler, true)
|
|
try refresh(state)
|
|
return try type_snapshot_catch(state, execution, caught, channels.left)
|
|
}
|
|
|
|
@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
|
|
subject_type :: try expression_type(state, subject, 0)
|
|
try expand_inline_match(state, id, subject_type)
|
|
if (ast.expr_kind(&state.tree, id) != .match_expr) 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 {
|
|
state.demand_depth += 1
|
|
defer state.demand_depth -= 1
|
|
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) return no_index
|
|
span :: ast.node_span(&state.tree, subject)
|
|
if (ast.expr_kind(&state.tree, annotation) == .optional_type) {
|
|
present :: ast.expr_kind(&state.tree, value.node) != .literal_null
|
|
boolean :: try ast.add_boolean(state.builder, span, true)
|
|
payload :: if (present) boolean else try ast.add_literal(state.builder, .null, span)
|
|
optional :: try ast.add_optional_type(state.builder, span, try builtin(state, subject, .bool))
|
|
dispatch = try ast.add_typed_constant(state.builder, span, optional, payload)
|
|
} else {
|
|
if (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))
|
|
tags :: try metadata_variant_tags(state, try inline_match_shape(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 !optional_capture 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
|
|
}
|
|
shape :: try inline_match_shape(state, annotation)
|
|
slot :: type_record_slot(&state.tree, shape, ast.enum_literal(&state.tree, pattern))
|
|
if (slot == no_index) {
|
|
valid = false
|
|
continue
|
|
}
|
|
payload :: ast.field(&state.tree, ast.record_field(&state.tree, shape, 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)
|
|
payload :: try type_execution_value(state, unwrapped, child)
|
|
if (payload == no_node or !is_type(state, child)) return payload
|
|
result :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, subject), child, payload)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
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 == .slice) {
|
|
annotation :: try expression_type(state, id, 0)
|
|
if (!try metadata_sequence(state, annotation)) return true
|
|
value :: try type_slice_value(state, id, annotation)
|
|
if (value == no_node) return false
|
|
try replace(state, id, value)
|
|
try refresh(state)
|
|
return true
|
|
}
|
|
if (kind == .unary and ast.unary_expr(&state.tree, id).operator == .unwrap) {
|
|
operand :: ast.unary_expr(&state.tree, id).operand
|
|
annotation :: try expression_type(state, operand, 0)
|
|
if (!type_record_storage(state, annotation, 0)) return true
|
|
type :: try canonical_type(state, annotation)
|
|
if (type == no_node or ast.expr_kind(&state.tree, type) != .optional_type) return true
|
|
value :: try type_execution_value(state, operand, type)
|
|
if (value == no_node or !try type_array_initialized(state, value, id)) return false
|
|
payload :: ast.typed_constant(&state.tree, value).value
|
|
if (ast.expr_kind(&state.tree, payload) == .literal_null) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.null_unwrap, ast.node_span(&state.tree, id))
|
|
return false
|
|
}
|
|
selected :: ast.call_arg(&state.tree, payload, 0)
|
|
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
|
|
}
|
|
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 (ast.expr_kind(&state.tree, type) == .record_type
|
|
or (type_record_storage(state, type, 0) and 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
|
|
if (
|
|
(try metadata_sequence(state, array.type) or record)
|
|
and ast.expr_kind(&state.tree, array.value) == .literal_undefined
|
|
) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.read_before_initialization, ast.node_span(&state.tree, id))
|
|
return false
|
|
}
|
|
selected := no_node
|
|
selected_type := no_node
|
|
if (record) {
|
|
if (kind != .selection) return true
|
|
member :: ast.selection_expr(&state.tree, id).member
|
|
slot :: type_record_slot(&state.tree, 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?
|
|
if (!type_record_storage(state, array.type, 0))
|
|
selected_type = ast.field(&state.tree, ast.record_field(&state.tree, type, slot)).type?
|
|
} 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), false)
|
|
if (index == no_index) return false
|
|
selected = ast.array_element(&state.tree, array.value, index)
|
|
if (!type_record_storage(state, array.type, 0))
|
|
selected_type = ast.array_type(&state.tree, type).element
|
|
} 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)
|
|
}
|
|
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 (selected_type != no_node and ast.expr_kind(&state.tree, selected) != .typed_constant) {
|
|
selected = try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, id),
|
|
selected_type, selected)
|
|
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(tree @ast.Tree, record, member ast.NodeId) usize {
|
|
if (ast.expr_kind(tree, member) != .literal_int) {
|
|
for (0..ast.record_field_count(tree, record)) |i| {
|
|
field :: ast.field(tree, ast.record_field(tree, record, i))
|
|
if (field.name != null and ast.name_id(tree, field.name?) == ast.name_id(tree, member))
|
|
return i
|
|
}
|
|
return no_index
|
|
}
|
|
ordinal := usize(0)
|
|
for (ast.literal_text(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(tree, record)) return no_index
|
|
if (ast.field(tree, ast.record_field(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 (!try type_record_check(state, type)) return no_node
|
|
if (ast.expr_kind(&state.tree, expression) == .literal_undefined) {
|
|
result :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, expression), type, expression)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
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
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.type_mismatch, ast.node_span(&state.tree, expression))
|
|
return no_node
|
|
}
|
|
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) {
|
|
literal :: ast.record_literal(&state.tree, expression)
|
|
if (literal.type != null and ast.expr_kind(&state.tree, literal.type?) == .enum_literal) {
|
|
name = ast.enum_literal(&state.tree, literal.type?)
|
|
count :: ast.record_field_count(&state.tree, expression)
|
|
if (count == 1 and ast.field(&state.tree, ast.record_field(&state.tree, expression, 0)).name == null) {
|
|
input = ast.field(&state.tree, ast.record_field(&state.tree, expression, 0)).value?
|
|
} else {
|
|
fields := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&fields)
|
|
for (0..count) |i| try arraylist.append(&fields, ast.record_field(&state.tree, expression, i))
|
|
input = try ast.add_record_literal(state.builder, ast.node_span(&state.tree, expression), null, fields.items)
|
|
try refresh(state)
|
|
}
|
|
} else {
|
|
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
|
|
}
|
|
if (ast.record_field_count(&state.tree, expression) != 1) {
|
|
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, expression, 0))
|
|
if (field.name) |member| name = member
|
|
if (field.value) |value| input = value
|
|
}
|
|
}
|
|
slot :: if (name != no_node) type_record_slot(&state.tree, type, name) else no_index
|
|
if (slot == no_index) {
|
|
code diagnostics.Code :: if (name != no_node) .unknown_field else .union_initializer_count
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
code, 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 (
|
|
empty and kind == .record_literal
|
|
and ast.record_literal(&state.tree, expression).type != null
|
|
and ast.expr_kind(&state.tree, ast.record_literal(&state.tree, expression).type?) == .enum_literal
|
|
and ast.record_field_count(&state.tree, expression) == 0
|
|
) input = no_node
|
|
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 (!type_record_storage(state, type, 0)) {
|
|
constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, type), type, true)
|
|
try refresh(state)
|
|
try arraylist.append(&state.rewrites.constraints, constraint)
|
|
return true
|
|
}
|
|
if (ast.expr_kind(&state.tree, type) == .optional_type)
|
|
return try type_record_check(state, ast.type_operand(&state.tree, type))
|
|
if (ast.expr_kind(&state.tree, type) == .fallible_type) {
|
|
channels :: ast.binary_expr(&state.tree, type)
|
|
error_type :: try canonical_type(state, channels.right)
|
|
if (error_type == no_node) return false
|
|
kind :: ast.expr_kind(&state.tree, error_type)
|
|
if (kind != .enum_type and kind != .record_type and kind != .union_type and kind != .sum_type) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_fallible_type, ast.node_span(&state.tree, type))
|
|
return false
|
|
}
|
|
if (!type_record_storage(state, error_type, 0)) {
|
|
checked :: try ast.add_binary_type(state.builder, ast.node_span(&state.tree, type),
|
|
try builtin(state, type, .void), error_type, true)
|
|
constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, type), checked, true)
|
|
try refresh(state)
|
|
try arraylist.append(&state.rewrites.constraints, constraint)
|
|
} else if (kind == .union_type and !ast.union_type(&state.tree, error_type).auto_tag
|
|
and ast.union_type(&state.tree, error_type).tag == null) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_fallible_type, ast.node_span(&state.tree, type))
|
|
return false
|
|
}
|
|
if (!try type_record_check(state, channels.left)) return false
|
|
return try type_record_check(state, channels.right)
|
|
}
|
|
if (ast.expr_kind(&state.tree, type) == .sum_type) {
|
|
members :: ast.binary_expr(&state.tree, type)
|
|
if (!try type_record_check(state, members.left) or !try type_record_check(state, members.right)) return false
|
|
if (try inline_match_shape(state, type) != no_node) return true
|
|
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_sum_type, ast.node_span(&state.tree, type))
|
|
return false
|
|
}
|
|
if (ast.expr_kind(&state.tree, type) == .slice_type) {
|
|
descriptor :: ast.slice_type(&state.tree, type)
|
|
if (descriptor.mutable or descriptor.sentinel != null) {
|
|
code diagnostics.Code :: if (descriptor.mutable) .comptime_value_escape else .invalid_operand
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, code, ast.node_span(&state.tree, type))
|
|
return false
|
|
}
|
|
return try type_record_check(state, descriptor.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)
|
|
}
|
|
tagged :: ast.expr_kind(&state.tree, type) == .union_type
|
|
tag := no_node
|
|
if (tagged) {
|
|
descriptor :: ast.union_type(&state.tree, type)
|
|
if (!descriptor.auto_tag and descriptor.tag == null) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_record_field_type, ast.node_span(&state.tree, type))
|
|
return false
|
|
}
|
|
if (descriptor.tag) |tag_annotation| {
|
|
tag = try canonical_type(state, tag_annotation)
|
|
if (tag == no_node or ast.expr_kind(&state.tree, tag) != .enum_type) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_union_tag, ast.node_span(&state.tree, tag_annotation))
|
|
return false
|
|
}
|
|
if (!try type_record_check(state, tag)) return false
|
|
}
|
|
}
|
|
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 (tagged) {
|
|
if (field.type == null or field.value != null) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .invalid_record_field_type, field.span)
|
|
return false
|
|
}
|
|
if (field.name == null) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .duplicate_field, field.span)
|
|
return false
|
|
}
|
|
if (tag != no_node) {
|
|
found := false
|
|
for (0..ast.enum_member_count(&state.tree, tag)) |j| {
|
|
member :: ast.field(&state.tree, ast.enum_member(&state.tree, tag, j))
|
|
if (member.name != null and ast.name_id(&state.tree, member.name?) == ast.name_id(&state.tree, field.name?)) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if (!found) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .invalid_union_tag, field.span)
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
if (field.name != null and type_record_slot(&state.tree, 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 record snapshots in declaration order through their declared context.
|
|
# undefined records retain per-leaf initialization state without applying defaults.
|
|
@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) == .literal_undefined)
|
|
return try type_storage_undefined(state, expression, annotation)
|
|
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.tree, 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.tree, 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, boundary bool) 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) or (!boundary and value.magnitude == u64(count))) {
|
|
code diagnostics.Code :: if (boundary) .invalid_slice_bounds else .index_out_of_bounds
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, code, 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 == .builtin_call and contextual_literal(state, value))
|
|
return try type_array_initialized(state, ast.call_arg(&state.tree, value, 0), 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_storage_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 and ast.expr_kind(&state.tree, type) == .union_type
|
|
and type_record_storage(state, type, 0)
|
|
) {
|
|
if (!try type_record_check(state, type)) return no_node
|
|
result :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, expression), type, expression)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
if (type != no_node and ast.expr_kind(&state.tree, type) == .record_type) {
|
|
if (!try type_record_check(state, type)) return no_node
|
|
fields := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&fields)
|
|
for (0..ast.record_field_count(&state.tree, type)) |i| {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, type, i))
|
|
if (!try type_step(state, expression)) return no_node
|
|
value :: try type_storage_undefined(state, expression, 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)
|
|
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
|
|
}
|
|
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_storage_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_storage_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_storage_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
|
|
}
|
|
|
|
# readonly metadata views own their leaves. mutable backing needs borrowing semantics,
|
|
# not a snapshot that silently stops observing later element writes.
|
|
@hide:file
|
|
type_slice_value 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
|
|
type :: try canonical_type(state, annotation)
|
|
if (!try type_record_check(state, type)) return no_node
|
|
descriptor :: ast.slice_type(&state.tree, type)
|
|
span :: ast.node_span(&state.tree, expression)
|
|
kind :: ast.expr_kind(&state.tree, expression)
|
|
if (kind == .literal_undefined) {
|
|
result :: try ast.add_typed_constant(state.builder, span, type, expression)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
if (kind == .typed_constant) {
|
|
actual :: ast.typed_constant(&state.tree, expression)
|
|
if (try same_type(state, type, actual.type)) return expression
|
|
}
|
|
source := expression
|
|
start := usize(0)
|
|
end := no_index
|
|
if (kind == .slice) {
|
|
view :: ast.slice_expr(&state.tree, expression)
|
|
if (view.sentinel != null) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .invalid_operand, span)
|
|
return no_node
|
|
}
|
|
source = view.base
|
|
}
|
|
if (ast.expr_kind(&state.tree, source) == .array_literal) {
|
|
for (0..ast.array_element_count(&state.tree, source)) |_| {
|
|
if (!try type_step(state, expression)) return no_node
|
|
}
|
|
length :: try ast.add_integer(state.builder, span, u64(ast.array_element_count(&state.tree, source)))
|
|
array :: try ast.add_array_type(state.builder, span, length, descriptor.element, false)
|
|
try refresh(state)
|
|
source = try type_array_value(state, source, array)
|
|
} else {
|
|
actual :: try expression_type(state, source, 0)
|
|
if (actual == no_node) return no_node
|
|
actual_kind :: ast.expr_kind(&state.tree, actual)
|
|
if ((actual_kind != .array_type and actual_kind != .slice_type)
|
|
or (actual_kind == .array_type and kind != .slice)) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .type_mismatch, span)
|
|
return no_node
|
|
}
|
|
mutable :: if (actual_kind == .array_type) ast.array_type(&state.tree, actual).mutable
|
|
else ast.slice_type(&state.tree, actual).mutable
|
|
element :: if (actual_kind == .array_type) ast.array_type(&state.tree, actual).element
|
|
else ast.slice_type(&state.tree, actual).element
|
|
if (mutable) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_value_escape, span)
|
|
return no_node
|
|
}
|
|
if (!try same_type(state, descriptor.element, element)) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .type_mismatch, span)
|
|
return no_node
|
|
}
|
|
source = try type_execution_value(state, source, actual)
|
|
}
|
|
if (source == no_node) return no_node
|
|
values :: ast.typed_constant(&state.tree, source).value
|
|
if (ast.expr_kind(&state.tree, values) == .literal_undefined) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .read_before_initialization, span)
|
|
return no_node
|
|
}
|
|
count :: ast.array_element_count(&state.tree, values)
|
|
end = count
|
|
if (kind == .slice) {
|
|
view :: ast.slice_expr(&state.tree, expression)
|
|
if (view.start) |bound| start = try type_array_index(state, bound, count, true)
|
|
if (start == no_index) return no_node
|
|
if (view.end) |bound| end = try type_array_index(state, bound, count, true)
|
|
if (end == no_index) return no_node
|
|
if (start > end) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .invalid_slice_bounds, span)
|
|
return no_node
|
|
}
|
|
}
|
|
elements := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&elements)
|
|
for (start..end) |i| {
|
|
if (!try type_step(state, expression)) return no_node
|
|
try arraylist.append(&elements, ast.array_element(&state.tree, values, i))
|
|
}
|
|
literal :: try ast.add_array_literal(state.builder, span, elements.items)
|
|
result :: try ast.add_typed_constant(state.builder, span, type, literal)
|
|
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
|
|
and (ast.expr_kind(&state.tree, type) != .union_type or !type_record_storage(state, type, 0)))
|
|
) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.invalid_index_assignment, ast.node_span(&state.tree, target))
|
|
return no_node
|
|
}
|
|
tagged :: ast.expr_kind(&state.tree, type) == .union_type
|
|
record :: ast.expr_kind(&state.tree, type) == .record_type or tagged
|
|
if (tagged and ast.expr_kind(&state.tree, array.value) == .literal_undefined) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.read_before_initialization, ast.node_span(&state.tree, target))
|
|
return no_node
|
|
}
|
|
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.tree, 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)
|
|
if (tagged) {
|
|
active :: ast.field(&state.tree, ast.record_field(&state.tree, array.value, 0))
|
|
member :: ast.field(&state.tree, ast.record_field(&state.tree, type, index)).name?
|
|
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, first))
|
|
return no_node
|
|
}
|
|
index = 0
|
|
}
|
|
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, false)
|
|
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
|
|
}
|
|
|
|
# optional metadata uses the same explicit presence encoding as serialized values.
|
|
# an inner null is a payload only when wrapped in some!, never outer absence.
|
|
@hide:file
|
|
type_optional_value proc(state @mut State, expression, type ast.NodeId) ast.NodeId ! Error {
|
|
if (!try type_record_check(state, type)) return no_node
|
|
kind :: ast.expr_kind(&state.tree, expression)
|
|
if (kind == .literal_undefined or kind == .literal_null) {
|
|
result :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, expression), type, expression)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
child :: ast.type_operand(&state.tree, type)
|
|
input := expression
|
|
if (kind == .typed_constant) {
|
|
actual :: ast.typed_constant(&state.tree, expression)
|
|
if (try same_type(state, type, actual.type)) return expression
|
|
} else if (kind == .builtin_call
|
|
and mem.eql(ast.literal_text(&state.tree, ast.builtin_call(&state.tree, expression).name), "some")) {
|
|
if (ast.call_arg_count(&state.tree, expression) != 1) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
|
.argument_count_mismatch, ast.node_span(&state.tree, expression))
|
|
return no_node
|
|
}
|
|
input = ast.call_arg(&state.tree, expression, 0)
|
|
}
|
|
payload :: try type_execution_value(state, input, child)
|
|
if (payload == no_node) return no_node
|
|
span :: ast.node_span(&state.tree, expression)
|
|
name :: try metadata_member(state, expression, "some")
|
|
arguments [1]ast.NodeId :: [payload]
|
|
present :: try ast.add_builtin_call(state.builder, span, name, arguments[..])
|
|
result :: try ast.add_typed_constant(state.builder, span, type, present)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
type_variant proc(state @mut State, annotation, name ast.NodeId) bool ! Error {
|
|
type :: try canonical_type(state, annotation)
|
|
if (type == no_node) return false
|
|
kind :: ast.expr_kind(&state.tree, type)
|
|
if (kind == .sum_type) {
|
|
members :: ast.binary_expr(&state.tree, type)
|
|
return try type_variant(state, members.left, name) or try type_variant(state, members.right, name)
|
|
}
|
|
if (kind == .union_type) return type_record_slot(&state.tree, type, name) != no_index
|
|
if (kind != .enum_type) return false
|
|
for (0..ast.enum_member_count(&state.tree, type)) |i| {
|
|
member :: ast.field(&state.tree, ast.enum_member(&state.tree, type, i)).name?
|
|
if (ast.name_id(&state.tree, member) == ast.name_id(&state.tree, name)) return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
# the payload's declared channel is retained even when success and error values
|
|
# have overlapping shapes. no type-bearing snapshot enters runtime IR.
|
|
@hide:file
|
|
type_fallible_value proc(state @mut State, expression, type ast.NodeId) ast.NodeId ! Error {
|
|
if (!try type_record_check(state, type)) return no_node
|
|
span :: ast.node_span(&state.tree, expression)
|
|
kind :: ast.expr_kind(&state.tree, expression)
|
|
if (kind == .literal_undefined) {
|
|
result :: try ast.add_typed_constant(state.builder, span, type, expression)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
channels :: ast.binary_expr(&state.tree, type)
|
|
actual :: try expression_type(state, expression, 0)
|
|
if (actual != no_node and ast.expr_kind(&state.tree, actual) == .fallible_type) {
|
|
if (try same_type(state, actual, type)) return expression
|
|
source :: ast.binary_expr(&state.tree, actual)
|
|
if (!try same_type(state, source.left, channels.left)
|
|
or !try type_subset(state, source.right, channels.right)) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .type_mismatch, span)
|
|
return no_node
|
|
}
|
|
return try type_fallible_value(state, ast.typed_constant(&state.tree, expression).value, type)
|
|
}
|
|
channel := channels.left
|
|
if (actual != no_node and !try same_type(state, actual, channels.left)
|
|
and try type_subset(state, actual, channels.right)) channel = channels.right
|
|
name := no_node
|
|
if (kind == .enum_literal) name = ast.enum_literal(&state.tree, expression)
|
|
if (kind == .record_literal and ast.record_literal(&state.tree, expression).type != null) {
|
|
tag :: ast.record_literal(&state.tree, expression).type?
|
|
if (ast.expr_kind(&state.tree, tag) == .enum_literal) name = ast.enum_literal(&state.tree, tag)
|
|
}
|
|
if (name != no_node and try type_variant(state, channels.right, name)
|
|
and !try type_variant(state, channels.left, name)) channel = channels.right
|
|
if (kind == .record_literal and ast.record_literal(&state.tree, expression).type == null) {
|
|
success :: try canonical_type(state, channels.left)
|
|
failure :: try canonical_type(state, channels.right)
|
|
if ((success == no_node or ast.expr_kind(&state.tree, success) != .record_type)
|
|
and ast.expr_kind(&state.tree, failure) == .record_type) channel = channels.right
|
|
}
|
|
payload := try type_execution_value(state, expression, channel)
|
|
if (payload == no_node) return no_node
|
|
if (ast.expr_kind(&state.tree, payload) != .typed_constant) {
|
|
payload = try ast.add_typed_constant(state.builder, span, channel, payload)
|
|
try refresh(state)
|
|
}
|
|
result :: try ast.add_typed_constant(state.builder, span, type, payload)
|
|
try refresh(state)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
type_sum_value proc(state @mut State, expression, type ast.NodeId) ast.NodeId ! Error {
|
|
if (!try type_record_check(state, type)) return no_node
|
|
actual :: try expression_type(state, expression, 0)
|
|
if (actual != no_node and try same_type(state, actual, type)) return expression
|
|
shape :: try inline_match_shape(state, type)
|
|
if (shape == no_node) return no_node
|
|
span :: ast.node_span(&state.tree, expression)
|
|
input := expression
|
|
if (actual != no_node and try type_subset(state, actual, type)) {
|
|
source :: try canonical_type(state, actual)
|
|
if (ast.expr_kind(&state.tree, source) == .record_type) {
|
|
for (0..ast.record_field_count(&state.tree, shape)) |i| {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, shape, i))
|
|
if (!try same_type(state, field.type?, actual)) continue
|
|
member :: try ast.add_field(state.builder, span, field.name, null, expression)
|
|
fields [1]ast.NodeId :: [member]
|
|
input = try ast.add_record_literal(state.builder, span, null, fields[..])
|
|
try refresh(state)
|
|
break
|
|
}
|
|
} else if (ast.expr_kind(&state.tree, expression) == .typed_constant)
|
|
input = ast.typed_constant(&state.tree, expression).value
|
|
}
|
|
value :: try type_union_value(state, input, shape)
|
|
if (value == no_node) return no_node
|
|
result :: try ast.add_typed_constant(state.builder, span, type,
|
|
ast.typed_constant(&state.tree, value).value)
|
|
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?)) {
|
|
if (try metadata_sequence(state, annotation))
|
|
return try type_slice_value(state, expression, annotation)
|
|
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 (try metadata_sequence(state, annotation))
|
|
return try type_slice_value(state, expression, annotation)
|
|
if (!is_type(state, annotation) and type_record_storage(state, annotation, 0)) {
|
|
type :: try canonical_type(state, annotation)
|
|
if (type != no_node and ast.expr_kind(&state.tree, type) == .optional_type)
|
|
return try type_optional_value(state, expression, type)
|
|
if (type != no_node and ast.expr_kind(&state.tree, type) == .fallible_type)
|
|
return try type_fallible_value(state, expression, type)
|
|
if (type != no_node and ast.expr_kind(&state.tree, type) == .sum_type)
|
|
return try type_sum_value(state, expression, type)
|
|
}
|
|
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)
|
|
result_type :: binding(state, try canonical_type(state, annotation))
|
|
if (result_type.kind == .builtin and result_type.data.builtin == .void) {
|
|
if (kind == .literal_void) return expression
|
|
value :: try evaluate_ir(state, expression, annotation)
|
|
if (value.kind == .invalid) return no_node
|
|
return (try materialize(state, value)).node
|
|
}
|
|
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{}
|
|
}
|
|
annotation :: try expression_type(state, expression, 0)
|
|
if (type_record_storage(state, annotation, 0)) {
|
|
type :: try canonical_type(state, annotation)
|
|
if (type != no_node and ast.expr_kind(&state.tree, type) == .optional_type) {
|
|
value :: try type_execution_value(state, expression, type)
|
|
if (value == no_node or !try type_array_initialized(state, value, expression)) return Value{}
|
|
return Value{ kind = .boolean, node = expression,
|
|
boolean = ast.expr_kind(&state.tree, ast.typed_constant(&state.tree, value).value) != .literal_null }
|
|
}
|
|
}
|
|
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
|
|
}
|
|
|
|
# borrowed places live only in one checked interpreter invocation. its result owns
|
|
# snapshots of the changed roots; neither slice pointers nor interpreter cells escape.
|
|
@hide:file
|
|
type_borrowed_place proc(state @State, id, declaration ast.NodeId, borrowed bool) bool {
|
|
resolved :: binding(state, id)
|
|
if (borrowed and resolved.kind == .declaration and resolved.data.declaration == declaration) return true
|
|
borrowing := borrowed
|
|
if (ast.node_class(&state.tree, id) == .expression) {
|
|
kind :: ast.expr_kind(&state.tree, id)
|
|
borrowing = borrowing or kind == .slice
|
|
if (kind == .unary) borrowing = borrowing or ast.unary_expr(&state.tree, id).operator == .address_of
|
|
}
|
|
for (0..ast.child_count(&state.tree, id)) |i|
|
|
if (type_borrowed_place(state, ast.child(&state.tree, id, i), declaration, borrowing)) return true
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
type_call_borrows proc(state @State, execution @TypeExecution, call ast.NodeId) bool {
|
|
for (execution.locals.items) |local| {
|
|
if (type_borrowed_place(state, call, local.declaration, false)) return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
@hide:file
|
|
TypeBorrow :: struct { index usize, previous ast.NodeId, temporary ast.NodeId }
|
|
|
|
@hide:file
|
|
type_snapshot_uninitialized proc(state @State, id ast.NodeId) bool {
|
|
if (ast.expr_kind(&state.tree, id) == .literal_undefined) return true
|
|
if (ast.expr_kind(&state.tree, id) == .typed_constant)
|
|
return type_snapshot_uninitialized(state, ast.typed_constant(&state.tree, id).value)
|
|
if (ast.expr_kind(&state.tree, id) == .array_literal) {
|
|
for (0..ast.array_element_count(&state.tree, id)) |i|
|
|
if (type_snapshot_uninitialized(state, ast.array_element(&state.tree, id, i))) return true
|
|
}
|
|
if (ast.expr_kind(&state.tree, id) == .record_literal) {
|
|
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 and type_snapshot_uninitialized(state, field.value?)) return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
# initialization state is not a value: recreate it as storage and write only
|
|
# initialized subobjects before entering the interpreter's borrowed call.
|
|
@hide:file
|
|
type_restore_snapshot proc(
|
|
state @mut State, target, input ast.NodeId, statements @mut arraylist.ArrayList(ast.NodeId),
|
|
) void ! Error {
|
|
value := input
|
|
if (ast.expr_kind(&state.tree, value) == .typed_constant) value = ast.typed_constant(&state.tree, value).value
|
|
kind :: ast.expr_kind(&state.tree, value)
|
|
if (kind == .literal_undefined) return
|
|
if (!type_snapshot_uninitialized(state, value)) {
|
|
assignment :: try ast.add_assignment_stmt(state.builder, ast.node_span(&state.tree, value), target, input)
|
|
try arraylist.append(statements, assignment)
|
|
try refresh(state)
|
|
return
|
|
}
|
|
count :: if (kind == .array_literal) ast.array_element_count(&state.tree, value)
|
|
else ast.record_field_count(&state.tree, value)
|
|
for (0..count) |i| {
|
|
child := no_node
|
|
place := no_node
|
|
span :: ast.node_span(&state.tree, value)
|
|
if (kind == .array_literal) {
|
|
child = ast.array_element(&state.tree, value, i)
|
|
index :: try ast.add_integer(state.builder, span, u64(i))
|
|
place = try ast.add_index(state.builder, span, target, index)
|
|
} else {
|
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, value, i))
|
|
child = field.value?
|
|
member := no_node
|
|
if (field.name) |name| member = name
|
|
else member = try ast.add_integer(state.builder, span, u64(i))
|
|
place = try ast.add_selection(state.builder, span, target, member)
|
|
}
|
|
try refresh(state)
|
|
try type_restore_snapshot(state, place, child, statements)
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
type_borrow_call proc(state @mut State, execution @mut TypeExecution, call ast.NodeId) ast.NodeId ! Error {
|
|
saved_demand :: state.demand_depth
|
|
state.demand_depth = 0
|
|
defer state.demand_depth = saved_demand
|
|
span :: ast.node_span(&state.tree, call)
|
|
borrowed := arraylist.init(TypeBorrow, state.allocator)
|
|
defer arraylist.deinit(&borrowed)
|
|
statements := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&statements)
|
|
fields := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&fields)
|
|
values := arraylist.init(ast.NodeId, state.allocator)
|
|
defer arraylist.deinit(&values)
|
|
for (execution.locals.items) |local, i| {
|
|
if (!type_borrowed_place(state, call, local.declaration, false)) continue
|
|
if (type_record_storage(state, local.annotation, 0)) {
|
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_value_escape, span)
|
|
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
|
|
partial :: type_snapshot_uninitialized(state, local.value)
|
|
initializer := local.value
|
|
if (partial) initializer = try ast.add_literal(state.builder, .undefined, span)
|
|
temporary :: try ast.add_statement_decl(state.builder, span, span, strpool.no_str,
|
|
local.annotation, mutability, initializer)
|
|
try refresh(state)
|
|
try arraylist.append(&statements, temporary)
|
|
if (partial) try type_restore_snapshot(state, try reference(state, call, temporary), local.value, &statements)
|
|
try arraylist.append(&borrowed, TypeBorrow{ index = i, previous = local.value, temporary = temporary })
|
|
}
|
|
snapshot := no_node
|
|
{
|
|
saved_values :: execution.evaluate_values
|
|
execution.evaluate_values = false
|
|
defer execution.evaluate_values = saved_values
|
|
defer for (borrowed.items) |entry| execution.locals.items[entry.index].value = entry.previous
|
|
for (borrowed.items) |entry|
|
|
execution.locals.items[entry.index].value = try reference(state, call, entry.temporary)
|
|
snapshot = try type_snapshot(state, execution, call)
|
|
}
|
|
if (snapshot == no_node) return no_node
|
|
try expand(state, snapshot)
|
|
result_type :: try expression_type(state, snapshot, 0)
|
|
if (result_type == no_node) return no_node
|
|
resolved_result :: binding(state, try canonical_type(state, result_type))
|
|
void_result :: resolved_result.kind == .builtin and resolved_result.data.builtin == .void
|
|
result_offset :: if (void_result) usize(0) else usize(1)
|
|
if (void_result) {
|
|
discarded :: try ast.add_expression_stmt(state.builder, snapshot)
|
|
try arraylist.append(&statements, discarded)
|
|
} else {
|
|
result_local :: try ast.add_statement_decl(state.builder, span, span, strpool.no_str,
|
|
result_type, .constant, snapshot)
|
|
try refresh(state)
|
|
try arraylist.append(&statements, result_local)
|
|
result_field :: try ast.add_field(state.builder, span, null, result_type, null)
|
|
result_value :: try ast.add_field(state.builder, span, null, null, try reference(state, call, result_local))
|
|
try arraylist.append(&fields, result_field)
|
|
try arraylist.append(&values, result_value)
|
|
}
|
|
for (borrowed.items) |entry| {
|
|
field :: try ast.add_field(state.builder, span, null, execution.locals.items[entry.index].annotation, null)
|
|
value :: try ast.add_field(state.builder, span, null, null, try reference(state, call, entry.temporary))
|
|
try arraylist.append(&fields, field)
|
|
try arraylist.append(&values, value)
|
|
}
|
|
tuple :: try ast.add_record_type(state.builder, span, fields.items)
|
|
literal :: try ast.add_record_literal(state.builder, span, null, values.items)
|
|
returned :: try ast.add_return_stmt(state.builder, span, literal)
|
|
try arraylist.append(&statements, returned)
|
|
body :: try ast.add_block(state.builder, span, statements.items)
|
|
try refresh(state)
|
|
saved_uninitialized :: state.snapshot_uninitialized
|
|
state.snapshot_uninitialized = true
|
|
defer state.snapshot_uninitialized = saved_uninitialized
|
|
result :: try evaluate_ir_body(state, call, body, tuple)
|
|
if (result.kind != .aggregate) return no_node
|
|
for (borrowed.items) |entry, i| {
|
|
value :: ast.field(&state.tree, ast.record_field(&state.tree, result.node, i + result_offset)).value?
|
|
owned :: try ast.add_typed_constant(state.builder, span,
|
|
execution.locals.items[entry.index].annotation, value)
|
|
try refresh(state)
|
|
execution.locals.items[entry.index].value = owned
|
|
}
|
|
if (void_result) {
|
|
value :: try ast.add_literal(state.builder, .void, span)
|
|
try refresh(state)
|
|
return value
|
|
}
|
|
value :: ast.field(&state.tree, ast.record_field(&state.tree, result.node, 0)).value?
|
|
owned :: try ast.add_typed_constant(state.builder, span, result_type, value)
|
|
try refresh(state)
|
|
return owned
|
|
}
|
|
|
|
@hide:file
|
|
execute_type_block proc(state @mut State, execution @mut TypeExecution, block ast.NodeId) TypeFlow ! Error {
|
|
if (execution.residual != null and !execution.evaluate_values) {
|
|
block_value :: try residual_block(state, execution, block, execution.residual_boundary)
|
|
if (block_value == no_node) return .invalid
|
|
statement :: try ast.add_block_stmt(state.builder, block_value)
|
|
try refresh(state)
|
|
try arraylist.append(execution.residual?, statement)
|
|
return execution.residual_flow
|
|
}
|
|
if (block == execution.inline_body) {
|
|
saved_demand :: state.demand_depth
|
|
state.demand_depth = 0
|
|
defer state.demand_depth = saved_demand
|
|
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)
|
|
kind :: ast.stmt_kind(&state.tree, statement)
|
|
if (kind != .defer_stmt and !(kind == .errdefer_stmt and execution.failed)) 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_view :: ast.cleanup_stmt(&state.tree, statement)
|
|
if (kind == .errdefer_stmt and cleanup_view.capture != null) {
|
|
annotation :: ast.binary_expr(&state.tree, try canonical_type(state, execution.result_type)).right
|
|
value :: ast.typed_constant(&state.tree, execution.returned).value
|
|
try arraylist.append(&execution.locals, TypeLocal{
|
|
declaration = cleanup_view.capture?, value = value, annotation = annotation,
|
|
})
|
|
}
|
|
cleanup :: try execute_type_block(state, execution, cleanup_view.body)
|
|
if (kind == .errdefer_stmt and cleanup_view.capture != null)
|
|
execution.locals.items = execution.locals.items[..execution.locals.items.len - 1]
|
|
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: {
|
|
result :: try canonical_type(state, execution.result_type)
|
|
if (result == no_node or ast.expr_kind(&state.tree, result) != .fallible_type) {
|
|
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) {
|
|
type :: try canonical_type(state, execution.result_type)
|
|
if (type != no_node and ast.expr_kind(&state.tree, type) == .fallible_type) {
|
|
failed :: try type_fallible_failed(state, execution.returned, type, value)
|
|
if (failed == null) return .invalid
|
|
execution.failed = failed?
|
|
}
|
|
}
|
|
if (execution.returned != no_node) return .returned
|
|
} else {
|
|
execution.returned = try type_void_completion(state, execution.result_type, statement)
|
|
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,
|
|
contextual = declaration.type == null and flexible(state, expression),
|
|
})
|
|
}
|
|
.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)
|
|
if (expression == no_node) return .invalid
|
|
if (execution.locals.items[index].contextual and !flexible(state, expression)) {
|
|
if (!try expand_type_matches(state, expression)) return .invalid
|
|
actual :: try expression_type(state, expression, 0)
|
|
resolved_type :: binding(state, actual)
|
|
numeric :: (try integer_type(state, actual)
|
|
or (resolved_type.kind == .builtin
|
|
and builtin_identity(resolved_type.data.builtin) == .float))
|
|
if (numeric) execution.locals.items[index].annotation = actual
|
|
}
|
|
value :: try type_execution_value(state, expression, execution.locals.items[index].annotation)
|
|
if (value == no_node) return .invalid
|
|
execution.locals.items[index].value = value
|
|
contextual :: execution.locals.items[index].contextual and flexible(state, expression)
|
|
execution.locals.items[index].contextual = contextual
|
|
}
|
|
.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 == .invalid) {
|
|
state.evaluation_diagnostic = resolved.data.diagnostic
|
|
return Value{}
|
|
}
|
|
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, .slice_constant: {
|
|
value :: if (kind == .union_constant) ast.union_constant(&state.tree, id) else ast.slice_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, .slice_constant: {
|
|
annotation :: if (kind == .union_constant) ast.union_constant(&state.tree, id).type
|
|
else if (kind == .slice_constant) ast.slice_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") or mem.eql(name, "rem")) 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.tree, 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_call(state, id)
|
|
}
|