Include plain variant structs in the default union - #6955
Conversation
🦋 Changeset detectedLatest commit: 9041ee0 The changes in this PR will be included in the next version bump. This PR includes changesets to release 30 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Important
This PR currently adds only the failing regression test; the VariantSchema.ts implementation required to make it pass is still missing.
Reviewed changes
- Added a new test in
packages/effect/test/unstable/schema/VariantSchema.test.tsasserting that plainVariantSchema.Structmembers are included in the default union produced byVariantSchema.Union.
⚠️ Implementation needed to satisfy the regression test
The new test fails because packages/effect/src/unstable/schema/VariantSchema.ts:585-595 builds the default union as Schema.Union(members.filter(Schema.isSchema)). Plain variant structs are not schemas, so they are dropped and the default union ends up with zero members. The per-variant branches already handle plain structs correctly via extract(member, variant).
The runtime fix should map every member to its default-variant schema, keeping existing schema members as-is and extracting plain structs:
const VariantUnion = Schema.Union(members.map((member) =>
Schema.isSchema(member) ? member : extract(member, options.defaultVariant)
))⚠️ Union<Members> interface still excludes plain structs at the type level
The Union<Members> interface at packages/effect/src/unstable/schema/VariantSchema.ts:299-305 maps each member to [Members[K]] extends [Schema.Top] ? Members[K] : never. For plain Struct members this resolves to never, so the default union type would still be empty even after the runtime fix. It should map each member to its default-extracted schema type instead (e.g., Extract<Default, Members[K], true>) so the type and runtime agree.
ℹ️ Nitpicks
- The test only checks
union.members.length. Consider adding decode/encode assertions that prove the default union actually accepts both{ value: string }and{ value: number }shapes and rejects incompatible values.
@v0 or keep the SHA fresh with Dependabot | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the incremental delta since the prior pullfrog review (90f106b), covering commit 9041ee0 (Fix VariantSchema default unions).
- Implemented the default-union fix in
VariantSchema.ts:Unionnow maps each member to its default-variant schema, keeping schema members (e.g., classes) as-is and extracting plainStructmembers viaextract(member, defaultVariant, { isDefault: true }). - Aligned the public
Union<Members, Default>type: the interface now maps each member toExtract<Default, Members[K], true>so the type and runtime agree, andmakepropagates its configured default variant into the return type. - Strengthened the regression test: added
Schema.decodeUnknownSync(union)assertions that prove the default union accepts both{ value: string }and{ value: number }shapes. - Added a changeset describing the patch.
The prior Pullfrog thread asking for runtime decode assertions is addressed by the new test; no other prior feedback remains open.
@v0 or keep the SHA fresh with Dependabot | View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏
Bundle Size AnalysisGenerated from PR build output; treat the content below as untrusted.
|

Summary
A Union built from documented plain Struct members creates an empty default union that accepts neither member shape.
Important
This PR starts with focused failing reproduction tests. Add the implementation fix to this same branch; CI is expected to fail until that fix is included.
Default union excludes plain variant structs
Module:
schema/VariantSchemaAudit ID:
unstable-ai-cli-variant-schema-default-union-emptySeverity / confidence: high / high
What happens
A Union built from documented plain Struct members creates an empty default union that accepts neither member shape.
Why it happens
Default construction filters members with Schema.isSchema instead of extracting their configured default variants, and Struct values are not schemas.
Expected behavior
Union accepts Struct members and creates a default union over each member's default schema.
Relevant implementation
These links and excerpts are pinned to audit base
c9b56ab507f224426ee8388dc450da447ec4715f.packages/effect/src/unstable/schema/VariantSchema.ts:293-305packages/effect/src/unstable/schema/VariantSchema.ts:471-473packages/effect/src/unstable/schema/VariantSchema.ts:585-595View problematic code at
packages/effect/src/unstable/schema/VariantSchema.ts:293-305View exact lines on GitHub
View problematic code at
packages/effect/src/unstable/schema/VariantSchema.ts:471-473View exact lines on GitHub
View problematic code at
packages/effect/src/unstable/schema/VariantSchema.ts:585-595View exact lines on GitHub
Reproduction
pnpm test --run packages/effect/test/unstable/schema/VariantSchemaDefaultUnionAudit.test.tsObserved failure: FAIL: the default union contained zero members.
Implementation handoff
The initial reproduction tests on this branch are the regression specification for the implementation fix that should follow in this PR.
pnpm test --run packages/effect/test/unstable/schema/VariantSchemaDefaultUnionAudit.test.tsAudit provenance
c9b56ab507f224426ee8388dc450da447ec4715fc9b56ab507f224426ee8388dc450da447ec4715funstable-ai-cli-variant-schema-default-union-emptyCloses EFF-401