Skip to content

Commit 0e86a8f

Browse files
committed
Report unmeasurable variance when assignability is unknown
1 parent b824cbc commit 0e86a8f

5 files changed

+338
-11
lines changed

src/compiler/checker.ts

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17366,35 +17366,39 @@ namespace ts {
1736617366
return false;
1736717367
}
1736817368

17369-
function isTypeRelatedTo(source: Type, target: Type, relation: ESMap<string, RelationComparisonResult>) {
17369+
function isTypeRelatedTo(source: Type, target: Type, relation: ESMap<string, RelationComparisonResult>): boolean {
17370+
return isTypeRelatedToWorker(source, target, relation) !== Ternary.False;
17371+
}
17372+
17373+
function isTypeRelatedToWorker(source: Type, target: Type, relation: ESMap<string, RelationComparisonResult>): Ternary {
1737017374
if (isFreshLiteralType(source)) {
1737117375
source = (source as FreshableType).regularType;
1737217376
}
1737317377
if (isFreshLiteralType(target)) {
1737417378
target = (target as FreshableType).regularType;
1737517379
}
1737617380
if (source === target) {
17377-
return true;
17381+
return Ternary.True;
1737817382
}
1737917383
if (relation !== identityRelation) {
1738017384
if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || isSimpleTypeRelatedTo(source, target, relation)) {
17381-
return true;
17385+
return Ternary.True;
1738217386
}
1738317387
}
1738417388
else {
17385-
if (source.flags !== target.flags) return false;
17386-
if (source.flags & TypeFlags.Singleton) return true;
17389+
if (source.flags !== target.flags) return Ternary.False;
17390+
if (source.flags & TypeFlags.Singleton) return Ternary.True;
1738717391
}
1738817392
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) {
1738917393
const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation));
1739017394
if (related !== undefined) {
17391-
return !!(related & RelationComparisonResult.Succeeded);
17395+
return (related & RelationComparisonResult.Succeeded) ? Ternary.True : Ternary.False;
1739217396
}
1739317397
}
1739417398
if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) {
17395-
return checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined);
17399+
return checkTypeRelatedToWorker(source, target, relation, /*errorNode*/ undefined);
1739617400
}
17397-
return false;
17401+
return Ternary.False;
1739817402
}
1739917403

