Skip to content

Commit

Permalink
Monomorphic Symbol access (#51880)
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton authored Dec 15, 2022
1 parent b9aa8a4 commit 2993ea8
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 150 deletions.
225 changes: 118 additions & 107 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

36 changes: 26 additions & 10 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5744,19 +5744,20 @@ export interface Symbol {
members?: SymbolTable; // Class, interface or object literal instance members
exports?: SymbolTable; // Module exports
globalExports?: SymbolTable; // Conditional global UMD exports
/** @internal */ id?: SymbolId; // Unique id (used to look up SymbolLinks)
/** @internal */ mergeId?: number; // Merge id (used to look up merged symbol)
/** @internal */ parent?: Symbol; // Parent symbol
/** @internal */ exportSymbol?: Symbol; // Exported symbol associated with this symbol
/** @internal */ constEnumOnlyModule?: boolean; // True if module contains only const enums or other modules with only const enums
/** @internal */ id: SymbolId; // Unique id (used to look up SymbolLinks)
/** @internal */ mergeId: number; // Merge id (used to look up merged symbol)
/** @internal */ parent?: Symbol; // Parent symbol
/** @internal */ exportSymbol?: Symbol; // Exported symbol associated with this symbol
/** @internal */ constEnumOnlyModule: boolean | undefined; // True if module contains only const enums or other modules with only const enums
/** @internal */ isReferenced?: SymbolFlags; // True if the symbol is referenced elsewhere. Keeps track of the meaning of a reference in case a symbol is both a type parameter and parameter.
/** @internal */ isReplaceableByMethod?: boolean; // Can this Javascript class property be replaced by a method symbol?
/** @internal */ isAssigned?: boolean; // True if the symbol is a parameter with assignments
/** @internal */ isAssigned?: boolean; // True if the symbol is a parameter with assignments
/** @internal */ assignmentDeclarationMembers?: Map<number, Declaration>; // detected late-bound assignment declarations associated with the symbol
}

/** @internal */
export interface SymbolLinks {
_symbolLinksBrand: any;
immediateTarget?: Symbol; // Immediate target of an alias. May be another alias. Do not access directly, use `checker.getImmediateAliasedSymbol` instead.
aliasTarget?: Symbol, // Resolved (non-alias) target of an alias
target?: Symbol; // Original version of an instantiated symbol
Expand All @@ -5767,7 +5768,7 @@ export interface SymbolLinks {
declaredType?: Type; // Type of class, interface, enum, type alias, or type parameter
typeParameters?: TypeParameter[]; // Type parameters of type alias (undefined if non-generic)
outerTypeParameters?: TypeParameter[]; // Outer type parameters of anonymous object type
instantiations?: Map<string, Type>; // Instantiations of generic type alias (undefined if non-generic)
instantiations?: Map<string, Type>; // Instantiations of generic type alias (undefined if non-generic)
aliasSymbol?: Symbol; // Alias associated with generic type alias instantiation
aliasTypeArguments?: readonly Type[] // Alias type arguments (if any)
inferredClassSymbol?: Map<SymbolId, TransientSymbol>; // Symbol of an inferred ES5 constructor function
Expand Down Expand Up @@ -5840,23 +5841,38 @@ export const enum CheckFlags {
}

/** @internal */
export interface TransientSymbol extends Symbol, SymbolLinks {
export interface TransientSymbolLinks extends SymbolLinks {
checkFlags: CheckFlags;
}

/** @internal */
export interface MappedSymbol extends TransientSymbol {
export interface TransientSymbol extends Symbol {
links: TransientSymbolLinks;
}

/** @internal */
export interface MappedSymbolLinks extends TransientSymbolLinks {
mappedType: MappedType;
keyType: Type;
}

/** @internal */
export interface ReverseMappedSymbol extends TransientSymbol {
export interface MappedSymbol extends TransientSymbol {
links: MappedSymbolLinks;
}

/** @internal */
export interface ReverseMappedSymbolLinks extends TransientSymbolLinks {
propertyType: Type;
mappedType: MappedType;
constraintType: IndexType;
}

/** @internal */
export interface ReverseMappedSymbol extends TransientSymbol {
links: ReverseMappedSymbolLinks;
}

export const enum InternalSymbolName {
Call = "__call", // Call signatures
Constructor = "__constructor", // Constructor implementations
Expand Down
16 changes: 12 additions & 4 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6825,7 +6825,7 @@ export function closeFileWatcher(watcher: FileWatcher) {

/** @internal */
export function getCheckFlags(symbol: Symbol): CheckFlags {
return symbol.flags & SymbolFlags.Transient ? (symbol as TransientSymbol).checkFlags : 0;
return symbol.flags & SymbolFlags.Transient ? (symbol as TransientSymbol).links.checkFlags : 0;
}

/** @internal */
Expand All @@ -6837,7 +6837,8 @@ export function getDeclarationModifierFlagsFromSymbol(s: Symbol, isWrite = false
return s.parent && s.parent.flags & SymbolFlags.Class ? flags : flags & ~ModifierFlags.AccessibilityModifier;
}
if (getCheckFlags(s) & CheckFlags.Synthetic) {
const checkFlags = (s as TransientSymbol).checkFlags;
// NOTE: potentially unchecked cast to TransientSymbol
const checkFlags = (s as TransientSymbol).links.checkFlags;
const accessModifier = checkFlags & CheckFlags.ContainsPrivate ? ModifierFlags.Private :
checkFlags & CheckFlags.ContainsPublic ? ModifierFlags.Public :
ModifierFlags.Protected;
Expand Down Expand Up @@ -7266,9 +7267,16 @@ function Symbol(this: Symbol, flags: SymbolFlags, name: __String) {
this.escapedName = name;
this.declarations = undefined;
this.valueDeclaration = undefined;
this.id = undefined;
this.mergeId = undefined;
this.id = 0;
this.mergeId = 0;
this.parent = undefined;
this.members = undefined;
this.exports = undefined;
this.exportSymbol = undefined;
this.constEnumOnlyModule = undefined;
this.isReferenced = undefined;
this.isAssigned = undefined;
(this as any).links = undefined; // used by TransientSymbol
}

function Type(this: Type, checker: TypeChecker, flags: TypeFlags) {
Expand Down
6 changes: 3 additions & 3 deletions src/services/codefixes/fixInvalidImportSyntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
isExpression,
isImportCall,
isNamedDeclaration,
isTransientSymbol,
makeImport,
ModuleKind,
NamespaceImport,
Expand All @@ -23,7 +24,6 @@ import {
SourceFile,
SyntaxKind,
textChanges,
TransientSymbol,
} from "../_namespaces/ts";
import {
createCodeFixActionWithoutFixAll,
Expand Down Expand Up @@ -107,11 +107,11 @@ function getActionsForInvalidImportLocation(context: CodeFixContext): CodeFixAct

function getImportCodeFixesForExpression(context: CodeFixContext, expr: Node): CodeFixAction[] | undefined {
const type = context.program.getTypeChecker().getTypeAtLocation(expr);
if (!(type.symbol && (type.symbol as TransientSymbol).originatingImport)) {
if (!(type.symbol && isTransientSymbol(type.symbol) && type.symbol.links.originatingImport)) {
return [];
}
const fixes: CodeFixAction[] = [];
const relatedImport = (type.symbol as TransientSymbol).originatingImport!; // TODO: GH#18217
const relatedImport = type.symbol.links.originatingImport;
if (!isImportCall(relatedImport)) {
addRange(fixes, getCodeFixesForImportDeclaration(context, relatedImport));
}
Expand Down
12 changes: 6 additions & 6 deletions src/services/codefixes/inferFromUsage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
isRestParameter,
isRightSideOfQualifiedNameOrPropertyAccess,
isSetAccessorDeclaration,
isTransientSymbol,
isVariableDeclaration,
isVariableStatement,
LanguageServiceHost,
Expand Down Expand Up @@ -88,7 +89,6 @@ import {
SourceFile,
Symbol,
SymbolFlags,
SymbolLinks,
SyntaxKind,
textChanges,
Token,
Expand Down Expand Up @@ -1045,7 +1045,7 @@ function inferTypeFromReferences(program: Program, references: readonly Identifi
const members = mapEntries(props, (name, types) => {
const isOptional = types.length < anons.length ? SymbolFlags.Optional : 0;
const s = checker.createSymbol(SymbolFlags.Property | isOptional, name as __String);
s.type = checker.getUnionType(types);
s.links.type = checker.getUnionType(types);
return [name, s];
});
const indexInfos = [];
Expand Down Expand Up @@ -1080,7 +1080,7 @@ function inferTypeFromReferences(program: Program, references: readonly Identifi

const candidateTypes = (usage.candidateTypes || []).map(t => checker.getBaseTypeOfLiteralType(t));
const callsType = usage.calls?.length ? inferStructuralType(usage) : undefined;
if (callsType && candidateTypes) {
if (callsType && candidateTypes) { // TODO: should this be `some(candidateTypes)`?
types.push(checker.getUnionType([callsType, ...candidateTypes], UnionReduction.Subtype));
}
else {
Expand All @@ -1101,7 +1101,7 @@ function inferTypeFromReferences(program: Program, references: readonly Identifi
if (usage.properties) {
usage.properties.forEach((u, name) => {
const symbol = checker.createSymbol(SymbolFlags.Property, name);
symbol.type = combineFromUsage(u);
symbol.links.type = combineFromUsage(u);
members.set(name, symbol);
});
}
Expand Down Expand Up @@ -1202,7 +1202,7 @@ function inferTypeFromReferences(program: Program, references: readonly Identifi
if (elementType) {
genericParamType = elementType;
}
const targetType = (usageParam as SymbolLinks).type
const targetType = tryCast(usageParam, isTransientSymbol)?.links.type
|| (usageParam.valueDeclaration ? checker.getTypeOfSymbolAtLocation(usageParam, usageParam.valueDeclaration) : checker.getAnyType());
types.push(...inferTypeParameters(genericParamType, targetType, typeParameter));
}
Expand All @@ -1221,7 +1221,7 @@ function inferTypeFromReferences(program: Program, references: readonly Identifi
const length = Math.max(...calls.map(c => c.argumentTypes.length));
for (let i = 0; i < length; i++) {
const symbol = checker.createSymbol(SymbolFlags.FunctionScopedVariable, escapeLeadingUnderscores(`arg${i}`));
symbol.type = combineTypes(calls.map(call => call.argumentTypes[i] || checker.getUndefinedType()));
symbol.links.type = combineTypes(calls.map(call => call.argumentTypes[i] || checker.getUndefinedType()));
if (calls.some(call => call.argumentTypes[i] === undefined)) {
symbol.flags |= SymbolFlags.Optional;
}
Expand Down
2 changes: 1 addition & 1 deletion src/services/codefixes/returnValueCorrect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ registerCodeFix({

function createObjectTypeFromLabeledExpression(checker: TypeChecker, label: Identifier, expression: Expression) {
const member = checker.createSymbol(SymbolFlags.Property, label.escapedText);
member.type = checker.getTypeAtLocation(expression);
member.links.type = checker.getTypeAtLocation(expression);
const members = createSymbolTable([member]);
return checker.createAnonymousType(/*symbol*/ undefined, members, [], [], []);
}
Expand Down
2 changes: 1 addition & 1 deletion src/services/refactors/extractSymbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2124,7 +2124,7 @@ function collectReadsAndWrites(
const decl = find(visibleDeclarationsInExtractedRange, d => d.symbol === sym);
if (decl) {
if (isVariableDeclaration(decl)) {
const idString = decl.symbol.id!.toString();
const idString = decl.symbol.id.toString();
if (!exposedVariableSymbolSet.has(idString)) {
exposedVariableDeclarations.push(decl);
exposedVariableSymbolSet.set(idString, true);
Expand Down
11 changes: 7 additions & 4 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,9 @@ import {
isTagName,
isTextWhiteSpaceLike,
isThisTypeParameter,
JSDoc,
isTransientSymbol,
JsDoc,
JSDoc,
JSDocContainer,
JSDocTagInfo,
JsonSourceFile,
Expand Down Expand Up @@ -304,7 +305,6 @@ import {
toPath,
tracing,
TransformFlags,
TransientSymbol,
Type,
TypeChecker,
TypeFlags,
Expand Down Expand Up @@ -615,6 +615,9 @@ class SymbolObject implements Symbol {
escapedName: __String;
declarations!: Declaration[];
valueDeclaration!: Declaration;
id = 0;
mergeId = 0;
constEnumOnlyModule: boolean | undefined;

// Undefined is used to indicate the value has not been computed. If, after computing, the
// symbol has no doc comment, then the empty array will be returned.
Expand Down Expand Up @@ -656,8 +659,8 @@ class SymbolObject implements Symbol {
if (!this.documentationComment) {
this.documentationComment = emptyArray; // Set temporarily to avoid an infinite loop finding inherited docs

if (!this.declarations && (this as Symbol as TransientSymbol).target && ((this as Symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration) {
const labelDecl = ((this as Symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration!;
if (!this.declarations && isTransientSymbol(this) && this.links.target && isTransientSymbol(this.links.target) && this.links.target.links.tupleLabelDeclaration) {
const labelDecl = this.links.target.links.tupleLabelDeclaration;
this.documentationComment = getDocumentationComment([labelDecl], checker);
}
else {
Expand Down
6 changes: 3 additions & 3 deletions src/services/signatureHelp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
isTemplateLiteralToken,
isTemplateSpan,
isTemplateTail,
isTransientSymbol,
last,
lastOrUndefined,
ListFormat,
Expand Down Expand Up @@ -77,7 +78,6 @@ import {
TaggedTemplateExpression,
TemplateExpression,
TextSpan,
TransientSymbol,
tryCast,
Type,
TypeChecker,
Expand Down Expand Up @@ -724,7 +724,7 @@ function itemInfoForParameters(candidateSignature: Signature, checker: TypeCheck
const isVariadic: (parameterList: readonly Symbol[]) => boolean =
!checker.hasEffectiveRestParameter(candidateSignature) ? _ => false
: lists.length === 1 ? _ => true
: pList => !!(pList.length && (pList[pList.length - 1] as TransientSymbol).checkFlags & CheckFlags.RestParameter);
: pList => !!(pList.length && tryCast(pList[pList.length - 1], isTransientSymbol)?.links.checkFlags! & CheckFlags.RestParameter);
return lists.map(parameterList => ({
isVariadic: isVariadic(parameterList),
parameters: parameterList.map(p => createSignatureHelpParameterForParameter(p, checker, enclosingDeclaration, sourceFile, printer)),
Expand All @@ -739,7 +739,7 @@ function createSignatureHelpParameterForParameter(parameter: Symbol, checker: Ty
printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer);
});
const isOptional = checker.isOptionalParameter(parameter.valueDeclaration as ParameterDeclaration);
const isRest = !!((parameter as TransientSymbol).checkFlags & CheckFlags.RestParameter);
const isRest = isTransientSymbol(parameter) && !!(parameter.links.checkFlags & CheckFlags.RestParameter);
return { name: parameter.name, documentation: parameter.getDocumentationComment(checker), displayParts, isOptional, isRest };
}

Expand Down
7 changes: 4 additions & 3 deletions src/services/symbolDisplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
isObjectBindingPattern,
isTaggedTemplateExpression,
isThisInTypeQuery,
isTransientSymbol,
isTypeAliasDeclaration,
isVarConst,
JSDocTagInfo,
Expand Down Expand Up @@ -176,7 +177,7 @@ function getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeCheck
if (flags & SymbolFlags.Signature) return ScriptElementKind.indexSignatureElement;

if (flags & SymbolFlags.Property) {
if (flags & SymbolFlags.Transient && (symbol as TransientSymbol).checkFlags & CheckFlags.Synthetic) {
if (flags & SymbolFlags.Transient && (symbol as TransientSymbol).links.checkFlags & CheckFlags.Synthetic) {
// If union property is result of union of non method (property/accessors/variables), it is labeled as property
const unionPropertyKind = forEach(typeChecker.getRootSymbols(symbol), rootSymbol => {
const rootSymbolFlags = rootSymbol.getFlags();
Expand Down Expand Up @@ -645,8 +646,8 @@ export function getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker: Typ
else {
addRange(displayParts, typeToDisplayParts(typeChecker, type, enclosingDeclaration));
}
if ((symbol as TransientSymbol).target && ((symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration) {
const labelDecl = ((symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration!;
if (isTransientSymbol(symbol) && symbol.links.target && isTransientSymbol(symbol.links.target) && symbol.links.target.links.tupleLabelDeclaration) {
const labelDecl = symbol.links.target.links.tupleLabelDeclaration;
Debug.assertNode(labelDecl.name, isIdentifier);
displayParts.push(spacePart());
displayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
Expand Down
12 changes: 4 additions & 8 deletions src/services/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ import {
isTaggedTemplateExpression,
isTemplateLiteralKind,
isToken,
isTransientSymbol,
isTypeAliasDeclaration,
isTypeElement,
isTypeNode,
Expand Down Expand Up @@ -336,7 +337,6 @@ import {
textSpanEnd,
Token,
tokenToString,
TransientSymbol,
tryCast,
Type,
TypeChecker,
Expand Down Expand Up @@ -2999,9 +2999,9 @@ export function getScriptKind(fileName: string, host: LanguageServiceHost): Scri
/** @internal */
export function getSymbolTarget(symbol: Symbol, checker: TypeChecker): Symbol {
let next: Symbol = symbol;
while (isAliasSymbol(next) || (isTransientSymbol(next) && next.target)) {
if (isTransientSymbol(next) && next.target) {
next = next.target;
while (isAliasSymbol(next) || (isTransientSymbol(next) && next.links.target)) {
if (isTransientSymbol(next) && next.links.target) {
next = next.links.target;
}
else {
next = skipAlias(next, checker);
Expand All @@ -3010,10 +3010,6 @@ export function getSymbolTarget(symbol: Symbol, checker: TypeChecker): Symbol {
return next;
}

function isTransientSymbol(symbol: Symbol): symbol is TransientSymbol {
return (symbol.flags & SymbolFlags.Transient) !== 0;
}

function isAliasSymbol(symbol: Symbol): boolean {
return (symbol.flags & SymbolFlags.Alias) !== 0;
}
Expand Down

0 comments on commit 2993ea8

Please sign in to comment.