Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monomorphic Symbol access #51880

Merged
merged 1 commit into from
Dec 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 120 additions & 109 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 @@ -5735,19 +5735,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;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this primarily to catch an unconditional cast to SymbolLinks from Symbol and decided to leave it in to prevent that from happening in the future.

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 @@ -5758,7 +5759,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 @@ -5831,23 +5832,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 @@ -6821,7 +6821,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 @@ -6833,7 +6833,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 @@ -7262,9 +7263,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 @@ -3003,9 +3003,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 @@ -3014,10 +3014,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