Skip to content

Commit 7032f6b

Browse files
authored
Include index signatures for tagged primitives in keyof (#45773)
* Properly include tagged primitive types in keyof * Add regression test
1 parent 3c8e45b commit 7032f6b

File tree

6 files changed

+85
-2
lines changed

6 files changed

+85
-2
lines changed

src/compiler/checker.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12789,7 +12789,7 @@ namespace ts {
1278912789

1279012790
function isValidIndexKeyType(type: Type): boolean {
1279112791
return !!(type.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.ESSymbol)) || isPatternLiteralType(type) ||
12792-
!!(type.flags & TypeFlags.Intersection) && !isGenericIndexType(type) && !isGenericObjectType(type) && some((type as IntersectionType).types, isValidIndexKeyType);
12792+
!!(type.flags & TypeFlags.Intersection) && !isGenericType(type) && some((type as IntersectionType).types, isValidIndexKeyType);
1279312793
}
1279412794

1279512795
function getConstraintDeclaration(type: TypeParameter): TypeNode | undefined {
@@ -14545,10 +14545,14 @@ namespace ts {
1454514545
return neverType;
1454614546
}
1454714547

14548+
function isKeyTypeIncluded(keyType: Type, include: TypeFlags): boolean {
14549+
return !!(keyType.flags & include || keyType.flags & TypeFlags.Intersection && some((keyType as IntersectionType).types, t => isKeyTypeIncluded(t, include)));
14550+
}
14551+
1454814552
function getLiteralTypeFromProperties(type: Type, include: TypeFlags, includeOrigin: boolean) {
1454914553
const origin = includeOrigin && (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference) || type.aliasSymbol) ? createOriginIndexType(type) : undefined;
1455014554
const propertyTypes = map(getPropertiesOfType(type), prop => getLiteralTypeFromProperty(prop, include));
14551-
const indexKeyTypes = map(getIndexInfosOfType(type), info => info !== enumNumberIndexInfo && info.keyType.flags & include ?
14555+
const indexKeyTypes = map(getIndexInfosOfType(type), info => info !== enumNumberIndexInfo && isKeyTypeIncluded(info.keyType, include) ?
1455214556
info.keyType === stringType && include & TypeFlags.Number ? stringOrNumberType : info.keyType : neverType);
1455314557
return getUnionType(concatenate(propertyTypes, indexKeyTypes), UnionReduction.Literal,
1455414558
/*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, origin);

tests/baselines/reference/indexSignatures1.errors.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,4 +507,13 @@ tests/cases/conformance/types/members/indexSignatures1.ts(312,43): error TS2322:
507507
~~~~~~~~~~~~~~~
508508
!!! error TS2322: Type '{ [sym]: string; }' is not assignable to type '{ [key: number]: string; }'.
509509
!!! error TS2322: Object literal may only specify known properties, and '[sym]' does not exist in type '{ [key: number]: string; }'.
510+
511+
// Repro from #45772
512+
513+
type Id = string & { __tag: 'id '};
514+
type Rec1 = { [key: Id]: number };
515+
type Rec2 = Record<Id, number>;
516+
517+
type K1 = keyof Rec1; // Id
518+
type K2 = keyof Rec2; // Id
510519

tests/baselines/reference/indexSignatures1.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,15 @@ const aa: AA = { [sym]: '123' };
311311
const obj1: { [key: symbol]: string } = { [sym]: 'hello '};
312312
const obj2: { [key: string]: string } = { [sym]: 'hello '}; // Permitted for backwards compatibility
313313
const obj3: { [key: number]: string } = { [sym]: 'hello '}; // Error
314+
315+
// Repro from #45772
316+
317+
type Id = string & { __tag: 'id '};
318+
type Rec1 = { [key: Id]: number };
319+
type Rec2 = Record<Id, number>;
320+
321+
type K1 = keyof Rec1; // Id
322+
type K2 = keyof Rec2; // Id
314323

315324

316325
//// [indexSignatures1.js]
@@ -660,3 +669,12 @@ declare const obj2: {
660669
declare const obj3: {
661670
[key: number]: string;
662671
};
672+
declare type Id = string & {
673+
__tag: 'id ';
674+
};
675+
declare type Rec1 = {
676+
[key: Id]: number;
677+
};
678+
declare type Rec2 = Record<Id, number>;
679+
declare type K1 = keyof Rec1;
680+
declare type K2 = keyof Rec2;

tests/baselines/reference/indexSignatures1.symbols

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,3 +899,27 @@ const obj3: { [key: number]: string } = { [sym]: 'hello '}; // Error
899899
>[sym] : Symbol([sym], Decl(indexSignatures1.ts, 311, 41))
900900
>sym : Symbol(sym, Decl(indexSignatures1.ts, 2, 5))
901901

902+
// Repro from #45772
903+
904+
type Id = string & { __tag: 'id '};
905+
>Id : Symbol(Id, Decl(indexSignatures1.ts, 311, 59))
906+
>__tag : Symbol(__tag, Decl(indexSignatures1.ts, 315, 20))
907+
908+
type Rec1 = { [key: Id]: number };
909+
>Rec1 : Symbol(Rec1, Decl(indexSignatures1.ts, 315, 35))
910+
>key : Symbol(key, Decl(indexSignatures1.ts, 316, 15))
911+
>Id : Symbol(Id, Decl(indexSignatures1.ts, 311, 59))
912+
913+
type Rec2 = Record<Id, number>;
914+
>Rec2 : Symbol(Rec2, Decl(indexSignatures1.ts, 316, 34))
915+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
916+
>Id : Symbol(Id, Decl(indexSignatures1.ts, 311, 59))
917+
918+
type K1 = keyof Rec1; // Id
919+
>K1 : Symbol(K1, Decl(indexSignatures1.ts, 317, 31))
920+
>Rec1 : Symbol(Rec1, Decl(indexSignatures1.ts, 315, 35))
921+
922+
type K2 = keyof Rec2; // Id
923+
>K2 : Symbol(K2, Decl(indexSignatures1.ts, 319, 21))
924+
>Rec2 : Symbol(Rec2, Decl(indexSignatures1.ts, 316, 34))
925+

tests/baselines/reference/indexSignatures1.types

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,3 +1051,22 @@ const obj3: { [key: number]: string } = { [sym]: 'hello '}; // Error
10511051
>sym : unique symbol
10521052
>'hello ' : "hello "
10531053

1054+
// Repro from #45772
1055+
1056+
type Id = string & { __tag: 'id '};
1057+
>Id : Id
1058+
>__tag : "id "
1059+
1060+
type Rec1 = { [key: Id]: number };
1061+
>Rec1 : Rec1
1062+
>key : Id
1063+
1064+
type Rec2 = Record<Id, number>;
1065+
>Rec2 : Rec2
1066+
1067+
type K1 = keyof Rec1; // Id
1068+
>K1 : Id
1069+
1070+
type K2 = keyof Rec2; // Id
1071+
>K2 : Id
1072+

tests/cases/conformance/types/members/indexSignatures1.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,12 @@ const aa: AA = { [sym]: '123' };
314314
const obj1: { [key: symbol]: string } = { [sym]: 'hello '};
315315
const obj2: { [key: string]: string } = { [sym]: 'hello '}; // Permitted for backwards compatibility
316316
const obj3: { [key: number]: string } = { [sym]: 'hello '}; // Error
317+
318+
// Repro from #45772
319+
320+
type Id = string & { __tag: 'id '};
321+
type Rec1 = { [key: Id]: number };
322+
type Rec2 = Record<Id, number>;
323+
324+
type K1 = keyof Rec1; // Id
325+
type K2 = keyof Rec2; // Id

0 commit comments

Comments
 (0)