Fix a bunch of parser/formatter bugs found in fuzzing

Notably:
* Unified how parens are formatted between (1) when we have a ParensAround, and (2) when we've decided an Apply needs to have parens
* Made unary minus require the be indented to the same level as any other expression continuation. (it used to accidentally have rules meant for binary operators applied)
* Don't apply extra indent to the backpassing continuation in the case that the call does itself require indentation
* Make `try@foo` correctly parse as `try @foo`, so that formatting doesn't change the tree when it adds that space
* Detect more cases where we need to outdent trailing e.g. {} blocks in applies
* Approximately a bagillion other things, 90% of which I added tests for, and none of which affected the formatting of examples or builtins
This commit is contained in:
Joshua Warner 2024-11-17 19:34:47 -08:00
parent 335a8eb258
commit ed62bcc15a
No known key found for this signature in database
GPG key ID: 89AD497003F93FDD
347 changed files with 8219 additions and 1162 deletions

View file

@ -28,12 +28,26 @@ pub struct Spaces<'a, T> {
pub after: &'a [CommentOrNewline<'a>],
}
impl<'a, T: Copy> ExtractSpaces<'a> for Spaces<'a, T> {
type Item = T;
fn extract_spaces(&self) -> Spaces<'a, T> {
*self
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct SpacesBefore<'a, T> {
pub before: &'a [CommentOrNewline<'a>],
pub item: T,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct SpacesAfter<'a, T> {
pub after: &'a [CommentOrNewline<'a>],
pub item: T,
}
#[derive(Copy, Clone, PartialEq)]
pub enum Spaced<'a, T> {
Item(T),
@ -491,7 +505,11 @@ pub enum Expr<'a> {
Backpassing(&'a [Loc<Pattern<'a>>], &'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
Dbg,
DbgStmt(&'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
DbgStmt {
first: &'a Loc<Expr<'a>>,
extra_args: &'a [&'a Loc<Expr<'a>>],
continuation: &'a Loc<Expr<'a>>,
},
// This form of debug is a desugared call to roc_dbg
LowLevelDbg(&'a (&'a str, &'a str), &'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
@ -627,7 +645,7 @@ pub fn is_expr_suffixed(expr: &Expr) -> bool {
Expr::ParensAround(sub_loc_expr) => is_expr_suffixed(sub_loc_expr),
// expression in a closure
Expr::Closure(_, sub_loc_expr) => is_expr_suffixed(&sub_loc_expr.value),
Expr::Closure(_, _) => false,
// expressions inside a Defs
Expr::Defs(defs, expr) => {
@ -671,7 +689,15 @@ pub fn is_expr_suffixed(expr: &Expr) -> bool {
Expr::OpaqueRef(_) => false,
Expr::Backpassing(_, _, _) => false, // TODO: we might want to check this?
Expr::Dbg => false,
Expr::DbgStmt(a, b) => is_expr_suffixed(&a.value) || is_expr_suffixed(&b.value),
Expr::DbgStmt {
first,
extra_args,
continuation,
} => {
is_expr_suffixed(&first.value)
|| extra_args.iter().any(|a| is_expr_suffixed(&a.value))
|| is_expr_suffixed(&continuation.value)
}
Expr::LowLevelDbg(_, a, b) => is_expr_suffixed(&a.value) || is_expr_suffixed(&b.value),
Expr::Try => false,
Expr::UnaryOp(a, _) => is_expr_suffixed(&a.value),
@ -930,10 +956,17 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
expr_stack.push(&a.value);
expr_stack.push(&b.value);
}
DbgStmt(condition, cont) => {
DbgStmt {
first,
extra_args,
continuation,
} => {
expr_stack.reserve(2);
expr_stack.push(&condition.value);
expr_stack.push(&cont.value);
expr_stack.push(&first.value);
for arg in extra_args.iter() {
expr_stack.push(&arg.value);
}
expr_stack.push(&continuation.value);
}
LowLevelDbg(_, condition, cont) => {
expr_stack.reserve(2);
@ -2311,6 +2344,7 @@ impl_extract_spaces!(Tag);
impl_extract_spaces!(AssignedField<T>);
impl_extract_spaces!(TypeAnnotation);
impl_extract_spaces!(ImplementsAbility);
impl_extract_spaces!(ImplementsAbilities);
impl<'a, T: Copy> ExtractSpaces<'a> for Spaced<'a, T> {
type Item = T;
@ -2476,7 +2510,7 @@ impl<'a> Malformed for Expr<'a> {
Defs(defs, body) => defs.is_malformed() || body.is_malformed(),
Backpassing(args, call, body) => args.iter().any(|arg| arg.is_malformed()) || call.is_malformed() || body.is_malformed(),
Dbg => false,
DbgStmt(condition, continuation) => condition.is_malformed() || continuation.is_malformed(),
DbgStmt { first, extra_args, continuation } => first.is_malformed() || extra_args.iter().any(|a| a.is_malformed()) || continuation.is_malformed(),
LowLevelDbg(_, condition, continuation) => condition.is_malformed() || continuation.is_malformed(),
Try => false,
Return(return_value, after_return) => return_value.is_malformed() || after_return.is_some_and(|ar| ar.is_malformed()),

View file

@ -298,8 +298,11 @@ fn loc_possibly_negative_or_negated_term<'a>(
let parse_unary_negate = move |arena, state: State<'a>, min_indent: u32| {
let initial = state.clone();
let (_, (loc_op, loc_expr), state) =
and(loc(unary_negate()), loc_term(options)).parse(arena, state, min_indent)?;
let (_, (loc_op, loc_expr), state) = and(
loc(unary_negate()),
loc_possibly_negative_or_negated_term(options),
)
.parse(arena, state, min_indent)?;
let loc_expr = numeric_negate_expression(arena, initial, loc_op, loc_expr, &[]);
@ -307,19 +310,23 @@ fn loc_possibly_negative_or_negated_term<'a>(
};
one_of![
parse_unary_negate,
parse_unary_negate.trace("d"),
// this will parse negative numbers, which the unary negate thing up top doesn't (for now)
loc(specialize_err(EExpr::Number, number_literal_help())),
loc(specialize_err(EExpr::Number, number_literal_help())).trace("c"),
loc(map_with_arena(
and(
loc(byte(b'!', EExpr::Start)),
space0_before_e(loc_term(options), EExpr::IndentStart)
space0_before_e(
loc_possibly_negative_or_negated_term(options),
EExpr::IndentStart
)
),
|arena: &'a Bump, (loc_op, loc_expr): (Loc<_>, _)| {
Expr::UnaryOp(arena.alloc(loc_expr), Loc::at(loc_op.region, UnaryOp::Not))
}
)),
loc_term_or_underscore_or_conditional(options)
))
.trace("b"),
loc_term_or_underscore_or_conditional(options).trace("a")
]
}
@ -328,7 +335,7 @@ fn fail_expr_start_e<'a, T: 'a>() -> impl Parser<'a, T, EExpr<'a>> {
}
fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> {
move |_arena: &'a Bump, state: State<'a>, _min_indent: u32| {
move |_arena: &'a Bump, state: State<'a>, min_indent: u32| {
// a minus is unary iff
//
// - it is preceded by whitespace (spaces, newlines, comments)
@ -339,7 +346,10 @@ fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> {
.map(|c| c.is_ascii_whitespace() || *c == b'#')
.unwrap_or(false);
if state.bytes().starts_with(b"-") && !followed_by_whitespace {
if state.bytes().starts_with(b"-")
&& !followed_by_whitespace
&& state.column() >= min_indent
{
// the negate is only unary if it is not followed by whitespace
let state = state.advance(1);
Ok((MadeProgress, (), state))
@ -459,7 +469,7 @@ fn parse_expr_after_apply<'a>(
before_op: State<'a>,
initial_state: State<'a>,
) -> Result<(Progress, Expr<'a>, State<'a>), (Progress, EExpr<'a>)> {
match loc(bin_op(check_for_defs)).parse(arena, state.clone(), min_indent) {
match loc(bin_op(check_for_defs)).parse(arena, state.clone(), call_min_indent) {
Err((MadeProgress, f)) => Err((MadeProgress, f)),
Ok((_, loc_op, state)) => {
expr_state.consume_spaces(arena);
@ -845,13 +855,13 @@ fn numeric_negate_expression<'a, T>(
let region = Region::new(start, expr.region.end());
let new_expr = match expr.value {
Expr::Num(string) => {
Expr::Num(string) if !string.starts_with('-') => {
let new_string =
unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) };
Expr::Num(new_string)
}
Expr::Float(string) => {
Expr::Float(string) if !string.starts_with('-') => {
let new_string =
unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) };
@ -860,11 +870,11 @@ fn numeric_negate_expression<'a, T>(
Expr::NonBase10Int {
string,
base,
is_negative,
is_negative: false,
} => {
// don't include the minus sign here; it will not be parsed right
Expr::NonBase10Int {
is_negative: !is_negative,
is_negative: true,
string,
base,
}
@ -1151,9 +1161,7 @@ fn parse_stmt_alias_or_opaque<'a>(
AliasOrOpaque::Alias => {
let (_, signature, state) = alias_signature().parse(arena, state, min_indent)?;
// TODO: this code used to be broken and it dropped the spaces after the operator.
// The formatter is not expecting this, so let's keep it as is for now.
// let signature = signature.map(|v| v.maybe_before(arena, spaces_after_operator));
let signature = signature.map(|v| v.maybe_before(arena, spaces_after_operator));
let header = TypeHeader {
name: Loc::at(expr.region, name),
@ -1172,9 +1180,7 @@ fn parse_stmt_alias_or_opaque<'a>(
let (_, (signature, derived), state) =
opaque_signature().parse(arena, state, indented_more)?;
// TODO: this code used to be broken and it dropped the spaces after the operator.
// The formatter is not expecting this, so let's keep it as is for now.
// let signature = signature.map(|v| v.maybe_before(arena, spaces_after_operator));
let signature = signature.map(|v| v.maybe_before(arena, spaces_after_operator));
let header = TypeHeader {
name: Loc::at(expr.region, name),
@ -1855,7 +1861,7 @@ fn parse_expr_end<'a>(
Err((NoProgress, _)) => {
let before_op = state.clone();
// try an operator
match loc(bin_op(check_for_defs)).parse(arena, state.clone(), min_indent) {
match loc(bin_op(check_for_defs)).parse(arena, state.clone(), call_min_indent) {
Err((MadeProgress, f)) => Err((MadeProgress, f)),
Ok((_, loc_op, state)) => {
expr_state.consume_spaces(arena);
@ -1899,7 +1905,7 @@ fn parse_stmt_after_apply<'a>(
initial_state: State<'a>,
) -> ParseResult<'a, Stmt<'a>, EExpr<'a>> {
let before_op = state.clone();
match loc(operator()).parse(arena, state.clone(), min_indent) {
match loc(operator()).parse(arena, state.clone(), call_min_indent) {
Err((MadeProgress, f)) => Err((MadeProgress, f)),
Ok((_, loc_op, state)) => {
expr_state.consume_spaces(arena);
@ -2172,7 +2178,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
| Expr::If { .. }
| Expr::When(_, _)
| Expr::Dbg
| Expr::DbgStmt(_, _)
| Expr::DbgStmt { .. }
| Expr::LowLevelDbg(_, _, _)
| Expr::Return(_, _)
| Expr::MalformedSuffixed(..)
@ -3086,16 +3092,13 @@ fn stmts_to_defs<'a>(
_,
) = e
{
if args.len() != 1 {
// TODO: this should be done in can, not parsing!
return Err(EExpr::Dbg(
EExpect::DbgArity(sp_stmt.item.region.start()),
sp_stmt.item.region.start(),
));
}
let condition = &args[0];
let rest = stmts_to_expr(&stmts[i + 1..], arena)?;
let e = Expr::DbgStmt(condition, arena.alloc(rest));
let e = Expr::DbgStmt {
first: condition,
extra_args: &args[1..],
continuation: arena.alloc(rest),
};
let e = if sp_stmt.before.is_empty() {
e
@ -3211,7 +3214,11 @@ fn stmts_to_defs<'a>(
if exprify_dbg {
let e = if i + 1 < stmts.len() {
let rest = stmts_to_expr(&stmts[i + 1..], arena)?;
Expr::DbgStmt(arena.alloc(condition), arena.alloc(rest))
Expr::DbgStmt {
first: arena.alloc(condition),
extra_args: &[],
continuation: arena.alloc(rest),
}
} else {
Expr::Apply(
arena.alloc(Loc {
@ -3799,9 +3806,9 @@ enum OperatorOrDef {
}
fn bin_op<'a>(check_for_defs: bool) -> impl Parser<'a, BinOp, EExpr<'a>> {
move |_, state: State<'a>, _m| {
move |_, state: State<'a>, min_indent| {
let start = state.pos();
let (_, op, state) = operator_help(EExpr::Start, EExpr::BadOperator, state)?;
let (_, op, state) = operator_help(EExpr::Start, EExpr::BadOperator, state, min_indent)?;
let err_progress = if check_for_defs {
MadeProgress
} else {
@ -3822,7 +3829,8 @@ fn bin_op<'a>(check_for_defs: bool) -> impl Parser<'a, BinOp, EExpr<'a>> {
}
fn operator<'a>() -> impl Parser<'a, OperatorOrDef, EExpr<'a>> {
(move |_, state, _m| operator_help(EExpr::Start, EExpr::BadOperator, state)).trace("operator")
(move |_, state, min_indent| operator_help(EExpr::Start, EExpr::BadOperator, state, min_indent))
.trace("operator")
}
#[inline(always)]
@ -3830,6 +3838,7 @@ fn operator_help<'a, F, G, E>(
to_expectation: F,
to_error: G,
mut state: State<'a>,
min_indent: u32,
) -> ParseResult<'a, OperatorOrDef, E>
where
F: Fn(Position) -> E,
@ -3855,7 +3864,21 @@ where
match chomped {
"" => Err((NoProgress, to_expectation(state.pos()))),
"+" => good!(OperatorOrDef::BinOp(BinOp::Plus), 1),
"-" => good!(OperatorOrDef::BinOp(BinOp::Minus), 1),
"-" => {
// A unary minus must only match if we are at the correct indent level; indent level doesn't
// matter for the rest of the operators.
// Note that a unary minus is distinguished by not having a space after it
let has_whitespace = matches!(
state.bytes().get(1),
Some(b' ' | b'#' | b'\n' | b'\r' | b'\t') | None
);
if !has_whitespace && state.column() < min_indent {
return Err((NoProgress, to_expectation(state.pos())));
}
good!(OperatorOrDef::BinOp(BinOp::Minus), 1)
}
"*" => good!(OperatorOrDef::BinOp(BinOp::Star), 1),
"/" => good!(OperatorOrDef::BinOp(BinOp::Slash), 1),
"%" => good!(OperatorOrDef::BinOp(BinOp::Percent), 1),
@ -3883,6 +3906,10 @@ where
}
"<-" => good!(OperatorOrDef::Backpassing, 2),
"!" => Err((NoProgress, to_error("!", state.pos()))),
"&" => {
// makes no progress, so it does not interfere with record updaters / `&foo`
Err((NoProgress, to_error("&", state.pos())))
}
_ => bad_made_progress!(chomped),
}
}

View file

@ -396,10 +396,22 @@ impl<'a> Normalize<'a> for ValueDef<'a> {
match *self {
Annotation(a, b) => Annotation(a.normalize(arena), b.normalize(arena)),
Body(a, b) => Body(
arena.alloc(a.normalize(arena)),
arena.alloc(b.normalize(arena)),
),
Body(a, b) => {
let a = a.normalize(arena);
let b = b.normalize(arena);
let is_unit_assignment = if let Pattern::RecordDestructure(collection) = a.value {
collection.is_empty()
} else {
false
};
if is_unit_assignment {
Stmt(arena.alloc(b))
} else {
Body(arena.alloc(a), arena.alloc(b))
}
}
AnnotatedBody {
ann_pattern,
ann_type,
@ -560,26 +572,6 @@ impl<'a> Normalize<'a> for StrLiteral<'a> {
match *self {
StrLiteral::PlainLine(t) => StrLiteral::PlainLine(t),
StrLiteral::Line(t) => {
let mut needs_merge = false;
let mut last_was_mergable = false;
for segment in t.iter() {
let mergable = matches!(
segment,
StrSegment::Plaintext(_)
| StrSegment::Unicode(_)
| StrSegment::EscapedChar(_)
);
if mergable && last_was_mergable {
needs_merge = true;
break;
}
last_was_mergable = mergable;
}
if !needs_merge {
return StrLiteral::Line(t.normalize(arena));
}
let mut new_segments = Vec::new_in(arena);
let mut last_text = String::new_in(arena);
@ -713,33 +705,22 @@ impl<'a> Normalize<'a> for Expr<'a> {
arena.alloc(b.normalize(arena)),
),
Expr::Crash => Expr::Crash,
Expr::Defs(a, b) => {
let mut defs = a.clone();
defs.space_before = vec![Default::default(); defs.len()];
defs.space_after = vec![Default::default(); defs.len()];
defs.regions = vec![Region::zero(); defs.len()];
defs.spaces.clear();
for type_def in defs.type_defs.iter_mut() {
*type_def = type_def.normalize(arena);
}
for value_def in defs.value_defs.iter_mut() {
*value_def = value_def.normalize(arena);
}
Expr::Defs(arena.alloc(defs), arena.alloc(b.normalize(arena)))
}
Expr::Defs(a, b) => fold_defs(arena, a.defs(), b.value.normalize(arena)),
Expr::Backpassing(a, b, c) => Expr::Backpassing(
arena.alloc(a.normalize(arena)),
arena.alloc(b.normalize(arena)),
arena.alloc(c.normalize(arena)),
),
Expr::Dbg => Expr::Dbg,
Expr::DbgStmt(a, b) => Expr::DbgStmt(
arena.alloc(a.normalize(arena)),
arena.alloc(b.normalize(arena)),
),
Expr::DbgStmt {
first,
extra_args,
continuation,
} => Expr::DbgStmt {
first: arena.alloc(first.normalize(arena)),
extra_args: extra_args.normalize(arena),
continuation: arena.alloc(continuation.normalize(arena)),
},
Expr::LowLevelDbg(x, a, b) => Expr::LowLevelDbg(
x,
arena.alloc(a.normalize(arena)),
@ -755,7 +736,22 @@ impl<'a> Normalize<'a> for Expr<'a> {
}
Expr::BinOps(a, b) => Expr::BinOps(a.normalize(arena), arena.alloc(b.normalize(arena))),
Expr::UnaryOp(a, b) => {
Expr::UnaryOp(arena.alloc(a.normalize(arena)), b.normalize(arena))
let a = a.normalize(arena);
match (a.value, b.value) {
(Expr::Num(text), UnaryOp::Negate) if !text.starts_with('-') => {
let mut res = String::new_in(arena);
res.push('-');
res.push_str(text);
Expr::Num(res.into_bump_str())
}
(Expr::Float(text), UnaryOp::Negate) if !text.starts_with('-') => {
let mut res = String::new_in(arena);
res.push('-');
res.push_str(text);
Expr::Float(res.into_bump_str())
}
_ => Expr::UnaryOp(arena.alloc(a), b.normalize(arena)),
}
}
Expr::If {
if_thens,
@ -776,7 +772,7 @@ impl<'a> Normalize<'a> for Expr<'a> {
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
Expr::SpaceBefore(a, _) => a.normalize(arena),
Expr::SpaceAfter(a, _) => a.normalize(arena),
Expr::SingleQuote(a) => Expr::Num(a),
Expr::SingleQuote(a) => Expr::SingleQuote(a),
Expr::EmptyRecordBuilder(a) => {
Expr::EmptyRecordBuilder(arena.alloc(a.normalize(arena)))
}
@ -791,6 +787,61 @@ impl<'a> Normalize<'a> for Expr<'a> {
}
}
fn fold_defs<'a>(
arena: &'a Bump,
mut defs: impl Iterator<Item = Result<&'a TypeDef<'a>, &'a ValueDef<'a>>>,
final_expr: Expr<'a>,
) -> Expr<'a> {
let mut new_defs = Defs::default();
while let Some(def) = defs.next() {
match def {
Ok(td) => {
let td = td.normalize(arena);
new_defs.push_type_def(td, Region::zero(), &[], &[]);
}
Err(vd) => {
let vd = vd.normalize(arena);
match vd {
ValueDef::Stmt(&Loc {
value:
Expr::Apply(
&Loc {
value: Expr::Dbg, ..
},
args,
_,
),
..
}) => {
let rest = fold_defs(arena, defs, final_expr);
let new_final = Expr::DbgStmt {
first: args[0],
extra_args: &args[1..],
continuation: arena.alloc(Loc::at_zero(rest)),
};
if new_defs.is_empty() {
return new_final;
}
return Expr::Defs(
arena.alloc(new_defs),
arena.alloc(Loc::at_zero(new_final)),
);
}
_ => {
new_defs.push_value_def(vd, Region::zero(), &[], &[]);
}
}
}
}
}
if new_defs.is_empty() {
return final_expr;
}
Expr::Defs(arena.alloc(new_defs), arena.alloc(Loc::at_zero(final_expr)))
}
fn remove_spaces_bad_ident(ident: BadIdent) -> BadIdent {
match ident {
BadIdent::Start(_) => BadIdent::Start(Position::zero()),
@ -847,7 +898,7 @@ impl<'a> Normalize<'a> for Pattern<'a> {
is_negative,
},
Pattern::FloatLiteral(a) => Pattern::FloatLiteral(a),
Pattern::StrLiteral(a) => Pattern::StrLiteral(a),
Pattern::StrLiteral(a) => Pattern::StrLiteral(a.normalize(arena)),
Pattern::Underscore(a) => Pattern::Underscore(a),
Pattern::Malformed(a) => Pattern::Malformed(a),
Pattern::MalformedIdent(a, b) => Pattern::MalformedIdent(a, remove_spaces_bad_ident(b)),
@ -1465,7 +1516,6 @@ impl<'a> Normalize<'a> for EExpect<'a> {
EExpect::Continuation(arena.alloc(inner_err.normalize(arena)), Position::zero())
}
EExpect::IndentCondition(_) => EExpect::IndentCondition(Position::zero()),
EExpect::DbgArity(_) => EExpect::DbgArity(Position::zero()),
}
}
}

View file

@ -513,7 +513,6 @@ pub enum EExpect<'a> {
Condition(&'a EExpr<'a>, Position),
Continuation(&'a EExpr<'a>, Position),
IndentCondition(Position),
DbgArity(Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]
@ -836,7 +835,7 @@ where
}
// This should be enough for anyone. Right? RIGHT?
let indent_text = "| ; : ! ".repeat(20);
let indent_text = "| ; : ! ".repeat(100);
let cur_indent = INDENT.with(|i| *i.borrow());
@ -1060,11 +1059,15 @@ where
Some(
b' ' | b'#' | b'\n' | b'\r' | b'\t' | b',' | b'(' | b')' | b'[' | b']' | b'{'
| b'}' | b'"' | b'\'' | b'/' | b'\\' | b'+' | b'*' | b'%' | b'^' | b'&' | b'|'
| b'<' | b'>' | b'=' | b'!' | b'~' | b'`' | b';' | b':' | b'?' | b'.',
| b'<' | b'>' | b'=' | b'!' | b'~' | b'`' | b';' | b':' | b'?' | b'.' | b'@',
) => {
state = state.advance(width);
Ok((MadeProgress, (), state))
}
Some(b'-') if keyword_str != "expect" => {
state = state.advance(width);
Ok((MadeProgress, (), state))
}
None => {
state = state.advance(width);
Ok((MadeProgress, (), state))
@ -1669,6 +1672,21 @@ where
}
}
/// Creates a parser that fails if the next byte is the given byte.
pub fn error_on_byte<'a, T, E, F>(byte_to_match: u8, to_error: F) -> impl Parser<'a, T, E>
where
T: 'a,
E: 'a,
F: Fn(Position) -> E,
{
debug_assert_ne!(byte_to_match, b'\n');
move |_arena: &'a Bump, state: State<'a>, _min_indent: u32| match state.bytes().first() {
Some(x) if *x == byte_to_match => Err((MadeProgress, to_error(state.pos()))),
_ => Err((NoProgress, to_error(state.pos()))),
}
}
/// Runs two parsers in succession. If both parsers succeed, the output is a tuple of both outputs.
/// Both parsers must have the same error type.
///

View file

@ -10,9 +10,9 @@ use crate::expr::record_field;
use crate::ident::{lowercase_ident, lowercase_ident_keyword_e};
use crate::keyword;
use crate::parser::{
absolute_column_min_indent, and, collection_trailing_sep_e, either, increment_min_indent,
indented_seq, loc, map, map_with_arena, skip_first, skip_second, succeed, then, zero_or_more,
ERecord, ETypeAbilityImpl,
absolute_column_min_indent, and, collection_trailing_sep_e, either, error_on_byte,
increment_min_indent, indented_seq, loc, map, map_with_arena, skip_first, skip_second, succeed,
then, zero_or_more, ERecord, ETypeAbilityImpl,
};
use crate::parser::{
allocated, backtrackable, byte, fail, optional, specialize_err, specialize_err_ref, two_bytes,
@ -27,13 +27,13 @@ use roc_region::all::{Loc, Position, Region};
pub fn located<'a>(
is_trailing_comma_valid: bool,
) -> impl Parser<'a, Loc<TypeAnnotation<'a>>, EType<'a>> {
expression(is_trailing_comma_valid, false)
expression(is_trailing_comma_valid, false).trace("a")
}
pub fn located_opaque_signature<'a>(
is_trailing_comma_valid: bool,
) -> impl Parser<'a, Loc<TypeAnnotation<'a>>, EType<'a>> {
expression(is_trailing_comma_valid, true)
expression(is_trailing_comma_valid, true).trace("b")
}
#[inline(always)]
@ -241,7 +241,7 @@ fn loc_type_in_parens<'a>(
loc(and(
collection_trailing_sep_e(
byte(b'(', ETypeInParens::Open),
specialize_err_ref(ETypeInParens::Type, expression(true, false)),
specialize_err_ref(ETypeInParens::Type, expression(true, false).trace("c")),
byte(b',', ETypeInParens::End),
byte(b')', ETypeInParens::End),
TypeAnnotation::SpaceBefore,
@ -334,7 +334,7 @@ fn record_type_field<'a>() -> impl Parser<'a, AssignedField<'a, TypeAnnotation<'
))
.parse(arena, state, min_indent)?;
let val_parser = specialize_err_ref(ETypeRecord::Type, expression(true, false));
let val_parser = specialize_err_ref(ETypeRecord::Type, expression(true, false).trace("d"));
match opt_loc_val {
Some(First(_)) => {
@ -582,37 +582,39 @@ fn expression<'a>(
let (p1, first, state) = space0_before_e(term(stop_at_surface_has), EType::TIndentStart)
.parse(arena, state, min_indent)?;
let result = and(
zero_or_more(skip_first(
byte(b',', EType::TFunctionArgument),
one_of![
space0_around_ee(
term(stop_at_surface_has),
EType::TIndentStart,
EType::TIndentEnd
let (p2, rest, rest_state) = zero_or_more(skip_first(
backtrackable(byte(b',', EType::TFunctionArgument)),
one_of![
map_with_arena(
and(
backtrackable(space0_e(EType::TIndentStart)),
and(term(stop_at_surface_has), space0_e(EType::TIndentEnd)),
),
fail(EType::TFunctionArgument)
],
))
.trace("type_annotation:expression:rest_args"),
and(
space0_e(EType::TIndentStart),
one_of![
map(two_bytes(b'-', b'>', EType::TStart), |_| {
FunctionArrow::Pure
}),
map(two_bytes(b'=', b'>', EType::TStart), |_| {
FunctionArrow::Effectful
}),
],
)
.trace("type_annotation:expression:arrow"),
comma_args_help,
),
error_on_byte(b',', EType::TFunctionArgument)
],
))
.trace("type_annotation:expression:rest_args")
.parse(arena, state.clone(), min_indent)?;
let result = and(
space0_e(EType::TIndentStart),
one_of![
map(two_bytes(b'-', b'>', EType::TStart), |_| {
FunctionArrow::Pure
}),
map(two_bytes(b'=', b'>', EType::TStart), |_| {
FunctionArrow::Effectful
}),
],
)
.parse(arena, state.clone(), min_indent);
.trace("type_annotation:expression:arrow")
.parse(arena, rest_state, min_indent);
let (progress, annot, state) = match result {
Ok((p2, (rest, (space_before_arrow, arrow)), state)) => {
let (p3, return_type, state) =
Ok((p3, (space_before_arrow, arrow), state)) => {
let (p4, return_type, state) =
space0_before_e(term(stop_at_surface_has), EType::TIndentStart)
.parse(arena, state, min_indent)?;
@ -636,7 +638,7 @@ fn expression<'a>(
region,
value: TypeAnnotation::Function(output, arrow, arena.alloc(return_type)),
};
let progress = p1.or(p2).or(p3);
let progress = p1.or(p2).or(p3).or(p4);
(progress, result, state)
}
Err(err) => {
@ -664,7 +666,10 @@ fn expression<'a>(
// Finally, try to parse a where clause if there is one.
// The where clause must be at least as deep as where the type annotation started.
match implements_clause_chain().parse(arena, state.clone(), min_indent) {
match implements_clause_chain()
.trace("implements_clause_chain")
.parse(arena, state.clone(), min_indent)
{
Ok((where_progress, (spaces_before, implements_chain), state)) => {
let region =
Region::span_across(&annot.region, &implements_chain.last().unwrap().region);
@ -694,6 +699,36 @@ fn expression<'a>(
.trace("type_annotation:expression")
}
fn comma_args_help<'a>(
arena: &'a Bump,
(spaces_before, (loc_val, spaces_after)): (
&'a [CommentOrNewline<'a>],
(Loc<TypeAnnotation<'a>>, &'a [CommentOrNewline<'a>]),
),
) -> Loc<TypeAnnotation<'a>> {
if spaces_before.is_empty() {
if spaces_after.is_empty() {
loc_val
} else {
arena
.alloc(loc_val.value)
.with_spaces_after(spaces_after, loc_val.region)
}
} else if spaces_after.is_empty() {
arena
.alloc(loc_val.value)
.with_spaces_before(spaces_before, loc_val.region)
} else {
let wrapped_expr = arena
.alloc(loc_val.value)
.with_spaces_after(spaces_after, loc_val.region);
arena
.alloc(wrapped_expr.value)
.with_spaces_before(spaces_before, wrapped_expr.region)
}
}
/// Parse a basic type annotation that's a combination of variables
/// (which are lowercase and unqualified, e.g. `a` in `List a`),
/// type applications (which are uppercase and optionally qualified, e.g.