Skip to content

Commit 98d5745

Browse files
fix: skip block-scoped variable check for label identifiers
Labels and variables occupy separate namespaces in JavaScript/TypeScript. When a label name coincides with a later let/const variable declaration, the compiler incorrectly reports "Block-scoped variable used before its declaration" on the label identifier or break/continue target. This adds a guard in checkResolvedBlockScopedVariable to skip the check when the error location is the label of a LabeledStatement, BreakStatement, or ContinueStatement, since these reference labels, not variables. Fixes #30408
1 parent 7b8cb3b commit 98d5745

2 files changed

Lines changed: 37 additions & 0 deletions

File tree

src/compiler/checker.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3603,6 +3603,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
36033603
// constructor functions aren't block scoped
36043604
return;
36053605
}
3606+
// Labels are in a separate namespace from variables; a label identifier
3607+
// that happens to share a name with a block-scoped variable should not
3608+
// trigger a "used before its declaration" error.
3609+
if (errorLocation.parent) {
3610+
const parent = errorLocation.parent;
3611+
if (
3612+
parent.kind === SyntaxKind.LabeledStatement && (parent as LabeledStatement).label === errorLocation ||
3613+
(parent.kind === SyntaxKind.BreakStatement || parent.kind === SyntaxKind.ContinueStatement) && (parent as BreakOrContinueStatement).label === errorLocation
3614+
) {
3615+
return;
3616+
}
3617+
}
36063618
// Block-scoped variables cannot be used before their definition
36073619
const declaration = result.declarations?.find(
36083620
d => isBlockOrCatchScoped(d) || isClassLike(d) || (d.kind === SyntaxKind.EnumDeclaration),
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// @target: es2015
2+
3+
// Labels should not conflict with block-scoped variables declared later.
4+
// A label named `foo` is in a different namespace from a variable named `foo`.
5+
6+
// Label defined before let variable of the same name
7+
foo: while (true) {
8+
break foo;
9+
}
10+
let foo = 1;
11+
12+
// Label defined before const variable of the same name
13+
bar: for (;;) {
14+
continue bar;
15+
}
16+
const bar = 2;
17+
18+
// Nested labeled statement with same-name variable
19+
outer: for (let i = 0; i < 1; i++) {
20+
inner: for (let j = 0; j < 1; j++) {
21+
break outer;
22+
}
23+
}
24+
let outer = "hello";
25+
let inner = "world";

0 commit comments

Comments
 (0)