Skip to content

Commit 3e201e7

Browse files
authored
Merge pull request #26517 from Microsoft/fixMappedArrayTypeConstraint
Fix mapped array type constraint
2 parents b746da2 + 53f5e84 commit 3e201e7

File tree

6 files changed

+272
-44
lines changed

6 files changed

+272
-44
lines changed

src/compiler/checker.ts

Lines changed: 67 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7119,14 +7119,31 @@ namespace ts {
71197119
return !!(typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default));
71207120
}
71217121

7122+
function getApparentTypeOfMappedType(type: MappedType) {
7123+
return type.resolvedApparentType || (type.resolvedApparentType = getResolvedApparentTypeOfMappedType(type));
7124+
}
7125+
7126+
function getResolvedApparentTypeOfMappedType(type: MappedType) {
7127+
const typeVariable = getHomomorphicTypeVariable(type);
7128+
if (typeVariable) {
7129+
const constraint = getConstraintOfTypeParameter(typeVariable);
7130+
if (constraint && (isArrayType(constraint) || isReadonlyArrayType(constraint) || isTupleType(constraint))) {
7131+
const mapper = makeUnaryTypeMapper(typeVariable, constraint);
7132+
return instantiateType(type, combineTypeMappers(mapper, type.mapper));
7133+
}
7134+
}
7135+
return type;
7136+
}
7137+
71227138
/**
71237139
* For a type parameter, return the base constraint of the type parameter. For the string, number,
71247140
* boolean, and symbol primitive types, return the corresponding object types. Otherwise return the
71257141
* type itself. Note that the apparent type of a union type is the union type itself.
71267142
*/
71277143
function getApparentType(type: Type): Type {
71287144
const t = type.flags & TypeFlags.Instantiable ? getBaseConstraintOfType(type) || emptyObjectType : type;
7129-
return t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(<IntersectionType>t) :
7145+
return getObjectFlags(t) & ObjectFlags.Mapped ? getApparentTypeOfMappedType(<MappedType>t) :
7146+
t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(<IntersectionType>t) :
71307147
t.flags & TypeFlags.StringLike ? globalStringType :
71317148
t.flags & TypeFlags.NumberLike ? globalNumberType :
71327149
t.flags & TypeFlags.BooleanLike ? globalBooleanType :
@@ -10159,8 +10176,19 @@ namespace ts {
1015910176
}
1016010177
}
1016110178

10179+
function getHomomorphicTypeVariable(type: MappedType) {
10180+
const constraintType = getConstraintTypeFromMappedType(type);
10181+
if (constraintType.flags & TypeFlags.Index) {
10182+
const typeVariable = (<IndexType>constraintType).type;
10183+
if (typeVariable.flags & TypeFlags.TypeParameter) {
10184+
return <TypeParameter>typeVariable;
10185+
}
10186+
}
10187+
return undefined;
10188+
}
10189+
1016210190
function instantiateMappedType(type: MappedType, mapper: TypeMapper): Type {
10163-
// For a momomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping
10191+
// For a homomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping
1016410192
// operation depends on T as follows:
1016510193
// * If T is a primitive type no mapping is performed and the result is simply T.
1016610194
// * If T is a union type we distribute the mapped type over the union.
@@ -10170,32 +10198,25 @@ namespace ts {
1017010198
// For example, when T is instantiated to a union type A | B, we produce { [P in keyof A]: X } |
1017110199
// { [P in keyof B]: X }, and when when T is instantiated to a union type A | undefined, we produce
1017210200
// { [P in keyof A]: X } | undefined.
10173-
const constraintType = getConstraintTypeFromMappedType(type);
10174-
if (constraintType.flags & TypeFlags.Index) {
10175-
const typeVariable = (<IndexType>constraintType).type;
10176-
if (typeVariable.flags & TypeFlags.TypeParameter) {
10177-
const mappedTypeVariable = instantiateType(typeVariable, mapper);
10178-
if (typeVariable !== mappedTypeVariable) {
10179-
return mapType(mappedTypeVariable, t => {
10180-
if (isMappableType(t)) {
10181-
const replacementMapper = createReplacementMapper(typeVariable, t, mapper);
10182-
return isArrayType(t) ? createArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) :
10183-
isReadonlyArrayType(t) ? createReadonlyArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) :
10184-
isTupleType(t) ? instantiateMappedTupleType(t, type, replacementMapper) :
10185-
instantiateAnonymousType(type, replacementMapper);
10186-
}
10187-
return t;
10188-
});
10189-
}
10201+
const typeVariable = getHomomorphicTypeVariable(type);
10202+
if (typeVariable) {
10203+
const mappedTypeVariable = instantiateType(typeVariable, mapper);
10204+
if (typeVariable !== mappedTypeVariable) {
10205+
return mapType(mappedTypeVariable, t => {
10206+
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType) {
10207+
const replacementMapper = createReplacementMapper(typeVariable, t, mapper);
10208+
return isArrayType(t) ? createArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) :
10209+
isReadonlyArrayType(t) ? createReadonlyArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) :
10210+
isTupleType(t) ? instantiateMappedTupleType(t, type, replacementMapper) :
10211+
instantiateAnonymousType(type, replacementMapper);
10212+
}
10213+
return t;
10214+
});
1019010215
}
1019110216
}
1019210217
return instantiateAnonymousType(type, mapper);
1019310218
}
1019410219

