@Richard Feldman with regard to https://roc.zulipchat.com/#narrow/stream/304641-ideas/topic/Dbg.20expression/near/405449931 and https://github.com/roc-lang/roc/issues/5894, where should I start with this change?
I made a syntax snapshot test which tries to parse f (dbg 1)
and fails with The source code for this test did not successfully parse!: "Expr(InParens(End(@3), @2), @0)"
. That tells me that there's parsing work to do before we can desugar the expr. Where's a good place to start tracing the parser code?
good question! I'm on mobile so don't have an exact link, but I'd look for where we do if
parsing
because that's also a case where we have a special keyword (if
rather than dbg
) followed by an expression, and then the whole thing ends up being an expression
yes this guy
fn expr_start<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
one_of![
loc!(specialize(EExpr::If, if_expr_help(options))),
loc!(specialize(EExpr::When, when::expr_help(options))),
loc!(specialize(EExpr::Expect, expect_help(options))),
loc!(specialize(EExpr::Dbg, dbg_help(options))),
loc!(specialize(EExpr::Closure, closure_help(options))),
loc!(expr_operator_chain(options)),
fail_expr_start_e()
]
.trace("expr_start")
}
So you think we should be able to update dbg_help
to handle both statements and expressions?
I'm not sure what the code in that function looks like offhand, but seems totally fine to make it separate if you like
Hello 9 month old thread!
I picked this up again recently and made some progress. I've hacked together a working solution by parsing dbg
in expression position and then using the expr body for both the message and the continuation part of LowLevelDbg
. This works in most cases, but there's a bug where nested debugs like dbg (dbg (dbg x)
print to the terminal an exponential number of times. This is because the print side effect is doubled for each nested dbg, since we print once in the message code and again in the continuation.
The simplest solution I can think of would be to desugar the expression dbg x
into
(
v0 = x
dbg v0
v0
)
Which means dbg (dbg x)
would be
(
v0 = (
v1 = x
dbg v1
v1
)
dbg v0
v0
)
which will only print twice.
Unfortunate desugar_expr
doesn't have access to a VarStore
so I can't generate new variables while desugaring. Is there a good reason for that? Should I try to pass the var store in during desugaring? Should I move some of the desugar code to canonicalization instead? Should I try a different solution?
but there's a bug where nested debugs like
dbg (dbg (dbg x)
print to the terminal an exponential number of times
Idk, that might be a feature.
jk...
And I don't see why desugaring couldn't make a variable. That sounds like it should be a fine solution
I got some great PR feedback from @Joshua Warner about handling dbg
similar to crash
, where we just parse the dbg keyword and then in canonicalization we pattern match on Apply(Dbg, args, called_via)
and desugar that into a LowLevelDbg
. Doing it this way means the parser can be more lenient, and then in canonicalization we can report if dbg was given too many or no args.
Question: When crash
is called with too many or no args we continue compiling and fall-back to crash "hit a crash!"
. What would be an appropriate equivalent for dbg
? I'm leaning towards dbg {}
.
There's an argument to be made for hard-failing instead of using a default, since no matter what fallback value we use it will likely produce a type error.
Here's an example with {}
as the default
main =
x = 1 + dbg + 2
Stdout.line! "test"
── TYPE MISMATCH in day_01/main.roc ────────────────────────────────────────────
This 2nd argument to + has an unexpected type:
11│ x = 1 + dbg + 2
^^^
This 63 value is a:
{}
But + needs its 2nd argument to be:
Num *
── UNAPPLIED DBG in day_01/main.roc ────────────────────────────────────────────
This dbg doesn't have a value given to it:
11│ x = 1 + dbg + 2
^^^
dbg must be passed a value to print at the exact place it's used. dbg
can't be used as a value that's passed around, like functions can be -
it must be applied immediately!
────────────────────────────────────────────────────────────────────────────────
2 errors and 5 warnings found in 239 ms
.
Running program anyway…
────────────────────────────────────────────────────────────────────────────────
Roc crashed with:
Hit an erroneous type when creating a layout for `4.IdentId(19)`
"this 63 value"?
Also, any way we can hide the type error and just print the debug error?
Otherwise, having a default seems fine
"this 63 value"?
Ohhhh yeah, I figured that was an existing bug but it's actually my b, I'll fix that.
Fixed so it will now read "The argument is a record of type:" instead of "This 63 value is a:"
I'm not sure if there's a non-hacky way to hide the type error. It would involve suppressing the error for invalid calls to dbg, but leaving it if the user actually calls dbg {}
and that's a type mismatch. I think that having these two errors next to each other isn't the worst.
Ok
No worries
Last updated: Jul 06 2025 at 12:14 UTC