Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16515,7 +16515,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))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (isArrayType(t) || (t.flags & TypeFlags.Any) && (constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, or(isArrayType, isTupleType))) {
if (isArrayType(t) || (t.flags & TypeFlags.Any) && (constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, isArrayOrTupleLikeType)) {

? 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 a T extends {length: number} may also justify an array mapping rather than a string index signature.

Copy link
Member Author

@DanielRosenwasser DanielRosenwasser Oct 5, 2021

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:

return isArrayType(type) || !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType);

which I think would consider any an array-like type.

Copy link
Member

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 with unknown 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.

Copy link
Member Author

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.

return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper));
}
if (isGenericTupleType(t)) {
Expand Down
32 changes: 30 additions & 2 deletions tests/baselines/reference/mappedTypeWithAny.errors.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
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(42,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(43,5): error TS2322: Type 'Objectish<any>' is not assignable to type 'any[]'.


==== tests/cases/conformance/types/mapped/mappedTypeWithAny.ts (1 errors) ====
==== tests/cases/conformance/types/mapped/mappedTypeWithAny.ts (3 errors) ====
type Item = { value: string };
type ItemMap<T> = { [P in keyof T]: Item };

Expand All @@ -28,4 +30,30 @@ 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. When `U` is later instantiated with `any`,
// the result should also be some sort of array.
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[]'.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My intent was that IndirectArrayish<U> should be a mapped object type that has a type variable constraint of unknown[]; it seems like that might not be the case, so I actually might need some help here.

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

@DanielRosenwasser DanielRosenwasser Oct 5, 2021

Choose a reason for hiding this comment

The 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 IndirectArrayish<any> and Objectish<any> aren't generic mapped types, are they?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uhh, I was thinking of something like relating a generic IndirectArrayish<U> to a U[]. In this case, I don't think we can distinguish between a Objectish<any> and an IndirectArrayish<any> since they're exactly equivalent - the "is this constrained to arrayish types only" check you have is purely syntactic (it only cares if the mapped type was both declared homomorphic and that variable is array-constrained) - it can't pick up information from wrapping declarations or anything.

Copy link
Member Author

@DanielRosenwasser DanielRosenwasser Oct 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the intent here was that when we declared IndirectArrayish<U extends unknown[]>, we'd end up with a fresh generic mapped type whose type variable (U) has the array constraint. Is that not what's happening?

}

declare function stringifyArray<T extends readonly any[]>(arr: T): { -readonly [K in keyof T]: string };
let abc: any[] = stringifyArray(void 0 as any);
43 changes: 42 additions & 1 deletion tests/baselines/reference/mappedTypeWithAny.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,43 @@ 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. When `U` is later instantiated with `any`,
// the result should also be some sort of array.
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);

//// [mappedTypeWithAny.js]
"use strict";
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);


//// [mappedTypeWithAny.d.ts]
Expand Down Expand Up @@ -58,3 +87,15 @@ 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[];
67 changes: 67 additions & 0 deletions tests/baselines/reference/mappedTypeWithAny.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,70 @@ 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. When `U` is later instantiated with `any`,
// the result should also be some sort of array.
type IndirectArrayish<U extends unknown[]> = Objectish<U>;
>IndirectArrayish : Symbol(IndirectArrayish, Decl(mappedTypeWithAny.ts, 30, 61))
>U : Symbol(U, Decl(mappedTypeWithAny.ts, 36, 22))
>Objectish : Symbol(Objectish, Decl(mappedTypeWithAny.ts, 29, 62))
>U : Symbol(U, Decl(mappedTypeWithAny.ts, 36, 22))

function bar(arrayish: Arrayish<any>, objectish: Objectish<any>, indirectArrayish: IndirectArrayish<any>) {
>bar : Symbol(bar, Decl(mappedTypeWithAny.ts, 36, 58))
>arrayish : Symbol(arrayish, Decl(mappedTypeWithAny.ts, 38, 13))
>Arrayish : Symbol(Arrayish, Decl(mappedTypeWithAny.ts, 23, 1))
>objectish : Symbol(objectish, Decl(mappedTypeWithAny.ts, 38, 37))
>Objectish : Symbol(Objectish, Decl(mappedTypeWithAny.ts, 29, 62))
>indirectArrayish : Symbol(indirectArrayish, Decl(mappedTypeWithAny.ts, 38, 64))
>IndirectArrayish : Symbol(IndirectArrayish, Decl(mappedTypeWithAny.ts, 30, 61))

let arr: any[];
>arr : Symbol(arr, Decl(mappedTypeWithAny.ts, 39, 7))

arr = arrayish;
>arr : Symbol(arr, Decl(mappedTypeWithAny.ts, 39, 7))
>arrayish : Symbol(arrayish, Decl(mappedTypeWithAny.ts, 38, 13))

arr = objectish;
>arr : Symbol(arr, Decl(mappedTypeWithAny.ts, 39, 7))
>objectish : Symbol(objectish, Decl(mappedTypeWithAny.ts, 38, 37))

arr = indirectArrayish;
>arr : Symbol(arr, Decl(mappedTypeWithAny.ts, 39, 7))
>indirectArrayish : Symbol(indirectArrayish, Decl(mappedTypeWithAny.ts, 38, 64))
}

declare function stringifyArray<T extends readonly any[]>(arr: T): { -readonly [K in keyof T]: string };
>stringifyArray : Symbol(stringifyArray, Decl(mappedTypeWithAny.ts, 43, 1))
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 45, 32))
>arr : Symbol(arr, Decl(mappedTypeWithAny.ts, 45, 58))
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 45, 32))
>K : Symbol(K, Decl(mappedTypeWithAny.ts, 45, 80))
>T : Symbol(T, Decl(mappedTypeWithAny.ts, 45, 32))

let abc: any[] = stringifyArray(void 0 as any);
>abc : Symbol(abc, Decl(mappedTypeWithAny.ts, 46, 3))
>stringifyArray : Symbol(stringifyArray, Decl(mappedTypeWithAny.ts, 43, 1))

54 changes: 54 additions & 0 deletions tests/baselines/reference/mappedTypeWithAny.types
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,57 @@ 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. When `U` is later instantiated with `any`,
// the result should also be some sort of array.
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

19 changes: 19 additions & 0 deletions tests/baselines/reference/promiseAllOnAny01.symbols
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))
}
21 changes: 21 additions & 0 deletions tests/baselines/reference/promiseAllOnAny01.types
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[]
}
8 changes: 8 additions & 0 deletions tests/cases/compiler/promiseAllOnAny01.ts
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;
}
23 changes: 23 additions & 0 deletions tests/cases/conformance/types/mapped/mappedTypeWithAny.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,26 @@ 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. When `U` is later instantiated with `any`,
// the result should also be some sort of array.
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);