Skip to content

Commit 2b0a8fc

Browse files
Sirraidezyn0217
andauthored
[Clang] Implement C++26’s P2893R3 ‘Variadic friends’ (#101448)
Implement P2893R3 ‘Variadic friends’ for C++26. This closes #98587. Co-authored-by: Younan Zhang <[email protected]>
1 parent b5e63cc commit 2b0a8fc

28 files changed

+942
-170
lines changed

clang/docs/LanguageExtensions.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,6 +1505,7 @@ Attributes on Lambda-Expressions C+
15051505
Attributes on Structured Bindings __cpp_structured_bindings C++26 C++03
15061506
Pack Indexing __cpp_pack_indexing C++26 C++03
15071507
``= delete ("should have a reason");`` __cpp_deleted_function C++26 C++03
1508+
Variadic Friends __cpp_variadic_friend C++26 C++03
15081509
-------------------------------------------- -------------------------------- ------------- -------------
15091510
Designated initializers (N494) C99 C89
15101511
Array & element qualification (N2607) C23 C89

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ C++2c Feature Support
126126
- Add ``__builtin_is_virtual_base_of`` intrinsic, which supports
127127
`P2985R0 A type trait for detecting virtual base classes <https://wg21.link/p2985r0>`_
128128

129+
- Implemented `P2893R3 Variadic Friends <https://wg21.link/P2893>`_
130+
129131
Resolutions to C++ Defect Reports
130132
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
131133

clang/include/clang/AST/DeclFriend.h

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ class FriendDecl final
7070
// Location of the 'friend' specifier.
7171
SourceLocation FriendLoc;
7272

73+
// Location of the '...', if present.
74+
SourceLocation EllipsisLoc;
75+
7376
/// True if this 'friend' declaration is unsupported. Eventually we
7477
/// will support every possible friend declaration, but for now we
7578
/// silently ignore some and set this flag to authorize all access.
@@ -82,10 +85,11 @@ class FriendDecl final
8285
unsigned NumTPLists : 31;
8386

8487
FriendDecl(DeclContext *DC, SourceLocation L, FriendUnion Friend,
85-
SourceLocation FriendL,
88+
SourceLocation FriendL, SourceLocation EllipsisLoc,
8689
ArrayRef<TemplateParameterList *> FriendTypeTPLists)
8790
: Decl(Decl::Friend, DC, L), Friend(Friend), FriendLoc(FriendL),
88-
UnsupportedFriend(false), NumTPLists(FriendTypeTPLists.size()) {
91+
EllipsisLoc(EllipsisLoc), UnsupportedFriend(false),
92+
NumTPLists(FriendTypeTPLists.size()) {
8993
for (unsigned i = 0; i < NumTPLists; ++i)
9094
getTrailingObjects<TemplateParameterList *>()[i] = FriendTypeTPLists[i];
9195
}
@@ -110,7 +114,7 @@ class FriendDecl final
110114

111115
static FriendDecl *
112116
Create(ASTContext &C, DeclContext *DC, SourceLocation L, FriendUnion Friend_,
113-
SourceLocation FriendL,
117+
SourceLocation FriendL, SourceLocation EllipsisLoc = {},
114118
ArrayRef<TemplateParameterList *> FriendTypeTPLists = std::nullopt);
115119
static FriendDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID,
116120
unsigned FriendTypeNumTPLists);
@@ -143,8 +147,24 @@ class FriendDecl final
143147
return FriendLoc;
144148
}
145149

150+
/// Retrieves the location of the '...', if present.
151+
SourceLocation getEllipsisLoc() const { return EllipsisLoc; }
152+
146153
/// Retrieves the source range for the friend declaration.
147154
SourceRange getSourceRange() const override LLVM_READONLY {
155+
if (TypeSourceInfo *TInfo = getFriendType()) {
156+
SourceLocation StartL =
157+
(NumTPLists == 0) ? getFriendLoc()
158+
: getTrailingObjects<TemplateParameterList *>()[0]
159+
->getTemplateLoc();
160+
SourceLocation EndL = isPackExpansion() ? getEllipsisLoc()
161+
: TInfo->getTypeLoc().getEndLoc();
162+
return SourceRange(StartL, EndL);
163+
}
164+
165+
if (isPackExpansion())
166+
return SourceRange(getFriendLoc(), getEllipsisLoc());
167+
148168
if (NamedDecl *ND = getFriendDecl()) {
149169
if (const auto *FD = dyn_cast<FunctionDecl>(ND))
150170
return FD->getSourceRange();
@@ -158,15 +178,8 @@ class FriendDecl final
158178
}
159179
return SourceRange(getFriendLoc(), ND->getEndLoc());
160180
}
161-
else if (TypeSourceInfo *TInfo = getFriendType()) {
162-
SourceLocation StartL =
163-
(NumTPLists == 0) ? getFriendLoc()
164-
: getTrailingObjects<TemplateParameterList *>()[0]
165-
->getTemplateLoc();
166-
return SourceRange(StartL, TInfo->getTypeLoc().getEndLoc());
167-
}
168-
else
169-
return SourceRange(getFriendLoc(), getLocation());
181+
182+
return SourceRange(getFriendLoc(), getLocation());
170183
}
171184

