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 all 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
153 changes: 90 additions & 63 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
152 changes: 152 additions & 0 deletions tests/baselines/reference/controlFlowGenericTypes.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(49,15): error TS2345: Argument of type 'undefined' is not assignable to parameter of type 'Box<T>'.
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(55,15): error TS2345: Argument of type 'undefined' is not assignable to parameter of type 'Box<unknown>'.
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(81,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(90,44): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(91,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 (5 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";
}

interface Box<T> {
item: T;
}

declare function isBox(x: any): x is Box<unknown>;
declare function isUndefined(x: unknown): x is undefined;
declare function unbox<T>(x: Box<T>): T;

function g1<T extends Box<T> | undefined>(x: T) {
if (isBox(x)) {
unbox(x);
}
}

function g2<T extends Box<T> | undefined>(x: T) {
if (!isUndefined(x)) {
unbox(x);
}
}

function g3<T extends Box<T> | undefined>(x: T) {
if (!isBox(x)) {
unbox(x); // Error
~
!!! error TS2345: Argument of type 'undefined' is not assignable to parameter of type 'Box<T>'.
}
}

function g4<T extends Box<T> | undefined>(x: T) {
if (isUndefined(x)) {
unbox(x); // Error
~
!!! error TS2345: Argument of type 'undefined' is not assignable to parameter of type 'Box<unknown>'.
}
}

// 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;
};

Loading