on master help sign in
Let a change's item name a wing (review S6)

A change's item, inline or on a line of an open change, is now name=value or wing=value, the wing read through record fields only: m(open.cart=(not open.cart.m)) sets field open of field cart of m, as Hoon's m(open.cart x). A cell's items keep plain names. Parser: +read-item and +read-line take a wing then a tight = in a change (+named-item). They return a +slot, which carries the wing, and +bracket-node makes it an item or an edit; +edit now holds the wing's names. m(a.b 1) and m(a.b) say "expected `=` right after `a.b`" (S9), at the space or the ), as m(b c) does for a name. Checker: +change-wing-field reads an edit's wing from the target, outermost first. An unknown field is reported at its name; a wing through a field that is surely no record (a list, an atom, a unit) at that field; a wing through a tuple or a type variable fails closed. The result keeps the target's type. Two items that set the same field, or one inside the other, are a module error at both items, from +duplicate-edits, as a repeated record field is. lex-tight-change-bad-5 now tests m(a.b 1); the repeated edit of types-record-change-generic moved to types-change-repeated, since it is now a module error. grammar.md, the lowering table and compiler.md say what the code does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U4FzmThon6Q3x1uL8yUKEY

commita8bf207a049632ac3734359a86ad7d8594cee3aa authorClaude <noreply@anthropic.com> dateTue 3 Nov 2026, 03:08 (2 hours ago) parentba46974
63 files changed+1182−156
docs/compiler.md+1−1 docs/spec/core-language.md+1−1 docs/spec/grammar.md+4−2 jibb-dev/lib/jibb.hoon+154−57 tests/golden/inline-cell-wing-face-bad/about.txt+3 tests/golden/inline-cell-wing-face-bad/source.jibb+1 tests/golden/inline-cell-wing-face-bad/tokens.txt+8 tests/golden/inline-cell-wing-face-bad/tree.txt+3 tests/golden/inline-change-wing/about.txt+3 tests/golden/inline-change-wing/source.jibb+2 tests/golden/inline-change-wing/tokens.txt+18 tests/golden/inline-change-wing/tree.txt+9 tests/golden/lex-tight-change-bad-10/about.txt+3 tests/golden/lex-tight-change-bad-10/source.jibb+1 tests/golden/lex-tight-change-bad-10/tokens.txt+7 tests/golden/lex-tight-change-bad-10/tree.txt+3 tests/golden/lex-tight-change-bad-11/about.txt+3 tests/golden/lex-tight-change-bad-11/source.jibb+2 tests/golden/lex-tight-change-bad-11/tokens.txt+9 tests/golden/lex-tight-change-bad-11/tree.txt+3 tests/golden/lex-tight-change-bad-5/about.txt+3−3 tests/golden/lex-tight-change-bad-5/source.jibb+1−1 tests/golden/lex-tight-change-bad-5/tokens.txt+1−2 tests/golden/lex-tight-change-bad-5/tree.txt+3−3 tests/golden/lex-tight-change-ok-2/about.txt+3 tests/golden/lex-tight-change-ok-2/source.jibb+7 tests/golden/lex-tight-change-ok-2/tokens.txt+55 tests/golden/lex-tight-change-ok-2/tree.txt+20 tests/golden/types-change-overlap-lines/about.txt+4 tests/golden/types-change-overlap-lines/modules.txt+1 tests/golden/types-change-overlap-lines/source.jibb+7 tests/golden/types-change-overlap-lines/tokens.txt+49 tests/golden/types-change-overlap-lines/tree.txt+18 tests/golden/types-change-overlap-lines/types.txt+8 tests/golden/types-change-overlap/about.txt+4 tests/golden/types-change-overlap/modules.txt+1 tests/golden/types-change-overlap/source.jibb+4 tests/golden/types-change-overlap/tokens.txt+57 tests/golden/types-change-overlap/tree.txt+22 tests/golden/types-change-overlap/types.txt+8 tests/golden/types-change-repeated-wing/about.txt+4 tests/golden/types-change-repeated-wing/modules.txt+1 tests/golden/types-change-repeated-wing/source.jibb+3 tests/golden/types-change-repeated-wing/tokens.txt+41 tests/golden/types-change-repeated-wing/tree.txt+16 tests/golden/types-change-repeated-wing/types.txt+8 tests/golden/types-change-repeated/about.txt+4 tests/golden/types-change-repeated/modules.txt+1 tests/golden/types-change-repeated/source.jibb+2 tests/golden/types-change-repeated/tokens.txt+39 tests/golden/types-change-repeated/tree.txt+15 tests/golden/types-change-repeated/types.txt+8 tests/golden/types-change-wing/about.txt+4 tests/golden/types-change-wing/modules.txt+1 tests/golden/types-change-wing/source.jibb+24 tests/golden/types-change-wing/tokens.txt+289 tests/golden/types-change-wing/tree.txt+111 tests/golden/types-change-wing/types.txt+46 tests/golden/types-record-change-generic/about.txt+1−1 tests/golden/types-record-change-generic/source.jibb−1 tests/golden/types-record-change-generic/tokens.txt+35−59 tests/golden/types-record-change-generic/tree.txt+15−24 tests/golden/types-record-change-generic/types.txt−1
docs/compiler.md+1−1
@@ -82,7 +82,7 @@ The Hoon bridge translates imported `*` as the any-noun type, directly or within
8282
8383A bare tag that provably matches no case of a known union reports the union and tag sites. This covers casts, single-argument calls, contextual unit payloads, list items and supported branch arms, including unnamed imported unions. For syntactic tuples, ordered fit replay can identify a missing tag in a nested position; an earlier mismatch or uncertainty stops the replay instead of guessing. Existing tags whose shape or payload does not fit retain the ordinary mismatch; unresolved tags stay pending.
8484
85−A change can replace uniquely named fields on a fully known, faced record spine, including through a nested wing. Replacements receive the field type as context, substitutions and pending constraints are retained, and the resulting type remains the original record. Missing fields, empty changes and definite replacement mismatches report sites; the expected-type site is the edit, since the original field declaration location is not retained. Unknown or partly unfaced spines, ambiguous fields and repeated edits remain unsupported. A cast to a known record also supplies field context to a tuple or record initializer, including nested list and union fields. The expected-type site is the cast mold; the original field declaration location is not retained. Isolated cart-style `init` and `update` expressions infer in this fragment. The complete cart-store source fails closed because store kind and declaration scope are not yet checked.
85+A change can replace uniquely named fields on a fully known, faced record spine, including through a nested wing. An item named by a wing, as `m(open.cart=x)`, reads its field through such spines, outermost first. Replacements receive the field type as context, substitutions and pending constraints are retained, and the resulting type remains the original record. Missing fields, empty changes and definite replacement mismatches report sites; the expected-type site is the edit, since the original field declaration location is not retained. A missing field in an item's wing is reported at that name, and a wing through a field that is surely no record, such as a list, an atom or a unit, at the field it goes through. Unknown or partly unfaced spines, wings through tuples and type variables, and ambiguous fields remain unsupported. Two items that set the same field, or one a field inside the other's, are a module error at both items, as a repeated record field is. A cast to a known record also supplies field context to a tuple or record initializer, including nested list and union fields. The expected-type site is the cast mold; the original field declaration location is not retained. Isolated cart-style `init` and `update` expressions infer in this fragment. The complete cart-store source fails closed because store kind and declaration scope are not yet checked.
8686
8787Nested labels of the three public re-exports are normalized recursively to `ask`, `qath` and `raw-poke`, so prepared records and instantiated types do not expose their private `.internal` suffixes. Other private names remain private.
8888
docs/spec/core-language.md+1−1
@@ -84,7 +84,7 @@ Types, faces and casts leave nothing behind. Everything else lowers by these rul
8484| `title.p`, `space.theme.theme` | `slot` at the field's axis, nested for longer wings |
8585| `(proj record)` | Apply a `lam` to the source once; its body constructs the expected record from the required fields using `slot` and `cons`, discarding extra fields |
8686| `card.card`, an import | The qualified name `components/card:card` |
87−| `m(draft=t error=~)` | One `edit` per item, in order: `(edit 7 (edit 6 m t) ~)` |
87+| `m(draft=t error=~)` | One `edit` per item, in order: `(edit 7 (edit 6 m t) ~)`. A wing's item edits at the wing's axis, so with `cart` at 2 and `open` at 3 inside it, `m(open.cart=x)` is `(edit 5 m x)`. Each value is computed from `m` as it was |
8888| `f a b c` | `(app f (cons a (cons b c)))`: the arguments are the sample |
8989| `\|= [a=@ b=@] body` | `(lam _1 (let a (slot 2 _1) (let b (slot 3 _1) body)))`; a single face, as in `\|= m=model`, names the `lam` itself |
9090| `\|=%got` | `(lam _1 (cons %got _1))` |
docs/spec/grammar.md+4−2
@@ -42,7 +42,7 @@ Every literal reads exactly as Hoon reads it: `1000` is an error (write `1.000`)
4242
4343- **Call**: `f a b`; parentheses only group. A call open in parentheses puts its head and first arguments on the first line, then one argument per indented line; a `(` alone at the end of a line puts the head on the first indented line.
4444- **Lists**: `~[a b c]`, or `~[` at line end with one item per line. On one line, whitespace separates items, so calls need parentheses: `~[(text "hi")]`, which the checker suggests for `~[text "hi"]`. On indented lines, each line is an item.
45−- **Faced cells**: `[text="milk" done=%.n]`, or one `name=value` per line of an open bracket. A change takes one or more of the same items, as `m(done=%.y)`; `m()` is an error.
45+- **Faced cells**: `[text="milk" done=%.n]`, or one `name=value` per line of an open bracket. A change takes one or more of the same items, as `m(done=%.y)`; `m()` is an error. A change's item, inline or on a line, may name a wing instead, read through record fields only: `m(open.cart=(not open.cart.m))` sets field `open` of field `cart`, as Hoon's `m(open.cart x)`. Every value is computed from the original record. Two items that set the same field, or one a field inside the other's, as `m(cart=c open.cart=%.y)`, are an error. A cell's items take plain names.
4646- **Pairs and units**: `gap+%s2` and `` `x `` bind tighter than calls, so `pad+%s3 gap+%s2` is two items.
4747- **Wing** `title.p`, also for imports and prelude groups, which are records: `card.card`, `space.theme.theme`, `get.by`. **Change** `m(draft="x" error=~)`.
4848- **Inline gate**: `|=(p=post.blog title.p)`, or `|=(p title.p)` when the call fixes the type. A bare name in a sample always binds. `|=%got` is case `%got`'s constructor, typed by the gate expected. A bare name binds at any depth of a sample, so `|= [a=@ b]` binds `a` and `b`.
@@ -246,7 +246,9 @@ operand = literal | qath | ref | change | pair | unit | group | list | tuple
246246 | gate | ctor | pathlit | "!!" ;
247247ref = wing ;
248248wing = name { "." name } ;
249−change = wing "(" item { item } ")" | wing "(" { item } open ; (* no space before "("; items named *)
249+change = wing "(" edit { edit } ")" | wing "(" { edit } elines ; (* no space before "(" *)
250+edit = wing "=" operand ; (* a name, or a wing through record fields *)
251+elines = INDENT wing "=" expr { NEWLINE wing "=" expr } DEDENT ;
250252pair = name "+" operand ; (* [%name operand] *)
251253unit = "`" operand ; (* [~ operand] *)
252254group = "(" expr ")" | "(" { operand } open ;
jibb-dev/lib/jibb.hoon+154−57
@@ -405,6 +405,7 @@
405405:: Why: after the layout pass the grammar is LL(2). Each choice is made by
406406:: the next token, at most with the one after it and whether it is tight:
407407:: name, then = tight a face (items, lines, fields, samples)
408+:: wing, then = tight a change's item or line, wing=value
408409:: name, then + tight a pair
409410:: name or wing, then ( tight a change
410411:: |=, then a term tight a constructor; else, where an
@@ -472,7 +473,9 @@
472473:: that line, as () is no group. A ( followed by a rune is
473474:: the closed form. Lines of an open group or list take no
474475:: name= (doc question)
475−:: change every item and line has name= (the EBNF's "items named")
476+:: change every item and line has name= or wing=, as open.cart=
477+:: (the EBNF's edit) -> %change of edits, each with its
478+:: names as written; a tuple's item takes no wing=
476479:: rune |= -> %gate; ^- -> %cast; ~& -> %trace; ?- -> %branch.
477480:: !! is an operand, not a rune (Decisions): alone where an
478481:: expression goes it is a call of one operand, and !! a
@@ -604,11 +607,12 @@
604607:: found the end of the line" (+fail-space). No later line can give
605608:: a tight form its next token. A wing and a ( after a space are no
606609:: error: that is a call; nor is / or %/ alone, a whole path. A
607−:: change's item is name=, so a name there with no = right after it
608−:: fails just past the name, whatever follows: `m(b c)` at the
609−:: space, `m(b)` at the ), `m(a"x)` at the " with the layout pass's
610−:: message after the parser's (+unnamed, +moved). `m(a==1)` has its
611−:: =, and fails at the second, where the layout pass does.
610+:: change's item is name= or wing=, so a name or a wing there with
611+:: no = right after it fails just past it, whatever follows: `m(b c)`
612+:: and `m(a.b 1)` at the space, `m(b)` at the ), `m(a"x)` at the "
613+:: with the layout pass's message after the parser's (+unnamed,
614+:: +moved). `m(a==1)` has its =, and fails at the second, where the
615+:: layout pass does.
612616:: - Where the place needs text on its own line and the line ends,
613617:: whatever line comes next: at the end of that line, just past its
614618:: last token, "found the end of the line" (+fail-line), as no later
@@ -964,9 +968,10 @@
964968::
965969+$ item $~([*span ~ *expr] [at=span face=(unit term) val=expr])
966970::
967−:: +edit: a change's item or line, name=value
971+:: +edit: a change's item or line, name=value or wing=value; names
972+:: as written, as a %wing's: open.cart is ~[%open %cart]
968973::
969−+$ edit $~([*span %$ *expr] [at=span name=term val=expr])
974++$ edit $~([*span ~ *expr] [at=span names=(list term) val=expr])
970975::
971976:: +arm: a ?- arm, a pattern and an expression or body
972977::
@@ -2280,7 +2285,7 @@
22802285++ sexp-edit
22812286 |= e=edit
22822287 ^- sexp
2283− (nod "edit" at.e (trip name.e) ~[(sexp-expr val.e)])
2288+ (nod "edit" at.e (show-names names.e) ~[(sexp-expr val.e)])
22842289::
22852290++ sexp-arm
22862291 |= a=arm
@@ -2689,24 +2694,32 @@
26892694 ?~ t.els i.els
26902695 [%call [beg:(span-of i.els) end:(span-of (rear t.els))] i.els t.els]
26912696::
2697+:: +slot: a bracket's item or line as read, before +bracket-node
2698+:: makes it an item or an edit: its name=, or a change's wing=, else ~
2699+::
2700++$ slot [at=span face=(list term) val=expr]
2701+::
26922702:: +bracket-node: a tuple, list or change from its items, ending at fin
26932703::
26942704++ bracket-node
26952705 |= $: kind=?(%tuple %list %change)
26962706 op=token
26972707 target=(unit [t=token names=(list term)])
2698− its=(list item)
2708+ its=(list slot)
26992709 fin=loc
27002710 ==
27012711 ^- expr
27022712 ?- kind
2703− %tuple [%tuple [beg.at.op fin] its]
2704− %list [%list [beg.at.op fin] (turn its |=(i=item val.i))]
2713+ %tuple
2714+ :+ %tuple [beg.at.op fin]
2715+ (turn its |=(i=slot ^-(item [at.i ?~(face.i ~ `i.face.i) val.i])))
2716+ ::
2717+ %list [%list [beg.at.op fin] (turn its |=(i=slot val.i))]
27052718 ::
27062719 %change
27072720 =/ beg=loc ?~(target beg.at.op beg.at.t.u.target)
27082721 :^ %change [beg fin] ?~(target ~ names.u.target)
2709− (turn its |=(i=item ^-(edit [at.i ?~(face.i %$ u.face.i) val.i])))
2722+ (turn its |=(i=slot ^-(edit [at.i face.i val.i])))
27102723 ==
27112724::
27122725+| %parser
@@ -2927,23 +2940,22 @@
29272940 ::
29282941 :: +unnamed: a change's item or line without its name=
29292942 ::
2930− :: A name could still begin one, so the error is just past it, as
2931− :: a tight form's is (+fail-space): `m(b c)` at the space, `m(b)`
2932− :: at the ). So is an error token's text there, unless it begins
2933− :: with the =: `m(a"x)` at the ", with the layout pass's message
2934− :: after the parser's (+moved), but `m(a==1)` at the lexer's error,
2935− :: the second =. So is a wing's first name: `m(a.b=1)` fails at the
2936− :: dot (+fail-in). At an error token whose text can't begin a name,
2937− :: at that text, as `m( @sd` at the @ (+fail-in).
2943+ :: A name or a wing could still begin one, so the error is just
2944+ :: past it, as a tight form's is (+fail-space): `m(b c)` and
2945+ :: `m(a.b 1)` at the space, `m(b)` at the ). So is an error token's
2946+ :: text there, unless it begins with the =: `m(a"x)` at the ", with
2947+ :: the layout pass's message after the parser's (+moved), but
2948+ :: `m(a==1)` at the lexer's error, the second =. At an error token
2949+ :: whose text can't begin a name, at that text, as `m( @sd` at the
2950+ :: @ (+fail-in).
29382951 ::
29392952 ++ unnamed
29402953 |= ts=(list token)
29412954 ^- [%| p=error]
29422955 =/ why=tape "a change's items are named, as in m(done=%.y)"
29432956 ?: ?=([[* * [%error *]] *] ts) (bad-in ts "a named item" lower-mask)
2944− ?: ?=([[* * [%wing *]] *] ts)
2945− (bad-in-why ts "a named item" lower-mask why)
2946− ?. ?=([[* * [%name *]] *] ts) (bad-why ts "a named item" why)
2957+ ?. ?=([[* * ?([%name *] [%wing *])] *] ts)
2958+ (bad-why ts "a named item" why)
29472959 =/ want=tape "`=` right after {(show-found i.ts)}"
29482960 ?: ?& ?=([* [* * [%error *]] *] ts)
29492961 =(end.at.i.ts from.lex.i.t.ts)
@@ -3462,9 +3474,9 @@
34623474 (unclosed par ")" after)
34633475 ;< [els=(list expr) after=(list token)] then (read-operands ts)
34643476 ?: ?=([[* * %indent] *] after)
3465− ;< [ls=(list item) more=(list token)] then
3477+ ;< [ls=(list slot) more=(list token)] then
34663478 (read-lines %group t.after)
3467− =/ inner=expr (call-of (weld els (turn ls |=(i=item val.i))))
3479+ =/ inner=expr (call-of (weld els (turn ls |=(i=slot val.i))))
34683480 [%& [%group [beg.at.par end:(span-of inner)] inner] more]
34693481 ?~ els
34703482 ?: ?=([[* * ?(%newline %dedent)] *] after)
@@ -3489,7 +3501,7 @@
34893501 ==
34903502 ^- (pare expr)
34913503 =/ close=?(%')' %']') ?:(?=(%change kind) %')' %']')
3492− =| its=(list item)
3504+ =| its=(list slot)
34933505 |- ^- (pare expr)
34943506 ?~ ts (bad ts "an item")
34953507 =* t i.ts
@@ -3497,8 +3509,8 @@
34973509 [%& (bracket-node kind op target (flop its) end.at.t) t.ts]
34983510 ?: (is-closer t) (unclosed op (trip close) ts)
34993511 ?: ?=(%indent lex.t)
3500− ;< [ls=(list item) more=(list token)] then (read-lines kind t.ts)
3501− =/ every=(list item) (weld (flop its) ls)
3512+ ;< [ls=(list slot) more=(list token)] then (read-lines kind t.ts)
3513+ =/ every=(list slot) (weld (flop its) ls)
35023514 =/ fin=loc ?~(every end.at.op end.at:(rear every))
35033515 [%& (bracket-node kind op target every fin) more]
35043516 ?: &(?=(^ its) (shut op `at.i.its ts))
@@ -3506,21 +3518,21 @@
35063518 ?: ?=(?(%newline %dedent) lex.t)
35073519 (unclosed-open op (trip close) "an item" ts)
35083520 ?: &(tight.t ?=(^ its)) (no-space ts)
3509− ;< [i=item more=(list token)] then (read-item kind ts)
3521+ ;< [i=slot more=(list token)] then (read-item kind ts)
35103522 $(its [i its], ts more)
35113523 ::
35123524 :: +read-item: [ name= ] operand; a tuple's item may be named, a
3513− :: change's must be, a list's is not
3525+ :: change's must be, by a name or a wing, a list's is not
35143526 ::
35153527 ++ read-item
35163528 |= [kind=?(%tuple %list %change) ts=(list token)]
3517− ^- (pare item)
3518− ?: &(!?=(%list kind) ?=([[* * [%name *]] [* %& %'='] *] ts))
3529+ ^- (pare slot)
3530+ ?: &(?=([* * *] ts) (named-item kind ts))
35193531 =* nam i.ts
35203532 =/ rest=(list token) t.t.ts
35213533 ?: |(?=(~ rest) !tight.i.rest) (bad-space i.t.ts rest "a value")
35223534 ;< [v=expr after=(list token)] then (read-operand rest)
3523− [%& [[beg.at.nam end:(span-of v)] `p.lex.nam v] after]
3535+ [%& [[beg.at.nam end:(span-of v)] (names-of lex.nam) v] after]
35243536 ?: ?=(%change kind) (unnamed ts)
35253537 ?: &(?=(^ ts) (is-rune i.ts))
35263538 %: bad-in-why ts "an expression" operand-mask
@@ -3529,15 +3541,25 @@
35293541 ;< [v=expr after=(list token)] then (read-operand ts)
35303542 [%& [(span-of v) ~ v] after]
35313543 ::
3544+ :: +named-item: whether ts begins an item's or a line's name=: a
3545+ :: tuple's or a change's name, or a change's wing, then = tight
3546+ ::
3547+ ++ named-item
3548+ |= [kind=?(%tuple %list %change %group) ts=(list token)]
3549+ ^- ?
3550+ ?. ?=([* [* %& %'='] *] ts) |
3551+ ?: ?=([[* * [%name *]] *] ts) ?=(?(%tuple %change) kind)
3552+ &(?=(%change kind) ?=([[* * [%wing *]] *] ts))
3553+ ::
35323554 :: +read-lines: after an open bracket's INDENT, its lines, an item
35333555 :: each, to its DEDENT
35343556 ::
35353557 ++ read-lines
35363558 |= [kind=?(%tuple %list %change %group) ts=(list token)]
3537− ^- (pare (list item))
3538− =| ls=(list item)
3539− |- ^- (pare (list item))
3540− ;< [l=item after=(list token)] then (read-line kind ts)
3559+ ^- (pare (list slot))
3560+ =| ls=(list slot)
3561+ |- ^- (pare (list slot))
3562+ ;< [l=slot after=(list token)] then (read-line kind ts)
35413563 ?: ?=([[* * %newline] *] after) $(ls [l ls], ts t.after)
35423564 ?: ?=([[* * %dedent] *] after) [%& (flop [l ls]) t.after]
35433565 ?. ?=([[* * ?(%')' %']')] *] after) (bad-end-in after (tail-can val.l))
@@ -3564,22 +3586,20 @@
35643586 "its items end at the first line that isn't deeper"
35653587 ::
35663588 :: +read-line: an open bracket's line, [ name= ] expr; a tuple's may
3567− :: be named, a change's must be, a list's and a group's are not
3568− :: (doc question)
3589+ :: be named, a change's must be, by a name or a wing, a list's and a
3590+ :: group's are not (doc question)
35693591 ::
35703592 ++ read-line
35713593 |= [kind=?(%tuple %list %change %group) ts=(list token)]
3572− ^- (pare item)
3594+ ^- (pare slot)
35733595 =/ cf (closer-first ts "an item")
35743596 ?^ cf u.cf
3575− ?: ?& ?=(?(%tuple %change) kind)
3576− ?=([[* * [%name *]] [* %& %'='] *] ts)
3577− ==
3597+ ?: &(?=([* * *] ts) (named-item kind ts))
35783598 =* nam i.ts
35793599 =/ rest=(list token) t.t.ts
35803600 ?: |(?=(~ rest) !tight.i.rest) (bad-space i.t.ts rest "a value")
35813601 ;< [v=expr after=(list token)] then (read-expr rest)
3582− [%& [[beg.at.nam end:(span-of v)] `p.lex.nam v] after]
3602+ [%& [[beg.at.nam end:(span-of v)] (names-of lex.nam) v] after]
35833603 ?: ?=(%change kind) (unnamed ts)
35843604 ;< [v=expr after=(list token)] then (read-expr ts)
35853605 [%& [(span-of v) ~ v] after]
@@ -4437,7 +4457,7 @@
44374457++ outline-edit
44384458 |= e=edit
44394459 ^- outline
4440− [%edit at.e ~[%name] %kid ~[(outline-expr val.e)]]
4460+ [%edit at.e ~[%name %wing] %kid ~[(outline-expr val.e)]]
44414461::
44424462++ outline-arm
44434463 |= a=arm
@@ -8735,7 +8755,9 @@
87358755 `subject
87368756::
87378757:: Infer changes only against a fully known record spine. Retain the
8738−:: target type; unknown spines and repeated edits remain unsupported.
8758+:: target type; unknown spines and overlapping edits remain unsupported.
8759+:: An edit's wing, as open.cart, reads through record fields only,
8760+:: outermost first, and its value fits the field it names.
87398761++ change-spine-known
87408762 |= [t=itype sub=isub]
87418763 ^- ?
@@ -8747,19 +8769,18 @@
87478769 $(t tal.t)
87488770::
87498771++ infer-change-edits
8750− |= [edits=(list edit) target=itype env=ienv state=istate seen=(list term)]
8772+ |= [edits=(list edit) target=itype env=ienv state=istate seen=(list (list term))]
87518773 ^- iresult
87528774 ?~ edits [%& target state]
87538775 =/ entry=edit i.edits
8754− ?: (lien seen |=(name=term =(name name.entry)))
8776+ =/ outer=(list term) (flop names.entry)
8777+ ?: (lien seen |=(prior=(list term) (wing-overlap prior outer)))
87558778 [%| ["repeated change fields are not supported by partial inference" ~]]
87568779 ?. (change-spine-known target sub.state)
87578780 [%| ["change target needs a known record in partial inference" ~]]
8758− =/ found=(list itype) (record-fields-named name.entry target sub.state)
8759− ?~ found
8760− [%| ["record has no field `{(trip name.entry)}`" ~[[at.entry "unknown field"]]]]
8761− ?^ t.found [%| ["change field is ambiguous in partial inference" ~]]
8762− =/ expected=itype i.found
8781+ =/ field=(each itype idiag) (change-wing-field entry target sub.state)
8782+ ?: ?=(%| -.field) [%| p.field]
8783+ =/ expected=itype p.field
87638784 =/ value=iresult (infer-call-context-arg val.entry expected env state at.entry)
87648785 ?: ?=(%| -.value) value
87658786 =/ fit=ifit (itype-fit typ.p.value expected sub.state.p.value next.state.p.value)
@@ -8768,7 +8789,67 @@
87688789 =/ decimal=(unit idiag) (decimal-misfit val.entry typ.p.value expected sub.state.p.value next.state.p.value)
87698790 ?^ decimal [%| u.decimal]
87708791 [%| ["change field and replacement do not unify" ~[[at.entry (site-type-note "expected replacement" expected sub.state.p.value)] [(span-of val.entry) (site-type-note "replacement" typ.p.value sub.state.p.value)]]]]
8771− $(edits t.edits, state state.p.value(sub sub.fit, next next.fit), seen [name.entry seen])
8792+ $(edits t.edits, state state.p.value(sub sub.fit, next next.fit), seen [outer seen])
8793+::
8794+:: +change-wing-field: the type of the field an edit's wing names in
8795+:: a known record target, read through record fields, outermost first
8796+::
8797+++ change-wing-field
8798+ |= [entry=edit target=itype sub=isub]
8799+ ^- (each itype idiag)
8800+ =/ outer=(list term) (flop names.entry)
8801+ =/ k=@ud (lent names.entry)
8802+ =/ typ=itype target
8803+ =| through=(unit [name=term at=span])
8804+ |- ^- (each itype idiag)
8805+ ?~ outer [%& typ]
8806+ =/ here=span (edit-name-at entry (dec k))
8807+ ?: &(?=(^ through) !(change-spine-known typ sub))
8808+ ?. (itype-not-record typ sub)
8809+ [%| ["change wing needs a known record in partial inference" ~]]
8810+ =/ label=tape "field `{(trip name.u.through)}`"
8811+ :- %|
8812+ :- "change wing `{(show-names names.entry)}` goes through `{(trip name.u.through)}`, which is not a record"
8813+ ~[[at.u.through (site-type-note label typ sub)]]
8814+ =/ found=(list itype) (record-fields-named i.outer typ sub)
8815+ ?~ found
8816+ [%| ["record has no field `{(trip i.outer)}`" ~[[here "unknown field"]]]]
8817+ ?^ t.found [%| ["change field is ambiguous in partial inference" ~]]
8818+ $(outer t.outer, k (dec k), typ i.found, through `[i.outer here])
8819+::
8820+:: +edit-name-at: where the kth name of an edit's wing is written,
8821+:: counting from 0: its names are joined by dots from the edit's start
8822+::
8823+++ edit-name-at
8824+ |= [e=edit k=@ud]
8825+ ^- span
8826+ =/ col=@ud col.beg.at.e
8827+ =/ ns=(list term) names.e
8828+ |- ^- span
8829+ ?~ ns at.e
8830+ =/ len=@ud (met 3 i.ns)
8831+ ?: =(0 k) [[line.beg.at.e col] [line.beg.at.e (add col len)]]
8832+ $(ns t.ns, k (dec k), col +((add col len)))
8833+::
8834+:: +itype-not-record: whether a type is surely no record: an atom, a
8835+:: loobean, a list or other collection, a gate, a union or a noun
8836+::
8837+++ itype-not-record
8838+ |= [t=itype sub=isub]
8839+ ^- ?
8840+ =. t (itype-walk t sub)
8841+ ?=(?(%atom %any-atom %flag %const %tag %collection %fn %union %noun %empty) -.t)
8842+::
8843+:: +wing-overlap: whether two edits' wings, outermost first, name the
8844+:: same field or one a field inside the other's
8845+::
8846+++ wing-overlap
8847+ |= [a=(list term) b=(list term)]
8848+ ^- ?
8849+ ?~ a &
8850+ ?~ b &
8851+ ?. =(i.a i.b) |
8852+ $(a t.a, b t.b)
87728853::
87738854:: Backtick constructs the canonical prelude unit, after checking its
87748855:: prepared template has the standard null and [~ u=A] cases.
@@ -11948,13 +12029,29 @@
1194812029 =/ first=(unit idiag) ?:(?=(%expr -.i.segs) (duplicate-expr p.i.segs) ~)
1194912030 ?^ first first
1195012031 $(segs t.segs)
12032+:: A change's items each set a field once: two that name the same field,
12033+:: or one a field inside the other's, as cart and open.cart, overlap.
1195112034++ duplicate-edits
1195212035 |= edits=(list edit)
1195312036 ^- (unit idiag)
12037+ =| seen=(list edit)
12038+ |- ^- (unit idiag)
1195412039 ?~ edits ~
11955− =/ first (duplicate-expr val.i.edits)
12040+ =/ e=edit i.edits
12041+ =/ prior=(list edit)
12042+ (skim seen |=(p=edit (wing-overlap (flop names.p) (flop names.e))))
12043+ ?^ prior
12044+ =/ same=? =(names.i.prior names.e)
12045+ =/ sites=(list [at=span note=tape])
12046+ :~ [at.i.prior "first field"]
12047+ [at.e ?:(same "repeated field" "overlapping field")]
12048+ ==
12049+ ?: same `["change field `{(show-names names.e)}` is repeated" sites]
12050+ =/ a=tape (show-names names.i.prior)
12051+ `["change fields `{a}` and `{(show-names names.e)}` overlap" sites]
12052+ =/ first (duplicate-expr val.e)
1195612053 ?^ first first
11957− $(edits t.edits)
12054+ $(edits t.edits, seen [e seen])
1195812055++ duplicate-arms
1195912056 |= arms=(list arm)
1196012057 ^- (unit idiag)
tests/golden/inline-cell-wing-face-bad/about.txtnew file+3
@@ -0,0 +1,3 @@
1+source: Grammar, Expressions: "A cell's items take plain names."
2+tests: a cell's item named by a wing, which only a change's item may be
3+expect: error at 1:10, the =: the wing `a.b` is the cell's first item, an operand, and the = after it goes tight where a space between items goes
tests/golden/inline-cell-wing-face-bad/source.jibbnew file+1
@@ -0,0 +1 @@
1+++ x [a.b=1]
tests/golden/inline-cell-wing-face-bad/tokens.txtnew file+8
@@ -0,0 +1,8 @@
1+1:1 glyph ++
2+1:4 name x
3+1:6 glyph [
4+1:7 tight wing a.b
5+1:10 tight glyph =
6+1:11 tight lit 1
7+1:12 tight glyph ]
8+1:13 NEWLINE
tests/golden/inline-cell-wing-face-bad/tree.txtnew file+3
@@ -0,0 +1,3 @@
1+/tests/golden/inline-cell-wing-face-bad/source.jibb:1:10: expected a space between items, found `=`; `name=value` goes in a cell, as [a=1], or a change, as m(a=1)
2+++ x [a.b=1]
3+ ^
tests/golden/inline-change-wing/about.txtnew file+3
@@ -0,0 +1,3 @@
1+source: docs/reviews/2026-11-02-syntax-review.md, S6, the proposed `toggle-open`; Grammar, Expressions: "`m(open.cart=(not open.cart.m))` sets field `open` of field `cart`, as Hoon's `m(open.cart x)`."
2+tests: the review's nested change, written with a wing item
3+expect: parses: one change of m with the edit open.cart
tests/golden/inline-change-wing/source.jibbnew file+2
@@ -0,0 +1,2 @@
1+++ toggle-open |= m=model
2+ m(open.cart=(not open.cart.m))
tests/golden/inline-change-wing/tokens.txtnew file+18
@@ -0,0 +1,18 @@
1+1:1 glyph ++
2+1:4 name toggle-open
3+1:16 glyph |=
4+1:19 name m
5+1:20 tight glyph =
6+1:21 tight name model
7+2:3 INDENT
8+2:3 name m
9+2:4 tight glyph (
10+2:5 tight wing open.cart
11+2:14 tight glyph =
12+2:15 tight glyph (
13+2:16 tight name not
14+2:20 wing open.cart.m
15+2:31 tight glyph )
16+2:32 tight glyph )
17+2:33 DEDENT
18+2:33 NEWLINE
tests/golden/inline-change-wing/tree.txtnew file+9
@@ -0,0 +1,9 @@
1+(module
2+ (value 1:1 toggle-open
3+ (gate 1:16
4+ (face 1:19 m (ref 1:21 model))
5+ (body 2:3
6+ (change 2:3 m
7+ (edit 2:5 open.cart
8+ (group 2:15
9+ (call 2:16 (wing 2:16 not) (wing 2:20 open.cart.m)))))))))
tests/golden/lex-tight-change-bad-10/about.txtnew file+3
@@ -0,0 +1,3 @@
1+source: Grammar, Expressions: "A change's item, inline or on a line, may name a wing instead, read through record fields only: `m(open.cart=(not open.cart.m))` sets field `open` of field `cart`, as Hoon's `m(open.cart x)`."; the EBNF: `edit = wing "=" operand ;` and `elines = INDENT wing "=" expr { NEWLINE wing "=" expr } DEDENT ;`
2+tests: a change's only item a wing with no `=` after it, then the `)`
3+expect: error at 1:11, the ): `a.b` could still begin the item `a.b=…`, and the ) is the first character that can't go on (library section 3, Errors)
tests/golden/lex-tight-change-bad-10/source.jibbnew file+1
@@ -0,0 +1 @@
1+++ x m(a.b)
tests/golden/lex-tight-change-bad-10/tokens.txtnew file+7
@@ -0,0 +1,7 @@
1+1:1 glyph ++
2+1:4 name x
3+1:6 name m
4+1:7 tight glyph (
5+1:8 tight wing a.b
6+1:11 tight glyph )
7+1:12 NEWLINE
tests/golden/lex-tight-change-bad-10/tree.txtnew file+3
@@ -0,0 +1,3 @@
1+/tests/golden/lex-tight-change-bad-10/source.jibb:1:11: expected `=` right after `a.b`, found `)`; a change's items are named, as in m(done=%.y)
2+++ x m(a.b)
3+ ^
tests/golden/lex-tight-change-bad-11/about.txtnew file+3
@@ -0,0 +1,3 @@
1+source: Grammar, Expressions: "A change's item, inline or on a line, may name a wing instead, read through record fields only: `m(open.cart=(not open.cart.m))` sets field `open` of field `cart`, as Hoon's `m(open.cart x)`."; the EBNF: `edit = wing "=" operand ;` and `elines = INDENT wing "=" expr { NEWLINE wing "=" expr } DEDENT ;`
2+tests: a line of an open change holding a wing, a space and a value, with no `=`
3+expect: error at 2:12, the space: `open.cart` could still begin the line `open.cart=…`, and a wing= takes its `=` at once (library section 3, Errors)
tests/golden/lex-tight-change-bad-11/source.jibbnew file+2
@@ -0,0 +1,2 @@
1+++ x m(
2+ open.cart %.n
tests/golden/lex-tight-change-bad-11/tokens.txtnew file+9
@@ -0,0 +1,9 @@
1+1:1 glyph ++
2+1:4 name x
3+1:6 name m
4+1:7 tight glyph (
5+2:3 INDENT
6+2:3 wing open.cart
7+2:13 lit %.n
8+2:16 DEDENT
9+2:16 NEWLINE
tests/golden/lex-tight-change-bad-11/tree.txtnew file+3
@@ -0,0 +1,3 @@
1+/tests/golden/lex-tight-change-bad-11/source.jibb:2:12: expected `=` right after `open.cart`, found a space; a change's items are named, as in m(done=%.y)
2+ open.cart %.n
3+ ^
tests/golden/lex-tight-change-bad-5/about.txt+3−3
@@ -1,3 +1,3 @@
1−source: Grammar tab, Expressions: "**Faced cells**: `[text="milk" done=%.n]`, or one `name=value` per line of an open bracket. A change takes the same items, as `m(done=%.y)`."; the EBNF: `change = wing "(" { item } ")" | wing "(" { item } open ; (* no space before "("; items named *)`
2−tests: a change's item named by a wing, as Hoon's %= allows
3−expect: error at 1:9, the dot: an item's name is a name, which `a` could still begin and the dot can't continue, as `++ a.b 1` fails at its dot (library section 3, Errors)
1+source: Grammar, Expressions: "A change's item, inline or on a line, may name a wing instead, read through record fields only: `m(open.cart=(not open.cart.m))` sets field `open` of field `cart`, as Hoon's `m(open.cart x)`."; the EBNF: `edit = wing "=" operand ;` and `elines = INDENT wing "=" expr { NEWLINE wing "=" expr } DEDENT ;`
2+tests: a change's item that is a wing with no `=` after it, followed by a space and a value, as Hoon's %= writes m(a.b 1)
3+expect: error at 1:11, the space: `a.b` could still begin the item `a.b=…`, and a wing= takes its `=` at once, so the space is the first character that can't go on; the message says "expected `=` right after `a.b`" (library section 3, Errors)
tests/golden/lex-tight-change-bad-5/source.jibb+1−1
@@ -1 +1 @@
1−++ x m(a.b=1)
1+++ x m(a.b 1)
tests/golden/lex-tight-change-bad-5/tokens.txt+1−2
@@ -3,7 +3,6 @@
331:6 name m
441:7 tight glyph (
551:8 tight wing a.b
6−1:11 tight glyph =
7−1:12 tight lit 1
6+1:12 lit 1
871:13 tight glyph )
981:14 NEWLINE
tests/golden/lex-tight-change-bad-5/tree.txt+3−3
@@ -1,3 +1,3 @@
1−/tests/golden/lex-tight-change-bad-5/source.jibb:1:9: expected a named item, found `a.b`; a change's items are named, as in m(done=%.y)
2−++ x m(a.b=1)
3− ^
1+/tests/golden/lex-tight-change-bad-5/source.jibb:1:11: expected `=` right after `a.b`, found a space; a change's items are named, as in m(done=%.y)
2+++ x m(a.b 1)
3+ ^
tests/golden/lex-tight-change-ok-2/about.txtnew file+3
@@ -0,0 +1,3 @@
1+source: Grammar, Expressions: "A change's item, inline or on a line, may name a wing instead, read through record fields only: `m(open.cart=(not open.cart.m))` sets field `open` of field `cart`, as Hoon's `m(open.cart x)`."; the EBNF: `edit = wing "=" operand ;` and `elines = INDENT wing "=" expr { NEWLINE wing "=" expr } DEDENT ;`
2+tests: a change's items named by wings: alone, beside a named item, under a wing target, and on the lines of an open change
3+expect: parses: four changes, each edit printed with its wing as written
tests/golden/lex-tight-change-ok-2/source.jibbnew file+7
@@ -0,0 +1,7 @@
1+++ a m(a.b=1)
2+++ b m(open.cart=(not open.cart.m) note="")
3+++ c cart.m(level.deep=5)
4+++ d m(
5+ open.cart=%.n
6+ level.deep.cart=(add 1 level.deep.cart.m)
7+ note=""
tests/golden/lex-tight-change-ok-2/tokens.txtnew file+55
@@ -0,0 +1,55 @@
1+1:1 glyph ++
2+1:4 name a
3+1:6 name m
4+1:7 tight glyph (
5+1:8 tight wing a.b
6+1:11 tight glyph =
7+1:12 tight lit 1
8+1:13 tight glyph )
9+2:1 NEWLINE
10+2:1 glyph ++
11+2:4 name b
12+2:6 name m
13+2:7 tight glyph (
14+2:8 tight wing open.cart
15+2:17 tight glyph =
16+2:18 tight glyph (
17+2:19 tight name not
18+2:23 wing open.cart.m
19+2:34 tight glyph )
20+2:36 name note
21+2:40 tight glyph =
22+2:41 tight str ""
23+2:43 tight glyph )
24+3:1 NEWLINE
25+3:1 glyph ++
26+3:4 name c
27+3:6 wing cart.m
28+3:12 tight glyph (
29+3:13 tight wing level.deep
30+3:23 tight glyph =
31+3:24 tight lit 5
32+3:25 tight glyph )
33+4:1 NEWLINE
34+4:1 glyph ++
35+4:4 name d
36+4:6 name m
37+4:7 tight glyph (
38+5:3 INDENT
39+5:3 wing open.cart
40+5:12 tight glyph =
41+5:13 tight lit %.n
42+6:3 NEWLINE
43+6:3 wing level.deep.cart
44+6:18 tight glyph =
45+6:19 tight glyph (
46+6:20 tight name add
47+6:24 lit 1
48+6:26 wing level.deep.cart.m
49+6:43 tight glyph )
50+7:3 NEWLINE
51+7:3 name note
52+7:7 tight glyph =
53+7:8 tight str ""
54+7:10 DEDENT
55+7:10 NEWLINE
tests/golden/lex-tight-change-ok-2/tree.txtnew file+20
@@ -0,0 +1,20 @@
1+(module
2+ (value 1:1 a
3+ (change 1:6 m
4+ (edit 1:8 a.b (lit 1:12 1))))
5+ (value 2:1 b
6+ (change 2:6 m
7+ (edit 2:8 open.cart
8+ (group 2:18
9+ (call 2:19 (wing 2:19 not) (wing 2:23 open.cart.m))))
10+ (edit 2:36 note (str 2:41 ""))))
11+ (value 3:1 c
12+ (change 3:6 cart.m
13+ (edit 3:13 level.deep (lit 3:24 5))))
14+ (value 4:1 d
15+ (change 4:6 m
16+ (edit 5:3 open.cart (lit 5:13 %.n))
17+ (edit 6:3 level.deep.cart
18+ (group 6:19
19+ (call 6:20 (wing 6:20 add) (lit 6:24 1) (wing 6:26 level.deep.cart.m))))
20+ (edit 7:3 note (str 7:8 "")))))
tests/golden/types-change-overlap-lines/about.txtnew file+4
@@ -0,0 +1,4 @@
1+source: Grammar, Expressions: "Two items that set the same field, or one a field inside the other's, as `m(cart=c open.cart=%.y)`, are an error."
2+tests: an open change whose third line sets a field holding its second line's field
3+expect: parses
4+expect-types: a partial inference report: the module error names both lines
tests/golden/types-change-overlap-lines/modules.txtnew file+1
@@ -0,0 +1 @@
1+# no module fixtures
tests/golden/types-change-overlap-lines/source.jibbnew file+7
@@ -0,0 +1,7 @@
1++$ cart [items=(list @t) open=?]
2++$ model [cart=cart note=@t]
3+++ overlap |= m=model
4+ m(
5+ note="x"
6+ open.cart=%.y
7+ cart=cart.m
tests/golden/types-change-overlap-lines/tokens.txtnew file+49
@@ -0,0 +1,49 @@
1+1:1 glyph +$
2+1:4 name cart
3+1:9 glyph [
4+1:10 tight name items
5+1:15 tight glyph =
6+1:16 tight glyph (
7+1:17 tight name list
8+1:22 aura @t
9+1:24 tight glyph )
10+1:26 name open
11+1:30 tight glyph =
12+1:31 tight glyph ?
13+1:32 tight glyph ]
14+2:1 NEWLINE
15+2:1 glyph +$
16+2:4 name model
17+2:10 glyph [
18+2:11 tight name cart
19+2:15 tight glyph =
20+2:16 tight name cart
21+2:21 name note
22+2:25 tight glyph =
23+2:26 tight aura @t
24+2:28 tight glyph ]
25+3:1 NEWLINE
26+3:1 glyph ++
27+3:4 name overlap
28+3:12 glyph |=
29+3:15 name m
30+3:16 tight glyph =
31+3:17 tight name model
32+4:3 INDENT
33+4:3 name m
34+4:4 tight glyph (
35+5:5 INDENT
36+5:5 name note
37+5:9 tight glyph =
38+5:10 tight str "x"
39+6:5 NEWLINE
40+6:5 wing open.cart
41+6:14 tight glyph =
42+6:15 tight lit %.y
43+7:5 NEWLINE
44+7:5 name cart
45+7:9 tight glyph =
46+7:10 tight wing cart.m
47+7:16 DEDENT
48+7:16 DEDENT
49+7:16 NEWLINE
tests/golden/types-change-overlap-lines/tree.txtnew file+18
@@ -0,0 +1,18 @@
1+(module
2+ (mold 1:1 cart
3+ (cell 1:9
4+ (face 1:10 items
5+ (apply 1:16 list (aura 1:22 @t)))
6+ (face 1:26 open (flag 1:31))))
7+ (mold 2:1 model
8+ (cell 2:10
9+ (face 2:11 cart (ref 2:16 cart))
10+ (face 2:21 note (aura 2:26 @t))))
11+ (value 3:1 overlap
12+ (gate 3:12
13+ (face 3:15 m (ref 3:17 model))
14+ (body 4:3
15+ (change 4:3 m
16+ (edit 5:5 note (str 5:10 "x"))
17+ (edit 6:5 open.cart (lit 6:15 %.y))
18+ (edit 7:5 cart (wing 7:10 cart.m)))))))
tests/golden/types-change-overlap-lines/types.txtnew file+8
@@ -0,0 +1,8 @@
1+partial inference; module not checked
2+change fields `open.cart` and `cart` overlap
3+/tests/golden/types-change-overlap-lines/source.jibb:6:5: first field
4+ open.cart=%.y
5+ ^
6+/tests/golden/types-change-overlap-lines/source.jibb:7:5: overlapping field
7+ cart=cart.m
8+ ^
tests/golden/types-change-overlap/about.txtnew file+4
@@ -0,0 +1,4 @@
1+source: Grammar, Expressions: "Two items that set the same field, or one a field inside the other's, as `m(cart=c open.cart=%.y)`, are an error."
2+tests: a change whose second item sets a field inside its first's, beside a change of two sibling fields
3+expect: parses
4+expect-types: a partial inference report: the module error names both items
tests/golden/types-change-overlap/modules.txtnew file+1
@@ -0,0 +1 @@
1+# no module fixtures
tests/golden/types-change-overlap/source.jibbnew file+4
@@ -0,0 +1,4 @@
1++$ cart [items=(list @t) open=?]
2++$ model [cart=cart note=@t]
3+++ fine |= m=model m(open.cart=%.y items.cart=~)
4+++ overlap |= m=model m(cart=cart.m open.cart=%.y)
tests/golden/types-change-overlap/tokens.txtnew file+57
@@ -0,0 +1,57 @@
1+1:1 glyph +$
2+1:4 name cart
3+1:9 glyph [
4+1:10 tight name items
5+1:15 tight glyph =
6+1:16 tight glyph (
7+1:17 tight name list
8+1:22 aura @t
9+1:24 tight glyph )
10+1:26 name open
11+1:30 tight glyph =
12+1:31 tight glyph ?
13+1:32 tight glyph ]
14+2:1 NEWLINE
15+2:1 glyph +$
16+2:4 name model
17+2:10 glyph [
18+2:11 tight name cart
19+2:15 tight glyph =
20+2:16 tight name cart
21+2:21 name note
22+2:25 tight glyph =
23+2:26 tight aura @t
24+2:28 tight glyph ]
25+3:1 NEWLINE
26+3:1 glyph ++
27+3:4 name fine
28+3:9 glyph |=
29+3:12 name m
30+3:13 tight glyph =
31+3:14 tight name model
32+3:20 name m
33+3:21 tight glyph (
34+3:22 tight wing open.cart
35+3:31 tight glyph =
36+3:32 tight lit %.y
37+3:36 wing items.cart
38+3:46 tight glyph =
39+3:47 tight lit ~
40+3:48 tight glyph )
41+4:1 NEWLINE
42+4:1 glyph ++
43+4:4 name overlap
44+4:12 glyph |=
45+4:15 name m
46+4:16 tight glyph =
47+4:17 tight name model
48+4:23 name m
49+4:24 tight glyph (
50+4:25 tight name cart
51+4:29 tight glyph =
52+4:30 tight wing cart.m
53+4:37 wing open.cart
54+4:46 tight glyph =
55+4:47 tight lit %.y
56+4:50 tight glyph )
57+4:51 NEWLINE
tests/golden/types-change-overlap/tree.txtnew file+22
@@ -0,0 +1,22 @@
1+(module
2+ (mold 1:1 cart
3+ (cell 1:9
4+ (face 1:10 items
5+ (apply 1:16 list (aura 1:22 @t)))
6+ (face 1:26 open (flag 1:31))))
7+ (mold 2:1 model
8+ (cell 2:10
9+ (face 2:11 cart (ref 2:16 cart))
10+ (face 2:21 note (aura 2:26 @t))))
11+ (value 3:1 fine
12+ (gate 3:9
13+ (face 3:12 m (ref 3:14 model))
14+ (change 3:20 m
15+ (edit 3:22 open.cart (lit 3:32 %.y))
16+ (edit 3:36 items.cart (lit 3:47 ~)))))
17+ (value 4:1 overlap
18+ (gate 4:12
19+ (face 4:15 m (ref 4:17 model))
20+ (change 4:23 m
21+ (edit 4:25 cart (wing 4:30 cart.m))
22+ (edit 4:37 open.cart (lit 4:47 %.y))))))
tests/golden/types-change-overlap/types.txtnew file+8
@@ -0,0 +1,8 @@
1+partial inference; module not checked
2+change fields `cart` and `open.cart` overlap
3+/tests/golden/types-change-overlap/source.jibb:4:25: first field
4+++ overlap |= m=model m(cart=cart.m open.cart=%.y)
5+ ^
6+/tests/golden/types-change-overlap/source.jibb:4:37: overlapping field
7+++ overlap |= m=model m(cart=cart.m open.cart=%.y)
8+ ^
tests/golden/types-change-repeated-wing/about.txtnew file+4
@@ -0,0 +1,4 @@
1+source: Grammar, Expressions: "Two items that set the same field, or one a field inside the other's, as `m(cart=c open.cart=%.y)`, are an error."
2+tests: a change that sets one field twice through the same wing
3+expect: parses
4+expect-types: a partial inference report: the module error names both items
tests/golden/types-change-repeated-wing/modules.txtnew file+1
@@ -0,0 +1 @@
1+# no module fixtures
tests/golden/types-change-repeated-wing/source.jibbnew file+3
@@ -0,0 +1,3 @@
1++$ cart [items=(list @t) open=?]
2++$ model [cart=cart note=@t]
3+++ repeated |= m=model m(open.cart=%.y open.cart=%.n)
tests/golden/types-change-repeated-wing/tokens.txtnew file+41
@@ -0,0 +1,41 @@
1+1:1 glyph +$
2+1:4 name cart
3+1:9 glyph [
4+1:10 tight name items
5+1:15 tight glyph =
6+1:16 tight glyph (
7+1:17 tight name list
8+1:22 aura @t
9+1:24 tight glyph )
10+1:26 name open
11+1:30 tight glyph =
12+1:31 tight glyph ?
13+1:32 tight glyph ]
14+2:1 NEWLINE
15+2:1 glyph +$
16+2:4 name model
17+2:10 glyph [
18+2:11 tight name cart
19+2:15 tight glyph =
20+2:16 tight name cart
21+2:21 name note
22+2:25 tight glyph =
23+2:26 tight aura @t
24+2:28 tight glyph ]
25+3:1 NEWLINE
26+3:1 glyph ++
27+3:4 name repeated
28+3:13 glyph |=
29+3:16 name m
30+3:17 tight glyph =
31+3:18 tight name model
32+3:24 name m
33+3:25 tight glyph (
34+3:26 tight wing open.cart
35+3:35 tight glyph =
36+3:36 tight lit %.y
37+3:40 wing open.cart
38+3:49 tight glyph =
39+3:50 tight lit %.n
40+3:53 tight glyph )
41+3:54 NEWLINE
tests/golden/types-change-repeated-wing/tree.txtnew file+16
@@ -0,0 +1,16 @@
1+(module
2+ (mold 1:1 cart
3+ (cell 1:9
4+ (face 1:10 items
5+ (apply 1:16 list (aura 1:22 @t)))
6+ (face 1:26 open (flag 1:31))))
7+ (mold 2:1 model
8+ (cell 2:10
9+ (face 2:11 cart (ref 2:16 cart))
10+ (face 2:21 note (aura 2:26 @t))))
11+ (value 3:1 repeated
12+ (gate 3:13
13+ (face 3:16 m (ref 3:18 model))
14+ (change 3:24 m
15+ (edit 3:26 open.cart (lit 3:36 %.y))
16+ (edit 3:40 open.cart (lit 3:50 %.n))))))
tests/golden/types-change-repeated-wing/types.txtnew file+8
@@ -0,0 +1,8 @@
1+partial inference; module not checked
2+change field `open.cart` is repeated
3+/tests/golden/types-change-repeated-wing/source.jibb:3:26: first field
4+++ repeated |= m=model m(open.cart=%.y open.cart=%.n)
5+ ^
6+/tests/golden/types-change-repeated-wing/source.jibb:3:40: repeated field
7+++ repeated |= m=model m(open.cart=%.y open.cart=%.n)
8+ ^
tests/golden/types-change-repeated/about.txtnew file+4
@@ -0,0 +1,4 @@
1+source: Grammar, Expressions: "Two items that set the same field, or one a field inside the other's, as `m(cart=c open.cart=%.y)`, are an error."
2+tests: a change that sets one named field twice, which was types-record-change-generic's `duplicate` and failed closed before wing items
3+expect: parses
4+expect-types: a partial inference report: the module error names both items
tests/golden/types-change-repeated/modules.txtnew file+1
@@ -0,0 +1 @@
1+# no module fixtures
tests/golden/types-change-repeated/source.jibbnew file+2
@@ -0,0 +1,2 @@
1++$ box |$ [A] [value=A keep=A]
2+++ duplicate |= [s=(box @t) v=@t] s(value=v value=v)
tests/golden/types-change-repeated/tokens.txtnew file+39
@@ -0,0 +1,39 @@
1+1:1 glyph +$
2+1:4 name box
3+1:8 glyph |$
4+1:11 glyph [
5+1:12 tight tvar A
6+1:13 tight glyph ]
7+1:15 glyph [
8+1:16 tight name value
9+1:21 tight glyph =
10+1:22 tight tvar A
11+1:24 name keep
12+1:28 tight glyph =
13+1:29 tight tvar A
14+1:30 tight glyph ]
15+2:1 NEWLINE
16+2:1 glyph ++
17+2:4 name duplicate
18+2:14 glyph |=
19+2:17 glyph [
20+2:18 tight name s
21+2:19 tight glyph =
22+2:20 tight glyph (
23+2:21 tight name box
24+2:25 aura @t
25+2:27 tight glyph )
26+2:29 name v
27+2:30 tight glyph =
28+2:31 tight aura @t
29+2:33 tight glyph ]
30+2:35 name s
31+2:36 tight glyph (
32+2:37 tight name value
33+2:42 tight glyph =
34+2:43 tight name v
35+2:45 name value
36+2:50 tight glyph =
37+2:51 tight name v
38+2:52 tight glyph )
39+2:53 NEWLINE
tests/golden/types-change-repeated/tree.txtnew file+15
@@ -0,0 +1,15 @@
1+(module
2+ (mold 1:1 box
3+ (generic 1:8 A
4+ (cell 1:15
5+ (face 1:16 value (tvar 1:22 A))
6+ (face 1:24 keep (tvar 1:29 A)))))
7+ (value 2:1 duplicate
8+ (gate 2:14
9+ (cell 2:17
10+ (face 2:18 s
11+ (apply 2:20 box (aura 2:25 @t)))
12+ (face 2:29 v (aura 2:31 @t)))
13+ (change 2:35 s
14+ (edit 2:37 value (wing 2:43 v))
15+ (edit 2:45 value (wing 2:51 v))))))
tests/golden/types-change-repeated/types.txtnew file+8
@@ -0,0 +1,8 @@
1+partial inference; module not checked
2+change field `value` is repeated
3+/tests/golden/types-change-repeated/source.jibb:2:37: first field
4+++ duplicate |= [s=(box @t) v=@t] s(value=v value=v)
5+ ^
6+/tests/golden/types-change-repeated/source.jibb:2:45: repeated field
7+++ duplicate |= [s=(box @t) v=@t] s(value=v value=v)
8+ ^
tests/golden/types-change-wing/about.txtnew file+4
@@ -0,0 +1,4 @@
1+source: Grammar, Expressions: "A change's item, inline or on a line, may name a wing instead, read through record fields only: `m(open.cart=(not open.cart.m))` sets field `open` of field `cart`, as Hoon's `m(open.cart x)`. Every value is computed from the original record."
2+tests: Wing items change nested record fields, beside named items and sibling fields, on the lines of an open change and under a wing target, and the result has the original record's type; values read the original record. A wing through a list, an atom or a unit, an unknown field at either end of a wing and a value of the wrong type, inline or on a line, are errors at the name or the item. A wing through a tuple or a capital, and a change of an unknown target, fail closed.
3+expect: parses
4+expect-types: a partial inference report
tests/golden/types-change-wing/modules.txtnew file+1
@@ -0,0 +1 @@
1+# no module fixtures
tests/golden/types-change-wing/source.jibbnew file+24
@@ -0,0 +1,24 @@
1++$ cart [items=(list @t) open=? deep=[level=@ud] pair=[@t @ud]]
2++$ model [cart=cart note=@t data=(unit @t)]
3++$ box |$ [A] [value=A]
4+++ flip-open |= m=model m(open.cart=(not open.cart.m))
5+++ siblings |= m=model m(open.cart=%.y items.cart=~ note="x" level.deep.cart=5)
6+++ original |= m=model m(level.deep.cart=(add 1 level.deep.cart.m) open.cart=open.cart.m)
7+++ lines |= m=model
8+ m(
9+ open.cart=%.n
10+ level.deep.cart=(add 1 level.deep.cart.m)
11+++ wing-target |= m=model cart.m(level.deep=1)
12+++ through-list |= m=model m(x.items.cart="a")
13+++ through-atom |= m=model m(x.note="a")
14+++ through-unit |= m=model m(u.data="a")
15+++ unknown-outer |= m=model m(open.carts=%.y)
16+++ unknown-inner |= m=model m(opened.cart=%.y)
17+++ wrong-value |= m=model m(open.cart="yes")
18+++ wrong-line |= m=model
19+ m(
20+ note="ok"
21+ level.deep.cart=~zod
22+++ through-tuple |= m=model m(x.pair.cart=1)
23+++ through-capital |= b=(box A) b(x.value=1)
24+++ unknown-target |= x x(a.b=1)
tests/golden/types-change-wing/tokens.txtnew file+289
@@ -0,0 +1,289 @@
1+1:1 glyph +$
2+1:4 name cart
3+1:9 glyph [
4+1:10 tight name items
5+1:15 tight glyph =
6+1:16 tight glyph (
7+1:17 tight name list
8+1:22 aura @t
9+1:24 tight glyph )
10+1:26 name open
11+1:30 tight glyph =
12+1:31 tight glyph ?
13+1:33 name deep
14+1:37 tight glyph =
15+1:38 tight glyph [
16+1:39 tight name level
17+1:44 tight glyph =
18+1:45 tight aura @ud
19+1:48 tight glyph ]
20+1:50 name pair
21+1:54 tight glyph =
22+1:55 tight glyph [
23+1:56 tight aura @t
24+1:59 aura @ud
25+1:62 tight glyph ]
26+1:63 tight glyph ]
27+2:1 NEWLINE
28+2:1 glyph +$
29+2:4 name model
30+2:10 glyph [
31+2:11 tight name cart
32+2:15 tight glyph =
33+2:16 tight name cart
34+2:21 name note
35+2:25 tight glyph =
36+2:26 tight aura @t
37+2:29 name data
38+2:33 tight glyph =
39+2:34 tight glyph (
40+2:35 tight name unit
41+2:40 aura @t
42+2:42 tight glyph )
43+2:43 tight glyph ]
44+3:1 NEWLINE
45+3:1 glyph +$
46+3:4 name box
47+3:8 glyph |$
48+3:11 glyph [
49+3:12 tight tvar A
50+3:13 tight glyph ]
51+3:15 glyph [
52+3:16 tight name value
53+3:21 tight glyph =
54+3:22 tight tvar A
55+3:23 tight glyph ]
56+4:1 NEWLINE
57+4:1 glyph ++
58+4:4 name flip-open
59+4:14 glyph |=
60+4:17 name m
61+4:18 tight glyph =
62+4:19 tight name model
63+4:25 name m
64+4:26 tight glyph (
65+4:27 tight wing open.cart
66+4:36 tight glyph =
67+4:37 tight glyph (
68+4:38 tight name not
69+4:42 wing open.cart.m
70+4:53 tight glyph )
71+4:54 tight glyph )
72+5:1 NEWLINE
73+5:1 glyph ++
74+5:4 name siblings
75+5:13 glyph |=
76+5:16 name m
77+5:17 tight glyph =
78+5:18 tight name model
79+5:24 name m
80+5:25 tight glyph (
81+5:26 tight wing open.cart
82+5:35 tight glyph =
83+5:36 tight lit %.y
84+5:40 wing items.cart
85+5:50 tight glyph =
86+5:51 tight lit ~
87+5:53 name note
88+5:57 tight glyph =
89+5:58 tight str "x"
90+5:62 wing level.deep.cart
91+5:77 tight glyph =
92+5:78 tight lit 5
93+5:79 tight glyph )
94+6:1 NEWLINE
95+6:1 glyph ++
96+6:4 name original
97+6:13 glyph |=
98+6:16 name m
99+6:17 tight glyph =
100+6:18 tight name model
101+6:24 name m
102+6:25 tight glyph (
103+6:26 tight wing level.deep.cart
104+6:41 tight glyph =
105+6:42 tight glyph (
106+6:43 tight name add
107+6:47 lit 1
108+6:49 wing level.deep.cart.m
109+6:66 tight glyph )
110+6:68 wing open.cart
111+6:77 tight glyph =
112+6:78 tight wing open.cart.m
113+6:89 tight glyph )
114+7:1 NEWLINE
115+7:1 glyph ++
116+7:4 name lines
117+7:10 glyph |=
118+7:13 name m
119+7:14 tight glyph =
120+7:15 tight name model
121+8:3 INDENT
122+8:3 name m
123+8:4 tight glyph (
124+9:5 INDENT
125+9:5 wing open.cart
126+9:14 tight glyph =
127+9:15 tight lit %.n
128+10:5 NEWLINE
129+10:5 wing level.deep.cart
130+10:20 tight glyph =
131+10:21 tight glyph (
132+10:22 tight name add
133+10:26 lit 1
134+10:28 wing level.deep.cart.m
135+10:45 tight glyph )
136+11:1 DEDENT
137+11:1 DEDENT
138+11:1 NEWLINE
139+11:1 glyph ++
140+11:4 name wing-target
141+11:16 glyph |=
142+11:19 name m
143+11:20 tight glyph =
144+11:21 tight name model
145+11:27 wing cart.m
146+11:33 tight glyph (
147+11:34 tight wing level.deep
148+11:44 tight glyph =
149+11:45 tight lit 1
150+11:46 tight glyph )
151+12:1 NEWLINE
152+12:1 glyph ++
153+12:4 name through-list
154+12:17 glyph |=
155+12:20 name m
156+12:21 tight glyph =
157+12:22 tight name model
158+12:28 name m
159+12:29 tight glyph (
160+12:30 tight wing x.items.cart
161+12:42 tight glyph =
162+12:43 tight str "a"
163+12:46 tight glyph )
164+13:1 NEWLINE
165+13:1 glyph ++
166+13:4 name through-atom
167+13:17 glyph |=
168+13:20 name m
169+13:21 tight glyph =
170+13:22 tight name model
171+13:28 name m
172+13:29 tight glyph (
173+13:30 tight wing x.note
174+13:36 tight glyph =
175+13:37 tight str "a"
176+13:40 tight glyph )
177+14:1 NEWLINE
178+14:1 glyph ++
179+14:4 name through-unit
180+14:17 glyph |=
181+14:20 name m
182+14:21 tight glyph =
183+14:22 tight name model
184+14:28 name m
185+14:29 tight glyph (
186+14:30 tight wing u.data
187+14:36 tight glyph =
188+14:37 tight str "a"
189+14:40 tight glyph )
190+15:1 NEWLINE
191+15:1 glyph ++
192+15:4 name unknown-outer
193+15:18 glyph |=
194+15:21 name m
195+15:22 tight glyph =
196+15:23 tight name model
197+15:29 name m
198+15:30 tight glyph (
199+15:31 tight wing open.carts
200+15:41 tight glyph =
201+15:42 tight lit %.y
202+15:45 tight glyph )
203+16:1 NEWLINE
204+16:1 glyph ++
205+16:4 name unknown-inner
206+16:18 glyph |=
207+16:21 name m
208+16:22 tight glyph =
209+16:23 tight name model
210+16:29 name m
211+16:30 tight glyph (
212+16:31 tight wing opened.cart
213+16:42 tight glyph =
214+16:43 tight lit %.y
215+16:46 tight glyph )
216+17:1 NEWLINE
217+17:1 glyph ++
218+17:4 name wrong-value
219+17:16 glyph |=
220+17:19 name m
221+17:20 tight glyph =
222+17:21 tight name model
223+17:27 name m
224+17:28 tight glyph (
225+17:29 tight wing open.cart
226+17:38 tight glyph =
227+17:39 tight str "yes"
228+17:44 tight glyph )
229+18:1 NEWLINE
230+18:1 glyph ++
231+18:4 name wrong-line
232+18:15 glyph |=
233+18:18 name m
234+18:19 tight glyph =
235+18:20 tight name model
236+19:3 INDENT
237+19:3 name m
238+19:4 tight glyph (
239+20:5 INDENT
240+20:5 name note
241+20:9 tight glyph =
242+20:10 tight str "ok"
243+21:5 NEWLINE
244+21:5 wing level.deep.cart
245+21:20 tight glyph =
246+21:21 tight lit ~zod
247+22:1 DEDENT
248+22:1 DEDENT
249+22:1 NEWLINE
250+22:1 glyph ++
251+22:4 name through-tuple
252+22:18 glyph |=
253+22:21 name m
254+22:22 tight glyph =
255+22:23 tight name model
256+22:29 name m
257+22:30 tight glyph (
258+22:31 tight wing x.pair.cart
259+22:42 tight glyph =
260+22:43 tight lit 1
261+22:44 tight glyph )
262+23:1 NEWLINE
263+23:1 glyph ++
264+23:4 name through-capital
265+23:20 glyph |=
266+23:23 name b
267+23:24 tight glyph =
268+23:25 tight glyph (
269+23:26 tight name box
270+23:30 tvar A
271+23:31 tight glyph )
272+23:33 name b
273+23:34 tight glyph (
274+23:35 tight wing x.value
275+23:42 tight glyph =
276+23:43 tight lit 1
277+23:44 tight glyph )
278+24:1 NEWLINE
279+24:1 glyph ++
280+24:4 name unknown-target
281+24:19 glyph |=
282+24:22 name x
283+24:24 name x
284+24:25 tight glyph (
285+24:26 tight wing a.b
286+24:29 tight glyph =
287+24:30 tight lit 1
288+24:31 tight glyph )
289+24:32 NEWLINE
tests/golden/types-change-wing/tree.txtnew file+111
@@ -0,0 +1,111 @@
1+(module
2+ (mold 1:1 cart
3+ (cell 1:9
4+ (face 1:10 items
5+ (apply 1:16 list (aura 1:22 @t)))
6+ (face 1:26 open (flag 1:31))
7+ (face 1:33 deep
8+ (cell 1:38
9+ (face 1:39 level (aura 1:45 @ud))))
10+ (face 1:50 pair
11+ (cell 1:55 (aura 1:56 @t) (aura 1:59 @ud)))))
12+ (mold 2:1 model
13+ (cell 2:10
14+ (face 2:11 cart (ref 2:16 cart))
15+ (face 2:21 note (aura 2:26 @t))
16+ (face 2:29 data
17+ (apply 2:34 unit (aura 2:40 @t)))))
18+ (mold 3:1 box
19+ (generic 3:8 A
20+ (cell 3:15
21+ (face 3:16 value (tvar 3:22 A)))))
22+ (value 4:1 flip-open
23+ (gate 4:14
24+ (face 4:17 m (ref 4:19 model))
25+ (change 4:25 m
26+ (edit 4:27 open.cart
27+ (group 4:37
28+ (call 4:38 (wing 4:38 not) (wing 4:42 open.cart.m)))))))
29+ (value 5:1 siblings
30+ (gate 5:13
31+ (face 5:16 m (ref 5:18 model))
32+ (change 5:24 m
33+ (edit 5:26 open.cart (lit 5:36 %.y))
34+ (edit 5:40 items.cart (lit 5:51 ~))
35+ (edit 5:53 note (str 5:58 "x"))
36+ (edit 5:62 level.deep.cart (lit 5:78 5)))))
37+ (value 6:1 original
38+ (gate 6:13
39+ (face 6:16 m (ref 6:18 model))
40+ (change 6:24 m
41+ (edit 6:26 level.deep.cart
42+ (group 6:42
43+ (call 6:43 (wing 6:43 add) (lit 6:47 1) (wing 6:49 level.deep.cart.m))))
44+ (edit 6:68 open.cart (wing 6:78 open.cart.m)))))
45+ (value 7:1 lines
46+ (gate 7:10
47+ (face 7:13 m (ref 7:15 model))
48+ (body 8:3
49+ (change 8:3 m
50+ (edit 9:5 open.cart (lit 9:15 %.n))
51+ (edit 10:5 level.deep.cart
52+ (group 10:21
53+ (call 10:22 (wing 10:22 add) (lit 10:26 1) (wing 10:28 level.deep.cart.m))))))))
54+ (value 11:1 wing-target
55+ (gate 11:16
56+ (face 11:19 m (ref 11:21 model))
57+ (change 11:27 cart.m
58+ (edit 11:34 level.deep (lit 11:45 1)))))
59+ (value 12:1 through-list
60+ (gate 12:17
61+ (face 12:20 m (ref 12:22 model))
62+ (change 12:28 m
63+ (edit 12:30 x.items.cart (str 12:43 "a")))))
64+ (value 13:1 through-atom
65+ (gate 13:17
66+ (face 13:20 m (ref 13:22 model))
67+ (change 13:28 m
68+ (edit 13:30 x.note (str 13:37 "a")))))
69+ (value 14:1 through-unit
70+ (gate 14:17
71+ (face 14:20 m (ref 14:22 model))
72+ (change 14:28 m
73+ (edit 14:30 u.data (str 14:37 "a")))))
74+ (value 15:1 unknown-outer
75+ (gate 15:18
76+ (face 15:21 m (ref 15:23 model))
77+ (change 15:29 m
78+ (edit 15:31 open.carts (lit 15:42 %.y)))))
79+ (value 16:1 unknown-inner
80+ (gate 16:18
81+ (face 16:21 m (ref 16:23 model))
82+ (change 16:29 m
83+ (edit 16:31 opened.cart (lit 16:43 %.y)))))
84+ (value 17:1 wrong-value
85+ (gate 17:16
86+ (face 17:19 m (ref 17:21 model))
87+ (change 17:27 m
88+ (edit 17:29 open.cart (str 17:39 "yes")))))
89+ (value 18:1 wrong-line
90+ (gate 18:15
91+ (face 18:18 m (ref 18:20 model))
92+ (body 19:3
93+ (change 19:3 m
94+ (edit 20:5 note (str 20:10 "ok"))
95+ (edit 21:5 level.deep.cart (lit 21:21 ~zod))))))
96+ (value 22:1 through-tuple
97+ (gate 22:18
98+ (face 22:21 m (ref 22:23 model))
99+ (change 22:29 m
100+ (edit 22:31 x.pair.cart (lit 22:43 1)))))
101+ (value 23:1 through-capital
102+ (gate 23:20
103+ (face 23:23 b
104+ (apply 23:25 box (tvar 23:30 A)))
105+ (change 23:33 b
106+ (edit 23:35 x.value (lit 23:43 1)))))
107+ (value 24:1 unknown-target
108+ (gate 24:19
109+ (bind 24:22 x)
110+ (change 24:24 x
111+ (edit 24:26 a.b (lit 24:30 1))))))
tests/golden/types-change-wing/types.txtnew file+46
@@ -0,0 +1,46 @@
1+partial inference; module not checked
2++$ cart: not checked
3++$ model: not checked
4++$ box: not checked
5+++ flip-open: $-([cart=[items=(list @t) [open=? [deep=[level=@ud] pair=[@t @ud]]]] [note=@t data=(unit @t)]] [cart=[items=(list @t) [open=? [deep=[level=@ud] pair=[@t @ud]]]] [note=@t data=(unit @t)]])
6+++ siblings: $-([cart=[items=(list @t) [open=? [deep=[level=@ud] pair=[@t @ud]]]] [note=@t data=(unit @t)]] [cart=[items=(list @t) [open=? [deep=[level=@ud] pair=[@t @ud]]]] [note=@t data=(unit @t)]])
7+++ original: $-([cart=[items=(list @t) [open=? [deep=[level=@ud] pair=[@t @ud]]]] [note=@t data=(unit @t)]] [cart=[items=(list @t) [open=? [deep=[level=@ud] pair=[@t @ud]]]] [note=@t data=(unit @t)]])
8+++ lines: $-([cart=[items=(list @t) [open=? [deep=[level=@ud] pair=[@t @ud]]]] [note=@t data=(unit @t)]] [cart=[items=(list @t) [open=? [deep=[level=@ud] pair=[@t @ud]]]] [note=@t data=(unit @t)]])
9+++ wing-target: $-([cart=[items=(list @t) [open=? [deep=[level=@ud] pair=[@t @ud]]]] [note=@t data=(unit @t)]] [items=(list @t) [open=? [deep=[level=@ud] pair=[@t @ud]]]])
10+++ through-list: change wing `x.items.cart` goes through `items`, which is not a record
11+/tests/golden/types-change-wing/source.jibb:12:32: field `items` has type (list @t)
12+++ through-list |= m=model m(x.items.cart="a")
13+ ^
14+++ through-atom: change wing `x.note` goes through `note`, which is not a record
15+/tests/golden/types-change-wing/source.jibb:13:32: field `note` has type @t
16+++ through-atom |= m=model m(x.note="a")
17+ ^
18+++ through-unit: change wing `u.data` goes through `data`, which is not a record
19+/tests/golden/types-change-wing/source.jibb:14:32: field `data`
20+++ through-unit |= m=model m(u.data="a")
21+ ^
22+++ unknown-outer: record has no field `carts`
23+/tests/golden/types-change-wing/source.jibb:15:36: unknown field
24+++ unknown-outer |= m=model m(open.carts=%.y)
25+ ^
26+++ unknown-inner: record has no field `opened`
27+/tests/golden/types-change-wing/source.jibb:16:31: unknown field
28+++ unknown-inner |= m=model m(opened.cart=%.y)
29+ ^
30+++ wrong-value: change field and replacement do not unify
31+/tests/golden/types-change-wing/source.jibb:17:29: expected replacement has type ?
32+++ wrong-value |= m=model m(open.cart="yes")
33+ ^
34+/tests/golden/types-change-wing/source.jibb:17:39: replacement has type @t
35+++ wrong-value |= m=model m(open.cart="yes")
36+ ^
37+++ wrong-line: change field and replacement do not unify
38+/tests/golden/types-change-wing/source.jibb:21:5: expected replacement has type @ud
39+ level.deep.cart=~zod
40+ ^
41+/tests/golden/types-change-wing/source.jibb:21:21: replacement has type @p
42+ level.deep.cart=~zod
43+ ^
44+++ through-tuple: change wing needs a known record in partial inference
45+++ through-capital: change wing needs a known record in partial inference
46+++ unknown-target: change target needs a known record in partial inference
tests/golden/types-record-change-generic/about.txt+1−1
@@ -1,4 +1,4 @@
11source: Grammar, Molds: "Type variables are capitals, generalized per `++`"; Modules: no name may shadow one already in scope.
2−tests: A replacement preserves shared generic field variables; forcing a written capital concrete stays unsupported and a mismatched replacement reports both sites. Unrelated tags and invalid expressions stay visible; repeated edits fail closed.
2+tests: A replacement preserves shared generic field variables; forcing a written capital concrete stays unsupported and a mismatched replacement reports both sites. Unrelated tags and invalid expressions stay visible. A repeated edit is now a module error, tested in types-change-repeated.
33expect: parses
44expect-types: a partial inference report
tests/golden/types-record-change-generic/source.jibb−1
@@ -2,7 +2,6 @@
22++ keep |= [s=(box A) v=A] s(value=v)
33++ force |= s=(box A) s(value="constant")
44++ mixed |= [s=(box @t) v=@p] s(value=v)
5−++ duplicate |= [s=(box @t) v=@t] s(value=v value=v)
65++ unrelated |= [s=(box @t) v=@t]
76 ~& %orphan
87 s(value=v)
tests/golden/types-record-change-generic/tokens.txt+35−59
@@ -72,7 +72,7 @@
72724:40 tight glyph )
73735:1 NEWLINE
74745:1 glyph ++
75−5:4 name duplicate
75+5:4 name unrelated
76765:14 glyph |=
77775:17 glyph [
78785:18 tight name s
@@ -85,61 +85,37 @@
85855:30 tight glyph =
86865:31 tight aura @t
87875:33 tight glyph ]
88−5:35 name s
89−5:36 tight glyph (
90−5:37 tight name value
91−5:42 tight glyph =
92−5:43 tight name v
93−5:45 name value
94−5:50 tight glyph =
95−5:51 tight name v
96−5:52 tight glyph )
97−6:1 NEWLINE
98−6:1 glyph ++
99−6:4 name unrelated
100−6:14 glyph |=
101−6:17 glyph [
102−6:18 tight name s
103−6:19 tight glyph =
104−6:20 tight glyph (
105−6:21 tight name box
106−6:25 aura @t
107−6:27 tight glyph )
108−6:29 name v
109−6:30 tight glyph =
110−6:31 tight aura @t
111−6:33 tight glyph ]
112−7:3 INDENT
113−7:3 glyph ~&
114−7:6 lit %orphan
115−8:5 INDENT
116−8:5 name s
117−8:6 tight glyph (
118−8:7 tight name value
119−8:12 tight glyph =
120−8:13 tight name v
121−8:14 tight glyph )
122−9:1 DEDENT
123−9:1 DEDENT
124−9:1 NEWLINE
125−9:1 glyph ++
126−9:4 name invalid-value
127−9:18 glyph |=
128−9:21 glyph [
129−9:22 tight name s
130−9:23 tight glyph =
131−9:24 tight glyph (
132−9:25 tight name box
133−9:29 aura @t
134−9:31 tight glyph )
135−9:33 name v
136−9:34 tight glyph =
137−9:35 tight aura @t
138−9:37 tight glyph ]
139−9:39 name s
140−9:40 tight glyph (
141−9:41 tight name value
142−9:46 tight glyph =
143−9:47 tight name missing
144−9:54 tight glyph )
145−9:55 NEWLINE
88+6:3 INDENT
89+6:3 glyph ~&
90+6:6 lit %orphan
91+7:5 INDENT
92+7:5 name s
93+7:6 tight glyph (
94+7:7 tight name value
95+7:12 tight glyph =
96+7:13 tight name v
97+7:14 tight glyph )
98+8:1 DEDENT
99+8:1 DEDENT
100+8:1 NEWLINE
101+8:1 glyph ++
102+8:4 name invalid-value
103+8:18 glyph |=
104+8:21 glyph [
105+8:22 tight name s
106+8:23 tight glyph =
107+8:24 tight glyph (
108+8:25 tight name box
109+8:29 aura @t
110+8:31 tight glyph )
111+8:33 name v
112+8:34 tight glyph =
113+8:35 tight aura @t
114+8:37 tight glyph ]
115+8:39 name s
116+8:40 tight glyph (
117+8:41 tight name value
118+8:46 tight glyph =
119+8:47 tight name missing
120+8:54 tight glyph )
121+8:55 NEWLINE
tests/golden/types-record-change-generic/tree.txt+15−24
@@ -26,32 +26,23 @@
2626 (face 4:25 v (aura 4:27 @p)))
2727 (change 4:31 s
2828 (edit 4:33 value (wing 4:39 v)))))
29− (value 5:1 duplicate
29+ (value 5:1 unrelated
3030 (gate 5:14
3131 (cell 5:17
3232 (face 5:18 s
3333 (apply 5:20 box (aura 5:25 @t)))
3434 (face 5:29 v (aura 5:31 @t)))
35− (change 5:35 s
36− (edit 5:37 value (wing 5:43 v))
37− (edit 5:45 value (wing 5:51 v)))))
38− (value 6:1 unrelated
39− (gate 6:14
40− (cell 6:17
41− (face 6:18 s
42− (apply 6:20 box (aura 6:25 @t)))
43− (face 6:29 v (aura 6:31 @t)))
44− (body 7:3
45− (trace 7:3
46− (lit 7:6 %orphan)
47− (body 8:5
48− (change 8:5 s
49− (edit 8:7 value (wing 8:13 v))))))))
50− (value 9:1 invalid-value
51− (gate 9:18
52− (cell 9:21
53− (face 9:22 s
54− (apply 9:24 box (aura 9:29 @t)))
55− (face 9:33 v (aura 9:35 @t)))
56− (change 9:39 s
57− (edit 9:41 value (wing 9:47 missing))))))
35+ (body 6:3
36+ (trace 6:3
37+ (lit 6:6 %orphan)
38+ (body 7:5
39+ (change 7:5 s
40+ (edit 7:7 value (wing 7:13 v))))))))
41+ (value 8:1 invalid-value
42+ (gate 8:18
43+ (cell 8:21
44+ (face 8:22 s
45+ (apply 8:24 box (aura 8:29 @t)))
46+ (face 8:33 v (aura 8:35 @t)))
47+ (change 8:39 s
48+ (edit 8:41 value (wing 8:47 missing))))))
tests/golden/types-record-change-generic/types.txt−1
@@ -9,6 +9,5 @@ partial inference; module not checked
99/tests/golden/types-record-change-generic/source.jibb:4:39: replacement has type @p
1010++ mixed |= [s=(box @t) v=@p] s(value=v)
1111 ^
12−++ duplicate: repeated change fields are not supported by partial inference
1312++ unrelated: tag needs context in partial inference
1413++ invalid-value: name is not available to partial inference