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

Merged
merged 12 commits into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 24 additions & 7 deletions crates/compiler/fmt/src/annotation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1206,7 +1206,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 @@ -1313,11 +1312,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 @@ -1327,7 +1327,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 @@ -1346,8 +1347,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 @@ -1358,6 +1358,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 @@ -1451,7 +1469,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
155 changes: 84 additions & 71 deletions crates/compiler/fmt/src/def.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
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::{
expr_lift_and_lower, expr_lift_spaces, expr_lift_spaces_after, expr_lift_spaces_before,
fmt_str_literal, is_str_multiline, merge_spaces_conservative, sub_expr_requests_parens,
expr_lift_spaces, expr_lift_spaces_after, expr_lift_spaces_before, fmt_str_literal,
is_str_multiline, merge_spaces_conservative, sub_expr_requests_parens,
};
use crate::node::Nodify;
use crate::pattern::pattern_lift_spaces_before;
use crate::pattern::{pattern_lift_spaces, pattern_lift_spaces_before};
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::Spaceable;
use roc_parse::ast::{
AbilityMember, Defs, Expr, ExtractSpaces, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport,
Expand Down Expand Up @@ -182,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 @@ -312,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 @@ -396,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 @@ -412,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 @@ -797,7 +832,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 @@ -826,7 +861,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 @@ -957,34 +992,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 @@ -996,7 +1004,12 @@ pub fn fmt_body<'a>(
let indent = buf.cur_line_indent();
buf.push_str("=");

let body = expr_lift_and_lower(Parens::NotNeeded, buf.text.bump(), body);
let body_lifted = expr_lift_spaces(Parens::NotNeeded, buf.text.bump(), body);

let after = body_lifted.after;
let body = body_lifted
.item
.maybe_before(buf.text.bump(), body_lifted.before);

if body.is_multiline() {
match body {
Expand All @@ -1009,10 +1022,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 @@ -1057,7 +1067,7 @@ pub fn fmt_body<'a>(
buf.ensure_ends_with_newline();
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
}
Expr::Defs(..) | Expr::BinOps(_, _) => {
Expr::Defs(..) | Expr::BinOps(..) => {
// Binop chains always get a newline. Otherwise you can have things like:
//
// something = foo
Expand Down Expand Up @@ -1092,6 +1102,8 @@ pub fn fmt_body<'a>(
buf.spaces(1);
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
}

fmt_spaces(buf, after.iter(), indent);
}

fn starts_with_expect_ident(expr: &Expr<'_>) -> bool {
Expand All @@ -1116,6 +1128,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
23 changes: 4 additions & 19 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::node::Prec;
use crate::pattern::{
fmt_pattern, pattern_lift_spaces, snakify_camel_ident, starts_with_inline_comment,
Expand Down Expand Up @@ -264,7 +264,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 @@ -367,14 +367,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 Expand Up @@ -1026,14 +1018,6 @@ pub fn fmt_str_literal(buf: &mut Buf, literal: StrLiteral, indent: u16) {
}
}

pub fn expr_lift_and_lower<'a, 'b: 'a>(
_parens: Parens,
arena: &'a Bump,
expr: &Expr<'b>,
) -> Expr<'a> {
lower(arena, expr_lift_spaces(Parens::NotNeeded, arena, expr))
}

pub fn expr_lift_spaces<'a, 'b: 'a>(
parens: Parens,
arena: &'a Bump,
Expand Down Expand Up @@ -1496,7 +1480,6 @@ fn fmt_binops<'a>(
buf: &mut Buf,
lefts: &'a [(Loc<Expr<'a>>, Loc<BinOp>)],
loc_right_side: &'a Loc<Expr<'a>>,

indent: u16,
) {
let is_multiline = loc_right_side.value.is_multiline()
Expand Down Expand Up @@ -1825,6 +1808,8 @@ fn guard_needs_parens(value: &Expr<'_>) -> bool {
Expr::ParensAround(expr) | Expr::SpaceBefore(expr, _) | Expr::SpaceAfter(expr, _) => {
guard_needs_parens(expr)
}
Expr::BinOps(_lefts, right) => guard_needs_parens(&right.value),
Expr::UnaryOp(inner, _) => guard_needs_parens(&inner.value),
Expr::Closure(_, body) => guard_needs_parens(&body.value),
Expr::Defs(_, final_expr) => guard_needs_parens(&final_expr.value),
_ => false,
Expand Down
Loading
Loading