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

1
Cargo.lock generated
View file

@ -2559,6 +2559,7 @@ dependencies = [
"roc_module",
"roc_parse",
"roc_region",
"soa",
]
[[package]]

View file

@ -1104,10 +1104,21 @@ pub fn desugar_expr<'a>(
// Allow naked dbg, necessary for piping values into dbg with the `Pizza` binop
loc_expr
}
DbgStmt(condition, continuation) => {
DbgStmt {
first: condition,
extra_args,
continuation,
} => {
let desugared_condition = &*env.arena.alloc(desugar_expr(env, scope, condition));
let desugared_continuation = &*env.arena.alloc(desugar_expr(env, scope, continuation));
if let Some(last) = extra_args.last() {
let args_region = Region::span_across(&condition.region, &last.region);
env.problem(Problem::OverAppliedDbg {
region: args_region,
});
}
env.arena.alloc(Loc {
value: *desugar_dbg_stmt(env, desugared_condition, desugared_continuation),
region: loc_expr.region,

View file

@ -1245,7 +1245,7 @@ pub fn canonicalize_expr<'a>(
(loc_expr.value, output)
}
ast::Expr::DbgStmt(_, _) => {
ast::Expr::DbgStmt { .. } => {
internal_error!("DbgStmt should have been desugared by now")
}
ast::Expr::LowLevelDbg((source_location, source), message, continuation) => {
@ -2546,7 +2546,7 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
| ast::Expr::Tag(_)
| ast::Expr::OpaqueRef(_) => true,
// Newlines are disallowed inside interpolation, and these all require newlines
ast::Expr::DbgStmt(_, _)
ast::Expr::DbgStmt { .. }
| ast::Expr::LowLevelDbg(_, _, _)
| ast::Expr::Return(_, _)
| ast::Expr::When(_, _)

View file

@ -15,3 +15,4 @@ roc_region = { path = "../region" }
roc_error_macros = { path = "../../error_macros" }
bumpalo.workspace = true
soa.workspace = true

View file

@ -1,11 +1,14 @@
use crate::{
collection::{fmt_collection, Braces},
expr::merge_spaces_conservative,
spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT},
Buf,
};
use bumpalo::{collections::Vec, Bump};
use roc_parse::ast::{
AbilityImpls, AssignedField, Collection, Expr, ExtractSpaces, FunctionArrow,
ImplementsAbilities, ImplementsAbility, ImplementsClause, Tag, TypeAnnotation, TypeHeader,
AbilityImpls, AssignedField, Collection, CommentOrNewline, Expr, ExtractSpaces, FunctionArrow,
ImplementsAbilities, ImplementsAbility, ImplementsClause, Spaceable, Spaces, SpacesAfter,
SpacesBefore, Tag, TypeAnnotation, TypeHeader,
};
use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc;
@ -35,6 +38,7 @@ use roc_region::all::Loc;
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Parens {
NotNeeded,
InCollection,
InFunctionType,
InApply,
InOperator,
@ -148,7 +152,10 @@ impl<'a> Formattable for TypeAnnotation<'a> {
true
}
Wildcard | Inferred | BoundVariable(_) | Malformed(_) => false,
TypeAnnotation::Wildcard
| TypeAnnotation::Inferred
| BoundVariable(_)
| Malformed(_) => false,
Function(args, _arrow, result) => {
result.value.is_multiline()
|| args.iter().any(|loc_arg| loc_arg.value.is_multiline())
@ -165,8 +172,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
Some(ann) if ann.value.is_multiline() => return true,
_ => {}
}
fields.items.iter().any(|field| field.value.is_multiline())
is_collection_multiline(fields)
}
Record { fields, ext } => {
@ -175,7 +181,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
_ => {}
}
fields.items.iter().any(|field| field.value.is_multiline())
is_collection_multiline(fields)
}
TagUnion { tags, ext } => {
@ -184,221 +190,333 @@ impl<'a> Formattable for TypeAnnotation<'a> {
_ => {}
}
tags.iter().any(|tag| tag.value.is_multiline())
!tags.final_comments().is_empty() || tags.iter().any(|tag| tag.value.is_multiline())
}
}
}
fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) {
use roc_parse::ast::TypeAnnotation::*;
fmt_ty_ann(self, buf, indent, parens, newlines, false);
}
}
let self_is_multiline = self.is_multiline();
fn fmt_ty_ann(
me: &TypeAnnotation<'_>,
buf: &mut Buf<'_>,
indent: u16,
parens: Parens,
newlines: Newlines,
newline_at_top: bool,
) {
let me = ann_lift_spaces(buf.text.bump(), me);
match self {
Function(args, arrow, ret) => {
let needs_parens = parens != Parens::NotNeeded;
let self_is_multiline = me.item.is_multiline();
buf.indent(indent);
if !me.before.is_empty() {
buf.ensure_ends_with_newline();
fmt_comments_only(buf, me.before.iter(), NewlineAt::Bottom, indent);
}
if newline_at_top {
buf.ensure_ends_with_newline();
}
if needs_parens {
buf.push('(')
}
match &me.item {
TypeAnnotation::SpaceBefore(_ann, _spaces) | TypeAnnotation::SpaceAfter(_ann, _spaces) => {
unreachable!()
}
TypeAnnotation::Function(args, arrow, ret) => {
let needs_parens = parens != Parens::NotNeeded;
let mut it = args.iter().enumerate().peekable();
buf.indent(indent);
while let Some((index, argument)) = it.next() {
let is_first = index == 0;
let is_multiline = &argument.value.is_multiline();
if !is_first && !is_multiline && self_is_multiline {
buf.newline();
}
argument.value.format_with_options(
buf,
Parens::InFunctionType,
Newlines::Yes,
indent,
);
if it.peek().is_some() {
buf.push_str(",");
if !self_is_multiline {
buf.spaces(1);
}
}
}
if self_is_multiline {
buf.newline();
buf.indent(indent);
} else {
buf.spaces(1);
}
match arrow {
FunctionArrow::Pure => buf.push_str("->"),
FunctionArrow::Effectful => buf.push_str("=>"),
}
buf.spaces(1);
ret.value
.format_with_options(buf, Parens::InFunctionType, Newlines::No, indent);
if needs_parens {
buf.push(')')
}
if needs_parens {
buf.push('(')
}
Apply(pkg, name, arguments) => {
buf.indent(indent);
let write_parens = parens == Parens::InApply && !arguments.is_empty();
if write_parens {
buf.push('(')
}
for (index, argument) in args.iter().enumerate() {
let is_first = index == 0;
if !pkg.is_empty() {
buf.push_str(pkg);
buf.push('.');
}
buf.push_str(name);
let needs_indent = except_last(arguments).any(|a| a.is_multiline())
|| arguments
.last()
.map(|a| {
a.is_multiline()
&& (!a.extract_spaces().before.is_empty()
|| !is_outdentable(&a.value))
})
.unwrap_or_default();
let arg_indent = if needs_indent {
indent + INDENT
} else {
indent
};
for arg in arguments.iter() {
if needs_indent {
let arg = arg.extract_spaces();
fmt_spaces(buf, arg.before.iter(), arg_indent);
buf.ensure_ends_with_newline();
arg.item.format_with_options(
buf,
Parens::InApply,
Newlines::Yes,
arg_indent,
);
fmt_spaces(buf, arg.after.iter(), arg_indent);
} else {
if !is_first {
buf.push_str(",");
if !self_is_multiline {
buf.spaces(1);
arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent);
}
}
if write_parens {
buf.push(')')
}
let newline_at_top = !is_first && self_is_multiline;
fmt_ty_ann(
&argument.value,
buf,
indent,
Parens::InFunctionType,
Newlines::Yes,
newline_at_top,
);
}
BoundVariable(v) => {
if self_is_multiline {
buf.newline();
buf.indent(indent);
buf.push_str(v)
}
Wildcard => {
buf.indent(indent);
buf.push('*')
}
Inferred => {
buf.indent(indent);
buf.push('_')
}
TagUnion { tags, ext } => {
fmt_collection(buf, indent, Braces::Square, *tags, newlines);
if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent);
}
}
Tuple { elems: fields, ext } => {
fmt_collection(buf, indent, Braces::Round, *fields, newlines);
if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent);
}
}
Record { fields, ext } => {
fmt_collection(buf, indent, Braces::Curly, *fields, newlines);
if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent);
}
}
As(lhs, _spaces, TypeHeader { name, vars }) => {
// TODO use _spaces?
lhs.value
.format_with_options(buf, Parens::InFunctionType, Newlines::No, indent);
} else {
buf.spaces(1);
buf.push_str("as");
buf.spaces(1);
buf.push_str(name.value);
for var in *vars {
buf.spaces(1);
var.value
.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
}
Where(annot, implements_clauses) => {
annot.format_with_options(buf, parens, newlines, indent);
if implements_clauses
.iter()
.any(|implements| implements.is_multiline())
{
buf.newline();
buf.indent(indent);
match arrow {
FunctionArrow::Pure => buf.push_str("->"),
FunctionArrow::Effectful => buf.push_str("=>"),
}
buf.spaces(1);
ret.value
.format_with_options(buf, Parens::InFunctionType, Newlines::No, indent);
if needs_parens {
buf.push(')')
}
}
TypeAnnotation::Apply(pkg, name, arguments) => {
buf.indent(indent);
let write_parens = parens == Parens::InApply && !arguments.is_empty();
if write_parens {
buf.push('(')
}
if !pkg.is_empty() {
buf.push_str(pkg);
buf.push('.');
}
buf.push_str(name);
let needs_indent = except_last(arguments).any(|a| a.is_multiline())
|| arguments
.last()
.map(|a| {
a.is_multiline()
&& (!a.extract_spaces().before.is_empty()
|| !ty_is_outdentable(&a.value))
})
.unwrap_or_default();
let arg_indent = if needs_indent {
indent + INDENT
} else {
indent
};
for arg in arguments.iter() {
if needs_indent {
let arg = arg.extract_spaces();
fmt_spaces(buf, arg.before.iter(), arg_indent);
buf.ensure_ends_with_newline();
arg.item
.format_with_options(buf, Parens::InApply, Newlines::Yes, arg_indent);
fmt_spaces(buf, arg.after.iter(), arg_indent);
} else {
buf.spaces(1);
}
for (i, has) in implements_clauses.iter().enumerate() {
buf.push_str(if i == 0 {
roc_parse::keyword::WHERE
} else {
","
});
buf.spaces(1);
has.format_with_options(buf, parens, newlines, indent);
arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent);
}
}
SpaceBefore(ann, spaces) => {
buf.ensure_ends_with_newline();
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
ann.format_with_options(buf, parens, newlines, indent)
if write_parens {
buf.push(')')
}
SpaceAfter(ann, spaces) => {
ann.format_with_options(buf, parens, newlines, indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
}
TypeAnnotation::BoundVariable(v) => {
buf.indent(indent);
buf.push_str(v)
}
TypeAnnotation::Wildcard => {
buf.indent(indent);
buf.push('*')
}
TypeAnnotation::Inferred => {
buf.indent(indent);
buf.push('_')
}
TypeAnnotation::TagUnion { tags, ext } => {
fmt_collection(buf, indent, Braces::Square, *tags, newlines);
fmt_ext(ext, buf, indent);
}
TypeAnnotation::Tuple { elems: fields, ext } => {
fmt_ty_collection(buf, indent, Braces::Round, *fields, newlines);
fmt_ext(ext, buf, indent);
}
TypeAnnotation::Record { fields, ext } => {
fmt_collection(buf, indent, Braces::Curly, *fields, newlines);
fmt_ext(ext, buf, indent);
}
TypeAnnotation::As(lhs, _spaces, TypeHeader { name, vars }) => {
// TODO use _spaces?
lhs.value
.format_with_options(buf, Parens::InFunctionType, Newlines::No, indent);
buf.spaces(1);
buf.push_str("as");
buf.spaces(1);
buf.push_str(name.value);
for var in *vars {
buf.spaces(1);
var.value
.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
Malformed(raw) => {
}
TypeAnnotation::Where(annot, implements_clauses) => {
annot.format_with_options(buf, parens, newlines, indent);
if implements_clauses
.iter()
.any(|implements| implements.is_multiline())
{
buf.newline();
buf.indent(indent);
buf.push_str(raw)
} else {
buf.spaces(1);
}
for (i, has) in implements_clauses.iter().enumerate() {
buf.push_str(if i == 0 {
roc_parse::keyword::WHERE
} else {
","
});
buf.spaces(1);
has.format_with_options(buf, parens, newlines, indent);
}
}
TypeAnnotation::Malformed(raw) => {
buf.indent(indent);
buf.push_str(raw)
}
}
if !me.after.is_empty() {
fmt_comments_only(buf, me.after.iter(), NewlineAt::Bottom, indent);
}
}
fn lower<'a, 'b: 'a>(
arena: &'b Bump,
lifted: Spaces<'b, TypeAnnotation<'b>>,
) -> TypeAnnotation<'b> {
if lifted.before.is_empty() && lifted.after.is_empty() {
return lifted.item;
}
if lifted.before.is_empty() {
return TypeAnnotation::SpaceAfter(arena.alloc(lifted.item), lifted.after);
}
if lifted.after.is_empty() {
return TypeAnnotation::SpaceBefore(arena.alloc(lifted.item), lifted.before);
}
TypeAnnotation::SpaceBefore(
arena.alloc(TypeAnnotation::SpaceAfter(
arena.alloc(lifted.item),
lifted.after,
)),
lifted.before,
)
}
fn fmt_ty_collection(
buf: &mut Buf<'_>,
indent: u16,
braces: Braces,
items: Collection<'_, Loc<TypeAnnotation<'_>>>,
newlines: Newlines,
) {
let arena = buf.text.bump();
let mut new_items: Vec<'_, NodeSpaces<'_, Node<'_>>> =
Vec::with_capacity_in(items.len(), arena);
let mut last_after: &[CommentOrNewline<'_>] = &[];
for (i, item) in items.items.iter().enumerate() {
let func_ty_risky = i > 0;
let lifted = ann_lift_to_node(
if func_ty_risky {
Parens::InCollection
} else {
Parens::NotNeeded
},
arena,
&item.value,
);
let before = merge_spaces_conservative(arena, last_after, lifted.before);
last_after = lifted.after;
new_items.push(NodeSpaces {
before,
item: lifted.item,
after: &[],
});
}
let final_comments = merge_spaces_conservative(arena, last_after, items.final_comments());
let new_items =
Collection::with_items_and_comments(arena, new_items.into_bump_slice(), final_comments);
fmt_collection(buf, indent, braces, new_items, newlines)
}
fn fmt_ext(ext: &Option<&Loc<TypeAnnotation<'_>>>, buf: &mut Buf<'_>, indent: u16) {
if let Some(loc_ext_ann) = *ext {
let me = ann_lift_spaces(buf.text.bump(), &loc_ext_ann.value);
let parens_needed = !me.before.is_empty() || ext_needs_parens(me.item);
if parens_needed {
// We need to make sure to not have whitespace before the ext of a type,
// since that would make it parse as something else.
buf.push('(');
loc_ext_ann.value.format(buf, indent + INDENT);
buf.indent(indent);
buf.push(')');
} else {
loc_ext_ann.value.format(buf, indent + INDENT);
}
}
}
fn is_outdentable(ann: &TypeAnnotation) -> bool {
matches!(
ann.extract_spaces().item,
TypeAnnotation::Tuple { .. } | TypeAnnotation::Record { .. }
)
fn ext_needs_parens(item: TypeAnnotation<'_>) -> bool {
match item {
TypeAnnotation::Record { .. }
| TypeAnnotation::TagUnion { .. }
| TypeAnnotation::Tuple { .. }
| TypeAnnotation::BoundVariable(..)
| TypeAnnotation::Wildcard
| TypeAnnotation::Inferred => false,
TypeAnnotation::Apply(_module, _func, args) => !args.is_empty(),
_ => true,
}
}
pub fn ty_is_outdentable(mut rhs: &TypeAnnotation) -> bool {
loop {
match rhs {
TypeAnnotation::SpaceBefore(sub_def, spaces) => {
let is_only_newlines = spaces.iter().all(|s| s.is_newline());
if !is_only_newlines || !sub_def.is_multiline() {
return false;
}
rhs = sub_def;
}
TypeAnnotation::SpaceAfter(sub_def, _) => {
rhs = sub_def;
}
TypeAnnotation::Where(ann, _clauses) => {
if !ann.is_multiline() {
return false;
}
rhs = &ann.value;
}
TypeAnnotation::Record { .. }
| TypeAnnotation::TagUnion { .. }
| TypeAnnotation::Tuple { .. } => return rhs.is_multiline(),
_ => return false,
}
}
}
/// Fields are subtly different on the type and term level:
@ -466,6 +584,7 @@ fn format_assigned_field_help<T>(
}
buf.spaces(separator_spaces);
buf.indent(indent);
buf.push(':');
buf.spaces(1);
ann.value.format(buf, indent);
@ -483,6 +602,7 @@ fn format_assigned_field_help<T>(
}
buf.spaces(separator_spaces);
buf.indent(indent);
buf.push('?');
buf.spaces(1);
ann.value.format(buf, indent);
@ -704,3 +824,279 @@ pub fn except_last<T>(items: &[T]) -> impl Iterator<Item = &T> {
items[..items.len() - 1].iter()
}
}
pub fn ann_lift_spaces<'a, 'b: 'a>(
arena: &'a Bump,
ann: &TypeAnnotation<'b>,
) -> Spaces<'a, TypeAnnotation<'a>> {
match ann {
TypeAnnotation::Apply(module, func, args) => {
if args.is_empty() {
return Spaces {
item: *ann,
before: &[],
after: &[],
};
}
let mut new_args = Vec::with_capacity_in(args.len(), arena);
if !args.is_empty() {
for arg in args.iter().take(args.len() - 1) {
let lifted = ann_lift_spaces(arena, &arg.value);
new_args.push(Loc::at(arg.region, lower(arena, lifted)));
}
}
let after = if let Some(last) = args.last() {
let lifted = ann_lift_spaces(arena, &last.value);
if lifted.before.is_empty() {
new_args.push(Loc::at(last.region, lifted.item));
} else {
new_args.push(Loc::at(
last.region,
TypeAnnotation::SpaceBefore(arena.alloc(lifted.item), lifted.before),
));
}
lifted.after
} else {
&[]
};
Spaces {
before: &[],
item: TypeAnnotation::Apply(module, func, new_args.into_bump_slice()),
after,
}
}
TypeAnnotation::SpaceBefore(expr, spaces) => {
let mut inner = ann_lift_spaces(arena, expr);
inner.before = merge_spaces_conservative(arena, spaces, inner.before);
inner
}
TypeAnnotation::SpaceAfter(expr, spaces) => {
let mut inner = ann_lift_spaces(arena, expr);
inner.after = merge_spaces_conservative(arena, inner.after, spaces);
inner
}
_ => Spaces {
before: &[],
item: *ann,
after: &[],
},
}
}
pub fn ann_lift_spaces_before<'a, 'b: 'a>(
arena: &'a Bump,
ann: &TypeAnnotation<'b>,
) -> SpacesBefore<'a, TypeAnnotation<'a>> {
let lifted = ann_lift_spaces(arena, ann);
SpacesBefore {
before: lifted.before,
item: lifted.item.maybe_after(arena, lifted.after),
}
}
pub fn ann_lift_spaces_after<'a, 'b: 'a>(
arena: &'a Bump,
ann: &TypeAnnotation<'b>,
) -> SpacesAfter<'a, TypeAnnotation<'a>> {
let lifted = ann_lift_spaces(arena, ann);
SpacesAfter {
item: lifted.item.maybe_before(arena, lifted.before),
after: lifted.after,
}
}
type Sp<'a> = &'a [CommentOrNewline<'a>];
#[derive(Copy, Clone, Debug)]
enum Node<'a> {
DelimitedSequence(Braces, &'a [(Sp<'a>, Node<'a>)], Sp<'a>),
TypeAnnotation(TypeAnnotation<'a>),
}
impl<'a> Formattable for Node<'a> {
fn is_multiline(&self) -> bool {
match self {
Node::DelimitedSequence(_braces, lefts, right) => {
if !right.is_empty() {
return true;
}
for (sp, l) in *lefts {
if l.is_multiline() || !sp.is_empty() {
return true;
}
}
false
}
Node::TypeAnnotation(type_annotation) => type_annotation.is_multiline(),
}
}
fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) {
match self {
Node::DelimitedSequence(braces, lefts, right) => {
buf.indent(indent);
buf.push(braces.start());
for (sp, l) in *lefts {
if !sp.is_empty() {
fmt_spaces(buf, sp.iter(), indent);
}
l.format_with_options(buf, parens, newlines, indent);
}
if !right.is_empty() {
fmt_spaces(buf, right.iter(), indent);
}
buf.indent(indent);
buf.push(braces.end());
}
Node::TypeAnnotation(type_annotation) => {
type_annotation.format_with_options(buf, parens, newlines, indent);
}
}
}
}
fn ann_lift_to_node<'a, 'b: 'a>(
parens: Parens,
arena: &'a Bump,
ann: &TypeAnnotation<'b>,
) -> Spaces<'a, Node<'a>> {
match ann {
TypeAnnotation::Apply(module, func, args) => {
if args.is_empty() {
return Spaces {
item: Node::TypeAnnotation(*ann),
before: &[],
after: &[],
};
}
let mut new_args = Vec::with_capacity_in(args.len(), arena);
if !args.is_empty() {
for arg in args.iter().take(args.len() - 1) {
let lifted = ann_lift_spaces(arena, &arg.value);
new_args.push(Loc::at(arg.region, lower(arena, lifted)));
}
}
let after = if let Some(last) = args.last() {
let lifted = ann_lift_spaces(arena, &last.value);
if lifted.before.is_empty() {
new_args.push(Loc::at(last.region, lifted.item));
} else {
new_args.push(Loc::at(
last.region,
TypeAnnotation::SpaceBefore(arena.alloc(lifted.item), lifted.before),
));
}
lifted.after
} else {
&[]
};
Spaces {
before: &[],
item: Node::TypeAnnotation(TypeAnnotation::Apply(
module,
func,
new_args.into_bump_slice(),
)),
after,
}
}
TypeAnnotation::SpaceBefore(expr, spaces) => {
let mut inner = ann_lift_to_node(parens, arena, expr);
inner.before = merge_spaces_conservative(arena, spaces, inner.before);
inner
}
TypeAnnotation::SpaceAfter(expr, spaces) => {
let mut inner = ann_lift_to_node(parens, arena, expr);
inner.after = merge_spaces_conservative(arena, inner.after, spaces);
inner
}
TypeAnnotation::Function(args, _purity, res) => {
let new_args = arena.alloc_slice_copy(args);
let before = if let Some(first) = new_args.first_mut() {
let lifted = ann_lift_spaces_before(arena, &first.value);
first.value = lifted.item;
lifted.before
} else {
&[]
};
let new_res = ann_lift_spaces_after(arena, &res.value);
let new_ann = TypeAnnotation::Function(
new_args,
FunctionArrow::Pure,
arena.alloc(Loc::at_zero(new_res.item)),
);
let inner = Spaces {
before,
item: Node::TypeAnnotation(new_ann),
after: new_res.after,
};
if parens == Parens::InCollection {
let node = Node::DelimitedSequence(
Braces::Round,
arena.alloc_slice_copy(&[(inner.before, inner.item)]),
inner.after,
);
Spaces {
before: &[],
item: node,
after: &[],
}
} else {
inner
}
}
_ => Spaces {
before: &[],
item: Node::TypeAnnotation(*ann),
after: &[],
},
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct NodeSpaces<'a, T> {
pub before: &'a [CommentOrNewline<'a>],
pub item: T,
pub after: &'a [CommentOrNewline<'a>],
}
impl<'a, T: Copy> ExtractSpaces<'a> for NodeSpaces<'a, T> {
type Item = T;
fn extract_spaces(&self) -> Spaces<'a, T> {
Spaces {
before: self.before,
item: self.item,
after: self.after,
}
}
}
impl<'a, V: Formattable> Formattable for NodeSpaces<'a, V> {
fn is_multiline(&self) -> bool {
!self.before.is_empty() || !self.after.is_empty() || self.item.is_multiline()
}
fn format_with_options(
&self,
buf: &mut Buf,
parens: crate::annotation::Parens,
newlines: Newlines,
indent: u16,
) {
fmt_spaces(buf, self.before.iter(), indent);
self.item.format_with_options(buf, parens, newlines, indent);
fmt_spaces(buf, self.after.iter(), indent);
}
}

