@@ -157,6 +157,7 @@ import {
157157 ElementAccessChain,
158158 ElementAccessExpression,
159159 ElementFlags,
160+ ElementWithComputedPropertyName,
160161 EmitFlags,
161162 EmitHint,
162163 emitModuleKindIsNonNodeESM,
@@ -6700,7 +6701,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
67006701 }
67016702 function shouldWriteTypeOfFunctionSymbol() {
67026703 const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method) && // typeof static method
6703- some(symbol.declarations, declaration => isStatic(declaration));
6704+ some(symbol.declarations, declaration => isStatic(declaration) && !isLateBindableIndexSignature(getNameOfDeclaration(declaration)!) );
67046705 const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) &&
67056706 (symbol.parent || // is exported function symbol
67066707 forEach(symbol.declarations, declaration => declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock));
@@ -7056,6 +7057,37 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
70567057 return ids;
70577058 }
70587059
7060+ function indexInfoToObjectComputedNamesOrSignatureDeclaration(indexInfo: IndexInfo, context: NodeBuilderContext, typeNode: TypeNode | undefined): [IndexSignatureDeclaration] | PropertySignature[] {
7061+ if (indexInfo.components) {
7062+ // 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
7063+ let unusableResult = false;
7064+ const result = map(indexInfo.components, e => {
7065+ if (e.name && isComputedPropertyName(e.name) && isEntityNameExpression(e.name.expression)) {
7066+ trackComputedName(e.name.expression, context.enclosingDeclaration, context);
7067+ }
7068+ else {
7069+ // Computed name didn't take the form `[a.b.c]: something` - bail on using the computed name.
7070+ // TODO: Issue isolated declarations error on this fallback?
7071+ unusableResult = true;
7072+ }
7073+ return setTextRange(
7074+ context,
7075+ factory.createPropertySignature(
7076+ indexInfo.isReadonly ? [factory.createModifier(SyntaxKind.ReadonlyKeyword)] : undefined,
7077+ e.name,
7078+ /*questionToken*/ undefined,
7079+ typeNode || typeToTypeNodeHelper(getTypeOfSymbol(e.symbol), context),
7080+ ),
7081+ e,
7082+ );
7083+ });
7084+ if (!unusableResult) {
7085+ return result;
7086+ }
7087+ }
7088+ return [indexInfoToIndexSignatureDeclarationHelper(indexInfo, context, typeNode)];
7089+ }
7090+
70597091 function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] | undefined {
70607092 if (checkTruncationLength(context)) {
70617093 if (context.flags & NodeBuilderFlags.NoTruncation) {
@@ -7072,7 +7104,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
70727104 typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, context) as ConstructSignatureDeclaration);
70737105 }
70747106 for (const info of resolvedType.indexInfos) {
7075- typeElements.push(indexInfoToIndexSignatureDeclarationHelper (info, context, resolvedType.objectFlags & ObjectFlags.ReverseMapped ? createElidedInformationPlaceholder(context) : undefined));
7107+ typeElements.push(...indexInfoToObjectComputedNamesOrSignatureDeclaration (info, context, resolvedType.objectFlags & ObjectFlags.ReverseMapped ? createElidedInformationPlaceholder(context) : undefined));
70767108 }
70777109
70787110 const properties = resolvedType.properties;
@@ -16247,8 +16279,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1624716279 return symbolTable.get(InternalSymbolName.Index);
1624816280 }
1624916281
16250- function createIndexInfo(keyType: Type, type: Type, isReadonly: boolean, declaration?: IndexSignatureDeclaration): IndexInfo {
16251- return { keyType, type, isReadonly, declaration };
16282+ function createIndexInfo(keyType: Type, type: Type, isReadonly: boolean, declaration?: IndexSignatureDeclaration, components?: ElementWithComputedPropertyName[] ): IndexInfo {
16283+ return { keyType, type, isReadonly, declaration, components };
1625216284 }
1625316285
1625416286 function getIndexInfosOfSymbol(symbol: Symbol): IndexInfo[] {
@@ -19845,7 +19877,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1984519877 }
1984619878
1984719879 function getIndexInfoWithReadonly(info: IndexInfo, readonly: boolean) {
19848- return info.isReadonly !== readonly ? createIndexInfo(info.keyType, info.type, readonly, info.declaration) : info;
19880+ return info.isReadonly !== readonly ? createIndexInfo(info.keyType, info.type, readonly, info.declaration, info.components ) : info;
1984919881 }
1985019882
1985119883 function createLiteralType(flags: TypeFlags, value: string | number | PseudoBigInt, symbol?: Symbol, regularType?: LiteralType) {
@@ -20722,7 +20754,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2072220754 }
2072320755
2072420756 function instantiateIndexInfo(info: IndexInfo, mapper: TypeMapper) {
20725- return createIndexInfo(info.keyType, instantiateType(info.type, mapper), info.isReadonly, info.declaration);
20757+ return createIndexInfo(info.keyType, instantiateType(info.type, mapper), info.isReadonly, info.declaration, info.components );
2072620758 }
2072720759
2072820760 // Returns true if the given expression contains (at any level of nesting) a function or arrow expression
@@ -25506,7 +25538,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2550625538 }
2550725539 }
2550825540 }
25509- const result = createAnonymousType(type.symbol, members, emptyArray, emptyArray, sameMap(getIndexInfosOfType(type), info => createIndexInfo(info.keyType, getWidenedType(info.type), info.isReadonly)));
25541+ const result = createAnonymousType(type.symbol, members, emptyArray, emptyArray, sameMap(getIndexInfosOfType(type), info => createIndexInfo(info.keyType, getWidenedType(info.type), info.isReadonly, info.declaration, info.components )));
2551025542 result.objectFlags |= getObjectFlags(type) & (ObjectFlags.JSLiteral | ObjectFlags.NonInferrableType); // Retain js literal flag through widening
2551125543 return result;
2551225544 }
@@ -32881,9 +32913,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3288132913 isTypeAssignableToKind(checkComputedPropertyName(firstDecl.name), TypeFlags.ESSymbol));
3288232914 }
3288332915
32916+ function isSymbolWithComputedName(symbol: Symbol) {
32917+ const firstDecl = symbol.declarations?.[0];
32918+ return firstDecl && isNamedDeclaration(firstDecl) && isComputedPropertyName(firstDecl.name);
32919+ }
32920+
3288432921 // NOTE: currently does not make pattern literal indexers, eg `${number}px`
3288532922 function getObjectLiteralIndexInfo(isReadonly: boolean, offset: number, properties: Symbol[], keyType: Type): IndexInfo {
3288632923 const propTypes: Type[] = [];
32924+ let components: ElementWithComputedPropertyName[] | undefined;
3288732925 for (let i = offset; i < properties.length; i++) {
3288832926 const prop = properties[i];
3288932927 if (
@@ -32892,10 +32930,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3289232930 keyType === esSymbolType && isSymbolWithSymbolName(prop)
3289332931 ) {
3289432932 propTypes.push(getTypeOfSymbol(properties[i]));
32933+ if (isSymbolWithComputedName(properties[i])) {
32934+ components = append(components, properties[i].declarations?.[0]! as ElementWithComputedPropertyName);
32935+ }
3289532936 }
3289632937 }
3289732938 const unionType = propTypes.length ? getUnionType(propTypes, UnionReduction.Subtype) : undefinedType;
32898- return createIndexInfo(keyType, unionType, isReadonly);
32939+ return createIndexInfo(keyType, unionType, isReadonly, /*declaration*/ undefined, components );
3289932940 }
3290032941
3290132942 function getImmediateAliasedSymbol(symbol: Symbol): Symbol | undefined {
@@ -45911,9 +45952,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4591145952 const typeDeclaration = symbol.valueDeclaration;
4591245953 if (typeDeclaration && isClassLike(typeDeclaration)) {
4591345954 for (const member of typeDeclaration.members) {
45914- // Only process instance properties with computed names here. Static properties cannot be in conflict with indexers,
45915- // and properties with literal names were already checked.
45916- if (!isStatic(member) && !hasBindableName(member)) {
45955+ // Only process instance properties against instance index signatures and static properties against static index signatures
45956+ if (
45957+ (
45958+ (!isStaticIndex && !isStatic(member)) ||
45959+ (isStaticIndex && isStatic(member))
45960+ ) && !hasBindableName(member)
45961+ ) {
4591745962 const symbol = getSymbolOfDeclaration(member);
4591845963 checkIndexConstraintForProperty(type, symbol, getTypeOfExpression((member as DynamicNamedDeclaration).name.expression), getNonMissingTypeOfSymbol(symbol));
4591945964 }
@@ -50540,6 +50585,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
5054050585 result ||= [];
5054150586 for (const info of infoList!) {
5054250587 if (info.declaration) continue;
50588+ if (info.components) {
50589+ let unusableResult = false;
50590+ const recreatedComponents = map(info.components, e => {
50591+ if (e.name && isComputedPropertyName(e.name) && isEntityNameExpression(e.name.expression)) {
50592+ trackComputedName(e.name.expression);
50593+ }
50594+ else {
50595+ // Computed name didn't take the form `[a.b.c]: something` - bail on using the computed name.
50596+ // TODO: Issue isolated declarations error on this fallback?
50597+ unusableResult = true;
50598+ }
50599+ const mods = infoList === staticInfos ? [factory.createModifier(SyntaxKind.StaticKeyword)] as Modifier[] : undefined;
50600+ return factory.createPropertyDeclaration(
50601+ append(mods, info.isReadonly ? factory.createModifier(SyntaxKind.ReadonlyKeyword) : undefined),
50602+ e.name,
50603+ /*questionOrExclamationToken*/ undefined,
50604+ nodeBuilder.typeToTypeNode(getTypeOfSymbol(e.symbol), enclosing, flags, internalFlags, tracker),
50605+ /*initializer*/ undefined,
50606+ );
50607+ });
50608+ if (!unusableResult) {
50609+ result.push(...recreatedComponents);
50610+ continue;
50611+ }
50612+ }
5054350613 const node = nodeBuilder.indexInfoToIndexSignatureDeclaration(info, enclosing, flags, internalFlags, tracker);
5054450614 if (node && infoList === staticInfos) {
5054550615 (((node as Mutable<typeof node>).modifiers ||= factory.createNodeArray()) as MutableNodeArray<Modifier>).unshift(factory.createModifier(SyntaxKind.StaticKeyword));
@@ -50550,6 +50620,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
5055050620 }
5055150621 }
5055250622 return result;
50623+
50624+ function trackComputedName(accessExpression: EntityNameOrEntityNameExpression) {
50625+ if (!tracker.trackSymbol) return;
50626+ // get symbol of the first identifier of the entityName
50627+ const firstIdentifier = getFirstIdentifier(accessExpression);
50628+ const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nameNotFoundMessage*/ undefined, /*isUse*/ true);
50629+ if (name) {
50630+ tracker.trackSymbol(name, enclosing, SymbolFlags.Value);
50631+ }
50632+ }
5055350633 },
5055450634 };
5055550635
@@ -52000,7 +52080,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
5200052080 }
5200152081
5200252082 function checkGrammarForInvalidDynamicName(node: DeclarationName, message: DiagnosticMessage) {
52003- if (isNonBindableDynamicName(node)) {
52083+ // 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
52084+ if (isNonBindableDynamicName(node) && !isEntityNameExpression(isElementAccessExpression(node) ? skipParentheses(node.argumentExpression) : (node as ComputedPropertyName).expression)) {
5200452085 return grammarErrorOnNode(node, message);
5200552086 }
5200652087 }
0 commit comments