Skip to content

Commit 3938958

Browse files
authored
Properly remove generic types that are constrained to 'null | undefined' in getNonNullableType (#44219)
* Improve getNonNullableType function * Add tests * More closely match previous behavior * Add non-strict mode test
1 parent 52cefdf commit 3938958

9 files changed

+332
-6
lines changed

Diff for: src/compiler/checker.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -20325,14 +20325,17 @@ namespace ts {
2032520325
}
2032620326

2032720327
function getGlobalNonNullableTypeInstantiation(type: Type) {
20328+
// First reduce away any constituents that are assignable to 'undefined' or 'null'. This not only eliminates
20329+
// 'undefined' and 'null', but also higher-order types such as a type parameter 'U extends undefined | null'
20330+
// that isn't eliminated by a NonNullable<T> instantiation.
20331+
const reducedType = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
2032820332
if (!deferredGlobalNonNullableTypeAlias) {
2032920333
deferredGlobalNonNullableTypeAlias = getGlobalSymbol("NonNullable" as __String, SymbolFlags.TypeAlias, /*diagnostic*/ undefined) || unknownSymbol;
2033020334
}
20331-
// Use NonNullable global type alias if available to improve quick info/declaration emit
20332-
if (deferredGlobalNonNullableTypeAlias !== unknownSymbol) {
20333-
return getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [type]);
20334-
}
20335-
return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); // Type alias unavailable, fall back to non-higher-order behavior
20335+
// If the NonNullable<T> type is available, return an instantiation. Otherwise just return the reduced type.
20336+
return deferredGlobalNonNullableTypeAlias !== unknownSymbol ?
20337+
getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [reducedType]) :
20338+
reducedType;
2033620339
}
2033720340

2033820341
function getNonNullableType(type: Type): Type {
@@ -24124,7 +24127,7 @@ namespace ts {
2412424127
}
2412524128

2412624129
function isGenericTypeWithUnionConstraint(type: Type) {
24127-
return !!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & TypeFlags.Union);
24130+
return !!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & (TypeFlags.Nullable | TypeFlags.Union));
2412824131
}
2412924132

