mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 21:39:07 +00:00
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:
parent
335a8eb258
commit
ed62bcc15a
347 changed files with 8219 additions and 1162 deletions
|
@ -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()),
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue