mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 22:34:45 +00:00
parse default optional expressions in pattern matches
This commit is contained in:
parent
3656257191
commit
29c3eebace
6 changed files with 209 additions and 26 deletions
|
@ -1,5 +1,5 @@
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
use crate::expr::Expr;
|
use crate::expr::{canonicalize_expr, Expr};
|
||||||
use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
|
use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
use roc_module::ident::{Ident, Lowercase, TagName};
|
use roc_module::ident::{Ident, Lowercase, TagName};
|
||||||
|
@ -302,6 +302,48 @@ pub fn canonicalize_pattern<'a>(
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
OptionalField(label, loc_default) => {
|
||||||
|
// an optional DOES introduce the label into scope!
|
||||||
|
match scope.introduce(
|
||||||
|
label.into(),
|
||||||
|
&env.exposed_ident_ids,
|
||||||
|
&mut env.ident_ids,
|
||||||
|
region,
|
||||||
|
) {
|
||||||
|
Ok(symbol) => {
|
||||||
|
// TODO use output?
|
||||||
|
let (can_default, _output) = canonicalize_expr(
|
||||||
|
env,
|
||||||
|
var_store,
|
||||||
|
scope,
|
||||||
|
loc_default.region,
|
||||||
|
&loc_default.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
destructs.push(Located {
|
||||||
|
region: loc_pattern.region,
|
||||||
|
value: RecordDestruct {
|
||||||
|
var: var_store.fresh(),
|
||||||
|
label: Lowercase::from(label),
|
||||||
|
symbol,
|
||||||
|
typ: DestructType::Optional(var_store.fresh(), can_default),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err((original_region, shadow)) => {
|
||||||
|
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
||||||
|
original_region,
|
||||||
|
shadow: shadow.clone(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// No matter what the other patterns
|
||||||
|
// are, we're definitely shadowed and will
|
||||||
|
// get a runtime exception as soon as we
|
||||||
|
// encounter the first bad pattern.
|
||||||
|
opt_erroneous = Some(Pattern::Shadowed(original_region, shadow));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
_ => unreachable!("Any other pattern should have given a parse error"),
|
_ => unreachable!("Any other pattern should have given a parse error"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -315,7 +357,10 @@ pub fn canonicalize_pattern<'a>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
RequiredField(_name, _loc_pattern) | OptionalField(_name, _loc_pattern) => {
|
RequiredField(_name, _loc_pattern) => {
|
||||||
|
unreachable!("should have been handled in RecordDestructure");
|
||||||
|
}
|
||||||
|
OptionalField(_name, _loc_pattern) => {
|
||||||
unreachable!("should have been handled in RecordDestructure");
|
unreachable!("should have been handled in RecordDestructure");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,9 +25,9 @@ impl<'a> Formattable<'a> for Pattern<'a> {
|
||||||
Pattern::Nested(nested_pat) => nested_pat.is_multiline(),
|
Pattern::Nested(nested_pat) => nested_pat.is_multiline(),
|
||||||
|
|
||||||
Pattern::RecordDestructure(fields) => fields.iter().any(|f| f.is_multiline()),
|
Pattern::RecordDestructure(fields) => fields.iter().any(|f| f.is_multiline()),
|
||||||
Pattern::RequiredField(_, subpattern) | Pattern::OptionalField(_, subpattern) => {
|
Pattern::RequiredField(_, subpattern) => subpattern.is_multiline(),
|
||||||
subpattern.is_multiline()
|
|
||||||
}
|
Pattern::OptionalField(_, expr) => expr.is_multiline(),
|
||||||
|
|
||||||
Pattern::Identifier(_)
|
Pattern::Identifier(_)
|
||||||
| Pattern::GlobalTag(_)
|
| Pattern::GlobalTag(_)
|
||||||
|
|
|
@ -322,7 +322,7 @@ pub enum Pattern<'a> {
|
||||||
|
|
||||||
/// An optional field pattern, e.g. { x ? Just 0 } -> ...
|
/// An optional field pattern, e.g. { x ? Just 0 } -> ...
|
||||||
/// Can only occur inside of a RecordDestructure
|
/// Can only occur inside of a RecordDestructure
|
||||||
OptionalField(&'a str, &'a Loc<Pattern<'a>>),
|
OptionalField(&'a str, &'a Loc<Expr<'a>>),
|
||||||
|
|
||||||
/// This is used only to avoid cloning when reordering expressions (e.g. in desugar()).
|
/// 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.
|
/// It lets us take an (&Expr) and create a plain (Expr) from it.
|
||||||
|
@ -428,8 +428,21 @@ impl<'a> Pattern<'a> {
|
||||||
(RequiredField(x, inner_x), RequiredField(y, inner_y)) => {
|
(RequiredField(x, inner_x), RequiredField(y, inner_y)) => {
|
||||||
x == y && inner_x.value.equivalent(&inner_y.value)
|
x == y && inner_x.value.equivalent(&inner_y.value)
|
||||||
}
|
}
|
||||||
(OptionalField(x, inner_x), OptionalField(y, inner_y)) => {
|
(OptionalField(x, _inner_x), OptionalField(y, _inner_y)) => {
|
||||||
x == y && inner_x.value.equivalent(&inner_y.value)
|
x == y
|
||||||
|
// TODO
|
||||||
|
//
|
||||||
|
// We can give an annotation like so
|
||||||
|
//
|
||||||
|
// { x, y } : { x : Int, y : Bool }
|
||||||
|
// { x, y } = rec
|
||||||
|
//
|
||||||
|
// But what about:
|
||||||
|
//
|
||||||
|
// { x, y ? False } : { x : Int, y ? Bool }
|
||||||
|
// { x, y ? False } = rec
|
||||||
|
//
|
||||||
|
// inner_x.value.equivalent(&inner_y.value)
|
||||||
}
|
}
|
||||||
(Nested(x), Nested(y)) => x.equivalent(y),
|
(Nested(x), Nested(y)) => x.equivalent(y),
|
||||||
|
|
||||||
|
|
|
@ -348,10 +348,9 @@ pub fn assigned_expr_field_to_pattern<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AssignedField::OptionalValue(name, spaces, value) => {
|
AssignedField::OptionalValue(name, spaces, value) => {
|
||||||
let pattern = expr_to_pattern(arena, &value.value)?;
|
|
||||||
let result = arena.alloc(Located {
|
let result = arena.alloc(Located {
|
||||||
region: value.region,
|
region: value.region,
|
||||||
value: pattern,
|
value: value.value.clone(),
|
||||||
});
|
});
|
||||||
if spaces.is_empty() {
|
if spaces.is_empty() {
|
||||||
Pattern::OptionalField(name.value, result)
|
Pattern::OptionalField(name.value, result)
|
||||||
|
@ -378,13 +377,13 @@ pub fn assigned_expr_field_to_pattern<'a>(
|
||||||
/// Used for patterns like { x: Just _ }
|
/// Used for patterns like { x: Just _ }
|
||||||
pub fn assigned_pattern_field_to_pattern<'a>(
|
pub fn assigned_pattern_field_to_pattern<'a>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
assigned_field: &AssignedField<'a, Pattern<'a>>,
|
assigned_field: &AssignedField<'a, Expr<'a>>,
|
||||||
backup_region: Region,
|
backup_region: Region,
|
||||||
) -> Result<Located<Pattern<'a>>, Fail> {
|
) -> Result<Located<Pattern<'a>>, Fail> {
|
||||||
// the assigned fields always store spaces, but this slice is often empty
|
// the assigned fields always store spaces, but this slice is often empty
|
||||||
Ok(match assigned_field {
|
Ok(match assigned_field {
|
||||||
AssignedField::RequiredValue(name, spaces, value) => {
|
AssignedField::RequiredValue(name, spaces, value) => {
|
||||||
let pattern = value.value.clone();
|
let pattern = expr_to_pattern(arena, &value.value)?;
|
||||||
let region = Region::span_across(&value.region, &value.region);
|
let region = Region::span_across(&value.region, &value.region);
|
||||||
let result = arena.alloc(Located {
|
let result = arena.alloc(Located {
|
||||||
region: value.region,
|
region: value.region,
|
||||||
|
@ -929,22 +928,72 @@ fn underscore_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
|
||||||
|
|
||||||
fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> {
|
fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> {
|
||||||
then(
|
then(
|
||||||
record_without_update!(loc_pattern(min_indent), min_indent),
|
collection!(
|
||||||
move |arena, state, assigned_fields| {
|
char('{'),
|
||||||
let mut patterns = Vec::with_capacity_in(assigned_fields.len(), arena);
|
move |arena: &'a bumpalo::Bump,
|
||||||
for assigned_field in assigned_fields {
|
state: crate::parser::State<'a>|
|
||||||
match assigned_pattern_field_to_pattern(
|
-> crate::parser::ParseResult<'a, Located<crate::ast::Pattern<'a>>> {
|
||||||
arena,
|
use crate::blankspace::{space0, space0_before};
|
||||||
&assigned_field.value,
|
use crate::ident::lowercase_ident;
|
||||||
assigned_field.region,
|
use crate::parser::Either::*;
|
||||||
) {
|
use roc_region::all::Region;
|
||||||
Ok(pattern) => patterns.push(pattern),
|
|
||||||
Err(e) => return Err((e, state)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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(either!(
|
||||||
|
skip_first!(
|
||||||
|
char(':'),
|
||||||
|
space0_before(loc_pattern(min_indent), min_indent)
|
||||||
|
),
|
||||||
|
skip_first!(char('?'), space0_before(loc!(expr(min_indent)), min_indent))
|
||||||
|
))
|
||||||
|
.parse(arena, state)?;
|
||||||
|
|
||||||
|
let answer = match opt_loc_val {
|
||||||
|
Some(either) => match either {
|
||||||
|
First(loc_val) => Located {
|
||||||
|
region: Region::span_across(&loc_label.region, &loc_val.region),
|
||||||
|
value: Pattern::RequiredField(loc_label.value, arena.alloc(loc_val)),
|
||||||
|
},
|
||||||
|
Second(loc_val) => Located {
|
||||||
|
region: Region::span_across(&loc_label.region, &loc_val.region),
|
||||||
|
value: Pattern::OptionalField(loc_label.value, 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() {
|
||||||
|
Located {
|
||||||
|
region: loc_label.region,
|
||||||
|
value: Pattern::SpaceAfter(
|
||||||
|
arena.alloc(Pattern::Identifier(loc_label.value)),
|
||||||
|
spaces,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Located {
|
||||||
|
region: loc_label.region,
|
||||||
|
value: Pattern::Identifier(loc_label.value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((answer, state))
|
||||||
|
},
|
||||||
|
char(','),
|
||||||
|
char('}'),
|
||||||
|
min_indent
|
||||||
|
),
|
||||||
|
move |_arena, state, loc_patterns| {
|
||||||
Ok((
|
Ok((
|
||||||
Pattern::RecordDestructure(patterns.into_bump_slice()),
|
Pattern::RecordDestructure(loc_patterns.into_bump_slice()),
|
||||||
state,
|
state,
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
|
|
|
@ -2629,4 +2629,16 @@ mod solve_expr {
|
||||||
"{ a : { x : Num a, y : Float, z : c }, b : { blah : Str, x : Num a, y : Float, z : c } }",
|
"{ a : { x : Num a, y : Float, z : c }, b : { blah : Str, x : Num a, y : Float, z : c } }",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn optional_field_function() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
\{ x, y ? 0 } -> x + y
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"{ x : Num a, y ? Num a }* -> Num a",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3011,4 +3011,68 @@ mod solve_uniq_expr {
|
||||||
"Attr * (Attr Shared (Num (Attr Shared *)) -> Attr * (Num (Attr * *)))",
|
"Attr * (Attr Shared (Num (Attr Shared *)) -> Attr * (Num (Attr * *)))",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OPTIONAL RECORD FIELDS
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn optional_field_unifies_with_missing() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
negatePoint : { x : Int, y : Int, z ? Num c } -> { x : Int, y : Int, z : Num c }
|
||||||
|
|
||||||
|
negatePoint { x: 1, y: 2 }
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Attr * { x : (Attr * Int), y : (Attr * Int), z : (Attr * (Num (Attr * c))) }",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn open_optional_field_unifies_with_missing() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
negatePoint : { x : Int, y : Int, z ? Num c }r -> { x : Int, y : Int, z : Num c }r
|
||||||
|
|
||||||
|
a = negatePoint { x: 1, y: 2 }
|
||||||
|
b = negatePoint { x: 1, y: 2, blah : "hi" }
|
||||||
|
|
||||||
|
{ a, b }
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Attr * { a : (Attr * { x : (Attr * Int), y : (Attr * Int), z : (Attr * (Num (Attr * c))) }), b : (Attr * { blah : (Attr * Str), x : (Attr * Int), y : (Attr * Int), z : (Attr * (Num (Attr * c))) }) }"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn optional_field_unifies_with_present() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
negatePoint : { x : Num a, y : Num b, z ? c } -> { x : Num a, y : Num b, z : c }
|
||||||
|
|
||||||
|
negatePoint { x: 1, y: 2.1, z: 0x3 }
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Attr * { x : (Attr * (Num (Attr * a))), y : (Attr * Float), z : (Attr * Int) }",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn open_optional_field_unifies_with_present() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
negatePoint : { x : Num a, y : Num b, z ? c }r -> { x : Num a, y : Num b, z : c }r
|
||||||
|
|
||||||
|
a = negatePoint { x: 1, y: 2.1 }
|
||||||
|
b = negatePoint { x: 1, y: 2.1, blah : "hi" }
|
||||||
|
|
||||||
|
{ a, b }
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Attr * { a : (Attr * { x : (Attr * (Num (Attr * a))), y : (Attr * Float), z : (Attr * c) }), b : (Attr * { blah : (Attr * Str), x : (Attr * (Num (Attr * a))), y : (Attr * Float), z : (Attr * c) }) }"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue