Stream: compiler development

Topic: interpreter handling of erroneous calls to builtin functions


view this post on Zulip ugi (Apr 22 2026 at 15:02):

the other day I was playing with fuzz-canonicalize and found a category of programs which trigger "unreachable code reached"-crashes inside the new zig compiler relating to builtin-function calls inside src/eval/interpreter.zig

for example, running ./zig-out/bin/roc repro.roc on the following file:

# repro.roc
main! = Str.is_eq("a", "b", "c")

results in a compiler crash (reached unreachable code).

essentially, this error (and similar errors in the same category) boils down to fn callLowLevelBuiltin in src/eval/interpreter.zig assuming that it gets the right number of arguments for a builtin function (with std.debug.assert(args.len == 2)).

since I could not constrain my curiosity about this, I am now here to ask:

I appreciate the effort you guys are putting in! Thank you for creating such a delightful language!

view this post on Zulip Anton (Apr 22 2026 at 15:40):

Thanks for reaching out @ugi.
For this code we do throw the "TOO MANY ARGS" error:

main! = |_args| {
    echo!(Str.inspect(Str.is_eq("a", "b", "c")))
    Ok({})
}

So I am going to start my search with checking why that is not the case for your repro.roc

view this post on Zulip ugi (Apr 22 2026 at 16:07):

Interesting, it seems that your code does not trigger compile time evaluation (via interpreter) while mine does, probably because the erroneous expression is inside a closure and not a top-level-decl?

view this post on Zulip ugi (Apr 22 2026 at 16:13):

Also I think, that in my repro.roc, the type checker does pick up the "TOO MANY ARGS" error, but the interpreter ignores it and tries to run the builtin function anyway, hitting the std.debug.assert(args.len == 2)

view this post on Zulip Anton (Apr 22 2026 at 17:05):

I think your analysis is correct @ugi. Two possible changes changes come to mind:

What do you think @Richard Feldman?

view this post on Zulip ugi (Apr 22 2026 at 18:15):

I would also like to mention, that this category of problems does not only contain args count validation specifically, but function signature validation generally. (but only for builtin functions, not for user-defined functions)
For example, the following roc code would also crash with "reached unreachable code":

main! = Str.concat(5, "b")

This time because the interpreter assumes (and asserts, tough implicitly with the .? operator) that the first arg is a Str.

(NOTE: using Str.is_eq with wrong types would not trigger a crash, because in the .str_is_eq branch of fn callLowLevelBuiltin the argument types are explicitly checked, but they are not explicitly checked in the .str_concat branch)

view this post on Zulip Richard Feldman (Apr 23 2026 at 03:31):

I haven't looked into this specifically (and I'm neck-deep in some really thorny compiler backend changes right now and I'm trying to stay focused on them!) but I will say that in the long term I definitely want to aim for "no compile-time errors block later stages of the compiler from running" as a general design principle, and that includes compile-time evaluation of constants

view this post on Zulip Richard Feldman (Apr 23 2026 at 03:32):

that said, I think it's okay if in the short term we don't live up to that goal yet

view this post on Zulip Richard Feldman (Apr 23 2026 at 03:32):

so my preference regarding this option:

Do not run compile time evaluation if there are type checker errors unless --allow-errors was passed.

...would be not to have a CLI flag for it, but rather just block for now and in the future silently transition to nonblocking once we get the bugs ironed out :smile:

view this post on Zulip Anton (Apr 24 2026 at 09:16):

With block do you mean bail out and print the errors?


Last updated: May 01 2026 at 12:45 UTC