PNC for Patterns, stabilize formatting

This commit is contained in:
Anthony Bullard 2024-12-31 08:25:11 -06:00
parent bac165fd99
commit 3b0db07fa1
No known key found for this signature in database
78 changed files with 789 additions and 332 deletions

View file

@ -190,7 +190,7 @@ pub enum FormatProblem {
pub fn format_src(arena: &Bump, src: &str, flags: MigrationFlags) -> Result<String, FormatProblem> {
let ast = arena.alloc(parse_all(arena, src).unwrap_or_else(|e| {
user_error!("Unexpected parse failure when parsing this formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, e)
user_error!("Unexpected parse failure when parsing this formatting:\n\n{src}\n\nParse error was:\n\n{:#?}\n\n", e)
}));
let mut buf = Buf::new_in(arena, flags);
fmt_all(&mut buf, ast);

View file

@ -8,7 +8,7 @@
use roc_error_macros::internal_error;
use roc_module::{called_via::CalledVia, symbol::Symbol};
use roc_parse::ast::{self, Collection};
use roc_parse::ast::{self, Collection, PatternApplyStyle};
use roc_region::all::{Loc, Region};
use crate::{env::Env, pattern::Pattern, scope::Scope};
@ -27,6 +27,7 @@ fn to_encoder<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
DERIVED_REGION,
ast::Pattern::Identifier { ident: payload },
)]),
ast::PatternApplyStyle::Whitespace,
);
// Encode.toEncoder payload
@ -132,6 +133,7 @@ fn hash<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
DERIVED_REGION,
ast::Pattern::Identifier { ident: payload },
)]),
PatternApplyStyle::Whitespace,
);
// Hash.hash hasher payload
@ -178,6 +180,7 @@ fn is_eq<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
DERIVED_REGION,
ast::Pattern::Identifier { ident: payload1 },
)]),
PatternApplyStyle::Whitespace,
);
// \@Opaq payload2
let opaque2 = ast::Pattern::Apply(
@ -186,6 +189,7 @@ fn is_eq<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
DERIVED_REGION,
ast::Pattern::Identifier { ident: payload2 },
)]),
PatternApplyStyle::Whitespace,
);
// Bool.isEq payload1 payload2
@ -232,6 +236,7 @@ fn to_inspector<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
DERIVED_REGION,
ast::Pattern::Identifier { ident: payload },
)]),
PatternApplyStyle::Whitespace,
);
// Inspect.toInspector payload

View file

