Refactor lambda binding and parameter handling

Introduce `BoundKind::Parameter` to represent function parameters.
Modify `Binder` to correctly handle parameters within lambda
definitions, ensuring they are only defined within function scopes.
Update `LambdaCollector` to register the body of lambdas as templates
for global definitions.
Adjust `Dumper` to accurately represent lambda parameters.
Update `Specializer` and `TCO` to handle the new `BoundKind::Parameter`.
Refactor `Call` and `TailCall` to use a single `args` node, often a
tuple.
Adjust type signatures in RTL to use `StaticType::Tuple` for function
parameters.
This commit is contained in:
Michael Schimmel
2026-02-20 12:14:22 +01:00
parent 8d6d3be5c2
commit e2279f214b
17 changed files with 497 additions and 247 deletions
+23 -14
View File
@@ -110,6 +110,19 @@ impl Binder {
}))
},
UntypedKind::Parameter(sym) => {
// Parameters are ONLY allowed if we are inside a function (Binder stack > 1)
if self.functions.len() <= 1 {
return Err(format!("Parameter '{}' is not allowed in root scope.", sym.name));
}
let current_fn = self.functions.last_mut().unwrap();
let slot = current_fn.scope.define(sym)?;
Ok(self.make_node(node.identity.clone(), BoundKind::Parameter {
name: sym.clone(),
slot
}))
},
UntypedKind::If { cond, then_br, else_br } => {
let cond = self.bind(cond)?;
let then_br = self.bind(then_br)?;
@@ -176,20 +189,19 @@ impl Binder {
},
UntypedKind::Lambda { params, body } => {
let identity = node.identity.clone();
self.functions.push(FunctionCompiler::new());
{
let current_fn = self.functions.last_mut().unwrap();
for param in params {
current_fn.scope.define(param)?;
}
}
// 1. Bind the parameter pattern/tuple
let params_bound = self.bind(params)?;
// 2. Bind the body
let body_bound = self.bind(body)?;
let compiled_fn = self.functions.pop().unwrap();
Ok(self.make_node(node.identity.clone(), BoundKind::Lambda {
param_count: params.len() as u32,
Ok(self.make_node(identity, BoundKind::Lambda {
params: Box::new(params_bound),
upvalues: compiled_fn.upvalues,
body: Rc::new(body_bound),
}))
@@ -197,14 +209,11 @@ impl Binder {
UntypedKind::Call { callee, args } => {
let callee = self.bind(callee)?;
let mut bound_args = Vec::new();
for arg in args {
bound_args.push(self.bind(arg)?);
}
let args = self.bind(args)?;
Ok(self.make_node(node.identity.clone(), BoundKind::Call {
callee: Box::new(callee),
args: bound_args
args: Box::new(args)
}))
},
+36 -23
View File
@@ -21,11 +21,23 @@ impl<T> Clone for Box<dyn BoundExtension<T>> {
}
}
/// Type alias for a node that has been bound. Defaults to untyped (T = ()).
pub type BoundNode<T = ()> = Node<BoundKind<T>, T>;
/// Type alias for a node that has been fully type-checked.
pub type TypedNode = BoundNode<StaticType>;
#[derive(Debug, Clone)]
pub enum BoundKind<T = ()> {
Nop,
Constant(Value),
/// A positional parameter in a Lambda's parameter list.
Parameter {
name: Symbol,
slot: u32,
},
// Variable Access (Resolved)
Get {
addr: Address,
@@ -35,53 +47,53 @@ pub enum BoundKind<T = ()> {
// Variable Update (Assignment)
Set {
addr: Address,
value: Box<Node<BoundKind<T>, T>>,
value: Box<BoundNode<T>>,
},
// Variable Declaration (Local)
DefLocal {
name: Symbol,
slot: u32,
value: Box<Node<BoundKind<T>, T>>,
value: Box<BoundNode<T>>,
captured_by: Vec<Identity>,
},
If {
cond: Box<Node<BoundKind<T>, T>>,
then_br: Box<Node<BoundKind<T>, T>>,
else_br: Option<Box<Node<BoundKind<T>, T>>>,
cond: Box<BoundNode<T>>,
then_br: Box<BoundNode<T>>,
else_br: Option<Box<BoundNode<T>>>,
},
// Global Definition (Locals are implicit by stack position)
DefGlobal {
name: Symbol,
global_index: u32,
value: Box<Node<BoundKind<T>, T>>,
value: Box<BoundNode<T>>,
},
Lambda {
param_count: u32,
params: Box<BoundNode<T>>,
// The list of variables captured from enclosing scopes
upvalues: Vec<Address>,
body: Rc<Node<BoundKind<T>, T>>,
body: Rc<BoundNode<T>>,
},
Call {
callee: Box<Node<BoundKind<T>, T>>,
args: Vec<Node<BoundKind<T>, T>>,
callee: Box<BoundNode<T>>,
args: Box<BoundNode<T>>,
},
TailCall {
callee: Box<Node<BoundKind<T>, T>>,
args: Vec<Node<BoundKind<T>, T>>,
callee: Box<BoundNode<T>>,
args: Box<BoundNode<T>>,
},
Block {
exprs: Vec<Node<BoundKind<T>, T>>,
exprs: Vec<BoundNode<T>>,
},
Tuple {
elements: Vec<Node<BoundKind<T>, T>>,
elements: Vec<BoundNode<T>>,
},
Map {
@@ -93,7 +105,7 @@ pub enum BoundKind<T = ()> {
/// The original call from the untyped AST.
original_call: Rc<Node<crate::ast::nodes::UntypedKind>>,
/// The result of binding the expanded AST.
bound_expanded: Box<Node<BoundKind<T>, T>>,
bound_expanded: Box<BoundNode<T>>,
},
/// DSL-specific extension slot
@@ -101,25 +113,26 @@ pub enum BoundKind<T = ()> {
}
/// A single entry in a Map literal (Key-Value pair)
pub type MapEntry<T> = (Node<BoundKind<T>, T>, Node<BoundKind<T>, T>);
/// Type alias for a node that has been bound (addresses resolved) but not yet type-checked.
pub type BoundNode = Node<BoundKind<()>, ()>;
/// Type alias for a node that has been fully type-checked and is ready for execution.
pub type TypedNode = Node<BoundKind<StaticType>, StaticType>;
pub type MapEntry<T> = (BoundNode<T>, BoundNode<T>);
impl<T> BoundKind<T> {
pub fn display_name(&self) -> String {
match self {
BoundKind::Nop => "NOP".to_string(),
BoundKind::Constant(v) => format!("CONST({})", v),
BoundKind::Parameter { name, slot } => format!("PARAM({}, Slot:{})", name.name, slot),
BoundKind::Get { addr, name } => format!("GET({}, {:?})", name.name, addr),
BoundKind::Set { addr, .. } => format!("SET({:?})", addr),
BoundKind::DefLocal { name, slot, .. } => format!("DEF_LOCAL({}, Slot:{})", name.name, slot),
BoundKind::If { .. } => "IF".to_string(),
BoundKind::DefGlobal { name, global_index, .. } => format!("DEF_GLOBAL({}, Idx:{})", name.name, global_index),
BoundKind::Lambda { upvalues, .. } => format!("LAMBDA(Captures:{})", upvalues.len()),
BoundKind::Lambda { params, upvalues, .. } => {
let p_str = match &params.kind {
BoundKind::Tuple { elements } => format!("p:{}", elements.len()),
_ => "p:1".to_string(),
};
format!("LAMBDA({}, Captures:{})", p_str, upvalues.len())
},
BoundKind::Call { .. } => "CALL".to_string(),
BoundKind::TailCall { .. } => "T-CALL".to_string(),
BoundKind::Block { .. } => "BLOCK".to_string(),
+27 -8
View File
@@ -117,9 +117,14 @@ impl Dumper {
self.indent -= 1;
}
BoundKind::Lambda { param_count, upvalues, body } => {
self.log(&format!("Lambda (Params: {}, Upvalues: {})", param_count, upvalues.len()), node);
BoundKind::Lambda { params, upvalues, body } => {
self.log(&format!("Lambda (Upvalues: {})", upvalues.len()), node);
self.indent += 1;
self.write_indent();
self.output.push_str("Parameters:\n");
self.visit(params);
if !upvalues.is_empty() {
self.write_indent();
self.output.push_str(&format!("Upvalues: {:?}\n", upvalues));
@@ -128,23 +133,37 @@ impl Dumper {
self.indent -= 1;
}
BoundKind::Parameter { name, slot } => {
self.log(&format!("Parameter (Name: '{}', Slot: {})", name.name, slot), node);
}
BoundKind::Call { callee, args } => {
self.log("Call", node);
self.indent += 1;
self.write_indent();
self.output.push_str("Callee:\n");
self.visit(callee);
for arg in args {
self.visit(arg);
}
self.write_indent();
self.output.push_str("Arguments:\n");
self.visit(args);
self.indent -= 1;
}
BoundKind::TailCall { callee, args } => {
self.log("TailCall (TCO)", node);
self.indent += 1;
self.write_indent();
self.output.push_str("Callee:\n");
self.visit(callee);
for arg in args {
self.visit(arg);
}
self.write_indent();
self.output.push_str("Arguments:\n");
self.visit(args);
self.indent -= 1;
}
+9 -9
View File
@@ -23,9 +23,10 @@ impl<'a> LambdaCollector<'a> {
}
BoundKind::DefGlobal { global_index, value, .. } => {
// If we define a global that is a lambda, register it as a template.
if let BoundKind::Lambda { .. } = &value.kind {
self.registry.insert(*global_index, (**value).clone());
// If we define a global that is a lambda, register its BODY as the template.
// This allows the VM to skip the Lambda/Parameter nodes during execution.
if let BoundKind::Lambda { body, .. } = &value.kind {
self.registry.insert(*global_index, (**body).clone());
}
self.visit(value);
}
@@ -33,9 +34,9 @@ impl<'a> LambdaCollector<'a> {
BoundKind::Set { addr, value } => {
// Also track assignments to globals if they hold lambdas.
if let Address::Global(global_index) = addr
&& let BoundKind::Lambda { .. } = &value.kind
&& let BoundKind::Lambda { body, .. } = &value.kind
{
self.registry.insert(*global_index, (**value).clone());
self.registry.insert(*global_index, (**body).clone());
}
self.visit(value);
}
@@ -48,7 +49,8 @@ impl<'a> LambdaCollector<'a> {
}
}
BoundKind::Lambda { body, .. } => {
BoundKind::Lambda { params, body, .. } => {
self.visit(params);
self.visit(body);
}
@@ -58,9 +60,7 @@ impl<'a> LambdaCollector<'a> {
BoundKind::Call { callee, args } | BoundKind::TailCall { callee, args } => {
self.visit(callee);
for arg in args {
self.visit(arg);
}
self.visit(args);
}
BoundKind::Tuple { elements } => {
+47 -28
View File
@@ -78,7 +78,7 @@ impl<E: MacroEvaluator> MacroExpander<E> {
fn expand_recursive(&mut self, node: Node<UntypedKind>) -> Result<Node<UntypedKind>, String> {
match node.kind {
UntypedKind::MacroDecl { name, params, body } => {
let p_names: Vec<Rc<str>> = params.into_iter().map(|s| s.name).collect();
let p_names = self.extract_param_names(&params)?;
self.registry.define(name.name, p_names, *body);
Ok(Node {
identity: node.identity,
@@ -90,25 +90,27 @@ impl<E: MacroEvaluator> MacroExpander<E> {
UntypedKind::Call { callee, args } => {
if let UntypedKind::Identifier(ref sym) = callee.kind
&& let Some((params, body)) = self.registry.lookup(&sym.name) {
let mut expanded_args = Vec::new();
for arg in args {
expanded_args.push(self.expand_recursive(arg)?);
}
let expanded = self.expand_call(node.identity.clone(), &sym.name, params, expanded_args, body)?;
let expanded_args = self.expand_recursive(*args)?;
// Extract argument nodes from the expanded tuple
let arg_elements = if let UntypedKind::Tuple { elements } = expanded_args.kind {
elements
} else {
vec![expanded_args]
};
let expanded = self.expand_call(node.identity.clone(), &sym.name, params, arg_elements, body)?;
return self.expand_recursive(expanded);
}
let expanded_callee = self.expand_recursive(*callee)?;
let mut expanded_args = Vec::new();
for arg in args {
expanded_args.push(self.expand_recursive(arg)?);
}
let expanded_args = self.expand_recursive(*args)?;
Ok(Node {
identity: node.identity,
kind: UntypedKind::Call {
callee: Box::new(expanded_callee),
args: expanded_args,
args: Box::new(expanded_args),
},
ty: (),
})
@@ -131,12 +133,16 @@ impl<E: MacroEvaluator> MacroExpander<E> {
UntypedKind::Lambda { params, body } => {
self.registry.push();
let expanded_params = self.expand_recursive(*params)?;
let expanded_body = self.expand_recursive(Node::clone(&body))?;
self.registry.pop();
Ok(Node {
identity: node.identity,
kind: UntypedKind::Lambda { params, body: Rc::new(expanded_body) },
kind: UntypedKind::Lambda {
params: Box::new(expanded_params),
body: Rc::new(expanded_body)
},
ty: (),
})
}
@@ -242,7 +248,11 @@ impl<E: MacroEvaluator> MacroExpander<E> {
kind: UntypedKind::Identifier(Symbol::from(name)),
ty: (),
}),
args: bindings.into_values().collect(),
args: Box::new(Node {
identity: identity.clone(),
kind: UntypedKind::Tuple { elements: bindings.into_values().collect() },
ty: (),
}),
},
ty: (),
};
@@ -257,6 +267,20 @@ impl<E: MacroEvaluator> MacroExpander<E> {
})
}
fn extract_param_names(&self, node: &Node<UntypedKind>) -> Result<Vec<Rc<str>>, String> {
match &node.kind {
UntypedKind::Parameter(sym) => Ok(vec![sym.name.clone()]),
UntypedKind::Tuple { elements } => {
let mut names = Vec::new();
for el in elements {
names.extend(self.extract_param_names(el)?);
}
Ok(names)
}
_ => Err("Invalid parameter pattern in macro declaration".to_string()),
}
}
fn expand_template(&self, node: Node<UntypedKind>, state: &mut ExpansionState) -> Result<Node<UntypedKind>, String> {
match node.kind {
UntypedKind::Identifier(mut sym) => {
@@ -265,6 +289,11 @@ impl<E: MacroEvaluator> MacroExpander<E> {
Ok(Node { identity: node.identity, kind: UntypedKind::Identifier(sym), ty: () })
}
UntypedKind::Parameter(mut sym) => {
sym.context = Some(state.expansion_id.clone());
Ok(Node { identity: node.identity, kind: UntypedKind::Parameter(sym), ty: () })
}
UntypedKind::Placeholder(inner) => {
// Break out of template for substitution/evaluation
if let UntypedKind::Identifier(ref sym) = inner.kind
@@ -353,18 +382,10 @@ impl<E: MacroEvaluator> MacroExpander<E> {
UntypedKind::Call { callee, args } => {
let expanded_callee = self.expand_template(*callee, state)?;
let mut expanded_args = Vec::new();
for arg in args {
if let UntypedKind::Splice(ref inner) = arg.kind {
let val = self.evaluator.evaluate(inner, state.bindings)?;
self.handle_splice_value(val, node.identity.clone(), &mut expanded_args)?;
} else {
expanded_args.push(self.expand_template(arg, state)?);
}
}
let expanded_args = self.expand_template(*args, state)?;
Ok(Node {
identity: node.identity,
kind: UntypedKind::Call { callee: Box::new(expanded_callee), args: expanded_args },
kind: UntypedKind::Call { callee: Box::new(expanded_callee), args: Box::new(expanded_args) },
ty: (),
})
}
@@ -384,14 +405,12 @@ impl<E: MacroEvaluator> MacroExpander<E> {
})
}
UntypedKind::Lambda { mut params, body } => {
for p in &mut params {
p.context = Some(state.expansion_id.clone());
}
UntypedKind::Lambda { params, body } => {
let expanded_params = self.expand_template(*params, state)?;
let body = self.expand_template(Node::clone(&body), state)?;
Ok(Node {
identity: node.identity,
kind: UntypedKind::Lambda { params, body: Rc::new(body) },
kind: UntypedKind::Lambda { params: Box::new(expanded_params), body: Rc::new(body) },
ty: (),
})
}
+45 -22
View File
@@ -49,13 +49,13 @@ impl Specializer {
fn visit_node(&self, node: TypedNode) -> TypedNode {
let (new_kind, new_ty) = match node.kind {
BoundKind::Call { callee, args } => {
let (new_callee, new_args, ret_ty) = self.specialize_call_logic(*callee, args, node.ty.clone());
(BoundKind::Call { callee: Box::new(new_callee), args: new_args }, ret_ty)
let (new_callee, new_args, ret_ty) = self.specialize_call_logic(*callee, *args, node.ty.clone());
(BoundKind::Call { callee: Box::new(new_callee), args: Box::new(new_args) }, ret_ty)
},
BoundKind::TailCall { callee, args } => {
let (new_callee, new_args, ret_ty) = self.specialize_call_logic(*callee, args, node.ty.clone());
(BoundKind::TailCall { callee: Box::new(new_callee), args: new_args }, ret_ty)
let (new_callee, new_args, ret_ty) = self.specialize_call_logic(*callee, *args, node.ty.clone());
(BoundKind::TailCall { callee: Box::new(new_callee), args: Box::new(new_args) }, ret_ty)
},
// Recursive traversal for other nodes
@@ -69,9 +69,10 @@ impl Specializer {
let exprs = exprs.into_iter().map(|e| self.visit_node(e)).collect();
(BoundKind::Block { exprs }, node.ty)
},
BoundKind::Lambda { param_count, upvalues, body } => {
BoundKind::Lambda { params, upvalues, body } => {
let params = Box::new(self.visit_node(*params));
let body = Rc::new(self.visit_node((*body).clone()));
(BoundKind::Lambda { param_count, upvalues, body }, node.ty)
(BoundKind::Lambda { params, upvalues, body }, node.ty)
},
BoundKind::DefLocal { name, slot, value, captured_by } => {
let value = Box::new(self.visit_node(*value));
@@ -109,10 +110,10 @@ impl Specializer {
}
}
fn specialize_call_logic(&self, callee: TypedNode, args: Vec<TypedNode>, original_ty: StaticType) -> (TypedNode, Vec<TypedNode>, StaticType) {
fn specialize_call_logic(&self, callee: TypedNode, args: TypedNode, original_ty: StaticType) -> (TypedNode, TypedNode, StaticType) {
// 1. Specialize children first
let new_callee = self.visit_node(callee);
let new_args: Vec<TypedNode> = args.into_iter().map(|a| self.visit_node(a)).collect();
let new_args = self.visit_node(args);
// 2. Check if this call is a candidate (Callee is Get(Address))
let address = if let BoundKind::Get { addr, .. } = &new_callee.kind {
@@ -123,7 +124,12 @@ impl Specializer {
};
// 3. Check if all argument types are statically known
let arg_types: Vec<StaticType> = new_args.iter().map(|a| a.ty.clone()).collect();
let arg_types: Vec<StaticType> = if let StaticType::Tuple(elements) = &new_args.ty {
elements.clone()
} else {
vec![new_args.ty.clone()]
};
if arg_types.iter().any(|t| matches!(t, StaticType::Any)) {
// Cannot specialize with unknown types
return (new_callee, new_args, original_ty);
@@ -139,7 +145,7 @@ impl Specializer {
identity: new_callee.identity.clone(),
kind: BoundKind::Constant(val.clone()),
ty: StaticType::Function(Box::new(Signature {
params: arg_types,
params: StaticType::Tuple(arg_types),
ret: ret_ty.clone(),
})),
};
@@ -158,7 +164,7 @@ impl Specializer {
identity: new_callee.identity.clone(),
kind: BoundKind::Constant(val.clone()),
ty: StaticType::Function(Box::new(Signature {
params: arg_types,
params: StaticType::Tuple(arg_types),
ret: ret_ty.clone(),
})),
};
@@ -192,7 +198,7 @@ impl Specializer {
identity: new_callee.identity.clone(),
kind: BoundKind::Constant(res_val),
ty: StaticType::Function(Box::new(Signature {
params: arg_types,
params: StaticType::Tuple(arg_types),
ret: res_ty.clone(),
})),
};
@@ -262,7 +268,19 @@ mod tests {
let func_node = BoundNode {
identity: make_identity(),
kind: BoundKind::Lambda {
param_count: 1,
params: Box::new(BoundNode {
identity: make_identity(),
kind: BoundKind::Tuple {
elements: vec![
BoundNode {
identity: make_identity(),
kind: BoundKind::Parameter { name: name.clone(), slot: 0 },
ty: ()
}
]
},
ty: ()
}),
upvalues: vec![],
body: Rc::new(BoundNode { identity: make_identity(), kind: BoundKind::Nop, ty: () })
},
@@ -278,12 +296,13 @@ mod tests {
let spec = Specializer::new(Some(Rc::new(registry)), Some(compiler), None, None);
// Call(Get(Local(0)), [Arg(Int)])
// Call(Get(Local(0)), Tuple([Arg(Int)]))
let callee = make_typed_node(BoundKind::Get { addr, name: name.clone() }, StaticType::Any);
let arg = make_typed_node(BoundKind::Constant(Value::Int(1)), StaticType::Int);
let args_tuple = make_typed_node(BoundKind::Tuple { elements: vec![arg] }, StaticType::Tuple(vec![StaticType::Int]));
let call_node = make_typed_node(
BoundKind::Call { callee: Box::new(callee), args: vec![arg] },
BoundKind::Call { callee: Box::new(callee), args: Box::new(args_tuple) },
StaticType::Any
);
@@ -310,12 +329,13 @@ mod tests {
let name0 = Symbol::from("f");
let name1 = Symbol::from("x");
// Call(Get(Local(0)), [Get(Local(1))]) where arg is Any
let callee = make_typed_node(BoundKind::Get { addr: Address::Local(0), name: name0 }, StaticType::Function(Box::new(Signature { params: vec![StaticType::Any], ret: StaticType::Void })));
// Call(Get(Local(0)), Tuple([Get(Local(1))])) where arg is Any
let callee = make_typed_node(BoundKind::Get { addr: Address::Local(0), name: name0 }, StaticType::Function(Box::new(Signature { params: StaticType::Tuple(vec![StaticType::Any]), ret: StaticType::Void })));
let arg = make_typed_node(BoundKind::Get { addr: Address::Local(1), name: name1 }, StaticType::Any);
let args_tuple = make_typed_node(BoundKind::Tuple { elements: vec![arg] }, StaticType::Tuple(vec![StaticType::Any]));
let call_node = make_typed_node(
BoundKind::Call { callee: Box::new(callee), args: vec![arg] },
BoundKind::Call { callee: Box::new(callee), args: Box::new(args_tuple) },
StaticType::Void
);
@@ -349,12 +369,13 @@ mod tests {
spec.cache.borrow_mut().insert(key, (specialized_val.clone(), ret_ty.clone()));
// Create the call node: Call(Get(0), [Arg(Int)])
// Create the call node: Call(Get(0), Tuple([Arg(Int)]))
let callee = make_typed_node(BoundKind::Get { addr, name }, StaticType::Any);
let arg = make_typed_node(BoundKind::Constant(Value::Int(1)), StaticType::Int);
let args_tuple = make_typed_node(BoundKind::Tuple { elements: vec![arg] }, StaticType::Tuple(vec![StaticType::Int]));
let call_node = make_typed_node(
BoundKind::Call { callee: Box::new(callee), args: vec![arg] },
BoundKind::Call { callee: Box::new(callee), args: Box::new(args_tuple) },
StaticType::Any
);
@@ -393,9 +414,10 @@ mod tests {
let callee = make_typed_node(BoundKind::Get { addr, name }, StaticType::Any);
let arg = make_typed_node(BoundKind::Constant(Value::Int(1)), StaticType::Int);
let args_tuple = make_typed_node(BoundKind::Tuple { elements: vec![arg] }, StaticType::Tuple(vec![StaticType::Int]));
let call_node = make_typed_node(
BoundKind::Call { callee: Box::new(callee), args: vec![arg] },
BoundKind::Call { callee: Box::new(callee), args: Box::new(args_tuple) },
StaticType::Any
);
@@ -431,10 +453,11 @@ mod tests {
let callee = make_typed_node(BoundKind::Get { addr, name }, StaticType::Any);
let arg = make_typed_node(BoundKind::Constant(Value::Int(1)), StaticType::Int);
let args_tuple = make_typed_node(BoundKind::Tuple { elements: vec![arg] }, StaticType::Tuple(vec![StaticType::Int]));
// Use TailCall here
let call_node = make_typed_node(
BoundKind::TailCall { callee: Box::new(callee), args: vec![arg] },
BoundKind::TailCall { callee: Box::new(callee), args: Box::new(args_tuple) },
StaticType::Any
);
+3 -3
View File
@@ -16,7 +16,7 @@ impl TCO {
match node.kind {
BoundKind::Call { callee, args } => {
let new_callee = Box::new(Self::transform(*callee, false));
let new_args: Vec<_> = args.into_iter().map(|arg| Self::transform(arg, false)).collect();
let new_args = Box::new(Self::transform(*args, false));
if is_tail_position {
Node {
@@ -75,13 +75,13 @@ impl TCO {
}
},
BoundKind::Lambda { param_count, upvalues, body } => {
BoundKind::Lambda { params, upvalues, body } => {
// The body of a lambda is implicitly in tail position when the lambda is called.
let new_body = Rc::new(Self::transform((*body).clone(), true));
Node {
kind: BoundKind::Lambda {
param_count,
params,
upvalues,
body: new_body,
},
+63 -28
View File
@@ -48,7 +48,7 @@ impl TypeChecker {
pub fn check(&self, node: BoundNode, arg_types: &[StaticType]) -> Result<TypedNode, String> {
match node.kind {
BoundKind::Lambda { param_count, upvalues, body } => {
BoundKind::Lambda { params, upvalues, body } => {
// 1. Determine types of captured variables (Root lambdas have none)
let mut upvalue_types = Vec::with_capacity(upvalues.len());
for _ in &upvalues {
@@ -57,35 +57,33 @@ impl TypeChecker {
// 2. Create the specialized context
let root_ctx = TypeContext::new(0, vec![], None);
let mut lambda_ctx = TypeContext::new(param_count + 64, upvalue_types, Some(&root_ctx));
let mut lambda_ctx = TypeContext::new(64, upvalue_types, Some(&root_ctx));
// 3. INJECT specialized argument types into slots
for (i, ty) in arg_types.iter().enumerate() {
if (i as u32) < param_count {
lambda_ctx.set_local_type(i as u32, ty.clone());
}
}
let arg_tuple_ty = if arg_types.is_empty() {
StaticType::Any // No specialized info
} else {
StaticType::Tuple(arg_types.to_vec())
};
let params_typed = self.check_params(*params, &arg_tuple_ty, &mut lambda_ctx)?;
// 4. Check body with the new types
let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?;
let ret_ty = body_typed.ty.clone();
// 5. Construct specialized function type
let final_params = if arg_types.is_empty() {
vec![StaticType::Any; param_count as usize]
} else {
arg_types.to_vec()
};
let final_params_ty = params_typed.ty.clone();
let fn_ty = StaticType::Function(Box::new(crate::ast::types::Signature {
params: final_params,
params: final_params_ty,
ret: ret_ty,
}));
Ok(Node {
identity: node.identity,
kind: BoundKind::Lambda {
param_count,
params: Box::new(params_typed),
upvalues,
body: Rc::new(body_typed)
},
@@ -97,7 +95,11 @@ impl TypeChecker {
let virtual_lambda = BoundNode {
identity: node.identity.clone(),
kind: BoundKind::Lambda {
param_count: 0,
params: Box::new(Node {
identity: node.identity.clone(),
kind: BoundKind::Tuple { elements: vec![] },
ty: (),
}),
upvalues: vec![],
body: Rc::new(node)
},
@@ -108,6 +110,38 @@ impl TypeChecker {
}
}
fn check_params(&self, node: BoundNode, specialized_ty: &StaticType, ctx: &mut TypeContext) -> Result<TypedNode, String> {
let (kind, ty) = match node.kind {
BoundKind::Parameter { name, slot } => {
ctx.set_local_type(slot, specialized_ty.clone());
(BoundKind::Parameter { name, slot }, specialized_ty.clone())
}
BoundKind::Tuple { elements } => {
let mut typed_elements = Vec::new();
let mut elem_types = Vec::new();
for (i, el) in elements.into_iter().enumerate() {
let sub_ty = match specialized_ty {
StaticType::Tuple(t) => t.get(i).cloned().unwrap_or(StaticType::Any),
StaticType::Vector(inner, _) => (**inner).clone(),
_ => StaticType::Any,
};
let t = self.check_params(el, &sub_ty, ctx)?;
elem_types.push(t.ty.clone());
typed_elements.push(t);
}
(BoundKind::Tuple { elements: typed_elements }, StaticType::Tuple(elem_types))
}
_ => return Err("Invalid node in parameter list".to_string()),
};
Ok(Node {
identity: node.identity,
kind,
ty,
})
}
fn check_node(&self, node: BoundNode, ctx: &mut TypeContext) -> Result<TypedNode, String> {
let (kind, ty) = match node.kind {
BoundKind::Nop => (BoundKind::Nop, StaticType::Void),
@@ -117,6 +151,10 @@ impl TypeChecker {
(BoundKind::Constant(v), ty)
},
BoundKind::Parameter { name, slot } => {
(BoundKind::Parameter { name, slot }, ctx.get_type(Address::Local(slot)))
},
BoundKind::Get { addr, name } => {
let ty = if let Address::Global(idx) = addr {
self.global_types.borrow().get(&idx).cloned().unwrap_or(StaticType::Any)
@@ -194,7 +232,7 @@ impl TypeChecker {
(BoundKind::Block { exprs: typed_exprs }, last_ty)
},
BoundKind::Lambda { param_count, upvalues, body } => {
BoundKind::Lambda { params, upvalues, body } => {
// 1. Determine types of captured variables
let mut upvalue_types = Vec::with_capacity(upvalues.len());
for &addr in &upvalues {
@@ -202,20 +240,21 @@ impl TypeChecker {
}
// 2. Create nested context for lambda body
let mut lambda_ctx = TypeContext::new(param_count + 64, upvalue_types, Some(ctx));
let mut lambda_ctx = TypeContext::new(64, upvalue_types, Some(ctx));
// 3. Check body
// 3. Check parameters and body
let params_typed = self.check_params(*params, &StaticType::Any, &mut lambda_ctx)?;
let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?;
let ret_ty = body_typed.ty.clone();
// 4. Construct function type: fn(any ... any) -> Ret
// 4. Construct function type
let fn_ty = StaticType::Function(Box::new(crate::ast::types::Signature {
params: vec![StaticType::Any; param_count as usize],
params: params_typed.ty.clone(),
ret: ret_ty,
}));
(BoundKind::Lambda {
param_count,
params: Box::new(params_typed),
upvalues,
body: Rc::new(body_typed)
}, fn_ty)
@@ -223,17 +262,13 @@ impl TypeChecker {
BoundKind::Call { callee, args } => {
let callee_typed = self.check_node(*callee, ctx)?;
let mut typed_args = Vec::new();
for arg in args {
typed_args.push(self.check_node(arg, ctx)?);
}
let args_typed = self.check_node(*args, ctx)?;
let arg_types: Vec<_> = typed_args.iter().map(|a| a.ty.clone()).collect();
let ret_ty = callee_typed.ty.resolve_call(&arg_types).unwrap_or(StaticType::Any);
let ret_ty = callee_typed.ty.resolve_call(&args_typed.ty).unwrap_or(StaticType::Any);
(BoundKind::Call {
callee: Box::new(callee_typed),
args: typed_args
args: Box::new(args_typed)
}, ret_ty)
},
+25 -8
View File
@@ -19,6 +19,20 @@ impl UpvalueAnalyzer {
.collect()
}
fn visit_params(node: &Node<UntypedKind>, scope: &mut HashMap<Symbol, Identity>) {
match &node.kind {
UntypedKind::Parameter(sym) => {
scope.insert(sym.clone(), node.identity.clone());
}
UntypedKind::Tuple { elements } => {
for el in elements {
Self::visit_params(el, scope);
}
}
_ => {} // Ignore other nodes in parameter patterns for now
}
}
fn visit(
node: &Node<UntypedKind>,
scopes: &mut Vec<HashMap<Symbol, Identity>>,
@@ -50,15 +64,20 @@ impl UpvalueAnalyzer {
}
UntypedKind::Lambda { params, body } => {
let mut new_scope = HashMap::new();
for p in params {
new_scope.insert(p.clone(), node.identity.clone());
}
Self::visit_params(params, &mut new_scope);
scopes.push(new_scope);
// The current node is the lambda causing captures in its body
Self::visit(body, scopes, capture_map, Some(node.identity.clone()));
scopes.pop();
}
UntypedKind::MacroDecl { params, body, .. } => {
let mut new_scope = HashMap::new();
Self::visit_params(params, &mut new_scope);
scopes.push(new_scope);
Self::visit(body, scopes, capture_map, current_lambda);
scopes.pop();
}
UntypedKind::If { cond, then_br, else_br } => {
Self::visit(cond, scopes, capture_map, current_lambda.clone());
Self::visit(then_br, scopes, capture_map, current_lambda.clone());
@@ -72,9 +91,7 @@ impl UpvalueAnalyzer {
}
UntypedKind::Call { callee, args } => {
Self::visit(callee, scopes, capture_map, current_lambda.clone());
for arg in args {
Self::visit(arg, scopes, capture_map, current_lambda.clone());
}
Self::visit(args, scopes, capture_map, current_lambda.clone());
}
UntypedKind::Block { exprs } => {
for expr in exprs {
@@ -95,10 +112,10 @@ impl UpvalueAnalyzer {
UntypedKind::Expansion { call: _, expanded } => {
Self::visit(expanded, scopes, capture_map, current_lambda);
}
UntypedKind::MacroDecl { body, .. } | UntypedKind::Template(body) | UntypedKind::Placeholder(body) | UntypedKind::Splice(body) => {
UntypedKind::Template(body) | UntypedKind::Placeholder(body) | UntypedKind::Splice(body) => {
Self::visit(body, scopes, capture_map, current_lambda);
}
UntypedKind::Nop | UntypedKind::Constant(_) | UntypedKind::Extension(_) => {}
UntypedKind::Nop | UntypedKind::Constant(_) | UntypedKind::Extension(_) | UntypedKind::Parameter(_) => {}
}
}
}