Hi! I've been poking around the glue platform, seeing what shapes it outputs for given types required in platform.roc. I've noticed some cases where it incorrectly handles []
type variables. I thought I remembered some discussion on this already, but I haven't been able to find it again in Zulip, nor in GitHub issues, so I thought I'd post my findings here.
The type [Always U32, Never []]
gets turned into the following shape by the glue platform: (TagUnion (Enumeration {name: "U1", size: 4, tags: ["Always", "Never"]}))
. It seems something about the prescence of the []
type is causing it to discard all the tag unions payloads, when it should discard just the Never
tag. The same happens if a tag has a unbound type variable payload, which should have outright crashed.
Just a []
on its own turns into the shape (TagUnion (NonRecursive {discriminantOffset: 0, discriminantSize: 1, name: "U1", tags: []}))
, even though EmptyTagUnion
is available.
Lastly, since the Result type seems to be special-cased, I tried Result U32 []
, which surprisingly resulted in the shape (Num U32)
. Layout-wise, this is correct, though I would have expected TagUnion (SingleTagStruct _)
instead.
At least for the first two, all signs point to tag unions being reported as Enumeration
tag unions by the glue platform where they shouldn't. Poking around in the platform code, it seems to only turn LayoutRepr::Builtin(Builtin::Int(_))
and LayoutRepr::Builtin(Builtin::Bool)
into Enumeration
s. If the problem were in the platform, I would expect to see it erroneously turn a LayoutRepr::Union(_)
into an Enumeration
. This means there's either something else going wrong in the platform I haven't spotted yet, or the problem was already present in mono. If it's the latter, I'd be way out of my depth at the moment.
Any thoughts on what to do with this from here?
I think @Brendan Hansknecht looked into a similar issue before
Really cool that you're finding these issues.
I have guesses, but mostly hope it is just a rust side roc glue issue and not a true compiler issue.
I would start by digging into the rust side of the glue crate
Fair, I'll see if I can verify which side the problem is coming from
I believe I've found the problem!
It has to do with enumerations and single tag unions with an integer payload having the same mono representation. When deciding on what shape to report a tag union as, the glue platform first checks the amount of tags it has. If it's exactly one, it figures it's dealing with a single tag struct, and reports it accordingly. If there's more than one, it takes the mono representation of the tag union and works backwards from there. Specifically, if it then sees mono representing what it knows is a tag union with more than one variant as an integer, it figures it must be dealing with an Enumeration.
This hits an edge case with the [Always U32, Never []]
type. The glue platform sees it has two tags in it, so it already decides this can't be a single tag union. When mono correctly eliminates the Never
tag, it sees the tag union only has one tag left with an integer payload, and correctly represents it as just the integer payload. The platform then sees a multi-tag union represented as an integer, and decides it must be the Enumeration [Always, Never]
.
Happily, this means this is isn't a mono bug after all!
Looking into it some more, I believe it'd be possible to fix this inside the glue platform. Every time it tries to report the shape of a tag union, it could recursively check each payload for if it is unconstructable if:
1) it is an empty tag union
2) it is a tag union that only contains tags with unconstructable payloads,
3) it is a record with an empty tag union value.
I can't say this feels like the most elegant possible solution, as mono must be already doing some of this checking itself but discards it by the time it reaches the glue platform, but I think I've got a shot at making it work. Should I give it a go or make an issue for someone else to find a more elegant solution?
I'd say go for it, but if it starts feeling like too big of a tangent, put it back for some other time :big_smile:
There seem to be two methods (on the UnionLabels
type) for retrieving the types of tag union payloads: variables()
and iter_from_subs()
. I wonder what the difference is between these two, and what the Subs
type is?
@Folkert de Vries might know?
well, Subs
is short for (type) substitutions. It is whatis known as a union-find data structure, that we use to resolve the sort of "well a is equal to, but b is equal to c, so a should be equal to c" kind of question
Gotcha. I think I've found the answer to my other question just now. It seems .variables()
just retuns slices of indices into the Subs
data structure which you can then retrieve the Variable
s from yourself, while .iter_from_subs()
indexes into Subs
for you. Kind of obvious looking at it now, guess I just needed fresh eyes!
Last updated: Jul 06 2025 at 12:14 UTC