View file

@ -1,11 +1,13 @@
use roc_parse::ast::{Collection, CommentOrNewline, ExtractSpaces};
use roc_parse::{
ast::{Collection, CommentOrNewline, ExtractSpaces},
expr::merge_spaces,
};
use crate::{
annotation::{is_collection_multiline, Formattable, Newlines},
spaces::{fmt_comments_only, NewlineAt, INDENT},
Buf,
};
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Braces {
Round,
@ -13,26 +15,35 @@ pub enum Braces {
Curly,
}
pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
impl Braces {
pub fn start(self) -> char {
match self {
Braces::Round => '(',
Braces::Curly => '{',
Braces::Square => '[',
}
}
pub fn end(self) -> char {
match self {
Braces::Round => ')',
Braces::Curly => '}',
Braces::Square => ']',
}
}
}
pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable + std::fmt::Debug>(
buf: &mut Buf<'buf>,
indent: u16,
braces: Braces,
items: Collection<'a, T>,
newline: Newlines,
) where
<T as ExtractSpaces<'a>>::Item: Formattable,
<T as ExtractSpaces<'a>>::Item: Formattable + std::fmt::Debug,
{
let start = match braces {
Braces::Round => '(',
Braces::Curly => '{',
Braces::Square => '[',
};
let end = match braces {
Braces::Round => ')',
Braces::Curly => '}',
Braces::Square => ']',
};
let start = braces.start();
let end = braces.end();
if is_collection_multiline(&items) {
let braces_indent = indent;
@ -43,10 +54,21 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
buf.indent(braces_indent);
buf.push(start);
let mut last_after: &[CommentOrNewline<'_>] = &[];
for (index, item) in items.iter().enumerate() {
let is_first_item = index == 0;
let item = item.extract_spaces();
let is_only_newlines = item.before.iter().all(|s| s.is_newline());
let last_after_was_only_newlines = last_after.iter().all(|s| s.is_newline());
if !last_after.is_empty() {
if last_after.iter().any(|s| s.is_newline()) {
buf.newline();
}
fmt_comments_only(buf, last_after.iter(), NewlineAt::None, item_indent);
}
if item.before.is_empty() || is_only_newlines {
buf.ensure_ends_with_newline();
@ -64,13 +86,14 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
if item
.before
.starts_with(&[CommentOrNewline::Newline, CommentOrNewline::Newline])
&& last_after_was_only_newlines
{
// If there's a comment, and it's not on the first item,
// and it's preceded by at least one blank line, maintain 1 blank line.
// (We already ensured that it ends in a newline, so this will turn that
// into a blank line.)
buf.newline();
buf.ensure_ends_with_blank_line();
}
}
@ -91,32 +114,30 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
buf.indent(item_indent);
buf.push(',');
if !item.after.is_empty() {
if item.after.iter().any(|s| s.is_newline()) {
buf.newline();
}
last_after = item.after;
}
fmt_comments_only(buf, item.after.iter(), NewlineAt::None, item_indent);
let final_comments = if !last_after.is_empty() {
if last_after.iter().any(|s| s.is_newline()) {
buf.newline();
}
}
if items.final_comments().iter().any(|s| s.is_newline()) {
buf.newline();
}
merge_spaces(buf.text.bump(), last_after, items.final_comments())
} else {
if items.final_comments().iter().any(|s| s.is_newline()) {
buf.newline();
}
if items
.final_comments()
.starts_with(&[CommentOrNewline::Newline, CommentOrNewline::Newline])
items.final_comments()
};
if has_comments(final_comments)
&& final_comments.starts_with(&[CommentOrNewline::Newline, CommentOrNewline::Newline])
{
buf.newline();
buf.ensure_ends_with_blank_line();
}
fmt_comments_only(
buf,
items.final_comments().iter(),
NewlineAt::None,
item_indent,
);
fmt_comments_only(buf, final_comments.iter(), NewlineAt::None, item_indent);
buf.ensure_ends_with_newline();
buf.indent(braces_indent);
@ -144,3 +165,13 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
buf.push(end);
}
fn has_comments(spaces: &[CommentOrNewline<'_>]) -> bool {
for space in spaces {
match space {
CommentOrNewline::Newline => {}
CommentOrNewline::LineComment(_) | CommentOrNewline::DocComment(_) => return true,
}
}
false
}

View file

@ -1,15 +1,27 @@
use crate::annotation::{is_collection_multiline, Formattable, Newlines, Parens};
use crate::annotation::{
ann_lift_spaces, ann_lift_spaces_after, is_collection_multiline, ty_is_outdentable,
Formattable, Newlines, Parens,
};
use crate::collection::{fmt_collection, Braces};
use crate::expr::fmt_str_literal;
use crate::pattern::fmt_pattern;
use crate::spaces::{fmt_default_newline, fmt_default_spaces, fmt_spaces, INDENT};
use crate::expr::{
expr_lift_and_lower, expr_lift_spaces, expr_lift_spaces_after, expr_lift_spaces_before,
fmt_str_literal, is_str_multiline, sub_expr_requests_parens,
};
use crate::pattern::{fmt_pattern, pattern_lift_spaces};
use crate::pattern::{pattern_lift_spaces_before, starts_with_inline_comment};
use crate::spaces::{
fmt_comments_only, fmt_default_newline, fmt_default_spaces, fmt_spaces, NewlineAt, INDENT,
};
use crate::Buf;
use bumpalo::Bump;
use roc_error_macros::internal_error;
use roc_parse::ast::{
AbilityMember, Defs, Expr, ExtractSpaces, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport,
ModuleImportParams, Pattern, Spaces, StrLiteral, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
AbilityMember, CommentOrNewline, Defs, Expr, ExtractSpaces, ImportAlias, ImportAsKeyword,
ImportExposingKeyword, ImportedModuleName, IngestedFileAnnotation, IngestedFileImport,
ModuleImport, ModuleImportParams, Pattern, Spaceable, Spaces, SpacesAfter, SpacesBefore,
StrLiteral, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
};
use roc_parse::expr::merge_spaces;
use roc_parse::header::Keyword;
use roc_region::all::Loc;
@ -28,18 +40,24 @@ impl<'a> Formattable for Defs<'a> {
indent: u16,
) {
let mut prev_spaces = true;
let arena = buf.text.bump();
for (index, def) in self.defs().enumerate() {
let spaces_before = &self.spaces[self.space_before[index].indices()];
let spaces_after = &self.spaces[self.space_after[index].indices()];
let def = def_lift_spaces(buf.text.bump(), def);
let spaces_before = merge_spaces(arena, spaces_before, def.before);
let spaces_after = merge_spaces(arena, def.after, spaces_after);
if prev_spaces {
fmt_spaces(buf, spaces_before.iter(), indent);
} else {
fmt_default_newline(buf, spaces_before, indent);
}
match def {
match def.item {
Ok(type_def) => type_def.format(buf, indent),
Err(value_def) => value_def.format(buf, indent),
}
@ -51,6 +69,339 @@ impl<'a> Formattable for Defs<'a> {
}
}
pub fn def_lift_spaces<'a, 'b: 'a>(
arena: &'a Bump,
def: Result<&'a TypeDef<'b>, &'a ValueDef<'b>>,
) -> Spaces<'a, Result<TypeDef<'a>, ValueDef<'a>>> {
match def {
Ok(td) => {
let td = tydef_lift_spaces(arena, *td);
Spaces {
before: td.before,
item: Ok(td.item),
after: td.after,
}
}
Err(vd) => {
let vd = valdef_lift_spaces(arena, *vd);
Spaces {
before: vd.before,
item: Err(vd.item),
after: vd.after,
}
}
}
}
fn lift_spaces_after<'a, 'b: 'a, T: 'b + ExtractSpaces<'a> + Spaceable<'a>>(
arena: &'a Bump,
item: T,
) -> SpacesAfter<'a, <T as ExtractSpaces<'a>>::Item>
where
<T as ExtractSpaces<'a>>::Item: Spaceable<'a>,
{
let spaces = item.extract_spaces();
SpacesAfter {
item: spaces.item.maybe_before(arena, spaces.before),
after: spaces.after,
}
}
pub fn tydef_lift_spaces<'a, 'b: 'a>(arena: &'a Bump, def: TypeDef<'b>) -> Spaces<'a, TypeDef<'a>> {
match def {
TypeDef::Alias { header, ann } => {
let ann_lifted = ann_lift_spaces_after(arena, &ann.value);
Spaces {
before: &[],
item: TypeDef::Alias {
header,
ann: Loc::at(ann.region, ann_lifted.item),
},
after: ann_lifted.after,
}
}
TypeDef::Opaque {
header,
typ,
derived,
} => {
if let Some(derived) = derived {
let derived_lifted = lift_spaces_after(arena, derived.value);
Spaces {
before: &[],
item: TypeDef::Opaque {
header,
typ,
derived: Some(Loc::at(derived.region, derived_lifted.item)),
},
after: derived_lifted.after,
}
} else {
let typ_lifted = ann_lift_spaces_after(arena, &typ.value);
Spaces {
before: &[],
item: TypeDef::Opaque {
header,
typ: Loc::at(typ.region, typ_lifted.item),
derived,
},
after: typ_lifted.after,
}
}
}
TypeDef::Ability {
header: _,
loc_implements: _,
members: _,
} => {
// TODO: if the fuzzer ever generates examples where it's important to lift spaces from the members,
// we'll need to implement this. I'm not sure that's possible, though.
Spaces {
before: &[],
item: def,
after: &[],
}
}
}
}
pub fn valdef_lift_spaces<'a, 'b: 'a>(
arena: &'a Bump,
def: ValueDef<'b>,
) -> Spaces<'a, ValueDef<'a>> {
match def {
ValueDef::Annotation(pat, ann) => {
let pat_lifted = pattern_lift_spaces_before(arena, &pat.value);
let ann_lifted = ann_lift_spaces_after(arena, &ann.value);
Spaces {
before: pat_lifted.before,
item: ValueDef::Annotation(
Loc::at(pat.region, pat_lifted.item),
Loc::at(ann.region, ann_lifted.item),
),
after: ann_lifted.after,
}
}
ValueDef::Body(pat, expr) => {
let pat_lifted = pattern_lift_spaces_before(arena, &pat.value);
let expr_lifted = expr_lift_spaces_after(Parens::NotNeeded, arena, &expr.value);
Spaces {
before: pat_lifted.before,
item: ValueDef::Body(
arena.alloc(Loc::at(pat.region, pat_lifted.item)),
arena.alloc(Loc::at(expr.region, expr_lifted.item)),
),
after: expr_lifted.after,
}
}
ValueDef::AnnotatedBody {
ann_pattern,
ann_type,
lines_between,
body_pattern,
body_expr,
} => {
let ann_pattern_lifted = pattern_lift_spaces_before(arena, &ann_pattern.value);
let ann_type_lifted = ann_lift_spaces_after(arena, &ann_type.value);
let body_pattern_lifted = pattern_lift_spaces_before(arena, &body_pattern.value);
let body_expr_lifted =
expr_lift_spaces_after(Parens::NotNeeded, arena, &body_expr.value);
let lines_between = merge_spaces(
arena,
ann_type_lifted.after,
merge_spaces(arena, lines_between, body_pattern_lifted.before),
);
Spaces {
before: ann_pattern_lifted.before,
item: ValueDef::AnnotatedBody {
ann_pattern: arena.alloc(Loc::at(ann_pattern.region, ann_pattern_lifted.item)),
ann_type: arena.alloc(Loc::at(ann_type.region, ann_type_lifted.item)),
lines_between,
body_pattern: arena
.alloc(Loc::at(body_pattern.region, body_pattern_lifted.item)),
body_expr: arena.alloc(Loc::at(body_expr.region, body_expr_lifted.item)),
},
after: body_expr_lifted.after,
}
}
ValueDef::Dbg {
condition,
preceding_comment,
} => {
let condition_lifted =
expr_lift_spaces_after(Parens::NotNeeded, arena, &condition.value);
Spaces {
before: &[],
item: ValueDef::Dbg {
condition: arena.alloc(Loc::at(condition.region, condition_lifted.item)),
preceding_comment,
},
after: condition_lifted.after,
}
}
ValueDef::Expect {
condition,
preceding_comment,
} => {
let condition_lifted =
expr_lift_spaces_after(Parens::NotNeeded, arena, &condition.value);
Spaces {
before: &[],
item: ValueDef::Expect {
condition: arena.alloc(Loc::at(condition.region, condition_lifted.item)),
preceding_comment,
},
after: condition_lifted.after,
}
}
ValueDef::ModuleImport(module_import) => {
// Module imports begin with 'import', and end with either a ImportAlias or Collection
// No spaces in sight!
Spaces {
before: &[],
item: ValueDef::ModuleImport(module_import),
after: &[],
}
}
ValueDef::IngestedFileImport(mut ingested_file_import) => {
// Ingested file imports begin with 'import', but can end with a TypeAnnotation, which can have spaces
let after = if let Some(ann) = &mut ingested_file_import.annotation {
let lifted = ann_lift_spaces_after(arena, &ann.annotation.value);
ann.annotation.value = lifted.item;
lifted.after
} else {
&[]
};
Spaces {
before: &[],
item: ValueDef::IngestedFileImport(ingested_file_import),
after,
}
}
ValueDef::Stmt(expr) => {
let expr_lifted = expr_lift_spaces(Parens::NotNeeded, arena, &expr.value);
Spaces {
before: expr_lifted.before,
item: ValueDef::Stmt(arena.alloc(Loc::at(expr.region, expr_lifted.item))),
after: expr_lifted.after,
}
}
ValueDef::StmtAfterExpr => Spaces {
before: &[],
item: ValueDef::StmtAfterExpr,
after: &[],
},
}
}
pub fn valdef_lift_spaces_before<'a, 'b: 'a>(
arena: &'a Bump,
def: ValueDef<'b>,
) -> SpacesBefore<'a, ValueDef<'a>> {
match def {
ValueDef::Annotation(pat, ann) => {
let pat_lifted = pattern_lift_spaces_before(arena, &pat.value);
SpacesBefore {
before: pat_lifted.before,
item: ValueDef::Annotation(Loc::at(pat.region, pat_lifted.item), ann),
}
}
ValueDef::Body(pat, expr) => {
let pat_lifted = pattern_lift_spaces_before(arena, &pat.value);
SpacesBefore {
before: pat_lifted.before,
item: ValueDef::Body(arena.alloc(Loc::at(pat.region, pat_lifted.item)), expr),
}
}
ValueDef::AnnotatedBody {
ann_pattern,
ann_type,
lines_between,
body_pattern,
body_expr,
} => {
let ann_pattern_lifted = pattern_lift_spaces_before(arena, &ann_pattern.value);
let ann_type_lifted = ann_lift_spaces_after(arena, &ann_type.value);
let body_pattern_lifted = pattern_lift_spaces_before(arena, &body_pattern.value);
let lines_between = merge_spaces(
arena,
ann_type_lifted.after,
merge_spaces(arena, lines_between, body_pattern_lifted.before),
);
SpacesBefore {
before: ann_pattern_lifted.before,
item: ValueDef::AnnotatedBody {
ann_pattern: arena.alloc(Loc::at(ann_pattern.region, ann_pattern_lifted.item)),
ann_type: arena.alloc(Loc::at(ann_type.region, ann_type_lifted.item)),
lines_between,
body_pattern: arena
.alloc(Loc::at(body_pattern.region, body_pattern_lifted.item)),
body_expr,
},
}
}
ValueDef::Dbg {
condition,
preceding_comment,
} => SpacesBefore {
before: &[],
item: ValueDef::Dbg {
condition,
preceding_comment,
},
},
ValueDef::Expect {
condition,
preceding_comment,
} => SpacesBefore {
before: &[],
item: ValueDef::Expect {
condition,
preceding_comment,
},
},
ValueDef::ModuleImport(module_import) => {
// Module imports always start with 'import', no spaces
SpacesBefore {
before: &[],
item: ValueDef::ModuleImport(module_import),
}
}
ValueDef::IngestedFileImport(ingested_file_import) => {
// Similarly, ingested file imports always start with 'import', no spaces
SpacesBefore {
before: &[],
item: ValueDef::IngestedFileImport(ingested_file_import),
}
}
ValueDef::Stmt(expr) => {
let expr_lifted = expr_lift_spaces_before(Parens::NotNeeded, arena, &expr.value);
SpacesBefore {
before: expr_lifted.before,
item: ValueDef::Stmt(arena.alloc(Loc::at(expr.region, expr_lifted.item))),
}
}
ValueDef::StmtAfterExpr => SpacesBefore {
before: &[],
item: ValueDef::StmtAfterExpr,
},
}
}
impl<'a> Formattable for TypeDef<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::TypeDef::*;
@ -66,34 +417,23 @@ impl<'a> Formattable for TypeDef<'a> {
use roc_parse::ast::TypeDef::*;
match self {
Alias {
header: TypeHeader { name, vars },
ann,
} => {
Alias { header, ann } => {
header.format(buf, indent);
buf.indent(indent);
buf.push_str(name.value);
for var in *vars {
buf.spaces(1);
let need_parens = matches!(var.value, Pattern::Apply(..));
if need_parens {
buf.push_str("(");
}
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
buf.indent(indent);
if need_parens {
buf.push_str(")");
}
}
buf.push_str(" :");
buf.spaces(1);
ann.format(buf, indent)
let ann = ann_lift_spaces(buf.text.bump(), &ann.value);
let inner_indent = if ty_is_outdentable(&ann.item) {
indent
} else {
indent + INDENT
};
fmt_comments_only(buf, ann.before.iter(), NewlineAt::Bottom, inner_indent);
ann.item.format(buf, inner_indent);
fmt_spaces(buf, ann.after.iter(), indent);
}
Opaque {
header,
@ -127,17 +467,11 @@ impl<'a> Formattable for TypeDef<'a> {
}
}
Ability {
header: TypeHeader { name, vars },
header,
loc_implements: _,
members,
} => {
buf.indent(indent);
buf.push_str(name.value);
for var in *vars {
buf.spaces(1);
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
buf.indent(indent);
}
header.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
buf.spaces(1);
buf.push_str(roc_parse::keyword::IMPLEMENTS);
@ -180,10 +514,62 @@ impl<'a> Formattable for TypeHeader<'a> {
buf.indent(indent);
buf.push_str(self.name.value);
let vars_indent = if self.vars.iter().any(|v| v.is_multiline()) {
indent + INDENT
} else {
indent
};
let mut last_after: &[CommentOrNewline<'_>] = &[];
let mut last_multiline = false;
for var in self.vars.iter() {
buf.spaces(1);
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
buf.indent(indent);
let var = pattern_lift_spaces(buf.text.bump(), &var.value);
let before = if !last_after.is_empty() {
merge_spaces(buf.text.bump(), last_after, var.before)
} else {
var.before
};
if !before.is_empty() {
if !var.item.is_multiline() {
fmt_comments_only(buf, before.iter(), NewlineAt::Bottom, vars_indent)
} else {
fmt_spaces(buf, before.iter(), vars_indent);
}
}
buf.ensure_ends_with_whitespace();
last_after = var.after;
last_multiline = var.item.is_multiline();
let need_parens = matches!(var.item, Pattern::Apply(..));
if need_parens {
buf.push_str("(");
}
fmt_pattern(buf, &var.item, vars_indent, Parens::NotNeeded);
buf.indent(vars_indent);
if need_parens {
buf.push_str(")");
}
}
if !last_after.is_empty() {
if starts_with_inline_comment(last_after.iter()) {
buf.spaces(1);
}
if !last_multiline {
fmt_comments_only(buf, last_after.iter(), NewlineAt::Bottom, indent)
} else {
fmt_spaces(buf, last_after.iter(), indent);
}
}
}
}
@ -441,7 +827,7 @@ impl<'a> Formattable for ValueDef<'a> {
);
}
Body(loc_pattern, loc_expr) => {
fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent);
fmt_body(buf, true, &loc_pattern.value, &loc_expr.value, indent);
}
Dbg { condition, .. } => fmt_dbg_in_def(buf, condition, self.is_multiline(), indent),
Expect { condition, .. } => fmt_expect(buf, condition, self.is_multiline(), indent),
@ -457,7 +843,7 @@ impl<'a> Formattable for ValueDef<'a> {
fmt_annotated_body_comment(buf, indent, lines_between);
buf.newline();
fmt_body(buf, &body_pattern.value, &body_expr.value, indent);
fmt_body(buf, false, &body_pattern.value, &body_expr.value, indent);
}
ModuleImport(module_import) => module_import.format(buf, indent),
IngestedFileImport(ingested_file_import) => ingested_file_import.format(buf, indent),
@ -483,20 +869,25 @@ fn fmt_general_def<L: Formattable>(
buf.push_str(sep);
buf.spaces(1);
let should_outdent = should_outdent(rhs);
let rhs_lifted = ann_lift_spaces(buf.text.bump(), rhs);
if should_outdent {
match rhs {
TypeAnnotation::SpaceBefore(sub_def, _) => {
sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
_ => {
rhs.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
}
if ty_is_outdentable(&rhs_lifted.item) && rhs_lifted.before.iter().all(|s| s.is_newline()) {
rhs_lifted
.item
.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
} else {
rhs.format_with_options(buf, Parens::NotNeeded, newlines, indent + INDENT);
buf.ensure_ends_with_newline();
fmt_comments_only(
buf,
rhs_lifted.before.iter(),
NewlineAt::Bottom,
indent + INDENT,
);
rhs_lifted
.item
.format_with_options(buf, Parens::NotNeeded, newlines, indent + INDENT);
}
fmt_comments_only(buf, rhs_lifted.after.iter(), NewlineAt::Bottom, indent);
} else {
buf.spaces(1);
buf.push_str(sep);
@ -505,28 +896,6 @@ fn fmt_general_def<L: Formattable>(
}
}
fn should_outdent(mut rhs: &TypeAnnotation) -> bool {
loop {
match rhs {
TypeAnnotation::SpaceBefore(sub_def, spaces) => {
let is_only_newlines = spaces.iter().all(|s| s.is_newline());
if !is_only_newlines || !sub_def.is_multiline() {
return false;
}
rhs = sub_def;
}
TypeAnnotation::Where(ann, _clauses) => {
if !ann.is_multiline() {
return false;
}
rhs = &ann.value;
}
TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => return true,
_ => return false,
}
}
}
fn fmt_dbg_in_def<'a>(buf: &mut Buf, condition: &'a Loc<Expr<'a>>, _: bool, indent: u16) {
buf.ensure_ends_with_newline();
buf.indent(indent);
@ -608,21 +977,36 @@ pub fn fmt_annotated_body_comment<'a>(
}
}
pub fn fmt_body<'a>(buf: &mut Buf, pattern: &'a Pattern<'a>, body: &'a Expr<'a>, indent: u16) {
pub fn fmt_body<'a>(
buf: &mut Buf,
allow_simplify_empty_record_destructure: bool,
pattern: &'a Pattern<'a>,
body: &'a Expr<'a>,
indent: u16,
) {
let pattern_extracted = pattern.extract_spaces();
// Check if this is an assignment into the unit value
let is_unit_assignment = if let Pattern::RecordDestructure(collection) = pattern {
collection.is_empty()
let is_unit_assignment = if let Pattern::RecordDestructure(collection) = pattern_extracted.item
{
allow_simplify_empty_record_destructure
&& collection.is_empty()
&& pattern_extracted.before.iter().all(|s| s.is_newline())
&& pattern_extracted.after.iter().all(|s| s.is_newline())
} else {
false
};
// Don't format the `{} =` for defs with this pattern
if !is_unit_assignment {
pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
buf.indent(indent);
buf.push_str(" =");
if is_unit_assignment {
return body.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
buf.indent(indent);
buf.push_str(" =");
let body = expr_lift_and_lower(Parens::NotNeeded, buf.text.bump(), body);
if body.is_multiline() {
match body {
Expr::SpaceBefore(sub_def, spaces) => {
@ -634,7 +1018,10 @@ pub fn fmt_body<'a>(buf: &mut Buf, pattern: &'a Pattern<'a>, body: &'a Expr<'a>,
_ => false,
};
if should_outdent {
if is_unit_assignment {
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
} else if should_outdent {
buf.spaces(1);
sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
} else {
@ -646,6 +1033,32 @@ pub fn fmt_body<'a>(buf: &mut Buf, pattern: &'a Pattern<'a>, body: &'a Expr<'a>,
);
}
}
Expr::Apply(
Loc {
value: Expr::Str(StrLiteral::Block(..)),
..
},
..,
) => {
buf.spaces(1);
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
}
Expr::Str(s) => {
if is_str_multiline(&s) {
buf.ensure_ends_with_newline();
} else {
buf.spaces(1);
}
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
}
_ if starts_with_block_string_literal(&body) => {
buf.ensure_ends_with_newline();
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
}
Expr::When(..) => {
buf.ensure_ends_with_newline();
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
}
Expr::Defs(..) | Expr::BinOps(_, _) | Expr::Backpassing(..) => {
// Binop chains always get a newline. Otherwise you can have things like:
//
@ -662,9 +1075,15 @@ pub fn fmt_body<'a>(buf: &mut Buf, pattern: &'a Pattern<'a>, body: &'a Expr<'a>,
buf.newline();
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
}
Expr::When(..) | Expr::Str(StrLiteral::Block(_)) => {
buf.ensure_ends_with_newline();
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
Expr::ParensAround(&Expr::SpaceBefore(sub_def, _)) => {
let needs_indent = !sub_expr_requests_parens(sub_def);
let indent = if needs_indent {
indent + INDENT
} else {
indent
};
buf.spaces(1);
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
}
_ => {
buf.spaces(1);
@ -677,6 +1096,18 @@ pub fn fmt_body<'a>(buf: &mut Buf, pattern: &'a Pattern<'a>, body: &'a Expr<'a>,
}
}
pub fn starts_with_block_string_literal(expr: &Expr<'_>) -> bool {
match expr {
Expr::Str(s) => is_str_multiline(s),
Expr::SpaceAfter(inner, _) | Expr::SpaceBefore(inner, _) => {
starts_with_block_string_literal(inner)
}
Expr::Apply(inner, _, _) => starts_with_block_string_literal(&inner.value),
Expr::TrySuffix { target: _, expr } => starts_with_block_string_literal(expr),
_ => false,
}
}
impl<'a> Formattable for AbilityMember<'a> {
fn is_multiline(&self) -> bool {
self.name.value.is_multiline() || self.typ.is_multiline()

File diff suppressed because it is too large Load diff

View file

@ -18,12 +18,14 @@ pub struct Buf<'a> {
spaces_to_flush: usize,
newlines_to_flush: usize,
beginning_of_line: bool,
line_indent: u16,
}
impl<'a> Buf<'a> {
pub fn new_in(arena: &'a Bump) -> Buf<'a> {
Buf {
text: String::new_in(arena),
line_indent: 0,
spaces_to_flush: 0,
newlines_to_flush: 0,
beginning_of_line: true,
@ -40,11 +42,18 @@ impl<'a> Buf<'a> {
pub fn indent(&mut self, indent: u16) {
if self.beginning_of_line {
self.line_indent = indent;
self.spaces_to_flush = indent as usize;
}
self.beginning_of_line = false;
}
pub fn cur_line_indent(&self) -> u16 {
debug_assert!(!self.beginning_of_line, "cur_line_indent before indent");
self.line_indent
}
#[track_caller]
pub fn push(&mut self, ch: char) {
debug_assert!(!self.beginning_of_line);
debug_assert!(
@ -61,6 +70,7 @@ impl<'a> Buf<'a> {
self.text.push(ch);
}
#[track_caller]
pub fn push_str_allow_spaces(&mut self, s: &str) {
debug_assert!(
!self.beginning_of_line,
@ -73,6 +83,7 @@ impl<'a> Buf<'a> {
self.text.push_str(s);
}
#[track_caller]
pub fn push_str(&mut self, s: &str) {
debug_assert!(
!self.beginning_of_line,
@ -129,6 +140,12 @@ impl<'a> Buf<'a> {
}
}
pub fn ensure_ends_with_whitespace(&mut self) {
if !self.text.is_empty() && self.newlines_to_flush == 0 && self.spaces_to_flush == 0 {
self.spaces_to_flush = 1;
}
}
fn flush_spaces(&mut self) {
for _ in 0..self.newlines_to_flush {
self.text.push('\n');

View file

@ -1,8 +1,15 @@
use crate::annotation::{Formattable, Newlines, Parens};
use crate::expr::{fmt_str_literal, format_sq_literal, is_str_multiline};
use crate::expr::{
expr_is_multiline, expr_lift_spaces_after, fmt_str_literal, format_sq_literal, is_str_multiline,
};
use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT};
use crate::Buf;
use roc_parse::ast::{Base, CommentOrNewline, Pattern, PatternAs};
use bumpalo::Bump;
use roc_parse::ast::{
Base, CommentOrNewline, Pattern, PatternAs, Spaceable, Spaces, SpacesAfter, SpacesBefore,
};
use roc_parse::expr::merge_spaces;
use roc_region::all::Loc;
pub fn fmt_pattern<'a>(buf: &mut Buf, pattern: &'a Pattern<'a>, indent: u16, parens: Parens) {
pattern.format_with_options(buf, parens, Newlines::No, indent);
@ -54,7 +61,7 @@ impl<'a> Formattable for Pattern<'a> {
Pattern::RecordDestructure(fields) => fields.iter().any(|f| f.is_multiline()),
Pattern::RequiredField(_, subpattern) => subpattern.is_multiline(),
Pattern::OptionalField(_, expr) => expr.is_multiline(),
Pattern::OptionalField(_, expr) => expr_is_multiline(&expr.value, true),
Pattern::As(pattern, pattern_as) => pattern.is_multiline() || pattern_as.is_multiline(),
Pattern::ListRest(opt_pattern_as) => match opt_pattern_as {
@ -86,222 +93,307 @@ impl<'a> Formattable for Pattern<'a> {
}
}
fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) {
use self::Pattern::*;
fn format_with_options(&self, buf: &mut Buf, parens: Parens, _newlines: Newlines, indent: u16) {
fmt_pattern_inner(self, buf, parens, indent, self.is_multiline());
}
}
match self {
Identifier { ident: string } => {
buf.indent(indent);
buf.push_str(string);
}
Tag(name) | OpaqueRef(name) => {
buf.indent(indent);
buf.push_str(name);
}
Apply(loc_pattern, loc_arg_patterns) => {
buf.indent(indent);
// Sometimes, an Apply pattern needs parens around it.
// In particular when an Apply's argument is itself an Apply (> 0) arguments
let parens = !loc_arg_patterns.is_empty() && (parens == Parens::InApply);
fn fmt_pattern_inner(
pat: &Pattern<'_>,
buf: &mut Buf,
parens: Parens,
indent: u16,
outer_is_multiline: bool,
) {
use self::Pattern::*;
let indent_more = if self.is_multiline() {
indent + INDENT
let me = pattern_lift_spaces(buf.text.bump(), pat);
if !me.before.is_empty() {
if !outer_is_multiline {
fmt_comments_only(buf, me.before.iter(), NewlineAt::Bottom, indent)
} else {
fmt_spaces(buf, me.before.iter(), indent);
}
}
let is_multiline = me.item.is_multiline();
match me.item {
Identifier { ident: string } => {
buf.indent(indent);
buf.push_str(string);
}
Tag(name) | OpaqueRef(name) => {
buf.indent(indent);
buf.push_str(name);
}
Apply(loc_pattern, loc_arg_patterns) => {
buf.indent(indent);
// Sometimes, an Apply pattern needs parens around it.
// In particular when an Apply's argument is itself an Apply (> 0) arguments
let parens = !loc_arg_patterns.is_empty() && (parens == Parens::InApply);
let indent_more = if is_multiline {
indent + INDENT
} else {
indent
};
if parens {
buf.push('(');
}
let pat = pattern_lift_spaces(buf.text.bump(), &loc_pattern.value);
if !pat.before.is_empty() {
if !is_multiline {
fmt_comments_only(buf, pat.before.iter(), NewlineAt::Bottom, indent)
} else {
indent
};
if parens {
buf.push('(');
}
loc_pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
for loc_arg in loc_arg_patterns.iter() {
buf.spaces(1);
loc_arg.format_with_options(buf, Parens::InApply, Newlines::No, indent_more);
}
if parens {
buf.push(')');
fmt_spaces(buf, pat.before.iter(), indent);
}
}
RecordDestructure(loc_patterns) => {
buf.indent(indent);
buf.push_str("{");
if !loc_patterns.is_empty() {
buf.spaces(1);
let mut it = loc_patterns.iter().peekable();
while let Some(loc_pattern) = it.next() {
loc_pattern.format(buf, indent);
fmt_pattern_inner(&pat.item, buf, Parens::InApply, indent, is_multiline);
if it.peek().is_some() {
buf.push_str(",");
buf.spaces(1);
if !pat.after.is_empty() {
if !is_multiline {
fmt_comments_only(buf, pat.after.iter(), NewlineAt::Bottom, indent_more)
} else {
fmt_spaces(buf, pat.after.iter(), indent_more);
}
}
for loc_arg in loc_arg_patterns.iter() {
buf.spaces(1);
fmt_pattern_inner(
&loc_arg.value,
buf,
Parens::InApply,
indent_more,
is_multiline,
);
}
if parens {
buf.push(')');
}
}
RecordDestructure(loc_patterns) => {
buf.indent(indent);
buf.push_str("{");
if !loc_patterns.is_empty() {
buf.spaces(1);
let mut it = loc_patterns.iter().peekable();
while let Some(loc_pattern) = it.next() {
let item = pattern_lift_spaces(buf.text.bump(), &loc_pattern.value);
if !item.before.is_empty() {
if !is_multiline {
fmt_comments_only(buf, item.before.iter(), NewlineAt::Bottom, indent)
} else {
fmt_spaces(buf, item.before.iter(), indent);
}
}
fmt_pattern_inner(&item.item, buf, Parens::NotNeeded, indent, is_multiline);
let is_multiline = item.item.is_multiline();
if it.peek().is_some() {
buf.push_str(",");
buf.spaces(1);
}
if !item.after.is_empty() {
if starts_with_inline_comment(item.after.iter()) {
buf.spaces(1);
}
if !is_multiline {
fmt_comments_only(buf, item.after.iter(), NewlineAt::Bottom, indent)
} else {
fmt_spaces(buf, item.after.iter(), indent);
}
}
}
buf.spaces(1);
}
buf.indent(indent);
buf.push_str("}");
}
RequiredField(name, loc_pattern) => {
buf.indent(indent);
buf.push_str(name);
buf.push_str(":");
buf.spaces(1);
fmt_pattern_inner(
&loc_pattern.value,
buf,
Parens::NotNeeded,
indent,
is_multiline,
);
}
OptionalField(name, loc_pattern) => {
buf.indent(indent);
buf.push_str(name);
buf.push_str(" ?");
buf.spaces(1);
loc_pattern.format(buf, indent);
}
NumLiteral(string) => {
buf.indent(indent);
buf.push_str(string);
}
NonBase10Literal {
base,
string,
is_negative,
} => {
buf.indent(indent);
if is_negative {
buf.push('-');
}
match base {
Base::Hex => buf.push_str("0x"),
Base::Octal => buf.push_str("0o"),
Base::Binary => buf.push_str("0b"),
Base::Decimal => { /* nothing */ }
}
buf.push_str(string);
}
FloatLiteral(string) => {
buf.indent(indent);
buf.push_str(string);
}
StrLiteral(literal) => fmt_str_literal(buf, literal, indent),
SingleQuote(string) => {
buf.indent(indent);
format_sq_literal(buf, string);
}
Underscore(name) => {
buf.indent(indent);
buf.push('_');
buf.push_str(name);
}
Tuple(loc_patterns) => {
buf.indent(indent);
buf.push_str("(");
let mut it = loc_patterns.iter().peekable();
while let Some(loc_pattern) = it.next() {
fmt_pattern_inner(
&loc_pattern.value,
buf,
Parens::NotNeeded,
indent,
is_multiline,
);
if it.peek().is_some() {
buf.indent(indent);
buf.push_str(",");
buf.spaces(1);
}
buf.push_str("}");
}
RequiredField(name, loc_pattern) => {
buf.indent(indent);
buf.push_str(name);
buf.push_str(":");
buf.spaces(1);
loc_pattern.format(buf, indent);
}
buf.indent(indent);
buf.push_str(")");
}
List(loc_patterns) => {
buf.indent(indent);
buf.push_str("[");
OptionalField(name, loc_pattern) => {
buf.indent(indent);
buf.push_str(name);
buf.push_str(" ?");
buf.spaces(1);
loc_pattern.format(buf, indent);
}
let mut it = loc_patterns.iter().peekable();
while let Some(loc_pattern) = it.next() {
fmt_pattern_inner(
&loc_pattern.value,
buf,
Parens::NotNeeded,
indent,
is_multiline,
);
&NumLiteral(string) => {
buf.indent(indent);
buf.push_str(string);
}
&NonBase10Literal {
base,
string,
is_negative,
} => {
buf.indent(indent);
if is_negative {
buf.push('-');
}
match base {
Base::Hex => buf.push_str("0x"),
Base::Octal => buf.push_str("0o"),
Base::Binary => buf.push_str("0b"),
Base::Decimal => { /* nothing */ }
}
buf.push_str(string);
}
&FloatLiteral(string) => {
buf.indent(indent);
buf.push_str(string);
}
StrLiteral(literal) => fmt_str_literal(buf, *literal, indent),
SingleQuote(string) => {
buf.indent(indent);
format_sq_literal(buf, string);
}
Underscore(name) => {
buf.indent(indent);
buf.push('_');
buf.push_str(name);
}
Tuple(loc_patterns) => {
buf.indent(indent);
buf.push_str("(");
let mut it = loc_patterns.iter().peekable();
while let Some(loc_pattern) = it.next() {
loc_pattern.format(buf, indent);
if it.peek().is_some() {
buf.push_str(",");
buf.spaces(1);
}
}
buf.push_str(")");
}
List(loc_patterns) => {
buf.indent(indent);
buf.push_str("[");
let mut it = loc_patterns.iter().peekable();
while let Some(loc_pattern) = it.next() {
loc_pattern.format(buf, indent);
if it.peek().is_some() {
buf.push_str(",");
buf.spaces(1);
}
}
buf.push_str("]");
}
ListRest(opt_pattern_as) => {
buf.indent(indent);
buf.push_str("..");
if let Some((list_rest_spaces, pattern_as)) = opt_pattern_as {
// these spaces "belong" to the `..`, which can never be multiline
fmt_comments_only(buf, list_rest_spaces.iter(), NewlineAt::Bottom, indent);
pattern_as.format(buf, indent + INDENT);
if it.peek().is_some() {
buf.indent(indent);
buf.push_str(",");
buf.spaces(1);
}
}
As(pattern, pattern_as) => {
let needs_parens = parens == Parens::InAsPattern;
buf.indent(indent);
buf.push_str("]");
}
ListRest(opt_pattern_as) => {
buf.indent(indent);
buf.push_str("..");
if needs_parens {
buf.push('(');
}
fmt_pattern(buf, &pattern.value, indent, parens);
if let Some((list_rest_spaces, pattern_as)) = opt_pattern_as {
// these spaces "belong" to the `..`, which can never be multiline
fmt_comments_only(buf, list_rest_spaces.iter(), NewlineAt::Bottom, indent);
pattern_as.format(buf, indent + INDENT);
if needs_parens {
buf.push(')');
}
}
}
// Space
SpaceBefore(sub_pattern, spaces) => {
if !sub_pattern.is_multiline() {
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent)
} else {
fmt_spaces(buf, spaces.iter(), indent);
}
As(pattern, pattern_as) => {
let needs_parens = parens == Parens::InAsPattern;
sub_pattern.format_with_options(buf, parens, newlines, indent);
}
SpaceAfter(sub_pattern, spaces) => {
sub_pattern.format_with_options(buf, parens, newlines, indent);
if starts_with_inline_comment(spaces.iter()) {
buf.spaces(1);
}
if !sub_pattern.is_multiline() {
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent)
} else {
fmt_spaces(buf, spaces.iter(), indent);
}
}
// Malformed
Malformed(string) | MalformedIdent(string, _) => {
if needs_parens {
buf.indent(indent);
buf.push_str(string);
buf.push('(');
}
QualifiedIdentifier { module_name, ident } => {
buf.indent(indent);
if !module_name.is_empty() {
buf.push_str(module_name);
buf.push('.');
}
buf.push_str(ident);
fmt_pattern(buf, &pattern.value, indent, parens);
pattern_as.format(buf, indent + INDENT);
if needs_parens {
buf.indent(indent);
buf.push(')');
}
}
SpaceBefore(..) | SpaceAfter(..) => unreachable!("handled by lift_spaces"),
// Malformed
Malformed(string) | MalformedIdent(string, _) => {
buf.indent(indent);
buf.push_str(string);
}
QualifiedIdentifier { module_name, ident } => {
buf.indent(indent);
if !module_name.is_empty() {
buf.push_str(module_name);
buf.push('.');
}
buf.push_str(ident);
}
}
if !me.after.is_empty() {
if starts_with_inline_comment(me.after.iter()) {
buf.spaces(1);
}
if !outer_is_multiline {
fmt_comments_only(buf, me.after.iter(), NewlineAt::Bottom, indent)
} else {
fmt_spaces(buf, me.after.iter(), indent);
}
}
}
fn starts_with_inline_comment<'a, I: IntoIterator<Item = &'a CommentOrNewline<'a>>>(
pub fn starts_with_inline_comment<'a, I: IntoIterator<Item = &'a CommentOrNewline<'a>>>(
spaces: I,
) -> bool {
matches!(
@ -309,3 +401,102 @@ fn starts_with_inline_comment<'a, I: IntoIterator<Item = &'a CommentOrNewline<'a
Some(CommentOrNewline::LineComment(_))
)
}
pub fn pattern_lift_spaces<'a, 'b: 'a>(
arena: &'a Bump,
pat: &Pattern<'b>,
) -> Spaces<'a, Pattern<'a>> {
match pat {
Pattern::Apply(func, args) => {
let func_lifted = pattern_lift_spaces(arena, &func.value);
let args = arena.alloc_slice_copy(args);
let (before, func, after) = if let Some(last) = args.last_mut() {
let last_lifted = pattern_lift_spaces(arena, &last.value);
if last_lifted.before.is_empty() {
*last = Loc::at(last.region, last_lifted.item)
} else {
*last = Loc::at(
last.region,
Pattern::SpaceBefore(arena.alloc(last_lifted.item), last_lifted.before),
);
}
let f = if func_lifted.after.is_empty() {
func_lifted.item
} else {
Pattern::SpaceAfter(arena.alloc(func_lifted.item), func_lifted.after)
};
(
func_lifted.before,
Loc::at(func.region, f),
last_lifted.after,
)
} else {
(
func_lifted.before,
Loc::at(func.region, func_lifted.item),
func_lifted.after,
)
};
Spaces {
before,
item: Pattern::Apply(arena.alloc(func), args),
after,
}
}
Pattern::OptionalField(name, expr) => {
let lifted = expr_lift_spaces_after(Parens::NotNeeded, arena, &expr.value);
Spaces {
before: &[],
item: Pattern::OptionalField(name, arena.alloc(Loc::at(expr.region, lifted.item))),
after: lifted.after,
}
}
Pattern::RequiredField(name, pat) => {
let lifted = pattern_lift_spaces_after(arena, &pat.value);
Spaces {
before: &[],
item: Pattern::RequiredField(name, arena.alloc(Loc::at(pat.region, lifted.item))),
after: lifted.after,
}
}
Pattern::SpaceBefore(expr, spaces) => {
let mut inner = pattern_lift_spaces(arena, expr);
inner.before = merge_spaces(arena, spaces, inner.before);
inner
}
Pattern::SpaceAfter(expr, spaces) => {
let mut inner = pattern_lift_spaces(arena, expr);
inner.after = merge_spaces(arena, inner.after, spaces);
inner
}
_ => Spaces {
before: &[],
item: *pat,
after: &[],
},
}
}
pub fn pattern_lift_spaces_before<'a, 'b: 'a>(
arena: &'a Bump,
pat: &Pattern<'b>,
) -> SpacesBefore<'a, Pattern<'a>> {
let lifted = pattern_lift_spaces(arena, pat);
SpacesBefore {
before: lifted.before,
item: lifted.item.maybe_after(arena, lifted.after),
}
}
pub fn pattern_lift_spaces_after<'a, 'b: 'a>(
arena: &'a Bump,
pat: &Pattern<'b>,
) -> SpacesAfter<'a, Pattern<'a>> {
let lifted = pattern_lift_spaces(arena, pat);
SpacesAfter {
item: lifted.item.maybe_before(arena, lifted.before),
after: lifted.after,
}
}

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.

View file

@ -1,6 +1,6 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use bumpalo::Bump;
use libfuzzer_sys::fuzz_target;
use test_syntax::test_helpers::Input;
fuzz_target!(|data: &[u8]| {

View file

@ -102,6 +102,12 @@ fn round_trip_once(input: Input<'_>) -> Option<String> {
return Some("Different ast".to_string());
}
let reformatted = reparsed_ast.format();
if output != reformatted {
return Some("Formatting not stable".to_string());
}
None
}

View file

@ -239,7 +239,7 @@ impl<'a> Input<'a> {
self.as_str(),
output.as_ref().as_str(),
actual,
reparsed_ast_normalized
reparsed_ast
);
}

View file

@ -0,0 +1 @@
Expr(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(End(@78), @74), @72), @71), @69), @68), @66), @65), @61), @60), @58), @57), @53), @52), @50), @49), @47), @46), @44), @43), @41), @40), @39), @38), @36), @35), @31), @30), @28), @27), @23), @22), @20), @19), @17), @16), @11), @10), @8), @7), @3), @2), @2), @0)

