Add tuple type recomputation after inlining
Introduce helper functions to recompute the `StaticType` of a tuple node based on its children's types. This ensures that after inlining, tuples within the AST have concrete types, rather than unresolved type variables, even when derived from polymorphic functions. Also adds tests to verify this behavior for both homogeneous and heterogeneous tuples.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
use crate::ast::nodes::{
|
||||
Address, AnalyzedNode, AssignBinding, GlobalAnalyzedRegistry,
|
||||
IdentifierBinding, LambdaBinding, Node, NodeKind, UpvalueIdx, VirtualId,
|
||||
IdentifierBinding, LambdaBinding, Node, NodeKind, NodeMetrics, UpvalueIdx, VirtualId,
|
||||
};
|
||||
use crate::ast::types::{Purity, Value};
|
||||
use crate::ast::types::{Purity, StaticType, Value};
|
||||
use crate::ast::vm::GlobalStore;
|
||||
|
||||
use std::cell::RefCell;
|
||||
@@ -13,6 +13,48 @@ use super::inliner::Inliner;
|
||||
use super::substitution_map::SubstitutionMap;
|
||||
use super::utils::{collect_pattern_addrs, PathTracker, UsageInfo};
|
||||
|
||||
/// Recompute the `StaticType` for a Tuple node from its children's concrete types.
|
||||
/// Mirrors the type-computation logic in `TypeChecker::check_node` for `NodeKind::Tuple`.
|
||||
fn compute_tuple_type(elements: &[Rc<AnalyzedNode>]) -> StaticType {
|
||||
if elements.is_empty() {
|
||||
return StaticType::Vector(Box::new(StaticType::Any), 0);
|
||||
}
|
||||
let first_ty = &elements[0].ty.original.ty;
|
||||
let all_same = elements.iter().all(|e| e.ty.original.ty == *first_ty);
|
||||
|
||||
if all_same {
|
||||
match first_ty {
|
||||
StaticType::Vector(inner, len) => {
|
||||
StaticType::Matrix(inner.clone(), vec![elements.len(), *len])
|
||||
}
|
||||
StaticType::Matrix(inner, shape) => {
|
||||
let mut new_shape = vec![elements.len()];
|
||||
new_shape.extend(shape);
|
||||
StaticType::Matrix(inner.clone(), new_shape)
|
||||
}
|
||||
_ => StaticType::Vector(Box::new(first_ty.clone()), elements.len()),
|
||||
}
|
||||
} else {
|
||||
StaticType::Tuple(elements.iter().map(|e| e.ty.original.ty.clone()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a new `NodeMetrics` with an updated `StaticType`, reusing the original
|
||||
/// node's identity, kind, and comments. Only call when the type actually changed.
|
||||
fn updated_metrics(old: &NodeMetrics, new_ty: StaticType) -> NodeMetrics {
|
||||
let orig = &old.original;
|
||||
NodeMetrics {
|
||||
original: Rc::new(Node {
|
||||
identity: orig.identity.clone(),
|
||||
kind: orig.kind.clone(),
|
||||
ty: new_ty,
|
||||
comments: orig.comments.clone(),
|
||||
}),
|
||||
purity: old.purity,
|
||||
is_recursive: old.is_recursive,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Optimizer {
|
||||
pub enabled: bool,
|
||||
max_passes: usize,
|
||||
@@ -663,7 +705,13 @@ impl Optimizer {
|
||||
if !changed {
|
||||
return node_rc;
|
||||
}
|
||||
(NodeKind::Tuple { elements: new_elements }, node.ty.clone())
|
||||
let recomputed = compute_tuple_type(&new_elements);
|
||||
let metrics = if recomputed != node.ty.original.ty {
|
||||
updated_metrics(&node.ty, recomputed)
|
||||
} else {
|
||||
node.ty.clone()
|
||||
};
|
||||
(NodeKind::Tuple { elements: new_elements }, metrics)
|
||||
}
|
||||
NodeKind::Record { fields, layout } => {
|
||||
let mut mapped_fields = Vec::with_capacity(fields.len());
|
||||
|
||||
@@ -216,3 +216,36 @@ fn test_slot_reuse_non_overlapping_scopes() {
|
||||
dump
|
||||
);
|
||||
}
|
||||
|
||||
// ── Tuple type recomputation after inlining ─────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_inlined_tuple_has_concrete_type_homogeneous() {
|
||||
// A polymorphic function returning a tuple, called with all-Int args.
|
||||
// After inlining, the tuple type must be concrete (no unresolved TypeVars).
|
||||
let env = Environment::new();
|
||||
let source = r#"(do
|
||||
(def wrap (fn [x y z] [x y z]))
|
||||
(wrap 1 2 3)
|
||||
)"#;
|
||||
let dump = env.dump_ast_compact(source).unwrap();
|
||||
assert!(
|
||||
!dump.contains('?'),
|
||||
"Inlined tuple must not contain unresolved TypeVars. Dump:\n{dump}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inlined_tuple_has_concrete_type_heterogeneous() {
|
||||
// Heterogeneous case: Int + String should produce Tuple([int, str]), not ?0/?1.
|
||||
let env = Environment::new();
|
||||
let source = r#"(do
|
||||
(def wrap (fn [x y] [x y]))
|
||||
(wrap 1 "hello")
|
||||
)"#;
|
||||
let dump = env.dump_ast_compact(source).unwrap();
|
||||
assert!(
|
||||
!dump.contains('?'),
|
||||
"Inlined tuple must not contain unresolved TypeVars. Dump:\n{dump}"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user