Skip to content

Commit b6f888c

Browse files
committed
Progress
1 parent 864a7d3 commit b6f888c

File tree

2 files changed

+69
-5
lines changed

2 files changed

+69
-5
lines changed

src/compiler/checker.ts

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4337,6 +4337,8 @@ namespace ts {
43374337
if (!(symbol && !isPropertyOrMethodDeclarationSymbol(symbol))) {
43384338
return undefined;
43394339
}
4340+
// TODO(jakebailey): do something different with caching here rather than clearing node.id?
4341+
// Maybe, include in the key the length of the original chain?
43404342
const links = getSymbolLinks(symbol);
43414343
const cache = (links.accessibleChainCache ||= new Map());
43424344
// Go from enclosingDeclaration to the first scope we check, so the cache is keyed off the scope and thus shared more
@@ -5090,7 +5092,7 @@ namespace ts {
50905092
}
50915093
if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams &&
50925094
type.flags & TypeFlags.TypeParameter &&
5093-
!isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration)) {
5095+
typeParameterIsCachedOrNotAccessible(type, context)) {
50945096
const name = typeParameterToName(type, context);
50955097
context.approximateLength += idText(name).length;
50965098
return factory.createTypeReferenceNode(factory.createIdentifier(idText(name)), /*typeArguments*/ undefined);
@@ -5880,6 +5882,31 @@ namespace ts {
58805882
typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, context));
58815883
}
58825884

5885+
// For regular function/method declarations, the enclosing declaration will already be signature.declaration,
5886+
// so this is a no-op, but for arrow functions and function expressions, the enclosing declaration will be
5887+
// the declaration that the arrow function / function expression is assigned to.
5888+
//
5889+
// If the parameters or return type include "typeof globalThis.paramName", using the wrong scope will lead
5890+
// us to believe that we can emit "typeof paramName" instead, even though that would refer to the parameter,
5891+
// not the global. Make sure we are in the right scope by changing the enclosingDeclaration to the function.
5892+
//
5893+
// We can't use the declaration directly; it may be in another file and so we may lose access to symbols
5894+
// accessible to the current enclosing declaration, or gain access to symbols not accessible to the current
5895+
// enclosing declaration. To keep this chain accurate, clone the function as a non-synthesized node (so that
5896+
// it can be used as an enclosing declaration) and make its parent the current enclosing declaration.
5897+
//
5898+
// If the declaration is in a JS file, then we don't need to do this at all, as there are no annotations besides
5899+
// JSDoc, which are always outside the function declaration, so are not in the parameter scope.
5900+
const saveEnclosingDeclaration = context.enclosingDeclaration;
5901+
const originalDeclaration = signature.declaration;
5902+
if (saveEnclosingDeclaration && originalDeclaration && originalDeclaration !== saveEnclosingDeclaration && !isInJSFile(originalDeclaration)) {
5903+
// Debug.assert(!isJSDocSignature(originalDeclaration));
5904+
const clone = parseNodeFactory.cloneNode(originalDeclaration);
5905+
// setTextRange(clone, originalDeclaration); // TODO(jakebailey): Is this needed?
5906+
setParent(clone, saveEnclosingDeclaration); // TODO(jakebailey): we can chain these if we want.
5907+
context.enclosingDeclaration = clone;
5908+
}
5909+
58835910
const expandedParams = getExpandedParameters(signature, /*skipUnionExpanding*/ true)[0];
58845911
// If the expanded parameter list had a variadic in a non-trailing position, don't expand it
58855912
const parameters = (some(expandedParams, p => p !== expandedParams[expandedParams.length - 1] && !!(getCheckFlags(p) & CheckFlags.RestParameter)) ? signature.parameters : expandedParams).map(parameter => symbolToParameterDeclaration(parameter, context, kind === SyntaxKind.Constructor, options?.privateSymbolVisitor, options?.bundledImports));
@@ -5936,6 +5963,7 @@ namespace ts {
59365963
node.typeArguments = factory.createNodeArray(typeArguments);
59375964
}
59385965

5966+
context.enclosingDeclaration = saveEnclosingDeclaration;
59395967
return node;
59405968
}
59415969

@@ -6420,7 +6448,29 @@ namespace ts {
64206448
return false;
64216449
}
64226450