View file

@ -0,0 +1 @@
.:(i,i,(i,(i,ii,(i,(i,(i,i,(i,(i,i,(i,(J(i,(i,(i,(i,(i,i,(i,(i,i,(i,(i,(i,(J[]

View file

@ -0,0 +1,43 @@
Defs(
Defs {
tags: [
EitherIndex(0),
],
regions: [
@0-6,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [
Alias {
header: TypeHeader {
name: @0-1 "M",
vars: [],
},
ann: @4-5 SpaceBefore(
BoundVariable(
"r",
),
[
Newline,
],
),
},
],
value_defs: [],
},
@7-8 SpaceBefore(
Var {
module_name: "",
ident: "h",
},
[
Newline,
],
),
)

View file

@ -0,0 +1,48 @@
Defs(
Defs {
tags: [
EitherIndex(0),
],
regions: [
@0-7,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [
Alias {
header: TypeHeader {
name: @0-1 "A",
vars: [
@4-5 SpaceBefore(
Identifier {
ident: "p",
},
[
LineComment(
"",
),
],
),
],
},
ann: @6-7 BoundVariable(
"e",
),
},
],
value_defs: [],
},
@8-9 SpaceBefore(
Tag(
"A",
),
[
Newline,
],
),
)

View file

@ -0,0 +1,44 @@
Defs(
Defs {
tags: [
EitherIndex(0),
],
regions: [
@0-7,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [
Alias {
header: TypeHeader {
name: @0-1 "K",
vars: [],
},
ann: @5-6 SpaceBefore(
BoundVariable(
"s",
),
[
LineComment(
"",
),
],
),
},
],
value_defs: [],
},
@8-9 SpaceBefore(
Tag(
"K",
),
[
Newline,
],
),
)

View file

@ -0,0 +1,3 @@
K:(#
s)
K

View file

@ -0,0 +1,59 @@
SpaceAfter(
Defs(
Defs {
tags: [
EitherIndex(0),
],
regions: [
@0-9,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [
Alias {
header: TypeHeader {
name: @0-1 "O",
vars: [],
},
ann: @2-9 Apply(
"",
"O",
[
@4-5 SpaceAfter(
BoundVariable(
"z",
),
[
Newline,
LineComment(
"",
),
],
),
],
),
},
],
value_defs: [],
},
@10-11 SpaceBefore(
Var {
module_name: "",
ident: "b",
},
[
Newline,
],
),
),
[
LineComment(
"",
),
],
)

View file

@ -0,0 +1,47 @@
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-1,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Stmt(
@0-1 Var {
module_name: "",
ident: "p",
},
),
],
},
@2-8 SpaceBefore(
UnaryOp(
@4-7 SpaceBefore(
TrySuffix {
target: Task,
expr: AccessorFunction(
RecordField(
"p!",
),
),
},
[
Newline,
],
),
@2-3 Not,
),
[
Newline,
],
),
)

View file

@ -0,0 +1,3 @@
p
!
.p!!

View file

@ -0,0 +1,58 @@
SpaceAfter(
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-11,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Annotation(
@0-1 Identifier {
ident: "r",
},
@4-5 SpaceBefore(
SpaceAfter(
BoundVariable(
"r",
),
[
Newline,
LineComment(
"",
),
LineComment(
"",
),
],
),
[
Newline,
],
),
),
],
},
@12-13 SpaceBefore(
Var {
module_name: "",
ident: "h",
},
[
Newline,
],
),
),
[
Newline,
],
)

View file

@ -0,0 +1,6 @@
r:(
r
#
#
)
h

View file

@ -0,0 +1,51 @@
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-9,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Annotation(
@0-7 RecordDestructure(
[
@1-6 SpaceAfter(
RequiredField(
"l",
@5-6 Identifier {
ident: "s",
},
),
[
LineComment(
"",
),
],
),
],
),
@8-9 BoundVariable(
"s",
),
),
],
},
@10-11 SpaceBefore(
Var {
module_name: "",
ident: "o",
},
[
Newline,
],
),
)

View file

@ -0,0 +1,58 @@
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-9,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Annotation(
@0-1 Identifier {
ident: "k",
},
@3-9 SpaceBefore(
TagUnion {
ext: Some(
@8-9 BoundVariable(
"m",
),
),
tags: Collection {
items: [
@4-5 Apply {
name: @4-5 "T",
args: [],
},
],
final_comments: [
Newline,
],
},
},
[
Newline,
],
),
),
],
},
@11-12 SpaceBefore(
Tag(
"D",
),
[
LineComment(
"",
),
],
),
)

View file

@ -0,0 +1,5 @@
1 : (
f,
(ww -> p),
)e
Mh

View file

@ -0,0 +1,61 @@
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-13,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Annotation(
@0-1 NumLiteral(
"1",
),
@2-13 Tuple {
elems: [
@3-4 SpaceAfter(
BoundVariable(
"f",
),
[
Newline,
],
),
@6-11 Function(
[
@6-8 BoundVariable(
"ww",
),
],
Pure,
@10-11 BoundVariable(
"p",
),
),
],
ext: Some(
@12-13 BoundVariable(
"e",
),
),
},
),
],
},
@14-16 SpaceBefore(
Tag(
"Mh",
),
[
Newline,
],
),
)

View file

@ -0,0 +1,3 @@
1:(f
,ww->p)e
Mh

View file

@ -0,0 +1,50 @@
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-8,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
AnnotatedBody {
ann_pattern: Apply(
@0-1 Tag(
"E",
),
[],
),
ann_type: @2-3 Apply(
"",
"B",
[],
),
lines_between: [
Newline,
],
body_pattern: @4-6 RecordDestructure(
[],
),
body_expr: @7-8 Tag(
"B",
),
},
],
},
@9-10 SpaceBefore(
Tag(
"B",
),
[
Newline,
],
),
)

View file

@ -0,0 +1,51 @@
Defs(
Defs {
tags: [
EitherIndex(0),
],
regions: [
@0-7,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [
Alias {
header: TypeHeader {
name: @0-1 "A",
vars: [
@3-4 SpaceBefore(
Identifier {
ident: "p",
},
[
Newline,
],
),
],
},
ann: @6-7 SpaceBefore(
BoundVariable(
"e",
),
[
Newline,
],
),
},
],
value_defs: [],
},
@8-9 SpaceBefore(
Tag(
"A",
),
[
Newline,
],
),
)

View file

@ -0,0 +1,55 @@
Defs(
Defs {
tags: [
EitherIndex(0),
],
regions: [
@0-9,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [
Alias {
header: TypeHeader {
name: @0-1 "A",
vars: [
@3-4 SpaceAfter(
SpaceBefore(
Identifier {
ident: "e",
},
[
Newline,
],
),
[
LineComment(
"g",
),
],
),
],
},
ann: @8-9 Apply(
"",
"A",
[],
),
},
],
value_defs: [],
},
@10-12 SpaceBefore(
Tag(
"AA",
),
[
Newline,
],
),
)

View file

@ -0,0 +1,56 @@
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-10,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Annotation(
@0-1 NumLiteral(
"3",
),
@2-10 Function(
[
@2-7 Tuple {
elems: Collection {
items: [],
final_comments: [
LineComment(
"",
),
],
},
ext: Some(
@6-7 BoundVariable(
"n",
),
),
},
],
Pure,
@9-10 BoundVariable(
"n",
),
),
),
],
},
@12-13 SpaceBefore(
Num(
"0",
),
[
Newline,
],
),
)

View file

@ -0,0 +1,3 @@
3:(#
)n->n
0

View file

@ -0,0 +1,52 @@
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-8,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Annotation(
@0-1 Identifier {
ident: "d",
},
@2-8 Tuple {
elems: Collection {
items: [
@3-4 Apply(
"",
"J",
[],
),
],
final_comments: [
Newline,
],
},
ext: Some(
@7-8 BoundVariable(
"g",
),
),
},
),
],
},
@9-10 SpaceBefore(
Num(
"2",
),
[
Newline,
],
),
)

View file

@ -0,0 +1,51 @@
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-9,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Annotation(
@0-1 Identifier {
ident: "p",
},
@2-9 Tuple {
elems: Collection {
items: [],
final_comments: [
Newline,
],
},
ext: Some(
@7-8 SpaceBefore(
BoundVariable(
"i",
),
[
Newline,
],
),
),
},
),
],
},
@10-12 SpaceBefore(
Record(
[],
),
[
Newline,
],
),
)

