Skip to content

Commit f84ec3e

Browse files
authored
Fix type parameter comparability to consistently allow comparisons on unconstrained type parameters (microsoft#48861)
* Fix type parameter comparability to consistently allow comparisons on unconstrained type parameters * Less elaboration, non-strict-mode fix
1 parent 1071240 commit f84ec3e

10 files changed

+291
-351
lines changed

src/compiler/checker.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19347,6 +19347,20 @@ namespace ts {
1934719347
}
1934819348
}
1934919349
}
19350+
if (relation === comparableRelation && sourceFlags & TypeFlags.TypeParameter) {
19351+
// This is a carve-out in comparability to essentially forbid comparing a type parameter
19352+
// with another type parameter unless one extends the other. (Remember: comparability is mostly bidirectional!)
19353+
let constraint = getConstraintOfTypeParameter(source);
19354+
if (constraint && hasNonCircularBaseConstraint(source)) {
19355+
while (constraint && constraint.flags & TypeFlags.TypeParameter) {
19356+
if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false)) {
19357+
return result;
19358+
}
19359+
constraint = getConstraintOfTypeParameter(constraint);
19360+
}
19361+
}
19362+
return Ternary.False;
19363+
}
1935019364
}
1935119365
else if (targetFlags & TypeFlags.Index) {
1935219366
const targetType = (target as IndexType).type;
@@ -19558,21 +19572,21 @@ namespace ts {
1955819572
if (sourceFlags & TypeFlags.TypeVariable) {
1955919573
// IndexedAccess comparisons are handled above in the `targetFlags & TypeFlage.IndexedAccess` branch
1956019574
if (!(sourceFlags & TypeFlags.IndexedAccess && targetFlags & TypeFlags.IndexedAccess)) {
19561-
const constraint = getConstraintOfType(source as TypeVariable);
19562-
if (!constraint || (sourceFlags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) {
19575+
const constraint = getConstraintOfType(source as TypeVariable) || unknownType;
19576+
if (!getConstraintOfType(source as TypeVariable) || (sourceFlags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) {
1956319577
// A type variable with no constraint is not related to the non-primitive object type.
1956419578
if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive), RecursionFlags.Both)) {
1956519579
resetErrorInfo(saveErrorInfo);
1956619580
return result;
1956719581
}
1956819582
}
1956919583
// hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed
19570-
else if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) {
19584+
if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) {
1957119585
resetErrorInfo(saveErrorInfo);
1957219586
return result;
1957319587
}
1957419588
// slower, fuller, this-instantiated check (necessary when comparing raw `this` types from base classes), see `subclassWithPolymorphicThisIsAssignable.ts` test for example
19575-
else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, RecursionFlags.Source, reportErrors && !(targetFlags & sourceFlags & TypeFlags.TypeParameter), /*headMessage*/ undefined, intersectionState)) {
19589+
else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, RecursionFlags.Source, reportErrors && constraint !== unknownType && !(targetFlags & sourceFlags & TypeFlags.TypeParameter), /*headMessage*/ undefined, intersectionState)) {
1957619590
resetErrorInfo(saveErrorInfo);
1957719591
return result;
1957819592
}

tests/baselines/reference/comparisonOperatorWithNoRelationshipTypeParameter.errors.txt

