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
+1
View File
@@ -32,6 +32,7 @@
## Rust ## Rust
* Performance: Bevorzuge `Rc<dyn Trait>` + lokales `downcast_ref` gegenüber Deep Copies via `Rc::new(obj.clone())`. * Performance: Bevorzuge `Rc<dyn Trait>` + lokales `downcast_ref` gegenüber Deep Copies via `Rc::new(obj.clone())`.
* CRITICAL: Keine Wildcards (`_ =>`, `other => other`) in `match`-Ausdrücken auf `NodeKind` oder `SyntaxKind`. Alle Varianten müssen explizit aufgeführt werden. Exhaustive Matches erzwingen, dass neue Varianten an jeder Stelle bewusst behandelt werden.
## Interaktion ## Interaktion
-52
View File
@@ -1,52 +0,0 @@
# Bug: Program-Node bricht Destructuring-Typinferenz
## Reproduktion
Ausgehend vom aktuellen Stand (Lambda-Wrapping in `compile_pipeline`, `parse_expression` in `compile`):
### Schritt 1: In `compile()` `parse_expression` durch `parse_program` ersetzen
In `src/ast/environment.rs`, Methode `compile()`:
```rust
// VORHER (funktioniert):
let syntax_ast = parser.parse_expression();
// NACHHER (Typinferenz bricht):
let syntax_ast = parser.parse_program();
```
Außerdem die `at_eof`-Prüfung entfernen (weil `parse_program` alles konsumiert).
### Schritt 2: Testen
```bash
cargo run --release --bin ast -- -d -e "((fn [[x y]] (+ x y)) [10 20])"
```
**Erwartet:** `Constant: 30` im Dump (Optimizer faltet den Ausdruck)
**Tatsächlich:** Kein Folding. Die Lambda-Parameter `x` und `y` haben Typ `Any` statt `Int`. HM step 10 (Unifikation) wird übersprungen weil `has_typevar_component` false ist.
### Schritt 3: Zusätzlich Lambda-Wrapping entfernen (verschärft das Problem)
In `compile_pipeline()` die Zeile `let wrapped = self.wrap_as_lambda(bound);` entfernen.
Dann bekommt `check_node_as_bound` einen `Program`-Node statt eines `Lambda`-Nodes. Ergebnis ist dasselbe: `Any`-Parameter, kein Folding.
## Ursache (unvollständig analysiert)
Der AST-Unterschied:
- **Funktioniert:** `Lambda(Call(Lambda([[x y]], body), [10 20]))` — kein Program-Node
- **Bricht:** `Lambda(Program(Call(Lambda([[x y]], body), [10 20])))` — Program-Node dazwischen
Der TypeChecker erzeugt TypeVars (`?0`, `?1`) für die innere Lambda-Parameter. Aber in `check_params_tuple` (check.rs, Zeile ~241) wird `TypeVar` im Match auf `_ => StaticType::Any` aufgelöst statt propagiert. Dadurch enthält die Signatur `fn([[any any]]) -> int` statt `fn([[?0 ?1]]) -> int`, und HM step 10 überspringt die Unifikation.
Warum das nur mit Program-Node passiert und nicht ohne, ist unklar. Der `check_params_tuple`-Code hat sich nicht geändert.
## Betroffener Test
```
tests/destructuring.rs::test_nested_destructuring_optimization
```
+50 -82
View File
@@ -1,6 +1,6 @@
use crate::ast::nodes::{ use crate::ast::nodes::{
Address, AnalyzedNode, GlobalIdx, IdentifierBinding, Node, Address, AnalyzedNode, GlobalIdx, IdentifierBinding, Node,
NodeKind, NodeMetrics, TypedNode, TypedPhase, NodeKind, NodeMetrics, TypedNode,
}; };
use crate::ast::types::{Identity, Purity, Value}; use crate::ast::types::{Identity, Purity, Value};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@@ -58,10 +58,56 @@ impl<'a> Analyzer<'a> {
self.collect_globals(e); self.collect_globals(e);
} }
} }
_ => { NodeKind::If { cond, then_br, else_br } => {
node.kind self.collect_globals(cond);
.for_each_child(|child| self.collect_globals(child)); 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( diag.push_error(
format!("Invalid node in pattern: {:?}", node.kind), format!("Invalid node in pattern: {:?}", node.kind),
Some(node.identity.clone()), 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( diag.push_error(
format!("Invalid node in assignment pattern: {:?}", node.kind), format!("Invalid node in assignment pattern: {:?}", node.kind),
Some(node.identity.clone()), Some(node.identity.clone()),
+13 -1
View File
@@ -108,7 +108,19 @@ impl CapturePass {
field, 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 node
} }
+35 -29
View File
@@ -129,9 +129,14 @@ impl Lowering {
.map(|e| Rc::new(Self::transform(e.clone(), is_tail_position, allocator))), .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() { if exprs.is_empty() {
NodeKind::Block { exprs: vec![] } if is_program {
NodeKind::Program { exprs: vec![] }
} else {
NodeKind::Block { exprs: vec![] }
}
} else { } else {
// Collect VirtualIds of non-captured locals before // Collect VirtualIds of non-captured locals before
// transforming. After all exprs are lowered their // transforming. After all exprs are lowered their
@@ -157,33 +162,11 @@ impl Lowering {
allocator.free_slot(vid); allocator.free_slot(vid);
} }
NodeKind::Block { exprs: new_exprs } if is_program {
} NodeKind::Program { exprs: new_exprs }
} } else {
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,
)));
} }
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), .. }, binding: IdentifierBinding::Declaration { addr: Address::Local(vid), .. },
.. ..
} => out.push(*vid), } => out.push(*vid),
// Non-local declarations (Global, Upvalue) are not stack-allocated.
NodeKind::Identifier { .. } => {}
NodeKind::Tuple { elements } => { NodeKind::Tuple { elements } => {
for e in elements { for e in elements {
Self::collect_pattern_vids(e, out); 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 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 { if removable {
@@ -821,7 +841,17 @@ impl Optimizer {
refreshed_metrics(&node.ty, last_ty), 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 { Rc::new(Node {
@@ -881,7 +911,28 @@ impl Optimizer {
elements: new_elements, 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 { Rc::new(Node {
comments: node.comments.clone(), comments: node.comments.clone(),
@@ -899,7 +950,27 @@ impl Optimizer {
} }
} }
NodeKind::Nop => {} 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)?, } => self.globals.as_ref()?.get(idx.0 as usize)?,
NodeKind::Constant(val) => val.clone(), 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 { let result = match func_val {
Value::Function(f) => (f.func)(&arg_values), 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); 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); 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 { .. }, binding: IdentifierBinding::Declaration { .. },
.. ..
} => {} } => {}
NodeKind::Identifier { .. } => {}
NodeKind::Def { .. } => {} NodeKind::Def { .. } => {}
NodeKind::Assign { info, .. } => { NodeKind::Assign { info, .. } => {
if let Some(addr) = info.addr { if let Some(addr) = info.addr {
@@ -187,7 +188,25 @@ impl UsageInfo {
self.collect_pattern(el); 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); 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); out.push(*addr);
} }
// Non-declaration identifiers (references) don't bind new slots.
NodeKind::Identifier { .. } => {}
NodeKind::Def { pattern: inner, .. } => { NodeKind::Def { pattern: inner, .. } => {
collect_pattern_addrs(inner, out); 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); 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::Nop => (NodeKind::Nop, StaticType::Void),
NodeKind::Error => (NodeKind::Error, StaticType::Error), 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( diag.push_error(
"Invalid node in parameter list", "Invalid node in parameter list",
Some(node.identity.clone()), 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(), ty: ty.clone(),
comments: pattern.comments.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 typed_exprs = Vec::new();
let mut last_ty = StaticType::Void; let mut last_ty = StaticType::Void;
@@ -495,19 +536,12 @@ impl TypeChecker {
typed_exprs.push(Rc::new(t)); typed_exprs.push(Rc::new(t));
} }
(NodeKind::Block { exprs: typed_exprs }, last_ty) let kind = if is_program {
} NodeKind::Program { exprs: typed_exprs }
NodeKind::Program { exprs } => { } else {
let mut typed_exprs = Vec::new(); NodeKind::Block { exprs: typed_exprs }
let mut last_ty = StaticType::Void; };
(kind, last_ty)
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)
} }
NodeKind::Lambda { 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>) { fn after_eval(&mut self, vm: &VM, node: &ExecNode, res: &Result<Value, String>) {
self.indent = self.indent.saturating_sub(1); self.indent = self.indent.saturating_sub(1);
let pad = self.pad(); let pad = self.pad();
match &node.kind { if matches!(&node.kind, NodeKind::Def { .. } | NodeKind::Assign { .. }) {
NodeKind::Def { .. } | NodeKind::Assign { .. } => { let s_pad = format!("{}| ", pad);
let s_pad = format!("{}| ", pad); self.logs.push(format!("{}--- Scope Status ---", s_pad));
self.logs.push(format!("{}--- Scope Status ---", s_pad)); self.logs.push(format!(
self.logs.push(format!( "{}Stack (top 5): {:?}",
"{}Stack (top 5): {:?}", s_pad,
s_pad, vm.stack.iter().rev().take(5).collect::<Vec<_>>()
vm.stack.iter().rev().take(5).collect::<Vec<_>>() ));
));
}
_ => {}
} }
let res_str = match res { let res_str = match res {
Ok(v) => format!("{}", v), Ok(v) => format!("{}", v),
@@ -864,7 +861,26 @@ impl VM {
} }
Ok(()) 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)?; let val = self.eval_internal(obs, args)?;
if let Some(slice) = val.as_slice() { if let Some(slice) = val.as_slice() {
self.stack.extend_from_slice(slice); self.stack.extend_from_slice(slice);
@@ -1014,7 +1030,27 @@ impl VM {
} }
Ok(()) 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()),
} }
} }
} }