Parse support for simple friend declarations (#7889)

This change only supports `friend` with an identifier operand, because
there are a lot of open questions about the desired syntax beyond that.
This commit is contained in:
Geoff Romer
2026-10-05 17:30:29 +00:00
committed by GitHub
parent d31a8b67d3
commit 8f258eaaa6
9 changed files with 208 additions and 0 deletions
+9
View File
@@ -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 =
+1
View File
@@ -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:
+1
View File
@@ -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)
@@ -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,
+34
View File
@@ -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
+3
View File
@@ -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)
+13
View File
@@ -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 <identifier> ;
// ^~~~~~~~~~~~~~
// friend ??? ;
// ^~~~~
// (state done)
CARBON_PARSE_STATE(FriendDecl)
// Handles a function's introducer.
//
// fn ...
+126
View File
@@ -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)
+19
View File
@@ -1650,6 +1650,25 @@ struct BaseDecl {
Lex::SemiTokenIndex token;
};
// Friend declaration
// ------------------
using FriendIntroducer =
LeafNode<NodeKind::FriendIntroducer, Lex::FriendTokenIndex>;
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
// --------------------------------------