Introduce optional record fields

This commit is contained in:
Richard Feldman 2020-07-18 11:21:32 -04:00
parent ecd24932bc
commit 85f51ef39d
9 changed files with 309 additions and 60 deletions

View file

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

View file

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

View file

@ -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 => {