View file

@ -0,0 +1,3 @@
Str.getUnsafe haystack haystackIndex
==
Str.getUnsafe needle needleIndex

View file

@ -0,0 +1,56 @@
SpaceAfter(
BinOps(
[
(
@0-36 SpaceAfter(
Apply(
@0-13 Var {
module_name: "Str",
ident: "getUnsafe",
},
[
@14-22 Var {
module_name: "",
ident: "haystack",
},
@23-36 Var {
module_name: "",
ident: "haystackIndex",
},
],
Space,
),
[
Newline,
],
),
@37-39 Equals,
),
],
@40-72 Apply(
@40-53 SpaceBefore(
Var {
module_name: "Str",
ident: "getUnsafe",
},
[
Newline,
],
),
[
@54-60 Var {
module_name: "",
ident: "needle",
},
@61-72 Var {
module_name: "",
ident: "needleIndex",
},
],
Space,
),
),
[
Newline,
],
)

View file

@ -0,0 +1,3 @@
Str.getUnsafe haystack haystackIndex
==
Str.getUnsafe needle needleIndex

View file

@ -0,0 +1,51 @@
SpaceAfter(
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-3,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Stmt(
@0-3 BinOps(
[
(
@0-1 Var {
module_name: "",
ident: "i",
},
@1-2 LessThan,
),
],
@2-3 Num(
"2",
),
),
),
],
},
@4-8 SpaceBefore(
ParensAround(
Num(
"-6",
),
),
[
Newline,
],
),
),
[
Newline,
],
)

