mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 12:18:19 +00:00
Merge pull request #6883 from smores56/new-builder-syntax
Implement new builder syntax alongside old one
This commit is contained in:
commit
b9a17f4a49
20 changed files with 1226 additions and 214 deletions
|
@ -9,7 +9,7 @@ use roc_module::called_via::{BinOp, CalledVia};
|
|||
use roc_module::ident::ModuleName;
|
||||
use roc_parse::ast::Expr::{self, *};
|
||||
use roc_parse::ast::{
|
||||
AssignedField, Collection, ModuleImportParams, Pattern, RecordBuilderField, StrLiteral,
|
||||
AssignedField, Collection, ModuleImportParams, OldRecordBuilderField, Pattern, StrLiteral,
|
||||
StrSegment, ValueDef, WhenBranch,
|
||||
};
|
||||
use roc_region::all::{LineInfo, Loc, Region};
|
||||
|
@ -300,8 +300,11 @@ pub fn desugar_expr<'a>(
|
|||
| MalformedClosure
|
||||
| MalformedSuffixed(..)
|
||||
| PrecedenceConflict { .. }
|
||||
| MultipleRecordBuilders { .. }
|
||||
| UnappliedRecordBuilder { .. }
|
||||
| MultipleOldRecordBuilders(_)
|
||||
| UnappliedOldRecordBuilder(_)
|
||||
| EmptyRecordBuilder(_)
|
||||
| SingleFieldRecordBuilder(_)
|
||||
| OptionalFieldInRecordBuilder { .. }
|
||||
| Tag(_)
|
||||
| OpaqueRef(_)
|
||||
| Crash => loc_expr,
|
||||
|
@ -485,10 +488,236 @@ pub fn desugar_expr<'a>(
|
|||
}
|
||||
}
|
||||
}
|
||||
RecordBuilder(_) => arena.alloc(Loc {
|
||||
value: UnappliedRecordBuilder(loc_expr),
|
||||
OldRecordBuilder(_) => 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
|
||||
let new_mapper = desugar_expr(arena, mapper, src, line_info, module_path);
|
||||
|
||||
if fields.is_empty() {
|
||||
return arena.alloc(Loc {
|
||||
value: EmptyRecordBuilder(loc_expr),
|
||||
region: loc_expr.region,
|
||||
});
|
||||
} else if fields.len() == 1 {
|
||||
return arena.alloc(Loc {
|
||||
value: SingleFieldRecordBuilder(loc_expr),
|
||||
region: loc_expr.region,
|
||||
});
|
||||
}
|
||||
|
||||
let mut field_names = Vec::with_capacity_in(fields.len(), arena);
|
||||
let mut field_vals = Vec::with_capacity_in(fields.len(), arena);
|
||||
|
||||
for field in fields.items {
|
||||
match desugar_field(arena, &field.value, src, line_info, module_path) {
|
||||
AssignedField::RequiredValue(loc_name, _, loc_val) => {
|
||||
field_names.push(loc_name);
|
||||
field_vals.push(loc_val);
|
||||
}
|
||||
AssignedField::LabelOnly(loc_name) => {
|
||||
field_names.push(loc_name);
|
||||
field_vals.push(arena.alloc(Loc {
|
||||
region: loc_name.region,
|
||||
value: Expr::Var {
|
||||
module_name: "",
|
||||
ident: loc_name.value,
|
||||
},
|
||||
}));
|
||||
}
|
||||
AssignedField::OptionalValue(loc_name, _, loc_val) => {
|
||||
return arena.alloc(Loc {
|
||||
region: loc_expr.region,
|
||||
value: OptionalFieldInRecordBuilder(arena.alloc(loc_name), loc_val),
|
||||
});
|
||||
}
|
||||
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => {
|
||||
unreachable!("Should have been desugared in `desugar_field`")
|
||||
}
|
||||
AssignedField::Malformed(_name) => {}
|
||||
}
|
||||
}
|
||||
|
||||
let closure_arg_from_field = |field: Loc<&'a str>| Loc {
|
||||
region: field.region,
|
||||
value: Pattern::Identifier {
|
||||
ident: arena.alloc_str(&format!("#{}", field.value)),
|
||||
},
|
||||
};
|
||||
|
||||
let combiner_closure_in_region = |region| {
|
||||
let closure_body = Tuple(Collection::with_items(
|
||||
Vec::from_iter_in(
|
||||
[
|
||||
&*arena.alloc(Loc::at(
|
||||
region,
|
||||
Expr::Var {
|
||||
module_name: "",
|
||||
ident: "#record_builder_closure_arg_a",
|
||||
},
|
||||
)),
|
||||
&*arena.alloc(Loc::at(
|
||||
region,
|
||||
Expr::Var {
|
||||
module_name: "",
|
||||
ident: "#record_builder_closure_arg_b",
|
||||
},
|
||||
)),
|
||||
],
|
||||
arena,
|
||||
)
|
||||
.into_bump_slice(),
|
||||
));
|
||||
|
||||
arena.alloc(Loc::at(
|
||||
region,
|
||||
Closure(
|
||||
arena.alloc_slice_copy(&[
|
||||
Loc::at(
|
||||
region,
|
||||
Pattern::Identifier {
|
||||
ident: "#record_builder_closure_arg_a",
|
||||
},
|
||||
),
|
||||
Loc::at(
|
||||
region,
|
||||
Pattern::Identifier {
|
||||
ident: "#record_builder_closure_arg_b",
|
||||
},
|
||||
),
|
||||
]),
|
||||
arena.alloc(Loc::at(region, closure_body)),
|
||||
),
|
||||
))
|
||||
};
|
||||
|
||||
let closure_args = {
|
||||
if field_names.len() == 2 {
|
||||
arena.alloc_slice_copy(&[
|
||||
closure_arg_from_field(field_names[0]),
|
||||
closure_arg_from_field(field_names[1]),
|
||||
])
|
||||
} else {
|
||||
let second_to_last_arg =
|
||||
closure_arg_from_field(field_names[field_names.len() - 2]);
|
||||
let last_arg = closure_arg_from_field(field_names[field_names.len() - 1]);
|
||||
|
||||
let mut second_arg = Pattern::Tuple(Collection::with_items(
|
||||
arena.alloc_slice_copy(&[second_to_last_arg, last_arg]),
|
||||
));
|
||||
let mut second_arg_region =
|
||||
Region::span_across(&second_to_last_arg.region, &last_arg.region);
|
||||
|
||||
for index in (1..(field_names.len() - 2)).rev() {
|
||||
second_arg =
|
||||
Pattern::Tuple(Collection::with_items(arena.alloc_slice_copy(&[
|
||||
closure_arg_from_field(field_names[index]),
|
||||
Loc::at(second_arg_region, second_arg),
|
||||
])));
|
||||
second_arg_region =
|
||||
Region::span_across(&field_names[index].region, &second_arg_region);
|
||||
}
|
||||
|
||||
arena.alloc_slice_copy(&[
|
||||
closure_arg_from_field(field_names[0]),
|
||||
Loc::at(second_arg_region, second_arg),
|
||||
])
|
||||
}
|
||||
};
|
||||
|
||||
let record_val = Record(Collection::with_items(
|
||||
Vec::from_iter_in(
|
||||
field_names.iter().map(|field_name| {
|
||||
Loc::at(
|
||||
field_name.region,
|
||||
AssignedField::RequiredValue(
|
||||
Loc::at(field_name.region, field_name.value),
|
||||
&[],
|
||||
arena.alloc(Loc::at(
|
||||
field_name.region,
|
||||
Expr::Var {
|
||||
module_name: "",
|
||||
ident: arena.alloc_str(&format!("#{}", field_name.value)),
|
||||
},
|
||||
)),
|
||||
),
|
||||
)
|
||||
}),
|
||||
arena,
|
||||
)
|
||||
.into_bump_slice(),
|
||||
));
|
||||
|
||||
let record_combiner_closure = arena.alloc(Loc {
|
||||
region: loc_expr.region,
|
||||
value: Closure(
|
||||
closure_args,
|
||||
arena.alloc(Loc::at(loc_expr.region, record_val)),
|
||||
),
|
||||
});
|
||||
|
||||
if field_names.len() == 2 {
|
||||
return arena.alloc(Loc {
|
||||
region: loc_expr.region,
|
||||
value: Apply(
|
||||
new_mapper,
|
||||
arena.alloc_slice_copy(&[
|
||||
field_vals[0],
|
||||
field_vals[1],
|
||||
record_combiner_closure,
|
||||
]),
|
||||
CalledVia::RecordBuilder,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
let mut inner_combined = arena.alloc(Loc {
|
||||
region: Region::span_across(
|
||||
&field_vals[field_names.len() - 2].region,
|
||||
&field_vals[field_names.len() - 1].region,
|
||||
),
|
||||
value: Apply(
|
||||
new_mapper,
|
||||
arena.alloc_slice_copy(&[
|
||||
field_vals[field_names.len() - 2],
|
||||
field_vals[field_names.len() - 1],
|
||||
combiner_closure_in_region(loc_expr.region),
|
||||
]),
|
||||
CalledVia::RecordBuilder,
|
||||
),
|
||||
});
|
||||
|
||||
for index in (1..(field_names.len() - 2)).rev() {
|
||||
inner_combined = arena.alloc(Loc {
|
||||
region: Region::span_across(&field_vals[index].region, &inner_combined.region),
|
||||
value: Apply(
|
||||
new_mapper,
|
||||
arena.alloc_slice_copy(&[
|
||||
field_vals[index],
|
||||
inner_combined,
|
||||
combiner_closure_in_region(loc_expr.region),
|
||||
]),
|
||||
CalledVia::RecordBuilder,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
arena.alloc(Loc {
|
||||
region: loc_expr.region,
|
||||
value: Apply(
|
||||
new_mapper,
|
||||
arena.alloc_slice_copy(&[
|
||||
field_vals[0],
|
||||
inner_combined,
|
||||
record_combiner_closure,
|
||||
]),
|
||||
CalledVia::RecordBuilder,
|
||||
),
|
||||
})
|
||||
}
|
||||
BinOps(lefts, right) => desugar_bin_ops(
|
||||
arena,
|
||||
loc_expr.region,
|
||||
|
@ -513,15 +742,15 @@ pub fn desugar_expr<'a>(
|
|||
let mut current = loc_arg.value;
|
||||
let arg = loop {
|
||||
match current {
|
||||
RecordBuilder(fields) => {
|
||||
OldRecordBuilder(fields) => {
|
||||
if builder_apply_exprs.is_some() {
|
||||
return arena.alloc(Loc {
|
||||
value: MultipleRecordBuilders(loc_expr),
|
||||
value: MultipleOldRecordBuilders(loc_expr),
|
||||
region: loc_expr.region,
|
||||
});
|
||||
}
|
||||
|
||||
let builder_arg = record_builder_arg(arena, loc_arg.region, fields);
|
||||
let builder_arg = old_record_builder_arg(arena, loc_arg.region, fields);
|
||||
builder_apply_exprs = Some(builder_arg.apply_exprs);
|
||||
|
||||
break builder_arg.closure;
|
||||
|
@ -557,7 +786,7 @@ pub fn desugar_expr<'a>(
|
|||
let args = std::slice::from_ref(arena.alloc(apply));
|
||||
|
||||
apply = arena.alloc(Loc {
|
||||
value: Apply(desugared_expr, args, CalledVia::RecordBuilder),
|
||||
value: Apply(desugared_expr, args, CalledVia::OldRecordBuilder),
|
||||
region: loc_expr.region,
|
||||
});
|
||||
}
|
||||
|
@ -1007,16 +1236,16 @@ fn desugar_pattern<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
struct RecordBuilderArg<'a> {
|
||||
struct OldRecordBuilderArg<'a> {
|
||||
closure: &'a Loc<Expr<'a>>,
|
||||
apply_exprs: Vec<'a, &'a Loc<Expr<'a>>>,
|
||||
}
|
||||
|
||||
fn record_builder_arg<'a>(
|
||||
fn old_record_builder_arg<'a>(
|
||||
arena: &'a Bump,
|
||||
region: Region,
|
||||
fields: Collection<'a, Loc<RecordBuilderField<'a>>>,
|
||||
) -> RecordBuilderArg<'a> {
|
||||
fields: Collection<'a, Loc<OldRecordBuilderField<'a>>>,
|
||||
) -> OldRecordBuilderArg<'a> {
|
||||
let mut record_fields = Vec::with_capacity_in(fields.len(), arena);
|
||||
let mut apply_exprs = Vec::with_capacity_in(fields.len(), arena);
|
||||
let mut apply_field_names = Vec::with_capacity_in(fields.len(), arena);
|
||||
|
@ -1028,10 +1257,10 @@ fn record_builder_arg<'a>(
|
|||
|
||||
let new_field = loop {
|
||||
match current {
|
||||
RecordBuilderField::Value(label, spaces, expr) => {
|
||||
OldRecordBuilderField::Value(label, spaces, expr) => {
|
||||
break AssignedField::RequiredValue(label, spaces, expr)
|
||||
}
|
||||
RecordBuilderField::ApplyValue(label, _, _, expr) => {
|
||||
OldRecordBuilderField::ApplyValue(label, _, _, expr) => {
|
||||
apply_field_names.push(label);
|
||||
apply_exprs.push(expr);
|
||||
|
||||
|
@ -1045,14 +1274,14 @@ fn record_builder_arg<'a>(
|
|||
|
||||
break AssignedField::RequiredValue(label, &[], var);
|
||||
}
|
||||
RecordBuilderField::LabelOnly(label) => break AssignedField::LabelOnly(label),
|
||||
RecordBuilderField::SpaceBefore(sub_field, _) => {
|
||||
OldRecordBuilderField::LabelOnly(label) => break AssignedField::LabelOnly(label),
|
||||
OldRecordBuilderField::SpaceBefore(sub_field, _) => {
|
||||
current = *sub_field;
|
||||
}
|
||||
RecordBuilderField::SpaceAfter(sub_field, _) => {
|
||||
OldRecordBuilderField::SpaceAfter(sub_field, _) => {
|
||||
current = *sub_field;
|
||||
}
|
||||
RecordBuilderField::Malformed(malformed) => {
|
||||
OldRecordBuilderField::Malformed(malformed) => {
|
||||
break AssignedField::Malformed(malformed)
|
||||
}
|
||||
}
|
||||
|
@ -1092,7 +1321,7 @@ fn record_builder_arg<'a>(
|
|||
});
|
||||
}
|
||||
|
||||
RecordBuilderArg {
|
||||
OldRecordBuilderArg {
|
||||
closure: body,
|
||||
apply_exprs,
|
||||
}
|
||||
|
|
|
@ -1021,8 +1021,11 @@ pub fn canonicalize_expr<'a>(
|
|||
can_defs_with_return(env, var_store, inner_scope, env.arena.alloc(defs), loc_ret)
|
||||
})
|
||||
}
|
||||
ast::Expr::RecordBuilder(_) => {
|
||||
internal_error!("RecordBuilder should have been desugared by now")
|
||||
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")
|
||||
}
|
||||
ast::Expr::Backpassing(_, _, _) => {
|
||||
internal_error!("Backpassing should have been desugared by now")
|
||||
|
@ -1340,18 +1343,43 @@ pub fn canonicalize_expr<'a>(
|
|||
use roc_problem::can::RuntimeError::*;
|
||||
(RuntimeError(MalformedSuffixed(region)), Output::default())
|
||||
}
|
||||
ast::Expr::MultipleRecordBuilders(sub_expr) => {
|
||||
ast::Expr::MultipleOldRecordBuilders(sub_expr) => {
|
||||
use roc_problem::can::RuntimeError::*;
|
||||
|
||||
let problem = MultipleRecordBuilders(sub_expr.region);
|
||||
let problem = MultipleOldRecordBuilders(sub_expr.region);
|
||||
env.problem(Problem::RuntimeError(problem.clone()));
|
||||
|
||||
(RuntimeError(problem), Output::default())
|
||||
}
|
||||
ast::Expr::UnappliedRecordBuilder(sub_expr) => {
|
||||
ast::Expr::UnappliedOldRecordBuilder(sub_expr) => {
|
||||
use roc_problem::can::RuntimeError::*;
|
||||
|
||||
let problem = UnappliedRecordBuilder(sub_expr.region);
|
||||
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::*;
|
||||
|
||||
let problem = EmptyRecordBuilder(sub_expr.region);
|
||||
env.problem(Problem::RuntimeError(problem.clone()));
|
||||
|
||||
(RuntimeError(problem), Output::default())
|
||||
}
|
||||
ast::Expr::SingleFieldRecordBuilder(sub_expr) => {
|
||||
use roc_problem::can::RuntimeError::*;
|
||||
|
||||
let problem = SingleFieldRecordBuilder(sub_expr.region);
|
||||
env.problem(Problem::RuntimeError(problem.clone()));
|
||||
|
||||
(RuntimeError(problem), Output::default())
|
||||
}
|
||||
ast::Expr::OptionalFieldInRecordBuilder(loc_name, loc_value) => {
|
||||
use roc_problem::can::RuntimeError::*;
|
||||
|
||||
let sub_region = Region::span_across(&loc_name.region, &loc_value.region);
|
||||
let problem = OptionalFieldInRecordBuilder {record: region, field: sub_region };
|
||||
env.problem(Problem::RuntimeError(problem.clone()));
|
||||
|
||||
(RuntimeError(problem), Output::default())
|
||||
|
@ -2414,9 +2442,12 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
|
|||
ast::Expr::Tuple(fields) => fields
|
||||
.iter()
|
||||
.all(|loc_field| is_valid_interpolation(&loc_field.value)),
|
||||
ast::Expr::MultipleRecordBuilders(loc_expr)
|
||||
| ast::Expr::MalformedSuffixed(loc_expr)
|
||||
| ast::Expr::UnappliedRecordBuilder(loc_expr)
|
||||
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)
|
||||
| ast::Expr::PrecedenceConflict(PrecedenceConflict { expr: loc_expr, .. })
|
||||
| ast::Expr::UnaryOp(loc_expr, _)
|
||||
| ast::Expr::Closure(_, loc_expr) => is_valid_interpolation(&loc_expr.value),
|
||||
|
@ -2458,24 +2489,39 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
|
|||
| ast::AssignedField::SpaceAfter(_, _) => false,
|
||||
})
|
||||
}
|
||||
ast::Expr::RecordBuilder(fields) => fields.iter().all(|loc_field| match loc_field.value {
|
||||
ast::RecordBuilderField::Value(_label, comments, loc_expr) => {
|
||||
comments.is_empty() && is_valid_interpolation(&loc_expr.value)
|
||||
}
|
||||
ast::RecordBuilderField::ApplyValue(
|
||||
_label,
|
||||
comments_before,
|
||||
comments_after,
|
||||
loc_expr,
|
||||
) => {
|
||||
comments_before.is_empty()
|
||||
&& comments_after.is_empty()
|
||||
&& is_valid_interpolation(&loc_expr.value)
|
||||
}
|
||||
ast::RecordBuilderField::Malformed(_) | ast::RecordBuilderField::LabelOnly(_) => true,
|
||||
ast::RecordBuilderField::SpaceBefore(_, _)
|
||||
| ast::RecordBuilderField::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 {
|
||||
ast::AssignedField::RequiredValue(_label, loc_comments, loc_val)
|
||||
| ast::AssignedField::OptionalValue(_label, loc_comments, loc_val) => {
|
||||
loc_comments.is_empty() && is_valid_interpolation(&loc_val.value)
|
||||
}
|
||||
ast::AssignedField::Malformed(_) | ast::AssignedField::LabelOnly(_) => true,
|
||||
ast::AssignedField::SpaceBefore(_, _)
|
||||
| ast::AssignedField::SpaceAfter(_, _) => false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,9 +17,12 @@ mod test_can {
|
|||
use core::panic;
|
||||
use roc_can::expr::Expr::{self, *};
|
||||
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::{Position, Region};
|
||||
use roc_region::all::{Loc, Position, Region};
|
||||
use roc_types::subs::Variable;
|
||||
use std::{f64, i64};
|
||||
|
||||
fn assert_can_runtime_error(input: &str, expected: RuntimeError) {
|
||||
|
@ -651,7 +654,7 @@ mod test_can {
|
|||
|
||||
// RECORD BUILDERS
|
||||
#[test]
|
||||
fn record_builder_desugar() {
|
||||
fn old_record_builder_desugar() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
succeed = \_ -> crash "succeed"
|
||||
|
@ -766,7 +769,7 @@ mod test_can {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn record_builder_field_names_do_not_shadow() {
|
||||
fn old_record_builder_field_names_do_not_shadow() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
succeed = \_ -> crash "succeed"
|
||||
|
@ -806,7 +809,7 @@ mod test_can {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_record_builders_error() {
|
||||
fn multiple_old_record_builders_error() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
succeed
|
||||
|
@ -822,17 +825,17 @@ mod test_can {
|
|||
assert_eq!(problems.len(), 1);
|
||||
assert!(problems.iter().all(|problem| matches!(
|
||||
problem,
|
||||
Problem::RuntimeError(roc_problem::can::RuntimeError::MultipleRecordBuilders { .. })
|
||||
Problem::RuntimeError(roc_problem::can::RuntimeError::MultipleOldRecordBuilders { .. })
|
||||
)));
|
||||
|
||||
assert!(matches!(
|
||||
loc_expr.value,
|
||||
Expr::RuntimeError(roc_problem::can::RuntimeError::MultipleRecordBuilders { .. })
|
||||
Expr::RuntimeError(roc_problem::can::RuntimeError::MultipleOldRecordBuilders { .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hanging_record_builder() {
|
||||
fn hanging_old_record_builder() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
{ a: <- apply "a" }
|
||||
|
@ -846,15 +849,189 @@ mod test_can {
|
|||
assert_eq!(problems.len(), 1);
|
||||
assert!(problems.iter().all(|problem| matches!(
|
||||
problem,
|
||||
Problem::RuntimeError(roc_problem::can::RuntimeError::UnappliedRecordBuilder { .. })
|
||||
Problem::RuntimeError(roc_problem::can::RuntimeError::UnappliedOldRecordBuilder { .. })
|
||||
)));
|
||||
|
||||
assert!(matches!(
|
||||
loc_expr.value,
|
||||
Expr::RuntimeError(roc_problem::can::RuntimeError::UnappliedRecordBuilder { .. })
|
||||
Expr::RuntimeError(roc_problem::can::RuntimeError::UnappliedOldRecordBuilder { .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_record_builder_desugar() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
map2 = \a, b, combine -> combine a b
|
||||
double = \n -> n * 2
|
||||
|
||||
c = 3
|
||||
|
||||
{ map2 <-
|
||||
a: 1,
|
||||
b: double 2,
|
||||
c
|
||||
}
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let out = can_expr_with(&arena, test_home(), src);
|
||||
|
||||
assert_eq!(out.problems.len(), 0);
|
||||
|
||||
// Assert that we desugar to:
|
||||
//
|
||||
// map2
|
||||
// (1)
|
||||
// (map2
|
||||
// (double 2)
|
||||
// (c)
|
||||
// (\#a, #b -> (#a, #b))
|
||||
// )
|
||||
// (\a, (b, c) -> { a: #a, b: #b, c: #c })
|
||||
|
||||
let first_map2_args = assert_func_call(
|
||||
&out.loc_expr.value,
|
||||
"map2",
|
||||
CalledVia::RecordBuilder,
|
||||
&out.interns,
|
||||
);
|
||||
let (first_arg, second_arg, third_arg) = match &first_map2_args[..] {
|
||||
[first, second, third] => (&first.1.value, &second.1.value, &third.1.value),
|
||||
_ => panic!("map2 didn't receive three arguments"),
|
||||
};
|
||||
|
||||
assert_num_value(first_arg, 1);
|
||||
|
||||
let inner_map2_args =
|
||||
assert_func_call(second_arg, "map2", CalledVia::RecordBuilder, &out.interns);
|
||||
let (first_inner_arg, second_inner_arg, third_inner_arg) = match &inner_map2_args[..] {
|
||||
[first, second, third] => (&first.1.value, &second.1.value, &third.1.value),
|
||||
_ => panic!("inner map2 didn't receive three arguments"),
|
||||
};
|
||||
|
||||
let double_args =
|
||||
assert_func_call(first_inner_arg, "double", CalledVia::Space, &out.interns);
|
||||
assert_eq!(double_args.len(), 1);
|
||||
assert_num_value(&double_args[0].1.value, 2);
|
||||
|
||||
assert_var_usage(second_inner_arg, "c", &out.interns);
|
||||
|
||||
match third_inner_arg {
|
||||
Expr::Closure(ClosureData {
|
||||
arguments,
|
||||
loc_body,
|
||||
..
|
||||
}) => {
|
||||
assert_eq!(arguments.len(), 2);
|
||||
assert_pattern_name(
|
||||
&arguments[0].2.value,
|
||||
"#record_builder_closure_arg_a",
|
||||
&out.interns,
|
||||
);
|
||||
assert_pattern_name(
|
||||
&arguments[1].2.value,
|
||||
"#record_builder_closure_arg_b",
|
||||
&out.interns,
|
||||
);
|
||||
|
||||
match &loc_body.value {
|
||||
Expr::Tuple { elems, .. } => {
|
||||
assert_eq!(elems.len(), 2);
|
||||
assert_var_usage(
|
||||
&elems[0].1.value,
|
||||
"#record_builder_closure_arg_a",
|
||||
&out.interns,
|
||||
);
|
||||
assert_var_usage(
|
||||
&elems[1].1.value,
|
||||
"#record_builder_closure_arg_b",
|
||||
&out.interns,
|
||||
);
|
||||
}
|
||||
_ => panic!("Closure body was not a tuple"),
|
||||
}
|
||||
}
|
||||
_ => panic!("inner map2's combiner was not a closure"),
|
||||
}
|
||||
|
||||
match third_arg {
|
||||
Expr::Closure(ClosureData {
|
||||
arguments,
|
||||
loc_body,
|
||||
..
|
||||
}) => {
|
||||
assert_eq!(arguments.len(), 2);
|
||||
assert_pattern_name(&arguments[0].2.value, "#a", &out.interns);
|
||||
match &arguments[1].2.value {
|
||||
Pattern::TupleDestructure { destructs, .. } => {
|
||||
assert_eq!(destructs.len(), 2);
|
||||
assert_pattern_name(&destructs[0].value.typ.1.value, "#b", &out.interns);
|
||||
assert_pattern_name(&destructs[1].value.typ.1.value, "#c", &out.interns);
|
||||
}
|
||||
_ => panic!("Second arg to builder func was not a tuple destructure"),
|
||||
}
|
||||
|
||||
match &loc_body.value {
|
||||
Expr::Record { fields, .. } => {
|
||||
assert_eq!(fields.len(), 3);
|
||||
|
||||
assert_eq!(get_field_var_sym(fields, "a").as_str(&out.interns), "#a");
|
||||
assert_eq!(get_field_var_sym(fields, "b").as_str(&out.interns), "#b");
|
||||
assert_eq!(get_field_var_sym(fields, "c").as_str(&out.interns), "#c");
|
||||
}
|
||||
_ => panic!("Closure body was not a tuple"),
|
||||
}
|
||||
}
|
||||
_ => panic!("inner map2's combiner was not a closure"),
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_num_value(expr: &Expr, num: usize) {
|
||||
match expr {
|
||||
Expr::Num(_, num_str, _, _) => {
|
||||
assert_eq!(&**num_str, &num.to_string())
|
||||
}
|
||||
_ => panic!("Expr wasn't a Num with value {num}: {:?}", expr),
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_var_usage(expr: &Expr, name: &str, interns: &roc_module::symbol::Interns) {
|
||||
match expr {
|
||||
Expr::Var(sym, _) => assert_eq!(sym.as_str(interns), name),
|
||||
_ => panic!("Expr was not a variable usage: {:?}", expr),
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_func_call(
|
||||
expr: &Expr,
|
||||
name: &str,
|
||||
called_via: CalledVia,
|
||||
interns: &roc_module::symbol::Interns,
|
||||
) -> Vec<(Variable, Loc<Expr>)> {
|
||||
match expr {
|
||||
Expr::LetNonRec(_, loc_expr) => {
|
||||
assert_func_call(&loc_expr.value, name, called_via, interns)
|
||||
}
|
||||
Expr::Call(fun, args, called) if called == &called_via => {
|
||||
match &fun.1.value {
|
||||
Expr::Var(sym, _) => assert_eq!(sym.as_str(interns), name),
|
||||
_ => panic!("Builder didn't desugar with mapper at front"),
|
||||
};
|
||||
|
||||
args.clone()
|
||||
}
|
||||
_ => panic!("Expr was not a RecordBuilder Call: {:?}", expr),
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_pattern_name(pattern: &Pattern, name: &str, interns: &roc_module::symbol::Interns) {
|
||||
match pattern {
|
||||
Pattern::Identifier(sym) => assert_eq!(sym.as_str(interns), name),
|
||||
_ => panic!("Pattern was not an identifier: {:?}", pattern),
|
||||
}
|
||||
}
|
||||
|
||||
// TAIL CALLS
|
||||
fn get_closure(expr: &Expr, i: usize) -> roc_can::expr::Recursive {
|
||||
match expr {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue