Skip to content

[Clang] Implement C++26’s P2893R3 ‘Variadic friends’ #101448

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1fd8db6
[Parser] Parse variadic friends
Sirraide Jul 31, 2024
ddb83c2
[Sema] Instantiate variadic friends
Sirraide Jul 31, 2024
5f5eb7e
[ASTPrinter] Group friend decls
Sirraide Jul 31, 2024
c603a5d
[Tests] Move some tests
Sirraide Aug 1, 2024
da65e3b
Feature-test macro + release notes
Sirraide Aug 1, 2024
070e5bc
clang-format
Sirraide Aug 1, 2024
70e7621
[NFC] auto -> auto*
Sirraide Aug 5, 2024
a520f33
[Clang] Add feature test macro and compat warnings
Sirraide Aug 5, 2024
38e6f09
Merge branch 'main' into variadic-friends
Sirraide Aug 5, 2024
99b85b4
Handle more invalid cases
Sirraide Aug 5, 2024
b6547be
clang-format
Sirraide Aug 5, 2024
4214b46
clang-format, again
Sirraide Aug 5, 2024
bf437ab
Remove feature test macro etc in preparation of splitting the impleme…
Sirraide Aug 5, 2024
e175ce6
Add test for cwg2917
Sirraide Aug 5, 2024
5f9d44a
Run make-cxx-dr-status
Sirraide Aug 5, 2024
f75da38
Update JSONNodeDumper
Sirraide Aug 5, 2024
36d46ff
Add more tests
Sirraide Aug 6, 2024
b3e3c2c
So long, FriendPackDecl
Sirraide Aug 6, 2024
bce98e6
Address most of Corentin’s feedback
Sirraide Aug 13, 2024
410d32b
Handle invalid pack expansions properly
Sirraide Aug 13, 2024
fdcac04
clang-format
Sirraide Aug 13, 2024
2ffff78
Update JSON/TextNodeDumper
Sirraide Aug 13, 2024
0d2a059
Revert "Remove feature test macro etc in preparation of splitting the…
Sirraide Aug 13, 2024
d689e73
Remove duplicate feature-test macro
Sirraide Aug 13, 2024
d311ff5
This is already tested elsewhere
Sirraide Aug 13, 2024
f668edc
Merge branch 'main' into variadic-friends
Sirraide Aug 15, 2024
10629bb
Mark decl as invalid
Sirraide Aug 15, 2024
2375ff5
Remove unrelated changes from cxx_dr_status
Sirraide Aug 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1505,6 +1505,7 @@ Attributes on Lambda-Expressions C+
Attributes on Structured Bindings __cpp_structured_bindings C++26 C++03
Pack Indexing __cpp_pack_indexing C++26 C++03
``= delete ("should have a reason");`` __cpp_deleted_function C++26 C++03
Variadic Friends __cpp_variadic_friend C++26 C++03
-------------------------------------------- -------------------------------- ------------- -------------
Designated initializers (N494) C99 C89
Array & element qualification (N2607) C23 C89
Expand Down
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ C++2c Feature Support
- Add ``__builtin_is_virtual_base_of`` intrinsic, which supports
`P2985R0 A type trait for detecting virtual base classes <https://wg21.link/p2985r0>`_

- Implemented `P2893R3 Variadic Friends <https://wg21.link/P2893>`_

Resolutions to C++ Defect Reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
39 changes: 27 additions & 12 deletions clang/include/clang/AST/DeclFriend.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ class FriendDecl final
// Location of the 'friend' specifier.
SourceLocation FriendLoc;

// Location of the '...', if present.
SourceLocation EllipsisLoc;

/// True if this 'friend' declaration is unsupported. Eventually we
/// will support every possible friend declaration, but for now we
/// silently ignore some and set this flag to authorize all access.
Expand All @@ -82,10 +85,11 @@ class FriendDecl final
unsigned NumTPLists : 31;

FriendDecl(DeclContext *DC, SourceLocation L, FriendUnion Friend,
SourceLocation FriendL,
SourceLocation FriendL, SourceLocation EllipsisLoc,
ArrayRef<TemplateParameterList *> FriendTypeTPLists)
: Decl(Decl::Friend, DC, L), Friend(Friend), FriendLoc(FriendL),
UnsupportedFriend(false), NumTPLists(FriendTypeTPLists.size()) {
EllipsisLoc(EllipsisLoc), UnsupportedFriend(false),
NumTPLists(FriendTypeTPLists.size()) {
for (unsigned i = 0; i < NumTPLists; ++i)
getTrailingObjects<TemplateParameterList *>()[i] = FriendTypeTPLists[i];
}
Expand All @@ -110,7 +114,7 @@ class FriendDecl final

static FriendDecl *
Create(ASTContext &C, DeclContext *DC, SourceLocation L, FriendUnion Friend_,
SourceLocation FriendL,
SourceLocation FriendL, SourceLocation EllipsisLoc = {},
ArrayRef<TemplateParameterList *> FriendTypeTPLists = std::nullopt);
static FriendDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID,
unsigned FriendTypeNumTPLists);
Expand Down Expand Up @@ -143,8 +147,24 @@ class FriendDecl final
return FriendLoc;
}

/// Retrieves the location of the '...', if present.
SourceLocation getEllipsisLoc() const { return EllipsisLoc; }

/// Retrieves the source range for the friend declaration.
SourceRange getSourceRange() const override LLVM_READONLY {
if (TypeSourceInfo *TInfo = getFriendType()) {
SourceLocation StartL =
(NumTPLists == 0) ? getFriendLoc()
: getTrailingObjects<TemplateParameterList *>()[0]
->getTemplateLoc();
SourceLocation EndL = isPackExpansion() ? getEllipsisLoc()
: TInfo->getTypeLoc().getEndLoc();
return SourceRange(StartL, EndL);
}

if (isPackExpansion())
return SourceRange(getFriendLoc(), getEllipsisLoc());

if (NamedDecl *ND = getFriendDecl()) {
if (const auto *FD = dyn_cast<FunctionDecl>(ND))
return FD->getSourceRange();
Expand All @@ -158,15 +178,8 @@ class FriendDecl final
}
return SourceRange(getFriendLoc(), ND->getEndLoc());
}
else if (TypeSourceInfo *TInfo = getFriendType()) {
SourceLocation StartL =
(NumTPLists == 0) ? getFriendLoc()
: getTrailingObjects<TemplateParameterList *>()[0]
->getTemplateLoc();
return SourceRange(StartL, TInfo->getTypeLoc().getEndLoc());
}
else
return SourceRange(getFriendLoc(), getLocation());

return SourceRange(getFriendLoc(), getLocation());
}

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

bool isPackExpansion() const { return EllipsisLoc.isValid(); }

// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
static bool classofKind(Kind K) { return K == Decl::Friend; }
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/DiagnosticParseKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,12 @@ def warn_cxx23_delete_with_message : Warning<
"'= delete' with a message is incompatible with C++ standards before C++2c">,
DefaultIgnore, InGroup<CXXPre26Compat>;

def ext_variadic_friends : ExtWarn<
"variadic 'friend' declarations are a C++2c extension">, InGroup<CXX26>;
def warn_cxx23_variadic_friends : Warning<
"variadic 'friend' declarations are incompatible with C++ standards before C++2c">,
DefaultIgnore, InGroup<CXXPre26Compat>;

// C++11 default member initialization
def ext_nonstatic_member_init : ExtWarn<
"default member initializer for non-static data member is a C++11 "
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -1741,6 +1741,10 @@ def ext_friend_tag_redecl_outside_namespace : ExtWarn<
"enclosing namespace is a Microsoft extension; add a nested name specifier">,
InGroup<MicrosoftUnqualifiedFriend>;
def err_pure_friend : Error<"friend declaration cannot have a pure-specifier">;
def err_friend_template_decl_multiple_specifiers: Error<
"a friend declaration that befriends a template must contain exactly one type-specifier">;
def friend_template_decl_malformed_pack_expansion : Error<
"friend declaration expands pack %0 that is declared it its own template parameter list">;