Lines changed: 1 addition & 337 deletions
Large diffs are not rendered by default.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
tests/cases/compiler/genericWithNoConstraintComparableWithCurlyCurly.ts(23,5): error TS2352: Conversion of type '{}' to type 'T' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
2+
'T' could be instantiated with an arbitrary type which could be unrelated to '{}'.
3+
4+
5+
==== tests/cases/compiler/genericWithNoConstraintComparableWithCurlyCurly.ts (1 errors) ====
6+
function foo<T>() {
7+
let x = {};
8+
x as T;
9+
}
10+
11+
function bar<T extends unknown>() {
12+
let x = {};
13+
x as T;
14+
}
15+
16+
function baz<T extends {}>() {
17+
let x = {};
18+
x as T;
19+
}
20+
21+
function bat<T extends object>() {
22+
let x = {};
23+
x as T;
24+
}
25+
26+
function no<T extends null | undefined>() {
27+
let x = {};
28+
x as T; // should error
29+
~~~~~~
30+
!!! error TS2352: Conversion of type '{}' to type 'T' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
31+
!!! error TS2352: 'T' could be instantiated with an arbitrary type which could be unrelated to '{}'.
32+
}
33+
34+
function yes<T extends object | null | undefined>() {
35+
let x = {};
36+
x as T;
37+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//// [genericWithNoConstraintComparableWithCurlyCurly.ts]
2+
function foo<T>() {
3+
let x = {};
4+
x as T;
5+
}
6+
7+
function bar<T extends unknown>() {
8+
let x = {};
9+
x as T;
10+
}
11+
12+
function baz<T extends {}>() {
13+
let x = {};
14+
x as T;
15+
}
16+
17+
function bat<T extends object>() {
18+
let x = {};
19+
x as T;
20+
}
21+
22+
function no<T extends null | undefined>() {
23+
let x = {};
24+
x as T; // should error
25+
}
26+
27+
function yes<T extends object | null | undefined>() {
28+
let x = {};
29+
x as T;
30+
}
31+
32+
//// [genericWithNoConstraintComparableWithCurlyCurly.js]
33+
"use strict";
34+
function foo() {
35+
var x = {};
36+
x;
37+
}
38+
function bar() {
39+
var x = {};
40+
x;
41+
}
42+
function baz() {
43+
var x = {};
44+
x;
45+
}
46+
function bat() {
47+
var x = {};
48+
x;
49+
}
50+
function no() {
51+
var x = {};
52+
x; // should error
53+
}
54+
function yes() {
55+
var x = {};
56+
x;
57+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
=== tests/cases/compiler/genericWithNoConstraintComparableWithCurlyCurly.ts ===
2+
function foo<T>() {
3+
>foo : Symbol(foo, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 0, 0))
4+
>T : Symbol(T, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 0, 13))
5+
6+
let x = {};
7+
>x : Symbol(x, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 1, 7))
8+
9+
x as T;
10+
>x : Symbol(x, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 1, 7))
11+
>T : Symbol(T, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 0, 13))
12+
}
13+
14+
function bar<T extends unknown>() {
15+
>bar : Symbol(bar, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 3, 1))
16+
>T : Symbol(T, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 5, 13))
17+
18+
let x = {};
19+
>x : Symbol(x, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 6, 7))
20+
21+
x as T;
22+
>x : Symbol(x, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 6, 7))
23+
>T : Symbol(T, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 5, 13))
24+
}
25+
26+
function baz<T extends {}>() {
27+
>baz : Symbol(baz, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 8, 1))
28+
>T : Symbol(T, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 10, 13))
29+
30+
let x = {};
31+
>x : Symbol(x, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 11, 7))
32+
33+
x as T;
34+
>x : Symbol(x, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 11, 7))
35+
>T : Symbol(T, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 10, 13))
36+
}
37+
38+
function bat<T extends object>() {
39+
>bat : Symbol(bat, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 13, 1))
40+
>T : Symbol(T, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 15, 13))
41+
42+
let x = {};
43+
>x : Symbol(x, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 16, 7))
44+
45+
x as T;
46+
>x : Symbol(x, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 16, 7))
47+
>T : Symbol(T, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 15, 13))
48+
}
49+
50+
function no<T extends null | undefined>() {
51+
>no : Symbol(no, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 18, 1))
52+
>T : Symbol(T, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 20, 12))
53+
54+
let x = {};
55+
>x : Symbol(x, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 21, 7))
56+
57+
x as T; // should error
58+
>x : Symbol(x, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 21, 7))
59+
>T : Symbol(T, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 20, 12))
60+
}
61+
62+
function yes<T extends object | null | undefined>() {
63+
>yes : Symbol(yes, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 23, 1))
64+
>T : Symbol(T, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 25, 13))
65+
66+
let x = {};
67+
>x : Symbol(x, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 26, 7))
68+
69+
x as T;
70+
>x : Symbol(x, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 26, 7))
71+
>T : Symbol(T, Decl(genericWithNoConstraintComparableWithCurlyCurly.ts, 25, 13))
72+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
=== tests/cases/compiler/genericWithNoConstraintComparableWithCurlyCurly.ts ===
2+
function foo<T>() {
3+
>foo : <T>() => void
4+
5+
let x = {};
6+
>x : {}
7+
>{} : {}
8+
9+
x as T;
10+
>x as T : T
11+
>x : {}
12+
}
13+
14+
function bar<T extends unknown>() {
15+
>bar : <T extends unknown>() => void
16+
17+
let x = {};
18+
>x : {}
19+
>{} : {}
20+
21+
x as T;
22+
>x as T : T
23+
>x : {}
24+
}
25+
26+
function baz<T extends {}>() {
27+
>baz : <T extends {}>() => void
28+
29+
let x = {};
30+
>x : {}
31+
>{} : {}
32+
33+
x as T;
34+
>x as T : T
35+
>x : {}
36+
}
37+
38+
function bat<T extends object>() {
39+
>bat : <T extends object>() => void
40+
41+
let x = {};
42+
>x : {}
43+
>{} : {}
44+
45+
x as T;
46+
>x as T : T
47+
>x : {}
48+
}
49+
50+
function no<T extends null | undefined>() {
51+
>no : <T extends null | undefined>() => void
52+
>null : null
53+
54+
let x = {};
55+
>x : {}
56+
>{} : {}
57+
58+
x as T; // should error
59+
>x as T : T
60+
>x : {}
61+
}
62+
63+
function yes<T extends object | null | undefined>() {
64+
>yes : <T extends object | null | undefined>() => void
65+
>null : null
66+
67+
let x = {};
68+
>x : {}
69+
>{} : {}
70+
71+
x as T;
72+
>x as T : T
73+
>x : {}
74+
}

