diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9a3c0cc773..9bcd6708b2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7119,6 +7119,22 @@ namespace ts { return !!(typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default)); } + function getApparentTypeOfMappedType(type: MappedType) { + return type.resolvedApparentType || (type.resolvedApparentType = getResolvedApparentTypeOfMappedType(type)); + } + + function getResolvedApparentTypeOfMappedType(type: MappedType) { + const typeVariable = getHomomorphicTypeVariable(type); + if (typeVariable) { + const constraint = getConstraintOfTypeParameter(typeVariable); + if (constraint && (isArrayType(constraint) || isReadonlyArrayType(constraint) || isTupleType(constraint))) { + const mapper = makeUnaryTypeMapper(typeVariable, constraint); + return instantiateType(type, combineTypeMappers(mapper, type.mapper)); + } + } + return type; + } + /** * For a type parameter, return the base constraint of the type parameter. For the string, number, * boolean, and symbol primitive types, return the corresponding object types. Otherwise return the @@ -7126,7 +7142,8 @@ namespace ts { */ function getApparentType(type: Type): Type { const t = type.flags & TypeFlags.Instantiable ? getBaseConstraintOfType(type) || emptyObjectType : type; - return t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(t) : + return getObjectFlags(t) & ObjectFlags.Mapped ? getApparentTypeOfMappedType(t) : + t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(t) : t.flags & TypeFlags.StringLike ? globalStringType : t.flags & TypeFlags.NumberLike ? globalNumberType : t.flags & TypeFlags.BooleanLike ? globalBooleanType : @@ -10159,8 +10176,19 @@ namespace ts { } } + function getHomomorphicTypeVariable(type: MappedType) { + const constraintType = getConstraintTypeFromMappedType(type); + if (constraintType.flags & TypeFlags.Index) { + const typeVariable = (constraintType).type; + if (typeVariable.flags & TypeFlags.TypeParameter) { + return typeVariable; + } + } + return undefined; + } + function instantiateMappedType(type: MappedType, mapper: TypeMapper): Type { - // For a momomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping + // For a homomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping // operation depends on T as follows: // * If T is a primitive type no mapping is performed and the result is simply T. // * If T is a union type we distribute the mapped type over the union. @@ -10170,32 +10198,25 @@ namespace ts { // For example, when T is instantiated to a union type A | B, we produce { [P in keyof A]: X } | // { [P in keyof B]: X }, and when when T is instantiated to a union type A | undefined, we produce // { [P in keyof A]: X } | undefined. - const constraintType = getConstraintTypeFromMappedType(type); - if (constraintType.flags & TypeFlags.Index) { - const typeVariable = (constraintType).type; - if (typeVariable.flags & TypeFlags.TypeParameter) { - const mappedTypeVariable = instantiateType(typeVariable, mapper); - if (typeVariable !== mappedTypeVariable) { - return mapType(mappedTypeVariable, t => { - if (isMappableType(t)) { - const replacementMapper = createReplacementMapper(typeVariable, t, mapper); - return isArrayType(t) ? createArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) : - isReadonlyArrayType(t) ? createReadonlyArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) : - isTupleType(t) ? instantiateMappedTupleType(t, type, replacementMapper) : - instantiateAnonymousType(type, replacementMapper); - } - return t; - }); - } + const typeVariable = getHomomorphicTypeVariable(type); + if (typeVariable) { + const mappedTypeVariable = instantiateType(typeVariable, mapper); + if (typeVariable !== mappedTypeVariable) { + return mapType(mappedTypeVariable, t => { + if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType) { + const replacementMapper = createReplacementMapper(typeVariable, t, mapper); + return isArrayType(t) ? createArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) : + isReadonlyArrayType(t) ? createReadonlyArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) : + isTupleType(t) ? instantiateMappedTupleType(t, type, replacementMapper) : + instantiateAnonymousType(type, replacementMapper); + } + return t; + }); } } return instantiateAnonymousType(type, mapper); } - function isMappableType(type: Type) { - return type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection); - } - function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) { const minLength = tupleType.target.minLength; const elementTypes = map(tupleType.typeArguments || emptyArray, (_, i) => @@ -11623,7 +11644,6 @@ namespace ts { const constraint = getConstraintForRelation(target); if (constraint) { if (result = isRelatedTo(source, constraint, reportErrors)) { - errorInfo = saveErrorInfo; return result; } } @@ -11642,7 +11662,6 @@ namespace ts { const indexedAccessType = getIndexedAccessType(source, getTypeParameterFromMappedType(target)); const templateType = getTemplateTypeFromMappedType(target); if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) { - errorInfo = saveErrorInfo; return result; } } @@ -11716,6 +11735,23 @@ namespace ts { } } else { + // An empty object type is related to any mapped type that includes a '?' modifier. + if (isPartialMappedType(target) && !isGenericMappedType(source) && isEmptyObjectType(source)) { + return Ternary.True; + } + if (isGenericMappedType(target)) { + if (isGenericMappedType(source)) { + if (result = mappedTypeRelatedTo(source, target, reportErrors)) { + errorInfo = saveErrorInfo; + return result; + } + } + return Ternary.False; + } + const sourceIsPrimitive = !!(source.flags & TypeFlags.Primitive); + if (relation !== identityRelation) { + source = getApparentType(source); + } if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target && !(getObjectFlags(source) & ObjectFlags.MarkerType || getObjectFlags(target) & ObjectFlags.MarkerType)) { // We have type references to the same generic type, and the type references are not marker @@ -11751,34 +11787,21 @@ namespace ts { } // Even if relationship doesn't hold for unions, intersections, or generic type references, // it may hold in a structural comparison. - const sourceIsPrimitive = !!(source.flags & TypeFlags.Primitive); - if (relation !== identityRelation) { - source = getApparentType(source); - } // In a check of the form X = A & B, we will have previously checked if A relates to X or B relates // to X. Failing both of those we want to check if the aggregation of A and B's members structurally // relates to X. Thus, we include intersection types on the source side here. if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) { // Report structural errors only if we haven't reported any errors yet const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !sourceIsPrimitive; - // An empty object type is related to any mapped type that includes a '?' modifier. - if (isPartialMappedType(target) && !isGenericMappedType(source) && isEmptyObjectType(source)) { - result = Ternary.True; - } - else if (isGenericMappedType(target)) { - result = isGenericMappedType(source) ? mappedTypeRelatedTo(source, target, reportStructuralErrors) : Ternary.False; - } - else { - result = propertiesRelatedTo(source, target, reportStructuralErrors); + result = propertiesRelatedTo(source, target, reportStructuralErrors); + if (result) { + result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors); if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors); + result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors); if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors); + result &= indexTypesRelatedTo(source, target, IndexKind.String, sourceIsPrimitive, reportStructuralErrors); if (result) { - result &= indexTypesRelatedTo(source, target, IndexKind.String, sourceIsPrimitive, reportStructuralErrors); - if (result) { - result &= indexTypesRelatedTo(source, target, IndexKind.Number, sourceIsPrimitive, reportStructuralErrors); - } + result &= indexTypesRelatedTo(source, target, IndexKind.Number, sourceIsPrimitive, reportStructuralErrors); } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e490179ce0..0bcea5df74 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3949,6 +3949,7 @@ namespace ts { constraintType?: Type; templateType?: Type; modifiersType?: Type; + resolvedApparentType?: Type; } export interface EvolvingArrayType extends ObjectType { diff --git a/tests/baselines/reference/mappedTypesArraysTuples.js b/tests/baselines/reference/mappedTypesArraysTuples.js index 489d8493e0..d3d976c04d 100644 --- a/tests/baselines/reference/mappedTypesArraysTuples.js +++ b/tests/baselines/reference/mappedTypesArraysTuples.js @@ -61,6 +61,28 @@ function f1(a: number, b: Promise, c: string[], d: Promise) { let x3 = all(a, b, c); let x4 = all(a, b, c, d); } + +function f2(a: Boxified) { + let x: Box | undefined = a.pop(); + let y: Box[] = a.concat(a); +} + +// Repro from #26163 + +type ElementType = T extends Array ? U : never; +type Mapped = { [K in keyof T]: T[K] }; + +type F = ElementType>; +type R1 = F<[string, number, boolean]>; // string | number | boolean +type R2 = ElementType>; // string | number | boolean + +// Repro from #26163 + +declare function acceptArray(arr: any[]): void; +declare function mapArray(arr: T): Mapped; +function acceptMappedArray(arr: T) { + acceptArray(mapArray(arr)); +} //// [mappedTypesArraysTuples.js] @@ -77,6 +99,13 @@ function f1(a, b, c, d) { var x3 = all(a, b, c); var x4 = all(a, b, c, d); } +function f2(a) { + var x = a.pop(); + var y = a.concat(a); +} +function acceptMappedArray(arr) { + acceptArray(mapArray(arr)); +} //// [mappedTypesArraysTuples.d.ts] @@ -142,3 +171,14 @@ declare type Awaitified = { }; declare function all(...values: T): Promise>; declare function f1(a: number, b: Promise, c: string[], d: Promise): void; +declare function f2(a: Boxified): void; +declare type ElementType = T extends Array ? U : never; +declare type Mapped = { + [K in keyof T]: T[K]; +}; +declare type F = ElementType>; +declare type R1 = F<[string, number, boolean]>; +declare type R2 = ElementType>; +declare function acceptArray(arr: any[]): void; +declare function mapArray(arr: T): Mapped; +declare function acceptMappedArray(arr: T): void; diff --git a/tests/baselines/reference/mappedTypesArraysTuples.symbols b/tests/baselines/reference/mappedTypesArraysTuples.symbols index 7d743235a4..1815b354b6 100644 --- a/tests/baselines/reference/mappedTypesArraysTuples.symbols +++ b/tests/baselines/reference/mappedTypesArraysTuples.symbols @@ -245,3 +245,86 @@ function f1(a: number, b: Promise, c: string[], d: Promise) { >d : Symbol(d, Decl(mappedTypesArraysTuples.ts, 56, 55)) } +function f2(a: Boxified) { +>f2 : Symbol(f2, Decl(mappedTypesArraysTuples.ts, 61, 1)) +>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 63, 12)) +>a : Symbol(a, Decl(mappedTypesArraysTuples.ts, 63, 29)) +>Boxified : Symbol(Boxified, Decl(mappedTypesArraysTuples.ts, 0, 27)) +>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 63, 12)) + + let x: Box | undefined = a.pop(); +>x : Symbol(x, Decl(mappedTypesArraysTuples.ts, 64, 7)) +>Box : Symbol(Box, Decl(mappedTypesArraysTuples.ts, 0, 0)) +>a.pop : Symbol(Array.pop, Decl(lib.es5.d.ts, --, --)) +>a : Symbol(a, Decl(mappedTypesArraysTuples.ts, 63, 29)) +>pop : Symbol(Array.pop, Decl(lib.es5.d.ts, --, --)) + + let y: Box[] = a.concat(a); +>y : Symbol(y, Decl(mappedTypesArraysTuples.ts, 65, 7)) +>Box : Symbol(Box, Decl(mappedTypesArraysTuples.ts, 0, 0)) +>a.concat : Symbol(Array.concat, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>a : Symbol(a, Decl(mappedTypesArraysTuples.ts, 63, 29)) +>concat : Symbol(Array.concat, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>a : Symbol(a, Decl(mappedTypesArraysTuples.ts, 63, 29)) +} + +// Repro from #26163 + +type ElementType = T extends Array ? U : never; +>ElementType : Symbol(ElementType, Decl(mappedTypesArraysTuples.ts, 66, 1)) +>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 70, 17)) +>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 70, 17)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>U : Symbol(U, Decl(mappedTypesArraysTuples.ts, 70, 43)) +>U : Symbol(U, Decl(mappedTypesArraysTuples.ts, 70, 43)) + +type Mapped = { [K in keyof T]: T[K] }; +>Mapped : Symbol(Mapped, Decl(mappedTypesArraysTuples.ts, 70, 59)) +>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 71, 12)) +>K : Symbol(K, Decl(mappedTypesArraysTuples.ts, 71, 20)) +>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 71, 12)) +>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 71, 12)) +>K : Symbol(K, Decl(mappedTypesArraysTuples.ts, 71, 20)) + +type F = ElementType>; +>F : Symbol(F, Decl(mappedTypesArraysTuples.ts, 71, 42)) +>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 73, 7)) +>ElementType : Symbol(ElementType, Decl(mappedTypesArraysTuples.ts, 66, 1)) +>Mapped : Symbol(Mapped, Decl(mappedTypesArraysTuples.ts, 70, 59)) +>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 73, 7)) + +type R1 = F<[string, number, boolean]>; // string | number | boolean +>R1 : Symbol(R1, Decl(mappedTypesArraysTuples.ts, 73, 35)) +>F : Symbol(F, Decl(mappedTypesArraysTuples.ts, 71, 42)) + +type R2 = ElementType>; // string | number | boolean +>R2 : Symbol(R2, Decl(mappedTypesArraysTuples.ts, 74, 39)) +>ElementType : Symbol(ElementType, Decl(mappedTypesArraysTuples.ts, 66, 1)) +>Mapped : Symbol(Mapped, Decl(mappedTypesArraysTuples.ts, 70, 59)) + +// Repro from #26163 + +declare function acceptArray(arr: any[]): void; +>acceptArray : Symbol(acceptArray, Decl(mappedTypesArraysTuples.ts, 75, 57)) +>arr : Symbol(arr, Decl(mappedTypesArraysTuples.ts, 79, 29)) + +declare function mapArray(arr: T): Mapped; +>mapArray : Symbol(mapArray, Decl(mappedTypesArraysTuples.ts, 79, 47)) +>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 80, 26)) +>arr : Symbol(arr, Decl(mappedTypesArraysTuples.ts, 80, 43)) +>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 80, 26)) +>Mapped : Symbol(Mapped, Decl(mappedTypesArraysTuples.ts, 70, 59)) +>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 80, 26)) + +function acceptMappedArray(arr: T) { +>acceptMappedArray : Symbol(acceptMappedArray, Decl(mappedTypesArraysTuples.ts, 80, 62)) +>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 81, 27)) +>arr : Symbol(arr, Decl(mappedTypesArraysTuples.ts, 81, 44)) +>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 81, 27)) + + acceptArray(mapArray(arr)); +>acceptArray : Symbol(acceptArray, Decl(mappedTypesArraysTuples.ts, 75, 57)) +>mapArray : Symbol(mapArray, Decl(mappedTypesArraysTuples.ts, 79, 47)) +>arr : Symbol(arr, Decl(mappedTypesArraysTuples.ts, 81, 44)) +} + diff --git a/tests/baselines/reference/mappedTypesArraysTuples.types b/tests/baselines/reference/mappedTypesArraysTuples.types index e246f1d975..42ab457b78 100644 --- a/tests/baselines/reference/mappedTypesArraysTuples.types +++ b/tests/baselines/reference/mappedTypesArraysTuples.types @@ -182,3 +182,62 @@ function f1(a: number, b: Promise, c: string[], d: Promise) { >d : Promise } +function f2(a: Boxified) { +>f2 : (a: Boxified) => void +>a : Boxified + + let x: Box | undefined = a.pop(); +>x : Box | undefined +>a.pop() : Box | undefined +>a.pop : () => Box | undefined +>a : Boxified +>pop : () => Box | undefined + + let y: Box[] = a.concat(a); +>y : Box[] +>a.concat(a) : Box[] +>a.concat : { (...items: ConcatArray>[]): Box[]; (...items: (Box | ConcatArray>)[]): Box[]; } +>a : Boxified +>concat : { (...items: ConcatArray>[]): Box[]; (...items: (Box | ConcatArray>)[]): Box[]; } +>a : Boxified +} + +// Repro from #26163 + +type ElementType = T extends Array ? U : never; +>ElementType : ElementType + +type Mapped = { [K in keyof T]: T[K] }; +>Mapped : Mapped + +type F = ElementType>; +>F : ElementType> + +type R1 = F<[string, number, boolean]>; // string | number | boolean +>R1 : string | number | boolean + +type R2 = ElementType>; // string | number | boolean +>R2 : string | number | boolean + +// Repro from #26163 + +declare function acceptArray(arr: any[]): void; +>acceptArray : (arr: any[]) => void +>arr : any[] + +declare function mapArray(arr: T): Mapped; +>mapArray : (arr: T) => Mapped +>arr : T + +function acceptMappedArray(arr: T) { +>acceptMappedArray : (arr: T) => void +>arr : T + + acceptArray(mapArray(arr)); +>acceptArray(mapArray(arr)) : void +>acceptArray : (arr: any[]) => void +>mapArray(arr) : Mapped +>mapArray : (arr: T) => Mapped +>arr : T +} + diff --git a/tests/cases/conformance/types/mapped/mappedTypesArraysTuples.ts b/tests/cases/conformance/types/mapped/mappedTypesArraysTuples.ts index 0607462fe0..c936a86c89 100644 --- a/tests/cases/conformance/types/mapped/mappedTypesArraysTuples.ts +++ b/tests/cases/conformance/types/mapped/mappedTypesArraysTuples.ts @@ -63,3 +63,25 @@ function f1(a: number, b: Promise, c: string[], d: Promise) { let x3 = all(a, b, c); let x4 = all(a, b, c, d); } + +function f2(a: Boxified) { + let x: Box | undefined = a.pop(); + let y: Box[] = a.concat(a); +} + +// Repro from #26163 + +type ElementType = T extends Array ? U : never; +type Mapped = { [K in keyof T]: T[K] }; + +type F = ElementType>; +type R1 = F<[string, number, boolean]>; // string | number | boolean +type R2 = ElementType>; // string | number | boolean + +// Repro from #26163 + +declare function acceptArray(arr: any[]): void; +declare function mapArray(arr: T): Mapped; +function acceptMappedArray(arr: T) { + acceptArray(mapArray(arr)); +}