From 1f3fb40bccba6d01bd55a09c82efa5e98fbd8fc7 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 29 Mar 2026 18:59:38 +0200 Subject: [PATCH] Update record type computation This commit refactors the way record types are computed within the optimizer. Previously, the optimizer would often reuse the existing `NodeMetrics` for records, which could lead to stale or polymorphic types. The changes introduce a new function, `compute_record_type`, which explicitly recalculates the `StaticType` for a `Record` node based on the concrete types of its child fields. This ensures that after optimizations like inlining and folding, record types are as concrete as possible, eliminating unresolved `TypeVars`. Additionally, the `folder.rs` module is updated to recompute the `RecordLayout` when creating constant record values, further improving type accuracy. Two new tests are added to verify that inlined records, including those with multiple fields, correctly result in concrete types. --- src/ast/compiler/optimizer/engine.rs | 23 +++++++++++++++++-- src/ast/compiler/optimizer/folder.rs | 11 +++++++++- tests/optimizer.rs | 33 ++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/ast/compiler/optimizer/engine.rs b/src/ast/compiler/optimizer/engine.rs index f81ce08..7eb05b6 100644 --- a/src/ast/compiler/optimizer/engine.rs +++ b/src/ast/compiler/optimizer/engine.rs @@ -2,7 +2,8 @@ use crate::ast::nodes::{ Address, AnalyzedNode, AssignBinding, GlobalAnalyzedRegistry, IdentifierBinding, LambdaBinding, Node, NodeKind, NodeMetrics, UpvalueIdx, VirtualId, }; -use crate::ast::types::{Purity, Signature, StaticType, Value}; +use crate::ast::types::{Purity, RecordLayout, Signature, StaticType, Value}; +use std::sync::Arc; use crate::ast::vm::GlobalStore; use std::cell::RefCell; @@ -39,6 +40,23 @@ fn compute_tuple_type(elements: &[Rc]) -> StaticType { } } +/// Recompute the `StaticType` for a Record node from its children's concrete types. +/// Mirrors the type-computation logic in `TypeChecker::check_node` for `NodeKind::Record`. +fn compute_record_type( + layout: &Arc, + fields: &[(Rc, Rc)], +) -> StaticType { + let new_fields: Vec<_> = layout + .fields + .iter() + .zip(fields.iter()) + .map(|((keyword, _old_ty), (_key_node, val_node))| { + (*keyword, val_node.ty.original.ty.clone()) + }) + .collect(); + StaticType::Record(RecordLayout::get_or_create(new_fields)) +} + /// Return `NodeMetrics` with an updated `StaticType` if it differs from the /// original, otherwise clone cheaply. Avoids unnecessary `Rc` allocations. fn refreshed_metrics(old: &NodeMetrics, new_ty: StaticType) -> NodeMetrics { @@ -745,12 +763,13 @@ impl Optimizer { return node_rc; } + let metrics = refreshed_metrics(&node.ty, compute_record_type(layout, &mapped_fields)); ( NodeKind::Record { fields: mapped_fields, layout: layout.clone(), }, - node.ty.clone(), + metrics, ) } NodeKind::Expansion { diff --git a/src/ast/compiler/optimizer/folder.rs b/src/ast/compiler/optimizer/folder.rs index f1296cd..5f632a7 100644 --- a/src/ast/compiler/optimizer/folder.rs +++ b/src/ast/compiler/optimizer/folder.rs @@ -69,7 +69,16 @@ impl<'a> Folder<'a> { } } - let record_val = Value::Record(layout.clone(), Rc::new(constant_values)); + // Recompute layout from concrete value types to eliminate stale TypeVars. + let new_fields: Vec<_> = layout + .fields + .iter() + .zip(values.iter()) + .map(|((keyword, _), v_node)| (*keyword, v_node.ty.original.ty.clone())) + .collect(); + let new_layout = RecordLayout::get_or_create(new_fields); + + let record_val = Value::Record(new_layout, Rc::new(constant_values)); Some(self.make_constant_node(record_val, template)) } diff --git a/tests/optimizer.rs b/tests/optimizer.rs index cd46707..a1dbeb9 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -277,3 +277,36 @@ fn test_block_and_lambda_propagate_concrete_type_after_inlining() { "Block type must be concrete. Line:\n{block_line}" ); } + +// ── Record type recomputation after inlining ──────────────────────────────── + +#[test] +fn test_inlined_record_has_concrete_type() { + // A polymorphic function returning a record, called with a concrete arg. + // After inlining + folding, the record type must be concrete (no TypeVars). + let env = Environment::new(); + let source = r#"(do + (def wrap (fn [x] {:val x})) + (wrap 42) + )"#; + let dump = env.dump_ast_compact(source).unwrap(); + assert!( + !dump.contains('?'), + "Inlined record must not contain unresolved TypeVars. Dump:\n{dump}" + ); +} + +#[test] +fn test_inlined_record_has_concrete_type_multi_field() { + // Heterogeneous case: Int + String fields should produce concrete record type. + let env = Environment::new(); + let source = r#"(do + (def mkr (fn [a b] {:x a :y b})) + (mkr 1 "hello") + )"#; + let dump = env.dump_ast_compact(source).unwrap(); + assert!( + !dump.contains('?'), + "Inlined multi-field record must not contain unresolved TypeVars. Dump:\n{dump}" + ); +}