@ -11,8 +11,9 @@ use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::ModuleName;
use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::{
is_expr_suffixed, AssignedField, Collection, Defs, ModuleImportParams, Pattern, ResultTryKind,
StrLiteral, StrSegment, TryTarget, TypeAnnotation, ValueDef, WhenBranch,
is_expr_suffixed, AssignedField, Collection, Defs, ModuleImportParams, Pattern,
PatternApplyStyle, ResultTryKind, StrLiteral, StrSegment, TryTarget, TypeAnnotation, ValueDef,
WhenBranch,
};
use roc_problem::can::Problem;
use roc_region::all::{Loc, Region};
@ -177,7 +178,11 @@ fn new_op_call_expr<'a>(
env.arena.alloc(Loc::at(left.region, Pattern::Tag("Ok")));
branch_1_patts.push(Loc::at(
left.region,
Pattern::Apply(branch_1_tag, branch_1_patts_args.into_bump_slice()),
Pattern::Apply(
branch_1_tag,
branch_1_patts_args.into_bump_slice(),
PatternApplyStyle::ParensAndCommas,
),
));
let branch_one: &WhenBranch<'_> = env.arena.alloc(WhenBranch {
patterns: branch_1_patts.into_bump_slice(),
@ -198,7 +203,11 @@ fn new_op_call_expr<'a>(
env.arena.alloc(Loc::at(left.region, Pattern::Tag("Err")));
branch_2_patts.push(Loc::at(
right.region,
Pattern::Apply(branch_2_tag, branch_2_patts_args.into_bump_slice()),
Pattern::Apply(
branch_2_tag,
branch_2_patts_args.into_bump_slice(),
PatternApplyStyle::ParensAndCommas,
),
));
let branch_two: &WhenBranch<'_> = env.arena.alloc(WhenBranch {
patterns: branch_2_patts.into_bump_slice(),
@ -1413,7 +1422,7 @@ fn desugar_pattern<'a>(env: &mut Env<'a>, scope: &mut Scope, pattern: Pattern<'a
| MalformedIdent(_, _)
| QualifiedIdentifier { .. } => pattern,
Apply(tag, arg_patterns) => {
Apply(tag, arg_patterns, style) => {
// Skip desugaring the tag, it should either be a Tag or OpaqueRef
let mut desugared_arg_patterns = Vec::with_capacity_in(arg_patterns.len(), env.arena);
for arg_pattern in arg_patterns.iter() {
@ -1423,7 +1432,7 @@ fn desugar_pattern<'a>(env: &mut Env<'a>, scope: &mut Scope, pattern: Pattern<'a
});
}
Apply(tag, desugared_arg_patterns.into_bump_slice())
Apply(tag, desugared_arg_patterns.into_bump_slice(), style)
}
RecordDestructure(field_patterns) => {
RecordDestructure(desugar_record_destructures(env, scope, field_patterns))

View file

@ -411,7 +411,7 @@ pub fn canonicalize_pattern<'a>(
)));
Pattern::UnsupportedPattern(region)
}
Apply(tag, patterns) => {
Apply(tag, patterns, _) => {
let mut can_patterns = Vec::with_capacity(patterns.len());
for loc_pattern in *patterns {
let can_pattern = canonicalize_pattern(

View file

@ -78,6 +78,7 @@ Defs {
ident: "name",
},
],
Whitespace,
),
],
value: @125-154 Apply(

View file

@ -13,14 +13,14 @@ 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 crate::{Buf, MigrationFlags};
use bumpalo::Bump;
use roc_error_macros::internal_error;
use roc_parse::ast::{
AbilityMember, Defs, Expr, ExtractSpaces, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport,
ModuleImportParams, Pattern, Spaceable, Spaces, SpacesAfter, SpacesBefore, StrLiteral,
TypeAnnotation, TypeDef, TypeHeader, ValueDef,
ModuleImportParams, Pattern, PatternApplyStyle, Spaceable, Spaces, SpacesAfter, SpacesBefore,
StrLiteral, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
};
use roc_parse::expr::merge_spaces;
use roc_parse::header::Keyword;
@ -564,6 +564,11 @@ impl<'a> Formattable for TypeHeader<'a> {
_newlines: Newlines,
indent: u16,
) {
let old_flags = buf.flags;
buf.flags = MigrationFlags {
parens_and_commas: false,
..old_flags
};
pattern_fmt_apply(
buf,
Pattern::Tag(self.name.value),
@ -571,7 +576,9 @@ impl<'a> Formattable for TypeHeader<'a> {
Parens::NotNeeded,
indent,
self.vars.iter().any(|v| v.is_multiline()),
PatternApplyStyle::Whitespace,
);
buf.flags = old_flags;
}
}
@ -582,6 +589,7 @@ fn type_head_lift_spaces<'a, 'b: 'a>(
let pat = Pattern::Apply(
arena.alloc(Loc::at(head.name.region, Pattern::Tag(head.name.value))),
head.vars,
PatternApplyStyle::Whitespace,
);
pattern_lift_spaces(arena, &pat)
@ -903,7 +911,9 @@ impl<'a> Formattable for ValueDef<'a> {
fn ann_pattern_needs_parens(value: &Pattern<'_>) -> bool {
match value.extract_spaces().item {
Pattern::Tag(_) => true,
Pattern::Apply(func, _args) if matches!(func.extract_spaces().item, Pattern::Tag(..)) => {
Pattern::Apply(func, _args, _style)
if matches!(func.extract_spaces().item, Pattern::Tag(..)) =>
{
true
}
_ => false,

View file

@ -11,7 +11,7 @@ use crate::spaces::{
use crate::Buf;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_module::called_via::{self, BinOp, UnaryOp};
use roc_module::called_via::{self, BinOp, CalledVia, UnaryOp};
use roc_parse::ast::{
AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces, Pattern, Spaceable,
Spaces, SpacesAfter, SpacesBefore, TryTarget, WhenBranch,
@ -96,8 +96,9 @@ fn format_expr_only(
}
Expr::Apply(loc_expr, loc_args, _) => {
let apply_needs_parens = parens == Parens::InApply || parens == Parens::InApplyLastArg;
if apply_needs_parens && !loc_args.is_empty() {
if buf.flags().parens_and_commas {
fmt_apply(loc_expr, loc_args, indent, buf, false);
} else if apply_needs_parens && !loc_args.is_empty() {
fmt_parens(item, buf, indent);
} else {
fmt_apply(loc_expr, loc_args, indent, buf, false);
@ -716,7 +717,9 @@ fn fmt_apply(
let is_first_arg = i == 0;
let arg = expr_lift_spaces(
if is_last_arg {
if use_commas_and_parens {
Parens::NotNeeded
} else if is_last_arg {
Parens::InApplyLastArg
} else {
Parens::InApply
@ -1038,7 +1041,7 @@ pub fn expr_lift_spaces<'a, 'b: 'a>(
) -> Spaces<'a, Expr<'a>> {
match expr {
Expr::Apply(func, args, called_via) => {
if args.is_empty() {
if args.is_empty() && !matches!(called_via, CalledVia::ParensAndCommas) {
return expr_lift_spaces(Parens::NotNeeded, arena, &func.value);
}
@ -1082,7 +1085,9 @@ pub fn expr_lift_spaces<'a, 'b: 'a>(
}
};
if parens == Parens::InApply || parens == Parens::InApplyLastArg {
if (parens == Parens::InApply || parens == Parens::InApplyLastArg)
&& !matches!(called_via, CalledVia::ParensAndCommas)
{
res = Spaces {
before: &[],
item: Expr::ParensAround(arena.alloc(lower(arena, res))),

View file

@ -7,7 +7,8 @@ use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT};
use crate::Buf;
use bumpalo::Bump;
use roc_parse::ast::{
Base, CommentOrNewline, Pattern, PatternAs, Spaceable, Spaces, SpacesAfter, SpacesBefore,
Base, CommentOrNewline, Pattern, PatternApplyStyle, PatternAs, Spaceable, Spaces, SpacesAfter,
SpacesBefore,
};
use roc_parse::expr::merge_spaces;
use roc_region::all::Loc;
@ -72,7 +73,7 @@ impl<'a> Formattable for Pattern<'a> {
}
},
Pattern::StrLiteral(literal) => is_str_multiline(literal),
Pattern::Apply(pat, args) => {
Pattern::Apply(pat, args, _) => {
pat.is_multiline() || args.iter().any(|a| a.is_multiline())
}
@ -162,7 +163,22 @@ fn fmt_pattern_only(
buf.indent(indent);
buf.push_str(name);
}
Pattern::Apply(loc_pattern, loc_arg_patterns) => {
Pattern::Apply(
loc_pattern,
loc_arg_patterns,
style @ PatternApplyStyle::ParensAndCommas,
) => {
pattern_fmt_apply(
buf,
loc_pattern.value,
loc_arg_patterns,
Parens::NotNeeded,
indent,
is_multiline,
*style,
);
}
Pattern::Apply(loc_pattern, loc_arg_patterns, style) => {
pattern_fmt_apply(
buf,
loc_pattern.value,
@ -170,6 +186,7 @@ fn fmt_pattern_only(
parens,
indent,
is_multiline,
*style,
);
}
Pattern::RecordDestructure(loc_patterns) => {
@ -445,11 +462,14 @@ pub fn pattern_fmt_apply(
parens: Parens,
indent: u16,
is_multiline: bool,
style: PatternApplyStyle,
) {
let use_commas_and_parens =
matches!(style, PatternApplyStyle::ParensAndCommas) || buf.flags().parens_and_commas;
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 = !args.is_empty() && parens == Parens::InApply;
let parens = !args.is_empty() && parens == Parens::InApply && !use_commas_and_parens;
let indent_more = if is_multiline {
indent + INDENT
@ -473,14 +493,27 @@ pub fn pattern_fmt_apply(
fmt_pattern_only(&func.item, buf, Parens::InApply, indent, is_multiline);
if use_commas_and_parens {
buf.push('(');
}
let mut last_after = func.after;
let mut add_newlines = is_multiline;
for loc_arg in args.iter() {
buf.spaces(1);
for (i, loc_arg) in args.iter().enumerate() {
let is_last_arg = i == args.len() - 1;
let is_first_arg = i == 0;
let parens = Parens::InApply;
if !(is_first_arg && use_commas_and_parens) {
buf.spaces(1);
}
let parens = if use_commas_and_parens {
Parens::NotNeeded
} else {
Parens::InApply
};
let arg = pattern_lift_spaces(buf.text.bump(), &loc_arg.value);
let mut was_multiline = arg.item.is_multiline();
@ -527,6 +560,9 @@ pub fn pattern_fmt_apply(
buf.push_str("(implements)");
} else {
fmt_pattern_only(&arg.item, buf, parens, indent_more, arg.item.is_multiline());
if use_commas_and_parens && (!is_last_arg || add_newlines) {
buf.push(',');
}
}
last_after = arg.after;
@ -542,7 +578,7 @@ pub fn pattern_fmt_apply(
}
}
if parens {
if parens || use_commas_and_parens {
buf.push(')');
}
}
@ -597,7 +633,7 @@ fn pattern_prec(pat: Pattern<'_>) -> Prec {
| Pattern::Tuple(..)
| Pattern::List(..)
| Pattern::ListRest(_) => Prec::Term,
Pattern::Apply(_, _) | Pattern::As(_, _) => Prec::Apply,
Pattern::Apply(_, _, _) | Pattern::As(_, _) => Prec::Apply,
Pattern::SpaceBefore(inner, _) | Pattern::SpaceAfter(inner, _) => pattern_prec(*inner),
Pattern::Malformed(_) | Pattern::MalformedIdent(..) => Prec::Term,
}
@ -617,7 +653,7 @@ pub fn pattern_lift_spaces<'a, 'b: 'a>(
pat: &Pattern<'b>,
) -> Spaces<'a, Pattern<'a>> {
match pat {
Pattern::Apply(func, args) => {
Pattern::Apply(func, args, style) => {
let func_lifted = pattern_lift_spaces(arena, &func.value);
let args = arena.alloc_slice_copy(args);
@ -652,7 +688,7 @@ pub fn pattern_lift_spaces<'a, 'b: 'a>(
};
Spaces {
before,
item: Pattern::Apply(arena.alloc(func), args),
item: Pattern::Apply(arena.alloc(func), args, *style),
after,
}
}
@ -712,7 +748,7 @@ fn handle_multiline_str_spaces<'a>(pat: &Pattern<'_>, before: &mut &'a [CommentO
fn starts_with_block_str(item: &Pattern<'_>) -> bool {
match item {
Pattern::As(inner, _) | Pattern::Apply(inner, _) => starts_with_block_str(&inner.value),
Pattern::As(inner, _) | Pattern::Apply(inner, _, _) => starts_with_block_str(&inner.value),
Pattern::SpaceBefore(inner, _) | Pattern::SpaceAfter(inner, _) => {
starts_with_block_str(inner)
}

View file

@ -1745,6 +1745,12 @@ impl<'a> PatternAs<'a> {
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PatternApplyStyle {
Whitespace,
ParensAndCommas,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Pattern<'a> {
// Identifier
@ -1760,7 +1766,11 @@ pub enum Pattern<'a> {
OpaqueRef(&'a str),
Apply(&'a Loc<Pattern<'a>>, &'a [Loc<Pattern<'a>>]),
Apply(
&'a Loc<Pattern<'a>>,
&'a [Loc<Pattern<'a>>],
PatternApplyStyle,
),
/// This is Located<Pattern> rather than Located<str> so we can record comments
/// around the destructured names, e.g. { x ### x does stuff ###, y }
@ -1841,8 +1851,8 @@ impl<'a> Pattern<'a> {
false
}
}
Apply(constructor_x, args_x) => {
if let Apply(constructor_y, args_y) = other {
Apply(constructor_x, args_x, _) => {
if let Apply(constructor_y, args_y, _) = other {
let equivalent_args = args_x
.iter()
.zip(args_y.iter())
@ -2670,7 +2680,7 @@ impl<'a> Malformed for Pattern<'a> {
Identifier{ .. } |
Tag(_) |
OpaqueRef(_) => false,
Apply(func, args) => func.is_malformed() || args.iter().any(|arg| arg.is_malformed()),
Apply(func, args, _) => func.is_malformed() || args.iter().any(|arg| arg.is_malformed()),
RecordDestructure(items) => items.iter().any(|item| item.is_malformed()),
RequiredField(_, pat) => pat.is_malformed(),
OptionalField(_, expr) => expr.is_malformed(),

View file

@ -2,8 +2,8 @@ use crate::ast::{
is_expr_suffixed, AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces,
Implements, ImplementsAbilities, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport,
ModuleImportParams, Pattern, Spaceable, Spaced, Spaces, SpacesBefore, TryTarget,
TypeAnnotation, TypeDef, TypeHeader, ValueDef,
ModuleImportParams, Pattern, PatternApplyStyle, Spaceable, Spaced, Spaces, SpacesBefore,
TryTarget, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
};
use crate::blankspace::{
loc_space0_e, require_newline_or_eof, space0_after_e, space0_around_ee, space0_before_e,
@ -181,7 +181,7 @@ fn loc_term_or_underscore_or_conditional<'a>(
}
}
loc_term_or_underscore(check_for_arrow).parse(arena, state, min_indent)
loc_term_or_closure(check_for_arrow).parse(arena, state, min_indent)
}
}
fn loc_conditional<'a>(
@ -198,9 +198,22 @@ fn loc_conditional<'a>(
/// In some contexts we want to parse the `_` as an expression, so it can then be turned into a
/// pattern later
fn loc_term_or_underscore<'a>(
fn loc_term_or_closure<'a>(
check_for_arrow: CheckForArrow,
) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
one_of!(
loc(specialize_err(
EExpr::Closure,
closure_help(check_for_arrow)
)),
loc_term(),
)
.trace("term_or_underscore")
}
fn loc_term<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
map_with_arena(
and(
one_of!(
loc_expr_in_parens_etc_help(),
loc(specialize_err(EExpr::Str, string_like_literal_help())),
@ -208,74 +221,57 @@ fn loc_term_or_underscore<'a>(
EExpr::Number,
positive_number_literal_help()
)),
loc(specialize_err(
EExpr::Closure,
closure_help(check_for_arrow)
)),
loc(crash_kw()),
loc(specialize_err(EExpr::Dbg, dbg_kw())),
loc(try_kw()),
loc(underscore_expression()),
loc(record_literal_help()),
loc(specialize_err(EExpr::List, list_literal_help())),
loc(apply_with_pnc()),
ident_seq(),
),
optional(pnc_args()),
),
|arena, (expr, maybe_arg_loc)| {
if let Some(arg_loc) = maybe_arg_loc {
let e = Expr::Apply(arena.alloc(expr), arg_loc.value, CalledVia::ParensAndCommas);
Loc {
value: e,
region: Region::span_across(&expr.region, &arg_loc.region),
}
} else {
expr
}
},
)
.trace("term_or_underscore")
.trace("term")
}
fn loc_term<'a>(check_for_arrow: CheckForArrow) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
one_of!(
loc_expr_in_parens_etc_help(),
loc(specialize_err(EExpr::Str, string_like_literal_help())),
loc(specialize_err(
EExpr::Number,
positive_number_literal_help()
)),
loc(specialize_err(
EExpr::Closure,
closure_help(check_for_arrow)
)),
loc(crash_kw()),
loc(specialize_err(EExpr::Dbg, dbg_kw())),
loc(try_kw()),
loc(record_literal_help()),
loc(specialize_err(EExpr::List, list_literal_help())),
loc(apply_with_pnc()),
ident_seq(),
)
}
fn apply_with_pnc<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
fn pnc_args<'a>() -> impl Parser<'a, Loc<&'a [&'a Loc<Expr<'a>>]>, EExpr<'a>> {
|arena: &'a Bump, state: State<'a>, min_indent: u32| {
let (_, ident, new_state) = ident_seq().parse(arena, state, min_indent)?;
let (_, args, newer_state) = specialize_err(
map_with_arena(
specialize_err(
EExpr::InParens,
collection_trailing_sep_e(
loc(collection_trailing_sep_e(
byte(b'(', EInParens::Open),
specialize_err_ref(EInParens::Expr, loc_expr_block(false, true)),
specialize_err_ref(EInParens::Expr, loc_expr_block(true)),
byte(b',', EInParens::End),
byte(b')', EInParens::End),
Expr::SpaceBefore,
)),
),
)
.parse(arena, new_state, min_indent)?;
let arg_slice: &[&Loc<Expr<'a>>] = {
|arena, arg_loc| {
let mut args_vec = Vec::new_in(arena);
for arg in args.items.iter() {
for arg in arg_loc.value.items.iter() {
let a: &Loc<Expr<'a>> = arena.alloc(arg);
args_vec.push(a);
}
args_vec.into_bump_slice()
};
Ok((
Progress::MadeProgress,
Expr::Apply(arena.alloc(ident), arg_slice, CalledVia::ParensAndCommas),
newer_state,
))
Loc {
value: args_vec.into_bump_slice(),
region: arg_loc.region,
}
},
)
.parse(arena, state, min_indent)
}
}
@ -1743,7 +1739,8 @@ fn parse_negated_term<'a>(
initial_state: State<'a>,
loc_op: Loc<BinOp>,
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let (_, negated_expr, state) = loc_term(check_for_arrow).parse(arena, state, min_indent)?;
let (_, negated_expr, state) =
loc_term_or_closure(check_for_arrow).parse(arena, state, min_indent)?;
let new_end = state.pos();
let arg = numeric_negate_expression(
@ -1793,7 +1790,7 @@ fn parse_expr_end<'a>(
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let parser = skip_first(
crate::blankspace::check_indent(EExpr::IndentEnd),
loc_term_or_underscore(check_for_arrow),
loc_term_or_closure(check_for_arrow),
);
match parser.parse(arena, state.clone(), call_min_indent) {
@ -2089,7 +2086,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
Expr::Underscore(opt_name) => Pattern::Underscore(opt_name),
Expr::Tag(value) => Pattern::Tag(value),
Expr::OpaqueRef(value) => Pattern::OpaqueRef(value),
Expr::Apply(loc_val, loc_args, _) => {
Expr::Apply(loc_val, loc_args, called_via) => {
let region = loc_val.region;
let value = expr_to_pattern_help(arena, &loc_val.value)?;
let val_pattern = arena.alloc(Loc { region, value });
@ -2103,7 +2100,15 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
arg_patterns.push(Loc { region, value });
}
let pattern = Pattern::Apply(val_pattern, arg_patterns.into_bump_slice());
let pattern = Pattern::Apply(
val_pattern,
arg_patterns.into_bump_slice(),
if matches!(called_via, CalledVia::ParensAndCommas) {
PatternApplyStyle::ParensAndCommas
} else {
PatternApplyStyle::Whitespace
},
);
pattern
}
@ -3277,7 +3282,7 @@ fn starts_with_spaces_conservative(value: &Pattern<'_>) -> bool {
| Pattern::ListRest(_)
| Pattern::OpaqueRef(_) => false,
Pattern::As(left, _) => starts_with_spaces_conservative(&left.value),
Pattern::Apply(left, _) => starts_with_spaces_conservative(&left.value),
Pattern::Apply(left, _, _) => starts_with_spaces_conservative(&left.value),
Pattern::RecordDestructure(_) => false,
Pattern::RequiredField(_, _) | Pattern::OptionalField(_, _) => false,
Pattern::SpaceBefore(_, _) => true,
@ -3293,6 +3298,7 @@ fn header_to_pat<'a>(arena: &'a Bump, header: TypeHeader<'a>) -> Pattern<'a> {
Pattern::Apply(
arena.alloc(Loc::at(header.name.region, Pattern::Tag(header.name.value))),
header.vars,
PatternApplyStyle::Whitespace,
)
}
}
@ -3343,7 +3349,7 @@ fn pat_ends_with_spaces_conservative(pat: &Pattern<'_>) -> bool {
| Pattern::ListRest(_)
| Pattern::As(_, _)
| Pattern::OpaqueRef(_) => false,
Pattern::Apply(_, args) => args
Pattern::Apply(_, args, _) => args
.last()
.map_or(false, |a| pat_ends_with_spaces_conservative(&a.value)),
Pattern::RecordDestructure(_) => false,
@ -3365,7 +3371,7 @@ pub fn join_alias_to_body<'a>(
body_expr: &'a Loc<Expr<'a>>,
) -> ValueDef<'a> {
let loc_name = arena.alloc(header.name.map(|x| Pattern::Tag(x)));
let ann_pattern = Pattern::Apply(loc_name, header.vars);
let ann_pattern = Pattern::Apply(loc_name, header.vars, PatternApplyStyle::Whitespace);
let vars_region = Region::across_all(header.vars.iter().map(|v| &v.region));
let region_ann_pattern = Region::span_across(&loc_name.region, &vars_region);

View file

@ -879,9 +879,10 @@ impl<'a> Normalize<'a> for Pattern<'a> {
Pattern::Identifier { ident } => Pattern::Identifier { ident },
Pattern::Tag(a) => Pattern::Tag(a),
Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a),
Pattern::Apply(a, b) => Pattern::Apply(
Pattern::Apply(a, b, c) => Pattern::Apply(
arena.alloc(a.normalize(arena)),
arena.alloc(b.normalize(arena)),
c,
),
Pattern::RecordDestructure(a) => Pattern::RecordDestructure(a.normalize(arena)),
Pattern::RequiredField(a, b) => {
@ -1257,6 +1258,8 @@ impl<'a> Normalize<'a> for EPattern<'a> {
EPattern::AccessorFunction(_) => EPattern::AccessorFunction(Position::zero()),
EPattern::RecordUpdaterFunction(_) => EPattern::RecordUpdaterFunction(Position::zero()),
EPattern::Str(e, _) => EPattern::Str(e.normalize(arena), Position::zero()),
EPattern::ParenStart(_) => EPattern::ParenStart(Position::zero()),
EPattern::ParenEnd(_) => EPattern::ParenEnd(Position::zero()),
}
}
}

View file

@ -1066,6 +1066,9 @@ pub enum EPattern<'a> {
AccessorFunction(Position),
RecordUpdaterFunction(Position),
Str(EString<'a>, Position),
ParenStart(Position),
ParenEnd(Position),
}
impl<'a> EPattern<'a> {
@ -1090,7 +1093,9 @@ impl<'a> EPattern<'a> {
| EPattern::IndentEnd(position)
| EPattern::AsIndentStart(position)
| EPattern::AccessorFunction(position)
| EPattern::RecordUpdaterFunction(position) => Region::from_pos(*position),
| EPattern::RecordUpdaterFunction(position)
| EPattern::ParenStart(position)
| EPattern::ParenEnd(position) => Region::from_pos(*position),
}
}
}

View file

@ -1,11 +1,13 @@
use crate::ast::{Collection, ExtractSpaces, Implements, Pattern, PatternAs, Spaceable};
use crate::blankspace::{space0_e, spaces, spaces_before};
use crate::ast::{
Collection, ExtractSpaces, Implements, Pattern, PatternApplyStyle, PatternAs, Spaceable,
};
use crate::blankspace::{space0_before_optional_after, space0_e, spaces, spaces_before};
use crate::ident::{lowercase_ident, parse_ident, Accessor, Ident};
use crate::keyword;
use crate::parser::{
self, backtrackable, byte, collection_trailing_sep_e, fail_when, loc, map, optional,
skip_first, specialize_err, specialize_err_ref, then, three_bytes, two_bytes, zero_or_more,
EPattern, PInParens, PList, PRecord, Parser,
self, backtrackable, byte, collection_trailing_sep_e, fail_when, loc, map, map_with_arena,
optional, skip_first, skip_second, specialize_err, specialize_err_ref, then, three_bytes,
two_bytes, zero_or_more, EPattern, PInParens, PList, PRecord, Parser,
};
use crate::parser::{either, Progress::*};
use crate::state::State;
@ -85,15 +87,16 @@ fn loc_pattern_help_help<'a>(
) -> impl Parser<'a, Loc<Pattern<'a>>, EPattern<'a>> {
one_of!(
specialize_err(EPattern::PInParens, loc_pattern_in_parens_help()),
loc(string_like_pattern_help()),
loc(number_pattern_help()),
loc(underscore_pattern_help()),
loc_ident_pattern_help(can_have_arguments),
loc(specialize_err(
EPattern::Record,
crate::pattern::record_pattern_help()
)),
loc(specialize_err(EPattern::List, list_pattern_help())),
loc(number_pattern_help()),
loc(string_like_pattern_help()),
// Move my pnc tag apply parser here
loc_ident_pattern_help(can_have_arguments),
)
}
@ -121,10 +124,6 @@ fn pattern_as<'a>() -> impl Parser<'a, PatternAs<'a>, EPattern<'a>> {
}
}
fn loc_tag_pattern_args_help<'a>() -> impl Parser<'a, Vec<'a, Loc<Pattern<'a>>>, EPattern<'a>> {
zero_or_more(loc_tag_pattern_arg(false))
}
/// Like `loc_tag_pattern_args_help`, but stops if a "implements" keyword is seen (indicating an ability).
fn loc_type_def_tag_pattern_args_help<'a>(
) -> impl Parser<'a, Vec<'a, Loc<Pattern<'a>>>, EPattern<'a>> {
@ -339,6 +338,37 @@ fn loc_ident_pattern_help<'a>(
let (_, loc_ident, state) = specialize_err(|_, pos| EPattern::Start(pos), loc(parse_ident))
.parse(arena, state, min_indent)?;
let commas_and_paren_args_help = map_with_arena(
collection_trailing_sep_e(
byte(b'(', EPattern::ParenStart),
loc_tag_pattern_arg(false),
byte(b',', EPattern::NotAPattern),
byte(b')', EPattern::ParenEnd),
Pattern::SpaceBefore,
),
|arena, args| {
let mut args_vec = Vec::new_in(arena);
for arg in args.iter() {
let a: &Loc<Pattern<'a>> = arena.alloc(arg);
args_vec.push(*a);
}
(
args_vec.into_bump_slice(),
PatternApplyStyle::ParensAndCommas,
)
},
);
let whitespace_args =
map_with_arena(loc_type_def_tag_pattern_args_help(), |arena, args| {
let mut args_vec = Vec::new_in(arena);
for arg in args.iter() {
let a: &Loc<Pattern<'a>> = arena.alloc(arg);
args_vec.push(*a);
}
(args_vec.into_bump_slice(), PatternApplyStyle::Whitespace)
});
match loc_ident.value {
Ident::Tag(tag) => {
let loc_tag = Loc {
@ -347,9 +377,18 @@ fn loc_ident_pattern_help<'a>(
};
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
if can_have_arguments {
let (_, loc_args, state) =
loc_type_def_tag_pattern_args_help().parse(arena, state, min_indent)?;
let (_, (args, style), state) = if can_have_arguments {
one_of!(commas_and_paren_args_help, whitespace_args)
.parse(arena, state, min_indent)?
} else {
match commas_and_paren_args_help.parse(arena, state.clone(), min_indent) {
Ok((_, res, new_state)) => (MadeProgress, res, new_state),
Err((NoProgress, _)) => return Ok((MadeProgress, loc_tag, state)),
Err((MadeProgress, e)) => return Err((MadeProgress, e)),
}
};
let loc_args: &[Loc<Pattern<'_>>] = { args };
if loc_args.is_empty() {
Ok((MadeProgress, loc_tag, state))
@ -358,14 +397,10 @@ fn loc_ident_pattern_help<'a>(
std::iter::once(&loc_ident.region)
.chain(loc_args.iter().map(|loc_arg| &loc_arg.region)),
);
let value =
Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
let value = Pattern::Apply(&*arena.alloc(loc_tag), loc_args, style);
Ok((MadeProgress, Loc { region, value }, state))
}
} else {
Ok((MadeProgress, loc_tag, state))
}
}
Ident::OpaqueRef(name) => {
let loc_pat = Loc {
@ -374,9 +409,14 @@ fn loc_ident_pattern_help<'a>(
};
// Make sure `@Foo Bar 1` is parsed as `@Foo (Bar) 1`, and not `@Foo (Bar 1)`
if can_have_arguments {
let (_, loc_args, state) =
loc_tag_pattern_args_help().parse(arena, state, min_indent)?;
let (_, (args, style), state) = if can_have_arguments {
one_of!(commas_and_paren_args_help, whitespace_args)
.parse(arena, state, min_indent)?
} else {
commas_and_paren_args_help.parse(arena, state, min_indent)?
};
let loc_args: &[Loc<Pattern<'_>>] = { args };
if loc_args.is_empty() {
Ok((MadeProgress, loc_pat, state))
@ -385,14 +425,10 @@ fn loc_ident_pattern_help<'a>(
std::iter::once(&loc_ident.region)
.chain(loc_args.iter().map(|loc_arg| &loc_arg.region)),
);
let value =
Pattern::Apply(&*arena.alloc(loc_pat), loc_args.into_bump_slice());
let value = Pattern::Apply(&*arena.alloc(loc_pat), loc_args, style);
Ok((MadeProgress, Loc { region, value }, state))
}
} else {
Ok((MadeProgress, loc_pat, state))
}
}
Ident::Access {
module_name, parts, ..
@ -589,3 +625,90 @@ fn record_pattern_field<'a>() -> impl Parser<'a, Loc<Pattern<'a>>, PRecord<'a>>
}
}
}
pub fn pattern_end<'a>() -> impl Parser<'a, (), EPattern<'a>> {
|_arena, state: State<'a>, _min_indent: u32| {
if state.has_reached_end() {
Ok((NoProgress, (), state))
} else {
Err((NoProgress, EPattern::End(state.pos())))
}
}
}
pub fn test_parse_pattern<'a>(
min_indent: u32,
arena: &'a bumpalo::Bump,
state: State<'a>,
) -> Result<Loc<Pattern<'a>>, EPattern<'a>> {
let parser = skip_second(
space0_before_optional_after(
loc_pattern_help(),
EPattern::IndentStart,
EPattern::IndentEnd,
),
pattern_end(),
);
match parser.parse(arena, state, min_indent) {
Ok((_, expression, _)) => Ok(expression),
Err((_, fail)) => Err(fail),
}
}
#[cfg(test)]
mod test_parse_pattern {
use super::*;
use pretty_assertions::assert_eq;
use roc_region::all::Position;
fn new_region(start: u32, end: u32) -> Region {
Region::new(Position::new(start), Position::new(end))
}
#[test]
fn test_parse_ident_tag() {
let arena = Bump::new();
let state = State::new("Ok a".as_ref());
let min_indent = 0;
let (_, res, _) = loc_pattern_help().parse(&arena, state, min_indent).unwrap();
let expected_tag = Loc {
value: Pattern::Tag("Ok"),
region: new_region(0, 2),
};
let expected_args = [Loc {
value: Pattern::Identifier { ident: "a" },
region: new_region(3, 4),
}];
let expected = Loc {
value: Pattern::Apply(&expected_tag, &expected_args, PatternApplyStyle::Whitespace),
region: new_region(0, 4),
};
assert_eq!(format!("{res:#?}"), format!("{expected:#?}"));
}
#[test]
fn test_parse_ident_tag_pnc() {
let arena = Bump::new();
let state = State::new("Ok(a)".as_ref());
let min_indent = 0;
let (_, res, _) = loc_pattern_help().parse(&arena, state, min_indent).unwrap();
let expected_tag = Loc {
value: Pattern::Tag("Ok"),
region: new_region(0, 2),
};
let expected_args = [Loc {
value: Pattern::Identifier { ident: "a" },
region: new_region(3, 4),
}];
let expected = Loc {
value: Pattern::Apply(
&expected_tag,
&expected_args,
PatternApplyStyle::ParensAndCommas,
),
region: new_region(0, 4),
};
assert_eq!(format!("{res:#?}"), format!("{expected:#?}"));
}
}

View file

@ -1,6 +1,7 @@
use crate::ast;
use crate::ast::Defs;
use crate::ast::Header;
use crate::ast::Pattern;
use crate::ast::SpacesBefore;
use crate::header::parse_module_defs;
use crate::parser::SourceError;
@ -32,6 +33,18 @@ pub fn parse_loc_with<'a>(
}
}
pub fn parse_pattern_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<Loc<Pattern<'a>>, SourceError<'a, SyntaxError<'a>>> {
let state = State::new(input.as_bytes());
match crate::pattern::test_parse_pattern(0, arena, state.clone()) {
Ok(loc_patt) => Ok(loc_patt),
Err(fail) => Err(SyntaxError::Pattern(fail).into_source_error(&state)),
}
}
pub fn parse_defs_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Defs<'a>, SyntaxError<'a>> {
let state = State::new(input.as_bytes());

View file

@ -10,6 +10,8 @@
//!
//! Note that `--release` is important, as this tool is very slow in debug mode.
use std::io::Read;
use test_syntax::{
minimize::{print_minimizations, Options},
test_helpers::InputKind,
@ -18,7 +20,7 @@ use test_syntax::{
fn main() {
let args = std::env::args().collect::<Vec<String>>();
if args.len() < 3 || args.len() > 5 {
eprintln!("Usage: {} [--minimize-full-error] [--minimize-initial-parse-error] [expr|full|moduledefs|header] <input>", args[0]);
eprintln!("Usage: {} [--minimize-full-error] [--minimize-initial-parse-error] [expr|full|moduledefs|header|pattern] <input>", args[0]);
std::process::exit(1);
}
@ -46,13 +48,21 @@ fn main() {
"full" => InputKind::Full,
"moduledefs" => InputKind::ModuleDefs,
"header" => InputKind::Header,
"pattern" => InputKind::Pattern,
_ => {
eprintln!("Invalid input kind: {}", args[index]);
std::process::exit(1);
}
};
let input = &args[index + 1];
let mut buf = String::new();
let text = if input == "-" {
std::io::stdin().read_to_string(&mut buf).unwrap();
buf.to_string()
} else {
std::fs::read_to_string(input).unwrap()
};
let text = std::fs::read_to_string(&args[index + 1]).unwrap();
let found_error = print_minimizations(&text, options);
std::process::exit(if found_error { 0 } else { 1 });
}

View file

@ -9,13 +9,13 @@ use roc_error_macros::set_panic_not_exit;
use roc_fmt::{annotation::Formattable, header::fmt_header, MigrationFlags};
use roc_module::ident::QualifiedModuleName;
use roc_module::symbol::{IdentIds, Interns, ModuleIds, PackageModuleIds, Symbol};
use roc_parse::ast::RecursiveValueDefIter;
use roc_parse::ast::ValueDef;
use roc_parse::ast::{Pattern, RecursiveValueDefIter};
use roc_parse::header::parse_module_defs;
use roc_parse::parser::Parser;
use roc_parse::parser::SyntaxError;
use roc_parse::state::State;
use roc_parse::test_helpers::parse_loc_with;
use roc_parse::test_helpers::{parse_loc_with, parse_pattern_with};
use roc_parse::{ast::Malformed, normalize::Normalize};
use roc_parse::{
ast::{Defs, Expr, FullAst, Header, SpacesBefore},
@ -45,6 +45,9 @@ pub enum Input<'a> {
/// Both the header and the module defs
Full(&'a str),
/// A single pattern
Pattern(&'a str),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -53,6 +56,7 @@ pub enum InputKind {
ModuleDefs,
Expr,
Full,
Pattern,
}
impl InputKind {
@ -62,6 +66,7 @@ impl InputKind {
InputKind::ModuleDefs => Input::ModuleDefs(text),
InputKind::Expr => Input::Expr(text),
InputKind::Full => Input::Full(text),
InputKind::Pattern => Input::Pattern(text),
}
}
}
@ -73,6 +78,7 @@ pub enum InputOwned {
ModuleDefs(String),
Expr(String),
Full(String),
Pattern(String),
}
impl InputOwned {
@ -82,6 +88,7 @@ impl InputOwned {
InputOwned::ModuleDefs(s) => Input::ModuleDefs(s),
InputOwned::Expr(s) => Input::Expr(s),
InputOwned::Full(s) => Input::Full(s),
InputOwned::Pattern(s) => Input::Pattern(s),
}
}
}
@ -96,6 +103,8 @@ pub enum Output<'a> {
Expr(Loc<Expr<'a>>),
Full(FullAst<'a>),
Pattern(Loc<Pattern<'a>>),
}
impl<'a> Output<'a> {
@ -127,6 +136,10 @@ impl<'a> Output<'a> {
buf.fmt_end_of_file();
InputOwned::Full(buf.as_str().to_string())
}
Output::Pattern(patt) => {
patt.format(&mut buf, 0);
InputOwned::Pattern(buf.as_str().to_string())
}
}
}
@ -136,6 +149,7 @@ impl<'a> Output<'a> {
Output::ModuleDefs(defs) => format!("{defs:#?}\n"),
Output::Expr(expr) => format!("{expr:#?}\n"),
Output::Full { .. } => format!("{self:#?}\n"),
Output::Pattern(patt) => format!("{patt:#?}\n"),
}
}
@ -150,6 +164,7 @@ impl<'a> Output<'a> {
Output::Full(_) => {
// TODO: canonicalize full ast
}
Output::Pattern(_) => {}
Output::Expr(loc_expr) => {
let mut var_store = VarStore::default();
let mut imported: Vec<(QualifiedModuleName, Region)> = vec![];
@ -248,6 +263,7 @@ impl<'a> Malformed for Output<'a> {
Output::ModuleDefs(defs) => defs.is_malformed(),
Output::Expr(expr) => expr.is_malformed(),
Output::Full(full) => full.is_malformed(),
Output::Pattern(patt) => patt.is_malformed(),
}
}
}
@ -259,6 +275,7 @@ impl<'a> Normalize<'a> for Output<'a> {
Output::ModuleDefs(defs) => Output::ModuleDefs(defs.normalize(arena)),
Output::Expr(expr) => Output::Expr(expr.normalize(arena)),
Output::Full(full) => Output::Full(full.normalize(arena)),
Output::Pattern(patt) => Output::Pattern(patt.normalize(arena)),
}
}
}
@ -270,6 +287,7 @@ impl<'a> Input<'a> {
Input::ModuleDefs(s) => s,
Input::Expr(s) => s,
Input::Full(s) => s,
Input::Pattern(s) => s,
}
}
@ -308,6 +326,11 @@ impl<'a> Input<'a> {
Ok(Output::Full(FullAst { header, defs }))
}
Input::Pattern(input) => {
let patt = parse_pattern_with(arena, input).map_err(|e| e.problem)?;
Ok(Output::Pattern(patt))
}
}
}
@ -325,9 +348,11 @@ impl<'a> Input<'a> {
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);
panic!("Unexpected parse failure when parsing this for formatting:\n\n{}\n\nParse error was:\n\n{:#?}\n\n", self.as_str(), err);
});
println!("Actual {actual:#?}");
let output = actual.format();
handle_formatted_output(output.as_ref());

View file

@ -21,6 +21,7 @@
"E",
),
[],
Whitespace,
),
ann_type: @2-3 Apply(
"",

View file

@ -26,6 +26,7 @@
ident: "x",
},
],
Whitespace,
),
ann_type: @11-25 TagUnion {
ext: None,
@ -54,6 +55,7 @@
ident: "x",
},
],
Whitespace,
),
body_expr: @37-46 Apply(
@37-43 Tag(

View file

@ -0,0 +1,16 @@
@0-4 SpaceAfter(
Apply(
@0-2 Tag(
"Ok",
),
[
@3-4 Identifier {
ident: "a",
},
],
ParensAndCommas,
),
[
Newline,
],
)

View file

@ -0,0 +1,16 @@
@0-4 SpaceAfter(
Apply(
@0-2 Tag(
"Ok",
),
[
@3-4 Identifier {
ident: "a",
},
],
Whitespace,
),
[
Newline,
],
)

View file

@ -27,6 +27,7 @@
ident: "inner",
},
],
Whitespace,
),
PatternAs {
spaces_before: [],

View file

@ -38,6 +38,7 @@
},
),
],
Whitespace,
),
],
@17-18 SpaceBefore(

View file

@ -20,6 +20,7 @@
],
),
],
ParensAndCommas,
),
],
@11-13 Num(

View file

@ -16,6 +16,7 @@
],
),
],
Whitespace,
),
],
@8-10 UnaryOp(

View file

@ -19,6 +19,7 @@
],
),
],
Whitespace,
),
],
@10-12 Var {

View file

@ -16,7 +16,7 @@
type_defs: [],
value_defs: [
Annotation(
@0-1 Apply(
@0-10 Apply(
@0-1 NumLiteral(
"1",
),
@ -35,6 +35,7 @@
],
),
],
ParensAndCommas,
),
@11-13 BoundVariable(
"gi",

View file

@ -1,5 +1,5 @@
1
(0 #
0)
1(
0( #
0,),)
f : f
t

View file

@ -17,7 +17,8 @@
type_defs: [],
value_defs: [
Annotation(
@0-1 Apply(
@0-12 Apply(
@0-12 Apply(
@0-1 NumLiteral(
"1",
),
@ -39,15 +40,21 @@
],
),
],
ParensAndCommas,
),
[
Newline,
],
),
],
ParensAndCommas,
),
[
@12-13 Identifier {
ident: "f",
},
],
Whitespace,
),
@14-15 BoundVariable(
"f",

View file

@ -16,7 +16,8 @@
type_defs: [],
value_defs: [
Annotation(
@0-1 Apply(
@0-15 Apply(
@0-15 Apply(
@0-1 NumLiteral(
"1",
),
@ -40,15 +41,21 @@
],
),
],
ParensAndCommas,
),
[
Newline,
],
),
],
ParensAndCommas,
),
[
@15-16 Identifier {
ident: "f",
},
],
Whitespace,
),
@17-20 BoundVariable(
"i7f",

View file

@ -34,6 +34,7 @@
ident: "f",
},
],
Whitespace,
),
@14-15 BoundVariable(
"f",

View file

@ -26,6 +26,7 @@
ident: "str",
},
],
Whitespace,
),
@12-36 Apply(
@12-17 Tag(

View file

@ -35,6 +35,7 @@
ident: "e",
},
],
Whitespace,
),
@8-14 Apply(
@9-10 SpaceAfter(
@ -52,6 +53,7 @@
ident: "p",
},
],
Whitespace,
),
],
),

