feat: execute tagged-union matches in type procedures

Preserve resolver failures on synthetic selector captures before elaborating payloads; selector bodies omit the original capture uses.

Verified bro build, bro test (250 passed), and tests/native.py (536 interpreter/native programs at -O0 and -O2, allocator, LLVM and CLI checks).
This commit is contained in:
2026-10-27 01:41:19 +01:00
parent bf65f3c2c3
commit 182bb3c3e0
5 changed files with 236 additions and 13 deletions
+63 -5
View File
@@ -1117,6 +1117,14 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
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
}
}
if (i > 0 and arm.expression != ast.match_arm_view(&state.tree,
ast.match_arm(&state.tree, id, 0)).expression) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
@@ -1144,6 +1152,48 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
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
annotation :: try expression_type(state, subject, 0)
value :: 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))
for (0..ast.record_field_count(&state.tree, annotation)) |i| {
field :: ast.field(&state.tree, ast.record_field(&state.tree, annotation, i))
if (field.name != null and payload.name != null
and ast.name_id(&state.tree, field.name?) == ast.name_id(&state.tree, payload.name?)) {
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_expression_body proc(state @mut State, id ast.NodeId, arm ast.MatchArmView) ast.NodeId ! Error {
if (!arm.expression) return no_node
if (arm.capture == null) return arm.body
value :: try type_match_capture(state, id)
if (value == no_node) return no_node
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),
}
defer arraylist.deinit(&execution.locals)
defer mem.free(state.allocator, execution.mapping)
mem.set(execution.mapping, no_node)
try arraylist.append(&execution.locals, TypeLocal{
declaration = arm.capture?, value = value, annotation = ast.typed_constant(&state.tree, value).type,
})
return try type_snapshot(state, &execution, arm.body)
}
@hide:file
type_value_expression proc(state @mut State, input ast.NodeId) ast.NodeId ! Error {
expression := input
@@ -1152,8 +1202,8 @@ type_value_expression proc(state @mut State, input ast.NodeId) ast.NodeId ! Erro
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))
if (!arm.expression or arm.capture != null) return no_node
expression = arm.body
expression = try type_match_expression_body(state, expression, arm)
if (expression == no_node) return no_node
}
return expression
}
@@ -1334,10 +1384,18 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
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))
if (arm.capture != null) return .invalid
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
try arraylist.append(&execution.locals, TypeLocal{
declaration = capture, value = value, annotation = ast.typed_constant(&state.tree, value).type,
})
}
if (!arm.expression) return try execute_type_block(state, execution, arm.body)
value :: try type_value_expression(state, ast.match_arm_view(&state.tree,
ast.match_arm(&state.tree, snapshot, index)).body)
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