1740017404
function isIgnoredJsxProperty(source: Type, sourceProp: Symbol) {
@@ -17436,6 +17440,18 @@ namespace ts {
1743617440
containingMessageChain?: () => DiagnosticMessageChain | undefined,
1743717441
errorOutputContainer?: { errors?: Diagnostic[], skipLogging?: boolean },
1743817442
): boolean {
17443+
return checkTypeRelatedToWorker(source, target, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer) !== Ternary.False;
17444+
}
17445+
17446+
function checkTypeRelatedToWorker(
17447+
source: Type,
17448+
target: Type,
17449+
relation: ESMap<string, RelationComparisonResult>,
17450+
errorNode: Node | undefined,
17451+
headMessage?: DiagnosticMessage,
17452+
containingMessageChain?: () => DiagnosticMessageChain | undefined,
17453+
errorOutputContainer?: { errors?: Diagnostic[], skipLogging?: boolean },
17454+
): Ternary {
1743917455

1744017456
let errorInfo: DiagnosticMessageChain | undefined;
1744117457
let relatedInfo: [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined;
@@ -17502,7 +17518,7 @@ namespace ts {
1750217518
}
1750317519

1750417520

17505-
return result !== Ternary.False;
17521+
return result;
1750617522

1750717523
function resetErrorInfo(saved: ReturnType<typeof captureErrorCalculationState>) {
1750817524
errorInfo = saved.errorInfo;
@@ -19744,8 +19760,15 @@ namespace ts {
1974419760
// invariance, covariance, contravariance or bivariance.
1974519761
const typeWithSuper = createMarkerType(cache, tp, markerSuperType);
1974619762
const typeWithSub = createMarkerType(cache, tp, markerSubType);
19747-
let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? VarianceFlags.Covariant : 0) |
19748-
(isTypeAssignableTo(typeWithSuper, typeWithSub) ? VarianceFlags.Contravariant : 0);
19763+
const covariant = isTypeRelatedToWorker(typeWithSub, typeWithSuper, assignableRelation);
19764+
const contravariant = isTypeRelatedToWorker(typeWithSuper, typeWithSub, assignableRelation);
19765+
let variance = 0;
19766+
if (covariant === Ternary.Unknown || contravariant === Ternary.Unknown) {
19767+
unmeasurable = true;
19768+
}
19769+
else {
19770+
variance = (covariant ? VarianceFlags.Covariant : 0) | (contravariant ? VarianceFlags.Contravariant : 0);
19771+
}
1974919772
// If the instantiations appear to be related bivariantly it may be because the
1975019773
// type parameter is independent (i.e. it isn't witnessed anywhere in the generic
1975119774
// type). To determine this we compare instantiations where the type parameter is
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
tests/cases/compiler/checkOrderDependenceGenericAssignability.ts(46,7): error TS2322: Type 'Parent<unknown>' is not assignable to type 'Parent<string>'.
2+
The types of 'child.a' are incompatible between these types.
3+
Type 'unknown' is not assignable to type 'string'.
4+
5+
6+
==== tests/cases/compiler/checkOrderDependenceGenericAssignability.ts (1 errors) ====
7+
/*
8+
9+
Debugging notes: variance measurement for `Parent` is getting set to
10+
`VarianceFlags.Independent`, implying that its type parameter is never
11+
witnessed at all. It arrived at this conclusion by checking the assignability
12+
of `Parent` instantiated with marker types. It first checks assignability in
13+
both directions with instantiations with super/sub-related marker types, and
14+
assignability appears to return true in both directions; however, it actually
15+
is returning `Ternary.Unknown`, due to being unable to answer questions about
16+
the assignability of the types' `parent` and `child` properties without knowing
17+
their variances. After (incorrectly) concluding that `Parent` is bivariant on `A`,
18+
it checks another set of instantiations with markers that are unrelated to each
19+
other. That too comes back as `Ternary.Unknown` but is interpreted as true, so
20+
the variance gets updated to `Independent`, since instantiating `Parent` with
21+
all kinds of different markers with different assignability to each other
22+
apparently had no effect on the instantiations' assignability to each other.
23+
24+
I'm not sure if any of those comparisons ever actually looked at `a` and `b`,
25+
which should provide some non-recursive concrete variance information. I'm also
26+
not sure if `outofbandVarianceMarkerHandler` should have been called at some point,
27+
but it was not.
28+
29+
*/
30+
31+
interface Parent<A> {
32+
child: Child<A> | null;
33+
parent: Parent<A> | null;
34+
}
35+
36+
interface Child<A, B = unknown> extends Parent<A> {
37+
readonly a: A;
38+
// This field isn't necessary to the repro, but the
39+
// type parameter is, so including it
40+
readonly b: B;
41+
}
42+
43+
function fn<A>(inp: Child<A>) {
44+
// This assignability check defeats the later one
45+
const a: Child<unknown> = inp;
46+
}
47+
48+
// Allowed initialization of pu
49+
const pu: Parent<unknown> = { child: { a: 0, b: 0, child: null, parent: null }, parent: null };
50+
51+
// Should error
52+
const notString: Parent<string> = pu;
53+
~~~~~~~~~
54+
!!! error TS2322: Type 'Parent<unknown>' is not assignable to type 'Parent<string>'.
55+
!!! error TS2322: The types of 'child.a' are incompatible between these types.
56+
!!! error TS2322: Type 'unknown' is not assignable to type 'string'.
57+
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//// [checkOrderDependenceGenericAssignability.ts]
2+
/*
3+
4+
Debugging notes: variance measurement for `Parent` is getting set to
5+
`VarianceFlags.Independent`, implying that its type parameter is never
6+
witnessed at all. It arrived at this conclusion by checking the assignability
7+
of `Parent` instantiated with marker types. It first checks assignability in
8+
both directions with instantiations with super/sub-related marker types, and
9+
assignability appears to return true in both directions; however, it actually
10+
is returning `Ternary.Unknown`, due to being unable to answer questions about
11+
the assignability of the types' `parent` and `child` properties without knowing
12+
their variances. After (incorrectly) concluding that `Parent` is bivariant on `A`,
13+
it checks another set of instantiations with markers that are unrelated to each
14+
other. That too comes back as `Ternary.Unknown` but is interpreted as true, so
15+
the variance gets updated to `Independent`, since instantiating `Parent` with
16+
all kinds of different markers with different assignability to each other
17+
apparently had no effect on the instantiations' assignability to each other.
18+
19+
I'm not sure if any of those comparisons ever actually looked at `a` and `b`,
20+
which should provide some non-recursive concrete variance information. I'm also
21+
not sure if `outofbandVarianceMarkerHandler` should have been called at some point,
22+
but it was not.
23+
24+
*/
25+
26+
interface Parent<A> {
27+
child: Child<A> | null;
28+
parent: Parent<A> | null;
29+
}
30+
31+
interface Child<A, B = unknown> extends Parent<A> {
32+
readonly a: A;
33+
// This field isn't necessary to the repro, but the
34+
// type parameter is, so including it
35+
readonly b: B;
36+
}
37+
38+
function fn<A>(inp: Child<A>) {
39+
// This assignability check defeats the later one
40+
const a: Child<unknown> = inp;
41+
}
42+
43+
// Allowed initialization of pu
44+
const pu: Parent<unknown> = { child: { a: 0, b: 0, child: null, parent: null }, parent: null };
45+
46+
// Should error
47+
const notString: Parent<string> = pu;
48+
49+
50+
//// [checkOrderDependenceGenericAssignability.js]
51+
/*
52+
53+
Debugging notes: variance measurement for `Parent` is getting set to
54+
`VarianceFlags.Independent`, implying that its type parameter is never
55+
witnessed at all. It arrived at this conclusion by checking the assignability
56+
of `Parent` instantiated with marker types. It first checks assignability in
57+
both directions with instantiations with super/sub-related marker types, and
58+
assignability appears to return true in both directions; however, it actually
59+
is returning `Ternary.Unknown`, due to being unable to answer questions about
60+
the assignability of the types' `parent` and `child` properties without knowing
61+
their variances. After (incorrectly) concluding that `Parent` is bivariant on `A`,
62+
it checks another set of instantiations with markers that are unrelated to each
63+
other. That too comes back as `Ternary.Unknown` but is interpreted as true, so
64+
the variance gets updated to `Independent`, since instantiating `Parent` with
65+
all kinds of different markers with different assignability to each other
66+
apparently had no effect on the instantiations' assignability to each other.
67+
68+
I'm not sure if any of those comparisons ever actually looked at `a` and `b`,
69+
which should provide some non-recursive concrete variance information. I'm also
70+
not sure if `outofbandVarianceMarkerHandler` should have been called at some point,
71+
but it was not.
72+
73+
*/
74+
function fn(inp) {
75+
// This assignability check defeats the later one
76+
var a = inp;
77+
}
78+
// Allowed initialization of pu
79+
var pu = { child: { a: 0, b: 0, child: null, parent: null }, parent: null };
80+
// Should error
81+
var notString = pu;
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
=== tests/cases/compiler/checkOrderDependenceGenericAssignability.ts ===
2+
/*
3+
4+
Debugging notes: variance measurement for `Parent` is getting set to
5+
`VarianceFlags.Independent`, implying that its type parameter is never
6+
witnessed at all. It arrived at this conclusion by checking the assignability
7+
of `Parent` instantiated with marker types. It first checks assignability in
8+
both directions with instantiations with super/sub-related marker types, and
9+
assignability appears to return true in both directions; however, it actually
10+
is returning `Ternary.Unknown`, due to being unable to answer questions about
11+
the assignability of the types' `parent` and `child` properties without knowing
12+
their variances. After (incorrectly) concluding that `Parent` is bivariant on `A`,
13+
it checks another set of instantiations with markers that are unrelated to each
14+
other. That too comes back as `Ternary.Unknown` but is interpreted as true, so
15+
the variance gets updated to `Independent`, since instantiating `Parent` with
16+
all kinds of different markers with different assignability to each other
17+
apparently had no effect on the instantiations' assignability to each other.
18+
19+
I'm not sure if any of those comparisons ever actually looked at `a` and `b`,
20+
which should provide some non-recursive concrete variance information. I'm also
21+
not sure if `outofbandVarianceMarkerHandler` should have been called at some point,
22+
but it was not.
23+
24+
*/
25+
26+
interface Parent<A> {
27+
>Parent : Symbol(Parent, Decl(checkOrderDependenceGenericAssignability.ts, 0, 0))
28+
>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 24, 17))
29+
30+
child: Child<A> | null;
31+
>child : Symbol(Parent.child, Decl(checkOrderDependenceGenericAssignability.ts, 24, 21))
32+
>Child : Symbol(Child, Decl(checkOrderDependenceGenericAssignability.ts, 27, 1))
33+
>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 24, 17))
34+
35+
parent: Parent<A> | null;
36+
>parent : Symbol(Parent.parent, Decl(checkOrderDependenceGenericAssignability.ts, 25, 25))
37+
>Parent : Symbol(Parent, Decl(checkOrderDependenceGenericAssignability.ts, 0, 0))
38+
>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 24, 17))
39+
}
40+
41+
interface Child<A, B = unknown> extends Parent<A> {
42+
>Child : Symbol(Child, Decl(checkOrderDependenceGenericAssignability.ts, 27, 1))
43+
>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 29, 16))
44+
>B : Symbol(B, Decl(checkOrderDependenceGenericAssignability.ts, 29, 18))
45+
>Parent : Symbol(Parent, Decl(checkOrderDependenceGenericAssignability.ts, 0, 0))
46+
>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 29, 16))
47+
48+
readonly a: A;
49+
>a : Symbol(Child.a, Decl(checkOrderDependenceGenericAssignability.ts, 29, 51))
50+
>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 29, 16))
51+
52+
// This field isn't necessary to the repro, but the
53+
// type parameter is, so including it
54+
readonly b: B;
55+
>b : Symbol(Child.b, Decl(checkOrderDependenceGenericAssignability.ts, 30, 16))
56+
>B : Symbol(B, Decl(checkOrderDependenceGenericAssignability.ts, 29, 18))
57+
}
58+
59+
function fn<A>(inp: Child<A>) {
60+
>fn : Symbol(fn, Decl(checkOrderDependenceGenericAssignability.ts, 34, 1))
61+
>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 36, 12))
62+
>inp : Symbol(inp, Decl(checkOrderDependenceGenericAssignability.ts, 36, 15))
63+
>Child : Symbol(Child, Decl(checkOrderDependenceGenericAssignability.ts, 27, 1))
64+
>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 36, 12))
65+
66+
// This assignability check defeats the later one
67+
const a: Child<unknown> = inp;
68+
>a : Symbol(a, Decl(checkOrderDependenceGenericAssignability.ts, 38, 7))
69+
>Child : Symbol(Child, Decl(checkOrderDependenceGenericAssignability.ts, 27, 1))
70+
>inp : Symbol(inp, Decl(checkOrderDependenceGenericAssignability.ts, 36, 15))
71+
}
72+
73+
// Allowed initialization of pu
74+
const pu: Parent<unknown> = { child: { a: 0, b: 0, child: null, parent: null }, parent: null };
75+
>pu : Symbol(pu, Decl(checkOrderDependenceGenericAssignability.ts, 42, 5))
76+
>Parent : Symbol(Parent, Decl(checkOrderDependenceGenericAssignability.ts, 0, 0))
77+
>child : Symbol(child, Decl(checkOrderDependenceGenericAssignability.ts, 42, 29))
78+
>a : Symbol(a, Decl(checkOrderDependenceGenericAssignability.ts, 42, 38))
79+
>b : Symbol(b, Decl(checkOrderDependenceGenericAssignability.ts, 42, 44))
80+
>child : Symbol(child, Decl(checkOrderDependenceGenericAssignability.ts, 42, 50))
81+
>parent : Symbol(parent, Decl(checkOrderDependenceGenericAssignability.ts, 42, 63))
82+
>parent : Symbol(parent, Decl(checkOrderDependenceGenericAssignability.ts, 42, 79))
83+
84+
// Should error
85+
const notString: Parent<string> = pu;
86+
>notString : Symbol(notString, Decl(checkOrderDependenceGenericAssignability.ts, 45, 5))
87+
>Parent : Symbol(Parent, Decl(checkOrderDependenceGenericAssignability.ts, 0, 0))
88+
>pu : Symbol(pu, Decl(checkOrderDependenceGenericAssignability.ts, 42, 5))
89+

0 commit comments

Comments
 (0)