Stream: beginners

Topic: new compiler: exiting 1 on success?


view this post on Zulip Rick Hull (Jan 31 2026 at 08:14):

This one is odd; I am working on the Sexp2 module and noticed this behavior, only in this specific case, after whittling down to minimal test case:

rwh@ht seatzero (master *)$ roc spike/test_sexp2.roc; echo $?
dbg: [<tag_union variant=3>, <tag_union variant=2>, <tag_union variant=2>, <tag_union variant=2>, <tag_union variant=1>]
dbg: ["(", "1", "2", "3", ")"]
Sexp2 runtime harness reached
1
rwh@ht seatzero (master *)$ cat spike/test_sexp2.roc
app [main!] { pf: platform "../../basic-cli/platform/main.roc" }

import pf.Stdout
import Sexp2

main! = |_args| {
    tokens = Sexp2.tokenize("(1 2 3)")
    dbg tokens
    names = Sexp2.token_names(tokens)
    dbg names

    expect Bool.True

    Stdout.line!("Sexp2 runtime harness reached")
    Ok({})
}

view this post on Zulip Luke Boswell (Jan 31 2026 at 08:26):

What if your use Str.inspect on the tokens? nvm I'm pretty sure it's using that under the hood

view this post on Zulip Luke Boswell (Jan 31 2026 at 08:27):

If you implement a to_str : Secp2 -> Str method does that change anything? it's called to_inspect

view this post on Zulip Luke Boswell (Jan 31 2026 at 08:28):

I vaguely recall an idea that inspect basically desugars to that method under the hood, but I can't remember

view this post on Zulip Rick Hull (Jan 31 2026 at 08:30):

note, this is Sexp (as in s-expression). let me investigate that, though i've moved on from this curiousity

view this post on Zulip Rick Hull (Jan 31 2026 at 08:32):

In the meantime, not related to this curiosity, i have:

The stack overflow you saw when running roc test --verbose spike/Sexp3.roc (after the
added equality-based expect cases) wasn’t caused by a logic mistake in the Sexp parser
itself—it was the Roc compiler hitting a stack overflow while evaluating those expect
expressions. The crash message explicitly says the compiler overflowed its stack, which
matches the behavior we observed when we defined is_eq methods and tried to compare
Token/Value instances with ==. Removing those comparisons (and instead comparing the
formatted string or the token names) lets roc test complete successfully, so the parser
never “ran away” at runtime. In short: the compiler’s expect evaluation blew up when
trying to derive equality for those custom types, so it’s a compiler bug triggered by
that pattern, not a user error.

view this post on Zulip Rick Hull (Jan 31 2026 at 08:33):

i can pause on this and try to make a minimal repro, but I want to try your to_str trick first

view this post on Zulip Luke Boswell (Jan 31 2026 at 08:36):

test/fx/inspect_custom_test.roc

app [main!] { pf: platform "./platform/main.roc" }

import pf.Stdout

# Define an opaque type with a custom to_inspect method
Color := [Red, Green, Blue].{
    to_inspect : Color -> Str
    to_inspect = |color| match color {
        Red => "Color::Red"
        Green => "Color::Green"
        Blue => "Color::Blue"
    }
}

main! = || {
    red : Color
    red = Red

    # Test Str.inspect with custom to_inspect method
    result = Str.inspect(red)
    Stdout.line!(result)

    # Compare with what the default would be
    Stdout.line!("Expected: Color::Red")
}

view this post on Zulip Luke Boswell (Jan 31 2026 at 08:38):

Yeah -- consider roc test somewhat broken... if it works :tada: but it clearly needs some love

view this post on Zulip Rick Hull (Jan 31 2026 at 08:45):

here is the strace for exiting 1 on success: https://gist.github.com/rickhull/4247a1dddbc40069ad7f853a47bcb552

view this post on Zulip Rick Hull (Jan 31 2026 at 08:47):

it's too late for me now, but I'm not sure about your inspect test; in this case, I am executing with roc and not roc test, and in the strace gist, this is after roc build

gpt-codex-mini says:

The strace capture settles it: the binary prints all the dbg/Stdout lines you care about
and then ends with exit_group(1) — there’s no panic, crash, or expect failure at
runtime, so it’s literally choosing to return status 1. That’s exactly what roc does for
these little test harnesses when you run them with roc spike/... instead of roc test
spike/...: the compiler builds a dedicated test binary and the runtime intentionally
exits 1 (even on success) because it wasn’t invoked through the roc test driver that
reports expect results. In short, the harness is behaving as designed — it’s not a logic
bug in your code, it’s the way Roc signals “don’t treat this as a normal runnable app;
use roc test so the expect blocks run and report success.”

view this post on Zulip Luke Boswell (Jan 31 2026 at 09:08):

Ah yeah, by default a program with a dbg in it returns a non-zero exit code

view this post on Zulip Luke Boswell (Jan 31 2026 at 09:17):

Actually I might be getting confused there...

Here's an older discussion #show and tell > draft blog post about purity inference @ 💬 -- Richard proposed adding a --release flag that would throw a non-zero code if there were any dbg's

view this post on Zulip Luke Boswell (Jan 31 2026 at 09:17):

Need to look into that and document it somewhere I guess


Last updated: Feb 20 2026 at 12:27 UTC