Skip to content

Commit

Permalink
Merge pull request #29740 from Microsoft/fixCircularMappedArrayTuple
Browse files Browse the repository at this point in the history
Fix issues related to circular mapped array and tuple types
  • Loading branch information
ahejlsberg authored Feb 6, 2019
2 parents 33af4ea + f8eb671 commit d9ee867
Show file tree
Hide file tree
Showing 7 changed files with 309 additions and 21 deletions.
28 changes: 12 additions & 16 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10854,7 +10854,7 @@ namespace ts {
function getHomomorphicTypeVariable(type: MappedType) {
const constraintType = getConstraintTypeFromMappedType(type);
if (constraintType.flags & TypeFlags.Index) {
const typeVariable = (<IndexType>constraintType).type;
const typeVariable = getActualTypeVariable((<IndexType>constraintType).type);
if (typeVariable.flags & TypeFlags.TypeParameter) {
return <TypeParameter>typeVariable;
}
Expand All @@ -10877,26 +10877,15 @@ namespace ts {
if (typeVariable) {
const mappedTypeVariable = instantiateType(typeVariable, mapper);
if (typeVariable !== mappedTypeVariable) {
// If we are already in the process of creating an instantiation of this mapped type,
// return the error type. This situation only arises if we are instantiating the mapped
// type for an array or tuple type, as we then need to eagerly resolve the (possibly
// circular) element type(s).
if (type.instantiating) {
return errorType;
}
type.instantiating = true;
const modifiers = getMappedTypeModifiers(type);
const result = mapType(mappedTypeVariable, t => {
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType) {
return mapType(mappedTypeVariable, t => {
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && t !== errorType) {
const replacementMapper = createReplacementMapper(typeVariable, t, mapper);
return isArrayType(t) ? createArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper), getModifiedReadonlyState(isReadonlyArrayType(t), modifiers)) :
return isArrayType(t) ? instantiateMappedArrayType(t, type, replacementMapper) :
isTupleType(t) ? instantiateMappedTupleType(t, type, replacementMapper) :
instantiateAnonymousType(type, replacementMapper);
}
return t;
});
type.instantiating = false;
return result;
}
}
return instantiateAnonymousType(type, mapper);
Expand All @@ -10906,6 +10895,12 @@ namespace ts {
return modifiers & MappedTypeModifiers.IncludeReadonly ? true : modifiers & MappedTypeModifiers.ExcludeReadonly ? false : state;
}

function instantiateMappedArrayType(arrayType: Type, mappedType: MappedType, mapper: TypeMapper) {
const elementType = instantiateMappedTypeTemplate(mappedType, numberType, /*isOptional*/ true, mapper);
return elementType === errorType ? errorType :
createArrayType(elementType, getModifiedReadonlyState(isReadonlyArrayType(arrayType), getMappedTypeModifiers(mappedType)));
}

function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) {
const minLength = tupleType.target.minLength;
const elementTypes = map(tupleType.typeArguments || emptyArray, (_, i) =>
Expand All @@ -10915,7 +10910,8 @@ namespace ts {
modifiers & MappedTypeModifiers.ExcludeOptional ? getTypeReferenceArity(tupleType) - (tupleType.target.hasRestElement ? 1 : 0) :
minLength;
const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, modifiers);
return createTupleType(elementTypes, newMinLength, tupleType.target.hasRestElement, newReadonly, tupleType.target.associatedNames);
return contains(elementTypes, errorType) ? errorType :
createTupleType(elementTypes, newMinLength, tupleType.target.hasRestElement, newReadonly, tupleType.target.associatedNames);
}

function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) {
Expand Down
1 change: 0 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4114,7 +4114,6 @@ namespace ts {
templateType?: Type;
modifiersType?: Type;
resolvedApparentType?: Type;
instantiating?: boolean;
}

export interface EvolvingArrayType extends ObjectType {
Expand Down
44 changes: 43 additions & 1 deletion tests/baselines/reference/recursiveMappedTypes.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(7,6): error TS2456:
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(8,11): error TS2313: Type parameter 'K' has a circular constraint.
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(11,6): error TS2456: Type alias 'Recurse2' circularly references itself.
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(12,11): error TS2313: Type parameter 'K' has a circular constraint.
tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(20,19): error TS2589: Type instantiation is excessively deep and possibly infinite.


==== tests/cases/conformance/types/mapped/recursiveMappedTypes.ts (6 errors) ====
==== tests/cases/conformance/types/mapped/recursiveMappedTypes.ts (7 errors) ====
// Recursive mapped types simply appear empty

type Recurse = {
Expand Down Expand Up @@ -39,6 +40,47 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(12,11): error TS231
type tup = [number, number, number, number];

function foo(arg: Circular<tup>): tup {
~~~~~~~~~~~~~
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.
return arg;
}

// Repro from #29442

type DeepMap<T extends unknown[], R> = {
[K in keyof T]: T[K] extends unknown[] ? DeepMap<T[K], R> : R;
};

type tpl = [string, [string, [string]]];
type arr = string[][];

type t1 = DeepMap<tpl, number>; // [number, [number, [number]]]
type t2 = DeepMap<arr, number>; // number[][]

// Repro from #29577

type Transform<T> = { [K in keyof T]: Transform<T[K]> };

interface User {
avatar: string;
}

interface Guest {
displayName: string;
}

interface Product {
users: (User | Guest)[];
}

declare var product: Transform<Product>;
product.users; // (Transform<User> | Transform<Guest>)[]

// Repro from #29702

type Remap1<T> = { [P in keyof T]: Remap1<T[P]>; };
type Remap2<T> = T extends object ? { [P in keyof T]: Remap2<T[P]>; } : T;

type a = Remap1<string[]>; // string[]
type b = Remap2<string[]>; // string[]

40 changes: 40 additions & 0 deletions tests/baselines/reference/recursiveMappedTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,45 @@ type tup = [number, number, number, number];
function foo(arg: Circular<tup>): tup {
return arg;
}

// Repro from #29442

type DeepMap<T extends unknown[], R> = {
[K in keyof T]: T[K] extends unknown[] ? DeepMap<T[K], R> : R;
};

type tpl = [string, [string, [string]]];
type arr = string[][];

type t1 = DeepMap<tpl, number>; // [number, [number, [number]]]
type t2 = DeepMap<arr, number>; // number[][]

// Repro from #29577

type Transform<T> = { [K in keyof T]: Transform<T[K]> };

interface User {
avatar: string;
}

interface Guest {
displayName: string;
}

interface Product {
users: (User | Guest)[];
}

declare var product: Transform<Product>;
product.users; // (Transform<User> | Transform<Guest>)[]

// Repro from #29702

type Remap1<T> = { [P in keyof T]: Remap1<T[P]>; };
type Remap2<T> = T extends object ? { [P in keyof T]: Remap2<T[P]>; } : T;

type a = Remap1<string[]>; // string[]
type b = Remap2<string[]>; // string[]


//// [recursiveMappedTypes.js]
Expand All @@ -30,6 +69,7 @@ exports.__esModule = true;
function foo(arg) {
return arg;
}
product.users; // (Transform<User> | Transform<Guest>)[]


//// [recursiveMappedTypes.d.ts]
Expand Down
110 changes: 110 additions & 0 deletions tests/baselines/reference/recursiveMappedTypes.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,113 @@ function foo(arg: Circular<tup>): tup {
>arg : Symbol(arg, Decl(recursiveMappedTypes.ts, 19, 13))
}

// Repro from #29442

type DeepMap<T extends unknown[], R> = {
>DeepMap : Symbol(DeepMap, Decl(recursiveMappedTypes.ts, 21, 1))
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 25, 13))
>R : Symbol(R, Decl(recursiveMappedTypes.ts, 25, 33))

[K in keyof T]: T[K] extends unknown[] ? DeepMap<T[K], R> : R;
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 26, 3))
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 25, 13))
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 25, 13))
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 26, 3))
>DeepMap : Symbol(DeepMap, Decl(recursiveMappedTypes.ts, 21, 1))
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 25, 13))
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 26, 3))
>R : Symbol(R, Decl(recursiveMappedTypes.ts, 25, 33))
>R : Symbol(R, Decl(recursiveMappedTypes.ts, 25, 33))

};