2413024133
function containsGenericType(type: Type): boolean {

Diff for: tests/baselines/reference/nonNullableReduction.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//// [nonNullableReduction.ts]
2+
// Repros from #43425
3+
4+
type Transform1<T> = ((value: string) => T) | (string extends T ? undefined : never);
5+
type Transform2<T> = string extends T ? ((value: string) => T) | undefined : (value: string) => T;
6+
7+
function test<T>(f1: Transform1<T>, f2: Transform2<T>) {
8+
f1?.("hello");
9+
f2?.("hello");
10+
}
11+
12+
function f1<T>(x: T | (string extends T ? null | undefined : never)) {
13+
let z = x!; // NonNullable<T>
14+
}
15+
16+
function f2<T, U extends null | undefined>(x: T | U) {
17+
let z = x!; // NonNullable<T>
18+
}
19+
20+
21+
//// [nonNullableReduction.js]
22+
"use strict";
23+
// Repros from #43425
24+
function test(f1, f2) {
25+
f1 === null || f1 === void 0 ? void 0 : f1("hello");
26+
f2 === null || f2 === void 0 ? void 0 : f2("hello");
27+
}
28+
function f1(x) {
29+
var z = x; // NonNullable<T>
30+
}
31+
function f2(x) {
32+
var z = x; // NonNullable<T>
33+
}
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
=== tests/cases/compiler/nonNullableReduction.ts ===
2+
// Repros from #43425
3+
4+
type Transform1<T> = ((value: string) => T) | (string extends T ? undefined : never);
5+
>Transform1 : Symbol(Transform1, Decl(nonNullableReduction.ts, 0, 0))
6+
>T : Symbol(T, Decl(nonNullableReduction.ts, 2, 16))
7+
>value : Symbol(value, Decl(nonNullableReduction.ts, 2, 23))
8+
>T : Symbol(T, Decl(nonNullableReduction.ts, 2, 16))
9+
>T : Symbol(T, Decl(nonNullableReduction.ts, 2, 16))
10+
11+
type Transform2<T> = string extends T ? ((value: string) => T) | undefined : (value: string) => T;
12+
>Transform2 : Symbol(Transform2, Decl(nonNullableReduction.ts, 2, 85))
13+
>T : Symbol(T, Decl(nonNullableReduction.ts, 3, 16))
14+
>T : Symbol(T, Decl(nonNullableReduction.ts, 3, 16))
15+
>value : Symbol(value, Decl(nonNullableReduction.ts, 3, 42))
16+
>T : Symbol(T, Decl(nonNullableReduction.ts, 3, 16))
17+
>value : Symbol(value, Decl(nonNullableReduction.ts, 3, 78))
18+
>T : Symbol(T, Decl(nonNullableReduction.ts, 3, 16))
19+
20+
function test<T>(f1: Transform1<T>, f2: Transform2<T>) {
21+
>test : Symbol(test, Decl(nonNullableReduction.ts, 3, 98))
22+
>T : Symbol(T, Decl(nonNullableReduction.ts, 5, 14))
23+
>f1 : Symbol(f1, Decl(nonNullableReduction.ts, 5, 17))
24+
>Transform1 : Symbol(Transform1, Decl(nonNullableReduction.ts, 0, 0))
25+
>T : Symbol(T, Decl(nonNullableReduction.ts, 5, 14))
26+
>f2 : Symbol(f2, Decl(nonNullableReduction.ts, 5, 35))
27+
>Transform2 : Symbol(Transform2, Decl(nonNullableReduction.ts, 2, 85))
28+
>T : Symbol(T, Decl(nonNullableReduction.ts, 5, 14))
29+
30+
f1?.("hello");
31+
>f1 : Symbol(f1, Decl(nonNullableReduction.ts, 5, 17))
32+
33+
f2?.("hello");
34+
>f2 : Symbol(f2, Decl(nonNullableReduction.ts, 5, 35))
35+
}
36+
37+
function f1<T>(x: T | (string extends T ? null | undefined : never)) {
38+
>f1 : Symbol(f1, Decl(nonNullableReduction.ts, 8, 1))
39+
>T : Symbol(T, Decl(nonNullableReduction.ts, 10, 12))
40+
>x : Symbol(x, Decl(nonNullableReduction.ts, 10, 15))
41+
>T : Symbol(T, Decl(nonNullableReduction.ts, 10, 12))
42+
>T : Symbol(T, Decl(nonNullableReduction.ts, 10, 12))
43+
44+
let z = x!; // NonNullable<T>
45+
>z : Symbol(z, Decl(nonNullableReduction.ts, 11, 7))
46+
>x : Symbol(x, Decl(nonNullableReduction.ts, 10, 15))
47+
}
48+
49+
function f2<T, U extends null | undefined>(x: T | U) {
50+
>f2 : Symbol(f2, Decl(nonNullableReduction.ts, 12, 1))
51+
>T : Symbol(T, Decl(nonNullableReduction.ts, 14, 12))
52+
>U : Symbol(U, Decl(nonNullableReduction.ts, 14, 14))
53+
>x : Symbol(x, Decl(nonNullableReduction.ts, 14, 43))
54+
>T : Symbol(T, Decl(nonNullableReduction.ts, 14, 12))
55+
>U : Symbol(U, Decl(nonNullableReduction.ts, 14, 14))
56+
57+
let z = x!; // NonNullable<T>
58+
>z : Symbol(z, Decl(nonNullableReduction.ts, 15, 7))
59+
>x : Symbol(x, Decl(nonNullableReduction.ts, 14, 43))
60+
}
61+

Diff for: tests/baselines/reference/nonNullableReduction.types

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
=== tests/cases/compiler/nonNullableReduction.ts ===
2+
// Repros from #43425
3+
4+
type Transform1<T> = ((value: string) => T) | (string extends T ? undefined : never);
5+
>Transform1 : Transform1<T>
6+
>value : string
7+
8+
type Transform2<T> = string extends T ? ((value: string) => T) | undefined : (value: string) => T;
9+
>Transform2 : Transform2<T>
10+
>value : string
11+
>value : string
12+
13+
function test<T>(f1: Transform1<T>, f2: Transform2<T>) {
14+
>test : <T>(f1: Transform1<T>, f2: Transform2<T>) => void
15+
>f1 : Transform1<T>
16+
>f2 : Transform2<T>
17+
18+
f1?.("hello");
19+
>f1?.("hello") : T | undefined
20+
>f1 : ((value: string) => T) | undefined
21+
>"hello" : "hello"
22+
23+
f2?.("hello");
24+
>f2?.("hello") : T | undefined
25+
>f2 : ((value: string) => T) | ((value: string) => T) | undefined
26+
>"hello" : "hello"
27+
}
28+
29+
function f1<T>(x: T | (string extends T ? null | undefined : never)) {
30+
>f1 : <T>(x: T | (string extends T ? null | undefined : never)) => void
31+
>x : T | (string extends T ? null | undefined : never)
32+
>null : null
33+
34+
let z = x!; // NonNullable<T>
35+
>z : NonNullable<T>
36+
>x! : NonNullable<T>
37+
>x : T | (string extends T ? null | undefined : never)
38+
}
39+
40+
function f2<T, U extends null | undefined>(x: T | U) {
41+
>f2 : <T, U extends null | undefined>(x: T | U) => void
42+
>null : null
43+
>x : T | U
44+
45+
let z = x!; // NonNullable<T>
46+
>z : NonNullable<T>
47+
>x! : NonNullable<T>
48+
>x : T | U
49+
}
50+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//// [nonNullableReductionNonStrict.ts]
2+
// Repros from #43425
3+
4+
type Transform1<T> = ((value: string) => T) | (string extends T ? undefined : never);
5+
type Transform2<T> = string extends T ? ((value: string) => T) | undefined : (value: string) => T;
6+
7+
function test<T>(f1: Transform1<T>, f2: Transform2<T>) {
8+
f1?.("hello");
9+
f2?.("hello");
10+
}
11+
12+
function f1<T>(x: T | (string extends T ? null | undefined : never)) {
13+
let z = x!; // NonNullable<T>
14+
}
15+
16+
function f2<T, U extends null | undefined>(x: T | U) {
17+
let z = x!; // NonNullable<T>
18+
}
19+
20+
21+
//// [nonNullableReductionNonStrict.js]
22+
// Repros from #43425
23+
function test(f1, f2) {
24+
f1 === null || f1 === void 0 ? void 0 : f1("hello");
25+
f2 === null || f2 === void 0 ? void 0 : f2("hello");
26+
}
27+
function f1(x) {
28+
var z = x; // NonNullable<T>
29+
}
30+
function f2(x) {
31+
var z = x; // NonNullable<T>
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
=== tests/cases/compiler/nonNullableReductionNonStrict.ts ===
2+
// Repros from #43425
3+
4+
type Transform1<T> = ((value: string) => T) | (string extends T ? undefined : never);
5+
>Transform1 : Symbol(Transform1, Decl(nonNullableReductionNonStrict.ts, 0, 0))
6+
>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 2, 16))
7+
>value : Symbol(value, Decl(nonNullableReductionNonStrict.ts, 2, 23))
8+
>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 2, 16))
9+
>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 2, 16))
10+
11+
type Transform2<T> = string extends T ? ((value: string) => T) | undefined : (value: string) => T;
12+
>Transform2 : Symbol(Transform2, Decl(nonNullableReductionNonStrict.ts, 2, 85))
13+
>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 3, 16))
14+
>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 3, 16))
15+
>value : Symbol(value, Decl(nonNullableReductionNonStrict.ts, 3, 42))
16+
>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 3, 16))
17+
>value : Symbol(value, Decl(nonNullableReductionNonStrict.ts, 3, 78))
18+
>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 3, 16))
19+
20+
function test<T>(f1: Transform1<T>, f2: Transform2<T>) {
21+
>test : Symbol(test, Decl(nonNullableReductionNonStrict.ts, 3, 98))
22+
>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 5, 14))
23+
>f1 : Symbol(f1, Decl(nonNullableReductionNonStrict.ts, 5, 17))
24+
>Transform1 : Symbol(Transform1, Decl(nonNullableReductionNonStrict.ts, 0, 0))
25+
>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 5, 14))
26+
>f2 : Symbol(f2, Decl(nonNullableReductionNonStrict.ts, 5, 35))
27+
>Transform2 : Symbol(Transform2, Decl(nonNullableReductionNonStrict.ts, 2, 85))
28+
>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 5, 14))
29+
30+
f1?.("hello");
31+
>f1 : Symbol(f1, Decl(nonNullableReductionNonStrict.ts, 5, 17))
32+
33+
f2?.("hello");
34+
>f2 : Symbol(f2, Decl(nonNullableReductionNonStrict.ts, 5, 35))
35+
}
36+
37+
function f1<T>(x: T | (string extends T ? null | undefined : never)) {
38+
>f1 : Symbol(f1, Decl(nonNullableReductionNonStrict.ts, 8, 1))
39+
>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 10, 12))
40+
>x : Symbol(x, Decl(nonNullableReductionNonStrict.ts, 10, 15))
41+
>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 10, 12))
42+
>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 10, 12))
43+
44+
let z = x!; // NonNullable<T>
45+
>z : Symbol(z, Decl(nonNullableReductionNonStrict.ts, 11, 7))
46+
>x : Symbol(x, Decl(nonNullableReductionNonStrict.ts, 10, 15))
47+
}
48+
49+
function f2<T, U extends null | undefined>(x: T | U) {
50+
>f2 : Symbol(f2, Decl(nonNullableReductionNonStrict.ts, 12, 1))
51+
>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 14, 12))
52+
>U : Symbol(U, Decl(nonNullableReductionNonStrict.ts, 14, 14))
53+
>x : Symbol(x, Decl(nonNullableReductionNonStrict.ts, 14, 43))
54+
>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 14, 12))
55+
>U : Symbol(U, Decl(nonNullableReductionNonStrict.ts, 14, 14))
56+
57+
let z = x!; // NonNullable<T>
58+
>z : Symbol(z, Decl(nonNullableReductionNonStrict.ts, 15, 7))
59+
>x : Symbol(x, Decl(nonNullableReductionNonStrict.ts, 14, 43))
60+
}
61+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
=== tests/cases/compiler/nonNullableReductionNonStrict.ts ===
2+
// Repros from #43425
3+
4+
type Transform1<T> = ((value: string) => T) | (string extends T ? undefined : never);
5+
>Transform1 : Transform1<T>
6+
>value : string
7+
8+
type Transform2<T> = string extends T ? ((value: string) => T) | undefined : (value: string) => T;
9+
>Transform2 : Transform2<T>
10+
>value : string
11+
>value : string
12+
13+
function test<T>(f1: Transform1<T>, f2: Transform2<T>) {
14+
>test : <T>(f1: Transform1<T>, f2: Transform2<T>) => void
15+
>f1 : Transform1<T>
16+
>f2 : Transform2<T>
17+
18+
f1?.("hello");
19+
>f1?.("hello") : T
20+
>f1 : (value: string) => T
21+
>"hello" : "hello"
22+
23+
f2?.("hello");
24+
>f2?.("hello") : T
25+
>f2 : ((value: string) => T) | ((value: string) => T)
26+
>"hello" : "hello"
27+
}
28+
29+
function f1<T>(x: T | (string extends T ? null | undefined : never)) {
30+
>f1 : <T>(x: T | (string extends T ? null | undefined : never)) => void
31+
>x : T | (string extends T ? null : never)
32+
>null : null
33+
34+
let z = x!; // NonNullable<T>
35+
>z : T | (string extends T ? null : never)
36+
>x! : T | (string extends T ? null : never)
37+
>x : T | (string extends T ? null : never)
38+
}
39+
40+
function f2<T, U extends null | undefined>(x: T | U) {
41+
>f2 : <T, U extends null>(x: T | U) => void
42+
>null : null
43+
>x : T | U
44+
45+
let z = x!; // NonNullable<T>
46+
>z : T | U
47+
>x! : T | U
48+
>x : T | U
49+
}
50+

