mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Support interpreting non-literal computed properties in classes as implicit index signatures (#59860)
This commit is contained in:
+148
-30
@@ -583,6 +583,7 @@ import {
|
||||
isImportTypeNode,
|
||||
isInCompoundLikeAssignment,
|
||||
isIndexedAccessTypeNode,
|
||||
isIndexSignatureDeclaration,
|
||||
isInExpressionContext,
|
||||
isInfinityOrNaNString,
|
||||
isInitializedProperty,
|
||||
@@ -878,6 +879,7 @@ import {
|
||||
ModuleResolutionKind,
|
||||
ModuleSpecifierResolutionHost,
|
||||
Mutable,
|
||||
MutableNodeArray,
|
||||
NamedDeclaration,
|
||||
NamedExports,
|
||||
NamedImportsOrExports,
|
||||
@@ -13345,12 +13347,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
* - The type of its expression is a string or numeric literal type, or is a `unique symbol` type.
|
||||
*/
|
||||
function isLateBindableName(node: DeclarationName): node is LateBoundName {
|
||||
return isLateBindableAST(node)
|
||||
&& isTypeUsableAsPropertyName(isComputedPropertyName(node) ? checkComputedPropertyName(node) : checkExpressionCached((node as ElementAccessExpression).argumentExpression));
|
||||
}
|
||||
|
||||
function isLateBindableIndexSignature(node: DeclarationName): node is LateBoundName {
|
||||
return isLateBindableAST(node)
|
||||
&& isTypeUsableAsIndexSignature(isComputedPropertyName(node) ? checkComputedPropertyName(node) : checkExpressionCached((node as ElementAccessExpression).argumentExpression));
|
||||
}
|
||||
|
||||
function isLateBindableAST(node: DeclarationName) {
|
||||
if (!isComputedPropertyName(node) && !isElementAccessExpression(node)) {
|
||||
return false;
|
||||
}
|
||||
const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression;
|
||||
return isEntityNameExpression(expr)
|
||||
&& isTypeUsableAsPropertyName(isComputedPropertyName(node) ? checkComputedPropertyName(node) : checkExpressionCached(expr));
|
||||
return isEntityNameExpression(expr);
|
||||
}
|
||||
|
||||
function isTypeUsableAsIndexSignature(type: Type): boolean {
|
||||
return isTypeAssignableTo(type, stringNumberSymbolType);
|
||||
}
|
||||
|
||||
function isLateBoundName(name: __String): boolean {
|
||||
@@ -13367,6 +13382,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return !!name && isLateBindableName(name);
|
||||
}
|
||||
|
||||
function hasLateBindableIndexSignature(node: Declaration) {
|
||||
const name = getNameOfDeclaration(node);
|
||||
return !!name && isLateBindableIndexSignature(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether a declaration has an early-bound name or a dynamic name that can be late-bound.
|
||||
*/
|
||||
@@ -13474,6 +13494,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return links.resolvedSymbol;
|
||||
}
|
||||
|
||||
function lateBindIndexSignature(parent: Symbol, earlySymbols: SymbolTable | undefined, lateSymbols: Map<__String, TransientSymbol>, decl: LateBoundDeclaration | LateBoundBinaryExpressionDeclaration) {
|
||||
// First, late bind the index symbol itself, if needed
|
||||
let indexSymbol = lateSymbols.get(InternalSymbolName.Index);
|
||||
if (!indexSymbol) {
|
||||
const early = earlySymbols?.get(InternalSymbolName.Index);
|
||||
if (!early) {
|
||||
indexSymbol = createSymbol(SymbolFlags.None, InternalSymbolName.Index, CheckFlags.Late);
|
||||
}
|
||||
else {
|
||||
indexSymbol = cloneSymbol(early);
|
||||
indexSymbol.links.checkFlags |= CheckFlags.Late;
|
||||
}
|
||||
lateSymbols.set(InternalSymbolName.Index, indexSymbol);
|
||||
}
|
||||
// Then just add the computed name as a late bound declaration
|
||||
// (note: unlike `addDeclarationToLateBoundSymbol` we do not set up a `.lateSymbol` on `decl`'s links,
|
||||
// since that would point at an index symbol and not a single property symbol, like most consumers would expect)
|
||||
if (!indexSymbol.declarations) {
|
||||
indexSymbol.declarations = [decl];
|
||||
}
|
||||
else if (!decl.symbol.isReplaceableByMethod) {
|
||||
indexSymbol.declarations.push(decl);
|
||||
}
|
||||
}
|
||||
|
||||
function getResolvedMembersOrExportsOfSymbol(symbol: Symbol, resolutionKind: MembersOrExportsResolutionKind): Map<__String, Symbol> {
|
||||
const links = getSymbolLinks(symbol);
|
||||
if (!links[resolutionKind]) {
|
||||
@@ -13497,6 +13542,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
if (hasLateBindableName(member)) {
|
||||
lateBindMember(symbol, earlySymbols, lateSymbols, member);
|
||||
}
|
||||
else if (hasLateBindableIndexSignature(member)) {
|
||||
lateBindIndexSignature(symbol, earlySymbols, lateSymbols, member as Node as LateBoundDeclaration | LateBoundBinaryExpressionDeclaration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14160,7 +14208,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
|
||||
const indexSymbol = getIndexSymbolFromSymbolTable(members);
|
||||
if (indexSymbol) {
|
||||
indexInfos = getIndexInfosOfIndexSymbol(indexSymbol);
|
||||
indexInfos = getIndexInfosOfIndexSymbol(indexSymbol, arrayFrom(members.values()));
|
||||
}
|
||||
else {
|
||||
if (baseConstructorIndexInfo) {
|
||||
@@ -16192,7 +16240,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
|
||||
function getIndexSymbol(symbol: Symbol): Symbol | undefined {
|
||||
return symbol.members ? getIndexSymbolFromSymbolTable(symbol.members) : undefined;
|
||||
return symbol.members ? getIndexSymbolFromSymbolTable(getMembersOfSymbol(symbol)) : undefined;
|
||||
}
|
||||
|
||||
function getIndexSymbolFromSymbolTable(symbolTable: SymbolTable): Symbol | undefined {
|
||||
@@ -16205,24 +16253,67 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
|
||||
function getIndexInfosOfSymbol(symbol: Symbol): IndexInfo[] {
|
||||
const indexSymbol = getIndexSymbol(symbol);
|
||||
return indexSymbol ? getIndexInfosOfIndexSymbol(indexSymbol) : emptyArray;
|
||||
return indexSymbol ? getIndexInfosOfIndexSymbol(indexSymbol, arrayFrom(getMembersOfSymbol(symbol).values())) : emptyArray;
|
||||
}
|
||||
|
||||
function getIndexInfosOfIndexSymbol(indexSymbol: Symbol): IndexInfo[] {
|
||||
// note intentional similarities to index signature building in `checkObjectLiteral` for parity
|
||||
function getIndexInfosOfIndexSymbol(indexSymbol: Symbol, siblingSymbols: Symbol[] | undefined = indexSymbol.parent ? arrayFrom(getMembersOfSymbol(indexSymbol.parent).values()) : undefined): IndexInfo[] {
|
||||
if (indexSymbol.declarations) {
|
||||
const indexInfos: IndexInfo[] = [];
|
||||
for (const declaration of (indexSymbol.declarations as IndexSignatureDeclaration[])) {
|
||||
if (declaration.parameters.length === 1) {
|
||||
const parameter = declaration.parameters[0];
|
||||
if (parameter.type) {
|
||||
forEachType(getTypeFromTypeNode(parameter.type), keyType => {
|
||||
if (isValidIndexKeyType(keyType) && !findIndexInfo(indexInfos, keyType)) {
|
||||
indexInfos.push(createIndexInfo(keyType, declaration.type ? getTypeFromTypeNode(declaration.type) : anyType, hasEffectiveModifier(declaration, ModifierFlags.Readonly), declaration));
|
||||
let hasComputedNumberProperty = false;
|
||||
let readonlyComputedNumberProperty = true;
|
||||
let hasComputedSymbolProperty = false;
|
||||
let readonlyComputedSymbolProperty = true;
|
||||
let hasComputedStringProperty = false;
|
||||
let readonlyComputedStringProperty = true;
|
||||
const computedPropertySymbols: Symbol[] = [];
|
||||
for (const declaration of indexSymbol.declarations) {
|
||||
if (isIndexSignatureDeclaration(declaration)) {
|
||||
if (declaration.parameters.length === 1) {
|
||||
const parameter = declaration.parameters[0];
|
||||
if (parameter.type) {
|
||||
forEachType(getTypeFromTypeNode(parameter.type), keyType => {
|
||||
if (isValidIndexKeyType(keyType) && !findIndexInfo(indexInfos, keyType)) {
|
||||
indexInfos.push(createIndexInfo(keyType, declaration.type ? getTypeFromTypeNode(declaration.type) : anyType, hasEffectiveModifier(declaration, ModifierFlags.Readonly), declaration));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (hasLateBindableIndexSignature(declaration)) {
|
||||
const declName = isBinaryExpression(declaration) ? declaration.left as ElementAccessExpression : (declaration as LateBoundDeclaration).name;
|
||||
const keyType = isElementAccessExpression(declName) ? checkExpressionCached(declName.argumentExpression) : checkComputedPropertyName(declName);
|
||||
if (findIndexInfo(indexInfos, keyType)) {
|
||||
continue; // Explicit index for key type takes priority
|
||||
}
|
||||
if (isTypeAssignableTo(keyType, stringNumberSymbolType)) {
|
||||
if (isTypeAssignableTo(keyType, numberType)) {
|
||||
hasComputedNumberProperty = true;
|
||||
if (!hasEffectiveReadonlyModifier(declaration)) {
|
||||
readonlyComputedNumberProperty = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (isTypeAssignableTo(keyType, esSymbolType)) {
|
||||
hasComputedSymbolProperty = true;
|
||||
if (!hasEffectiveReadonlyModifier(declaration)) {
|
||||
readonlyComputedSymbolProperty = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
hasComputedStringProperty = true;
|
||||
if (!hasEffectiveReadonlyModifier(declaration)) {
|
||||
readonlyComputedStringProperty = false;
|
||||
}
|
||||
}
|
||||
computedPropertySymbols.push(declaration.symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
const allPropertySymbols = concatenate(computedPropertySymbols, filter(siblingSymbols, s => s !== indexSymbol));
|
||||
// aggregate similar index infos implied to be the same key to the same combined index info
|
||||
if (hasComputedStringProperty && !findIndexInfo(indexInfos, stringType)) indexInfos.push(getObjectLiteralIndexInfo(readonlyComputedStringProperty, 0, allPropertySymbols, stringType));
|
||||
if (hasComputedNumberProperty && !findIndexInfo(indexInfos, numberType)) indexInfos.push(getObjectLiteralIndexInfo(readonlyComputedNumberProperty, 0, allPropertySymbols, numberType));
|
||||
if (hasComputedSymbolProperty && !findIndexInfo(indexInfos, esSymbolType)) indexInfos.push(getObjectLiteralIndexInfo(readonlyComputedSymbolProperty, 0, allPropertySymbols, esSymbolType));
|
||||
return indexInfos;
|
||||
}
|
||||
return emptyArray;
|
||||
@@ -32790,7 +32881,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
isTypeAssignableToKind(checkComputedPropertyName(firstDecl.name), TypeFlags.ESSymbol));
|
||||
}
|
||||
|
||||
function getObjectLiteralIndexInfo(node: ObjectLiteralExpression, offset: number, properties: Symbol[], keyType: Type): IndexInfo {
|
||||
// NOTE: currently does not make pattern literal indexers, eg `${number}px`
|
||||
function getObjectLiteralIndexInfo(isReadonly: boolean, offset: number, properties: Symbol[], keyType: Type): IndexInfo {
|
||||
const propTypes: Type[] = [];
|
||||
for (let i = offset; i < properties.length; i++) {
|
||||
const prop = properties[i];
|
||||
@@ -32803,7 +32895,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
}
|
||||
const unionType = propTypes.length ? getUnionType(propTypes, UnionReduction.Subtype) : undefinedType;
|
||||
return createIndexInfo(keyType, unionType, isConstContext(node));
|
||||
return createIndexInfo(keyType, unionType, isReadonly);
|
||||
}
|
||||
|
||||
function getImmediateAliasedSymbol(symbol: Symbol): Symbol | undefined {
|
||||
@@ -33008,9 +33100,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
|
||||
function createObjectLiteralType() {
|
||||
const indexInfos = [];
|
||||
if (hasComputedStringProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, stringType));
|
||||
if (hasComputedNumberProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, numberType));
|
||||
if (hasComputedSymbolProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, esSymbolType));
|
||||
const isReadonly = isConstContext(node);
|
||||
if (hasComputedStringProperty) indexInfos.push(getObjectLiteralIndexInfo(isReadonly, offset, propertiesArray, stringType));
|
||||
if (hasComputedNumberProperty) indexInfos.push(getObjectLiteralIndexInfo(isReadonly, offset, propertiesArray, numberType));
|
||||
if (hasComputedSymbolProperty) indexInfos.push(getObjectLiteralIndexInfo(isReadonly, offset, propertiesArray, esSymbolType));
|
||||
const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, indexInfos);
|
||||
result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral;
|
||||
if (isJSObjectLiteral) {
|
||||
@@ -41575,18 +41668,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
const indexSymbol = getIndexSymbol(getSymbolOfDeclaration(node));
|
||||
if (indexSymbol?.declarations) {
|
||||
const indexSignatureMap = new Map<TypeId, { type: Type; declarations: IndexSignatureDeclaration[]; }>();
|
||||
for (const declaration of (indexSymbol.declarations as IndexSignatureDeclaration[])) {
|
||||
if (declaration.parameters.length === 1 && declaration.parameters[0].type) {
|
||||
forEachType(getTypeFromTypeNode(declaration.parameters[0].type), type => {
|
||||
const entry = indexSignatureMap.get(getTypeId(type));
|
||||
if (entry) {
|
||||
entry.declarations.push(declaration);
|
||||
}
|
||||
else {
|
||||
indexSignatureMap.set(getTypeId(type), { type, declarations: [declaration] });
|
||||
}
|
||||
});
|
||||
for (const declaration of indexSymbol.declarations) {
|
||||
if (isIndexSignatureDeclaration(declaration)) {
|
||||
if (declaration.parameters.length === 1 && declaration.parameters[0].type) {
|
||||
forEachType(getTypeFromTypeNode(declaration.parameters[0].type), type => {
|
||||
const entry = indexSignatureMap.get(getTypeId(type));
|
||||
if (entry) {
|
||||
entry.declarations.push(declaration);
|
||||
}
|
||||
else {
|
||||
indexSignatureMap.set(getTypeId(type), { type, declarations: [declaration] });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// Do nothing for late-bound index signatures: allow these to duplicate one another and explicit indexes
|
||||
}
|
||||
indexSignatureMap.forEach(entry => {
|
||||
if (entry.declarations.length > 1) {
|
||||
@@ -50433,6 +50529,28 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
},
|
||||
isImportRequiredByAugmentation,
|
||||
isDefinitelyReferenceToGlobalSymbolObject,
|
||||
createLateBoundIndexSignatures: (cls, enclosing, flags, internalFlags, tracker) => {
|
||||
const sym = cls.symbol;
|
||||
const staticInfos = getIndexInfosOfType(getTypeOfSymbol(sym));
|
||||
const instanceIndexSymbol = getIndexSymbol(sym);
|
||||
const instanceInfos = instanceIndexSymbol && getIndexInfosOfIndexSymbol(instanceIndexSymbol, arrayFrom(getMembersOfSymbol(sym).values()));
|
||||
let result;
|
||||
for (const infoList of [staticInfos, instanceInfos]) {
|
||||
if (!length(infoList)) continue;
|
||||
result ||= [];
|
||||
for (const info of infoList!) {
|
||||
if (info.declaration) continue;
|
||||
const node = nodeBuilder.indexInfoToIndexSignatureDeclaration(info, enclosing, flags, internalFlags, tracker);
|
||||
if (node && infoList === staticInfos) {
|
||||
(((node as Mutable<typeof node>).modifiers ||= factory.createNodeArray()) as MutableNodeArray<Modifier>).unshift(factory.createModifier(SyntaxKind.StaticKeyword));
|
||||
}
|
||||
if (node) {
|
||||
result.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
function isImportRequiredByAugmentation(node: ImportDeclaration) {
|
||||
|
||||
@@ -1167,6 +1167,7 @@ export const notImplementedResolver: EmitResolver = {
|
||||
getDeclarationStatementsForSourceFile: notImplemented,
|
||||
isImportRequiredByAugmentation: notImplemented,
|
||||
isDefinitelyReferenceToGlobalSymbolObject: notImplemented,
|
||||
createLateBoundIndexSignatures: notImplemented,
|
||||
};
|
||||
|
||||
const enum PipelinePhase {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
canHaveModifiers,
|
||||
canProduceDiagnostics,
|
||||
ClassDeclaration,
|
||||
ClassElement,
|
||||
compact,
|
||||
concatenate,
|
||||
ConditionalTypeNode,
|
||||
@@ -1652,7 +1653,8 @@ export function transformDeclarations(context: TransformationContext): Transform
|
||||
/*initializer*/ undefined,
|
||||
),
|
||||
] : undefined;
|
||||
const memberNodes = concatenate(concatenate(privateIdentifier, parameterProperties), visitNodes(input.members, visitDeclarationSubtree, isClassElement));
|
||||
const lateIndexes = resolver.createLateBoundIndexSignatures(input, enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, symbolTracker);
|
||||
const memberNodes = concatenate(concatenate(concatenate<ClassElement>(privateIdentifier, lateIndexes), parameterProperties), visitNodes(input.members, visitDeclarationSubtree, isClassElement));
|
||||
const members = factory.createNodeArray(memberNodes);
|
||||
|
||||
const extendsClause = getEffectiveBaseTypeNode(input);
|
||||
|
||||
@@ -5048,7 +5048,7 @@ export interface TypeChecker {
|
||||
/** @internal */ getTypeOfPropertyOfType(type: Type, propertyName: string): Type | undefined;
|
||||
getIndexInfoOfType(type: Type, kind: IndexKind): IndexInfo | undefined;
|
||||
getIndexInfosOfType(type: Type): readonly IndexInfo[];
|
||||
getIndexInfosOfIndexSymbol: (indexSymbol: Symbol) => IndexInfo[];
|
||||
getIndexInfosOfIndexSymbol: (indexSymbol: Symbol, siblingSymbols?: Symbol[] | undefined) => IndexInfo[];
|
||||
getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[];
|
||||
getIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined;
|
||||
/** @internal */ getIndexType(type: Type): Type;
|
||||
@@ -5867,6 +5867,7 @@ export interface EmitResolver {
|
||||
getDeclarationStatementsForSourceFile(node: SourceFile, flags: NodeBuilderFlags, internalFlags: InternalNodeBuilderFlags, tracker: SymbolTracker): Statement[] | undefined;
|
||||
isImportRequiredByAugmentation(decl: ImportDeclaration): boolean;
|
||||
isDefinitelyReferenceToGlobalSymbolObject(node: Node): boolean;
|
||||
createLateBoundIndexSignatures(cls: ClassLikeDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, internalFlags: InternalNodeBuilderFlags, tracker: SymbolTracker): IndexSignatureDeclaration[] | undefined;
|
||||
}
|
||||
|
||||
// dprint-ignore
|
||||
|
||||
Reference in New Issue
Block a user