Refactor Record's internal representation

This commit refactors the internal representation of `BoundKind::Record`
to store a `RecordLayout` and a `Vec` of values, rather than a `Vec` of
key-value pairs. This change simplifies the representation and improves
efficiency by decoupling the record's structure from its specific values
during compilation and analysis.

The `RecordLayout` now defines the structure of the record, and the
values are stored in a separate vector, ordered according to the layout.
This allows for better optimization and type checking, as the record's
shape is explicitly defined and immutable once created.
This commit is contained in:
Michael Schimmel
2026-02-28 16:47:23 +01:00
parent 096f166153
commit eab3e02199
15 changed files with 88 additions and 103 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
;; Benchmark: 1.9us
;; Benchmark-Repeat: 1211
;; Benchmark: 1.3us
;; Benchmark-Repeat: 1599
;; Output: ["Alice" 101 :admin "Zürich" ["Alice" "Bob"] true]
; ---------------------------------------------------------
+8 -10
View File
@@ -265,16 +265,15 @@ impl<'a> Analyzer<'a> {
)
}
BoundKind::Record { fields } => {
let mut new_fields = Vec::with_capacity(fields.len());
BoundKind::Record { layout, values } => {
let mut new_values = Vec::with_capacity(values.len());
let mut p = Purity::Pure;
for (k, v) in fields {
let km = self.visit(Rc::new(k.clone()));
for v in values {
let vm = self.visit(Rc::new(v.clone()));
p = p.min(km.ty.purity).min(vm.ty.purity);
new_fields.push((km, vm));
p = p.min(vm.ty.purity);
new_values.push(vm);
}
(BoundKind::Record { fields: new_fields }, p)
(BoundKind::Record { layout: layout.clone(), values: new_values }, p)
}
BoundKind::Expansion {
@@ -348,9 +347,8 @@ impl NodeExt for BoundKind<crate::ast::types::StaticType> {
f(e);
}
}
BoundKind::Record { fields } => {
for (k, v) in fields {
f(k);
BoundKind::Record { values, .. } => {
for v in values {
f(v);
}
}
+17 -3
View File
@@ -374,14 +374,28 @@ impl Binder {
}
UntypedKind::Record { fields } => {
let mut bound_fields = Vec::new();
let mut bound_values = Vec::new();
let mut layout_fields = Vec::new();
for (k, v) in fields {
bound_fields.push((self.bind(k)?, self.bind(v)?));
let key_node = self.bind(k)?;
let val_node = self.bind(v)?;
if let BoundKind::Constant(crate::ast::types::Value::Keyword(kw)) = key_node.kind {
layout_fields.push((kw, crate::ast::types::StaticType::Any));
} else {
return Err(format!("Record keys must be keywords, found at {:?}", key_node.identity.location));
}
bound_values.push(val_node);
}
let layout = crate::ast::types::RecordLayout::get_or_create(layout_fields);
Ok(self.make_node(
node.identity.clone(),
BoundKind::Record {
fields: bound_fields,
layout,
values: bound_values,
},
))
}
+6 -3
View File
@@ -145,7 +145,8 @@ pub enum BoundKind<T = ()> {
},
Record {
fields: Vec<RecordField<T>>,
layout: std::sync::Arc<crate::ast::types::RecordLayout>,
values: Vec<BoundNode<T>>,
},
/// An expanded macro call, preserving the original call for debugging and UI.
@@ -250,7 +251,9 @@ where
(BoundKind::Again { args: aa }, BoundKind::Again { args: ab }) => aa == ab,
(BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => ea == eb,
(BoundKind::Tuple { elements: ea }, BoundKind::Tuple { elements: eb }) => ea == eb,
(BoundKind::Record { fields: fa }, BoundKind::Record { fields: fb }) => fa == fb,
(BoundKind::Record { layout: la, values: va }, BoundKind::Record { layout: lb, values: vb }) => {
std::sync::Arc::ptr_eq(la, lb) && va == vb
}
(
BoundKind::Expansion {
original_call: oa,
@@ -303,7 +306,7 @@ impl<T> BoundKind<T> {
BoundKind::Again { .. } => "AGAIN".to_string(),
BoundKind::Block { .. } => "BLOCK".to_string(),
BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()),
BoundKind::Record { fields } => format!("RECORD({})", fields.len()),
BoundKind::Record { values, .. } => format!("RECORD({})", values.len()),
BoundKind::Expansion { .. } => "EXPANSION".to_string(),
BoundKind::Extension(ext) => ext.display_name(),
}
+4 -8
View File
@@ -108,16 +108,12 @@ impl CapturePass {
};
}
BoundKind::Record { fields } => {
BoundKind::Record { layout, values } => {
node.kind = BoundKind::Record {
fields: fields
layout,
values: values
.into_iter()
.map(|(k, v)| {
(
Self::transform(k, capture_map),
Self::transform(v, capture_map),
)
})
.map(|v| Self::transform(v, capture_map))
.collect(),
};
}
+3 -4
View File
@@ -222,11 +222,10 @@ impl Dumper {
self.indent -= 1;
}
BoundKind::Record { fields } => {
self.log("Record", node);
BoundKind::Record { layout, values } => {
self.log(&format!("Record (Layout: {} fields)", layout.fields.len()), node);
self.indent += 1;
for (k, v) in fields {
self.visit(k);
for v in values {
self.visit(v);
}
self.indent -= 1;
+2 -3
View File
@@ -82,9 +82,8 @@ impl<'a, T: Clone> LambdaCollector<'a, T> {
}
}
BoundKind::Record { fields } => {
for (k, v) in fields {
self.visit(k);
BoundKind::Record { values, .. } => {
for v in values {
self.visit(v);
}
}
+5 -5
View File
@@ -543,17 +543,17 @@ impl Optimizer {
.collect();
(BoundKind::Tuple { elements }, node.ty.clone())
}
BoundKind::Record { ref fields } => {
let mapped_fields: Vec<_> = fields
BoundKind::Record { ref layout, ref values } => {
let mapped_values: Vec<_> = values
.iter()
.map(|(k, v)| (self.visit_node(k.clone(), sub, path), self.visit_node(v.clone(), sub, path)))
.map(|v| self.visit_node(v.clone(), sub, path))
.collect();
if self.enabled && let Some(folded) = folder.try_fold_record(&mapped_fields, &node) {
if self.enabled && let Some(folded) = folder.try_fold_record(layout, &mapped_values, &node) {
return folded;
}
(BoundKind::Record { fields: mapped_fields }, node.ty.clone())
(BoundKind::Record { layout: layout.clone(), values: mapped_values }, node.ty.clone())
}
BoundKind::Expansion {
ref original_call,
+7 -13
View File
@@ -50,27 +50,21 @@ impl<'a> Folder<'a> {
pub fn try_fold_record(
&self,
fields: &[(AnalyzedNode, AnalyzedNode)],
layout: &std::sync::Arc<RecordLayout>,
values: &[AnalyzedNode],
template: &AnalyzedNode,
) -> Option<AnalyzedNode> {
let mut layout_fields = Vec::with_capacity(fields.len());
let mut values = Vec::with_capacity(fields.len());
let mut constant_values = Vec::with_capacity(values.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;
}
for v_node in values {
if let BoundKind::Constant(val) = &v_node.kind {
constant_values.push(val.clone());
} else {
return None;
}
}
let layout = RecordLayout::get_or_create(layout_fields);
let record_val = Value::Record(layout, Rc::new(values));
let record_val = Value::Record(layout.clone(), Rc::new(constant_values));
Some(self.make_constant_node(record_val, template))
}
@@ -169,17 +169,12 @@ impl SubstitutionMap {
.collect();
(BoundKind::Tuple { elements }, node.ty.clone())
}
BoundKind::Record { fields } => {
let fields = fields
BoundKind::Record { layout, values } => {
let values = values
.into_iter()
.map(|(k, v)| {
(
self.reindex_upvalues(k, mapping),
self.reindex_upvalues(v, mapping),
)
})
.map(|v| self.reindex_upvalues(v, mapping))
.collect();
(BoundKind::Record { fields }, node.ty.clone())
(BoundKind::Record { layout, values }, node.ty.clone())
}
k => (k, node.ty.clone()),
};
+2 -3
View File
@@ -138,9 +138,8 @@ impl UsageInfo {
self.collect(e);
}
}
BoundKind::Record { fields } => {
for (k, v) in fields {
self.collect(k);
BoundKind::Record { values, .. } => {
for v in values {
self.collect(v);
}
}
+4 -4
View File
@@ -131,12 +131,12 @@ impl Specializer {
let elements = elements.into_iter().map(|e| self.visit_node(e)).collect();
(BoundKind::Tuple { elements }, node.ty.clone())
}
BoundKind::Record { fields } => {
let fields = fields
BoundKind::Record { layout, values } => {
let values = values
.into_iter()
.map(|(k, v)| (self.visit_node(k), self.visit_node(v)))
.map(|v| self.visit_node(v))
.collect();
(BoundKind::Record { fields }, node.ty.clone())
(BoundKind::Record { layout, values }, node.ty.clone())
}
BoundKind::Expansion {
original_call,
+7 -9
View File
@@ -115,17 +115,15 @@ impl TCO {
pattern: Box::new(Self::transform(Rc::new((**pattern).clone()), false)),
value: Box::new(Self::transform(Rc::new((**value).clone()), false)),
},
BoundKind::Record { fields } => {
let new_fields = fields
BoundKind::Record { layout, values } => {
let new_values = values
.iter()
.map(|(k, v)| {
(
Self::transform(Rc::new(k.clone()), false),
Self::transform(Rc::new(v.clone()), false),
)
})
.map(|v| Self::transform(Rc::new(v.clone()), false))
.collect();
BoundKind::Record { fields: new_fields }
BoundKind::Record {
layout: layout.clone(),
values: new_values,
}
}
BoundKind::Tuple { elements } => {
let new_elements = elements
+12 -14
View File
@@ -563,25 +563,23 @@ impl TypeChecker {
)
}
BoundKind::Record { fields } => {
let mut typed_fields = Vec::with_capacity(fields.len());
let mut fields_ty = Vec::with_capacity(fields.len());
for (k, v) in fields {
let kt = self.check_node(k, ctx)?;
BoundKind::Record { layout, values } => {
let mut typed_values = Vec::with_capacity(values.len());
let mut fields_ty = Vec::with_capacity(values.len());
for (i, v) in values.into_iter().enumerate() {
let vt = self.check_node(v, ctx)?;
if let BoundKind::Constant(crate::ast::types::Value::Keyword(kw)) = &kt.kind {
fields_ty.push((*kw, vt.ty.clone()));
}
typed_fields.push((kt, vt));
fields_ty.push((layout.fields[i].0, vt.ty.clone()));
typed_values.push(vt);
}
let layout = crate::ast::types::RecordLayout::get_or_create(fields_ty);
let new_layout = crate::ast::types::RecordLayout::get_or_create(fields_ty);
(
BoundKind::Record {
fields: typed_fields,
layout: new_layout.clone(),
values: typed_values,
},
StaticType::Record(layout),
StaticType::Record(new_layout),
)
}
+5 -13
View File
@@ -526,20 +526,12 @@ impl VM {
}
Ok(Value::make_tuple(vals))
}
BoundKind::Record { fields } => {
let mut keys = Vec::with_capacity(fields.len());
let mut values = Vec::with_capacity(fields.len());
for (k, v) in fields {
let key = self.eval_internal(obs, k)?;
let val = self.eval_internal(obs, v)?;
if let Value::Keyword(kw) = key {
keys.push(kw);
values.push(val);
} else {
return Err(format!("Record key must be keyword, got {}", key));
}
BoundKind::Record { layout, values } => {
let mut evaluated_values = Vec::with_capacity(values.len());
for v in values {
evaluated_values.push(self.eval_internal(obs, v)?);
}
Ok(Value::make_record(keys, values))
Ok(Value::Record(layout.clone(), std::rc::Rc::new(evaluated_values)))
}
BoundKind::Expansion { bound_expanded, .. } => self.eval_internal(obs, bound_expanded),
BoundKind::Extension(ext) => Err(format!(