tests/baselines/reference/intersectionNarrowing.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ function f4<T>(x: T & 1 | T & 2) {
5959

6060
case 1: x; break; // T & 1
6161
>1 : 1
62-
>x : T & 1
62+
>x : (T & 1) | (T & 2)
6363

6464
case 2: x; break; // T & 2
6565
>2 : 2
66-
>x : T & 2
66+
>x : (T & 1) | (T & 2)
6767

6868
default: x; // Should narrow to never
6969
>x : never

tests/baselines/reference/unknownType1.errors.txt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ tests/cases/conformance/types/unknown/unknownType1.ts(112,9): error TS2322: Type
1616
tests/cases/conformance/types/unknown/unknownType1.ts(113,9): error TS2322: Type 'unknown' is not assignable to type '{}'.
1717
tests/cases/conformance/types/unknown/unknownType1.ts(114,9): error TS2322: Type 'unknown' is not assignable to type '{} | null | undefined'.
1818
tests/cases/conformance/types/unknown/unknownType1.ts(120,9): error TS2322: Type 'T' is not assignable to type 'object'.
19-
Type 'unknown' is not assignable to type 'object'.
2019
tests/cases/conformance/types/unknown/unknownType1.ts(128,5): error TS2322: Type 'number[]' is not assignable to type '{ [x: string]: unknown; }'.
2120
Index signature for type 'string' is missing in type 'number[]'.
2221
tests/cases/conformance/types/unknown/unknownType1.ts(129,5): error TS2322: Type 'number' is not assignable to type '{ [x: string]: unknown; }'.
@@ -26,9 +25,7 @@ tests/cases/conformance/types/unknown/unknownType1.ts(150,17): error TS2355: A f
2625
tests/cases/conformance/types/unknown/unknownType1.ts(156,14): error TS2700: Rest types may only be created from object types.
2726
tests/cases/conformance/types/unknown/unknownType1.ts(162,5): error TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor.
2827
tests/cases/conformance/types/unknown/unknownType1.ts(171,9): error TS2322: Type 'U' is not assignable to type '{}'.
29-
Type 'unknown' is not assignable to type '{}'.
3028
tests/cases/conformance/types/unknown/unknownType1.ts(181,5): error TS2322: Type 'T' is not assignable to type '{}'.
31-
Type 'unknown' is not assignable to type '{}'.
3229

3330

3431
==== tests/cases/conformance/types/unknown/unknownType1.ts (27 errors) ====
@@ -188,7 +185,6 @@ tests/cases/conformance/types/unknown/unknownType1.ts(181,5): error TS2322: Type
188185
let y: object = x; // Error
189186
~
190187
!!! error TS2322: Type 'T' is not assignable to type 'object'.
191-
!!! error TS2322: Type 'unknown' is not assignable to type 'object'.
192188
}
193189

