Merge branch 'main' of github.com:lukewilliamboswell/roc into rust-docs

This commit is contained in:
Anton-4 2022-11-08 15:57:24 +01:00
commit a6a2b59a79
No known key found for this signature in database
GPG Key ID: A13F4A6E21141925
14 changed files with 378 additions and 378 deletions

View File

@ -18,7 +18,6 @@ jobs:
with:
use-quiet-mode: 'yes'
use-verbose-mode: 'yes'
check-modified-files-only: 'yes'
base-branch: 'main'
check-modified-files-only: 'yes'
if: ${{ github.event_name == 'pull_request' }}

View File

@ -570,6 +570,26 @@ We refer to whatever comes before a `->` in a `when` expression as a *pattern* -
patterns in branching conditionals like `when` is known as [pattern matching](https://en.wikipedia.org/wiki/Pattern_matching). You may hear people say things like "let's pattern match on `Custom` here" as a way to
suggest making a `when` branch that begins with something like `Custom description ->`.
### Pattern Matching on Lists
You can also pattern match on lists, like so:
when myList is
[] -> 0 # the list is empty
[Foo, ..] -> 1 # it starts with a Foo tag
[_, ..] -> 2 # it contains at least one element, which we ignore
[Foo, Bar, ..] -> 3 # it starts with a Foo tag followed by a Bar tag
[Foo, Bar, Baz] -> 4 # it contains exactly three elements: Foo, Bar, and Baz
[Foo, a, ..] -> 5 # it starts with a Foo tag followed by something we name `a`
[Ok a, ..] -> 6 # it starts with an Ok tag containing a payload named `a`
[.., Foo] -> 7 # it ends with a Foo tag
[A, B, .., C, D] -> 8 # it has certain elements at the beginning and and
This can be both more concise and more efficient (at runtime) than calling [`List.get`](https://www.roc-lang.org/builtins/List#get)
multiple times, since each call to `get` requires a separate conditional to handle the different
`Result`s they return.
> **Note:** Each list pattern can only have one `..`, which is known as the "rest pattern" because it's where the _rest_ of the list goes.
## Booleans
In many programming languages, `true` and `false` are special language keywords that refer to

View File

@ -14,10 +14,8 @@ pub fn space0_around_ee<'a, P, S, E>(
indent_after_problem: fn(Position) -> E,
) -> impl Parser<'a, Loc<S>, E>
where
S: Spaceable<'a>,
S: 'a,
P: Parser<'a, Loc<S>, E>,
P: 'a,
S: 'a + Spaceable<'a>,
P: 'a + Parser<'a, Loc<S>, E>,
E: 'a + SpaceProblem,
{
parser::map_with_arena(
@ -29,16 +27,32 @@ where
)
}
pub fn space0_around_e_no_after_indent_check<'a, P, S, E>(
parser: P,
indent_before_problem: fn(Position) -> E,
) -> impl Parser<'a, Loc<S>, E>
where
S: 'a + Spaceable<'a>,
P: 'a + Parser<'a, Loc<S>, E>,
E: 'a + SpaceProblem,
{
parser::map_with_arena(
and(
space0_e(indent_before_problem),
and(parser, space0_no_after_indent_check()),
),
spaces_around_help,
)
}
pub fn space0_before_optional_after<'a, P, S, E>(
parser: P,
indent_before_problem: fn(Position) -> E,
indent_after_problem: fn(Position) -> E,
) -> impl Parser<'a, Loc<S>, E>
where
S: Spaceable<'a>,
S: 'a,
P: Parser<'a, Loc<S>, E>,
P: 'a,
S: 'a + Spaceable<'a>,
P: 'a + Parser<'a, Loc<S>, E>,
E: 'a + SpaceProblem,
{
parser::map_with_arena(
@ -64,8 +78,7 @@ fn spaces_around_help<'a, S>(
),
) -> Loc<S>
where
S: Spaceable<'a>,
S: 'a,
S: 'a + Spaceable<'a>,
{
let (spaces_before, (loc_val, spaces_after)) = tuples;
@ -97,10 +110,8 @@ pub fn space0_before_e<'a, P, S, E>(
indent_problem: fn(Position) -> E,
) -> impl Parser<'a, Loc<S>, E>
where
S: Spaceable<'a>,
S: 'a,
P: Parser<'a, Loc<S>, E>,
P: 'a,
S: 'a + Spaceable<'a>,
P: 'a + Parser<'a, Loc<S>, E>,
E: 'a + SpaceProblem,
{
parser::map_with_arena(
@ -122,10 +133,8 @@ pub fn space0_after_e<'a, P, S, E>(
indent_problem: fn(Position) -> E,
) -> impl Parser<'a, Loc<S>, E>
where
S: Spaceable<'a>,
S: 'a,
P: Parser<'a, Loc<S>, E>,
P: 'a,
S: 'a + Spaceable<'a>,
P: 'a + Parser<'a, Loc<S>, E>,
E: 'a + SpaceProblem,
{
parser::map_with_arena(
@ -200,6 +209,38 @@ where
}
}
#[inline(always)]
fn space0_no_after_indent_check<'a, E>() -> impl Parser<'a, &'a [CommentOrNewline<'a>], E>
where
E: 'a + SpaceProblem,
{
move |arena, state: State<'a>, _min_indent: u32| match fast_eat_spaces(&state) {
FastSpaceState::HasTab(position) => Err((
MadeProgress,
E::space_problem(BadInputError::HasTab, position),
state,
)),
FastSpaceState::Good {
newlines,
consumed,
column: _,
} => {
if consumed == 0 {
Ok((NoProgress, &[] as &[_], state))
} else {
let comments_and_newlines = Vec::with_capacity_in(newlines, arena);
let spaces = eat_spaces(state, comments_and_newlines);
Ok((
MadeProgress,
spaces.comments_and_newlines.into_bump_slice(),
spaces.state,
))
}
}
}
}
enum FastSpaceState {
Good {
newlines: usize,

View File

@ -3,15 +3,16 @@ use crate::ast::{
Pattern, Spaceable, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
};
use crate::blankspace::{
space0_after_e, space0_around_ee, space0_before_e, space0_before_optional_after, space0_e,
space0_after_e, space0_around_e_no_after_indent_check, space0_around_ee, space0_before_e,
space0_before_optional_after, space0_e,
};
use crate::ident::{lowercase_ident, parse_ident, Ident};
use crate::keyword;
use crate::parser::{
self, backtrackable, increment_min_indent, optional, parse_word1, reset_min_indent, sep_by1,
sep_by1_e, set_min_indent, specialize, specialize_ref, then, trailing_sep_by0, word1, word2,
EClosure, EExpect, EExpr, EIf, EInParens, EList, ENumber, EPattern, ERecord, EString, EType,
EWhen, Either, ParseResult, Parser,
self, backtrackable, increment_min_indent, line_min_indent, optional, reset_min_indent,
sep_by1, sep_by1_e, set_min_indent, specialize, specialize_ref, then, trailing_sep_by0, word1,
word1_indent, word2, EClosure, EExpect, EExpr, EIf, EInParens, EList, ENumber, EPattern,
ERecord, EString, EType, EWhen, Either, ParseResult, Parser,
};
use crate::pattern::{loc_closure_param, loc_has_parser};
use crate::state::State;
@ -39,10 +40,7 @@ pub fn test_parse_expr<'a>(
arena: &'a bumpalo::Bump,
state: State<'a>,
) -> Result<Loc<Expr<'a>>, EExpr<'a>> {
let parser = skip_second!(
space0_before_e(parse_loc_expr, EExpr::IndentStart,),
expr_end()
);
let parser = skip_second!(space0_before_e(loc_expr(), EExpr::IndentStart,), expr_end());
match parser.parse(arena, state, min_indent) {
Ok((_, expression, _)) => Ok(expression),
@ -76,7 +74,9 @@ impl Default for ExprParseOptions {
pub fn expr_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
move |arena, state: State<'a>, min_indent: u32| {
parse_loc_expr(arena, state, min_indent).map(|(a, b, c)| (a, b.value, c))
loc_expr()
.parse(arena, state, min_indent)
.map(|(a, b, c)| (a, b.value, c))
}
}
@ -100,7 +100,7 @@ fn loc_expr_in_parens_help_help<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EInParen
between!(
word1(b'(', EInParens::Open),
space0_around_ee(
specialize_ref(EInParens::Expr, parse_loc_expr),
specialize_ref(EInParens::Expr, loc_expr()),
EInParens::IndentOpen,
EInParens::IndentEnd,
),
@ -182,12 +182,9 @@ fn record_field_access<'a>() -> impl Parser<'a, &'a str, EExpr<'a>> {
/// In some contexts we want to parse the `_` as an expression, so it can then be turned into a
/// pattern later
fn parse_loc_term_or_underscore_or_conditional<'a>(
min_indent: u32,
fn loc_term_or_underscore_or_conditional<'a>(
options: ExprParseOptions,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Loc<Expr<'a>>, EExpr<'a>> {
) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
one_of!(
loc_expr_in_parens_etc_help(),
loc!(specialize(EExpr::If, if_expr_help(options))),
@ -204,17 +201,13 @@ fn parse_loc_term_or_underscore_or_conditional<'a>(
ident_to_expr
)),
)
.parse(arena, state, min_indent)
}
/// In some contexts we want to parse the `_` as an expression, so it can then be turned into a
/// pattern later
fn parse_loc_term_or_underscore<'a>(
min_indent: u32,
fn loc_term_or_underscore<'a>(
options: ExprParseOptions,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Loc<Expr<'a>>, EExpr<'a>> {
) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
one_of!(
loc_expr_in_parens_etc_help(),
loc!(specialize(EExpr::Str, string_literal_help())),
@ -229,15 +222,9 @@ fn parse_loc_term_or_underscore<'a>(
ident_to_expr
)),
)
.parse(arena, state, min_indent)
}
fn parse_loc_term<'a>(
min_indent: u32,
options: ExprParseOptions,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Loc<Expr<'a>>, EExpr<'a>> {
fn loc_term<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
one_of!(
loc_expr_in_parens_etc_help(),
loc!(specialize(EExpr::Str, string_literal_help())),
@ -251,7 +238,6 @@ fn parse_loc_term<'a>(
ident_to_expr
)),
)
.parse(arena, state, min_indent)
}
fn underscore_expression<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
@ -280,10 +266,7 @@ fn loc_possibly_negative_or_negated_term<'a>(
let initial = state.clone();
let (_, (loc_op, loc_expr), state) =
and!(loc!(unary_negate()), |a, s, m| parse_loc_term(
m, options, a, s
))
.parse(arena, state, min_indent)?;
and!(loc!(unary_negate()), loc_term(options)).parse(arena, state, min_indent)?;
let loc_expr = numeric_negate_expression(arena, initial, loc_op, loc_expr, &[]);
@ -292,16 +275,12 @@ fn loc_possibly_negative_or_negated_term<'a>(
// this will parse negative numbers, which the unary negate thing up top doesn't (for now)
loc!(specialize(EExpr::Number, number_literal_help())),
loc!(map_with_arena!(
and!(loc!(word1(b'!', EExpr::Start)), |a, s, m| {
parse_loc_term(m, options, a, s)
}),
and!(loc!(word1(b'!', EExpr::Start)), loc_term(options)),
|arena: &'a Bump, (loc_op, loc_expr): (Loc<_>, _)| {
Expr::UnaryOp(arena.alloc(loc_expr), Loc::at(loc_op.region, UnaryOp::Not))
}
)),
|arena, state, min_indent: u32| {
parse_loc_term_or_underscore_or_conditional(min_indent, options, arena, state)
}
loc_term_or_underscore_or_conditional(options)
]
}
@ -332,53 +311,41 @@ fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> {
}
}
fn parse_expr_start<'a>(
min_indent: u32,
options: ExprParseOptions,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Loc<Expr<'a>>, EExpr<'a>> {
fn expr_start<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
one_of![
loc!(specialize(EExpr::If, if_expr_help(options))),
loc!(specialize(EExpr::When, when::expr_help(options))),
loc!(specialize(EExpr::Expect, expect_help(options))),
loc!(specialize(EExpr::Closure, closure_help(options))),
loc!(move |a, s, m| parse_expr_operator_chain(m, options, a, s)),
loc!(expr_operator_chain(options)),
fail_expr_start_e()
]
.trace("expr_start")
.parse(arena, state, min_indent)
}
fn parse_expr_operator_chain<'a>(
min_indent: u32,
options: ExprParseOptions,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let min_indent = state.check_indent(min_indent, EExpr::IndentStart)?;
fn expr_operator_chain<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
line_min_indent(move |arena, state: State<'a>, min_indent: u32| {
let (_, expr, state) =
loc_possibly_negative_or_negated_term(options).parse(arena, state, min_indent)?;
let (_, expr, state) =
loc_possibly_negative_or_negated_term(options).parse(arena, state, min_indent)?;
let initial_state = state.clone();
let end = state.pos();
let initial = state.clone();
let end = state.pos();
match space0_e(EExpr::IndentEnd).parse(arena, state, min_indent) {
Err((_, _, state)) => Ok((MadeProgress, expr.value, state)),
Ok((_, spaces_before_op, state)) => {
let expr_state = ExprState {
operators: Vec::new_in(arena),
arguments: Vec::new_in(arena),
expr,
spaces_after: spaces_before_op,
end,
};
match space0_e(EExpr::IndentEnd).parse(arena, state, min_indent) {
Err((_, _, state)) => Ok((MadeProgress, expr.value, state)),
Ok((_, spaces_before_op, state)) => {
let expr_state = ExprState {
operators: Vec::new_in(arena),
arguments: Vec::new_in(arena),
expr,
spaces_after: spaces_before_op,
initial,
end,
};
parse_expr_end(min_indent, options, expr_state, arena, state)
parse_expr_end(min_indent, options, expr_state, arena, state, initial_state)
}
}
}
})
}
#[derive(Debug)]
@ -387,7 +354,6 @@ struct ExprState<'a> {
arguments: Vec<'a, &'a Loc<Expr<'a>>>,
expr: Loc<Expr<'a>>,
spaces_after: &'a [CommentOrNewline<'a>],
initial: State<'a>,
end: Position,
}
@ -477,23 +443,17 @@ impl<'a> ExprState<'a> {
}
#[allow(clippy::unnecessary_wraps)]
fn parse_expr_final<'a>(
expr_state: ExprState<'a>,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
fn parse_expr_final<'a>(expr_state: ExprState<'a>, arena: &'a Bump) -> Expr<'a> {
let right_arg = to_call(arena, expr_state.arguments, expr_state.expr);
let expr = if expr_state.operators.is_empty() {
if expr_state.operators.is_empty() {
right_arg.value
} else {
Expr::BinOps(
expr_state.operators.into_bump_slice(),
arena.alloc(right_arg),
)
};
Ok((MadeProgress, expr, state))
}
}
fn to_call<'a>(
@ -632,7 +592,7 @@ pub fn parse_single_def<'a>(
}
Ok((_, expect_flavor, state)) => {
let parse_def_expr =
space0_before_e(increment_min_indent(parse_loc_expr), EExpr::IndentEnd);
space0_before_e(increment_min_indent(loc_expr()), EExpr::IndentEnd);
let (_, loc_def_expr, state) =
parse_def_expr.parse(arena, state, min_indent)?;
@ -726,7 +686,7 @@ pub fn parse_single_def<'a>(
match operator().parse(arena, state, min_indent) {
Ok((_, BinOp::Assignment, state)) => {
let parse_def_expr =
space0_before_e(increment_min_indent(parse_loc_expr), EExpr::IndentEnd);
space0_before_e(increment_min_indent(loc_expr()), EExpr::IndentEnd);
let (_, loc_def_expr, state) =
parse_def_expr.parse(arena, state, min_indent)?;
@ -745,8 +705,11 @@ pub fn parse_single_def<'a>(
))
}
Ok((_, BinOp::IsAliasType, state)) => {
let (_, ann_type, state) = alias_signature_with_space_before(min_indent + 1)
.parse(arena, state, min_indent)?;
// the increment_min_indent here is probably _wrong_, since alias_signature_with_space_before does
// that internally.
// TODO: re-evaluate this
let parser = increment_min_indent(alias_signature_with_space_before());
let (_, ann_type, state) = parser.parse(arena, state, min_indent)?;
let region = Region::span_across(&loc_pattern.region, &ann_type.region);
match &loc_pattern.value {
@ -1066,7 +1029,7 @@ fn parse_defs_expr<'a>(
Err(bad) => Err(bad),
Ok((_, def_state, state)) => {
// this is no def, because there is no `=` or `:`; parse as an expr
let parse_final_expr = space0_before_e(parse_loc_expr, EExpr::IndentEnd);
let parse_final_expr = space0_before_e(loc_expr(), EExpr::IndentEnd);
match parse_final_expr.parse(arena, state, min_indent) {
Err((_, fail, state)) => {
@ -1087,19 +1050,12 @@ fn parse_defs_expr<'a>(
}
}
}
fn alias_signature_with_space_before<'a>(
min_indent: u32,
) -> impl Parser<'a, Loc<TypeAnnotation<'a>>, EExpr<'a>> {
move |arena, state, _min_indent| {
specialize(
EExpr::Type,
space0_before_e(
set_min_indent(min_indent + 1, type_annotation::located(false)),
EType::TIndentStart,
),
)
.parse(arena, state, min_indent + 1)
}
fn alias_signature_with_space_before<'a>() -> impl Parser<'a, Loc<TypeAnnotation<'a>>, EExpr<'a>> {
increment_min_indent(specialize(
EExpr::Type,
space0_before_e(type_annotation::located(false), EType::TIndentStart),
))
}
fn opaque_signature_with_space_before<'a>(
@ -1160,8 +1116,8 @@ fn finish_parsing_alias_or_opaque<'a>(
match kind {
AliasOrOpaque::Alias => {
let (_, signature, state) = alias_signature_with_space_before(indented_more)
.parse(arena, state, min_indent)?;
let (_, signature, state) =
alias_signature_with_space_before().parse(arena, state, min_indent)?;
let def_region = Region::span_across(&expr.region, &signature.region);
@ -1263,11 +1219,10 @@ mod ability {
};
/// Parses a single ability demand line; see `parse_demand`.
fn parse_demand_help<'a>(
start_column: u32,
) -> impl Parser<'a, AbilityMember<'a>, EAbility<'a>> {
fn parse_demand_help<'a>() -> impl Parser<'a, AbilityMember<'a>, EAbility<'a>> {
map!(
and!(
// Require the type to be more indented than the name
absolute_indented_seq!(
specialize(|_, pos| EAbility::DemandName(pos), loc!(lowercase_ident())),
skip_first!(
and!(
@ -1275,11 +1230,7 @@ mod ability {
space0_e(EAbility::DemandName),
word1(b':', EAbility::DemandColon)
),
specialize(
EAbility::Type,
// Require the type to be more indented than the name
set_min_indent(start_column + 1, type_annotation::located(true))
)
specialize(EAbility::Type, type_annotation::located(true))
)
),
|(name, typ): (Loc<&'a str>, Loc<TypeAnnotation<'a>>)| {
@ -1352,7 +1303,7 @@ mod ability {
_ => {
let indent_column = state.column();
let parser = parse_demand_help(indent_column);
let parser = parse_demand_help();
match parser.parse(arena, state, min_indent) {
Err((MadeProgress, fail, state)) => {
@ -1445,6 +1396,7 @@ fn parse_expr_operator<'a>(
loc_op: Loc<BinOp>,
arena: &'a Bump,
state: State<'a>,
initial_state: State<'a>,
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let (_, spaces_after_operator, state) =
space0_e(EExpr::IndentEnd).parse(arena, state, min_indent)?;
@ -1459,18 +1411,18 @@ fn parse_expr_operator<'a>(
BinOp::Minus if expr_state.end != op_start && op_end == new_start => {
// negative terms
let (_, negated_expr, state) = parse_loc_term(min_indent, options, arena, state)?;
let (_, negated_expr, state) = loc_term(options).parse(arena, state, min_indent)?;
let new_end = state.pos();
let arg = numeric_negate_expression(
arena,
expr_state.initial,
initial_state,
loc_op,
negated_expr,
expr_state.spaces_after,
);
expr_state.initial = state.clone();
let initial_state = state.clone();
let (spaces, state) = match space0_e(EExpr::IndentEnd).parse(arena, state, min_indent) {
Err((_, _, state)) => (&[] as &[_], state),
@ -1481,7 +1433,7 @@ fn parse_expr_operator<'a>(
expr_state.spaces_after = spaces;
expr_state.end = new_end;
parse_expr_end(min_indent, options, expr_state, arena, state)
parse_expr_end(min_indent, options, expr_state, arena, state, initial_state)
}
BinOp::Assignment => {
let expr_region = expr_state.expr.region;
@ -1494,7 +1446,7 @@ fn parse_expr_operator<'a>(
let (value_def, def_region, state) = {
match expr_to_pattern_help(arena, &call.value) {
Ok(good) => {
let (_, mut body, state) = parse_loc_expr(arena, state, indented_more)?;
let (_, mut body, state) = loc_expr().parse(arena, state, indented_more)?;
// put the spaces from after the operator in front of the call
if !spaces_after_operator.is_empty() {
@ -1539,7 +1491,8 @@ fn parse_expr_operator<'a>(
let (loc_pattern, loc_body, state) = {
match expr_to_pattern_help(arena, &call.value) {
Ok(good) => {
let (_, mut ann_type, state) = parse_loc_expr(arena, state, indented_more)?;
let (_, mut ann_type, state) =
loc_expr().parse(arena, state, indented_more)?;
// put the spaces from after the operator in front of the call
if !spaces_after_operator.is_empty() {
@ -1559,7 +1512,7 @@ fn parse_expr_operator<'a>(
}
};
let parse_cont = space0_before_e(parse_loc_expr, EExpr::IndentEnd);
let parse_cont = space0_before_e(loc_expr(), EExpr::IndentEnd);
let (_, loc_cont, state) = parse_cont.parse(arena, state, min_indent)?;
@ -1590,7 +1543,7 @@ fn parse_expr_operator<'a>(
Ok((_, mut new_expr, state)) => {
let new_end = state.pos();
expr_state.initial = state.clone();
let initial_state = state.clone();
// put the spaces from after the operator in front of the new_expr
if !spaces_after_operator.is_empty() {
@ -1610,7 +1563,8 @@ fn parse_expr_operator<'a>(
expr_state.end = new_end;
expr_state.spaces_after = &[];
parse_expr_final(expr_state, arena, state)
let expr = parse_expr_final(expr_state, arena);
Ok((MadeProgress, expr, state))
}
Ok((_, spaces, state)) => {
let args = std::mem::replace(&mut expr_state.arguments, Vec::new_in(arena));
@ -1623,7 +1577,7 @@ fn parse_expr_operator<'a>(
expr_state.spaces_after = spaces;
// TODO new start?
parse_expr_end(min_indent, options, expr_state, arena, state)
parse_expr_end(min_indent, options, expr_state, arena, state, initial_state)
}
}
}
@ -1640,10 +1594,11 @@ fn parse_expr_end<'a>(
mut expr_state: ExprState<'a>,
arena: &'a Bump,
state: State<'a>,
initial_state: State<'a>,
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let parser = skip_first!(
crate::blankspace::check_indent(EExpr::IndentEnd),
move |a, s, m| parse_loc_term_or_underscore(m, options, a, s)
loc_term_or_underscore(options)
);
match parser.parse(arena, state.clone(), min_indent) {
@ -1715,7 +1670,7 @@ fn parse_expr_end<'a>(
expr_state.spaces_after = &[];
}
expr_state.initial = state.clone();
let initial_state = state.clone();
match space0_e(EExpr::IndentEnd).parse(arena, state, min_indent) {
Err((_, _, state)) => {
@ -1723,14 +1678,15 @@ fn parse_expr_end<'a>(
expr_state.end = new_end;
expr_state.spaces_after = &[];
parse_expr_final(expr_state, arena, state)
let expr = parse_expr_final(expr_state, arena);
Ok((MadeProgress, expr, state))
}
Ok((_, new_spaces, state)) => {
expr_state.arguments.push(arena.alloc(arg));
expr_state.end = new_end;
expr_state.spaces_after = new_spaces;
parse_expr_end(min_indent, options, expr_state, arena, state)
parse_expr_end(min_indent, options, expr_state, arena, state, initial_state)
}
}
}
@ -1741,8 +1697,16 @@ fn parse_expr_end<'a>(
Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)),
Ok((_, loc_op, state)) => {
expr_state.consume_spaces(arena);
expr_state.initial = before_op;
parse_expr_operator(min_indent, options, expr_state, loc_op, arena, state)
let initial_state = before_op;
parse_expr_operator(
min_indent,
options,
expr_state,
loc_op,
arena,
state,
initial_state,
)
}
Err((NoProgress, _, mut state)) => {
// try multi-backpassing
@ -1778,14 +1742,14 @@ fn parse_expr_end<'a>(
Err((_, fail, state)) => Err((MadeProgress, fail, state)),
Ok((_, _, state)) => {
let parse_body = space0_before_e(
increment_min_indent(parse_loc_expr),
increment_min_indent(loc_expr()),
EExpr::IndentEnd,
);
let (_, loc_body, state) =
parse_body.parse(arena, state, min_indent)?;
let parse_cont = space0_before_e(parse_loc_expr, EExpr::IndentEnd);
let parse_cont = space0_before_e(loc_expr(), EExpr::IndentEnd);
let (_, loc_cont, state) =
parse_cont.parse(arena, state, min_indent)?;
@ -1802,10 +1766,10 @@ fn parse_expr_end<'a>(
} else if options.check_for_arrow && state.bytes().starts_with(b"->") {
Err((MadeProgress, EExpr::BadOperator("->", state.pos()), state))
} else {
// roll back space parsing
let state = expr_state.initial.clone();
let expr = parse_expr_final(expr_state, arena);
parse_expr_final(expr_state, arena, state)
// roll back space parsing
Ok((MadeProgress, expr, initial_state))
}
}
}
@ -1813,43 +1777,18 @@ fn parse_expr_end<'a>(
}
}
pub fn parse_loc_expr<'a>(
arena: &'a Bump,
state: State<'a>,
min_indent: u32,
) -> ParseResult<'a, Loc<Expr<'a>>, EExpr<'a>> {
parse_loc_expr_with_options(
min_indent,
ExprParseOptions {
accept_multi_backpassing: true,
..Default::default()
},
arena,
state,
)
pub fn loc_expr<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
expr_start(ExprParseOptions {
accept_multi_backpassing: true,
..Default::default()
})
}
pub fn loc_expr_no_multi_backpassing<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
move |arena, state, min_indent| {
parse_loc_expr_with_options(
min_indent,
ExprParseOptions {
accept_multi_backpassing: false,
..Default::default()
},
arena,
state,
)
}
}
fn parse_loc_expr_with_options<'a>(
min_indent: u32,
options: ExprParseOptions,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Loc<Expr<'a>>, EExpr<'a>> {
parse_expr_start(min_indent, options, arena, state)
expr_start(ExprParseOptions {
accept_multi_backpassing: false,
..Default::default()
})
}
/// If the given Expr would parse the same way as a valid Pattern, convert it.
@ -2026,64 +1965,43 @@ pub fn toplevel_defs<'a>() -> impl Parser<'a, Defs<'a>, EExpr<'a>> {
// PARSER HELPERS
fn closure_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EClosure<'a>> {
move |arena, state, min_indent| parse_closure_help(arena, state, min_indent, options)
}
fn parse_closure_help<'a>(
arena: &'a Bump,
state: State<'a>,
_min_indent: u32,
options: ExprParseOptions,
) -> ParseResult<'a, Expr<'a>, EClosure<'a>> {
let start_indent = state.line_indent();
let min_indent = start_indent;
// All closures start with a '\' - e.g. (\x -> x + 1)
let (_, (), state) = parse_word1(state, min_indent, b'\\', EClosure::Start)?;
// After the first token, all other tokens must be indented past the start of the line
let min_indent = min_indent + 1;
// Once we see the '\', we're committed to parsing this as a closure.
// It may turn out to be malformed, but it is definitely a closure.
// Parse the params
// Params are comma-separated
let (_, params, state) = sep_by1_e(
word1(b',', EClosure::Comma),
space0_around_ee(
specialize(EClosure::Pattern, loc_closure_param()),
EClosure::IndentArg,
EClosure::IndentArrow,
// closure_help_help(options)
map_with_arena!(
// After the first token, all other tokens must be indented past the start of the line
indented_seq!(
// All closures start with a '\' - e.g. (\x -> x + 1)
word1_indent(b'\\', EClosure::Start),
// Once we see the '\', we're committed to parsing this as a closure.
// It may turn out to be malformed, but it is definitely a closure.
and!(
// Parse the params
// Params are comma-separated
sep_by1_e(
word1(b',', EClosure::Comma),
space0_around_ee(
specialize(EClosure::Pattern, loc_closure_param()),
EClosure::IndentArg,
EClosure::IndentArrow,
),
EClosure::Arg,
),
skip_first!(
// Parse the -> which separates params from body
word2(b'-', b'>', EClosure::Arrow),
// Parse the body
space0_before_e(
specialize_ref(EClosure::Body, expr_start(options)),
EClosure::IndentBody
)
)
)
),
EClosure::Arg,
|arena: &'a Bump, (params, body)| {
let params: Vec<'a, Loc<Pattern<'a>>> = params;
let params: &'a [Loc<Pattern<'a>>] = params.into_bump_slice();
Expr::Closure(params, arena.alloc(body))
}
)
.parse(arena, state, min_indent)
.map_err(|(_p, e, s)| (MadeProgress, e, s))?;
let (_, loc_body, state) = skip_first!(
// Parse the -> which separates params from body
word2(b'-', b'>', EClosure::Arrow),
// Parse the body
space0_before_e(
specialize_ref(EClosure::Body, move |arena, state, min_indent| {
parse_loc_expr_with_options(min_indent, options, arena, state)
}),
EClosure::IndentBody
)
)
.parse(arena, state, min_indent)
.map_err(|(_p, e, s)| (MadeProgress, e, s))?;
let params: Vec<'a, Loc<Pattern<'a>>> = params;
let params: &'a [Loc<Pattern<'a>>] = params.into_bump_slice();
Ok((
MadeProgress,
Expr::Closure(params, arena.alloc(loc_body)),
state,
))
}
mod when {
@ -2092,55 +2010,28 @@ mod when {
/// Parser for when expressions.
pub fn expr_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EWhen<'a>> {
then(
map_with_arena!(
and!(
when_with_indent(),
skip_second!(
space0_around_ee(
specialize_ref(EWhen::Condition, move |arena, state, min_indent| {
parse_loc_expr_with_options(min_indent, options, arena, state)
}),
indented_seq!(
parser::keyword_e(keyword::WHEN, EWhen::When),
space0_around_e_no_after_indent_check(
specialize_ref(EWhen::Condition, expr_start(options)),
EWhen::IndentCondition,
EWhen::IndentIs,
),
parser::keyword_e(keyword::IS, EWhen::Is)
)
),
// Note that we allow the `is` to be at any indent level, since this doesn't introduce any
// ambiguity. The formatter will fix it up.
//
// We require that branches are indented relative to the line containing the `is`.
indented_seq!(
parser::keyword_e(keyword::IS, EWhen::Is),
branches(options)
)
),
move |arena, state, _progress, (case_indent, loc_condition), min_indent| {
if case_indent < min_indent {
return Err((
MadeProgress,
// TODO maybe pass case_indent here?
EWhen::PatternAlignment(5, state.pos()),
state,
));
}
// Everything in the branches must be indented at least as much as the case itself.
let min_indent = case_indent;
let (_p1, branches, state) = branches(options)
.parse(arena, state, min_indent)
.map_err(|(_p, e, s)| (MadeProgress, e, s))?;
Ok((
MadeProgress,
Expr::When(arena.alloc(loc_condition), branches.into_bump_slice()),
state,
))
},
move |arena: &'a Bump, (loc_condition, branches): (Loc<Expr<'a>>, Vec<'a, &'a WhenBranch<'a>>)| {
Expr::When(arena.alloc(loc_condition), branches.into_bump_slice())
}
)
.trace("when")
}
/// Parsing when with indentation.
fn when_with_indent<'a>() -> impl Parser<'a, u32, EWhen<'a>> {
move |arena, state: State<'a>, _min_indent: u32| {
let min_indent = state.line_indent() + 1;
parser::keyword_e(keyword::WHEN, EWhen::When)
.parse(arena, state, min_indent)
.map(|(progress, (), state)| (progress, min_indent, state))
}
}
fn branches<'a>(
@ -2175,11 +2066,7 @@ mod when {
and!(
then(
branch_alternatives(options, Some(pattern_indent_level)),
move |_arena,
state,
_,
((indent_column, loc_patterns), loc_guard),
_min_indent| {
move |_arena, state, _, ((indent_column, loc_patterns), loc_guard)| {
if pattern_indent_level == indent_column {
Ok((MadeProgress, (loc_patterns, loc_guard), state))
} else {
@ -2243,9 +2130,10 @@ mod when {
parser::keyword_e(keyword::IF, EWhen::IfToken),
// TODO we should require space before the expression but not after
space0_around_ee(
specialize_ref(EWhen::IfGuard, move |arena, state, min_indent| {
parse_loc_expr_with_options(min_indent + 1, options, arena, state)
}),
specialize_ref(
EWhen::IfGuard,
increment_min_indent(expr_start(options))
),
EWhen::IndentIfGuard,
EWhen::IndentArrow,
)
@ -2351,7 +2239,7 @@ mod when {
skip_first!(
word2(b'-', b'>', EWhen::Arrow),
space0_before_e(
specialize_ref(EWhen::Branch, parse_loc_expr),
specialize_ref(EWhen::Branch, loc_expr()),
EWhen::IndentBranch,
)
)
@ -2361,38 +2249,24 @@ mod when {
}
fn if_branch<'a>() -> impl Parser<'a, (Loc<Expr<'a>>, Loc<Expr<'a>>), EIf<'a>> {
move |arena, state, min_indent| {
// NOTE: only parse spaces before the expression
let (_, cond, state) = space0_around_ee(
specialize_ref(EIf::Condition, move |arena, state, min_indent| {
parse_loc_expr(arena, state, min_indent)
}),
EIf::IndentCondition,
EIf::IndentThenToken,
)
.parse(arena, state, min_indent)
.map_err(|(_, f, s)| (MadeProgress, f, s))?;
let (_, _, state) = parser::keyword_e(keyword::THEN, EIf::Then)
.parse(arena, state, min_indent)
.map_err(|(_, f, s)| (MadeProgress, f, s))?;
let (_, then_branch, state) = space0_around_ee(
specialize_ref(EIf::ThenBranch, move |arena, state, min_indent| {
parse_loc_expr(arena, state, min_indent)
}),
EIf::IndentThenBranch,
EIf::IndentElseToken,
)
.parse(arena, state, min_indent)
.map_err(|(_, f, s)| (MadeProgress, f, s))?;
let (_, _, state) = parser::keyword_e(keyword::ELSE, EIf::Else)
.parse(arena, state, min_indent)
.map_err(|(_, f, s)| (MadeProgress, f, s))?;
Ok((MadeProgress, (cond, then_branch), state))
}
skip_second!(
and!(
skip_second!(
space0_around_ee(
specialize_ref(EIf::Condition, loc_expr()),
EIf::IndentCondition,
EIf::IndentThenToken,
),
parser::keyword_e(keyword::THEN, EIf::Then)
),
space0_around_ee(
specialize_ref(EIf::ThenBranch, loc_expr()),
EIf::IndentThenBranch,
EIf::IndentElseToken,
)
),
parser::keyword_e(keyword::ELSE, EIf::Else)
)
}
fn expect_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpect<'a>> {
@ -2403,9 +2277,10 @@ fn expect_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpe
parser::keyword_e(keyword::EXPECT, EExpect::Expect).parse(arena, state, min_indent)?;
let (_, condition, state) = space0_before_e(
specialize_ref(EExpect::Condition, move |arena, state, _m| {
parse_loc_expr_with_options(start_column + 1, options, arena, state)
}),
specialize_ref(
EExpect::Condition,
set_min_indent(start_column + 1, expr_start(options)),
),
EExpect::IndentCondition,
)
.parse(arena, state, start_column + 1)
@ -2413,7 +2288,7 @@ fn expect_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpe
let parse_cont = specialize_ref(
EExpect::Continuation,
space0_before_e(parse_loc_expr, EExpr::IndentEnd),
space0_before_e(loc_expr(), EExpr::IndentEnd),
);
let (_, loc_cont, state) = parse_cont.parse(arena, state, min_indent)?;
@ -2456,9 +2331,7 @@ fn if_expr_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EIf<
};
let (_, else_branch, state) = space0_before_e(
specialize_ref(EIf::ElseBranch, move |arena, state, min_indent| {
parse_loc_expr_with_options(min_indent, options, arena, state)
}),
specialize_ref(EIf::ElseBranch, expr_start(options)),
EIf::IndentElseBranch,
)
.parse(arena, state_final_else, min_indent)
@ -2544,8 +2417,8 @@ fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> {
}
fn list_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EList<'a>> {
move |arena, state, min_indent| {
let (_, elements, state) = collection_trailing_sep_e!(
map_with_arena!(
collection_trailing_sep_e!(
word1(b'[', EList::Open),
specialize_ref(EList::Expr, loc_expr_no_multi_backpassing()),
word1(b',', EList::End),
@ -2553,14 +2426,13 @@ fn list_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EList<'a>> {
EList::Open,
EList::IndentEnd,
Expr::SpaceBefore
)
.parse(arena, state, min_indent)?;
let elements = elements.ptrify_items(arena);
let expr = Expr::List(elements);
Ok((MadeProgress, expr, state))
}
),
|arena, elements: Collection<'a, _>| {
let elements = elements.ptrify_items(arena);
Expr::List(elements)
}
)
.trace("list_literal")
}
pub fn record_value_field<'a>() -> impl Parser<'a, AssignedField<'a, Expr<'a>>, ERecord<'a>> {
@ -2675,8 +2547,12 @@ fn record_help<'a>() -> impl Parser<
fn record_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
then(
loc!(specialize(EExpr::Record, record_help())),
move |arena, state, _, loc_record, min_indent| {
and!(
loc!(specialize(EExpr::Record, record_help())),
// there can be field access, e.g. `{ x : 4 }.x`
optional(record_field_access_chain())
),
move |arena, state, _, (loc_record, accesses)| {
let (opt_update, loc_assigned_fields_with_comments) = loc_record.value;
// This is a record literal, not a destructure.
@ -2696,10 +2572,6 @@ fn record_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
)),
};
// there can be field access, e.g. `{ x : 4 }.x`
let (_, accesses, state) =
optional(record_field_access_chain()).parse(arena, state, min_indent)?;
if let Some(fields) = accesses {
for field in fields {
// Wrap the previous answer in the new one, so we end up

View File

@ -470,7 +470,6 @@ pub enum EWhen<'a> {
Condition(&'a EExpr<'a>, Position),
Branch(&'a EExpr<'a>, Position),
IndentIs(Position),
IndentCondition(Position),
IndentPattern(Position),
IndentArrow(Position),
@ -869,13 +868,13 @@ where
P1: Parser<'a, Before, E>,
After: 'a,
E: 'a,
F: Fn(&'a Bump, State<'a>, Progress, Before, u32) -> ParseResult<'a, After, E>,
F: Fn(&'a Bump, State<'a>, Progress, Before) -> ParseResult<'a, After, E>,
{
move |arena, state, min_indent| {
parser
.parse(arena, state, min_indent)
.and_then(|(progress, output, next_state)| {
transform(arena, next_state, progress, output, min_indent)
transform(arena, next_state, progress, output)
})
}
}
@ -1376,6 +1375,64 @@ macro_rules! and {
};
}
/// Similar to `and`, but we modify the min_indent of the second parser to be
/// 1 greater than the line_indent() at the start of the first parser.
#[macro_export]
macro_rules! indented_seq {
($p1:expr, $p2:expr) => {
move |arena: &'a bumpalo::Bump, state: $crate::state::State<'a>, _min_indent: u32| {
let start_indent = state.line_indent();
// TODO: we should account for min_indent here, but this doesn't currently work
// because min_indent is sometimes larger than it really should be, which is in turn
// due to uses of `increment_indent`.
//
// let p1_indent = std::cmp::max(start_indent, min_indent);
let p1_indent = start_indent;
let p2_indent = p1_indent + 1;
// We have to clone this because if the first parser passes and then
// the second one fails, we need to revert back to the original state.
let original_state = state.clone();
match $p1.parse(arena, state, p1_indent) {
Ok((p1, (), state)) => match $p2.parse(arena, state, p2_indent) {
Ok((p2, out2, state)) => Ok((p1.or(p2), out2, state)),
Err((p2, fail, _)) => Err((p1.or(p2), fail, original_state)),
},
Err((progress, fail, state)) => Err((progress, fail, state)),
}
}
};
}
/// Similar to `and`, but we modify the min_indent of the second parser to be
/// 1 greater than the column() at the start of the first parser.
#[macro_export]
macro_rules! absolute_indented_seq {
($p1:expr, $p2:expr) => {
move |arena: &'a bumpalo::Bump, state: $crate::state::State<'a>, _min_indent: u32| {
let start_indent = state.column();
let p1_indent = start_indent;
let p2_indent = p1_indent + 1;
// We have to clone this because if the first parser passes and then
// the second one fails, we need to revert back to the original state.
let original_state = state.clone();
match $p1.parse(arena, state, p1_indent) {
Ok((p1, out1, state)) => match $p2.parse(arena, state, p2_indent) {
Ok((p2, out2, state)) => Ok((p1.or(p2), (out1, out2), state)),
Err((p2, fail, _)) => Err((p1.or(p2), fail, original_state)),
},
Err((progress, fail, state)) => Err((progress, fail, state)),
}
}
};
}
#[macro_export]
macro_rules! one_of {
($p1:expr, $p2:expr) => {
@ -1450,6 +1507,16 @@ where
move |arena, state, min_indent| parser.parse(arena, state, min_indent + 1)
}
pub fn line_min_indent<'a, P, T, X: 'a>(parser: P) -> impl Parser<'a, T, X>
where
P: Parser<'a, T, X>,
{
move |arena, state: State<'a>, min_indent| {
let min_indent = std::cmp::max(state.line_indent(), min_indent);
parser.parse(arena, state, min_indent)
}
}
pub fn absolute_column_min_indent<'a, P, T, X: 'a>(parser: P) -> impl Parser<'a, T, X>
where
P: Parser<'a, T, X>,
@ -1517,28 +1584,25 @@ where
}
}
pub fn parse_word1<'a, ToError, E>(
state: State<'a>,
min_indent: u32,
word: u8,
to_error: ToError,
) -> ParseResult<'a, (), E>
pub fn word1_indent<'a, ToError, E>(word: u8, to_error: ToError) -> impl Parser<'a, (), E>
where
ToError: Fn(Position) -> E,
E: 'a,
{
debug_assert_ne!(word, b'\n');
if min_indent > state.column() {
return Err((NoProgress, to_error(state.pos()), state));
}
match state.bytes().first() {
Some(x) if *x == word => {
let state = state.advance(1);
Ok((MadeProgress, (), state))
move |_arena: &'a Bump, state: State<'a>, min_indent: u32| {
if min_indent > state.column() {
return Err((NoProgress, to_error(state.pos()), state));
}
match state.bytes().first() {
Some(x) if *x == word => {
let state = state.advance(1);
Ok((MadeProgress, (), state))
}
_ => Err((NoProgress, to_error(state.pos()), state)),
}
_ => Err((NoProgress, to_error(state.pos()), state)),
}
}

View File

@ -115,7 +115,7 @@ fn loc_tag_pattern_arg<'a>(
pub fn loc_has_parser<'a>() -> impl Parser<'a, Loc<Has<'a>>, EPattern<'a>> {
then(
loc_tag_pattern_arg(false),
|_arena, state, progress, pattern, _min_indent| {
|_arena, state, progress, pattern| {
if matches!(pattern.value, Pattern::Identifier("has")) {
Ok((progress, Loc::at(pattern.region, Has::Has), state))
} else {
@ -228,7 +228,7 @@ fn list_element_pattern<'a>() -> impl Parser<'a, Loc<Pattern<'a>>, PList<'a>> {
fn three_list_rest_pattern_error<'a>() -> impl Parser<'a, Loc<Pattern<'a>>, PList<'a>> {
then(
loc!(word3(b'.', b'.', b'.', PList::Rest)),
|_arena, state, _progress, word, _min_indent| {
|_arena, state, _progress, word| {
Err((MadeProgress, PList::Rest(word.region.start()), state))
},
)

View File

@ -99,7 +99,7 @@ fn parse_type_alias_after_as<'a>() -> impl Parser<'a, TypeHeader<'a>, EType<'a>>
space0_before_e(term(false), EType::TAsIndentStart),
// TODO: introduce a better combinator for this.
// `check_type_alias` doesn't need to modify the state or progress, but it needs to access `state.pos()`
|arena, state, progress, output, _min_indent| {
|arena, state, progress, output| {
let res = check_type_alias(arena, output);
match res {

View File

@ -1 +1 @@
Expr(Closure(Arg(@1), @1), @0)
Expr(Closure(Arg(@1), @0), @0)

View File

@ -1 +1 @@
Expr(Closure(IndentBody(@5), @3), @0)
Expr(Closure(IndentBody(@5), @0), @0)

View File

@ -1 +1 @@
Expr(When(Arrow(@24), @24), @0)
Expr(When(Arrow(@24), @0), @0)

View File

@ -1 +1 @@
Expr(When(Arrow(@26), @20), @0)
Expr(When(Arrow(@26), @0), @0)

View File

@ -6,6 +6,7 @@ use roc_collections::MutSet;
use roc_mono::ir::OptLevel;
use roc_parse::ast::{Expr, Pattern, TypeDef, TypeHeader, ValueDef};
use roc_parse::expr::{parse_single_def, ExprParseOptions, SingleDef};
use roc_parse::parser::Parser;
use roc_parse::parser::{EClosure, EExpr, EPattern};
use roc_parse::parser::{EWhen, Either};
use roc_parse::state::State;
@ -317,7 +318,7 @@ fn parse_src<'a>(arena: &'a Bump, line: &'a str) -> ParseOutcome<'a> {
_ => {
let src_bytes = line.as_bytes();
match roc_parse::expr::parse_loc_expr(arena, State::new(src_bytes), 0) {
match roc_parse::expr::loc_expr().parse(arena, State::new(src_bytes), 0) {
Ok((_, loc_expr, _)) => ParseOutcome::Expr(loc_expr.value),
// Special case some syntax errors to allow for multi-line inputs
Err((_, EExpr::Closure(EClosure::Body(_, _), _), _))

View File

@ -1384,7 +1384,7 @@ fn to_when_report<'a>(
EWhen::IfToken(_pos) => unreachable!("the if-token is optional"),
EWhen::When(_pos) => unreachable!("another branch would be taken"),
EWhen::Is(pos) | EWhen::IndentIs(pos) => to_unfinished_when_report(
EWhen::Is(pos) => to_unfinished_when_report(
alloc,
lines,
filename,

View File

@ -4851,6 +4851,7 @@ mod test_reporting {
I am partway through parsing a `when` expression, but got stuck here:
4 when Just 4 is
5 Just when ->
^
@ -4886,6 +4887,8 @@ mod test_reporting {
I was partway through parsing a `when` expression, but I got stuck here:
4 when 5 is
5 1 -> 2
6 _
^