Skip to content

Commit a591a42

Browse files
DanielRosenwassertypescript-bot
authored andcommitted
Cherry-pick PR microsoft#53388 into release-5.0
Component commits: e2aa5c6 Exclude special index signature rule from strict subtype relation ae63c73 Add tests 54703cd Add more test cases. 37911f8 Accepted baselines.
1 parent 5348903 commit a591a42

File tree

6 files changed

+741
-69
lines changed

6 files changed

+741
-69
lines changed

src/compiler/checker.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -22357,7 +22357,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2235722357
const targetHasStringIndex = some(indexInfos, info => info.keyType === stringType);
2235822358
let result = Ternary.True;
2235922359
for (const targetInfo of indexInfos) {
22360-
const related = !sourceIsPrimitive && targetHasStringIndex && targetInfo.type.flags & TypeFlags.Any ? Ternary.True :
22360+
const related = relation !== strictSubtypeRelation && !sourceIsPrimitive && targetHasStringIndex && targetInfo.type.flags & TypeFlags.Any ? Ternary.True :
2236122361
isGenericMappedType(source) && targetHasStringIndex ? isRelatedTo(getTemplateTypeFromMappedType(source), targetInfo.type, RecursionFlags.Both, reportErrors) :
2236222362
typeRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState);
2236322363
if (!related) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
tests/cases/compiler/narrowingMutualSubtypes.ts(117,17): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'any[] | Record<string, any>'.
2+
No index signature with a parameter of type 'string' was found on type 'any[] | Record<string, any>'.
3+
tests/cases/compiler/narrowingMutualSubtypes.ts(118,29): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'any[] | Record<string, any>'.
4+
No index signature with a parameter of type 'string' was found on type 'any[] | Record<string, any>'.
5+
6+
7+
==== tests/cases/compiler/narrowingMutualSubtypes.ts (2 errors) ====
8+
// Check that `any` is a strict supertype of `unknown`
9+
10+
declare const ru1: { [x: string]: unknown };
11+
declare const ra1: { [x: string]: any };
12+
13+
const a1a = [ru1, ra1]; // { [x: string]: any }[]
14+
const a1b = [ra1, ru1]; // { [x: string]: any }[]
15+
16+
declare const ra2: { [x: string]: any };
17+
declare const ru2: { [x: string]: unknown };
18+
19+
const a2a = [ru2, ra2]; // { [x: string]: any }[]
20+
const a2b = [ra2, ru2]; // { [x: string]: any }[]
21+
22+
// Check that `{}` is strict supertype of any non-empty object
23+
24+
const c3 = {};
25+
declare const r3: { [x: string]: unknown }
26+
27+
const a3a = [c3, r3]; // {}[]
28+
const a3b = [r3, c3]; // {}[]
29+
30+
declare const r4: { [x: string]: unknown }
31+
const c4 = {};
32+
33+
const a4a = [c4, r4]; // {}[]
34+
const a4b = [r4, c4]; // {}[]
35+
36+
// Check that {} is a strict supertype of Record<string, unknown>
37+
38+
declare function isObject1(value: unknown): value is Record<string, unknown>;
39+
40+
function gg1(x: {}) {
41+
if (isObject1(x)) {
42+
x; // Record<string, unknown>
43+
}
44+
else {
45+
x; // {}
46+
}
47+
x; // {}
48+
}
49+
50+
declare function isObject2(value: unknown): value is {};
51+
52+
function gg2(x: Record<string, unknown>) {
53+
if (isObject2(x)) {
54+
x; // Record<string, unknown>
55+
}
56+
else {
57+
x; // never
58+
}
59+
x; // Record<string, unknown>
60+
}
61+
62+
// Check that {} is a strict supertype of Record<string, any>
63+
64+
declare function isObject3(value: unknown): value is Record<string, any>;
65+
66+
function gg3(x: {}) {
67+
if (isObject3(x)) {
68+
x; // Record<string, any>
69+
}
70+
else {
71+
x; // {}
72+
}
73+
x; // {}
74+
}
75+
76+
declare function isObject4(value: unknown): value is {};
77+
78+
function gg4(x: Record<string, any>) {
79+
if (isObject4(x)) {
80+
x; // Record<string, any>
81+
}
82+
else {
83+
x; // never
84+
}
85+
x; // Record<string, any>
86+
}
87+
88+
// Repro from #50916
89+
90+
type Identity<T> = {[K in keyof T]: T[K]};
91+
92+
type Self<T> = T extends unknown ? Identity<T> : never;
93+
94+
function is<T>(value: T): value is Self<T> {
95+
return true;
96+
}
97+
98+
type Union = {a: number} | {b: number} | {c: number};
99+
100+
function example(x: Union) {
101+
if (is(x)) {}
102+
if (is(x)) {}
103+
if (is(x)) {}
104+
if (is(x)) {}
105+
if (is(x)) {}
106+
if (is(x)) {}
107+
if (is(x)) {}
108+
if (is(x)) {}
109+
x; // Union
110+
}
111+
112+
function checksArrayOrObject1(obj: Record<string, any> | Record<string, any>[]) {
113+
// "accidentally" guards the first branch on the length
114+
if (Array.isArray(obj) && obj.length) {
115+
for (let key in obj) {
116+
if (obj[key] !== undefined) {
117+
console.log(obj[key])
118+
}
119+
}
120+
}
121+
else {
122+
// 'obj' should probably not include an array type here.
123+
for (let key in obj) {
124+
if (obj[key] !== undefined) {
125+
~~~~~~~~
126+
!!! error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'any[] | Record<string, any>'.
127+
!!! error TS7053: No index signature with a parameter of type 'string' was found on type 'any[] | Record<string, any>'.
128+
console.log(obj[key])
129+
~~~~~~~~
130+
!!! error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'any[] | Record<string, any>'.
131+
!!! error TS7053: No index signature with a parameter of type 'string' was found on type 'any[] | Record<string, any>'.
132+
}
133+
}
134+
}
135+
}
136+
137+
function checksArrayOrObject2(obj: Record<string, any> | Record<string, any>[]) {
138+
if (Array.isArray(obj)) {
139+
// obj should only be an array type here
140+
for (let key in obj) {
141+
if (obj[key] !== undefined) {
142+
console.log(obj[key])
143+
}
144+
}
145+
}
146+
else {
147+
// 'obj' should probably not include an array type here.
148+
for (let key in obj) {
149+
if (obj[key] !== undefined) {
150+
console.log(obj[key])
151+
}
152+
}
153+
}
154+
}
155+