10195-
function isMappableType(type: Type) {
10196-
return type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection);
10197-
}
10198-
1019910220
function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) {
1020010221
const minLength = tupleType.target.minLength;
1020110222
const elementTypes = map(tupleType.typeArguments || emptyArray, (_, i) =>
@@ -11623,7 +11644,6 @@ namespace ts {
1162311644
const constraint = getConstraintForRelation(target);
1162411645
if (constraint) {
1162511646
if (result = isRelatedTo(source, constraint, reportErrors)) {
11626-
errorInfo = saveErrorInfo;
1162711647
return result;
1162811648
}
1162911649
}
@@ -11642,7 +11662,6 @@ namespace ts {
1164211662
const indexedAccessType = getIndexedAccessType(source, getTypeParameterFromMappedType(target));
1164311663
const templateType = getTemplateTypeFromMappedType(target);
1164411664
if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) {
11645-
errorInfo = saveErrorInfo;
1164611665
return result;
1164711666
}
1164811667
}
@@ -11716,6 +11735,23 @@ namespace ts {
1171611735
}
1171711736
}
1171811737
else {
11738+
// An empty object type is related to any mapped type that includes a '?' modifier.
11739+
if (isPartialMappedType(target) && !isGenericMappedType(source) && isEmptyObjectType(source)) {
11740+
return Ternary.True;
11741+
}
11742+
if (isGenericMappedType(target)) {
11743+
if (isGenericMappedType(source)) {
11744+
if (result = mappedTypeRelatedTo(source, target, reportErrors)) {
11745+
errorInfo = saveErrorInfo;
11746+
return result;
11747+
}
11748+
}
11749+
return Ternary.False;
11750+
}
11751+
const sourceIsPrimitive = !!(source.flags & TypeFlags.Primitive);
11752+
if (relation !== identityRelation) {
11753+
source = getApparentType(source);
11754+
}
1171911755
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target &&
1172011756
!(getObjectFlags(source) & ObjectFlags.MarkerType || getObjectFlags(target) & ObjectFlags.MarkerType)) {
1172111757
// We have type references to the same generic type, and the type references are not marker
@@ -11751,34 +11787,21 @@ namespace ts {
1175111787
}
1175211788
// Even if relationship doesn't hold for unions, intersections, or generic type references,
1175311789
// it may hold in a structural comparison.
11754-
const sourceIsPrimitive = !!(source.flags & TypeFlags.Primitive);
11755-
if (relation !== identityRelation) {
11756-
source = getApparentType(source);
11757-
}
1175811790
// In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
1175911791
// to X. Failing both of those we want to check if the aggregation of A and B's members structurally
1176011792
// relates to X. Thus, we include intersection types on the source side here.
1176111793
if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) {
1176211794
// Report structural errors only if we haven't reported any errors yet
1176311795
const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !sourceIsPrimitive;
11764-
// An empty object type is related to any mapped type that includes a '?' modifier.
11765-
if (isPartialMappedType(target) && !isGenericMappedType(source) && isEmptyObjectType(source)) {
11766-
result = Ternary.True;
11767-
}
11768-
else if (isGenericMappedType(target)) {
11769-
result = isGenericMappedType(source) ? mappedTypeRelatedTo(source, target, reportStructuralErrors) : Ternary.False;
11770-
}
11771-
else {
11772-
result = propertiesRelatedTo(source, target, reportStructuralErrors);
11796+
result = propertiesRelatedTo(source, target, reportStructuralErrors);
11797+
if (result) {
11798+
result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors);
1177311799
if (result) {
11774-
result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors);
11800+
result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors);
1177511801
if (result) {
11776-
result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors);
11802+
result &= indexTypesRelatedTo(source, target, IndexKind.String, sourceIsPrimitive, reportStructuralErrors);
1177711803
if (result) {
11778-
result &= indexTypesRelatedTo(source, target, IndexKind.String, sourceIsPrimitive, reportStructuralErrors);
11779-
if (result) {
11780-
result &= indexTypesRelatedTo(source, target, IndexKind.Number, sourceIsPrimitive, reportStructuralErrors);
11781-
}
11804+
result &= indexTypesRelatedTo(source, target, IndexKind.Number, sourceIsPrimitive, reportStructuralErrors);
1178211805
}
1178311806
}
1178411807
}

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3949,6 +3949,7 @@ namespace ts {
39493949
constraintType?: Type;
39503950
templateType?: Type;
39513951
modifiersType?: Type;
3952+
resolvedApparentType?: Type;
39523953
}
39533954

