Implement block / indent based parsing

... and enforce that defs can only occur in blocks (or, inside parenthesized expressions)
This commit is contained in:
Joshua Warner 2024-07-08 21:14:51 -07:00
parent d5db3137a3
commit 4f32f43048
No known key found for this signature in database
GPG Key ID: 89AD497003F93FDD
304 changed files with 12050 additions and 8876 deletions

View File

@ -6,9 +6,9 @@ use bumpalo::Bump;
use roc_error_macros::{internal_error, user_error};
use roc_fmt::def::fmt_defs;
use roc_fmt::module::fmt_module;
use roc_fmt::spaces::RemoveSpaces;
use roc_fmt::{Ast, Buf};
use roc_parse::module::parse_module_defs;
use roc_parse::remove_spaces::RemoveSpaces;
use roc_parse::{module, parser::SyntaxError, state::State};
#[derive(Copy, Clone, Debug)]

View File

@ -1361,10 +1361,6 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {
And => (ModuleName::BOOL, "and"),
Or => (ModuleName::BOOL, "or"),
Pizza => unreachable!("Cannot desugar the |> operator"),
Assignment => unreachable!("Cannot desugar the = operator"),
IsAliasType => unreachable!("Cannot desugar the : operator"),
IsOpaqueType => unreachable!("Cannot desugar the := operator"),
Backpassing => unreachable!("Cannot desugar the <- operator"),
}
}

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@ -73,22 +75,22 @@ Defs {
type_defs: [],
value_defs: [
AnnotatedBody {
ann_pattern: @31-42 Identifier {
ann_pattern: @31-43 Identifier {
ident: "#!0_stmt",
},
ann_type: @31-42 Apply(
ann_type: @31-43 Apply(
"",
"Task",
[
@31-42 Record {
@31-43 Record {
fields: [],
ext: None,
},
@31-42 Inferred,
@31-43 Inferred,
],
),
comment: None,
body_pattern: @31-42 Identifier {
body_pattern: @31-43 Identifier {
ident: "#!0_stmt",
},
body_expr: @31-42 Apply(
@ -116,7 +118,7 @@ Defs {
),
@31-42 Closure(
[
@31-42 Underscore(
@31-43 Underscore(
"#!stmt",
),
],

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@ -76,7 +78,7 @@ Defs {
Index(2147483648),
],
regions: [
@78-91,
@82-91,
],
space_before: [
Slice(start = 0, length = 0),
@ -113,8 +115,8 @@ Defs {
body_pattern: @78-79 Identifier {
ident: "#!0_expr",
},
body_expr: @78-91 Apply(
@78-91 Var {
body_expr: @82-91 Apply(
@82-91 Var {
module_name: "",
ident: "line",
},
@ -129,7 +131,7 @@ Defs {
},
],
},
@78-91 Var {
@82-91 Var {
module_name: "",
ident: "#!0_expr",
},

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@ -38,7 +40,7 @@ Defs {
ident: "foo",
},
],
@29-49 LowLevelDbg(
@29-36 LowLevelDbg(
(
"test.roc:3",
" ",

View File

@ -13,17 +13,19 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-24 Apply(
@11-24 Var {
@11-17 Apply(
@11-17 Var {
module_name: "Task",
ident: "await",
},
@ -32,13 +34,13 @@ Defs {
module_name: "",
ident: "a",
},
@11-24 Closure(
@11-17 Closure(
[
@15-17 Identifier {
ident: "#!0_arg",
},
],
@11-24 LowLevelDbg(
@11-17 LowLevelDbg(
(
"test.roc:2",
"in",

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -17,11 +17,12 @@ Defs {
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 2, length = 0),
Slice(start = 2, length = 1),
],
spaces: [
Newline,
Newline,
Newline,
],
type_defs: [],
value_defs: [

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,33 +13,56 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-31 Expect(
@18-24 Apply(
@20-22 Var {
module_name: "Bool",
ident: "isEq",
},
[
@18-19 Num(
"1",
),
@23-24 Num(
"2",
),
@11-31 Defs(
Defs {
tags: [
Index(2147483648),
],
BinOp(
Equals,
),
),
regions: [
@11-24,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Expect {
condition: @18-24 Apply(
@20-22 Var {
module_name: "Bool",
ident: "isEq",
},
[
@18-19 Num(
"1",
),
@23-24 Num(
"2",
),
],
BinOp(
Equals,
),
),
preceding_comment: @11-11,
},
],
},
@29-31 Var {
module_name: "",
ident: "x",

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,17 +13,19 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@0-26 Apply(
@0-26 Var {
@7-26 Apply(
@7-26 Var {
module_name: "",
ident: "foo",
},

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@ -57,8 +59,8 @@ Defs {
ident: "await",
},
[
@29-41 Apply(
@29-41 Var {
@33-41 Apply(
@33-41 Var {
module_name: "",
ident: "foo",
},

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,17 +13,19 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-3 Identifier {
ident: "run",
},
@0-22 Apply(
@0-22 Var {
@6-22 Apply(
@6-22 Var {
module_name: "Task",
ident: "await",
},
@ -32,14 +34,14 @@ Defs {
module_name: "",
ident: "nextMsg",
},
@0-22 Closure(
@6-22 Closure(
[
Identifier {
ident: "#!0_arg",
},
],
@0-22 Apply(
@0-22 Var {
@6-22 Apply(
@6-22 Var {
module_name: "",
ident: "line",
},

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@ -28,7 +30,7 @@ Defs {
ident: "await",
},
[
@26-56 Defs(
@11-56 Defs(
Defs {
tags: [
Index(2147483648),
@ -46,22 +48,22 @@ Defs {
type_defs: [],
value_defs: [
AnnotatedBody {
ann_pattern: @26-56 Identifier {
ann_pattern: @11-57 Identifier {
ident: "#!0_stmt",
},
ann_type: @26-56 Apply(
ann_type: @11-57 Apply(
"",
"Task",
[
@26-56 Record {
@11-57 Record {
fields: [],
ext: None,
},
@26-56 Inferred,
@11-57 Inferred,
],
),
comment: None,
body_pattern: @26-56 Identifier {
body_pattern: @11-57 Identifier {
ident: "#!0_stmt",
},
body_expr: @11-56 Apply(
@ -106,7 +108,7 @@ Defs {
),
@11-56 Closure(
[
@26-56 Underscore(
@11-57 Underscore(
"#!stmt",
),
],

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@ -28,8 +30,8 @@ Defs {
ident: "x",
},
],
@24-30 Apply(
@24-30 Var {
@28-30 Apply(
@28-30 Var {
module_name: "Task",
ident: "await",
},
@ -40,7 +42,7 @@ Defs {
Index(2147483648),
],
regions: [
@24-30,
@28-30,
],
space_before: [
Slice(start = 0, length = 0),
@ -71,19 +73,19 @@ Defs {
body_pattern: @24-25 Identifier {
ident: "#!0_expr",
},
body_expr: @24-30 Var {
body_expr: @28-30 Var {
module_name: "",
ident: "x",
},
},
],
},
@24-30 Var {
@28-30 Var {
module_name: "",
ident: "#!0_expr",
},
),
@24-30 Closure(
@28-30 Closure(
[
@24-25 Identifier {
ident: "r",

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@ -44,27 +46,27 @@ Defs {
ident: "await",
},
[
@24-33 Var {
@28-33 Var {
module_name: "",
ident: "bar",
},
@15-19 Closure(
[
@24-33 Identifier {
@28-33 Identifier {
ident: "#!0_arg",
},
],
@24-33 Apply(
@24-33 Var {
@28-33 Apply(
@28-33 Var {
module_name: "Task",
ident: "await",
},
[
@24-33 Var {
@28-33 Var {
module_name: "",
ident: "#!0_arg",
},
@24-33 Closure(
@28-33 Closure(
[
@24-25 Identifier {
ident: "b",

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -13,9 +13,11 @@ Defs {
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
spaces: [
Newline,
],
spaces: [],
type_defs: [],
value_defs: [
Body(

View File

@ -382,7 +382,7 @@ mod test_can {
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 0);
assert_eq!(problems, Vec::new());
}
#[test]
@ -399,7 +399,7 @@ mod test_can {
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 0);
assert_eq!(problems, Vec::new());
}
#[test]

View File

@ -543,7 +543,7 @@ impl<'a> Formattable for Expr<'a> {
}
}
fn is_str_multiline(literal: &StrLiteral) -> bool {
pub fn is_str_multiline(literal: &StrLiteral) -> bool {
use roc_parse::ast::StrLiteral::*;
match literal {
@ -671,10 +671,6 @@ fn push_op(buf: &mut Buf, op: BinOp) {
called_via::BinOp::And => buf.push_str("&&"),
called_via::BinOp::Or => buf.push_str("||"),
called_via::BinOp::Pizza => buf.push_str("|>"),
called_via::BinOp::Assignment => unreachable!(),
called_via::BinOp::IsAliasType => unreachable!(),
called_via::BinOp::IsOpaqueType => unreachable!(),
called_via::BinOp::Backpassing => unreachable!(),
}
}
@ -1708,10 +1704,6 @@ fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
| BinOp::And
| BinOp::Or
| BinOp::Pizza => true,
BinOp::Assignment
| BinOp::IsAliasType
| BinOp::IsOpaqueType
| BinOp::Backpassing => false,
})
}
Expr::If(_, _) => true,

View File

@ -3,10 +3,8 @@ use std::cmp::max;
use crate::annotation::{is_collection_multiline, Formattable, Newlines, Parens};
use crate::collection::{fmt_collection, Braces};
use crate::expr::fmt_str_literal;
use crate::spaces::RemoveSpaces;
use crate::spaces::{fmt_comments_only, fmt_default_spaces, fmt_spaces, NewlineAt, INDENT};
use crate::Buf;
use bumpalo::Bump;
use roc_parse::ast::{Collection, CommentOrNewline, Header, Module, Spaced, Spaces};
use roc_parse::header::{
AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword, HostedHeader, ImportsEntry,
@ -58,12 +56,6 @@ macro_rules! keywords {
buf.push_str($name::KEYWORD);
}
}
impl<'a> RemoveSpaces<'a> for $name {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
)*
}
}

View File

@ -1,5 +1,5 @@
use crate::annotation::{Formattable, Newlines, Parens};
use crate::expr::{fmt_str_literal, format_sq_literal};
use crate::expr::{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};
@ -48,7 +48,7 @@ impl<'a> Formattable for Pattern<'a> {
pattern
);
spaces.iter().any(|s| s.is_comment())
spaces.iter().any(|s| s.is_comment()) || pattern.is_multiline()
}
Pattern::RecordDestructure(fields) => fields.iter().any(|f| f.is_multiline()),
@ -63,15 +63,17 @@ impl<'a> Formattable for Pattern<'a> {
list_rest_spaces.iter().any(|s| s.is_comment()) || pattern_as.is_multiline()
}
},
Pattern::StrLiteral(literal) => is_str_multiline(literal),
Pattern::Apply(pat, args) => {
pat.is_multiline() || args.iter().any(|a| a.is_multiline())
}
Pattern::Identifier { .. }
| Pattern::Tag(_)
| Pattern::OpaqueRef(_)
| Pattern::Apply(_, _)
| Pattern::NumLiteral(..)
| Pattern::NonBase10Literal { .. }
| Pattern::FloatLiteral(..)
| Pattern::StrLiteral(_)
| Pattern::SingleQuote(_)
| Pattern::Underscore(_)
| Pattern::Malformed(_)
@ -100,7 +102,13 @@ impl<'a> Formattable for Pattern<'a> {
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 parens = !loc_arg_patterns.is_empty() && (parens == Parens::InApply);
let indent_more = if self.is_multiline() {
indent + INDENT
} else {
indent
};
if parens {
buf.push('(');
@ -110,7 +118,7 @@ impl<'a> Formattable for Pattern<'a> {
for loc_arg in loc_arg_patterns.iter() {
buf.spaces(1);
loc_arg.format_with_options(buf, Parens::InApply, Newlines::No, indent);
loc_arg.format_with_options(buf, Parens::InApply, Newlines::No, indent_more);
}
if parens {

View File

@ -1,23 +1,5 @@
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use roc_module::called_via::{BinOp, UnaryOp};
use roc_parse::{
ast::{
AbilityImpls, AbilityMember, AssignedField, Collection, CommentOrNewline, Defs, Expr,
Header, Implements, ImplementsAbilities, ImplementsAbility, ImplementsClause, ImportAlias,
ImportAsKeyword, ImportExposingKeyword, ImportedModuleName, IngestedFileAnnotation,
IngestedFileImport, Module, ModuleImport, ModuleImportParams, OldRecordBuilderField,
Pattern, PatternAs, Spaced, Spaces, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef,
TypeHeader, ValueDef, WhenBranch,
},
header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, KeywordItem, ModuleHeader, ModuleName,
ModuleParams, PackageEntry, PackageHeader, PackageName, PlatformHeader, PlatformRequires,
ProvidesTo, To, TypedIdent,
},
ident::{BadIdent, UppercaseIdent},
};
use roc_region::all::{Loc, Position, Region};
use roc_parse::{ast::CommentOrNewline, remove_spaces::RemoveSpaces};
use crate::{Ast, Buf};
@ -211,20 +193,6 @@ fn fmt_docs(buf: &mut Buf, docs: &str) {
buf.push_str(docs.trim_end());
}
/// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting.
///
/// Currently this consists of:
/// * Removing newlines
/// * Removing comments
/// * Removing parens in Exprs
///
/// Long term, we actually want this transform to preserve comments (so we can assert they're maintained by formatting)
/// - but there are currently several bugs where they're _not_ preserved.
/// TODO: ensure formatting retains comments
pub trait RemoveSpaces<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self;
}
impl<'a> RemoveSpaces<'a> for Ast<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
Ast {
@ -233,834 +201,3 @@ impl<'a> RemoveSpaces<'a> for Ast<'a> {
}
}
}
impl<'a> RemoveSpaces<'a> for Defs<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let mut defs = self.clone();
defs.spaces.clear();
defs.space_before.clear();
defs.space_after.clear();
for type_def in defs.type_defs.iter_mut() {
*type_def = type_def.remove_spaces(arena);
}
for value_def in defs.value_defs.iter_mut() {
*value_def = value_def.remove_spaces(arena);
}
for region_def in defs.regions.iter_mut() {
*region_def = region_def.remove_spaces(arena);
}
defs
}
}
impl<'a, V: RemoveSpaces<'a>> RemoveSpaces<'a> for Spaces<'a, V> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
Spaces {
before: &[],
item: self.item.remove_spaces(arena),
after: &[],
}
}
}
impl<'a, K: RemoveSpaces<'a>, V: RemoveSpaces<'a>> RemoveSpaces<'a> for KeywordItem<'a, K, V> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
KeywordItem {
keyword: self.keyword.remove_spaces(arena),
item: self.item.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ProvidesTo<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
ProvidesTo {
provides_keyword: self.provides_keyword.remove_spaces(arena),
entries: self.entries.remove_spaces(arena),
types: self.types.remove_spaces(arena),
to_keyword: self.to_keyword.remove_spaces(arena),
to: self.to.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for Module<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let header = match &self.header {
Header::Module(header) => Header::Module(ModuleHeader {
after_keyword: &[],
params: header.params.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
interface_imports: header.interface_imports.remove_spaces(arena),
}),
Header::App(header) => Header::App(AppHeader {
before_provides: &[],
provides: header.provides.remove_spaces(arena),
before_packages: &[],
packages: header.packages.remove_spaces(arena),
old_imports: header.old_imports.remove_spaces(arena),
old_provides_to_new_package: header
.old_provides_to_new_package
.remove_spaces(arena),
}),
Header::Package(header) => Header::Package(PackageHeader {
before_exposes: &[],
exposes: header.exposes.remove_spaces(arena),
before_packages: &[],
packages: header.packages.remove_spaces(arena),
}),
Header::Platform(header) => Header::Platform(PlatformHeader {
before_name: &[],
name: header.name.remove_spaces(arena),
requires: header.requires.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena),
}),
Header::Hosted(header) => Header::Hosted(HostedHeader {
before_name: &[],
name: header.name.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
generates: header.generates.remove_spaces(arena),
generates_with: header.generates_with.remove_spaces(arena),
}),
};
Module {
comments: &[],
header,
}
}
}
impl<'a> RemoveSpaces<'a> for ModuleParams<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
ModuleParams {
params: self.params.remove_spaces(arena),
before_arrow: &[],
after_arrow: &[],
}
}
}
impl<'a> RemoveSpaces<'a> for Region {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
Region::zero()
}
}
impl<'a> RemoveSpaces<'a> for &'a str {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
self
}
}
impl<'a, T: RemoveSpaces<'a> + Copy> RemoveSpaces<'a> for Spaced<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Spaced::Item(a) => Spaced::Item(a.remove_spaces(arena)),
Spaced::SpaceBefore(a, _) => a.remove_spaces(arena),
Spaced::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ExposedName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for ModuleName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for PackageName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for To<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
To::ExistingPackage(a) => To::ExistingPackage(a),
To::NewPackage(a) => To::NewPackage(a.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for TypedIdent<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
TypedIdent {
ident: self.ident.remove_spaces(arena),
spaces_before_colon: &[],
ann: self.ann.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
PlatformRequires {
rigids: self.rigids.remove_spaces(arena),
signature: self.signature.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for PackageEntry<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
PackageEntry {
shorthand: self.shorthand,
spaces_after_shorthand: &[],
platform_marker: match self.platform_marker {
Some(_) => Some(&[]),
None => None,
},
package_name: self.package_name.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ImportsEntry<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
ImportsEntry::Module(a, b) => ImportsEntry::Module(a, b.remove_spaces(arena)),
ImportsEntry::Package(a, b, c) => ImportsEntry::Package(a, b, c.remove_spaces(arena)),
ImportsEntry::IngestedFile(a, b) => {
ImportsEntry::IngestedFile(a, b.remove_spaces(arena))
}
}
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Option<T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
self.as_ref().map(|a| a.remove_spaces(arena))
}
}
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc<T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let res = self.value.remove_spaces(arena);
Loc::at(Region::zero(), res)
}
}
impl<'a, A: RemoveSpaces<'a>, B: RemoveSpaces<'a>> RemoveSpaces<'a> for (A, B) {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
(self.0.remove_spaces(arena), self.1.remove_spaces(arena))
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Collection<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let mut items = Vec::with_capacity_in(self.items.len(), arena);
for item in self.items {
items.push(item.remove_spaces(arena));
}
Collection::with_items(items.into_bump_slice())
}
}
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for &'a [T] {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let mut items = Vec::with_capacity_in(self.len(), arena);
for item in *self {
let res = item.remove_spaces(arena);
items.push(res);
}
items.into_bump_slice()
}
}
impl<'a> RemoveSpaces<'a> for UnaryOp {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for BinOp {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for &'a T {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
arena.alloc((*self).remove_spaces(arena))
}
}
impl<'a> RemoveSpaces<'a> for TypeDef<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
use TypeDef::*;
match *self {
Alias {
header: TypeHeader { name, vars },
ann,
} => Alias {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
ann: ann.remove_spaces(arena),
},
Opaque {
header: TypeHeader { name, vars },
typ,
derived,
} => Opaque {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
typ: typ.remove_spaces(arena),
derived: derived.remove_spaces(arena),
},
Ability {
header: TypeHeader { name, vars },
loc_implements: loc_has,
members,
} => Ability {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
loc_implements: loc_has.remove_spaces(arena),
members: members.remove_spaces(arena),
},
}
}
}
impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
use ValueDef::*;
match *self {
Annotation(a, b) => Annotation(a.remove_spaces(arena), b.remove_spaces(arena)),
Body(a, b) => Body(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
AnnotatedBody {
ann_pattern,
ann_type,
comment: _,
body_pattern,
body_expr,
} => AnnotatedBody {
ann_pattern: arena.alloc(ann_pattern.remove_spaces(arena)),
ann_type: arena.alloc(ann_type.remove_spaces(arena)),
comment: None,
body_pattern: arena.alloc(body_pattern.remove_spaces(arena)),
body_expr: arena.alloc(body_expr.remove_spaces(arena)),
},
Dbg {
condition,
preceding_comment: _,
} => Dbg {
condition: arena.alloc(condition.remove_spaces(arena)),
preceding_comment: Region::zero(),
},
Expect {
condition,
preceding_comment: _,
} => Expect {
condition: arena.alloc(condition.remove_spaces(arena)),
preceding_comment: Region::zero(),
},
ExpectFx {
condition,
preceding_comment: _,
} => ExpectFx {
condition: arena.alloc(condition.remove_spaces(arena)),
preceding_comment: Region::zero(),
},
ModuleImport(module_import) => ModuleImport(module_import.remove_spaces(arena)),
IngestedFileImport(ingested_file_import) => {
IngestedFileImport(ingested_file_import.remove_spaces(arena))
}
Stmt(loc_expr) => Stmt(arena.alloc(loc_expr.remove_spaces(arena))),
}
}
}
impl<'a> RemoveSpaces<'a> for ModuleImport<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
ModuleImport {
before_name: &[],
name: self.name.remove_spaces(arena),
params: self.params.remove_spaces(arena),
alias: self.alias.remove_spaces(arena),
exposed: self.exposed.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ModuleImportParams<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
ModuleImportParams {
before: &[],
params: self.params.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for IngestedFileImport<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
IngestedFileImport {
before_path: &[],
path: self.path.remove_spaces(arena),
name: self.name.remove_spaces(arena),
annotation: self.annotation.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ImportedModuleName<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
ImportedModuleName {
package: self.package.remove_spaces(arena),
name: self.name.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ImportAlias<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for ImportAsKeyword {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for ImportExposingKeyword {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for IngestedFileAnnotation<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
IngestedFileAnnotation {
before_colon: &[],
annotation: self.annotation.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for Implements<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
Implements::Implements
}
}
impl<'a> RemoveSpaces<'a> for AbilityMember<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
AbilityMember {
name: self.name.remove_spaces(arena),
typ: self.typ.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for WhenBranch<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
WhenBranch {
patterns: self.patterns.remove_spaces(arena),
value: self.value.remove_spaces(arena),
guard: self.guard.remove_spaces(arena),
}
}
}
impl<'a, T: RemoveSpaces<'a> + Copy + std::fmt::Debug> RemoveSpaces<'a> for AssignedField<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
AssignedField::RequiredValue(a, _, c) => AssignedField::RequiredValue(
a.remove_spaces(arena),
arena.alloc([]),
arena.alloc(c.remove_spaces(arena)),
),
AssignedField::OptionalValue(a, _, c) => AssignedField::OptionalValue(
a.remove_spaces(arena),
arena.alloc([]),
arena.alloc(c.remove_spaces(arena)),
),
AssignedField::LabelOnly(a) => AssignedField::LabelOnly(a.remove_spaces(arena)),
AssignedField::Malformed(a) => AssignedField::Malformed(a),
AssignedField::SpaceBefore(a, _) => a.remove_spaces(arena),
AssignedField::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for OldRecordBuilderField<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
OldRecordBuilderField::Value(a, _, c) => OldRecordBuilderField::Value(
a.remove_spaces(arena),
&[],
arena.alloc(c.remove_spaces(arena)),
),
OldRecordBuilderField::ApplyValue(a, _, _, c) => OldRecordBuilderField::ApplyValue(
a.remove_spaces(arena),
&[],
&[],
arena.alloc(c.remove_spaces(arena)),
),
OldRecordBuilderField::LabelOnly(a) => {
OldRecordBuilderField::LabelOnly(a.remove_spaces(arena))
}
OldRecordBuilderField::Malformed(a) => OldRecordBuilderField::Malformed(a),
OldRecordBuilderField::SpaceBefore(a, _) => a.remove_spaces(arena),
OldRecordBuilderField::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for StrLiteral<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
StrLiteral::PlainLine(t) => StrLiteral::PlainLine(t),
StrLiteral::Line(t) => StrLiteral::Line(t.remove_spaces(arena)),
StrLiteral::Block(t) => StrLiteral::Block(t.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for StrSegment<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
StrSegment::Plaintext(t) => StrSegment::Plaintext(t),
StrSegment::Unicode(t) => StrSegment::Unicode(t.remove_spaces(arena)),
StrSegment::EscapedChar(c) => StrSegment::EscapedChar(c),
StrSegment::Interpolated(t) => StrSegment::Interpolated(t.remove_spaces(arena)),
StrSegment::DeprecatedInterpolated(t) => {
StrSegment::DeprecatedInterpolated(t.remove_spaces(arena))
}
}
}
}
impl<'a> RemoveSpaces<'a> for Expr<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Expr::Float(a) => Expr::Float(a),
Expr::Num(a) => Expr::Num(a),
Expr::NonBase10Int {
string,
base,
is_negative,
} => Expr::NonBase10Int {
string,
base,
is_negative,
},
Expr::Str(a) => Expr::Str(a.remove_spaces(arena)),
Expr::RecordAccess(a, b) => Expr::RecordAccess(arena.alloc(a.remove_spaces(arena)), b),
Expr::AccessorFunction(a) => Expr::AccessorFunction(a),
Expr::TupleAccess(a, b) => Expr::TupleAccess(arena.alloc(a.remove_spaces(arena)), b),
Expr::TaskAwaitBang(a) => Expr::TaskAwaitBang(arena.alloc(a.remove_spaces(arena))),
Expr::List(a) => Expr::List(a.remove_spaces(arena)),
Expr::RecordUpdate { update, fields } => Expr::RecordUpdate {
update: arena.alloc(update.remove_spaces(arena)),
fields: fields.remove_spaces(arena),
},
Expr::Record(a) => Expr::Record(a.remove_spaces(arena)),
Expr::OldRecordBuilder(a) => Expr::OldRecordBuilder(a.remove_spaces(arena)),
Expr::RecordBuilder { mapper, fields } => Expr::RecordBuilder {
mapper: arena.alloc(mapper.remove_spaces(arena)),
fields: fields.remove_spaces(arena),
},
Expr::Tuple(a) => Expr::Tuple(a.remove_spaces(arena)),
Expr::Var { module_name, ident } => Expr::Var { module_name, ident },
Expr::Underscore(a) => Expr::Underscore(a),
Expr::Tag(a) => Expr::Tag(a),
Expr::OpaqueRef(a) => Expr::OpaqueRef(a),
Expr::Closure(a, b) => Expr::Closure(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(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.remove_spaces(arena);
}
for value_def in defs.value_defs.iter_mut() {
*value_def = value_def.remove_spaces(arena);
}
Expr::Defs(arena.alloc(defs), arena.alloc(b.remove_spaces(arena)))
}
Expr::Backpassing(a, b, c) => Expr::Backpassing(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
arena.alloc(c.remove_spaces(arena)),
),
Expr::Expect(a, b) => Expr::Expect(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::Dbg(a, b) => Expr::Dbg(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::LowLevelDbg(_, _, _) => unreachable!(
"LowLevelDbg should only exist after desugaring, not during formatting"
),
Expr::Apply(a, b, c) => Expr::Apply(
arena.alloc(a.remove_spaces(arena)),
b.remove_spaces(arena),
c,
),
Expr::BinOps(a, b) => {
Expr::BinOps(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
}
Expr::UnaryOp(a, b) => {
Expr::UnaryOp(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
}
Expr::If(a, b) => Expr::If(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena))),
Expr::When(a, b) => {
Expr::When(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
}
Expr::ParensAround(a) => {
// The formatter can remove redundant parentheses, so also remove these when normalizing for comparison.
a.remove_spaces(arena)
}
Expr::MalformedIdent(a, b) => Expr::MalformedIdent(a, remove_spaces_bad_ident(b)),
Expr::MalformedClosure => Expr::MalformedClosure,
Expr::MalformedSuffixed(a) => Expr::MalformedSuffixed(a),
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
Expr::MultipleOldRecordBuilders(a) => Expr::MultipleOldRecordBuilders(a),
Expr::UnappliedOldRecordBuilder(a) => Expr::UnappliedOldRecordBuilder(a),
Expr::EmptyRecordBuilder(a) => Expr::EmptyRecordBuilder(a),
Expr::SingleFieldRecordBuilder(a) => Expr::SingleFieldRecordBuilder(a),
Expr::OptionalFieldInRecordBuilder(name, a) => {
Expr::OptionalFieldInRecordBuilder(name, a)
}
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
Expr::SingleQuote(a) => Expr::Num(a),
}
}
}
fn remove_spaces_bad_ident(ident: BadIdent) -> BadIdent {
match ident {
BadIdent::Start(_) => BadIdent::Start(Position::zero()),
BadIdent::Space(e, _) => BadIdent::Space(e, Position::zero()),
BadIdent::UnderscoreAlone(_) => BadIdent::UnderscoreAlone(Position::zero()),
BadIdent::UnderscoreInMiddle(_) => BadIdent::UnderscoreInMiddle(Position::zero()),
BadIdent::UnderscoreAtStart {
position: _,
declaration_region,
} => BadIdent::UnderscoreAtStart {
position: Position::zero(),
declaration_region,
},
BadIdent::QualifiedTag(_) => BadIdent::QualifiedTag(Position::zero()),
BadIdent::WeirdAccessor(_) => BadIdent::WeirdAccessor(Position::zero()),
BadIdent::WeirdDotAccess(_) => BadIdent::WeirdDotAccess(Position::zero()),
BadIdent::WeirdDotQualified(_) => BadIdent::WeirdDotQualified(Position::zero()),
BadIdent::StrayDot(_) => BadIdent::StrayDot(Position::zero()),
BadIdent::BadOpaqueRef(_) => BadIdent::BadOpaqueRef(Position::zero()),
BadIdent::QualifiedTupleAccessor(_) => BadIdent::QualifiedTupleAccessor(Position::zero()),
}
}
impl<'a> RemoveSpaces<'a> for Pattern<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Pattern::Identifier { ident } => Pattern::Identifier { ident },
Pattern::Tag(a) => Pattern::Tag(a),
Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a),
Pattern::Apply(a, b) => Pattern::Apply(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Pattern::RecordDestructure(a) => Pattern::RecordDestructure(a.remove_spaces(arena)),
Pattern::RequiredField(a, b) => {
Pattern::RequiredField(a, arena.alloc(b.remove_spaces(arena)))
}
Pattern::OptionalField(a, b) => {
Pattern::OptionalField(a, arena.alloc(b.remove_spaces(arena)))
}
Pattern::As(pattern, pattern_as) => Pattern::As(
arena.alloc(pattern.remove_spaces(arena)),
pattern_as.remove_spaces(arena),
),
Pattern::NumLiteral(a) => Pattern::NumLiteral(a),
Pattern::NonBase10Literal {
string,
base,
is_negative,
} => Pattern::NonBase10Literal {
string,
base,
is_negative,
},
Pattern::FloatLiteral(a) => Pattern::FloatLiteral(a),
Pattern::StrLiteral(a) => Pattern::StrLiteral(a),
Pattern::Underscore(a) => Pattern::Underscore(a),
Pattern::Malformed(a) => Pattern::Malformed(a),
Pattern::MalformedIdent(a, b) => Pattern::MalformedIdent(a, remove_spaces_bad_ident(b)),
Pattern::QualifiedIdentifier { module_name, ident } => {
Pattern::QualifiedIdentifier { module_name, ident }
}
Pattern::SpaceBefore(a, _) => a.remove_spaces(arena),
Pattern::SpaceAfter(a, _) => a.remove_spaces(arena),
Pattern::SingleQuote(a) => Pattern::SingleQuote(a),
Pattern::List(pats) => Pattern::List(pats.remove_spaces(arena)),
Pattern::Tuple(pats) => Pattern::Tuple(pats.remove_spaces(arena)),
Pattern::ListRest(opt_pattern_as) => Pattern::ListRest(
opt_pattern_as
.map(|(_, pattern_as)| ([].as_ref(), pattern_as.remove_spaces(arena))),
),
}
}
}
impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
TypeAnnotation::Function(a, b) => TypeAnnotation::Function(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
TypeAnnotation::Apply(a, b, c) => TypeAnnotation::Apply(a, b, c.remove_spaces(arena)),
TypeAnnotation::BoundVariable(a) => TypeAnnotation::BoundVariable(a),
TypeAnnotation::As(a, _, TypeHeader { name, vars }) => TypeAnnotation::As(
arena.alloc(a.remove_spaces(arena)),
&[],
TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
),
TypeAnnotation::Tuple { elems: fields, ext } => TypeAnnotation::Tuple {
elems: fields.remove_spaces(arena),
ext: ext.remove_spaces(arena),
},
TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record {
fields: fields.remove_spaces(arena),
ext: ext.remove_spaces(arena),
},
TypeAnnotation::TagUnion { ext, tags } => TypeAnnotation::TagUnion {
ext: ext.remove_spaces(arena),
tags: tags.remove_spaces(arena),
},
TypeAnnotation::Inferred => TypeAnnotation::Inferred,
TypeAnnotation::Wildcard => TypeAnnotation::Wildcard,
TypeAnnotation::Where(annot, has_clauses) => TypeAnnotation::Where(
arena.alloc(annot.remove_spaces(arena)),
arena.alloc(has_clauses.remove_spaces(arena)),
),
TypeAnnotation::SpaceBefore(a, _) => a.remove_spaces(arena),
TypeAnnotation::SpaceAfter(a, _) => a.remove_spaces(arena),
TypeAnnotation::Malformed(a) => TypeAnnotation::Malformed(a),
}
}
}
impl<'a> RemoveSpaces<'a> for ImplementsClause<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
ImplementsClause {
var: self.var.remove_spaces(arena),
abilities: self.abilities.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for Tag<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Tag::Apply { name, args } => Tag::Apply {
name: name.remove_spaces(arena),
args: args.remove_spaces(arena),
},
Tag::Malformed(a) => Tag::Malformed(a),
Tag::SpaceBefore(a, _) => a.remove_spaces(arena),
Tag::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for AbilityImpls<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
AbilityImpls::AbilityImpls(impls) => {
AbilityImpls::AbilityImpls(impls.remove_spaces(arena))
}
AbilityImpls::SpaceBefore(has, _) | AbilityImpls::SpaceAfter(has, _) => {
has.remove_spaces(arena)
}
}
}
}
impl<'a> RemoveSpaces<'a> for ImplementsAbility<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
ImplementsAbility::ImplementsAbility { ability, impls } => {
ImplementsAbility::ImplementsAbility {
ability: ability.remove_spaces(arena),
impls: impls.remove_spaces(arena),
}
}
ImplementsAbility::SpaceBefore(has, _) | ImplementsAbility::SpaceAfter(has, _) => {
has.remove_spaces(arena)
}
}
}
}
impl<'a> RemoveSpaces<'a> for ImplementsAbilities<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
ImplementsAbilities::Implements(derived) => {
ImplementsAbilities::Implements(derived.remove_spaces(arena))
}
ImplementsAbilities::SpaceBefore(derived, _)
| ImplementsAbilities::SpaceAfter(derived, _) => derived.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for PatternAs<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
PatternAs {
spaces_before: &[],
identifier: self.identifier.remove_spaces(arena),
}
}
}

View File

@ -4320,16 +4320,26 @@ mod test_reporting {
{ x, y }
"
),
@r"
TOO MANY ARGS in /code/proj/Main.roc
@r###"
STATEMENT AFTER EXPRESSION in tmp/double_equals_in_def/Test.roc
This value is not a function, but it was given 3 arguments:
I just finished parsing an expression with a series of definitions,
and this line is indented as if it's intended to be part of that
expression:
1 app "test" provides [main] to "./platform"
2
3 main =
4 x = 3
5 y =
6 x == 5
^
7 Num.add 1 2
^
Are there any missing commas? Or missing parentheses?
"
However, I already saw the final expression in that series of
definitions.
"###
);
test_report!(
@ -5018,7 +5028,7 @@ mod test_reporting {
I was partway through parsing an `import`, but I got stuck here:
4 import svg.Path a
^
^
I was expecting to see the `as` keyword, like:
@ -5417,14 +5427,25 @@ mod test_reporting {
2 -> 2
"
),
@r"
NOT END OF FILE in tmp/when_outdented_branch/Test.roc
@r###"
UNKNOWN OPERATOR in tmp/when_outdented_branch/Test.roc
I expected to reach the end of the file, but got stuck here:
This looks like an operator, but it's not one I recognize!
1 app "test" provides [main] to "./platform"
2
3 main =
4 when 4 is
5 5 -> 2
6 2 -> 2
^
"
^^
Looks like you are trying to define a function.
In roc, functions are always written as a lambda, like
increment = \n -> n + 1
"###
);
test_report!(
@ -5436,12 +5457,13 @@ mod test_reporting {
_ -> 2
"
),
@r"
@r###"
UNEXPECTED ARROW in tmp/when_over_indented_underscore/Test.roc
I am parsing a `when` expression right now, but this arrow is confusing
me:
4 when 4 is
5 5 -> 2
6 _ -> 2
^^
@ -5461,7 +5483,7 @@ mod test_reporting {
Notice the indentation. All patterns are aligned, and each branch is
indented a bit more than the corresponding pattern. That is important!
"
"###
);
test_report!(
@ -5473,12 +5495,13 @@ mod test_reporting {
2 -> 2
"
),
@r"
@r###"
UNEXPECTED ARROW in tmp/when_over_indented_int/Test.roc
I am parsing a `when` expression right now, but this arrow is confusing
me:
4 when 4 is
5 5 -> Num.neg
6 2 -> 2
^^
@ -5498,7 +5521,7 @@ mod test_reporting {
Notice the indentation. All patterns are aligned, and each branch is
indented a bit more than the corresponding pattern. That is important!
"
"###
);
// TODO I think we can do better here
@ -6136,27 +6159,21 @@ All branches in an `if` must have the same type!
main = 5 -> 3
"
),
|golden| pretty_assertions::assert_eq!(
golden,
&format!(
r#"── UNKNOWN OPERATOR in tmp/wild_case_arrow/Test.roc ────────────────────────────
@r###"
SYNTAX PROBLEM in tmp/wild_case_arrow/Test.roc
This looks like an operator, but it's not one I recognize!
I got stuck here:
1 app "test" provides [main] to "./platform"
2
3 main =
4 main = 5 -> 3
^^
1 app "test" provides [main] to "./platform"
2
3 main =
4 main = 5 -> 3
^
Looks like you are trying to define a function.{}
In roc, functions are always written as a lambda, like{}
increment = \n -> n + 1"#,
' ', ' '
)
)
Whatever I am running into is confusing me a lot! Normally I can give
fairly specific hints, but something is really tripping me up this
time.
"###
);
#[test]
@ -10971,13 +10988,13 @@ In roc, functions are always written as a lambda, like{}
0
"
),
@r"
@r###"
UNNECESSARY DEFINITION in /code/proj/Main.roc
This destructure assignment doesn't introduce any new variables:
4 Pair _ _ = Pair 0 1
^^^^
^^^^^^^^
If you don't need to use the value on the right-hand-side of this
assignment, consider removing the assignment. Since Roc is purely
@ -11019,7 +11036,7 @@ In roc, functions are always written as a lambda, like{}
assignment, consider removing the assignment. Since Roc is purely
functional, assignments that don't introduce variables cannot affect a
program's behavior!
"
"###
);
test_report!(

View File

@ -833,7 +833,7 @@ fn platform_parse_error() {
match multiple_modules("platform_parse_error", modules) {
Err(report) => {
assert!(report.contains("NOT END OF FILE"));
assert!(report.contains("STATEMENT AFTER EXPRESSION"));
assert!(report.contains("blah 1 2 3 # causing a parse error on purpose"));
}
Ok(_) => unreachable!("we expect failure here"),

View File

@ -3,7 +3,7 @@ use self::BinOp::*;
use std::cmp::Ordering;
use std::fmt;
const PRECEDENCES: [(BinOp, u8); 20] = [
const PRECEDENCES: [(BinOp, u8); 16] = [
(Caret, 8),
(Star, 7),
(Slash, 7),
@ -20,14 +20,9 @@ const PRECEDENCES: [(BinOp, u8); 20] = [
(GreaterThanOrEq, 2),
(And, 1),
(Or, 0),
// These should never come up
(Assignment, 255),
(IsAliasType, 255),
(IsOpaqueType, 255),
(Backpassing, 255),
];
const ASSOCIATIVITIES: [(BinOp, Associativity); 20] = [
const ASSOCIATIVITIES: [(BinOp, Associativity); 16] = [
(Caret, RightAssociative),
(Star, LeftAssociative),
(Slash, LeftAssociative),
@ -44,14 +39,9 @@ const ASSOCIATIVITIES: [(BinOp, Associativity); 20] = [
(GreaterThanOrEq, NonAssociative),
(And, RightAssociative),
(Or, RightAssociative),
// These should never come up
(Assignment, LeftAssociative),
(IsAliasType, LeftAssociative),
(IsOpaqueType, LeftAssociative),
(Backpassing, LeftAssociative),
];
const DISPLAY_STRINGS: [(BinOp, &str); 20] = [
const DISPLAY_STRINGS: [(BinOp, &str); 16] = [
(Caret, "^"),
(Star, "*"),
(Slash, "/"),
@ -68,10 +58,6 @@ const DISPLAY_STRINGS: [(BinOp, &str); 20] = [
(GreaterThanOrEq, ">="),
(And, "&&"),
(Or, "||"),
(Assignment, "="),
(IsAliasType, ":"),
(IsOpaqueType, ":="),
(Backpassing, "<-"),
];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -147,10 +133,6 @@ pub enum BinOp {
GreaterThanOrEq,
And,
Or,
Assignment,
IsAliasType,
IsOpaqueType,
Backpassing,
// lowest precedence
}
@ -161,7 +143,6 @@ impl BinOp {
Caret | Star | Slash | Percent | Plus | Minus | LessThan | GreaterThan => 1,
DoubleSlash | Equals | NotEquals | LessThanOrEq | GreaterThanOrEq | And | Or
| Pizza => 2,
Assignment | IsAliasType | IsOpaqueType | Backpassing => unreachable!(),
}
}
}
@ -195,25 +176,13 @@ pub enum Associativity {
impl BinOp {
pub fn associativity(self) -> Associativity {
// The compiler should never pass any of these to this function!
debug_assert_ne!(self, Assignment);
debug_assert_ne!(self, IsAliasType);
debug_assert_ne!(self, IsOpaqueType);
debug_assert_ne!(self, Backpassing);
const ASSOCIATIVITY_TABLE: [Associativity; 20] = generate_associativity_table();
const ASSOCIATIVITY_TABLE: [Associativity; 16] = generate_associativity_table();
ASSOCIATIVITY_TABLE[self as usize]
}
fn precedence(self) -> u8 {
// The compiler should never pass any of these to this function!
debug_assert_ne!(self, Assignment);
debug_assert_ne!(self, IsAliasType);
debug_assert_ne!(self, IsOpaqueType);
debug_assert_ne!(self, Backpassing);
const PRECEDENCE_TABLE: [u8; 20] = generate_precedence_table();
const PRECEDENCE_TABLE: [u8; 16] = generate_precedence_table();
PRECEDENCE_TABLE[self as usize]
}
@ -233,19 +202,14 @@ impl Ord for BinOp {
impl std::fmt::Display for BinOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
debug_assert_ne!(*self, Assignment);
debug_assert_ne!(*self, IsAliasType);
debug_assert_ne!(*self, IsOpaqueType);
debug_assert_ne!(*self, Backpassing);
const DISPLAY_TABLE: [&str; 20] = generate_display_table();
const DISPLAY_TABLE: [&str; 16] = generate_display_table();
write!(f, "{}", DISPLAY_TABLE[*self as usize])
}
}
const fn generate_precedence_table() -> [u8; 20] {
let mut table = [0u8; 20];
const fn generate_precedence_table() -> [u8; 16] {
let mut table = [0u8; 16];
let mut i = 0;
while i < PRECEDENCES.len() {
@ -256,8 +220,8 @@ const fn generate_precedence_table() -> [u8; 20] {
table
}
const fn generate_associativity_table() -> [Associativity; 20] {
let mut table = [NonAssociative; 20];
const fn generate_associativity_table() -> [Associativity; 16] {
let mut table = [NonAssociative; 16];
let mut i = 0;
while i < ASSOCIATIVITIES.len() {
@ -268,8 +232,8 @@ const fn generate_associativity_table() -> [Associativity; 20] {
table
}
const fn generate_display_table() -> [&'static str; 20] {
let mut table = [""; 20];
const fn generate_display_table() -> [&'static str; 16] {
let mut table = [""; 16];
let mut i = 0;
while i < DISPLAY_STRINGS.len() {

View File

@ -21,6 +21,12 @@ pub struct Spaces<'a, T> {
pub after: &'a [CommentOrNewline<'a>],
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct SpacesBefore<'a, T> {
pub before: &'a [CommentOrNewline<'a>],
pub item: T,
}
#[derive(Copy, Clone, PartialEq)]
pub enum Spaced<'a, T> {
Item(T),
@ -1204,6 +1210,21 @@ impl<'a> Defs<'a> {
})
}
pub fn loc_defs<'b>(
&'b self,
) -> impl Iterator<Item = Result<Loc<TypeDef<'a>>, Loc<ValueDef<'a>>>> + 'b {
self.tags
.iter()
.enumerate()
.map(|(i, tag)| match tag.split() {
Ok(type_index) => Ok(Loc::at(self.regions[i], self.type_defs[type_index.index()])),
Err(value_index) => Err(Loc::at(
self.regions[i],
self.value_defs[value_index.index()],
)),
})
}
pub fn list_value_defs(&self) -> impl Iterator<Item = (usize, &ValueDef<'a>)> {
self.tags
.iter()
@ -2072,6 +2093,28 @@ pub trait Spaceable<'a> {
fn before(&'a self, _: &'a [CommentOrNewline<'a>]) -> Self;
fn after(&'a self, _: &'a [CommentOrNewline<'a>]) -> Self;
fn maybe_before(self, arena: &'a Bump, spaces: &'a [CommentOrNewline<'a>]) -> Self
where
Self: Sized + 'a,
{
if spaces.is_empty() {
self
} else {
arena.alloc(self).before(spaces)
}
}
fn maybe_after(self, arena: &'a Bump, spaces: &'a [CommentOrNewline<'a>]) -> Self
where
Self: Sized + 'a,
{
if spaces.is_empty() {
self
} else {
arena.alloc(self).after(spaces)
}
}
fn with_spaces_before(&'a self, spaces: &'a [CommentOrNewline<'a>], region: Region) -> Loc<Self>
where
Self: Sized,

View File

@ -333,7 +333,7 @@ where
let start = state.pos();
match spaces().parse(arena, state, min_indent) {
Ok((progress, spaces, state)) => {
if progress == NoProgress || state.column() >= min_indent {
if spaces.is_empty() || state.column() >= min_indent {
Ok((progress, spaces, state))
} else {
Err((progress, indent_problem(start)))
@ -344,6 +344,60 @@ where
}
}
pub fn require_newline_or_eof<'a, E>(newline_problem: fn(Position) -> E) -> impl Parser<'a, (), E>
where
E: 'a + SpaceProblem,
{
move |arena: &'a Bump, state: State<'a>, min_indent| {
// TODO: we can do this more efficiently by stopping as soon as we see a '#' or a newline
let (_, res, _) = space0_e(newline_problem).parse(arena, state.clone(), min_indent)?;
if !res.is_empty() || state.has_reached_end() {
Ok((NoProgress, (), state))
} else {
Err((NoProgress, newline_problem(state.pos())))
}
}
}
pub fn loc_space0_e<'a, E>(
indent_problem: fn(Position) -> E,
) -> impl Parser<'a, Loc<&'a [CommentOrNewline<'a>]>, E>
where
E: 'a + SpaceProblem,
{
move |arena, state: State<'a>, min_indent: u32| {
let mut newlines = Vec::new_in(arena);
let start = state.pos();
let mut comment_start = None;
let mut comment_end = None;
let res = consume_spaces(state, |start, space, end| {
newlines.push(space);
if !matches!(space, CommentOrNewline::Newline) {
if comment_start.is_none() {
comment_start = Some(start);
}
comment_end = Some(end);
}
});
match res {
Ok((progress, state)) => {
if newlines.is_empty() || state.column() >= min_indent {
let start = comment_start.unwrap_or(state.pos());
let end = comment_end.unwrap_or(state.pos());
let region = Region::new(start, end);
Ok((progress, Loc::at(region, newlines.into_bump_slice()), state))
} else {
Err((progress, indent_problem(start)))
}
}
Err((progress, err)) => Err((progress, err)),
}
}
}
fn begins_with_crlf(bytes: &[u8]) -> bool {
bytes.len() >= 2 && bytes[0] == b'\r' && bytes[1] == b'\n'
}
@ -387,7 +441,7 @@ where
F: FnMut(Position, CommentOrNewline<'a>, Position),
{
let mut progress = NoProgress;
let mut found_newline = false;
let mut found_newline = state.is_at_start_of_file();
loop {
let whitespace = fast_eat_whitespace(state.bytes());
if whitespace > 0 {

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,7 @@ pub mod module;
pub mod number_literal;
pub mod pattern;
pub mod problems;
pub mod remove_spaces;
pub mod src64;
pub mod state;
pub mod string_literal;

View File

@ -330,6 +330,7 @@ pub enum EExpr<'a> {
Start(Position),
End(Position),
BadExprEnd(Position),
StmtAfterExpr(Position),
Space(BadInputError, Position),
Dot(Position),
@ -355,6 +356,8 @@ pub enum EExpr<'a> {
QualifiedTag(Position),
BackpassComma(Position),
BackpassArrow(Position),
BackpassContinue(Position),
DbgContinue(Position),
When(EWhen<'a>, Position),
If(EIf<'a>, Position),
@ -383,6 +386,7 @@ pub enum EExpr<'a> {
IndentEnd(Position),
UnexpectedComma(Position),
UnexpectedTopLevelExpr(Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]
@ -851,8 +855,9 @@ where
let cur_indent = INDENT.with(|i| *i.borrow());
println!(
"{:<5?}: {}{:<50}",
"{:<5?}:{:<2} {}{:<50}",
state.pos(),
min_indent,
&indent_text[..cur_indent * 2],
self.message
);
@ -868,8 +873,9 @@ where
};
println!(
"{:<5?}: {}{:<50} {:<15} {:?}",
"{:<5?}:{:<2} {}{:<50} {:<15} {:?}",
state.pos(),
min_indent,
&indent_text[..cur_indent * 2],
self.message,
format!("{:?}", progress),

File diff suppressed because it is too large Load Diff

View File

@ -129,6 +129,10 @@ impl<'a> State<'a> {
pub fn len_region(&self, length: u32) -> Region {
Region::new(self.pos(), self.pos().bump_column(length))
}
pub fn is_at_start_of_file(&self) -> bool {
self.offset == 0
}
}
impl<'a> fmt::Debug for State<'a> {

View File

@ -22,7 +22,7 @@ pub fn parse_loc_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<Loc<ast::Expr<'a>>, SourceError<'a, SyntaxError<'a>>> {
let state = State::new(input.trim().as_bytes());
let state = State::new(input.as_bytes());
match crate::expr::test_parse_expr(0, arena, state.clone()) {
Ok(loc_expr) => Ok(loc_expr),
@ -31,7 +31,7 @@ pub fn parse_loc_with<'a>(
}
pub fn parse_defs_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Defs<'a>, SyntaxError<'a>> {
let state = State::new(input.trim().as_bytes());
let state = State::new(input.as_bytes());
parse_module_defs(arena, state, Defs::default())
}
@ -40,7 +40,7 @@ pub fn parse_header_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<ast::Module<'a>, SyntaxError<'a>> {
let state = State::new(input.trim().as_bytes());
let state = State::new(input.as_bytes());
match crate::module::parse_header(arena, state.clone()) {
Ok((header, _)) => Ok(header),

View File

@ -2176,8 +2176,8 @@ fn refcount_nullable_unwrapped_needing_no_refcount_issue_5027() {
await : Effect, (Str -> Effect) -> Effect
await = \fx, cont ->
after
fx
cont
fx
cont
succeed : {} -> Effect
succeed = \{} -> (\{} -> "success")

View File

@ -11,6 +11,7 @@ cargo-fuzz = true
[dependencies]
test_syntax = { path = "../../test_syntax" }
roc_parse = { path = "../../parse" }
bumpalo = { version = "3.12.0", features = ["collections"] }
libfuzzer-sys = "0.4"

View File

@ -1,14 +1,18 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use bumpalo::Bump;
use libfuzzer_sys::fuzz_target;
use roc_parse::ast::Malformed;
use test_syntax::test_helpers::Input;
fuzz_target!(|data: &[u8]| {
if let Ok(input) = std::str::from_utf8(data) {
let input = Input::Expr(input);
let arena = Bump::new();
if input.parse_in(&arena).is_ok() {
input.check_invariants(|_| (), true);
let ast = input.parse_in(&arena);
if let Ok(ast) = ast {
if !ast.is_malformed() {
input.check_invariants(|_| (), true);
}
}
}
});

View File

@ -0,0 +1,23 @@
use test_syntax::{minimize::print_minimizations, test_helpers::InputKind};
fn main() {
let args = std::env::args().collect::<Vec<String>>();
if args.len() != 3 {
eprintln!("Usage: {} [expr|full|moduledefs|header] <input>", args[0]);
std::process::exit(1);
}
let kind = match args[1].as_str() {
"expr" => InputKind::Expr,
"full" => InputKind::Full,
"moduledefs" => InputKind::ModuleDefs,
"header" => InputKind::Header,
_ => {
eprintln!("Invalid input kind: {}", args[1]);
std::process::exit(1);
}
};
let text = std::fs::read_to_string(&args[2]).unwrap();
print_minimizations(&text, kind);
}

View File

@ -1 +1,2 @@
pub mod minimize;
pub mod test_helpers;

View File

@ -0,0 +1,204 @@
use crate::test_helpers::{Input, InputKind};
use bumpalo::Bump;
use roc_parse::{ast::Malformed, remove_spaces::RemoveSpaces};
pub fn print_minimizations(text: &str, kind: InputKind) {
let Some(original_error) = round_trip_once_and_extract_error(text, kind) else {
eprintln!("No error found");
return;
};
eprintln!("Error found: {}", original_error);
eprintln!("Proceeding with minimization");
let mut s = text.to_string();
loop {
let mut found = false;
for update in candidate_minimizations(s.clone()) {
let mut new_s = String::with_capacity(s.len());
let mut offset = 0;
for (start, end, replacement) in update.replacements.clone() {
new_s.push_str(&s[offset..start]);
new_s.push_str(&replacement);
offset = end;
}
new_s.push_str(&s[offset..]);
assert!(
new_s.len() < s.len(),
"replacements: {:?}",
update.replacements
);
if let Some(result) = round_trip_once_and_extract_error(&new_s, kind) {
if result == original_error {
eprintln!("Successfully minimized, new length: {}", new_s.len());
s = new_s;
found = true;
break;
}
}
}
if !found {
eprintln!("No more minimizations found");
break;
}
}
eprintln!("Final result:");
println!("{}", s);
}
fn round_trip_once_and_extract_error(text: &str, kind: InputKind) -> Option<String> {
let input = kind.with_text(text);
let res = std::panic::catch_unwind(|| round_trip_once(input));
match res {
Ok(res) => res,
Err(e) => {
if let Some(s) = e.downcast_ref::<&'static str>() {
return Some(s.to_string());
}
if let Some(s) = e.downcast_ref::<String>() {
return Some(s.clone());
}
Some("Panic during parsing".to_string())
}
}
}
fn round_trip_once(input: Input<'_>) -> Option<String> {
let arena = Bump::new();
let actual = match input.parse_in(&arena) {
Ok(a) => a,
Err(e) => {
return Some(format!(
"Initial parse failed: {:?}",
e.remove_spaces(&arena)
))
} // todo: strip pos info, use the error
};
if actual.is_malformed() {
return Some("Initial parse is malformed".to_string());
}
let output = actual.format();
let reparsed_ast = match output.as_ref().parse_in(&arena) {
Ok(r) => r,
Err(e) => return Some(format!("Reparse failed: {:?}", e.remove_spaces(&arena))),
};
let ast_normalized = actual.remove_spaces(&arena);
let reparsed_ast_normalized = reparsed_ast.remove_spaces(&arena);
if format!("{ast_normalized:?}") != format!("{reparsed_ast_normalized:?}") {
return Some("Different ast".to_string());
}
None
}
struct Update {
replacements: Vec<(usize, usize, String)>,
}
fn candidate_minimizations(s: String) -> Box<dyn Iterator<Item = Update>> {
let mut line_offsets = vec![0];
line_offsets.extend(s.match_indices('\n').map(|(i, _)| i + 1));
let line_count = line_offsets.len();
let s_len = s.len();
let line_indents = line_offsets
.iter()
.map(|&offset| s[offset..].chars().take_while(|&c| c == ' ').count())
.collect::<Vec<_>>();
let line_offsets_clone = line_offsets.clone();
// first, try to remove every group of 1, 2, 3, ... lines - in reverse order (so, trying removing n lines first, then n-1, etc)
let line_removals = (1..=line_count).rev().flat_map(move |n| {
let line_offsets_clone = line_offsets.clone();
(0..line_count - n).map(move |start| {
let end = start + n;
let start_offset = line_offsets_clone[start];
let end_offset = line_offsets_clone[end];
let replacement = String::new();
let replacements = vec![(start_offset, end_offset, replacement)];
Update { replacements }
})
});
let line_offsets = line_offsets_clone;
let line_offsets_clone = line_offsets.clone();
// then, try to dedent every group of 1, 2, 3, ... lines - in reverse order (so, trying dedenting n lines first, then n-1, etc)
// just remove one space at a time, for now
let line_dedents = (1..=line_count).rev().flat_map(move |n| {
let line_offsets_clone = line_offsets.clone();
let line_indents_clone = line_indents.clone();
(0..line_count - n).filter_map(move |start| {
// first check if all lines are either zero-width or have greater than zero indent
let end = start + n;
for i in start..end {
if line_indents_clone[i] == 0
&& line_offsets_clone[i] + 1
< line_offsets_clone.get(i + 1).cloned().unwrap_or(s_len)
{
return None;
}
}
let mut replacements = vec![];
for i in start..end {
let offset = line_offsets_clone[i];
let indent = line_indents_clone[i];
if indent > 0 {
replacements.push((offset, offset + 1, String::new()));
}
}
Some(Update { replacements })
})
});
// then, try to select every range of 1, 2, 3, ... lines - in normal order this time!
// we remove the lines before and after the range
let line_selects = (1..line_count - 1).flat_map(move |n| {
assert!(n > 0);
let line_offsets_clone = line_offsets_clone.clone();
(0..line_count - n).map(move |start| {
let end = start + n;
let start_offset = line_offsets_clone[start];
let end_offset = line_offsets_clone[end];
assert!(end_offset > start_offset);
assert!(start_offset > 0 || end_offset < s_len);
let replacements = vec![
(0, start_offset, String::new()),
(end_offset, s_len, String::new()),
];
Update { replacements }
})
});
// then, try to remove every range of 1, 2, 3, ... characters - in reverse order (so, trying removing n characters first, then n-1, etc)
let charseq_removals = (1..s.len()).rev().flat_map(move |n| {
(0..s.len() - n).map(move |start| {
let end = start + n;
let replacement = String::new();
let replacements = vec![(start, end, replacement)];
Update { replacements }
})
});
Box::new(
line_removals
.chain(line_dedents)
.chain(line_selects)
.chain(charseq_removals)
.filter(|u| !u.replacements.is_empty()),
)
}

View File

@ -4,12 +4,12 @@ use roc_parse::{
ast::{Defs, Expr, Malformed, Module},
module::parse_module_defs,
parser::{Parser, SyntaxError},
remove_spaces::RemoveSpaces,
state::State,
test_helpers::{parse_defs_with, parse_expr_with, parse_header_with},
};
use roc_test_utils::assert_multiline_str_eq;
use roc_fmt::spaces::RemoveSpaces;
use roc_fmt::Buf;
/// Source code to parse. Usually in the form of a test case.
@ -28,6 +28,25 @@ pub enum Input<'a> {
Full(&'a str),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum InputKind {
Header,
ModuleDefs,
Expr,
Full,
}
impl InputKind {
pub fn with_text(self, text: &str) -> Input {
match self {
InputKind::Header => Input::Header(text),
InputKind::ModuleDefs => Input::ModuleDefs(text),
InputKind::Expr => Input::Expr(text),
InputKind::Full => Input::Full(text),
}
}
}
// Owned version of `Input`
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InputOwned {
@ -38,7 +57,7 @@ pub enum InputOwned {
}
impl InputOwned {
fn as_ref(&self) -> Input {
pub fn as_ref(&self) -> Input {
match self {
InputOwned::Header(s) => Input::Header(s),
InputOwned::ModuleDefs(s) => Input::ModuleDefs(s),
@ -64,7 +83,7 @@ pub enum Output<'a> {
}
impl<'a> Output<'a> {
fn format(&self) -> InputOwned {
pub fn format(&self) -> InputOwned {
let arena = Bump::new();
let mut buf = Buf::new_in(&arena);
match self {
@ -172,7 +191,7 @@ impl<'a> Input<'a> {
let (header, defs) = header.upgrade_header_imports(arena);
let module_defs = parse_module_defs(arena, state, defs).unwrap();
let module_defs = parse_module_defs(arena, state, defs)?;
Ok(Output::Full {
header,

View File

@ -0,0 +1 @@
Expr(BackpassContinue(@8), @0)

View File

@ -0,0 +1 @@
Expr(BadExprEnd(@4), @0)

View File

@ -1 +1 @@
Expr(DefMissingFinalExpr2(Start(@11), @11), @0)
Expr(IndentEnd(@11), @0)

View File

@ -0,0 +1 @@
Expr(BadExprEnd(@3), @0)

View File

@ -1 +1 @@
Expr(Start(@0), @0)
Expr(IndentEnd(@12), @0)

View File

@ -1 +1 @@
Expr(If(Else(@16), @0), @0)
Expr(If(Else(@17), @0), @0)

View File

@ -1 +1 @@
Expr(List(End(@6), @0), @0)
Expr(List(End(@7), @0), @0)

View File

@ -0,0 +1 @@
Expr(InParens(Expr(BadExprEnd(@8), @5), @4), @0)

View File

@ -0,0 +1 @@
Expr(BadExprEnd(@11), @0)

View File

@ -1 +1 @@
Expr(Closure(Pattern(PInParens(End(@4), @1), @1), @0), @0)
Expr(Closure(Pattern(PInParens(End(@5), @1), @1), @0), @0)

View File

@ -1 +1 @@
Expr(Closure(Pattern(PInParens(End(@5), @1), @1), @0), @0)
Expr(Closure(Pattern(PInParens(End(@6), @1), @1), @0), @0)

View File

@ -1 +1 @@
Expr(Closure(Pattern(PInParens(End(@2), @1), @1), @0), @0)
Expr(Closure(Pattern(PInParens(End(@3), @1), @1), @0), @0)

View File

@ -1 +1 @@
Expr(Closure(Pattern(PInParens(End(@4), @1), @1), @0), @0)
Expr(Closure(Pattern(PInParens(End(@5), @1), @1), @0), @0)

View File

@ -1 +1 @@
Expr(Type(TRecord(End(@13), @4), @4), @0)
Expr(Type(TRecord(End(@14), @4), @4), @0)

View File

@ -1 +1 @@
Expr(Type(TRecord(End(@5), @4), @4), @0)
Expr(Type(TRecord(End(@6), @4), @4), @0)

View File

@ -1 +1 @@
Expr(Type(TRecord(End(@16), @4), @4), @0)
Expr(Type(TRecord(End(@17), @4), @4), @0)

View File

@ -1 +1 @@
Expr(Type(TTagUnion(End(@9), @4), @4), @0)
Expr(Type(TTagUnion(End(@10), @4), @4), @0)

View File

@ -1 +1 @@
Expr(Type(TTagUnion(End(@5), @4), @4), @0)
Expr(Type(TTagUnion(End(@6), @4), @4), @0)

View File

@ -1 +1 @@
Expr(Type(TInParens(End(@9), @4), @4), @0)
Expr(Type(TInParens(End(@10), @4), @4), @0)

View File

@ -1 +1 @@
Expr(Type(TInParens(End(@5), @4), @4), @0)
Expr(Type(TInParens(End(@6), @4), @4), @0)

View File

@ -1 +1 @@
Expr(Closure(Arrow(@10), @4), @0)
Expr(Closure(IndentArrow(@10), @4), @0)

View File

@ -0,0 +1 @@
Expr(Import(EndNewline(@15), @0), @0)

View File

@ -1 +1 @@
Expr(When(Arrow(@26), @0), @0)
Expr(When(IndentPattern(@26), @0), @0)

View File

@ -1 +1 @@
Expr(BadExprEnd(@22), @0)
Expr(BadOperator("->", @24), @0)

View File

@ -1 +1 @@
Expr(When(Branch(BadOperator("->", @34), @19), @0), @0)
Expr(When(IndentPattern(@34), @0), @0)

View File

@ -1 +1 @@
Expr(When(Branch(BadOperator("->", @28), @19), @0), @0)
Expr(When(IndentPattern(@28), @0), @0)

View File

@ -1 +1 @@
Expr(DefMissingFinalExpr2(ElmStyleFunction(@18-22, @23), @11), @0)
Expr(BadExprEnd(@11), @0)

View File

@ -1 +1 @@
Expr(BadOperator("->", @9), @0)
Expr(BadExprEnd(@8), @0)

View File

@ -0,0 +1,3 @@
when x is
bar.and -> 1
_ -> 4

View File

@ -1,40 +1,45 @@
When(
@5-6 Var {
module_name: "",
ident: "x",
},
SpaceAfter(
When(
@5-6 Var {
module_name: "",
ident: "x",
},
[
WhenBranch {
patterns: [
@14-21 SpaceBefore(
Malformed(
"bar.and",
),
[
Newline,
],
),
],
value: @25-26 Num(
"1",
),
guard: None,
},
WhenBranch {
patterns: [
@31-32 SpaceBefore(
Underscore(
"",
),
[
Newline,
],
),
],
value: @36-37 Num(
"4",
),
guard: None,
},
],
),
[
WhenBranch {
patterns: [
@14-21 SpaceBefore(
Malformed(
"bar.and",
),
[
Newline,
],
),
],
value: @25-26 Num(
"1",
),
guard: None,
},
WhenBranch {
patterns: [
@31-32 SpaceBefore(
Underscore(
"",
),
[
Newline,
],
),
],
value: @36-37 Num(
"4",
),
guard: None,
},
Newline,
],
)

View File

@ -0,0 +1,3 @@
when x is
Foo.and -> 1
_ -> 4

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