From f16d4af8f2d53f23139d1b60996de2fb0561e300 Mon Sep 17 00:00:00 2001 From: wdsmini Date: Thu, 26 Mar 2026 22:52:28 +0800 Subject: [PATCH 1/2] fix(30408): improve error message for labels used before definition The error message 'Jump target cannot cross function boundary' was confusing because it didn't indicate which label was causing the issue. This change adds the label name to the error message when available. Before: 'Jump target cannot cross function boundary.' After: 'Jump target 'loopend' cannot cross function boundary.' Fixes #30408 --- src/compiler/checker.ts | 2 +- src/compiler/diagnosticMessages.json | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0567712f11da3..3f3291a5f0917 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -53030,7 +53030,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let current: Node = node; while (current) { if (isFunctionLikeOrClassStaticBlockDeclaration(current)) { - return grammarErrorOnNode(node, Diagnostics.Jump_target_cannot_cross_function_boundary); + return grammarErrorOnNode(node, Diagnostics.Jump_target_0_cannot_cross_function_boundary, node.label ? node.label.text : ""); } switch (current.kind) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 46c280799d7f9..a7ee23c49b69d 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -307,6 +307,10 @@ "category": "Error", "code": 1107 }, + "Jump target '{0}' cannot cross function boundary.": { + "category": "Error", + "code": 1107 + }, "A 'return' statement can only be used within a function body.": { "category": "Error", "code": 1108 From cbf197511028eff94982f0b0a777f4c367d65bd4 Mon Sep 17 00:00:00 2001 From: wei_ds Date: Mon, 30 Mar 2026 19:06:51 +0800 Subject: [PATCH 2/2] 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. --- src/compiler/checker.ts | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3f3291a5f0917..8fb83983e8b06 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8661,6 +8661,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function symbolToTypeNode(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, overrideTypeArguments?: readonly TypeNode[]): TypeNode { + // Prevent infinite recursion when a function's return type references itself (e.g., ReturnType) + if (!context.visitedSymbols) { + context.visitedSymbols = new Set(); + } + const symbolKey = `${getSymbolId(symbol)}|${meaning}`; + if (context.visitedSymbols.has(symbolKey)) { + // Detected recursive symbol reference, return never type to avoid crash + return factory.createKeywordTypeNode(SyntaxKind.NeverKeyword); + } + context.visitedSymbols.add(symbolKey); + const chain = lookupSymbolChain(symbol, context, meaning, !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope)); // If we're using aliases outside the current scope, dont bother with the module const isTypeOf = meaning === SymbolFlags.Value; @@ -8723,33 +8734,39 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } const lit = factory.createLiteralTypeNode(factory.createStringLiteral(specifier)); context.approximateLength += specifier.length + 10; // specifier + import("") + let result: TypeNode; if (!nonRootParts || isEntityName(nonRootParts)) { if (nonRootParts) { const lastId = isIdentifier(nonRootParts) ? nonRootParts : nonRootParts.right; setIdentifierTypeArguments(lastId, /*typeArguments*/ undefined); } - return factory.createImportTypeNode(lit, attributes, nonRootParts as EntityName, typeParameterNodes as readonly TypeNode[], isTypeOf); + result = factory.createImportTypeNode(lit, attributes, nonRootParts as EntityName, typeParameterNodes as readonly TypeNode[], isTypeOf); } else { const splitNode = getTopmostIndexedAccessType(nonRootParts); const qualifier = (splitNode.objectType as TypeReferenceNode).typeName; - return factory.createIndexedAccessTypeNode(factory.createImportTypeNode(lit, attributes, qualifier, typeParameterNodes as readonly TypeNode[], isTypeOf), splitNode.indexType); + result = factory.createIndexedAccessTypeNode(factory.createImportTypeNode(lit, attributes, qualifier, typeParameterNodes as readonly TypeNode[], isTypeOf), splitNode.indexType); } + context.visitedSymbols.delete(symbolKey); + return result; } const entityName = createAccessFromSymbolChain(chain, chain.length - 1, 0); + let result: TypeNode; if (isIndexedAccessTypeNode(entityName)) { - return entityName; // Indexed accesses can never be `typeof` + result = entityName; // Indexed accesses can never be `typeof` } - if (isTypeOf) { - return factory.createTypeQueryNode(entityName); + else if (isTypeOf) { + result = factory.createTypeQueryNode(entityName); } else { const lastId = isIdentifier(entityName) ? entityName : entityName.right; const lastTypeArgs = getIdentifierTypeArguments(lastId); setIdentifierTypeArguments(lastId, /*typeArguments*/ undefined); - return factory.createTypeReferenceNode(entityName, lastTypeArgs as NodeArray); + result = factory.createTypeReferenceNode(entityName, lastTypeArgs as NodeArray); } + context.visitedSymbols.delete(symbolKey); + return result; function createAccessFromSymbolChain(chain: Symbol[], index: number, stopper: number): EntityName | IndexedAccessTypeNode { const typeParameterNodes = index === (chain.length - 1) ? overrideTypeArguments : lookupTypeParameterNodes(chain, index, context); @@ -54300,6 +54317,7 @@ interface NodeBuilderContext extends SyntacticTypeNodeBuilderContext { reportedDiagnostic: boolean; trackedSymbols: TrackedSymbol[] | undefined; visitedTypes: Set | undefined; + visitedSymbols: Set | undefined; symbolDepth: Map | undefined; inferTypeParameters: TypeParameter[] | undefined; approximateLength: number;