View file

@ -0,0 +1,2 @@
i<2
(-6)

View file

@ -0,0 +1,2 @@
foo
|> Dict.keepIf \(k, _v) -> List.contains keysToDelete k |> Bool.not

View file

@ -0,0 +1,72 @@
SpaceAfter(
BinOps(
[
(
@0-3 SpaceAfter(
Var {
module_name: "",
ident: "foo",
},
[
Newline,
],
),
@4-6 Pizza,
),
],
@7-71 Apply(
@7-18 Var {
module_name: "Dict",
ident: "keepIf",
},
[
@19-71 Closure(
[
@20-27 Tuple(
[
@21-22 Identifier {
ident: "k",
},
@24-26 Underscore(
"v",
),
],
),
],
@31-71 BinOps(
[
(
@31-59 Apply(
@31-44 Var {
module_name: "List",
ident: "contains",
},
[
@45-57 Var {
module_name: "",
ident: "keysToDelete",
},
@58-59 Var {
module_name: "",
ident: "k",
},
],
Space,
),
@60-62 Pizza,
),
],
@63-71 Var {
module_name: "Bool",
ident: "not",
},
),
),
],
Space,
),
),
[
Newline,
],
)

View file

@ -0,0 +1,2 @@
foo
|> Dict.keepIf \(k, _v) -> List.contains keysToDelete k |> Bool.not

