Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix yet more fuzzing bugs #7510

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
31 changes: 24 additions & 7 deletions crates/compiler/fmt/src/annotation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1074,7 +1074,6 @@ impl<'a> Nodify<'a> for TypeAnnotation<'a> {
before: first_node.before,
node: Node::CommaSequence {
allow_blank_lines: false,
allow_newlines: true,
indent_rest: false,
first: arena.alloc(first_node.node),
rest: rest_nodes.into_bump_slice(),
Expand Down Expand Up @@ -1181,11 +1180,12 @@ impl<'a> Nodify<'a> for TypeAnnotation<'a> {
.to_node(arena, flags)
.add_parens(arena, Parens::NotNeeded);

let mut needs_indent = annot.needs_indent || !annot.after.is_empty();
let before = filter_newlines(arena, annot.after);
let mut needs_indent = annot.needs_indent || !before.is_empty();

items.push(Item {
comma_before: false,
before: annot.after,
before,
newline: false,
space: true,
node: Node::Literal(roc_parse::keyword::WHERE),
Expand All @@ -1195,7 +1195,8 @@ impl<'a> Nodify<'a> for TypeAnnotation<'a> {

for (i, clause) in implements_clauses.iter().enumerate() {
let node = clause.value.to_node(arena, flags);
let before = merge_spaces_conservative(arena, last_after, node.before);
let before =
filter_newlines(arena, merge_spaces(arena, last_after, node.before));
last_after = node.after;
items.push(Item {
before,
Expand All @@ -1214,8 +1215,7 @@ impl<'a> Nodify<'a> for TypeAnnotation<'a> {
first: arena.alloc(annot.node),
rest: arena.alloc_slice_copy(&items),
allow_blank_lines: false,
allow_newlines: false,
indent_rest: false,
indent_rest: true,
},
after: last_after,
needs_indent,
Expand All @@ -1226,6 +1226,24 @@ impl<'a> Nodify<'a> for TypeAnnotation<'a> {
}
}

fn filter_newlines<'a, 'b: 'a>(
arena: &'a Bump,
items: &'b [CommentOrNewline<'b>],
) -> &'a [CommentOrNewline<'a>] {
let count = items.iter().filter(|i| i.is_newline()).count();
if count > 0 {
let mut new_items = Vec::with_capacity_in(items.len() - count, arena);
for item in items {
if !item.is_newline() {
new_items.push(*item);
}
}
arena.alloc_slice_copy(&new_items)
} else {
items
}
}

impl<'a> Nodify<'a> for &'a str {
fn to_node<'b>(&'a self, arena: &'b Bump, flags: MigrationFlags) -> NodeInfo<'b>
where
Expand Down Expand Up @@ -1319,7 +1337,6 @@ impl<'a> Nodify<'a> for ImplementsClause<'a> {
first: arena.alloc(var.node),
rest: arena.alloc_slice_copy(&items),
allow_blank_lines: false,
allow_newlines: true,
indent_rest: false,
},
after: last_after,
Expand Down
141 changes: 73 additions & 68 deletions crates/compiler/fmt/src/def.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::annotation::{
ann_lift_spaces, ann_lift_spaces_after, is_collection_multiline, ty_is_outdentable,
Formattable, Newlines, Parens,
ann_lift_spaces_after, is_collection_multiline, Formattable, Newlines, Parens,
};
use crate::collection::{fmt_collection, Braces};
use crate::expr::{
Expand All @@ -19,8 +18,8 @@ use roc_error_macros::internal_error;
use roc_parse::ast::{
AbilityMember, Defs, Expr, ExtractSpaces, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport,
ModuleImportParams, Pattern, Spaces, SpacesBefore, StrLiteral, TypeAnnotation, TypeDef,
TypeHeader, ValueDef,
ModuleImportParams, Pattern, Spaceable, Spaces, SpacesBefore, StrLiteral, TypeAnnotation,
TypeDef, TypeHeader, ValueDef,
};
use roc_parse::expr::merge_spaces;
use roc_parse::header::Keyword;
Expand Down Expand Up @@ -183,16 +182,29 @@ pub fn valdef_lift_spaces<'a, 'b: 'a>(
}
}
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);
let pat_lifted = pattern_lift_spaces(arena, &pat.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,
// Don't format the `{} =` for defs with this pattern
if is_body_unit_assignment(&pat_lifted, &expr.extract_spaces()) {
let lifted = expr_lift_spaces(Parens::NotNeeded, arena, &expr.value);
Spaces {
before: lifted.before,
item: ValueDef::Stmt(arena.alloc(Loc::at_zero(lifted.item))),
after: lifted.after,
}
} else {
let lifted = expr_lift_spaces(Parens::NotNeeded, arena, &expr.value);
Spaces {
before: pat_lifted.before,
item: ValueDef::Body(
arena.alloc(Loc::at(
pat.region,
pat_lifted.item.maybe_after(arena, pat_lifted.after),
)),
expr,
),
after: lifted.after,
}
}
}
ValueDef::AnnotatedBody {
Expand Down Expand Up @@ -313,11 +325,26 @@ pub fn valdef_lift_spaces_before<'a, 'b: 'a>(
}
}
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),
let pat_lifted = pattern_lift_spaces(arena, &pat.value);

