From 676618b17d74cf323250c722a57b927f3064633b Mon Sep 17 00:00:00 2001 From: creazyfrog Date: Sun, 26 Apr 2026 01:23:53 -0700 Subject: [PATCH] fix(checker): emit precise error when labeled continue/break targets a non-enclosing label When a labeled continue or break statement references a label that exists in the same function but is not an ancestor (e.g. a forward reference or a sibling statement), the checker walked up to the function boundary and emitted TS1107 "Jump target cannot cross function boundary". This message is misleading: the label is inside the same function, so no function boundary is actually being crossed. The real problem is that the label is not an *enclosing* statement. Fix: before emitting TS1107 at a function boundary, check whether the named label exists anywhere within that function scope via a new helper `labelExistsInScope`. If it does, emit the more accurate diagnostic: - TS1115: "A 'continue' statement can only jump to a label of an enclosing iteration statement." - TS1116: "A 'break' statement can only jump to a label of an enclosing statement." TS1107 is still emitted for the genuine cross-function case (label is in an outer function). Fixes #30408 Signed-off-by: creazyfrog --- src/compiler/checker.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0567712f11da3..fd7eb67457e40 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -53026,10 +53026,32 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } + function labelExistsInScope(scope: Node, labelName: __String): boolean { + return !!forEachChild(scope, function walk(child): boolean | undefined { + if (child.kind === SyntaxKind.LabeledStatement && (child as LabeledStatement).label.escapedText === labelName) { + return true; + } + // Do not descend into nested function scopes — a label there is not accessible. + if (!isFunctionLikeOrClassStaticBlockDeclaration(child)) { + return forEachChild(child, walk); + } + }); + } + function checkGrammarBreakOrContinueStatement(node: BreakOrContinueStatement): boolean { let current: Node = node; while (current) { if (isFunctionLikeOrClassStaticBlockDeclaration(current)) { + // If a label was specified and it exists within this function scope (but not + // as an enclosing ancestor), emit a more precise diagnostic rather than the + // generic "Jump target cannot cross function boundary", which implies the label + // lives in an outer function when in reality it is a non-enclosing forward reference. + if (node.label && labelExistsInScope(current, node.label.escapedText)) { + const message = node.kind === SyntaxKind.ContinueStatement + ? Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement + : Diagnostics.A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement; + return grammarErrorOnNode(node, message); + } return grammarErrorOnNode(node, Diagnostics.Jump_target_cannot_cross_function_boundary); }