mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 04:08:19 +00:00
Remove old record builder syntax
This commit is contained in:
parent
9678046d91
commit
2da08be8ef
20 changed files with 57 additions and 2280 deletions
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue