Skip to content

Commit cbf1975

Browse files
committed
fix: prevent stack overflow in symbolToTypeNode with recursive return type references
Fixes #63273 Add recursive reference detection in symbolToTypeNode using a visitedSymbols set in NodeBuilderContext. When a symbol is detected as already being processed, return never type to break the cycle. This prevents compiler crashes when a function's return type references itself via ReturnType<typeof functionName>.
1 parent f16d4af commit cbf1975

1 file changed

Lines changed: 24 additions & 6 deletions

File tree

src/compiler/checker.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8661,6 +8661,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
86618661
}
86628662

86638663
function symbolToTypeNode(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, overrideTypeArguments?: readonly TypeNode[]): TypeNode {
8664+
// Prevent infinite recursion when a function's return type references itself (e.g., ReturnType<typeof clone>)
8665+
if (!context.visitedSymbols) {
8666+
context.visitedSymbols = new Set();
8667+
}
8668+
const symbolKey = `${getSymbolId(symbol)}|${meaning}`;
8669+
if (context.visitedSymbols.has(symbolKey)) {
8670+
// Detected recursive symbol reference, return never type to avoid crash
8671+
return factory.createKeywordTypeNode(SyntaxKind.NeverKeyword);
8672+
}
8673+
context.visitedSymbols.add(symbolKey);
8674+
86648675
const chain = lookupSymbolChain(symbol, context, meaning, !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope)); // If we're using aliases outside the current scope, dont bother with the module
86658676

86668677
const isTypeOf = meaning === SymbolFlags.Value;
@@ -8723,33 +8734,39 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
87238734
}
87248735
const lit = factory.createLiteralTypeNode(factory.createStringLiteral(specifier));
87258736
context.approximateLength += specifier.length + 10; // specifier + import("")
8737+
let result: TypeNode;
87268738
if (!nonRootParts || isEntityName(nonRootParts)) {
87278739
if (nonRootParts) {
87288740
const lastId = isIdentifier(nonRootParts) ? nonRootParts : nonRootParts.right;
87298741
setIdentifierTypeArguments(lastId, /*typeArguments*/ undefined);
87308742
}
8731-
return factory.createImportTypeNode(lit, attributes, nonRootParts as EntityName, typeParameterNodes as readonly TypeNode[], isTypeOf);
8743+
result = factory.createImportTypeNode(lit, attributes, nonRootParts as EntityName, typeParameterNodes as readonly TypeNode[], isTypeOf);
87328744
}
87338745
else {
87348746
const splitNode = getTopmostIndexedAccessType(nonRootParts);
87358747
const qualifier = (splitNode.objectType as TypeReferenceNode).typeName;
8736-
return factory.createIndexedAccessTypeNode(factory.createImportTypeNode(lit, attributes, qualifier, typeParameterNodes as readonly TypeNode[], isTypeOf), splitNode.indexType);
8748+
result = factory.createIndexedAccessTypeNode(factory.createImportTypeNode(lit, attributes, qualifier, typeParameterNodes as readonly TypeNode[], isTypeOf), splitNode.indexType);
87378749
}
8750+
context.visitedSymbols.delete(symbolKey);
8751+
return result;
87388752
}
87398753

87408754
const entityName = createAccessFromSymbolChain(chain, chain.length - 1, 0);
8755+
let result: TypeNode;
87418756
if (isIndexedAccessTypeNode(entityName)) {
8742-
return entityName; // Indexed accesses can never be `typeof`
8757+
result = entityName; // Indexed accesses can never be `typeof`
87438758
}
8744-
if (isTypeOf) {
8745-
return factory.createTypeQueryNode(entityName);
8759+
else if (isTypeOf) {
8760+
result = factory.createTypeQueryNode(entityName);
87468761
}
87478762
else {
87488763
const lastId = isIdentifier(entityName) ? entityName : entityName.right;
87498764
const lastTypeArgs = getIdentifierTypeArguments(lastId);
87508765
setIdentifierTypeArguments(lastId, /*typeArguments*/ undefined);
8751-
return factory.createTypeReferenceNode(entityName, lastTypeArgs as NodeArray<TypeNode>);
8766+
result = factory.createTypeReferenceNode(entityName, lastTypeArgs as NodeArray<TypeNode>);
87528767
}
8768+
context.visitedSymbols.delete(symbolKey);
8769+
return result;
87538770

87548771
function createAccessFromSymbolChain(chain: Symbol[], index: number, stopper: number): EntityName | IndexedAccessTypeNode {
87558772
const typeParameterNodes = index === (chain.length - 1) ? overrideTypeArguments : lookupTypeParameterNodes(chain, index, context);
@@ -54300,6 +54317,7 @@ interface NodeBuilderContext extends SyntacticTypeNodeBuilderContext {
5430054317
reportedDiagnostic: boolean;
5430154318
trackedSymbols: TrackedSymbol[] | undefined;
5430254319
visitedTypes: Set<number> | undefined;
54320+
visitedSymbols: Set<string> | undefined;
5430354321
symbolDepth: Map<string, number> | undefined;
5430454322
inferTypeParameters: TypeParameter[] | undefined;
5430554323
approximateLength: number;

0 commit comments

Comments
 (0)