Fix round-trip parse->fmt->parse for dbg stmts with more than one arg

This commit is contained in:
Joshua Warner 2024-12-01 11:25:57 -08:00
parent cfd83ffcdf
commit 912db1b76b
No known key found for this signature in database
GPG key ID: 89AD497003F93FDD
25 changed files with 244 additions and 66 deletions

View file

@ -1104,10 +1104,21 @@ pub fn desugar_expr<'a>(
// Allow naked dbg, necessary for piping values into dbg with the `Pizza` binop
loc_expr
}
DbgStmt(condition, continuation) => {
DbgStmt {
first: condition,
extra_args,
continuation,
} => {
let desugared_condition = &*env.arena.alloc(desugar_expr(env, scope, condition));
let desugared_continuation = &*env.arena.alloc(desugar_expr(env, scope, continuation));
if let Some(last) = extra_args.last() {
let args_region = Region::span_across(&condition.region, &last.region);
env.problem(Problem::OverAppliedDbg {
region: args_region,
});
}
env.arena.alloc(Loc {
value: *desugar_dbg_stmt(env, desugared_condition, desugared_continuation),
region: loc_expr.region,

View file

@ -1245,7 +1245,7 @@ pub fn canonicalize_expr<'a>(
(loc_expr.value, output)
}
ast::Expr::DbgStmt(_, _) => {
ast::Expr::DbgStmt { .. } => {
internal_error!("DbgStmt should have been desugared by now")
}
ast::Expr::LowLevelDbg((source_location, source), message, continuation) => {
@ -2546,7 +2546,7 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
| ast::Expr::Tag(_)
| ast::Expr::OpaqueRef(_) => true,
// Newlines are disallowed inside interpolation, and these all require newlines
ast::Expr::DbgStmt(_, _)
ast::Expr::DbgStmt { .. }
| ast::Expr::LowLevelDbg(_, _, _)
| ast::Expr::Return(_, _)
| ast::Expr::When(_, _)

View file

@ -7,6 +7,7 @@ use crate::spaces::{
INDENT,
};
use crate::Buf;
use bumpalo::collections::Vec;
use roc_module::called_via::{self, BinOp};
use roc_parse::ast::{
is_expr_suffixed, AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces,
@ -64,7 +65,9 @@ impl<'a> Formattable for Expr<'a> {
loc_expr.is_multiline() || args.iter().any(|loc_arg| loc_arg.is_multiline())
}
DbgStmt(condition, _) => condition.is_multiline(),
DbgStmt {
first, extra_args, ..
} => first.is_multiline() || extra_args.iter().any(|arg| arg.is_multiline()),
LowLevelDbg(_, _, _) => unreachable!(
"LowLevelDbg should only exist after desugaring, not during formatting"
),
@ -446,8 +449,12 @@ impl<'a> Formattable for Expr<'a> {
buf.indent(indent);
buf.push_str("dbg");
}
DbgStmt(condition, continuation) => {
fmt_dbg_stmt(buf, condition, continuation, parens, indent);
DbgStmt {
first,
extra_args,
continuation,
} => {
fmt_dbg_stmt(buf, first, extra_args, continuation, parens, indent);
}
LowLevelDbg(_, _, _) => unreachable!(
"LowLevelDbg should only exist after desugaring, not during formatting"
@ -1021,19 +1028,24 @@ fn fmt_when<'a>(
fn fmt_dbg_stmt<'a>(
buf: &mut Buf,
condition: &'a Loc<Expr<'a>>,
extra_args: &'a [&'a Loc<Expr<'a>>],
continuation: &'a Loc<Expr<'a>>,
parens: Parens,
indent: u16,
) {
let mut args = Vec::with_capacity_in(extra_args.len() + 1, buf.text.bump());
args.push(condition);
args.extend_from_slice(extra_args);
Expr::Apply(
&Loc::at_zero(Expr::Dbg),
&[condition],
args.into_bump_slice(),
called_via::CalledVia::Space,
)
.format_with_options(buf, parens, Newlines::Yes, indent);
// Always put a blank line after the `dbg` line(s)
buf.ensure_ends_with_blank_line();
// Always put a newline after the `dbg` line(s)
buf.ensure_ends_with_newline();
continuation.format(buf, indent);
}

View file

@ -491,7 +491,11 @@ pub enum Expr<'a> {
Backpassing(&'a [Loc<Pattern<'a>>], &'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
Dbg,
DbgStmt(&'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
DbgStmt {
first: &'a Loc<Expr<'a>>,
extra_args: &'a [&'a Loc<Expr<'a>>],
continuation: &'a Loc<Expr<'a>>,
},
// This form of debug is a desugared call to roc_dbg
LowLevelDbg(&'a (&'a str, &'a str), &'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
@ -671,7 +675,15 @@ pub fn is_expr_suffixed(expr: &Expr) -> bool {
Expr::OpaqueRef(_) => false,
Expr::Backpassing(_, _, _) => false, // TODO: we might want to check this?
Expr::Dbg => false,
Expr::DbgStmt(a, b) => is_expr_suffixed(&a.value) || is_expr_suffixed(&b.value),
Expr::DbgStmt {
first,
extra_args,
continuation,
} => {
is_expr_suffixed(&first.value)
|| extra_args.iter().any(|a| is_expr_suffixed(&a.value))
|| is_expr_suffixed(&continuation.value)
}
Expr::LowLevelDbg(_, a, b) => is_expr_suffixed(&a.value) || is_expr_suffixed(&b.value),
Expr::Try => false,
Expr::UnaryOp(a, _) => is_expr_suffixed(&a.value),
@ -930,10 +942,17 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
expr_stack.push(&a.value);
expr_stack.push(&b.value);
}
DbgStmt(condition, cont) => {
DbgStmt {
first,
extra_args,
continuation,
} => {
expr_stack.reserve(2);
expr_stack.push(&condition.value);
expr_stack.push(&cont.value);
expr_stack.push(&first.value);
for arg in extra_args.iter() {
expr_stack.push(&arg.value);
}
expr_stack.push(&continuation.value);
}
LowLevelDbg(_, condition, cont) => {
expr_stack.reserve(2);
@ -2476,7 +2495,7 @@ impl<'a> Malformed for Expr<'a> {
Defs(defs, body) => defs.is_malformed() || body.is_malformed(),
Backpassing(args, call, body) => args.iter().any(|arg| arg.is_malformed()) || call.is_malformed() || body.is_malformed(),
Dbg => false,
DbgStmt(condition, continuation) => condition.is_malformed() || continuation.is_malformed(),
DbgStmt { first, extra_args, continuation } => first.is_malformed() || extra_args.iter().any(|a| a.is_malformed()) || continuation.is_malformed(),
LowLevelDbg(_, condition, continuation) => condition.is_malformed() || continuation.is_malformed(),
Try => false,
Return(return_value, after_return) => return_value.is_malformed() || after_return.is_some_and(|ar| ar.is_malformed()),

View file

@ -2172,7 +2172,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
| Expr::If { .. }
| Expr::When(_, _)
| Expr::Dbg
| Expr::DbgStmt(_, _)
| Expr::DbgStmt { .. }
| Expr::LowLevelDbg(_, _, _)
| Expr::Return(_, _)
| Expr::MalformedSuffixed(..)
@ -3086,16 +3086,13 @@ fn stmts_to_defs<'a>(
_,
) = e
{
if args.len() != 1 {
// TODO: this should be done in can, not parsing!
return Err(EExpr::Dbg(
EExpect::DbgArity(sp_stmt.item.region.start()),
sp_stmt.item.region.start(),
));
}
let condition = &args[0];
let rest = stmts_to_expr(&stmts[i + 1..], arena)?;
let e = Expr::DbgStmt(condition, arena.alloc(rest));
let e = Expr::DbgStmt {
first: condition,
extra_args: &args[1..],
continuation: arena.alloc(rest),
};
let e = if sp_stmt.before.is_empty() {
e
@ -3211,7 +3208,11 @@ fn stmts_to_defs<'a>(
if exprify_dbg {
let e = if i + 1 < stmts.len() {
let rest = stmts_to_expr(&stmts[i + 1..], arena)?;
Expr::DbgStmt(arena.alloc(condition), arena.alloc(rest))
Expr::DbgStmt {
first: arena.alloc(condition),
extra_args: &[],
continuation: arena.alloc(rest),
}
} else {
Expr::Apply(
arena.alloc(Loc {

View file

@ -713,33 +713,22 @@ impl<'a> Normalize<'a> for Expr<'a> {
arena.alloc(b.normalize(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.normalize(arena);
}
for value_def in defs.value_defs.iter_mut() {
*value_def = value_def.normalize(arena);
}
Expr::Defs(arena.alloc(defs), arena.alloc(b.normalize(arena)))
}
Expr::Defs(a, b) => fold_defs(arena, a.defs(), b.value.normalize(arena)),
Expr::Backpassing(a, b, c) => Expr::Backpassing(
arena.alloc(a.normalize(arena)),
arena.alloc(b.normalize(arena)),
arena.alloc(c.normalize(arena)),
),
Expr::Dbg => Expr::Dbg,
Expr::DbgStmt(a, b) => Expr::DbgStmt(
arena.alloc(a.normalize(arena)),
arena.alloc(b.normalize(arena)),
),
Expr::DbgStmt {
first,
extra_args,
continuation,
} => Expr::DbgStmt {
first: arena.alloc(first.normalize(arena)),
extra_args: extra_args.normalize(arena),
continuation: arena.alloc(continuation.normalize(arena)),
},
Expr::LowLevelDbg(x, a, b) => Expr::LowLevelDbg(
x,
arena.alloc(a.normalize(arena)),
@ -791,6 +780,61 @@ impl<'a> Normalize<'a> for Expr<'a> {
}
}
fn fold_defs<'a>(
arena: &'a Bump,
mut defs: impl Iterator<Item = Result<&'a TypeDef<'a>, &'a ValueDef<'a>>>,
final_expr: Expr<'a>,
) -> Expr<'a> {
let mut new_defs = Defs::default();
while let Some(def) = defs.next() {
match def {
Ok(td) => {
let td = td.normalize(arena);
new_defs.push_type_def(td, Region::zero(), &[], &[]);
}
Err(vd) => {
let vd = vd.normalize(arena);
match vd {
ValueDef::Stmt(&Loc {
value:
Expr::Apply(
&Loc {
value: Expr::Dbg, ..
},
args,
_,
),
..
}) => {
let rest = fold_defs(arena, defs, final_expr);
let new_final = Expr::DbgStmt {
first: args[0],
extra_args: &args[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(), &[], &[]);
}
}
}
}
}
if new_defs.is_empty() {
return final_expr;
}
Expr::Defs(arena.alloc(new_defs), arena.alloc(Loc::at_zero(final_expr)))
}
fn remove_spaces_bad_ident(ident: BadIdent) -> BadIdent {
match ident {
BadIdent::Start(_) => BadIdent::Start(Position::zero()),
@ -1465,7 +1509,6 @@ impl<'a> Normalize<'a> for EExpect<'a> {
EExpect::Continuation(arena.alloc(inner_err.normalize(arena)), Position::zero())
}
EExpect::IndentCondition(_) => EExpect::IndentCondition(Position::zero()),
EExpect::DbgArity(_) => EExpect::DbgArity(Position::zero()),
}
}
}

View file

@ -513,7 +513,6 @@ pub enum EExpect<'a> {
Condition(&'a EExpr<'a>, Position),
Continuation(&'a EExpr<'a>, Position),
IndentCondition(Position),
DbgArity(Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]

View file

@ -0,0 +1 @@
dbg dbg g g

View file

@ -0,0 +1,20 @@
SpaceAfter(
Apply(
@0-3 Dbg,
[
@4-7 Dbg,
@8-9 Var {
module_name: "",
ident: "g",
},
@10-11 Var {
module_name: "",
ident: "g",
},
],
Space,
),
[
Newline,
],
)

View file

@ -0,0 +1 @@
dbg dbg g g

View file

@ -0,0 +1,20 @@
Apply(
@0-3 Dbg,
[
@4-7 Dbg,
@9-10 SpaceBefore(
Var {
module_name: "",
ident: "a",
},
[
Newline,
],
),
@11-12 Var {
module_name: "",
ident: "g",
},
],
Space,
)

View file

@ -0,0 +1,2 @@
dbg dbg
a g

View file

@ -0,0 +1,4 @@
dbg
izzb
interfacesb

View file

@ -0,0 +1,30 @@
SpaceAfter(
Apply(
@0-3 Dbg,
[
@6-10 SpaceBefore(
Var {
module_name: "",
ident: "izzb",
},
[
Newline,
Newline,
],
),
@13-24 SpaceBefore(
Var {
module_name: "",
ident: "interfacesb",
},
[
Newline,
],
),
],
Space,
),
[
Newline,
],
)

View file

@ -0,0 +1,4 @@
dbg
izzb
interfacesb

View file

@ -1,7 +1,7 @@
SpaceBefore(
SpaceAfter(
DbgStmt(
@6-12 ParensAround(
DbgStmt {
first: @6-12 ParensAround(
BinOps(
[
(
@ -16,7 +16,8 @@ SpaceBefore(
),
),
),
@15-16 SpaceBefore(
extra_args: [],
continuation: @15-16 SpaceBefore(
Num(
"4",
),
@ -25,7 +26,7 @@ SpaceBefore(
Newline,
],
),
),
},
[
Newline,
],

View file

@ -1,6 +1,6 @@
SpaceAfter(
DbgStmt(
@4-16 Tuple(
DbgStmt {
first: @4-16 Tuple(
[
@5-6 Num(
"5",
@ -15,7 +15,8 @@ SpaceAfter(
),
],
),
@18-19 SpaceBefore(
extra_args: [],
continuation: @18-19 SpaceBefore(
Num(
"4",
),
@ -24,7 +25,7 @@ SpaceAfter(
Newline,
],
),
),
},
[
Newline,
],

View file

@ -1,6 +1,6 @@
SpaceAfter(
DbgStmt(
@6-14 SpaceBefore(
DbgStmt {
first: @6-14 SpaceBefore(
ParensAround(
Apply(
@6-7 Var {
@ -25,7 +25,8 @@ SpaceAfter(
Newline,
],
),
@16-21 SpaceBefore(
extra_args: [],
continuation: @16-21 SpaceBefore(
Apply(
@16-17 Var {
module_name: "",
@ -48,7 +49,7 @@ SpaceAfter(
Newline,
],
),
),
},
[
Newline,
],

View file

@ -322,6 +322,9 @@ mod test_snapshots {
pass/crash.expr,
pass/crazy_pat_ann.expr,
pass/dbg.expr,
pass/dbg_double.expr,
pass/dbg_double_newline.expr,
pass/dbg_newline_apply.expr,
pass/dbg_stmt.expr,
pass/dbg_stmt_multiline.expr,
pass/dbg_stmt_two_exprs.expr,

View file

@ -687,8 +687,13 @@ impl IterTokens for Loc<Expr<'_>> {
.chain(e2.iter_tokens(arena))
.collect_in(arena),
Expr::Dbg => onetoken(Token::Keyword, region, arena),
Expr::DbgStmt(e1, e2) => (e1.iter_tokens(arena).into_iter())
.chain(e2.iter_tokens(arena))
Expr::DbgStmt {
first,
extra_args,
continuation,
} => (first.iter_tokens(arena).into_iter())
.chain(extra_args.iter_tokens(arena))
.chain(continuation.iter_tokens(arena))
.collect_in(arena),
Expr::LowLevelDbg(_, e1, e2) => (e1.iter_tokens(arena).into_iter())
.chain(e2.iter_tokens(arena))

View file

@ -1512,7 +1512,6 @@ fn to_dbg_or_expect_report<'a>(
to_space_report(alloc, lines, filename, err, *pos)
}
roc_parse::parser::EExpect::DbgArity(_) => todo!(),
roc_parse::parser::EExpect::Dbg(_) => unreachable!("another branch would be taken"),
roc_parse::parser::EExpect::Expect(_) => unreachable!("another branch would be taken"),