Check for array types when instantiating mapped type constraints with any (#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 f01ae16e65.

* Accepted baselines.
This commit is contained in:
Daniel Rosenwasser 2021-10-27 15:03:01 -07:00 committed by GitHub
parent abfd537503
commit f494742ce9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 324 additions and 4 deletions

View file

@ -16570,7 +16570,8 @@ namespace ts {
return mapTypeWithAlias(getReducedType(mappedTypeVariable), t => {
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && !isErrorType(t)) {
if (!type.declaration.nameType) {
if (isArrayType(t)) {
let constraint;
if (isArrayType(t) || (t.flags & TypeFlags.Any) && (constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, or(isArrayType, isTupleType))) {
return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper));
}
if (isGenericTupleType(t)) {

View file

@ -1,7 +1,11 @@
tests/cases/conformance/types/mapped/mappedTypeWithAny.ts(23,16): error TS2339: Property 'notAValue' does not exist on type 'Data'.
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.
tests/cases/conformance/types/mapped/mappedTypeWithAny.ts(46,5): error TS2322: Type 'Objectish<any>' is not assignable to type 'any[]'.
tests/cases/conformance/types/mapped/mappedTypeWithAny.ts(53,5): error TS2322: Type 'string[]' is not assignable to type '[any, any]'.
Target requires 2 element(s) but source may have fewer.
==== tests/cases/conformance/types/mapped/mappedTypeWithAny.ts (1 errors) ====
==== tests/cases/conformance/types/mapped/mappedTypeWithAny.ts (4 errors) ====
type Item = { value: string };
type ItemMap<T> = { [P in keyof T]: Item };
@ -28,4 +32,39 @@ tests/cases/conformance/types/mapped/mappedTypeWithAny.ts(23,16): error TS2339:
~~~~~~~~~
!!! error TS2339: Property 'notAValue' does not exist on type 'Data'.
}
// Issue #46169.
// We want mapped types whose constraint is `keyof T` to
// map over `any` differently, depending on whether `T`
// is constrained to array and tuple types.
type Arrayish<T extends unknown[]> = { [K in keyof T]: T[K] };
type Objectish<T extends unknown> = { [K in keyof T]: T[K] };
// When a mapped type whose constraint is `keyof T` is instantiated,
// `T` may be instantiated with a `U` which is constrained to
// array and tuple types. *Ideally*, when `U` is later instantiated with `any`,
// the result should also be some sort of array; however, at the moment we don't seem
// to have an easy way to preserve that information. More than just that, it would be
// inconsistent for two instantiations of `Objectish<any>` to produce different outputs
// depending on the usage-site. As a result, `IndirectArrayish` does not act like `Arrayish`.
type IndirectArrayish<U extends unknown[]> = Objectish<U>;
function bar(arrayish: Arrayish<any>, objectish: Objectish<any>, indirectArrayish: IndirectArrayish<any>) {
let arr: any[];
arr = arrayish;
arr = objectish;
~~~
!!! error TS2740: Type 'Objectish<any>' is missing the following properties from type 'any[]': length, pop, push, concat, and 16 more.
arr = indirectArrayish;
~~~
!!! error TS2322: Type 'Objectish<any>' is not assignable to type 'any[]'.
}
declare function stringifyArray<T extends readonly any[]>(arr: T): { -readonly [K in keyof T]: string };
let abc: any[] = stringifyArray(void 0 as any);
declare function stringifyPair<T extends readonly [any, any]>(arr: T): { -readonly [K in keyof T]: string };
let def: [any, any] = stringifyPair(void 0 as any);
~~~
!!! error TS2322: Type 'string[]' is not assignable to type '[any, any]'.
!!! error TS2322: Target requires 2 element(s) but source may have fewer.

View file

@ -23,7 +23,35 @@ for (let id in z) {
let data = z[id];
let x = data.notAValue; // Error
}
// Issue #46169.
// We want mapped types whose constraint is `keyof T` to
// map over `any` differently, depending on whether `T`
// is constrained to array and tuple types.
type Arrayish<T extends unknown[]> = { [K in keyof T]: T[K] };
type Objectish<T extends unknown> = { [K in keyof T]: T[K] };
// When a mapped type whose constraint is `keyof T` is instantiated,
// `T` may be instantiated with a `U` which is constrained to
// array and tuple types. *Ideally*, when `U` is later instantiated with `any`,
// the result should also be some sort of array; however, at the moment we don't seem
// to have an easy way to preserve that information. More than just that, it would be
// inconsistent for two instantiations of `Objectish<any>` to produce different outputs
// depending on the usage-site. As a result, `IndirectArrayish` does not act like `Arrayish`.
type IndirectArrayish<U extends unknown[]> = Objectish<U>;
function bar(arrayish: Arrayish<any>, objectish: Objectish<any>, indirectArrayish: IndirectArrayish<any>) {
let arr: any[];
arr = arrayish;
arr = objectish;
arr = indirectArrayish;
}
declare function stringifyArray<T extends readonly any[]>(arr: T): { -readonly [K in keyof T]: string };
let abc: any[] = stringifyArray(void 0 as any);
declare function stringifyPair<T extends readonly [any, any]>(arr: T): { -readonly [K in keyof T]: string };
let def: [any, any] = stringifyPair(void 0 as any);
//// [mappedTypeWithAny.js]
"use strict";
@ -31,6 +59,14 @@ for (var id in z) {
var data = z[id];
var x = data.notAValue; // Error
}
function bar(arrayish, objectish, indirectArrayish) {
var arr;
arr = arrayish;
arr = objectish;
arr = indirectArrayish;
}
var abc = stringifyArray(void 0);
var def = stringifyPair(void 0);
//// [mappedTypeWithAny.d.ts]
@ -58,3 +94,19 @@ declare type StrictDataMap<T> = {
[P in keyof T]: Data;
};
declare let z: StrictDataMap<any>;
declare type Arrayish<T extends unknown[]> = {
[K in keyof T]: T[K];
};
declare type Objectish<T extends unknown> = {
[K in keyof T]: T[K];
};
declare type IndirectArrayish<U extends unknown[]> = Objectish<U>;
declare function bar(arrayish: Arrayish<any>, objectish: Objectish<any>, indirectArrayish: IndirectArrayish<any>): void;
declare function stringifyArray<T extends readonly any[]>(arr: T): {
-readonly [K in keyof T]: string;
};
declare let abc: any[];
declare function stringifyPair<T extends readonly [any, any]>(arr: T): {
-readonly [K in keyof T]: string;
};
declare let def: [any, any];

View file

@ -69,3 +69,85 @@ for (let id in z) {
>data : Symbol(data, Decl(mappedTypeWithAny.ts, 21, 5))
}
// Issue #46169.
// We want mapped types whose constraint is `keyof T` to
// map over `any` differently, depending on whether `T`
// is constrained to array and tuple types.
type Arrayish<T extends unknown[]> = { [K in keyof T]: T[K] };
>Arrayish : Symbol(Arrayish, Decl(mappedTypeWithAny.ts, 23, 1))
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 29, 14))
>K : Symbol(K, Decl(mappedTypeWithAny.ts, 29, 40))
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 29, 14))
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 29, 14))
>K : Symbol(K, Decl(mappedTypeWithAny.ts, 29, 40))
type Objectish<T extends unknown> = { [K in keyof T]: T[K] };
>Objectish : Symbol(Objectish, Decl(mappedTypeWithAny.ts, 29, 62))
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 30, 15))
>K : Symbol(K, Decl(mappedTypeWithAny.ts, 30, 39))
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 30, 15))
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 30, 15))
>K : Symbol(K, Decl(mappedTypeWithAny.ts, 30, 39))
// When a mapped type whose constraint is `keyof T` is instantiated,
// `T` may be instantiated with a `U` which is constrained to
// array and tuple types. *Ideally*, when `U` is later instantiated with `any`,
// the result should also be some sort of array; however, at the moment we don't seem
// to have an easy way to preserve that information. More than just that, it would be
// inconsistent for two instantiations of `Objectish<any>` to produce different outputs
// depending on the usage-site. As a result, `IndirectArrayish` does not act like `Arrayish`.
type IndirectArrayish<U extends unknown[]> = Objectish<U>;
>IndirectArrayish : Symbol(IndirectArrayish, Decl(mappedTypeWithAny.ts, 30, 61))
>U : Symbol(U, Decl(mappedTypeWithAny.ts, 39, 22))
>Objectish : Symbol(Objectish, Decl(mappedTypeWithAny.ts, 29, 62))
>U : Symbol(U, Decl(mappedTypeWithAny.ts, 39, 22))
function bar(arrayish: Arrayish<any>, objectish: Objectish<any>, indirectArrayish: IndirectArrayish<any>) {
>bar : Symbol(bar, Decl(mappedTypeWithAny.ts, 39, 58))
>arrayish : Symbol(arrayish, Decl(mappedTypeWithAny.ts, 41, 13))
>Arrayish : Symbol(Arrayish, Decl(mappedTypeWithAny.ts, 23, 1))
>objectish : Symbol(objectish, Decl(mappedTypeWithAny.ts, 41, 37))
>Objectish : Symbol(Objectish, Decl(mappedTypeWithAny.ts, 29, 62))
>indirectArrayish : Symbol(indirectArrayish, Decl(mappedTypeWithAny.ts, 41, 64))
>IndirectArrayish : Symbol(IndirectArrayish, Decl(mappedTypeWithAny.ts, 30, 61))
let arr: any[];
>arr : Symbol(arr, Decl(mappedTypeWithAny.ts, 42, 7))
arr = arrayish;
>arr : Symbol(arr, Decl(mappedTypeWithAny.ts, 42, 7))
>arrayish : Symbol(arrayish, Decl(mappedTypeWithAny.ts, 41, 13))
arr = objectish;
>arr : Symbol(arr, Decl(mappedTypeWithAny.ts, 42, 7))
>objectish : Symbol(objectish, Decl(mappedTypeWithAny.ts, 41, 37))
arr = indirectArrayish;
>arr : Symbol(arr, Decl(mappedTypeWithAny.ts, 42, 7))
>indirectArrayish : Symbol(indirectArrayish, Decl(mappedTypeWithAny.ts, 41, 64))
}
declare function stringifyArray<T extends readonly any[]>(arr: T): { -readonly [K in keyof T]: string };
>stringifyArray : Symbol(stringifyArray, Decl(mappedTypeWithAny.ts, 46, 1))
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 48, 32))
>arr : Symbol(arr, Decl(mappedTypeWithAny.ts, 48, 58))
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 48, 32))
>K : Symbol(K, Decl(mappedTypeWithAny.ts, 48, 80))
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 48, 32))
let abc: any[] = stringifyArray(void 0 as any);
>abc : Symbol(abc, Decl(mappedTypeWithAny.ts, 49, 3))
>stringifyArray : Symbol(stringifyArray, Decl(mappedTypeWithAny.ts, 46, 1))
declare function stringifyPair<T extends readonly [any, any]>(arr: T): { -readonly [K in keyof T]: string };
>stringifyPair : Symbol(stringifyPair, Decl(mappedTypeWithAny.ts, 49, 47))
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 51, 31))
>arr : Symbol(arr, Decl(mappedTypeWithAny.ts, 51, 62))
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 51, 31))
>K : Symbol(K, Decl(mappedTypeWithAny.ts, 51, 84))
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 51, 31))
let def: [any, any] = stringifyPair(void 0 as any);
>def : Symbol(def, Decl(mappedTypeWithAny.ts, 52, 3))
>stringifyPair : Symbol(stringifyPair, Decl(mappedTypeWithAny.ts, 49, 47))

