Remove old record builder syntax

This commit is contained in:
Sam Mohr 2024-09-21 04:44:44 -07:00
parent 9678046d91
commit 2da08be8ef
No known key found for this signature in database
GPG key ID: EA41D161A3C1BC99
20 changed files with 57 additions and 2280 deletions

View file

@ -11,8 +11,8 @@ use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::ModuleName;
use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::{
AssignedField, Collection, Defs, ModuleImportParams, OldRecordBuilderField, Pattern,
StrLiteral, StrSegment, TypeAnnotation, ValueDef, WhenBranch,
AssignedField, Collection, Defs, ModuleImportParams, Pattern, StrLiteral, StrSegment,
TypeAnnotation, ValueDef, WhenBranch,
};
use roc_problem::can::Problem;
use roc_region::all::{Loc, Region};
@ -321,8 +321,6 @@ pub fn desugar_expr<'a>(
| MalformedClosure
| MalformedSuffixed(..)
| PrecedenceConflict { .. }
| MultipleOldRecordBuilders(_)
| UnappliedOldRecordBuilder(_)
| EmptyRecordBuilder(_)
| SingleFieldRecordBuilder(_)
| OptionalFieldInRecordBuilder { .. }
@ -555,10 +553,6 @@ pub fn desugar_expr<'a>(
}
}
}
OldRecordBuilder(_) => env.arena.alloc(Loc {
value: UnappliedOldRecordBuilder(loc_expr),
region: loc_expr.region,
}),
RecordBuilder { mapper, fields } => {
// NOTE the `mapper` is always a `Var { .. }`, we only desugar it to get rid of
// any spaces before/after
@ -853,25 +847,11 @@ pub fn desugar_expr<'a>(
}
Apply(loc_fn, loc_args, called_via) => {
let mut desugared_args = Vec::with_capacity_in(loc_args.len(), env.arena);
let mut builder_apply_exprs = None;
for loc_arg in loc_args.iter() {
let mut current = loc_arg.value;
let arg = loop {
match current {
OldRecordBuilder(fields) => {
if builder_apply_exprs.is_some() {
return env.arena.alloc(Loc {
value: MultipleOldRecordBuilders(loc_expr),
region: loc_expr.region,
});
}
let builder_arg = old_record_builder_arg(env, loc_arg.region, fields);
builder_apply_exprs = Some(builder_arg.apply_exprs);
break builder_arg.closure;
}
SpaceBefore(expr, _) | SpaceAfter(expr, _) => {
current = *expr;
}
@ -884,33 +864,14 @@ pub fn desugar_expr<'a>(
let desugared_args = desugared_args.into_bump_slice();
let mut apply: &Loc<Expr> = env.arena.alloc(Loc {
env.arena.alloc(Loc {
value: Apply(
desugar_expr(env, scope, loc_fn),
desugared_args,
*called_via,
),
region: loc_expr.region,
});
match builder_apply_exprs {
None => {}
Some(apply_exprs) => {
for expr in apply_exprs {
let desugared_expr = desugar_expr(env, scope, expr);
let args = std::slice::from_ref(env.arena.alloc(apply));
apply = env.arena.alloc(Loc {
value: Apply(desugared_expr, args, CalledVia::OldRecordBuilder),
region: loc_expr.region,
});
}
}
}
apply
})
}
When(loc_cond_expr, branches) => {
let loc_desugared_cond = &*env.arena.alloc(desugar_expr(env, scope, loc_cond_expr));
@ -1383,97 +1344,6 @@ fn desugar_dbg_stmt<'a>(
))
}
struct OldRecordBuilderArg<'a> {
closure: &'a Loc<Expr<'a>>,
apply_exprs: Vec<'a, &'a Loc<Expr<'a>>>,
}
fn old_record_builder_arg<'a>(
env: &mut Env<'a>,
region: Region,
fields: Collection<'a, Loc<OldRecordBuilderField<'a>>>,
) -> OldRecordBuilderArg<'a> {
let mut record_fields = Vec::with_capacity_in(fields.len(), env.arena);
let mut apply_exprs = Vec::with_capacity_in(fields.len(), env.arena);
let mut apply_field_names = Vec::with_capacity_in(fields.len(), env.arena);
// Build the record that the closure will return and gather apply expressions
for field in fields.iter() {
let mut current = field.value;
let new_field = loop {
match current {
OldRecordBuilderField::Value(label, spaces, expr) => {
break AssignedField::RequiredValue(label, spaces, expr)
}
OldRecordBuilderField::ApplyValue(label, _, _, expr) => {
apply_field_names.push(label);
apply_exprs.push(expr);
let var = env.arena.alloc(Loc {
region: label.region,
value: Expr::Var {
module_name: "",
ident: env.arena.alloc("#".to_owned() + label.value),
},
});
break AssignedField::RequiredValue(label, &[], var);
}
OldRecordBuilderField::LabelOnly(label) => break AssignedField::LabelOnly(label),
OldRecordBuilderField::SpaceBefore(sub_field, _) => {
current = *sub_field;
}
OldRecordBuilderField::SpaceAfter(sub_field, _) => {
current = *sub_field;
}
OldRecordBuilderField::Malformed(malformed) => {
break AssignedField::Malformed(malformed)
}
}
};
record_fields.push(Loc {
value: new_field,
region: field.region,
});
}
let record_fields = fields.replace_items(record_fields.into_bump_slice());
let mut body = env.arena.alloc(Loc {
value: Record(record_fields),
region,
});
// Construct the builder's closure
//
// { x: #x, y: #y, z: 3 }
// \#y -> { x: #x, y: #y, z: 3 }
// \#x -> \#y -> { x: #x, y: #y, z: 3 }
for label in apply_field_names.iter().rev() {
let name = env.arena.alloc("#".to_owned() + label.value);
let ident = roc_parse::ast::Pattern::Identifier { ident: name };
let arg_pattern = env.arena.alloc(Loc {
value: ident,
region: label.region,
});
body = env.arena.alloc(Loc {
value: Closure(std::slice::from_ref(arg_pattern), body),
region,
});
}
OldRecordBuilderArg {
closure: body,
apply_exprs,
}
}
// TODO move this desugaring to canonicalization, so we can use Symbols instead of strings
#[inline(always)]
fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {

View file

@ -1013,11 +1013,8 @@ pub fn canonicalize_expr<'a>(
can_defs_with_return(env, var_store, inner_scope, env.arena.alloc(defs), loc_ret)
})
}
ast::Expr::OldRecordBuilder(_) => {
internal_error!("Old record builder should have been desugared by now")
}
ast::Expr::RecordBuilder { .. } => {
internal_error!("New record builder should have been desugared by now")
internal_error!("Record builder should have been desugared by now")
}
ast::Expr::Backpassing(_, _, _) => {
internal_error!("Backpassing should have been desugared by now")
@ -1356,22 +1353,6 @@ pub fn canonicalize_expr<'a>(
use roc_problem::can::RuntimeError::*;
(RuntimeError(MalformedSuffixed(region)), Output::default())
}
ast::Expr::MultipleOldRecordBuilders(sub_expr) => {
use roc_problem::can::RuntimeError::*;
let problem = MultipleOldRecordBuilders(sub_expr.region);
env.problem(Problem::RuntimeError(problem.clone()));
(RuntimeError(problem), Output::default())
}
ast::Expr::UnappliedOldRecordBuilder(sub_expr) => {
use roc_problem::can::RuntimeError::*;
let problem = UnappliedOldRecordBuilder(sub_expr.region);
env.problem(Problem::RuntimeError(problem.clone()));
(RuntimeError(problem), Output::default())
}
ast::Expr::EmptyRecordBuilder(sub_expr) => {
use roc_problem::can::RuntimeError::*;
@ -2552,8 +2533,6 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
.iter()
.all(|loc_field| is_valid_interpolation(&loc_field.value)),
ast::Expr::MalformedSuffixed(loc_expr)
| ast::Expr::MultipleOldRecordBuilders(loc_expr)
| ast::Expr::UnappliedOldRecordBuilder(loc_expr)
| ast::Expr::EmptyRecordBuilder(loc_expr)
| ast::Expr::SingleFieldRecordBuilder(loc_expr)
| ast::Expr::OptionalFieldInRecordBuilder(_, loc_expr)
@ -2603,27 +2582,6 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
| ast::AssignedField::SpaceAfter(_, _) => false,
})
}
ast::Expr::OldRecordBuilder(fields) => {
fields.iter().all(|loc_field| match loc_field.value {
ast::OldRecordBuilderField::Value(_label, comments, loc_expr) => {
comments.is_empty() && is_valid_interpolation(&loc_expr.value)
}
ast::OldRecordBuilderField::ApplyValue(
_label,
comments_before,
comments_after,
loc_expr,
) => {
comments_before.is_empty()
&& comments_after.is_empty()
&& is_valid_interpolation(&loc_expr.value)
}
ast::OldRecordBuilderField::Malformed(_)
| ast::OldRecordBuilderField::LabelOnly(_) => true,
ast::OldRecordBuilderField::SpaceBefore(_, _)
| ast::OldRecordBuilderField::SpaceAfter(_, _) => false,
})
}
ast::Expr::RecordBuilder { mapper, fields } => {
is_valid_interpolation(&mapper.value)
&& fields.iter().all(|loc_field| match loc_field.value {

View file

@ -19,7 +19,6 @@ mod test_can {
use roc_can::expr::{ClosureData, IntValue, Recursive};
use roc_can::pattern::Pattern;
use roc_module::called_via::CalledVia;
use roc_module::symbol::Symbol;
use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::{Loc, Position, Region};
use roc_types::subs::Variable;
@ -653,101 +652,6 @@ mod test_can {
}
// RECORD BUILDERS
#[test]
fn old_record_builder_desugar() {
let src = indoc!(
r#"
succeed = \_ -> crash "succeed"
apply = \_ -> crash "get"
d = 3
succeed {
a: 1,
b: <- apply "b",
c: <- apply "c",
d
}
"#
);
let arena = Bump::new();
let out = can_expr_with(&arena, test_home(), src);
assert_eq!(out.problems.len(), 0);
// Assert that we desugar to:
//
// (apply "c") ((apply "b") (succeed \b -> \c -> { a: 1, b, c, d }))
// (apply "c") ..
let (apply_c, c_to_b) = simplify_curried_call(&out.loc_expr.value);
assert_apply_call(apply_c, "c", &out.interns);
// (apply "b") ..
let (apply_b, b_to_succeed) = simplify_curried_call(c_to_b);
assert_apply_call(apply_b, "b", &out.interns);
// (succeed ..)
let (succeed, b_closure) = simplify_curried_call(b_to_succeed);
match succeed {
Var(sym, _) => assert_eq!(sym.as_str(&out.interns), "succeed"),
_ => panic!("Not calling succeed: {:?}", succeed),
}
// \b -> ..
let (b_sym, c_closure) = simplify_builder_closure(b_closure);
// \c -> ..
let (c_sym, c_body) = simplify_builder_closure(c_closure);
// { a: 1, b, c, d }
match c_body {
Record { fields, .. } => {
match get_field_expr(fields, "a") {
Num(_, num_str, _, _) => {
assert_eq!(num_str.to_string(), "1");
}
expr => panic!("a is not a Num: {:?}", expr),
}
assert_eq!(get_field_var_sym(fields, "b"), b_sym);
assert_eq!(get_field_var_sym(fields, "c"), c_sym);
assert_eq!(get_field_var_sym(fields, "d").as_str(&out.interns), "d");
}
_ => panic!("Closure body wasn't a Record: {:?}", c_body),
}
}
fn simplify_curried_call(expr: &Expr) -> (&Expr, &Expr) {
match expr {
LetNonRec(_, loc_expr) => simplify_curried_call(&loc_expr.value),
Call(fun, args, _) => (&fun.1.value, &args[0].1.value),
_ => panic!("Final Expr is not a Call: {:?}", expr),
}
}
fn assert_apply_call(expr: &Expr, expected: &str, interns: &roc_module::symbol::Interns) {
match simplify_curried_call(expr) {
(Var(sym, _), Str(val)) => {
assert_eq!(sym.as_str(interns), "apply");
assert_eq!(val.to_string(), expected);
}
call => panic!("Not a valid (get {}) call: {:?}", expected, call),
};
}
fn simplify_builder_closure(expr: &Expr) -> (Symbol, &Expr) {
use roc_can::pattern::Pattern::*;
match expr {
Closure(closure) => match &closure.arguments[0].2.value {
Identifier(sym) => (*sym, &closure.loc_body.value),
pattern => panic!("Not an identifier pattern: {:?}", pattern),
},
_ => panic!("Not a closure: {:?}", expr),
}
}
fn get_field_expr<'a>(
fields: &'a roc_collections::SendMap<roc_module::ident::Lowercase, roc_can::expr::Field>,
@ -769,97 +673,7 @@ mod test_can {
}
#[test]
fn old_record_builder_field_names_do_not_shadow() {
let src = indoc!(
r#"
succeed = \_ -> crash "succeed"
parse = \_ -> crash "parse"
number = "42"
succeed {
number: <- parse number,
raw: number,
}
"#
);
let arena = Bump::new();
let out = can_expr_with(&arena, test_home(), src);
assert_eq!(out.problems.len(), 0);
let (_, number_to_succeed) = simplify_curried_call(&out.loc_expr.value);
let (_, number_closure) = simplify_curried_call(number_to_succeed);
let (apply_number_sym, record) = simplify_builder_closure(number_closure);
match record {
Record { fields, .. } => {
assert_eq!(get_field_var_sym(fields, "number"), apply_number_sym);
match get_field_expr(fields, "raw") {
Var(number_sym, _) => {
assert_ne!(number_sym.ident_id(), apply_number_sym.ident_id());
assert_eq!(number_sym.as_str(&out.interns), "number")
}
expr => panic!("a is not a Num: {:?}", expr),
}
}
_ => panic!("Closure body wasn't a Record: {:?}", record),
}
}
#[test]
fn multiple_old_record_builders_error() {
let src = indoc!(
r#"
succeed
{ a: <- apply "a" }
{ b: <- apply "b" }
"#
);
let arena = Bump::new();
let CanExprOut {
problems, loc_expr, ..
} = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 1);
assert!(problems.iter().all(|problem| matches!(
problem,
Problem::RuntimeError(roc_problem::can::RuntimeError::MultipleOldRecordBuilders { .. })
)));
assert!(matches!(
loc_expr.value,
Expr::RuntimeError(roc_problem::can::RuntimeError::MultipleOldRecordBuilders { .. })
));
}
#[test]
fn hanging_old_record_builder() {
let src = indoc!(
r#"
{ a: <- apply "a" }
"#
);
let arena = Bump::new();
let CanExprOut {
problems, loc_expr, ..
} = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 1);
assert!(problems.iter().all(|problem| matches!(
problem,
Problem::RuntimeError(roc_problem::can::RuntimeError::UnappliedOldRecordBuilder { .. })
)));
assert!(matches!(
loc_expr.value,
Expr::RuntimeError(roc_problem::can::RuntimeError::UnappliedOldRecordBuilder { .. })
));
}
#[test]
fn new_record_builder_desugar() {
fn record_builder_desugar() {
let src = indoc!(
r#"
map2 = \a, b, combine -> combine a b