Refactor: Use new_inner for substitution maps

The `SubstitutionMap::new_inner()` method provides a more robust way to
create nested substitution maps, ensuring that only global substitutions
are inherited. This prevents potential issues with overlapping local and
upvalue addresses in nested scopes, such as during lambda inlining.

This change also includes:
- A new `try_fold_record` method in `folder.rs` to optimize record
  literals into constant values.
- Updates to `inliner.rs` to recognize `Value::Record` for inlining.
- Various test cases to verify record optimization and constant folding.
This commit is contained in:
Michael Schimmel
2026-02-28 16:01:56 +01:00
parent 580c0893c1
commit 3f5d2620ec
5 changed files with 104 additions and 17 deletions
+13 -8
View File
@@ -229,7 +229,7 @@ impl Optimizer {
&& path.enter_lambda(&callee.identity)
{
path.inlining_depth += 1;
let mut inner_sub = SubstitutionMap::new();
let mut inner_sub = sub.new_inner();
let collapsed = if inliner
.prepare_beta_reduction(params, &arg_nodes, body, &mut inner_sub)
.is_some()
@@ -266,7 +266,7 @@ impl Optimizer {
&& positional_count.is_some()
&& !lambda_node.ty.is_recursive
{
let mut inner_sub = SubstitutionMap::new();
let mut inner_sub = sub.new_inner();
path.inlining_stack.insert(*idx);
path.inlining_depth += 1;
let collapsed = if inliner
@@ -467,7 +467,7 @@ impl Optimizer {
let mut new_upvalues = Vec::new();
let mut mapping = Vec::new();
let mut next_inner_subs = SubstitutionMap::new();
let mut next_inner_subs = sub.new_inner();
next_inner_subs.assigned = info.assigned;
for (old_idx, capture_addr) in original_upvalues.iter().enumerate() {
@@ -543,12 +543,17 @@ impl Optimizer {
.collect();
(BoundKind::Tuple { elements }, node.ty.clone())
}
BoundKind::Record { fields } => {
let fields = fields
.into_iter()
.map(|(k, v)| (self.visit_node(k, sub, path), self.visit_node(v, sub, path)))
BoundKind::Record { ref fields } => {
let mapped_fields: Vec<_> = fields
.iter()
.map(|(k, v)| (self.visit_node(k.clone(), sub, path), self.visit_node(v.clone(), sub, path)))
.collect();
(BoundKind::Record { fields }, node.ty.clone())
if self.enabled && let Some(folded) = folder.try_fold_record(&mapped_fields, &node) {
return folded;
}
(BoundKind::Record { fields: mapped_fields }, node.ty.clone())
}
BoundKind::Expansion {
ref original_call,
+27 -1
View File
@@ -1,6 +1,6 @@
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, NodeMetrics};
use crate::ast::nodes::Node;
use crate::ast::types::{Purity, StaticType, Value};
use crate::ast::types::{Purity, StaticType, Value, RecordLayout};
use std::cell::RefCell;
use std::rc::Rc;
@@ -48,6 +48,32 @@ impl<'a> Folder<'a> {
}
}
pub fn try_fold_record(
&self,
fields: &[(AnalyzedNode, AnalyzedNode)],
template: &AnalyzedNode,
) -> Option<AnalyzedNode> {
let mut layout_fields = Vec::with_capacity(fields.len());
let mut values = Vec::with_capacity(fields.len());
for (k_node, v_node) in fields {
if let BoundKind::Constant(Value::Keyword(kw)) = &k_node.kind {
if let BoundKind::Constant(val) = &v_node.kind {
layout_fields.push((*kw, val.static_type()));
values.push(val.clone());
} else {
return None;
}
} else {
return None;
}
}
let layout = RecordLayout::get_or_create(layout_fields);
let record_val = Value::Record(layout, Rc::new(values));
Some(self.make_constant_node(record_val, template))
}
pub fn try_fold_pure(
&self,
callee: &AnalyzedNode,
+1
View File
@@ -31,6 +31,7 @@ impl<'a> Inliner<'a> {
| Value::Bool(_)
| Value::Text(_)
| Value::Keyword(_)
| Value::Record(_, _)
| Value::DateTime(_) => true,
Value::Object(obj) => {
if let Some(closure) = obj.as_any().downcast_ref::<Closure>() {
@@ -19,6 +19,24 @@ impl SubstitutionMap {
Self::default()
}
/// Creates a new SubstitutionMap for an inner scope (like an inlined Lambda).
/// Safely inherits only Global substitutions, because Local and Upvalue
/// addresses are relative to the specific function frame and would overlap.
pub fn new_inner(&self) -> Self {
let mut inner = Self::new();
for (k, v) in &self.values {
if matches!(k, Address::Global(_)) {
inner.values.insert(*k, v.clone());
}
}
for (k, v) in &self.ast_substitutions {
if matches!(k, Address::Global(_)) {
inner.ast_substitutions.insert(*k, v.clone());
}
}
inner
}
pub fn add_ast_substitution(&mut self, addr: Address, node: AnalyzedNode) {
self.ast_substitutions.insert(addr, node);
}