-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Check for array types when instantiating mapped type constraints with any
#46218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ac8be7c
8741cdf
151e1a6
a27f15d
d8844a7
eebb1d4
99d9b56
9ff4385
6aa3670
b3ca064
63a6bca
d1939aa
de7f98c
f01ae16
f342c82
46283e2
b3492a9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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[]'. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My intent was that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you'd need a new assignability rule for mapped types matching that pattern to accomplish that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I see what you're saying - that you need to have something recursive that checks if you're arrayish or a generic mapped type that would produce an array result. function mappedTypeConstraintProducesArrayResult(type: Type) {
if (isArrayType(type) || isTupleType(type)) return true;
if (type.flags & TypeFlags.Union) return everyType(type, mappedTypeConstraintProducesArrayResult);
const typeVariable = type.flags & TypeFlags.MappedType && getHomomorphicTypeVariable(type as MappedType);
if (typeVariable && !type.declaration.nameType) {
const constraint = getConstraintOfTypeParameter(typeVariable);
return !!(constraint && mappedTypeConstraintProducesArrayResult(constraint));
}
return false;
} But There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Uhh, I was thinking of something like relating a generic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess the intent here was that when we declared |
||
} | ||
|
||
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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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[] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
? Or is including the x-like types (length properties and 0 properties) going to mess up the behavior in a case we care about? I'm thinking passing
any
to aT extends {length: number}
may also justify an array mapping rather than astring
index signature.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about the tuple-like type thing, and that's one concern - I'm already not 100% convinced that that would always be desirable. The bigger concern is the implementation of
isArrayLikeType
:which I think would consider
any
an array-like type.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Raw
any
in a constraint position is eagerly replaced withunknown
nowadays (so it behaves as a proper top type constraint and not as an odd anyish thing) so it shouldn't be an issue I don't think.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that might be fine, but for now there's some precedent in
getResolvedApparentTypeOfMappedType
for the current behavior.