mirror of
https://github.com/roc-lang/roc.git
synced 2024-10-03 21:57:31 +03:00
Merge pull request #7110 from smores56/remove-old-record-builder
Remove Old Record Builder Syntax
This commit is contained in:
commit
82036e2e2e
@ -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));
|
||||
@ -1390,97 +1351,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
|
||||
|
@ -5,7 +5,7 @@ use crate::{
|
||||
};
|
||||
use roc_parse::ast::{
|
||||
AbilityImpls, AssignedField, Collection, Expr, ExtractSpaces, ImplementsAbilities,
|
||||
ImplementsAbility, ImplementsClause, OldRecordBuilderField, Tag, TypeAnnotation, TypeHeader,
|
||||
ImplementsAbility, ImplementsClause, Tag, TypeAnnotation, TypeHeader,
|
||||
};
|
||||
use roc_parse::ident::UppercaseIdent;
|
||||
use roc_region::all::Loc;
|
||||
@ -524,101 +524,6 @@ fn format_assigned_field_help<T>(
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for OldRecordBuilderField<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
is_multiline_record_builder_field_help(self)
|
||||
}
|
||||
|
||||
fn format_with_options(&self, buf: &mut Buf, _parens: Parens, newlines: Newlines, indent: u16) {
|
||||
// we abuse the `Newlines` type to decide between multiline or single-line layout
|
||||
format_record_builder_field_help(self, buf, indent, newlines == Newlines::Yes);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_multiline_record_builder_field_help(afield: &OldRecordBuilderField<'_>) -> bool {
|
||||
use self::OldRecordBuilderField::*;
|
||||
|
||||
match afield {
|
||||
Value(_, spaces, ann) => !spaces.is_empty() || ann.value.is_multiline(),
|
||||
ApplyValue(_, colon_spaces, arrow_spaces, ann) => {
|
||||
!colon_spaces.is_empty() || !arrow_spaces.is_empty() || ann.value.is_multiline()
|
||||
}
|
||||
LabelOnly(_) => false,
|
||||
SpaceBefore(_, _) | SpaceAfter(_, _) => true,
|
||||
Malformed(text) => text.chars().any(|c| c == '\n'),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_record_builder_field_help(
|
||||
zelf: &OldRecordBuilderField,
|
||||
buf: &mut Buf,
|
||||
indent: u16,
|
||||
is_multiline: bool,
|
||||
) {
|
||||
use self::OldRecordBuilderField::*;
|
||||
|
||||
match zelf {
|
||||
Value(name, spaces, ann) => {
|
||||
if is_multiline {
|
||||
buf.newline();
|
||||
}
|
||||
|
||||
buf.indent(indent);
|
||||
buf.push_str(name.value);
|
||||
|
||||
if !spaces.is_empty() {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
|
||||
buf.push(':');
|
||||
buf.spaces(1);
|
||||
ann.value.format(buf, indent);
|
||||
}
|
||||
ApplyValue(name, colon_spaces, arrow_spaces, ann) => {
|
||||
if is_multiline {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
}
|
||||
|
||||
buf.push_str(name.value);
|
||||
|
||||
if !colon_spaces.is_empty() {
|
||||
fmt_spaces(buf, colon_spaces.iter(), indent);
|
||||
}
|
||||
|
||||
buf.push(':');
|
||||
buf.spaces(1);
|
||||
|
||||
if !arrow_spaces.is_empty() {
|
||||
fmt_spaces(buf, arrow_spaces.iter(), indent);
|
||||
}
|
||||
|
||||
buf.push_str("<-");
|
||||
buf.spaces(1);
|
||||
ann.value.format(buf, indent);
|
||||
}
|
||||
LabelOnly(name) => {
|
||||
if is_multiline {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
}
|
||||
|
||||
buf.push_str(name.value);
|
||||
}
|
||||
SpaceBefore(sub_field, spaces) => {
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
|
||||
format_record_builder_field_help(sub_field, buf, indent, is_multiline);
|
||||
}
|
||||
SpaceAfter(sub_field, spaces) => {
|
||||
format_record_builder_field_help(sub_field, buf, indent, is_multiline);
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
|
||||
}
|
||||
Malformed(raw) => {
|
||||
buf.push_str(raw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for Tag<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
use self::Tag::*;
|
||||
|
@ -10,7 +10,7 @@ use crate::Buf;
|
||||
use roc_module::called_via::{self, BinOp};
|
||||
use roc_parse::ast::{
|
||||
is_expr_suffixed, AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces,
|
||||
OldRecordBuilderField, Pattern, TryTarget, WhenBranch,
|
||||
Pattern, TryTarget, WhenBranch,
|
||||
};
|
||||
use roc_parse::ast::{StrLiteral, StrSegment};
|
||||
use roc_parse::ident::Accessor;
|
||||
@ -91,8 +91,6 @@ impl<'a> Formattable for Expr<'a> {
|
||||
| PrecedenceConflict(roc_parse::ast::PrecedenceConflict {
|
||||
expr: loc_subexpr, ..
|
||||
})
|
||||
| MultipleOldRecordBuilders(loc_subexpr)
|
||||
| UnappliedOldRecordBuilder(loc_subexpr)
|
||||
| EmptyRecordBuilder(loc_subexpr)
|
||||
| SingleFieldRecordBuilder(loc_subexpr)
|
||||
| OptionalFieldInRecordBuilder(_, loc_subexpr) => loc_subexpr.is_multiline(),
|
||||
@ -118,7 +116,6 @@ impl<'a> Formattable for Expr<'a> {
|
||||
Record(fields) => is_collection_multiline(fields),
|
||||
Tuple(fields) => is_collection_multiline(fields),
|
||||
RecordUpdate { fields, .. } => is_collection_multiline(fields),
|
||||
OldRecordBuilder(fields) => is_collection_multiline(fields),
|
||||
RecordBuilder { fields, .. } => is_collection_multiline(fields),
|
||||
}
|
||||
}
|
||||
@ -244,10 +241,7 @@ impl<'a> Formattable for Expr<'a> {
|
||||
a.extract_spaces().item.is_multiline()
|
||||
&& matches!(
|
||||
a.value.extract_spaces().item,
|
||||
Expr::Tuple(_)
|
||||
| Expr::List(_)
|
||||
| Expr::Record(_)
|
||||
| Expr::OldRecordBuilder(_)
|
||||
Expr::Tuple(_) | Expr::List(_) | Expr::Record(_)
|
||||
)
|
||||
&& a.extract_spaces().before == [CommentOrNewline::Newline]
|
||||
})
|
||||
@ -392,16 +386,6 @@ impl<'a> Formattable for Expr<'a> {
|
||||
assigned_field_to_space_before,
|
||||
);
|
||||
}
|
||||
OldRecordBuilder(fields) => {
|
||||
fmt_record_like(
|
||||
buf,
|
||||
None,
|
||||
*fields,
|
||||
indent,
|
||||
format_record_builder_field_multiline,
|
||||
record_builder_field_to_space_before,
|
||||
);
|
||||
}
|
||||
Closure(loc_patterns, loc_ret) => {
|
||||
fmt_closure(buf, loc_patterns, loc_ret, indent);
|
||||
}
|
||||
@ -563,8 +547,6 @@ impl<'a> Formattable for Expr<'a> {
|
||||
}
|
||||
MalformedClosure => {}
|
||||
PrecedenceConflict { .. } => {}
|
||||
MultipleOldRecordBuilders { .. } => {}
|
||||
UnappliedOldRecordBuilder { .. } => {}
|
||||
EmptyRecordBuilder { .. } => {}
|
||||
SingleFieldRecordBuilder { .. } => {}
|
||||
OptionalFieldInRecordBuilder(_, _) => {}
|
||||
@ -625,11 +607,7 @@ pub(crate) fn format_sq_literal(buf: &mut Buf, s: &str) {
|
||||
fn is_outdentable(expr: &Expr) -> bool {
|
||||
matches!(
|
||||
expr.extract_spaces().item,
|
||||
Expr::Tuple(_)
|
||||
| Expr::List(_)
|
||||
| Expr::Record(_)
|
||||
| Expr::OldRecordBuilder(_)
|
||||
| Expr::Closure(..)
|
||||
Expr::Tuple(_) | Expr::List(_) | Expr::Record(_) | Expr::Closure(..)
|
||||
)
|
||||
}
|
||||
|
||||
@ -1644,113 +1622,6 @@ fn assigned_field_to_space_before<'a, T>(
|
||||
}
|
||||
}
|
||||
|
||||
fn format_record_builder_field_multiline(
|
||||
buf: &mut Buf,
|
||||
field: &OldRecordBuilderField,
|
||||
indent: u16,
|
||||
separator_prefix: &str,
|
||||
) {
|
||||
use self::OldRecordBuilderField::*;
|
||||
match field {
|
||||
Value(name, spaces, ann) => {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
buf.push_str(name.value);
|
||||
|
||||
if !spaces.is_empty() {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
buf.indent(indent);
|
||||
}
|
||||
|
||||
buf.push_str(separator_prefix);
|
||||
buf.push_str(":");
|
||||
|
||||
if ann.value.is_multiline() {
|
||||
buf.newline();
|
||||
ann.value.format(buf, indent + INDENT);
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
ann.value.format(buf, indent);
|
||||
}
|
||||
|
||||
buf.push(',');
|
||||
}
|
||||
ApplyValue(name, colon_spaces, arrow_spaces, ann) => {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
buf.push_str(name.value);
|
||||
|
||||
if !colon_spaces.is_empty() {
|
||||
fmt_spaces(buf, colon_spaces.iter(), indent);
|
||||
buf.indent(indent);
|
||||
}
|
||||
|
||||
buf.push_str(separator_prefix);
|
||||
buf.push(':');
|
||||
buf.spaces(1);
|
||||
|
||||
if !arrow_spaces.is_empty() {
|
||||
fmt_spaces(buf, arrow_spaces.iter(), indent);
|
||||
buf.indent(indent + INDENT);
|
||||
}
|
||||
|
||||
buf.push_str("<-");
|
||||
|
||||
if ann.value.is_multiline() {
|
||||
buf.newline();
|
||||
ann.value.format(buf, indent + INDENT);
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
ann.value.format(buf, indent);
|
||||
}
|
||||
buf.push(',');
|
||||
}
|
||||
LabelOnly(name) => {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
buf.push_str(name.value);
|
||||
buf.push(',');
|
||||
}
|
||||
SpaceBefore(sub_field, _spaces) => {
|
||||
// We have something like that:
|
||||
// ```
|
||||
// # comment
|
||||
// field,
|
||||
// ```
|
||||
// we'd like to preserve this
|
||||
|
||||
format_record_builder_field_multiline(buf, sub_field, indent, separator_prefix);
|
||||
}
|
||||
SpaceAfter(sub_field, spaces) => {
|
||||
// We have something like that:
|
||||
// ```
|
||||
// field # comment
|
||||
// , otherfield
|
||||
// ```
|
||||
// we'd like to transform it into:
|
||||
// ```
|
||||
// field,
|
||||
// # comment
|
||||
// otherfield
|
||||
// ```
|
||||
format_record_builder_field_multiline(buf, sub_field, indent, separator_prefix);
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, indent);
|
||||
}
|
||||
Malformed(raw) => {
|
||||
buf.push_str(raw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn record_builder_field_to_space_before<'a>(
|
||||
field: &'a OldRecordBuilderField<'a>,
|
||||
) -> Option<(&OldRecordBuilderField<'a>, &'a [CommentOrNewline<'a>])> {
|
||||
match field {
|
||||
OldRecordBuilderField::SpaceBefore(sub_field, spaces) => Some((sub_field, spaces)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
|
||||
match expr {
|
||||
Expr::BinOps(left_side, _) => {
|
||||
|
@ -4972,30 +4972,6 @@ mod test_reporting {
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
record_builder_in_module_params,
|
||||
indoc!(
|
||||
r"
|
||||
import Menu {
|
||||
echo,
|
||||
name: <- applyName
|
||||
}
|
||||
"
|
||||
),@r###"
|
||||
── OLD-STYLE RECORD BUILDER IN MODULE PARAMS in ...r_in_module_params/Test.roc ─
|
||||
|
||||
I was partway through parsing module params, but I got stuck here:
|
||||
|
||||
4│ import Menu {
|
||||
5│ echo,
|
||||
6│ name: <- applyName
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This looks like an old-style record builder field, but those are not
|
||||
allowed in module params.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
record_update_in_module_params,
|
||||
indoc!(
|
||||
@ -10661,163 +10637,6 @@ All branches in an `if` must have the same type!
|
||||
|
||||
// Record Builders
|
||||
|
||||
test_report!(
|
||||
optional_field_in_old_record_builder,
|
||||
indoc!(
|
||||
r#"
|
||||
{
|
||||
a: <- apply "a",
|
||||
b,
|
||||
c ? "optional"
|
||||
}
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
── BAD OLD-STYLE RECORD BUILDER in ...nal_field_in_old_record_builder/Test.roc ─
|
||||
|
||||
I am partway through parsing a record builder, and I found an optional
|
||||
field:
|
||||
|
||||
1│ app "test" provides [main] to "./platform"
|
||||
2│
|
||||
3│ main =
|
||||
4│ {
|
||||
5│ a: <- apply "a",
|
||||
6│ b,
|
||||
7│ c ? "optional"
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Optional fields can only appear when you destructure a record.
|
||||
"#
|
||||
);
|
||||
|
||||
test_report!(
|
||||
record_update_old_builder,
|
||||
indoc!(
|
||||
r#"
|
||||
{ rec &
|
||||
a: <- apply "a",
|
||||
b: 3
|
||||
}
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
── BAD RECORD UPDATE in tmp/record_update_old_builder/Test.roc ─────────────────
|
||||
|
||||
I am partway through parsing a record update, and I found an old-style
|
||||
record builder field:
|
||||
|
||||
1│ app "test" provides [main] to "./platform"
|
||||
2│
|
||||
3│ main =
|
||||
4│ { rec &
|
||||
5│ a: <- apply "a",
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Old-style record builders cannot be updated like records.
|
||||
"#
|
||||
);
|
||||
|
||||
test_report!(
|
||||
multiple_old_record_builders,
|
||||
indoc!(
|
||||
r#"
|
||||
succeed
|
||||
{ a: <- apply "a" }
|
||||
{ b: <- apply "b" }
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
── MULTIPLE OLD-STYLE RECORD BUILDERS in /code/proj/Main.roc ───────────────────
|
||||
|
||||
This function is applied to multiple old-style record builders:
|
||||
|
||||
4│> succeed
|
||||
5│> { a: <- apply "a" }
|
||||
6│> { b: <- apply "b" }
|
||||
|
||||
Note: Functions can only take at most one old-style record builder!
|
||||
|
||||
Tip: You can combine them or apply them separately.
|
||||
"#
|
||||
);
|
||||
|
||||
test_report!(
|
||||
unapplied_old_record_builder,
|
||||
indoc!(
|
||||
r#"
|
||||
{ a: <- apply "a" }
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
── UNAPPLIED OLD-STYLE RECORD BUILDER in /code/proj/Main.roc ───────────────────
|
||||
|
||||
This old-style record builder was not applied to a function:
|
||||
|
||||
4│ { a: <- apply "a" }
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
However, we need a function to construct the record.
|
||||
|
||||
Note: Functions must be applied directly. The pipe operator (|>) cannot be used.
|
||||
"#
|
||||
);
|
||||
|
||||
test_report!(
|
||||
old_record_builder_apply_non_function,
|
||||
indoc!(
|
||||
r#"
|
||||
succeed = \_ -> crash ""
|
||||
|
||||
succeed {
|
||||
a: <- "a",
|
||||
}
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
── TOO MANY ARGS in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
This value is not a function, but it was given 1 argument:
|
||||
|
||||
7│ a: <- "a",
|
||||
^^^
|
||||
|
||||
Tip: Remove <- to assign the field directly.
|
||||
"#
|
||||
);
|
||||
|
||||
// Skipping test because opaque types defined in the same module
|
||||
// do not fail with the special opaque type error
|
||||
//
|
||||
// test_report!(
|
||||
// record_builder_apply_opaque,
|
||||
// indoc!(
|
||||
// r#"
|
||||
// succeed = \_ -> crash ""
|
||||
|
||||
// Decode := {}
|
||||
|
||||
// get : Str -> Decode
|
||||
// get = \_ -> @Decode {}
|
||||
|
||||
// succeed {
|
||||
// a: <- get "a",
|
||||
// # missing |> apply ^
|
||||
// }
|
||||
// "#
|
||||
// ),
|
||||
// @r#"
|
||||
// ── TOO MANY ARGS in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
// This value is an opaque type, so it cannot be called with an argument:
|
||||
|
||||
// 12│ a: <- get "a",
|
||||
// ^^^^^^^
|
||||
|
||||
// Hint: Did you mean to apply it to a function first?
|
||||
// "#
|
||||
// );
|
||||
|
||||
test_report!(
|
||||
empty_record_builder,
|
||||
indoc!(
|
||||
|
@ -75,10 +75,6 @@ pub enum CalledVia {
|
||||
/// e.g. "$(first) $(last)" is transformed into Str.concat (Str.concat first " ") last.
|
||||
StringInterpolation,
|
||||
|
||||
/// This call is the result of desugaring an old style Record Builder field.
|
||||
/// e.g. succeed { a <- get "a" } is transformed into (get "a") (succeed \a -> { a })
|
||||
OldRecordBuilder,
|
||||
|
||||
/// This call is the result of desugaring a map2-based Record Builder field. e.g.
|
||||
/// ```roc
|
||||
/// { Task.parallel <-
|
||||
|
@ -456,13 +456,6 @@ pub enum Expr<'a> {
|
||||
|
||||
Tuple(Collection<'a, &'a Loc<Expr<'a>>>),
|
||||
|
||||
// Record Builders
|
||||
/// Applicative record builders, e.g.
|
||||
/// build {
|
||||
/// foo: <- getData Foo,
|
||||
/// bar: <- getData Bar,
|
||||
/// }
|
||||
OldRecordBuilder(Collection<'a, Loc<OldRecordBuilderField<'a>>>),
|
||||
/// Mapper-based record builders, e.g.
|
||||
/// { Task.parallel <-
|
||||
/// foo: Task.getData Foo,
|
||||
@ -541,8 +534,6 @@ pub enum Expr<'a> {
|
||||
// Both operators were non-associative, e.g. (True == False == False).
|
||||
// We should tell the author to disambiguate by grouping them with parens.
|
||||
PrecedenceConflict(&'a PrecedenceConflict<'a>),
|
||||
MultipleOldRecordBuilders(&'a Loc<Expr<'a>>),
|
||||
UnappliedOldRecordBuilder(&'a Loc<Expr<'a>>),
|
||||
EmptyRecordBuilder(&'a Loc<Expr<'a>>),
|
||||
SingleFieldRecordBuilder(&'a Loc<Expr<'a>>),
|
||||
OptionalFieldInRecordBuilder(&'a Loc<&'a str>, &'a Loc<Expr<'a>>),
|
||||
@ -663,9 +654,6 @@ pub fn is_expr_suffixed(expr: &Expr) -> bool {
|
||||
.iter()
|
||||
.any(|field| is_assigned_value_suffixed(&field.value)),
|
||||
Expr::Tuple(items) => items.iter().any(|x| is_expr_suffixed(&x.value)),
|
||||
Expr::OldRecordBuilder(items) => items
|
||||
.iter()
|
||||
.any(|rbf| is_record_builder_field_suffixed(&rbf.value)),
|
||||
Expr::RecordBuilder { mapper: _, fields } => fields
|
||||
.iter()
|
||||
.any(|field| is_assigned_value_suffixed(&field.value)),
|
||||
@ -688,8 +676,6 @@ pub fn is_expr_suffixed(expr: &Expr) -> bool {
|
||||
Expr::MalformedClosure => false,
|
||||
Expr::MalformedSuffixed(_) => false,
|
||||
Expr::PrecedenceConflict(_) => false,
|
||||
Expr::MultipleOldRecordBuilders(_) => false,
|
||||
Expr::UnappliedOldRecordBuilder(_) => false,
|
||||
Expr::EmptyRecordBuilder(_) => false,
|
||||
Expr::SingleFieldRecordBuilder(_) => false,
|
||||
Expr::OptionalFieldInRecordBuilder(_, _) => false,
|
||||
@ -717,17 +703,6 @@ fn is_assigned_value_suffixed<'a>(value: &AssignedField<'a, Expr<'a>>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_record_builder_field_suffixed(field: &OldRecordBuilderField<'_>) -> bool {
|
||||
match field {
|
||||
OldRecordBuilderField::Value(_, _, a) => is_expr_suffixed(&a.value),
|
||||
OldRecordBuilderField::ApplyValue(_, _, _, a) => is_expr_suffixed(&a.value),
|
||||
OldRecordBuilderField::LabelOnly(_) => false,
|
||||
OldRecordBuilderField::SpaceBefore(a, _) => is_record_builder_field_suffixed(a),
|
||||
OldRecordBuilderField::SpaceAfter(a, _) => is_record_builder_field_suffixed(a),
|
||||
OldRecordBuilderField::Malformed(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split_around<T>(items: &[T], target: usize) -> (&[T], &[T]) {
|
||||
let (before, rest) = items.split_at(target);
|
||||
let after = &rest[1..];
|
||||
@ -935,26 +910,6 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
|
||||
expr_stack.push(&loc_expr.value);
|
||||
}
|
||||
}
|
||||
OldRecordBuilder(fields) => {
|
||||
expr_stack.reserve(fields.len());
|
||||
for loc_record_builder_field in fields.items {
|
||||
let mut current_field = loc_record_builder_field.value;
|
||||
|
||||
loop {
|
||||
use OldRecordBuilderField::*;
|
||||
|
||||
match current_field {
|
||||
Value(_, _, loc_val) => break expr_stack.push(&loc_val.value),
|
||||
ApplyValue(_, _, _, loc_val) => {
|
||||
break expr_stack.push(&loc_val.value)
|
||||
}
|
||||
SpaceBefore(next_field, _) => current_field = *next_field,
|
||||
SpaceAfter(next_field, _) => current_field = *next_field,
|
||||
LabelOnly(_) | Malformed(_) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RecordBuilder {
|
||||
mapper: map2,
|
||||
fields,
|
||||
@ -1039,9 +994,7 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
|
||||
| SpaceAfter(expr, _)
|
||||
| ParensAround(expr) => expr_stack.push(expr),
|
||||
|
||||
MultipleOldRecordBuilders(loc_expr)
|
||||
| UnappliedOldRecordBuilder(loc_expr)
|
||||
| EmptyRecordBuilder(loc_expr)
|
||||
EmptyRecordBuilder(loc_expr)
|
||||
| SingleFieldRecordBuilder(loc_expr)
|
||||
| OptionalFieldInRecordBuilder(_, loc_expr) => expr_stack.push(&loc_expr.value),
|
||||
|
||||
@ -1667,30 +1620,6 @@ impl<'a, Val> AssignedField<'a, Val> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum OldRecordBuilderField<'a> {
|
||||
// A field with a value, e.g. `{ name: "blah" }`
|
||||
Value(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Expr<'a>>),
|
||||
|
||||
// A field with a function we can apply to build part of the record, e.g. `{ name: <- apply getName }`
|
||||
ApplyValue(
|
||||
Loc<&'a str>,
|
||||
&'a [CommentOrNewline<'a>],
|
||||
&'a [CommentOrNewline<'a>],
|
||||
&'a Loc<Expr<'a>>,
|
||||
),
|
||||
|
||||
// A label with no value, e.g. `{ name }` (this is sugar for { name: name })
|
||||
LabelOnly(Loc<&'a str>),
|
||||
|
||||
// We preserve this for the formatter; canonicalization ignores it.
|
||||
SpaceBefore(&'a OldRecordBuilderField<'a>, &'a [CommentOrNewline<'a>]),
|
||||
SpaceAfter(&'a OldRecordBuilderField<'a>, &'a [CommentOrNewline<'a>]),
|
||||
|
||||
/// A malformed assigned field, which will code gen to a runtime error
|
||||
Malformed(&'a str),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum CommentOrNewline<'a> {
|
||||
Newline,
|
||||
@ -2225,15 +2154,6 @@ impl<'a, Val> Spaceable<'a> for AssignedField<'a, Val> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Spaceable<'a> for OldRecordBuilderField<'a> {
|
||||
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||
OldRecordBuilderField::SpaceBefore(self, spaces)
|
||||
}
|
||||
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||
OldRecordBuilderField::SpaceAfter(self, spaces)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Spaceable<'a> for Tag<'a> {
|
||||
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||
Tag::SpaceBefore(self, spaces)
|
||||
@ -2536,7 +2456,6 @@ impl<'a> Malformed for Expr<'a> {
|
||||
Record(items) => items.is_malformed(),
|
||||
Tuple(items) => items.is_malformed(),
|
||||
|
||||
OldRecordBuilder(items) => items.is_malformed(),
|
||||
RecordBuilder { mapper: map2, fields } => map2.is_malformed() || fields.is_malformed(),
|
||||
|
||||
Closure(args, body) => args.iter().any(|arg| arg.is_malformed()) || body.is_malformed(),
|
||||
@ -2560,8 +2479,6 @@ impl<'a> Malformed for Expr<'a> {
|
||||
MalformedClosure |
|
||||
MalformedSuffixed(..) |
|
||||
PrecedenceConflict(_) |
|
||||
MultipleOldRecordBuilders(_) |
|
||||
UnappliedOldRecordBuilder(_) |
|
||||
EmptyRecordBuilder(_) |
|
||||
SingleFieldRecordBuilder(_) |
|
||||
OptionalFieldInRecordBuilder(_, _) => true,
|
||||
@ -2641,19 +2558,6 @@ impl<'a, T: Malformed> Malformed for AssignedField<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Malformed for OldRecordBuilderField<'a> {
|
||||
fn is_malformed(&self) -> bool {
|
||||
match self {
|
||||
OldRecordBuilderField::Value(_, _, expr)
|
||||
| OldRecordBuilderField::ApplyValue(_, _, _, expr) => expr.is_malformed(),
|
||||
OldRecordBuilderField::LabelOnly(_) => false,
|
||||
OldRecordBuilderField::SpaceBefore(field, _)
|
||||
| OldRecordBuilderField::SpaceAfter(field, _) => field.is_malformed(),
|
||||
OldRecordBuilderField::Malformed(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Malformed for Pattern<'a> {
|
||||
fn is_malformed(&self) -> bool {
|
||||
use Pattern::*;
|
||||
|
@ -2,8 +2,8 @@ use crate::ast::{
|
||||
is_expr_suffixed, AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces,
|
||||
Implements, ImplementsAbilities, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
|
||||
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport,
|
||||
ModuleImportParams, OldRecordBuilderField, Pattern, Spaceable, Spaced, Spaces, SpacesBefore,
|
||||
TryTarget, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
|
||||
ModuleImportParams, Pattern, Spaceable, Spaced, Spaces, SpacesBefore, TryTarget,
|
||||
TypeAnnotation, TypeDef, TypeHeader, ValueDef,
|
||||
};
|
||||
use crate::blankspace::{
|
||||
loc_space0_e, require_newline_or_eof, space0_after_e, space0_around_ee, space0_before_e,
|
||||
@ -925,15 +925,11 @@ fn import_params<'a>() -> impl Parser<'a, ModuleImportParams<'a>, EImportParams<
|
||||
.fields
|
||||
.map_items_result(arena, |loc_field| {
|
||||
match loc_field.value.to_assigned_field(arena) {
|
||||
Ok(AssignedField::IgnoredValue(_, _, _)) => Err((
|
||||
AssignedField::IgnoredValue(_, _, _) => Err((
|
||||
MadeProgress,
|
||||
EImportParams::RecordIgnoredFieldFound(loc_field.region),
|
||||
)),
|
||||
Ok(field) => Ok(Loc::at(loc_field.region, field)),
|
||||
Err(FoundApplyValue) => Err((
|
||||
MadeProgress,
|
||||
EImportParams::RecordApplyFound(loc_field.region),
|
||||
)),
|
||||
field => Ok(Loc::at(loc_field.region, field)),
|
||||
}
|
||||
})?;
|
||||
|
||||
@ -2179,8 +2175,6 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
|
||||
| Expr::MalformedClosure
|
||||
| Expr::MalformedSuffixed(..)
|
||||
| Expr::PrecedenceConflict { .. }
|
||||
| Expr::MultipleOldRecordBuilders { .. }
|
||||
| Expr::UnappliedOldRecordBuilder { .. }
|
||||
| Expr::EmptyRecordBuilder(_)
|
||||
| Expr::SingleFieldRecordBuilder(_)
|
||||
| Expr::OptionalFieldInRecordBuilder(_, _)
|
||||
@ -2189,7 +2183,6 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
|
||||
| Expr::UnaryOp(_, _)
|
||||
| Expr::TrySuffix { .. }
|
||||
| Expr::Crash
|
||||
| Expr::OldRecordBuilder(..)
|
||||
| Expr::RecordBuilder { .. } => return Err(()),
|
||||
|
||||
Expr::Str(string) => Pattern::StrLiteral(string),
|
||||
@ -3390,38 +3383,12 @@ pub enum RecordField<'a> {
|
||||
LabelOnly(Loc<&'a str>),
|
||||
SpaceBefore(&'a RecordField<'a>, &'a [CommentOrNewline<'a>]),
|
||||
SpaceAfter(&'a RecordField<'a>, &'a [CommentOrNewline<'a>]),
|
||||
ApplyValue(
|
||||
Loc<&'a str>,
|
||||
&'a [CommentOrNewline<'a>],
|
||||
&'a [CommentOrNewline<'a>],
|
||||
&'a Loc<Expr<'a>>,
|
||||
),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FoundApplyValue;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NotOldBuilderFieldValue {
|
||||
FoundOptionalValue,
|
||||
FoundIgnoredValue,
|
||||
}
|
||||
|
||||
impl<'a> RecordField<'a> {
|
||||
fn is_apply_value(&self) -> bool {
|
||||
let mut current = self;
|
||||
|
||||
loop {
|
||||
match current {
|
||||
RecordField::ApplyValue(_, _, _, _) => break true,
|
||||
RecordField::SpaceBefore(field, _) | RecordField::SpaceAfter(field, _) => {
|
||||
current = *field;
|
||||
}
|
||||
_ => break false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_ignored_value(&self) -> bool {
|
||||
let mut current = self;
|
||||
|
||||
@ -3436,74 +3403,34 @@ impl<'a> RecordField<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_assigned_field(
|
||||
self,
|
||||
arena: &'a Bump,
|
||||
) -> Result<AssignedField<'a, Expr<'a>>, FoundApplyValue> {
|
||||
pub fn to_assigned_field(self, arena: &'a Bump) -> AssignedField<'a, Expr<'a>> {
|
||||
use AssignedField::*;
|
||||
|
||||
match self {
|
||||
RecordField::RequiredValue(loc_label, spaces, loc_expr) => {
|
||||
Ok(RequiredValue(loc_label, spaces, loc_expr))
|
||||
RequiredValue(loc_label, spaces, loc_expr)
|
||||
}
|
||||
|
||||
RecordField::OptionalValue(loc_label, spaces, loc_expr) => {
|
||||
Ok(OptionalValue(loc_label, spaces, loc_expr))
|
||||
OptionalValue(loc_label, spaces, loc_expr)
|
||||
}
|
||||
|
||||
RecordField::IgnoredValue(loc_label, spaces, loc_expr) => {
|
||||
Ok(IgnoredValue(loc_label, spaces, loc_expr))
|
||||
IgnoredValue(loc_label, spaces, loc_expr)
|
||||
}
|
||||
|
||||
RecordField::LabelOnly(loc_label) => Ok(LabelOnly(loc_label)),
|
||||
|
||||
RecordField::ApplyValue(_, _, _, _) => Err(FoundApplyValue),
|
||||
RecordField::LabelOnly(loc_label) => LabelOnly(loc_label),
|
||||
|
||||
RecordField::SpaceBefore(field, spaces) => {
|
||||
let assigned_field = field.to_assigned_field(arena)?;
|
||||
let assigned_field = field.to_assigned_field(arena);
|
||||
|
||||
Ok(SpaceBefore(arena.alloc(assigned_field), spaces))
|
||||
SpaceBefore(arena.alloc(assigned_field), spaces)
|
||||
}
|
||||
|
||||
RecordField::SpaceAfter(field, spaces) => {
|
||||
let assigned_field = field.to_assigned_field(arena)?;
|
||||
let assigned_field = field.to_assigned_field(arena);
|
||||
|
||||
Ok(SpaceAfter(arena.alloc(assigned_field), spaces))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_builder_field(
|
||||
self,
|
||||
arena: &'a Bump,
|
||||
) -> Result<OldRecordBuilderField<'a>, NotOldBuilderFieldValue> {
|
||||
use OldRecordBuilderField::*;
|
||||
|
||||
match self {
|
||||
RecordField::RequiredValue(loc_label, spaces, loc_expr) => {
|
||||
Ok(Value(loc_label, spaces, loc_expr))
|
||||
}
|
||||
|
||||
RecordField::OptionalValue(_, _, _) => Err(NotOldBuilderFieldValue::FoundOptionalValue),
|
||||
|
||||
RecordField::IgnoredValue(_, _, _) => Err(NotOldBuilderFieldValue::FoundIgnoredValue),
|
||||
|
||||
RecordField::LabelOnly(loc_label) => Ok(LabelOnly(loc_label)),
|
||||
|
||||
RecordField::ApplyValue(loc_label, colon_spaces, arrow_spaces, loc_expr) => {
|
||||
Ok(ApplyValue(loc_label, colon_spaces, arrow_spaces, loc_expr))
|
||||
}
|
||||
|
||||
RecordField::SpaceBefore(field, spaces) => {
|
||||
let builder_field = field.to_builder_field(arena)?;
|
||||
|
||||
Ok(SpaceBefore(arena.alloc(builder_field), spaces))
|
||||
}
|
||||
|
||||
RecordField::SpaceAfter(field, spaces) => {
|
||||
let builder_field = field.to_builder_field(arena)?;
|
||||
|
||||
Ok(SpaceAfter(arena.alloc(builder_field), spaces))
|
||||
SpaceAfter(arena.alloc(assigned_field), spaces)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3557,14 +3484,10 @@ pub fn record_field<'a>() -> impl Parser<'a, RecordField<'a>, ERecord<'a>> {
|
||||
match field_data {
|
||||
Either::First((loc_label, (spaces, opt_loc_val))) => {
|
||||
match opt_loc_val {
|
||||
Some(Either::First((_, RecordFieldExpr::Value(loc_val)))) => {
|
||||
Some(Either::First((_, loc_val))) => {
|
||||
RequiredValue(loc_label, spaces, arena.alloc(loc_val))
|
||||
}
|
||||
|
||||
Some(Either::First((_, RecordFieldExpr::Apply(arrow_spaces, loc_val)))) => {
|
||||
ApplyValue(loc_label, spaces, arrow_spaces, arena.alloc(loc_val))
|
||||
}
|
||||
|
||||
Some(Either::Second((_, loc_val))) => {
|
||||
OptionalValue(loc_label, spaces, arena.alloc(loc_val))
|
||||
}
|
||||
@ -3591,34 +3514,17 @@ pub fn record_field<'a>() -> impl Parser<'a, RecordField<'a>, ERecord<'a>> {
|
||||
)
|
||||
}
|
||||
|
||||
enum RecordFieldExpr<'a> {
|
||||
Apply(&'a [CommentOrNewline<'a>], Loc<Expr<'a>>),
|
||||
Value(Loc<Expr<'a>>),
|
||||
}
|
||||
|
||||
fn record_field_expr<'a>() -> impl Parser<'a, RecordFieldExpr<'a>, ERecord<'a>> {
|
||||
fn record_field_expr<'a>() -> impl Parser<'a, Loc<Expr<'a>>, ERecord<'a>> {
|
||||
map_with_arena(
|
||||
and(
|
||||
spaces(),
|
||||
either(
|
||||
and(
|
||||
two_bytes(b'<', b'-', ERecord::Arrow),
|
||||
spaces_before(specialize_err_ref(ERecord::Expr, loc_expr(false))),
|
||||
),
|
||||
specialize_err_ref(ERecord::Expr, loc_expr(false)),
|
||||
),
|
||||
),
|
||||
|arena: &'a bumpalo::Bump, (spaces, either)| match either {
|
||||
Either::First((_, loc_expr)) => RecordFieldExpr::Apply(spaces, loc_expr),
|
||||
Either::Second(loc_expr) => RecordFieldExpr::Value({
|
||||
if spaces.is_empty() {
|
||||
loc_expr
|
||||
} else {
|
||||
arena
|
||||
.alloc(loc_expr.value)
|
||||
.with_spaces_before(spaces, loc_expr.region)
|
||||
}
|
||||
}),
|
||||
and(spaces(), specialize_err_ref(ERecord::Expr, loc_expr(false))),
|
||||
|arena: &'a bumpalo::Bump, (spaces, loc_expr)| {
|
||||
if spaces.is_empty() {
|
||||
loc_expr
|
||||
} else {
|
||||
arena
|
||||
.alloc(loc_expr.value)
|
||||
.with_spaces_before(spaces, loc_expr.region)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -3686,13 +3592,11 @@ fn record_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
|
||||
record_update_help(arena, update, record.fields)
|
||||
}
|
||||
Some((mapper, RecordHelpPrefix::Mapper)) => {
|
||||
new_record_builder_help(arena, mapper, record.fields)
|
||||
record_builder_help(arena, mapper, record.fields)
|
||||
}
|
||||
None => {
|
||||
let special_field_found = record.fields.iter().find_map(|field| {
|
||||
if field.value.is_apply_value() {
|
||||
Some(old_record_builder_help(arena, record.fields))
|
||||
} else if field.value.is_ignored_value() {
|
||||
if field.value.is_ignored_value() {
|
||||
Some(Err(EExpr::RecordUpdateIgnoredField(field.region)))
|
||||
} else {
|
||||
None
|
||||
@ -3701,7 +3605,7 @@ fn record_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
|
||||
|
||||
special_field_found.unwrap_or_else(|| {
|
||||
let fields = record.fields.map_items(arena, |loc_field| {
|
||||
loc_field.map(|field| field.to_assigned_field(arena).unwrap())
|
||||
loc_field.map(|field| field.to_assigned_field(arena))
|
||||
});
|
||||
|
||||
Ok(Expr::Record(fields))
|
||||
@ -3728,14 +3632,13 @@ fn record_update_help<'a>(
|
||||
) -> Result<Expr<'a>, EExpr<'a>> {
|
||||
let result = fields.map_items_result(arena, |loc_field| {
|
||||
match loc_field.value.to_assigned_field(arena) {
|
||||
Ok(AssignedField::IgnoredValue(_, _, _)) => {
|
||||
AssignedField::IgnoredValue(_, _, _) => {
|
||||
Err(EExpr::RecordUpdateIgnoredField(loc_field.region))
|
||||
}
|
||||
Ok(builder_field) => Ok(Loc {
|
||||
builder_field => Ok(Loc {
|
||||
region: loc_field.region,
|
||||
value: builder_field,
|
||||
}),
|
||||
Err(FoundApplyValue) => Err(EExpr::RecordUpdateOldBuilderField(loc_field.region)),
|
||||
}
|
||||
});
|
||||
|
||||
@ -3745,19 +3648,18 @@ fn record_update_help<'a>(
|
||||
})
|
||||
}
|
||||
|
||||
fn new_record_builder_help<'a>(
|
||||
fn record_builder_help<'a>(
|
||||
arena: &'a Bump,
|
||||
mapper: Loc<Expr<'a>>,
|
||||
fields: Collection<'a, Loc<RecordField<'a>>>,
|
||||
) -> Result<Expr<'a>, EExpr<'a>> {
|
||||
let result = fields.map_items_result(arena, |loc_field| {
|
||||
match loc_field.value.to_assigned_field(arena) {
|
||||
Ok(builder_field) => Ok(Loc {
|
||||
region: loc_field.region,
|
||||
value: builder_field,
|
||||
}),
|
||||
Err(FoundApplyValue) => Err(EExpr::RecordBuilderOldBuilderField(loc_field.region)),
|
||||
}
|
||||
let builder_field = loc_field.value.to_assigned_field(arena);
|
||||
|
||||
Ok(Loc {
|
||||
region: loc_field.region,
|
||||
value: builder_field,
|
||||
})
|
||||
});
|
||||
|
||||
result.map(|fields| Expr::RecordBuilder {
|
||||
@ -3766,28 +3668,6 @@ fn new_record_builder_help<'a>(
|
||||
})
|
||||
}
|
||||
|
||||
fn old_record_builder_help<'a>(
|
||||
arena: &'a Bump,
|
||||
fields: Collection<'a, Loc<RecordField<'a>>>,
|
||||
) -> Result<Expr<'a>, EExpr<'a>> {
|
||||
let result = fields.map_items_result(arena, |loc_field| {
|
||||
match loc_field.value.to_builder_field(arena) {
|
||||
Ok(builder_field) => Ok(Loc {
|
||||
region: loc_field.region,
|
||||
value: builder_field,
|
||||
}),
|
||||
Err(NotOldBuilderFieldValue::FoundOptionalValue) => {
|
||||
Err(EExpr::OptionalValueInOldRecordBuilder(loc_field.region))
|
||||
}
|
||||
Err(NotOldBuilderFieldValue::FoundIgnoredValue) => {
|
||||
Err(EExpr::IgnoredValueInOldRecordBuilder(loc_field.region))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
result.map(Expr::OldRecordBuilder)
|
||||
}
|
||||
|
||||
fn apply_expr_access_chain<'a>(
|
||||
arena: &'a Bump,
|
||||
value: Expr<'a>,
|
||||
|
@ -1,905 +0,0 @@
|
||||
use crate::ast::{Collection, CommentOrNewline, Defs, Header, Module, Spaced, Spaces};
|
||||
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
|
||||
use crate::expr::merge_spaces;
|
||||
use crate::header::{
|
||||
package_entry, package_name, AppHeader, ExposedName, ExposesKeyword, HostedHeader,
|
||||
ImportsCollection, ImportsEntry, ImportsKeyword, ImportsKeywordItem, Keyword, KeywordItem,
|
||||
ModuleHeader, ModuleName, ModuleParams, PackageEntry, PackageHeader, PackagesKeyword,
|
||||
PlatformHeader, PlatformRequires, ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword,
|
||||
TypedIdent,
|
||||
};
|
||||
use crate::ident::{self, lowercase_ident, unqualified_ident, UppercaseIdent};
|
||||
use crate::parser::Progress::{self, *};
|
||||
use crate::parser::{
|
||||
and, backtrackable, byte, collection_trailing_sep_e, increment_min_indent, loc, map,
|
||||
map_with_arena, optional, reset_min_indent, skip_first, skip_second, specialize_err, succeed,
|
||||
two_bytes, zero_or_more, EExposes, EHeader, EImports, EPackages, EParams, EProvides, ERequires,
|
||||
ETypedIdent, Parser, SourceError, SpaceProblem, SyntaxError,
|
||||
};
|
||||
use crate::pattern::record_pattern_fields;
|
||||
use crate::state::State;
|
||||
use crate::string_literal::{self, parse_str_literal};
|
||||
use crate::type_annotation;
|
||||
use roc_region::all::{Loc, Position, Region};
|
||||
|
||||
fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> {
|
||||
|_arena, state: State<'a>, _min_indent: u32| {
|
||||
if state.has_reached_end() {
|
||||
Ok((NoProgress, (), state))
|
||||
} else {
|
||||
Err((NoProgress, SyntaxError::NotEndOfFile(state.pos())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_module_defs<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
state: State<'a>,
|
||||
defs: Defs<'a>,
|
||||
) -> Result<Defs<'a>, SyntaxError<'a>> {
|
||||
let min_indent = 0;
|
||||
match crate::expr::parse_top_level_defs(arena, state.clone(), defs) {
|
||||
Ok((_, defs, state)) => match end_of_file().parse(arena, state, min_indent) {
|
||||
Ok(_) => Ok(defs),
|
||||
Err((_, fail)) => Err(fail),
|
||||
},
|
||||
Err((_, fail)) => Err(SyntaxError::Expr(fail, state.pos())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_header<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
state: State<'a>,
|
||||
) -> Result<(Module<'a>, State<'a>), SourceError<'a, EHeader<'a>>> {
|
||||
let min_indent = 0;
|
||||
match header().parse(arena, state.clone(), min_indent) {
|
||||
Ok((_, module, state)) => Ok((module, state)),
|
||||
Err((_, fail)) => Err(SourceError::new(fail, &state)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
|
||||
use crate::parser::keyword;
|
||||
|
||||
record!(Module {
|
||||
comments: space0_e(EHeader::IndentStart),
|
||||
header: one_of![
|
||||
map(
|
||||
skip_first(
|
||||
keyword("module", EHeader::Start),
|
||||
increment_min_indent(module_header())
|
||||
),
|
||||
Header::Module
|
||||
),
|
||||
map(
|
||||
skip_first(
|
||||
keyword("interface", EHeader::Start),
|
||||
increment_min_indent(interface_header())
|
||||
),
|
||||
Header::Module
|
||||
),
|
||||
map(
|
||||
skip_first(
|
||||
keyword("app", EHeader::Start),
|
||||
increment_min_indent(one_of![app_header(), old_app_header()])
|
||||
),
|
||||
Header::App
|
||||
),
|
||||
map(
|
||||
skip_first(
|
||||
keyword("package", EHeader::Start),
|
||||
increment_min_indent(one_of![package_header(), old_package_header()])
|
||||
),
|
||||
Header::Package
|
||||
),
|
||||
map(
|
||||
skip_first(
|
||||
keyword("platform", EHeader::Start),
|
||||
increment_min_indent(platform_header())
|
||||
),
|
||||
Header::Platform
|
||||
),
|
||||
map(
|
||||
skip_first(
|
||||
keyword("hosted", EHeader::Start),
|
||||
increment_min_indent(hosted_header())
|
||||
),
|
||||
Header::Hosted
|
||||
),
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn module_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
|
||||
record!(ModuleHeader {
|
||||
after_keyword: space0_e(EHeader::IndentStart),
|
||||
params: optional(specialize_err(EHeader::Params, module_params())),
|
||||
exposes: specialize_err(EHeader::Exposes, exposes_list()),
|
||||
interface_imports: succeed(None)
|
||||
})
|
||||
.trace("module_header")
|
||||
}
|
||||
|
||||
fn module_params<'a>() -> impl Parser<'a, ModuleParams<'a>, EParams<'a>> {
|
||||
record!(ModuleParams {
|
||||
params: specialize_err(EParams::Pattern, record_pattern_fields()),
|
||||
before_arrow: skip_second(
|
||||
space0_e(EParams::BeforeArrow),
|
||||
loc(two_bytes(b'-', b'>', EParams::Arrow))
|
||||
),
|
||||
after_arrow: space0_e(EParams::AfterArrow),
|
||||
})
|
||||
}
|
||||
|
||||
// TODO does this need to be a macro?
|
||||
macro_rules! merge_n_spaces {
|
||||
($arena:expr, $($slice:expr),*) => {
|
||||
{
|
||||
let mut merged = bumpalo::collections::Vec::with_capacity_in(0 $(+ $slice.len())*, $arena);
|
||||
$(merged.extend_from_slice($slice);)*
|
||||
merged.into_bump_slice()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Parse old interface headers so we can format them into module headers
|
||||
#[inline(always)]
|
||||
fn interface_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
|
||||
let after_keyword = map_with_arena(
|
||||
and(
|
||||
skip_second(
|
||||
space0_e(EHeader::IndentStart),
|
||||
loc(module_name_help(EHeader::ModuleName)),
|
||||
),
|
||||
specialize_err(EHeader::Exposes, exposes_kw()),
|
||||
),
|
||||
|arena: &'a bumpalo::Bump,
|
||||
(before_name, kw): (&'a [CommentOrNewline<'a>], Spaces<'a, ExposesKeyword>)| {
|
||||
merge_n_spaces!(arena, before_name, kw.before, kw.after)
|
||||
},
|
||||
);
|
||||
|
||||
record!(ModuleHeader {
|
||||
after_keyword: after_keyword,
|
||||
params: succeed(None),
|
||||
exposes: specialize_err(EHeader::Exposes, exposes_list()).trace("exposes_list"),
|
||||
interface_imports: map(
|
||||
specialize_err(EHeader::Imports, imports()),
|
||||
imports_none_if_empty
|
||||
)
|
||||
.trace("imports"),
|
||||
})
|
||||
.trace("interface_header")
|
||||
}
|
||||
|
||||
fn imports_none_if_empty(value: ImportsKeywordItem<'_>) -> Option<ImportsKeywordItem<'_>> {
|
||||
if value.item.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn hosted_header<'a>() -> impl Parser<'a, HostedHeader<'a>, EHeader<'a>> {
|
||||
record!(HostedHeader {
|
||||
before_name: space0_e(EHeader::IndentStart),
|
||||
name: loc(module_name_help(EHeader::ModuleName)),
|
||||
exposes: specialize_err(EHeader::Exposes, exposes_values_kw()),
|
||||
imports: specialize_err(EHeader::Imports, imports()),
|
||||
})
|
||||
.trace("hosted_header")
|
||||
}
|
||||
|
||||
fn chomp_module_name(buffer: &[u8]) -> Result<&str, Progress> {
|
||||
use encode_unicode::CharExt;
|
||||
|
||||
let mut chomped = 0;
|
||||
|
||||
if let Ok((first_letter, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
|
||||
if first_letter.is_uppercase() {
|
||||
chomped += width;
|
||||
} else {
|
||||
return Err(Progress::NoProgress);
|
||||
}
|
||||
}
|
||||
|
||||
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
|
||||
// After the first character, only these are allowed:
|
||||
//
|
||||
// * Unicode alphabetic chars - you might include `鹏` if that's clear to your readers
|
||||
// * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric()
|
||||
// * A '.' separating module parts
|
||||
if ch.is_alphabetic() || ch.is_ascii_digit() {
|
||||
chomped += width;
|
||||
} else if ch == '.' {
|
||||
chomped += width;
|
||||
|
||||
if let Ok((first_letter, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
|
||||
if first_letter.is_uppercase() {
|
||||
chomped += width;
|
||||
} else if first_letter == '{' {
|
||||
// the .{ starting a `Foo.{ bar, baz }` importing clauses
|
||||
chomped -= width;
|
||||
break;
|
||||
} else {
|
||||
return Err(Progress::MadeProgress);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we're done
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let name = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
|
||||
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, ()> {
|
||||
|_, mut state: State<'a>, _min_indent: u32| match chomp_module_name(state.bytes()) {
|
||||
Ok(name) => {
|
||||
let width = name.len();
|
||||
state = state.advance(width);
|
||||
|
||||
Ok((MadeProgress, ModuleName::new(name), state))
|
||||
}
|
||||
Err(progress) => Err((progress, ())),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
|
||||
record!(AppHeader {
|
||||
before_provides: space0_e(EHeader::IndentStart),
|
||||
provides: specialize_err(EHeader::Exposes, exposes_list()),
|
||||
before_packages: space0_e(EHeader::IndentStart),
|
||||
packages: specialize_err(EHeader::Packages, loc(packages_collection())),
|
||||
old_imports: succeed(None),
|
||||
old_provides_to_new_package: succeed(None),
|
||||
})
|
||||
.trace("app_header")
|
||||
}
|
||||
|
||||
struct OldAppHeader<'a> {
|
||||
pub before_name: &'a [CommentOrNewline<'a>],
|
||||
pub packages: Option<Loc<OldAppPackages<'a>>>,
|
||||
pub imports: Option<KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>>,
|
||||
pub provides: ProvidesTo<'a>,
|
||||
}
|
||||
|
||||
type OldAppPackages<'a> =
|
||||
KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>;
|
||||
|
||||
#[inline(always)]
|
||||
fn old_app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
|
||||
let old = record!(OldAppHeader {
|
||||
before_name: skip_second(
|
||||
space0_e(EHeader::IndentStart),
|
||||
loc(crate::parser::specialize_err(
|
||||
EHeader::AppName,
|
||||
string_literal::parse_str_literal()
|
||||
))
|
||||
),
|
||||
packages: optional(specialize_err(EHeader::Packages, loc(packages()))),
|
||||
imports: optional(specialize_err(EHeader::Imports, imports())),
|
||||
provides: specialize_err(EHeader::Provides, provides_to()),
|
||||
});
|
||||
|
||||
map_with_arena(old, |arena: &'a bumpalo::Bump, old: OldAppHeader<'a>| {
|
||||
let mut before_packages: &'a [CommentOrNewline] = &[];
|
||||
|
||||
let packages = match old.packages {
|
||||
Some(packages) => {
|
||||
before_packages = merge_spaces(
|
||||
arena,
|
||||
packages.value.keyword.before,
|
||||
packages.value.keyword.after,
|
||||
);
|
||||
|
||||
if let To::ExistingPackage(platform_shorthand) = old.provides.to.value {
|
||||
packages.map(|coll| {
|
||||
coll.item.map_items(arena, |loc_spaced_pkg| {
|
||||
if loc_spaced_pkg.value.item().shorthand == platform_shorthand {
|
||||
loc_spaced_pkg.map(|spaced_pkg| {
|
||||
spaced_pkg.map(arena, |pkg| {
|
||||
let mut new_pkg = *pkg;
|
||||
new_pkg.platform_marker = Some(merge_spaces(
|
||||
arena,
|
||||
old.provides.to_keyword.before,
|
||||
old.provides.to_keyword.after,
|
||||
));
|
||||
new_pkg
|
||||
})
|
||||
})
|
||||
} else {
|
||||
*loc_spaced_pkg
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
packages.map(|kw| kw.item)
|
||||
}
|
||||
}
|
||||
None => Loc {
|
||||
region: Region::zero(),
|
||||
value: Collection::empty(),
|
||||
},
|
||||
};
|
||||
|
||||
let provides = match old.provides.types {
|
||||
Some(types) => {
|
||||
let mut combined_items = bumpalo::collections::Vec::with_capacity_in(
|
||||
old.provides.entries.items.len() + types.items.len(),
|
||||
arena,
|
||||
);
|
||||
|
||||
combined_items.extend_from_slice(old.provides.entries.items);
|
||||
|
||||
for loc_spaced_type_ident in types.items {
|
||||
combined_items.push(loc_spaced_type_ident.map(|spaced_type_ident| {
|
||||
spaced_type_ident.map(arena, |type_ident| {
|
||||
ExposedName::new(From::from(*type_ident))
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
let value_comments = old.provides.entries.final_comments();
|
||||
let type_comments = types.final_comments();
|
||||
|
||||
let mut combined_comments = bumpalo::collections::Vec::with_capacity_in(
|
||||
value_comments.len() + type_comments.len(),
|
||||
arena,
|
||||
);
|
||||
combined_comments.extend_from_slice(value_comments);
|
||||
combined_comments.extend_from_slice(type_comments);
|
||||
|
||||
Collection::with_items_and_comments(
|
||||
arena,
|
||||
combined_items.into_bump_slice(),
|
||||
combined_comments.into_bump_slice(),
|
||||
)
|
||||
}
|
||||
None => old.provides.entries,
|
||||
};
|
||||
|
||||
AppHeader {
|
||||
before_provides: merge_spaces(
|
||||
arena,
|
||||
old.before_name,
|
||||
old.provides.provides_keyword.before,
|
||||
),
|
||||
provides,
|
||||
before_packages: merge_spaces(
|
||||
arena,
|
||||
before_packages,
|
||||
old.provides.provides_keyword.after,
|
||||
),
|
||||
packages,
|
||||
old_imports: old.imports.and_then(imports_none_if_empty),
|
||||
old_provides_to_new_package: match old.provides.to.value {
|
||||
To::NewPackage(new_pkg) => Some(new_pkg),
|
||||
To::ExistingPackage(_) => None,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn package_header<'a>() -> impl Parser<'a, PackageHeader<'a>, EHeader<'a>> {
|
||||
record!(PackageHeader {
|
||||
before_exposes: space0_e(EHeader::IndentStart),
|
||||
exposes: specialize_err(EHeader::Exposes, exposes_module_collection()),
|
||||
before_packages: space0_e(EHeader::IndentStart),
|
||||
packages: specialize_err(EHeader::Packages, loc(packages_collection())),
|
||||
})
|
||||
.trace("package_header")
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
struct OldPackageHeader<'a> {
|
||||
before_name: &'a [CommentOrNewline<'a>],
|
||||
exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>>,
|
||||
packages:
|
||||
Loc<KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>>,
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn old_package_header<'a>() -> impl Parser<'a, PackageHeader<'a>, EHeader<'a>> {
|
||||
map_with_arena(
|
||||
record!(OldPackageHeader {
|
||||
before_name: skip_second(
|
||||
space0_e(EHeader::IndentStart),
|
||||
specialize_err(EHeader::PackageName, package_name())
|
||||
),
|
||||
exposes: specialize_err(EHeader::Exposes, exposes_modules()),
|
||||
packages: specialize_err(EHeader::Packages, loc(packages())),
|
||||
}),
|
||||
|arena: &'a bumpalo::Bump, old: OldPackageHeader<'a>| {
|
||||
let before_exposes = merge_n_spaces!(
|
||||
arena,
|
||||
old.before_name,
|
||||
old.exposes.keyword.before,
|
||||
old.exposes.keyword.after
|
||||
);
|
||||
let before_packages = merge_spaces(
|
||||
arena,
|
||||
old.packages.value.keyword.before,
|
||||
old.packages.value.keyword.after,
|
||||
);
|
||||
|
||||
PackageHeader {
|
||||
before_exposes,
|
||||
exposes: old.exposes.item,
|
||||
before_packages,
|
||||
packages: old.packages.map(|kw| kw.item),
|
||||
}
|
||||
},
|
||||
)
|
||||
.trace("old_package_header")
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
|
||||
record!(PlatformHeader {
|
||||
before_name: space0_e(EHeader::IndentStart),
|
||||
name: loc(specialize_err(EHeader::PlatformName, package_name())),
|
||||
requires: specialize_err(EHeader::Requires, requires()),
|
||||
exposes: specialize_err(EHeader::Exposes, exposes_modules()),
|
||||
packages: specialize_err(EHeader::Packages, packages()),
|
||||
imports: specialize_err(EHeader::Imports, imports()),
|
||||
provides: specialize_err(EHeader::Provides, provides_exposed()),
|
||||
})
|
||||
.trace("platform_header")
|
||||
}
|
||||
|
||||
fn provides_to_package<'a>() -> impl Parser<'a, To<'a>, EProvides<'a>> {
|
||||
one_of![
|
||||
specialize_err(
|
||||
|_, pos| EProvides::Identifier(pos),
|
||||
map(lowercase_ident(), To::ExistingPackage)
|
||||
),
|
||||
specialize_err(EProvides::Package, map(package_name(), To::NewPackage))
|
||||
]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn provides_to<'a>() -> impl Parser<'a, ProvidesTo<'a>, EProvides<'a>> {
|
||||
record!(ProvidesTo {
|
||||
provides_keyword: spaces_around_keyword(
|
||||
ProvidesKeyword,
|
||||
EProvides::Provides,
|
||||
EProvides::IndentProvides,
|
||||
EProvides::IndentListStart
|
||||
),
|
||||
entries: collection_trailing_sep_e(
|
||||
byte(b'[', EProvides::ListStart),
|
||||
exposes_entry(EProvides::Identifier),
|
||||
byte(b',', EProvides::ListEnd),
|
||||
byte(b']', EProvides::ListEnd),
|
||||
Spaced::SpaceBefore
|
||||
),
|
||||
types: optional(backtrackable(provides_types())),
|
||||
to_keyword: spaces_around_keyword(
|
||||
ToKeyword,
|
||||
EProvides::To,
|
||||
EProvides::IndentTo,
|
||||
EProvides::IndentListStart
|
||||
),
|
||||
to: loc(provides_to_package()),
|
||||
})
|
||||
.trace("provides_to")
|
||||
}
|
||||
|
||||
fn provides_exposed<'a>() -> impl Parser<
|
||||
'a,
|
||||
KeywordItem<'a, ProvidesKeyword, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>,
|
||||
EProvides<'a>,
|
||||
> {
|
||||
record!(KeywordItem {
|
||||
keyword: spaces_around_keyword(
|
||||
ProvidesKeyword,
|
||||
EProvides::Provides,
|
||||
EProvides::IndentProvides,
|
||||
EProvides::IndentListStart
|
||||
),
|
||||
item: collection_trailing_sep_e(
|
||||
byte(b'[', EProvides::ListStart),
|
||||
exposes_entry(EProvides::Identifier),
|
||||
byte(b',', EProvides::ListEnd),
|
||||
byte(b']', EProvides::ListEnd),
|
||||
Spaced::SpaceBefore
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn provides_types<'a>(
|
||||
) -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, UppercaseIdent<'a>>>>, EProvides<'a>> {
|
||||
skip_first(
|
||||
// We only support spaces here, not newlines, because this is not intended
|
||||
// to be the design forever. Someday it will hopefully work like Elm,
|
||||
// where platform authors can provide functions like Browser.sandbox which
|
||||
// present an API based on ordinary-looking type variables.
|
||||
zero_or_more(byte(
|
||||
b' ',
|
||||
// HACK: If this errors, EProvides::Provides is not an accurate reflection
|
||||
// of what went wrong. However, this is both skipped and zero_or_more,
|
||||
// so this error should never be visible to anyone in practice!
|
||||
EProvides::Provides,
|
||||
)),
|
||||
collection_trailing_sep_e(
|
||||
byte(b'{', EProvides::ListStart),
|
||||
provides_type_entry(EProvides::Identifier),
|
||||
byte(b',', EProvides::ListEnd),
|
||||
byte(b'}', EProvides::ListEnd),
|
||||
Spaced::SpaceBefore,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn provides_type_entry<'a, F, E>(
|
||||
to_expectation: F,
|
||||
) -> impl Parser<'a, Loc<Spaced<'a, UppercaseIdent<'a>>>, E>
|
||||
where
|
||||
F: Fn(Position) -> E,
|
||||
F: Copy,
|
||||
E: 'a,
|
||||
{
|
||||
loc(map(
|
||||
specialize_err(move |_, pos| to_expectation(pos), ident::uppercase()),
|
||||
Spaced::Item,
|
||||
))
|
||||
}
|
||||
|
||||
fn exposes_entry<'a, F, E>(
|
||||
to_expectation: F,
|
||||
) -> impl Parser<'a, Loc<Spaced<'a, ExposedName<'a>>>, E>
|
||||
where
|
||||
F: Fn(Position) -> E,
|
||||
F: Copy,
|
||||
E: 'a,
|
||||
{
|
||||
loc(map(
|
||||
specialize_err(move |_, pos| to_expectation(pos), unqualified_ident()),
|
||||
|n| Spaced::Item(ExposedName::new(n)),
|
||||
))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn requires<'a>(
|
||||
) -> impl Parser<'a, KeywordItem<'a, RequiresKeyword, PlatformRequires<'a>>, ERequires<'a>> {
|
||||
record!(KeywordItem {
|
||||
keyword: spaces_around_keyword(
|
||||
RequiresKeyword,
|
||||
ERequires::Requires,
|
||||
ERequires::IndentRequires,
|
||||
ERequires::IndentListStart
|
||||
),
|
||||
item: platform_requires(),
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn platform_requires<'a>() -> impl Parser<'a, PlatformRequires<'a>, ERequires<'a>> {
|
||||
record!(PlatformRequires {
|
||||
rigids: skip_second(requires_rigids(), space0_e(ERequires::ListStart)),
|
||||
signature: requires_typed_ident()
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn requires_rigids<'a>(
|
||||
) -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, UppercaseIdent<'a>>>>, ERequires<'a>> {
|
||||
collection_trailing_sep_e(
|
||||
byte(b'{', ERequires::ListStart),
|
||||
specialize_err(
|
||||
|_, pos| ERequires::Rigid(pos),
|
||||
loc(map(ident::uppercase(), Spaced::Item)),
|
||||
),
|
||||
byte(b',', ERequires::ListEnd),
|
||||
byte(b'}', ERequires::ListEnd),
|
||||
Spaced::SpaceBefore,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn requires_typed_ident<'a>() -> impl Parser<'a, Loc<Spaced<'a, TypedIdent<'a>>>, ERequires<'a>> {
|
||||
skip_first(
|
||||
byte(b'{', ERequires::ListStart),
|
||||
skip_second(
|
||||
reset_min_indent(space0_around_ee(
|
||||
specialize_err(ERequires::TypedIdent, loc(typed_ident())),
|
||||
ERequires::ListStart,
|
||||
ERequires::ListEnd,
|
||||
)),
|
||||
byte(b'}', ERequires::ListStart),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn exposes_values_kw<'a>() -> impl Parser<
|
||||
'a,
|
||||
KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>,
|
||||
EExposes,
|
||||
> {
|
||||
record!(KeywordItem {
|
||||
keyword: exposes_kw(),
|
||||
item: exposes_list()
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn exposes_kw<'a>() -> impl Parser<'a, Spaces<'a, ExposesKeyword>, EExposes> {
|
||||
spaces_around_keyword(
|
||||
ExposesKeyword,
|
||||
EExposes::Exposes,
|
||||
EExposes::IndentExposes,
|
||||
EExposes::IndentListStart,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn exposes_list<'a>() -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>, EExposes>
|
||||
{
|
||||
collection_trailing_sep_e(
|
||||
byte(b'[', EExposes::ListStart),
|
||||
exposes_entry(EExposes::Identifier),
|
||||
byte(b',', EExposes::ListEnd),
|
||||
byte(b']', EExposes::ListEnd),
|
||||
Spaced::SpaceBefore,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn spaces_around_keyword<'a, K: Keyword, E>(
|
||||
keyword_item: K,
|
||||
expectation: fn(Position) -> E,
|
||||
indent_problem1: fn(Position) -> E,
|
||||
indent_problem2: fn(Position) -> E,
|
||||
) -> impl Parser<'a, Spaces<'a, K>, E>
|
||||
where
|
||||
E: 'a + SpaceProblem,
|
||||
{
|
||||
map(
|
||||
and(
|
||||
skip_second(
|
||||
// parse any leading space before the keyword
|
||||
backtrackable(space0_e(indent_problem1)),
|
||||
// parse the keyword
|
||||
crate::parser::keyword(K::KEYWORD, expectation),
|
||||
),
|
||||
// parse the trailing space
|
||||
space0_e(indent_problem2),
|
||||
),
|
||||
move |(before, after)| Spaces {
|
||||
before,
|
||||
item: keyword_item,
|
||||
after,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn exposes_modules<'a>() -> impl Parser<
|
||||
'a,
|
||||
KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>>,
|
||||
EExposes,
|
||||
> {
|
||||
record!(KeywordItem {
|
||||
keyword: spaces_around_keyword(
|
||||
ExposesKeyword,
|
||||
EExposes::Exposes,
|
||||
EExposes::IndentExposes,
|
||||
EExposes::IndentListStart
|
||||
),
|
||||
item: exposes_module_collection(),
|
||||
})
|
||||
}
|
||||
|
||||
fn exposes_module_collection<'a>(
|
||||
) -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>, EExposes> {
|
||||
collection_trailing_sep_e(
|
||||
byte(b'[', EExposes::ListStart),
|
||||
exposes_module(EExposes::Identifier),
|
||||
byte(b',', EExposes::ListEnd),
|
||||
byte(b']', EExposes::ListEnd),
|
||||
Spaced::SpaceBefore,
|
||||
)
|
||||
}
|
||||
|
||||
fn exposes_module<'a, F, E>(
|
||||
to_expectation: F,
|
||||
) -> impl Parser<'a, Loc<Spaced<'a, ModuleName<'a>>>, E>
|
||||
where
|
||||
F: Fn(Position) -> E,
|
||||
F: Copy,
|
||||
E: 'a,
|
||||
{
|
||||
loc(map(
|
||||
specialize_err(move |_, pos| to_expectation(pos), module_name()),
|
||||
Spaced::Item,
|
||||
))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn packages<'a>() -> impl Parser<
|
||||
'a,
|
||||
KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>,
|
||||
EPackages<'a>,
|
||||
> {
|
||||
record!(KeywordItem {
|
||||
keyword: packages_kw(),
|
||||
item: packages_collection()
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn packages_kw<'a>() -> impl Parser<'a, Spaces<'a, PackagesKeyword>, EPackages<'a>> {
|
||||
spaces_around_keyword(
|
||||
PackagesKeyword,
|
||||
EPackages::Packages,
|
||||
EPackages::IndentPackages,
|
||||
EPackages::IndentListStart,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn packages_collection<'a>(
|
||||
) -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>, EPackages<'a>> {
|
||||
collection_trailing_sep_e(
|
||||
byte(b'{', EPackages::ListStart),
|
||||
specialize_err(EPackages::PackageEntry, loc(package_entry())),
|
||||
byte(b',', EPackages::ListEnd),
|
||||
byte(b'}', EPackages::ListEnd),
|
||||
Spaced::SpaceBefore,
|
||||
)
|
||||
}
|
||||
|
||||
fn imports<'a>() -> impl Parser<
|
||||
'a,
|
||||
KeywordItem<'a, ImportsKeyword, Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>>,
|
||||
EImports,
|
||||
> {
|
||||
record!(KeywordItem {
|
||||
keyword: spaces_around_keyword(
|
||||
ImportsKeyword,
|
||||
EImports::Imports,
|
||||
EImports::IndentImports,
|
||||
EImports::IndentListStart
|
||||
),
|
||||
item: collection_trailing_sep_e(
|
||||
byte(b'[', EImports::ListStart),
|
||||
loc(imports_entry()),
|
||||
byte(b',', EImports::ListEnd),
|
||||
byte(b']', EImports::ListEnd),
|
||||
Spaced::SpaceBefore
|
||||
)
|
||||
})
|
||||
.trace("imports")
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn typed_ident<'a>() -> impl Parser<'a, Spaced<'a, TypedIdent<'a>>, ETypedIdent<'a>> {
|
||||
// e.g.
|
||||
//
|
||||
// printLine : Str -> Task {} *
|
||||
map(
|
||||
and(
|
||||
and(
|
||||
loc(specialize_err(
|
||||
|_, pos| ETypedIdent::Identifier(pos),
|
||||
lowercase_ident(),
|
||||
)),
|
||||
space0_e(ETypedIdent::IndentHasType),
|
||||
),
|
||||
skip_first(
|
||||
byte(b':', ETypedIdent::HasType),
|
||||
space0_before_e(
|
||||
specialize_err(
|
||||
ETypedIdent::Type,
|
||||
reset_min_indent(type_annotation::located(true)),
|
||||
),
|
||||
ETypedIdent::IndentType,
|
||||
),
|
||||
),
|
||||
),
|
||||
|((ident, spaces_before_colon), ann)| {
|
||||
Spaced::Item(TypedIdent {
|
||||
ident,
|
||||
spaces_before_colon,
|
||||
ann,
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn shortname<'a>() -> impl Parser<'a, &'a str, EImports> {
|
||||
specialize_err(|_, pos| EImports::Shorthand(pos), lowercase_ident())
|
||||
}
|
||||
|
||||
pub fn module_name_help<'a, F, E>(to_expectation: F) -> impl Parser<'a, ModuleName<'a>, E>
|
||||
where
|
||||
F: Fn(Position) -> E,
|
||||
E: 'a,
|
||||
F: 'a,
|
||||
{
|
||||
specialize_err(move |_, pos| to_expectation(pos), module_name())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn imports_entry<'a>() -> impl Parser<'a, Spaced<'a, ImportsEntry<'a>>, EImports> {
|
||||
type Temp<'a> = (
|
||||
(Option<&'a str>, ModuleName<'a>),
|
||||
Option<Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>,
|
||||
);
|
||||
|
||||
let spaced_import = |((opt_shortname, module_name), opt_values): Temp<'a>| {
|
||||
let exposed_values = opt_values.unwrap_or_else(Collection::empty);
|
||||
|
||||
let entry = match opt_shortname {
|
||||
Some(shortname) => ImportsEntry::Package(shortname, module_name, exposed_values),
|
||||
|
||||
None => ImportsEntry::Module(module_name, exposed_values),
|
||||
};
|
||||
|
||||
Spaced::Item(entry)
|
||||
};
|
||||
|
||||
one_of!(
|
||||
map(
|
||||
and(
|
||||
and(
|
||||
// e.g. `pf.`
|
||||
optional(backtrackable(skip_second(
|
||||
shortname(),
|
||||
byte(b'.', EImports::ShorthandDot)
|
||||
))),
|
||||
// e.g. `Task`
|
||||
module_name_help(EImports::ModuleName)
|
||||
),
|
||||
// e.g. `.{ Task, after}`
|
||||
optional(skip_first(
|
||||
byte(b'.', EImports::ExposingDot),
|
||||
collection_trailing_sep_e(
|
||||
byte(b'{', EImports::SetStart),
|
||||
exposes_entry(EImports::Identifier),
|
||||
byte(b',', EImports::SetEnd),
|
||||
byte(b'}', EImports::SetEnd),
|
||||
Spaced::SpaceBefore
|
||||
)
|
||||
))
|
||||
),
|
||||
spaced_import
|
||||
)
|
||||
.trace("normal_import"),
|
||||
map(
|
||||
and(
|
||||
and(
|
||||
// e.g. "filename"
|
||||
// TODO: str literal allows for multiline strings. We probably don't want that for file names.
|
||||
specialize_err(|_, pos| EImports::StrLiteral(pos), parse_str_literal()),
|
||||
// e.g. as
|
||||
and(
|
||||
and(
|
||||
space0_e(EImports::AsKeyword),
|
||||
two_bytes(b'a', b's', EImports::AsKeyword)
|
||||
),
|
||||
space0_e(EImports::AsKeyword)
|
||||
)
|
||||
),
|
||||
// e.g. file : Str
|
||||
specialize_err(|_, pos| EImports::TypedIdent(pos), typed_ident())
|
||||
),
|
||||
|((file_name, _), typed_ident)| {
|
||||
// TODO: look at blacking block strings during parsing.
|
||||
Spaced::Item(ImportsEntry::IngestedFile(file_name, typed_ident))
|
||||
}
|
||||
)
|
||||
.trace("ingest_file_import")
|
||||
)
|
||||
.trace("imports_entry")
|
||||
}
|
@ -8,9 +8,9 @@ use crate::{
|
||||
AbilityImpls, AbilityMember, AssignedField, Collection, Defs, Expr, FullAst, Header,
|
||||
Implements, ImplementsAbilities, ImplementsAbility, ImplementsClause, ImportAlias,
|
||||
ImportAsKeyword, ImportExposingKeyword, ImportedModuleName, IngestedFileAnnotation,
|
||||
IngestedFileImport, ModuleImport, ModuleImportParams, OldRecordBuilderField, Pattern,
|
||||
PatternAs, Spaced, Spaces, SpacesBefore, StrLiteral, StrSegment, Tag, TypeAnnotation,
|
||||
TypeDef, TypeHeader, ValueDef, WhenBranch,
|
||||
IngestedFileImport, ModuleImport, ModuleImportParams, Pattern, PatternAs, Spaced, Spaces,
|
||||
SpacesBefore, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
|
||||
WhenBranch,
|
||||
},
|
||||
header::{
|
||||
AppHeader, ExposedName, ExposesKeyword, HostedHeader, ImportsEntry, ImportsKeyword,
|
||||
@ -562,30 +562,6 @@ impl<'a, T: Normalize<'a> + Copy + std::fmt::Debug> Normalize<'a> for AssignedFi
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Normalize<'a> for OldRecordBuilderField<'a> {
|
||||
fn normalize(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
OldRecordBuilderField::Value(a, _, c) => OldRecordBuilderField::Value(
|
||||
a.normalize(arena),
|
||||
&[],
|
||||
arena.alloc(c.normalize(arena)),
|
||||
),
|
||||
OldRecordBuilderField::ApplyValue(a, _, _, c) => OldRecordBuilderField::ApplyValue(
|
||||
a.normalize(arena),
|
||||
&[],
|
||||
&[],
|
||||
arena.alloc(c.normalize(arena)),
|
||||
),
|
||||
OldRecordBuilderField::LabelOnly(a) => {
|
||||
OldRecordBuilderField::LabelOnly(a.normalize(arena))
|
||||
}
|
||||
OldRecordBuilderField::Malformed(a) => OldRecordBuilderField::Malformed(a),
|
||||
OldRecordBuilderField::SpaceBefore(a, _) => a.normalize(arena),
|
||||
OldRecordBuilderField::SpaceAfter(a, _) => a.normalize(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Normalize<'a> for StrLiteral<'a> {
|
||||
fn normalize(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
@ -738,7 +714,6 @@ impl<'a> Normalize<'a> for Expr<'a> {
|
||||
fields: fields.normalize(arena),
|
||||
},
|
||||
Expr::Record(a) => Expr::Record(a.normalize(arena)),
|
||||
Expr::OldRecordBuilder(a) => Expr::OldRecordBuilder(a.normalize(arena)),
|
||||
Expr::RecordBuilder { mapper, fields } => Expr::RecordBuilder {
|
||||
mapper: arena.alloc(mapper.normalize(arena)),
|
||||
fields: fields.normalize(arena),
|
||||
@ -817,12 +792,6 @@ impl<'a> Normalize<'a> for Expr<'a> {
|
||||
Expr::SpaceBefore(a, _) => a.normalize(arena),
|
||||
Expr::SpaceAfter(a, _) => a.normalize(arena),
|
||||
Expr::SingleQuote(a) => Expr::Num(a),
|
||||
Expr::MultipleOldRecordBuilders(a) => {
|
||||
Expr::MultipleOldRecordBuilders(arena.alloc(a.normalize(arena)))
|
||||
}
|
||||
Expr::UnappliedOldRecordBuilder(a) => {
|
||||
Expr::UnappliedOldRecordBuilder(arena.alloc(a.normalize(arena)))
|
||||
}
|
||||
Expr::EmptyRecordBuilder(a) => {
|
||||
Expr::EmptyRecordBuilder(arena.alloc(a.normalize(arena)))
|
||||
}
|
||||
@ -1092,12 +1061,6 @@ impl<'a> Normalize<'a> for EExpr<'a> {
|
||||
EExpr::Record(inner_err, _pos) => {
|
||||
EExpr::Record(inner_err.normalize(arena), Position::zero())
|
||||
}
|
||||
EExpr::OptionalValueInOldRecordBuilder(_pos) => {
|
||||
EExpr::OptionalValueInOldRecordBuilder(Region::zero())
|
||||
}
|
||||
EExpr::IgnoredValueInOldRecordBuilder(_pos) => {
|
||||
EExpr::OptionalValueInOldRecordBuilder(Region::zero())
|
||||
}
|
||||
EExpr::Str(inner_err, _pos) => EExpr::Str(inner_err.normalize(arena), Position::zero()),
|
||||
EExpr::Number(inner_err, _pos) => EExpr::Number(inner_err.clone(), Position::zero()),
|
||||
EExpr::List(inner_err, _pos) => {
|
||||
@ -1331,7 +1294,6 @@ impl<'a> Normalize<'a> for EImportParams<'a> {
|
||||
EImportParams::Record(inner_err.normalize(arena), Position::zero())
|
||||
}
|
||||
EImportParams::RecordUpdateFound(_) => EImportParams::RecordUpdateFound(Region::zero()),
|
||||
EImportParams::RecordApplyFound(_) => EImportParams::RecordApplyFound(Region::zero()),
|
||||
EImportParams::RecordIgnoredFieldFound(_) => {
|
||||
EImportParams::RecordIgnoredFieldFound(Region::zero())
|
||||
}
|
||||
|
@ -344,8 +344,6 @@ pub enum EExpr<'a> {
|
||||
|
||||
InParens(EInParens<'a>, Position),
|
||||
Record(ERecord<'a>, Position),
|
||||
OptionalValueInOldRecordBuilder(Region),
|
||||
IgnoredValueInOldRecordBuilder(Region),
|
||||
RecordUpdateOldBuilderField(Region),
|
||||
RecordUpdateIgnoredField(Region),
|
||||
RecordBuilderOldBuilderField(Region),
|
||||
@ -551,7 +549,6 @@ pub enum EImportParams<'a> {
|
||||
Record(ERecord<'a>, Position),
|
||||
RecordUpdateFound(Region),
|
||||
RecordBuilderFound(Region),
|
||||
RecordApplyFound(Region),
|
||||
RecordIgnoredFieldFound(Region),
|
||||
Space(BadInputError, Position),
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use crate::ast::{
|
||||
use crate::blankspace::{
|
||||
space0_around_ee, space0_before_e, space0_before_optional_after, space0_e,
|
||||
};
|
||||
use crate::expr::{record_field, FoundApplyValue};
|
||||
use crate::expr::record_field;
|
||||
use crate::ident::{lowercase_ident, lowercase_ident_keyword_e};
|
||||
use crate::keyword;
|
||||
use crate::parser::{
|
||||
@ -565,11 +565,10 @@ fn parse_implements_ability<'a>() -> impl Parser<'a, ImplementsAbility<'a>, ETyp
|
||||
fn ability_impl_field<'a>() -> impl Parser<'a, AssignedField<'a, Expr<'a>>, ERecord<'a>> {
|
||||
then(record_field(), move |arena, state, _, field| {
|
||||
match field.to_assigned_field(arena) {
|
||||
Ok(AssignedField::IgnoredValue(_, _, _)) => {
|
||||
AssignedField::IgnoredValue(_, _, _) => {
|
||||
Err((MadeProgress, ERecord::Field(state.pos())))
|
||||
}
|
||||
Ok(assigned_field) => Ok((MadeProgress, assigned_field, state)),
|
||||
Err(FoundApplyValue) => Err((MadeProgress, ERecord::Field(state.pos()))),
|
||||
assigned_field => Ok((MadeProgress, assigned_field, state)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -427,8 +427,6 @@ impl Problem {
|
||||
| Problem::RuntimeError(RuntimeError::EmptySingleQuote(region))
|
||||
| Problem::RuntimeError(RuntimeError::MultipleCharsInSingleQuote(region))
|
||||
| Problem::RuntimeError(RuntimeError::DegenerateBranch(region))
|
||||
| Problem::RuntimeError(RuntimeError::MultipleOldRecordBuilders(region))
|
||||
| Problem::RuntimeError(RuntimeError::UnappliedOldRecordBuilder(region))
|
||||
| Problem::RuntimeError(RuntimeError::EmptyRecordBuilder(region))
|
||||
| Problem::RuntimeError(RuntimeError::SingleFieldRecordBuilder(region))
|
||||
| Problem::RuntimeError(RuntimeError::OptionalFieldInRecordBuilder {
|
||||
@ -686,9 +684,6 @@ pub enum RuntimeError {
|
||||
|
||||
DegenerateBranch(Region),
|
||||
|
||||
MultipleOldRecordBuilders(Region),
|
||||
UnappliedOldRecordBuilder(Region),
|
||||
|
||||
EmptyRecordBuilder(Region),
|
||||
SingleFieldRecordBuilder(Region),
|
||||
OptionalFieldInRecordBuilder {
|
||||
@ -739,8 +734,6 @@ impl RuntimeError {
|
||||
| RuntimeError::DegenerateBranch(region)
|
||||
| RuntimeError::InvalidInterpolation(region)
|
||||
| RuntimeError::InvalidHexadecimal(region)
|
||||
| RuntimeError::MultipleOldRecordBuilders(region)
|
||||
| RuntimeError::UnappliedOldRecordBuilder(region)
|
||||
| RuntimeError::EmptyRecordBuilder(region)
|
||||
| RuntimeError::SingleFieldRecordBuilder(region)
|
||||
| RuntimeError::OptionalFieldInRecordBuilder {
|
||||
|
@ -1986,7 +1986,7 @@ mod test_fmt {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_record_builder() {
|
||||
fn record_builder() {
|
||||
expr_formats_same(indoc!(
|
||||
r"
|
||||
{ shoes <- leftShoe: nothing }
|
||||
@ -2077,170 +2077,6 @@ mod test_fmt {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_record_builder() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
{ a: 1, b: <- get "b" |> batch, c: <- get "c" |> batch, d }
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
{ a: 1, b: <- get "b" |> batch, c:<- get "c" |> batch }
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
{ a: 1, b: <- get "b" |> batch, c: <- get "c" |> batch }
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
{
|
||||
a: 1,
|
||||
b: <- get "b" |> batch,
|
||||
c: <- get "c" |> batch,
|
||||
d,
|
||||
}
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
{ a: 1, b: <- get "b" |> batch,
|
||||
c: <- get "c" |> batch, d }
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
{
|
||||
a: 1,
|
||||
b: <- get "b" |> batch,
|
||||
c: <- get "c" |> batch,
|
||||
d,
|
||||
}
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiline_record_builder_field() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
succeed {
|
||||
a: <- get "a" |> map (\x -> x * 2)
|
||||
|> batch,
|
||||
b: <- get "b" |> batch,
|
||||
c: items
|
||||
|> List.map \x -> x * 2
|
||||
}
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
succeed {
|
||||
a: <-
|
||||
get "a"
|
||||
|> map (\x -> x * 2)
|
||||
|> batch,
|
||||
b: <- get "b" |> batch,
|
||||
c:
|
||||
items
|
||||
|> List.map \x -> x * 2,
|
||||
}
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
succeed {
|
||||
a: # I like to comment in weird places
|
||||
<- get "a" |> batch,
|
||||
b: <- get "b" |> batch,
|
||||
}
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
succeed {
|
||||
a:
|
||||
# I like to comment in weird places
|
||||
<- get "a" |> batch,
|
||||
b: <- get "b" |> batch,
|
||||
}
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outdentable_record_builders() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
succeed { a: <- get "a" |> batch,
|
||||
b: <- get "b" |> batch,
|
||||
}
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
succeed {
|
||||
a: <- get "a" |> batch,
|
||||
b: <- get "b" |> batch,
|
||||
}
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
succeed
|
||||
{
|
||||
a: <- get "a" |> batch,
|
||||
b: <- get "b" |> batch,
|
||||
}
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
succeed {
|
||||
a: <- get "a" |> batch,
|
||||
b: <- get "b" |> batch,
|
||||
}
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_format_multiple_record_builders() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
succeed { a: <- get "a" }
|
||||
{ b: <- get "b" }
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
succeed
|
||||
{ a: <- get "a" }
|
||||
{ b: <- get "b" }
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn final_comments_in_records() {
|
||||
expr_formats_same(indoc!(
|
||||
|
@ -6,9 +6,8 @@ use roc_module::called_via::{BinOp, UnaryOp};
|
||||
use roc_parse::{
|
||||
ast::{
|
||||
AbilityImpls, AbilityMember, AssignedField, Collection, Defs, Expr, Header, Implements,
|
||||
ImplementsAbilities, ImplementsAbility, ImplementsClause, OldRecordBuilderField, Pattern,
|
||||
PatternAs, Spaced, StrLiteral, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
|
||||
WhenBranch,
|
||||
ImplementsAbilities, ImplementsAbility, ImplementsClause, Pattern, PatternAs, Spaced,
|
||||
StrLiteral, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch,
|
||||
},
|
||||
header::{
|
||||
AppHeader, ExposedName, HostedHeader, ImportsEntry, ModuleHeader, ModuleName, ModuleParams,
|
||||
@ -672,7 +671,6 @@ impl IterTokens for Loc<Expr<'_>> {
|
||||
.collect_in(arena),
|
||||
Expr::Record(rcd) => rcd.iter_tokens(arena),
|
||||
Expr::Tuple(tup) => tup.iter_tokens(arena),
|
||||
Expr::OldRecordBuilder(rb) => rb.iter_tokens(arena),
|
||||
Expr::RecordBuilder { mapper, fields } => (mapper.iter_tokens(arena).into_iter())
|
||||
.chain(fields.iter().flat_map(|f| f.iter_tokens(arena)))
|
||||
.collect_in(arena),
|
||||
@ -724,8 +722,6 @@ impl IterTokens for Loc<Expr<'_>> {
|
||||
Loc::at(region, *e).iter_tokens(arena)
|
||||
}
|
||||
Expr::ParensAround(e) => Loc::at(region, *e).iter_tokens(arena),
|
||||
Expr::MultipleOldRecordBuilders(e) => e.iter_tokens(arena),
|
||||
Expr::UnappliedOldRecordBuilder(e) => e.iter_tokens(arena),
|
||||
Expr::EmptyRecordBuilder(e) => e.iter_tokens(arena),
|
||||
Expr::SingleFieldRecordBuilder(e) => e.iter_tokens(arena),
|
||||
Expr::OptionalFieldInRecordBuilder(_name, e) => e.iter_tokens(arena),
|
||||
@ -748,24 +744,6 @@ impl IterTokens for Loc<Accessor<'_>> {
|
||||
}
|
||||
}
|
||||
|
||||
impl IterTokens for Loc<OldRecordBuilderField<'_>> {
|
||||
fn iter_tokens<'a>(&self, arena: &'a Bump) -> BumpVec<'a, Loc<Token>> {
|
||||
match self.value {
|
||||
OldRecordBuilderField::Value(field, _, e)
|
||||
| OldRecordBuilderField::ApplyValue(field, _, _, e) => field_token(field.region, arena)
|
||||
.into_iter()
|
||||
.chain(e.iter_tokens(arena))
|
||||
.collect_in(arena),
|
||||
OldRecordBuilderField::LabelOnly(field) => field_token(field.region, arena),
|
||||
OldRecordBuilderField::SpaceBefore(rbf, _)
|
||||
| OldRecordBuilderField::SpaceAfter(rbf, _) => {
|
||||
Loc::at(self.region, *rbf).iter_tokens(arena)
|
||||
}
|
||||
OldRecordBuilderField::Malformed(_) => bumpvec![in arena;],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IterTokens for &WhenBranch<'_> {
|
||||
fn iter_tokens<'a>(&self, arena: &'a Bump) -> BumpVec<'a, Loc<Token>> {
|
||||
let WhenBranch {
|
||||
|
@ -2488,32 +2488,6 @@ fn pretty_runtime_error<'b>(
|
||||
|
||||
title = "DEGENERATE BRANCH";
|
||||
}
|
||||
RuntimeError::MultipleOldRecordBuilders(region) => {
|
||||
let tip = alloc
|
||||
.tip()
|
||||
.append(alloc.reflow("You can combine them or apply them separately."));
|
||||
|
||||
doc = alloc.stack([
|
||||
alloc.reflow("This function is applied to multiple old-style record builders:"),
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc.note("Functions can only take at most one old-style record builder!"),
|
||||
tip,
|
||||
]);
|
||||
|
||||
title = "MULTIPLE OLD-STYLE RECORD BUILDERS";
|
||||
}
|
||||
RuntimeError::UnappliedOldRecordBuilder(region) => {
|
||||
doc = alloc.stack([
|
||||
alloc.reflow("This old-style record builder was not applied to a function:"),
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc.reflow("However, we need a function to construct the record."),
|
||||
alloc.note(
|
||||
"Functions must be applied directly. The pipe operator (|>) cannot be used.",
|
||||
),
|
||||
]);
|
||||
|
||||
title = "UNAPPLIED OLD-STYLE RECORD BUILDER";
|
||||
}
|
||||
RuntimeError::EmptyRecordBuilder(region) => {
|
||||
doc = alloc.stack([
|
||||
alloc.reflow("This record builder has no fields:"),
|
||||
|
@ -545,46 +545,6 @@ fn to_expr_report<'a>(
|
||||
to_record_report(alloc, lines, filename, erecord, *pos, start)
|
||||
}
|
||||
|
||||
EExpr::OptionalValueInOldRecordBuilder(region) => {
|
||||
let surroundings = Region::new(start, region.end());
|
||||
let region = lines.convert_region(*region);
|
||||
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow(
|
||||
r"I am partway through parsing a record builder, and I found an optional field:",
|
||||
),
|
||||
alloc.region_with_subregion(lines.convert_region(surroundings), region, severity),
|
||||
alloc.reflow("Optional fields can only appear when you destructure a record."),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "BAD OLD-STYLE RECORD BUILDER".to_string(),
|
||||
severity,
|
||||
}
|
||||
}
|
||||
|
||||
EExpr::RecordUpdateOldBuilderField(region) => {
|
||||
let surroundings = Region::new(start, region.end());
|
||||
let region = lines.convert_region(*region);
|
||||
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow(
|
||||
r"I am partway through parsing a record update, and I found an old-style record builder field:",
|
||||
),
|
||||
alloc.region_with_subregion(lines.convert_region(surroundings), region, severity),
|
||||
alloc.reflow("Old-style record builders cannot be updated like records."),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "BAD RECORD UPDATE".to_string(),
|
||||
severity,
|
||||
}
|
||||
}
|
||||
|
||||
EExpr::Space(error, pos) => to_space_report(alloc, lines, filename, error, *pos),
|
||||
|
||||
&EExpr::Number(ENumber::End, pos) => {
|
||||
@ -1550,23 +1510,6 @@ fn to_import_report<'a>(
|
||||
Params(EImportParams::Record(problem, pos), _) => {
|
||||
to_record_report(alloc, lines, filename, problem, *pos, start)
|
||||
}
|
||||
Params(EImportParams::RecordApplyFound(region), _) => {
|
||||
let surroundings = Region::new(start, region.end());
|
||||
let region = lines.convert_region(*region);
|
||||
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow("I was partway through parsing module params, but I got stuck here:"),
|
||||
alloc.region_with_subregion(lines.convert_region(surroundings), region, severity),
|
||||
alloc.reflow("This looks like an old-style record builder field, but those are not allowed in module params."),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "OLD-STYLE RECORD BUILDER IN MODULE PARAMS".to_string(),
|
||||
severity,
|
||||
}
|
||||
}
|
||||
Params(EImportParams::RecordIgnoredFieldFound(region), _) => {
|
||||
let surroundings = Region::new(start, region.end());
|
||||
let region = lines.convert_region(*region);
|
||||
|
@ -1238,14 +1238,7 @@ fn to_expr_report<'b>(
|
||||
),
|
||||
]),
|
||||
alloc.region(lines.convert_region(expr_region), severity),
|
||||
match called_via {
|
||||
CalledVia::OldRecordBuilder => {
|
||||
alloc.hint("Did you mean to apply it to a function first?")
|
||||
},
|
||||
_ => {
|
||||
alloc.reflow("I can't call an opaque type because I don't know what it is! Maybe you meant to unwrap it first?")
|
||||
}
|
||||
}
|
||||
alloc.reflow("I can't call an opaque type because I don't know what it is! Maybe you meant to unwrap it first?"),
|
||||
]),
|
||||
Other => alloc.stack([
|
||||
alloc.concat([
|
||||
@ -1261,14 +1254,6 @@ fn to_expr_report<'b>(
|
||||
]),
|
||||
alloc.region(lines.convert_region(expr_region), severity),
|
||||
match called_via {
|
||||
CalledVia::OldRecordBuilder => {
|
||||
alloc.concat([
|
||||
alloc.tip(),
|
||||
alloc.reflow("Remove "),
|
||||
alloc.backpassing_arrow(),
|
||||
alloc.reflow(" to assign the field directly.")
|
||||
])
|
||||
}
|
||||
CalledVia::RecordBuilder => {
|
||||
alloc.concat([
|
||||
alloc.note(""),
|
||||
|
@ -2311,8 +2311,6 @@ expect
|
||||
|
||||
If you want to see other examples of using record builders, look at the [Record Builder Example](https://www.roc-lang.org/examples/RecordBuilder/README.html) for a moderately-sized example or the [Arg.Builder](https://github.com/roc-lang/basic-cli/blob/main/platform/Arg/Builder.roc) module in our `basic-cli` platform for a complex example.
|
||||
|
||||
_Note: This syntax replaces the old `field: <- value` record builder syntax using applicative functors because it is much simpler to understand and use. The old syntax will be removed soon._
|
||||
|
||||
### [Reserved Keywords](#reserved-keywords) {#reserved-keywords}
|
||||
|
||||
These are reserved keywords in Roc. You can't choose any of them as names, except as record field names.
|
||||
|
Loading…
Reference in New Issue
Block a user