View file

@ -56,3 +56,72 @@ for (let id in z) {
>notAValue : any
}
// Issue #46169.
// We want mapped types whose constraint is `keyof T` to
// map over `any` differently, depending on whether `T`
// is constrained to array and tuple types.
type Arrayish<T extends unknown[]> = { [K in keyof T]: T[K] };
>Arrayish : Arrayish<T>
type Objectish<T extends unknown> = { [K in keyof T]: T[K] };
>Objectish : Objectish<T>
// When a mapped type whose constraint is `keyof T` is instantiated,
// `T` may be instantiated with a `U` which is constrained to
// array and tuple types. *Ideally*, when `U` is later instantiated with `any`,
// the result should also be some sort of array; however, at the moment we don't seem
// to have an easy way to preserve that information. More than just that, it would be
// inconsistent for two instantiations of `Objectish<any>` to produce different outputs
// depending on the usage-site. As a result, `IndirectArrayish` does not act like `Arrayish`.
type IndirectArrayish<U extends unknown[]> = Objectish<U>;
>IndirectArrayish : Objectish<U>
function bar(arrayish: Arrayish<any>, objectish: Objectish<any>, indirectArrayish: IndirectArrayish<any>) {
>bar : (arrayish: Arrayish<any>, objectish: Objectish<any>, indirectArrayish: IndirectArrayish<any>) => void
>arrayish : any[]
>objectish : Objectish<any>
>indirectArrayish : Objectish<any>
let arr: any[];
>arr : any[]
arr = arrayish;
>arr = arrayish : any[]
>arr : any[]
>arrayish : any[]
arr = objectish;
>arr = objectish : Objectish<any>
>arr : any[]
>objectish : Objectish<any>
arr = indirectArrayish;
>arr = indirectArrayish : Objectish<any>
>arr : any[]
>indirectArrayish : Objectish<any>
}
declare function stringifyArray<T extends readonly any[]>(arr: T): { -readonly [K in keyof T]: string };
>stringifyArray : <T extends readonly any[]>(arr: T) => { -readonly [K in keyof T]: string; }
>arr : T
let abc: any[] = stringifyArray(void 0 as any);
>abc : any[]
>stringifyArray(void 0 as any) : string[]
>stringifyArray : <T extends readonly any[]>(arr: T) => { -readonly [K in keyof T]: string; }
>void 0 as any : any
>void 0 : undefined
>0 : 0
declare function stringifyPair<T extends readonly [any, any]>(arr: T): { -readonly [K in keyof T]: string };
>stringifyPair : <T extends readonly [any, any]>(arr: T) => { -readonly [K in keyof T]: string; }
>arr : T
let def: [any, any] = stringifyPair(void 0 as any);
>def : [any, any]
>stringifyPair(void 0 as any) : string[]
>stringifyPair : <T extends readonly [any, any]>(arr: T) => { -readonly [K in keyof T]: string; }
>void 0 as any : any
>void 0 : undefined
>0 : 0

View file

@ -0,0 +1,19 @@
=== tests/cases/compiler/promiseAllOnAny01.ts ===
async function foo(x: any) {
>foo : Symbol(foo, Decl(promiseAllOnAny01.ts, 0, 0))
>x : Symbol(x, Decl(promiseAllOnAny01.ts, 0, 19))
let abc = await Promise.all(x);
>abc : Symbol(abc, Decl(promiseAllOnAny01.ts, 1, 7))
>Promise.all : Symbol(PromiseConstructor.all, Decl(lib.es2015.promise.d.ts, --, --))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --))
>all : Symbol(PromiseConstructor.all, Decl(lib.es2015.promise.d.ts, --, --))
>x : Symbol(x, Decl(promiseAllOnAny01.ts, 0, 19))
let result: any[] = abc;
>result : Symbol(result, Decl(promiseAllOnAny01.ts, 2, 7))
>abc : Symbol(abc, Decl(promiseAllOnAny01.ts, 1, 7))
return result;
>result : Symbol(result, Decl(promiseAllOnAny01.ts, 2, 7))
}

View file

@ -0,0 +1,21 @@
=== tests/cases/compiler/promiseAllOnAny01.ts ===
async function foo(x: any) {
>foo : (x: any) => Promise<any[]>
>x : any
let abc = await Promise.all(x);
>abc : any[]
>await Promise.all(x) : any[]
>Promise.all(x) : Promise<any[]>
>Promise.all : <T extends readonly unknown[] | []>(values: T) => Promise<{ -readonly [P in keyof T]: Awaited<T[P]>; }>
>Promise : PromiseConstructor
>all : <T extends readonly unknown[] | []>(values: T) => Promise<{ -readonly [P in keyof T]: Awaited<T[P]>; }>
>x : any
let result: any[] = abc;
>result : any[]
>abc : any[]
return result;
>result : any[]
}