tests/baselines/reference/narrowingMutualSubtypes.js

+125-7
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ const c4 = {};
2727
const a4a = [c4, r4]; // {}[]
2828
const a4b = [r4, c4]; // {}[]
2929

30-
// Check that narrowing preserves original type in false branch for non-identical mutual subtypes
30+
// Check that {} is a strict supertype of Record<string, unknown>
3131

3232
declare function isObject1(value: unknown): value is Record<string, unknown>;
3333

34-
function gg(x: {}) {
34+
function gg1(x: {}) {
3535
if (isObject1(x)) {
3636
x; // Record<string, unknown>
3737
}
@@ -45,14 +45,40 @@ declare function isObject2(value: unknown): value is {};
4545

4646
function gg2(x: Record<string, unknown>) {
4747
if (isObject2(x)) {
48-
x; // {}
48+
x; // Record<string, unknown>
4949
}
5050
else {
51-
x; // Record<string, unknown>
51+
x; // never
5252
}
5353
x; // Record<string, unknown>
5454
}
5555

56+
// Check that {} is a strict supertype of Record<string, any>
57+
58+
declare function isObject3(value: unknown): value is Record<string, any>;
59+
60+
function gg3(x: {}) {
61+
if (isObject3(x)) {
62+
x; // Record<string, any>
63+
}
64+
else {
65+
x; // {}
66+
}
67+
x; // {}
68+
}
69+
70+
declare function isObject4(value: unknown): value is {};
71+
72+
function gg4(x: Record<string, any>) {
73+
if (isObject4(x)) {
74+
x; // Record<string, any>
75+
}
76+
else {
77+
x; // never
78+
}
79+
x; // Record<string, any>
80+
}
81+
5682
// Repro from #50916
5783

5884
type Identity<T> = {[K in keyof T]: T[K]};
@@ -76,6 +102,44 @@ function example(x: Union) {
76102
if (is(x)) {}
77103
x; // Union
78104
}
105+
106+
function checksArrayOrObject1(obj: Record<string, any> | Record<string, any>[]) {
107+
// "accidentally" guards the first branch on the length
108+
if (Array.isArray(obj) && obj.length) {
109+
for (let key in obj) {
110+
if (obj[key] !== undefined) {
111+
console.log(obj[key])
112+
}
113+
}
114+
}
115+
else {
116+
// 'obj' should probably not include an array type here.
117+
for (let key in obj) {
118+
if (obj[key] !== undefined) {
119+
console.log(obj[key])
120+
}
121+
}
122+
}
123+
}
124+
125+
function checksArrayOrObject2(obj: Record<string, any> | Record<string, any>[]) {
126+
if (Array.isArray(obj)) {
127+
// obj should only be an array type here
128+
for (let key in obj) {
129+
if (obj[key] !== undefined) {
130+
console.log(obj[key])
131+
}
132+
}
133+
}
134+
else {
135+
// 'obj' should probably not include an array type here.
136+
for (let key in obj) {
137+
if (obj[key] !== undefined) {
138+
console.log(obj[key])
139+
}
140+
}
141+
}
142+
}
79143

80144

81145
//// [narrowingMutualSubtypes.js]
@@ -92,7 +156,7 @@ var a3b = [r3, c3]; // {}[]
92156
var c4 = {};
93157
var a4a = [c4, r4]; // {}[]
94158
var a4b = [r4, c4]; // {}[]
95-
function gg(x) {
159+
function gg1(x) {
96160
if (isObject1(x)) {
97161
x; // Record<string, unknown>
98162
}
@@ -103,13 +167,31 @@ function gg(x) {
103167
}
104168
function gg2(x) {
105169
if (isObject2(x)) {
106-
x; // {}
170+
x; // Record<string, unknown>
107171
}
108172
else {
109-
x; // Record<string, unknown>
173+
x; // never
110174
}
111175
x; // Record<string, unknown>
112176
}
177+
function gg3(x) {
178+
if (isObject3(x)) {
179+
x; // Record<string, any>
180+
}
181+
else {
182+
x; // {}
183+
}
184+
x; // {}
185+
}
186+
function gg4(x) {
187+
if (isObject4(x)) {
188+
x; // Record<string, any>
189+
}
190+
else {
191+
x; // never
192+
}
193+
x; // Record<string, any>
194+
}
113195
function is(value) {
114196
return true;
115197
}
@@ -124,3 +206,39 @@ function example(x) {
124206
if (is(x)) { }
125207
x; // Union
126208
}
209+
function checksArrayOrObject1(obj) {
210+
// "accidentally" guards the first branch on the length
211+
if (Array.isArray(obj) && obj.length) {
212+
for (var key in obj) {
213+
if (obj[key] !== undefined) {
214+
console.log(obj[key]);
215+
}
216+
}
217+
}
218+
else {
219+
// 'obj' should probably not include an array type here.
220+
for (var key in obj) {
221+
if (obj[key] !== undefined) {
222+
console.log(obj[key]);
223+
}
224+
}
225+
}
226+
}
227+
function checksArrayOrObject2(obj) {
228+
if (Array.isArray(obj)) {
229+
// obj should only be an array type here
230+
for (var key in obj) {
231+
if (obj[key] !== undefined) {
232+
console.log(obj[key]);
233+
}
234+
}
235+
}
236+
else {
237+
// 'obj' should probably not include an array type here.
238+
for (var key in obj) {
239+
if (obj[key] !== undefined) {
240+
console.log(obj[key]);
241+
}
242+
}
243+
}
244+
}

0 commit comments

Comments
 (0)