diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f9cd044a79331..eb8397a18b019 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20239,14 +20239,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 { @@ -24035,7 +24038,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 { 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..20a4a82179f7f --- /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 : ((value: string) => T) | undefined +>"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/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/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 +} 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 +}