View file

@ -1,11 +1,11 @@
@0-22 SpaceAfter(
@0-23 SpaceAfter(
Defs(
Defs {
tags: [
EitherIndex(0),
],
regions: [
@0-20,
@0-21,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
@ -19,23 +19,23 @@
header: TypeHeader {
name: @0-1 "C",
vars: [
@2-3 SpaceAfter(
NumLiteral(
@2-3 NumLiteral(
"4",
),
],
},
loc_implements: @7-17 SpaceBefore(
Implements,
[
LineComment(
"",
),
],
),
],
},
loc_implements: @6-16 Implements,
members: [
AbilityMember {
name: @17-18 "e",
typ: @19-20 BoundVariable(
name: @18-19 "e",
typ: @20-21 BoundVariable(
"m",
),
},
@ -44,7 +44,7 @@
],
value_defs: [],
},
@21-22 SpaceBefore(
@22-23 SpaceBefore(
Tag(
"C",
),

View file

@ -0,0 +1,3 @@
C 4 #
implements e:m
C

View file

@ -33,6 +33,7 @@
],
),
],
Whitespace,
),
@15-16 BoundVariable(
"s",

View file

@ -1,6 +1,6 @@
1
(0 #
f)
1(
0 #
f,)
(0 #
f) : f
e

View file

@ -17,7 +17,8 @@
type_defs: [],
value_defs: [
Annotation(
@0-1 Apply(
@0-9 Apply(
@0-9 Apply(
@0-1 NumLiteral(
"1",
),
@ -38,7 +39,12 @@
ident: "f",
},
],
Whitespace,
),
],
ParensAndCommas,
),
[
@10-16 Apply(
@11-12 SpaceAfter(
NumLiteral(
@ -55,8 +61,10 @@
ident: "f",
},
],
Whitespace,
),
],
Whitespace,
),
@18-19 BoundVariable(
"f",

View file

@ -34,6 +34,7 @@
),
),
],
Whitespace,
),
@12-13 Apply(
"",

View file

@ -42,12 +42,14 @@
),
),
],
Whitespace,
),
[
@12-13 Identifier {
ident: "f",
},
],
Whitespace,
),
@14-15 Apply(
"",

View file

@ -33,8 +33,10 @@
"0",
),
],
Whitespace,
),
],
Whitespace,
),
@15-16 BoundVariable(
"f",

View file

@ -36,6 +36,7 @@
"2",
),
],
Whitespace,
),
@12-13 Apply(
"",

View file

@ -5,8 +5,7 @@
"0",
),
[
@4-7 ParensAround(
Defs(
@4-7 Defs(
Defs {
tags: [
EitherIndex(2147483648),
@ -40,9 +39,8 @@
],
),
),
),
],
Space,
ParensAndCommas,
),
),
@0-1 Negate,

