From 44750125589ed3de94f292f333a90d3d13f3c73f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 22 May 2021 17:59:48 -0700 Subject: [PATCH 1/4] Improve getNonNullableType function --- src/compiler/checker.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b57a505c1699a..d64d1e5da3b3e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20320,14 +20320,17 @@ namespace ts { } function getGlobalNonNullableTypeInstantiation(type: Type) { + // First reduce away any constituents that are assignable to 'undefined' or 'null'. This not only eliminates + // 'undefined' and 'null', but also higher-order types such as a type parameter 'U extends undefined | null' + // that isn't eliminated by a NonNullable instantiation. + const reducedType = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); if (!deferredGlobalNonNullableTypeAlias) { deferredGlobalNonNullableTypeAlias = getGlobalSymbol("NonNullable" as __String, SymbolFlags.TypeAlias, /*diagnostic*/ undefined) || unknownSymbol; } - // Use NonNullable global type alias if available to improve quick info/declaration emit - if (deferredGlobalNonNullableTypeAlias !== unknownSymbol) { - return getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [type]); - } - return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); // Type alias unavailable, fall back to non-higher-order behavior + // If the NonNullable type is available, return an instantiation. Otherwise just return the reduced type. + return deferredGlobalNonNullableTypeAlias !== unknownSymbol ? + getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [reducedType]) : + reducedType; } function getNonNullableType(type: Type): Type { From 86f09ae87c4d38c0dbaea4f16e5b7f1a4a276609 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 22 May 2021 18:04:20 -0700 Subject: [PATCH 2/4] Add tests --- .../reference/nonNullableReduction.js | 33 ++++++++++ .../reference/nonNullableReduction.symbols | 61 +++++++++++++++++++ .../reference/nonNullableReduction.types | 50 +++++++++++++++ tests/cases/compiler/nonNullableReduction.ts | 19 ++++++ 4 files changed, 163 insertions(+) create mode 100644 tests/baselines/reference/nonNullableReduction.js create mode 100644 tests/baselines/reference/nonNullableReduction.symbols create mode 100644 tests/baselines/reference/nonNullableReduction.types create mode 100644 tests/cases/compiler/nonNullableReduction.ts diff --git a/tests/baselines/reference/nonNullableReduction.js b/tests/baselines/reference/nonNullableReduction.js new file mode 100644 index 0000000000000..57de1f96a7b71 --- /dev/null +++ b/tests/baselines/reference/nonNullableReduction.js @@ -0,0 +1,33 @@ +//// [nonNullableReduction.ts] +// Repros from #43425 + +type Transform1 = ((value: string) => T) | (string extends T ? undefined : never); +type Transform2 = string extends T ? ((value: string) => T) | undefined : (value: string) => T; + +function test(f1: Transform1, f2: Transform2) { + f1?.("hello"); + f2?.("hello"); +} + +function f1(x: T | (string extends T ? null | undefined : never)) { + let z = x!; // NonNullable +} + +function f2(x: T | U) { + let z = x!; // NonNullable +} + + +//// [nonNullableReduction.js] +"use strict"; +// Repros from #43425 +function test(f1, f2) { + f1 === null || f1 === void 0 ? void 0 : f1("hello"); + f2 === null || f2 === void 0 ? void 0 : f2("hello"); +} +function f1(x) { + var z = x; // NonNullable +} +function f2(x) { + var z = x; // NonNullable +} diff --git a/tests/baselines/reference/nonNullableReduction.symbols b/tests/baselines/reference/nonNullableReduction.symbols new file mode 100644 index 0000000000000..6007449ca6d4c --- /dev/null +++ b/tests/baselines/reference/nonNullableReduction.symbols @@ -0,0 +1,61 @@ +=== tests/cases/compiler/nonNullableReduction.ts === +// Repros from #43425 + +type Transform1 = ((value: string) => T) | (string extends T ? undefined : never); +>Transform1 : Symbol(Transform1, Decl(nonNullableReduction.ts, 0, 0)) +>T : Symbol(T, Decl(nonNullableReduction.ts, 2, 16)) +>value : Symbol(value, Decl(nonNullableReduction.ts, 2, 23)) +>T : Symbol(T, Decl(nonNullableReduction.ts, 2, 16)) +>T : Symbol(T, Decl(nonNullableReduction.ts, 2, 16)) + +type Transform2 = string extends T ? ((value: string) => T) | undefined : (value: string) => T; +>Transform2 : Symbol(Transform2, Decl(nonNullableReduction.ts, 2, 85)) +>T : Symbol(T, Decl(nonNullableReduction.ts, 3, 16)) +>T : Symbol(T, Decl(nonNullableReduction.ts, 3, 16)) +>value : Symbol(value, Decl(nonNullableReduction.ts, 3, 42)) +>T : Symbol(T, Decl(nonNullableReduction.ts, 3, 16)) +>value : Symbol(value, Decl(nonNullableReduction.ts, 3, 78)) +>T : Symbol(T, Decl(nonNullableReduction.ts, 3, 16)) + +function test(f1: Transform1, f2: Transform2) { +>test : Symbol(test, Decl(nonNullableReduction.ts, 3, 98)) +>T : Symbol(T, Decl(nonNullableReduction.ts, 5, 14)) +>f1 : Symbol(f1, Decl(nonNullableReduction.ts, 5, 17)) +>Transform1 : Symbol(Transform1, Decl(nonNullableReduction.ts, 0, 0)) +>T : Symbol(T, Decl(nonNullableReduction.ts, 5, 14)) +>f2 : Symbol(f2, Decl(nonNullableReduction.ts, 5, 35)) +>Transform2 : Symbol(Transform2, Decl(nonNullableReduction.ts, 2, 85)) +>T : Symbol(T, Decl(nonNullableReduction.ts, 5, 14)) + + f1?.("hello"); +>f1 : Symbol(f1, Decl(nonNullableReduction.ts, 5, 17)) + + f2?.("hello"); +>f2 : Symbol(f2, Decl(nonNullableReduction.ts, 5, 35)) +} + +function f1(x: T | (string extends T ? null | undefined : never)) { +>f1 : Symbol(f1, Decl(nonNullableReduction.ts, 8, 1)) +>T : Symbol(T, Decl(nonNullableReduction.ts, 10, 12)) +>x : Symbol(x, Decl(nonNullableReduction.ts, 10, 15)) +>T : Symbol(T, Decl(nonNullableReduction.ts, 10, 12)) +>T : Symbol(T, Decl(nonNullableReduction.ts, 10, 12)) + + let z = x!; // NonNullable +>z : Symbol(z, Decl(nonNullableReduction.ts, 11, 7)) +>x : Symbol(x, Decl(nonNullableReduction.ts, 10, 15)) +} + +function f2(x: T | U) { +>f2 : Symbol(f2, Decl(nonNullableReduction.ts, 12, 1)) +>T : Symbol(T, Decl(nonNullableReduction.ts, 14, 12)) +>U : Symbol(U, Decl(nonNullableReduction.ts, 14, 14)) +>x : Symbol(x, Decl(nonNullableReduction.ts, 14, 43)) +>T : Symbol(T, Decl(nonNullableReduction.ts, 14, 12)) +>U : Symbol(U, Decl(nonNullableReduction.ts, 14, 14)) + + let z = x!; // NonNullable +>z : Symbol(z, Decl(nonNullableReduction.ts, 15, 7)) +>x : Symbol(x, Decl(nonNullableReduction.ts, 14, 43)) +} + diff --git a/tests/baselines/reference/nonNullableReduction.types b/tests/baselines/reference/nonNullableReduction.types new file mode 100644 index 0000000000000..5c0863fc379d2 --- /dev/null +++ b/tests/baselines/reference/nonNullableReduction.types @@ -0,0 +1,50 @@ +=== tests/cases/compiler/nonNullableReduction.ts === +// Repros from #43425 + +type Transform1 = ((value: string) => T) | (string extends T ? undefined : never); +>Transform1 : Transform1 +>value : string + +type Transform2 = string extends T ? ((value: string) => T) | undefined : (value: string) => T; +>Transform2 : Transform2 +>value : string +>value : string + +function test(f1: Transform1, f2: Transform2) { +>test : (f1: Transform1, f2: Transform2) => void +>f1 : Transform1 +>f2 : Transform2 + + f1?.("hello"); +>f1?.("hello") : T | undefined +>f1 : Transform1 +>"hello" : "hello" + + f2?.("hello"); +>f2?.("hello") : T | undefined +>f2 : ((value: string) => T) | ((value: string) => T) | undefined +>"hello" : "hello" +} + +function f1(x: T | (string extends T ? null | undefined : never)) { +>f1 : (x: T | (string extends T ? null | undefined : never)) => void +>x : T | (string extends T ? null | undefined : never) +>null : null + + let z = x!; // NonNullable +>z : NonNullable +>x! : NonNullable +>x : T | (string extends T ? null | undefined : never) +} + +function f2(x: T | U) { +>f2 : (x: T | U) => void +>null : null +>x : T | U + + let z = x!; // NonNullable +>z : NonNullable +>x! : NonNullable +>x : T | U +} + diff --git a/tests/cases/compiler/nonNullableReduction.ts b/tests/cases/compiler/nonNullableReduction.ts new file mode 100644 index 0000000000000..1465d0e650ae6 --- /dev/null +++ b/tests/cases/compiler/nonNullableReduction.ts @@ -0,0 +1,19 @@ +// @strict: true + +// Repros from #43425 + +type Transform1 = ((value: string) => T) | (string extends T ? undefined : never); +type Transform2 = string extends T ? ((value: string) => T) | undefined : (value: string) => T; + +function test(f1: Transform1, f2: Transform2) { + f1?.("hello"); + f2?.("hello"); +} + +function f1(x: T | (string extends T ? null | undefined : never)) { + let z = x!; // NonNullable +} + +function f2(x: T | U) { + let z = x!; // NonNullable +} From d45806a9844d7495c0cc25e70aff637cbf9b16fd Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 23 May 2021 17:03:11 -0700 Subject: [PATCH 3/4] More closely match previous behavior --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d64d1e5da3b3e..ff3ec78a931a8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -24122,7 +24122,7 @@ namespace ts { } function isGenericTypeWithUnionConstraint(type: Type) { - return !!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & TypeFlags.Union); + return !!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & (TypeFlags.Nullable | TypeFlags.Union)); } function containsGenericType(type: Type): boolean { From 634b01ab3a1e783d3302f023d432783d734953ca Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 23 May 2021 17:03:26 -0700 Subject: [PATCH 4/4] Add non-strict mode test --- .../reference/nonNullableReduction.types | 2 +- .../nonNullableReductionNonStrict.js | 32 ++++++++++ .../nonNullableReductionNonStrict.symbols | 61 +++++++++++++++++++ .../nonNullableReductionNonStrict.types | 50 +++++++++++++++ .../compiler/nonNullableReductionNonStrict.ts | 17 ++++++ 5 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/nonNullableReductionNonStrict.js create mode 100644 tests/baselines/reference/nonNullableReductionNonStrict.symbols create mode 100644 tests/baselines/reference/nonNullableReductionNonStrict.types create mode 100644 tests/cases/compiler/nonNullableReductionNonStrict.ts diff --git a/tests/baselines/reference/nonNullableReduction.types b/tests/baselines/reference/nonNullableReduction.types index 5c0863fc379d2..20a4a82179f7f 100644 --- a/tests/baselines/reference/nonNullableReduction.types +++ b/tests/baselines/reference/nonNullableReduction.types @@ -17,7 +17,7 @@ function test(f1: Transform1, f2: Transform2) { f1?.("hello"); >f1?.("hello") : T | undefined ->f1 : Transform1 +>f1 : ((value: string) => T) | undefined >"hello" : "hello" f2?.("hello"); diff --git a/tests/baselines/reference/nonNullableReductionNonStrict.js b/tests/baselines/reference/nonNullableReductionNonStrict.js new file mode 100644 index 0000000000000..29c88cdec7fc1 --- /dev/null +++ b/tests/baselines/reference/nonNullableReductionNonStrict.js @@ -0,0 +1,32 @@ +//// [nonNullableReductionNonStrict.ts] +// Repros from #43425 + +type Transform1 = ((value: string) => T) | (string extends T ? undefined : never); +type Transform2 = string extends T ? ((value: string) => T) | undefined : (value: string) => T; + +function test(f1: Transform1, f2: Transform2) { + f1?.("hello"); + f2?.("hello"); +} + +function f1(x: T | (string extends T ? null | undefined : never)) { + let z = x!; // NonNullable +} + +function f2(x: T | U) { + let z = x!; // NonNullable +} + + +//// [nonNullableReductionNonStrict.js] +// Repros from #43425 +function test(f1, f2) { + f1 === null || f1 === void 0 ? void 0 : f1("hello"); + f2 === null || f2 === void 0 ? void 0 : f2("hello"); +} +function f1(x) { + var z = x; // NonNullable +} +function f2(x) { + var z = x; // NonNullable +} diff --git a/tests/baselines/reference/nonNullableReductionNonStrict.symbols b/tests/baselines/reference/nonNullableReductionNonStrict.symbols new file mode 100644 index 0000000000000..20e0c8ee0ff80 --- /dev/null +++ b/tests/baselines/reference/nonNullableReductionNonStrict.symbols @@ -0,0 +1,61 @@ +=== tests/cases/compiler/nonNullableReductionNonStrict.ts === +// Repros from #43425 + +type Transform1 = ((value: string) => T) | (string extends T ? undefined : never); +>Transform1 : Symbol(Transform1, Decl(nonNullableReductionNonStrict.ts, 0, 0)) +>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 2, 16)) +>value : Symbol(value, Decl(nonNullableReductionNonStrict.ts, 2, 23)) +>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 2, 16)) +>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 2, 16)) + +type Transform2 = string extends T ? ((value: string) => T) | undefined : (value: string) => T; +>Transform2 : Symbol(Transform2, Decl(nonNullableReductionNonStrict.ts, 2, 85)) +>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 3, 16)) +>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 3, 16)) +>value : Symbol(value, Decl(nonNullableReductionNonStrict.ts, 3, 42)) +>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 3, 16)) +>value : Symbol(value, Decl(nonNullableReductionNonStrict.ts, 3, 78)) +>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 3, 16)) + +function test(f1: Transform1, f2: Transform2) { +>test : Symbol(test, Decl(nonNullableReductionNonStrict.ts, 3, 98)) +>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 5, 14)) +>f1 : Symbol(f1, Decl(nonNullableReductionNonStrict.ts, 5, 17)) +>Transform1 : Symbol(Transform1, Decl(nonNullableReductionNonStrict.ts, 0, 0)) +>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 5, 14)) +>f2 : Symbol(f2, Decl(nonNullableReductionNonStrict.ts, 5, 35)) +>Transform2 : Symbol(Transform2, Decl(nonNullableReductionNonStrict.ts, 2, 85)) +>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 5, 14)) + + f1?.("hello"); +>f1 : Symbol(f1, Decl(nonNullableReductionNonStrict.ts, 5, 17)) + + f2?.("hello"); +>f2 : Symbol(f2, Decl(nonNullableReductionNonStrict.ts, 5, 35)) +} + +function f1(x: T | (string extends T ? null | undefined : never)) { +>f1 : Symbol(f1, Decl(nonNullableReductionNonStrict.ts, 8, 1)) +>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 10, 12)) +>x : Symbol(x, Decl(nonNullableReductionNonStrict.ts, 10, 15)) +>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 10, 12)) +>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 10, 12)) + + let z = x!; // NonNullable +>z : Symbol(z, Decl(nonNullableReductionNonStrict.ts, 11, 7)) +>x : Symbol(x, Decl(nonNullableReductionNonStrict.ts, 10, 15)) +} + +function f2(x: T | U) { +>f2 : Symbol(f2, Decl(nonNullableReductionNonStrict.ts, 12, 1)) +>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 14, 12)) +>U : Symbol(U, Decl(nonNullableReductionNonStrict.ts, 14, 14)) +>x : Symbol(x, Decl(nonNullableReductionNonStrict.ts, 14, 43)) +>T : Symbol(T, Decl(nonNullableReductionNonStrict.ts, 14, 12)) +>U : Symbol(U, Decl(nonNullableReductionNonStrict.ts, 14, 14)) + + let z = x!; // NonNullable +>z : Symbol(z, Decl(nonNullableReductionNonStrict.ts, 15, 7)) +>x : Symbol(x, Decl(nonNullableReductionNonStrict.ts, 14, 43)) +} + diff --git a/tests/baselines/reference/nonNullableReductionNonStrict.types b/tests/baselines/reference/nonNullableReductionNonStrict.types new file mode 100644 index 0000000000000..075ee5a41fd1b --- /dev/null +++ b/tests/baselines/reference/nonNullableReductionNonStrict.types @@ -0,0 +1,50 @@ +=== tests/cases/compiler/nonNullableReductionNonStrict.ts === +// Repros from #43425 + +type Transform1 = ((value: string) => T) | (string extends T ? undefined : never); +>Transform1 : Transform1 +>value : string + +type Transform2 = string extends T ? ((value: string) => T) | undefined : (value: string) => T; +>Transform2 : Transform2 +>value : string +>value : string + +function test(f1: Transform1, f2: Transform2) { +>test : (f1: Transform1, f2: Transform2) => void +>f1 : Transform1 +>f2 : Transform2 + + f1?.("hello"); +>f1?.("hello") : T +>f1 : (value: string) => T +>"hello" : "hello" + + f2?.("hello"); +>f2?.("hello") : T +>f2 : ((value: string) => T) | ((value: string) => T) +>"hello" : "hello" +} + +function f1(x: T | (string extends T ? null | undefined : never)) { +>f1 : (x: T | (string extends T ? null | undefined : never)) => void +>x : T | (string extends T ? null : never) +>null : null + + let z = x!; // NonNullable +>z : T | (string extends T ? null : never) +>x! : T | (string extends T ? null : never) +>x : T | (string extends T ? null : never) +} + +function f2(x: T | U) { +>f2 : (x: T | U) => void +>null : null +>x : T | U + + let z = x!; // NonNullable +>z : T | U +>x! : T | U +>x : T | U +} + diff --git a/tests/cases/compiler/nonNullableReductionNonStrict.ts b/tests/cases/compiler/nonNullableReductionNonStrict.ts new file mode 100644 index 0000000000000..6212431709dad --- /dev/null +++ b/tests/cases/compiler/nonNullableReductionNonStrict.ts @@ -0,0 +1,17 @@ +// Repros from #43425 + +type Transform1 = ((value: string) => T) | (string extends T ? undefined : never); +type Transform2 = string extends T ? ((value: string) => T) | undefined : (value: string) => T; + +function test(f1: Transform1, f2: Transform2) { + f1?.("hello"); + f2?.("hello"); +} + +function f1(x: T | (string extends T ? null | undefined : never)) { + let z = x!; // NonNullable +} + +function f2(x: T | U) { + let z = x!; // NonNullable +}