// Don't format the `{} =` for defs with this pattern
if is_body_unit_assignment(&pat_lifted, &expr.extract_spaces()) {
let lifted = expr_lift_spaces_before(Parens::NotNeeded, arena, &expr.value);
SpacesBefore {
before: lifted.before,
item: ValueDef::Stmt(arena.alloc(Loc::at_zero(lifted.item))),
}
} else {
SpacesBefore {
before: pat_lifted.before,
item: ValueDef::Body(
arena.alloc(Loc::at(
pat.region,
pat_lifted.item.maybe_after(arena, pat_lifted.after),
)),
expr,
),
}
}
}
ValueDef::AnnotatedBody {
Expand Down Expand Up @@ -397,6 +424,20 @@ pub fn valdef_lift_spaces_before<'a, 'b: 'a>(
}
}

fn is_body_unit_assignment(pat: &Spaces<'_, Pattern<'_>>, body: &Spaces<'_, Expr<'_>>) -> bool {
if let Pattern::RecordDestructure(collection) = pat.item {
collection.is_empty()
&& pat.before.iter().all(|s| s.is_newline())
&& pat.after.iter().all(|s| s.is_newline())
&& !matches!(body.item, Expr::Defs(..))
&& !matches!(body.item, Expr::Return(..))
&& !matches!(body.item, Expr::DbgStmt { .. })
&& !starts_with_expect_ident(&body.item)
} else {
false
}
}

impl<'a> Formattable for TypeDef<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::TypeDef::*;
Expand All @@ -413,22 +454,15 @@ impl<'a> Formattable for TypeDef<'a> {

match self {
Alias { header, ann } => {
header.format(buf, indent);

buf.indent(indent);
buf.push_str(" :");
buf.spaces(1);

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);
fmt_general_def(
header,
Parens::NotNeeded,
buf,
indent,
":",
&ann.value,
newlines,
);
}
Opaque {
header,
Expand Down Expand Up @@ -849,7 +883,7 @@ impl<'a> Formattable for ValueDef<'a> {
);
}
Body(loc_pattern, loc_expr) => {
fmt_body(buf, true, &loc_pattern.value, &loc_expr.value, indent);
fmt_body(buf, &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),
Expand Down Expand Up @@ -878,7 +912,7 @@ impl<'a> Formattable for ValueDef<'a> {
fmt_annotated_body_comment(buf, indent, lines_between);

buf.newline();
fmt_body(buf, false, &body_pattern.value, &body_expr.value, indent);
fmt_body(buf, &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),
Expand Down Expand Up @@ -1009,34 +1043,7 @@ pub fn fmt_annotated_body_comment<'a>(
}
}

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_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())
&& !matches!(body.extract_spaces().item, Expr::Defs(..))
&& !matches!(body.extract_spaces().item, Expr::Return(..))
&& !matches!(body.extract_spaces().item, Expr::DbgStmt { .. })
&& !starts_with_expect_ident(body)
} else {
false
};

// Don't format the `{} =` for defs with this pattern
if is_unit_assignment {
return body.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}

pub fn fmt_body<'a>(buf: &mut Buf, pattern: &'a Pattern<'a>, body: &'a Expr<'a>, indent: u16) {
pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);