6451+
// TODO(jakebailey): Is this change still needed?
6452+
// Returns true if this type parameter should be converted to a name via typeParameterToName,
6453+
// either because it is not accessible, or because it's a use of a type variable that has
6454+
// already been rewritten by typeParameterToName and so needs that rewritten name.
6455+
function typeParameterIsCachedOrNotAccessible(type: TypeParameter, context: NodeBuilderContext) {
6456+
return !!getCachedTypeParameterName(type, context) || !isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration);
6457+
}
6458+
6459+
function getCachedTypeParameterName(type: TypeParameter, context: NodeBuilderContext) {
6460+
if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && context.typeParameterNames) {
6461+
const cached = context.typeParameterNames.get(getTypeId(type));
6462+
if (cached) {
6463+
return cached;
6464+
}
6465+
}
6466+
return undefined;
6467+
}
6468+
64236469
function typeParameterToName(type: TypeParameter, context: NodeBuilderContext) {
6470+
const cached = getCachedTypeParameterName(type, context);
6471+
if (cached) {
6472+
return cached;
6473+
}
64246474
if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && context.typeParameterNames) {
64256475
const cached = context.typeParameterNames.get(getTypeId(type));
64266476
if (cached) {
@@ -6601,9 +6651,22 @@ namespace ts {
66016651
return initial;
66026652
}
66036653

6654+
function isChildOfEnclosingDeclaration(enclosingDeclaration: Node | undefined, node: Node): boolean {
6655+
return !!findAncestor(node, n => isEnclosingDeclarationOrOriginal(n));
6656+
// If our enclosing declaration was created via cloning, then the enclosing declaration won't be
6657+
// an ancestor of say, a parameter declaration, but an original node may be.
6658+
function isEnclosingDeclarationOrOriginal(n: Node): boolean {
6659+
for (let e = enclosingDeclaration; e; e = e.original) {
6660+
if (e === n) {
6661+
return true;
6662+
}
6663+
}
6664+
return false;
6665+
}
6666+
}
66046667

66056668
function getDeclarationWithTypeAnnotation(symbol: Symbol, enclosingDeclaration: Node | undefined) {
6606-
return symbol.declarations && find(symbol.declarations, s => !!getEffectiveTypeAnnotationNode(s) && (!enclosingDeclaration || !!findAncestor(s, n => n === enclosingDeclaration)));
6669+
return symbol.declarations && find(symbol.declarations, s => !!getEffectiveTypeAnnotationNode(s) && (!enclosingDeclaration || isChildOfEnclosingDeclaration(enclosingDeclaration, s)));
66076670
}
66086671

66096672
function existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing: TypeNode, type: Type) {
@@ -6652,7 +6715,7 @@ namespace ts {
66526715
function serializeReturnTypeForSignature(context: NodeBuilderContext, type: Type, signature: Signature, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) {
66536716
if (!isErrorType(type) && context.enclosingDeclaration) {
66546717
const annotation = signature.declaration && getEffectiveReturnTypeNode(signature.declaration);
6655-
if (!!findAncestor(annotation, n => n === context.enclosingDeclaration) && annotation) {
6718+
if (annotation && isChildOfEnclosingDeclaration(context.enclosingDeclaration, annotation)) { // TODO(jakebailey): this also needs to check original; move to helper
66566719
const annotated = getTypeFromTypeNode(annotation);
66576720
const thisInstantiated = annotated.flags & TypeFlags.TypeParameter && (annotated as TypeParameter).isThisType ? instantiateType(annotated, signature.mapper) : annotated;
66586721
if (thisInstantiated === type && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(annotation, type)) {
@@ -6684,7 +6747,7 @@ namespace ts {
66846747
}
66856748
if (isIdentifier(node)) {
66866749
const type = getDeclaredTypeOfSymbol(sym);
6687-
const name = sym.flags & SymbolFlags.TypeParameter && !isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration) ? typeParameterToName(type, context) : factory.cloneNode(node);
6750+
const name = sym.flags & SymbolFlags.TypeParameter && typeParameterIsCachedOrNotAccessible(type, context) ? typeParameterToName(type, context) : factory.cloneNode(node);
66886751
name.symbol = sym; // for quickinfo, which uses identifier symbol information
66896752
return { introducesError, node: setEmitFlags(setOriginalNode(name, node), EmitFlags.NoAsciiEscaping) };
66906753
}
@@ -7116,7 +7179,7 @@ namespace ts {
71167179
visitedSymbols.add(getSymbolId(visitedSym));
71177180
// Only actually serialize symbols within the correct enclosing declaration, otherwise do nothing with the out-of-context symbol
71187181
const skipMembershipCheck = !isPrivate; // We only call this on exported symbols when we know they're in the correct scope
7119-
if (skipMembershipCheck || (!!length(symbol.declarations) && some(symbol.declarations, d => !!findAncestor(d, n => n === enclosingDeclaration)))) {
7182+
if (skipMembershipCheck || (!!length(symbol.declarations) && some(symbol.declarations, d => isChildOfEnclosingDeclaration(enclosingDeclaration, d)))) {
71207183
const oldContext = context;
71217184
context = cloneNodeBuilderContext(context);
71227185
serializeSymbolWorker(symbol, isPrivate, propertyAsAlias);

src/compiler/factory/nodeFactory.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5576,6 +5576,7 @@ namespace ts {
55765576
clone[key] = node[key];
55775577
}
55785578

5579+
clone.id = undefined; // New node, new ID.
55795580
return clone;
55805581
}
55815582

0 commit comments

Comments
 (0)