39543955
export interface EvolvingArrayType extends ObjectType {

tests/baselines/reference/mappedTypesArraysTuples.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,28 @@ function f1(a: number, b: Promise<number>, c: string[], d: Promise<string[]>) {
6161
let x3 = all(a, b, c);
6262
let x4 = all(a, b, c, d);
6363
}
64+
65+
function f2<T extends any[]>(a: Boxified<T>) {
66+
let x: Box<any> | undefined = a.pop();
67+
let y: Box<any>[] = a.concat(a);
68+
}
69+
70+
// Repro from #26163
71+
72+
type ElementType<T> = T extends Array<infer U> ? U : never;
73+
type Mapped<T> = { [K in keyof T]: T[K] };
74+
75+
type F<T> = ElementType<Mapped<T>>;
76+
type R1 = F<[string, number, boolean]>; // string | number | boolean
77+
type R2 = ElementType<Mapped<[string, number, boolean]>>; // string | number | boolean
78+
79+
// Repro from #26163
80+
81+
declare function acceptArray(arr: any[]): void;
82+
declare function mapArray<T extends any[]>(arr: T): Mapped<T>;
83+
function acceptMappedArray<T extends any[]>(arr: T) {
84+
acceptArray(mapArray(arr));
85+
}
6486

6587

6688
//// [mappedTypesArraysTuples.js]
@@ -77,6 +99,13 @@ function f1(a, b, c, d) {
7799
var x3 = all(a, b, c);
78100
var x4 = all(a, b, c, d);
79101
}
102+
function f2(a) {
103+
var x = a.pop();
104+
var y = a.concat(a);
105+
}
106+
function acceptMappedArray(arr) {
107+
acceptArray(mapArray(arr));
108+
}
80109

81110

82111
//// [mappedTypesArraysTuples.d.ts]
@@ -142,3 +171,14 @@ declare type Awaitified<T> = {
142171
};
143172
declare function all<T extends any[]>(...values: T): Promise<Awaitified<T>>;
144173
declare function f1(a: number, b: Promise<number>, c: string[], d: Promise<string[]>): void;
174+
declare function f2<T extends any[]>(a: Boxified<T>): void;
175+
declare type ElementType<T> = T extends Array<infer U> ? U : never;
176+
declare type Mapped<T> = {
177+
[K in keyof T]: T[K];
178+
};
179+
declare type F<T> = ElementType<Mapped<T>>;
180+
declare type R1 = F<[string, number, boolean]>;
181+
declare type R2 = ElementType<Mapped<[string, number, boolean]>>;
182+
declare function acceptArray(arr: any[]): void;
183+
declare function mapArray<T extends any[]>(arr: T): Mapped<T>;
184+
declare function acceptMappedArray<T extends any[]>(arr: T): void;

tests/baselines/reference/mappedTypesArraysTuples.symbols

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,3 +245,86 @@ function f1(a: number, b: Promise<number>, c: string[], d: Promise<string[]>) {
245245
>d : Symbol(d, Decl(mappedTypesArraysTuples.ts, 56, 55))
246246
}
247247

248+
function f2<T extends any[]>(a: Boxified<T>) {
249+
>f2 : Symbol(f2, Decl(mappedTypesArraysTuples.ts, 61, 1))
250+
>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 63, 12))
251+
>a : Symbol(a, Decl(mappedTypesArraysTuples.ts, 63, 29))
252+
>Boxified : Symbol(Boxified, Decl(mappedTypesArraysTuples.ts, 0, 27))
253+
>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 63, 12))
254+
255+
let x: Box<any> | undefined = a.pop();
256+
>x : Symbol(x, Decl(mappedTypesArraysTuples.ts, 64, 7))
257+
>Box : Symbol(Box, Decl(mappedTypesArraysTuples.ts, 0, 0))
258+
>a.pop : Symbol(Array.pop, Decl(lib.es5.d.ts, --, --))
259+
>a : Symbol(a, Decl(mappedTypesArraysTuples.ts, 63, 29))
260+
>pop : Symbol(Array.pop, Decl(lib.es5.d.ts, --, --))
261+
262+
let y: Box<any>[] = a.concat(a);
263+
>y : Symbol(y, Decl(mappedTypesArraysTuples.ts, 65, 7))
264+
>Box : Symbol(Box, Decl(mappedTypesArraysTuples.ts, 0, 0))
265+
>a.concat : Symbol(Array.concat, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
266+
>a : Symbol(a, Decl(mappedTypesArraysTuples.ts, 63, 29))
267+
>concat : Symbol(Array.concat, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
268+
>a : Symbol(a, Decl(mappedTypesArraysTuples.ts, 63, 29))
269+
}
270+
271+
// Repro from #26163
272+
273+
type ElementType<T> = T extends Array<infer U> ? U : never;
274+
>ElementType : Symbol(ElementType, Decl(mappedTypesArraysTuples.ts, 66, 1))
275+
>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 70, 17))
276+
>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 70, 17))
277+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
278+
>U : Symbol(U, Decl(mappedTypesArraysTuples.ts, 70, 43))
279+
>U : Symbol(U, Decl(mappedTypesArraysTuples.ts, 70, 43))
280+
281+
type Mapped<T> = { [K in keyof T]: T[K] };
282+
>Mapped : Symbol(Mapped, Decl(mappedTypesArraysTuples.ts, 70, 59))
283+
>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 71, 12))
284+
>K : Symbol(K, Decl(mappedTypesArraysTuples.ts, 71, 20))
285+
>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 71, 12))
286+
>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 71, 12))
287+
>K : Symbol(K, Decl(mappedTypesArraysTuples.ts, 71, 20))
288+
289+
type F<T> = ElementType<Mapped<T>>;
290+
>F : Symbol(F, Decl(mappedTypesArraysTuples.ts, 71, 42))
291+
>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 73, 7))
292+
>ElementType : Symbol(ElementType, Decl(mappedTypesArraysTuples.ts, 66, 1))
293+
>Mapped : Symbol(Mapped, Decl(mappedTypesArraysTuples.ts, 70, 59))
294+
>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 73, 7))
295+
296+
type R1 = F<[string, number, boolean]>; // string | number | boolean
297+
>R1 : Symbol(R1, Decl(mappedTypesArraysTuples.ts, 73, 35))
298+
>F : Symbol(F, Decl(mappedTypesArraysTuples.ts, 71, 42))
299+
300+
type R2 = ElementType<Mapped<[string, number, boolean]>>; // string | number | boolean
301+
>R2 : Symbol(R2, Decl(mappedTypesArraysTuples.ts, 74, 39))
302+
>ElementType : Symbol(ElementType, Decl(mappedTypesArraysTuples.ts, 66, 1))
303+
>Mapped : Symbol(Mapped, Decl(mappedTypesArraysTuples.ts, 70, 59))
304+
305+
// Repro from #26163
306+
307+
declare function acceptArray(arr: any[]): void;
308+
>acceptArray : Symbol(acceptArray, Decl(mappedTypesArraysTuples.ts, 75, 57))
309+
>arr : Symbol(arr, Decl(mappedTypesArraysTuples.ts, 79, 29))
310+
311+
declare function mapArray<T extends any[]>(arr: T): Mapped<T>;
312+
>mapArray : Symbol(mapArray, Decl(mappedTypesArraysTuples.ts, 79, 47))
313+
>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 80, 26))
314+
>arr : Symbol(arr, Decl(mappedTypesArraysTuples.ts, 80, 43))
315+
>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 80, 26))
316+
>Mapped : Symbol(Mapped, Decl(mappedTypesArraysTuples.ts, 70, 59))
317+
>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 80, 26))
318+
319+
function acceptMappedArray<T extends any[]>(arr: T) {
320+
>acceptMappedArray : Symbol(acceptMappedArray, Decl(mappedTypesArraysTuples.ts, 80, 62))
321+
>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 81, 27))
322+
>arr : Symbol(arr, Decl(mappedTypesArraysTuples.ts, 81, 44))
323+
>T : Symbol(T, Decl(mappedTypesArraysTuples.ts, 81, 27))
324+
325+
acceptArray(mapArray(arr));
326+
>acceptArray : Symbol(acceptArray, Decl(mappedTypesArraysTuples.ts, 75, 57))
327+
>mapArray : Symbol(mapArray, Decl(mappedTypesArraysTuples.ts, 79, 47))
328+
>arr : Symbol(arr, Decl(mappedTypesArraysTuples.ts, 81, 44))
329+
}
330+

0 commit comments

Comments
 (0)