if pattern.is_multiline() {
Expand All @@ -1061,10 +1068,7 @@ pub fn fmt_body<'a>(
_ => false,
};

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 {
if should_outdent {
buf.spaces(1);
sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
} else {
Expand Down Expand Up @@ -1168,6 +1172,7 @@ pub fn starts_with_block_string_literal(expr: &Expr<'_>) -> bool {
}
Expr::Apply(inner, _, _) => starts_with_block_string_literal(&inner.value),
Expr::RecordAccess(inner, _) => starts_with_block_string_literal(inner),
Expr::TupleAccess(inner, _) => starts_with_block_string_literal(inner),
Expr::PncApply(inner, _) => starts_with_block_string_literal(&inner.value),
Expr::TrySuffix(inner) => starts_with_block_string_literal(inner),
_ => false,
Expand Down
12 changes: 2 additions & 10 deletions crates/compiler/fmt/src/expr.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::annotation::{except_last, is_collection_multiline, Formattable, Newlines, Parens};
use crate::collection::{fmt_collection, Braces};
use crate::def::{fmt_defs, valdef_lift_spaces_before};
use crate::def::{fmt_defs, starts_with_block_string_literal, valdef_lift_spaces_before};
use crate::pattern::{
fmt_pattern, pattern_lift_spaces, snakify_camel_ident, starts_with_inline_comment,
};
Expand Down Expand Up @@ -263,7 +263,7 @@ fn format_expr_only(
let before_all_newlines = lifted.before.iter().all(|s| s.is_newline());

let needs_newline =
!before_all_newlines || term_starts_with_multiline_str(&lifted.item);
!before_all_newlines || starts_with_block_string_literal(&lifted.item);

let needs_parens = (needs_newline
&& matches!(unary_op.value, called_via::UnaryOp::Negate))
Expand Down Expand Up @@ -366,14 +366,6 @@ fn format_expr_only(
}
}

fn term_starts_with_multiline_str(expr: &Expr<'_>) -> bool {
match expr {
Expr::Str(text) => is_str_multiline(text),
Expr::PncApply(inner, _) => term_starts_with_multiline_str(&inner.value),
_ => false,
}
}

fn prepare_expr_field_collection<'a>(
arena: &'a Bump,
items: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
Expand Down
20 changes: 5 additions & 15 deletions crates/compiler/fmt/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
annotation::{Formattable, Newlines, Parens},
collection::Braces,
expr::merge_spaces_conservative,
spaces::{fmt_comments_only, fmt_spaces, fmt_spaces_no_blank_lines, NewlineAt, INDENT},
spaces::{fmt_spaces, fmt_spaces_no_blank_lines, INDENT},
Buf, MigrationFlags,
};

Expand Down Expand Up @@ -87,7 +87,6 @@ pub enum Node<'a> {
},
CommaSequence {
allow_blank_lines: bool,
allow_newlines: bool,
indent_rest: bool,
first: &'a Node<'a>,
rest: &'a [Item<'a>],
Expand Down Expand Up @@ -123,12 +122,8 @@ pub struct Item<'a> {
}

impl<'a> Item<'a> {
fn is_multiline(&self, allow_newlines: bool) -> bool {
let has_newlines = if allow_newlines {
!self.before.is_empty()
} else {
self.before.iter().any(|c| c.is_comment())
};
fn is_multiline(&self) -> bool {
let has_newlines = !self.before.is_empty();
self.newline || has_newlines || self.node.is_multiline()
}
}
Expand Down Expand Up @@ -272,7 +267,6 @@ impl<'b> NodeInfo<'b> {
},
node: Node::CommaSequence {
allow_blank_lines: false,
allow_newlines: true,
indent_rest,
first: arena.alloc(first.node),
rest: rest.into_bump_slice(),
Expand Down Expand Up @@ -342,11 +336,10 @@ impl<'a> Formattable for Node<'a> {
} => after.is_multiline() || items.iter().any(|item| item.is_multiline()),
Node::CommaSequence {
allow_blank_lines: _,
allow_newlines,
indent_rest: _,
first,
rest,
} => first.is_multiline() || rest.iter().any(|item| item.is_multiline(*allow_newlines)),
} => first.is_multiline() || rest.iter().any(|item| item.is_multiline()),
Node::Literal(_) => false,
Node::TypeAnnotation(type_annotation) => type_annotation.is_multiline(),
Node::Pattern(pat) => pat.is_multiline(),
Expand Down Expand Up @@ -409,7 +402,6 @@ impl<'a> Formattable for Node<'a> {
}
Node::CommaSequence {
allow_blank_lines,
allow_newlines,
indent_rest,
first,
rest,
Expand All @@ -428,10 +420,8 @@ impl<'a> Formattable for Node<'a> {
}
if *allow_blank_lines {
fmt_spaces(buf, item.before.iter(), indent);
} else if *allow_newlines {
fmt_spaces_no_blank_lines(buf, item.before.iter(), inner_indent);
} else {
fmt_comments_only(buf, item.before.iter(), NewlineAt::Bottom, inner_indent);
fmt_spaces_no_blank_lines(buf, item.before.iter(), inner_indent);
}
if item.newline {
buf.ensure_ends_with_newline();
Expand Down
Loading
Loading