Stream: beginners

Topic: Unclear type error


view this post on Zulip Jonathan (May 31 2026 at 00:01):

I'm not entirely sure how to interpret the following error. I'll include the whole file for context but it isn't completely tidy

RBTree.Node({left: RBTree.Node({colour: R} as left_data)} as parent_data) => {
    uneq_right = RBTree.Node(
        {colour: R, left: left_data.right, key: parent_data.key, value: parent_data.value, right: parent_data.right}
    )
    uneq_right->eqL().apply(|new_right| {
        new_parent = RBTree.Node({colour: B, left: left_data.left, key: left_data.key, value: left_data.value, right: new_right})
        new_parent
    })
}

Error:

-- TYPE MISMATCH ---------------------------------

The first argument being passed to this function has the wrong type:
    ┌─ RBTree.roc:343:21
    │
343 │                     uneq_right->eqL().apply(|new_right| {
    │                     ^^^^^^^^^^

This argument has the type:

    RBTree(k, v)

But eqL needs the first argument to be:

    RBTree(k, v)

eqL:

eqL : RBTree(k, v) -> Intermediate(RBTree(k, v))

Full RBTree.roc

view this post on Zulip Jonathan (May 31 2026 at 00:04):

There's also the following error

-- TYPE MISMATCH ---------------------------------

The first argument being passed to this function has the wrong type:
    ┌─ RBTree.roc:408:49
    │
408 │                     (new_right, {key, value}) = inner->delMin()
    │                                                 ^^^^^

This argument has the type:

    { colour: [B, R], key: k, left: RBTree(k, v), right: Error, value: v }

But delMin needs the first argument to be:

    RBTree.Inner(k, v)

which is precisely the alias given by RBTree.Inner (the type of the payload of the Node):

    Inner(k, v) : {
        colour: [R, B],
        left: RBTree(k, v),
        key: k,
        value: v,
        right: RBTree(k, v)
    }

view this post on Zulip Jonathan (May 31 2026 at 00:06):

Wondering if this has something to do with #beginners > Type module which depends on a type defined within? given the kind of recursive reference to RBTree(k, v) from RBTree.Inner(k, v)

view this post on Zulip Jonathan (May 31 2026 at 00:33):

Forgot to say this was with roc built from source but a couple days ago, I'll try again with tip if anything has changed.

view this post on Zulip Ian McLerran (May 31 2026 at 01:40):

These look like compiler bugs to me... I think the compiler is assigning different internal identities to structurally-identical recursive nominal types. I'll see if I can produce a min repro.

view this post on Zulip Richard Feldman (May 31 2026 at 01:48):

yeah it is, I'm looking into it!

view this post on Zulip Richard Feldman (May 31 2026 at 01:55):

@Jonathan I think as a workaround, you can add a type annotation to delete:

delete : RBTree(k, v), k -> RBTree(k, v) where [
      k.is_eq: k, k -> Bool,
      k.is_lt: k, k -> Bool,
      k.is_gt: k, k -> Bool,
  ]
  delete = |tree, query| { ... }

view this post on Zulip Richard Feldman (May 31 2026 at 01:55):

(actually, strictly speaking you only need is_lt and is_eq constraints there)

view this post on Zulip Ian McLerran (May 31 2026 at 04:25):

Issue filed at #9491

view this post on Zulip Ian McLerran (May 31 2026 at 05:04):

From the best I could minimize, it looks like when a parametric recursive nominal type is destructured in one helper and then passed into a separate self-recursive function, Roc reports a bogus RBTree(k) vs RBTree(k) mismatch even though both types are identical.

view this post on Zulip Jonathan (May 31 2026 at 12:00):

Richard Feldman said:

delete : RBTree(k, v), k -> RBTree(k, v) where [
      k.is_eq: k, k -> Bool,
      k.is_lt: k, k -> Bool,
      k.is_gt: k, k -> Bool,
  ]

This got it working!

    x = RBTree.empty()
    x2 = x.insert(1, "Hello")
        .insert(2, "World!")
        .insert(3, "Nothin!")
        .delete(3)
    print!(x2.pairs())
roc main.roc --no-cache
[(1.0, "Hello"), (2.0, "world!")]

(I still feel like being able to alias common combinations like is_eq, is_lt, is_gt into Cmp(k) or Ord(k) or hasCmp(k) etc would be quite useful)

view this post on Zulip Richard Feldman (May 31 2026 at 14:25):

yeah I have a design for that but haven't implemented it, in part because I actually have personally been a fan of writing them out because of how explicit they are :smile:

view this post on Zulip Richard Feldman (May 31 2026 at 14:26):

but we can always add that capability in the future

view this post on Zulip Jonathan (May 31 2026 at 18:39):

That makes sense, whilst it does add a bit more line noise for me with repeated patterns, it's the kind of thing a good lsp code action (or agent I guess) will write.

view this post on Zulip Jonathan (May 31 2026 at 18:51):

Ian McLerran said:

Issue filed at #9491

One thing I've been meaning to ask, which relates to one of the changes that removes type parameters in this issue: can I create an alias inside a module that refers to the module's introduced type parameters? Eg

MyMod(a) := ...

and inside

MTry : Try(MyMod(a), err)

I think at some point roc check passed this.

Didn't work now :) nvm

view this post on Zulip Jared Ramirez (Jun 01 2026 at 20:40):

I've identified the issue here, working on a fix!

view this post on Zulip Jared Ramirez (Jun 02 2026 at 20:49):

have a fix up here: https://github.com/roc-lang/roc/pull/9512


Last updated: Jun 16 2026 at 16:19 UTC