I have a function insert, that has the type
insert : RBTree(k, v), k, v -> RBTree(k, v) where [
k.is_eq: k, k -> Bool,
k.is_lt: k, k -> Bool,
k.is_gt: k, k -> Bool
]
And a function empty, that has the type
empty : () -> RBTree(k, v) where [
k.is_eq: k, k -> Bool,
k.is_lt: k, k -> Bool,
k.is_gt: k, k -> Bool
]
I'm not sure why this snippet
test_insert = || {
set : RBTree(I32,Str)
set = RBTree.empty()
set2: RBTree(I32, Str)
set2 = set.insert(0.I32, "")
True
}
receives the following error
-- TYPE MISMATCH ---------------------------------
The insert method on RBTree has an incompatible type:
┌─ /Users/jrp2018/Fun/roc/RBTree.roc:209:12
│
209 │ set2 = set.insert(0.I32, "")
│ ^^^
The method insert has the type:
RBTree(k, v), k, v -> RBTree(k, v)
where [k.is_eq : k, k -> Bool
, k.is_gt : k, k -> Bool
, k.is_lt : k, k -> Bool]
But I need it to have the type:
RBTree(I32, Str), I32, Str -> RBTree(I32, Str)
As far as I can see, the concrete types match the pattern and the constraints?
Hmm. I tried to produce a minimal example with exactly the same type restrictions, and the compiler had no issue with the below. I would have thought that manually specifying the function types would have isolated the problem to be at the call site
main! = |_args| {
x : MyType(I32, Str)
x = MyType.empty()
x2 : MyType(I32, Str)
x2 = x.insert(0.I32, "")
Ok({})
}
MyType(a, b) :: [A].{
insert : MyType(a, b), a, b -> MyType(a, b)
where [a.is_eq : a, a -> Bool
, a.is_gt : a, a -> Bool
, a.is_lt : a, a -> Bool]
insert = |cl, x, y| cl
empty : () -> MyType(a, b) where [
a.is_eq: a, a -> Bool,
a.is_lt: a, a -> Bool,
a.is_gt: a, a -> Bool
]
empty = || {
A
}
}
Can you share the full RBTree code?
Thanks, here you go :) https://gist.github.com/jrrrp/ae6fe1e91a1932593e13f790451a8471
But to add to the story. If I remove that test at the bottom that is getting the type mismatch error, and write the exact same thing in main.roc, I don't get any errors. It only seemed to be when it was in the same file. I am running roc test RBTree.roc --no-cache for one and just roc --no-cache to run the same in main
Though when I write a very simple 'MyType' in its own file, mirroring with the same function signatures as RBTree, with the 'same' test in the bottom, that doesn't have an error.
I get thread 231107 panic: reached unreachable code with your original RBTree.roc and the latest Roc from main branch. I will take a look...
If it does type check for you, I was getting a malloc heap corruption during roc build that I think I tracked down to the spread operator (line 83 works vs 93 doesn't) but couldn't reproduce outside of that context. (This was before I wrote the test at the bottom, which I suppose triggers the error earlier in the compiler?)
Anton said:
I get
thread 231107 panic: reached unreachable codewith your original RBTree.roc and the latest Roc from main branch. I will take a look...
I'm on a couple days old roc so will give it a try
For me the panic triggers during canonicalization
I don't get that same error with the newest roc nightly. Still the same type mismatch. Is RBTree defined in main.roc for you or RBTree.roc?
(I also had this error before, but figured that it was due to the type mismatch:
-- COMPTIME EVAL ERROR ---------------------------
This definition could not be evaluated at compile time:
┌─ /Users/jrp2018/Fun/roc/RBTree.roc:208:19
│
208 │ x2 = x.insert(0.I32, "")
│ ^^^^^
The evaluation failed with error:
Numeric literal cannot be used as this type (type doesn't support from_numeral)
)
I am doing ./zig-out/bin/roc check RBTree.roc --no-cache
That gives the same error as before, but I am using nightlies (macos-apple silicon). I can try from source and see
Building from source I get the same error as you now (interestingly I couldn't build with the downloaded zig-aarch64-macos-0.15.2 but nix -> buildcmd worked fine)
I think the unreachable case (trace said it was from canonicalizeExpr for the arguments inside parseMethodCall?) is triggered by this apply but I couldn't reduce it further from here.
b = a.apply(|new_right| RBTree.Node({colour: before.colour, key: before.key, value: before.value, right: new_right, left: before.left}))
Furthermore, this gives the same crash and stack trace
b = a.apply(|x| before) (before is a tree node introduced earlier)
but this doesn't, and it proceeds to the type mismatch error: b = a.apply(|x| x). I have no idea if this is useful at this point ^^
I have a different minimal repro, I will keep you posted on the fix.
PR#9236 fixes one unreachable panic. I will take a look at the remaining errors now.
Thank you Anton!
I'm now looking at #9237
The compiler serializes recursive self-references (e.g., Tree inside Node(Tree)) as .err content to break cycles
I suppose this is why sometimes the errors are less informative? I've had cases where type checker expects X but got "Error". I don't have a snippet right now but I think it was with a recursive type.
Uhu, could be
Actually wait a sec (Thought I had made a mistake with order of fold_rev args but nope)
Anton I'm not sure if one of the PRs you had on the go was targeting this, but if it helps I hit a very similar but more simple problem without any extra crashes when testing some of the missing list builtins, and it seems to be sensitive to the content of the function not just the signature :thinking: (repro below). For now I can just not put expects in the bottom and all is ok :)
ListFuns :: [].{
# No problem
noop : a -> a
noop = |it| it
# No problem
noopList : List(a) -> List(a)
noopList = |it| it
# No problem (e.g. something that isn't a noop)
doubleList : List(a) -> List(a)
doubleList = |it| it.concat(it)
# Problem (type mismatch: I32 != a), but only when tested in this file
reverse : List(a) -> List(a)
reverse = |list| {
destination = List.with_capacity(list.len())
list.fold_rev(destination, |item, dest| dest.append(item))
}
}
# ===========
# Running expects inside ListFuns.roc
#
# expect ListFuns.noop(2) == 2 # All good
# expect ListFuns.noopList([2]) == [2] # All good
# expect ListFuns.doubleList([1]) == [1, 1] # All good
# expect ListFuns.reverse([2]) == [2] # Type mismatch - expected 'a'.
# # Also missing method, comptime eval
# # error etc.
#
# ===========
# Import into main.roc, and `roc test main.roc`
#
# expect ListFuns.noop(2) == 2 # All good
# expect ListFuns.noopList([2]) == [2] # All good
# expect ListFuns.doubleList([1]) == [1, 1] # All good
# expect ListFuns.reverse([2]) == [2] # All good
Thanks @Jonathan, that's a useful repo :)
I hit a panic: integer overflow when running ./zig-out/bin/roc test main.roc --no-cache. I am going to start by looking at that first.
Fixed in PR#9241
Just to be completely nit-picky here (and to help my drive-by understanding of zig), shouldn't the test here say that it was an "underflow" issue?
Yeah, zig panics with integer overflow when there is an underflow :p I think I am going to leave it as is, it seems the least confusing.
I'm looking at the actual compile errors from the ListFuns file now
I'd like to try and help, but I can't figure out why all my traces are stripped despite being a debug build :sweat_smile:
Are you using a headerless main app like here: https://github.com/roc-lang/roc/blob/main/test/echo/hello.roc
No I was using Luke's simple-cli
I'll check what happens without
Same issue there. ListFuns when tested/called in main -> no problem. Tested in the same module -> type mismatches
Oh yes, that is expected, but I meant the headerless main app helps with the traces.
Ahh! Thank you haha. Yeh I was trying to atos my way back but it wasn't looking right at all.
Fix in PR#9243
Last updated: Mar 20 2026 at 12:28 UTC