Stream: bugs

Topic: Compiler crash from unresolved / unused type variable


view this post on Zulip Yannick (Sep 28 2025 at 17:49):

Hey, I was just playing around with Abilities and found some crash when I have a unused value that is also still a type variable and not resolved to a concrete type:

app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br" }

import pf.Stdout
import pf.Arg exposing [Arg]

ExampleAbility implements
    unpack : a -> actualValue where a implements ExampleAbility

StringWithAbility := Str implements [ExampleAbility { unpack: readString }]
readString : StringWithAbility -> Str
readString = |@StringWithAbility txt| txt

readAbility : anyExampleAbility -> b where anyExampleAbility implements ExampleAbility
readAbility = |strg| unpack(strg)

main! : List Arg => Result {} _
main! = |_args|
    unpacked = readAbility(@StringWithAbility("test"))
    Stdout.line!("Hello !")

If I hover over unpacked it shows me b as the type.
The compiler crashes with

thread 'main' panicked at crates/compiler/mono/src/borrow.rs:361:34:
internal error: entered unreachable code:
    No borrow signature for LambdaName { name: `#UserApp.readAbility`, 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

As soon as I use the unpacked it is resolved to Str and no crash occurs.

view this post on Zulip Brendan Hansknecht (Sep 28 2025 at 22:14):

Interesting :thinking:

view this post on Zulip Brendan Hansknecht (Sep 28 2025 at 22:16):

My immediate thoughts:

  1. Abilities type check in some surprising ways. I would argue that your code shouldn't work at all (b should not be able to converted to Str), but abilities have some surprising flexibility related to late binding and likely some missing type checking
  2. The borrow signature error is likely due to b being an undefined type and that confusing the compiler unlike the concrete type Str.

view this post on Zulip Brendan Hansknecht (Sep 28 2025 at 22:17):

I think your actualValue needs to be restricted.
Either, you need to make it Str, or you need to give it an ability to say what you can validly do with an actualValue

view this post on Zulip Brendan Hansknecht (Sep 28 2025 at 22:21):

Think of it like this. If I told you that I am going to give you something, but you have no idea what it would be, what valid actions can you make with what I give you.

The answer is you can essentially do nothing cause you know nothing about what I am giving you.

That is the case here with this ability. You are saying that you are return an unknown type variable with no specification. As such, you should not be able to do anything with it. It is actually likely a big that it can become a Str cause there is no guarantee of that.

The error you should be getting is something like

Type mismatch: Your function requires a Str but you were given a actualValue. actualValue can not be restricted to be a Str.

view this post on Zulip Brendan Hansknecht (Sep 28 2025 at 22:21):

Hopefully this makes some sense

view this post on Zulip Brendan Hansknecht (Sep 28 2025 at 22:22):

Anyway, definitely at a minimum, the errors here being bad and some things working are each compiler bugs, but they are for the old rust compiler and likely not worth trying to fix at this point.


We definitely can help you work around issue like this and build a useful program.

view this post on Zulip Brendan Hansknecht (Sep 28 2025 at 22:38):

Oh, one other point of clarification, because of how binding works for ability type variables. If you type unpacked (or use it in a way that demands a specific type), roc will anchor to that typing. It is kinda like a form of late binding that is special to output types of abilities. This is how Decode is able to work at all. But due to it being a late binding anchor, it has lots of edge cases that can easily be underdefined or fail to type check. (Which is the error case here)

view this post on Zulip Yannick (Sep 29 2025 at 05:11):

I see, thank you.
I would have assumed unpacked can know that it is a str and not a actualValue due to it using the StringWithAbiliys readString.

Right now it seems typing it works kind of like an asInstanceOf of a any value. E.g. I can do this and it compiles fine (unpacke is seen as num type):

main! = |_args|
    unpacked = readAbility(@StringWithAbility("test"))
    other = Num.to_str(unpacked)
    Stdout.line!("Hello ${other}!")

For my usecase, let's say I would want to have an ability "returns the most important value of a record" (of an opaque type).
mostImportant: record -> a where record implements MostImportantAbility won't work, same as above.
How would I define this ability? Or does this only make sense in that I also give a an ability that at sometime resolves to a concrete type?

view this post on Zulip Brendan Hansknecht (Sep 29 2025 at 15:24):

Interesting. What is the end goal here?

Any yeah, that ability will only work if you always type the output when calling it.

view this post on Zulip Yannick (Sep 29 2025 at 15:46):

I was looking into implementing CRDTs in roc, seemed like an interesting enough use-case to lean more about the language.
My idea was to define a Mergeable/CRDT ability. Then this could be implemented for a counter or a record and so on.
Initially I was looking into an API like this

Mergeable implements
    read : a -> actualValue where a implements Mergeable
    mergeWithRemote : a, remoteUpdate -> a where a implements Mergeable
    update : a, localUpdate -> (a, remoteUpdate) where a implements Mergeable

But now I am not sure anymore if that approach makes sense :slight_smile:


Last updated: Oct 18 2025 at 12:13 UTC