Skip to content

Commit

Permalink
[Clang] Implement C++26’s P2893R3 ‘Variadic friends’ (#101448)
Browse files Browse the repository at this point in the history
Implement P2893R3 ‘Variadic friends’ for C++26.

This closes #98587.

Co-authored-by: Younan Zhang <[email protected]>
  • Loading branch information
Sirraide and zyn0217 authored Aug 15, 2024
1 parent b5e63cc commit 2b0a8fc
Show file tree
Hide file tree
Showing 28 changed files with 942 additions and 170 deletions.
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 @@ -452,7 +452,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 @@ -2007,9 +2007,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 @@ -2241,9 +2248,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 @@ -2818,6 +2844,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 @@ -2850,6 +2877,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 @@ -3051,6 +3090,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

0 comments on commit 2b0a8fc

Please sign in to comment.