Skip to content

Improve logic for choosing between co- and contra-variant inferences #46392

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 2 commits into from
Oct 19, 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
7 changes: 3 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22329,12 +22329,11 @@ namespace ts {
if (signature) {
const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined;
if (inference.contraCandidates) {
const inferredContravariantType = getContravariantInference(inference);
// If we have both co- and contra-variant inferences, we prefer the contra-variant inference
// unless the co-variant inference is a subtype and not 'never'.
// unless the co-variant inference is a subtype of some contra-variant inference and not 'never'.
inferredType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) &&
isTypeSubtypeOf(inferredCovariantType, inferredContravariantType) ?
inferredCovariantType : inferredContravariantType;
some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t)) ?
inferredCovariantType : getContravariantInference(inference);
}
else if (inferredCovariantType) {
inferredType = inferredCovariantType;
Expand Down
74 changes: 74 additions & 0 deletions tests/baselines/reference/coAndContraVariantInferences.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//// [coAndContraVariantInferences.ts]
type A = { kind: 'a' };
type B = { kind: 'b' };

declare const a: A;
declare const b: B;

declare function fab(arg: A | B): void;

declare function foo<T>(x: { kind: T }, f: (arg: { kind: T }) => void): void;

foo(a, fab);
foo(b, fab);

// Repro from #45603

interface Action<TName extends string,TPayload> {
name: TName,
payload: TPayload
}

const actionA = { payload: 'any-string' } as Action<'ACTION_A', string>;
const actionB = { payload: true } as Action<'ACTION_B', boolean>;

function call<TName extends string,TPayload>(
action: Action<TName,TPayload>,
fn: (action: Action<TName,TPayload>)=> any,
) {
fn(action);
}

const printFn = (action: typeof actionA | typeof actionB)=> console.log(action);

call(actionA, printFn);
call(actionB, printFn);


//// [coAndContraVariantInferences.js]
"use strict";
foo(a, fab);
foo(b, fab);
var actionA = { payload: 'any-string' };
var actionB = { payload: true };
function call(action, fn) {
fn(action);
}
var printFn = function (action) { return console.log(action); };
call(actionA, printFn);
call(actionB, printFn);


//// [coAndContraVariantInferences.d.ts]
declare type A = {
kind: 'a';
};
declare type B = {
kind: 'b';
};
declare const a: A;
declare const b: B;
declare function fab(arg: A | B): void;
declare function foo<T>(x: {
kind: T;
}, f: (arg: {
kind: T;
}) => void): void;
interface Action<TName extends string, TPayload> {
name: TName;
payload: TPayload;
}
declare const actionA: Action<"ACTION_A", string>;
declare const actionB: Action<"ACTION_B", boolean>;
declare function call<TName extends string, TPayload>(action: Action<TName, TPayload>, fn: (action: Action<TName, TPayload>) => any): void;
declare const printFn: (action: typeof actionA | typeof actionB) => void;
114 changes: 114 additions & 0 deletions tests/baselines/reference/coAndContraVariantInferences.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
=== tests/cases/compiler/coAndContraVariantInferences.ts ===
type A = { kind: 'a' };
>A : Symbol(A, Decl(coAndContraVariantInferences.ts, 0, 0))
>kind : Symbol(kind, Decl(coAndContraVariantInferences.ts, 0, 10))

type B = { kind: 'b' };
>B : Symbol(B, Decl(coAndContraVariantInferences.ts, 0, 23))
>kind : Symbol(kind, Decl(coAndContraVariantInferences.ts, 1, 10))

declare const a: A;
>a : Symbol(a, Decl(coAndContraVariantInferences.ts, 3, 13))
>A : Symbol(A, Decl(coAndContraVariantInferences.ts, 0, 0))

declare const b: B;
>b : Symbol(b, Decl(coAndContraVariantInferences.ts, 4, 13))
>B : Symbol(B, Decl(coAndContraVariantInferences.ts, 0, 23))

declare function fab(arg: A | B): void;
>fab : Symbol(fab, Decl(coAndContraVariantInferences.ts, 4, 19))
>arg : Symbol(arg, Decl(coAndContraVariantInferences.ts, 6, 21))
>A : Symbol(A, Decl(coAndContraVariantInferences.ts, 0, 0))
>B : Symbol(B, Decl(coAndContraVariantInferences.ts, 0, 23))

declare function foo<T>(x: { kind: T }, f: (arg: { kind: T }) => void): void;
>foo : Symbol(foo, Decl(coAndContraVariantInferences.ts, 6, 39))
>T : Symbol(T, Decl(coAndContraVariantInferences.ts, 8, 21))
>x : Symbol(x, Decl(coAndContraVariantInferences.ts, 8, 24))
>kind : Symbol(kind, Decl(coAndContraVariantInferences.ts, 8, 28))
>T : Symbol(T, Decl(coAndContraVariantInferences.ts, 8, 21))
>f : Symbol(f, Decl(coAndContraVariantInferences.ts, 8, 39))
>arg : Symbol(arg, Decl(coAndContraVariantInferences.ts, 8, 44))
>kind : Symbol(kind, Decl(coAndContraVariantInferences.ts, 8, 50))
>T : Symbol(T, Decl(coAndContraVariantInferences.ts, 8, 21))

foo(a, fab);
>foo : Symbol(foo, Decl(coAndContraVariantInferences.ts, 6, 39))
>a : Symbol(a, Decl(coAndContraVariantInferences.ts, 3, 13))
>fab : Symbol(fab, Decl(coAndContraVariantInferences.ts, 4, 19))

foo(b, fab);
>foo : Symbol(foo, Decl(coAndContraVariantInferences.ts, 6, 39))
>b : Symbol(b, Decl(coAndContraVariantInferences.ts, 4, 13))
>fab : Symbol(fab, Decl(coAndContraVariantInferences.ts, 4, 19))

// Repro from #45603

interface Action<TName extends string,TPayload> {
>Action : Symbol(Action, Decl(coAndContraVariantInferences.ts, 11, 12))
>TName : Symbol(TName, Decl(coAndContraVariantInferences.ts, 15, 17))
>TPayload : Symbol(TPayload, Decl(coAndContraVariantInferences.ts, 15, 38))

name: TName,
>name : Symbol(Action.name, Decl(coAndContraVariantInferences.ts, 15, 49))
>TName : Symbol(TName, Decl(coAndContraVariantInferences.ts, 15, 17))

payload: TPayload
>payload : Symbol(Action.payload, Decl(coAndContraVariantInferences.ts, 16, 16))
>TPayload : Symbol(TPayload, Decl(coAndContraVariantInferences.ts, 15, 38))
}

const actionA = { payload: 'any-string' } as Action<'ACTION_A', string>;
>actionA : Symbol(actionA, Decl(coAndContraVariantInferences.ts, 20, 5))
>payload : Symbol(payload, Decl(coAndContraVariantInferences.ts, 20, 17))
>Action : Symbol(Action, Decl(coAndContraVariantInferences.ts, 11, 12))

const actionB = { payload: true } as Action<'ACTION_B', boolean>;
>actionB : Symbol(actionB, Decl(coAndContraVariantInferences.ts, 21, 5))
>payload : Symbol(payload, Decl(coAndContraVariantInferences.ts, 21, 17))
>Action : Symbol(Action, Decl(coAndContraVariantInferences.ts, 11, 12))

function call<TName extends string,TPayload>(
>call : Symbol(call, Decl(coAndContraVariantInferences.ts, 21, 65))
>TName : Symbol(TName, Decl(coAndContraVariantInferences.ts, 23, 14))
>TPayload : Symbol(TPayload, Decl(coAndContraVariantInferences.ts, 23, 35))

action: Action<TName,TPayload>,
>action : Symbol(action, Decl(coAndContraVariantInferences.ts, 23, 45))
>Action : Symbol(Action, Decl(coAndContraVariantInferences.ts, 11, 12))
>TName : Symbol(TName, Decl(coAndContraVariantInferences.ts, 23, 14))
>TPayload : Symbol(TPayload, Decl(coAndContraVariantInferences.ts, 23, 35))

fn: (action: Action<TName,TPayload>)=> any,
>fn : Symbol(fn, Decl(coAndContraVariantInferences.ts, 24, 33))
>action : Symbol(action, Decl(coAndContraVariantInferences.ts, 25, 7))
>Action : Symbol(Action, Decl(coAndContraVariantInferences.ts, 11, 12))
>TName : Symbol(TName, Decl(coAndContraVariantInferences.ts, 23, 14))
>TPayload : Symbol(TPayload, Decl(coAndContraVariantInferences.ts, 23, 35))

) {
fn(action);
>fn : Symbol(fn, Decl(coAndContraVariantInferences.ts, 24, 33))
>action : Symbol(action, Decl(coAndContraVariantInferences.ts, 23, 45))
}

const printFn = (action: typeof actionA | typeof actionB)=> console.log(action);
>printFn : Symbol(printFn, Decl(coAndContraVariantInferences.ts, 30, 5))
>action : Symbol(action, Decl(coAndContraVariantInferences.ts, 30, 17))
>actionA : Symbol(actionA, Decl(coAndContraVariantInferences.ts, 20, 5))
>actionB : Symbol(actionB, Decl(coAndContraVariantInferences.ts, 21, 5))
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>action : Symbol(action, Decl(coAndContraVariantInferences.ts, 30, 17))

call(actionA, printFn);
>call : Symbol(call, Decl(coAndContraVariantInferences.ts, 21, 65))
>actionA : Symbol(actionA, Decl(coAndContraVariantInferences.ts, 20, 5))
>printFn : Symbol(printFn, Decl(coAndContraVariantInferences.ts, 30, 5))

call(actionB, printFn);
>call : Symbol(call, Decl(coAndContraVariantInferences.ts, 21, 65))
>actionB : Symbol(actionB, Decl(coAndContraVariantInferences.ts, 21, 5))
>printFn : Symbol(printFn, Decl(coAndContraVariantInferences.ts, 30, 5))

104 changes: 104 additions & 0 deletions tests/baselines/reference/coAndContraVariantInferences.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
=== tests/cases/compiler/coAndContraVariantInferences.ts ===
type A = { kind: 'a' };
>A : A
>kind : "a"

type B = { kind: 'b' };
>B : B
>kind : "b"

declare const a: A;
>a : A

declare const b: B;
>b : B

declare function fab(arg: A | B): void;
>fab : (arg: A | B) => void
>arg : A | B

declare function foo<T>(x: { kind: T }, f: (arg: { kind: T }) => void): void;
>foo : <T>(x: { kind: T;}, f: (arg: { kind: T;}) => void) => void
>x : { kind: T; }
>kind : T
>f : (arg: { kind: T;}) => void
>arg : { kind: T; }
>kind : T

foo(a, fab);
>foo(a, fab) : void
>foo : <T>(x: { kind: T; }, f: (arg: { kind: T; }) => void) => void
>a : A
>fab : (arg: A | B) => void

foo(b, fab);
>foo(b, fab) : void
>foo : <T>(x: { kind: T; }, f: (arg: { kind: T; }) => void) => void
>b : B
>fab : (arg: A | B) => void

// Repro from #45603

interface Action<TName extends string,TPayload> {
name: TName,
>name : TName

payload: TPayload
>payload : TPayload
}

const actionA = { payload: 'any-string' } as Action<'ACTION_A', string>;
>actionA : Action<"ACTION_A", string>
>{ payload: 'any-string' } as Action<'ACTION_A', string> : Action<"ACTION_A", string>
>{ payload: 'any-string' } : { payload: string; }
>payload : string
>'any-string' : "any-string"

const actionB = { payload: true } as Action<'ACTION_B', boolean>;
>actionB : Action<"ACTION_B", boolean>
>{ payload: true } as Action<'ACTION_B', boolean> : Action<"ACTION_B", boolean>
>{ payload: true } : { payload: true; }
>payload : true
>true : true

function call<TName extends string,TPayload>(
>call : <TName extends string, TPayload>(action: Action<TName, TPayload>, fn: (action: Action<TName, TPayload>) => any) => void

action: Action<TName,TPayload>,
>action : Action<TName, TPayload>

fn: (action: Action<TName,TPayload>)=> any,
>fn : (action: Action<TName, TPayload>) => any
>action : Action<TName, TPayload>

) {
fn(action);
>fn(action) : any
>fn : (action: Action<TName, TPayload>) => any
>action : Action<TName, TPayload>
}

const printFn = (action: typeof actionA | typeof actionB)=> console.log(action);
>printFn : (action: typeof actionA | typeof actionB) => void
>(action: typeof actionA | typeof actionB)=> console.log(action) : (action: typeof actionA | typeof actionB) => void
>action : Action<"ACTION_A", string> | Action<"ACTION_B", boolean>
>actionA : Action<"ACTION_A", string>
>actionB : Action<"ACTION_B", boolean>
>console.log(action) : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>action : Action<"ACTION_A", string> | Action<"ACTION_B", boolean>

call(actionA, printFn);
>call(actionA, printFn) : void
>call : <TName extends string, TPayload>(action: Action<TName, TPayload>, fn: (action: Action<TName, TPayload>) => any) => void
>actionA : Action<"ACTION_A", string>
>printFn : (action: Action<"ACTION_A", string> | Action<"ACTION_B", boolean>) => void

call(actionB, printFn);
>call(actionB, printFn) : void
>call : <TName extends string, TPayload>(action: Action<TName, TPayload>, fn: (action: Action<TName, TPayload>) => any) => void
>actionB : Action<"ACTION_B", boolean>
>printFn : (action: Action<"ACTION_A", string> | Action<"ACTION_B", boolean>) => void

37 changes: 37 additions & 0 deletions tests/cases/compiler/coAndContraVariantInferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// @strict: true
// @declaration: true

type A = { kind: 'a' };
type B = { kind: 'b' };

declare const a: A;
declare const b: B;

declare function fab(arg: A | B): void;

declare function foo<T>(x: { kind: T }, f: (arg: { kind: T }) => void): void;

foo(a, fab);
foo(b, fab);

// Repro from #45603

interface Action<TName extends string,TPayload> {
name: TName,
payload: TPayload
}

const actionA = { payload: 'any-string' } as Action<'ACTION_A', string>;
const actionB = { payload: true } as Action<'ACTION_B', boolean>;

function call<TName extends string,TPayload>(
action: Action<TName,TPayload>,
fn: (action: Action<TName,TPayload>)=> any,
) {
fn(action);
}

const printFn = (action: typeof actionA | typeof actionB)=> console.log(action);

call(actionA, printFn);
call(actionB, printFn);