Skip to content

Commit 0e905be

Browse files
authored
Index signatures for symbols and template literal strings (#44512)
* Switch index signature storage to 'indexInfos: IndexInfo[]' property * Accept new baselines * Remove another usage of IndexKind enum * Update getIndexedAccessType and resolveMappedTypeMembers * Accept new baselines * Update grammar checking for index signatures * Accept new baselines * Consider all index signatures in mapped types and union types * Accept new baselines * Update getIndexType * Accept new baselines * Intersect multiple applicable index signatures * Use getApplicableIndexInfo instead of hardwired string/number handling * Update index signature relationship checking * Report type for which index signature is missing * Report type for which index signature is missing * Accept new baselines * Make 'number' index signatures consistently apply to numeric strings * Accept new baselines * Update fourslash test * Revise index constraint checking * Accept new baselines * Update error messages * Accept new baselines * Update type inference from index signatures * Update isKnownProperty * Update contextual typing based on index signatures * Accept new baselines * Support union types in index signature declarations * Accept new baselines * Check duplicate index signatures / remove redundant template literals from unions with string * Accept new baselines * Include key type in diagnostic / check symbol-named properties * Accept new baselines * Minor fix * Add tests * Accept new baselines * Add optimized findApplicableIndexInfoForName * Accept new baselines * Another place we don't need to obtain literal type for property name * Accept new baselines * Don't create literal types that are going to be discarded * Individual maps for string, number, bigint, and enum literal types * Remove ineffective optimizations * Accept new baselines * Permit intersections as key types in index signatures * Index expression in element access is template literal context * Add tests * Accept new baselines * Symbol index signatures from object literals with computed symbol properties * Accept new baselines * Add more tests * Accept new baselines * Implement Go To Definition for all applicable index signatures * Add fourslash test * Accept new API baselines
1 parent b72f67f commit 0e905be

File tree

173 files changed

+4903
-1722
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

173 files changed

+4903
-1722
lines changed

src/compiler/checker.ts

Lines changed: 558 additions & 613 deletions
Large diffs are not rendered by default.

src/compiler/diagnosticMessages.json

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,6 @@
7575
"category": "Error",
7676
"code": 1022
7777
},
78-
"An index signature parameter type must be either 'string' or 'number'.": {
79-
"category": "Error",
80-
"code": 1023
81-
},
8278
"'readonly' modifier can only appear on a property declaration or index signature.": {
8379
"category": "Error",
8480
"code": 1024
@@ -891,6 +887,10 @@
891887
"category": "Error",
892888
"code": 1267
893889
},
890+
"An index signature parameter type must be 'string', 'number', 'symbol', or a template literal type.": {
891+
"category": "Error",
892+
"code": 1268
893+
},
894894

895895
"'with' statements are not allowed in an async function block.": {
896896
"category": "Error",
@@ -996,11 +996,7 @@
996996
"category": "Error",
997997
"code": 1335
998998
},
999-
"An index signature parameter type cannot be a type alias. Consider writing '[{0}: {1}]: {2}' instead.": {
1000-
"category": "Error",
1001-
"code": 1336
1002-
},
1003-
"An index signature parameter type cannot be a union type. Consider using a mapped object type instead.": {
999+
"An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.": {
10041000
"category": "Error",
10051001
"code": 1337
10061002
},
@@ -1518,11 +1514,11 @@
15181514
"category": "Error",
15191515
"code": 2328
15201516
},
1521-
"Index signature is missing in type '{0}'.": {
1517+
"Index signature for type '{0}' is missing in type '{1}'.": {
15221518
"category": "Error",
15231519
"code": 2329
15241520
},
1525-
"Index signatures are incompatible.": {
1521+
"'{0}' and '{1}' index signatures are incompatible.": {
15261522
"category": "Error",
15271523
"code": 2330
15281524
},
@@ -1698,14 +1694,10 @@
16981694
"category": "Error",
16991695
"code": 2373
17001696
},
1701-
"Duplicate string index signature.": {
1697+
"Duplicate index signature for type '{0}'.": {
17021698
"category": "Error",
17031699
"code": 2374
17041700
},
1705-
"Duplicate number index signature.": {
1706-
"category": "Error",
1707-
"code": 2375
1708-
},
17091701
"A 'super' call must be the first statement in the constructor when a class contains initialized properties, parameter properties, or private identifiers.": {
17101702
"category": "Error",
17111703
"code": 2376
@@ -1842,15 +1834,11 @@
18421834
"category": "Error",
18431835
"code": 2410
18441836
},
1845-
"Property '{0}' of type '{1}' is not assignable to string index type '{2}'.": {
1837+
"Property '{0}' of type '{1}' is not assignable to '{2}' index type '{3}'.": {
18461838
"category": "Error",
18471839
"code": 2411
18481840
},
1849-
"Property '{0}' of type '{1}' is not assignable to numeric index type '{2}'.": {
1850-
"category": "Error",
1851-
"code": 2412
1852-
},
1853-
"Numeric index type '{0}' is not assignable to string index type '{1}'.": {
1841+
"'{0}' index type '{1}' is not assignable to '{2}' index type '{3}'.": {
18541842
"category": "Error",
18551843
"code": 2413
18561844
},
@@ -2690,6 +2678,10 @@
26902678
"category": "Error",
26912679
"code": 2633
26922680
},
2681+
"'{0}' index signatures are incompatible.": {
2682+
"category": "Error",
2683+
"code": 2634
2684+
},
26932685

26942686
"Cannot augment module '{0}' with value exports because it resolves to a non-module entity.": {
26952687
"category": "Error",

src/compiler/symbolWalker.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ namespace ts {
88
resolveStructuredTypeMembers: (type: ObjectType) => ResolvedType,
99
getTypeOfSymbol: (sym: Symbol) => Type,
1010
getResolvedSymbol: (node: Node) => Symbol,
11-
getIndexTypeOfStructuredType: (type: Type, kind: IndexKind) => Type | undefined,
1211
getConstraintOfTypeParameter: (typeParameter: TypeParameter) => Type | undefined,
1312
getFirstIdentifier: (node: EntityNameOrEntityNameExpression) => Identifier,
1413
getTypeArguments: (type: TypeReference) => readonly Type[]) {
@@ -140,13 +139,11 @@ namespace ts {
140139
}
141140

142141
function visitObjectType(type: ObjectType): void {
143-
const stringIndexType = getIndexTypeOfStructuredType(type, IndexKind.String);
144-
visitType(stringIndexType);
145-
const numberIndexType = getIndexTypeOfStructuredType(type, IndexKind.Number);
146-
visitType(numberIndexType);
147-
148-
// The two checks above *should* have already resolved the type (if needed), so this should be cached
149142
const resolved = resolveStructuredTypeMembers(type);
143+
for (const info of resolved.indexInfos) {
144+
visitType(info.keyType);
145+
visitType(info.type);
146+
}
150147
for (const signature of resolved.callSignatures) {
151148
visitSignature(signature);
152149
}

src/compiler/types.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4088,6 +4088,7 @@ namespace ts {
40884088
getPrivateIdentifierPropertyOfType(leftType: Type, name: string, location: Node): Symbol | undefined;
40894089
/* @internal */ getTypeOfPropertyOfType(type: Type, propertyName: string): Type | undefined;
40904090
getIndexInfoOfType(type: Type, kind: IndexKind): IndexInfo | undefined;
4091+
getIndexInfosOfType(type: Type): readonly IndexInfo[];
40914092
getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[];
40924093
getIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined;
40934094
getBaseTypes(type: InterfaceType): BaseType[];
@@ -4117,8 +4118,8 @@ namespace ts {
41174118
signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): SignatureDeclaration & {typeArguments?: NodeArray<TypeNode>} | undefined;
41184119
/* @internal */ signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, tracker?: SymbolTracker): SignatureDeclaration & {typeArguments?: NodeArray<TypeNode>} | undefined; // eslint-disable-line @typescript-eslint/unified-signatures
41194120
/** Note that the resulting nodes cannot be checked. */
4120-
indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): IndexSignatureDeclaration | undefined;
4121-
/* @internal */ indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, tracker?: SymbolTracker): IndexSignatureDeclaration | undefined; // eslint-disable-line @typescript-eslint/unified-signatures
4121+
indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): IndexSignatureDeclaration | undefined;
4122+
/* @internal */ indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, tracker?: SymbolTracker): IndexSignatureDeclaration | undefined; // eslint-disable-line @typescript-eslint/unified-signatures
41224123
/** Note that the resulting nodes cannot be checked. */
41234124
symbolToEntityName(symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): EntityName | undefined;
41244125
/** Note that the resulting nodes cannot be checked. */
@@ -4132,6 +4133,7 @@ namespace ts {
41324133

41334134
getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[];
41344135
getSymbolAtLocation(node: Node): Symbol | undefined;
4136+
/* @internal */ getIndexInfosAtLocation(node: Node): readonly IndexInfo[] | undefined;
41354137
getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: string): Symbol[];
41364138
/**
41374139
* The function returns the value (local variable) symbol of an identifier in the short-hand property assignment.
@@ -4244,7 +4246,7 @@ namespace ts {
42444246
/* @internal */ createPromiseType(type: Type): Type;
42454247

42464248
/* @internal */ isTypeAssignableTo(source: Type, target: Type): boolean;
4247-
/* @internal */ createAnonymousType(symbol: Symbol | undefined, members: SymbolTable, callSignatures: Signature[], constructSignatures: Signature[], stringIndexInfo: IndexInfo | undefined, numberIndexInfo: IndexInfo | undefined): Type;
4249+
/* @internal */ createAnonymousType(symbol: Symbol | undefined, members: SymbolTable, callSignatures: Signature[], constructSignatures: Signature[], indexInfos: IndexInfo[]): Type;
42484250
/* @internal */ createSignature(
42494251
declaration: SignatureDeclaration | undefined,
42504252
typeParameters: readonly TypeParameter[] | undefined,
@@ -4256,7 +4258,7 @@ namespace ts {
42564258
flags: SignatureFlags
42574259
): Signature;
42584260
/* @internal */ createSymbol(flags: SymbolFlags, name: __String): TransientSymbol;
4259-
/* @internal */ createIndexInfo(type: Type, isReadonly: boolean, declaration?: SignatureDeclaration): IndexInfo;
4261+
/* @internal */ createIndexInfo(keyType: Type, type: Type, isReadonly: boolean, declaration?: SignatureDeclaration): IndexInfo;
42604262
/* @internal */ isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasToMarkVisible: boolean): SymbolAccessibilityResult;
42614263
/* @internal */ tryFindAmbientModule(moduleName: string): Symbol | undefined;
42624264
/* @internal */ tryFindAmbientModuleWithoutAugmentations(moduleName: string): Symbol | undefined;
@@ -5272,8 +5274,7 @@ namespace ts {
52725274
/* @internal */ properties?: Symbol[]; // Properties
52735275
/* @internal */ callSignatures?: readonly Signature[]; // Call signatures of type
52745276
/* @internal */ constructSignatures?: readonly Signature[]; // Construct signatures of type
5275-
/* @internal */ stringIndexInfo?: IndexInfo; // String indexing info
5276-
/* @internal */ numberIndexInfo?: IndexInfo; // Numeric indexing info
5277+
/* @internal */ indexInfos?: readonly IndexInfo[]; // Index signatures
52775278
/* @internal */ objectTypeWithoutAbstractConstructSignatures?: ObjectType;
52785279
}
52795280

@@ -5298,8 +5299,7 @@ namespace ts {
52985299
declaredProperties: Symbol[]; // Declared members
52995300
declaredCallSignatures: Signature[]; // Declared call signatures
53005301
declaredConstructSignatures: Signature[]; // Declared construct signatures
5301-
declaredStringIndexInfo?: IndexInfo; // Declared string indexing info
5302-
declaredNumberIndexInfo?: IndexInfo; // Declared numeric indexing info
5302+
declaredIndexInfos: IndexInfo[]; // Declared index signatures
53035303
}
53045304

53055305
/**
@@ -5457,6 +5457,7 @@ namespace ts {
54575457
properties: Symbol[]; // Properties
54585458
callSignatures: readonly Signature[]; // Call signatures of type
54595459
constructSignatures: readonly Signature[]; // Construct signatures of type
5460+
indexInfos: readonly IndexInfo[]; // Index signatures
54605461
}
54615462

54625463
/* @internal */
@@ -5693,6 +5694,7 @@ namespace ts {
56935694
}
56945695

56955696
export interface IndexInfo {
5697+
keyType: Type;
56965698
type: Type;
56975699
isReadonly: boolean;
56985700
declaration?: IndexSignatureDeclaration;

src/services/codefixes/convertToMappedObjectType.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
namespace ts.codefix {
33
const fixIdAddMissingTypeof = "fixConvertToMappedObjectType";
44
const fixId = fixIdAddMissingTypeof;
5-
const errorCodes = [Diagnostics.An_index_signature_parameter_type_cannot_be_a_union_type_Consider_using_a_mapped_object_type_instead.code];
5+
const errorCodes = [Diagnostics.An_index_signature_parameter_type_cannot_be_a_literal_type_or_generic_type_Consider_using_a_mapped_object_type_instead.code];
66

77
type FixableDeclaration = InterfaceDeclaration | TypeAliasDeclaration;
88

src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ namespace ts.codefix {
7070
function createMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind): void {
7171
const indexInfoOfKind = checker.getIndexInfoOfType(type, kind);
7272
if (indexInfoOfKind) {
73-
insertInterfaceMemberNode(sourceFile, classDeclaration, checker.indexInfoToIndexSignatureDeclaration(indexInfoOfKind, kind, classDeclaration, /*flags*/ undefined, getNoopSymbolTrackerWithResolver(context))!);
73+
insertInterfaceMemberNode(sourceFile, classDeclaration, checker.indexInfoToIndexSignatureDeclaration(indexInfoOfKind, classDeclaration, /*flags*/ undefined, getNoopSymbolTrackerWithResolver(context))!);
7474
}
7575
}
7676

src/services/codefixes/fixInvalidImportSyntax.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,8 @@ namespace ts.codefix {
5757
Diagnostics.Type_0_is_not_assignable_to_type_1.code,
5858
Diagnostics.Type_0_is_not_assignable_to_type_1_Two_different_types_with_this_name_exist_but_they_are_unrelated.code,
5959
Diagnostics.Type_predicate_0_is_not_assignable_to_1.code,
60-
Diagnostics.Property_0_of_type_1_is_not_assignable_to_string_index_type_2.code,
61-
Diagnostics.Property_0_of_type_1_is_not_assignable_to_numeric_index_type_2.code,
62-
Diagnostics.Numeric_index_type_0_is_not_assignable_to_string_index_type_1.code,
60+
Diagnostics.Property_0_of_type_1_is_not_assignable_to_2_index_type_3.code,
61+
Diagnostics._0_index_type_1_is_not_assignable_to_2_index_type_3.code,
6362
Diagnostics.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2.code,
6463
Diagnostics.Property_0_in_type_1_is_not_assignable_to_type_2.code,
6564
Diagnostics.Property_0_of_JSX_spread_attribute_is_not_assignable_to_target_property.code,

src/services/codefixes/inferFromUsage.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -955,13 +955,15 @@ namespace ts.codefix {
955955
}
956956
calls.push(...checker.getSignaturesOfType(anon, SignatureKind.Call));
957957
constructs.push(...checker.getSignaturesOfType(anon, SignatureKind.Construct));
958-
if (anon.stringIndexInfo) {
959-
stringIndices.push(anon.stringIndexInfo.type);
960-
stringIndexReadonly = stringIndexReadonly || anon.stringIndexInfo.isReadonly;
958+
const stringIndexInfo = checker.getIndexInfoOfType(anon, IndexKind.String);
959+
if (stringIndexInfo) {
960+
stringIndices.push(stringIndexInfo.type);
961+
stringIndexReadonly = stringIndexReadonly || stringIndexInfo.isReadonly;
961962
}
962-
if (anon.numberIndexInfo) {
963-
numberIndices.push(anon.numberIndexInfo.type);
964-
numberIndexReadonly = numberIndexReadonly || anon.numberIndexInfo.isReadonly;
963+
const numberIndexInfo = checker.getIndexInfoOfType(anon, IndexKind.Number);
964+
if (numberIndexInfo) {
965+
numberIndices.push(numberIndexInfo.type);
966+
numberIndexReadonly = numberIndexReadonly || numberIndexInfo.isReadonly;
965967
}
966968
}
967969
const members = mapEntries(props, (name, types) => {
@@ -970,13 +972,15 @@ namespace ts.codefix {
970972
s.type = checker.getUnionType(types);
971973
return [name, s];
972974
});
975+
const indexInfos = [];
976+
if (stringIndices.length) indexInfos.push(checker.createIndexInfo(checker.getStringType(), checker.getUnionType(stringIndices), stringIndexReadonly));
977+
if (numberIndices.length) indexInfos.push(checker.createIndexInfo(checker.getNumberType(), checker.getUnionType(numberIndices), numberIndexReadonly));
973978
return checker.createAnonymousType(
974979
anons[0].symbol,
975980
members as UnderscoreEscapedMap<TransientSymbol>,
976981
calls,
977982
constructs,
978-
stringIndices.length ? checker.createIndexInfo(checker.getUnionType(stringIndices), stringIndexReadonly) : undefined,
979-
numberIndices.length ? checker.createIndexInfo(checker.getUnionType(numberIndices), numberIndexReadonly) : undefined);
983+
indexInfos);
980984
}
981985

982986
function inferTypes(usage: Usage): Type[] {
@@ -1015,8 +1019,8 @@ namespace ts.codefix {
10151019
}
10161020
const callSignatures: Signature[] = usage.calls ? [getSignatureFromCalls(usage.calls)] : [];
10171021
const constructSignatures: Signature[] = usage.constructs ? [getSignatureFromCalls(usage.constructs)] : [];
1018-
const stringIndexInfo = usage.stringIndex && checker.createIndexInfo(combineFromUsage(usage.stringIndex), /*isReadonly*/ false);
1019-
return checker.createAnonymousType(/*symbol*/ undefined, members, callSignatures, constructSignatures, stringIndexInfo, /*numberIndexInfo*/ undefined);
1022+
const indexInfos = usage.stringIndex ? [checker.createIndexInfo(checker.getStringType(), combineFromUsage(usage.stringIndex), /*isReadonly*/ false)] : [];
1023+
return checker.createAnonymousType(/*symbol*/ undefined, members, callSignatures, constructSignatures, indexInfos);
10201024
}
10211025

10221026
function inferNamedTypesFromProperties(usage: Usage): Type[] {
@@ -1121,7 +1125,7 @@ namespace ts.codefix {
11211125
}
11221126

11231127
function getFunctionFromCalls(calls: CallUsage[]) {
1124-
return checker.createAnonymousType(/*symbol*/ undefined, createSymbolTable(), [getSignatureFromCalls(calls)], emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined);
1128+
return checker.createAnonymousType(/*symbol*/ undefined, createSymbolTable(), [getSignatureFromCalls(calls)], emptyArray, emptyArray);
11251129
}
11261130

11271131
function getSignatureFromCalls(calls: CallUsage[]): Signature {

src/services/codefixes/returnValueCorrect.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ namespace ts.codefix {
7676
const member = checker.createSymbol(SymbolFlags.Property, label.escapedText);
7777
member.type = checker.getTypeAtLocation(expression);
7878
const members = createSymbolTable([member]);
79-
return checker.createAnonymousType(/*symbol*/ undefined, members, [], [], /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined);
79+
return checker.createAnonymousType(/*symbol*/ undefined, members, [], [], []);
8080
}
8181

8282
function getFixInfo(checker: TypeChecker, declaration: FunctionLikeDeclaration, expectType: Type, isFunctionType: boolean): Info | undefined {
@@ -152,8 +152,7 @@ namespace ts.codefix {
152152
createSymbolTable(),
153153
[newSig],
154154
[],
155-
/*stringIndexInfo*/ undefined,
156-
/*numberIndexInfo*/ undefined);
155+
[]);
157156
}
158157
else {
159158
exprType = checker.getAnyType();

src/services/goToDefinition.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -212,12 +212,7 @@ namespace ts.GoToDefinition {
212212

213213
// At 'x.foo', see if the type of 'x' has an index signature, and if so find its declarations.
214214
function getDefinitionInfoForIndexSignatures(node: Node, checker: TypeChecker): DefinitionInfo[] | undefined {
215-
if (!isPropertyAccessExpression(node.parent) || node.parent.name !== node) return;
216-
const type = checker.getTypeAtLocation(node.parent.expression);
217-
return mapDefined(type.isUnionOrIntersection() ? type.types : [type], nonUnionType => {
218-
const info = checker.getIndexInfoOfType(nonUnionType, IndexKind.String);
219-
return info && info.declaration && createDefinitionFromSignatureDeclaration(checker, info.declaration);
220-
});
215+
return mapDefined(checker.getIndexInfosAtLocation(node), info => info.declaration && createDefinitionFromSignatureDeclaration(checker, info.declaration));
221216
}
222217

223218
function getSymbol(node: Node, checker: TypeChecker): Symbol | undefined {

0 commit comments

Comments
 (0)