on master help sign in
Infer unit patterns and recursive Hoon identities
commite878edc869b8a5f2903faf0993b373c4602e9aed authoriko <mail@iko.soy> dateFri 30 Oct 2026, 20:57 (9 hours ago) parent4d45d2f
23 files changed+665−49
docs/compiler.md+5−1 docs/status.md+7−6 jibb-dev/lib/jibb.hoon+219−1 tests/clay-fixtures/ford-nested-union-bridge-expected.txt+1−1 tests/clay-fixtures/ford-nested-union-bridge.txt+4−2 tests/golden/prelude-1c-structural/types.txt+2−2 tests/golden/prelude-3g-basic/types.txt+2−2 tests/golden/prelude-3g-like/types.txt+2−2 tests/golden/prelude-3g-structural/types.txt+2−2 tests/golden/prelude-list-helpers/types.txt+2−2 tests/golden/prelude-list-unit/types.txt+2−2 tests/golden/prelude-unit-list-transforms/types.txt+6−6 tests/golden/types-unit-patterns/about.txt+4 tests/golden/types-unit-patterns/source.jibb+36 tests/golden/types-unit-patterns/tokens.txt+180 tests/golden/types-unit-patterns/tree.txt+88 tests/golden/types-unit-patterns/types.txt+10 tests/lib/jibb-bridge.hoon+14−1 tests/lib/jibb.hoon+6 tests/type-import/about.txt+14−11 tests/type-import/expected.txt+36−8 tests/type-import/source.txt+20 tests/type-import/sur.txt+3
docs/compiler.md+5−1
@@ -56,14 +56,18 @@ 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, closed local mold aliases and acyclic forward value references. It instantiates generalized top-level definitions when they are used. Acyclic local value definitions can refer forward and generalize variables that are free of their enclosing environment; captured variables and explicit capitals remain shared. 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 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. 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 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. It instantiates generalized top-level definitions when they are used. Acyclic local value definitions can refer forward and generalize variables that are free of their enclosing environment; captured variables and explicit capitals remain shared. 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 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. 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 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
63+A known union of `~` and a null-headed cell can be matched by `~` and `[~ value]` arms in either order. The payload binder is checked for shadowing. Unknown subjects, missing or duplicate arms, and unresolved branch results remain unsupported; this fragment infers the prelude’s `fall` and `need` signatures.
64+
6365A known closed union can also provide context to each arm of a complete loobean branch in a cast or a call with a known union sample. Nested branches and transparent groups work; a mismatch reports the expected union and the offending arm. General patterns and context through bodies with local definitions remain unsupported, and unrelated unresolved tags stay pending.
6466
6567The separate `jibb-dev/lib/jibb-import.hoon` is an unfinished reader for selected Hoon type graphs. It handles named recursion, nested map, set, queue, list, unit, jar and jug arguments, direct ordered maps, and direct `$^` unions whose source branches can be proved disjoint. It also reads ordered map fields in records when the map arguments are syntactically fixed atom molds and other fields are fixed atom molds or standard collections of static atom molds. Collection names must not be shadowed. The reader can also import a mark door sample, removing its outer sample face and validating its build subject. The reader checks explicit Hoon `$%`, `$?` and `$^` unions against the Grammar’s disjointness rule (doc question 22). It re-evaluates statically recognizable source branches before Hoon can collapse them, checks materialized fork cases, and rejects shapes it cannot prove disjoint. Static records may also have verified standard-collection and `path` siblings. One direct explicit union argument in a standard list, set, map or unit is accepted when its call wrapper and collection provenance are verified and its static cases are pairwise disjoint. A synthetic regression checks that forged wrapper subjects, samples and tails are rejected. Other collection-union forms, nested ordered maps, general source-level disjointness and complete integration with the checker remain unfinished. `+golden-types-imports` can consume fixed-aura atoms, valid term singletons, nested faced or unfaced cells, unions of supported, pairwise-disjoint cases, and opaque standard collections with supported arguments from caller-supplied reader results for declared `/-` imports; `+golden-types-with-marks` accepts the same shapes for declared marks. The caller must pass unchanged results from the source-aware reader: the bridge validates the supplied graph but cannot detect source cases Hoon collapsed before that graph was built. The opaque collection types retain their names and arguments for identity, unification and printing; Jibb-written collection molds and their prelude operations are not yet connected. Other forms remain unsupported. Its disjointness proof is conservative and can reject a valid union it cannot prove disjoint. Collection recognition requires the originating `/sur` core. The reader validates direct zuse subjects and Ford subjects consisting of faced imported cores and a validated tail, checking imported cores transitively; it rejects shadowed names, inconsistent core subjects and other subject shapes. For a recursive reference, the reader matches the exact Hoon hold against named arms in validated cores, including transitive Ford imports; it rejects missing, unlabelled or ambiguous origins. Its fixtures include three real Tlon files; `tools/importer-test` compares the internal projection under eval, and the ship test compares the same golden output. `tools/type-import-test` and `tools/type-mark-test` compare bridge reports under eval and on the ship. `tools/clay-fixtures` also passes Clay-built `/sur` molds and mark samples through the Clay-built importer and partial checker.
6668
69+Self-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.
70+
6771Callers 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 checker currently treats a failed import as unsupported rather than rendering the importer's error.
6872
6973`+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 reserves names from the supplied prelude, detects missing sources and cycles, and rejects a dependency if its interface cannot be inferred by the fragment. API files, modules with `main`, unsupported definitions and many other features remain outside this subset; the prelude inventory itself is incomplete. The older `+golden-types-with-modules` entry point fails closed for imports because it has no prelude scope. Neither entry point claims the module has been checked.
docs/status.md+7−6
@@ -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. Jibb-written collection molds, prelude collection operations and recursive references are not yet connected to this representation; malformed names and arities fail closed. 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 (doc question 22), while other nested unions, nested ordered maps, general union disjointness and complete import integration remain unfinished. The partial checker resolves a subset of ordinary library imports in dependency order using supplied Jibb sources and the current prelude names, 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, 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 branches in casts and known union-sample calls. Contextual null-headed cells fit their cases, but do not supply a `|=%$` constructor; unresolved nulls remain pending and are reported separately from tags. 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. 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; recursive local groups and 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` and `rear`, 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. Jibb-written collection molds and prelude collection operations are not yet connected to this representation; 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 (doc question 22), while other nested unions, nested ordered maps, general union disjointness and complete import integration remain unfinished. The partial checker resolves a subset of ordinary library imports in dependency order using supplied Jibb sources and the current prelude names, 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, 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 branches in casts and known union-sample calls. 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. 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. 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; recursive local groups and 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` and `rear`, 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
2424## Milestone 1: parser and mark
2525
@@ -37,8 +37,8 @@ What the plan asks for, and where it is:
3737
3838Besides the plan's list:
3939
40−- 493 golden cases: 277 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.
41−- The 493-case corpus passed under `urbit eval` in 59.0 s. The fake ship passed all 498 tests, including the importer, importer/checker and mark/checker bridges, module scope and unification checks, with 29.9 s for sync and 16.1 s for tests. The importer golden compares 141 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.
40+- 494 golden cases: 278 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.
41+- The 494-case corpus passed under `urbit eval` in 59.4 s. The fake ship passed all 499 tests, including the importer, importer/checker and mark/checker bridges, module scope and unification checks, with 25.3 s for sync and 24.3 s for tests. The importer golden compares 141 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.
4242- `tools/testdata/selftest` (22 checks) and `tools/testdata/ship-selftest` test the tools themselves.
4343- 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.
4444- 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).
@@ -74,14 +74,15 @@ Besides the plan's list:
7474| 250564c | 2026-10-30 | Infer selected named generic unions, test Hoon collection wrappers, and add list helpers |
7575| e95ef8f | 2026-10-30 | Bridge selected Hoon unions into partial inference and support generic union aliases |
7676| 56a899f | 2026-10-30 | Infer matching variable types in faced fields and bridge opaque Hoon collections |
77−| This change | 2026-10-30 | Resolve selected contextual null literals |
77+| 4d45d2f | 2026-10-30 | Resolve selected contextual null literals |
78+| This change | 2026-10-30 | Infer complete unit-shaped branches and bridge selected recursive Hoon molds |
7879
7980## Checking it
8081
8182On a machine set up as [development.md](development.md) says:
8283
8384```
84−tools/golden # 493 cases under urbit eval
85+tools/golden # 494 cases under urbit eval
8586tools/importer-test # the Hoon importer fixture projection
8687tools/type-import-test # the importer/checker bridge fixture
8788tools/type-mark-test # the mark/checker bridge fixture
@@ -91,7 +92,7 @@ tools/testdata/selftest # the tools themselves
9192tools/doc-blocks docs/spec # the doc's examples are golden cases
9293tools/ship start
9394tools/ship gen-tests --check # tests/lib/jibb.hoon is up to date
94−tools/ship test # the same 493 cases as -test arms on the ship
95+tools/ship test # the same 494 cases as -test arms on the ship
9596tools/testdata/ship-selftest
9697tools/ship check-commit # |commit stores .jibb files
9798tools/clay-fixtures # example /sur files and marks build through Clay
jibb-dev/lib/jibb.hoon+219−1
@@ -4611,6 +4611,7 @@
46114611 [%tag id=@ud literal=lit constructor=?]
46124612 [%const literal=lit]
46134613 [%collection name=term args=(list itype)]
4614+ [%recursive module=term name=term label=(unit @t)]
46144615 [%union cases=(list itype) origin=(unit [at=path name=term]) label=(unit @t) args=(list itype)]
46154616 [%face name=term body=itype]
46164617 [%fn sample=itype product=itype]
@@ -4663,6 +4664,7 @@
46634664 %tag =(n id.t)
46644665 %const |
46654666 %union |((lien cases.t |=(c=itype (itype-occurs n c sub))) (lien args.t |=(c=itype (itype-occurs n c sub))))
4667+ %recursive |
46664668 %collection (lien args.t |=(c=itype (itype-occurs n c sub)))
46674669 %face $(t body.t)
46684670 %fn |($(t sample.t) $(t product.t))
@@ -4681,6 +4683,8 @@
46814683 =. a (itype-walk a sub)
46824684 =. b (itype-walk b sub)
46834685 ?: =(a b) `sub
4686+ ?: &(?=(%recursive -.a) ?=(%recursive -.b))
4687+ ?:(&(=(module.a module.b) =(name.a name.b)) `sub ~)
46844688 ?: &(?=(%union -.a) ?=(%union -.b))
46854689 ?. =(origin.a origin.b) ~
46864690 ?: &(?=(~ args.a) ?=(~ args.b)) ?:(=(cases.a cases.b) `sub ~)
@@ -4756,6 +4760,7 @@
47564760 %tag |
47574761 %const |
47584762 %union |((lien cases.t |=(c=itype (itype-has-any-atom c sub))) (lien args.t |=(c=itype (itype-has-any-atom c sub))))
4763+ %recursive |
47594764 %collection (lien args.t |=(c=itype (itype-has-any-atom c sub)))
47604765 %face $(t body.t)
47614766 %fn |($(t sample.t) $(t product.t))
@@ -4825,6 +4830,7 @@
48254830 %tag &
48264831 %const &
48274832 %union |((lien cases.typ |=(c=itype (itype-has-fixed-atom c))) (lien args.typ |=(c=itype (itype-has-fixed-atom c))))
4833+ %recursive |
48284834 %collection (lien args.typ |=(c=itype (itype-has-fixed-atom c)))
48294835 %face $(typ body.typ)
48304836 %fn |($(typ sample.typ) $(typ product.typ))
@@ -4909,6 +4915,7 @@
49094915 %var &
49104916 %cell &
49114917 %union &
4918+ %recursive &
49124919 %collection &
49134920 %face $(t body.t)
49144921 %fn |($(t sample.t) $(t product.t))
@@ -4975,6 +4982,32 @@
49754982 * |
49764983 ==
49774984::
4985+++ itype-has-recursive
4986+ |= t=itype
4987+ ^- ?
4988+ ?- -.t
4989+ %recursive &
4990+ %collection (lien args.t |=(c=itype (itype-has-recursive c)))
4991+ %union (lien (weld args.t cases.t) |=(c=itype (itype-has-recursive c)))
4992+ %face $(t body.t)
4993+ %cell |($(t hed.t) $(t tal.t))
4994+ %fn |($(t sample.t) $(t product.t))
4995+ * |
4996+ ==
4997+::
4998+++ itype-has-hidden-recursive
4999+ |= t=itype
5000+ ^- ?
5001+ ?- -.t
5002+ %recursive ?=(~ label.t)
5003+ %collection (lien args.t |=(c=itype (itype-has-hidden-recursive c)))
5004+ %union (lien (weld args.t cases.t) |=(c=itype (itype-has-hidden-recursive c)))
5005+ %face $(t body.t)
5006+ %cell |($(t hed.t) $(t tal.t))
5007+ %fn |($(t sample.t) $(t product.t))
5008+ * |
5009+ ==
5010+::
49785011++ itype-fit-cases
49795012 |= [actual=itype cases=(list itype) sub=isub next=@ud]
49805013 ^- ifit
@@ -5022,6 +5055,9 @@
50225055 =. actual (itype-walk actual sub)
50235056 =. expected (itype-walk expected sub)
50245057 ?: =(actual expected) [%ok sub next]
5058+ ?: &(?=(%recursive -.actual) ?=(%recursive -.expected))
5059+ ?:(&(=(module.actual module.expected) =(name.actual name.expected)) [%ok sub next] [%bad ~])
5060+ ?: &(|(?=(%recursive -.actual) ?=(%recursive -.expected)) !|(?=(%var -.actual) ?=(%var -.expected))) [%unknown ~]
50255061 ?: &(?=(%union -.actual) ?=(%union -.expected))
50265062 ?. =(origin.actual origin.expected) [%unknown ~]
50275063 ?: &(?=(~ args.actual) ?=(~ args.expected))
@@ -5112,6 +5148,7 @@
51125148 =/ unified=(unit isub) (itype-unify actual expected sub)
51135149 ?~ unified
51145150 ?: |(?=(%face -.actual) ?=(%face -.expected)) [%unknown ~]
5151+ ?: |((itype-has-recursive (itype-normalize actual sub)) (itype-has-recursive (itype-normalize expected sub))) [%unknown ~]
51155152 ?: (itype-family-related actual expected sub) [%unknown ~]
51165153 [%bad ~]
51175154 [%ok u.unified next]
@@ -5126,6 +5163,7 @@
51265163 =/ unified=(unit isub) (itype-unify actual expected sub)
51275164 ?~ unified
51285165 ?: |((itype-has-union (itype-normalize actual sub)) (itype-has-union (itype-normalize expected sub))) [%unknown ~]
5166+ ?: |((itype-has-recursive (itype-normalize actual sub)) (itype-has-recursive (itype-normalize expected sub))) [%unknown ~]
51295167 ?: (itype-family-related actual expected sub) [%unknown ~]
51305168 [%bad ~]
51315169 [%ok u.unified next]
@@ -5171,6 +5209,20 @@
51715209 ["collection arguments should preserve aura-family uncertainty" problems]
51725210 =? problems !(itype-face-asym [%collection %list ~[[%face %x txt]]] list-t ~)
51735211 ["collection arguments should preserve face uncertainty" problems]
5212+ =/ recursive=itype [%recursive %fixture %chain `'chain.fixture']
5213+ =/ same-recursive=itype [%recursive %fixture %chain ~]
5214+ =/ other-recursive=itype [%recursive %other %chain `'chain.other']
5215+ =? problems ?=(~ (itype-unify recursive same-recursive ~))
5216+ ["recursive identity should not depend on its visible name" problems]
5217+ =? problems ?=(^ (itype-unify recursive other-recursive ~))
5218+ ["recursive identity should include the source module" problems]
5219+ =/ recursive-fit=ifit (itype-fit recursive txt ~ 0)
5220+ =? problems !?=(%unknown -.recursive-fit)
5221+ ["opaque recursive types should not be unfolded by a fit" problems]
5222+ =/ nested-fit=ifit
5223+ (itype-fit [%collection %list ~[a]] list-t ~[[0 recursive]] 1)
5224+ =? problems !?=(%unknown -.nested-fit)
5225+ ["recursive types behind substitutions should remain uncertain" problems]
51745226 problems
51755227::
51765228:: The expression inference below is deliberately a fragment: bindings,
@@ -5231,6 +5283,7 @@
52315283 ?- -.t
52325284 %fn &
52335285 %cell |($(t hed.t) $(t tal.t))
5286+ %recursive |
52345287 %collection (lien args.t |=(c=itype (itype-has-gate c)))
52355288 %face $(t body.t)
52365289 %union (lien cases.t |=(c=itype (itype-has-gate c)))
@@ -5269,6 +5322,7 @@
52695322 %tag ~
52705323 %const ~
52715324 %union (zing (turn (weld args.t cases.t) |=(c=itype (itype-vars c))))
5325+ %recursive ~
52725326 %collection (zing (turn args.t |=(c=itype (itype-vars c))))
52735327 %face $(t body.t)
52745328 %fn (weld $(t sample.t) $(t product.t))
@@ -5306,6 +5360,7 @@
53065360 %tag t
53075361 %const t
53085362 %union [%union (turn cases.t |=(c=itype (itype-rename c ren))) origin.t label.t (turn args.t |=(c=itype (itype-rename c ren)))]
5363+ %recursive t
53095364 %collection [%collection name.t (turn args.t |=(c=itype (itype-rename c ren)))]
53105365 %face [%face name.t $(t body.t)]
53115366 %fn [%fn $(t sample.t) $(t product.t)]
@@ -5328,6 +5383,7 @@
53285383 %tag |
53295384 %const |
53305385 %union |((lien cases.t |=(c=itype (itype-var-is-atom n c))) (lien args.t |=(c=itype (itype-var-is-atom n c))))
5386+ %recursive |
53315387 %collection (lien args.t |=(c=itype (itype-var-is-atom n c)))
53325388 %face $(t body.t)
53335389 %fn |($(t sample.t) $(t product.t))
@@ -5405,6 +5461,69 @@
54055461 ?. (is-lower i.chars) |
54065462 (levy t.chars |=(c=@tD (in c name-mask)))
54075463::
5464+:: A source-aware reader result is trusted for origin. Validate every
5465+:: supplied branch and keep the recursive mold opaque: unfolding its
5466+:: root while naming only its tail would change its type identity.
5467++$ irec-pattern [pat=upat has=?]
5468+++ rec-pattern
5469+ |= [node=* module=term name=term]
5470+ ^- (unit irec-pattern)
5471+ ?@ node ~
5472+ ?+ -.node ~
5473+ %atom
5474+ ?: ?=([%atom %n [~ @]] node)
5475+ ?. =(0 +>+.node) ~
5476+ `[[%atom `0] |]
5477+ ?: ?=([%atom %tas [~ @]] node)
5478+ =/ value=@ +>+.node
5479+ ?. |(=(value 0) (jibb-face-name value)) ~
5480+ `[[%atom `value] |]
5481+ ?. ?=([%atom @ ~] node) ~
5482+ =/ aura=@ta (@ta +<.node)
5483+ ?. |(?=(?(%u %ud %ux %uv %t %ta %tas %p %da %dr) aura) =('tD' aura)) ~
5484+ `[[%atom ~] |]
5485+ %ref
5486+ ?. ?=([%ref @ @] node) ~
5487+ ?. &(=(module +<.node) =(name +>.node)) ~
5488+ `[[%unknown ~] &]
5489+ %face
5490+ ?. ?=([%face @ *] node) ~
5491+ ?. (jibb-face-name +<.node) ~
5492+ $(node +>.node)
5493+ %cell
5494+ ?. ?=([%cell * *] node) ~
5495+ =/ left $(node +<.node)
5496+ ?~ left ~
5497+ =/ right $(node +>.node)
5498+ ?~ right ~
5499+ `[[%cell pat.u.left pat.u.right] |(has.u.left has.u.right)]
5500+ %fork
5501+ =/ cases (rec-patterns +.node module name)
5502+ ?~ cases ~
5503+ ?~ pats.u.cases ~
5504+ ?~ t.pats.u.cases ~
5505+ ?. (rec-patterns-disjoint pats.u.cases) ~
5506+ `[[%or pats.u.cases] has.u.cases]
5507+ ==
5508+::
5509+++ rec-patterns
5510+ |= [nodes=* module=term name=term]
5511+ ^- (unit [pats=(list upat) has=?])
5512+ ?@ nodes ?:(=(nodes 0) `[~ |] ~)
5513+ =/ first (rec-pattern -.nodes module name)
5514+ ?~ first ~
5515+ =/ rest $(nodes +.nodes)
5516+ ?~ rest ~
5517+ `[[pat.u.first pats.u.rest] |(has.u.first has.u.rest)]
5518+::
5519+++ rec-patterns-disjoint
5520+ |= pats=(list upat)
5521+ ^- ?
5522+ ?~ pats &
5523+ ?. (levy t.pats |=(p=upat =(%disjoint (union-relate i.pats p)))) |
5524+ $(pats t.pats)
5525+::
5526+::
54085527++ import-to-itype
54095528 |= node=*
54105529 ^- (unit itype)
@@ -5496,7 +5615,14 @@
54965615 =/ result=* result.i.found
54975616 ?@ result ~
54985617 ?. ?=(%& -.result) ~
5499− (import-to-itype +.result)
5618+ =/ node=* +.result
5619+ ?@ node (import-to-itype node)
5620+ ?: ?=(%ref -.node) (import-to-itype node)
5621+ =/ pattern=(unit irec-pattern) (rec-pattern node module name)
5622+ ?~ pattern (import-to-itype node)
5623+ ?. has.u.pattern (import-to-itype node)
5624+ `[%recursive module name `(crip "{(trip name)}.{(trip module)}")]
5625+
55005626::
55015627++ collect-sur-imports
55025628 |= imports=(list import)
@@ -5571,6 +5697,7 @@
55715697 ?~ alias ~
55725698 `(crip "{(trip u.alias)}.{(trip face)}")
55735699 [%union (turn cases.t |=(c=itype (itype-in-import c iface face))) origin.t label (turn args.t |=(c=itype (itype-in-import c iface face)))]
5700+ %recursive [%recursive module.t name.t ~]
55745701 %collection [%collection name.t (turn args.t |=(c=itype (itype-in-import c iface face)))]
55755702 %face [%face name.t $(t body.t)]
55765703 %fn [%fn $(t sample.t) $(t product.t)]
@@ -6006,6 +6133,85 @@
60066133 [%| ["branch arm does not fit its expected union" ~[[context "expected union"] [(span-of e) "branch arm"]]]]
60076134 [%& expected state.p.value(sub sub.fit, next next.fit)]
60086135::
6136+:: The supported unit pattern covers exactly an atom zero and a cell
6137+:: headed by zero, with a single binder for its payload. Unresolved
6138+:: subjects and all other pattern matrices stay outside this fragment.
6139+::
6140+++ unit-empty-pattern
6141+ |= p=pattern
6142+ ^- ?
6143+ ?. ?=(%lit -.p) |
6144+ =(%null form.lit.p)
6145+::
6146+++ unit-payload-pattern
6147+ |= p=pattern
6148+ ^- (unit term)
6149+ ?. ?=(%cell -.p) ~
6150+ ?~ items.p ~
6151+ ?~ t.items.p ~
6152+ ?^ t.t.items.p ~
6153+ ?. (unit-empty-pattern i.items.p) ~
6154+ ?. ?=(%bind -.i.t.items.p) ~
6155+ `name.i.t.items.p
6156+::
6157+++ unit-empty-case
6158+ |= t=itype
6159+ ^- ?
6160+ ?. ?=(%const -.t) |
6161+ =(0 val.literal.t)
6162+::
6163+++ unit-payload-case
6164+ |= t=itype
6165+ ^- (unit itype)
6166+ ?. ?=(%cell -.t) ~
6167+ ?. (unit-empty-case hed.t) ~
6168+ =/ payload=itype tal.t
6169+ =? payload ?=(%face -.payload) body.payload
6170+ `payload
6171+::
6172+++ unit-union-payload
6173+ |= t=itype
6174+ ^- (unit itype)
6175+ ?. ?=(%union -.t) ~
6176+ ?~ cases.t ~
6177+ ?~ t.cases.t ~
6178+ ?^ t.t.cases.t ~
6179+ ?: (unit-empty-case i.cases.t) (unit-payload-case i.t.cases.t)
6180+ ?: (unit-empty-case i.t.cases.t) (unit-payload-case i.cases.t)
6181+ ~
6182+::
6183+++ infer-unit-branch
6184+ |= [e=expr env=ienv state=istate]
6185+ ^- iresult
6186+ ?> ?=(%branch -.e)
6187+ ?~ arms.e [%| ["unit branch is not supported by partial inference" ~]]
6188+ ?~ t.arms.e [%| ["unit branch is not supported by partial inference" ~]]
6189+ ?^ t.t.arms.e [%| ["unit branch is not supported by partial inference" ~]]
6190+ =/ x=arm i.arms.e
6191+ =/ y=arm i.t.arms.e
6192+ =/ first-empty=? (unit-empty-pattern pat.x)
6193+ =/ first-name=(unit term) (unit-payload-pattern pat.x)
6194+ =/ second-name=(unit term) (unit-payload-pattern pat.y)
6195+ ?. |(&(first-empty ?=(^ second-name)) &((unit-empty-pattern pat.y) ?=(^ first-name)))
6196+ [%| ["unit branch pattern is not supported by partial inference" ~]]
6197+ =/ binder=(unit term) ?:(first-empty second-name first-name)
6198+ ?~ binder [%| ["unit branch pattern is not supported by partial inference" ~]]
6199+ ?: |(?=(^ (ienv-lookup u.binder env)) (lien globals.state |=(n=term =(n u.binder))))
6200+ [%| ["pattern binder shadows a name" ~]]
6201+ =/ subject=iresult (infer-fragment subject.e env state)
6202+ ?: ?=(%| -.subject) subject
6203+ =/ typ=itype (itype-normalize typ.p.subject sub.state.p.subject)
6204+ =/ payload=(unit itype) (unit-union-payload typ)
6205+ ?~ payload [%| ["unit branch subject is not supported by partial inference" ~]]
6206+ =/ extended=ienv [[u.binder [u.payload ~]] env]
6207+ =/ left=iresult (infer-fragment val.x ?:(first-empty env extended) state.p.subject)
6208+ ?: ?=(%| -.left) left
6209+ =/ right=iresult (infer-fragment val.y ?:(first-empty extended env) state.p.left)
6210+ ?: ?=(%| -.right) right
6211+ =/ joined=(unit isub) (itype-unify typ.p.left typ.p.right sub.state.p.right)
6212+ ?~ joined [%| ["unit branch results need unsupported inference" ~]]
6213+ [%& typ.p.left state.p.right(sub u.joined)]
6214+::
60096215++ infer-fragment
60106216 |= [e=expr env=ienv state=istate]
60116217 ^- iresult
@@ -6073,6 +6279,8 @@
60736279 ?^ t.t.arms.e [%| ["branch is not supported by partial inference" ~]]
60746280 =/ x=arm i.arms.e
60756281 =/ y=arm i.t.arms.e
6282+ ?: |((unit-empty-pattern pat.x) (unit-empty-pattern pat.y))
6283+ (infer-unit-branch e env state)
60766284 ?. ?=(%lit -.pat.x) [%| ["branch pattern is not supported by partial inference" ~]]
60776285 ?. ?=(%lit -.pat.y) [%| ["branch pattern is not supported by partial inference" ~]]
60786286 ?. &(=(%loobean form.lit.pat.x) =(%loobean form.lit.pat.y))
@@ -6093,6 +6301,8 @@
60936301 [%| ["branch widening is not supported by partial inference" ~]]
60946302 =/ joined=(unit isub) (itype-unify typ.p.left typ.p.right sub.state.p.right)
60956303 ?~ joined
6304+ ?: |((itype-has-recursive ltype) (itype-has-recursive rtype))
6305+ [%| ["recursive branch matching is not supported by partial inference" ~]]
60966306 ?: |((itype-has-tag ltype) (itype-has-tag rtype) (itype-has-union ltype) (itype-has-union rtype))
60976307 [%| ["branch tag or union context is not supported by partial inference" ~]]
60986308 =/ common=(unit itype) (itype-concrete-join typ.p.left typ.p.right sub.state.p.right)
@@ -6197,6 +6407,8 @@
61976407 =/ subject=iresult (infer-fragment subject.e env state)
61986408 ?: ?=(%| -.subject) subject
61996409 =/ st=istate state.p.subject
6410+ ?: (itype-has-recursive (itype-normalize typ.p.subject sub.st))
6411+ [%| ["recursive pattern is not supported by partial inference" ~]]
62006412 ?: (itype-has-face typ.p.subject sub.st)
62016413 [%| ["record patterns are not supported by partial inference" ~]]
62026414 =/ head=itype [%var next.st]
@@ -6228,6 +6440,7 @@
62286440 ?~ label.t (show-itype-cases cases.t sub)
62296441 ?~ args.t (trip u.label.t)
62306442 "({(trip u.label.t)} {(show-itype-cases args.t sub)})"
6443+ %recursive ?~(label.t "recursive mold" (trip u.label.t))
62316444 %collection "({(trip name.t)} {(show-itype-cases args.t sub)})"
62326445 %face "[{(trip name.t)}={(show-itype-context body.t sub)}]"
62336446 %fn "$-({(show-itype-context sample.t sub)} {(show-itype-context product.t sub)})"
@@ -6271,6 +6484,7 @@
62716484 %tag |
62726485 %const |
62736486 %union |((lien cases.typ |=(c=itype (itype-has-nvar c))) (lien args.typ |=(c=itype (itype-has-nvar c))))
6487+ %recursive |
62746488 %collection (lien args.typ |=(c=itype (itype-has-nvar c)))
62756489 %face $(typ body.typ)
62766490 %fn |($(typ sample.typ) $(typ product.typ))
@@ -6282,6 +6496,7 @@
62826496 ^- tape
62836497 =/ normalized=itype (itype-normalize typ sub)
62846498 ?: |(?=(%var -.normalized) ?=(%avar -.normalized) (itype-has-nvar normalized) (itype-has-tag normalized) (itype-has-union normalized)) label
6499+ ?: (itype-has-hidden-recursive normalized) label
62856500 =/ vars=(list @ud) (unique-type-vars (itype-vars normalized))
62866501 ?: (gth (lent vars) 26) label
62876502 =/ printable=itype (canonical-itype normalized vars)
@@ -6348,6 +6563,7 @@
63486563 %tag |
63496564 %const |
63506565 %union |((lien cases.t |=(c=itype (has-atom-var n c))) (lien args.t |=(c=itype (has-atom-var n c))))
6566+ %recursive |
63516567 %collection (lien args.t |=(c=itype (has-atom-var n c)))
63526568 %face $(t body.t)
63536569 %fn |($(t sample.t) $(t product.t))
@@ -6820,6 +7036,8 @@
68207036 $(ds t.ds, lines [(crip "++ {(trip name.d)}: `~` needs context in partial inference") lines])
68217037 ?: |((pending-tags tags.state.p.result sub.state.p.result) (itype-has-tag typ))
68227038 $(ds t.ds, lines [(crip "++ {(trip name.d)}: tag needs context in partial inference") lines])
7039+ ?: (itype-has-hidden-recursive typ)
7040+ $(ds t.ds, lines [(crip "++ {(trip name.d)}: recursive mold name is not available in this scope") lines])
68237041 ?: (itype-has-hidden-generic typ)
68247042 $(ds t.ds, lines [(crip "++ {(trip name.d)}: generic union name is not available in this scope") lines])
68257043 ?: (unsupported-atom-vars typ caps.state.p.result binders.state.p.result sub.state.p.result)
tests/clay-fixtures/ford-nested-union-bridge-expected.txt+1−1
@@ -1 +1 @@
1−'partial inference; module not checked\0a++ value: $-([id=@ud value=([a=@ud b=@t] @ud)] ([a=@ud b=@t] @ud))\0a++ values: $-([title=@t values=(list @ud)] (list @ud))'
1+'partial inference; module not checked\0a++ value: $-([id=@ud value=([a=@ud b=@t] @ud)] ([a=@ud b=@t] @ud))\0a++ values: $-([title=@t values=(list @ud)] (list @ud))\0a++ chain: $-(chain.blog chain.blog)'
tests/clay-fixtures/ford-nested-union-bridge.txt+4−2
@@ -2,11 +2,13 @@
22;< =bowl:spider bind:m get-bowl
33=/ now=@da now.bowl
44=/ src=vase .^(vase %ca /[(scot %p our.bowl)]/my-app/[(scot %da now)]/sur/import-fixture/hoon)
5+=/ blog=vase .^(vase %ca /[(scot %p our.bowl)]/my-app/[(scot %da now)]/sur/blog/hoon)
56=/ reader=vase .^(vase %ca /[(scot %p our.bowl)]/my-app/[(scot %da now)]/lib/jibb-import/hoon)
67=/ jibb=vase .^(vase %ca /[(scot %p our.bowl)]/my-app/[(scot %da now)]/lib/jibb/hoon)
78=/ read-gate=vase (slap reader (ream 'read-named'))
89=/ result=vase (slym read-gate [%import-fixture %nested -.src])
910=/ sample-result=vase (slym read-gate [%import-fixture %sample -.src])
11+=/ chain-result=vase (slym read-gate [%blog %chain -.blog])
1012=/ check-gate=vase (slap jibb (ream 'golden-types-imports'))
11−=/ text=@t '/- import-fixture\0a++ value |= x=nested.import-fixture value.x\0a++ values |= x=sample.import-fixture values.x\0a'
12−(pure:m (slym check-gate [/ui/test/jibb text ~[[%import-fixture %nested +.result] [%import-fixture %sample +.sample-result]]]))
13+=/ text=@t '/- import-fixture\0a/- blog\0a++ value |= x=nested.import-fixture value.x\0a++ values |= x=sample.import-fixture values.x\0a++ chain |= x=chain.blog x\0a'
14+(pure:m (slym check-gate [/ui/test/jibb text ~[[%import-fixture %nested +.result] [%import-fixture %sample +.sample-result] [%blog %chain +.chain-result]]]))
tests/golden/prelude-1c-structural/types.txt+2−2
@@ -5,8 +5,8 @@ partial inference; module not checked
55++ not: $-(? ?)
66+$ list: cases proven disjoint; mold not checked
77+$ unit: cases proven disjoint; mold not checked
8−++ fall: branch pattern is not supported by partial inference
9−++ need: branch pattern is not supported by partial inference
8+++ fall: $-([(unit A) A] A)
9+++ need: $-((unit A) A)
1010++ weld: sample mold is not supported by partial inference
1111++ turn: sample mold is not supported by partial inference
1212+$ dime: not checked
tests/golden/prelude-3g-basic/types.txt+2−2
@@ -5,8 +5,8 @@ partial inference; module not checked
55++ not: $-(? ?)
66+$ list: cases proven disjoint; mold not checked
77+$ unit: cases proven disjoint; mold not checked
8−++ fall: branch pattern is not supported by partial inference
9−++ need: branch pattern is not supported by partial inference
8+++ fall: $-([(unit A) A] A)
9+++ need: $-((unit A) A)
1010++ weld: sample mold is not supported by partial inference
1111++ turn: sample mold is not supported by partial inference
1212+$ dime: not checked
tests/golden/prelude-3g-like/types.txt+2−2
@@ -5,8 +5,8 @@ partial inference; module not checked
55++ not: $-(? ?)
66+$ list: cases proven disjoint; mold not checked
77+$ unit: cases proven disjoint; mold not checked
8−++ fall: branch pattern is not supported by partial inference
9−++ need: branch pattern is not supported by partial inference
8+++ fall: $-([(unit A) A] A)
9+++ need: $-((unit A) A)
1010++ weld: sample mold is not supported by partial inference
1111++ turn: sample mold is not supported by partial inference
1212+$ dime: not checked
tests/golden/prelude-3g-structural/types.txt+2−2
@@ -5,8 +5,8 @@ partial inference; module not checked
55++ not: $-(? ?)
66+$ list: cases proven disjoint; mold not checked
77+$ unit: cases proven disjoint; mold not checked
8−++ fall: branch pattern is not supported by partial inference
9−++ need: branch pattern is not supported by partial inference
8+++ fall: $-([(unit A) A] A)
9+++ need: $-((unit A) A)
1010++ weld: sample mold is not supported by partial inference
1111++ turn: sample mold is not supported by partial inference
1212+$ dime: not checked
tests/golden/prelude-list-helpers/types.txt+2−2
@@ -5,8 +5,8 @@ partial inference; module not checked
55++ not: $-(? ?)
66+$ list: cases proven disjoint; mold not checked
77+$ unit: cases proven disjoint; mold not checked
8−++ fall: branch pattern is not supported by partial inference
9−++ need: branch pattern is not supported by partial inference
8+++ fall: $-([(unit A) A] A)
9+++ need: $-((unit A) A)
1010++ weld: sample mold is not supported by partial inference
1111++ turn: sample mold is not supported by partial inference
1212+$ dime: not checked
tests/golden/prelude-list-unit/types.txt+2−2
@@ -5,7 +5,7 @@ partial inference; module not checked
55++ not: $-(? ?)
66+$ list: cases proven disjoint; mold not checked
77+$ unit: cases proven disjoint; mold not checked
8−++ fall: branch pattern is not supported by partial inference
9−++ need: branch pattern is not supported by partial inference
8+++ fall: $-([(unit A) A] A)
9+++ need: $-((unit A) A)
1010++ weld: sample mold is not supported by partial inference
1111++ turn: sample mold is not supported by partial inference
tests/golden/prelude-unit-list-transforms/types.txt+6−6
@@ -5,8 +5,8 @@ partial inference; module not checked
55++ not: $-(? ?)
66+$ list: cases proven disjoint; mold not checked
77+$ unit: cases proven disjoint; mold not checked
8−++ fall: branch pattern is not supported by partial inference
9−++ need: branch pattern is not supported by partial inference
8+++ fall: $-([(unit A) A] A)
9+++ need: $-((unit A) A)
1010++ weld: sample mold is not supported by partial inference
1111++ turn: sample mold is not supported by partial inference
1212+$ dime: not checked
@@ -39,10 +39,10 @@ partial inference; module not checked
3939++ lure: cast mold is not supported by partial inference
4040++ snoc: sample mold is not supported by partial inference
4141++ flop: sample mold is not supported by partial inference
42−++ drop: branch pattern is not supported by partial inference
43−++ bind: branch pattern is not supported by partial inference
44−++ biff: branch pattern is not supported by partial inference
45−++ both: branch pattern is not supported by partial inference
42+++ drop: unit branch results need unsupported inference
43+++ bind: expression is not supported by partial inference
44+++ biff: unit branch results need unsupported inference
45+++ both: expression is not supported by partial inference
4646++ clef: branch pattern is not supported by partial inference
4747++ skim: sample mold is not supported by partial inference
4848++ skip: sample mold is not supported by partial inference
tests/golden/types-unit-patterns/about.txtnew file+4
@@ -0,0 +1,4 @@
1+source: Grammar, Patterns: "Every case needs an arm or a catch-all" and "A name in a pattern always binds anew"; Modules: no name may shadow one already in scope.
2+tests: A known unit-shaped union is matched in either arm order; shadowed binders, unknown subjects, missing or duplicate arms, unresolved result tags and a discarded unrelated tag are not silently accepted.
3+expect: parses
4+expect-types: a partial inference report
tests/golden/types-unit-patterns/source.jibbnew file+36
@@ -0,0 +1,36 @@
1++$ maybe
2+ ~
3+ [~ value=@t]
4+++ need |= x=maybe
5+ ?- x
6+ ~ !!
7+ [~ value] value
8+++ reverse |= x=maybe
9+ ?- x
10+ [~ value] value
11+ ~ !!
12+++ shadow |= x=maybe
13+ ?- x
14+ ~ !!
15+ [~ x] x
16+++ unknown |= x
17+ ?- x
18+ ~ !!
19+ [~ value] value
20+++ missing |= x=maybe
21+ ?- x
22+ ~ !!
23+++ duplicate |= x=maybe
24+ ?- x
25+ ~ !!
26+ ~ !!
27+ [~ value] value
28+++ pending |= x=maybe
29+ ?- x
30+ ~ %none
31+ [~ value] %some
32+++ unrelated |= x=maybe
33+ ~& %unknown
34+ ?- x
35+ ~ !!
36+ [~ value] value
tests/golden/types-unit-patterns/tokens.txtnew file+180
@@ -0,0 +1,180 @@
1+1:1 glyph +$
2+1:4 name maybe
3+2:3 INDENT
4+2:3 lit ~
5+3:3 NEWLINE
6+3:3 glyph [
7+3:4 tight lit ~
8+3:6 name value
9+3:11 tight glyph =
10+3:12 tight aura @t
11+3:14 tight glyph ]
12+4:1 DEDENT
13+4:1 NEWLINE
14+4:1 glyph ++
15+4:4 name need
16+4:9 glyph |=
17+4:12 name x
18+4:13 tight glyph =
19+4:14 tight name maybe
20+5:3 INDENT
21+5:3 glyph ?-
22+5:6 name x
23+6:5 INDENT
24+6:5 lit ~
25+6:7 glyph !!
26+7:5 NEWLINE
27+7:5 glyph [
28+7:6 tight lit ~
29+7:8 name value
30+7:13 tight glyph ]
31+7:15 name value
32+8:1 DEDENT
33+8:1 DEDENT
34+8:1 NEWLINE
35+8:1 glyph ++
36+8:4 name reverse
37+8:12 glyph |=
38+8:15 name x
39+8:16 tight glyph =
40+8:17 tight name maybe
41+9:3 INDENT
42+9:3 glyph ?-
43+9:6 name x
44+10:5 INDENT
45+10:5 glyph [
46+10:6 tight lit ~
47+10:8 name value
48+10:13 tight glyph ]
49+10:15 name value
50+11:5 NEWLINE
51+11:5 lit ~
52+11:7 glyph !!
53+12:1 DEDENT
54+12:1 DEDENT
55+12:1 NEWLINE
56+12:1 glyph ++
57+12:4 name shadow
58+12:11 glyph |=
59+12:14 name x
60+12:15 tight glyph =
61+12:16 tight name maybe
62+13:3 INDENT
63+13:3 glyph ?-
64+13:6 name x
65+14:5 INDENT
66+14:5 lit ~
67+14:7 glyph !!
68+15:5 NEWLINE
69+15:5 glyph [
70+15:6 tight lit ~
71+15:8 name x
72+15:9 tight glyph ]
73+15:11 name x
74+16:1 DEDENT
75+16:1 DEDENT
76+16:1 NEWLINE
77+16:1 glyph ++
78+16:4 name unknown
79+16:12 glyph |=
80+16:15 name x
81+17:3 INDENT
82+17:3 glyph ?-
83+17:6 name x
84+18:5 INDENT
85+18:5 lit ~
86+18:7 glyph !!
87+19:5 NEWLINE
88+19:5 glyph [
89+19:6 tight lit ~
90+19:8 name value
91+19:13 tight glyph ]
92+19:15 name value
93+20:1 DEDENT
94+20:1 DEDENT
95+20:1 NEWLINE
96+20:1 glyph ++
97+20:4 name missing
98+20:12 glyph |=
99+20:15 name x
100+20:16 tight glyph =
101+20:17 tight name maybe
102+21:3 INDENT
103+21:3 glyph ?-
104+21:6 name x
105+22:5 INDENT
106+22:5 lit ~
107+22:7 glyph !!
108+23:1 DEDENT
109+23:1 DEDENT
110+23:1 NEWLINE
111+23:1 glyph ++
112+23:4 name duplicate
113+23:14 glyph |=
114+23:17 name x
115+23:18 tight glyph =
116+23:19 tight name maybe
117+24:3 INDENT
118+24:3 glyph ?-
119+24:6 name x
120+25:5 INDENT
121+25:5 lit ~
122+25:7 glyph !!
123+26:5 NEWLINE
124+26:5 lit ~
125+26:7 glyph !!
126+27:5 NEWLINE
127+27:5 glyph [
128+27:6 tight lit ~
129+27:8 name value
130+27:13 tight glyph ]
131+27:15 name value
132+28:1 DEDENT
133+28:1 DEDENT
134+28:1 NEWLINE
135+28:1 glyph ++
136+28:4 name pending
137+28:12 glyph |=
138+28:15 name x
139+28:16 tight glyph =
140+28:17 tight name maybe
141+29:3 INDENT
142+29:3 glyph ?-
143+29:6 name x
144+30:5 INDENT
145+30:5 lit ~
146+30:7 lit %none
147+31:5 NEWLINE
148+31:5 glyph [
149+31:6 tight lit ~
150+31:8 name value
151+31:13 tight glyph ]
152+31:15 lit %some
153+32:1 DEDENT
154+32:1 DEDENT
155+32:1 NEWLINE
156+32:1 glyph ++
157+32:4 name unrelated
158+32:14 glyph |=
159+32:17 name x
160+32:18 tight glyph =
161+32:19 tight name maybe
162+33:3 INDENT
163+33:3 glyph ~&
164+33:6 lit %unknown
165+34:5 INDENT
166+34:5 glyph ?-
167+34:8 name x
168+35:7 INDENT
169+35:7 lit ~
170+35:9 glyph !!
171+36:7 NEWLINE
172+36:7 glyph [
173+36:8 tight lit ~
174+36:10 name value
175+36:15 tight glyph ]
176+36:17 name value
177+36:22 DEDENT
178+36:22 DEDENT
179+36:22 DEDENT
180+36:22 NEWLINE
tests/golden/types-unit-patterns/tree.txtnew file+88
@@ -0,0 +1,88 @@
1+(module
2+ (mold 1:1 maybe
3+ (union 2:3
4+ (const 2:3 ~)
5+ (cell 3:3
6+ (const 3:4 ~)
7+ (face 3:6 value (aura 3:12 @t)))))
8+ (value 4:1 need
9+ (gate 4:9
10+ (face 4:12 x (ref 4:14 maybe))
11+ (body 5:3
12+ (branch 5:3
13+ (wing 5:6 x)
14+ (arm 6:5 (lit 6:5 ~) (crash 6:7))
15+ (arm 7:5
16+ (cell 7:5 (lit 7:6 ~) (bind 7:8 value))
17+ (wing 7:15 value))))))
18+ (value 8:1 reverse
19+ (gate 8:12
20+ (face 8:15 x (ref 8:17 maybe))
21+ (body 9:3
22+ (branch 9:3
23+ (wing 9:6 x)
24+ (arm 10:5
25+ (cell 10:5 (lit 10:6 ~) (bind 10:8 value))
26+ (wing 10:15 value))
27+ (arm 11:5 (lit 11:5 ~) (crash 11:7))))))
28+ (value 12:1 shadow
29+ (gate 12:11
30+ (face 12:14 x (ref 12:16 maybe))
31+ (body 13:3
32+ (branch 13:3
33+ (wing 13:6 x)
34+ (arm 14:5 (lit 14:5 ~) (crash 14:7))
35+ (arm 15:5
36+ (cell 15:5 (lit 15:6 ~) (bind 15:8 x))
37+ (wing 15:11 x))))))
38+ (value 16:1 unknown
39+ (gate 16:12
40+ (bind 16:15 x)
41+ (body 17:3
42+ (branch 17:3
43+ (wing 17:6 x)
44+ (arm 18:5 (lit 18:5 ~) (crash 18:7))
45+ (arm 19:5
46+ (cell 19:5 (lit 19:6 ~) (bind 19:8 value))
47+ (wing 19:15 value))))))
48+ (value 20:1 missing
49+ (gate 20:12
50+ (face 20:15 x (ref 20:17 maybe))
51+ (body 21:3
52+ (branch 21:3
53+ (wing 21:6 x)
54+ (arm 22:5 (lit 22:5 ~) (crash 22:7))))))
55+ (value 23:1 duplicate
56+ (gate 23:14
57+ (face 23:17 x (ref 23:19 maybe))
58+ (body 24:3
59+ (branch 24:3
60+ (wing 24:6 x)
61+ (arm 25:5 (lit 25:5 ~) (crash 25:7))
62+ (arm 26:5 (lit 26:5 ~) (crash 26:7))
63+ (arm 27:5
64+ (cell 27:5 (lit 27:6 ~) (bind 27:8 value))
65+ (wing 27:15 value))))))
66+ (value 28:1 pending
67+ (gate 28:12
68+ (face 28:15 x (ref 28:17 maybe))
69+ (body 29:3
70+ (branch 29:3
71+ (wing 29:6 x)
72+ (arm 30:5 (lit 30:5 ~) (lit 30:7 %none))
73+ (arm 31:5
74+ (cell 31:5 (lit 31:6 ~) (bind 31:8 value))
75+ (lit 31:15 %some))))))
76+ (value 32:1 unrelated
77+ (gate 32:14
78+ (face 32:17 x (ref 32:19 maybe))
79+ (body 33:3
80+ (trace 33:3
81+ (lit 33:6 %unknown)
82+ (body 34:5
83+ (branch 34:5
84+ (wing 34:8 x)
85+ (arm 35:7 (lit 35:7 ~) (crash 35:9))
86+ (arm 36:7
87+ (cell 36:7 (lit 36:8 ~) (bind 36:10 value))
88+ (wing 36:17 value)))))))))
tests/golden/types-unit-patterns/types.txtnew file+10
@@ -0,0 +1,10 @@
1+partial inference; module not checked
2++$ maybe: cases proven disjoint; mold not checked
3+++ need: $-(maybe @t)
4+++ reverse: $-(maybe @t)
5+++ shadow: pattern binder shadows a name
6+++ unknown: unit branch subject is not supported by partial inference
7+++ missing: branch pattern is not supported by partial inference
8+++ duplicate: branch is not supported by partial inference
9+++ pending: unit branch results need unsupported inference
10+++ unrelated: tag needs context in partial inference
tests/lib/jibb-bridge.hoon+14−1
@@ -31,6 +31,14 @@
3131 [%fixture %nested (read %nested)]
3232 [%fixture %constant (read %constant)]
3333 [%fixture %choice (read %choice)]
34+ [%fixture %chain (read %chain)]
35+ [%second %chain (read-named:jibb-import %second %chain cor)]
36+ [%fixture %other (read %other)]
37+ [%fixture %alias (read %alias)]
38+ [%fixture %foreign-ref [%& [%cell [%atom %ud ~] [%ref %other %foreign-ref]]]]
39+ [%fixture %overlap-ref [%& [%fork ~[[%atom %n [~ 0]] [%atom %tas [~ 0]] [%cell [%atom %ud ~] [%ref %fixture %overlap-ref]]]]]]
40+ [%fixture %raw-ref [%& [%ref %fixture %raw-ref]]]
41+ [%fixture %raw-null [%& [%atom %n [~ 0]]]]
3442 [%fixture %texts (read %texts)]
3543 [%fixture %numbers (read %numbers)]
3644 [%fixture %maybe (read %maybe)]
@@ -56,5 +64,10 @@
5664 [%missing %title [%& [%atom %t ~]]]
5765 ==
5866 =/ report=@t (golden-types-imports:jibb /test/jibb src external)
59− (cat 3 report (crip "\0areader-collapsed: {(show-result:jibb-import (read %collapsed))}"))
67+ =/ dep=@t (crip "/- fixture\0a++ chain-id |= x=chain.fixture x\0a")
68+ =/ root=@t (crip "/= helper /ui/helper\0a++ use chain-id.helper\0a")
69+ =/ prelude=@t (crip "++ prelude-id |= x x\0a")
70+ =/ modules=(list [at=path source=@t]) ~[[~[%ui %helper %jibb] dep]]
71+ =/ hidden=@t (golden-types-with-prelude:jibb /ui/main/jibb root external ~ modules prelude)
72+ (cat 3 report (cat 3 (crip "\0areader-collapsed: {(show-result:jibb-import (read %collapsed))}\0a") hidden))
6073--
tests/lib/jibb.hoon+6
@@ -1722,6 +1722,10 @@
17221722/* tok-493 %txt /tests/golden/types-union-unresolved-flow/tokens/txt
17231723/* tre-493 %txt /tests/golden/types-union-unresolved-flow/tree/txt
17241724/* typ-493 %txt /tests/golden/types-union-unresolved-flow/types/txt
1725+/* src-494 %jibb /tests/golden/types-unit-patterns/source/jibb
1726+/* tok-494 %txt /tests/golden/types-unit-patterns/tokens/txt
1727+/* tre-494 %txt /tests/golden/types-unit-patterns/tree/txt
1728+/* typ-494 %txt /tests/golden/types-unit-patterns/types/txt
17251729|%
17261730:: one arm per case
17271731::
@@ -2711,6 +2715,8 @@
27112715 (weld (check ~.types-union-unresolved src-492 `tok-492 `tre-492) (check-types ~.types-union-unresolved ~[%tests %golden ~.types-union-unresolved %source %jibb] src-492 `typ-492))
27122716++ test-types-union-unresolved-flow
27132717 (weld (check ~.types-union-unresolved-flow src-493 `tok-493 `tre-493) (check-types ~.types-union-unresolved-flow ~[%tests %golden ~.types-union-unresolved-flow %source %jibb] src-493 `typ-493))
2718+++ test-types-unit-patterns
2719+ (weld (check ~.types-unit-patterns src-494 `tok-494 `tre-494) (check-types ~.types-unit-patterns ~[%tests %golden ~.types-unit-patterns %source %jibb] src-494 `typ-494))
27142720::
27152721:: +check: run golden:jibb on a case and compare both outputs
27162722::
tests/type-import/about.txt+14−11
@@ -2,14 +2,17 @@ The checker accepts unchanged, successful results from the source-aware Hoon
22reader as a trusted boundary. It independently rejects malformed graphs and
33forks whose supplied cases overlap, but a graph alone cannot reveal source
44cases Hoon already collapsed. This fixture compiles a small /sur against zuse,
5−selects its molds, and passes the actual reader results to the inference
6−fragment. It checks fixed atoms, valid term singletons including Hoon `%$`,
7−nested faced records, unnamed unions, and opaque standard collections.
8−Matching and mismatched collections, nested union arguments, and malformed
9−collection names and arities are covered; Jibb-written collection molds and
10−recursive references remain unsupported. Forged
11−unsupported, malformed, singleton and overlapping forks are rejected; the
12−reader itself rejects an overlapping source union before Hoon can collapse it.
13−A missing import and a duplicate supplied mold fail closed. The types output
14−is partial and does not claim that the module was checked. The same fixture
15−runs under urbit eval and through -test on the fake ship.
5+selects its molds, and passes the actual reader results to inference. It checks
6+fixed atoms, term singletons including Hoon `%$`, nested faced records,
7+unnamed unions and opaque standard collections. It covers matching and
8+mismatched collections, nested union arguments, and malformed collection names
9+and arities. Jibb-written collection molds remain unsupported.
10+
11+A self-recursive /sur mold is an opaque module/name identity. Distinct names
12+or modules conflict; aliases, foreign references, unfolding and out-of-scope
13+names remain unsupported. The fixture checks these limits as well as malformed
14+references and overlapping recursive graphs. The reader itself rejects an
15+overlapping source union before Hoon can collapse it. A missing import and a
16+duplicate supplied mold fail closed. The types output is partial and does not
17+claim that the module was checked. The same fixture runs under urbit eval and
18+through -test on the fake ship.
tests/type-import/expected.txt+36−8
@@ -11,19 +11,19 @@ partial inference; module not checked
1111++ undeclared: sample mold is not supported by partial inference
1212++ yes: %no %yes
1313++ absent: cast mold and expression type do not unify
14−/test.jibb:13:14: cast mold
14+/test.jibb:14:14: cast mold
1515++ absent ^- choice.fixture %other
1616 ^
17−/test.jibb:13:29: expression
17+/test.jibb:14:29: expression
1818++ absent ^- choice.fixture %other
1919 ^
2020++ status: $-([status=(%deny %approve) text=@t] (%deny %approve))
2121++ nested-yes: [status=(%deny %approve) text=@t]
2222++ nested-absent: cast mold and expression type do not unify
23−/test.jibb:16:21: cast mold
23+/test.jibb:17:21: cast mold
2424++ nested-absent ^- nested-choice.fixture [%other "hi"]
2525 ^
26−/test.jibb:16:43: expression
26+/test.jibb:17:43: expression
2727++ nested-absent ^- nested-choice.fixture [%other "hi"]
2828 ^
2929++ overlap: sample mold is not supported by partial inference
@@ -40,17 +40,17 @@ partial inference; module not checked
4040++ get-items: $-([items=(list @t) slots=(unit @ud)] (list @t))
4141++ same-list: $-((list @t) (list @t))
4242++ wrong-item: call's sample and argument types do not unify
43−/test.jibb:30:37: called gate has type $-((list @t) (list @t))
43+/test.jibb:31:37: called gate has type $-((list @t) (list @t))
4444++ wrong-item |= x=numbers.fixture (text-list x)
4545 ^
46−/test.jibb:30:47: argument has type (list @ud)
46+/test.jibb:31:47: argument has type (list @ud)
4747++ wrong-item |= x=numbers.fixture (text-list x)
4848 ^
4949++ wrong-kind: call's sample and argument types do not unify
50−/test.jibb:31:35: called gate has type $-((list @t) (list @t))
50+/test.jibb:32:35: called gate has type $-((list @t) (list @t))
5151++ wrong-kind |= x=maybe.fixture (text-list x)
5252 ^
53−/test.jibb:31:45: argument has type (unit @t)
53+/test.jibb:32:45: argument has type (unit @t)
5454++ wrong-kind |= x=maybe.fixture (text-list x)
5555 ^
5656++ bad-kind: sample mold is not supported by partial inference
@@ -59,4 +59,32 @@ partial inference; module not checked
5959++ bucket-id: $-((map @ud @t) (map @ud @t))
6060++ raw-rec: sample mold is not supported by partial inference
6161++ direct-list: cast mold is not supported by partial inference
62+++ chain-id: $-(chain.fixture chain.fixture)
63+++ chain-again: $-(chain.fixture chain.fixture)
64+++ other-id: $-(other.fixture other.fixture)
65+++ chain-mismatch: call's sample and argument types do not unify
66+/test.jibb:42:39: called gate has type $-(chain.fixture chain.fixture)
67+++ chain-mismatch |= x=other.fixture (chain-id x)
68+ ^
69+/test.jibb:42:48: argument has type other.fixture
70+++ chain-mismatch |= x=other.fixture (chain-id x)
71+ ^
72+++ recursive-field: field is not available to partial inference
73+++ chain-null: cast constraint is not supported by partial inference
74+++ chain-alias: sample mold is not supported by partial inference
75+++ foreign-ref: sample mold is not supported by partial inference
76+++ overlap-ref: sample mold is not supported by partial inference
77+++ raw-ref: sample mold is not supported by partial inference
78+++ raw-null: sample mold is not supported by partial inference
79+++ cross-module: call's sample and argument types do not unify
80+/test.jibb:50:36: called gate has type $-(chain.fixture chain.fixture)
81+++ cross-module |= x=chain.second (chain-id x)
82+ ^
83+/test.jibb:50:45: argument has type chain.second
84+++ cross-module |= x=chain.second (chain-id x)
85+ ^
86+++ chain-pattern: recursive pattern is not supported by partial inference
87+++ chain-branch: recursive branch matching is not supported by partial inference
6288reader-collapsed: error: a Hoon union has overlapping cases
89+partial inference; module not checked
90+++ use: recursive mold name is not available in this scope
tests/type-import/source.txt+20
@@ -1,4 +1,5 @@
11/- fixture
2+/- second
23++ f |= x=title.fixture x
34++ g |= x=pair.fixture x
45++ face |= x=faced.fixture x
@@ -35,3 +36,22 @@
3536++ bucket-id |= x=buckets.fixture x
3637++ raw-rec |= x=raw-rec.fixture x
3738++ direct-list |= x=texts.fixture ^- (list @t) x
39+++ chain-id |= x=chain.fixture x
40+++ chain-again |= x=chain.fixture (chain-id x)
41+++ other-id |= x=other.fixture x
42+++ chain-mismatch |= x=other.fixture (chain-id x)
43+++ recursive-field |= x=chain.fixture next.x
44+++ chain-null ^- chain.fixture ~
45+++ chain-alias |= x=alias.fixture x
46+++ foreign-ref |= x=foreign-ref.fixture x
47+++ overlap-ref |= x=overlap-ref.fixture x
48+++ raw-ref |= x=raw-ref.fixture x
49+++ raw-null |= x=raw-null.fixture x
50+++ cross-module |= x=chain.second (chain-id x)
51+++ chain-pattern |= x=chain.fixture
52+ ?- x
53+ [a b] a
54+++ chain-branch |= [flag=? left=chain.fixture right=@t]
55+ ?- flag
56+ %.y left
57+ %.n right
tests/type-import/sur.txt+3
@@ -15,4 +15,7 @@
1515+$ tagged-list (list $?([%x @ud] [%y @t]))
1616+$ buckets (map @ud @t)
1717+$ collection-rec [items=(list @t) slots=(unit @ud)]
18++$ chain $@(~ [value=@ud next=chain])
19++$ other $@(~ [value=@ud next=other])
20++$ alias chain
1821--