From 2646828198fb468eabec12ebc6a9446d4e9a7cce Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 17 Nov 2016 20:18:00 -0800 Subject: [PATCH 1/6] Type relations for generic mapped types --- src/compiler/checker.ts | 112 ++++++++++++++++++++------- src/compiler/diagnosticMessages.json | 2 +- src/compiler/types.ts | 4 +- 3 files changed, 86 insertions(+), 32 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3d25fc8bbbb0d..5ea185556f7ba 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -120,6 +120,7 @@ namespace ts { const intersectionTypes = createMap(); const stringLiteralTypes = createMap(); const numericLiteralTypes = createMap(); + const indexedAccessTypes = createMap(); const evolvingArrayTypes: EvolvingArrayType[] = []; const unknownSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "unknown"); @@ -5907,6 +5908,7 @@ namespace ts { function getIndexType(type: Type): Type { return type.flags & TypeFlags.TypeParameter ? getIndexTypeForTypeParameter(type) : + getObjectFlags(type) & ObjectFlags.Mapped ? getConstraintTypeFromMappedType(type) : type.flags & TypeFlags.Any || getIndexInfoOfType(type, IndexKind.String) ? stringOrNumberType : getIndexInfoOfType(type, IndexKind.Number) ? getUnionType([numberType, getLiteralTypeFromPropertyNames(type)]) : getLiteralTypeFromPropertyNames(type); @@ -5920,18 +5922,13 @@ namespace ts { return links.resolvedType; } - function createIndexedAccessType(objectType: Type, indexType: TypeParameter) { + function createIndexedAccessType(objectType: Type, indexType: Type) { const type = createType(TypeFlags.IndexedAccess); type.objectType = objectType; type.indexType = indexType; return type; } - function getIndexedAccessTypeForTypeParameter(objectType: Type, indexType: TypeParameter) { - const indexedAccessTypes = indexType.resolvedIndexedAccessTypes || (indexType.resolvedIndexedAccessTypes = []); - return indexedAccessTypes[objectType.id] || (indexedAccessTypes[objectType.id] = createIndexedAccessType(objectType, indexType)); - } - function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode, cacheSymbol: boolean) { const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; const propName = indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral | TypeFlags.EnumLiteral) ? @@ -5995,13 +5992,41 @@ namespace ts { return unknownType; } + function getIndexedAccessForMappedType(type: MappedType, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) { + const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; + if (accessExpression && isAssignmentTarget(accessExpression) && type.declaration.readonlyToken) { + error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(type)); + return unknownType; + } + const mapper = createUnaryTypeMapper(getTypeParameterFromMappedType(type), indexType); + const templateMapper = type.mapper ? combineTypeMappers(type.mapper, mapper) : mapper; + return addOptionality(instantiateType(getTemplateTypeFromMappedType(type), templateMapper), !!type.declaration.questionToken); + } + function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) { - if (indexType.flags & TypeFlags.TypeParameter) { - if (accessNode && !isTypeAssignableTo(getConstraintOfTypeParameter(indexType) || emptyObjectType, getIndexType(objectType))) { - error(accessNode, Diagnostics.Type_0_is_not_constrained_to_keyof_1, typeToString(indexType), typeToString(objectType)); - return unknownType; + if (indexType.flags & TypeFlags.TypeParameter || + objectType.flags & TypeFlags.TypeParameter && indexType.flags & TypeFlags.Index || + isGenericMappedType(objectType)) { + // If either the object type or the index type are type parameters, or if the object type is a mapped + // type with a generic constraint, we are performing a higher-order index access where we cannot + // meaningfully access the properties of the object type. In those cases, we first check that the + // index type is assignable to 'keyof T' for the object type. + if (accessNode) { + const keyType = indexType.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(indexType) || emptyObjectType : indexType; + if (!isTypeAssignableTo(keyType, getIndexType(objectType))) { + error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType)); + return unknownType; + } } - return getIndexedAccessTypeForTypeParameter(objectType, indexType); + // If the object type is a mapped type { [P in K]: E }, we instantiate E using a mapper that substitutes + // the index type for P. For example, for an index access { [P in K]: Box }[X], we construct the + // type Box. + if (isGenericMappedType(objectType)) { + return getIndexedAccessForMappedType(objectType, indexType, accessNode); + } + // Otherwise we defer the operation by creating an indexed access type. + const id = objectType.id + "," + indexType.id; + return indexedAccessTypes[id] || (indexedAccessTypes[id] = createIndexedAccessType(objectType, indexType)); } const apparentType = getApparentType(objectType); if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Primitive)) { @@ -7153,12 +7178,24 @@ namespace ts { } if (target.flags & TypeFlags.TypeParameter) { - // Given a type parameter K with a constraint keyof T, a type S is - // assignable to K if S is assignable to keyof T. - const constraint = getConstraintOfTypeParameter(target); - if (constraint && constraint.flags & TypeFlags.Index) { - if (result = isRelatedTo(source, constraint, reportErrors)) { - return result; + // A source type { [P in keyof T]: X } is related to a target type T if X is related to T[P]. + if (getObjectFlags(source) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(source) === getIndexType(target)) { + if (!(source).declaration.questionToken) { + const templateType = getTemplateTypeFromMappedType(source); + const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(source)); + if (result = isRelatedTo(templateType, indexedAccessType, reportErrors)) { + return result; + } + } + } + else { + // Given a type parameter K with a constraint keyof T, a type S is + // assignable to K if S is assignable to keyof T. + const constraint = getConstraintOfTypeParameter(target); + if (constraint && constraint.flags & TypeFlags.Index) { + if (result = isRelatedTo(source, constraint, reportErrors)) { + return result; + } } } } @@ -7178,22 +7215,41 @@ namespace ts { } } } + else if (target.flags & TypeFlags.IndexedAccess) { + // if we have indexed access types with identical index types, see if relationship holds for + // the two object types. + if (source.flags & TypeFlags.IndexedAccess && (source).indexType === (target).indexType) { + if (result = isRelatedTo((source).objectType, (target).objectType, reportErrors)) { + return result; + } + } + } if (source.flags & TypeFlags.TypeParameter) { - let constraint = getConstraintOfTypeParameter(source); - - if (!constraint || constraint.flags & TypeFlags.Any) { - constraint = emptyObjectType; + // A source type T is related to a target type { [P in keyof T]: X } if T[P] is related to X. + if (getObjectFlags(target) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(target) === getIndexType(source)) { + const indexedAccessType = getIndexedAccessType(source, getTypeParameterFromMappedType(target)); + const templateType = getTemplateTypeFromMappedType(target); + if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) { + return result; + } } + else { + let constraint = getConstraintOfTypeParameter(source); + + if (!constraint || constraint.flags & TypeFlags.Any) { + constraint = emptyObjectType; + } - // The constraint may need to be further instantiated with its 'this' type. - constraint = getTypeWithThisArgument(constraint, source); + // The constraint may need to be further instantiated with its 'this' type. + constraint = getTypeWithThisArgument(constraint, source); - // Report constraint errors only if the constraint is not the empty object type - const reportConstraintErrors = reportErrors && constraint !== emptyObjectType; - if (result = isRelatedTo(constraint, target, reportConstraintErrors)) { - errorInfo = saveErrorInfo; - return result; + // Report constraint errors only if the constraint is not the empty object type + const reportConstraintErrors = reportErrors && constraint !== emptyObjectType; + if (result = isRelatedTo(constraint, target, reportConstraintErrors)) { + errorInfo = saveErrorInfo; + return result; + } } } else { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 2dd4e76f8f44b..68e7bb6e9826d 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1731,7 +1731,7 @@ "category": "Error", "code": 2535 }, - "Type '{0}' is not constrained to 'keyof {1}'.": { + "Type '{0}' cannot be used to index type '{1}'.": { "category": "Error", "code": 2536 }, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b5e3eefbb2d6a..2b8b6fe294e55 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2974,8 +2974,6 @@ namespace ts { /* @internal */ resolvedIndexType: IndexType; /* @internal */ - resolvedIndexedAccessTypes: IndexedAccessType[]; - /* @internal */ isThisType?: boolean; } @@ -2985,7 +2983,7 @@ namespace ts { export interface IndexedAccessType extends Type { objectType: Type; - indexType: TypeParameter; + indexType: Type; } export const enum SignatureKind { From 63387bb5e00d14642c6ca52470297dd5b14c8211 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 18 Nov 2016 06:13:04 -0800 Subject: [PATCH 2/6] Error on circular constraints in mapped types --- src/compiler/checker.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5ea185556f7ba..f6689471cd885 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6059,6 +6059,9 @@ namespace ts { type.aliasSymbol = getAliasSymbolForTypeNode(node); type.aliasTypeArguments = getAliasTypeArgumentsForTypeNode(node); links.resolvedType = type; + // Eagerly resolve the constraint type which forces an error if the constraint type circularly + // references itself through one or more type aliases. + getConstraintTypeFromMappedType(type); } return links.resolvedType; } From 79bdc267456fcf2a7f39013c07c84931f595ac79 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 18 Nov 2016 06:27:24 -0800 Subject: [PATCH 3/6] Accept new baselines --- .../reference/recursiveMappedTypes.errors.txt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/baselines/reference/recursiveMappedTypes.errors.txt diff --git a/tests/baselines/reference/recursiveMappedTypes.errors.txt b/tests/baselines/reference/recursiveMappedTypes.errors.txt new file mode 100644 index 0000000000000..3dd6a55cf8cdb --- /dev/null +++ b/tests/baselines/reference/recursiveMappedTypes.errors.txt @@ -0,0 +1,26 @@ +tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(4,6): error TS2456: Type alias 'Recurse' circularly references itself. +tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(8,6): error TS2456: Type alias 'Recurse1' circularly references itself. +tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(12,6): error TS2456: Type alias 'Recurse2' circularly references itself. + + +==== tests/cases/conformance/types/mapped/recursiveMappedTypes.ts (3 errors) ==== + + // Recursive mapped types simply appear empty + + type Recurse = { + ~~~~~~~ +!!! error TS2456: Type alias 'Recurse' circularly references itself. + [K in keyof Recurse]: Recurse[K] + } + + type Recurse1 = { + ~~~~~~~~ +!!! error TS2456: Type alias 'Recurse1' circularly references itself. + [K in keyof Recurse2]: Recurse2[K] + } + + type Recurse2 = { + ~~~~~~~~ +!!! error TS2456: Type alias 'Recurse2' circularly references itself. + [K in keyof Recurse1]: Recurse1[K] + } \ No newline at end of file From 075a3eb98fd5d639732bc6d47ee9c287c8cd6c64 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 18 Nov 2016 06:27:45 -0800 Subject: [PATCH 4/6] Add new tests --- .../mappedTypeRelationships.errors.txt | 165 ++++++++++++++++ .../reference/mappedTypeRelationships.js | 182 ++++++++++++++++++ .../types/mapped/mappedTypeRelationships.ts | 90 +++++++++ 3 files changed, 437 insertions(+) create mode 100644 tests/baselines/reference/mappedTypeRelationships.errors.txt create mode 100644 tests/baselines/reference/mappedTypeRelationships.js create mode 100644 tests/cases/conformance/types/mapped/mappedTypeRelationships.ts diff --git a/tests/baselines/reference/mappedTypeRelationships.errors.txt b/tests/baselines/reference/mappedTypeRelationships.errors.txt new file mode 100644 index 0000000000000..adafbc5f3a722 --- /dev/null +++ b/tests/baselines/reference/mappedTypeRelationships.errors.txt @@ -0,0 +1,165 @@ +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(12,5): error TS2322: Type 'T[keyof T]' is not assignable to type 'U[keyof T]'. + Type 'T' is not assignable to type 'U'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(17,5): error TS2322: Type 'T[K]' is not assignable to type 'U[K]'. + Type 'T' is not assignable to type 'U'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(21,5): error TS2536: Type 'keyof U' cannot be used to index type 'T'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(22,12): error TS2536: Type 'keyof U' cannot be used to index type 'T'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(26,5): error TS2536: Type 'K' cannot be used to index type 'T'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(27,12): error TS2536: Type 'K' cannot be used to index type 'T'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(31,5): error TS2322: Type 'T[keyof T] | undefined' is not assignable to type 'T[keyof T]'. + Type 'undefined' is not assignable to type 'T[keyof T]'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(36,5): error TS2322: Type 'T[K] | undefined' is not assignable to type 'T[K]'. + Type 'undefined' is not assignable to type 'T[K]'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(41,5): error TS2322: Type 'U[keyof T] | undefined' is not assignable to type 'T[keyof T]'. + Type 'undefined' is not assignable to type 'T[keyof T]'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(42,5): error TS2322: Type 'T[keyof T]' is not assignable to type 'U[keyof T] | undefined'. + Type 'T[keyof T]' is not assignable to type 'U[keyof T]'. + Type 'T' is not assignable to type 'U'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(46,5): error TS2322: Type 'U[K] | undefined' is not assignable to type 'T[K]'. + Type 'undefined' is not assignable to type 'T[K]'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(47,5): error TS2322: Type 'T[K]' is not assignable to type 'U[K] | undefined'. + Type 'T[K]' is not assignable to type 'U[K]'. + Type 'T' is not assignable to type 'U'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(52,5): error TS2542: Index signature in type 'Readonly' only permits reading. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(57,5): error TS2542: Index signature in type 'Readonly' only permits reading. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(62,5): error TS2542: Index signature in type 'Readonly' only permits reading. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(67,5): error TS2542: Index signature in type 'Readonly' only permits reading. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(71,5): error TS2322: Type 'Partial' is not assignable to type 'T'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(76,5): error TS2322: Type 'Partial' is not assignable to type 'T'. + + +==== tests/cases/conformance/types/mapped/mappedTypeRelationships.ts (18 errors) ==== + + function f1(x: T, k: keyof T) { + return x[k]; + } + + function f2(x: T, k: K) { + return x[k]; + } + + function f3(x: T, y: U, k: keyof T) { + x[k] = y[k]; + y[k] = x[k]; // Error + ~~~~ +!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'U[keyof T]'. +!!! error TS2322: Type 'T' is not assignable to type 'U'. + } + + function f4(x: T, y: U, k: K) { + x[k] = y[k]; + y[k] = x[k]; // Error + ~~~~ +!!! error TS2322: Type 'T[K]' is not assignable to type 'U[K]'. +!!! error TS2322: Type 'T' is not assignable to type 'U'. + } + + function f5(x: T, y: U, k: keyof U) { + x[k] = y[k]; // Error + ~~~~ +!!! error TS2536: Type 'keyof U' cannot be used to index type 'T'. + y[k] = x[k]; // Error + ~~~~ +!!! error TS2536: Type 'keyof U' cannot be used to index type 'T'. + } + + function f6(x: T, y: U, k: K) { + x[k] = y[k]; // Error + ~~~~ +!!! error TS2536: Type 'K' cannot be used to index type 'T'. + y[k] = x[k]; // Error + ~~~~ +!!! error TS2536: Type 'K' cannot be used to index type 'T'. + } + + function f10(x: T, y: Partial, k: keyof T) { + x[k] = y[k]; // Error + ~~~~ +!!! error TS2322: Type 'T[keyof T] | undefined' is not assignable to type 'T[keyof T]'. +!!! error TS2322: Type 'undefined' is not assignable to type 'T[keyof T]'. + y[k] = x[k]; + } + + function f11(x: T, y: Partial, k: K) { + x[k] = y[k]; // Error + ~~~~ +!!! error TS2322: Type 'T[K] | undefined' is not assignable to type 'T[K]'. +!!! error TS2322: Type 'undefined' is not assignable to type 'T[K]'. + y[k] = x[k]; + } + + function f12(x: T, y: Partial, k: keyof T) { + x[k] = y[k]; // Error + ~~~~ +!!! error TS2322: Type 'U[keyof T] | undefined' is not assignable to type 'T[keyof T]'. +!!! error TS2322: Type 'undefined' is not assignable to type 'T[keyof T]'. + y[k] = x[k]; // Error + ~~~~ +!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'U[keyof T] | undefined'. +!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'U[keyof T]'. +!!! error TS2322: Type 'T' is not assignable to type 'U'. + } + + function f13(x: T, y: Partial, k: K) { + x[k] = y[k]; // Error + ~~~~ +!!! error TS2322: Type 'U[K] | undefined' is not assignable to type 'T[K]'. +!!! error TS2322: Type 'undefined' is not assignable to type 'T[K]'. + y[k] = x[k]; // Error + ~~~~ +!!! error TS2322: Type 'T[K]' is not assignable to type 'U[K] | undefined'. +!!! error TS2322: Type 'T[K]' is not assignable to type 'U[K]'. +!!! error TS2322: Type 'T' is not assignable to type 'U'. + } + + function f20(x: T, y: Readonly, k: keyof T) { + x[k] = y[k]; + y[k] = x[k]; // Error + ~~~~ +!!! error TS2542: Index signature in type 'Readonly' only permits reading. + } + + function f21(x: T, y: Readonly, k: K) { + x[k] = y[k]; + y[k] = x[k]; // Error + ~~~~ +!!! error TS2542: Index signature in type 'Readonly' only permits reading. + } + + function f22(x: T, y: Readonly, k: keyof T) { + x[k] = y[k]; + y[k] = x[k]; // Error + ~~~~ +!!! error TS2542: Index signature in type 'Readonly' only permits reading. + } + + function f23(x: T, y: Readonly, k: K) { + x[k] = y[k]; + y[k] = x[k]; // Error + ~~~~ +!!! error TS2542: Index signature in type 'Readonly' only permits reading. + } + + function f30(x: T, y: Partial) { + x = y; // Error + ~ +!!! error TS2322: Type 'Partial' is not assignable to type 'T'. + y = x; + } + + function f31(x: T, y: Partial) { + x = y; // Error + ~ +!!! error TS2322: Type 'Partial' is not assignable to type 'T'. + y = x; + } + + function f40(x: T, y: Readonly) { + x = y; + y = x; + } + + function f41(x: T, y: Readonly) { + x = y; + y = x; + } \ No newline at end of file diff --git a/tests/baselines/reference/mappedTypeRelationships.js b/tests/baselines/reference/mappedTypeRelationships.js new file mode 100644 index 0000000000000..fcad6973ab7c7 --- /dev/null +++ b/tests/baselines/reference/mappedTypeRelationships.js @@ -0,0 +1,182 @@ +//// [mappedTypeRelationships.ts] + +function f1(x: T, k: keyof T) { + return x[k]; +} + +function f2(x: T, k: K) { + return x[k]; +} + +function f3(x: T, y: U, k: keyof T) { + x[k] = y[k]; + y[k] = x[k]; // Error +} + +function f4(x: T, y: U, k: K) { + x[k] = y[k]; + y[k] = x[k]; // Error +} + +function f5(x: T, y: U, k: keyof U) { + x[k] = y[k]; // Error + y[k] = x[k]; // Error +} + +function f6(x: T, y: U, k: K) { + x[k] = y[k]; // Error + y[k] = x[k]; // Error +} + +function f10(x: T, y: Partial, k: keyof T) { + x[k] = y[k]; // Error + y[k] = x[k]; +} + +function f11(x: T, y: Partial, k: K) { + x[k] = y[k]; // Error + y[k] = x[k]; +} + +function f12(x: T, y: Partial, k: keyof T) { + x[k] = y[k]; // Error + y[k] = x[k]; // Error +} + +function f13(x: T, y: Partial, k: K) { + x[k] = y[k]; // Error + y[k] = x[k]; // Error +} + +function f20(x: T, y: Readonly, k: keyof T) { + x[k] = y[k]; + y[k] = x[k]; // Error +} + +function f21(x: T, y: Readonly, k: K) { + x[k] = y[k]; + y[k] = x[k]; // Error +} + +function f22(x: T, y: Readonly, k: keyof T) { + x[k] = y[k]; + y[k] = x[k]; // Error +} + +function f23(x: T, y: Readonly, k: K) { + x[k] = y[k]; + y[k] = x[k]; // Error +} + +function f30(x: T, y: Partial) { + x = y; // Error + y = x; +} + +function f31(x: T, y: Partial) { + x = y; // Error + y = x; +} + +function f40(x: T, y: Readonly) { + x = y; + y = x; +} + +function f41(x: T, y: Readonly) { + x = y; + y = x; +} + +//// [mappedTypeRelationships.js] +function f1(x, k) { + return x[k]; +} +function f2(x, k) { + return x[k]; +} +function f3(x, y, k) { + x[k] = y[k]; + y[k] = x[k]; // Error +} +function f4(x, y, k) { + x[k] = y[k]; + y[k] = x[k]; // Error +} +function f5(x, y, k) { + x[k] = y[k]; // Error + y[k] = x[k]; // Error +} +function f6(x, y, k) { + x[k] = y[k]; // Error + y[k] = x[k]; // Error +} +function f10(x, y, k) { + x[k] = y[k]; // Error + y[k] = x[k]; +} +function f11(x, y, k) { + x[k] = y[k]; // Error + y[k] = x[k]; +} +function f12(x, y, k) { + x[k] = y[k]; // Error + y[k] = x[k]; // Error +} +function f13(x, y, k) { + x[k] = y[k]; // Error + y[k] = x[k]; // Error +} +function f20(x, y, k) { + x[k] = y[k]; + y[k] = x[k]; // Error +} +function f21(x, y, k) { + x[k] = y[k]; + y[k] = x[k]; // Error +} +function f22(x, y, k) { + x[k] = y[k]; + y[k] = x[k]; // Error +} +function f23(x, y, k) { + x[k] = y[k]; + y[k] = x[k]; // Error +} +function f30(x, y) { + x = y; // Error + y = x; +} +function f31(x, y) { + x = y; // Error + y = x; +} +function f40(x, y) { + x = y; + y = x; +} +function f41(x, y) { + x = y; + y = x; +} + + +//// [mappedTypeRelationships.d.ts] +declare function f1(x: T, k: keyof T): T[keyof T]; +declare function f2(x: T, k: K): T[K]; +declare function f3(x: T, y: U, k: keyof T): void; +declare function f4(x: T, y: U, k: K): void; +declare function f5(x: T, y: U, k: keyof U): void; +declare function f6(x: T, y: U, k: K): void; +declare function f10(x: T, y: Partial, k: keyof T): void; +declare function f11(x: T, y: Partial, k: K): void; +declare function f12(x: T, y: Partial, k: keyof T): void; +declare function f13(x: T, y: Partial, k: K): void; +declare function f20(x: T, y: Readonly, k: keyof T): void; +declare function f21(x: T, y: Readonly, k: K): void; +declare function f22(x: T, y: Readonly, k: keyof T): void; +declare function f23(x: T, y: Readonly, k: K): void; +declare function f30(x: T, y: Partial): void; +declare function f31(x: T, y: Partial): void; +declare function f40(x: T, y: Readonly): void; +declare function f41(x: T, y: Readonly): void; diff --git a/tests/cases/conformance/types/mapped/mappedTypeRelationships.ts b/tests/cases/conformance/types/mapped/mappedTypeRelationships.ts new file mode 100644 index 0000000000000..7bc1c1a16703e --- /dev/null +++ b/tests/cases/conformance/types/mapped/mappedTypeRelationships.ts @@ -0,0 +1,90 @@ +// @strictNullChecks: true +// @declaration: true + +function f1(x: T, k: keyof T) { + return x[k]; +} + +function f2(x: T, k: K) { + return x[k]; +} + +function f3(x: T, y: U, k: keyof T) { + x[k] = y[k]; + y[k] = x[k]; // Error +} + +function f4(x: T, y: U, k: K) { + x[k] = y[k]; + y[k] = x[k]; // Error +} + +function f5(x: T, y: U, k: keyof U) { + x[k] = y[k]; // Error + y[k] = x[k]; // Error +} + +function f6(x: T, y: U, k: K) { + x[k] = y[k]; // Error + y[k] = x[k]; // Error +} + +function f10(x: T, y: Partial, k: keyof T) { + x[k] = y[k]; // Error + y[k] = x[k]; +} + +function f11(x: T, y: Partial, k: K) { + x[k] = y[k]; // Error + y[k] = x[k]; +} + +function f12(x: T, y: Partial, k: keyof T) { + x[k] = y[k]; // Error + y[k] = x[k]; // Error +} + +function f13(x: T, y: Partial, k: K) { + x[k] = y[k]; // Error + y[k] = x[k]; // Error +} + +function f20(x: T, y: Readonly, k: keyof T) { + x[k] = y[k]; + y[k] = x[k]; // Error +} + +function f21(x: T, y: Readonly, k: K) { + x[k] = y[k]; + y[k] = x[k]; // Error +} + +function f22(x: T, y: Readonly, k: keyof T) { + x[k] = y[k]; + y[k] = x[k]; // Error +} + +function f23(x: T, y: Readonly, k: K) { + x[k] = y[k]; + y[k] = x[k]; // Error +} + +function f30(x: T, y: Partial) { + x = y; // Error + y = x; +} + +function f31(x: T, y: Partial) { + x = y; // Error + y = x; +} + +function f40(x: T, y: Readonly) { + x = y; + y = x; +} + +function f41(x: T, y: Readonly) { + x = y; + y = x; +} \ No newline at end of file From 2376e30410e112689acbd3268ee94aa113546c56 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 18 Nov 2016 13:30:56 -0800 Subject: [PATCH 5/6] Support apparent types for T[K] indexed access types --- src/compiler/checker.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f6689471cd885..024c3d4a983c3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4666,13 +4666,22 @@ namespace ts { return type.resolvedApparentType; } + /** + * The apparent type of an indexed access T[K] is the type of T's string index signature, if any. + */ + function getApparentTypeOfIndexedAccess(type: IndexedAccessType) { + return getIndexTypeOfType(getApparentType(type.objectType), IndexKind.String) || 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.TypeParameter ? getApparentTypeOfTypeParameter(type) : type; + const t = type.flags & TypeFlags.TypeParameter ? getApparentTypeOfTypeParameter(type) : + type.flags & TypeFlags.IndexedAccess ? getApparentTypeOfIndexedAccess(type) : + type; return t.flags & TypeFlags.StringLike ? globalStringType : t.flags & TypeFlags.NumberLike ? globalNumberType : t.flags & TypeFlags.BooleanLike ? globalBooleanType : From a78999078173b6ce5243f77493a86091108ae774 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 18 Nov 2016 13:31:10 -0800 Subject: [PATCH 6/6] Add tests --- .../mappedTypeRelationships.errors.txt | 18 ++++++++++ .../reference/mappedTypeRelationships.js | 34 +++++++++++++++++++ .../types/mapped/mappedTypeRelationships.ts | 18 ++++++++++ 3 files changed, 70 insertions(+) diff --git a/tests/baselines/reference/mappedTypeRelationships.errors.txt b/tests/baselines/reference/mappedTypeRelationships.errors.txt index adafbc5f3a722..e8e0371ed9ae2 100644 --- a/tests/baselines/reference/mappedTypeRelationships.errors.txt +++ b/tests/baselines/reference/mappedTypeRelationships.errors.txt @@ -162,4 +162,22 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(76,5): error TS2 function f41(x: T, y: Readonly) { x = y; y = x; + } + + type Item = { + name: string; + } + + type ItemMap = { + [x: string]: Item; + } + + function f50(obj: T, key: keyof T) { + let item: Item = obj[key]; + return obj[key].name; + } + + function f51(obj: T, key: K) { + let item: Item = obj[key]; + return obj[key].name; } \ No newline at end of file diff --git a/tests/baselines/reference/mappedTypeRelationships.js b/tests/baselines/reference/mappedTypeRelationships.js index fcad6973ab7c7..a81ff973da874 100644 --- a/tests/baselines/reference/mappedTypeRelationships.js +++ b/tests/baselines/reference/mappedTypeRelationships.js @@ -86,6 +86,24 @@ function f40(x: T, y: Readonly) { function f41(x: T, y: Readonly) { x = y; y = x; +} + +type Item = { + name: string; +} + +type ItemMap = { + [x: string]: Item; +} + +function f50(obj: T, key: keyof T) { + let item: Item = obj[key]; + return obj[key].name; +} + +function f51(obj: T, key: K) { + let item: Item = obj[key]; + return obj[key].name; } //// [mappedTypeRelationships.js] @@ -159,6 +177,14 @@ function f41(x, y) { x = y; y = x; } +function f50(obj, key) { + var item = obj[key]; + return obj[key].name; +} +function f51(obj, key) { + var item = obj[key]; + return obj[key].name; +} //// [mappedTypeRelationships.d.ts] @@ -180,3 +206,11 @@ declare function f30(x: T, y: Partial): void; declare function f31(x: T, y: Partial): void; declare function f40(x: T, y: Readonly): void; declare function f41(x: T, y: Readonly): void; +declare type Item = { + name: string; +}; +declare type ItemMap = { + [x: string]: Item; +}; +declare function f50(obj: T, key: keyof T): string; +declare function f51(obj: T, key: K): string; diff --git a/tests/cases/conformance/types/mapped/mappedTypeRelationships.ts b/tests/cases/conformance/types/mapped/mappedTypeRelationships.ts index 7bc1c1a16703e..4af71fbea0804 100644 --- a/tests/cases/conformance/types/mapped/mappedTypeRelationships.ts +++ b/tests/cases/conformance/types/mapped/mappedTypeRelationships.ts @@ -87,4 +87,22 @@ function f40(x: T, y: Readonly) { function f41(x: T, y: Readonly) { x = y; y = x; +} + +type Item = { + name: string; +} + +type ItemMap = { + [x: string]: Item; +} + +function f50(obj: T, key: keyof T) { + let item: Item = obj[key]; + return obj[key].name; +} + +function f51(obj: T, key: K) { + let item: Item = obj[key]; + return obj[key].name; } \ No newline at end of file