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

* Improve logic for choosing between co- and contra-variant inferences

* Add tests
This commit is contained in:
Anders Hejlsberg
2021-10-19 09:58:10 -07:00
committed by GitHub
parent fd6552a3c2
commit 56b6279818
5 changed files with 332 additions and 4 deletions
+3 -4
View File
@@ -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;
@@ -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;
@@ -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))
@@ -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
@@ -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);