Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properly remove generic types that are constrained to 'null | undefined' in getNonNullableType #44219

Merged
merged 4 commits into from May 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 9 additions & 6 deletions src/compiler/checker.ts
Expand Up @@ -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<T> 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<T> type is available, return an instantiation. Otherwise just return the reduced type.
return deferredGlobalNonNullableTypeAlias !== unknownSymbol ?
getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [reducedType]) :
reducedType;
}

function getNonNullableType(type: Type): Type {
Expand Down Expand Up @@ -24119,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 {
Expand Down
33 changes: 33 additions & 0 deletions tests/baselines/reference/nonNullableReduction.js
@@ -0,0 +1,33 @@
//// [nonNullableReduction.ts]
// Repros from #43425

type Transform1<T> = ((value: string) => T) | (string extends T ? undefined : never);
type Transform2<T> = string extends T ? ((value: string) => T) | undefined : (value: string) => T;

function test<T>(f1: Transform1<T>, f2: Transform2<T>) {
f1?.("hello");
f2?.("hello");
}

function f1<T>(x: T | (string extends T ? null | undefined : never)) {
let z = x!; // NonNullable<T>
}

function f2<T, U extends null | undefined>(x: T | U) {
let z = x!; // NonNullable<T>
}


//// [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<T>
}
function f2(x) {
var z = x; // NonNullable<T>
}
61 changes: 61 additions & 0 deletions tests/baselines/reference/nonNullableReduction.symbols
@@ -0,0 +1,61 @@
=== tests/cases/compiler/nonNullableReduction.ts ===
// Repros from #43425

type Transform1<T> = ((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<T> = 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<T>(f1: Transform1<T>, f2: Transform2<T>) {
>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<T>(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<T>
>z : Symbol(z, Decl(nonNullableReduction.ts, 11, 7))
>x : Symbol(x, Decl(nonNullableReduction.ts, 10, 15))
}

function f2<T, U extends null | undefined>(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<T>
>z : Symbol(z, Decl(nonNullableReduction.ts, 15, 7))
>x : Symbol(x, Decl(nonNullableReduction.ts, 14, 43))
}

50 changes: 50 additions & 0 deletions tests/baselines/reference/nonNullableReduction.types
@@ -0,0 +1,50 @@
=== tests/cases/compiler/nonNullableReduction.ts ===
// Repros from #43425

type Transform1<T> = ((value: string) => T) | (string extends T ? undefined : never);
>Transform1 : Transform1<T>
>value : string

type Transform2<T> = string extends T ? ((value: string) => T) | undefined : (value: string) => T;
>Transform2 : Transform2<T>
>value : string
>value : string

function test<T>(f1: Transform1<T>, f2: Transform2<T>) {
>test : <T>(f1: Transform1<T>, f2: Transform2<T>) => void
>f1 : Transform1<T>
>f2 : Transform2<T>

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<T>(x: T | (string extends T ? null | undefined : never)) {
>f1 : <T>(x: T | (string extends T ? null | undefined : never)) => void
>x : T | (string extends T ? null | undefined : never)
>null : null

let z = x!; // NonNullable<T>
>z : NonNullable<T>
>x! : NonNullable<T>
>x : T | (string extends T ? null | undefined : never)
}

function f2<T, U extends null | undefined>(x: T | U) {
>f2 : <T, U extends null | undefined>(x: T | U) => void
>null : null
>x : T | U

let z = x!; // NonNullable<T>
>z : NonNullable<T>
>x! : NonNullable<T>
>x : T | U
}

32 changes: 32 additions & 0 deletions tests/baselines/reference/nonNullableReductionNonStrict.js
@@ -0,0 +1,32 @@
//// [nonNullableReductionNonStrict.ts]
// Repros from #43425

type Transform1<T> = ((value: string) => T) | (string extends T ? undefined : never);
type Transform2<T> = string extends T ? ((value: string) => T) | undefined : (value: string) => T;

function test<T>(f1: Transform1<T>, f2: Transform2<T>) {
f1?.("hello");
f2?.("hello");
}

function f1<T>(x: T | (string extends T ? null | undefined : never)) {
let z = x!; // NonNullable<T>
}

function f2<T, U extends null | undefined>(x: T | U) {
let z = x!; // NonNullable<T>
}


//// [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<T>
}
function f2(x) {
var z = x; // NonNullable<T>
}
61 changes: 61 additions & 0 deletions tests/baselines/reference/nonNullableReductionNonStrict.symbols
@@ -0,0 +1,61 @@
=== tests/cases/compiler/nonNullableReductionNonStrict.ts ===
// Repros from #43425

type Transform1<T> = ((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<T> = 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<T>(f1: Transform1<T>, f2: Transform2<T>) {
>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<T>(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<T>
>z : Symbol(z, Decl(nonNullableReductionNonStrict.ts, 11, 7))
>x : Symbol(x, Decl(nonNullableReductionNonStrict.ts, 10, 15))
}

function f2<T, U extends null | undefined>(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<T>
>z : Symbol(z, Decl(nonNullableReductionNonStrict.ts, 15, 7))
>x : Symbol(x, Decl(nonNullableReductionNonStrict.ts, 14, 43))
}

50 changes: 50 additions & 0 deletions tests/baselines/reference/nonNullableReductionNonStrict.types
@@ -0,0 +1,50 @@
=== tests/cases/compiler/nonNullableReductionNonStrict.ts ===
// Repros from #43425

type Transform1<T> = ((value: string) => T) | (string extends T ? undefined : never);
>Transform1 : Transform1<T>
>value : string

type Transform2<T> = string extends T ? ((value: string) => T) | undefined : (value: string) => T;
>Transform2 : Transform2<T>
>value : string
>value : string

function test<T>(f1: Transform1<T>, f2: Transform2<T>) {
>test : <T>(f1: Transform1<T>, f2: Transform2<T>) => void
>f1 : Transform1<T>
>f2 : Transform2<T>

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<T>(x: T | (string extends T ? null | undefined : never)) {
>f1 : <T>(x: T | (string extends T ? null | undefined : never)) => void
>x : T | (string extends T ? null : never)
>null : null

let z = x!; // NonNullable<T>
>z : T | (string extends T ? null : never)
>x! : T | (string extends T ? null : never)
>x : T | (string extends T ? null : never)
}

function f2<T, U extends null | undefined>(x: T | U) {
>f2 : <T, U extends null>(x: T | U) => void
>null : null
>x : T | U

let z = x!; // NonNullable<T>
>z : T | U
>x! : T | U
>x : T | U
}

19 changes: 19 additions & 0 deletions tests/cases/compiler/nonNullableReduction.ts
@@ -0,0 +1,19 @@
// @strict: true

// Repros from #43425

type Transform1<T> = ((value: string) => T) | (string extends T ? undefined : never);
type Transform2<T> = string extends T ? ((value: string) => T) | undefined : (value: string) => T;

function test<T>(f1: Transform1<T>, f2: Transform2<T>) {
f1?.("hello");
f2?.("hello");
}

function f1<T>(x: T | (string extends T ? null | undefined : never)) {
let z = x!; // NonNullable<T>
}

function f2<T, U extends null | undefined>(x: T | U) {
let z = x!; // NonNullable<T>
}
17 changes: 17 additions & 0 deletions tests/cases/compiler/nonNullableReductionNonStrict.ts
@@ -0,0 +1,17 @@
// Repros from #43425

type Transform1<T> = ((value: string) => T) | (string extends T ? undefined : never);
type Transform2<T> = string extends T ? ((value: string) => T) | undefined : (value: string) => T;

function test<T>(f1: Transform1<T>, f2: Transform2<T>) {
f1?.("hello");
f2?.("hello");
}

function f1<T>(x: T | (string extends T ? null | undefined : never)) {
let z = x!; // NonNullable<T>
}

function f2<T, U extends null | undefined>(x: T | U) {
let z = x!; // NonNullable<T>
}