View file

@ -0,0 +1,5 @@
a :
N {
h,
}
g

View file

@ -0,0 +1,58 @@
SpaceAfter(
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-8,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Annotation(
@0-1 Identifier {
ident: "a",
},
@2-8 Apply(
"",
"N",
[
@3-8 Record {
fields: Collection {
items: [
@4-5 LabelOnly(
@4-5 "h",
),
],
final_comments: [
Newline,
],
},
ext: None,
},
],
),
),
],
},
@9-10 SpaceBefore(
Var {
module_name: "",
ident: "g",
},
[
Newline,
],
),
),
[
Newline,
],
)

View file

@ -0,0 +1,3 @@
a:N{h,
}
g

View file

@ -0,0 +1,4 @@
ex <- f #
s # q
<- f #
s

View file

@ -0,0 +1,52 @@
SpaceBefore(
Backpassing(
[
@1-3 Identifier {
ident: "ex",
},
],
@5-6 Var {
module_name: "",
ident: "f",
},
@8-18 SpaceBefore(
Backpassing(
[
@8-9 SpaceAfter(
Identifier {
ident: "s",
},
[
LineComment(
"q",
),
],
),
],
@14-15 Var {
module_name: "",
ident: "f",
},
@17-18 SpaceBefore(
Var {
module_name: "",
ident: "s",
},
[
LineComment(
"",
),
],
),
),
[
LineComment(
"",
),
],
),
),
[
Newline,
],
)

