Move PNC apply to separate Expr/Pattern variant

This commit is contained in:
Anthony Bullard 2025-01-07 17:22:19 -06:00
parent 96fc573b6b
commit 898b3f55e5
No known key found for this signature in database
70 changed files with 873 additions and 555 deletions

View file

@ -554,6 +554,7 @@ pub enum Expr<'a> {
/// To apply by name, do Apply(Var(...), ...)
/// To apply a tag by name, do Apply(Tag(...), ...)
Apply(&'a Loc<Expr<'a>>, &'a [&'a Loc<Expr<'a>>], CalledVia),
PncApply(&'a Loc<Expr<'a>>, Collection<'a, &'a Loc<Expr<'a>>>),
BinOps(&'a [(Loc<Expr<'a>>, Loc<BinOp>)], &'a Loc<Expr<'a>>),
UnaryOp(&'a Loc<Expr<'a>>, Loc<UnaryOp>),
@ -631,6 +632,7 @@ pub fn is_top_level_suffixed(expr: &Expr) -> bool {
match expr {
Expr::TrySuffix { .. } => true,
Expr::Apply(a, _, _) => is_top_level_suffixed(&a.value),
Expr::PncApply(a, _) => is_top_level_suffixed(&a.value),
Expr::SpaceBefore(a, _) => is_top_level_suffixed(a),
Expr::SpaceAfter(a, _) => is_top_level_suffixed(a),
_ => false,
@ -653,6 +655,15 @@ pub fn is_expr_suffixed(expr: &Expr) -> bool {
any_args_suffixed || is_function_suffixed
}
Expr::PncApply(sub_loc_expr, apply_arg_collection) => {
let is_function_suffixed = is_expr_suffixed(&sub_loc_expr.value);
let any_args_suffixed = apply_arg_collection
.iter()
.any(|arg| is_expr_suffixed(&arg.value));
any_args_suffixed || is_function_suffixed
}
// expression in a pipeline, `"hi" |> say!`
Expr::BinOps(firsts, last) => {
firsts
@ -1021,6 +1032,14 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
expr_stack.push(&loc_expr.value);
}
}
PncApply(fun, args) => {
expr_stack.reserve(args.len() + 1);
expr_stack.push(&fun.value);
for loc_expr in args.iter() {
expr_stack.push(&loc_expr.value);
}
}
BinOps(ops, expr) => {
expr_stack.reserve(ops.len() + 1);
@ -1725,12 +1744,6 @@ impl<'a> PatternAs<'a> {
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PatternApplyStyle {
Whitespace,
ParensAndCommas,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Pattern<'a> {
// Identifier
@ -1746,11 +1759,9 @@ pub enum Pattern<'a> {
OpaqueRef(&'a str),
Apply(
&'a Loc<Pattern<'a>>,
&'a [Loc<Pattern<'a>>],
PatternApplyStyle,
),
Apply(&'a Loc<Pattern<'a>>, &'a [Loc<Pattern<'a>>]),
PncApply(&'a Loc<Pattern<'a>>, Collection<'a, Loc<Pattern<'a>>>),
/// This is Located<Pattern> rather than Located<str> so we can record comments
/// around the destructured names, e.g. { x ### x does stuff ###, y }
@ -1831,8 +1842,34 @@ 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())
.all(|(p, q)| p.value.equivalent(&q.value));
constructor_x.value.equivalent(&constructor_y.value) && equivalent_args
} else if let PncApply(constructor_y, args_y) = other {
let equivalent_args = args_x
.iter()
.zip(args_y.iter())
.all(|(p, q)| p.value.equivalent(&q.value));
constructor_x.value.equivalent(&constructor_y.value) && equivalent_args
} else {
false
}
}
PncApply(constructor_x, args_x) => {
if let PncApply(constructor_y, args_y) = other {
let equivalent_args = args_x
.iter()
.zip(args_y.iter())
.all(|(p, q)| p.value.equivalent(&q.value));
constructor_x.value.equivalent(&constructor_y.value) && equivalent_args
} else if let Apply(constructor_y, args_y) = other {
let equivalent_args = args_x
.iter()
.zip(args_y.iter())
@ -2554,6 +2591,7 @@ impl<'a> Malformed for Expr<'a> {
LowLevelTry(loc_expr, _) => loc_expr.is_malformed(),
Return(return_value, after_return) => return_value.is_malformed() || after_return.is_some_and(|ar| ar.is_malformed()),
Apply(func, args, _) => func.is_malformed() || args.iter().any(|arg| arg.is_malformed()),
PncApply(func, args) => func.is_malformed() || args.iter().any(|arg| arg.is_malformed()),
BinOps(firsts, last) => firsts.iter().any(|(expr, _)| expr.is_malformed()) || last.is_malformed(),
UnaryOp(expr, _) => expr.is_malformed(),
If { if_thens, final_else, ..} => if_thens.iter().any(|(cond, body)| cond.is_malformed() || body.is_malformed()) || final_else.is_malformed(),
@ -2650,7 +2688,8 @@ 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()),
PncApply(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, PatternApplyStyle, Spaceable, Spaced, Spaces, SpacesBefore,
TryTarget, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
ModuleImportParams, Pattern, 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,
@ -233,12 +233,40 @@ fn loc_term<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
),
zero_or_more(pnc_args()),
),
|arena, (expr, arg_locs_vec)| {
|arena,
(expr, arg_locs_with_suffixes_vec): (
Loc<Expr<'a>>,
bumpalo::collections::Vec<
'a,
(
Loc<Collection<'a, &'a Loc<Expr>>>,
Option<Vec<'a, Suffix<'a>>>,
),
>,
)| {
let mut e = expr;
for args_loc in arg_locs_vec.iter() {
let orig_region = e.region;
for (args_loc, maybe_suffixes) in arg_locs_with_suffixes_vec.iter() {
let value = if matches!(
e,
Loc {
value: Expr::Dbg,
..
}
) {
Expr::Apply(arena.alloc(e), args_loc.value.items, CalledVia::Space)
} else if let Some(suffixes) = maybe_suffixes {
apply_expr_access_chain(
arena,
Expr::PncApply(arena.alloc(e), args_loc.value),
suffixes.clone(),
)
} else {
Expr::PncApply(arena.alloc(e), args_loc.value)
};
e = Loc {
value: Expr::Apply(arena.alloc(e), args_loc.value, CalledVia::ParensAndCommas),
region: Region::span_across(&expr.region, &args_loc.region),
value,
region: Region::span_across(&orig_region, &args_loc.region),
};
}
e
@ -247,9 +275,16 @@ fn loc_term<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
.trace("term")
}
fn pnc_args<'a>() -> impl Parser<'a, Loc<&'a [&'a Loc<Expr<'a>>]>, EExpr<'a>> {
fn pnc_args<'a>() -> impl Parser<
'a,
(
Loc<Collection<'a, &'a Loc<Expr<'a>>>>,
Option<Vec<'a, Suffix<'a>>>,
),
EExpr<'a>,
> {
|arena: &'a Bump, state: State<'a>, min_indent: u32| {
map_with_arena(
let args_then_suffixes = and(
specialize_err(
EExpr::InParens,
loc(collection_trailing_sep_e(
@ -260,32 +295,23 @@ fn pnc_args<'a>() -> impl Parser<'a, Loc<&'a [&'a Loc<Expr<'a>>]>, EExpr<'a>> {
Expr::SpaceBefore,
)),
),
|arena, arg_loc: Loc<Collection<'a, Loc<Expr<'a>>>>| {
let mut args_vec = Vec::new_in(arena);
let args_len = arg_loc.value.items.len();
for (i, arg) in arg_loc.value.items.iter().enumerate() {
if i == (args_len - 1) {
let last_comments = arg_loc.value.final_comments();
if !last_comments.is_empty() {
let sa = Expr::SpaceAfter(arena.alloc(arg.value), last_comments);
let arg_with_spaces: &Loc<Expr<'a>> = arena.alloc(Loc {
value: sa,
region: arg.region,
});
args_vec.push(arg_with_spaces);
} else {
let a: &Loc<Expr<'a>> = arena.alloc(arg);
args_vec.push(a);
}
} else {
let a: &Loc<Expr<'a>> = arena.alloc(arg);
args_vec.push(a);
}
}
Loc {
value: args_vec.into_bump_slice(),
region: arg_loc.region,
}
optional(record_field_access_chain()),
);
map_with_arena(
args_then_suffixes,
|arena: &'a Bump,
(loc_args_coll, maybe_suffixes): (
Loc<Collection<'a, Loc<Expr<'a>>>>,
Option<Vec<'a, Suffix<'a>>>,
)| {
let args = loc_args_coll.value.ptrify_items(arena);
(
Loc {
region: loc_args_coll.region,
value: args,
},
maybe_suffixes,
)
},
)
.parse(arena, state, min_indent)
@ -2117,7 +2143,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, called_via) => {
Expr::Apply(loc_val, loc_args, _) => {
let region = loc_val.region;
let value = expr_to_pattern_help(arena, &loc_val.value)?;
let val_pattern = arena.alloc(Loc { region, value });
@ -2131,18 +2157,22 @@ 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(),
if matches!(called_via, CalledVia::ParensAndCommas) {
PatternApplyStyle::ParensAndCommas
} else {
PatternApplyStyle::Whitespace
},
);
let pattern = Pattern::Apply(val_pattern, arg_patterns.into_bump_slice());
pattern
}
Expr::PncApply(loc_val, args) => {
let region = loc_val.region;
let value = expr_to_pattern_help(arena, &loc_val.value)?;
let val_pattern = arena.alloc(Loc { region, value });
let pattern_args = args.map_items_result(arena, |arg| {
let region = arg.region;
let value = expr_to_pattern_help(arena, &arg.value)?;
Ok(Loc { region, value })
})?;
Pattern::PncApply(val_pattern, pattern_args)
}
Expr::Try => Pattern::Identifier { ident: "try" },
@ -3143,6 +3173,31 @@ fn stmts_to_defs<'a>(
last_expr = Some(Loc::at(sp_stmt.item.region, e));
// don't re-process the rest of the statements; they got consumed by the dbg expr
break;
} else if let Expr::PncApply(
Loc {
value: Expr::Dbg, ..
},
args,
) = e
{
let condition = &args.items[0];
let rest = stmts_to_expr(&stmts[i + 1..], arena)?;
let e = Expr::DbgStmt {
first: condition,
extra_args: &args.items[1..],
continuation: arena.alloc(rest),
};
let e = if sp_stmt.before.is_empty() {
e
} else {
arena.alloc(e).before(sp_stmt.before)
};
last_expr = Some(Loc::at(sp_stmt.item.region, e));
// don't re-process the rest of the statements; they got consumed by the dbg expr
break;
} else {
@ -3313,7 +3368,8 @@ 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::PncApply(left, _) => starts_with_spaces_conservative(&left.value),
Pattern::RecordDestructure(_) => false,
Pattern::RequiredField(_, _) | Pattern::OptionalField(_, _) => false,
Pattern::SpaceBefore(_, _) => true,
@ -3329,7 +3385,6 @@ 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,
)
}
}
@ -3380,8 +3435,8 @@ fn pat_ends_with_spaces_conservative(pat: &Pattern<'_>) -> bool {
| Pattern::ListRest(_)
| Pattern::As(_, _)
| Pattern::OpaqueRef(_)
| Pattern::Apply(_, _, PatternApplyStyle::ParensAndCommas) => false,
Pattern::Apply(_, args, _) => args
| Pattern::PncApply(_, _) => false,
Pattern::Apply(_, args) => args
.last()
.map_or(false, |a| pat_ends_with_spaces_conservative(&a.value)),
Pattern::RecordDestructure(_) => false,
@ -3403,7 +3458,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, PatternApplyStyle::Whitespace);
let ann_pattern = Pattern::Apply(loc_name, header.vars);
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

@ -737,8 +737,13 @@ impl<'a> Normalize<'a> for Expr<'a> {
arena.alloc(a.normalize(arena)),
b.map(|loc_b| &*arena.alloc(loc_b.normalize(arena))),
),
Expr::Apply(a, b, c) => {
Expr::Apply(arena.alloc(a.normalize(arena)), b.normalize(arena), c)
Expr::Apply(a, b, called_via) => Expr::Apply(
arena.alloc(a.normalize(arena)),
b.normalize(arena),
called_via,
),
Expr::PncApply(a, b) => {
Expr::PncApply(arena.alloc(a.normalize(arena)), b.normalize(arena))
}
Expr::BinOps(a, b) => Expr::BinOps(a.normalize(arena), arena.alloc(b.normalize(arena))),
Expr::UnaryOp(a, b) => {
@ -835,6 +840,30 @@ fn fold_defs<'a>(
arena.alloc(Loc::at_zero(new_final)),
);
}
ValueDef::Stmt(&Loc {
value:
Expr::PncApply(
&Loc {
value: Expr::Dbg, ..
},
args,
),
..
}) => {
let rest = fold_defs(arena, defs, final_expr);
let new_final = Expr::DbgStmt {
first: args.items[0],
extra_args: &args.items[1..],
continuation: arena.alloc(Loc::at_zero(rest)),
};
if new_defs.is_empty() {
return new_final;
}
return Expr::Defs(
arena.alloc(new_defs),
arena.alloc(Loc::at_zero(new_final)),
);
}
_ => {
new_defs.push_value_def(vd, Region::zero(), &[], &[]);
}
@ -879,11 +908,13 @@ 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, c) => Pattern::Apply(
Pattern::Apply(a, b) => Pattern::Apply(
arena.alloc(a.normalize(arena)),
arena.alloc(b.normalize(arena)),
c,
),
Pattern::PncApply(a, b) => {
Pattern::PncApply(arena.alloc(a.normalize(arena)), b.normalize(arena))
}
Pattern::RecordDestructure(a) => Pattern::RecordDestructure(a.normalize(arena)),
Pattern::RequiredField(a, b) => {
Pattern::RequiredField(a, arena.alloc(b.normalize(arena)))

View file

@ -1,6 +1,4 @@
use crate::ast::{
Collection, ExtractSpaces, Implements, Pattern, PatternApplyStyle, PatternAs, Spaceable,
};
use crate::ast::{Collection, ExtractSpaces, Implements, Pattern, 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;
@ -337,35 +335,30 @@ 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(
enum ArgType<'a> {
PncArgs(Loc<Collection<'a, Loc<Pattern<'a>>>>),
WhitespaceArgs(&'a [Loc<Pattern<'a>>]),
}
let commas_and_paren_args_help = map(
loc(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,
)
},
)),
|args| ArgType::PncArgs(args),
);
let whitespace_args =
map_with_arena(loc_type_def_tag_pattern_args_help(), |arena, args| {
let mut args_vec = Vec::new_in(arena);
let mut args_vec = Vec::with_capacity_in(args.len(), 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)
ArgType::WhitespaceArgs(args_vec.into_bump_slice())
});
match loc_ident.value {
@ -376,7 +369,7 @@ fn loc_ident_pattern_help<'a>(
};
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
let (_, (args, style), state) = if can_have_arguments {
let (_, arg_type, state) = if can_have_arguments {
one_of!(commas_and_paren_args_help, whitespace_args)
.parse(arena, state, min_indent)?
} else {
@ -386,19 +379,27 @@ fn loc_ident_pattern_help<'a>(
Err((MadeProgress, e)) => return Err((MadeProgress, e)),
}
};
match arg_type {
ArgType::PncArgs(args) => {
let pnc_args = args.value;
let value = Pattern::PncApply(&*arena.alloc(loc_tag), pnc_args);
let region = Region::span_across(&loc_ident.region, &args.region);
Ok((MadeProgress, Loc { region, value }, state))
}
ArgType::WhitespaceArgs(args) => {
let loc_args: &[Loc<Pattern<'_>>] = { args };
if loc_args.is_empty() {
Ok((MadeProgress, loc_tag, state))
} else {
let region = Region::across_all(
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);
let loc_args: &[Loc<Pattern<'_>>] = { args };
if loc_args.is_empty() {
Ok((MadeProgress, loc_tag, state))
} else {
let region = Region::across_all(
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, style);
Ok((MadeProgress, Loc { region, value }, state))
Ok((MadeProgress, Loc { region, value }, state))
}
}
}
}
Ident::OpaqueRef(name) => {
@ -408,25 +409,37 @@ fn loc_ident_pattern_help<'a>(
};
// Make sure `@Foo Bar 1` is parsed as `@Foo (Bar) 1`, and not `@Foo (Bar 1)`
let (_, (args, style), state) = if can_have_arguments {
let (_, arg_type, 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)?
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_pat, state)),
Err((MadeProgress, e)) => return Err((MadeProgress, e)),
}
};
match arg_type {
ArgType::PncArgs(args) => {
let pnc_args = args.value;
let value = Pattern::PncApply(&*arena.alloc(loc_pat), pnc_args);
let region = Region::span_across(&loc_ident.region, &args.region);
Ok((MadeProgress, Loc { region, value }, state))
}
ArgType::WhitespaceArgs(args) => {
let loc_args: &[Loc<Pattern<'_>>] = { args };
if loc_args.is_empty() {
Ok((MadeProgress, loc_pat, state))
} else {
let region = Region::across_all(
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);
let loc_args: &[Loc<Pattern<'_>>] = { args };
if loc_args.is_empty() {
Ok((MadeProgress, loc_pat, state))
} else {
let region = Region::across_all(
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, style);
Ok((MadeProgress, Loc { region, value }, state))
Ok((MadeProgress, Loc { region, value }, state))
}
}
}
}
Ident::Access {
@ -683,7 +696,7 @@ mod test_parse_pattern {
region: new_region(3, 4),
}];
let expected = Loc {
value: Pattern::Apply(&expected_tag, &expected_args, PatternApplyStyle::Whitespace),
value: Pattern::Apply(&expected_tag, &expected_args),
region: new_region(0, 4),
};
assert_eq!(format!("{res:#?}"), format!("{expected:#?}"));
@ -699,17 +712,13 @@ mod test_parse_pattern {
value: Pattern::Tag("Ok"),
region: new_region(0, 2),
};
let expected_args = [Loc {
let expected_args = Collection::with_items(arena.alloc([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),
value: Pattern::PncApply(&expected_tag, expected_args),
region: new_region(0, 5),
};
assert_eq!(format!("{res:#?}"), format!("{expected:#?}"));
}