diff --git a/src/types/utils.ts b/src/types/utils.ts index a7b6db6..34e14cb 100644 --- a/src/types/utils.ts +++ b/src/types/utils.ts @@ -8,6 +8,18 @@ import { ObjectType } from "./objects"; */ export type NoInfer = T & ObjectType; +/** + * Prevent `T` from being distributed in a conditional type. + * A conditional is only distributed when the checked type is naked type param and T & {} is not a + * naked type param, but has the same contract as T. + * + * @note This must be used directly the condition itself: `NoDistribute extends U`, + * it won't work wrapping a type argument passed to a conditional type. + * + * @see https://www.typescriptlang.org/docs/handbook/advanced-types.html#distributive-conditional-types + */ +export type NoDistribute = T & {}; + /** * Mark a type as nullable (`null | undefined`). * @param T type that will become nullable diff --git a/test/utils/NoDistribute.test.ts b/test/utils/NoDistribute.test.ts new file mode 100644 index 0000000..510cc00 --- /dev/null +++ b/test/utils/NoDistribute.test.ts @@ -0,0 +1,35 @@ +import test from 'ava'; +import { assert } from '../helpers/assert'; + +import { NoDistribute } from '../../src'; + +test("can create a conditional type that won't distribute over unions", t => { + type IsString = T extends string ? "Yes" : "No"; + type IsStringNoDistribute = NoDistribute extends string ? "Yes" : "No"; + + /** + * Evaluates as: + * ("foo" extends string ? "Yes" : "No") + * | (42 extends string ? "Yes" : "No") + */ + type T1 = IsString<"foo" | 42>; + assert(t); + assert<"Yes" | "No", T1>(t); + + /** + * Evaluates as: + * ("foo" | 42) extends string ? "Yes" : "No" + */ + type T2 = IsStringNoDistribute<"foo" | 5>; + assert(t); + assert<"No", T2>(t); +}); + +test("cannot be used to prevent a distributive conditional from distributing", t => { + type IsString = T extends string ? "Yes" : "No"; + // It's the defintion of the conditional type that matters, + // not the type that's passed in, so this still distributes + type Test = IsString>; + assert(t); + assert<"Yes" | "No", Test>(t); +});