diff --git a/toolchain/check/handle_class.cpp b/toolchain/check/handle_class.cpp index 8e2d1591c316..02437612fd4b 100644 --- a/toolchain/check/handle_class.cpp +++ b/toolchain/check/handle_class.cpp @@ -443,6 +443,15 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool { return true; } +auto HandleParseNode(Context& context, Parse::FriendIntroducerId node_id) + -> bool { + return context.TODO(node_id, "friend decl not supported in check"); +} + +auto HandleParseNode(Context& context, Parse::FriendDeclId node_id) -> bool { + return context.TODO(node_id, "friend decl not supported in check"); +} + auto HandleParseNode(Context& context, Parse::ClassDefinitionId node_id) -> bool { auto class_id = diff --git a/toolchain/check/node_stack.h b/toolchain/check/node_stack.h index b1c630231108..da469ae30c79 100644 --- a/toolchain/check/node_stack.h +++ b/toolchain/check/node_stack.h @@ -507,6 +507,7 @@ class NodeStack { case Parse::NodeKind::Forall: case Parse::NodeKind::FormLiteralKeyword: case Parse::NodeKind::FormLiteralOpenParen: + case Parse::NodeKind::FriendIntroducer: case Parse::NodeKind::IdentifierNameQualifierWithParams: case Parse::NodeKind::IdentifierNameQualifierWithoutParams: case Parse::NodeKind::IdentifierPackageName: diff --git a/toolchain/diagnostics/kind.def b/toolchain/diagnostics/kind.def index 0818cf79aec8..de80e73f9854 100644 --- a/toolchain/diagnostics/kind.def +++ b/toolchain/diagnostics/kind.def @@ -200,6 +200,7 @@ CARBON_DIAGNOSTIC_KIND(ExpectedAssociatedConstantIdentifier) CARBON_DIAGNOSTIC_KIND(ExpectedAssociatedConstantColon) CARBON_DIAGNOSTIC_KIND(PhaseKeywordInAssociatedConstant) CARBON_DIAGNOSTIC_KIND(RequireExpectedImpls) +CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierForFriend) // Alias diagnostics. CARBON_DIAGNOSTIC_KIND(ExpectedAliasInitializer) diff --git a/toolchain/parse/handle_decl_scope_loop.cpp b/toolchain/parse/handle_decl_scope_loop.cpp index 233c5bbcd10c..bde08bbd8426 100644 --- a/toolchain/parse/handle_decl_scope_loop.cpp +++ b/toolchain/parse/handle_decl_scope_loop.cpp @@ -123,6 +123,8 @@ static constexpr auto DeclIntroducers = [] { set(Lex::TokenKind::Export, NodeKind::ExportIntroducer, StateKind::ExportName); // TODO: Treat `extend` as a declaration introducer. + set(Lex::TokenKind::Friend, NodeKind::FriendIntroducer, + StateKind::FriendDecl); set(Lex::TokenKind::Fn, NodeKind::FunctionIntroducer, StateKind::FunctionIntroducer); set(Lex::TokenKind::Impl, NodeKind::ImplIntroducer, diff --git a/toolchain/parse/handle_friend.cpp b/toolchain/parse/handle_friend.cpp new file mode 100644 index 000000000000..23df932082b2 --- /dev/null +++ b/toolchain/parse/handle_friend.cpp @@ -0,0 +1,34 @@ +// 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 + +#include "toolchain/parse/context.h" +#include "toolchain/parse/handle.h" + +namespace Carbon::Parse { + +// Handles processing of a complete `friend X` declaration. +auto HandleFriendDecl(Context& context) -> void { + auto state = context.PopState(); + if (state.has_error) { + context.RecoverFromDeclError(state, NodeKind::FriendDecl, + /*skip_past_likely_end=*/true); + return; + } + + if (!context.ConsumeAndAddLeafNodeIf(Lex::TokenKind::Identifier, + NodeKind::IdentifierNameExpr)) { + state.has_error = true; + CARBON_DIAGNOSTIC(ExpectedIdentifierForFriend, Error, + "expected identifier for friend declaration"); + context.emitter().Emit(*context.position(), ExpectedIdentifierForFriend); + context.RecoverFromDeclError(state, NodeKind::FriendDecl, + /*skip_past_likely_end=*/true); + return; + } + context.AddNodeExpectingDeclSemi(state, NodeKind::FriendDecl, + Lex::TokenKind::Friend, + /*is_def_allowed=*/false); +} + +} // namespace Carbon::Parse diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index 6b982d6042f8..bc86ef7448da 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -439,6 +439,9 @@ CARBON_PARSE_NODE_KIND_DECLARATION(BaseIntroducer) CARBON_PARSE_NODE_KIND_DECLARATION(BaseColon) CARBON_PARSE_NODE_KIND_DECLARATION(BaseDecl) +CARBON_PARSE_NODE_KIND_DECLARATION(FriendIntroducer) +CARBON_PARSE_NODE_KIND_DECLARATION(FriendDecl) + CARBON_PARSE_NODE_KIND_DECLARATION(InterfaceIntroducer) CARBON_PARSE_NODE_KIND_DECLARATION(InterfaceDefinitionStart) CARBON_PARSE_NODE_KIND_DECLARATION(InterfaceDefinition) diff --git a/toolchain/parse/state.def b/toolchain/parse/state.def index 286cf58c9157..2bc83e61156d 100644 --- a/toolchain/parse/state.def +++ b/toolchain/parse/state.def @@ -378,6 +378,10 @@ CARBON_PARSE_STATE(DeclNameAndParamsAfterParams) // ^~ // 1. FunctionIntroducer // +// friend ... +// ^~~~~~ +// 1. FriendDecl +// // impl ... // ^~~~ // 1. ImplAfterIntroducer @@ -753,6 +757,15 @@ CARBON_PARSE_STATE(WhereFinish) // (state done) CARBON_PARSE_STATE(ExprStatementFinish) +// Handles a `friend` declaration. +// +// friend ; +// ^~~~~~~~~~~~~~ +// friend ??? ; +// ^~~~~ +// (state done) +CARBON_PARSE_STATE(FriendDecl) + // Handles a function's introducer. // // fn ... diff --git a/toolchain/parse/testdata/class/friend.carbon b/toolchain/parse/testdata/class/friend.carbon new file mode 100644 index 000000000000..4b2649e3bab2 --- /dev/null +++ b/toolchain/parse/testdata/class/friend.carbon @@ -0,0 +1,126 @@ +// 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 +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/class/friend.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/class/friend.carbon + +// --- friend.carbon + +class Foo { + friend Bar; +} + +// --- fail_todo_qualified.carbon + +class Baz { + // CHECK:STDERR: fail_todo_qualified.carbon:[[@LINE+4]]:11: error: `friend` declarations must end with a `;` [ExpectedDeclSemi] + // CHECK:STDERR: friend P.Q; + // CHECK:STDERR: ^ + // CHECK:STDERR: + friend P.Q; +} + +// --- fail_bad_syntax.carbon + +class A { + // CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+4]]:9: error: expected identifier for friend declaration [ExpectedIdentifierForFriend] + // CHECK:STDERR: friend; + // CHECK:STDERR: ^ + // CHECK:STDERR: + friend; +} + +class B { + // CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+4]]:12: error: `friend` declarations must end with a `;` [ExpectedDeclSemi] + // CHECK:STDERR: friend X extra_token; + // CHECK:STDERR: ^~~~~~~~~~~ + // CHECK:STDERR: + friend X extra_token; +} + +class C { + friend +// CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+4]]:1: error: expected identifier for friend declaration [ExpectedIdentifierForFriend] +// CHECK:STDERR: } +// CHECK:STDERR: ^ +// CHECK:STDERR: +} + +class D { + friend X +// CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+4]]:1: error: `friend` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: } +// CHECK:STDERR: ^ +// CHECK:STDERR: +} + +class E { + // CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+4]]:10: error: expected identifier for friend declaration [ExpectedIdentifierForFriend] + // CHECK:STDERR: friend class; + // CHECK:STDERR: ^~~~~ + // CHECK:STDERR: + friend class; +} + +// CHECK:STDOUT: - filename: friend.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─ClassIntroducer 'class' +// CHECK:STDOUT: │ ├─IdentifierNameNotBeforeSignature 'Foo' +// CHECK:STDOUT: │ ╭─ClassDefinitionStart '{' +// CHECK:STDOUT: │ │ ╭─FriendIntroducer 'friend' +// CHECK:STDOUT: │ │ ├─IdentifierNameExpr 'Bar' +// CHECK:STDOUT: │ ├─FriendDecl ';' +// CHECK:STDOUT: ├─ClassDefinition '}' +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) +// CHECK:STDOUT: - filename: fail_todo_qualified.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─ClassIntroducer 'class' +// CHECK:STDOUT: │ ├─IdentifierNameNotBeforeSignature 'Baz' +// CHECK:STDOUT: │ ╭─ClassDefinitionStart '{' +// CHECK:STDOUT: │ │ ╭─FriendIntroducer 'friend' +// CHECK:STDOUT: │ │ ├─IdentifierNameExpr 'P' +// CHECK:STDOUT: │ ├─FriendDecl ';' has_error +// CHECK:STDOUT: ├─ClassDefinition '}' +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) +// CHECK:STDOUT: - filename: fail_bad_syntax.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─ClassIntroducer 'class' +// CHECK:STDOUT: │ ├─IdentifierNameNotBeforeSignature 'A' +// CHECK:STDOUT: │ ╭─ClassDefinitionStart '{' +// CHECK:STDOUT: │ │ ╭─FriendIntroducer 'friend' +// CHECK:STDOUT: │ ├─FriendDecl ';' has_error +// CHECK:STDOUT: ├─ClassDefinition '}' +// CHECK:STDOUT: │ ╭─ClassIntroducer 'class' +// CHECK:STDOUT: │ ├─IdentifierNameNotBeforeSignature 'B' +// CHECK:STDOUT: │ ╭─ClassDefinitionStart '{' +// CHECK:STDOUT: │ │ ╭─FriendIntroducer 'friend' +// CHECK:STDOUT: │ │ ├─IdentifierNameExpr 'X' +// CHECK:STDOUT: │ ├─FriendDecl ';' has_error +// CHECK:STDOUT: ├─ClassDefinition '}' +// CHECK:STDOUT: │ ╭─ClassIntroducer 'class' +// CHECK:STDOUT: │ ├─IdentifierNameNotBeforeSignature 'C' +// CHECK:STDOUT: │ ╭─ClassDefinitionStart '{' +// CHECK:STDOUT: │ │ ╭─FriendIntroducer 'friend' +// CHECK:STDOUT: │ ├─FriendDecl 'friend' has_error +// CHECK:STDOUT: ├─ClassDefinition '}' +// CHECK:STDOUT: │ ╭─ClassIntroducer 'class' +// CHECK:STDOUT: │ ├─IdentifierNameNotBeforeSignature 'D' +// CHECK:STDOUT: │ ╭─ClassDefinitionStart '{' +// CHECK:STDOUT: │ │ ╭─FriendIntroducer 'friend' +// CHECK:STDOUT: │ │ ├─IdentifierNameExpr 'X' +// CHECK:STDOUT: │ ├─FriendDecl 'X' has_error +// CHECK:STDOUT: ├─ClassDefinition '}' +// CHECK:STDOUT: │ ╭─ClassIntroducer 'class' +// CHECK:STDOUT: │ ├─IdentifierNameNotBeforeSignature 'E' +// CHECK:STDOUT: │ ╭─ClassDefinitionStart '{' +// CHECK:STDOUT: │ │ ╭─FriendIntroducer 'friend' +// CHECK:STDOUT: │ ├─FriendDecl ';' has_error +// CHECK:STDOUT: ├─ClassDefinition '}' +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) diff --git a/toolchain/parse/typed_nodes.h b/toolchain/parse/typed_nodes.h index 7481f442d682..55c46b6bd393 100644 --- a/toolchain/parse/typed_nodes.h +++ b/toolchain/parse/typed_nodes.h @@ -1650,6 +1650,25 @@ struct BaseDecl { Lex::SemiTokenIndex token; }; +// Friend declaration +// ------------------ + +using FriendIntroducer = + LeafNode; + +struct FriendDecl { + static constexpr auto Kind = NodeKind::FriendDecl.Define( + {.category = NodeCategory::Decl, .bracketed_by = FriendIntroducer::Kind}); + + FriendIntroducerId introducer; + + // TODO: figure out the more general syntax for the name part of a friend + // declaration. + IdentifierNameExprId name; + + Lex::SemiTokenIndex token; +}; + // Interface declarations and definitions // --------------------------------------