Skip to content

Commit a71841c

Browse files
authored
Improve logic that chooses co- vs. contra-variant inferences (#57909)
1 parent ed81ca6 commit a71841c

7 files changed

+305
-5
lines changed

src/compiler/checker.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -26811,15 +26811,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2681126811
const inferredContravariantType = inference.contraCandidates ? getContravariantInference(inference) : undefined;
2681226812
if (inferredCovariantType || inferredContravariantType) {
2681326813
// If we have both co- and contra-variant inferences, we prefer the co-variant inference if it is not 'never',
26814-
// all co-variant inferences are subtypes of it (i.e. it isn't one of a conflicting set of candidates), it is
26815-
// a subtype of some contra-variant inference, and no other type parameter is constrained to this type parameter
26814+
// all co-variant inferences are assignable to it (i.e. it isn't one of a conflicting set of candidates), it is
26815+
// assignable to some contra-variant inference, and no other type parameter is constrained to this type parameter
2681626816
// and has inferences that would conflict. Otherwise, we prefer the contra-variant inference.
26817+
// Similarly ignore co-variant `any` inference when both are available as almost everything is assignable to it
26818+
// and it would spoil the overall inference.
2681726819
const preferCovariantType = inferredCovariantType && (!inferredContravariantType ||
26818-
!(inferredCovariantType.flags & TypeFlags.Never) &&
26819-
some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t)) &&
26820+
!(inferredCovariantType.flags & (TypeFlags.Never | TypeFlags.Any)) &&
26821+
some(inference.contraCandidates, t => isTypeAssignableTo(inferredCovariantType, t)) &&
2682026822
every(context.inferences, other =>
2682126823
other !== inference && getConstraintOfTypeParameter(other.typeParameter) !== inference.typeParameter ||
26822-
every(other.candidates, t => isTypeSubtypeOf(t, inferredCovariantType))));
26824+
every(other.candidates, t => isTypeAssignableTo(t, inferredCovariantType))));
2682326825
inferredType = preferCovariantType ? inferredCovariantType : inferredContravariantType;
2682426826
fallbackType = preferCovariantType ? inferredContravariantType : inferredCovariantType;
2682526827
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//// [tests/cases/compiler/coAndContraVariantInferences7.ts] ////
2+
3+
=== coAndContraVariantInferences7.ts ===
4+
type Request<TSchema extends Schema> = {
5+
>Request : Symbol(Request, Decl(coAndContraVariantInferences7.ts, 0, 0))
6+
>TSchema : Symbol(TSchema, Decl(coAndContraVariantInferences7.ts, 0, 13))
7+
>Schema : Symbol(Schema, Decl(coAndContraVariantInferences7.ts, 2, 2))
8+
9+
query: TSchema["query"];
10+
>query : Symbol(query, Decl(coAndContraVariantInferences7.ts, 0, 40))
11+
>TSchema : Symbol(TSchema, Decl(coAndContraVariantInferences7.ts, 0, 13))
12+
13+
};
14+
15+
type Schema = { query?: unknown; body?: unknown };
16+
>Schema : Symbol(Schema, Decl(coAndContraVariantInferences7.ts, 2, 2))
17+
>query : Symbol(query, Decl(coAndContraVariantInferences7.ts, 4, 15))
18+
>body : Symbol(body, Decl(coAndContraVariantInferences7.ts, 4, 32))
19+
20+
declare function route<TSchema extends Schema>(obj: {
21+
>route : Symbol(route, Decl(coAndContraVariantInferences7.ts, 4, 50))
22+
>TSchema : Symbol(TSchema, Decl(coAndContraVariantInferences7.ts, 6, 23))
23+
>Schema : Symbol(Schema, Decl(coAndContraVariantInferences7.ts, 2, 2))
24+
>obj : Symbol(obj, Decl(coAndContraVariantInferences7.ts, 6, 47))
25+
26+
pre: (a: TSchema) => void;
27+
>pre : Symbol(pre, Decl(coAndContraVariantInferences7.ts, 6, 53))
28+
>a : Symbol(a, Decl(coAndContraVariantInferences7.ts, 7, 8))
29+
>TSchema : Symbol(TSchema, Decl(coAndContraVariantInferences7.ts, 6, 23))
30+
31+
schema: TSchema;
32+
>schema : Symbol(schema, Decl(coAndContraVariantInferences7.ts, 7, 28))
33+
>TSchema : Symbol(TSchema, Decl(coAndContraVariantInferences7.ts, 6, 23))
34+
35+
handle: (req: Request<TSchema>) => void;
36+
>handle : Symbol(handle, Decl(coAndContraVariantInferences7.ts, 8, 18))
37+
>req : Symbol(req, Decl(coAndContraVariantInferences7.ts, 9, 11))
38+
>Request : Symbol(Request, Decl(coAndContraVariantInferences7.ts, 0, 0))
39+
>TSchema : Symbol(TSchema, Decl(coAndContraVariantInferences7.ts, 6, 23))
40+
41+
}): void;
42+
43+
const validate = (_: { query?: unknown; body?: unknown }) => {};
44+
>validate : Symbol(validate, Decl(coAndContraVariantInferences7.ts, 12, 5))
45+
>_ : Symbol(_, Decl(coAndContraVariantInferences7.ts, 12, 18))
46+
>query : Symbol(query, Decl(coAndContraVariantInferences7.ts, 12, 22))
47+
>body : Symbol(body, Decl(coAndContraVariantInferences7.ts, 12, 39))
48+
49+
route({
50+
>route : Symbol(route, Decl(coAndContraVariantInferences7.ts, 4, 50))
51+
52+
pre: validate,
53+
>pre : Symbol(pre, Decl(coAndContraVariantInferences7.ts, 14, 7))
54+
>validate : Symbol(validate, Decl(coAndContraVariantInferences7.ts, 12, 5))
55+
56+
schema: {
57+
>schema : Symbol(schema, Decl(coAndContraVariantInferences7.ts, 15, 16))
58+
59+
query: "",
60+
>query : Symbol(query, Decl(coAndContraVariantInferences7.ts, 16, 11))
61+
62+
},
63+
handle: (req) => {
64+
>handle : Symbol(handle, Decl(coAndContraVariantInferences7.ts, 18, 4))
65+
>req : Symbol(req, Decl(coAndContraVariantInferences7.ts, 19, 11))
66+
67+
const test: string = req.query;
68+
>test : Symbol(test, Decl(coAndContraVariantInferences7.ts, 20, 9))
69+
>req.query : Symbol(query, Decl(coAndContraVariantInferences7.ts, 0, 40))
70+
>req : Symbol(req, Decl(coAndContraVariantInferences7.ts, 19, 11))
71+
>query : Symbol(query, Decl(coAndContraVariantInferences7.ts, 0, 40))
72+
73+
},
74+
});
75+
76+
export {};
77+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//// [tests/cases/compiler/coAndContraVariantInferences7.ts] ////
2+
3+
=== coAndContraVariantInferences7.ts ===
4+
type Request<TSchema extends Schema> = {
5+
>Request : Request<TSchema>
6+
> : ^^^^^^^^^^^^^^^^
7+
8+
query: TSchema["query"];
9+
>query : TSchema["query"]
10+
> : ^^^^^^^^^^^^^^^^
11+
12+
};
13+
14+
type Schema = { query?: unknown; body?: unknown };
15+
>Schema : Schema
16+
> : ^^^^^^
17+
>query : unknown
18+
> : ^^^^^^^
19+
>body : unknown
20+
> : ^^^^^^^
21+
22+
declare function route<TSchema extends Schema>(obj: {
23+
>route : <TSchema extends Schema>(obj: { pre: (a: TSchema) => void; schema: TSchema; handle: (req: Request<TSchema>) => void; }) => void
24+
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
25+
>obj : { pre: (a: TSchema) => void; schema: TSchema; handle: (req: Request<TSchema>) => void; }
26+
> : ^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^^
27+
28+
pre: (a: TSchema) => void;
29+
>pre : (a: TSchema) => void
30+
> : ^ ^^ ^^^^^
31+
>a : TSchema
32+
> : ^^^^^^^
33+
34+
schema: TSchema;
35+
>schema : TSchema
36+
> : ^^^^^^^
37+
38+
handle: (req: Request<TSchema>) => void;
39+
>handle : (req: Request<TSchema>) => void
40+
> : ^ ^^ ^^^^^
41+
>req : Request<TSchema>
42+
> : ^^^^^^^^^^^^^^^^
43+
44+
}): void;
45+
46+
const validate = (_: { query?: unknown; body?: unknown }) => {};
47+
>validate : (_: { query?: unknown; body?: unknown; }) => void
48+
> : ^ ^^ ^^^^^^^^^
49+
>(_: { query?: unknown; body?: unknown }) => {} : (_: { query?: unknown; body?: unknown; }) => void
50+
> : ^ ^^ ^^^^^^^^^
51+
>_ : { query?: unknown; body?: unknown; }
52+
> : ^^^^^^^^^^ ^^^^^^^^^ ^^^
53+
>query : unknown
54+
> : ^^^^^^^
55+
>body : unknown
56+
> : ^^^^^^^
57+
58+
route({
59+
>route({ pre: validate, schema: { query: "", }, handle: (req) => { const test: string = req.query; },}) : void
60+
> : ^^^^
61+
>route : <TSchema extends Schema>(obj: { pre: (a: TSchema) => void; schema: TSchema; handle: (req: Request<TSchema>) => void; }) => void
62+
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
63+
>{ pre: validate, schema: { query: "", }, handle: (req) => { const test: string = req.query; },} : { pre: (_: { query?: unknown; body?: unknown; }) => void; schema: { query: string; }; handle: (req: Request<{ query: string; }>) => void; }
64+
> : ^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
65+
66+
pre: validate,
67+
>pre : (_: { query?: unknown; body?: unknown; }) => void
68+
> : ^ ^^ ^^^^^^^^^
69+
>validate : (_: { query?: unknown; body?: unknown; }) => void
70+
> : ^ ^^ ^^^^^^^^^
71+
72+
schema: {
73+
>schema : { query: string; }
74+
> : ^^^^^^^^^^^^^^^^^^
75+
>{ query: "", } : { query: string; }
76+
> : ^^^^^^^^^^^^^^^^^^
77+
78+
query: "",
79+
>query : string
80+
> : ^^^^^^
81+
>"" : ""
82+
> : ^^
83+
84+
},
85+
handle: (req) => {
86+
>handle : (req: Request<{ query: string; }>) => void
87+
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
88+
>(req) => { const test: string = req.query; } : (req: Request<{ query: string; }>) => void
89+
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
90+
>req : Request<{ query: string; }>
91+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^
92+
93+
const test: string = req.query;
94+
>test : string
95+
> : ^^^^^^
96+
>req.query : string
97+
> : ^^^^^^
98+
>req : Request<{ query: string; }>
99+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^
100+
>query : string
101+
> : ^^^^^^
102+
103+
},
104+
});
105+
106+
export {};
107+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//// [tests/cases/compiler/coAndContraVariantInferences8.ts] ////
2+
3+
=== coAndContraVariantInferences8.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/58468
5+
6+
declare const fn: (() => void) | ((a: number) => void);
7+
>fn : Symbol(fn, Decl(coAndContraVariantInferences8.ts, 2, 13))
8+
>a : Symbol(a, Decl(coAndContraVariantInferences8.ts, 2, 35))
9+
10+
declare const x: number;
11+
>x : Symbol(x, Decl(coAndContraVariantInferences8.ts, 4, 13))
12+
13+
declare const y: any;
14+
>y : Symbol(y, Decl(coAndContraVariantInferences8.ts, 5, 13))
15+
16+
fn.call(null, x);
17+
>fn.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
18+
>fn : Symbol(fn, Decl(coAndContraVariantInferences8.ts, 2, 13))
19+
>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
20+
>x : Symbol(x, Decl(coAndContraVariantInferences8.ts, 4, 13))
21+
22+
fn.call(null, y);
23+
>fn.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
24+
>fn : Symbol(fn, Decl(coAndContraVariantInferences8.ts, 2, 13))
25+
>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
26+
>y : Symbol(y, Decl(coAndContraVariantInferences8.ts, 5, 13))
27+
28+
export {};
29+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//// [tests/cases/compiler/coAndContraVariantInferences8.ts] ////
2+
3+
=== coAndContraVariantInferences8.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/58468
5+
6+
declare const fn: (() => void) | ((a: number) => void);
7+
>fn : (() => void) | ((a: number) => void)
8+
> : ^^^^^^^ ^^^^^^ ^^ ^^^^^ ^
9+
>a : number
10+
> : ^^^^^^
11+
12+
declare const x: number;
13+
>x : number
14+
> : ^^^^^^
15+
16+
declare const y: any;
17+
>y : any
18+
19+
fn.call(null, x);
20+
>fn.call(null, x) : void
21+
> : ^^^^
22+
>fn.call : <T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R
23+
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^
24+
>fn : (() => void) | ((a: number) => void)
25+
> : ^^^^^^^ ^^^^^^ ^^ ^^^^^ ^
26+
>call : <T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R
27+
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^
28+
>x : number
29+
> : ^^^^^^
30+
31+
fn.call(null, y);
32+
>fn.call(null, y) : void
33+
> : ^^^^
34+
>fn.call : <T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R
35+
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^
36+
>fn : (() => void) | ((a: number) => void)
37+
> : ^^^^^^^ ^^^^^^ ^^ ^^^^^ ^
38+
>call : <T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R
39+
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^
40+
>y : any
41+
42+
export {};
43+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
type Request<TSchema extends Schema> = {
5+
query: TSchema["query"];
6+
};
7+
8+
type Schema = { query?: unknown; body?: unknown };
9+
10+
declare function route<TSchema extends Schema>(obj: {
11+
pre: (a: TSchema) => void;
12+
schema: TSchema;
13+
handle: (req: Request<TSchema>) => void;
14+
}): void;
15+
16+
const validate = (_: { query?: unknown; body?: unknown }) => {};
17+
18+
route({
19+
pre: validate,
20+
schema: {
21+
query: "",
22+
},
23+
handle: (req) => {
24+
const test: string = req.query;
25+
},
26+
});
27+
28+
export {};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
// https://github.com/microsoft/TypeScript/issues/58468
5+
6+
declare const fn: (() => void) | ((a: number) => void);
7+
8+
declare const x: number;
9+
declare const y: any;
10+
11+
fn.call(null, x);
12+
fn.call(null, y);
13+
14+
export {};

0 commit comments

Comments
 (0)