View file

@ -32,6 +32,7 @@
],
),
],
Whitespace,
),
],
@14-15 Var {

View file

@ -25,6 +25,7 @@
ident: "x",
},
],
Whitespace,
),
@7-8 BoundVariable(
"i",

View file

@ -23,6 +23,7 @@
],
),
],
Whitespace,
),
[
Newline,

View file

@ -26,6 +26,7 @@
ident: "it",
},
],
Whitespace,
),
@12-22 Apply(
@12-14 Var {

View file

@ -20,6 +20,7 @@
ident: "m",
},
],
Whitespace,
),
[
Newline,

View file

@ -28,6 +28,7 @@
ident: "a",
},
],
Whitespace,
),
],
},

View file

@ -1,4 +1,3 @@
1
(i, p #
) : f
1(i, p) #
: f
n

View file

@ -16,12 +16,10 @@
type_defs: [],
value_defs: [
Annotation(
@0-1 Apply(
@0-8 Apply(
@0-1 NumLiteral(
"1",
),
[
@1-8 Tuple(
[
@2-3 Identifier {
ident: "i",
@ -37,8 +35,7 @@
],
),
],
),
],
ParensAndCommas,
),
@9-10 BoundVariable(
"f",

View file

@ -37,6 +37,7 @@
],
),
],
Whitespace,
),
@7-8 BoundVariable(
"s",

View file

@ -23,6 +23,7 @@
},
),
],
Whitespace,
),
[
Newline,

View file

@ -43,11 +43,13 @@
ident: "ry",
},
],
Whitespace,
),
@47-48 Underscore(
"",
),
],
Whitespace,
),
[
Newline,

View file

@ -32,6 +32,7 @@
),
),
],
Whitespace,
),
],
},

