Skip to content

Commit fc85386

Browse files
authored
Improve logic that chooses co- vs. contra-variant inferences (#52123)
1 parent 4b52d3a commit fc85386

File tree

5 files changed

+888
-5
lines changed

5 files changed

+888
-5
lines changed

src/compiler/checker.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -24504,11 +24504,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2450424504
if (signature) {
2450524505
const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined;
2450624506
if (inference.contraCandidates) {
24507-
// If we have both co- and contra-variant inferences, we prefer the contra-variant inference
24508-
// unless the co-variant inference is a subtype of some contra-variant inference and not 'never'.
24509-
inferredType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) &&
24510-
some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t)) ?
24511-
inferredCovariantType : getContravariantInference(inference);
24507+
// If we have both co- and contra-variant inferences, we use the co-variant inference if it is not 'never',
24508+
// it is a subtype of some contra-variant inference, and no other type parameter is constrained to this type
24509+
// parameter and has inferences that would conflict. Otherwise, we use the contra-variant inference.
24510+
const useCovariantType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) &&
24511+
some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t)) &&
24512+
every(context.inferences, other => other === inference ||
24513+
getConstraintOfTypeParameter(other.typeParameter) !== inference.typeParameter ||
24514+
every(other.candidates, t => isTypeSubtypeOf(t, inferredCovariantType)));
24515+
inferredType = useCovariantType ? inferredCovariantType : getContravariantInference(inference);
2451224516
}
2451324517
else if (inferredCovariantType) {
2451424518
inferredType = inferredCovariantType;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
//// [coAndContraVariantInferences2.ts]
2+
interface A { a: string }
3+
interface B extends A { b: string }
4+
interface C extends A { c: string }
5+
6+
declare function cast<T, U extends T>(x: T, test: (x: T) => x is U): U;
7+
8+
declare function isC(x: A): x is C;
9+
10+
function f1(a: A, b: B) {
11+
const x1 = cast(a, isC); // cast<A, C>
12+
const x2 = cast(b, isC); // cast<A, C>
13+
}
14+
15+
declare function useA(a: A): void;
16+
17+
declare function consume<T, U extends T>(t: T, u: U, f: (x: T) => void): void;
18+
19+
function f2(b: B, c: C) {
20+
consume(b, c, useA); // consume<A, C>
21+
consume(c, b, useA); // consume<A, B>
22+
consume(b, b, useA); // consume<B, B>
23+
consume(c, c, useA); // consume<C, C>
24+
}
25+
26+
// Repro from #52111
27+
28+
enum SyntaxKind {
29+
Block,
30+
Identifier,
31+
CaseClause,
32+
FunctionExpression,
33+
FunctionDeclaration,
34+
}
35+
36+
interface Node { kind: SyntaxKind; }
37+
interface Expression extends Node { _expressionBrand: any; }
38+
interface Declaration extends Node { _declarationBrand: any; }
39+
interface Block extends Node { kind: SyntaxKind.Block; }
40+
interface Identifier extends Expression, Declaration { kind: SyntaxKind.Identifier; }
41+
interface CaseClause extends Node { kind: SyntaxKind.CaseClause; }
42+
interface FunctionDeclaration extends Declaration { kind: SyntaxKind.FunctionDeclaration; }
43+
44+
type HasLocals = Block | FunctionDeclaration;
45+
declare function canHaveLocals(node: Node): node is HasLocals;
46+
47+
declare function assertNode<T extends Node, U extends T>(node: T | undefined, test: (node: T) => node is U): asserts node is U;
48+
declare function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined): void;
49+
50+
function foo(node: FunctionDeclaration | CaseClause) {
51+
assertNode(node, canHaveLocals); // assertNode<Node, HasLocals>
52+
node; // FunctionDeclaration
53+
}
54+
55+
declare function isExpression(node: Node): node is Expression;
56+
57+
declare function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut;
58+
59+
function bar(node: Identifier | FunctionDeclaration) {
60+
const a = tryCast(node, isExpression); // tryCast<Expression, Node>
61+
}
62+
63+
// Repro from #49924
64+
65+
const enum SyntaxKind1 {
66+
ClassExpression,
67+
ClassStatement,
68+
}
69+
70+
interface Node1 {
71+
kind: SyntaxKind1;
72+
}
73+
74+
interface Statement1 extends Node1 {
75+
_statementBrand: any;
76+
}
77+
78+
interface ClassExpression1 extends Node1 {
79+
kind: SyntaxKind1.ClassExpression;
80+
}
81+
82+
interface ClassStatement1 extends Statement1 {
83+
kind: SyntaxKind1.ClassStatement;
84+
}
85+
86+
type ClassLike1 = ClassExpression1 | ClassStatement1;
87+
88+
declare function isClassLike(node: Node1): node is ClassLike1;
89+
90+
declare const statement: Statement1 | undefined;
91+
92+
const maybeClassStatement = tryCast(statement, isClassLike); // ClassLike1
93+
94+
// Repro from #49924
95+
96+
interface TypeNode extends Node {
97+
typeInfo: string;
98+
}
99+
100+
interface NodeArray<T extends Node> extends Array<T> {
101+
someProp: string;
102+
}
103+
104+
declare function isNodeArray<T extends Node>(array: readonly T[]): array is NodeArray<T>;
105+
106+
declare const types: readonly TypeNode[];
107+
108+
const x = tryCast(types, isNodeArray); // NodeAray<TypeNode>
109+
110+
111+
//// [coAndContraVariantInferences2.js]
112+
"use strict";
113+
function f1(a, b) {
114+
var x1 = cast(a, isC); // cast<A, C>
115+
var x2 = cast(b, isC); // cast<A, C>
116+
}
117+
function f2(b, c) {
118+
consume(b, c, useA); // consume<A, C>
119+
consume(c, b, useA); // consume<A, B>
120+
consume(b, b, useA); // consume<B, B>
121+
consume(c, c, useA); // consume<C, C>
122+
}
123+
// Repro from #52111
124+
var SyntaxKind;
125+
(function (SyntaxKind) {
126+
SyntaxKind[SyntaxKind["Block"] = 0] = "Block";
127+
SyntaxKind[SyntaxKind["Identifier"] = 1] = "Identifier";
128+
SyntaxKind[SyntaxKind["CaseClause"] = 2] = "CaseClause";
129+
SyntaxKind[SyntaxKind["FunctionExpression"] = 3] = "FunctionExpression";
130+
SyntaxKind[SyntaxKind["FunctionDeclaration"] = 4] = "FunctionDeclaration";
131+
})(SyntaxKind || (SyntaxKind = {}));
132+
function foo(node) {
133+
assertNode(node, canHaveLocals); // assertNode<Node, HasLocals>
134+
node; // FunctionDeclaration
135+
}
136+
function bar(node) {
137+
var a = tryCast(node, isExpression); // tryCast<Expression, Node>
138+
}
139+
var maybeClassStatement = tryCast(statement, isClassLike); // ClassLike1
140+
var x = tryCast(types, isNodeArray); // NodeAray<TypeNode>

0 commit comments

Comments
 (0)