@@ -157,6 +157,7 @@ import {
157157 ElementAccessChain,
158158 ElementAccessExpression,
159159 ElementFlags,
160+ ElementWithComputedPropertyName,
160161 EmitFlags,
161162 EmitHint,
162163 emitModuleKindIsNonNodeESM,
@@ -6851,7 +6852,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
68516852 }
68526853 function shouldWriteTypeOfFunctionSymbol() {
68536854 const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method) && // typeof static method
6854- some(symbol.declarations, declaration => isStatic(declaration));
6855+ some(symbol.declarations, declaration => isStatic(declaration) && !isLateBindableIndexSignature(getNameOfDeclaration(declaration)!) );
68556856 const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) &&
68566857 (symbol.parent || // is exported function symbol
68576858 forEach(symbol.declarations, declaration => declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock));
@@ -7207,6 +7208,37 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
72077208 return ids;
72087209 }
72097210
7211+ function indexInfoToObjectComputedNamesOrSignatureDeclaration(indexInfo: IndexInfo, context: NodeBuilderContext, typeNode: TypeNode | undefined): [IndexSignatureDeclaration] | PropertySignature[] {
7212+ if (indexInfo.components) {
7213+ // Index info is derived from object or class computed property names (plus explicit named members) - we can clone those instead of writing out the result computed index signature
7214+ let unusableResult = false;
7215+ const result = map(indexInfo.components, e => {
7216+ if (e.name && isComputedPropertyName(e.name) && isEntityNameExpression(e.name.expression)) {
7217+ trackComputedName(e.name.expression, context.enclosingDeclaration, context);
7218+ }
7219+ else {
7220+ // Computed name didn't take the form `[a.b.c]: something` - bail on using the computed name.
7221+ // TODO: Issue isolated declarations error on this fallback?
7222+ unusableResult = true;
7223+ }
7224+ return setTextRange(
7225+ context,
7226+ factory.createPropertySignature(
7227+ indexInfo.isReadonly ? [factory.createModifier(SyntaxKind.ReadonlyKeyword)] : undefined,
7228+ e.name,
7229+ /*questionToken*/ undefined,
7230+ typeNode || typeToTypeNodeHelper(getTypeOfSymbol(e.symbol), context),
7231+ ),
7232+ e,
7233+ );
7234+ });
7235+ if (!unusableResult) {
7236+ return result;
7237+ }
7238+ }
7239+ return [indexInfoToIndexSignatureDeclarationHelper(indexInfo, context, typeNode)];
7240+ }
7241+
72107242 function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] | undefined {
72117243 if (checkTruncationLength(context)) {
72127244 if (context.flags & NodeBuilderFlags.NoTruncation) {
@@ -7223,7 +7255,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
72237255 typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, context) as ConstructSignatureDeclaration);
72247256 }
72257257 for (const info of resolvedType.indexInfos) {
7226- typeElements.push(indexInfoToIndexSignatureDeclarationHelper (info, context, resolvedType.objectFlags & ObjectFlags.ReverseMapped ? createElidedInformationPlaceholder(context) : undefined));
7258+ typeElements.push(...indexInfoToObjectComputedNamesOrSignatureDeclaration (info, context, resolvedType.objectFlags & ObjectFlags.ReverseMapped ? createElidedInformationPlaceholder(context) : undefined));
72277259 }
72287260
72297261 const properties = resolvedType.properties;
@@ -15956,8 +15988,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1595615988 return symbolTable.get(InternalSymbolName.Index);
1595715989 }
1595815990
15959- function createIndexInfo(keyType: Type, type: Type, isReadonly: boolean, declaration?: IndexSignatureDeclaration): IndexInfo {
15960- return { keyType, type, isReadonly, declaration };
15991+ function createIndexInfo(keyType: Type, type: Type, isReadonly: boolean, declaration?: IndexSignatureDeclaration, components?: ElementWithComputedPropertyName[] ): IndexInfo {
15992+ return { keyType, type, isReadonly, declaration, components };
1596115993 }
1596215994
1596315995 function getIndexInfosOfSymbol(symbol: Symbol): IndexInfo[] {
@@ -19554,7 +19586,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1955419586 }
1955519587
1955619588 function getIndexInfoWithReadonly(info: IndexInfo, readonly: boolean) {
19557- return info.isReadonly !== readonly ? createIndexInfo(info.keyType, info.type, readonly, info.declaration) : info;
19589+ return info.isReadonly !== readonly ? createIndexInfo(info.keyType, info.type, readonly, info.declaration, info.components ) : info;
1955819590 }
1955919591
1956019592 function createLiteralType(flags: TypeFlags, value: string | number | PseudoBigInt, symbol?: Symbol, regularType?: LiteralType) {
@@ -20431,7 +20463,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2043120463 }
2043220464
2043320465 function instantiateIndexInfo(info: IndexInfo, mapper: TypeMapper) {
20434- return createIndexInfo(info.keyType, instantiateType(info.type, mapper), info.isReadonly, info.declaration);
20466+ return createIndexInfo(info.keyType, instantiateType(info.type, mapper), info.isReadonly, info.declaration, info.components );
2043520467 }
2043620468
2043720469 // Returns true if the given expression contains (at any level of nesting) a function or arrow expression
@@ -25215,7 +25247,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2521525247 }
2521625248 }
2521725249 }
25218- const result = createAnonymousType(type.symbol, members, emptyArray, emptyArray, sameMap(getIndexInfosOfType(type), info => createIndexInfo(info.keyType, getWidenedType(info.type), info.isReadonly)));
25250+ const result = createAnonymousType(type.symbol, members, emptyArray, emptyArray, sameMap(getIndexInfosOfType(type), info => createIndexInfo(info.keyType, getWidenedType(info.type), info.isReadonly, info.declaration, info.components )));
2521925251 result.objectFlags |= getObjectFlags(type) & (ObjectFlags.JSLiteral | ObjectFlags.NonInferrableType); // Retain js literal flag through widening
2522025252 return result;
2522125253 }
@@ -32625,9 +32657,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3262532657 isTypeAssignableToKind(checkComputedPropertyName(firstDecl.name), TypeFlags.ESSymbol));
3262632658 }
3262732659
32660+ function isSymbolWithComputedName(symbol: Symbol) {
32661+ const firstDecl = symbol.declarations?.[0];
32662+ return firstDecl && isNamedDeclaration(firstDecl) && isComputedPropertyName(firstDecl.name);
32663+ }
32664+
3262832665 // NOTE: currently does not make pattern literal indexers, eg `${number}px`
3262932666 function getObjectLiteralIndexInfo(isReadonly: boolean, offset: number, properties: Symbol[], keyType: Type): IndexInfo {
3263032667 const propTypes: Type[] = [];
32668+ let components: ElementWithComputedPropertyName[] | undefined;
3263132669 for (let i = offset; i < properties.length; i++) {
3263232670 const prop = properties[i];
3263332671 if (
@@ -32636,10 +32674,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3263632674 keyType === esSymbolType && isSymbolWithSymbolName(prop)
3263732675 ) {
3263832676 propTypes.push(getTypeOfSymbol(properties[i]));
32677+ if (isSymbolWithComputedName(properties[i])) {
32678+ components = append(components, properties[i].declarations?.[0]! as ElementWithComputedPropertyName);
32679+ }
3263932680 }
3264032681 }
3264132682 const unionType = propTypes.length ? getUnionType(propTypes, UnionReduction.Subtype) : undefinedType;
32642- return createIndexInfo(keyType, unionType, isReadonly);
32683+ return createIndexInfo(keyType, unionType, isReadonly, /*declaration*/ undefined, components );
3264332684 }
3264432685
3264532686 function getImmediateAliasedSymbol(symbol: Symbol): Symbol | undefined {
@@ -45721,9 +45762,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4572145762 const typeDeclaration = symbol.valueDeclaration;
4572245763 if (typeDeclaration && isClassLike(typeDeclaration)) {
4572345764 for (const member of typeDeclaration.members) {
45724- // Only process instance properties with computed names here. Static properties cannot be in conflict with indexers,
45725- // and properties with literal names were already checked.
45726- if (!isStatic(member) && !hasBindableName(member)) {
45765+ // Only process instance properties against instance index signatures and static properties against static index signatures
45766+ if (
45767+ (
45768+ (!isStaticIndex && !isStatic(member)) ||
45769+ (isStaticIndex && isStatic(member))
45770+ ) && !hasBindableName(member)
45771+ ) {
4572745772 const symbol = getSymbolOfDeclaration(member);
4572845773 checkIndexConstraintForProperty(type, symbol, getTypeOfExpression((member as DynamicNamedDeclaration).name.expression), getNonMissingTypeOfSymbol(symbol));
4572945774 }
@@ -50288,6 +50333,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
5028850333 result ||= [];
5028950334 for (const info of infoList!) {
5029050335 if (info.declaration) continue;
50336+ if (info.components) {
50337+ let unusableResult = false;
50338+ const recreatedComponents = map(info.components, e => {
50339+ if (e.name && isComputedPropertyName(e.name) && isEntityNameExpression(e.name.expression)) {
50340+ trackComputedName(e.name.expression);
50341+ }
50342+ else {
50343+ // Computed name didn't take the form `[a.b.c]: something` - bail on using the computed name.
50344+ // TODO: Issue isolated declarations error on this fallback?
50345+ unusableResult = true;
50346+ }
50347+ const mods = infoList === staticInfos ? [factory.createModifier(SyntaxKind.StaticKeyword)] as Modifier[] : undefined;
50348+ return factory.createPropertyDeclaration(
50349+ append(mods, info.isReadonly ? factory.createModifier(SyntaxKind.ReadonlyKeyword) : undefined),
50350+ e.name,
50351+ /*questionOrExclamationToken*/ undefined,
50352+ nodeBuilder.typeToTypeNode(getTypeOfSymbol(e.symbol), enclosing, flags, internalFlags, tracker),
50353+ /*initializer*/ undefined,
50354+ );
50355+ });
50356+ if (!unusableResult) {
50357+ result.push(...recreatedComponents);
50358+ continue;
50359+ }
50360+ }
5029150361 const node = nodeBuilder.indexInfoToIndexSignatureDeclaration(info, enclosing, flags, internalFlags, tracker);
5029250362 if (node && infoList === staticInfos) {
5029350363 (((node as Mutable<typeof node>).modifiers ||= factory.createNodeArray()) as MutableNodeArray<Modifier>).unshift(factory.createModifier(SyntaxKind.StaticKeyword));
@@ -50298,6 +50368,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
5029850368 }
5029950369 }
5030050370 return result;
50371+
50372+ function trackComputedName(accessExpression: EntityNameOrEntityNameExpression) {
50373+ if (!tracker.trackSymbol) return;
50374+ // get symbol of the first identifier of the entityName
50375+ const firstIdentifier = getFirstIdentifier(accessExpression);
50376+ const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nameNotFoundMessage*/ undefined, /*isUse*/ true);
50377+ if (name) {
50378+ tracker.trackSymbol(name, enclosing, SymbolFlags.Value);
50379+ }
50380+ }
5030150381 },
5030250382 };
5030350383
@@ -51750,7 +51830,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
5175051830 }
5175151831
5175251832 function checkGrammarForInvalidDynamicName(node: DeclarationName, message: DiagnosticMessage) {
51753- if (isNonBindableDynamicName(node)) {
51833+ // Even non-bindable names are allowed as late-bound implied index signatures so long as the name is a simple `a.b.c` type name expression
51834+ if (isNonBindableDynamicName(node) && !isEntityNameExpression(isElementAccessExpression(node) ? skipParentheses(node.argumentExpression) : (node as ComputedPropertyName).expression)) {
5175451835 return grammarErrorOnNode(node, message);
5175551836 }
5175651837 }
0 commit comments