View file

@ -0,0 +1,5 @@
ex<-f#
s#q
<-f#
s

View file

@ -0,0 +1,56 @@
Defs(
Defs {
tags: [
EitherIndex(2147483648),
EitherIndex(2147483649),
],
regions: [
@0-1,
@2-6,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 1, length: 0 },
],
spaces: [
Newline,
],
type_defs: [],
value_defs: [
Stmt(
@0-1 Num(
"0",
),
),
Stmt(
@2-6 UnaryOp(
@4-6 SpaceBefore(
AccessorFunction(
RecordField(
"d",
),
),
[
Newline,
],
),
@2-3 Not,
),
),
],
},
@7-9 SpaceBefore(
AccessorFunction(
RecordField(
"d",
),
),
[
Newline,
],
),
)

View file

@ -0,0 +1,40 @@
SpaceAfter(
BinOps(
[
(
@0-1 Tag(
"N",
),
@1-2 LessThan,
),
],
@2-7 Apply(
@2-3 Var {
module_name: "",
ident: "l",
},
[
@4-7 ParensAround(
BinOps(
[
(
@4-5 Var {
module_name: "",
ident: "r",
},
@5-6 Star,
),
],
@6-7 Tag(
"N",
),
),
),
],
Space,
),
),
[
Newline,
],
)

