Enforce exhaustive matches for AST nodes

This commit addresses the critical rule regarding exhaustive `match`
expressions on AST node kinds. By explicitly listing all variants
instead of using wildcards (`_ =>`), we ensure that new AST node types
are consciously handled throughout the compilation pipeline. This
practice prevents subtle bugs and makes the compiler more robust against
future changes.

The `CLAUDE.md` file has been updated to reflect this critical rule.

The deleted `BUG_program_node_typeinference.md` file indicates that a
previously identified bug related to type inference with `Program` nodes
has been resolved, likely as a consequence of enforcing these exhaustive
matches.
This commit is contained in:
2026-03-31 17:33:45 +02:00
parent 8d14da82c5
commit 7d6e040721
12 changed files with 444 additions and 205 deletions
+49 -13
View File
@@ -62,17 +62,14 @@ impl VMObserver for TracingObserver {
fn after_eval(&mut self, vm: &VM, node: &ExecNode, res: &Result<Value, String>) {
self.indent = self.indent.saturating_sub(1);
let pad = self.pad();
match &node.kind {
NodeKind::Def { .. } | NodeKind::Assign { .. } => {
let s_pad = format!("{}| ", pad);
self.logs.push(format!("{}--- Scope Status ---", s_pad));
self.logs.push(format!(
"{}Stack (top 5): {:?}",
s_pad,
vm.stack.iter().rev().take(5).collect::<Vec<_>>()
));
}
_ => {}
if matches!(&node.kind, NodeKind::Def { .. } | NodeKind::Assign { .. }) {
let s_pad = format!("{}| ", pad);
self.logs.push(format!("{}--- Scope Status ---", s_pad));
self.logs.push(format!(
"{}Stack (top 5): {:?}",
s_pad,
vm.stack.iter().rev().take(5).collect::<Vec<_>>()
));
}
let res_str = match res {
Ok(v) => format!("{}", v),
@@ -864,7 +861,26 @@ impl VM {
}
Ok(())
}
_ => {
// All other variants: evaluate and push result(s) onto stack.
NodeKind::Nop
| NodeKind::Identifier { .. }
| NodeKind::FieldAccessor(_)
| NodeKind::Def { .. }
| NodeKind::Assign { .. }
| NodeKind::Lambda { .. }
| NodeKind::Call { .. }
| NodeKind::Again { .. }
| NodeKind::If { .. }
| NodeKind::Block { .. }
| NodeKind::Program { .. }
| NodeKind::Record { .. }
| NodeKind::GetField { .. }
| NodeKind::Extension(_)
| NodeKind::Error
| NodeKind::MacroDecl { .. }
| NodeKind::Template(_)
| NodeKind::Placeholder(_)
| NodeKind::Splice(_) => {
let val = self.eval_internal(obs, args)?;
if let Some(slice) = val.as_slice() {
self.stack.extend_from_slice(slice);
@@ -1014,7 +1030,27 @@ impl VM {
}
Ok(())
}
_ => Err("Invalid node in parameter pattern".to_string()),
// Parameter patterns should only contain Identifier and Tuple.
NodeKind::Nop
| NodeKind::Constant(_)
| NodeKind::FieldAccessor(_)
| NodeKind::Def { .. }
| NodeKind::Assign { .. }
| NodeKind::Lambda { .. }
| NodeKind::Call { .. }
| NodeKind::Again { .. }
| NodeKind::If { .. }
| NodeKind::Block { .. }
| NodeKind::Program { .. }
| NodeKind::Record { .. }
| NodeKind::GetField { .. }
| NodeKind::Expansion { .. }
| NodeKind::Extension(_)
| NodeKind::Error
| NodeKind::MacroDecl { .. }
| NodeKind::Template(_)
| NodeKind::Placeholder(_)
| NodeKind::Splice(_) => Err("Invalid node in parameter pattern".to_string()),
}
}
}