Stream: beginners

Topic: My very first compiler panic


view this post on Zulip Ilya Shmygol (Apr 16 2025 at 15:17):

How should I report a panic? The following code panicked after I added a couple of tests

module [match_all]

import Template exposing [parse_template]
import Match

match_all :
    Str,
    Str
    -> Result
        (Dict Str Str)
        [
            DoesNotMatch,
            TemplateError [
                MissingClosingBracket,
                InvalidTokenLength Str,
                InvalidTokenType Str,
                TooManyTokenParts Str
            ]
        ]
match_all = |template_str, input_str|
    specs =
        template_str
        |> parse_template
        |> Result.map_err(TemplateError)?

    input_str
    |> Str.to_utf8
    |> Match.all_specs(specs)

expect
    # that `match_all` returns `Ok` with an empty dict
    # when a template is a literal fully matching the input string
    template_str = "text"
    input_str = "text"
    fields = match_all(template_str, input_str)
    fields == Ok(Dict.empty({}))

expect
    # that `match_all` returns `Ok` with a proper field and value
    # when a template with named token matches the input string
    template_str = "Three symbols: {result:s:6}."
    input_str = "Three symbols: 123Abc."
    fields = match_all(template_str, input_str)
    fields == Ok(Dict.from_list([("result", "123Abc")]))

Output:

➜ just test
roc test src/main.roc
thread 'main' panicked at crates/compiler/mono/src/borrow.rs:361:34:
internal error: entered unreachable code:
        No borrow signature for LambdaName { name: `Match.65`, niche: Niche(Captures([])) } layout.

        Tip 1: This can happen when you call a function with fewer arguments than it expects.
        Like `Arg.list!` instead of `Arg.list! {}`.
        Tip 2: `roc check yourfile.roc` can sometimes give you a helpful error.

note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: Recipe `test` failed on line 8 with exit code 101

view this post on Zulip Ilya Shmygol (Apr 16 2025 at 15:22):

And it diffidently has something to do with expect. If I just drop this string top-level in a file everything is fine (apart from a warning regarding unused v):

 v = match_all("text", "text")

But in expect the same code panics:

expect
    v = match_all("text", "text")
    Bool.true

view this post on Zulip Brendan Hansknecht (Apr 16 2025 at 17:23):

If you can make an issue with a single file repro, that would be the most useful.

view this post on Zulip Brendan Hansknecht (Apr 16 2025 at 17:23):

Also, occasionally adding types or running roc check can resolve these kinds of issues or point to the root cause

view this post on Zulip Ilya Shmygol (Apr 18 2025 at 22:50):

I tried to put everything in one file and remove some code, which doesn't influence the panic, but now I face a Type Mismatch error. I would appreciate some help. Here is the code:

module [Spec, Matcher, all_specs, anything, literal]

Matcher a : List U8 -> Result (List U8) [DoesNotMatch]a

Spec a : {
    field_name : [Anonymous, Named Str],
    length : U64,
    matcher : Matcher a,
}

anything : Matcher a
anything = |input|
    Ok(input)

literal : List U8 -> Matcher a
literal = |value|
    |input|
        if input == value then
            Ok(input)
        else
            Err(DoesNotMatch)

split_input_segment :
    List U8,
    { length : U64 }*
    -> Result { input_segment : List U8, rest_input : List U8 } [DoesNotMatch]
split_input_segment = |input, { length }|
    { before: input_segment, others: rest_input } = List.split_at(input, length)
    if List.len(input_segment) < length then
        Err(DoesNotMatch)
    else
        Ok({ input_segment, rest_input })

## See `all_specs` for the public API.
all_specs_recursive : List U8, List (Spec _), Dict Str Str -> Result (Dict Str Str) [DoesNotMatch]
all_specs_recursive = |input, specs, acc|
    when specs is
        [] ->
            if input == [] then
                Ok(acc)
            else
                Err(DoesNotMatch)

        [spec, .. as rest_specs] ->
            { input_segment, rest_input } = split_input_segment(input, spec)?

            # Even thought `field_value` is not always used,
            # it's important to call `spec.matcher?` here
            field_value =
                input_segment
                |> spec.matcher?
                |> Str.from_utf8_lossy

            all_specs_recursive(
                rest_input,
                rest_specs,
                when spec.field_name is
                    Named(name) -> Dict.insert(acc, name, field_value)
                    Anonymous -> acc,
            )