def err_invalid_base_in_interface : Error<
"interface type cannot inherit from "
Expand Down
7 changes: 5 additions & 2 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -3800,7 +3800,8 @@ class Sema final : public SemaBase {
const ParsedAttributesView &DeclAttrs,
MultiTemplateParamsArg TemplateParams,
bool IsExplicitInstantiation,
RecordDecl *&AnonRecord);
RecordDecl *&AnonRecord,
SourceLocation EllipsisLoc = {});

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

Expand Down Expand Up @@ -5852,6 +5854,7 @@ class Sema final : public SemaBase {
unsigned TagSpec, SourceLocation TagLoc,
CXXScopeSpec &SS, IdentifierInfo *Name,
SourceLocation NameLoc,
SourceLocation EllipsisLoc,
const ParsedAttributesView &Attr,
MultiTemplateParamsArg TempParamLists);

Expand Down
7 changes: 5 additions & 2 deletions clang/lib/AST/ASTImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4429,11 +4429,14 @@ ExpectedDecl ASTNodeImporter::VisitFriendDecl(FriendDecl *D) {
auto FriendLocOrErr = import(D->getFriendLoc());
if (!FriendLocOrErr)
return FriendLocOrErr.takeError();
auto EllipsisLocOrErr = import(D->getEllipsisLoc());
if (!EllipsisLocOrErr)
return EllipsisLocOrErr.takeError();

FriendDecl *FrD;
if (GetImportedOrCreateDecl(FrD, D, Importer.getToContext(), DC,
*LocationOrErr, ToFU,
*FriendLocOrErr, ToTPLists))
*LocationOrErr, ToFU, *FriendLocOrErr,
*EllipsisLocOrErr, ToTPLists))
return FrD;

FrD->setAccess(D->getAccess());
Expand Down
14 changes: 7 additions & 7 deletions clang/lib/AST/DeclFriend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ FriendDecl *FriendDecl::getNextFriendSlowCase() {
NextFriend.get(getASTContext().getExternalSource()));
}

