Ignore underscore-prefixed fields in record builders

This commit is contained in:
Sam Mohr 2024-08-04 03:34:39 -07:00
parent 13f60cde09
commit cb8040f629
No known key found for this signature in database
GPG key ID: EA41D161A3C1BC99
16 changed files with 336 additions and 140 deletions

View file

@ -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()

View file

@ -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))
}
}
});

View file

@ -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),

View file

@ -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()),

View file

@ -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()))),
}