172185
/// Determines if this friend kind is unsupported.
@@ -177,6 +190,8 @@ class FriendDecl final
177190
UnsupportedFriend = Unsupported;
178191
}
179192

193+
bool isPackExpansion() const { return EllipsisLoc.isValid(); }
194+
180195
// Implement isa/cast/dyncast/etc.
181196
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
182197
static bool classofKind(Kind K) { return K == Decl::Friend; }

clang/include/clang/Basic/DiagnosticParseKinds.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -965,6 +965,12 @@ def warn_cxx23_delete_with_message : Warning<
965965
"'= delete' with a message is incompatible with C++ standards before C++2c">,
966966
DefaultIgnore, InGroup<CXXPre26Compat>;
967967

968+
def ext_variadic_friends : ExtWarn<
969+
"variadic 'friend' declarations are a C++2c extension">, InGroup<CXX26>;
970+
def warn_cxx23_variadic_friends : Warning<
971+
"variadic 'friend' declarations are incompatible with C++ standards before C++2c">,
972+
DefaultIgnore, InGroup<CXXPre26Compat>;
973+
968974
// C++11 default member initialization
969975
def ext_nonstatic_member_init : ExtWarn<
970976
"default member initializer for non-static data member is a C++11 "

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1741,6 +1741,10 @@ def ext_friend_tag_redecl_outside_namespace : ExtWarn<
17411741
"enclosing namespace is a Microsoft extension; add a nested name specifier">,
17421742
InGroup<MicrosoftUnqualifiedFriend>;
17431743
def err_pure_friend : Error<"friend declaration cannot have a pure-specifier">;
1744+
def err_friend_template_decl_multiple_specifiers: Error<
1745+
"a friend declaration that befriends a template must contain exactly one type-specifier">;
1746+
def friend_template_decl_malformed_pack_expansion : Error<
1747+
"friend declaration expands pack %0 that is declared it its own template parameter list">;
17441748

17451749
def err_invalid_base_in_interface : Error<
17461750
"interface type cannot inherit from "

clang/include/clang/Sema/Sema.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3800,7 +3800,8 @@ class Sema final : public SemaBase {
38003800
const ParsedAttributesView &DeclAttrs,
38013801
MultiTemplateParamsArg TemplateParams,
38023802
bool IsExplicitInstantiation,
3803-
RecordDecl *&AnonRecord);
3803+
RecordDecl *&AnonRecord,
3804+
SourceLocation EllipsisLoc = {});
38043805

38053806
/// BuildAnonymousStructOrUnion - Handle the declaration of an
38063807
/// anonymous structure or union. Anonymous unions are a C++ feature
@@ -5538,7 +5539,8 @@ class Sema final : public SemaBase {
55385539
/// parameters present at all, require proper matching, i.e.
55395540
/// template <> template \<class T> friend class A<int>::B;
55405541
Decl *ActOnFriendTypeDecl(Scope *S, const DeclSpec &DS,
5541-
MultiTemplateParamsArg TemplateParams);
5542+
MultiTemplateParamsArg TemplateParams,
5543+
SourceLocation EllipsisLoc);
55425544
NamedDecl *ActOnFriendFunctionDecl(Scope *S, Declarator &D,
55435545
MultiTemplateParamsArg TemplateParams);
55445546

@@ -5852,6 +5854,7 @@ class Sema final : public SemaBase {
58525854
unsigned TagSpec, SourceLocation TagLoc,
58535855
CXXScopeSpec &SS, IdentifierInfo *Name,
58545856
SourceLocation NameLoc,
5857+
SourceLocation EllipsisLoc,
58555858
const ParsedAttributesView &Attr,
58565859
MultiTemplateParamsArg TempParamLists);
58575860