all_specs : List U8, List (Spec _) -> Result (Dict Str Str) [DoesNotMatch]
all_specs = |input, specs|
    all_specs_recursive(input, specs, Dict.empty({}))

expect
    actual = all_specs(
        ['B', 'u', 'z', 'z', 'F', 'u', 'z', 'z'],
        [
            {
                field_name: Named("field1"),
                length: 4,
                matcher: literal(['B', 'u', 'z', 'z']),
            },
        ],
    )
    actual == Ok(Dict.from_list([("field1", "Buzz"), ("field2", "Fuzz")]))

match_all :
    Str,
    Str
    -> Result
        (Dict Str Str)
        [
            DoesNotMatch,
            TemplateError [
                    MissingClosingBracket,
                    InvalidTokenLength Str,
                    InvalidTokenType Str,
                    TooManyTokenParts Str,
                ],
        ]
match_all = |template_str, input_str|
    # specs =
    #     template_str
    #     |> parse_template
    #     |> Result.map_err(TemplateError)?
    specs = [
        {
            field_name: Named("field1"),
            length: 10,
            matcher: anything,
        },
    ]

    input_str
    |> Str.to_utf8
    |> all_specs(specs)

expect
    # that `match_all` returns `Ok` with an empty dict
    # when a template is a literal fully matching the input string
    template_str = "text"
    input_str = "text"
    fields = match_all(template_str, input_str)
    fields == Ok(Dict.empty({}))

The error is

── TYPE MISMATCH in ./Match.roc ────────────────────────────────────────────────

Something is off with the body of the match_all definition:

 80│       Str,
 81│       Str
 82│       -> Result
 83│           (Dict Str Str)
 84│           [
 85│               DoesNotMatch,
 86│               TemplateError [
 87│                       MissingClosingBracket,
 88│                       InvalidTokenLength Str,
 89│                       InvalidTokenType Str,
 90│                       TooManyTokenParts Str,
 91│                   ],
 92│           ]
 93│   match_all = |template_str, input_str|
 94│       # specs =
 95│       #     template_str
 96│       #     |> parse_template
 97│       #     |> Result.map_err(TemplateError)?
 98│       specs = [{
 99│           field_name: Named("field1"),
100│           length: 10,
101│           matcher: anything,
102│       }]
103│
104│>      input_str
105│>      |> Str.to_utf8
106│>      |> all_specs(specs)

This all_specs call produces:

    Result (Dict Str Str) […]a

But the type annotation on match_all says it should be:

    Result (Dict Str Str) [TemplateError [
        InvalidTokenLength Str,
        InvalidTokenType Str,
        MissingClosingBracket,
        TooManyTokenParts Str,
    ], …]

Interesting enough if the match_all function definition is in its own module, no type mismatch is raised.

view this post on Zulip Brendan Hansknecht (Apr 19 2025 at 01:17):

If it is a different file, maybe it panics before getting to the type mismatch? Anyway, will take a look later today or tomorrow morning when I have more time.

view this post on Zulip Ilya Shmygol (Apr 19 2025 at 07:27):

Brendan Hansknecht said:

If it is a different file, maybe it panics before getting to the type mismatch?

I don’t think so. The compiler panics only if I call match_all in an expect block. As a top-level call it works. Plus My editor (and roc check) doesn’t complain when match_all in a different file, but it does otherwise. I think it’s two different problems.

view this post on Zulip Anthony Bullard (Apr 19 2025 at 10:24):

I think it's the fact that your type annotation says it returns TemplateError OR DoNotMatch, but the code that returns TemplateError is commented out. So it will only return DoNotMatch

view this post on Zulip Ilya Shmygol (Apr 19 2025 at 23:56):

Maybe @Anthony Bullard is right. The type mismatch doesn't happen if I remove TemplateError. I'm still confused, by the fact it happens only if match_all in the same file. Anyway it has not much to do with the panic itself. I created a ticket for the compiler panic with some details in link to this thread: https://github.com/roc-lang/roc/issues/7754.

view this post on Zulip Anthony Bullard (Apr 20 2025 at 00:58):

At the end of the day the Rust version of the compiler just had too many panics

view this post on Zulip Fábio Beirão (Apr 22 2025 at 07:47):

And not enough of them were At the Disco :drum:
(I'll see myself out :grinning_face_with_smiling_eyes:)


Last updated: Jul 06 2025 at 12:14 UTC