View file

@ -0,0 +1,8 @@
5
- (
(
e =
r
1
)
)

View file

@ -0,0 +1,60 @@
BinOps(
[
(
@0-1 Num(
"5",
),
@1-2 Minus,
),
],
@3-15 ParensAround(
ParensAround(
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@4-12,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@4-5 Identifier {
ident: "e",
},
@6-12 ParensAround(
ParensAround(
SpaceBefore(
Var {
module_name: "",
ident: "r",
},
[
Newline,
],
),
),
),
),
],
},
@13-14 SpaceBefore(
Num(
"1",
),
[
Newline,
],
),
),
),
),
)

View file

@ -0,0 +1,3 @@
5-((e=((
r))
1))

View file

@ -0,0 +1,51 @@
SpaceAfter(
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-12,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Annotation(
@0-10 StrLiteral(
Line(
[
Interpolated(
@5-6 Var {
module_name: "",
ident: "g",
},
),
],
),
),
@11-12 BoundVariable(
"q",
),
),
],
},
@13-14 SpaceBefore(
Var {
module_name: "",
ident: "f",
},
[
Newline,
],
),
),
[
Newline,
],
)

View file

@ -0,0 +1,2 @@
"""$(g)""":q
f

View file

@ -0,0 +1,48 @@
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-12,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-1 Identifier {
ident: "t",
},
@2-12 Apply(
@2-10 Str(
PlainLine(
"\" ",
),
),
[
@10-12 Str(
PlainLine(
"",
),
),
],
Space,
),
),
],
},
@13-14 SpaceBefore(
Tag(
"S",
),
[
Newline,
],
),
)

View file

@ -0,0 +1,44 @@
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-6,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-1 Identifier {
ident: "a",
},
@2-6 ParensAround(
SpaceBefore(
Num(
"6",
),
[
Newline,
],
),
),
),
],
},
@7-8 SpaceBefore(
Var {
module_name: "",
ident: "a",
},
[
Newline,
],
),
)

View file

@ -0,0 +1,48 @@
BinOps(
[
(
@0-9 SpaceAfter(
Apply(
@0-2 Var {
module_name: "",
ident: "m0",
},
[
@2-9 Closure(
[
@3-4 Identifier {
ident: "w",
},
],
@6-9 Apply(
@6-7 TrySuffix {
target: Result,
expr: Var {
module_name: "",
ident: "w",
},
},
[
@8-9 Var {
module_name: "",
ident: "e",
},
],
Space,
),
),
],
Space,
),
[
Newline,
],
),
@10-11 Slash,
),
],
@11-12 Var {
module_name: "",
ident: "s",
},
)

Some files were not shown because too many files have changed in this diff Show more