diff --git a/toolchain/parser/parser_context.cpp b/toolchain/parser/parser_context.cpp index 878df10bdae3..54bc85ded1c8 100644 --- a/toolchain/parser/parser_context.cpp +++ b/toolchain/parser/parser_context.cpp @@ -77,15 +77,18 @@ auto ParserContext::AddNode(ParseNodeKind kind, TokenizedBuffer::Token token, } auto ParserContext::ConsumeAndAddOpenParen(TokenizedBuffer::Token default_token, - ParseNodeKind start_kind) -> void { + ParseNodeKind start_kind) + -> std::optional { if (auto open_paren = ConsumeIf(TokenKind::OpenParen)) { AddLeafNode(start_kind, *open_paren, /*has_error=*/false); + return open_paren; } else { CARBON_DIAGNOSTIC(ExpectedParenAfter, Error, "Expected `(` after `{0}`.", TokenKind); emitter_->Emit(*position_, ExpectedParenAfter, tokens().GetKind(default_token)); AddLeafNode(start_kind, default_token, /*has_error=*/true); + return std::nullopt; } } diff --git a/toolchain/parser/parser_context.h b/toolchain/parser/parser_context.h index a75518707d95..7426c4d58cfd 100644 --- a/toolchain/parser/parser_context.h +++ b/toolchain/parser/parser_context.h @@ -108,9 +108,10 @@ class ParserContext { // Parses an open paren token, possibly diagnosing if necessary. Creates a // leaf parse node of the specified start kind. The default_token is used when - // there's no open paren. + // there's no open paren. Returns the open paren token if it was found. auto ConsumeAndAddOpenParen(TokenizedBuffer::Token default_token, - ParseNodeKind start_kind) -> void; + ParseNodeKind start_kind) + -> std::optional; // Parses a closing symbol corresponding to the opening symbol // `expected_open`, possibly skipping forward and diagnosing if necessary. diff --git a/toolchain/parser/parser_handle_paren_condition.cpp b/toolchain/parser/parser_handle_paren_condition.cpp index 28942ff1344b..c12d0ec96482 100644 --- a/toolchain/parser/parser_handle_paren_condition.cpp +++ b/toolchain/parser/parser_handle_paren_condition.cpp @@ -12,11 +12,23 @@ static auto ParserHandleParenCondition(ParserContext& context, ParserState finish_state) -> void { auto state = context.PopState(); - context.ConsumeAndAddOpenParen(state.token, start_kind); - + std::optional open_paren = + context.ConsumeAndAddOpenParen(state.token, start_kind); + if (open_paren) { + state.token = *open_paren; + } state.state = finish_state; context.PushState(state); - context.PushState(ParserState::Expression); + + if (!open_paren && context.PositionIs(TokenKind::OpenCurlyBrace)) { + // For an open curly, assume the condition was completely omitted. + // Expression parsing would treat the { as a struct, but instead assume it's + // a code block and just emit an invalid parse. + context.AddLeafNode(ParseNodeKind::InvalidParse, *context.position(), + /*has_error=*/true); + } else { + context.PushState(ParserState::Expression); + } } auto ParserHandleParenConditionAsIf(ParserContext& context) -> void { @@ -32,17 +44,15 @@ auto ParserHandleParenConditionAsWhile(ParserContext& context) -> void { auto ParserHandleParenConditionFinishAsIf(ParserContext& context) -> void { auto state = context.PopState(); - context.ConsumeAndAddCloseSymbol( - *(TokenizedBuffer::TokenIterator(state.token) + 1), state, - ParseNodeKind::IfCondition); + context.ConsumeAndAddCloseSymbol(state.token, state, + ParseNodeKind::IfCondition); } auto ParserHandleParenConditionFinishAsWhile(ParserContext& context) -> void { auto state = context.PopState(); - context.ConsumeAndAddCloseSymbol( - *(TokenizedBuffer::TokenIterator(state.token) + 1), state, - ParseNodeKind::WhileCondition); + context.ConsumeAndAddCloseSymbol(state.token, state, + ParseNodeKind::WhileCondition); } } // namespace Carbon diff --git a/toolchain/parser/parser_handle_statement.cpp b/toolchain/parser/parser_handle_statement.cpp index 37f7a19107b4..6fe7249958ae 100644 --- a/toolchain/parser/parser_handle_statement.cpp +++ b/toolchain/parser/parser_handle_statement.cpp @@ -86,8 +86,12 @@ auto ParserHandleStatementContinueFinish(ParserContext& context) -> void { auto ParserHandleStatementForHeader(ParserContext& context) -> void { auto state = context.PopState(); - context.ConsumeAndAddOpenParen(state.token, ParseNodeKind::ForHeaderStart); - + std::optional open_paren = + context.ConsumeAndAddOpenParen(state.token, + ParseNodeKind::ForHeaderStart); + if (open_paren) { + state.token = *open_paren; + } state.state = ParserState::StatementForHeaderIn; if (context.PositionIs(TokenKind::Var)) { @@ -118,9 +122,8 @@ auto ParserHandleStatementForHeaderIn(ParserContext& context) -> void { auto ParserHandleStatementForHeaderFinish(ParserContext& context) -> void { auto state = context.PopState(); - context.ConsumeAndAddCloseSymbol( - *(TokenizedBuffer::TokenIterator(state.token) + 1), state, - ParseNodeKind::ForHeader); + context.ConsumeAndAddCloseSymbol(state.token, state, + ParseNodeKind::ForHeader); context.PushState(ParserState::CodeBlock); } diff --git a/toolchain/parser/parser_state.def b/toolchain/parser/parser_state.def index 942b9095c7a7..fe937e31b5d5 100644 --- a/toolchain/parser/parser_state.def +++ b/toolchain/parser/parser_state.def @@ -468,7 +468,9 @@ CARBON_PARSER_STATE_VARIANTS2(ParameterListFinish, Deduced, Regular) // Handles the processing of a `(condition)` up through the expression. // -// Always: +// If `OpenCurlyBrace`: +// 1. ParenConditionAs(If|While)Finish +// Else: // 1. Expression // 2. ParenConditionAs(If|While)Finish CARBON_PARSER_STATE_VARIANTS2(ParenCondition, If, While) diff --git a/toolchain/parser/testdata/for/fail_missing_cond.carbon b/toolchain/parser/testdata/for/fail_missing_cond.carbon new file mode 100644 index 000000000000..3185668bf2d6 --- /dev/null +++ b/toolchain/parser/testdata/for/fail_missing_cond.carbon @@ -0,0 +1,40 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +fn F() { + // CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+6]]:7: Expected `(` after `for`. + // CHECK:STDERR: for { + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+3]]:7: Expected `var` declaration. + // CHECK:STDERR: for { + // CHECK:STDERR: ^ + for { + } +// CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+6]]:1: Expected braced code block. +// CHECK:STDERR: } +// CHECK:STDERR: ^ +// CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+3]]:1: Expected expression. +// CHECK:STDERR: } +// CHECK:STDERR: ^ +} + +// CHECK:STDOUT: [ +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'Name', text: 'F'}, +// CHECK:STDOUT: {kind: 'ParameterListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ParameterList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ForHeaderStart', text: 'for', has_error: yes}, +// CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, +// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'ForHeader', text: 'for', has_error: yes, subtree_size: 4}, +// CHECK:STDOUT: {kind: 'CodeBlockStart', text: '}', has_error: yes}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '}', has_error: yes}, +// CHECK:STDOUT: {kind: 'CodeBlock', text: '}', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ForStatement', text: 'for', subtree_size: 8}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 14}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parser/testdata/for/fail_square_brackets.carbon b/toolchain/parser/testdata/for/fail_square_brackets.carbon new file mode 100644 index 000000000000..4b464e54abf2 --- /dev/null +++ b/toolchain/parser/testdata/for/fail_square_brackets.carbon @@ -0,0 +1,41 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +fn F() { + // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+12]]:7: Expected `(` after `for`. + // CHECK:STDERR: for [] { + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+9]]:7: Expected `var` declaration. + // CHECK:STDERR: for [] { + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+6]]:8: Expected expression. + // CHECK:STDERR: for [] { + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+3]]:8: Expected `;` in array type. + // CHECK:STDERR: for [] { + // CHECK:STDERR: ^ + for [] { + } +} + +// CHECK:STDOUT: [ +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'Name', text: 'F'}, +// CHECK:STDOUT: {kind: 'ParameterListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ParameterList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ForHeaderStart', text: 'for', has_error: yes}, +// CHECK:STDOUT: {kind: 'ArrayExpressionStart', text: '['}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: ']', has_error: yes}, +// CHECK:STDOUT: {kind: 'ArrayExpressionSemi', text: ']', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ArrayExpression', text: ']', has_error: yes, subtree_size: 4}, +// CHECK:STDOUT: {kind: 'ForHeader', text: 'for', has_error: yes, subtree_size: 6}, +// CHECK:STDOUT: {kind: 'CodeBlockStart', text: '{'}, +// CHECK:STDOUT: {kind: 'CodeBlock', text: '}', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'ForStatement', text: 'for', subtree_size: 9}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 15}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parser/testdata/if/fail_missing_cond.carbon b/toolchain/parser/testdata/if/fail_missing_cond.carbon new file mode 100644 index 000000000000..21723067680e --- /dev/null +++ b/toolchain/parser/testdata/if/fail_missing_cond.carbon @@ -0,0 +1,29 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +fn F() { + // CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+3]]:6: Expected `(` after `if`. + // CHECK:STDERR: if { + // CHECK:STDERR: ^ + if { + } +} + +// CHECK:STDOUT: [ +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'Name', text: 'F'}, +// CHECK:STDOUT: {kind: 'ParameterListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ParameterList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'IfConditionStart', text: 'if', has_error: yes}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '{', has_error: yes}, +// CHECK:STDOUT: {kind: 'IfCondition', text: 'if', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'CodeBlockStart', text: '{'}, +// CHECK:STDOUT: {kind: 'CodeBlock', text: '}', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IfStatement', text: 'if', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 12}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parser/testdata/if/fail_square_brackets.carbon b/toolchain/parser/testdata/if/fail_square_brackets.carbon new file mode 100644 index 000000000000..bda5aa55a8c8 --- /dev/null +++ b/toolchain/parser/testdata/if/fail_square_brackets.carbon @@ -0,0 +1,37 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +fn F() { + // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+9]]:6: Expected `(` after `if`. + // CHECK:STDERR: if [] {} + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+6]]:7: Expected expression. + // CHECK:STDERR: if [] {} + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+3]]:7: Expected `;` in array type. + // CHECK:STDERR: if [] {} + // CHECK:STDERR: ^ + if [] {} +} + +// CHECK:STDOUT: [ +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'Name', text: 'F'}, +// CHECK:STDOUT: {kind: 'ParameterListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ParameterList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'IfConditionStart', text: 'if', has_error: yes}, +// CHECK:STDOUT: {kind: 'ArrayExpressionStart', text: '['}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: ']', has_error: yes}, +// CHECK:STDOUT: {kind: 'ArrayExpressionSemi', text: ']', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ArrayExpression', text: ']', has_error: yes, subtree_size: 4}, +// CHECK:STDOUT: {kind: 'IfCondition', text: 'if', has_error: yes, subtree_size: 6}, +// CHECK:STDOUT: {kind: 'CodeBlockStart', text: '{'}, +// CHECK:STDOUT: {kind: 'CodeBlock', text: '}', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IfStatement', text: 'if', subtree_size: 9}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 15}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ]