diff --git a/src/ast/compiler/binder.rs b/src/ast/compiler/binder.rs index b62badd..f12f9c1 100644 --- a/src/ast/compiler/binder.rs +++ b/src/ast/compiler/binder.rs @@ -233,7 +233,8 @@ impl Binder { Ok(self.make_node(node.identity.clone(), BoundKind::Call { callee: Box::new(callee), - args: Box::new(args) + args: Box::new(args), + is_tail: false })) }, diff --git a/src/ast/compiler/bound_nodes.rs b/src/ast/compiler/bound_nodes.rs index 54fe4af..797522a 100644 --- a/src/ast/compiler/bound_nodes.rs +++ b/src/ast/compiler/bound_nodes.rs @@ -83,11 +83,7 @@ pub enum BoundKind { Call { callee: Box>, args: Box>, - }, - - TailCall { - callee: Box>, - args: Box>, + is_tail: bool, }, Block { @@ -145,11 +141,8 @@ where T: PartialEq { BoundKind::Lambda { params: pb, upvalues: ub, body: bb, positional_count: pcb }) => { pa == pb && ua == ub && ba == bb && pca == pcb } - (BoundKind::Call { callee: ca, args: aa }, BoundKind::Call { callee: cb, args: ab }) => { - ca == cb && aa == ab - } - (BoundKind::TailCall { callee: ca, args: aa }, BoundKind::TailCall { callee: cb, args: ab }) => { - ca == cb && aa == ab + (BoundKind::Call { callee: ca, args: aa, is_tail: ta }, BoundKind::Call { callee: cb, args: ab, is_tail: tb }) => { + ca == cb && aa == ab && ta == tb } (BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => { ea == eb @@ -191,8 +184,7 @@ impl BoundKind { }; format!("LAMBDA({}, Captures:{})", p_str, upvalues.len()) }, - BoundKind::Call { .. } => "CALL".to_string(), - BoundKind::TailCall { .. } => "T-CALL".to_string(), + BoundKind::Call { is_tail, .. } => if *is_tail { "T-CALL".to_string() } else { "CALL".to_string() }, BoundKind::Block { .. } => "BLOCK".to_string(), BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()), BoundKind::Record { fields } => format!("RECORD({})", fields.len()), diff --git a/src/ast/compiler/dumper.rs b/src/ast/compiler/dumper.rs index 8215bb8..1ccc9b8 100644 --- a/src/ast/compiler/dumper.rs +++ b/src/ast/compiler/dumper.rs @@ -137,23 +137,9 @@ impl Dumper { 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); - - self.write_indent(); - self.output.push_str("Arguments:\n"); - self.visit(args); - - self.indent -= 1; - } - - BoundKind::TailCall { callee, args } => { - self.log("TailCall (TCO)", node); + BoundKind::Call { callee, args, is_tail } => { + let label = if *is_tail { "Call (TCO)" } else { "Call" }; + self.log(label, node); self.indent += 1; self.write_indent(); diff --git a/src/ast/compiler/lambda_collector.rs b/src/ast/compiler/lambda_collector.rs index f000345..6c6e889 100644 --- a/src/ast/compiler/lambda_collector.rs +++ b/src/ast/compiler/lambda_collector.rs @@ -57,7 +57,7 @@ impl<'a> LambdaCollector<'a> { self.visit(value); } - BoundKind::Call { callee, args } | BoundKind::TailCall { callee, args } => { + BoundKind::Call { callee, args, .. } => { self.visit(callee); self.visit(args); } diff --git a/src/ast/compiler/optimizer.rs b/src/ast/compiler/optimizer.rs index 61c997f..3b2be06 100644 --- a/src/ast/compiler/optimizer.rs +++ b/src/ast/compiler/optimizer.rs @@ -34,7 +34,7 @@ impl Optimizer { fn visit_node(&self, node: TypedNode) -> TypedNode { let (new_kind, new_ty) = match node.kind { - BoundKind::Call { callee, args } | BoundKind::TailCall { callee, args } => { + BoundKind::Call { callee, args, is_tail } => { let callee = self.visit_node(*callee); let args = self.visit_node(*args); @@ -87,12 +87,12 @@ impl Optimizer { }; return Node { identity: node.identity, - kind: BoundKind::Call { callee: Box::new(cracked_lambda), args: Box::new(args) }, + kind: BoundKind::Call { callee: Box::new(cracked_lambda), args: Box::new(args), is_tail }, ty: node.ty, }; } - (BoundKind::Call { callee: Box::new(callee), args: Box::new(args) }, node.ty) + (BoundKind::Call { callee: Box::new(callee), args: Box::new(args), is_tail }, node.ty) }, BoundKind::If { cond, then_br, else_br } => { @@ -269,7 +269,7 @@ impl Optimizer { self.contains_def_local(cond) || self.contains_def_local(then_br) || else_br.as_ref().is_some_and(|e| self.contains_def_local(e)) } BoundKind::Block { exprs } => exprs.iter().any(|e| self.contains_def_local(e)), - BoundKind::Call { callee, args } | BoundKind::TailCall { callee, args } => self.contains_def_local(callee) || self.contains_def_local(args), + BoundKind::Call { callee, args, .. } => self.contains_def_local(callee) || self.contains_def_local(args), BoundKind::Lambda { .. } => false, // Nested lambda definitions are safe (they have their own frame) BoundKind::Tuple { elements } => elements.iter().any(|e| self.contains_def_local(e)), BoundKind::Record { fields } => fields.iter().any(|(k, v)| self.contains_def_local(k) || self.contains_def_local(v)), @@ -388,10 +388,10 @@ impl SubstitutionMap { let exprs = exprs.into_iter().map(|e| self.inline_recursive(e, depth, upvalue_subs)).collect(); (BoundKind::Block { exprs }, node.ty) }, - BoundKind::Call { callee, args } | BoundKind::TailCall { callee, args } => { + BoundKind::Call { callee, args, is_tail } => { let callee = Box::new(self.inline_recursive(*callee, depth, upvalue_subs)); let args = Box::new(self.inline_recursive(*args, depth, upvalue_subs)); - (BoundKind::Call { callee, args }, node.ty) + (BoundKind::Call { callee, args, is_tail }, node.ty) }, BoundKind::DefLocal { name, slot, value, captured_by } => { let value = Box::new(self.inline_recursive(*value, depth, upvalue_subs)); @@ -444,10 +444,10 @@ impl SubstitutionMap { let exprs = exprs.into_iter().map(|e| self.reindex_upvalues(e, mapping)).collect(); (BoundKind::Block { exprs }, node.ty) }, - BoundKind::Call { callee, args } | BoundKind::TailCall { callee, args } => { + BoundKind::Call { callee, args, is_tail } => { let callee = Box::new(self.reindex_upvalues(*callee, mapping)); let args = Box::new(self.reindex_upvalues(*args, mapping)); - (BoundKind::Call { callee, args }, node.ty) + (BoundKind::Call { callee, args, is_tail }, node.ty) }, BoundKind::DefLocal { name, slot, value, captured_by } => { let value = Box::new(self.reindex_upvalues(*value, mapping)); diff --git a/src/ast/compiler/specializer.rs b/src/ast/compiler/specializer.rs index f889e83..0220129 100644 --- a/src/ast/compiler/specializer.rs +++ b/src/ast/compiler/specializer.rs @@ -48,14 +48,9 @@ impl Specializer { fn visit_node(&self, node: TypedNode) -> TypedNode { let (new_kind, new_ty) = match node.kind { - BoundKind::Call { callee, args } => { + BoundKind::Call { callee, args, is_tail } => { 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: Box::new(new_args) }, ret_ty) + (BoundKind::Call { callee: Box::new(new_callee), args: Box::new(new_args), is_tail }, ret_ty) }, // Recursive traversal for other nodes diff --git a/src/ast/compiler/tco.rs b/src/ast/compiler/tco.rs index 103577f..7e5633c 100644 --- a/src/ast/compiler/tco.rs +++ b/src/ast/compiler/tco.rs @@ -14,26 +14,17 @@ impl TCO { fn transform(node: Node, T>, is_tail_position: bool) -> Node, T> { match node.kind { - BoundKind::Call { callee, args } => { + BoundKind::Call { callee, args, .. } => { let new_callee = Box::new(Self::transform(*callee, false)); let new_args = Box::new(Self::transform(*args, false)); - if is_tail_position { - Node { - kind: BoundKind::TailCall { - callee: new_callee, - args: new_args, - }, - ..node - } - } else { - Node { - kind: BoundKind::Call { - callee: new_callee, - args: new_args, - }, - ..node - } + Node { + kind: BoundKind::Call { + callee: new_callee, + args: new_args, + is_tail: is_tail_position, + }, + ..node } }, diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index b6df9c3..718a93f 100644 --- a/src/ast/compiler/type_checker.rs +++ b/src/ast/compiler/type_checker.rs @@ -263,7 +263,7 @@ impl TypeChecker { }, fn_ty) }, - BoundKind::Call { callee, args } => { + BoundKind::Call { callee, args, is_tail } => { let callee_typed = self.check_node(*callee, ctx)?; let args_typed = self.check_node(*args, ctx)?; @@ -271,7 +271,8 @@ impl TypeChecker { (BoundKind::Call { callee: Box::new(callee_typed), - args: Box::new(args_typed) + args: Box::new(args_typed), + is_tail }, ret_ty) }, @@ -335,8 +336,6 @@ impl TypeChecker { BoundKind::Extension(_ext) => { return Err(format!("TypeChecking for extension '{}' not implemented", _ext.display_name())); }, - - BoundKind::TailCall { .. } => unreachable!("TailCall generated before TypeChecker"), }; Ok(Node { diff --git a/src/ast/vm.rs b/src/ast/vm.rs index 471d8b0..85dcdee 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -188,48 +188,7 @@ macro_rules! dispatch_eval { Ok(Value::Object(Rc::new(closure))) }, - BoundKind::TailCall { callee, args } => { - let func_val = $self.$eval_method($($observer,)? callee)?; - - let arg_vals = match &args.kind { - BoundKind::Tuple { elements } => { - // FAST-PATH: If it's a flat tuple, evaluate directly into Vec - let mut vals = Vec::with_capacity(elements.len()); - let mut is_complex = false; - for e in elements { - if matches!(e.kind, BoundKind::Tuple { .. }) { - is_complex = true; - break; - } - vals.push($self.$eval_method($($observer,)? e)?); - } - if is_complex { - macro_rules! get_args { - ($s:ident, $a:ident) => { $s.prepare_args($a)? }; - ($s:ident, $o:ident, $a:ident) => { $s.prepare_args_observed($o, $a)? }; - } - get_args!($self, $($observer,)? args) - } else { - vals - } - } - _ => { - macro_rules! get_args { - ($s:ident, $a:ident) => { $s.prepare_args($a)? }; - ($s:ident, $o:ident, $a:ident) => { $s.prepare_args_observed($o, $a)? }; - } - get_args!($self, $($observer,)? args) - } - }; - - match func_val { - Value::Object(obj) => Ok(Value::TailCallRequest(Box::new((obj, arg_vals)))), - Value::Function(f) => Ok(f(arg_vals)), - _ => Err(format!("Tail call target is not a function: {}", func_val)), - } - }, - - BoundKind::Call { callee, args } => { + BoundKind::Call { callee, args, is_tail } => { let mut func_val = $self.$eval_method($($observer,)? callee)?; let mut arg_vals = match &args.kind { @@ -262,6 +221,14 @@ macro_rules! dispatch_eval { } }; + if *is_tail { + match func_val { + Value::Object(obj) => return Ok(Value::TailCallRequest(Box::new((obj, arg_vals)))), + Value::Function(f) => return Ok(f(arg_vals)), + _ => return Err(format!("Tail call target is not a function: {}", func_val)), + } + } + loop { match func_val { Value::Function(f) => break Ok(f(arg_vals)), @@ -792,6 +759,7 @@ mod tests { ty: StaticType::Tuple(vec![]), kind: BoundKind::Tuple { elements: vec![] }, }), + is_tail: false, }, }, // return x (Local 0)