Compiler: release-fast-0a4869dd (panic surfaced with a matching debug- build)
Command: roc check repro.roc
Problem
When a method on a nominal type (App := [].{ … }) returns a type that is defined in another module (here the platform-exposed Lib.Thing), and that method is instantiated from a module other than the one that declares the nominal type, monomorphization hits Common.invariant("erroneous checked type reached Monotype instantiation"). The program itself is well-typed — no type error is reported — and the crash disappears entirely if the exact same nominal declaration is moved into the module that uses it (see ok_same_module.roc, which checks clean). A release build SIGSEGVs at the same point (stack tracing disabled); a debug build panics with the message and stack below.
Repro
repro.roc — crashes. App (nominal, method returns Lib.Thing) lives in App.roc; main imports and instantiates it.
ok_same_module.roc — clean. Byte-for-byte the same code with App defined inline. The only variable is the module boundary.
$ roc check repro.roc
thread N panic: postcheck invariant violated: erroneous checked type reached Monotype instantiation
src/postcheck/monotype/lower.zig:8611:37 in instNodeContent (.def = try self.builder.typeDef(self.view, alias.origin_module, alias.name, alias.source_decl))
src/postcheck/monotype/lower.zig:8577:47 in instNode
src/postcheck/monotype/lower.zig:8693:78 in instNominalNode
src/postcheck/monotype/lower.zig:8651:59 in instNodeContent
…
The crash is in the nominal-node instantiation path (instNominalNode → the alias typeDef branch at lower.zig:8611): a checked type carried into monotype lowering is in the error state, which the invariant assumes cannot happen after a clean check.
Files
platform/main.roc:
platform ""
requires {} {
main : {
things : List(Box({ ctx : U64 } => Lib.Thing)),
},
}
exposes [Lib]
packages {}
provides { "roc_main": main_for_host! }
hosted {}
targets: {
inputs_dir: "targets/",
arm64mac: { inputs: [app], output: Archive },
x64mac: { inputs: [app], output: Archive },
}
import Lib
main_for_host! : {} => {}
main_for_host! = |_| {}
platform/Lib.roc:
Lib := [].{
Thing : { value : Str }
make : Str -> Lib.Thing
make = |v| { value: v }
}
App.roc:
App := [].{
# A method whose return type is the platform-defined nominal type `Lib.Thing`.
wrap = |v| Lib.make(v)
}
import pf.Lib
repro.roc (CRASHES — roc check repro.roc):
app [main] { pf: platform "platform/main.roc" }
import pf.Lib
import App
main = { things: [Box.box(handler!)] }
handler! = |_req| App.wrap("ok")
ok_same_module.roc (CHECKS CLEAN — the same code with App defined inline):
app [main] { pf: platform "platform/main.roc" }
import pf.Lib
App := [].{
wrap = |v| Lib.make(v)
}
main = { things: [Box.box(handler!)] }
handler! = |_req| App.wrap("ok")
Notes on scope
The return type's shape is not load-bearing — a trivial Lib.Thing : { value : Str } is enough; it does not need functions, boxes, or tag unions. The same panic (identical stack) is reached by several surface spellings of "a cross-module nominal method's type mentions a platform type", including: forwarding a value into a generic platform helper that returns the platform type; calling a platform function and returning a platform type; and adding an explicit -> Lib.Thing return annotation. Whether the reference is a return type, a forwarded generic result, or an annotation does not change the outcome. A cross-module nominal method that does not touch a platform type checks fine, so the boundary + the platform-defined type in the method's type are jointly required.
Compiler:
release-fast-0a4869dd(panic surfaced with a matchingdebug-build)Command:
roc check repro.rocProblem
When a method on a nominal type (
App := [].{ … }) returns a type that is defined in another module (here the platform-exposedLib.Thing), and that method is instantiated from a module other than the one that declares the nominal type, monomorphization hitsCommon.invariant("erroneous checked type reached Monotype instantiation"). The program itself is well-typed — no type error is reported — and the crash disappears entirely if the exact same nominal declaration is moved into the module that uses it (seeok_same_module.roc, which checks clean). A release build SIGSEGVs at the same point (stack tracing disabled); a debug build panics with the message and stack below.Repro
repro.roc— crashes.App(nominal, method returnsLib.Thing) lives inApp.roc;mainimports and instantiates it.ok_same_module.roc— clean. Byte-for-byte the same code withAppdefined inline. The only variable is the module boundary.The crash is in the nominal-node instantiation path (
instNominalNode→ the aliastypeDefbranch atlower.zig:8611): a checked type carried into monotype lowering is in the error state, which the invariant assumes cannot happen after a clean check.Files
platform/main.roc:
platform/Lib.roc:
App.roc:
repro.roc (CRASHES —
roc check repro.roc):ok_same_module.roc (CHECKS CLEAN — the same code with
Appdefined inline):Notes on scope
The return type's shape is not load-bearing — a trivial
Lib.Thing : { value : Str }is enough; it does not need functions, boxes, or tag unions. The same panic (identical stack) is reached by several surface spellings of "a cross-module nominal method's type mentions a platform type", including: forwarding a value into a generic platform helper that returns the platform type; calling a platform function and returning a platform type; and adding an explicit-> Lib.Thingreturn annotation. Whether the reference is a return type, a forwarded generic result, or an annotation does not change the outcome. A cross-module nominal method that does not touch a platform type checks fine, so the boundary + the platform-defined type in the method's type are jointly required.