diff --git a/.changeset/plain-variant-unions.md b/.changeset/plain-variant-unions.md new file mode 100644 index 00000000000..36715834798 --- /dev/null +++ b/.changeset/plain-variant-unions.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Include plain variant structs in the default variant union. diff --git a/packages/effect/src/unstable/schema/VariantSchema.ts b/packages/effect/src/unstable/schema/VariantSchema.ts index 963817baae8..2385b3d3fc8 100644 --- a/packages/effect/src/unstable/schema/VariantSchema.ts +++ b/packages/effect/src/unstable/schema/VariantSchema.ts @@ -296,12 +296,13 @@ type MissingSelfGeneric = * @category models * @since 4.0.0 */ -export interface Union>> extends - Schema.Union< - { - readonly [K in keyof Members]: [Members[K]] extends [Schema.Top] ? Members[K] : never - } - > +export interface Union>, Default extends string = string> + extends + Schema.Union< + { + readonly [K in keyof Members]: Extract + } + > {} /** @@ -410,7 +411,7 @@ export const make = < } readonly Union: >>( members: Members - ) => Union & Union.Variants + ) => Union & Union.Variants readonly extract: { ( variant: V @@ -469,7 +470,7 @@ export const make = < } } function UnionVariants(members: ReadonlyArray>) { - return Union(members, options.variants) + return Union(members, options.defaultVariant, options.variants) } const fieldEvolve = dual( 2, @@ -582,11 +583,18 @@ const Field = (schemas: A): Field => { return self } -const Union = >, Variants extends ReadonlyArray>( +const Union = < + Members extends ReadonlyArray>, + Default extends string, + Variants extends ReadonlyArray +>( members: Members, + defaultVariant: Default, variants: Variants ) => { - const VariantUnion = Schema.Union(members.filter((member) => Schema.isSchema(member))) as any + const VariantUnion = Schema.Union( + members.map((member) => Schema.isSchema(member) ? member : extract(member, defaultVariant, { isDefault: true })) + ) as any for (const variant of variants) { Object.defineProperty(VariantUnion, variant, { value: Schema.Union(members.map((member) => extract(member, variant))) diff --git a/packages/effect/test/unstable/schema/VariantSchema.test.ts b/packages/effect/test/unstable/schema/VariantSchema.test.ts index df84008e163..ba4506cbd42 100644 --- a/packages/effect/test/unstable/schema/VariantSchema.test.ts +++ b/packages/effect/test/unstable/schema/VariantSchema.test.ts @@ -37,6 +37,17 @@ describe("VariantSchema", () => { assert.deepStrictEqual(Schema.decodeSync(User.b)({ name: "Alice" }), { name: "Alice" }) assert.deepStrictEqual(Object.keys(User.fields), ["id", "name"]) }) + + it("includes plain variant structs in the default union", () => { + const Test = VariantSchema.make({ variants: ["a", "b"], defaultVariant: "a" }) + const first = Test.Struct({ value: Schema.String }) + const second = Test.Struct({ value: Schema.Number }) + const union = Test.Union([first, second]) + + assert.strictEqual(union.members.length, 2) + assert.deepStrictEqual(Schema.decodeUnknownSync(union)({ value: "foo" }), { value: "foo" }) + assert.deepStrictEqual(Schema.decodeUnknownSync(union)({ value: 42 }), { value: 42 }) + }) }) describe("Model", () => { diff --git a/packages/effect/typetest/VariantSchema.tst.ts b/packages/effect/typetest/VariantSchema.tst.ts index 605203ca962..5d0233c257a 100644 --- a/packages/effect/typetest/VariantSchema.tst.ts +++ b/packages/effect/typetest/VariantSchema.tst.ts @@ -26,9 +26,13 @@ describe("VariantSchema", () => { const second = Test.Struct({ value: Test.FieldOnly(["a", "b"])(Schema.Number) }) + const union = Test.Union([first, second]) expect(Test.Union).type.toBeCallableWith([first, second]) expect(Test.Union).type.not.toBeCallableWith(first, second) + expect>().type.toBe< + { readonly value: string } | { readonly value: number } + >() }) it("Class preserves constructor and variant schema types", () => {