8000 Add tracking for labelled statements, and errors on labelled jumps · robertknight/TypeScript@742637b · GitHub
[go: up one dir, main page]

Skip to content

Commit 742637b

Browse files
committed
Add tracking for labelled statements, and errors on labelled jumps
1 parent b9971f2 commit 742637b

File tree

97 files changed

+318
-580
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+318
-580
lines changed

src/compiler/core.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ module ts {
55
[index: string]: T;
66
}
77

8+
export interface StringSet extends Map<any> { }
9+
810
export function forEach<T, U>(array: T[], callback: (element: T) => U): U {
911
var result: U;
1012
if (array) {

src/compiler/parser.ts

Lines changed: 122 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ module ts {
5555
return skipTrivia(getSourceFileOfNode(node).text, node.pos);
5656
}
5757

58+
export function getSourceTextOfNodeFromSourceText(sourceText: string, node: Node): string {
59+
return sourceText.substring(skipTrivia(sourceText, node.pos), node.end);
60+
}
61+
5862
export function getSourceTextOfNode(node: Node): string {
5963
var text = getSourceFileOfNode(node).text;
6064
return text.substring(skipTrivia(text, node.pos), node.end);
@@ -400,18 +404,98 @@ module ts {
400404

401405
var labelledStatementInfo = (() => {
402406
// TODO(jfreeman): Implement a data structure for tracking labels
403-
var functionBoundarySentinel = <LabelledStatement>{};
404-
var currentLabelSet: Identifier[];
405-
var labelSetStack: Identifier[][];
407+
var functionBoundarySentinel: StringSet = {};
408+
var currentLabelSet: StringSet;
409+
var labelSetStack: StringSet[];
406410
var isIterationStack: boolean[];
407411

412+
function addLabel(label: Identifier): void {
413+
if (!currentLabelSet) {
414+
currentLabelSet = {};
415+
}
416+
currentLabelSet[label.text] = true;
417+
}
418+
419+
function pushCurrentLabelSet(isIterationStatement: boolean): void {
420+
if (!labelSetStack && !isIterationStack) {
421+
labelSetStack = [];
422+
isIterationStack = [];
423+
}
424+
Debug.assert(currentLabelSet !== undefined);
425+
labelSetStack.push(currentLabelSet);
426+
isIterationStack.push(isIterationStatement);
427+
currentLabelSet = undefined;
428+
}
429+
430+
function pushFunctionBoundary(): void {
431+
if (!labelSetStack && !isIterationStack) {
432+
labelSetStack = [];
433+
isIterationStack = [];
434+
}
435+
Debug.assert(currentLabelSet === undefined);
436+
labelSetStack.push(functionBoundarySentinel);
437+
438+
// It does not matter what we push here, since we will never ask if a function boundary
439+
// is an iteration statement
440+
isIterationStack.push(false);
441+
}
442+
443+
function pop(): void {
444+
// Assert that we are in a "pushed" state
445+
Debug.assert(labelSetStack.length && isIterationStack.length && currentLabelSet === undefined);
446+
labelSetStack.pop();
447+
isIterationStack.pop();
448+
}
449+
450+
function nodeIsNestedInLabel(label: Identifier, requireIterationStatement: boolean, stopAtFunctionBoundary: boolean): ControlBlockContext {
451+
if (!requireIterationStatement && currentLabelSet && hasProperty(currentLabelSet, label.text)) {
452+
return ControlBlockContext.Nested;
453+
}
454+
455+
if (!labelSetStack) {
456+
return ControlBlockContext.NotNested;
457+
}
458+
459+
// We want to start searching for the label at the lowest point in the tree,
460+
// and climb up from there. So we start at the end of the labelSetStack array.
461+
var crossedFunctionBoundary = false;
462+
for (var i = labelSetStack.length - 1; i >= 0; i--) {
463+
var labelSet = labelSetStack[i];
464+
// Not allowed to cross function boundaries, so stop if we encounter one
465+
if (labelSet === functionBoundarySentinel) {
466+
if (stopAtFunctionBoundary) {
467+
break;
468+
}
469+
else {
470+
crossedFunctionBoundary = true;
471+
continue;
472+
}
473+
}
474+
475+
// If we require an iteration statement, only search in the current
476+
// statement if it is an iteration statement
477+
if (requireIterationStatement && isIterationStack[i] === false) {
478+
continue;
479+
}
480+
481+
if (hasProperty(labelSet, label.text)) {
482+
return crossedFunctionBoundary ? ControlBlockContext.CrossingFunctionBoundary : ControlBlockContext.Nested;
483+
}
484+
}
485+
486+
// This is a bit of a misnomer. If the caller passed true for stopAtFunctionBoundary,
487+
// there actually may be an enclosing label across a function boundary, but we will
488+
// just return NotNested
489+
return ControlBlockContext.NotNested;
490+
}
491+
408492
// TODO(jfreeman): Fill in these stubs
409493
return {
410-
addLabel: (label: Identifier) => { },
411-
pushCurrentLabelSet: (isIterationStatement: boolean) => { },
412-
pushFunctionBoundary: () => { },
413-
pop: () => { },
414-
labelExists: (label: Identifier, requireIterationStatement: boolean): boolean => false,
494+
addLabel: addLabel,
495+
pushCurrentLabelSet: pushCurrentLabelSet,
496+
pushFunctionBoundary: pushFunctionBoundary,
497+
pop: pop,
498+
nodeIsNestedInLabel: nodeIsNestedInLabel,
415499
};
416500
})();
417501

@@ -2083,40 +2167,39 @@ module ts {
20832167
parseExpected(kind === SyntaxKind.BreakStatement ? SyntaxKind.BreakKeyword : SyntaxKind.ContinueKeyword);
20842168
if (!isSemicolon()) node.label = parseIdentifier();
20852169
parseSemicolon();
2170+
finishNode(node);
20862171

20872172
// In an ambient context, we will already give an error for having a statement.
20882173
if (!inAmbientContext && errorCountBeforeStatement === file.syntacticErrors.length) {
20892174
if (node.label) {
2090-
checkBreakOrContinueStatementWithLabel(node.label, kind);
2175+
checkBreakOrContinueStatementWithLabel(node);
20912176
}
20922177
else {
2093-
checkAnonymousBreakOrContinueStatement(kind, keywordStart, keywordLength);
2178+
checkAnonymousBreakOrContinueStatement(node);
20942179
}
20952180
}
2096-
return finishNode(node);
2181+
return node;
20972182
}
20982183

2099-
function checkAnonymousBreakOrContinueStatement(kind: SyntaxKind, errorStart: number, errorLength: number): void {
2100-
if (kind === SyntaxKind.BreakStatement) {
2184+
function checkAnonymousBreakOrContinueStatement(node: BreakOrContinueStatement): void {
2185+
if (node.kind === SyntaxKind.BreakStatement) {
21012186
if (inIterationStatement === ControlBlockContext.Nested
21022187
|| inSwitchStatement === ControlBlockContext.Nested) {
21032188
return;
21042189
}
21052190
else if (inIterationStatement === ControlBlockContext.NotNested
21062191
&& inSwitchStatement === ControlBlockContext.NotNested) {
2107-
grammarErrorAtPos(errorStart, errorLength,
2108-
Diagnostics.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement);
2192+
grammarErrorOnNode(node, Diagnostics.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement);
21092193
return;
21102194
}
21112195
// Fall through
21122196
}
2113-
else if (kind === SyntaxKind.ContinueStatement) {
2197+
else if (node.kind === SyntaxKind.ContinueStatement) {
21142198
if (inIterationStatement === ControlBlockContext.Nested) {
21152199
return;
21162200
}
21172201
else if (inIterationStatement === ControlBlockContext.NotNested) {
2118-
grammarErrorAtPos(errorStart, errorLength,
2119-
Diagnostics.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement);
2202+
grammarErrorOnNode(node, Diagnostics.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement);
21202203
return;
21212204
}
21222205
// Fall through
@@ -2127,19 +2210,31 @@ module ts {
21272210

21282211
Debug.assert(inIterationStatement === ControlBlockContext.CrossingFunctionBoundary
21292212
|| inSwitchStatement === ControlBlockContext.CrossingFunctionBoundary);
2130-
grammarErrorAtPos(errorStart, errorLength, Diagnostics.Jump_target_cannot_cross_function_boundary);
2213+
grammarErrorOnNode(node, Diagnostics.Jump_target_cannot_cross_function_boundary);
21312214
}
21322215

2133-
function checkBreakOrContinueStatementWithLabel(label: Identifier, kind: SyntaxKind): void {
2134-
if (labelledStatementInfo.labelExists(label, /*requireIterationStatement*/ kind === SyntaxKind.ContinueStatement)) {
2216+
function checkBreakOrContinueStatementWithLabel(node: BreakOrContinueStatement): void {
2217+
// For error specificity, if the label is not found, we want to distinguish whether it is because
2218+
// it crossed a function boundary or it was simply not found. To do this, we pass false for
2219+
// stopAtFunctionBoundary.
2220+
var nodeIsNestedInLabel = labelledStatementInfo.nodeIsNestedInLabel(node.label,
2221+
/*requireIterationStatement*/ node.kind === SyntaxKind.ContinueStatement,
2222+
/*stopAtFunctionBoundary*/ false);
2223+
if (nodeIsNestedInLabel === ControlBlockContext.Nested) {
2224+
return;
2225+
}
2226+
2227+
if (nodeIsNestedInLabel === ControlBlockContext.CrossingFunctionBoundary) {
2228+
grammarErrorOnNode(node, Diagnostics.Jump_target_cannot_cross_function_boundary);
21352229
return;
21362230
}
21372231

2138-
if (kind === SyntaxKind.ContinueStatement) {
2139-
grammarErrorOnNode(label, Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement);
2232+
// It is NotNested
2233+
if (node.kind === SyntaxKind.ContinueStatement) {
2234+
grammarErrorOnNode(node, Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement);
21402235
}
2141-
else if (kind === SyntaxKind.BreakStatement) {
2142-
grammarErrorOnNode(label, Diagnostics.A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement);
2236+
else if (node.kind === SyntaxKind.BreakStatement) {
2237+
grammarErrorOnNode(node, Diagnostics.A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement);
21432238
}
2144 10000 2239
else {
21452240
Debug.fail("checkBreakOrContinueStatementWithLabel");
@@ -2304,8 +2399,8 @@ module ts {
23042399
node.label = parseIdentifier();
23052400
parseExpected(SyntaxKind.ColonToken);
23062401

2307-
if (labelledStatementInfo.labelExists(node.label, /*requireIterationStatement*/ false)) {
2308-
grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0);
2402+
if (labelledStatementInfo.nodeIsNestedInLabel(node.label, /*requireIterationStatement*/ false, /*stopAtFunctionBoundary*/ true)) {
2403+
grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getSourceTextOfNodeFromSourceText(sourceText, node.label));
23092404
}
23102405
labelledStatementInfo.addLabel(node.label);
23112406

tests/baselines/reference/breakInIterationOrSwitchStatement1.js

Lines changed: 0 additions & 9 deletions
This file was deleted.

tests/baselines/reference/breakInIterationOrSwitchStatement2.js

Lines changed: 0 additions & 10 deletions
This file was deleted.

tests/baselines/reference/breakInIterationOrSwitchStatement3.js

Lines changed: 0 additions & 9 deletions
This file was deleted.

tests/baselines/reference/breakInIterationOrSwitchStatement4.errors.txt

Lines changed: 0 additions & 6 deletions
This file was deleted.

tests/baselines/reference/breakInIterationOrSwitchStatement4.js

Lines changed: 0 additions & 9 deletions
This file was deleted.

tests/baselines/reference/breakNotInIterationOrSwitchStatement1.errors.txt

Lines changed: 0 additions & 4 deletions
This file was deleted.

tests/baselines/reference/breakNotInIterationOrSwitchStatement2.errors.txt

Lines changed: 0 additions & 8 deletions
This file was deleted.

tests/baselines/reference/breakTarget1.js

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)
0