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

[WIP] Add new formatter that can do better pretty-printing #7011

Closed
wants to merge 17 commits into from
2,129 changes: 2,129 additions & 0 deletions crates/compiler/fmt/src/doc.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion crates/compiler/fmt/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ pub fn is_str_multiline(literal: &StrLiteral) -> bool {
}
}

fn needs_unicode_escape(ch: char) -> bool {
pub fn needs_unicode_escape(ch: char) -> bool {
matches!(ch, '\u{0000}'..='\u{001f}' | '\u{007f}'..='\u{009f}')
}

Expand Down
21 changes: 19 additions & 2 deletions crates/compiler/fmt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
pub mod annotation;
pub mod collection;
pub mod def;
pub mod doc;
pub mod expr;
pub mod header;
pub mod pattern;
Expand Down Expand Up @@ -79,8 +80,16 @@ impl<'a> Buf<'a> {
"push_str: `{s}` with text:\n{}",
self.text
);
debug_assert!(!s.contains('\n'));
debug_assert!(!s.ends_with(' '));
debug_assert!(
!s.contains('\n'),
"Don't call buf.push_str with a newline: `{}`",
s
);
debug_assert!(
!s.ends_with(' '),
"Don't call buf.push_str with a space at the end: `{}`",
s
);

if !s.is_empty() {
self.flush_spaces();
Expand Down Expand Up @@ -113,6 +122,14 @@ impl<'a> Buf<'a> {
self.beginning_of_line = true;
}

/// Ensures the current buffer ends in a space, if it didn't already.
/// Doesn't add a space if the buffer already ends in one.
pub fn ensure_ends_with_space(&mut self) {
if !self.text.is_empty() && self.spaces_to_flush == 0 {
self.spaces(1)
}
}

/// Ensures the current buffer ends in a newline, if it didn't already.
/// Doesn't add a newline if the buffer already ends in one.
pub fn ensure_ends_with_newline(&mut self) {
Expand Down
13 changes: 13 additions & 0 deletions crates/compiler/test_syntax/src/minimize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,19 @@ fn round_trip_once(input: Input<'_>) -> Option<String> {
return Some("Different ast".to_string());
}

let output2 = actual.format2();

let reparsed_ast2 = match output2.as_ref().parse_in(&arena) {
Ok(r) => r,
Err(e) => return Some(format!("Reparse2 failed: {:?}", e.normalize(&arena))),
};

let reparsed_ast2_normalized = reparsed_ast2.normalize(&arena);

if format!("{ast_normalized:?}") != format!("{reparsed_ast2_normalized:?}") {
return Some("Different ast2".to_string());
}

None
}

Expand Down
64 changes: 61 additions & 3 deletions crates/compiler/test_syntax/src/test_helpers.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use bumpalo::Bump;
use roc_fmt::{annotation::Formattable, header::fmt_header};
use roc_fmt::{
annotation::Formattable,
doc::{doc_fmt_expr, doc_fmt_module},
header::fmt_header,
};
use roc_parse::{
ast::{Defs, Expr, FullAst, Header, Malformed, SpacesBefore},
header::parse_module_defs,
Expand Down Expand Up @@ -115,6 +119,19 @@ impl<'a> Output<'a> {
Output::Full { .. } => format!("{self:#?}\n"),
}
}

pub fn format2(&self) -> InputOwned {
let arena = Bump::new();
let buf = Buf::new_in(&arena);
match self {
Output::Header(header) => InputOwned::Header(doc_fmt_module(Some(header), None)),
Output::ModuleDefs(defs) => InputOwned::ModuleDefs(doc_fmt_module(None, Some(defs))),
Output::Expr(expr) => InputOwned::Expr(doc_fmt_expr(expr)),
Output::Full(full) => {
InputOwned::Full(doc_fmt_module(Some(&full.header), Some(&full.defs)))
}
}
}
}

impl<'a> Malformed for Output<'a> {
Expand Down Expand Up @@ -203,7 +220,7 @@ impl<'a> Input<'a> {
panic!("Unexpected parse failure when parsing this for formatting:\n\n{}\n\nParse error was:\n\n{:?}\n\n", self.as_str(), err);
});

let output = actual.format();
let output = actual.format2();

handle_formatted_output(output.as_ref());

Expand Down Expand Up @@ -245,7 +262,7 @@ impl<'a> Input<'a> {

// Now verify that the resultant formatting is _idempotent_ - i.e. that it doesn't change again if re-formatted
if check_idempotency {
let reformatted = reparsed_ast.format();
let reformatted = reparsed_ast.format2();

if output != reformatted {
eprintln!("Formatting bug; formatting is not stable.\nOriginal code:\n{}\n\nFormatted code:\n{}\n\nAST:\n{:#?}\n\nReparsed AST:\n{:#?}\n\n",
Expand All @@ -259,4 +276,45 @@ impl<'a> Input<'a> {
}
}
}

pub fn check_invariants2(&self) {
let arena = Bump::new();

let actual = self.parse_in(&arena).unwrap_or_else(|err| {
panic!("Unexpected parse failure when parsing this for formatting:\n\n{}\n\nParse error was:\n\n{:?}\n\n", self.as_str(), err);
});

let output = actual.format2();

let reparsed_ast = output.as_ref().parse_in(&arena).unwrap_or_else(|err| {
panic!(
"After formatting, the source code no longer parsed!\n\n\
Parse error was: {:?}\n\n\
The original code was:\n\n{}\n\n\
The code that failed to parse:\n\n{}\n\n\
The original ast was:\n\n{:#?}\n\n",
err,
self.as_str(),
output.as_ref().as_str(),
actual
);
});

let ast_normalized = actual.normalize(&arena);
let reparsed_ast_normalized = reparsed_ast.normalize(&arena);

if format!("{ast_normalized:?}") != format!("{reparsed_ast_normalized:?}") {
panic!(
"Formatting bug; formatting didn't reparse to the same AST (after removing spaces)\n\n\
* * * Source code before formatting:\n{}\n\n\
* * * Source code after formatting:\n{}\n\n\
* * * AST before formatting:\n{:#?}\n\n\
* * * AST after formatting:\n{:#?}\n\n",
self.as_str(),
output.as_ref().as_str(),
actual,
reparsed_ast_normalized
);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
when x is
when
x
is
bar.and -> 1
_ -> 4
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
when x is
when
x
is
Foo.and -> 1
_ -> 4
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
J : R

n_p
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module []
e = \r ->
when
t
is
E ->
when
g
is
P ->
if y then
e
else
#.t=
s
124 changes: 124 additions & 0 deletions crates/compiler/test_syntax/tests/snapshots/pass/abcd.full.result-ast
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
Full(
FullAst {
header: SpacesBefore {
before: [],
item: Module(
ModuleHeader {
after_keyword: [],
params: None,
exposes: [],
interface_imports: None,
},
),
},
defs: Defs {
tags: [
Index(2147483648),
],
regions: [
@9-66,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
type_defs: [],
value_defs: [
Body(
@9-10 Identifier {
ident: "e",
},
@11-66 Closure(
[
@12-13 Identifier {
ident: "r",
},
],
@15-66 When(
@20-21 Var {
module_name: "",
ident: "t",
},
[
WhenBranch {
patterns: [
@25-26 SpaceBefore(
Tag(
"E",
),
[
Newline,
],
),
],
value: @28-66 When(
@33-34 Var {
module_name: "",
ident: "g",
},
[
WhenBranch {
patterns: [
@38-39 SpaceBefore(
Tag(
"P",
),
[
Newline,
],
),
],
value: @41-66 If(
[
(
@44-45 Var {
module_name: "",
ident: "y",
},
@51-52 SpaceBefore(
SpaceAfter(
Var {
module_name: "",
ident: "e",
},
[
Newline,
],
),
[
Newline,
],
),
),
],
@65-66 SpaceBefore(
Var {
module_name: "",
ident: "s",
},
[
LineComment(
".t=",
),
],
),
),
guard: None,
},
],
),
guard: None,
},
],
),
),
),
],
},
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module []e=\r->when t is
E->when g is
P->if y then
e
else#.t=
s
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
Hash implements
hash : a
-> U64

Hash implements hash: a -> U64
1
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
Hash implements
hash : a -> U64
hash2 : a -> U64

hash: a -> U64
hash2: a -> U64
1
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Hash implements hash : a -> U64 where a implements Hash

Hash implements
hash: a -> U64 where a implements Hash
1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Ab1 implements ab1 : a -> {} where a implements Ab1

Ab2 implements ab2 : a -> {} where a implements Ab2

Ab1 implements
ab1: a -> { } where a implements Ab1
Ab2 implements
ab2: a -> { } where a implements Ab2
1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
m : (k -> b) -> b
Loading
Loading