From b9971f28767c3393bb4a0ce124a21b73e87bf760 Mon Sep 17 00:00:00 2001 From: Jason Freeman Date: Wed, 23 Jul 2014 16:48:18 -0700 Subject: [PATCH] Add consumption points and error reporting for labelled statement errors --- .../diagnosticInformationMap.generated.ts | 4 +- src/compiler/diagnosticMessages.json | 16 +++-- src/compiler/parser.ts | 58 +++++++++++++++++-- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index 54dd92ca36a..2358447cc02 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -91,7 +91,6 @@ module ts { Invalid_left_hand_side_in_for_in_statement: { code: 1103, category: DiagnosticCategory.Error, key: "Invalid left-hand side in 'for...in' statement." }, A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement: { code: 1104, category: DiagnosticCategory.Error, key: "A 'continue' statement can only be used within an enclosing iteration statement." }, A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement: { code: 1105, category: DiagnosticCategory.Error, key: "A 'break' statement can only be used within an enclosing iteration or switch statement." }, - Jump_target_not_found: { code: 1106, category: DiagnosticCategory.Error, key: "Jump target not found." }, Jump_target_cannot_cross_function_boundary: { code: 1107, category: DiagnosticCategory.Error, key: "Jump target cannot cross function boundary." }, A_return_statement_can_only_be_used_within_a_function_body: { code: 1108, category: DiagnosticCategory.Error, key: "A 'return' statement can only be used within a function body." }, Expression_expected: { code: -9999999, category: DiagnosticCategory.Error, key: "Expression expected." }, @@ -99,6 +98,9 @@ module ts { A_constructor_implementation_cannot_be_declared_in_an_ambient_context: { code: 1111, category: DiagnosticCategory.Error, key: "A constructor implementation cannot be declared in an ambient context." }, A_class_member_cannot_be_declared_optional: { code: 1112, category: DiagnosticCategory.Error, key: "A class member cannot be declared optional." }, A_default_clause_cannot_appear_more_than_once_in_a_switch_statement: { code: 1113, category: DiagnosticCategory.Error, key: "A 'default' clause cannot appear more than once in a 'switch' statement." }, + Duplicate_label_0: { code: 1114, category: DiagnosticCategory.Error, key: "Duplicate label '{0}'" }, + A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement: { code: 1115, category: DiagnosticCategory.Error, key: "A 'continue' statement can only jump to a label of an enclosing iteration statement." }, + A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement: { code: 1116, category: DiagnosticCategory.Error, key: "A 'break' statement can only jump to a label of an enclosing statement." }, Duplicate_identifier_0: { code: 2000, category: DiagnosticCategory.Error, key: "Duplicate identifier '{0}'." }, new_T_cannot_be_used_to_create_an_array_Use_new_Array_T_instead: { code: 2068, category: DiagnosticCategory.Error, key: "'new T[]' cannot be used to create an array. Use 'new Array()' instead." }, Multiple_constructor_implementations_are_not_allowed: { code: 2070, category: DiagnosticCategory.Error, key: "Multiple constructor implementations are not allowed." }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 05f5280ad8f..04e28234f83 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -356,10 +356,6 @@ "category": "Error", "code": 1105 }, - "Jump target not found.": { - "category": "Error", - "code": 1106 - }, "Jump target cannot cross function boundary.": { "category": "Error", "code": 1107 @@ -388,6 +384,18 @@ "category": "Error", "code": 1113 }, + "Duplicate label '{0}'": { + "category": "Error", + "code": 1114 + }, + "A 'continue' statement can only jump to a label of an enclosing iteration statement.": { + "category": "Error", + "code": 1115 + }, + "A 'break' statement can only jump to a label of an enclosing statement.": { + "category": "Error", + "code": 1116 + }, "Duplicate identifier '{0}'.": { "category": "Error", diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 76b556decb4..ada08165029 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -401,13 +401,17 @@ module ts { var labelledStatementInfo = (() => { // TODO(jfreeman): Implement a data structure for tracking labels var functionBoundarySentinel = {}; - var labelledStatementStack: LabelledStatement[]; + var currentLabelSet: Identifier[]; + var labelSetStack: Identifier[][]; + var isIterationStack: boolean[]; // TODO(jfreeman): Fill in these stubs return { - pushLabelledStatement: (statement: LabelledStatement) => { }, + addLabel: (label: Identifier) => { }, + pushCurrentLabelSet: (isIterationStatement: boolean) => { }, pushFunctionBoundary: () => { }, - pop: () => { } + pop: () => { }, + labelExists: (label: Identifier, requireIterationStatement: boolean): boolean => false, }; })(); @@ -2082,7 +2086,10 @@ module ts { // In an ambient context, we will already give an error for having a statement. if (!inAmbientContext && errorCountBeforeStatement === file.syntacticErrors.length) { - if (!node.label) { + if (node.label) { + checkBreakOrContinueStatementWithLabel(node.label, kind); + } + else { checkAnonymousBreakOrContinueStatement(kind, keywordStart, keywordLength); } } @@ -2123,6 +2130,22 @@ module ts { grammarErrorAtPos(errorStart, errorLength, Diagnostics.Jump_target_cannot_cross_function_boundary); } + function checkBreakOrContinueStatementWithLabel(label: Identifier, kind: SyntaxKind): void { + if (labelledStatementInfo.labelExists(label, /*requireIterationStatement*/ kind === SyntaxKind.ContinueStatement)) { + return; + } + + if (kind === SyntaxKind.ContinueStatement) { + grammarErrorOnNode(label, Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement); + } + else if (kind === SyntaxKind.BreakStatement) { + grammarErrorOnNode(label, Diagnostics.A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement); + } + else { + Debug.fail("checkBreakOrContinueStatementWithLabel"); + } + } + function parseReturnStatement(): ReturnStatement { var node = createNode(SyntaxKind.ReturnStatement); var errorCountBeforeReturnStatement = file.syntacticErrors.length; @@ -2261,11 +2284,34 @@ module ts { return finishNode(node); } + function isIterationStatementStart(): boolean { + return token === SyntaxKind.WhileKeyword || token === SyntaxKind.DoKeyword || token === SyntaxKind.ForKeyword; + } + + function parseStatementWithLabelSet(): Statement { + labelledStatementInfo.pushCurrentLabelSet(isIterationStatementStart()); + var statement = parseStatement(); + labelledStatementInfo.pop(); + return statement; + } + + function isLabel(): boolean { + return isIdentifier() && lookAhead(() => nextToken() === SyntaxKind.ColonToken); + } + function parseLabelledStatement(): LabelledStatement { var node = createNode(SyntaxKind.LabelledStatement); node.label = parseIdentifier(); parseExpected(SyntaxKind.ColonToken); - node.statement = parseStatement(); + + if (labelledStatementInfo.labelExists(node.label, /*requireIterationStatement*/ false)) { + grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0); + } + labelledStatementInfo.addLabel(node.label); + + // We only want to call parseStatementWithLabelSet when the label set is complete + // Therefore, keep parsing labels until we know we're done. + node.statement = isLabel() ? parseLabelledStatement() : parseStatementWithLabelSet(); return finishNode(node); } @@ -2355,7 +2401,7 @@ module ts { case SyntaxKind.DebuggerKeyword: return parseDebuggerStatement(); default: - if (isIdentifier() && lookAhead(() => nextToken() === SyntaxKind.ColonToken)) { + if (isLabel()) { return parseLabelledStatement(); } return parseExpressionStatement();