FriendDecl *FriendDecl::Create(ASTContext &C, DeclContext *DC,
SourceLocation L,
FriendUnion Friend,
SourceLocation FriendL,
ArrayRef<TemplateParameterList *> FriendTypeTPLists) {
FriendDecl *
FriendDecl::Create(ASTContext &C, DeclContext *DC, SourceLocation L,
FriendUnion Friend, SourceLocation FriendL,
SourceLocation EllipsisLoc,
ArrayRef<TemplateParameterList *> FriendTypeTPLists) {
#ifndef NDEBUG
if (Friend.is<NamedDecl *>()) {
const auto *D = Friend.get<NamedDecl*>();
Expand All @@ -56,8 +56,8 @@ FriendDecl *FriendDecl::Create(ASTContext &C, DeclContext *DC,
std::size_t Extra =
FriendDecl::additionalSizeToAlloc<TemplateParameterList *>(
FriendTypeTPLists.size());
auto *FD = new (C, DC, Extra) FriendDecl(DC, L, Friend, FriendL,
FriendTypeTPLists);
auto *FD = new (C, DC, Extra)
FriendDecl(DC, L, Friend, FriendL, EllipsisLoc, FriendTypeTPLists);
cast<CXXRecordDecl>(DC)->pushFriendDecl(FD);
return FD;
}
Expand Down
5 changes: 4 additions & 1 deletion clang/lib/AST/DeclPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -868,7 +868,7 @@ void DeclPrinter::VisitFriendDecl(FriendDecl *D) {
for (unsigned i = 0; i < NumTPLists; ++i)
printTemplateParameters(D->getFriendTypeTemplateParameterList(i));
Out << "friend ";
Out << " " << TSI->getType().getAsString(Policy);
Out << TSI->getType().getAsString(Policy);
}
else if (FunctionDecl *FD =
dyn_cast<FunctionDecl>(D->getFriendDecl())) {
Expand All @@ -885,6 +885,9 @@ void DeclPrinter::VisitFriendDecl(FriendDecl *D) {
Out << "friend ";
VisitRedeclarableTemplateDecl(CTD);
}

if (D->isPackExpansion())
Out << "...";
}

void DeclPrinter::VisitFieldDecl(FieldDecl *D) {
Expand Down
1 change: 1 addition & 0 deletions clang/lib/AST/JSONNodeDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,7 @@ void JSONNodeDumper::VisitAccessSpecDecl(const AccessSpecDecl *ASD) {
void JSONNodeDumper::VisitFriendDecl(const FriendDecl *FD) {
if (const TypeSourceInfo *T = FD->getFriendType())
JOS.attribute("type", createQualType(T->getType()));
attributeOnlyIfTrue("isPackExpansion", FD->isPackExpansion());
}

void JSONNodeDumper::VisitObjCIvarDecl(const ObjCIvarDecl *D) {
Expand Down
1 change: 1 addition & 0 deletions clang/lib/AST/ODRHash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ class ODRDeclVisitor : public ConstDeclVisitor<ODRDeclVisitor> {
} else {
AddDecl(D->getFriendDecl());
}
Hash.AddBoolean(D->isPackExpansion());
}

void VisitTemplateTypeParmDecl(const TemplateTypeParmDecl *D) {
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/AST/TextNodeDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2697,6 +2697,8 @@ void TextNodeDumper::VisitAccessSpecDecl(const AccessSpecDecl *D) {
void TextNodeDumper::VisitFriendDecl(const FriendDecl *D) {
if (TypeSourceInfo *T = D->getFriendType())
dumpType(T->getType());
if (D->isPackExpansion())
OS << "...";
}

void TextNodeDumper::VisitObjCIvarDecl(const ObjCIvarDecl *D) {
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Frontend/InitPreprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
// C++26 features supported in earlier language modes.
Builder.defineMacro("__cpp_pack_indexing", "202311L");
Builder.defineMacro("__cpp_deleted_function", "202403L");
Builder.defineMacro("__cpp_variadic_friend", "202403L");

if (LangOpts.Char8)
Builder.defineMacro("__cpp_char8_t", "202207L");
Expand Down
98 changes: 93 additions & 5 deletions clang/lib/Parse/ParseDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ Decl *Parser::ParseLinkage(ParsingDeclSpec &DS, DeclaratorContext Context) {
///
/// export-function-declaration:
/// 'export' function-declaration
///
///
/// export-declaration-group:
/// 'export' '{' function-declaration-seq[opt] '}'
///
Expand Down Expand Up @@ -2004,9 +2004,16 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,

const PrintingPolicy &Policy = Actions.getASTContext().getPrintingPolicy();
TagUseKind TUK;
if (isDefiningTypeSpecifierContext(DSC, getLangOpts().CPlusPlus) ==
AllowDefiningTypeSpec::No ||
(getLangOpts().OpenMP && OpenMPDirectiveParsing))

// C++26 [class.mem.general]p10: If a name-declaration matches the
// syntactic requirements of friend-type-declaration, it is a
// friend-type-declaration.
if (getLangOpts().CPlusPlus && DS.isFriendSpecifiedFirst() &&
Tok.isOneOf(tok::comma, tok::ellipsis))
TUK = TagUseKind::Friend;
else if (isDefiningTypeSpecifierContext(DSC, getLangOpts().CPlusPlus) ==
AllowDefiningTypeSpec::No ||
(getLangOpts().OpenMP && OpenMPDirectiveParsing))
TUK = TagUseKind::Reference;
else if (Tok.is(tok::l_brace) ||
(DSC != DeclSpecContext::DSC_association &&
Expand Down Expand Up @@ -2238,9 +2245,28 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
diag::err_keyword_not_allowed,
/*DiagnoseEmptyAttrs=*/true);

// Consume '...' first so we error on the ',' after it if there is one.
SourceLocation EllipsisLoc;
TryConsumeToken(tok::ellipsis, EllipsisLoc);

// CWG 2917: In a template-declaration whose declaration is a
// friend-type-declaration, the friend-type-specifier-list shall
// consist of exactly one friend-type-specifier.
//
// Essentially, the following is obviously nonsense, so disallow it:
//
// template <typename>
// friend class S, int;
//
if (Tok.is(tok::comma)) {
Diag(Tok.getLocation(),
diag::err_friend_template_decl_multiple_specifiers);
SkipUntil(tok::semi, StopBeforeMatch);
}

TagOrTempResult = Actions.ActOnTemplatedFriendTag(
getCurScope(), DS.getFriendSpecLoc(), TagType, StartLoc, SS, Name,
NameLoc, attrs,
NameLoc, EllipsisLoc, attrs,
MultiTemplateParamsArg(TemplateParams ? &(*TemplateParams)[0] : nullptr,
TemplateParams ? TemplateParams->size() : 0));
} else {
Expand Down Expand Up @@ -2815,6 +2841,7 @@ void Parser::MaybeParseAndDiagnoseDeclSpecAfterCXX11VirtSpecifierSeq(
/// member-declaration:
/// decl-specifier-seq[opt] member-declarator-list[opt] ';'
/// function-definition ';'[opt]
/// [C++26] friend-type-declaration
/// ::[opt] nested-name-specifier template[opt] unqualified-id ';'[TODO]
/// using-declaration [TODO]
/// [C++0x] static_assert-declaration
Expand Down Expand Up @@ -2847,6 +2874,18 @@ void Parser::MaybeParseAndDiagnoseDeclSpecAfterCXX11VirtSpecifierSeq(
/// constant-initializer:
/// '=' constant-expression
///
/// friend-type-declaration:
/// 'friend' friend-type-specifier-list ;
///
/// friend-type-specifier-list:
/// friend-type-specifier ...[opt]
/// friend-type-specifier-list , friend-type-specifier ...[opt]
///
/// friend-type-specifier:
/// simple-type-specifier
/// elaborated-type-specifier
/// typename-specifier
///
Parser::DeclGroupPtrTy Parser::ParseCXXClassMemberDeclaration(
AccessSpecifier AS, ParsedAttributes &AccessAttrs,
ParsedTemplateInfo &TemplateInfo, ParsingDeclRAIIObject *TemplateDiags) {
Expand Down Expand Up @@ -3048,6 +3087,55 @@ Parser::DeclGroupPtrTy Parser::ParseCXXClassMemberDeclaration(
if (DS.hasTagDefinition())
Actions.ActOnDefinedDeclarationSpecifier(DS.getRepAsDecl());

// Handle C++26's variadic friend declarations. These don't even have
// declarators, so we get them out of the way early here.
if (DS.isFriendSpecifiedFirst() && Tok.isOneOf(tok::comma, tok::ellipsis)) {
Diag(Tok.getLocation(), getLangOpts().CPlusPlus26
? diag::warn_cxx23_variadic_friends
: diag::ext_variadic_friends);

SourceLocation FriendLoc = DS.getFriendSpecLoc();
SmallVector<Decl *> Decls;

// Handles a single friend-type-specifier.
auto ParsedFriendDecl = [&](ParsingDeclSpec &DeclSpec) {
SourceLocation VariadicLoc;
TryConsumeToken(tok::ellipsis, VariadicLoc);

RecordDecl *AnonRecord = nullptr;
Decl *D = Actions.ParsedFreeStandingDeclSpec(
getCurScope(), AS, DeclSpec, DeclAttrs, TemplateParams, false,
AnonRecord, VariadicLoc);
DeclSpec.complete(D);
if (!D) {
SkipUntil(tok::semi, tok::r_brace);
return true;
}

Decls.push_back(D);
return false;
};

if (ParsedFriendDecl(DS))
return nullptr;

while (TryConsumeToken(tok::comma)) {
ParsingDeclSpec DeclSpec(*this, TemplateDiags);
const char *PrevSpec = nullptr;
unsigned DiagId = 0;
DeclSpec.SetFriendSpec(FriendLoc, PrevSpec, DiagId);
ParseDeclarationSpecifiers(DeclSpec, TemplateInfo, AS,
DeclSpecContext::DSC_class, nullptr);
if (ParsedFriendDecl(DeclSpec))
return nullptr;
}

ExpectAndConsume(tok::semi, diag::err_expected_semi_after_stmt,
"friend declaration");

return Actions.BuildDeclaratorGroup(Decls);
}

ParsingDeclarator DeclaratorInfo(*this, DS, DeclAttrs,
DeclaratorContext::Member);
if (TemplateInfo.TemplateParams)
Expand Down
Loading