From 09d2fd6f9f6dc098735acb2461dd0f2850e8533d Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 7 Sep 2021 09:23:42 -0700 Subject: [PATCH 1/2] Properly include tagged primitive types in keyof --- src/compiler/checker.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b71cfe8f3e97a..8d0ba7bfeb0cc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12789,7 +12789,7 @@ namespace ts { function isValidIndexKeyType(type: Type): boolean { return !!(type.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.ESSymbol)) || isPatternLiteralType(type) || - !!(type.flags & TypeFlags.Intersection) && !isGenericIndexType(type) && !isGenericObjectType(type) && some((type as IntersectionType).types, isValidIndexKeyType); + !!(type.flags & TypeFlags.Intersection) && !isGenericType(type) && some((type as IntersectionType).types, isValidIndexKeyType); } function getConstraintDeclaration(type: TypeParameter): TypeNode | undefined { @@ -14545,10 +14545,14 @@ namespace ts { return neverType; } + function isKeyTypeIncluded(keyType: Type, include: TypeFlags): boolean { + return !!(keyType.flags & include || keyType.flags & TypeFlags.Intersection && some((keyType as IntersectionType).types, t => isKeyTypeIncluded(t, include))); + } + function getLiteralTypeFromProperties(type: Type, include: TypeFlags, includeOrigin: boolean) { const origin = includeOrigin && (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference) || type.aliasSymbol) ? createOriginIndexType(type) : undefined; const propertyTypes = map(getPropertiesOfType(type), prop => getLiteralTypeFromProperty(prop, include)); - const indexKeyTypes = map(getIndexInfosOfType(type), info => info !== enumNumberIndexInfo && info.keyType.flags & include ? + const indexKeyTypes = map(getIndexInfosOfType(type), info => info !== enumNumberIndexInfo && isKeyTypeIncluded(info.keyType, include) ? info.keyType === stringType && include & TypeFlags.Number ? stringOrNumberType : info.keyType : neverType); return getUnionType(concatenate(propertyTypes, indexKeyTypes), UnionReduction.Literal, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, origin); From 1774fcf0762acb3c9cc0ee02c8a6b199f9dd0654 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 7 Sep 2021 09:32:32 -0700 Subject: [PATCH 2/2] Add regression test --- .../reference/indexSignatures1.errors.txt | 9 +++++++ tests/baselines/reference/indexSignatures1.js | 18 ++++++++++++++ .../reference/indexSignatures1.symbols | 24 +++++++++++++++++++ .../reference/indexSignatures1.types | 19 +++++++++++++++ .../types/members/indexSignatures1.ts | 9 +++++++ 5 files changed, 79 insertions(+) diff --git a/tests/baselines/reference/indexSignatures1.errors.txt b/tests/baselines/reference/indexSignatures1.errors.txt index 1a3ff53720388..2a2bc5938ec9f 100644 --- a/tests/baselines/reference/indexSignatures1.errors.txt +++ b/tests/baselines/reference/indexSignatures1.errors.txt @@ -507,4 +507,13 @@ tests/cases/conformance/types/members/indexSignatures1.ts(312,43): error TS2322: ~~~~~~~~~~~~~~~ !!! error TS2322: Type '{ [sym]: string; }' is not assignable to type '{ [key: number]: string; }'. !!! error TS2322: Object literal may only specify known properties, and '[sym]' does not exist in type '{ [key: number]: string; }'. + + // Repro from #45772 + + type Id = string & { __tag: 'id '}; + type Rec1 = { [key: Id]: number }; + type Rec2 = Record; + + type K1 = keyof Rec1; // Id + type K2 = keyof Rec2; // Id \ No newline at end of file diff --git a/tests/baselines/reference/indexSignatures1.js b/tests/baselines/reference/indexSignatures1.js index 0202d3df3ede2..b50705230a2ca 100644 --- a/tests/baselines/reference/indexSignatures1.js +++ b/tests/baselines/reference/indexSignatures1.js @@ -311,6 +311,15 @@ const aa: AA = { [sym]: '123' }; const obj1: { [key: symbol]: string } = { [sym]: 'hello '}; const obj2: { [key: string]: string } = { [sym]: 'hello '}; // Permitted for backwards compatibility const obj3: { [key: number]: string } = { [sym]: 'hello '}; // Error + +// Repro from #45772 + +type Id = string & { __tag: 'id '}; +type Rec1 = { [key: Id]: number }; +type Rec2 = Record; + +type K1 = keyof Rec1; // Id +type K2 = keyof Rec2; // Id //// [indexSignatures1.js] @@ -660,3 +669,12 @@ declare const obj2: { declare const obj3: { [key: number]: string; }; +declare type Id = string & { + __tag: 'id '; +}; +declare type Rec1 = { + [key: Id]: number; +}; +declare type Rec2 = Record; +declare type K1 = keyof Rec1; +declare type K2 = keyof Rec2; diff --git a/tests/baselines/reference/indexSignatures1.symbols b/tests/baselines/reference/indexSignatures1.symbols index e5f0fea815033..3a2ed98c516eb 100644 --- a/tests/baselines/reference/indexSignatures1.symbols +++ b/tests/baselines/reference/indexSignatures1.symbols @@ -899,3 +899,27 @@ const obj3: { [key: number]: string } = { [sym]: 'hello '}; // Error >[sym] : Symbol([sym], Decl(indexSignatures1.ts, 311, 41)) >sym : Symbol(sym, Decl(indexSignatures1.ts, 2, 5)) +// Repro from #45772 + +type Id = string & { __tag: 'id '}; +>Id : Symbol(Id, Decl(indexSignatures1.ts, 311, 59)) +>__tag : Symbol(__tag, Decl(indexSignatures1.ts, 315, 20)) + +type Rec1 = { [key: Id]: number }; +>Rec1 : Symbol(Rec1, Decl(indexSignatures1.ts, 315, 35)) +>key : Symbol(key, Decl(indexSignatures1.ts, 316, 15)) +>Id : Symbol(Id, Decl(indexSignatures1.ts, 311, 59)) + +type Rec2 = Record; +>Rec2 : Symbol(Rec2, Decl(indexSignatures1.ts, 316, 34)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>Id : Symbol(Id, Decl(indexSignatures1.ts, 311, 59)) + +type K1 = keyof Rec1; // Id +>K1 : Symbol(K1, Decl(indexSignatures1.ts, 317, 31)) +>Rec1 : Symbol(Rec1, Decl(indexSignatures1.ts, 315, 35)) + +type K2 = keyof Rec2; // Id +>K2 : Symbol(K2, Decl(indexSignatures1.ts, 319, 21)) +>Rec2 : Symbol(Rec2, Decl(indexSignatures1.ts, 316, 34)) + diff --git a/tests/baselines/reference/indexSignatures1.types b/tests/baselines/reference/indexSignatures1.types index b543739f23850..720f4357c1904 100644 --- a/tests/baselines/reference/indexSignatures1.types +++ b/tests/baselines/reference/indexSignatures1.types @@ -1051,3 +1051,22 @@ const obj3: { [key: number]: string } = { [sym]: 'hello '}; // Error >sym : unique symbol >'hello ' : "hello " +// Repro from #45772 + +type Id = string & { __tag: 'id '}; +>Id : Id +>__tag : "id " + +type Rec1 = { [key: Id]: number }; +>Rec1 : Rec1 +>key : Id + +type Rec2 = Record; +>Rec2 : Rec2 + +type K1 = keyof Rec1; // Id +>K1 : Id + +type K2 = keyof Rec2; // Id +>K2 : Id + diff --git a/tests/cases/conformance/types/members/indexSignatures1.ts b/tests/cases/conformance/types/members/indexSignatures1.ts index 2d86c237681bd..f888275683363 100644 --- a/tests/cases/conformance/types/members/indexSignatures1.ts +++ b/tests/cases/conformance/types/members/indexSignatures1.ts @@ -314,3 +314,12 @@ const aa: AA = { [sym]: '123' }; const obj1: { [key: symbol]: string } = { [sym]: 'hello '}; const obj2: { [key: string]: string } = { [sym]: 'hello '}; // Permitted for backwards compatibility const obj3: { [key: number]: string } = { [sym]: 'hello '}; // Error + +// Repro from #45772 + +type Id = string & { __tag: 'id '}; +type Rec1 = { [key: Id]: number }; +type Rec2 = Record; + +type K1 = keyof Rec1; // Id +type K2 = keyof Rec2; // Id