type tpl = [string, [string, [string]]];
>tpl : Symbol(tpl, Decl(recursiveMappedTypes.ts, 27, 2))

type arr = string[][];
>arr : Symbol(arr, Decl(recursiveMappedTypes.ts, 29, 40))

type t1 = DeepMap<tpl, number>; // [number, [number, [number]]]
>t1 : Symbol(t1, Decl(recursiveMappedTypes.ts, 30, 22))
>DeepMap : Symbol(DeepMap, Decl(recursiveMappedTypes.ts, 21, 1))
>tpl : Symbol(tpl, Decl(recursiveMappedTypes.ts, 27, 2))

type t2 = DeepMap<arr, number>; // number[][]
>t2 : Symbol(t2, Decl(recursiveMappedTypes.ts, 32, 31))
>DeepMap : Symbol(DeepMap, Decl(recursiveMappedTypes.ts, 21, 1))
>arr : Symbol(arr, Decl(recursiveMappedTypes.ts, 29, 40))

// Repro from #29577

type Transform<T> = { [K in keyof T]: Transform<T[K]> };
>Transform : Symbol(Transform, Decl(recursiveMappedTypes.ts, 33, 31))
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 37, 15))
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 37, 23))
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 37, 15))
>Transform : Symbol(Transform, Decl(recursiveMappedTypes.ts, 33, 31))
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 37, 15))
>K : Symbol(K, Decl(recursiveMappedTypes.ts, 37, 23))

interface User {
>User : Symbol(User, Decl(recursiveMappedTypes.ts, 37, 56))

avatar: string;
>avatar : Symbol(User.avatar, Decl(recursiveMappedTypes.ts, 39, 16))
}

