ianstormtaylor / superstruct

A simple and composable way to validate data in JavaScript (and TypeScript).

Home Page:https://docs.superstructjs.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

`intersection` flattens unions too much

Gelio opened this issue · comments

Because intersection uses UnionToIntersection on its arguments (except for the first one), it flattens any unions that appear in the intersection.

import * as s from "superstruct";
import { expectType, type TypeEqual } from "ts-expect";


const baseUnion = s.union([
    s.type({
        flavor: s.literal("a"),
    }),
    s.type({
        flavor: s.literal("b"),
        extraProperty: s.string(),
    }),
]);

const someOtherObject = s.type({
    foo: s.literal("bar")
});

const unionAsFirstInIntersection = s.intersection([
    baseUnion,
    someOtherObject,
]);

type ExpectedResult = ({ flavor: "a" } | { flavor: "b"; extraProperty: string }) & { foo: "bar" };

expectType<TypeEqual<s.Infer<typeof unionAsFirstInIntersection>, ExpectedResult>>(true);

const unionAsSecondInIntersection = s.intersection([
    someOtherObject,
    baseUnion,
]);

expectType<TypeEqual<s.Infer<typeof unionAsSecondInIntersection>, ExpectedResult>>(true); // error
expectType<TypeEqual<typeof unionAsSecondInIntersection, s.Struct<never, null>>>(false); // error

TypeScript playground

Notice that when baseUnion is used as the first in the intersection array, it works fine. When baseUnion appears second, UnionToIntesection flattens it too much, which causes the final unionAsSecondInIntersection struct to be never.

Workaround

Use union as the first element of the array provided to intersection. unionAsFirstInIntersection is constructed correctly.

commented
type IntersectionTypes<Types extends any[]> = Types extends [
    infer T,
    ...infer O
  ]
  ? T extends Struct<any, any>
    ? Infer<T> & IntersectionTypes<O>
    : unknown
  : unknown;
  

export function intersection<Types extends [...StructAny[]]>(
  ...types: Types
): Type<IntersectionTypes<Types>, null> {
  return ss.intersection(types as any) as any
}  

I fixed with this.