Skip to content

Properly handle co- and contra-variant inferences in strict mode #26566

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

Merged
merged 5 commits into from
Aug 21, 2018
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
9 changes: 5 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13638,10 +13638,11 @@ namespace ts {
if (!inferredType) {
const signature = context.signature;
if (signature) {
if (inference.contraCandidates) {
// If we have contravariant inferences we find the best common subtype and treat
// that as a single covariant candidate.
inference.candidates = append(inference.candidates, getContravariantInference(inference));
if (inference.contraCandidates && (!inference.candidates || inference.candidates.length === 1 && inference.candidates[0].flags & TypeFlags.Never)) {
// If we have contravariant inferences, but no covariant inferences or a single
// covariant inference of 'never', we find the best common subtype and treat that
// as a single covariant candidate.
inference.candidates = [getContravariantInference(inference)];
inference.contraCandidates = undefined;
}
if (inference.candidates) {
Expand Down
53 changes: 50 additions & 3 deletions tests/baselines/reference/strictFunctionTypes1.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,29 @@ const x11 = f3(never, fo, fx); // "def"

declare function foo<T>(a: ReadonlyArray<T>): T;
let x = foo([]); // never

// Modified repros from #26127

interface A { a: string }
interface B extends A { b: string }

declare function acceptUnion(x: A | number): void;
declare function acceptA(x: A): void;

declare let a: A;
declare let b: B;

declare function coAndContra<T>(value: T, func: (t: T) => void): T;

const t1: A = coAndContra(a, acceptUnion);
const t2: B = coAndContra(b, acceptA);
const t3: A = coAndContra(never, acceptA);

declare function coAndContraArray<T>(value: T[], func: (t: T) => void): T[];

const t4: A[] = coAndContraArray([a], acceptUnion);
const t5: B[] = coAndContraArray([b], acceptA);
const t6: A[] = coAndContraArray([], acceptA);


//// [strictFunctionTypes1.js]
Expand All @@ -36,6 +59,12 @@ var x4 = f4(fo, fs); // Func<string>
var x10 = f2(never, fo, fs); // string
var x11 = f3(never, fo, fx); // "def"
var x = foo([]); // never
var t1 = coAndContra(a, acceptUnion);
var t2 = coAndContra(b, acceptA);
var t3 = coAndContra(never, acceptA);
var t4 = coAndContraArray([a], acceptUnion);
var t5 = coAndContraArray([b], acceptA);
var t6 = coAndContraArray([], acceptA);


//// [strictFunctionTypes1.d.ts]
Expand All @@ -50,11 +79,29 @@ declare function fo(x: Object): void;
declare function fs(x: string): void;
declare function fx(f: (x: "def") => void): void;
declare const x1: (x: string) => void;
declare const x2: string;
declare const x3: Object;
declare const x2 = "abc";
declare const x3: string;
declare const x4: Func<string>;
declare const never: never;
declare const x10: string;
declare const x11: Object;
declare const x11: "def";
declare function foo<T>(a: ReadonlyArray<T>): T;
declare let x: never;
interface A {
a: string;
}
interface B extends A {
b: string;
}
declare function acceptUnion(x: A | number): void;
declare function acceptA(x: A): void;
declare let a: A;
declare let b: B;
declare function coAndContra<T>(value: T, func: (t: T) => void): T;
declare const t1: A;
declare const t2: B;
declare const t3: A;
declare function coAndContraArray<T>(value: T[], func: (t: T) => void): T[];
declare const t4: A[];
declare const t5: B[];
declare const t6: A[];
90 changes: 90 additions & 0 deletions tests/baselines/reference/strictFunctionTypes1.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,93 @@ let x = foo([]); // never
>x : Symbol(x, Decl(strictFunctionTypes1.ts, 25, 3))
>foo : Symbol(foo, Decl(strictFunctionTypes1.ts, 20, 30))

// Modified repros from #26127

interface A { a: string }
>A : Symbol(A, Decl(strictFunctionTypes1.ts, 25, 16))
>a : Symbol(A.a, Decl(strictFunctionTypes1.ts, 29, 13))

interface B extends A { b: string }
>B : Symbol(B, Decl(strictFunctionTypes1.ts, 29, 25))
>A : Symbol(A, Decl(strictFunctionTypes1.ts, 25, 16))
>b : Symbol(B.b, Decl(strictFunctionTypes1.ts, 30, 23))

declare function acceptUnion(x: A | number): void;
>acceptUnion : Symbol(acceptUnion, Decl(strictFunctionTypes1.ts, 30, 35))
>x : Symbol(x, Decl(strictFunctionTypes1.ts, 32, 29))
>A : Symbol(A, Decl(strictFunctionTypes1.ts, 25, 16))

declare function acceptA(x: A): void;
>acceptA : Symbol(acceptA, Decl(strictFunctionTypes1.ts, 32, 50))
>x : Symbol(x, Decl(strictFunctionTypes1.ts, 33, 25))
>A : Symbol(A, Decl(strictFunctionTypes1.ts, 25, 16))

declare let a: A;
>a : Symbol(a, Decl(strictFunctionTypes1.ts, 35, 11))
>A : Symbol(A, Decl(strictFunctionTypes1.ts, 25, 16))

declare let b: B;
>b : Symbol(b, Decl(strictFunctionTypes1.ts, 36, 11))
>B : Symbol(B, Decl(strictFunctionTypes1.ts, 29, 25))

declare function coAndContra<T>(value: T, func: (t: T) => void): T;
>coAndContra : Symbol(coAndContra, Decl(strictFunctionTypes1.ts, 36, 17))
>T : Symbol(T, Decl(strictFunctionTypes1.ts, 38, 29))
>value : Symbol(value, Decl(strictFunctionTypes1.ts, 38, 32))
>T : Symbol(T, Decl(strictFunctionTypes1.ts, 38, 29))
>func : Symbol(func, Decl(strictFunctionTypes1.ts, 38, 41))
>t : Symbol(t, Decl(strictFunctionTypes1.ts, 38, 49))
>T : Symbol(T, Decl(strictFunctionTypes1.ts, 38, 29))
>T : Symbol(T, Decl(strictFunctionTypes1.ts, 38, 29))

const t1: A = coAndContra(a, acceptUnion);
>t1 : Symbol(t1, Decl(strictFunctionTypes1.ts, 40, 5))
>A : Symbol(A, Decl(strictFunctionTypes1.ts, 25, 16))
>coAndContra : Symbol(coAndContra, Decl(strictFunctionTypes1.ts, 36, 17))
>a : Symbol(a, Decl(strictFunctionTypes1.ts, 35, 11))
>acceptUnion : Symbol(acceptUnion, Decl(strictFunctionTypes1.ts, 30, 35))

const t2: B = coAndContra(b, acceptA);
>t2 : Symbol(t2, Decl(strictFunctionTypes1.ts, 41, 5))
>B : Symbol(B, Decl(strictFunctionTypes1.ts, 29, 25))
>coAndContra : Symbol(coAndContra, Decl(strictFunctionTypes1.ts, 36, 17))
>b : Symbol(b, Decl(strictFunctionTypes1.ts, 36, 11))
>acceptA : Symbol(acceptA, Decl(strictFunctionTypes1.ts, 32, 50))

const t3: A = coAndContra(never, acceptA);
>t3 : Symbol(t3, Decl(strictFunctionTypes1.ts, 42, 5))
>A : Symbol(A, Decl(strictFunctionTypes1.ts, 25, 16))
>coAndContra : Symbol(coAndContra, Decl(strictFunctionTypes1.ts, 36, 17))
>never : Symbol(never, Decl(strictFunctionTypes1.ts, 17, 13))
>acceptA : Symbol(acceptA, Decl(strictFunctionTypes1.ts, 32, 50))

declare function coAndContraArray<T>(value: T[], func: (t: T) => void): T[];
>coAndContraArray : Symbol(coAndContraArray, Decl(strictFunctionTypes1.ts, 42, 42))
>T : Symbol(T, Decl(strictFunctionTypes1.ts, 44, 34))
>value : Symbol(value, Decl(strictFunctionTypes1.ts, 44, 37))
>T : Symbol(T, Decl(strictFunctionTypes1.ts, 44, 34))
>func : Symbol(func, Decl(strictFunctionTypes1.ts, 44, 48))
>t : Symbol(t, Decl(strictFunctionTypes1.ts, 44, 56))
>T : Symbol(T, Decl(strictFunctionTypes1.ts, 44, 34))
>T : Symbol(T, Decl(strictFunctionTypes1.ts, 44, 34))

const t4: A[] = coAndContraArray([a], acceptUnion);
>t4 : Symbol(t4, Decl(strictFunctionTypes1.ts, 46, 5))
>A : Symbol(A, Decl(strictFunctionTypes1.ts, 25, 16))
>coAndContraArray : Symbol(coAndContraArray, Decl(strictFunctionTypes1.ts, 42, 42))
>a : Symbol(a, Decl(strictFunctionTypes1.ts, 35, 11))
>acceptUnion : Symbol(acceptUnion, Decl(strictFunctionTypes1.ts, 30, 35))

const t5: B[] = coAndContraArray([b], acceptA);
>t5 : Symbol(t5, Decl(strictFunctionTypes1.ts, 47, 5))
>B : Symbol(B, Decl(strictFunctionTypes1.ts, 29, 25))
>coAndContraArray : Symbol(coAndContraArray, Decl(strictFunctionTypes1.ts, 42, 42))
>b : Symbol(b, Decl(strictFunctionTypes1.ts, 36, 11))
>acceptA : Symbol(acceptA, Decl(strictFunctionTypes1.ts, 32, 50))

const t6: A[] = coAndContraArray([], acceptA);
>t6 : Symbol(t6, Decl(strictFunctionTypes1.ts, 48, 5))
>A : Symbol(A, Decl(strictFunctionTypes1.ts, 25, 16))
>coAndContraArray : Symbol(coAndContraArray, Decl(strictFunctionTypes1.ts, 42, 42))
>acceptA : Symbol(acceptA, Decl(strictFunctionTypes1.ts, 32, 50))

90 changes: 84 additions & 6 deletions tests/baselines/reference/strictFunctionTypes1.types
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,16 @@ const x1 = f1(fo, fs); // (x: string) => void
>fs : (x: string) => void

const x2 = f2("abc", fo, fs); // "abc"
>x2 : string
>f2("abc", fo, fs) : string
>x2 : "abc"
>f2("abc", fo, fs) : "abc"
>f2 : <T>(obj: T, f1: (x: T) => void, f2: (x: T) => void) => T
>"abc" : "abc"
>fo : (x: Object) => void
>fs : (x: string) => void

const x3 = f3("abc", fo, fx); // "abc" | "def"
>x3 : Object
>f3("abc", fo, fx) : Object
>x3 : "def" | "abc"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should discuss this later, but this inference seems bad

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this inference is correct. The fx argument is a function taking a function, and since contra-contra-variant is the same as co-variant, we make two co-variant inferences, "abc" and "def". Per normal rules we union together literal inferences for the same type parameter, so we end up with "abc" | "def".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed the double 180; you're right

>f3("abc", fo, fx) : "def" | "abc"
>f3 : <T>(obj: T, f1: (x: T) => void, f2: (f: (x: T) => void) => void) => T
>"abc" : "abc"
>fo : (x: Object) => void
Expand All @@ -87,8 +87,8 @@ const x10 = f2(never, fo, fs); // string
>fs : (x: string) => void

const x11 = f3(never, fo, fx); // "def"
>x11 : Object
>f3(never, fo, fx) : Object
>x11 : "def"
>f3(never, fo, fx) : "def"
>f3 : <T>(obj: T, f1: (x: T) => void, f2: (f: (x: T) => void) => void) => T
>never : never
>fo : (x: Object) => void
Expand All @@ -106,3 +106,81 @@ let x = foo([]); // never
>foo : <T>(a: ReadonlyArray<T>) => T
>[] : never[]

// Modified repros from #26127

interface A { a: string }
>a : string

interface B extends A { b: string }
>b : string

declare function acceptUnion(x: A | number): void;
>acceptUnion : (x: number | A) => void
>x : number | A

declare function acceptA(x: A): void;
>acceptA : (x: A) => void
>x : A

declare let a: A;
>a : A

declare let b: B;
>b : B

declare function coAndContra<T>(value: T, func: (t: T) => void): T;
>coAndContra : <T>(value: T, func: (t: T) => void) => T
>value : T
>func : (t: T) => void
>t : T

const t1: A = coAndContra(a, acceptUnion);
>t1 : A
>coAndContra(a, acceptUnion) : A
>coAndContra : <T>(value: T, func: (t: T) => void) => T
>a : A
>acceptUnion : (x: number | A) => void

const t2: B = coAndContra(b, acceptA);
>t2 : B
>coAndContra(b, acceptA) : B
>coAndContra : <T>(value: T, func: (t: T) => void) => T
>b : B
>acceptA : (x: A) => void

const t3: A = coAndContra(never, acceptA);
>t3 : A
>coAndContra(never, acceptA) : A
>coAndContra : <T>(value: T, func: (t: T) => void) => T
>never : never
>acceptA : (x: A) => void

declare function coAndContraArray<T>(value: T[], func: (t: T) => void): T[];
>coAndContraArray : <T>(value: T[], func: (t: T) => void) => T[]
>value : T[]
>func : (t: T) => void
>t : T

const t4: A[] = coAndContraArray([a], acceptUnion);
>t4 : A[]
>coAndContraArray([a], acceptUnion) : A[]
>coAndContraArray : <T>(value: T[], func: (t: T) => void) => T[]
>[a] : A[]
>a : A
>acceptUnion : (x: number | A) => void

const t5: B[] = coAndContraArray([b], acceptA);
>t5 : B[]
>coAndContraArray([b], acceptA) : B[]
>coAndContraArray : <T>(value: T[], func: (t: T) => void) => T[]
>[b] : B[]
>b : B
>acceptA : (x: A) => void

const t6: A[] = coAndContraArray([], acceptA);
>t6 : A[]
>coAndContraArray([], acceptA) : A[]
>coAndContraArray : <T>(value: T[], func: (t: T) => void) => T[]
>[] : never[]
>acceptA : (x: A) => void

23 changes: 23 additions & 0 deletions tests/cases/compiler/strictFunctionTypes1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,26 @@ const x11 = f3(never, fo, fx); // "def"

declare function foo<T>(a: ReadonlyArray<T>): T;
let x = foo([]); // never

// Modified repros from #26127

interface A { a: string }
interface B extends A { b: string }

declare function acceptUnion(x: A | number): void;
declare function acceptA(x: A): void;

declare let a: A;
declare let b: B;

declare function coAndContra<T>(value: T, func: (t: T) => void): T;

const t1: A = coAndContra(a, acceptUnion);
const t2: B = coAndContra(b, acceptA);
const t3: A = coAndContra(never, acceptA);

declare function coAndContraArray<T>(value: T[], func: (t: T) => void): T[];

const t4: A[] = coAndContraArray([a], acceptUnion);
const t5: B[] = coAndContraArray([b], acceptA);
const t6: A[] = coAndContraArray([], acceptA);