Skip to content

:refer-all chains don't establish type identity for spec-level type-name resolution #61

Description

@kumavis

Summary

When module M3 imports M2 with :refer-all, and M2 imports M1 with :refer-all, and M1 defines a type T, M3 can use T in function specs and bodies via the transitive chain. But the elaborator treats the spec's unqualified T and a body expression's fully-qualified prologos::M1::T (e.g. from a function call returning T) as DIFFERENT types — same definition, different name resolution path.

The fix is to add an explicit :refer [T] in M3 even though :refer-all transitively re-exports it. Specs and bodies then both resolve T to the same fully-qualified name.

Pitfall write-up at docs/tracking/2026-04-27_GOBLIN_PITFALLS.md entry #33.

Repro

In OCapN Phase 25.4 (commit f633e5a):

  • prologos::ocapn::promise defines PromiseState
  • prologos::ocapn::vat defines lookup-promise : Nat Vat -> Option PromiseState (imports promise via :refer-all)
  • prologos::ocapn::captp-bridge does require [prologos::ocapn::promise :refer-all] AND require [prologos::ocapn::vat :refer-all]
  • prologos::ocapn::bridge-interop-helpers does require [prologos::ocapn::captp-bridge :refer-all] (transitive chain to promise)

The failing function in bridge-interop-helpers.prologos:

spec lookup-after-step BridgeStep Nat -> [Option PromiseState]
defn lookup-after-step [step pid]
  lookup-promise pid [bridge-step-vat step]

Elaborator output:

Type mismatch
  expected: [Pi [x BridgeStep] [Pi [y Nat] [Option PromiseState]]]
  got:      [Pi [x BridgeStep] [Pi [y Nat]
              [Option prologos::ocapn::promise::PromiseState]]]

The spec's [Option PromiseState] resolves PromiseState via the :refer-all chain — but apparently NOT to the same fully-qualified binding as the body's lookup-promise return type. The elaborator's type-equality check is name-based and fails.

Workaround

Add an explicit :refer [PromiseState ...] to bridge-interop-helpers:

require [prologos::ocapn::promise
         :refer [PromiseState pst-unresolved pst-fulfilled pst-broken]]

After this, the spec's PromiseState resolves to the fully-qualified name and matches the body's. Function compiles cleanly.

Hypothesis

The :refer-all import re-exports terms but doesn't re-establish bindings in the importing module's "type-name resolution table" used by the spec parser. Specs are processed earlier than function bodies and may use a different name-lookup scope. The transitive case (M1 :refer-all chained through M2 :refer-all) compounds the issue: M3's spec parser doesn't see T as a known type until an explicit :refer [T] is added.

Why it's worth filing

  1. Confusing error message: "Type mismatch [Pi T] vs [Pi qualified::T]" looks like a tautology at first glance. The qualifier is the only difference.
  2. :refer-all chains are common in Prologos library modules — every multi-module library hits this when the chain crosses 2+ modules.
  3. Fix would make :refer-all behave as users expect — the term-vs-type asymmetry is surprising.

Frequency

Hit once in OCapN Phase 25.4 (during the workaround for #60). Will likely hit more often as protocol bridges and other library modules grow chains of :refer-all. Lower impact than #60 (the workaround is one line) but worth fixing.

Cross-references

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions