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

Improve narrowing of generic types in control flow analysis #43183

Merged
merged 15 commits into from
Mar 20, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 67 additions & 32 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function bar<T extends A | B>(x: T) {

var y: A | B = x; // Ok
>y : A | B
>x : T
>x : A | B
}

bar(new A);
Expand Down
16 changes: 1 addition & 15 deletions tests/baselines/reference/conditionalTypes1.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,11 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(12,5): error TS23
tests/cases/conformance/types/conditional/conditionalTypes1.ts(17,5): error TS2322: Type 'T' is not assignable to type 'NonNullable<T>'.
Type 'string | undefined' is not assignable to type 'NonNullable<T>'.
Type 'undefined' is not assignable to type 'NonNullable<T>'.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(18,9): error TS2322: Type 'T' is not assignable to type 'string'.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(24,5): error TS2322: Type 'T[keyof T] | undefined' is not assignable to type 'NonNullable<Partial<T>[keyof T]>'.
Type 'undefined' is not assignable to type 'NonNullable<Partial<T>[keyof T]>'.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(29,5): error TS2322: Type 'T["x"]' is not assignable to type 'NonNullable<T["x"]>'.
Type 'string | undefined' is not assignable to type 'NonNullable<T["x"]>'.
Type 'undefined' is not assignable to type 'NonNullable<T["x"]>'.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(30,9): error TS2322: Type 'T["x"]' is not assignable to type 'string'.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(103,5): error TS2322: Type 'FunctionProperties<T>' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to 'FunctionProperties<T>'.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(104,5): error TS2322: Type 'NonFunctionProperties<T>' is not assignable to type 'T'.
Expand Down Expand Up @@ -63,7 +57,7 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(288,43): error TS
Type 'boolean' is not assignable to type 'true'.


==== tests/cases/conformance/types/conditional/conditionalTypes1.ts (22 errors) ====
==== tests/cases/conformance/types/conditional/conditionalTypes1.ts (20 errors) ====
type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"
type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c"

Expand All @@ -88,10 +82,6 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(288,43): error TS
!!! error TS2322: Type 'string | undefined' is not assignable to type 'NonNullable<T>'.
!!! error TS2322: Type 'undefined' is not assignable to type 'NonNullable<T>'.
let s1: string = x; // Error
~~
!!! error TS2322: Type 'T' is not assignable to type 'string'.
!!! error TS2322: Type 'string | undefined' is not assignable to type 'string'.
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
let s2: string = y;
}

