Skip to content

Commit 106e727

Browse files
DanielRosenwassermprobst
authored andcommitted
Check for array types when instantiating mapped type constraints with any (microsoft#46218)
* Added/updated tests. * Accepted baselines. * Update test. * Update instantiateMappedType to work specially when 'any' replaced an array. * Accepted baselines. * Ensure check works when constraint is a union of arrayish types, just like in `Promise.all`. * Accepted baselines. * Update test for indirect instantiation of a mapped type. * Accepted baselines. * Update test comment. * Accepted baselines. * Added tuple test case. * Accepted baselines. * Don't add special behavior for tuples. * Accepted baselines. * Revert "Don't add special behavior for tuples." This reverts commit f01ae16. * Accepted baselines.
1 parent 3da1737 commit 106e727

File tree

9 files changed

+324
-4
lines changed

9 files changed

+324
-4
lines changed

src/compiler/checker.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -16570,7 +16570,8 @@ namespace ts {
1657016570
return mapTypeWithAlias(getReducedType(mappedTypeVariable), t => {
1657116571
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && !isErrorType(t)) {
1657216572
if (!type.declaration.nameType) {
16573-
if (isArrayType(t)) {
16573+
let constraint;
16574+
if (isArrayType(t) || (t.flags & TypeFlags.Any) && (constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, or(isArrayType, isTupleType))) {
1657416575
return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper));
1657516576
}
1657616577
if (isGenericTupleType(t)) {

tests/baselines/reference/mappedTypeWithAny.errors.txt

+41-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
tests/cases/conformance/types/mapped/mappedTypeWithAny.ts(23,16): error TS2339: Property 'notAValue' does not exist on type 'Data'.
2+
tests/cases/conformance/types/mapped/mappedTypeWithAny.ts(45,5): error TS2740: Type 'Objectish<any>' is missing the following properties from type 'any[]': length, pop, push, concat, and 16 more.
3+
tests/cases/conformance/types/mapped/mappedTypeWithAny.ts(46,5): error TS2322: Type 'Objectish<any>' is not assignable to type 'any[]'.
4+
tests/cases/conformance/types/mapped/mappedTypeWithAny.ts(53,5): error TS2322: Type 'string[]' is not assignable to type '[any, any]'.
5+
Target requires 2 element(s) but source may have fewer.
26

37

4-
==== tests/cases/conformance/types/mapped/mappedTypeWithAny.ts (1 errors) ====
8+
==== tests/cases/conformance/types/mapped/mappedTypeWithAny.ts (4 errors) ====
59
type Item = { value: string };
610
type ItemMap<T> = { [P in keyof T]: Item };
711

@@ -28,4 +32,39 @@ tests/cases/conformance/types/mapped/mappedTypeWithAny.ts(23,16): error TS2339:
2832
~~~~~~~~~
2933
!!! error TS2339: Property 'notAValue' does not exist on type 'Data'.
3034
}
31-
35+
36+
// Issue #46169.
37+
// We want mapped types whose constraint is `keyof T` to
38+
// map over `any` differently, depending on whether `T`
39+
// is constrained to array and tuple types.
40+
type Arrayish<T extends unknown[]> = { [K in keyof T]: T[K] };
41+
type Objectish<T extends unknown> = { [K in keyof T]: T[K] };
42+
43+
// When a mapped type whose constraint is `keyof T` is instantiated,
44+
// `T` may be instantiated with a `U` which is constrained to
45+
// array and tuple types. *Ideally*, when `U` is later instantiated with `any`,
46+
// the result should also be some sort of array; however, at the moment we don't seem
47+
// to have an easy way to preserve that information. More than just that, it would be
48+
// inconsistent for two instantiations of `Objectish<any>` to produce different outputs
49+
// depending on the usage-site. As a result, `IndirectArrayish` does not act like `Arrayish`.
50+
type IndirectArrayish<U extends unknown[]> = Objectish<U>;
51+
52+
function bar(arrayish: Arrayish<any>, objectish: Objectish<any>, indirectArrayish: IndirectArrayish<any>) {
53+
let arr: any[];
54+
arr = arrayish;
55+
arr = objectish;
56+
~~~
57+
!!! error TS2740: Type 'Objectish<any>' is missing the following properties from type 'any[]': length, pop, push, concat, and 16 more.
58+
arr = indirectArrayish;
59+
~~~
60+
!!! error TS2322: Type 'Objectish<any>' is not assignable to type 'any[]'.
61+
}
62+
63+
declare function stringifyArray<T extends readonly any[]>(arr: T): { -readonly [K in keyof T]: string };
64+
let abc: any[] = stringifyArray(void 0 as any);
65+
66+
declare function stringifyPair<T extends readonly [any, any]>(arr: T): { -readonly [K in keyof T]: string };
67+
let def: [any, any] = stringifyPair(void 0 as any);
68+
~~~
69+
!!! error TS2322: Type 'string[]' is not assignable to type '[any, any]'.
70+
!!! error TS2322: Target requires 2 element(s) but source may have fewer.

tests/baselines/reference/mappedTypeWithAny.js

+53-1
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,50 @@ for (let id in z) {
2323
let data = z[id];
2424
let x = data.notAValue; // Error
2525
}
26-
26+
27+
// Issue #46169.
28+
// We want mapped types whose constraint is `keyof T` to
29+
// map over `any` differently, depending on whether `T`
30+
// is constrained to array and tuple types.
31+
type Arrayish<T extends unknown[]> = { [K in keyof T]: T[K] };
32+
type Objectish<T extends unknown> = { [K in keyof T]: T[K] };
33+
34+
// When a mapped type whose constraint is `keyof T` is instantiated,
35+
// `T` may be instantiated with a `U` which is constrained to
36+
// array and tuple types. *Ideally*, when `U` is later instantiated with `any`,
37+
// the result should also be some sort of array; however, at the moment we don't seem
38+
// to have an easy way to preserve that information. More than just that, it would be
39+
// inconsistent for two instantiations of `Objectish<any>` to produce different outputs
40+
// depending on the usage-site. As a result, `IndirectArrayish` does not act like `Arrayish`.
41+
type IndirectArrayish<U extends unknown[]> = Objectish<U>;
42+
43+
function bar(arrayish: Arrayish<any>, objectish: Objectish<any>, indirectArrayish: IndirectArrayish<any>) {
44+
let arr: any[];
45+
arr = arrayish;
46+
arr = objectish;
47+
arr = indirectArrayish;
48+
}
49+
50+
declare function stringifyArray<T extends readonly any[]>(arr: T): { -readonly [K in keyof T]: string };
51+
let abc: any[] = stringifyArray(void 0 as any);
52+
53+
declare function stringifyPair<T extends readonly [any, any]>(arr: T): { -readonly [K in keyof T]: string };
54+
let def: [any, any] = stringifyPair(void 0 as any);
2755

2856
//// [mappedTypeWithAny.js]
2957
"use strict";
3058
for (var id in z) {
3159
var data = z[id];
3260
var x = data.notAValue; // Error
3361
}
62+
function bar(arrayish, objectish, indirectArrayish) {
63+
var arr;
64+
arr = arrayish;
65+
arr = objectish;
66+
arr = indirectArrayish;
67+
}
68+
var abc = stringifyArray(void 0);
69+
var def = stringifyPair(void 0);
3470

3571

3672
//// [mappedTypeWithAny.d.ts]
@@ -58,3 +94,19 @@ declare type StrictDataMap<T> = {
5894
[P in keyof T]: Data;
5995
};
6096
declare let z: StrictDataMap<any>;
97+
declare type Arrayish<T extends unknown[]> = {
98+
[K in keyof T]: T[K];
99+
};
100+
declare type Objectish<T extends unknown> = {
101+
[K in keyof T]: T[K];
102+
};
103+
declare type IndirectArrayish<U extends unknown[]> = Objectish<U>;
104+
declare function bar(arrayish: Arrayish<any>, objectish: Objectish<any>, indirectArrayish: IndirectArrayish<any>): void;
105+
declare function stringifyArray<T extends readonly any[]>(arr: T): {
106+
-readonly [K in keyof T]: string;
107+
};
108+
declare let abc: any[];
109+
declare function stringifyPair<T extends readonly [any, any]>(arr: T): {
110+
-readonly [K in keyof T]: string;
111+
};
112+
declare let def: [any, any];

tests/baselines/reference/mappedTypeWithAny.symbols

+82
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,85 @@ for (let id in z) {
6969
>data : Symbol(data, Decl(mappedTypeWithAny.ts, 21, 5))
7070
}
7171

72+
// Issue #46169.
73+
// We want mapped types whose constraint is `keyof T` to
74+
// map over `any` differently, depending on whether `T`
75+
// is constrained to array and tuple types.
76+
type Arrayish<T extends unknown[]> = { [K in keyof T]: T[K] };
77+
>Arrayish : Symbol(Arrayish, Decl(mappedTypeWithAny.ts, 23, 1))
78+
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 29, 14))
79+
>K : Symbol(K, Decl(mappedTypeWithAny.ts, 29, 40))
80+
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 29, 14))
81+
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 29, 14))
82+
>K : Symbol(K, Decl(mappedTypeWithAny.ts, 29, 40))
83+
84+
type Objectish<T extends unknown> = { [K in keyof T]: T[K] };
85+
>Objectish : Symbol(Objectish, Decl(mappedTypeWithAny.ts, 29, 62))
86+
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 30, 15))
87+
>K : Symbol(K, Decl(mappedTypeWithAny.ts, 30, 39))
88+
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 30, 15))
89+
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 30, 15))
90+
>K : Symbol(K, Decl(mappedTypeWithAny.ts, 30, 39))
91+
92+
// When a mapped type whose constraint is `keyof T` is instantiated,
93+
// `T` may be instantiated with a `U` which is constrained to
94+
// array and tuple types. *Ideally*, when `U` is later instantiated with `any`,
95+
// the result should also be some sort of array; however, at the moment we don't seem
96+
// to have an easy way to preserve that information. More than just that, it would be
97+
// inconsistent for two instantiations of `Objectish<any>` to produce different outputs
98+
// depending on the usage-site. As a result, `IndirectArrayish` does not act like `Arrayish`.
99+
type IndirectArrayish<U extends unknown[]> = Objectish<U>;
100+
>IndirectArrayish : Symbol(IndirectArrayish, Decl(mappedTypeWithAny.ts, 30, 61))
101+
>U : Symbol(U, Decl(mappedTypeWithAny.ts, 39, 22))
102+
>Objectish : Symbol(Objectish, Decl(mappedTypeWithAny.ts, 29, 62))
103+
>U : Symbol(U, Decl(mappedTypeWithAny.ts, 39, 22))
104+
105+
function bar(arrayish: Arrayish<any>, objectish: Objectish<any>, indirectArrayish: IndirectArrayish<any>) {
106+
>bar : Symbol(bar, Decl(mappedTypeWithAny.ts, 39, 58))
107+
>arrayish : Symbol(arrayish, Decl(mappedTypeWithAny.ts, 41, 13))
108+
>Arrayish : Symbol(Arrayish, Decl(mappedTypeWithAny.ts, 23, 1))
109+
>objectish : Symbol(objectish, Decl(mappedTypeWithAny.ts, 41, 37))
110+
>Objectish : Symbol(Objectish, Decl(mappedTypeWithAny.ts, 29, 62))
111+
>indirectArrayish : Symbol(indirectArrayish, Decl(mappedTypeWithAny.ts, 41, 64))
112+
>IndirectArrayish : Symbol(IndirectArrayish, Decl(mappedTypeWithAny.ts, 30, 61))
113+
114+
let arr: any[];
115+
>arr : Symbol(arr, Decl(mappedTypeWithAny.ts, 42, 7))
116+
117+
arr = arrayish;
118+
>arr : Symbol(arr, Decl(mappedTypeWithAny.ts, 42, 7))
119+
>arrayish : Symbol(arrayish, Decl(mappedTypeWithAny.ts, 41, 13))
120+
121+
arr = objectish;
122+
>arr : Symbol(arr, Decl(mappedTypeWithAny.ts, 42, 7))
123+
>objectish : Symbol(objectish, Decl(mappedTypeWithAny.ts, 41, 37))
124+
125+
arr = indirectArrayish;
126+
>arr : Symbol(arr, Decl(mappedTypeWithAny.ts, 42, 7))
127+
>indirectArrayish : Symbol(indirectArrayish, Decl(mappedTypeWithAny.ts, 41, 64))
128+
}
129+
130+
declare function stringifyArray<T extends readonly any[]>(arr: T): { -readonly [K in keyof T]: string };
131+
>stringifyArray : Symbol(stringifyArray, Decl(mappedTypeWithAny.ts, 46, 1))
132+
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 48, 32))
133+
>arr : Symbol(arr, Decl(mappedTypeWithAny.ts, 48, 58))
134+
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 48, 32))
135+
>K : Symbol(K, Decl(mappedTypeWithAny.ts, 48, 80))
136+
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 48, 32))
137+
138+
let abc: any[] = stringifyArray(void 0 as any);
139+
>abc : Symbol(abc, Decl(mappedTypeWithAny.ts, 49, 3))
140+
>stringifyArray : Symbol(stringifyArray, Decl(mappedTypeWithAny.ts, 46, 1))
141+
142+
declare function stringifyPair<T extends readonly [any, any]>(arr: T): { -readonly [K in keyof T]: string };
143+
>stringifyPair : Symbol(stringifyPair, Decl(mappedTypeWithAny.ts, 49, 47))
144+
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 51, 31))
145+
>arr : Symbol(arr, Decl(mappedTypeWithAny.ts, 51, 62))
146+
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 51, 31))
147+
>K : Symbol(K, Decl(mappedTypeWithAny.ts, 51, 84))
148+
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 51, 31))
149+
150+
let def: [any, any] = stringifyPair(void 0 as any);
151+
>def : Symbol(def, Decl(mappedTypeWithAny.ts, 52, 3))
152+
>stringifyPair : Symbol(stringifyPair, Decl(mappedTypeWithAny.ts, 49, 47))
153+

