`intersection` flattens unions too much
Gelio opened this issue · comments
Grzegorz Rozdzialik commented
Because intersection
uses UnionToIntersection
on its arguments (except for the first one), it flattens any union
s 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
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.
Morlay 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.