Skip to content

roc check panics: "erroneous checked type reached Monotype instantiation" for a cross-module nominal method returning a platform type #10220

Description

@grayrest

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions