feat: add record spread syntax { ...base, field: val }
Adds spread operator for records, allowing concise record updates:
let p2 = { ...p, x: 5.0 }
Changes across the full pipeline:
- Lexer: new DotDotDot (...) token
- AST: optional spread field on Record variant
- Parser: detect ... at start of record expression
- Typechecker: merge spread record fields with explicit overrides
- Interpreter: evaluate spread, overlay explicit fields
- JS backend: emit native JS spread syntax
- C backend: copy spread into temp, assign overrides
- Formatter, linter, LSP, symbol table: propagate spread
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -339,7 +339,10 @@ fn references_params(expr: &Expr, params: &[&str]) -> bool {
|
||||
Expr::Lambda { body, .. } => references_params(body, params),
|
||||
Expr::Tuple { elements, .. } => elements.iter().any(|e| references_params(e, params)),
|
||||
Expr::List { elements, .. } => elements.iter().any(|e| references_params(e, params)),
|
||||
Expr::Record { fields, .. } => fields.iter().any(|(_, e)| references_params(e, params)),
|
||||
Expr::Record { spread, fields, .. } => {
|
||||
spread.as_ref().is_some_and(|s| references_params(s, params))
|
||||
|| fields.iter().any(|(_, e)| references_params(e, params))
|
||||
}
|
||||
Expr::Match { scrutinee, arms, .. } => {
|
||||
references_params(scrutinee, params)
|
||||
|| arms.iter().any(|a| references_params(&a.body, params))
|
||||
@@ -516,8 +519,9 @@ fn has_recursive_calls(func_name: &str, body: &Expr) -> bool {
|
||||
Expr::Tuple { elements, .. } | Expr::List { elements, .. } => {
|
||||
elements.iter().any(|e| has_recursive_calls(func_name, e))
|
||||
}
|
||||
Expr::Record { fields, .. } => {
|
||||
fields.iter().any(|(_, e)| has_recursive_calls(func_name, e))
|
||||
Expr::Record { spread, fields, .. } => {
|
||||
spread.as_ref().is_some_and(|s| has_recursive_calls(func_name, s))
|
||||
|| fields.iter().any(|(_, e)| has_recursive_calls(func_name, e))
|
||||
}
|
||||
Expr::Field { object, .. } | Expr::TupleIndex { object, .. } => has_recursive_calls(func_name, object),
|
||||
Expr::Let { value, body, .. } => {
|
||||
@@ -672,6 +676,7 @@ fn generate_auto_migration_expr(
|
||||
|
||||
// Build the record expression
|
||||
Some(Expr::Record {
|
||||
spread: None,
|
||||
fields: field_exprs,
|
||||
span,
|
||||
})
|
||||
@@ -1744,7 +1749,11 @@ impl TypeChecker {
|
||||
span,
|
||||
} => self.infer_block(statements, result, *span),
|
||||
|
||||
Expr::Record { fields, span } => self.infer_record(fields, *span),
|
||||
Expr::Record {
|
||||
spread,
|
||||
fields,
|
||||
span,
|
||||
} => self.infer_record(spread.as_deref(), fields, *span),
|
||||
|
||||
Expr::Tuple { elements, span } => self.infer_tuple(elements, *span),
|
||||
|
||||
@@ -2551,12 +2560,46 @@ impl TypeChecker {
|
||||
self.infer_expr(result)
|
||||
}
|
||||
|
||||
fn infer_record(&mut self, fields: &[(Ident, Expr)], _span: Span) -> Type {
|
||||
let field_types: Vec<(String, Type)> = fields
|
||||
fn infer_record(
|
||||
&mut self,
|
||||
spread: Option<&Expr>,
|
||||
fields: &[(Ident, Expr)],
|
||||
span: Span,
|
||||
) -> Type {
|
||||
// Start with spread fields if present
|
||||
let mut field_types: Vec<(String, Type)> = if let Some(spread_expr) = spread {
|
||||
let spread_type = self.infer_expr(spread_expr);
|
||||
match spread_type {
|
||||
Type::Record(spread_fields) => spread_fields,
|
||||
_ => {
|
||||
self.errors.push(TypeError {
|
||||
message: format!(
|
||||
"Spread expression must be a record type, got {}",
|
||||
spread_type
|
||||
),
|
||||
span,
|
||||
});
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
// Apply explicit field overrides
|
||||
let explicit_types: Vec<(String, Type)> = fields
|
||||
.iter()
|
||||
.map(|(name, expr)| (name.name.clone(), self.infer_expr(expr)))
|
||||
.collect();
|
||||
|
||||
for (name, typ) in explicit_types {
|
||||
if let Some(existing) = field_types.iter_mut().find(|(n, _)| n == &name) {
|
||||
existing.1 = typ;
|
||||
} else {
|
||||
field_types.push((name, typ));
|
||||
}
|
||||
}
|
||||
|
||||
Type::Record(field_types)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user