tests/baselines/reference/mappedTypeWithAny.types

+69
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,72 @@ for (let id in z) {
5656
>notAValue : any
5757
}
5858

59+
// Issue #46169.
60+
// We want mapped types whose constraint is `keyof T` to
61+
// map over `any` differently, depending on whether `T`
62+
// is constrained to array and tuple types.
63+
type Arrayish<T extends unknown[]> = { [K in keyof T]: T[K] };
64+
>Arrayish : Arrayish<T>
65+
66+
type Objectish<T extends unknown> = { [K in keyof T]: T[K] };
67+
>Objectish : Objectish<T>
68+
69+
// When a mapped type whose constraint is `keyof T` is instantiated,
70+
// `T` may be instantiated with a `U` which is constrained to
71+
// array and tuple types. *Ideally*, when `U` is later instantiated with `any`,
72+
// the result should also be some sort of array; however, at the moment we don't seem
73+
// to have an easy way to preserve that information. More than just that, it would be
74+
// inconsistent for two instantiations of `Objectish<any>` to produce different outputs
75+
// depending on the usage-site. As a result, `IndirectArrayish` does not act like `Arrayish`.
76+
type IndirectArrayish<U extends unknown[]> = Objectish<U>;
77+
>IndirectArrayish : Objectish<U>
78+
79+
function bar(arrayish: Arrayish<any>, objectish: Objectish<any>, indirectArrayish: IndirectArrayish<any>) {
80+
>bar : (arrayish: Arrayish<any>, objectish: Objectish<any>, indirectArrayish: IndirectArrayish<any>) => void
81+
>arrayish : any[]
82+
>objectish : Objectish<any>
83+
>indirectArrayish : Objectish<any>
84+
85+
let arr: any[];
86+
>arr : any[]
87+
88+
arr = arrayish;
89+
>arr = arrayish : any[]
90+
>arr : any[]
91+
>arrayish : any[]
92+
93+
arr = objectish;
94+
>arr = objectish : Objectish<any>
95+
>arr : any[]
96+
>objectish : Objectish<any>
97+
98+
arr = indirectArrayish;
99+
>arr = indirectArrayish : Objectish<any>
100+
>arr : any[]
101+
>indirectArrayish : Objectish<any>
102+
}
103+
104+
declare function stringifyArray<T extends readonly any[]>(arr: T): { -readonly [K in keyof T]: string };
105+
>stringifyArray : <T extends readonly any[]>(arr: T) => { -readonly [K in keyof T]: string; }
106+
>arr : T
107+
108+
let abc: any[] = stringifyArray(void 0 as any);
109+
>abc : any[]
110+
>stringifyArray(void 0 as any) : string[]
111+
>stringifyArray : <T extends readonly any[]>(arr: T) => { -readonly [K in keyof T]: string; }
112+
>void 0 as any : any
113+
>void 0 : undefined
114+
>0 : 0
115+
116+
declare function stringifyPair<T extends readonly [any, any]>(arr: T): { -readonly [K in keyof T]: string };
117+
>stringifyPair : <T extends readonly [any, any]>(arr: T) => { -readonly [K in keyof T]: string; }
118+
>arr : T
119+
120+
let def: [any, any] = stringifyPair(void 0 as any);
121+
>def : [any, any]
122+
>stringifyPair(void 0 as any) : string[]
123+
>stringifyPair : <T extends readonly [any, any]>(arr: T) => { -readonly [K in keyof T]: string; }
124+
>void 0 as any : any
125+
>void 0 : undefined
126+
>0 : 0
127+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
=== tests/cases/compiler/promiseAllOnAny01.ts ===
2+
async function foo(x: any) {
3+
>foo : Symbol(foo, Decl(promiseAllOnAny01.ts, 0, 0))
4+
>x : Symbol(x, Decl(promiseAllOnAny01.ts, 0, 19))
5+
6+
let abc = await Promise.all(x);
7+
>abc : Symbol(abc, Decl(promiseAllOnAny01.ts, 1, 7))
8+
>Promise.all : Symbol(PromiseConstructor.all, Decl(lib.es2015.promise.d.ts, --, --))
9+
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
10+
>all : Symbol(PromiseConstructor.all, Decl(lib.es2015.promise.d.ts, --, --))
11+
>x : Symbol(x, Decl(promiseAllOnAny01.ts, 0, 19))
12+
13+
let result: any[] = abc;
14+
>result : Symbol(result, Decl(promiseAllOnAny01.ts, 2, 7))
15+
>abc : Symbol(abc, Decl(promiseAllOnAny01.ts, 1, 7))
16+
17+
return result;
18+
>result : Symbol(result, Decl(promiseAllOnAny01.ts, 2, 7))
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
=== tests/cases/compiler/promiseAllOnAny01.ts ===
2+
async function foo(x: any) {
3+
>foo : (x: any) => Promise<any[]>
4+
>x : any
5+
6+
let abc = await Promise.all(x);
7+
>abc : any[]
8+
>await Promise.all(x) : any[]
9+
>Promise.all(x) : Promise<any[]>
10+
>Promise.all : <T extends readonly unknown[] | []>(values: T) => Promise<{ -readonly [P in keyof T]: Awaited<T[P]>; }>
11+
>Promise : PromiseConstructor
12+
>all : <T extends readonly unknown[] | []>(values: T) => Promise<{ -readonly [P in keyof T]: Awaited<T[P]>; }>
13+
>x : any
14+
15+
let result: any[] = abc;
16+
>result : any[]
17+
>abc : any[]
18+
19+
return result;
20+
>result : any[]
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// @noEmit: true
2+
// @lib: es5,es2015.promise
3+
4+
async function foo(x: any) {
5+
let abc = await Promise.all(x);
6+
let result: any[] = abc;
7+
return result;
8+
}

tests/cases/conformance/types/mapped/mappedTypeWithAny.ts

+29
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,32 @@ for (let id in z) {
2525
let data = z[id];
2626
let x = data.notAValue; // Error
2727
}
28+
29+
// Issue #46169.
30+
// We want mapped types whose constraint is `keyof T` to
31+
// map over `any` differently, depending on whether `T`
32+
// is constrained to array and tuple types.
33+
type Arrayish<T extends unknown[]> = { [K in keyof T]: T[K] };
34+
type Objectish<T extends unknown> = { [K in keyof T]: T[K] };
35+
36+
// When a mapped type whose constraint is `keyof T` is instantiated,
37+
// `T` may be instantiated with a `U` which is constrained to
38+
// array and tuple types. *Ideally*, when `U` is later instantiated with `any`,
39+
// the result should also be some sort of array; however, at the moment we don't seem
40+
// to have an easy way to preserve that information. More than just that, it would be
41+
// inconsistent for two instantiations of `Objectish<any>` to produce different outputs
42+
// depending on the usage-site. As a result, `IndirectArrayish` does not act like `Arrayish`.
43+
type IndirectArrayish<U extends unknown[]> = Objectish<U>;
44+
45+
function bar(arrayish: Arrayish<any>, objectish: Objectish<any>, indirectArrayish: IndirectArrayish<any>) {
46+
let arr: any[];
47+
arr = arrayish;
48+
arr = objectish;
49+
arr = indirectArrayish;
50+
}
51+
52+
declare function stringifyArray<T extends readonly any[]>(arr: T): { -readonly [K in keyof T]: string };
53+
let abc: any[] = stringifyArray(void 0 as any);
54+
55+
declare function stringifyPair<T extends readonly [any, any]>(arr: T): { -readonly [K in keyof T]: string };
56+
let def: [any, any] = stringifyPair(void 0 as any);

0 commit comments

Comments
 (0)