on master help sign in
Scope local capitals and report direct import errors
commit45afb13fccd406bacdcccf305a4219d73f4d2850 authoriko <mail@iko.soy> dateFri 30 Oct 2026, 23:11 (1 day ago) parent10cbf9c
16 files changed+838−26
docs/compiler.md+2−2 docs/development.md+3−1 docs/status.md+4−3 jibb-dev/lib/jibb.hoon+235−12 tests/golden/types-local-capitals/about.txt+1−1 tests/golden/types-local-capitals/source.jibb+32 tests/golden/types-local-capitals/tokens.txt+227−3 tests/golden/types-local-capitals/tree.txt+103−1 tests/golden/types-local-capitals/types.txt+22−3 tests/lib/jibb-import-diagnostic.hoon+56 tests/type-import-diagnostic/about.txt+13 tests/type-import-diagnostic/expected.txt+76 tests/type-import-diagnostic/mark.txt+5 tests/type-import-diagnostic/source.txt+13 tests/type-import-diagnostic/sur.txt+5 tools/type-import-diagnostic-test+41
docs/compiler.md+2−2
@@ -56,7 +56,7 @@ The core is split into chapters, `+| %name`, in this order:
5656| `%span-check` | `+check-spans`, which `+golden` runs on every tree, and `+check-spans-test` |
5757| `%type-inference` | The initial inference fragment and its unification helpers; not yet a module type checker |
5858
59−The inference fragment supports fixed auras, unrestricted `@`, one-way widening into it and toward aura-family ancestors, generic variables and gates, faced and unfaced cells, cell sample binders, known record field reads, a two-binder cell pattern, a complete two-arm loobean branch, a complete two-arm unit-shaped branch, closed local mold aliases and acyclic forward value references. For a supported single syntactic top-level recursive gate, the checker infers the sample first and uses that exact sample, including its field faces, in a monomorphic self placeholder. The completed body must unify with the placeholder before the gate is generalized; incompatible recursive arguments can report the call and argument sites. Uses of generalized top-level definitions instantiate their schemes independently. For a complete loobean branch with one direct self-call arm, the checker can infer the other arm independently and use its closed, fully resolved type to seed the recursive result; it then checks the whole body. Open or pending base types do not seed the result, and other branch shapes still depend on the existing partial inference. A supported single local recursive gate also uses a monomorphic self reference while its body is checked, then generalizes variables free of its enclosing environment; captured variables remain shared. Acyclic local value definitions can refer forward. A written capital newly introduced in a local definition remains unsupported until the checker collects its enclosing lexical scope, including occurrences written later; a capital already present in the outer scope can be captured. Mutual recursive groups, polymorphic recursion and value cycles remain unsupported. Acyclic simple local mold aliases can also refer forward, and are scoped to the body, not its gate's sample. Nonrecursive generic aliases of supported molds can be applied at exact arity, including locally and through library interfaces; their parameters are lexical. Closed, provably disjoint nonrecursive unions and tagged single-case molds support contextual term values, `~` and `|=%tag` constructors. A null-headed cell can match a union case but does not supply a `|=%$` constructor; unresolved tags and nulls remain pending, with distinct partial-inference diagnostics. Nonrecursive top-level generic union molds with provably disjoint cases also support contextual tags and constructors at exact arity; their arguments are retained through generalization and supported library interfaces. Nonrecursive generic aliases can reorder, repeat or omit parameters while preserving a supported union’s origin, including through local scopes and library interfaces. A generic union declared directly inside a body remains unsupported, as do mutually recursive local groups and recursive molds. A value with type variable `A` can fit a field whose type is that same `A`, including in generic union payloads and nested or repeated record fields. A differing-variable face constraint remains unsupported. When inference forces two distinct explicitly written capitals equal, it reports both source sites; forcing a capital to a concrete type remains unsupported in this fragment. It can infer and generalize atom constraints arising from explicit capitals or bare sample binders, preserve those constraints through aliases, instantiate them independently, and print multiple constraints. Unresolved decimal variables default to `@ud` for inferred types without gates; defaulting inside gate types and other constraint cases remain incomplete. It does not yet check a module, complete imports, general patterns, general deferred constraints, recursive molds, dependency cycles or most errors with both conflicting excerpts. Definite cast and call mismatches, incompatible branches and overlapping union cases report both sites and excerpts. Concrete branch results can join at an explicitly present parent aura or `@`. Some known tuple and record positions acquire matching faces; other variable-dependent face merges, general branch tag context and ambiguous call-result propagation remain unsupported; function compatibility involving nested `@` and variable-dependent joins remain unsupported except for equal types. The reporting path repeats inference of a recursive gate rather than instantiating its generalized interface inside its own body. This adds work, and the current dependency pass can take quadratic time on long forward chains; the milestone's speed target remains unverified. A separate conservative pass reports unions whose cases are provably disjoint or overlapping and leaves other cases unresolved; it does not claim the mold is checked. Unsupported forms are reported as such, and the partial output says the module has not been checked.
59+The inference fragment supports fixed auras, unrestricted `@`, one-way widening into it and toward aura-family ancestors, generic variables and gates, faced and unfaced cells, cell sample binders, known record field reads, a two-binder cell pattern, a complete two-arm loobean branch, a complete two-arm unit-shaped branch, closed local mold aliases and acyclic forward value references. For a supported single syntactic top-level recursive gate, the checker infers the sample first and uses that exact sample, including its field faces, in a monomorphic self placeholder. The completed body must unify with the placeholder before the gate is generalized; incompatible recursive arguments can report the call and argument sites. Uses of generalized top-level definitions instantiate their schemes independently. For a complete loobean branch with one direct self-call arm, the checker can infer the other arm independently and use its closed, fully resolved type to seed the recursive result; it then checks the whole body. Open or pending base types do not seed the result, and other branch shapes still depend on the existing partial inference. A supported single local recursive gate also uses a monomorphic self reference while its body is checked, then generalizes variables free of its enclosing environment; captured variables remain shared. Acyclic local value definitions can refer forward. The checker collects written capitals per `++` before inference, excluding nested definitions: a capital used in an enclosing definition is shared with its locals, even when written later, while a capital introduced only in a local definition is generalized there. Sibling locals can introduce independent capitals with the same spelling; generic mold parameters keep their own scope. Mutual recursive groups, polymorphic recursion and value cycles remain unsupported. Acyclic simple local mold aliases can also refer forward, and are scoped to the body, not its gate's sample. Nonrecursive generic aliases of supported molds can be applied at exact arity, including locally and through library interfaces; their parameters are lexical. Closed, provably disjoint nonrecursive unions and tagged single-case molds support contextual term values, `~` and `|=%tag` constructors. A null-headed cell can match a union case but does not supply a `|=%$` constructor; unresolved tags and nulls remain pending, with distinct partial-inference diagnostics. Nonrecursive top-level generic union molds with provably disjoint cases also support contextual tags and constructors at exact arity; their arguments are retained through generalization and supported library interfaces. Nonrecursive generic aliases can reorder, repeat or omit parameters while preserving a supported union’s origin, including through local scopes and library interfaces. A generic union declared directly inside a body remains unsupported, as do mutually recursive local groups and recursive molds. A value with type variable `A` can fit a field whose type is that same `A`, including in generic union payloads and nested or repeated record fields. A differing-variable face constraint remains unsupported. When inference forces two distinct explicitly written capitals equal, it reports both source sites; forcing a capital to a concrete type remains unsupported in this fragment. It can infer and generalize atom constraints arising from explicit capitals or bare sample binders, preserve those constraints through aliases, instantiate them independently, and print multiple constraints. Unresolved decimal variables default to `@ud` for inferred types without gates; defaulting inside gate types and other constraint cases remain incomplete. It does not yet check a module, complete imports, general patterns, general deferred constraints, recursive molds, dependency cycles or most errors with both conflicting excerpts. Definite cast and call mismatches, incompatible branches and overlapping union cases report both sites and excerpts. Concrete branch results can join at an explicitly present parent aura or `@`. Some known tuple and record positions acquire matching faces; other variable-dependent face merges, general branch tag context and ambiguous call-result propagation remain unsupported; function compatibility involving nested `@` and variable-dependent joins remain unsupported except for equal types. The reporting path repeats inference of a recursive gate rather than instantiating its generalized interface inside its own body. This adds work, and the current dependency pass can take quadratic time on long forward chains; the milestone's speed target remains unverified. A separate conservative pass reports unions whose cases are provably disjoint or overlapping and leaves other cases unresolved; it does not claim the mold is checked. Unsupported forms are reported as such, and the partial output says the module has not been checked.
6060
6161For supported closed unions, the type printer keeps the union’s origin separate from its displayed name. It prints an in-scope name such as `msg` or `msg.lib`, including in records and gates; an accessible imported nongeneric alias may name a transitive union. A nongeneric body-local union that escapes its scope, or an origin without an accessible name, prints its cases separated by spaces, parenthesized when nested. That case-list form is print-only. A supported generic union prints its name and all arguments, including unused arguments; when its name is unavailable, the partial printer reports that limitation instead of dropping its arguments. Two faces for the same imported origin remain compatible; compatibility between distinct union origins, including in gate types, remains unsupported. Some comparisons of generic union arguments also remain unsupported rather than deciding equality or mismatch. The Hoon bridge also translates some imported unnamed unions into this representation; unsupported cases remain errors.
6262
@@ -72,7 +72,7 @@ The separate `jibb-dev/lib/jibb-import.hoon` is an unfinished reader for selecte
7272
7373Self-recursive `/sur` molds can cross the bridge as opaque module-and-name identities when their supported source-aware graph passes validation. Aliases, foreign references, recursive marks, unfolding, construction and field access remain unsupported; the partial printer declines recursive names that are not in scope.
7474
75−Callers that have a `/sur` core and a name can use `+read-named`: it checks for a locally declared arm before asking Hoon to evaluate the mold, and reports evaluation failures with the final Hoon trace line. `+read-mold-in` remains available when the caller already has the selected type. The opt-in `+read-named-diagnostic` and `+read-mark-diagnostic` preserve successful graphs and distinguish definite `%invalid` aura and core evidence emitted directly by the reader from `%unsupported` failures. Other failures, including unverified union disjointness and vase recognition based only on a name, remain unsupported. These entry points revalidate and re-walk failed types; the checker does not consume them yet and currently treats a failed import as unsupported rather than rendering the importer's error. It also fails closed on repeated field faces along an imported record spine, including recursive roots; the diagnostic remains generic. Hoon type graphs can erase unfaced nested-cell boundaries, so the check may conservatively reject a valid source with equal names in distinct erased scopes. Faced nested records and union alternatives keep separate scopes.
75+Callers that have a `/sur` core and a name can use `+read-named`: it checks for a locally declared arm before asking Hoon to evaluate the mold, and reports evaluation failures with the final Hoon trace line. `+read-mold-in` remains available when the caller already has the selected type. The opt-in `+read-named-diagnostic` and `+read-mark-diagnostic` preserve successful graphs and distinguish definite `%invalid` aura and core evidence emitted directly by the reader from `%unsupported` failures. Other failures, including unverified union disjointness and vase recognition based only on a name, remain unsupported. These entry points revalidate and re-walk failed types; the opt-in `+golden-types-with-reader-diagnostics` accepts unchanged structured reader results and reports definite aura or core errors at direct imported `%ref` casts or whole-sample mold use sites, when the import and supplied input are unique. Nested shapes, local aliases, transitive modules and unsupported reader outcomes still report generic limitations; this entry point remains partial. Existing checker entry points continue treating failed imports as unsupported. It also fails closed on repeated field faces along an imported record spine, including recursive roots; the diagnostic remains generic. Hoon type graphs can erase unfaced nested-cell boundaries, so the check may conservatively reject a valid source with equal names in distinct erased scopes. Faced nested records and union alternatives keep separate scopes.
7676
7777`+golden-types-with-prelude` accepts case-local Jibb module sources alongside `/sur` and mark results and the vendored prelude source. It resolves reachable ordinary library modules in dependency order, caching completed interfaces per invocation and exporting supported closed value schemes, direct generic unions, and simple or nonrecursive generic mold aliases whose bodies the fragment supports. It detects missing sources and cycles and rejects a dependency if its interface cannot be inferred by the fragment.
7878
docs/development.md+3−1
@@ -61,6 +61,7 @@ git clone https://github.com/urbit/tools /root/src/tools
6161| `tools/golden` | Runs the golden cases under `urbit eval`: every case, or those matching `--case`; `--update` rewrites the expected outputs; `--show` prints them |
6262| `tools/importer-test` | Compares the partial Hoon importer's diagnostic trees and errors with `tests/importer/tree.txt` under `urbit eval` |
6363| `tools/type-import-test` | Composes the Hoon importer with the partial checker and compares `tests/type-import/expected.txt` under eval |
64+| `tools/type-import-diagnostic-test` | Compares the structured reader diagnostic bridge report under eval |
6465| `tools/type-mark-test` | Checks the partial checker’s mark import boundary against `tests/type-mark/expected.txt` under eval |
6566| `tools/type-module-scope-test` | Checks the partial module checker’s prelude boundary and duplicate-source rejection against `tests/type-module-scope/expected.txt` under eval |
6667| `tools/clay-fixtures` | Syncs `my-app`, builds its example `/sur` files and marks through Clay, checks selected mold and mark projections and the partial checker, and records Ford and mark subject shapes on the fake ship |
@@ -78,6 +79,7 @@ git clone https://github.com/urbit/tools /root/src/tools
7879tools/golden # all cases pass
7980tools/importer-test # Hoon importer fixture projections match
8081tools/type-import-test # importer/checker bridge golden report matches
82+tools/type-import-diagnostic-test # direct structured reader diagnostics match
8183tools/type-mark-test # mark/checker bridge golden report matches
8284tools/type-module-scope-test # module/prelude scope golden report matches
8385tools/hoon check-spans-test # prints <<>>
@@ -90,7 +92,7 @@ tools/ship check-commit # when the mark or the desks changed
9092tools/clay-fixtures # when the my-app Hoon fixtures or importer changed
9193```
9294
93−`tools/ship test` regenerates `tests/lib/jibb.hoon` and syncs the `jibb-dev` desk before it runs, so commit the regenerated file. A run with `--stub`, `--lib`, or a custom `--golden` isolates its generated golden tests from the repository's other test programs; run the default command afterward to restore and test those programs. It also runs the independent `tests/lib/jibb-import.hoon` test against the same importer fixtures and expected diagnostic projection as `tools/importer-test`. The projection is an internal tree, not the Jibb type printer. `tests/lib/jibb-bridge.hoon` also runs the importer/checker bridge fixture on the ship; `tools/type-import-test` runs the same report under eval. The mark bridge has its own `tests/lib/jibb-mark-bridge.hoon` ship test and `tools/type-mark-test` eval test; both compare the same fixture report. `tests/lib/jibb-module-scope.hoon` and `tools/type-module-scope-test` compare the prelude scope report on the ship and under eval.
95+`tools/ship test` regenerates `tests/lib/jibb.hoon` and syncs the `jibb-dev` desk before it runs, so commit the regenerated file. A run with `--stub`, `--lib`, or a custom `--golden` isolates its generated golden tests from the repository's other test programs; run the default command afterward to restore and test those programs. It also runs the independent `tests/lib/jibb-import.hoon` test against the same importer fixtures and expected diagnostic projection as `tools/importer-test`. The projection is an internal tree, not the Jibb type printer. `tests/lib/jibb-bridge.hoon` also runs the importer/checker bridge fixture on the ship; `tools/type-import-test` runs the same report under eval. The structured reader diagnostics have their own `tests/lib/jibb-import-diagnostic.hoon` ship test and `tools/type-import-diagnostic-test` eval test. The mark bridge has its own `tests/lib/jibb-mark-bridge.hoon` ship test and `tools/type-mark-test` eval test; both compare the same fixture report. `tests/lib/jibb-module-scope.hoon` and `tools/type-module-scope-test` compare the prelude scope report on the ship and under eval.
9496
9597## Golden cases
9698
docs/status.md+4−3
@@ -19,7 +19,7 @@ On 2026-10-30, @iko decided that Jibb drops `$?`, so a `+$` takes a union's case
1919
2020## Milestone 2: in progress
2121
22−The repository spec records the subsequent type-system and prelude decisions; those changes also need synchronization to the Claude document. An initial inference fragment and a standalone Hoon type reader are being tested. The fragment explicitly reports that it does not check a module; partial bridges pass selected Hoon atoms, term singletons, nested faced or unfaced cells, supported disjoint unions and opaque standard collections from `/sur` molds and marks into inference. Collections retain their names and supported arguments for printing and unification; nested supported union arguments are retained too. The bridge trusts unchanged results from the source-aware reader; a materialized type graph alone cannot reveal cases Hoon already collapsed. The source-validated standard prelude `list` mold shares this representation with imported lists; other Jibb-written collection molds and most prelude collection operations remain unsupported; malformed names and arities fail closed. Selected self-recursive `/sur` molds cross as opaque module-and-name identities; aliases, foreign references, recursive marks, unfolding and construction remain unsupported. The reader handles mark samples, recursive molds whose origins can be verified through Ford imports, nested collections, direct ordered maps, certain ordered-map fields, direct `$^` unions whose cases can be proved disjoint, and one verified explicit union argument in a list, set, map or unit. Ambiguous recursive origins fail closed; recognizable explicit unions are checked from source before Hoon can collapse their cases, as @iko confirmed for imported `$%`, `$?` and `$^`, while other nested unions, nested ordered maps, general union disjointness and complete import integration remain unfinished. Opt-in structured reader APIs preserve successful graphs and distinguish definite unsupported-aura and core evidence from failures the reader cannot prove; unverified union disjointness and name-only vase recognition remain unsupported. They revalidate and re-walk failed types, and the checker does not consume their diagnostics yet. The partial checker resolves a subset of ordinary library imports in dependency order using supplied Jibb sources and a source-derived partial prelude interface, caches completed interfaces per run, and detects missing sources and cycles. It does not yet check API or kind semantics, and the prelude inventory is incomplete. The inference fragment supports unrestricted `@`, selected one-way widenings including aura-family ancestors, generalized atom constraints arising from explicit capitals or bare sample binders, acyclic local value definitions with captures, selected single self-recursive top-level and local gates, simple and nonrecursive generic mold aliases, selected faced record casts and field reads, and contextual tags, `~` and constructors for closed disjoint unions, including direct nonrecursive generic unions and propagation through complete loobean and unit-shaped branches in casts and known union-sample calls, including nested branches. Multi-argument calls form a right-nested sample, retain known faces and shared type variables, and can use known sample positions to contextualize list and union arguments. A definite mismatch points to the failing argument when the fragment can establish it, otherwise to the combined sample; unsupported contextual forms still fail closed. For a supported recursive gate, the checker infers the sample first and uses its exact type, including field faces, for one monomorphic self reference. It generalizes after the body is checked; subsequent uses instantiate it independently. Incompatible recursive arguments can report the gate and argument sites. For a complete loobean branch with one direct self-call arm, a separately inferred closed base type can seed the recursive result before the whole body is checked. Open or pending base types do not seed it; other branch shapes remain subject to the fragment’s existing limits. A supported single local recursive gate also uses a monomorphic self reference and generalizes variables free of the enclosing environment after checking its body; captured variables remain shared. A capital newly introduced in a local definition remains unsupported until the checker collects its enclosing lexical scope, including occurrences written later; a capital already present in the outer scope can be captured. Mutual groups, polymorphic recursion and value cycles remain unsupported. Reporting repeats inference for a recursive gate, so its performance cost remains unmeasured. Contextual null-headed cells fit their cases, but do not supply a `|=%$` constructor; unresolved nulls remain pending and are reported separately from tags. A known union of `~` and a null-headed cell supports a complete `~` / `[~ value]` branch in either order, including shadow checks; unknown subjects, missing or duplicate arms, and unresolved branch results remain unsupported. This fragment infers the prelude’s `fall` and `need` signatures. With the validated prelude list and a known cast or gate sample, empty, nonempty and nested list literals infer their element types, including contextual union tags; a mismatched element names both sites. In that context `~` and zero-valued terms fit an empty list, and two-item unfaced cells form cons lists, including nested cons and imported-list contexts; invalid heads or tails name both sites. Faced or longer cells and a missing or altered prelude list fail closed. A known list result also supplies context through complete loobean, unit-shaped and list-shaped branches, including recursive and nested branches. For a known standard list with a supported element type, a complete `~` / `[item rest]` branch infers the element and tail types in either arm order and checks binders for shadowing. Uncontextualized list construction, unknown subjects, unsupported element types and other or incomplete list patterns remain unsupported. A value with type variable `A` can fit a field whose type is that same `A`, including in generic union payloads and nested or repeated record fields. Other variable-dependent face merges remain unsupported. Forced equality of distinct written capitals now reports both source sites, following @iko’s decision; forcing one to a concrete type remains unsupported. Imported record spines with repeated faces fail closed where used, but only with a generic unsupported diagnostic. Hoon may erase unfaced nested-cell boundaries, so the check can conservatively reject valid names in distinct erased scopes. Supported closed unions print using an in-scope name or imported alias; an unnamed nongeneric union prints its cases, parenthesized when nested. The latter is print-only. Generic union types retain all arguments in their printed name; if that name is unavailable, the partial printer reports the limitation. Different import faces for the same origin remain compatible; compatibility across distinct union origins and some generic-argument comparisons remain unsupported. Nonrecursive generic aliases can reorder, repeat or omit parameters while retaining a union’s origin, including through local scopes and library interfaces. A generic union declared directly inside a body remains unsupported, as do differing-variable face constraints in a union payload. A transitive generic union whose name is unavailable is reported as unsupported instead of hiding its arguments. Unfixed decimal literals default to `@ud` in gate-free inferred types; mutually recursive local groups and recursive molds, general branch tag context, other constraint cases and defaulting inside gate types still need work. Collection recognition checks the originating core and fails closed on shadows or unsupported Ford subject shapes; a wrapper regression also rejects forged subjects, samples and tails. Its golden fixtures include three real Tlon files. A Clay test passes a Ford-built nested union through the reader into partial inference. The partial prelude includes `murn`, `join`, `snip`, `rear` and the newly agreed shared-type `clap`, parsed but still awaiting checker and backend validation, and [prelude-inventory.md](prelude-inventory.md) is a source audit, not the complete inventory or signatures. The milestone remains unfinished.
22+The repository spec records the subsequent type-system and prelude decisions; those changes also need synchronization to the Claude document. An initial inference fragment and a standalone Hoon type reader are being tested. The fragment explicitly reports that it does not check a module; partial bridges pass selected Hoon atoms, term singletons, nested faced or unfaced cells, supported disjoint unions and opaque standard collections from `/sur` molds and marks into inference. Collections retain their names and supported arguments for printing and unification; nested supported union arguments are retained too. The bridge trusts unchanged results from the source-aware reader; a materialized type graph alone cannot reveal cases Hoon already collapsed. The source-validated standard prelude `list` mold shares this representation with imported lists; other Jibb-written collection molds and most prelude collection operations remain unsupported; malformed names and arities fail closed. Selected self-recursive `/sur` molds cross as opaque module-and-name identities; aliases, foreign references, recursive marks, unfolding and construction remain unsupported. The reader handles mark samples, recursive molds whose origins can be verified through Ford imports, nested collections, direct ordered maps, certain ordered-map fields, direct `$^` unions whose cases can be proved disjoint, and one verified explicit union argument in a list, set, map or unit. Ambiguous recursive origins fail closed; recognizable explicit unions are checked from source before Hoon can collapse their cases, as @iko confirmed for imported `$%`, `$?` and `$^`, while other nested unions, nested ordered maps, general union disjointness and complete import integration remain unfinished. Opt-in structured reader APIs preserve successful graphs and distinguish definite unsupported-aura and core evidence from failures the reader cannot prove; unverified union disjointness and name-only vase recognition remain unsupported. They revalidate and re-walk failed types. An opt-in partial checker entry point consumes unchanged reader results and reports definite aura or core errors at direct imported cast and whole-sample references when their inputs are unique; nested shapes, aliases, transitive modules and unsupported reader outcomes remain generic. The partial checker resolves a subset of ordinary library imports in dependency order using supplied Jibb sources and a source-derived partial prelude interface, caches completed interfaces per run, and detects missing sources and cycles. It does not yet check API or kind semantics, and the prelude inventory is incomplete. The inference fragment supports unrestricted `@`, selected one-way widenings including aura-family ancestors, generalized atom constraints arising from explicit capitals or bare sample binders, acyclic local value definitions with captures, selected single self-recursive top-level and local gates, simple and nonrecursive generic mold aliases, selected faced record casts and field reads, and contextual tags, `~` and constructors for closed disjoint unions, including direct nonrecursive generic unions and propagation through complete loobean and unit-shaped branches in casts and known union-sample calls, including nested branches. Multi-argument calls form a right-nested sample, retain known faces and shared type variables, and can use known sample positions to contextualize list and union arguments. A definite mismatch points to the failing argument when the fragment can establish it, otherwise to the combined sample; unsupported contextual forms still fail closed. For a supported recursive gate, the checker infers the sample first and uses its exact type, including field faces, for one monomorphic self reference. It generalizes after the body is checked; subsequent uses instantiate it independently. Incompatible recursive arguments can report the gate and argument sites. For a complete loobean branch with one direct self-call arm, a separately inferred closed base type can seed the recursive result before the whole body is checked. Open or pending base types do not seed it; other branch shapes remain subject to the fragment’s existing limits. A supported single local recursive gate also uses a monomorphic self reference and generalizes variables free of the enclosing environment after checking its body; captured variables remain shared. The checker collects written capitals per `++` before inference, excluding nested definitions: a capital used in an enclosing definition is shared with its locals even when written later, while a capital introduced only in a local definition is generalized there. Sibling locals can introduce independent capitals with the same spelling; generic mold parameters keep their own scope. Mutual groups, polymorphic recursion and value cycles remain unsupported. Reporting repeats inference for a recursive gate, so its performance cost remains unmeasured. Contextual null-headed cells fit their cases, but do not supply a `|=%$` constructor; unresolved nulls remain pending and are reported separately from tags. A known union of `~` and a null-headed cell supports a complete `~` / `[~ value]` branch in either order, including shadow checks; unknown subjects, missing or duplicate arms, and unresolved branch results remain unsupported. This fragment infers the prelude’s `fall` and `need` signatures. With the validated prelude list and a known cast or gate sample, empty, nonempty and nested list literals infer their element types, including contextual union tags; a mismatched element names both sites. In that context `~` and zero-valued terms fit an empty list, and two-item unfaced cells form cons lists, including nested cons and imported-list contexts; invalid heads or tails name both sites. Faced or longer cells and a missing or altered prelude list fail closed. A known list result also supplies context through complete loobean, unit-shaped and list-shaped branches, including recursive and nested branches. For a known standard list with a supported element type, a complete `~` / `[item rest]` branch infers the element and tail types in either arm order and checks binders for shadowing. Uncontextualized list construction, unknown subjects, unsupported element types and other or incomplete list patterns remain unsupported. A value with type variable `A` can fit a field whose type is that same `A`, including in generic union payloads and nested or repeated record fields. Other variable-dependent face merges remain unsupported. Forced equality of distinct written capitals now reports both source sites, following @iko’s decision; forcing one to a concrete type remains unsupported. Imported record spines with repeated faces fail closed where used, but only with a generic unsupported diagnostic. Hoon may erase unfaced nested-cell boundaries, so the check can conservatively reject valid names in distinct erased scopes. Supported closed unions print using an in-scope name or imported alias; an unnamed nongeneric union prints its cases, parenthesized when nested. The latter is print-only. Generic union types retain all arguments in their printed name; if that name is unavailable, the partial printer reports the limitation. Different import faces for the same origin remain compatible; compatibility across distinct union origins and some generic-argument comparisons remain unsupported. Nonrecursive generic aliases can reorder, repeat or omit parameters while retaining a union’s origin, including through local scopes and library interfaces. A generic union declared directly inside a body remains unsupported, as do differing-variable face constraints in a union payload. A transitive generic union whose name is unavailable is reported as unsupported instead of hiding its arguments. Unfixed decimal literals default to `@ud` in gate-free inferred types; mutually recursive local groups and recursive molds, general branch tag context, other constraint cases and defaulting inside gate types still need work. Collection recognition checks the originating core and fails closed on shadows or unsupported Ford subject shapes; a wrapper regression also rejects forged subjects, samples and tails. Its golden fixtures include three real Tlon files. A Clay test passes a Ford-built nested union through the reader into partial inference. The partial prelude includes `murn`, `join`, `snip`, `rear` and the newly agreed shared-type `clap`, parsed but still awaiting checker and backend validation, and [prelude-inventory.md](prelude-inventory.md) is a source audit, not the complete inventory or signatures. The milestone remains unfinished.
2323
2424The shared prelude interface reserves every name present in the supplied vendored source and makes only supported inferred entries available, including `list`, `unit`, `same`, `fall`, `need`, `lure`, `weld` and `snoc`. The validated standard `list` mold is injected when the prelude’s own gates are inferred; `weld` has an ordinary explicit result cast. An absent or altered `list` is not injected, and an altered declaration cannot leak through direct or transitive mold aliases. List-dependent prelude mold aliases remain unsupported because their templates are prepared before injection. Unsupported entries such as most list helpers remain reserved but unavailable; backend validation of the helpers remains outstanding. All callers use the same prelude origin, so a library can export a type containing unqualified `(unit A)` to another module. Preparation rejects empty, malformed, conflicting or import-bearing source; the prepared value is trusted and must come unchanged from `+prepare-prelude`. The module-scope fixture compares the prepared and source-taking paths against the real prelude and a two-module golden under eval and on the ship. The source and checker remain incomplete, so this does not establish complete shadowing coverage or a checked module.
2525
@@ -40,7 +40,7 @@ What the plan asks for, and where it is:
4040Besides the plan's list:
4141
4242- 514 golden cases: 298 that parse and 216 that fail, each failure at a stated line and column. One failing case has an unclear expectation (corpus question 3). The newer type-output cases exercise only the partial inference fragment. Each case's `about.txt` quotes the doc's words it tests.
43−- The 514-case corpus passed under `urbit eval` in 61.6 s. The fake ship passed all 519 tests, including the importer, importer/checker and mark/checker bridges, module scope and unification checks, with 31.3 s for sync and 56.8 s for tests. The importer golden compares 149 projections; `tools/clay-fixtures` builds the example Hoon fixtures through Clay, checks a recursive Ford import's origin, and passes Clay-built types through the Clay-built importer and partial checker.
43+- The 514-case corpus passed under `urbit eval` in 57.0 s. The fake ship passed all 520 tests, including the importer, importer/checker and mark/checker bridges, module scope and unification checks, with 31.8 s for sync and 35.3 s for tests. The importer golden compares 149 projections; `tools/clay-fixtures` builds the example Hoon fixtures through Clay, checks a recursive Ford import's origin, and passes Clay-built types through the Clay-built importer and partial checker.
4444- `tools/testdata/selftest` (22 checks) and `tools/testdata/ship-selftest` test the tools themselves.
4545- Two independent audits before 7c739b7, one of which compared the library with a reference parser written in Python from the doc over about 19,000 generated programs. Before ab797a9, a second Python parser, written from the decisions, agreed with the library on 8,000 generated programs and 900 hand-written probes.
4646- Speed: starting `urbit eval` and building the library takes about 8 s. After that, a 2,000-line module of 400 definitions parses in about a second. Loops over definitions, arms, lines and items grow no stack; nesting costs a few stack frames a level, and 100,000 nested brackets still parse (library section 3).
@@ -89,7 +89,8 @@ Besides the plan's list:
8989| c7082e0 | 2026-10-30 | Infer multi-argument calls |
9090| 918acbd | 2026-10-30 | Infer recursive gates from their samples first |
9191| 1470d18 | 2026-10-30 | Infer selected prelude list helpers |
92−| This change | 2026-10-30 | Infer selected local recursive gates and distinguish reader diagnostics |
92+| 10cbf9c | 2026-10-30 | Infer selected local recursive gates and distinguish reader diagnostics |
93+| This change | 2026-10-30 | Infer local capital scopes and report direct reader diagnostics |
9394
9495## Checking it
9596
jibb-dev/lib/jibb.hoon+235−12
@@ -5839,6 +5839,51 @@
58395839 ?: ?=(^ (itype-vars typ.u.out)) ~
58405840 `[[typ.u.out ~] state.u.out(params params.state, in-mold in-mold.state)]
58415841::
5842+:: A direct imported reference may carry definite reader evidence. Only
5843+:: uniquely supplied, declared inputs are used; unsupported data stay generic.
5844+++ direct-import-invalid
5845+ |= [sh=shape state=istate]
5846+ ^- (unit idiag)
5847+ ?. ?=(%ref -.sh) ~
5848+ ?~ names.sh ~
5849+ =/ n=term i.names.sh
5850+ =/ raw=*
5851+ ?~ t.names.sh
5852+ ?. (unique-value-name n marks.state) ~
5853+ ?: (lien surs.state |=(x=term =(x n))) ~
5854+ ?: ?=(^ (find-jibb-interface n jimports.state)) ~
5855+ ?: (lien globals.state |=(x=term =(x n))) ~
5856+ ?: (lien reserved-molds.state |=(x=term =(x n))) ~
5857+ ?: (lien molds.state |=(m=[name=term body=shape] =(n name.m))) ~
5858+ =/ found=imark-externals (skim mark-externals.state |=(e=[mark=term result=*] =(mark.e n)))
5859+ ?~ found ~
5860+ ?^ t.found ~
5861+ result.i.found
5862+ ?^ t.t.names.sh ~
5863+ =/ mod=term i.t.names.sh
5864+ ?: ?=(^ (find-jibb-interface mod jimports.state)) ~
5865+ ?. (unique-value-name mod surs.state) ~
5866+ ?: (lien marks.state |=(x=term =(x mod))) ~
5867+ ?: (lien globals.state |=(x=term =(x mod))) ~
5868+ ?: (lien reserved-molds.state |=(x=term =(x mod))) ~
5869+ ?: (lien molds.state |=(m=[name=term body=shape] =(mod name.m))) ~
5870+ =/ found=iexternals (skim externals.state |=(e=[module=term name=term result=*] &(=(module.e mod) =(name.e n))))
5871+ ?~ found ~
5872+ ?^ t.found ~
5873+ result.i.found
5874+ ?. ?=([%invalid ?(%aura %core) @] raw) ~
5875+ `["{(trip +>.raw)}" ~[[at.sh "invalid imported mold"]]]
5876+::
5877+++ direct-sample-invalid
5878+ |= [s=sample state=istate]
5879+ ^- (unit idiag)
5880+ ?- s
5881+ [%face *] (direct-import-invalid shape.s state)
5882+ [%bare *] (direct-import-invalid shape.s state)
5883+ [%pun *] (direct-import-invalid [%ref at.s ~[name.s]] state)
5884+ * ~
5885+ ==
5886+::
58425887++ infer-simple-shape
58435888 |= [sh=shape state=istate]
58445889 ^- (unit [typ=itype state=istate])
@@ -6086,6 +6131,145 @@
60866131 ?~ inferred |
60876132 $(locals t.locals)
60886133::
6134+:: Gather written capitals belonging to one ++. Nested local definitions
6135+:: establish their own scopes; inline gates remain in the enclosing scope.
6136++$ icap-site [name=@t at=span]
6137+++ scope-shape
6138+ |= s=shape
6139+ ^- (list icap-site)
6140+ ?- -.s
6141+ %tvar ~[[name.s at.s]]
6142+ %apply (zing (turn args.s scope-shape))
6143+ %cell (zing (turn fields.s scope-field))
6144+ %union (zing (turn cases.s scope-shape))
6145+ %fn (weld (scope-shape sample.s) (scope-shape product.s))
6146+ %generic
6147+ =/ inside=(list icap-site) (scope-shape body.s)
6148+ (skim inside |=(site=icap-site !(lien vars.s |=(v=@t =(v name.site)))))
6149+ * ~
6150+ ==
6151+::
6152+++ scope-field
6153+ |= f=field
6154+ ^- (list icap-site)
6155+ ?- -.f
6156+ %face (scope-shape shape.f)
6157+ %bare (scope-shape shape.f)
6158+ %pun ~
6159+ ==
6160+::
6161+++ scope-sample
6162+ |= s=sample
6163+ ^- (list icap-site)
6164+ ?- -.s
6165+ %face (scope-shape shape.s)
6166+ %bare (scope-shape shape.s)
6167+ %cell (zing (turn items.s scope-sample))
6168+ * ~
6169+ ==
6170+::
6171+++ scope-pieces
6172+ |= ps=(list piece)
6173+ ^- (list icap-site)
6174+ ?~ ps ~
6175+ =/ first=(list icap-site)
6176+ ?- -.i.ps
6177+ %expr (scope-expr p.i.ps)
6178+ %text ~
6179+ ==
6180+ (weld first $(ps t.ps))
6181+::
6182+++ scope-qsegs
6183+ |= ps=(list qseg)
6184+ ^- (list icap-site)
6185+ ?~ ps ~
6186+ =/ first=(list icap-site)
6187+ ?- -.i.ps
6188+ %expr (scope-expr p.i.ps)
6189+ %knot ~
6190+ ==
6191+ (weld first $(ps t.ps))
6192+::
6193+++ scope-edits
6194+ |= xs=(list edit)
6195+ ^- (list icap-site)
6196+ ?~ xs ~
6197+ =/ first=(list icap-site) (scope-expr val.i.xs)
6198+ (weld first $(xs t.xs))
6199+::
6200+++ scope-items
6201+ |= xs=(list item)
6202+ ^- (list icap-site)
6203+ ?~ xs ~
6204+ =/ first=(list icap-site) (scope-expr val.i.xs)
6205+ (weld first $(xs t.xs))
6206+::
6207+++ scope-arms
6208+ |= xs=(list arm)
6209+ ^- (list icap-site)
6210+ ?~ xs ~
6211+ =/ first=(list icap-site) (scope-expr val.i.xs)
6212+ (weld first $(xs t.xs))
6213+::
6214+++ scope-exprs
6215+ |= xs=(list expr)
6216+ ^- (list icap-site)
6217+ ?~ xs ~
6218+ =/ first=(list icap-site) (scope-expr i.xs)
6219+ (weld first $(xs t.xs))
6220+::
6221+++ scope-expr
6222+ |= e=expr
6223+ ^- (list icap-site)
6224+ ?- -.e
6225+ %str (scope-pieces pieces.e)
6226+ %qath (scope-qsegs segs.e)
6227+ %change (scope-edits edits.e)
6228+ %pair (scope-expr val.e)
6229+ %unit (scope-expr val.e)
6230+ %group (scope-expr inner.e)
6231+ %list (scope-exprs items.e)
6232+ %tuple (scope-items items.e)
6233+ %gate
6234+ =/ first=(list icap-site) (scope-sample sample.e)
6235+ =/ last=(list icap-site) (scope-expr body.e)
6236+ (weld first last)
6237+ %gate-wide
6238+ =/ first=(list icap-site) (scope-sample sample.e)
6239+ =/ last=(list icap-site) (scope-expr body.e)
6240+ (weld first last)
6241+ %call
6242+ =/ first=(list icap-site) (scope-expr head.e)
6243+ =/ last=(list icap-site) (scope-exprs args.e)
6244+ (weld first last)
6245+ %cast
6246+ =/ first=(list icap-site) (scope-shape shape.e)
6247+ =/ last=(list icap-site) (scope-expr val.e)
6248+ (weld first last)
6249+ %trace
6250+ =/ first=(list icap-site) (scope-expr val.e)
6251+ =/ last=(list icap-site) (scope-expr then.e)
6252+ (weld first last)
6253+ %branch
6254+ =/ first=(list icap-site) (scope-expr subject.e)
6255+ =/ last=(list icap-site) (scope-arms arms.e)
6256+ (weld first last)
6257+ %body (scope-expr final.e)
6258+ * ~
6259+ ==
6260+::
6261+++ reserve-cap-sites
6262+ |= [sites=(list icap-site) state=istate]
6263+ ^- istate
6264+ ?~ sites state
6265+ ?: ?=(^ (icap-lookup name.i.sites caps.state)) $(sites t.sites)
6266+ $(sites t.sites, state state(next +(next.state), caps [[name.i.sites next.state at.i.sites] caps.state]))
6267+::
6268+++ reserve-def-caps
6269+ |= [e=expr state=istate]
6270+ ^- istate
6271+ (reserve-cap-sites (scope-expr e) state)
6272+::
60896273++ infer-local-defs
60906274 |= [ds=(list def) env=ienv state=istate]
60916275 ^- ilocal-result
@@ -6098,24 +6282,27 @@
60986282 $(ds (flop remaining), remaining ~, progressed |)
60996283 =/ d=def i.ds
61006284 ?> ?=(%value -.d)
6101− =/ inferred=iresult (infer-fragment val.d env state)
6285+ =/ local-state=istate (reserve-def-caps val.d state)
6286+ =/ inferred=iresult (infer-fragment val.d env local-state)
61026287 =/ retried=? ?=(%| -.inferred)
6103− =? inferred retried (infer-recursive-gate d env state)
6288+ =? inferred retried (infer-recursive-gate d env local-state)
61046289 ?: ?=(%| -.inferred) $(ds t.ds, remaining [d remaining])
6105− :: A new local capital needs lexical scope collection before it can be
6106− :: generalized independently or shared with a later enclosing occurrence.
6107− ?. (levy caps.state.p.inferred |=(cap=icap ?=(^ (icap-lookup name.cap caps.state))))
6108− $(ds t.ds, remaining [d remaining])
6290+ =/ merged=(unit [first=icap second=icap])
6291+ (merged-capitals caps.state.p.inferred sub.state.p.inferred)
6292+ ?^ merged
6293+ [%| ["distinct type variables `{(trip name.first.u.merged)}` and `{(trip name.second.u.merged)}` must not be forced equal; use the same capital for a shared type" ~[[at.first.u.merged "first type variable"] [at.second.u.merged "second type variable"]]]]
6294+ ?: (constrained-capitals caps.state.p.inferred sub.state.p.inferred)
6295+ [%| ["explicit capital constraints are not supported by partial inference" ~]]
61096296 =/ typ=itype (itype-normalize typ.p.inferred sub.state.p.inferred)
61106297 ?: |((pending-tags tags.state.p.inferred sub.state.p.inferred) (itype-has-tag typ))
61116298 $(ds t.ds, remaining [d remaining])
61126299 ?: (unsupported-atom-vars typ caps.state.p.inferred binders.state.p.inferred sub.state.p.inferred)
61136300 $(ds t.ds, remaining [d remaining])
61146301 =/ free=(list @ud)
6115− (weld (free-env-vars env sub.state.p.inferred) (free-cap-vars caps.state.p.inferred sub.state.p.inferred))
6302+ (weld (free-env-vars env sub.state.p.inferred) (free-cap-vars caps.state sub.state.p.inferred))
61166303 =/ vars=(list @ud) (unique-type-vars (itype-vars typ))
61176304 =. vars (skim vars |=(n=@ud !(lien free |=(f=@ud =(f n)))))
6118− $(ds t.ds, env [[name.d [typ vars]] env], state state.p.inferred, progressed &)
6305+ $(ds t.ds, env [[name.d [typ vars]] env], state state.p.inferred(caps caps.state), progressed &)
61196306::
61206307:: Read only fields of a known record. Unresolved field constraints and
61216308:: ambiguous repeated faces are outside this fragment. Imported record
@@ -6540,7 +6727,10 @@
65406727 [%& typ.p.left state.p.right(sub u.joined)]
65416728 %cast
65426729 =/ target=(unit [typ=itype state=istate]) (infer-simple-shape shape.e state)
6543− ?~ target [%| ["cast mold is not supported by partial inference" ~]]
6730+ ?~ target
6731+ =/ diag=(unit idiag) (direct-import-invalid shape.e state)
6732+ ?^ diag [%| u.diag]
6733+ [%| ["cast mold is not supported by partial inference" ~]]
65446734 =/ value=iresult
65456735 ?: &(?=(%union -.typ.u.target) (context-union-branch val.e))
65466736 (infer-union-context val.e typ.u.target env state.u.target at.shape.e)
@@ -6559,13 +6749,19 @@
65596749 %crash [%& [%var next.state] state(next +(next.state))]
65606750 %gate
65616751 =/ sam=isample-result (infer-sample sample.e env state)
6562− ?: ?=(%| -.sam) [%| [p.sam ~]]
6752+ ?: ?=(%| -.sam)
6753+ =/ diag=(unit idiag) (direct-sample-invalid sample.e state)
6754+ ?: &(=(p.sam "sample mold is not supported by partial inference") ?=(^ diag)) [%| u.diag]
6755+ [%| [p.sam ~]]
65636756 =/ body=iresult $(e body.e, env env.p.sam, state state.p.sam)
65646757 ?: ?=(%| -.body) body
65656758 [%& [%fn typ.p.sam typ.p.body] state.p.body]
65666759 %gate-wide
65676760 =/ sam=isample-result (infer-sample sample.e env state)
6568− ?: ?=(%| -.sam) [%| [p.sam ~]]
6761+ ?: ?=(%| -.sam)
6762+ =/ diag=(unit idiag) (direct-sample-invalid sample.e state)
6763+ ?: &(=(p.sam "sample mold is not supported by partial inference") ?=(^ diag)) [%| u.diag]
6764+ [%| [p.sam ~]]
65696765 =/ body=iresult $(e body.e, env env.p.sam, state state.p.sam)
65706766 ?: ?=(%| -.body) body
65716767 [%& [%fn typ.p.sam typ.p.body] state.p.body]
@@ -7042,7 +7238,15 @@
70427238 %gate-wide (infer-sample sample.gate env state)
70437239 * !!
70447240 ==
7045− ?: ?=(%| -.sam) [%| [p.sam ~]]
7241+ ?: ?=(%| -.sam)
7242+ =/ diag=(unit idiag)
7243+ ?- -.gate
7244+ %gate (direct-sample-invalid sample.gate state)
7245+ %gate-wide (direct-sample-invalid sample.gate state)
7246+ * ~
7247+ ==
7248+ ?: &(=(p.sam "sample mold is not supported by partial inference") ?=(^ diag)) [%| u.diag]
7249+ [%| [p.sam ~]]
70467250 =/ id=@ud next.state.p.sam
70477251 =/ product=itype ?~(result [%var id] u.result)
70487252 =/ st=istate state.p.sam(next +(id))
@@ -7070,6 +7274,7 @@
70707274 ?^ (ienv-lookup name.d env) $(ds t.ds)
70717275 =/ top=(list term) (turn molds |=(m=[name=term body=shape] name.m))
70727276 =/ initial=istate [0 ~ ~ ~ molds globals externals surs mark-externals marks jimports reserved-molds ~ ~ ~ | at top prelude]
7277+ =. initial (reserve-def-caps val.d initial)
70737278 =/ result=iresult (infer-fragment val.d env initial)
70747279 =? result ?=(%| -.result) (infer-recursive-gate d env initial)
70757280 ?: ?=(%| -.result) $(ds t.ds)
@@ -7541,6 +7746,23 @@
75417746:: entries, as [mark result] with the result from +read-mark:jibb-import.
75427747:: Unknown, duplicated, failed and undeclared entries never resolve a mold.
75437748::
7749+:: Callers pass unchanged structured reader results; graph validation
7750+:: remains in the existing bridge, and failures stay available lazily.
7751+++ reader-result-for-checker
7752+ |= result=*
7753+ ^- *
7754+ ?: ?=([%ok *] result) [%& +.result]
7755+ result
7756+::
7757+++ golden-types-with-reader-diagnostics
7758+ |= [file=path src=@t externals=iexternals mark-externals=imark-externals]
7759+ ^- @t
7760+ =/ ex=iexternals
7761+ (turn externals |=(e=[module=term name=term result=*] [module.e name.e (reader-result-for-checker result.e)]))
7762+ =/ mx=imark-externals
7763+ (turn mark-externals |=(e=[mark=term result=*] [mark.e (reader-result-for-checker result.e)]))
7764+ (golden-types-with-marks file src ex mx)
7765+::
75447766++ golden-types-with-marks
75457767 |= [file=path src=@t externals=iexternals mark-externals=imark-externals]
75467768 ^- @t
@@ -7568,6 +7790,7 @@
75687790 $(ds t.ds, lines [(crip "++ {(trip name.d)}: name already in scope") lines])
75697791 =/ top=(list term) (turn molds |=(m=[name=term body=shape] name.m))
75707792 =/ initial=istate [0 ~ ~ ~ molds globals externals surs mark-externals marks jimports reserved-molds ~ ~ ~ | file top prelude]
7793+ =. initial (reserve-def-caps val.d initial)
75717794 =/ result=iresult
75727795 ?: |(?=(%gate -.val.d) ?=(%gate-wide -.val.d))
75737796 (infer-recursive-gate d env initial)
tests/golden/types-local-capitals/about.txt+1−1
@@ -1,4 +1,4 @@
11source: Grammar, Molds: "A capital in a local `++` shares the enclosing `++`'s variable of the same name if the enclosing `++` also uses it; otherwise the local `++` introduces and generalizes its own variable."
2−tests: A recursive local gate captures a capital in its enclosing sample; a bare local binder generalizes independently. The partial checker conservatively leaves new written local capitals unsupported, including acyclic definitions, siblings and a capital written later in the enclosing body. Recursive self aliases remain monomorphic.
2+tests: A recursive local gate captures a capital in its enclosing sample; a bare local binder generalizes independently. Local-only capitals generalize independently, including across sibling and nested definitions; a capital used later in the enclosing body is shared. Generic mold parameters keep their own scope. Atom constraints and distinct-capital errors retain their source sites. Recursive self aliases remain monomorphic and unresolved tags remain pending.
33expect: parses
44expect-types: a partial inference report
tests/golden/types-local-capitals/source.jibb+32
@@ -39,3 +39,35 @@
3939 %.n (inner %.y)
4040 ~& ^- @t x
4141 (inner %.y)
42+++ nested |= x=@t
43+ ++ first |= y=A
44+ ++ inner |= z=B z
45+ ~& (inner ~zod)
46+ y
47+ (first x)
48+++ atom |= x=@t
49+ ++ inner |= y=A
50+ ~& ^- @ y
51+ y
52+ ~& (inner x)
53+ (inner ~zod)
54+++ force-equal |= x=@t
55+ ++ inner |= [a=A b=B] ^- A b
56+ (inner x ~zod)
57+++ pending |= x=@t
58+ ++ inner |= y=A
59+ ~& %orphan
60+ y
61+ (inner x)
62+++ mold-param |= x=A
63+ +$ box |$ [A] [value=A count=@ud]
64+ ^- (box @t) [value="hi" count=1]
65+++ later-share |= x
66+ ++ inner |= y=A y
67+ ~& (inner x)
68+ ^- A x
69+++ atom-bad |= x=@t
70+ ++ inner |= y=A
71+ ~& ^- @ y
72+ y
73+ (inner [x ~zod])
tests/golden/types-local-capitals/tokens.txt+227−3
@@ -244,6 +244,230 @@
24424441:6 tight name inner
24524541:12 lit %.y
24624641:15 tight glyph )
247−41:16 DEDENT
248−41:16 DEDENT
249−41:16 NEWLINE
247+42:1 DEDENT
248+42:1 DEDENT
249+42:1 NEWLINE
250+42:1 glyph ++
251+42:4 name nested
252+42:11 glyph |=
253+42:14 name x
254+42:15 tight glyph =
255+42:16 tight aura @t
256+43:3 INDENT
257+43:3 glyph ++
258+43:6 name first
259+43:12 glyph |=
260+43:15 name y
261+43:16 tight glyph =
262+43:17 tight tvar A
263+44:5 INDENT
264+44:5 glyph ++
265+44:8 name inner
266+44:14 glyph |=
267+44:17 name z
268+44:18 tight glyph =
269+44:19 tight tvar B
270+44:21 name z
271+45:5 NEWLINE
272+45:5 glyph ~&
273+45:8 glyph (
274+45:9 tight name inner
275+45:15 lit ~zod
276+45:19 tight glyph )
277+46:7 INDENT
278+46:7 name y
279+47:3 DEDENT
280+47:3 DEDENT
281+47:3 NEWLINE
282+47:3 glyph (
283+47:4 tight name first
284+47:10 name x
285+47:11 tight glyph )
286+48:1 DEDENT
287+48:1 NEWLINE
288+48:1 glyph ++
289+48:4 name atom
290+48:9 glyph |=
291+48:12 name x
292+48:13 tight glyph =
293+48:14 tight aura @t
294+49:3 INDENT
295+49:3 glyph ++
296+49:6 name inner
297+49:12 glyph |=
298+49:15 name y
299+49:16 tight glyph =
300+49:17 tight tvar A
301+50:5 INDENT
302+50:5 glyph ~&
303+50:8 glyph ^-
304+50:11 aura @
305+50:13 name y
306+51:7 INDENT
307+51:7 name y
308+52:3 DEDENT
309+52:3 DEDENT
310+52:3 NEWLINE
311+52:3 glyph ~&
312+52:6 glyph (
313+52:7 tight name inner
314+52:13 name x
315+52:14 tight glyph )
316+53:5 INDENT
317+53:5 glyph (
318+53:6 tight name inner
319+53:12 lit ~zod
320+53:16 tight glyph )
321+54:1 DEDENT
322+54:1 DEDENT
323+54:1 NEWLINE
324+54:1 glyph ++
325+54:4 name force-equal
326+54:16 glyph |=
327+54:19 name x
328+54:20 tight glyph =
329+54:21 tight aura @t
330+55:3 INDENT
331+55:3 glyph ++
332+55:6 name inner
333+55:12 glyph |=
334+55:15 glyph [
335+55:16 tight name a
336+55:17 tight glyph =
337+55:18 tight tvar A
338+55:20 name b
339+55:21 tight glyph =
340+55:22 tight tvar B
341+55:23 tight glyph ]
342+55:25 glyph ^-
343+55:28 tvar A
344+55:30 name b
345+56:3 NEWLINE
346+56:3 glyph (
347+56:4 tight name inner
348+56:10 name x
349+56:12 lit ~zod
350+56:16 tight glyph )
351+57:1 DEDENT
352+57:1 NEWLINE
353+57:1 glyph ++
354+57:4 name pending
355+57:12 glyph |=
356+57:15 name x
357+57:16 tight glyph =
358+57:17 tight aura @t
359+58:3 INDENT
360+58:3 glyph ++
361+58:6 name inner
362+58:12 glyph |=
363+58:15 name y
364+58:16 tight glyph =
365+58:17 tight tvar A
366+59:5 INDENT
367+59:5 glyph ~&
368+59:8 lit %orphan
369+60:7 INDENT
370+60:7 name y
371+61:3 DEDENT
372+61:3 DEDENT
373+61:3 NEWLINE
374+61:3 glyph (
375+61:4 tight name inner
376+61:10 name x
377+61:11 tight glyph )
378+62:1 DEDENT
379+62:1 NEWLINE
380+62:1 glyph ++
381+62:4 name mold-param
382+62:15 glyph |=
383+62:18 name x
384+62:19 tight glyph =
385+62:20 tight tvar A
386+63:3 INDENT
387+63:3 glyph +$
388+63:6 name box
389+63:10 glyph |$
390+63:13 glyph [
391+63:14 tight tvar A
392+63:15 tight glyph ]
393+63:17 glyph [
394+63:18 tight name value
395+63:23 tight glyph =
396+63:24 tight tvar A
397+63:26 name count
398+63:31 tight glyph =
399+63:32 tight aura @ud
400+63:35 tight glyph ]
401+64:3 NEWLINE
402+64:3 glyph ^-
403+64:6 glyph (
404+64:7 tight name box
405+64:11 aura @t
406+64:13 tight glyph )
407+64:15 glyph [
408+64:16 tight name value
409+64:21 tight glyph =
410+64:22 tight str "hi"
411+64:27 name count
412+64:32 tight glyph =
413+64:33 tight lit 1
414+64:34 tight glyph ]
415+65:1 DEDENT
416+65:1 NEWLINE
417+65:1 glyph ++
418+65:4 name later-share
419+65:16 glyph |=
420+65:19 name x
421+66:3 INDENT
422+66:3 glyph ++
423+66:6 name inner
424+66:12 glyph |=
425+66:15 name y
426+66:16 tight glyph =
427+66:17 tight tvar A
428+66:19 name y
429+67:3 NEWLINE
430+67:3 glyph ~&
431+67:6 glyph (
432+67:7 tight name inner
433+67:13 name x
434+67:14 tight glyph )
435+68:5 INDENT
436+68:5 glyph ^-
437+68:8 tvar A
438+68:10 name x
439+69:1 DEDENT
440+69:1 DEDENT
441+69:1 NEWLINE
442+69:1 glyph ++
443+69:4 name atom-bad
444+69:13 glyph |=
445+69:16 name x
446+69:17 tight glyph =
447+69:18 tight aura @t
448+70:3 INDENT
449+70:3 glyph ++
450+70:6 name inner
451+70:12 glyph |=
452+70:15 name y
453+70:16 tight glyph =
454+70:17 tight tvar A
455+71:5 INDENT
456+71:5 glyph ~&
457+71:8 glyph ^-
458+71:11 aura @
459+71:13 name y
460+72:7 INDENT
461+72:7 name y
462+73:3 DEDENT
463+73:3 DEDENT
464+73:3 NEWLINE
465+73:3 glyph (
466+73:4 tight name inner
467+73:10 glyph [
468+73:11 tight name x
469+73:13 lit ~zod
470+73:17 tight glyph ]
471+73:18 tight glyph )
472+73:19 DEDENT
473+73:19 NEWLINE
tests/golden/types-local-capitals/tree.txt+103−1
@@ -124,4 +124,106 @@
124124 (cast 40:6 (aura 40:9 @t) (wing 40:12 x))
125125 (body 41:5
126126 (group 41:5
127− (call 41:6 (wing 41:6 inner) (lit 41:12 %.y)))))))))
127+ (call 41:6 (wing 41:6 inner) (lit 41:12 %.y))))))))
128+ (value 42:1 nested
129+ (gate 42:11
130+ (face 42:14 x (aura 42:16 @t))
131+ (body 43:3
132+ (value 43:3 first
133+ (gate 43:12
134+ (face 43:15 y (tvar 43:17 A))
135+ (body 44:5
136+ (value 44:5 inner
137+ (gate 44:14
138+ (face 44:17 z (tvar 44:19 B))
139+ (wing 44:21 z)))
140+ (trace 45:5
141+ (group 45:8
142+ (call 45:9 (wing 45:9 inner) (lit 45:15 ~zod)))
143+ (body 46:7 (wing 46:7 y))))))
144+ (group 47:3
145+ (call 47:4 (wing 47:4 first) (wing 47:10 x))))))
146+ (value 48:1 atom
147+ (gate 48:9
148+ (face 48:12 x (aura 48:14 @t))
149+ (body 49:3
150+ (value 49:3 inner
151+ (gate 49:12
152+ (face 49:15 y (tvar 49:17 A))
153+ (body 50:5
154+ (trace 50:5
155+ (cast 50:8 (aura 50:11 @) (wing 50:13 y))
156+ (body 51:7 (wing 51:7 y))))))
157+ (trace 52:3
158+ (group 52:6
159+ (call 52:7 (wing 52:7 inner) (wing 52:13 x)))
160+ (body 53:5
161+ (group 53:5
162+ (call 53:6 (wing 53:6 inner) (lit 53:12 ~zod))))))))
163+ (value 54:1 force-equal
164+ (gate 54:16
165+ (face 54:19 x (aura 54:21 @t))
166+ (body 55:3
167+ (value 55:3 inner
168+ (gate 55:12
169+ (cell 55:15
170+ (face 55:16 a (tvar 55:18 A))
171+ (face 55:20 b (tvar 55:22 B)))
172+ (cast 55:25 (tvar 55:28 A) (wing 55:30 b))))
173+ (group 56:3
174+ (call 56:4 (wing 56:4 inner) (wing 56:10 x) (lit 56:12 ~zod))))))
175+ (value 57:1 pending
176+ (gate 57:12
177+ (face 57:15 x (aura 57:17 @t))
178+ (body 58:3
179+ (value 58:3 inner
180+ (gate 58:12
181+ (face 58:15 y (tvar 58:17 A))
182+ (body 59:5
183+ (trace 59:5
184+ (lit 59:8 %orphan)
185+ (body 60:7 (wing 60:7 y))))))
186+ (group 61:3
187+ (call 61:4 (wing 61:4 inner) (wing 61:10 x))))))
188+ (value 62:1 mold-param
189+ (gate 62:15
190+ (face 62:18 x (tvar 62:20 A))
191+ (body 63:3
192+ (mold 63:3 box
193+ (generic 63:10 A
194+ (cell 63:17
195+ (face 63:18 value (tvar 63:24 A))
196+ (face 63:26 count (aura 63:32 @ud)))))
197+ (cast 64:3
198+ (apply 64:6 box (aura 64:11 @t))
199+ (tuple 64:15
200+ (face 64:16 value (str 64:22 "hi"))
201+ (face 64:27 count (lit 64:33 1)))))))
202+ (value 65:1 later-share
203+ (gate 65:16
204+ (bind 65:19 x)
205+ (body 66:3
206+ (value 66:3 inner
207+ (gate 66:12
208+ (face 66:15 y (tvar 66:17 A))
209+ (wing 66:19 y)))
210+ (trace 67:3
211+ (group 67:6
212+ (call 67:7 (wing 67:7 inner) (wing 67:13 x)))
213+ (body 68:5
214+ (cast 68:5 (tvar 68:8 A) (wing 68:10 x)))))))
215+ (value 69:1 atom-bad
216+ (gate 69:13
217+ (face 69:16 x (aura 69:18 @t))
218+ (body 70:3
219+ (value 70:3 inner
220+ (gate 70:12
221+ (face 70:15 y (tvar 70:17 A))
222+ (body 71:5
223+ (trace 71:5
224+ (cast 71:8 (aura 71:11 @) (wing 71:13 y))
225+ (body 72:7 (wing 72:7 y))))))
226+ (group 73:3
227+ (call 73:4
228+ (wing 73:4 inner)
229+ (tuple 73:10 (wing 73:11 x) (lit 73:13 ~zod))))))))
tests/golden/types-local-capitals/types.txt+22−3
@@ -1,9 +1,28 @@
11partial inference; module not checked
2−++ siblings: local definitions need unsupported inference
3−++ later: local definitions need unsupported inference
2+++ siblings: $-(@t @p)
3+++ later: explicit capital constraints are not supported by partial inference
44++ captured: $-(A A)
55++ implicit: $-(A @p)
66++ local-mono: local definitions need unsupported inference
7−++ fresh: local definitions need unsupported inference
7+++ fresh: $-(@t @t)
88++ captured-acyclic: $-(A A)
99++ implicit-capture: $-(@t @t)
10+++ nested: $-(@t @t)
11+++ atom: $-(@t @p)
12+++ force-equal: distinct type variables `A` and `B` must not be forced equal; use the same capital for a shared type
13+/tests/golden/types-local-capitals/source.jibb:55:18: first type variable
14+ ++ inner |= [a=A b=B] ^- A b
15+ ^
16+/tests/golden/types-local-capitals/source.jibb:55:22: second type variable
17+ ++ inner |= [a=A b=B] ^- A b
18+ ^
19+++ pending: local definitions need unsupported inference
20+++ mold-param: $-(A [value=@t count=@ud])
21+++ later-share: $-(A A)
22+++ atom-bad: call's sample and argument types do not unify
23+/tests/golden/types-local-capitals/source.jibb:73:4: called gate has type $-(A A) where A is an atom
24+ (inner [x ~zod])
25+ ^
26+/tests/golden/types-local-capitals/source.jibb:73:10: argument has type [@t @p]
27+ (inner [x ~zod])
28+ ^
tests/lib/jibb-import-diagnostic.hoonnew file+56
@@ -0,0 +1,56 @@
1+:: Exercise definite and unsupported reader diagnostics through the checker.
2+/+ jibb, jibb-import
3+/* sur %txt /tests/type-import-diagnostic/sur/txt
4+/* mark %txt /tests/type-import-diagnostic/mark/txt
5+/* source %txt /tests/type-import-diagnostic/source/txt
6+/* expected %txt /tests/type-import-diagnostic/expected/txt
7+|%
8+++ text
9+ |= lines=wain
10+ ^- @t
11+ (rap 3 (turn lines |=(line=@t (cat 3 line '\0a'))))
12+++ test-import-diagnostic
13+ ^- tang
14+ ?~ sur ~[leaf+"diagnostic /sur fixture is missing"]
15+ ?~ mark ~[leaf+"diagnostic mark fixture is missing"]
16+ ?~ source ~[leaf+"diagnostic source fixture is missing"]
17+ ?~ expected ~[leaf+"diagnostic expected output is missing"]
18+ =/ got (diagnostic-output (text sur) (text mark) (text source))
19+ ?. =((to-wain:format (cat 3 got '\0a')) expected)
20+ ~[leaf+"import diagnostic output differs from tests/type-import-diagnostic/expected.txt"]
21+ ~
22+++ diagnostic-output
23+ |= [sur-text=@t mark-text=@t src=@t]
24+ ^- @t
25+ =/ cor=type -:(ride -:!>(..zuse) sur-text)
26+ =/ mark-cor=type -:(ride -:!>(..zuse) mark-text)
27+ =/ ext=(list [module=term name=term result=*])
28+ :~ [%fixture %signed (read-named-diagnostic:jibb-import %fixture %signed cor)]
29+ [%fixture %core (read-named-diagnostic:jibb-import %fixture %core cor)]
30+ [%fixture %good (read-named-diagnostic:jibb-import %fixture %good cor)]
31+ ==
32+ =/ marks=(list [name=term result=*])
33+ ~[[%bad-mark (read-mark-diagnostic:jibb-import %bad-mark mark-cor)]]
34+ =/ real (golden-types-with-reader-diagnostics:jibb /ui/test/jibb src ext marks)
35+ =/ sample=@t (crip "/- foo\0a++ cast ^- bad.foo 0\0a++ sample |= x=bad.foo x\0a++ safe |= x=@t x\0a")
36+ =/ noimport=@t (crip "++ cast ^- bad.foo 0\0a++ safe |= x=@t x\0a")
37+ =/ shadow=@t (crip "/- foo\0a++ shadow |= shadow=bad.foo shadow\0a++ safe |= x=@t x\0a")
38+ =/ invalid [%invalid %aura 'signed and float auras cannot be imported']
39+ =/ unsupported [%unsupported 'Hoon type is unsupported']
40+ =/ dup (golden-types-with-reader-diagnostics:jibb /test/jibb sample ~[[%foo %bad invalid] [%foo %bad invalid]] ~)
41+ =/ inconclusive (golden-types-with-reader-diagnostics:jibb /test/jibb sample ~[[%foo %bad unsupported]] ~)
42+ =/ bad-code (golden-types-with-reader-diagnostics:jibb /test/jibb sample ~[[%foo %bad [%invalid %other 'forged']]] ~)
43+ =/ bad-message (golden-types-with-reader-diagnostics:jibb /test/jibb sample ~[[%foo %bad [%invalid %aura [1 2]]]] ~)
44+ =/ absent (golden-types-with-reader-diagnostics:jibb /test/jibb noimport ~[[%foo %bad invalid]] ~)
45+ =/ collision (golden-types-with-reader-diagnostics:jibb /test/jibb shadow ~[[%foo %bad invalid]] ~)
46+ =/ missing (golden-types-with-reader-diagnostics:jibb /test/jibb sample ~ ~)
47+ =/ unused-src=@t (crip "/- foo\0a++ safe |= x=@t x\0a")
48+ =/ unused (golden-types-with-reader-diagnostics:jibb /test/jibb unused-src ~[[%foo %bad invalid]] ~)
49+ =/ import-clash=@t (crip "/- foo\0a/% %foo\0a++ use |= x=bad.foo x\0a++ use-mark |= x=foo x\0a")
50+ =/ value-clash=@t (crip "/- foo\0a++ foo \"value\"\0a++ use |= x=bad.foo x\0a")
51+ =/ mold-clash=@t (crip "/% %foo\0a+$ foo @t\0a++ use |= x=foo x\0a")
52+ =/ ic (golden-types-with-reader-diagnostics:jibb /test/jibb import-clash ~[[%foo %bad invalid]] ~[[%foo invalid]])
53+ =/ vc (golden-types-with-reader-diagnostics:jibb /test/jibb value-clash ~[[%foo %bad invalid]] ~)
54+ =/ mc (golden-types-with-reader-diagnostics:jibb /test/jibb mold-clash ~ ~[[%foo invalid]])
55+ (of-wain:format ~['real reader:' real 'duplicate:' dup 'unsupported:' inconclusive 'unknown code:' bad-code 'malformed message:' bad-message 'missing:' missing 'unused:' unused 'undeclared:' absent 'shadow:' collision 'import clash:' ic 'value clash:' vc 'mold clash:' mc])
56+--
tests/type-import-diagnostic/about.txtnew file+13
@@ -0,0 +1,13 @@
1+The Overview's Hoon import table makes signed and float auras, cores and
2+gates errors where used; unused molds are skipped. This fixture compiles a
3+/sur file and a mark against zuse and passes unchanged structured reader
4+results to the partial checker. Proven errors at direct cast and whole-sample
5+references identify the Jibb use site. Valid sibling definitions still infer;
6+nested references and aliases remain unsupported by this partial entry point.
7+
8+Synthetic controls verify that missing, duplicate, unsupported, undeclared or
9+unused inputs, shadowing and competing names do not produce a misleading
10+import error. Unknown diagnostic codes and malformed messages fail closed;
11+these shape checks do not establish the provenance of supplied evidence. The
12+same expected report runs under urbit eval and -test on the fake ship, and does
13+not claim that the module was checked.
tests/type-import-diagnostic/expected.txtnew file+76
@@ -0,0 +1,76 @@
1+real reader:
2+partial inference; module not checked
3++$ alias: not checked
4++$ box: not checked
5+++ valid: $-(@t @t)
6+++ unused-valid: @t
7+++ sample: signed and float auras cannot be imported
8+/ui/test.jibb:7:16: invalid imported mold
9+++ sample |= x=signed.fixture x
10+ ^
11+++ nested: sample mold is not supported by partial inference
12+++ alias-use: cast mold is not supported by partial inference
13+++ mark-use: signed and float auras cannot be imported
14+/ui/test.jibb:10:18: invalid imported mold
15+++ mark-use |= x=bad-mark x
16+ ^
17+++ core-sample: cores and gates cannot be imported
18+/ui/test.jibb:11:21: invalid imported mold
19+++ core-sample |= x=core.fixture x
20+ ^
21+++ core-cast: cores and gates cannot be imported
22+/ui/test.jibb:12:17: invalid imported mold
23+++ core-cast ^- core.fixture !!
24+ ^
25+++ signed-cast: signed and float auras cannot be imported
26+/ui/test.jibb:13:19: invalid imported mold
27+++ signed-cast ^- signed.fixture !!
28+ ^
29+duplicate:
30+partial inference; module not checked
31+++ cast: cast mold is not supported by partial inference
32+++ sample: sample mold is not supported by partial inference
33+++ safe: $-(@t @t)
34+unsupported:
35+partial inference; module not checked
36+++ cast: cast mold is not supported by partial inference
37+++ sample: sample mold is not supported by partial inference
38+++ safe: $-(@t @t)
39+unknown code:
40+partial inference; module not checked
41+++ cast: cast mold is not supported by partial inference
42+++ sample: sample mold is not supported by partial inference
43+++ safe: $-(@t @t)
44+malformed message:
45+partial inference; module not checked
46+++ cast: cast mold is not supported by partial inference
47+++ sample: sample mold is not supported by partial inference
48+++ safe: $-(@t @t)
49+missing:
50+partial inference; module not checked
51+++ cast: cast mold is not supported by partial inference
52+++ sample: sample mold is not supported by partial inference
53+++ safe: $-(@t @t)
54+unused:
55+partial inference; module not checked
56+++ safe: $-(@t @t)
57+undeclared:
58+partial inference; module not checked
59+++ cast: cast mold is not supported by partial inference
60+++ safe: $-(@t @t)
61+shadow:
62+partial inference; module not checked
63+++ shadow: sample shadows a name
64+++ safe: $-(@t @t)
65+import clash:
66+partial inference; module not checked
67+++ use: sample mold is not supported by partial inference
68+++ use-mark: sample mold is not supported by partial inference
69+value clash:
70+partial inference; module not checked
71+++ foo: @t
72+++ use: sample mold is not supported by partial inference
73+mold clash:
74+partial inference; module not checked
75++$ foo: not checked
76+++ use: sample mold is not supported by partial inference
tests/type-import-diagnostic/mark.txtnew file+5
@@ -0,0 +1,5 @@
1+|_ a=@s
2+++ grab |% ++ noun @s --
3+++ grow |% ++ noun a --
4+++ grad %noun
5+--
tests/type-import-diagnostic/source.txtnew file+13
@@ -0,0 +1,13 @@
1+/- fixture
2+/% %bad-mark
3++$ alias signed.fixture
4++$ box [ok=@t nested=[bad=core.fixture]]
5+++ valid |= x=good.fixture x
6+++ unused-valid "ok"
7+++ sample |= x=signed.fixture x
8+++ nested |= x=box x
9+++ alias-use ^- alias 1
10+++ mark-use |= x=bad-mark x
11+++ core-sample |= x=core.fixture x
12+++ core-cast ^- core.fixture !!
13+++ signed-cast ^- signed.fixture !!
tests/type-import-diagnostic/sur.txtnew file+5
@@ -0,0 +1,5 @@
1+|%
2++$ signed @s
3++$ core $-(@t @t)
4++$ good @t
5+--
tools/type-import-diagnostic-testnew file+41
@@ -0,0 +1,41 @@
1+#!/usr/bin/env python3
2+"""Run the importer/checker bridge fixture under urbit eval."""
3+
4+from pathlib import Path
5+import difflib
6+import subprocess
7+import sys
8+import tempfile
9+
10+root = Path(__file__).resolve().parent.parent
11+fixture = root / 'tests' / 'type-import-diagnostic'
12+reader = (root / 'jibb-dev/lib/jibb-import.hoon').read_text()
13+tests = (root / 'tests/lib/jibb-import-diagnostic.hoon').read_text()
14+tests = tests[tests.index('\n|%\n') + 1:]
15+expression = ('=/ jibb .\n=/ jibb-import\n' + reader +
16+ '\n=/ sur=wain (to-wain:format sur-text)\n'
17+ '=/ mark=wain (to-wain:format mark-text)\n'
18+ '=/ source=wain (to-wain:format source-text)\n'
19+ '=/ expected=wain (to-wain:format expected-text)\n'
20+ '=/ bridge\n' + tests +
21+ '\n(diagnostic-output:bridge sur-text mark-text source-text)\n')
22+with tempfile.TemporaryDirectory(prefix='jibb-type-import-') as tmp:
23+ program = Path(tmp) / 'bridge.hoon'
24+ program.write_text(expression)
25+ result = subprocess.run([
26+ str(root / 'tools/hoon'), '--raw', '-f', str(program),
27+ '--cord', 'sur-text=' + str(fixture / 'sur.txt'),
28+ '--cord', 'mark-text=' + str(fixture / 'mark.txt'),
29+ '--cord', 'source-text=' + str(fixture / 'source.txt'),
30+ '--cord', 'expected-text=' + str(fixture / 'expected.txt')],
31+ stdout=subprocess.PIPE)
32+if result.returncode:
33+ sys.exit(result.returncode)
34+expected = (fixture / 'expected.txt').read_bytes()
35+if result.stdout != expected:
36+ sys.stderr.writelines(difflib.unified_diff(
37+ expected.decode().splitlines(keepends=True),
38+ result.stdout.decode().splitlines(keepends=True),
39+ fromfile='expected.txt', tofile='actual types'))
40+ sys.exit(1)
41+print('importer diagnostic bridge golden passed')