View file

@ -0,0 +1,8 @@
// @noEmit: true
// @lib: es5,es2015.promise
async function foo(x: any) {
let abc = await Promise.all(x);
let result: any[] = abc;
return result;
}

View file

@ -25,3 +25,32 @@ for (let id in z) {
let data = z[id];
let x = data.notAValue; // Error
}
// Issue #46169.
// We want mapped types whose constraint is `keyof T` to
// map over `any` differently, depending on whether `T`
// is constrained to array and tuple types.
type Arrayish<T extends unknown[]> = { [K in keyof T]: T[K] };
type Objectish<T extends unknown> = { [K in keyof T]: T[K] };
// When a mapped type whose constraint is `keyof T` is instantiated,
// `T` may be instantiated with a `U` which is constrained to
// array and tuple types. *Ideally*, when `U` is later instantiated with `any`,
// the result should also be some sort of array; however, at the moment we don't seem
// to have an easy way to preserve that information. More than just that, it would be
// inconsistent for two instantiations of `Objectish<any>` to produce different outputs
// depending on the usage-site. As a result, `IndirectArrayish` does not act like `Arrayish`.
type IndirectArrayish<U extends unknown[]> = Objectish<U>;
function bar(arrayish: Arrayish<any>, objectish: Objectish<any>, indirectArrayish: IndirectArrayish<any>) {
let arr: any[];
arr = arrayish;
arr = objectish;
arr = indirectArrayish;
}
declare function stringifyArray<T extends readonly any[]>(arr: T): { -readonly [K in keyof T]: string };
let abc: any[] = stringifyArray(void 0 as any);
declare function stringifyPair<T extends readonly [any, any]>(arr: T): { -readonly [K in keyof T]: string };
let def: [any, any] = stringifyPair(void 0 as any);