Expand All @@ -111,10 +101,6 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(288,43): error TS
!!! error TS2322: Type 'string | undefined' is not assignable to type 'NonNullable<T["x"]>'.
!!! error TS2322: Type 'undefined' is not assignable to type 'NonNullable<T["x"]>'.
let s1: string = x; // Error
~~
!!! error TS2322: Type 'T["x"]' is not assignable to type 'string'.
!!! error TS2322: Type 'string | undefined' is not assignable to type 'string'.
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
let s2: string = y;
}

Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/conditionalTypes1.types
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function f2<T extends string | undefined>(x: T, y: NonNullable<T>) {

let s1: string = x; // Error
>s1 : string
>x : T
>x : string

let s2: string = y;
>s2 : string
Expand Down Expand Up @@ -92,7 +92,7 @@ function f4<T extends { x: string | undefined }>(x: T["x"], y: NonNullable<T["x"

let s1: string = x; // Error
>s1 : string
>x : T["x"]
>x : string

let s2: string = y;
>s2 : string
Expand Down Expand Up @@ -476,11 +476,11 @@ function f21<T extends number | string>(x: T, y: ZeroOf<T>) {

let z1: number | string = y;
>z1 : string | number
>y : ZeroOf<T>
>y : "" | 0

let z2: 0 | "" = y;
>z2 : "" | 0
>y : ZeroOf<T>
>y : "" | 0

x = y; // Error
>x = y : ZeroOf<T>
Expand Down
114 changes: 114 additions & 0 deletions tests/baselines/reference/controlFlowGenericTypes.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(49,11): error TS2339: Property 'foo' does not exist on type 'MyUnion'.
Property 'foo' does not exist on type 'AA'.
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(58,44): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(59,11): error TS2339: Property 'foo' does not exist on type 'MyUnion'.
Property 'foo' does not exist on type 'AA'.


==== tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts (3 errors) ====
function f1<T extends string | undefined>(x: T, y: { a: T }, z: [T]): string {
if (x) {
x;
x.length;
return x;
}
if (y.a) {
y.a.length;
return y.a;
}
if (z[0]) {
z[0].length;
return z[0];
}
return "hello";
}

function f2<T>(x: Extract<T, string | undefined> | null): string {
if (x) {
x;
x.length;
return x;
}
return "hello";
}

// Repro from #13995

declare function takeA(val: 'A'): void;
export function bounceAndTakeIfA<AB extends 'A' | 'B'>(value: AB): AB {
if (value === 'A') {
takeA(value);
return value;
}
else {
return value;
}
}

// Repro from #13995

type Common = { id: number };
type AA = { tag: 'A', id: number };
type BB = { tag: 'B', id: number, foo: number };

type MyUnion = AA | BB;

const fn = (value: MyUnion) => {
value.foo; // Error
~~~
!!! error TS2339: Property 'foo' does not exist on type 'MyUnion'.
!!! error TS2339: Property 'foo' does not exist on type 'AA'.
if ('foo' in value) {
value.foo;
}
if (value.tag === 'B') {
value.foo;
}
};

const fn2 = <T extends MyUnion>(value: T): MyUnion => {
~~~~~~~
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
value.foo; // Error
~~~
!!! error TS2339: Property 'foo' does not exist on type 'MyUnion'.
!!! error TS2339: Property 'foo' does not exist on type 'AA'.
if ('foo' in value) {
value.foo;
}
if (value.tag === 'B') {
value.foo;
}
};

// Repro from #13995

type A1 = {
testable: true
doTest: () => void
}
type B1 = {
testable: false
};

type Union = A1 | B1

function notWorking<T extends Union>(object: T) {
if (!object.testable) return;
object.doTest();
}

// Repro from #42939

interface A {
a: number | null;
};

function get<K extends keyof A>(key: K, obj: A): number {
const value = obj[key];
if (value !== null) {
return value;
}
return 0;
};

170 changes: 170 additions & 0 deletions tests/baselines/reference/controlFlowGenericTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
//// [controlFlowGenericTypes.ts]
function f1<T extends string | undefined>(x: T, y: { a: T }, z: [T]): string {
if (x) {
x;
x.length;
return x;
}
if (y.a) {
y.a.length;
return y.a;
}
if (z[0]) {
z[0].length;
return z[0];
}
return "hello";
}

function f2<T>(x: Extract<T, string | undefined> | null): string {
if (x) {
x;
x.length;
return x;
}
return "hello";
}

// Repro from #13995

declare function takeA(val: 'A'): void;
export function bounceAndTakeIfA<AB extends 'A' | 'B'>(value: AB): AB {
if (value === 'A') {
takeA(value);
return value;
}
else {
return value;
}
}

// Repro from #13995

type Common = { id: number };
type AA = { tag: 'A', id: number };
type BB = { tag: 'B', id: number, foo: number };

type MyUnion = AA | BB;

const fn = (value: MyUnion) => {
value.foo; // Error
if ('foo' in value) {
value.foo;
}
if (value.tag === 'B') {
value.foo;
}
};

const fn2 = <T extends MyUnion>(value: T): MyUnion => {
value.foo; // Error
if ('foo' in value) {
value.foo;
}
if (value.tag === 'B') {
value.foo;
}
};

// Repro from #13995

type A1 = {
testable: true
doTest: () => void
}
type B1 = {
testable: false
};

type Union = A1 | B1

function notWorking<T extends Union>(object: T) {
if (!object.testable) return;
object.doTest();
}

// Repro from #42939

interface A {
a: number | null;
};

function get<K extends keyof A>(key: K, obj: A): number {
const value = obj[key];
if (value !== null) {
return value;
}
return 0;
};


//// [controlFlowGenericTypes.js]
"use strict";
exports.__esModule = true;
exports.bounceAndTakeIfA = void 0;
function f1(x, y, z) {
if (x) {
x;
x.length;
return x;
}
if (y.a) {
y.a.length;
return y.a;
}
if (z[0]) {
z[0].length;
return z[0];
}
return "hello";
}
function f2(x) {
if (x) {
x;
x.length;
return x;
}
return "hello";
}
function bounceAndTakeIfA(value) {
if (value === 'A') {
takeA(value);
return value;
}
else {
return value;
}
}
exports.bounceAndTakeIfA = bounceAndTakeIfA;
var fn = function (value) {
value.foo; // Error
if ('foo' in value) {
value.foo;
}
if (value.tag === 'B') {
value.foo;
}
};
var fn2 = function (value) {
value.foo; // Error
if ('foo' in value) {
value.foo;
}
if (value.tag === 'B') {
value.foo;
}
};
function notWorking(object) {
if (!object.testable)
return;
object.doTest();
}
;
function get(key, obj) {
var value = obj[key];
if (value !== null) {
return value;
}
return 0;
}
;
Loading