Skip to content

Commit

Permalink
Improve narrowing of generic types in control flow analysis (#43183)
Browse files Browse the repository at this point in the history
* Narrow type variables with union constraints when merited by contextual type

* Narrow generics with union type constraints as indicated by contextual type

* Accept new baselines

* Add tests

* Fix circularity for JSX elements

* Remove unnecessary isConstraintPosition information from flow cache key

* Update comment

* Add additional tests

* Rename to getNarrowableTypeForReference, remove getConstraintForLocation

* Add comment

* Fix removal of undefined in destructurings with initializers

* Use getContextFreeTypeOfExpression in discriminateContextualTypeByObjectMembers

* In obj[x], use constraint of obj's type only when x's type is non-generic

* Add comment
  • Loading branch information
ahejlsberg authored Mar 20, 2021
1 parent 8a5939f commit 15fae38
Show file tree
Hide file tree
Showing 30 changed files with 1,400 additions and 158 deletions.
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

0 comments on commit 15fae38

Please sign in to comment.