diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index 8b5fa9d1fca5..88ad19cb9d40 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -86,6 +86,7 @@ CARBON_DIAGNOSTIC_KIND(UnaryOperatorHasWhitespace) CARBON_DIAGNOSTIC_KIND(UnaryOperatorRequiresWhitespace) CARBON_DIAGNOSTIC_KIND(UnexpectedTokenAfterListElement) CARBON_DIAGNOSTIC_KIND(UnrecognizedDecl) +CARBON_DIAGNOSTIC_KIND(UnexpectedTokenInCompoundMemberAccess) // Package-related diagnostics. CARBON_DIAGNOSTIC_KIND(FirstDecl) diff --git a/toolchain/parse/handle_paren_expr.cpp b/toolchain/parse/handle_paren_expr.cpp index 35f673db588b..f6e28ecd7c3e 100644 --- a/toolchain/parse/handle_paren_expr.cpp +++ b/toolchain/parse/handle_paren_expr.cpp @@ -6,6 +6,43 @@ namespace Carbon::Parse { +auto HandleOnlyParenExpr(Context& context) -> void { + auto state = context.PopState(); + + // Advance past the open paren. + auto open_paren = context.ConsumeChecked(Lex::TokenKind::OpenParen); + context.AddLeafNode(NodeKind::ExprOpenParen, open_paren); + + state.token = open_paren; + context.PushState(state, State::OnlyParenExprFinish); + context.PushState(State::Expr); +} + +static auto FinishParenExpr(Context& context, + const Context::StateStackEntry& state) -> void { + context.AddNode(NodeKind::ParenExpr, context.Consume(), state.subtree_start, + state.has_error); +} + +auto HandleOnlyParenExprFinish(Context& context) -> void { + auto state = context.PopState(); + + if (!context.PositionIs(Lex::TokenKind::CloseParen)) { + if (!state.has_error) { + CARBON_DIAGNOSTIC(UnexpectedTokenInCompoundMemberAccess, Error, + "Expected `)`."); + context.emitter().Emit(*context.position(), + UnexpectedTokenInCompoundMemberAccess); + state.has_error = true; + } + + // Recover from the invalid token. + context.SkipTo(context.tokens().GetMatchedClosingToken(state.token)); + } + + FinishParenExpr(context, state); +} + auto HandleParenExpr(Context& context) -> void { auto state = context.PopState(); @@ -59,8 +96,7 @@ auto HandleTupleLiteralElementFinish(Context& context) -> void { auto HandleParenExprFinish(Context& context) -> void { auto state = context.PopState(); - context.AddNode(NodeKind::ParenExpr, context.Consume(), state.subtree_start, - state.has_error); + FinishParenExpr(context, state); } auto HandleTupleLiteralFinish(Context& context) -> void { diff --git a/toolchain/parse/handle_period.cpp b/toolchain/parse/handle_period.cpp index c515fdd2e3a7..570425681dd4 100644 --- a/toolchain/parse/handle_period.cpp +++ b/toolchain/parse/handle_period.cpp @@ -29,7 +29,7 @@ static auto HandlePeriodOrArrow(Context& context, NodeKind node_kind, context.PositionIs(Lex::TokenKind::OpenParen)) { state.state = paren_state; context.PushState(state); - context.PushState(State::ParenExpr); + context.PushState(State::OnlyParenExpr); return; } else { CARBON_DIAGNOSTIC(ExpectedIdentifierAfterDotOrArrow, Error, diff --git a/toolchain/parse/state.def b/toolchain/parse/state.def index aecb84c565d2..3c7db6ccd27f 100644 --- a/toolchain/parse/state.def +++ b/toolchain/parse/state.def @@ -399,12 +399,12 @@ CARBON_PARSE_STATE(DeclScopeLoop) // // expr . ( ... ) // ^ -// 1. ParenExpr +// 1. OnlyParenExpr // 2. CompoundMemberAccess // // expr -> ( ... ) // ^~ -// 1. ParenExpr +// 1. OnlyParenExpr // 2. CompoundPointerMemberAccess CARBON_PARSE_STATE_VARIANTS3(Period, Decl, Expr, Struct) @@ -815,6 +815,20 @@ CARBON_PARSE_STATE_VARIANTS3(ParenCondition, If, While, Match) // (state done) CARBON_PARSE_STATE_VARIANTS3(ParenConditionFinish, If, While, Match) +// Handles compound member access `(` after `->` or `.` +// +// ( ... ) +// ^ +// 1. Expr +// 2. OnlyParenExprFinish +CARBON_PARSE_STATE(OnlyParenExpr) + +// Handles the `)` after `->(`... or `.(`... +// +// ( ... ) +// ^ +CARBON_PARSE_STATE(OnlyParenExprFinish) + // Handles the `(` of a parenthesized single expression // // ( ) diff --git a/toolchain/parse/testdata/pointer/fail_tuple_instead_of_compound_member_access.carbon b/toolchain/parse/testdata/pointer/fail_tuple_instead_of_compound_member_access.carbon new file mode 100644 index 000000000000..b7eb61897f82 --- /dev/null +++ b/toolchain/parse/testdata/pointer/fail_tuple_instead_of_compound_member_access.carbon @@ -0,0 +1,114 @@ +// 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_tuple_instead_of_compound_member_access.carbon:[[@LINE+4]]:6: ERROR: Expected expression. + // CHECK:STDERR: x.(); + // CHECK:STDERR: ^ + // CHECK:STDERR: + x.(); + + // CHECK:STDERR: fail_tuple_instead_of_compound_member_access.carbon:[[@LINE+4]]:7: ERROR: Expected `)`. + // CHECK:STDERR: y.(1,); + // CHECK:STDERR: ^ + // CHECK:STDERR: + y.(1,); + + // CHECK:STDERR: fail_tuple_instead_of_compound_member_access.carbon:[[@LINE+4]]:7: ERROR: Expected `)`. + // CHECK:STDERR: z.(a, b); + // CHECK:STDERR: ^ + // CHECK:STDERR: + z.(a, b); + + // CHECK:STDERR: fail_tuple_instead_of_compound_member_access.carbon:[[@LINE+4]]:8: ERROR: Expected `)`. + // CHECK:STDERR: w.(c d e ()); + // CHECK:STDERR: ^ + // CHECK:STDERR: + w.(c d e ()); + + // CHECK:STDERR: fail_tuple_instead_of_compound_member_access.carbon:[[@LINE+4]]:7: ERROR: Expected expression. + // CHECK:STDERR: v->(); + // CHECK:STDERR: ^ + // CHECK:STDERR: + v->(); + + // CHECK:STDERR: fail_tuple_instead_of_compound_member_access.carbon:[[@LINE+4]]:8: ERROR: Expected `)`. + // CHECK:STDERR: u->(f,); + // CHECK:STDERR: ^ + // CHECK:STDERR: + u->(f,); + + // CHECK:STDERR: fail_tuple_instead_of_compound_member_access.carbon:[[@LINE+4]]:8: ERROR: Expected `)`. + // CHECK:STDERR: t->(2, 3); + // CHECK:STDERR: ^ + // CHECK:STDERR: + t->(2, 3); + + // CHECK:STDERR: fail_tuple_instead_of_compound_member_access.carbon:[[@LINE+3]]:9: ERROR: Expected `)`. + // CHECK:STDERR: s->(g h i ()); + // CHECK:STDERR: ^ + s->(g h i ()); +} + +// CHECK:STDOUT: - filename: fail_tuple_instead_of_compound_member_access.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'F'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'x'}, +// CHECK:STDOUT: {kind: 'ExprOpenParen', text: '('}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: ')', has_error: yes}, +// CHECK:STDOUT: {kind: 'ParenExpr', text: ')', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'y'}, +// CHECK:STDOUT: {kind: 'ExprOpenParen', text: '('}, +// CHECK:STDOUT: {kind: 'IntLiteral', text: '1'}, +// CHECK:STDOUT: {kind: 'ParenExpr', text: ')', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'z'}, +// CHECK:STDOUT: {kind: 'ExprOpenParen', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'ParenExpr', text: ')', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'w'}, +// CHECK:STDOUT: {kind: 'ExprOpenParen', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'c'}, +// CHECK:STDOUT: {kind: 'ParenExpr', text: ')', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'v'}, +// CHECK:STDOUT: {kind: 'ExprOpenParen', text: '('}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: ')', has_error: yes}, +// CHECK:STDOUT: {kind: 'ParenExpr', text: ')', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'PointerMemberAccessExpr', text: '->', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'u'}, +// CHECK:STDOUT: {kind: 'ExprOpenParen', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'f'}, +// CHECK:STDOUT: {kind: 'ParenExpr', text: ')', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'PointerMemberAccessExpr', text: '->', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 't'}, +// CHECK:STDOUT: {kind: 'ExprOpenParen', text: '('}, +// CHECK:STDOUT: {kind: 'IntLiteral', text: '2'}, +// CHECK:STDOUT: {kind: 'ParenExpr', text: ')', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'PointerMemberAccessExpr', text: '->', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 's'}, +// CHECK:STDOUT: {kind: 'ExprOpenParen', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'g'}, +// CHECK:STDOUT: {kind: 'ParenExpr', text: ')', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'PointerMemberAccessExpr', text: '->', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 54}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ]