mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 14:54:47 +00:00
Introduce optional record fields
This commit is contained in:
parent
ecd24932bc
commit
85f51ef39d
9 changed files with 309 additions and 60 deletions
|
@ -276,8 +276,14 @@ pub enum Tag<'a> {
|
|||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum AssignedField<'a, Val> {
|
||||
// Both a label and a value, e.g. `{ name: "blah" }`
|
||||
LabeledValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Val>),
|
||||
// A required field with a label, e.g. `{ name: "blah" }` or `{ name : Str }`
|
||||
RequiredValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Val>),
|
||||
|
||||
// An optional field with a label, e.g. `{ name ? "blah" }`
|
||||
//
|
||||
// NOTE: This only comes up in type annotations (e.g. `name ? Str`)
|
||||
// and in destructuring patterns (e.g. `{ name ? "blah" }`)
|
||||
OptionalValue(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>),
|
||||
|
@ -309,9 +315,14 @@ pub enum Pattern<'a> {
|
|||
/// around the destructured names, e.g. { x ### x does stuff ###, y }
|
||||
/// In practice, these patterns will always be Identifier
|
||||
RecordDestructure(&'a [Loc<Pattern<'a>>]),
|
||||
/// A field pattern, e.g. { x: Just 0 } -> ...
|
||||
/// can only occur inside of a RecordDestructure
|
||||
RecordField(&'a str, &'a Loc<Pattern<'a>>),
|
||||
|
||||
/// A required field pattern, e.g. { x: Just 0 } -> ...
|
||||
/// Can only occur inside of a RecordDestructure
|
||||
RequiredField(&'a str, &'a Loc<Pattern<'a>>),
|
||||
|
||||
/// An optional field pattern, e.g. { x ? Just 0 } -> ...
|
||||
/// Can only occur inside of a RecordDestructure
|
||||
OptionalField(&'a str, &'a Loc<Pattern<'a>>),
|
||||
|
||||
/// This is used only to avoid cloning when reordering expressions (e.g. in desugar()).
|
||||
/// It lets us take an (&Expr) and create a plain (Expr) from it.
|
||||
|
@ -414,7 +425,10 @@ impl<'a> Pattern<'a> {
|
|||
.iter()
|
||||
.zip(fields_y.iter())
|
||||
.all(|(p, q)| p.value.equivalent(&q.value)),
|
||||
(RecordField(x, inner_x), RecordField(y, inner_y)) => {
|
||||
(RequiredField(x, inner_x), RequiredField(y, inner_y)) => {
|
||||
x == y && inner_x.value.equivalent(&inner_y.value)
|
||||
}
|
||||
(OptionalField(x, inner_x), OptionalField(y, inner_y)) => {
|
||||
x == y && inner_x.value.equivalent(&inner_y.value)
|
||||
}
|
||||
(Nested(x), Nested(y)) => x.equivalent(y),
|
||||
|
|
|
@ -332,17 +332,32 @@ pub fn assigned_expr_field_to_pattern<'a>(
|
|||
) -> Result<Pattern<'a>, Fail> {
|
||||
// the assigned fields always store spaces, but this slice is often empty
|
||||
Ok(match assigned_field {
|
||||
AssignedField::LabeledValue(name, spaces, value) => {
|
||||
AssignedField::RequiredValue(name, spaces, value) => {
|
||||
let pattern = expr_to_pattern(arena, &value.value)?;
|
||||
let result = arena.alloc(Located {
|
||||
region: value.region,
|
||||
value: pattern,
|
||||
});
|
||||
if spaces.is_empty() {
|
||||
Pattern::RecordField(name.value, result)
|
||||
Pattern::RequiredField(name.value, result)
|
||||
} else {
|
||||
Pattern::SpaceAfter(
|
||||
arena.alloc(Pattern::RecordField(name.value, result)),
|
||||
arena.alloc(Pattern::RequiredField(name.value, result)),
|
||||
spaces,
|
||||
)
|
||||
}
|
||||
}
|
||||
AssignedField::OptionalValue(name, spaces, value) => {
|
||||
let pattern = expr_to_pattern(arena, &value.value)?;
|
||||
let result = arena.alloc(Located {
|
||||
region: value.region,
|
||||
value: pattern,
|
||||
});
|
||||
if spaces.is_empty() {
|
||||
Pattern::OptionalField(name.value, result)
|
||||
} else {
|
||||
Pattern::SpaceAfter(
|
||||
arena.alloc(Pattern::OptionalField(name.value, result)),
|
||||
spaces,
|
||||
)
|
||||
}
|
||||
|
@ -368,7 +383,7 @@ pub fn assigned_pattern_field_to_pattern<'a>(
|
|||
) -> Result<Located<Pattern<'a>>, Fail> {
|
||||
// the assigned fields always store spaces, but this slice is often empty
|
||||
Ok(match assigned_field {
|
||||
AssignedField::LabeledValue(name, spaces, value) => {
|
||||
AssignedField::RequiredValue(name, spaces, value) => {
|
||||
let pattern = value.value.clone();
|
||||
let region = Region::span_across(&value.region, &value.region);
|
||||
let result = arena.alloc(Located {
|
||||
|
@ -376,12 +391,31 @@ pub fn assigned_pattern_field_to_pattern<'a>(
|
|||
value: pattern,
|
||||
});
|
||||
if spaces.is_empty() {
|
||||
Located::at(region, Pattern::RecordField(name.value, result))
|
||||
Located::at(region, Pattern::RequiredField(name.value, result))
|
||||
} else {
|
||||
Located::at(
|
||||
region,
|
||||
Pattern::SpaceAfter(
|
||||
arena.alloc(Pattern::RecordField(name.value, result)),
|
||||
arena.alloc(Pattern::RequiredField(name.value, result)),
|
||||
spaces,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
AssignedField::OptionalValue(name, spaces, value) => {
|
||||
let pattern = value.value.clone();
|
||||
let region = Region::span_across(&value.region, &value.region);
|
||||
let result = arena.alloc(Located {
|
||||
region: value.region,
|
||||
value: pattern,
|
||||
});
|
||||
if spaces.is_empty() {
|
||||
Located::at(region, Pattern::OptionalField(name.value, result))
|
||||
} else {
|
||||
Located::at(
|
||||
region,
|
||||
Pattern::SpaceAfter(
|
||||
arena.alloc(Pattern::OptionalField(name.value, result)),
|
||||
spaces,
|
||||
),
|
||||
)
|
||||
|
@ -580,7 +614,7 @@ fn annotation_or_alias<'a>(
|
|||
loc_ann,
|
||||
)
|
||||
}
|
||||
RecordField(_, _) => {
|
||||
RequiredField(_, _) | OptionalField(_, _) => {
|
||||
unreachable!("This should only be possible inside a record destruture.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -912,21 +912,26 @@ macro_rules! record_field {
|
|||
use $crate::ast::AssignedField::*;
|
||||
use $crate::blankspace::{space0, space0_before};
|
||||
use $crate::ident::lowercase_ident;
|
||||
use $crate::parser::Either::*;
|
||||
|
||||
// You must have a field name, e.g. "email"
|
||||
let (loc_label, state) = loc!(lowercase_ident()).parse(arena, state)?;
|
||||
|
||||
let (spaces, state) = space0($min_indent).parse(arena, state)?;
|
||||
|
||||
// Having a value is optional; both `{ email }` and `{ email: blah }` work.
|
||||
// (This is true in both literals and types.)
|
||||
let (opt_loc_val, state) = $crate::parser::optional(skip_first!(
|
||||
char(':'),
|
||||
space0_before($val_parser, $min_indent)
|
||||
let (opt_loc_val, state) = $crate::parser::optional(either!(
|
||||
skip_first!(char(':'), space0_before($val_parser, $min_indent)),
|
||||
skip_first!(char('?'), space0_before($val_parser, $min_indent))
|
||||
))
|
||||
.parse(arena, state)?;
|
||||
|
||||
let answer = match opt_loc_val {
|
||||
Some(loc_val) => LabeledValue(loc_label, spaces, arena.alloc(loc_val)),
|
||||
Some(either) => match either {
|
||||
First(loc_val) => RequiredValue(loc_label, spaces, arena.alloc(loc_val)),
|
||||
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 => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue