Skip to content

import placed after a nominal block: qualified reference inside the block silently fails to resolve, then crashes monomorphization #10221

Description

@grayrest

Compiler: release-fast-0a4869dd (panic surfaced with a matching debug- build)
Command: roc check repro.roc
Severity: compiler crash; the offending reference produces neither a binding nor a clear "not found" diagnostic.

Problem

A module contains a nominal block App := [].{ … } whose method body qualifies a name from an import (Host.now!), and the import pf.Host line is placed after the block. The qualified reference does not bind to that import — but instead of reporting an unresolved name, the checker records an erroneous type and the compiler later aborts in monomorphization with Common.invariant("erroneous checked type reached Monotype instantiation"). The crash only surfaces once the block's method is used from another module (repro.roc calls App.stamp!). Moving import pf.Host above the nominal block makes the whole program check cleanly (App_ok.roc is that one-line-different version). Imports are otherwise order-insensitive elsewhere, so the after-a-nominal-block position appears to be a resolution gap specific to references inside the block's associated methods.

Repro

  • repro.roc + App.roc (import after the block) — crashes.
  • Swap App.roc for App_ok.roc (import above the block) — checks clean.
$ 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
  src/postcheck/monotype/lower.zig:8428:47   in constrainTypeToMono
  src/postcheck/monotype/lower.zig:9285:45   in restoredHoistedConstAtType
  src/postcheck/monotype/lower.zig:9306:51   in restoredHoistedExprAtType
  src/postcheck/monotype/lower.zig:18662:47  in lowerExprAtType
  …

This reaches the same invariant as #10220 but via a different path (hoisted-const restoration rather than instNominalNode), and from a different root cause: an unresolved qualified reference producing an error type rather than a diagnostic. The desirable outcome is either that the import resolves regardless of its position relative to the block, or that an out-of-scope reference is reported as such instead of crashing.

Files

platform/main.roc:

platform ""
    requires {} {
        main : {
            things : List(Box({ ctx : U64 } => U64)),
        },
    }
    exposes [Host]
    packages {}
    provides { "roc_main": main_for_host! }
    hosted { "roc_now": Host.now! }
    targets: {
        inputs_dir: "targets/",
        arm64mac: { inputs: [app], output: Archive },
        x64mac: { inputs: [app], output: Archive },
    }

import Host

main_for_host! : {} => {}
main_for_host! = |_| {}

platform/Host.roc:

Host := {}.{
    now! : () => U64
}

App.roc (import placed AFTER the block — triggers the crash):

App := [].{
    # `Host.now!` is referenced here, but `import pf.Host` is placed AFTER this
    # nominal block (below). Moving the import ABOVE the block makes it check
    # cleanly.
    stamp! : () => U64
    stamp! = || Host.now!()
}

import pf.Host

App_ok.roc (the fix — import ABOVE the block; swap in for App.roc and it checks clean):

import pf.Host

App := [].{
    stamp! : () => U64
    stamp! = || Host.now!()
}

repro.roc (CRASHES — roc check repro.roc):

app [main] { pf: platform "platform/main.roc" }

import App

main = { things: [Box.box(handler!)] }

handler! = |_req| App.stamp!()

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