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
+50 -82
View File
@@ -1,6 +1,6 @@
use crate::ast::nodes::{
Address, AnalyzedNode, GlobalIdx, IdentifierBinding, Node,
NodeKind, NodeMetrics, TypedNode, TypedPhase,
NodeKind, NodeMetrics, TypedNode,
};
use crate::ast::types::{Identity, Purity, Value};
use std::collections::{HashMap, HashSet};
@@ -58,10 +58,56 @@ impl<'a> Analyzer<'a> {
self.collect_globals(e);
}
}
_ => {
node.kind
.for_each_child(|child| self.collect_globals(child));
NodeKind::If { cond, then_br, else_br } => {
self.collect_globals(cond);
self.collect_globals(then_br);
if let Some(e) = else_br {
self.collect_globals(e);
}
}
NodeKind::Lambda { params, body, .. } => {
self.collect_globals(params);
self.collect_globals(body);
}
NodeKind::Call { callee, args } => {
self.collect_globals(callee);
self.collect_globals(args);
}
NodeKind::Again { args } => {
self.collect_globals(args);
}
NodeKind::Assign { target, value, .. } => {
self.collect_globals(target);
self.collect_globals(value);
}
NodeKind::Tuple { elements } => {
for e in elements {
self.collect_globals(e);
}
}
NodeKind::Record { fields, .. } => {
for (k, v) in fields {
self.collect_globals(k);
self.collect_globals(v);
}
}
NodeKind::GetField { rec, .. } => {
self.collect_globals(rec);
}
NodeKind::Expansion { expanded, .. } => {
self.collect_globals(expanded);
}
// Leaf nodes — no children.
NodeKind::Nop
| NodeKind::Constant(_)
| NodeKind::Identifier { .. }
| NodeKind::FieldAccessor(_)
| NodeKind::Error
| NodeKind::Extension(_)
| NodeKind::MacroDecl { .. }
| NodeKind::Template(_)
| NodeKind::Placeholder(_)
| NodeKind::Splice(_) => {}
}
}
@@ -323,81 +369,3 @@ impl<'a> Analyzer<'a> {
}
}
trait NodeExt {
fn for_each_child<F: FnMut(&TypedNode)>(&self, f: F);
}
impl NodeExt for NodeKind<TypedPhase> {
fn for_each_child<F: FnMut(&TypedNode)>(&self, mut f: F) {
match self {
NodeKind::If {
cond,
then_br,
else_br,
} => {
f(cond);
f(then_br);
if let Some(e) = else_br {
f(e);
}
}
NodeKind::Def { pattern, value, .. } => {
f(pattern);
f(value);
}
NodeKind::Assign { target, value, .. } => {
f(target);
f(value);
}
NodeKind::GetField { rec, .. } => {
f(rec);
}
NodeKind::Lambda { params, body, .. } => {
f(params);
f(body);
}
NodeKind::Call { callee, args } => {
f(callee);
f(args);
}
NodeKind::Block { exprs } | NodeKind::Program { exprs } => {
for e in exprs {
f(e);
}
}
NodeKind::Tuple { elements } => {
for e in elements {
f(e);
}
}
NodeKind::Record { fields, .. } => {
for (key, val) in fields {
f(key);
f(val);
}
}
NodeKind::Expansion { expanded, .. } => {
f(expanded);
}
NodeKind::Again { args } => {
f(args);
}
NodeKind::MacroDecl { params, body, .. } => {
f(params);
f(body);
}
NodeKind::Template(inner)
| NodeKind::Placeholder(inner)
| NodeKind::Splice(inner) => {
f(inner);
}
// Leaf nodes — no children to visit.
NodeKind::Nop
| NodeKind::Constant(_)
| NodeKind::Identifier { .. }
| NodeKind::FieldAccessor(_)
| NodeKind::Error
| NodeKind::Extension(_) => {}
}
}
}
+42 -2
View File
@@ -663,7 +663,27 @@ impl Binder {
},
)
}
_ => {
// All other syntax kinds are invalid in a binding pattern.
SyntaxKind::Nop
| SyntaxKind::Constant(_)
| SyntaxKind::FieldAccessor(_)
| SyntaxKind::Def { .. }
| SyntaxKind::Assign { .. }
| SyntaxKind::Lambda { .. }
| SyntaxKind::Call { .. }
| SyntaxKind::Again { .. }
| SyntaxKind::If { .. }
| SyntaxKind::Block { .. }
| SyntaxKind::Program { .. }
| SyntaxKind::Record { .. }
| SyntaxKind::GetField { .. }
| SyntaxKind::Expansion { .. }
| SyntaxKind::Extension(_)
| SyntaxKind::Error
| SyntaxKind::MacroDecl { .. }
| SyntaxKind::Template(_)
| SyntaxKind::Placeholder(_)
| SyntaxKind::Splice(_) => {
diag.push_error(
format!("Invalid node in pattern: {:?}", node.kind),
Some(node.identity.clone()),
@@ -704,7 +724,27 @@ impl Binder {
},
)
}
_ => {
// All other syntax kinds are invalid in an assignment pattern.
SyntaxKind::Nop
| SyntaxKind::Constant(_)
| SyntaxKind::FieldAccessor(_)
| SyntaxKind::Def { .. }
| SyntaxKind::Assign { .. }
| SyntaxKind::Lambda { .. }
| SyntaxKind::Call { .. }
| SyntaxKind::Again { .. }
| SyntaxKind::If { .. }
| SyntaxKind::Block { .. }
| SyntaxKind::Program { .. }
| SyntaxKind::Record { .. }
| SyntaxKind::GetField { .. }
| SyntaxKind::Expansion { .. }
| SyntaxKind::Extension(_)
| SyntaxKind::Error
| SyntaxKind::MacroDecl { .. }
| SyntaxKind::Template(_)
| SyntaxKind::Placeholder(_)
| SyntaxKind::Splice(_) => {
diag.push_error(
format!("Invalid node in assignment pattern: {:?}", node.kind),
Some(node.identity.clone()),
+13 -1
View File
@@ -108,7 +108,19 @@ impl CapturePass {
field,
},
other => other,
// Leaf nodes — no children to recurse into.
kind @ (NodeKind::Nop
| NodeKind::Constant(_)
| NodeKind::Identifier { .. }
| NodeKind::FieldAccessor(_)
| NodeKind::Error
| NodeKind::Extension(_)) => kind,
// Syntax-only variants should not appear after macro expansion.
kind @ (NodeKind::MacroDecl { .. }
| NodeKind::Template(_)
| NodeKind::Placeholder(_)
| NodeKind::Splice(_)) => kind,
};
node
}
+35 -29
View File
@@ -129,9 +129,14 @@ impl Lowering {
.map(|e| Rc::new(Self::transform(e.clone(), is_tail_position, allocator))),
},
NodeKind::Block { exprs } => {
NodeKind::Block { exprs } | NodeKind::Program { exprs } => {
let is_program = matches!(&node.kind, NodeKind::Program { .. });
if exprs.is_empty() {
NodeKind::Block { exprs: vec![] }
if is_program {
NodeKind::Program { exprs: vec![] }
} else {
NodeKind::Block { exprs: vec![] }
}
} else {
// Collect VirtualIds of non-captured locals before
// transforming. After all exprs are lowered their
@@ -157,33 +162,11 @@ impl Lowering {
allocator.free_slot(vid);
}
NodeKind::Block { exprs: new_exprs }
}
}
NodeKind::Program { exprs } => {
if exprs.is_empty() {
NodeKind::Program { exprs: vec![] }
} else {
let scope_locals = Self::collect_scope_locals(exprs);
let last_idx = exprs.len() - 1;
let mut new_exprs = Vec::with_capacity(exprs.len());
for (i, expr) in exprs.iter().enumerate() {
let is_last = i == last_idx;
new_exprs.push(Rc::new(Self::transform(
expr.clone(),
is_tail_position && is_last,
allocator,
)));
if is_program {
NodeKind::Program { exprs: new_exprs }
} else {
NodeKind::Block { exprs: new_exprs }
}
for vid in scope_locals {
allocator.free_slot(vid);
}
NodeKind::Program { exprs: new_exprs }
}
}
@@ -349,12 +332,35 @@ impl Lowering {
binding: IdentifierBinding::Declaration { addr: Address::Local(vid), .. },
..
} => out.push(*vid),
// Non-local declarations (Global, Upvalue) are not stack-allocated.
NodeKind::Identifier { .. } => {}
NodeKind::Tuple { elements } => {
for e in elements {
Self::collect_pattern_vids(e, out);
}
}
_ => {}
// Patterns should only contain Identifier and Tuple nodes.
// Other variants indicate a compiler bug upstream.
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(_) => {}
}
}
}
+75 -4
View File
@@ -578,7 +578,27 @@ impl Optimizer {
false
}
}
_ => e.ty.purity >= Purity::SideEffectFree,
// All other expression kinds: removable if side-effect-free.
NodeKind::Nop
| NodeKind::Constant(_)
| NodeKind::Identifier { .. }
| NodeKind::FieldAccessor(_)
| NodeKind::Lambda { .. }
| NodeKind::Call { .. }
| NodeKind::Again { .. }
| NodeKind::If { .. }
| NodeKind::Block { .. }
| NodeKind::Program { .. }
| NodeKind::Tuple { .. }
| NodeKind::Record { .. }
| NodeKind::GetField { .. }
| NodeKind::Expansion { .. }
| NodeKind::Extension(_)
| NodeKind::Error
| NodeKind::MacroDecl { .. }
| NodeKind::Template(_)
| NodeKind::Placeholder(_)
| NodeKind::Splice(_) => e.ty.purity >= Purity::SideEffectFree,
};
if removable {
@@ -821,7 +841,17 @@ impl Optimizer {
refreshed_metrics(&node.ty, last_ty),
)
}
k => (k.clone(), node.ty.clone()),
// Leaf nodes — no children to recurse into.
NodeKind::Nop => (NodeKind::Nop, node.ty.clone()),
NodeKind::Constant(v) => (NodeKind::Constant(v.clone()), node.ty.clone()),
NodeKind::Error => (NodeKind::Error, node.ty.clone()),
NodeKind::Extension(ext) => (NodeKind::Extension(ext.clone_box()), node.ty.clone()),
// Syntax-only variants should not appear in AnalyzedPhase.
NodeKind::MacroDecl { .. }
| NodeKind::Template(_)
| NodeKind::Placeholder(_)
| NodeKind::Splice(_) => (NodeKind::Nop, node.ty.clone()),
};
Rc::new(Node {
@@ -881,7 +911,28 @@ impl Optimizer {
elements: new_elements,
}
}
_ => return node_rc.clone(),
// Patterns only contain Identifier and Tuple nodes.
// All other variants pass through unchanged.
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(_) => return node_rc.clone(),
};
Rc::new(Node {
comments: node.comments.clone(),
@@ -899,7 +950,27 @@ impl Optimizer {
}
}
NodeKind::Nop => {}
_ => into.push(node),
// All other variants are individual argument values.
NodeKind::Constant(_)
| 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::Expansion { .. }
| NodeKind::Extension(_)
| NodeKind::Error
| NodeKind::MacroDecl { .. }
| NodeKind::Template(_)
| NodeKind::Placeholder(_)
| NodeKind::Splice(_) => into.push(node),
}
}
}
+22 -1
View File
@@ -105,7 +105,28 @@ impl<'a> Folder<'a> {
..
} => self.globals.as_ref()?.get(idx.0 as usize)?,
NodeKind::Constant(val) => val.clone(),
_ => return None,
// Only global identifiers and constants can be folded at compile time.
NodeKind::Identifier { .. }
| NodeKind::Nop
| NodeKind::FieldAccessor(_)
| NodeKind::Def { .. }
| NodeKind::Assign { .. }
| NodeKind::Lambda { .. }
| NodeKind::Call { .. }
| NodeKind::Again { .. }
| NodeKind::If { .. }
| NodeKind::Block { .. }
| NodeKind::Program { .. }
| NodeKind::Tuple { .. }
| NodeKind::Record { .. }
| NodeKind::GetField { .. }
| NodeKind::Expansion { .. }
| NodeKind::Extension(_)
| NodeKind::Error
| NodeKind::MacroDecl { .. }
| NodeKind::Template(_)
| NodeKind::Placeholder(_)
| NodeKind::Splice(_) => return None,
};
let result = match func_val {
Value::Function(f) => (f.func)(&arg_values),
+44 -2
View File
@@ -187,7 +187,28 @@ impl<'a> Inliner<'a> {
self.map_params_to_args(el, args, offset, sub, body_usage);
}
}
_ => {}
// Parameters should only contain Def, Identifier (Declaration), and Tuple.
// Reference identifiers don't appear in parameter patterns.
NodeKind::Identifier { .. }
| NodeKind::Nop
| NodeKind::Constant(_)
| NodeKind::FieldAccessor(_)
| 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(_) => {}
}
}
@@ -213,7 +234,28 @@ impl<'a> Inliner<'a> {
self.collect_parameter_slots_set(el, slots);
}
}
_ => {}
// Parameters should only contain Def, Identifier (Declaration), and Tuple.
// Reference identifiers don't appear in parameter patterns.
NodeKind::Identifier { .. }
| NodeKind::Nop
| NodeKind::Constant(_)
| NodeKind::FieldAccessor(_)
| 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(_) => {}
}
}
}
+63 -3
View File
@@ -176,6 +176,7 @@ impl UsageInfo {
binding: IdentifierBinding::Declaration { .. },
..
} => {}
NodeKind::Identifier { .. } => {}
NodeKind::Def { .. } => {}
NodeKind::Assign { info, .. } => {
if let Some(addr) = info.addr {
@@ -187,7 +188,25 @@ impl UsageInfo {
self.collect_pattern(el);
}
}
_ => {}
// Patterns should only contain Identifier, Def, Assign, and Tuple.
NodeKind::Nop
| NodeKind::Constant(_)
| NodeKind::FieldAccessor(_)
| 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(_) => {}
}
}
@@ -205,7 +224,27 @@ impl UsageInfo {
self.collect_assigned_from_target(el);
}
}
_ => {}
// Assignment targets 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(_) => {}
}
}
}
@@ -223,6 +262,8 @@ pub fn collect_pattern_addrs(pattern: &AnalyzedNode, out: &mut Vec<Address<Virtu
} => {
out.push(*addr);
}
// Non-declaration identifiers (references) don't bind new slots.
NodeKind::Identifier { .. } => {}
NodeKind::Def { pattern: inner, .. } => {
collect_pattern_addrs(inner, out);
}
@@ -231,6 +272,25 @@ pub fn collect_pattern_addrs(pattern: &AnalyzedNode, out: &mut Vec<Address<Virtu
collect_pattern_addrs(el, out);
}
}
_ => {}
// Patterns should only contain Identifier, Def, and Tuple.
NodeKind::Nop
| NodeKind::Constant(_)
| NodeKind::FieldAccessor(_)
| 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(_) => {}
}
}
+50 -16
View File
@@ -172,7 +172,25 @@ impl TypeChecker {
}
NodeKind::Nop => (NodeKind::Nop, StaticType::Void),
NodeKind::Error => (NodeKind::Error, StaticType::Error),
_ => {
// All remaining variants are invalid in a parameter list.
// Identifier::Reference should not appear in parameter patterns.
NodeKind::Identifier { .. }
| NodeKind::Constant(_)
| NodeKind::FieldAccessor(_)
| NodeKind::GetField { .. }
| NodeKind::Lambda { .. }
| NodeKind::Call { .. }
| NodeKind::Again { .. }
| NodeKind::If { .. }
| NodeKind::Block { .. }
| NodeKind::Program { .. }
| NodeKind::Record { .. }
| NodeKind::Expansion { .. }
| NodeKind::Extension(_)
| NodeKind::MacroDecl { .. }
| NodeKind::Template(_)
| NodeKind::Placeholder(_)
| NodeKind::Splice(_) => {
diag.push_error(
"Invalid node in parameter list",
Some(node.identity.clone()),
@@ -328,7 +346,29 @@ impl TypeChecker {
}
},
},
_ => NodeKind::Error,
// Tuple patterns are handled above with an early return.
// All other variants are invalid as def patterns.
NodeKind::Nop
| NodeKind::Constant(_)
| NodeKind::FieldAccessor(_)
| NodeKind::Def { .. }
| NodeKind::Assign { .. }
| NodeKind::Lambda { .. }
| NodeKind::Call { .. }
| NodeKind::Again { .. }
| NodeKind::If { .. }
| NodeKind::Block { .. }
| NodeKind::Program { .. }
| NodeKind::Tuple { .. }
| NodeKind::Record { .. }
| NodeKind::GetField { .. }
| NodeKind::Expansion { .. }
| NodeKind::Extension(_)
| NodeKind::Error
| NodeKind::MacroDecl { .. }
| NodeKind::Template(_)
| NodeKind::Placeholder(_)
| NodeKind::Splice(_) => NodeKind::Error,
},
ty: ty.clone(),
comments: pattern.comments.clone(),
@@ -485,7 +525,8 @@ impl TypeChecker {
)
}
NodeKind::Block { exprs } => {
NodeKind::Block { exprs } | NodeKind::Program { exprs } => {
let is_program = matches!(&node.kind, NodeKind::Program { .. });
let mut typed_exprs = Vec::new();
let mut last_ty = StaticType::Void;
@@ -495,19 +536,12 @@ impl TypeChecker {
typed_exprs.push(Rc::new(t));
}
(NodeKind::Block { exprs: typed_exprs }, last_ty)
}
NodeKind::Program { exprs } => {
let mut typed_exprs = Vec::new();
let mut last_ty = StaticType::Void;
for e in exprs {
let t = self.check_node(e, ctx, diag);
last_ty = t.ty.clone();
typed_exprs.push(Rc::new(t));
}
(NodeKind::Program { exprs: typed_exprs }, last_ty)
let kind = if is_program {
NodeKind::Program { exprs: typed_exprs }
} else {
NodeKind::Block { exprs: typed_exprs }
};
(kind, last_ty)
}
NodeKind::Lambda {
+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()),
}
}
}