Skip to content

Commit d7219b2

Browse files
authored
Merge pull request #27357 from Microsoft/fixBivariantInferences
Make contravariant inferences only from pure contravariant positions
2 parents 98ec1e8 + f59229b commit d7219b2

File tree

5 files changed

+97
-3
lines changed

5 files changed

+97
-3
lines changed

src/compiler/checker.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13462,6 +13462,7 @@ namespace ts {
1346213462
let symbolStack: Symbol[];
1346313463
let visited: Map<boolean>;
1346413464
let contravariant = false;
13465+
let bivariant = false;
1346513466
let propagationType: Type;
1346613467
let allowComplexConstraintInference = true;
1346713468
inferFromTypes(originalSource, originalTarget);
@@ -13548,11 +13549,13 @@ namespace ts {
1354813549
}
1354913550
if (priority === inference.priority) {
1355013551
const candidate = propagationType || source;
13551-
if (contravariant) {
13552-
inference.contraCandidates = append(inference.contraCandidates, candidate);
13552+
// We make contravariant inferences only if we are in a pure contravariant position,
13553+
// i.e. only if we have not descended into a bivariant position.
13554+
if (contravariant && !bivariant) {
13555+
inference.contraCandidates = appendIfUnique(inference.contraCandidates, candidate);
1355313556
}
1355413557
else {
13555-
inference.candidates = append(inference.candidates, candidate);
13558+
inference.candidates = appendIfUnique(inference.candidates, candidate);
1355613559
}
1355713560
}
1355813561
if (!(priority & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && !isTypeParameterAtTopLevel(originalTarget, <TypeParameter>target)) {
@@ -13800,7 +13803,12 @@ namespace ts {
1380013803

1380113804
function inferFromSignature(source: Signature, target: Signature, skipParameters: boolean) {
1380213805
if (!skipParameters) {
13806+
const saveBivariant = bivariant;
13807+
const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown;
13808+
// Once we descend into a bivariant signature we remain bivariant for all nested inferences
13809+
bivariant = bivariant || kind === SyntaxKind.MethodDeclaration || kind === SyntaxKind.MethodSignature || kind === SyntaxKind.Constructor;
1380313810
forEachMatchingParameterType(source, target, inferFromContravariantTypes);
13811+
bivariant = saveBivariant;
1380413812
}
1380513813
const sourceTypePredicate = getTypePredicateOfSignature(source);
1380613814
const targetTypePredicate = getTypePredicateOfSignature(target);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//// [bivariantInferences.ts]
2+
// Repro from #27337
3+
4+
interface Array<T> {
5+
equalsShallow<T>(this: ReadonlyArray<T>, other: ReadonlyArray<T>): boolean;
6+
}
7+
8+
declare const a: (string | number)[] | null[] | undefined[] | {}[];
9+
declare const b: (string | number)[] | null[] | undefined[] | {}[];
10+
11+
let x = a.equalsShallow(b);
12+
13+
14+
//// [bivariantInferences.js]
15+
"use strict";
16+
// Repro from #27337
17+
var x = a.equalsShallow(b);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
=== tests/cases/conformance/types/typeRelationships/typeInference/bivariantInferences.ts ===
2+
// Repro from #27337
3+
4+
interface Array<T> {
5+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(bivariantInferences.ts, 0, 0))
6+
>T : Symbol(T, Decl(lib.es5.d.ts, --, --), Decl(bivariantInferences.ts, 2, 16))
7+
8+
equalsShallow<T>(this: ReadonlyArray<T>, other: ReadonlyArray<T>): boolean;
9+
>equalsShallow : Symbol(Array.equalsShallow, Decl(bivariantInferences.ts, 2, 20))
10+
>T : Symbol(T, Decl(bivariantInferences.ts, 3, 18))
11+
>this : Symbol(this, Decl(bivariantInferences.ts, 3, 21))
12+
>ReadonlyArray : Symbol(ReadonlyArray, Decl(lib.es5.d.ts, --, --))
13+
>T : Symbol(T, Decl(bivariantInferences.ts, 3, 18))
14+
>other : Symbol(other, Decl(bivariantInferences.ts, 3, 44))
15+
>ReadonlyArray : Symbol(ReadonlyArray, Decl(lib.es5.d.ts, --, --))
16+
>T : Symbol(T, Decl(bivariantInferences.ts, 3, 18))
17+
}
18+
19+
declare const a: (string | number)[] | null[] | undefined[] | {}[];
20+
>a : Symbol(a, Decl(bivariantInferences.ts, 6, 13))
21+
22+
declare const b: (string | number)[] | null[] | undefined[] | {}[];
23+
>b : Symbol(b, Decl(bivariantInferences.ts, 7, 13))
24+
25+
let x = a.equalsShallow(b);
26+
>x : Symbol(x, Decl(bivariantInferences.ts, 9, 3))
27+
>a.equalsShallow : Symbol(equalsShallow, Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20))
28+
>a : Symbol(a, Decl(bivariantInferences.ts, 6, 13))
29+
>equalsShallow : Symbol(equalsShallow, Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20))
30+
>b : Symbol(b, Decl(bivariantInferences.ts, 7, 13))
31+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
=== tests/cases/conformance/types/typeRelationships/typeInference/bivariantInferences.ts ===
2+
// Repro from #27337
3+
4+
interface Array<T> {
5+
equalsShallow<T>(this: ReadonlyArray<T>, other: ReadonlyArray<T>): boolean;
6+
>equalsShallow : <T>(this: ReadonlyArray<T>, other: ReadonlyArray<T>) => boolean
7+
>this : ReadonlyArray<T>
8+
>other : ReadonlyArray<T>
9+
}
10+
11+
declare const a: (string | number)[] | null[] | undefined[] | {}[];
12+
>a : (string | number)[] | null[] | undefined[] | {}[]
13+
>null : null
14+
15+
declare const b: (string | number)[] | null[] | undefined[] | {}[];
16+
>b : (string | number)[] | null[] | undefined[] | {}[]
17+
>null : null
18+
19+
let x = a.equalsShallow(b);
20+
>x : boolean
21+
>a.equalsShallow(b) : boolean
22+
>a.equalsShallow : (<T>(this: ReadonlyArray<T>, other: ReadonlyArray<T>) => boolean) | (<T>(this: ReadonlyArray<T>, other: ReadonlyArray<T>) => boolean) | (<T>(this: ReadonlyArray<T>, other: ReadonlyArray<T>) => boolean) | (<T>(this: ReadonlyArray<T>, other: ReadonlyArray<T>) => boolean)
23+
>a : (string | number)[] | null[] | undefined[] | {}[]
24+
>equalsShallow : (<T>(this: ReadonlyArray<T>, other: ReadonlyArray<T>) => boolean) | (<T>(this: ReadonlyArray<T>, other: ReadonlyArray<T>) => boolean) | (<T>(this: ReadonlyArray<T>, other: ReadonlyArray<T>) => boolean) | (<T>(this: ReadonlyArray<T>, other: ReadonlyArray<T>) => boolean)
25+
>b : (string | number)[] | null[] | undefined[] | {}[]
26+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// @strict: true
2+
3+
// Repro from #27337
4+
5+
interface Array<T> {
6+
equalsShallow<T>(this: ReadonlyArray<T>, other: ReadonlyArray<T>): boolean;
7+
}
8+
9+
declare const a: (string | number)[] | null[] | undefined[] | {}[];
10+
declare const b: (string | number)[] | null[] | undefined[] | {}[];
11+
12+
let x = a.equalsShallow(b);

0 commit comments

Comments
 (0)