View file

@ -37,6 +37,7 @@
ident: "a",
},
],
Whitespace,
),
@10-11 BoundVariable(
"t",

View file

@ -45,6 +45,7 @@
"K",
),
],
Whitespace,
),
[
LineComment(

View file

@ -29,6 +29,7 @@
ident: "code",
},
],
Whitespace,
),
@30-33 Var {
module_name: "",

View file

@ -29,6 +29,7 @@
ident: "code",
},
],
Whitespace,
),
@29-32 Var {
module_name: "",

View file

@ -1,2 +1,2 @@
1 (0 0) f : f
1(0(0)) f : f
i

View file

@ -17,7 +17,8 @@
type_defs: [],
value_defs: [
Annotation(
@0-1 Apply(
@0-10 Apply(
@0-10 Apply(
@0-1 NumLiteral(
"1",
),
@ -36,11 +37,17 @@
],
),
],
ParensAndCommas,
),
],
ParensAndCommas,
),
[
@10-11 Identifier {
ident: "f",
},
],
Whitespace,
),
@12-13 BoundVariable(
"f",

View file

@ -28,6 +28,7 @@
),
),
],
Whitespace,
),
@8-15 Apply(
@8-9 Var {

View file

@ -50,6 +50,7 @@
"",
),
],
Whitespace,
),
@11-19 Apply(
@11-15 Tag(
@ -79,6 +80,7 @@
ident: "y",
},
],
Whitespace,
),
@31-39 Apply(
@31-35 Tag(
@ -108,6 +110,7 @@
"",
),
],
Whitespace,
),
@51-59 Apply(
@51-55 Tag(
@ -161,6 +164,7 @@
"",
),
],
Whitespace,
),
@90-98 Apply(
@90-94 Tag(
@ -174,8 +178,10 @@
ident: "y",
},
],
Whitespace,
),
],
Whitespace,
),
@102-128 Apply(
@102-106 Tag(

View file

@ -23,6 +23,7 @@
[],
),
],
Whitespace,
),
[
Newline,

View file

@ -37,6 +37,14 @@ mod test_fmt {
)
}
fn pattern_formats_same(input: &str) {
Input::Pattern(input.trim()).check_invariants(
check_formatting(input.trim()),
true,
Some(false),
);
}
fn fmt_module_and_defs<'a>(
arena: &Bump,
src: &str,
@ -3497,6 +3505,33 @@ mod test_fmt {
));
}
#[test]
fn zero_arg_application_with_parens() {
expr_formats_same(indoc!(
r"
a()
"
));
}
#[test]
fn try_then_application_with_parens() {
expr_formats_same(indoc!(
r"
try something!(arg)
"
));
}
#[test]
fn dbg_then_application_with_parens() {
expr_formats_same(indoc!(
r"
dbg something!(arg)
"
));
}
#[test]
fn single_line_application_with_parens() {
expr_formats_same(indoc!(
@ -3885,6 +3920,40 @@ mod test_fmt {
));
}
#[test]
fn multi_line_when_condition_2_pnc() {
expr_formats_same(indoc!(
r"
when
# this is quite complicated
complexFunction(a, b, c)
# Watch out
is
Complex(x, y) ->
simplify(x, y)
Simple(z) ->
z
"
));
}
#[test]
fn anthony_testing() {
expr_formats_same(indoc!(
r"
when alter (Ok value) is
Ok newValue ->
bucket = listGetUnsafe buckets bucketIndex
newData = List.set data (Num.toU64 bucket.dataIndex) (key, newValue)
@Dict { buckets, data: newData, maxBucketCapacity, maxLoadFactor, shifts }
Err Missing ->
removeBucket (@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }) bucketIndex
"
));
}
#[test]
fn multi_line_when_condition_3() {
expr_formats_to(
@ -6238,4 +6307,24 @@ mod test_fmt {
// "
// ));
// }
#[test]
fn pattern_tag_apply_with_whitespace_single_arg() {
pattern_formats_same(indoc!("Ok a"));
}
#[test]
fn pattern_tag_apply_with_pnc_single_arg() {
pattern_formats_same(indoc!("Ok(a)"));
}
#[test]
fn pattern_tag_apply_with_whitespace_multi_arg() {
pattern_formats_same(indoc!("Ok a b"));
}
#[test]
fn pattern_tag_apply_with_pnc_multi_arg() {
pattern_formats_same(indoc!("Ok(a, b)"));
}
}

View file

@ -33,6 +33,9 @@ mod test_snapshots {
(full => $input:expr) => {
Input::Full($input)
};
(pattern => $input:expr) => {
Input::Pattern($input)
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -324,6 +327,8 @@ mod test_snapshots {
pass/apply_record_parens_newline_field.expr,
pass/apply_tag.expr,
pass/apply_tag_pnc.expr,
pass/apply_tag_single_arg_pnc.pattern,
pass/apply_tag_single_arg_whitespace.pattern,
pass/apply_three_args.expr,
pass/apply_tuple_ext_parens_ty.expr,
pass/apply_two_args.expr,
@ -450,7 +455,7 @@ mod test_snapshots {
pass/if_in_record_field_opt_pat.expr,
pass/if_newline_then_negate_else_recordupdater.expr,
pass/if_then_weird_indent.expr,
pass/implements_after_parens_comment.expr,
pass/implements_after_comment_with_newline.expr,
pass/implements_annotation_comment.expr,
pass/implements_in_pat_after_comment.expr,
pass/implements_newline_in_fn_ty.expr,
@ -859,8 +864,6 @@ mod test_snapshots {
Err(err) => Err(format!("{err:?}")),
};
println!("{:?}", result);
if expect == TestExpectation::Pass {
let tokens = roc_parse::highlight::highlight(&source);
for token in tokens {

View file

@ -767,7 +767,7 @@ impl IterTokens for Loc<Pattern<'_>> {
Pattern::Identifier { .. } => onetoken(Token::Variable, region, arena),
Pattern::Tag(_) => onetoken(Token::Tag, region, arena),
Pattern::OpaqueRef(_) => onetoken(Token::Type, region, arena),
Pattern::Apply(p1, p2) => (p1.iter_tokens(arena).into_iter())
Pattern::Apply(p1, p2, _) => (p1.iter_tokens(arena).into_iter())
.chain(p2.iter_tokens(arena))
.collect_in(arena),
Pattern::RecordDestructure(ps) => ps.iter_tokens(arena),

View file

@ -2281,7 +2281,9 @@ fn to_pattern_report<'a>(
| EPattern::IndentEnd(pos)
| EPattern::AsIndentStart(pos)
| EPattern::AccessorFunction(pos)
| EPattern::RecordUpdaterFunction(pos) => to_unhandled_parse_error_report(
| EPattern::RecordUpdaterFunction(pos)
| EPattern::ParenStart(pos)
| EPattern::ParenEnd(pos) => to_unhandled_parse_error_report(
alloc,
lines,
filename,