194190
// Anything fresh but primitive assignable to { [x: string]: unknown }
@@ -257,7 +253,6 @@ tests/cases/conformance/types/unknown/unknownType1.ts(181,5): error TS2322: Type
257253
let y: {} = u;
258254
~
259255
!!! error TS2322: Type 'U' is not assignable to type '{}'.
260-
!!! error TS2322: Type 'unknown' is not assignable to type '{}'.
261256
}
262257

263258
// Repro from #26796
@@ -270,6 +265,5 @@ tests/cases/conformance/types/unknown/unknownType1.ts(181,5): error TS2322: Type
270265
return arg; // Error
271266
~~~~~~~~~~~
272267
!!! error TS2322: Type 'T' is not assignable to type '{}'.
273-
!!! error TS2322: Type 'unknown' is not assignable to type '{}'.
274268
}
275269

tests/baselines/reference/variadicTuples2.errors.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ tests/cases/conformance/types/tuple/variadicTuples2.ts(73,5): error TS2322: Type
4444
tests/cases/conformance/types/tuple/variadicTuples2.ts(74,5): error TS2322: Type '[number, ...T]' is not assignable to type '[number, ...number[]]'.
4545
Type at position 1 in source is not compatible with type at position 1 in target.
4646
Type 'T[number]' is not assignable to type 'number'.
47-
Type 'unknown' is not assignable to type 'number'.
4847
tests/cases/conformance/types/tuple/variadicTuples2.ts(107,16): error TS2345: Argument of type '[1, 2, 3, 4]' is not assignable to parameter of type '[...number[], (...values: number[]) => void]'.
4948
Type at position 3 in source is not compatible with type at position 1 in target.
5049
Type 'number' is not assignable to type '(...values: number[]) => void'.
@@ -206,7 +205,6 @@ tests/cases/conformance/types/tuple/variadicTuples2.ts(130,25): error TS2345: Ar
206205
!!! error TS2322: Type '[number, ...T]' is not assignable to type '[number, ...number[]]'.
207206
!!! error TS2322: Type at position 1 in source is not compatible with type at position 1 in target.
208207
!!! error TS2322: Type 'T[number]' is not assignable to type 'number'.
209-
!!! error TS2322: Type 'unknown' is not assignable to type 'number'.
210208
}
211209

212210
// Inference
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// @strict: true
2+
function foo<T>() {
3+
let x = {};
4+
x as T;
5+
}
6+
7+
function bar<T extends unknown>() {
8+
let x = {};
9+
x as T;
10+
}
11+
12+
function baz<T extends {}>() {
13+
let x = {};
14+
x as T;
15+
}
16+
17+
function bat<T extends object>() {
18+
let x = {};
19+
x as T;
20+
}
21+
22+
function no<T extends null | undefined>() {
23+
let x = {};
24+
x as T; // should error
25+
}
26+
27+
function yes<T extends object | null | undefined>() {
28+
let x = {};
29+
x as T;
30+
}

0 commit comments

Comments
 (0)