fix tag pattern argument bug

This commit is contained in:
Folkert 2020-11-09 23:22:08 +01:00
parent 58a7ea5572
commit 78063f87d7
2 changed files with 144 additions and 27 deletions

View file

@ -1002,7 +1002,7 @@ fn parse_closure_param<'a>(
) -> ParseResult<'a, Located<Pattern<'a>>> { ) -> ParseResult<'a, Located<Pattern<'a>>> {
one_of!( one_of!(
// An ident is the most common param, e.g. \foo -> ... // An ident is the most common param, e.g. \foo -> ...
loc_ident_pattern(min_indent), loc_ident_pattern(min_indent, true),
// Underscore is also common, e.g. \_ -> ... // Underscore is also common, e.g. \_ -> ...
loc!(underscore_pattern()), loc!(underscore_pattern()),
// You can destructure records in params, e.g. \{ x, y } -> ... // You can destructure records in params, e.g. \{ x, y } -> ...
@ -1026,7 +1026,7 @@ fn loc_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> {
one_of!( one_of!(
loc_parenthetical_pattern(min_indent), loc_parenthetical_pattern(min_indent),
loc!(underscore_pattern()), loc!(underscore_pattern()),
loc_ident_pattern(min_indent), loc_ident_pattern(min_indent, true),
loc!(record_destructure(min_indent)), loc!(record_destructure(min_indent)),
loc!(string_pattern()), loc!(string_pattern()),
loc!(number_pattern()) loc!(number_pattern())
@ -1034,6 +1034,38 @@ fn loc_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> {
) )
} }
fn loc_tag_pattern_args<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Located<Pattern<'a>>>> {
zero_or_more!(space1_before(loc_tag_pattern_arg(min_indent), min_indent))
}
fn loc_tag_pattern_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> {
skip_first!(
// If this is a reserved keyword ("if", "then", "case, "when"), then
// it is not a function argument!
not(reserved_keyword()),
// Don't parse operators, because they have a higher precedence than function application.
// If we encounter one, we're done parsing function args!
move |arena, state| loc_parse_tag_pattern_arg(min_indent, arena, state)
)
}
fn loc_parse_tag_pattern_arg<'a>(
min_indent: u16,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Located<Pattern<'a>>> {
one_of!(
loc_parenthetical_pattern(min_indent),
loc!(underscore_pattern()),
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
loc_ident_pattern(min_indent, false),
loc!(record_destructure(min_indent)),
loc!(string_pattern()),
loc!(number_pattern())
)
.parse(arena, state)
}
fn loc_parenthetical_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> { fn loc_parenthetical_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> {
between!( between!(
ascii_char(b'('), ascii_char(b'('),
@ -1133,20 +1165,24 @@ fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> {
) )
} }
fn loc_ident_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> { fn loc_ident_pattern<'a>(
min_indent: u16,
can_have_arguments: bool,
) -> impl Parser<'a, Located<Pattern<'a>>> {
move |arena: &'a Bump, state: State<'a>| { move |arena: &'a Bump, state: State<'a>| {
let (loc_ident, state) = loc!(ident()).parse(arena, state)?; let (loc_ident, state) = loc!(ident()).parse(arena, state)?;
match loc_ident.value { match loc_ident.value {
Ident::GlobalTag(tag) => { Ident::GlobalTag(tag) => {
let (loc_args, state) =
zero_or_more!(space1_before(loc_pattern(min_indent), min_indent))
.parse(arena, state)?;
let loc_tag = Located { let loc_tag = Located {
region: loc_ident.region, region: loc_ident.region,
value: Pattern::GlobalTag(tag), value: Pattern::GlobalTag(tag),
}; };
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
if can_have_arguments {
let (loc_args, state) = loc_tag_pattern_args(min_indent).parse(arena, state)?;
if loc_args.is_empty() { if loc_args.is_empty() {
Ok((loc_tag, state)) Ok((loc_tag, state))
} else { } else {
@ -1154,20 +1190,25 @@ fn loc_ident_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>
std::iter::once(&loc_ident.region) std::iter::once(&loc_ident.region)
.chain(loc_args.iter().map(|loc_arg| &loc_arg.region)), .chain(loc_args.iter().map(|loc_arg| &loc_arg.region)),
); );
let value = Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice()); let value =
Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
Ok((Located { region, value }, state)) Ok((Located { region, value }, state))
} }
} else {
Ok((loc_tag, state))
}
} }
Ident::PrivateTag(tag) => { Ident::PrivateTag(tag) => {
let (loc_args, state) =
zero_or_more!(space1_before(loc_pattern(min_indent), min_indent))
.parse(arena, state)?;
let loc_tag = Located { let loc_tag = Located {
region: loc_ident.region, region: loc_ident.region,
value: Pattern::PrivateTag(tag), value: Pattern::PrivateTag(tag),
}; };
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
if can_have_arguments {
let (loc_args, state) = loc_tag_pattern_args(min_indent).parse(arena, state)?;
if loc_args.is_empty() { if loc_args.is_empty() {
Ok((loc_tag, state)) Ok((loc_tag, state))
} else { } else {
@ -1175,10 +1216,14 @@ fn loc_ident_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>
std::iter::once(&loc_ident.region) std::iter::once(&loc_ident.region)
.chain(loc_args.iter().map(|loc_arg| &loc_arg.region)), .chain(loc_args.iter().map(|loc_arg| &loc_arg.region)),
); );
let value = Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice()); let value =
Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
Ok((Located { region, value }, state)) Ok((Located { region, value }, state))
} }
} else {
Ok((loc_tag, state))
}
} }
Ident::Access { module_name, parts } => { Ident::Access { module_name, parts } => {
// Plain identifiers (e.g. `foo`) are allowed in patterns, but // Plain identifiers (e.g. `foo`) are allowed in patterns, but

View file

@ -2980,4 +2980,76 @@ mod solve_expr {
"List x", "List x",
); );
} }
#[test]
fn double_tag_application() {
infer_eq_without_problem(
indoc!(
r#"
app Test provides [ main ] imports []
main =
if 1 == 1 then
Foo (Bar) 1
else
Foo Bar 1
"#
),
"[ Foo [ Bar ]* (Num *) ]*",
);
infer_eq_without_problem("Foo Bar 1", "[ Foo [ Bar ]* (Num *) ]*");
}
#[test]
fn double_tag_application_pattern_global() {
infer_eq_without_problem(
indoc!(
r#"
app Test provides [ main ] imports []
Bar : [ Bar ]
Foo : [ Foo Bar Int, Empty ]
foo : Foo
foo = Foo Bar 1
main =
when foo is
Foo Bar 1 ->
Foo Bar 2
x ->
x
"#
),
"[ Empty, Foo [ Bar ] Int ]",
);
}
#[test]
fn double_tag_application_pattern_private() {
infer_eq_without_problem(
indoc!(
r#"
app Test provides [ main ] imports []
Foo : [ @Foo [ @Bar ] Int, @Empty ]
foo : Foo
foo = @Foo @Bar 1
main =
when foo is
@Foo @Bar 1 ->
@Foo @Bar 2
x ->
x
"#
),
"[ @Empty, @Foo [ @Bar ] Int ]",
);
}
} }