Skip to content
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

Fix mapped array type constraint #26517

Merged
merged 3 commits into from
Aug 17, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
111 changes: 67 additions & 44 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7119,14 +7119,31 @@ 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
* type itself. Note that the apparent type of a union type is the union type itself.
*/
function getApparentType(type: Type): Type {
const t = type.flags & TypeFlags.Instantiable ? getBaseConstraintOfType(type) || emptyObjectType : type;
return t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(<IntersectionType>t) :
return getObjectFlags(t) & ObjectFlags.Mapped ? getApparentTypeOfMappedType(<MappedType>t) :
t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(<IntersectionType>t) :
t.flags & TypeFlags.StringLike ? globalStringType :
t.flags & TypeFlags.NumberLike ? globalNumberType :
t.flags & TypeFlags.BooleanLike ? globalBooleanType :
Expand Down Expand Up @@ -10159,8 +10176,19 @@ namespace ts {
}
}

function getHomomorphicTypeVariable(type: MappedType) {
const constraintType = getConstraintTypeFromMappedType(type);
if (constraintType.flags & TypeFlags.Index) {
const typeVariable = (<IndexType>constraintType).type;
if (typeVariable.flags & TypeFlags.TypeParameter) {
return <TypeParameter>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.
Expand All @@ -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 = (<IndexType>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) =>
Expand Down Expand Up @@ -11623,7 +11644,6 @@ namespace ts {
const constraint = getConstraintForRelation(target);
if (constraint) {
if (result = isRelatedTo(source, constraint, reportErrors)) {
errorInfo = saveErrorInfo;
return result;
}
}
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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 && (<TypeReference>source).target === (<TypeReference>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
Expand Down Expand Up @@ -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);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3949,6 +3949,7 @@ namespace ts {
constraintType?: Type;
templateType?: Type;
modifiersType?: Type;
resolvedApparentType?: Type;
}

export interface EvolvingArrayType extends ObjectType {
Expand Down
40 changes: 40 additions & 0 deletions tests/baselines/reference/mappedTypesArraysTuples.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,28 @@ function f1(a: number, b: Promise<number>, c: string[], d: Promise<string[]>) {
let x3 = all(a, b, c);
let x4 = all(a, b, c, d);
}

function f2<T extends any[]>(a: Boxified<T>) {
let x: Box<any> | undefined = a.pop();
let y: Box<any>[] = a.concat(a);
}

// Repro from #26163

type ElementType<T> = T extends Array<infer U> ? U : never;
type Mapped<T> = { [K in keyof T]: T[K] };

type F<T> = ElementType<Mapped<T>>;
type R1 = F<[string, number, boolean]>; // string | number | boolean
type R2 = ElementType<Mapped<[string, number, boolean]>>; // string | number | boolean

// Repro from #26163

declare function acceptArray(arr: any[]): void;
declare function mapArray<T extends any[]>(arr: T): Mapped<T>;
function acceptMappedArray<T extends any[]>(arr: T) {
acceptArray(mapArray(arr));
}


//// [mappedTypesArraysTuples.js]
Expand All @@ -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]
Expand Down Expand Up @@ -142,3 +171,14 @@ declare type Awaitified<T> = {
};
declare function all<T extends any[]>(...values: T): Promise<Awaitified<T>>;
declare function f1(a: number, b: Promise<number>, c: string[], d: Promise<string[]>): void;
declare function f2<T extends any[]>(a: Boxified<T>): void;
declare type ElementType<T> = T extends Array<infer U> ? U : never;
declare type Mapped<T> = {
[K in keyof T]: T[K];
};
declare type F<T> = ElementType<Mapped<T>>;
declare type R1 = F<[string, number, boolean]>;
declare type R2 = ElementType<Mapped<[string, number, boolean]>>;
declare function acceptArray(arr: any[]): void;
declare function mapArray<T extends any[]>(arr: T): Mapped<T>;
declare function acceptMappedArray<T extends any[]>(arr: T): void;
83 changes: 83 additions & 0 deletions tests/baselines/reference/mappedTypesArraysTuples.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,86 @@ function f1(a: number, b: Promise<number>, c: string[], d: Promise<string[]>) {
>d : Symbol(d, Decl(mappedTypesArraysTuples.ts, 56, 55))
}

function f2<T extends any[]>(a: Boxified<T>) {
>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<any> | 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<any>[] = 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> = T extends Array<infer U> ? 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<T> = { [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<T> = ElementType<Mapped<T>>;
>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<Mapped<[string, number, boolean]>>; // 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<T extends any[]>(arr: T): Mapped<T>;
>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<T extends any[]>(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))
}

Loading