Repository navigation
fix(downgrader): drop a repeated null branch in an anyOf, as Effect writes a nullish union - #237
Conversation
…rites a nullish union
Effect writes a nullish union, or an optional nullable one, as `anyOf [A, B, { type: "null" }, { type: "null" }]`. 3.0 kept both null branches, since each `{ type: "null" }` becomes its own `enum: [null]` array and `isSameBranch` compares keyword values by identity. Two `enum: [null]` arrays now count as the same value, so `dropEqualBranches` keeps null once, and a lone `anyOf [null, null]` (Effect's optional null) becomes the null schema instead of a `oneOf` with one branch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVaw7QJuqf3F5ycJ5Qq1rT
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Merging this PR will not alter performance1 warning
Comparing Footnotes
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
isSameBranchtreats twoenum: [null]values as equal —packages/downgrader/src/v3.1-to-v3.0.ts:899-906. A repeated{ type: "null" }branch in ananyOfnow collapses even when the two branches are distinct objects (each carrying its ownenum: [null]array), removing the prior object-identity dependence. The exception is scoped toname === 'enum'withisOnlyNullon both sides; key sets must still match exactly and every other keyword still compares by identity.- Tests —
packages/downgrader/tests/v3.1-to-v3.0.test.ts:1938-1957. Adjacent duplicate null branches collapse to one; null branches with differentdescriptionstay; two null branches separated by another branch stay (neighbor-only compare); duplicated['a', null]enums still stay. - README —
packages/downgrader/README.md:196. The equal-branches row now names Effect's nullish union as an example.
I traced the single consumer path (isSameBranch → dropEqualBranches, called only for anyOf at v3.1-to-v3.0.ts:1557): dropping a byte-identical adjacent null branch cannot change accepted values, and the other mergers already filtered null branches, so the only output change is the removed duplicate. Verified locally on cb5c525: the test suite passes, and reverting the source hunk makes the new assertion fail, confirming it is a live regression guard.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Requested by dinwwwh · project thread
Effect writes a nullish union, or an optional nullable one, as
anyOf [A, B, { type: "null" }, { type: "null" }]. For example,Schema.NullishOr(Schema.TaggedUnion(...))through oRPC's Effect converter does this. The 3.0 output kept both null branches, because each{ type: "null" }becomes its ownenum: [null]array andisSameBranchcompares keyword values by identity. The same JSON already lost the duplicate when both branches were one in-memory object, so the output depended on object identity.isSameBranchnow counts twoenum: [null]arrays as the same value, sodropEqualBrancheskeeps null once. A loneanyOf [null, null], which is how Effect writes an optionalSchema.Null, now becomes the null schema instead of aoneOfwith one branch. Null branches with different annotations still both stay, and so do otherenumcopies.Checked on 1,721 generated and real docs (Effect, TypeBox, Yup, Vine, Zod, Valibot, ArkType through oRPC beta.47, plus GitHub, OpenAI, Discord, Stripe and X). Every changed output only drops a repeated null branch.
[string, null, "k", null], and only neighbors are compared.Generated by Claude Code