interface Guest {
>Guest : Symbol(Guest, Decl(recursiveMappedTypes.ts, 41, 1))

displayName: string;
>displayName : Symbol(Guest.displayName, Decl(recursiveMappedTypes.ts, 43, 17))
}

interface Product {
>Product : Symbol(Product, Decl(recursiveMappedTypes.ts, 45, 1))

users: (User | Guest)[];
>users : Symbol(Product.users, Decl(recursiveMappedTypes.ts, 47, 19))
>User : Symbol(User, Decl(recursiveMappedTypes.ts, 37, 56))
>Guest : Symbol(Guest, Decl(recursiveMappedTypes.ts, 41, 1))
}

declare var product: Transform<Product>;
>product : Symbol(product, Decl(recursiveMappedTypes.ts, 51, 11))
>Transform : Symbol(Transform, Decl(recursiveMappedTypes.ts, 33, 31))
>Product : Symbol(Product, Decl(recursiveMappedTypes.ts, 45, 1))

product.users; // (Transform<User> | Transform<Guest>)[]
>product.users : Symbol(users, Decl(recursiveMappedTypes.ts, 47, 19))
>product : Symbol(product, Decl(recursiveMappedTypes.ts, 51, 11))
>users : Symbol(users, Decl(recursiveMappedTypes.ts, 47, 19))

// Repro from #29702

type Remap1<T> = { [P in keyof T]: Remap1<T[P]>; };
>Remap1 : Symbol(Remap1, Decl(recursiveMappedTypes.ts, 52, 14))
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 56, 12))
>P : Symbol(P, Decl(recursiveMappedTypes.ts, 56, 20))
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 56, 12))
>Remap1 : Symbol(Remap1, Decl(recursiveMappedTypes.ts, 52, 14))
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 56, 12))
>P : Symbol(P, Decl(recursiveMappedTypes.ts, 56, 20))

type Remap2<T> = T extends object ? { [P in keyof T]: Remap2<T[P]>; } : T;
>Remap2 : Symbol(Remap2, Decl(recursiveMappedTypes.ts, 56, 51))
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 57, 12))
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 57, 12))
>P : Symbol(P, Decl(recursiveMappedTypes.ts, 57, 39))
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 57, 12))
>Remap2 : Symbol(Remap2, Decl(recursiveMappedTypes.ts, 56, 51))
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 57, 12))
>P : Symbol(P, Decl(recursiveMappedTypes.ts, 57, 39))
>T : Symbol(T, Decl(recursiveMappedTypes.ts, 57, 12))

type a = Remap1<string[]>; // string[]
>a : Symbol(a, Decl(recursiveMappedTypes.ts, 57, 74))
>Remap1 : Symbol(Remap1, Decl(recursiveMappedTypes.ts, 52, 14))

type b = Remap2<string[]>; // string[]
>b : Symbol(b, Decl(recursiveMappedTypes.ts, 59, 26))
>Remap2 : Symbol(Remap2, Decl(recursiveMappedTypes.ts, 56, 51))

68 changes: 65 additions & 3 deletions tests/baselines/reference/recursiveMappedTypes.types
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,72 @@ type tup = [number, number, number, number];
>tup : [number, number, number, number]

function foo(arg: Circular<tup>): tup {
>foo : (arg: [any, any, any, any]) => [number, number, number, number]
>arg : [any, any, any, any]
>foo : (arg: any) => [number, number, number, number]
>arg : any

return arg;
>arg : [any, any, any, any]
>arg : any
}

// Repro from #29442

type DeepMap<T extends unknown[], R> = {
>DeepMap : DeepMap<T, R>

[K in keyof T]: T[K] extends unknown[] ? DeepMap<T[K], R> : R;
};

type tpl = [string, [string, [string]]];
>tpl : [string, [string, [string]]]

type arr = string[][];
>arr : string[][]

type t1 = DeepMap<tpl, number>; // [number, [number, [number]]]
>t1 : [number, [number, [number]]]

type t2 = DeepMap<arr, number>; // number[][]
>t2 : number[][]

// Repro from #29577

type Transform<T> = { [K in keyof T]: Transform<T[K]> };
>Transform : Transform<T>

interface User {
avatar: string;
>avatar : string
}

interface Guest {
displayName: string;
>displayName : string
}

interface Product {
users: (User | Guest)[];
>users : (User | Guest)[]
}

declare var product: Transform<Product>;
>product : Transform<Product>

product.users; // (Transform<User> | Transform<Guest>)[]
>product.users : (Transform<User> | Transform<Guest>)[]
>product : Transform<Product>
>users : (Transform<User> | Transform<Guest>)[]

// Repro from #29702

type Remap1<T> = { [P in keyof T]: Remap1<T[P]>; };
>Remap1 : Remap1<T>

type Remap2<T> = T extends object ? { [P in keyof T]: Remap2<T[P]>; } : T;
>Remap2 : Remap2<T>

type a = Remap1<string[]>; // string[]
>a : string[]

type b = Remap2<string[]>; // string[]
>b : string[]

Loading

0 comments on commit d9ee867

Please sign in to comment.