clang/lib/AST/ASTImporter.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4429,11 +4429,14 @@ ExpectedDecl ASTNodeImporter::VisitFriendDecl(FriendDecl *D) {
44294429
auto FriendLocOrErr = import(D->getFriendLoc());
44304430
if (!FriendLocOrErr)
44314431
return FriendLocOrErr.takeError();
4432+
auto EllipsisLocOrErr = import(D->getEllipsisLoc());
4433+
if (!EllipsisLocOrErr)
4434+
return EllipsisLocOrErr.takeError();
44324435

44334436
FriendDecl *FrD;
44344437
if (GetImportedOrCreateDecl(FrD, D, Importer.getToContext(), DC,
4435-
*LocationOrErr, ToFU,
4436-
*FriendLocOrErr, ToTPLists))
4438+
*LocationOrErr, ToFU, *FriendLocOrErr,
4439+
*EllipsisLocOrErr, ToTPLists))
44374440
return FrD;
44384441

44394442
FrD->setAccess(D->getAccess());

clang/lib/AST/DeclFriend.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ FriendDecl *FriendDecl::getNextFriendSlowCase() {
3131
NextFriend.get(getASTContext().getExternalSource()));
3232
}
3333

34-
FriendDecl *FriendDecl::Create(ASTContext &C, DeclContext *DC,
35-
SourceLocation L,
36-
FriendUnion Friend,
37-
SourceLocation FriendL,
38-
ArrayRef<TemplateParameterList *> FriendTypeTPLists) {
34+
FriendDecl *
35+
FriendDecl::Create(ASTContext &C, DeclContext *DC, SourceLocation L,
36+
FriendUnion Friend, SourceLocation FriendL,
37+
SourceLocation EllipsisLoc,
38+
ArrayRef<TemplateParameterList *> FriendTypeTPLists) {
3939
#ifndef NDEBUG
4040
if (Friend.is<NamedDecl *>()) {
4141
const auto *D = Friend.get<NamedDecl*>();
@@ -56,8 +56,8 @@ FriendDecl *FriendDecl::Create(ASTContext &C, DeclContext *DC,
5656
std::size_t Extra =
5757
FriendDecl::additionalSizeToAlloc<TemplateParameterList *>(
5858
FriendTypeTPLists.size());
59-
auto *FD = new (C, DC, Extra) FriendDecl(DC, L, Friend, FriendL,
60-
FriendTypeTPLists);
59+
auto *FD = new (C, DC, Extra)
60+
FriendDecl(DC, L, Friend, FriendL, EllipsisLoc, FriendTypeTPLists);
6161
cast<CXXRecordDecl>(DC)->pushFriendDecl(FD);
6262
return FD;
6363
}

clang/lib/AST/DeclPrinter.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -868,7 +868,7 @@ void DeclPrinter::VisitFriendDecl(FriendDecl *D) {
868868
for (unsigned i = 0; i < NumTPLists; ++i)
869869
printTemplateParameters(D->getFriendTypeTemplateParameterList(i));
870870
Out << "friend ";
871-
Out << " " << TSI->getType().getAsString(Policy);
871+
Out << TSI->getType().getAsString(Policy);
872872
}
873873
else if (FunctionDecl *FD =
874874
dyn_cast<FunctionDecl>(D->getFriendDecl())) {
@@ -885,6 +885,9 @@ void DeclPrinter::VisitFriendDecl(FriendDecl *D) {
885885
Out << "friend ";
886886
VisitRedeclarableTemplateDecl(CTD);
887887
}
888+
889+
if (D->isPackExpansion())
890+
Out << "...";
888891
}
889892

890893
void DeclPrinter::VisitFieldDecl(FieldDecl *D) {

clang/lib/AST/JSONNodeDumper.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,7 @@ void JSONNodeDumper::VisitAccessSpecDecl(const AccessSpecDecl *ASD) {
10901090
void JSONNodeDumper::VisitFriendDecl(const FriendDecl *FD) {
10911091
if (const TypeSourceInfo *T = FD->getFriendType())
10921092
JOS.attribute("type", createQualType(T->getType()));
1093+
attributeOnlyIfTrue("isPackExpansion", FD->isPackExpansion());
10931094
}
10941095

10951096
void JSONNodeDumper::VisitObjCIvarDecl(const ObjCIvarDecl *D) {

clang/lib/AST/ODRHash.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ class ODRDeclVisitor : public ConstDeclVisitor<ODRDeclVisitor> {
461461
} else {
462462
AddDecl(D->getFriendDecl());
463463
}
464+
Hash.AddBoolean(D->isPackExpansion());
464465
}
465466

466467
void VisitTemplateTypeParmDecl(const TemplateTypeParmDecl *D) {

clang/lib/AST/TextNodeDumper.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2697,6 +2697,8 @@ void TextNodeDumper::VisitAccessSpecDecl(const AccessSpecDecl *D) {
26972697
void TextNodeDumper::VisitFriendDecl(const FriendDecl *D) {
26982698
if (TypeSourceInfo *T = D->getFriendType())
26992699
dumpType(T->getType());
2700+
if (D->isPackExpansion())
2701+
OS << "...";
27002702
}
27012703

27022704
void TextNodeDumper::VisitObjCIvarDecl(const ObjCIvarDecl *D) {

clang/lib/Frontend/InitPreprocessor.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
765765
// C++26 features supported in earlier language modes.
766766
Builder.defineMacro("__cpp_pack_indexing", "202311L");
767767
Builder.defineMacro("__cpp_deleted_function", "202403L");
768+
Builder.defineMacro("__cpp_variadic_friend", "202403L");
768769

769770
if (LangOpts.Char8)
770771
Builder.defineMacro("__cpp_char8_t", "202207L");

clang/lib/Parse/ParseDeclCXX.cpp

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ Decl *Parser::ParseLinkage(ParsingDeclSpec &DS, DeclaratorContext Context) {
452452
///
453453
/// export-function-declaration:
454454
/// 'export' function-declaration
455-
///
455+
///
456456
/// export-declaration-group:
457457
/// 'export' '{' function-declaration-seq[opt] '}'
458458
///
@@ -2007,9 +2007,16 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
20072007

20082008
const PrintingPolicy &Policy = Actions.getASTContext().getPrintingPolicy();
20092009
TagUseKind TUK;
2010-
if (isDefiningTypeSpecifierContext(DSC, getLangOpts().CPlusPlus) ==
2011-
AllowDefiningTypeSpec::No ||
2012-
(getLangOpts().OpenMP && OpenMPDirectiveParsing))
2010+
2011+
// C++26 [class.mem.general]p10: If a name-declaration matches the
2012+
// syntactic requirements of friend-type-declaration, it is a
2013+
// friend-type-declaration.
2014+
if (getLangOpts().CPlusPlus && DS.isFriendSpecifiedFirst() &&
2015+
Tok.isOneOf(tok::comma, tok::ellipsis))
2016+
TUK = TagUseKind::Friend;
2017+
else if (isDefiningTypeSpecifierContext(DSC, getLangOpts().CPlusPlus) ==
2018+
AllowDefiningTypeSpec::No ||
2019+
(getLangOpts().OpenMP && OpenMPDirectiveParsing))
20132020
TUK = TagUseKind::Reference;
20142021
else if (Tok.is(tok::l_brace) ||
20152022
(DSC != DeclSpecContext::DSC_association &&
@@ -2241,9 +2248,28 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
22412248
diag::err_keyword_not_allowed,
22422249
/*DiagnoseEmptyAttrs=*/true);
22432250

2251+
// Consume '...' first so we error on the ',' after it if there is one.
2252+
SourceLocation EllipsisLoc;
2253+
TryConsumeToken(tok::ellipsis, EllipsisLoc);
2254+
2255+
// CWG 2917: In a template-declaration whose declaration is a
2256+
// friend-type-declaration, the friend-type-specifier-list shall
2257+
// consist of exactly one friend-type-specifier.
2258+
//
2259+
// Essentially, the following is obviously nonsense, so disallow it:
2260+
//
2261+
// template <typename>
2262+
// friend class S, int;
2263+
//
2264+
if (Tok.is(tok::comma)) {
2265+
Diag(Tok.getLocation(),
2266+
diag::err_friend_template_decl_multiple_specifiers);
2267+
SkipUntil(tok::semi, StopBeforeMatch);
2268+
}
2269+
22442270
TagOrTempResult = Actions.ActOnTemplatedFriendTag(
22452271
getCurScope(), DS.getFriendSpecLoc(), TagType, StartLoc, SS, Name,
2246-
NameLoc, attrs,
2272+
NameLoc, EllipsisLoc, attrs,
22472273
MultiTemplateParamsArg(TemplateParams ? &(*TemplateParams)[0] : nullptr,
22482274
TemplateParams ? TemplateParams->size() : 0));
22492275
} else {
@@ -2818,6 +2844,7 @@ void Parser::MaybeParseAndDiagnoseDeclSpecAfterCXX11VirtSpecifierSeq(
28182844
/// member-declaration:
28192845
/// decl-specifier-seq[opt] member-declarator-list[opt] ';'
28202846
/// function-definition ';'[opt]
2847+
/// [C++26] friend-type-declaration
28212848
/// ::[opt] nested-name-specifier template[opt] unqualified-id ';'[TODO]
28222849
/// using-declaration [TODO]
28232850
/// [C++0x] static_assert-declaration
@@ -2850,6 +2877,18 @@ void Parser::MaybeParseAndDiagnoseDeclSpecAfterCXX11VirtSpecifierSeq(
28502877
/// constant-initializer:
28512878
/// '=' constant-expression
28522879
///
2880+
/// friend-type-declaration:
2881+
/// 'friend' friend-type-specifier-list ;
2882+
///
2883+
/// friend-type-specifier-list:
2884+
/// friend-type-specifier ...[opt]
2885+
/// friend-type-specifier-list , friend-type-specifier ...[opt]
2886+
///
2887+
/// friend-type-specifier:
2888+
/// simple-type-specifier
2889+
/// elaborated-type-specifier
2890+
/// typename-specifier
2891+
///
28532892
Parser::DeclGroupPtrTy Parser::ParseCXXClassMemberDeclaration(
28542893
AccessSpecifier AS, ParsedAttributes &AccessAttrs,
28552894
ParsedTemplateInfo &TemplateInfo, ParsingDeclRAIIObject *TemplateDiags) {
@@ -3051,6 +3090,55 @@ Parser::DeclGroupPtrTy Parser::ParseCXXClassMemberDeclaration(
30513090
if (DS.hasTagDefinition())
30523091
Actions.ActOnDefinedDeclarationSpecifier(DS.getRepAsDecl());
30533092

3093+
// Handle C++26's variadic friend declarations. These don't even have
3094+
// declarators, so we get them out of the way early here.
3095+
if (DS.isFriendSpecifiedFirst() && Tok.isOneOf(tok::comma, tok::ellipsis)) {
3096+
Diag(Tok.getLocation(), getLangOpts().CPlusPlus26
3097+
? diag::warn_cxx23_variadic_friends
3098+
: diag::ext_variadic_friends);
3099+
3100+
SourceLocation FriendLoc = DS.getFriendSpecLoc();
3101+
SmallVector<Decl *> Decls;
3102+
3103+
// Handles a single friend-type-specifier.
3104+
auto ParsedFriendDecl = [&](ParsingDeclSpec &DeclSpec) {
3105+
SourceLocation VariadicLoc;
3106+
TryConsumeToken(tok::ellipsis, VariadicLoc);
3107+
3108+
RecordDecl *AnonRecord = nullptr;
3109+
Decl *D = Actions.ParsedFreeStandingDeclSpec(
3110+
getCurScope(), AS, DeclSpec, DeclAttrs, TemplateParams, false,
3111+
AnonRecord, VariadicLoc);
3112+
DeclSpec.complete(D);
3113+
if (!D) {
3114+
SkipUntil(tok::semi, tok::r_brace);
3115+
return true;
3116+
}
3117+
3118+
Decls.push_back(D);
3119+
return false;
3120+
};
3121+
3122+
if (ParsedFriendDecl(DS))
3123+
return nullptr;
3124+
3125+
while (TryConsumeToken(tok::comma)) {
3126+
ParsingDeclSpec DeclSpec(*this, TemplateDiags);
3127+
const char *PrevSpec = nullptr;
3128+
unsigned DiagId = 0;
3129+
DeclSpec.SetFriendSpec(FriendLoc, PrevSpec, DiagId);
3130+
ParseDeclarationSpecifiers(DeclSpec, TemplateInfo, AS,
3131+
DeclSpecContext::DSC_class, nullptr);
3132+
if (ParsedFriendDecl(DeclSpec))
3133+
return nullptr;
3134+
}
3135+
3136+
ExpectAndConsume(tok::semi, diag::err_expected_semi_after_stmt,
3137+
"friend declaration");
3138+
3139+
return Actions.BuildDeclaratorGroup(Decls);
3140+
}
3141+
30543142
ParsingDeclarator DeclaratorInfo(*this, DS, DeclAttrs,
30553143
DeclaratorContext::Member);
30563144
if (TemplateInfo.TemplateParams)

0 commit comments

Comments
 (0)