Description
Hello everyone 👋
I believe we've found a bug with how extensible variant types are represented in the runtime. Since they have the same representation as exceptions in the compiler, they also get the same runtime value as exceptions. This means that they go through Caml_exceptions
which creates a runtime value based on the internal counter
in this module. So, to be more explicit, consider the example:
type t = ..
module A = {
type t +=
| FromModuleA
}
module B = {
type t +=
| FromModuleB
}
module C = {
let handleRequest = (req: t) => {
switch req {
| A.FromModuleA => "received from A"
| B.FromModuleB => "received from B"
}
}
}
Imagine that each module sits on its own file. Depending on the order that we load them, we'll have FromModuleA
with FromModuleA/2
and FromModuleB
with FromModuleB/1
or the other way around. Which means that module C
can be pattern matching on FromModuleA/1
but might get the runtime value of FromModuleA/2
. And this is all dependent on how the modules are loaded because the /{int}
is totally dependent on the runtime counter from Caml_exceptions.js
.
Of course this also happens for exceptions, but in case of exceptions it's not common to be pattern matching on them across different runtimes.
So, my suggestion is to generate the value for the extensible type / exception on the compile time. And we can avoid having collisions with the counter during the compile time, just like we do in the runtime. The only difference is that once it's compiled, the variant will have the same representation across any different context you load the module, independently of the order.