types/rtti: own unit-bit subtraction as withoutUnits - #1591
Conversation
Working notes; the PR title and description are the commit message. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016HvbYkBMYWwQECL7myLhqs
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016HvbYkBMYWwQECL7myLhqs
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
functionalscript | 55d5231 | Commit Preview URL Branch Preview URL |
Aug 15 2026, 06:55 PM |
o2alexanderfedin
left a comment
There was a problem hiding this comment.
Equivalence verified exhaustively rather than by reading. stripUndefined is the only call site, and its result feeds only nodeSchema/unionSchema/cmp, all of which read kinds by name — so the two real differences from the old inline code (key order: unit first, then the node's own; and explicit-undefined kind keys surviving instead of being dropped) are unobservable.
Checked over all 16 unit bitsets × all 32 kind-presence combinations (512 nodes, incl. {}/never, unit-only, no-unit, top): identical key sets and values both ways. Also ran toJsonSchema from origin/main against this PR's over 20 shapes — optional string/number/bigint/boolean/array/record/unknown/union/null/tuple, { a: () => undefined }, all-optional, nested and 4-deep optionals, optionals inside array/record, top-minus-undefined — byte-identical JSON, nothing thrown.
No mask concern: withoutUnits hardcodes nothing, and the only mask is the caller's unitBit(undefined) = 2, matching unitList = ['null','undefined','false','true'] as the existing unitBits proof asserts. fjs/types/rtti/data/proof.f.mjs:122-144 covers both branches of n.unit ?? 0 and unit === 0 ? …, including empty-key drop, partial, no-op, never and top.
npx tsc exit 0; fjs t → 2844 pass / 0 fail; fjs ci leaves the tree clean. Changelog matches the 3-line form and the implemented todo strip-undefined-set-op.md is deleted here.
Implements
fjs/media/json/schema/todo/strip-undefined-set-op.md(deleted here).Why
stripUndefinedperformed a set operation on the rtti data form — remove theundefinedunit from a union — by enumerating every member ofUnionSetto copy it:The duplication is the smaller problem. The TODO's real point is the failure mode: if
UnionSetever gains a kind,unionSchema's per-kind eliminator visibly stops handling it, while this one silently drops it from every optional property's schema.What
fjs/types/rtti/dataownsUnionSetand its algebra, so it gains the missing operation:and
stripUndefinedbecomes:Spreading the other five kinds rather than copying them is what closes the failure mode — a kind added later cannot be dropped by code that never names it.
Two scoping decisions
withoutUnitstakesUnionSet, notNode. The TODO sketched(n: Node) => Node. The reference (string) case is kept in the schema module because why a reference is passed through unchanged is a schema-level judgment — "its definition is shared, and the extra{ "not": {} }member it may carry matches no JSON value anyway" — not part of the set algebra.fjs/types/rtti/datastays about sets.merge's field-by-field enumeration is left alone. It looks like the same smell but is not: each kind needs its owncmpItem, so it is a genuine per-kind eliminator, exactly likeunionSchema. Only the unit-only operation can avoid naming the kinds.Verification
npx tscclean;fjs test2844 pass, 0 fail.npm run cov:fjs/types/rtti/data/module.f.mjsandfjs/media/json/schema/module.f.mjsboth 100% lines/branches/functions.unit-key rule, a partial removal, a no-op removal,never, and — the point of the change — that the other five kinds survive untouched.UnionSet(unitfirst, then the node's own order, matchingmerge), so I comparedtoJsonSchemaoutput across 13 shapes built around optional properties — optional string/number/bigint/boolean/array/record/unknown, optional unions, nested and deeply nested optionals, an all-optional struct, and an optional whose set is onlyundefined. Byte-identical, no case throwing.Changelog:
types/rtti/data: newwithoutUnitsremoves unit bits from a union set, dropping theunitkey when it empties.media/json/schemauses it instead of rebuilding the union field by field🤖 Generated with Claude Code
https://claude.ai/code/session_016HvbYkBMYWwQECL7myLhqs
Generated by Claude Code