Skip to content

Commit

Permalink
Merge pull request #17455 from Microsoft/mappedTypeFixes
Browse files Browse the repository at this point in the history
Mapped and indexed access type fixes
  • Loading branch information
ahejlsberg authored Aug 2, 2017
2 parents bb34bce + 9e90094 commit 4672457
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 15 deletions.
30 changes: 17 additions & 13 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2583,10 +2583,8 @@ namespace ts {
}

function createTypeNodeFromObjectType(type: ObjectType): TypeNode {
if (type.objectFlags & ObjectFlags.Mapped) {
if (getConstraintTypeFromMappedType(<MappedType>type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) {
return createMappedTypeNodeFromType(<MappedType>type);
}
if (isGenericMappedType(type)) {
return createMappedTypeNodeFromType(<MappedType>type);
}

const resolved = resolveStructuredTypeMembers(type);
Expand Down Expand Up @@ -3489,11 +3487,9 @@ namespace ts {
}

function writeLiteralType(type: ObjectType, flags: TypeFormatFlags) {
if (type.objectFlags & ObjectFlags.Mapped) {
if (getConstraintTypeFromMappedType(<MappedType>type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) {
writeMappedType(<MappedType>type);
return;
}
if (isGenericMappedType(type)) {
writeMappedType(<MappedType>type);
return;
}

const resolved = resolveStructuredTypeMembers(type);
Expand Down Expand Up @@ -7592,10 +7588,16 @@ namespace ts {
}

function getIndexedAccessForMappedType(type: MappedType, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) {
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? <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;
if (accessNode) {
// Check if the index type is assignable to 'keyof T' for the object type.
if (!isTypeAssignableTo(indexType, getIndexType(type))) {
error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(type));
return unknownType;
}
if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && type.declaration.readonlyToken) {
error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(type));
return unknownType;
}
}
const mapper = createTypeMapper([getTypeParameterFromMappedType(type)], [indexType]);
const templateMapper = type.mapper ? combineTypeMappers(type.mapper, mapper) : mapper;
Expand Down Expand Up @@ -18657,6 +18659,8 @@ namespace ts {
}

function checkIndexedAccessType(node: IndexedAccessTypeNode) {
checkSourceElement(node.objectType);
checkSourceElement(node.indexType);
checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
tests/cases/compiler/anyIndexedAccessArrayNoException.ts(1,12): error TS1122: A tuple type element list cannot be empty.
tests/cases/compiler/anyIndexedAccessArrayNoException.ts(1,12): error TS2538: Type '[]' cannot be used as an index type.


==== tests/cases/compiler/anyIndexedAccessArrayNoException.ts (1 errors) ====
==== tests/cases/compiler/anyIndexedAccessArrayNoException.ts (2 errors) ====
var x: any[[]];
~~
!!! error TS1122: A tuple type element list cannot be empty.
~~
!!! error TS2538: Type '[]' cannot be used as an index type.

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(35,21): error
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(36,21): error TS2538: Type 'boolean' cannot be used as an index type.
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(41,31): error TS2538: Type 'boolean' cannot be used as an index type.
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(46,16): error TS2538: Type 'boolean' cannot be used as an index type.
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(49,12): error TS1122: A tuple type element list cannot be empty.
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(63,33): error TS2345: Argument of type '"size"' is not assignable to parameter of type '"name" | "width" | "height" | "visible"'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(64,33): error TS2345: Argument of type '"name" | "size"' is not assignable to parameter of type '"name" | "width" | "height" | "visible"'.
Type '"size"' is not assignable to type '"name" | "width" | "height" | "visible"'.
Expand All @@ -28,7 +29,7 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(76,5): error
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(77,5): error TS2322: Type 'keyof (T & U)' is not assignable to type 'keyof (T | U)'.


==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (24 errors) ====
==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (25 errors) ====
class Shape {
name: string;
width: number;
Expand Down Expand Up @@ -112,6 +113,8 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(77,5): error

type T60 = {}["toString"];
type T61 = []["toString"];
~~
!!! error TS1122: A tuple type element list cannot be empty.

declare let cond: boolean;

Expand Down
35 changes: 35 additions & 0 deletions tests/baselines/reference/mappedTypeErrors2.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
tests/cases/conformance/types/mapped/mappedTypeErrors2.ts(9,30): error TS2536: Type 'K' cannot be used to index type 'T1<K>'.
tests/cases/conformance/types/mapped/mappedTypeErrors2.ts(13,30): error TS2536: Type 'K' cannot be used to index type 'T3'.
tests/cases/conformance/types/mapped/mappedTypeErrors2.ts(15,47): error TS2536: Type 'S' cannot be used to index type 'AB'.
tests/cases/conformance/types/mapped/mappedTypeErrors2.ts(17,49): error TS2536: Type 'L' cannot be used to index type '{ [key in AB[S]]: true; }'.


==== tests/cases/conformance/types/mapped/mappedTypeErrors2.ts (4 errors) ====
// Repros from #17238

type AB = {
a: 'a'
b: 'a'
};

type T1<K extends keyof AB> = { [key in AB[K]]: true };
type T2<K extends 'a'|'b'> = T1<K>[K]; // Error
~~~~~~~~
!!! error TS2536: Type 'K' cannot be used to index type 'T1<K>'.

type R = AB[keyof AB]; // "a"
type T3 = { [key in R]: true };
type T4<K extends 'a'|'b'> = T3[K] // Error
~~~~~
!!! error TS2536: Type 'K' cannot be used to index type 'T3'.

type T5<S extends 'a'|'b'|'extra'> = {[key in AB[S]]: true}[S]; // Error
~~~~~
!!! error TS2536: Type 'S' cannot be used to index type 'AB'.

type T6<S extends 'a'|'b', L extends 'a'|'b'> = {[key in AB[S]]: true}[L]; // Error
~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2536: Type 'L' cannot be used to index type '{ [key in AB[S]]: true; }'.

type T7<S extends 'a'|'b', L extends 'a'> = {[key in AB[S]]: true}[L];

49 changes: 49 additions & 0 deletions tests/baselines/reference/mappedTypeErrors2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//// [mappedTypeErrors2.ts]
// Repros from #17238

type AB = {
a: 'a'
b: 'a'
};

type T1<K extends keyof AB> = { [key in AB[K]]: true };
type T2<K extends 'a'|'b'> = T1<K>[K]; // Error

type R = AB[keyof AB]; // "a"
type T3 = { [key in R]: true };
type T4<K extends 'a'|'b'> = T3[K] // Error

type T5<S extends 'a'|'b'|'extra'> = {[key in AB[S]]: true}[S]; // Error

type T6<S extends 'a'|'b', L extends 'a'|'b'> = {[key in AB[S]]: true}[L]; // Error

type T7<S extends 'a'|'b', L extends 'a'> = {[key in AB[S]]: true}[L];


//// [mappedTypeErrors2.js]
// Repros from #17238


//// [mappedTypeErrors2.d.ts]
declare type AB = {
a: 'a';
b: 'a';
};
declare type T1<K extends keyof AB> = {
[key in AB[K]]: true;
};
declare type T2<K extends 'a' | 'b'> = T1<K>[K];
declare type R = AB[keyof AB];
declare type T3 = {
[key in R]: true;
};
declare type T4<K extends 'a' | 'b'> = T3[K];
declare type T5<S extends 'a' | 'b' | 'extra'> = {
[key in AB[S]]: true;
}[S];
declare type T6<S extends 'a' | 'b', L extends 'a' | 'b'> = {
[key in AB[S]]: true;
}[L];
declare type T7<S extends 'a' | 'b', L extends 'a'> = {
[key in AB[S]]: true;
}[L];
22 changes: 22 additions & 0 deletions tests/cases/conformance/types/mapped/mappedTypeErrors2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// @strictNullChecks: true
// @declaration: true

// Repros from #17238

type AB = {
a: 'a'
b: 'a'
};

type T1<K extends keyof AB> = { [key in AB[K]]: true };
type T2<K extends 'a'|'b'> = T1<K>[K]; // Error

type R = AB[keyof AB]; // "a"
type T3 = { [key in R]: true };
type T4<K extends 'a'|'b'> = T3[K] // Error

type T5<S extends 'a'|'b'|'extra'> = {[key in AB[S]]: true}[S]; // Error

type T6<S extends 'a'|'b', L extends 'a'|'b'> = {[key in AB[S]]: true}[L]; // Error

type T7<S extends 'a'|'b', L extends 'a'> = {[key in AB[S]]: true}[L];

0 comments on commit 4672457

Please sign in to comment.