Refactor Destructure to handle assignments

Renames `DefDestructure` to `Destructure` to better reflect its use in
both definitions and assignments.
Introduces `bind_assign_pattern` to handle assignment destructuring in
the binder.
Adds `test_assign_destructuring` to verify assignment destructuring
functionality.
This commit is contained in:
Michael Schimmel
2026-02-24 08:51:24 +01:00
parent 2b0e7f49d7
commit 51d83562de
9 changed files with 162 additions and 16 deletions
+2 -2
View File
@@ -178,11 +178,11 @@ impl<'a> Analyzer<'a> {
) )
} }
BoundKind::DefDestructure { pattern, value } => { BoundKind::Destructure { pattern, value } => {
let pat_m = self.visit(Rc::new((**pattern).clone())); let pat_m = self.visit(Rc::new((**pattern).clone()));
let val_m = self.visit(Rc::new((**value).clone())); let val_m = self.visit(Rc::new((**value).clone()));
( (
BoundKind::DefDestructure { BoundKind::Destructure {
pattern: Box::new(pat_m), pattern: Box::new(pat_m),
value: Box::new(val_m), value: Box::new(val_m),
}, },
+37 -2
View File
@@ -209,7 +209,7 @@ impl Binder {
Ok(self.make_node( Ok(self.make_node(
node.identity.clone(), node.identity.clone(),
BoundKind::DefDestructure { BoundKind::Destructure {
pattern: Box::new(target_node), pattern: Box::new(target_node),
value: Box::new(val_node), value: Box::new(val_node),
}, },
@@ -230,7 +230,14 @@ impl Binder {
}, },
)) ))
} else { } else {
Err("Assignment target must be an identifier".to_string()) let target_node = self.bind_assign_pattern(target)?;
Ok(self.make_node(
node.identity.clone(),
BoundKind::Destructure {
pattern: Box::new(target_node),
value: Box::new(val_node),
},
))
} }
} }
@@ -456,6 +463,34 @@ impl Binder {
} }
} }
fn bind_assign_pattern(&mut self, node: &Node<UntypedKind>) -> Result<BoundNode, String> {
match &node.kind {
UntypedKind::Identifier(sym) => {
let addr = self.resolve_variable(sym)?;
Ok(self.make_node(
node.identity.clone(),
BoundKind::Set {
addr,
value: Box::new(self.make_node(node.identity.clone(), BoundKind::Nop)),
},
))
}
UntypedKind::Tuple { elements } => {
let mut bound_elems = Vec::new();
for e in elements {
bound_elems.push(self.bind_assign_pattern(e)?);
}
Ok(self.make_node(
node.identity.clone(),
BoundKind::Tuple {
elements: bound_elems,
},
))
}
_ => Err(format!("Invalid node in assignment pattern: {:?}", node.kind)),
}
}
fn make_node(&self, identity: Identity, kind: BoundKind<()>) -> BoundNode { fn make_node(&self, identity: Identity, kind: BoundKind<()>) -> BoundNode {
Node { Node {
identity, identity,
+5 -5
View File
@@ -82,8 +82,8 @@ pub enum BoundKind<T = ()> {
value: Box<BoundNode<T>>, value: Box<BoundNode<T>>,
}, },
/// A destructuring definition (can be local or global depending on the pattern) /// A destructuring operation (can be a definition or an assignment depending on the pattern)
DefDestructure { Destructure {
pattern: Box<BoundNode<T>>, pattern: Box<BoundNode<T>>,
value: Box<BoundNode<T>>, value: Box<BoundNode<T>>,
}, },
@@ -194,11 +194,11 @@ where
}, },
) => na == nb && ga == gb && va == vb, ) => na == nb && ga == gb && va == vb,
( (
BoundKind::DefDestructure { BoundKind::Destructure {
pattern: pa, pattern: pa,
value: va, value: va,
}, },
BoundKind::DefDestructure { BoundKind::Destructure {
pattern: pb, pattern: pb,
value: vb, value: vb,
}, },
@@ -265,7 +265,7 @@ impl<T> BoundKind<T> {
BoundKind::DefGlobal { BoundKind::DefGlobal {
name, global_index, .. name, global_index, ..
} => format!("DEF_GLOBAL({}, Idx:{})", name.name, global_index), } => format!("DEF_GLOBAL({}, Idx:{})", name.name, global_index),
BoundKind::DefDestructure { .. } => "DEF_DESTRUCTURE".to_string(), BoundKind::Destructure { .. } => "DESTRUCTURE".to_string(),
BoundKind::Lambda { BoundKind::Lambda {
params, upvalues, .. params, upvalues, ..
} => { } => {
+2 -2
View File
@@ -120,8 +120,8 @@ impl Dumper {
self.indent -= 1; self.indent -= 1;
} }
BoundKind::DefDestructure { pattern, value } => { BoundKind::Destructure { pattern, value } => {
self.log("DefDestructure", node); self.log("Destructure", node);
self.indent += 1; self.indent += 1;
self.write_indent(); self.write_indent();
self.output.push_str("Pattern:\n"); self.output.push_str("Pattern:\n");
+57
View File
@@ -95,6 +95,34 @@ impl Optimizer {
current current
} }
fn collect_pattern_usage(&self, node: &AnalyzedNode, info: &mut UsageInfo) {
match &node.kind {
BoundKind::Parameter { slot, .. } => {
info.assigned_locals.insert(*slot);
}
BoundKind::DefGlobal { global_index, .. } => {
info.assigned_globals.insert(*global_index);
}
BoundKind::Set { addr, .. } => {
match addr {
Address::Local(slot) => {
info.assigned_locals.insert(*slot);
}
Address::Global(idx) => {
info.assigned_globals.insert(*idx);
}
_ => {}
}
}
BoundKind::Tuple { elements } => {
for el in elements {
self.collect_pattern_usage(el, info);
}
}
_ => {}
}
}
fn visit_node( fn visit_node(
&self, &self,
node: AnalyzedNode, node: AnalyzedNode,
@@ -561,6 +589,31 @@ impl Optimizer {
) )
} }
BoundKind::Destructure {
ref pattern,
ref value,
} => {
let val_opt = Box::new(self.visit_node((**value).clone(), sub, path));
let pat_opt = Box::new(self.visit_node((**pattern).clone(), sub, path));
let mut info = UsageInfo::default();
self.collect_pattern_usage(&pat_opt, &mut info);
for slot in info.assigned_locals {
sub.locals.remove(&slot);
}
for idx in info.assigned_globals {
sub.globals.remove(&idx);
}
(
BoundKind::Destructure {
pattern: pat_opt,
value: val_opt,
},
node.ty.clone(),
)
}
BoundKind::Tuple { elements } => { BoundKind::Tuple { elements } => {
let elements = elements let elements = elements
.into_iter() .into_iter()
@@ -808,6 +861,10 @@ impl Optimizer {
fn collect_usage(&self, node: &AnalyzedNode, info: &mut UsageInfo) { fn collect_usage(&self, node: &AnalyzedNode, info: &mut UsageInfo) {
match &node.kind { match &node.kind {
BoundKind::Destructure { pattern, value } => {
self.collect_pattern_usage(pattern, info);
self.collect_usage(value, info);
}
BoundKind::Constant(v) => { BoundKind::Constant(v) => {
if let Value::Object(obj) = v if let Value::Object(obj) = v
&& let Some(closure) = obj.as_any().downcast_ref::<Closure>() && let Some(closure) = obj.as_any().downcast_ref::<Closure>()
+1 -1
View File
@@ -118,7 +118,7 @@ impl TCO {
global_index: *global_index, global_index: *global_index,
value: Box::new(Self::transform(Rc::new((**value).clone()), false)), value: Box::new(Self::transform(Rc::new((**value).clone()), false)),
}, },
BoundKind::DefDestructure { pattern, value } => BoundKind::DefDestructure { BoundKind::Destructure { pattern, value } => BoundKind::Destructure {
pattern: Box::new(Self::transform(Rc::new((**pattern).clone()), false)), pattern: Box::new(Self::transform(Rc::new((**pattern).clone()), false)),
value: Box::new(Self::transform(Rc::new((**value).clone()), false)), value: Box::new(Self::transform(Rc::new((**value).clone()), false)),
}, },
+19 -2
View File
@@ -163,6 +163,23 @@ impl TypeChecker {
specialized_ty.clone(), specialized_ty.clone(),
) )
} }
BoundKind::Set { addr, value: _value } => {
// In an assignment pattern, 'value' is just a Nop placeholder from the Binder.
// We update the type of the address (if it's a local or global) to match the specialized type.
// Note: For now, we assume assignments are compatible if types were already inferred,
// or we could add a check here against existing type.
(
BoundKind::Set {
addr,
value: Box::new(Node {
identity: node.identity.clone(),
kind: BoundKind::Nop,
ty: specialized_ty.clone(),
}),
},
specialized_ty.clone(),
)
}
BoundKind::Tuple { elements } => { BoundKind::Tuple { elements } => {
let mut typed_elements = Vec::new(); let mut typed_elements = Vec::new();
let mut elem_types = Vec::new(); let mut elem_types = Vec::new();
@@ -282,12 +299,12 @@ impl TypeChecker {
) )
} }
BoundKind::DefDestructure { pattern, value } => { BoundKind::Destructure { pattern, value } => {
let val_typed = self.check_node(*value, ctx)?; let val_typed = self.check_node(*value, ctx)?;
let pat_typed = self.check_params(*pattern, &val_typed.ty, ctx)?; let pat_typed = self.check_params(*pattern, &val_typed.ty, ctx)?;
let ty = val_typed.ty.clone(); let ty = val_typed.ty.clone();
( (
BoundKind::DefDestructure { BoundKind::Destructure {
pattern: Box::new(pat_typed), pattern: Box::new(pat_typed),
value: Box::new(val_typed), value: Box::new(val_typed),
}, },
+7 -2
View File
@@ -281,7 +281,7 @@ impl VM {
globals[idx] = val.clone(); globals[idx] = val.clone();
Ok(val) Ok(val)
} }
BoundKind::DefDestructure { pattern, value } => { BoundKind::Destructure { pattern, value } => {
let val = self.eval_internal(obs, value)?; let val = self.eval_internal(obs, value)?;
let mut offset = 0; let mut offset = 0;
@@ -289,7 +289,7 @@ impl VM {
if let Some(vals) = val.as_slice() { if let Some(vals) = val.as_slice() {
self.unpack(pattern, vals, &mut offset)?; self.unpack(pattern, vals, &mut offset)?;
} else { } else {
self.unpack(pattern, &[val.clone()], &mut offset)?; self.unpack(pattern, std::slice::from_ref(&val), &mut offset)?;
} }
Ok(val) Ok(val)
} }
@@ -704,6 +704,11 @@ impl VM {
globals[idx] = val; globals[idx] = val;
Ok(()) Ok(())
} }
BoundKind::Set { addr, .. } => {
let val = values.get(*offset).cloned().unwrap_or(Value::Void);
*offset += 1;
self.set_value(*addr, val)
}
BoundKind::Tuple { elements } => { BoundKind::Tuple { elements } => {
if let Some(sub_values) = values.get(*offset).and_then(|v| v.as_slice()) { if let Some(sub_values) = values.get(*offset).and_then(|v| v.as_slice()) {
*offset += 1; *offset += 1;
+32
View File
@@ -337,4 +337,36 @@ mod tests {
panic!("Expected tuple return from def, got {:?}", res); panic!("Expected tuple return from def, got {:?}", res);
} }
} }
#[test]
fn test_assign_destructuring() {
// 1. Simple assignment destructuring
{
let env = Environment::new();
let source_simple = "(do (def a 0) (def b 0) (assign [a b] [10 20]) (+ a b))";
assert_eq!(format!("{}", env.run_script(source_simple).unwrap()), "30");
}
// 2. Nested assignment destructuring
{
let env = Environment::new();
let source_nested =
"(do (def a 0) (def b 0) (def c 0) (assign [a [b c]] [1 [2 3]]) (+ a (+ b c)))";
assert_eq!(format!("{}", env.run_script(source_nested).unwrap()), "6");
}
// 3. Assignment returns the assigned value
{
let env = Environment::new();
let source_return = "(do (def a 0) (def b 0) (assign [a b] [5 6]))";
let res = env.run_script(source_return).unwrap();
if let Value::Tuple(vals) = res {
assert_eq!(vals.len(), 2);
assert_eq!(format!("{}", vals[0]), "5");
assert_eq!(format!("{}", vals[1]), "6");
} else {
panic!("Expected tuple return from assign, got {:?}", res);
}
}
}
} }