Diff for: tests/cases/compiler/nonNullableReduction.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// @strict: true
2+
3+
// Repros from #43425
4+
5+
type Transform1<T> = ((value: string) => T) | (string extends T ? undefined : never);
6+
type Transform2<T> = string extends T ? ((value: string) => T) | undefined : (value: string) => T;
7+
8+
function test<T>(f1: Transform1<T>, f2: Transform2<T>) {
9+
f1?.("hello");
10+
f2?.("hello");
11+
}
12+
13+
function f1<T>(x: T | (string extends T ? null | undefined : never)) {
14+
let z = x!; // NonNullable<T>
15+
}
16+
17+
function f2<T, U extends null | undefined>(x: T | U) {
18+
let z = x!; // NonNullable<T>
19+
}
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Repros from #43425
2+
3+
type Transform1<T> = ((value: string) => T) | (string extends T ? undefined : never);
4+
type Transform2<T> = string extends T ? ((value: string) => T) | undefined : (value: string) => T;
5+
6+
function test<T>(f1: Transform1<T>, f2: Transform2<T>) {
7+
f1?.("hello");
8+
f2?.("hello");
9+
}
10+
11+
function f1<T>(x: T | (string extends T ? null | undefined : never)) {
12+
let z = x!; // NonNullable<T>
13+
}
14+
15+
function f2<T, U extends null | undefined>(x: T | U) {
16+
let z = x!; // NonNullable<T>
17+
}

0 commit comments

Comments
 (0)