feat: execute nested compile-time type matches

This commit is contained in:
2026-10-27 01:03:19 +01:00
parent 85c60f2416
commit e5215c4092
7 changed files with 193 additions and 22 deletions
+56 -8
View File
@@ -67,7 +67,7 @@ deinit proc(result @mut Result) void {
while (index > 0) {
index -= 1
change :: result.changes.items[index]
ast.replace_node(result.builder, change.target, change.saved)
ast.replace_node_from_source(result.builder, change.target, change.saved)
}
arraylist.deinit(&result.changes)
arraylist.deinit(&result.constraints)
@@ -583,11 +583,14 @@ instantiate proc(state @mut State, index usize, call ast.NodeId) void ! Error {
}
}
if (is_type(state, view.return_type)) {
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))
if (returned == no_node) {
try reject_evaluation(state, call)
try reject_type_result(state, call, returned, constraints_start)
return
}
result := returned
@@ -603,9 +606,20 @@ instantiate proc(state @mut State, index usize, call ast.NodeId) void ! Error {
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| try expand(state, state.rewrites.constraints.items[i])
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)
@@ -637,6 +651,16 @@ reject_evaluation proc(state @mut State, call ast.NodeId) void ! Error {
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),
@@ -976,9 +1000,6 @@ TypeFlow :: enum { next, returned, break, continue, invalid }
return_type_value proc(state @mut State, block, first_owned 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.
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)),
@@ -1060,6 +1081,7 @@ nominal_type_value proc(state @State, id ast.NodeId) bool {
# 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 {
if (!try expand_type_matches(state, ast.match_expr(&state.tree, id).subject)) return no_index
arms := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&arms)
patterns := arraylist.init(ast.NodeId, state.allocator)
@@ -1074,8 +1096,11 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
return no_index
}
patterns.items = patterns.items[..0]
for (0..ast.match_pattern_count(&state.tree, arm_id)) |j|
try arraylist.append(&patterns, ast.match_pattern(&state.tree, arm_id, j))
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 arraylist.append(&patterns, pattern)
}
span :: ast.node_span(&state.tree, arm_id)
index :: try ast.add_integer(state.builder, span, u64(i))
selector :: try ast.add_match_arm(state.builder, span, patterns.items,
@@ -1105,10 +1130,31 @@ type_value_expression proc(state @mut State, input ast.NodeId) ast.NodeId ! Erro
return expression
}
# Normalize only selected expression arms before ordinary expansion can visit them.
# 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 (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 true
}
@hide:file
type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.NodeId ! Error {
if (annotation == no_node) return no_node
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 })
@@ -1117,6 +1163,7 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No
try arraylist.append(&state.rewrites.constraints, constraint)
return concrete.node
}
if (!try expand_type_matches(state, expression)) return no_node
try expand(state, expression)
resolved :: binding(state, expression)
if (resolved.kind == .invalid) {
@@ -1178,6 +1225,7 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
if (declaration.type) |type| annotation = try type_snapshot(state, execution, type)
else if (nominal_type_value(state, expression)) annotation = try builtin(state, expression, .type)
else {
if (!try expand_type_matches(state, expression)) return .invalid
try expand(state, expression)
annotation = try expression_type(state, expression, 0)
if (annotation == no_node) {