mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 19:58:18 +00:00
Merge pull request #6968 from smores56/ignored-record-builder-fields
Ignore underscore-prefixed fields in record builders
This commit is contained in:
commit
698bbc3cf1
23 changed files with 450 additions and 145 deletions
|
@ -467,7 +467,8 @@ pub fn find_type_def_symbols(
|
|||
while let Some(assigned_field) = inner_stack.pop() {
|
||||
match assigned_field {
|
||||
AssignedField::RequiredValue(_, _, t)
|
||||
| AssignedField::OptionalValue(_, _, t) => {
|
||||
| AssignedField::OptionalValue(_, _, t)
|
||||
| AssignedField::IgnoredValue(_, _, t) => {
|
||||
stack.push(&t.value);
|
||||
}
|
||||
AssignedField::LabelOnly(_) => {}
|
||||
|
@ -1386,6 +1387,7 @@ fn can_assigned_fields<'a>(
|
|||
|
||||
break 'inner label;
|
||||
}
|
||||
IgnoredValue(_, _, _) => unreachable!(),
|
||||
LabelOnly(loc_field_name) => {
|
||||
// Interpret { a, b } as { a : a, b : b }
|
||||
let field_name = Lowercase::from(loc_field_name.value);
|
||||
|
|
|
@ -631,7 +631,9 @@ fn canonicalize_claimed_ability_impl<'a>(
|
|||
// An error will already have been reported
|
||||
Err(())
|
||||
}
|
||||
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => {
|
||||
AssignedField::SpaceBefore(_, _)
|
||||
| AssignedField::SpaceAfter(_, _)
|
||||
| AssignedField::IgnoredValue(_, _, _) => {
|
||||
internal_error!("unreachable")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -521,44 +521,68 @@ pub fn desugar_expr<'a>(
|
|||
});
|
||||
}
|
||||
|
||||
let mut field_names = Vec::with_capacity_in(fields.len(), arena);
|
||||
let mut field_vals = Vec::with_capacity_in(fields.len(), arena);
|
||||
|
||||
for field in fields.items {
|
||||
match desugar_field(arena, &field.value, src, line_info, module_path) {
|
||||
AssignedField::RequiredValue(loc_name, _, loc_val) => {
|
||||
field_names.push(loc_name);
|
||||
field_vals.push(loc_val);
|
||||
}
|
||||
AssignedField::LabelOnly(loc_name) => {
|
||||
field_names.push(loc_name);
|
||||
field_vals.push(arena.alloc(Loc {
|
||||
region: loc_name.region,
|
||||
value: Expr::Var {
|
||||
module_name: "",
|
||||
ident: loc_name.value,
|
||||
},
|
||||
}));
|
||||
}
|
||||
AssignedField::OptionalValue(loc_name, _, loc_val) => {
|
||||
return arena.alloc(Loc {
|
||||
region: loc_expr.region,
|
||||
value: OptionalFieldInRecordBuilder(arena.alloc(loc_name), loc_val),
|
||||
});
|
||||
}
|
||||
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => {
|
||||
unreachable!("Should have been desugared in `desugar_field`")
|
||||
}
|
||||
AssignedField::Malformed(_name) => {}
|
||||
}
|
||||
struct FieldData<'d> {
|
||||
name: Loc<&'d str>,
|
||||
value: &'d Loc<Expr<'d>>,
|
||||
ignored: bool,
|
||||
}
|
||||
|
||||
let closure_arg_from_field = |field: Loc<&'a str>| Loc {
|
||||
region: field.region,
|
||||
value: Pattern::Identifier {
|
||||
ident: arena.alloc_str(&format!("#{}", field.value)),
|
||||
},
|
||||
};
|
||||
let mut field_data = Vec::with_capacity_in(fields.len(), arena);
|
||||
|
||||
for field in fields.items {
|
||||
let (name, value, ignored) =
|
||||
match desugar_field(arena, &field.value, src, line_info, module_path) {
|
||||
AssignedField::RequiredValue(loc_name, _, loc_val) => {
|
||||
(loc_name, loc_val, false)
|
||||
}
|
||||
AssignedField::IgnoredValue(loc_name, _, loc_val) => {
|
||||
(loc_name, loc_val, true)
|
||||
}
|
||||
AssignedField::LabelOnly(loc_name) => (
|
||||
loc_name,
|
||||
&*arena.alloc(Loc {
|
||||
region: loc_name.region,
|
||||
value: Expr::Var {
|
||||
module_name: "",
|
||||
ident: loc_name.value,
|
||||
},
|
||||
}),
|
||||
false,
|
||||
),
|
||||
AssignedField::OptionalValue(loc_name, _, loc_val) => {
|
||||
return arena.alloc(Loc {
|
||||
region: loc_expr.region,
|
||||
value: OptionalFieldInRecordBuilder(arena.alloc(loc_name), loc_val),
|
||||
});
|
||||
}
|
||||
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => {
|
||||
unreachable!("Should have been desugared in `desugar_field`")
|
||||
}
|
||||
AssignedField::Malformed(_name) => continue,
|
||||
};
|
||||
|
||||
field_data.push(FieldData {
|
||||
name,
|
||||
value,
|
||||
ignored,
|
||||
});
|
||||
}
|
||||
|
||||
let closure_arg_from_field =
|
||||
|FieldData {
|
||||
name,
|
||||
value: _,
|
||||
ignored,
|
||||
}: &FieldData<'a>| Loc {
|
||||
region: name.region,
|
||||
value: if *ignored {
|
||||
Pattern::Underscore(name.value)
|
||||
} else {
|
||||
Pattern::Identifier {
|
||||
ident: arena.alloc_str(&format!("#{}", name.value)),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let combiner_closure_in_region = |region| {
|
||||
let closure_body = Tuple(Collection::with_items(
|
||||
|
@ -607,15 +631,15 @@ pub fn desugar_expr<'a>(
|
|||
};
|
||||
|
||||
let closure_args = {
|
||||
if field_names.len() == 2 {
|
||||
if field_data.len() == 2 {
|
||||
arena.alloc_slice_copy(&[
|
||||
closure_arg_from_field(field_names[0]),
|
||||
closure_arg_from_field(field_names[1]),
|
||||
closure_arg_from_field(&field_data[0]),
|
||||
closure_arg_from_field(&field_data[1]),
|
||||
])
|
||||
} else {
|
||||
let second_to_last_arg =
|
||||
closure_arg_from_field(field_names[field_names.len() - 2]);
|
||||
let last_arg = closure_arg_from_field(field_names[field_names.len() - 1]);
|
||||
closure_arg_from_field(&field_data[field_data.len() - 2]);
|
||||
let last_arg = closure_arg_from_field(&field_data[field_data.len() - 1]);
|
||||
|
||||
let mut second_arg = Pattern::Tuple(Collection::with_items(
|
||||
arena.alloc_slice_copy(&[second_to_last_arg, last_arg]),
|
||||
|
@ -623,18 +647,18 @@ pub fn desugar_expr<'a>(
|
|||
let mut second_arg_region =
|
||||
Region::span_across(&second_to_last_arg.region, &last_arg.region);
|
||||
|
||||
for index in (1..(field_names.len() - 2)).rev() {
|
||||
for index in (1..(field_data.len() - 2)).rev() {
|
||||
second_arg =
|
||||
Pattern::Tuple(Collection::with_items(arena.alloc_slice_copy(&[
|
||||
closure_arg_from_field(field_names[index]),
|
||||
closure_arg_from_field(&field_data[index]),
|
||||
Loc::at(second_arg_region, second_arg),
|
||||
])));
|
||||
second_arg_region =
|
||||
Region::span_across(&field_names[index].region, &second_arg_region);
|
||||
Region::span_across(&field_data[index].name.region, &second_arg_region);
|
||||
}
|
||||
|
||||
arena.alloc_slice_copy(&[
|
||||
closure_arg_from_field(field_names[0]),
|
||||
closure_arg_from_field(&field_data[0]),
|
||||
Loc::at(second_arg_region, second_arg),
|
||||
])
|
||||
}
|
||||
|
@ -642,22 +666,26 @@ pub fn desugar_expr<'a>(
|
|||
|
||||
let record_val = Record(Collection::with_items(
|
||||
Vec::from_iter_in(
|
||||
field_names.iter().map(|field_name| {
|
||||
Loc::at(
|
||||
field_name.region,
|
||||
AssignedField::RequiredValue(
|
||||
Loc::at(field_name.region, field_name.value),
|
||||
&[],
|
||||
arena.alloc(Loc::at(
|
||||
field_name.region,
|
||||
Expr::Var {
|
||||
module_name: "",
|
||||
ident: arena.alloc_str(&format!("#{}", field_name.value)),
|
||||
},
|
||||
)),
|
||||
),
|
||||
)
|
||||
}),
|
||||
field_data
|
||||
.iter()
|
||||
.filter(|field| !field.ignored)
|
||||
.map(|field| {
|
||||
Loc::at(
|
||||
field.name.region,
|
||||
AssignedField::RequiredValue(
|
||||
field.name,
|
||||
&[],
|
||||
arena.alloc(Loc::at(
|
||||
field.name.region,
|
||||
Expr::Var {
|
||||
module_name: "",
|
||||
ident: arena
|
||||
.alloc_str(&format!("#{}", field.name.value)),
|
||||
},
|
||||
)),
|
||||
),
|
||||
)
|
||||
}),
|
||||
arena,
|
||||
)
|
||||
.into_bump_slice(),
|
||||
|
@ -671,14 +699,14 @@ pub fn desugar_expr<'a>(
|
|||
),
|
||||
});
|
||||
|
||||
if field_names.len() == 2 {
|
||||
if field_data.len() == 2 {
|
||||
return arena.alloc(Loc {
|
||||
region: loc_expr.region,
|
||||
value: Apply(
|
||||
new_mapper,
|
||||
arena.alloc_slice_copy(&[
|
||||
field_vals[0],
|
||||
field_vals[1],
|
||||
field_data[0].value,
|
||||
field_data[1].value,
|
||||
record_combiner_closure,
|
||||
]),
|
||||
CalledVia::RecordBuilder,
|
||||
|
@ -688,27 +716,30 @@ pub fn desugar_expr<'a>(
|
|||
|
||||
let mut inner_combined = arena.alloc(Loc {
|
||||
region: Region::span_across(
|
||||
&field_vals[field_names.len() - 2].region,
|
||||
&field_vals[field_names.len() - 1].region,
|
||||
&field_data[field_data.len() - 2].value.region,
|
||||
&field_data[field_data.len() - 1].value.region,
|
||||
),
|
||||
value: Apply(
|
||||
new_mapper,
|
||||
arena.alloc_slice_copy(&[
|
||||
field_vals[field_names.len() - 2],
|
||||
field_vals[field_names.len() - 1],
|
||||
field_data[field_data.len() - 2].value,
|
||||
field_data[field_data.len() - 1].value,
|
||||
combiner_closure_in_region(loc_expr.region),
|
||||
]),
|
||||
CalledVia::RecordBuilder,
|
||||
),
|
||||
});
|
||||
|
||||
for index in (1..(field_names.len() - 2)).rev() {
|
||||
for index in (1..(field_data.len() - 2)).rev() {
|
||||
inner_combined = arena.alloc(Loc {
|
||||
region: Region::span_across(&field_vals[index].region, &inner_combined.region),
|
||||
region: Region::span_across(
|
||||
&field_data[index].value.region,
|
||||
&inner_combined.region,
|
||||
),
|
||||
value: Apply(
|
||||
new_mapper,
|
||||
arena.alloc_slice_copy(&[
|
||||
field_vals[index],
|
||||
field_data[index].value,
|
||||
inner_combined,
|
||||
combiner_closure_in_region(loc_expr.region),
|
||||
]),
|
||||
|
@ -722,7 +753,7 @@ pub fn desugar_expr<'a>(
|
|||
value: Apply(
|
||||
new_mapper,
|
||||
arena.alloc_slice_copy(&[
|
||||
field_vals[0],
|
||||
field_data[0].value,
|
||||
inner_combined,
|
||||
record_combiner_closure,
|
||||
]),
|
||||
|
@ -1095,6 +1126,14 @@ fn desugar_field<'a>(
|
|||
spaces,
|
||||
desugar_expr(arena, loc_expr, src, line_info, module_path),
|
||||
),
|
||||
IgnoredValue(loc_str, spaces, loc_expr) => IgnoredValue(
|
||||
Loc {
|
||||
value: loc_str.value,
|
||||
region: loc_str.region,
|
||||
},
|
||||
spaces,
|
||||
desugar_expr(arena, loc_expr, src, line_info, module_path),
|
||||
),
|
||||
LabelOnly(loc_str) => {
|
||||
// Desugar { x } into { x: x }
|
||||
let loc_expr = Loc {
|
||||
|
|
|
@ -1846,6 +1846,11 @@ fn canonicalize_field<'a>(
|
|||
field_region: Region::span_across(&label.region, &loc_expr.region),
|
||||
}),
|
||||
|
||||
// An ignored value, e.g. `{ _name: 123 }`
|
||||
IgnoredValue(_, _, _) => {
|
||||
internal_error!("Somehow an IgnoredValue record field was not desugared!");
|
||||
}
|
||||
|
||||
// A label with no value, e.g. `{ name }` (this is sugar for { name: name })
|
||||
LabelOnly(_) => {
|
||||
internal_error!("Somehow a LabelOnly record field was not desugared!");
|
||||
|
@ -2433,7 +2438,8 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
|
|||
}
|
||||
ast::Expr::Record(fields) => fields.iter().all(|loc_field| match loc_field.value {
|
||||
ast::AssignedField::RequiredValue(_label, loc_comments, loc_val)
|
||||
| ast::AssignedField::OptionalValue(_label, loc_comments, loc_val) => {
|
||||
| ast::AssignedField::OptionalValue(_label, loc_comments, loc_val)
|
||||
| ast::AssignedField::IgnoredValue(_label, loc_comments, loc_val) => {
|
||||
loc_comments.is_empty() && is_valid_interpolation(&loc_val.value)
|
||||
}
|
||||
ast::AssignedField::Malformed(_) | ast::AssignedField::LabelOnly(_) => true,
|
||||
|
@ -2481,7 +2487,8 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
|
|||
is_valid_interpolation(&update.value)
|
||||
&& fields.iter().all(|loc_field| match loc_field.value {
|
||||
ast::AssignedField::RequiredValue(_label, loc_comments, loc_val)
|
||||
| ast::AssignedField::OptionalValue(_label, loc_comments, loc_val) => {
|
||||
| ast::AssignedField::OptionalValue(_label, loc_comments, loc_val)
|
||||
| ast::AssignedField::IgnoredValue(_label, loc_comments, loc_val) => {
|
||||
loc_comments.is_empty() && is_valid_interpolation(&loc_val.value)
|
||||
}
|
||||
ast::AssignedField::Malformed(_) | ast::AssignedField::LabelOnly(_) => true,
|
||||
|
@ -2514,7 +2521,8 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
|
|||
is_valid_interpolation(&mapper.value)
|
||||
&& fields.iter().all(|loc_field| match loc_field.value {
|
||||
ast::AssignedField::RequiredValue(_label, loc_comments, loc_val)
|
||||
| ast::AssignedField::OptionalValue(_label, loc_comments, loc_val) => {
|
||||
| ast::AssignedField::OptionalValue(_label, loc_comments, loc_val)
|
||||
| ast::AssignedField::IgnoredValue(_label, loc_comments, loc_val) => {
|
||||
loc_comments.is_empty() && is_valid_interpolation(&loc_val.value)
|
||||
}
|
||||
ast::AssignedField::Malformed(_) | ast::AssignedField::LabelOnly(_) => true,
|
||||
|
|
|
@ -428,9 +428,9 @@ fn is_multiline_assigned_field_help<T: Formattable>(afield: &AssignedField<'_, T
|
|||
use self::AssignedField::*;
|
||||
|
||||
match afield {
|
||||
RequiredValue(_, spaces, ann) | OptionalValue(_, spaces, ann) => {
|
||||
!spaces.is_empty() || ann.value.is_multiline()
|
||||
}
|
||||
RequiredValue(_, spaces, ann)
|
||||
| OptionalValue(_, spaces, ann)
|
||||
| IgnoredValue(_, spaces, ann) => !spaces.is_empty() || ann.value.is_multiline(),
|
||||
LabelOnly(_) => false,
|
||||
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => true,
|
||||
Malformed(text) => text.chars().any(|c| c == '\n'),
|
||||
|
@ -483,6 +483,24 @@ fn format_assigned_field_help<T>(
|
|||
buf.spaces(1);
|
||||
ann.value.format(buf, indent);
|
||||
}
|
||||
IgnoredValue(name, spaces, ann) => {
|
||||
if is_multiline {
|
||||
buf.newline();
|
||||
}
|
||||
|
||||
buf.indent(indent);
|
||||
buf.push('_');
|
||||
buf.push_str(name.value);
|
||||
|
||||
if !spaces.is_empty() {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
|
||||
buf.spaces(separator_spaces);
|
||||
buf.push(':');
|
||||
buf.spaces(1);
|
||||
ann.value.format(buf, indent);
|
||||
}
|
||||
LabelOnly(name) => {
|
||||
if is_multiline {
|
||||
buf.newline();
|
||||
|
|
|
@ -1529,6 +1529,23 @@ fn format_assigned_field_multiline<T>(
|
|||
ann.value.format(buf, indent);
|
||||
buf.push(',');
|
||||
}
|
||||
IgnoredValue(name, spaces, ann) => {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
buf.push('_');
|
||||
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(":");
|
||||
buf.spaces(1);
|
||||
ann.value.format(buf, indent);
|
||||
buf.push(',');
|
||||
}
|
||||
LabelOnly(name) => {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
|
|
|
@ -465,7 +465,8 @@ fn contains_unexposed_type(
|
|||
while let Some(field) = fields_to_process.pop() {
|
||||
match field {
|
||||
AssignedField::RequiredValue(_field, _spaces, loc_val)
|
||||
| AssignedField::OptionalValue(_field, _spaces, loc_val) => {
|
||||
| AssignedField::OptionalValue(_field, _spaces, loc_val)
|
||||
| AssignedField::IgnoredValue(_field, _spaces, loc_val) => {
|
||||
if contains_unexposed_type(&loc_val.value, exposed_module_ids, module_ids) {
|
||||
return true;
|
||||
}
|
||||
|
@ -721,7 +722,7 @@ fn record_field_to_doc(
|
|||
AssignedField::LabelOnly(label) => Some(RecordField::LabelOnly {
|
||||
name: label.value.to_string(),
|
||||
}),
|
||||
AssignedField::Malformed(_) => None,
|
||||
AssignedField::Malformed(_) | AssignedField::IgnoredValue(_, _, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -681,9 +681,9 @@ fn is_when_branch_suffixed(branch: &WhenBranch<'_>) -> bool {
|
|||
|
||||
fn is_assigned_value_suffixed<'a>(value: &AssignedField<'a, Expr<'a>>) -> bool {
|
||||
match value {
|
||||
AssignedField::RequiredValue(_, _, a) | AssignedField::OptionalValue(_, _, a) => {
|
||||
is_expr_suffixed(&a.value)
|
||||
}
|
||||
AssignedField::RequiredValue(_, _, a)
|
||||
| AssignedField::OptionalValue(_, _, a)
|
||||
| AssignedField::IgnoredValue(_, _, a) => is_expr_suffixed(&a.value),
|
||||
AssignedField::LabelOnly(_) => false,
|
||||
AssignedField::SpaceBefore(a, _) | AssignedField::SpaceAfter(a, _) => {
|
||||
is_assigned_value_suffixed(a)
|
||||
|
@ -869,9 +869,9 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
|
|||
use AssignedField::*;
|
||||
|
||||
match current {
|
||||
RequiredValue(_, _, loc_val) | OptionalValue(_, _, loc_val) => {
|
||||
break expr_stack.push(&loc_val.value)
|
||||
}
|
||||
RequiredValue(_, _, loc_val)
|
||||
| OptionalValue(_, _, loc_val)
|
||||
| IgnoredValue(_, _, loc_val) => break expr_stack.push(&loc_val.value),
|
||||
SpaceBefore(next, _) | SpaceAfter(next, _) => current = *next,
|
||||
LabelOnly(_) | Malformed(_) => break,
|
||||
}
|
||||
|
@ -1598,6 +1598,9 @@ pub enum AssignedField<'a, Val> {
|
|||
// and in destructuring patterns (e.g. `{ name ? "blah" }`)
|
||||
OptionalValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Val>),
|
||||
|
||||
// An ignored field, e.g. `{ _name: "blah" }` or `{ _ : Str }`
|
||||
IgnoredValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Val>),
|
||||
|
||||
// A label with no value, e.g. `{ name }` (this is sugar for { name: name })
|
||||
LabelOnly(Loc<&'a str>),
|
||||
|
||||
|
@ -1615,7 +1618,9 @@ impl<'a, Val> AssignedField<'a, Val> {
|
|||
|
||||
loop {
|
||||
match current {
|
||||
Self::RequiredValue(_, _, val) | Self::OptionalValue(_, _, val) => break Some(val),
|
||||
Self::RequiredValue(_, _, val)
|
||||
| Self::OptionalValue(_, _, val)
|
||||
| Self::IgnoredValue(_, _, val) => break Some(val),
|
||||
Self::LabelOnly(_) | Self::Malformed(_) => break None,
|
||||
Self::SpaceBefore(next, _) | Self::SpaceAfter(next, _) => current = *next,
|
||||
}
|
||||
|
@ -2577,9 +2582,9 @@ impl<T: Malformed> Malformed for Option<T> {
|
|||
impl<'a, T: Malformed> Malformed for AssignedField<'a, T> {
|
||||
fn is_malformed(&self) -> bool {
|
||||
match self {
|
||||
AssignedField::RequiredValue(_, _, val) | AssignedField::OptionalValue(_, _, val) => {
|
||||
val.is_malformed()
|
||||
}
|
||||
AssignedField::RequiredValue(_, _, val)
|
||||
| AssignedField::OptionalValue(_, _, val)
|
||||
| AssignedField::IgnoredValue(_, _, val) => val.is_malformed(),
|
||||
AssignedField::LabelOnly(_) => false,
|
||||
AssignedField::SpaceBefore(field, _) | AssignedField::SpaceAfter(field, _) => {
|
||||
field.is_malformed()
|
||||
|
|
|
@ -914,6 +914,10 @@ fn import_params<'a>() -> impl Parser<'a, ModuleImportParams<'a>, EImportParams<
|
|||
|
||||
let params = record.fields.map_items_result(arena, |loc_field| {
|
||||
match loc_field.value.to_assigned_field(arena) {
|
||||
Ok(AssignedField::IgnoredValue(_, _, _)) => Err((
|
||||
MadeProgress,
|
||||
EImportParams::RecordIgnoredFieldFound(loc_field.region),
|
||||
)),
|
||||
Ok(field) => Ok(Loc::at(loc_field.region, field)),
|
||||
Err(FoundApplyValue) => Err((
|
||||
MadeProgress,
|
||||
|
@ -2234,6 +2238,7 @@ fn assigned_expr_field_to_pattern_help<'a>(
|
|||
spaces,
|
||||
),
|
||||
AssignedField::Malformed(string) => Pattern::Malformed(string),
|
||||
AssignedField::IgnoredValue(_, _, _) => return Err(()),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -3322,6 +3327,7 @@ fn list_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EList<'a>> {
|
|||
pub enum RecordField<'a> {
|
||||
RequiredValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Expr<'a>>),
|
||||
OptionalValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Expr<'a>>),
|
||||
IgnoredValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Expr<'a>>),
|
||||
LabelOnly(Loc<&'a str>),
|
||||
SpaceBefore(&'a RecordField<'a>, &'a [CommentOrNewline<'a>]),
|
||||
SpaceAfter(&'a RecordField<'a>, &'a [CommentOrNewline<'a>]),
|
||||
|
@ -3337,7 +3343,10 @@ pub enum RecordField<'a> {
|
|||
pub struct FoundApplyValue;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FoundOptionalValue;
|
||||
pub enum NotOldBuilderFieldValue {
|
||||
FoundOptionalValue,
|
||||
FoundIgnoredValue,
|
||||
}
|
||||
|
||||
impl<'a> RecordField<'a> {
|
||||
fn is_apply_value(&self) -> bool {
|
||||
|
@ -3354,6 +3363,20 @@ impl<'a> RecordField<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_ignored_value(&self) -> bool {
|
||||
let mut current = self;
|
||||
|
||||
loop {
|
||||
match current {
|
||||
RecordField::IgnoredValue(_, _, _) => break true,
|
||||
RecordField::SpaceBefore(field, _) | RecordField::SpaceAfter(field, _) => {
|
||||
current = *field;
|
||||
}
|
||||
_ => break false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_assigned_field(
|
||||
self,
|
||||
arena: &'a Bump,
|
||||
|
@ -3369,6 +3392,10 @@ impl<'a> RecordField<'a> {
|
|||
Ok(OptionalValue(loc_label, spaces, loc_expr))
|
||||
}
|
||||
|
||||
RecordField::IgnoredValue(loc_label, spaces, loc_expr) => {
|
||||
Ok(IgnoredValue(loc_label, spaces, loc_expr))
|
||||
}
|
||||
|
||||
RecordField::LabelOnly(loc_label) => Ok(LabelOnly(loc_label)),
|
||||
|
||||
RecordField::ApplyValue(_, _, _, _) => Err(FoundApplyValue),
|
||||
|
@ -3390,7 +3417,7 @@ impl<'a> RecordField<'a> {
|
|||
fn to_builder_field(
|
||||
self,
|
||||
arena: &'a Bump,
|
||||
) -> Result<OldRecordBuilderField<'a>, FoundOptionalValue> {
|
||||
) -> Result<OldRecordBuilderField<'a>, NotOldBuilderFieldValue> {
|
||||
use OldRecordBuilderField::*;
|
||||
|
||||
match self {
|
||||
|
@ -3398,7 +3425,9 @@ impl<'a> RecordField<'a> {
|
|||
Ok(Value(loc_label, spaces, loc_expr))
|
||||
}
|
||||
|
||||
RecordField::OptionalValue(_, _, _) => Err(FoundOptionalValue),
|
||||
RecordField::OptionalValue(_, _, _) => Err(NotOldBuilderFieldValue::FoundOptionalValue),
|
||||
|
||||
RecordField::IgnoredValue(_, _, _) => Err(NotOldBuilderFieldValue::FoundIgnoredValue),
|
||||
|
||||
RecordField::LabelOnly(loc_label) => Ok(LabelOnly(loc_label)),
|
||||
|
||||
|
@ -3434,42 +3463,70 @@ pub fn record_field<'a>() -> impl Parser<'a, RecordField<'a>, ERecord<'a>> {
|
|||
use RecordField::*;
|
||||
|
||||
map_with_arena(
|
||||
and(
|
||||
specialize_err(|_, pos| ERecord::Field(pos), loc(lowercase_ident())),
|
||||
either(
|
||||
and(
|
||||
spaces(),
|
||||
optional(either(
|
||||
and(byte(b':', ERecord::Colon), record_field_expr()),
|
||||
and(
|
||||
byte(b'?', ERecord::QuestionMark),
|
||||
specialize_err(|_, pos| ERecord::Field(pos), loc(lowercase_ident())),
|
||||
and(
|
||||
spaces(),
|
||||
optional(either(
|
||||
and(byte(b':', ERecord::Colon), record_field_expr()),
|
||||
and(
|
||||
byte(b'?', ERecord::QuestionMark),
|
||||
spaces_before(specialize_err_ref(ERecord::Expr, loc_expr(false))),
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
and(
|
||||
loc(skip_first(
|
||||
byte(b'_', ERecord::UnderscoreField),
|
||||
optional(specialize_err(
|
||||
|_, pos| ERecord::Field(pos),
|
||||
lowercase_ident(),
|
||||
)),
|
||||
)),
|
||||
and(
|
||||
spaces(),
|
||||
skip_first(
|
||||
byte(b':', ERecord::Colon),
|
||||
spaces_before(specialize_err_ref(ERecord::Expr, loc_expr(false))),
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|arena: &'a bumpalo::Bump, (loc_label, (spaces, opt_loc_val))| {
|
||||
match opt_loc_val {
|
||||
Some(Either::First((_, RecordFieldExpr::Value(loc_val)))) => {
|
||||
RequiredValue(loc_label, spaces, arena.alloc(loc_val))
|
||||
}
|
||||
|arena: &'a bumpalo::Bump, field_data| {
|
||||
match field_data {
|
||||
Either::First((loc_label, (spaces, opt_loc_val))) => {
|
||||
match opt_loc_val {
|
||||
Some(Either::First((_, RecordFieldExpr::Value(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::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))
|
||||
}
|
||||
Some(Either::Second((_, loc_val))) => {
|
||||
OptionalValue(loc_label, spaces, arena.alloc(loc_val))
|
||||
}
|
||||
|
||||
// If no value was provided, record it as a Var.
|
||||
// Canonicalize will know what to do with a Var later.
|
||||
None => {
|
||||
if !spaces.is_empty() {
|
||||
SpaceAfter(arena.alloc(LabelOnly(loc_label)), spaces)
|
||||
} else {
|
||||
LabelOnly(loc_label)
|
||||
// If no value was provided, record it as a Var.
|
||||
// Canonicalize will know what to do with a Var later.
|
||||
None => {
|
||||
if !spaces.is_empty() {
|
||||
SpaceAfter(arena.alloc(LabelOnly(loc_label)), spaces)
|
||||
} else {
|
||||
LabelOnly(loc_label)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Either::Second((loc_opt_label, (spaces, loc_val))) => {
|
||||
let loc_label = loc_opt_label
|
||||
.map(|opt_label| opt_label.unwrap_or_else(|| arena.alloc_str("")));
|
||||
|
||||
IgnoredValue(loc_label, spaces, arena.alloc(loc_val))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -3573,20 +3630,23 @@ fn record_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
|
|||
new_record_builder_help(arena, mapper, record.fields)
|
||||
}
|
||||
None => {
|
||||
let is_old_record_builder = record
|
||||
.fields
|
||||
.iter()
|
||||
.any(|field| field.value.is_apply_value());
|
||||
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() {
|
||||
Some(Err(EExpr::RecordUpdateIgnoredField(field.region)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if is_old_record_builder {
|
||||
old_record_builder_help(arena, record.fields)
|
||||
} else {
|
||||
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())
|
||||
});
|
||||
|
||||
Ok(Expr::Record(fields))
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -3609,11 +3669,14 @@ 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(_, _, _)) => {
|
||||
Err(EExpr::RecordUpdateIgnoredField(loc_field.region))
|
||||
}
|
||||
Ok(builder_field) => Ok(Loc {
|
||||
region: loc_field.region,
|
||||
value: builder_field,
|
||||
}),
|
||||
Err(FoundApplyValue) => Err(EExpr::RecordUpdateAccumulator(loc_field.region)),
|
||||
Err(FoundApplyValue) => Err(EExpr::RecordUpdateOldBuilderField(loc_field.region)),
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -3634,7 +3697,7 @@ fn new_record_builder_help<'a>(
|
|||
region: loc_field.region,
|
||||
value: builder_field,
|
||||
}),
|
||||
Err(FoundApplyValue) => Err(EExpr::RecordBuilderAccumulator(loc_field.region)),
|
||||
Err(FoundApplyValue) => Err(EExpr::RecordBuilderOldBuilderField(loc_field.region)),
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -3654,7 +3717,12 @@ fn old_record_builder_help<'a>(
|
|||
region: loc_field.region,
|
||||
value: builder_field,
|
||||
}),
|
||||
Err(FoundOptionalValue) => Err(EExpr::OptionalValueInRecordBuilder(loc_field.region)),
|
||||
Err(NotOldBuilderFieldValue::FoundOptionalValue) => {
|
||||
Err(EExpr::OptionalValueInOldRecordBuilder(loc_field.region))
|
||||
}
|
||||
Err(NotOldBuilderFieldValue::FoundIgnoredValue) => {
|
||||
Err(EExpr::IgnoredValueInOldRecordBuilder(loc_field.region))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -372,9 +372,11 @@ pub enum EExpr<'a> {
|
|||
|
||||
InParens(EInParens<'a>, Position),
|
||||
Record(ERecord<'a>, Position),
|
||||
OptionalValueInRecordBuilder(Region),
|
||||
RecordUpdateAccumulator(Region),
|
||||
RecordBuilderAccumulator(Region),
|
||||
OptionalValueInOldRecordBuilder(Region),
|
||||
IgnoredValueInOldRecordBuilder(Region),
|
||||
RecordUpdateOldBuilderField(Region),
|
||||
RecordUpdateIgnoredField(Region),
|
||||
RecordBuilderOldBuilderField(Region),
|
||||
|
||||
// SingleQuote errors are folded into the EString
|
||||
Str(EString<'a>, Position),
|
||||
|
@ -428,6 +430,7 @@ pub enum ERecord<'a> {
|
|||
|
||||
Prefix(Position),
|
||||
Field(Position),
|
||||
UnderscoreField(Position),
|
||||
Colon(Position),
|
||||
QuestionMark(Position),
|
||||
Arrow(Position),
|
||||
|
@ -577,6 +580,7 @@ pub enum EImportParams<'a> {
|
|||
RecordUpdateFound(Region),
|
||||
RecordBuilderFound(Region),
|
||||
RecordApplyFound(Region),
|
||||
RecordIgnoredFieldFound(Region),
|
||||
Space(BadInputError, Position),
|
||||
}
|
||||
|
||||
|
@ -735,6 +739,7 @@ pub enum ETypeAbilityImpl<'a> {
|
|||
Open(Position),
|
||||
|
||||
Field(Position),
|
||||
UnderscoreField(Position),
|
||||
Colon(Position),
|
||||
Arrow(Position),
|
||||
Optional(Position),
|
||||
|
@ -756,6 +761,7 @@ impl<'a> From<ERecord<'a>> for ETypeAbilityImpl<'a> {
|
|||
ERecord::End(p) => ETypeAbilityImpl::End(p),
|
||||
ERecord::Open(p) => ETypeAbilityImpl::Open(p),
|
||||
ERecord::Field(p) => ETypeAbilityImpl::Field(p),
|
||||
ERecord::UnderscoreField(p) => ETypeAbilityImpl::UnderscoreField(p),
|
||||
ERecord::Colon(p) => ETypeAbilityImpl::Colon(p),
|
||||
ERecord::Arrow(p) => ETypeAbilityImpl::Arrow(p),
|
||||
ERecord::Space(s, p) => ETypeAbilityImpl::Space(s, p),
|
||||
|
|
|
@ -543,6 +543,11 @@ impl<'a, T: RemoveSpaces<'a> + Copy + std::fmt::Debug> RemoveSpaces<'a> for Assi
|
|||
arena.alloc([]),
|
||||
arena.alloc(c.remove_spaces(arena)),
|
||||
),
|
||||
AssignedField::IgnoredValue(a, _, c) => AssignedField::IgnoredValue(
|
||||
a.remove_spaces(arena),
|
||||
arena.alloc([]),
|
||||
arena.alloc(c.remove_spaces(arena)),
|
||||
),
|
||||
AssignedField::LabelOnly(a) => AssignedField::LabelOnly(a.remove_spaces(arena)),
|
||||
AssignedField::Malformed(a) => AssignedField::Malformed(a),
|
||||
AssignedField::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
|
@ -983,8 +988,11 @@ impl<'a> RemoveSpaces<'a> for EExpr<'a> {
|
|||
EExpr::Record(inner_err, _pos) => {
|
||||
EExpr::Record(inner_err.remove_spaces(arena), Position::zero())
|
||||
}
|
||||
EExpr::OptionalValueInRecordBuilder(_pos) => {
|
||||
EExpr::OptionalValueInRecordBuilder(Region::zero())
|
||||
EExpr::OptionalValueInOldRecordBuilder(_pos) => {
|
||||
EExpr::OptionalValueInOldRecordBuilder(Region::zero())
|
||||
}
|
||||
EExpr::IgnoredValueInOldRecordBuilder(_pos) => {
|
||||
EExpr::OptionalValueInOldRecordBuilder(Region::zero())
|
||||
}
|
||||
EExpr::Str(inner_err, _pos) => {
|
||||
EExpr::Str(inner_err.remove_spaces(arena), Position::zero())
|
||||
|
@ -998,8 +1006,15 @@ impl<'a> RemoveSpaces<'a> for EExpr<'a> {
|
|||
EExpr::UnexpectedComma(_pos) => EExpr::UnexpectedComma(Position::zero()),
|
||||
EExpr::UnexpectedTopLevelExpr(_pos) => EExpr::UnexpectedTopLevelExpr(Position::zero()),
|
||||
EExpr::StmtAfterExpr(_pos) => EExpr::StmtAfterExpr(Position::zero()),
|
||||
EExpr::RecordUpdateAccumulator(_) => EExpr::RecordUpdateAccumulator(Region::zero()),
|
||||
EExpr::RecordBuilderAccumulator(_) => EExpr::RecordBuilderAccumulator(Region::zero()),
|
||||
EExpr::RecordUpdateOldBuilderField(_pos) => {
|
||||
EExpr::RecordUpdateOldBuilderField(Region::zero())
|
||||
}
|
||||
EExpr::RecordUpdateIgnoredField(_pos) => {
|
||||
EExpr::RecordUpdateIgnoredField(Region::zero())
|
||||
}
|
||||
EExpr::RecordBuilderOldBuilderField(_pos) => {
|
||||
EExpr::RecordBuilderOldBuilderField(Region::zero())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1089,6 +1104,7 @@ impl<'a> RemoveSpaces<'a> for ERecord<'a> {
|
|||
ERecord::End(_) => ERecord::End(Position::zero()),
|
||||
ERecord::Open(_) => ERecord::Open(Position::zero()),
|
||||
ERecord::Field(_pos) => ERecord::Field(Position::zero()),
|
||||
ERecord::UnderscoreField(_pos) => ERecord::Field(Position::zero()),
|
||||
ERecord::Colon(_) => ERecord::Colon(Position::zero()),
|
||||
ERecord::QuestionMark(_) => ERecord::QuestionMark(Position::zero()),
|
||||
ERecord::Arrow(_) => ERecord::Arrow(Position::zero()),
|
||||
|
@ -1217,6 +1233,9 @@ impl<'a> RemoveSpaces<'a> for EImportParams<'a> {
|
|||
}
|
||||
EImportParams::RecordUpdateFound(_) => EImportParams::RecordUpdateFound(Region::zero()),
|
||||
EImportParams::RecordApplyFound(_) => EImportParams::RecordApplyFound(Region::zero()),
|
||||
EImportParams::RecordIgnoredFieldFound(_) => {
|
||||
EImportParams::RecordIgnoredFieldFound(Region::zero())
|
||||
}
|
||||
EImportParams::Space(inner_err, _) => {
|
||||
EImportParams::Space(*inner_err, Position::zero())
|
||||
}
|
||||
|
@ -1248,6 +1267,9 @@ impl<'a> RemoveSpaces<'a> for ETypeAbilityImpl<'a> {
|
|||
ETypeAbilityImpl::End(_) => ETypeAbilityImpl::End(Position::zero()),
|
||||
ETypeAbilityImpl::Open(_) => ETypeAbilityImpl::Open(Position::zero()),
|
||||
ETypeAbilityImpl::Field(_) => ETypeAbilityImpl::Field(Position::zero()),
|
||||
ETypeAbilityImpl::UnderscoreField(_) => {
|
||||
ETypeAbilityImpl::UnderscoreField(Position::zero())
|
||||
}
|
||||
ETypeAbilityImpl::Colon(_) => ETypeAbilityImpl::Colon(Position::zero()),
|
||||
ETypeAbilityImpl::Arrow(_) => ETypeAbilityImpl::Arrow(Position::zero()),
|
||||
ETypeAbilityImpl::Optional(_) => ETypeAbilityImpl::Optional(Position::zero()),
|
||||
|
|
|
@ -565,6 +565,9 @@ 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(_, _, _)) => {
|
||||
Err((MadeProgress, ERecord::Field(state.pos())))
|
||||
}
|
||||
Ok(assigned_field) => Ok((MadeProgress, assigned_field, state)),
|
||||
Err(FoundApplyValue) => Err((MadeProgress, ERecord::Field(state.pos()))),
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{ Foo.Bar.baz <-
|
||||
x: 5,
|
||||
y: 0,
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
SpaceAfter(
|
||||
RecordBuilder {
|
||||
mapper: @2-13 Var {
|
||||
module_name: "Foo.Bar",
|
||||
ident: "baz",
|
||||
},
|
||||
fields: [
|
||||
@17-21 RequiredValue(
|
||||
@17-18 "x",
|
||||
[],
|
||||
@20-21 Num(
|
||||
"5",
|
||||
),
|
||||
),
|
||||
@23-27 SpaceAfter(
|
||||
RequiredValue(
|
||||
@23-24 "y",
|
||||
[],
|
||||
@26-27 Num(
|
||||
"0",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
)
|
|
@ -0,0 +1,2 @@
|
|||
{ Foo.Bar.baz <- x: 5, y: 0
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{ Foo.Bar.baz <-
|
||||
x: 5,
|
||||
y: 0,
|
||||
_z: 3,
|
||||
_: 2,
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
SpaceAfter(
|
||||
RecordBuilder {
|
||||
mapper: @2-13 Var {
|
||||
module_name: "Foo.Bar",
|
||||
ident: "baz",
|
||||
},
|
||||
fields: [
|
||||
@17-21 RequiredValue(
|
||||
@17-18 "x",
|
||||
[],
|
||||
@20-21 Num(
|
||||
"5",
|
||||
),
|
||||
),
|
||||
@23-27 RequiredValue(
|
||||
@23-24 "y",
|
||||
[],
|
||||
@26-27 Num(
|
||||
"0",
|
||||
),
|
||||
),
|
||||
@29-34 IgnoredValue(
|
||||
@29-31 "z",
|
||||
[],
|
||||
@33-34 Num(
|
||||
"3",
|
||||
),
|
||||
),
|
||||
@36-40 SpaceAfter(
|
||||
IgnoredValue(
|
||||
@36-37 "",
|
||||
[],
|
||||
@39-40 Num(
|
||||
"2",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
)
|
|
@ -0,0 +1,2 @@
|
|||
{ Foo.Bar.baz <- x: 5, y: 0, _z: 3, _: 2
|
||||
}
|
|
@ -449,6 +449,8 @@ mod test_snapshots {
|
|||
pass/qualified_field.expr,
|
||||
pass/qualified_var.expr,
|
||||
pass/record_access_after_tuple.expr,
|
||||
pass/record_builder.expr,
|
||||
pass/record_builder_ignored_fields.expr,
|
||||
pass/record_destructure_def.expr,
|
||||
pass/record_func_type_decl.expr,
|
||||
pass/record_type_with_function.expr,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue