Skip to content

Commit 270b889

Browse files
authored
Merge pull request #104 from andnp/NoDistribute
feat: Add NoDistribute utility
2 parents 146ab9e + d5effb8 commit 270b889

File tree

2 files changed

+47
-0
lines changed

2 files changed

+47
-0
lines changed

src/types/utils.ts

+12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ import { ObjectType } from "./objects";
88
*/
99
export type NoInfer<T> = T & ObjectType<T>;
1010

11+
/**
12+
* Prevent `T` from being distributed in a conditional type.
13+
* A conditional is only distributed when the checked type is naked type param and T & {} is not a
14+
* naked type param, but has the same contract as T.
15+
*
16+
* @note This must be used directly the condition itself: `NoDistribute<T> extends U`,
17+
* it won't work wrapping a type argument passed to a conditional type.
18+
*
19+
* @see https://www.typescriptlang.org/docs/handbook/advanced-types.html#distributive-conditional-types
20+
*/
21+
export type NoDistribute<T> = T & {};
22+
1123
/**
1224
* Mark a type as nullable (`null | undefined`).
1325
* @param T type that will become nullable

test/utils/NoDistribute.test.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import test from 'ava';
2+
import { assert } from '../helpers/assert';
3+
4+
import { NoDistribute } from '../../src';
5+
6+
test("can create a conditional type that won't distribute over unions", t => {
7+
type IsString<T> = T extends string ? "Yes" : "No";
8+
type IsStringNoDistribute<T> = NoDistribute<T> extends string ? "Yes" : "No";
9+
10+
/**
11+
* Evaluates as:
12+
* ("foo" extends string ? "Yes" : "No")
13+
* | (42 extends string ? "Yes" : "No")
14+
*/
15+
type T1 = IsString<"foo" | 42>;
16+
assert<T1, "Yes" | "No">(t);
17+
assert<"Yes" | "No", T1>(t);
18+
19+
/**
20+
* Evaluates as:
21+
* ("foo" | 42) extends string ? "Yes" : "No"
22+
*/
23+
type T2 = IsStringNoDistribute<"foo" | 5>;
24+
assert<T2, "No">(t);
25+
assert<"No", T2>(t);
26+
});
27+
28+
test("cannot be used to prevent a distributive conditional from distributing", t => {
29+
type IsString<T> = T extends string ? "Yes" : "No";
30+
// It's the defintion of the conditional type that matters,
31+
// not the type that's passed in, so this still distributes
32+
type Test = IsString<NoDistribute<"foo" | 42>>;
33+
assert<Test, "Yes" | "No">(t);
34+
assert<"Yes" | "No", Test>(t);
35+
});

0 commit comments

Comments
 (0)