Skip to content

Commit 3a48630

Browse files
authored
[Clang][Sema] Diagnose friend declarations with enum elaborated-type-specifier in all language modes (#80171)
According to [dcl.type.elab] p4: > If an _elaborated-type-specifier_ appears with the `friend` specifier as an entire _member-declaration_, the _member-declaration_ shall have one of the following forms: > `friend` _class-key_ _nested-name-specifier_(opt) _identifier_ `;` > `friend` _class-key_ _simple-template-id_ `;` > `friend` _class-key_ _nested-name-specifier_ `template`(opt) _simple-template-id_ `;` Notably absent from this list is the `enum` form of an _elaborated-type-specifier_ "`enum` _nested-name-specifier_(opt) _identifier_", which appears to be intentional per the resolution of CWG2363. Most major implementations accept these declarations, so the diagnostic is a pedantic warning across all C++ versions. In addition to the trivial cases previously diagnosed in C++98, we now diagnose cases where the _elaborated-type-specifier_ has a dependent _nested-name-specifier_: ``` template<typename T> struct A { enum class E; }; struct B { template<typename T> friend enum A<T>::E; // pedantic warning: elaborated enumeration type cannot be a friend }; template<typename T> struct C { friend enum T::E; // pedantic warning: elaborated enumeration type cannot be a friend }; ```
1 parent 1b65742 commit 3a48630

File tree

19 files changed

+185
-146
lines changed

19 files changed

+185
-146
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ Improvements to Clang's diagnostics
159159
- The ``-Wshorten-64-to-32`` diagnostic is now grouped under ``-Wimplicit-int-conversion`` instead
160160
of ``-Wconversion``. Fixes `#69444 <https://github.com/llvm/llvm-project/issues/69444>`_.
161161

162+
- Clang now diagnoses friend declarations with an ``enum`` elaborated-type-specifier in language modes after C++98.
163+
162164
Improvements to Clang's time-trace
163165
----------------------------------
164166

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1637,10 +1637,10 @@ def err_inline_namespace_std : Error<
16371637
def err_unexpected_friend : Error<
16381638
"friends can only be classes or functions">;
16391639
def ext_enum_friend : ExtWarn<
1640-
"befriending enumeration type %0 is a C++11 extension">, InGroup<CXX11>;
1641-
def warn_cxx98_compat_enum_friend : Warning<
1642-
"befriending enumeration type %0 is incompatible with C++98">,
1643-
InGroup<CXX98Compat>, DefaultIgnore;
1640+
"elaborated enum specifier cannot be declared as a friend">,
1641+
InGroup<DiagGroup<"friend-enum">>;
1642+
def note_enum_friend : Note<
1643+
"remove 'enum%select{| struct| class}0' to befriend an enum">;
16441644
def ext_nonclass_type_friend : ExtWarn<
16451645
"non-class friend type %0 is a C++11 extension">, InGroup<CXX11>;
16461646
def warn_cxx98_compat_nonclass_type_friend : Warning<

clang/include/clang/Sema/DeclSpec.h

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -346,10 +346,7 @@ class DeclSpec {
346346
// FIXME: Attributes should be included here.
347347
};
348348

349-
enum FriendSpecified : bool {
350-
No,
351-
Yes,
352-
};
349+
enum FriendSpecified : bool { No, Yes };
353350

354351
private:
355352
// storage-class-specifier
@@ -400,7 +397,7 @@ class DeclSpec {
400397

401398
// friend-specifier
402399
LLVM_PREFERRED_TYPE(bool)
403-
unsigned Friend_specified : 1;
400+
unsigned FriendSpecifiedFirst : 1;
404401

405402
// constexpr-specifier
406403
LLVM_PREFERRED_TYPE(ConstexprSpecKind)
@@ -491,7 +488,7 @@ class DeclSpec {
491488
TypeSpecPipe(false), TypeSpecSat(false), ConstrainedAuto(false),
492489
TypeQualifiers(TQ_unspecified), FS_inline_specified(false),
493490
FS_forceinline_specified(false), FS_virtual_specified(false),
494-
FS_noreturn_specified(false), Friend_specified(false),
491+
FS_noreturn_specified(false), FriendSpecifiedFirst(false),
495492
ConstexprSpecifier(
496493
static_cast<unsigned>(ConstexprSpecKind::Unspecified)),
497494
Attrs(attrFactory), writtenBS(), ObjCQualifiers(nullptr) {}
@@ -818,9 +815,11 @@ class DeclSpec {
818815
const char *&PrevSpec, unsigned &DiagID);
819816

820817
FriendSpecified isFriendSpecified() const {
821-
return static_cast<FriendSpecified>(Friend_specified);
818+
return static_cast<FriendSpecified>(FriendLoc.isValid());
822819
}
823820

821+
bool isFriendSpecifiedFirst() const { return FriendSpecifiedFirst; }
822+
824823
SourceLocation getFriendSpecLoc() const { return FriendLoc; }
825824

826825
bool isModulePrivateSpecified() const { return ModulePrivateLoc.isValid(); }

clang/include/clang/Sema/Sema.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8039,9 +8039,6 @@ class Sema final {
80398039
SourceLocation RParenLoc, bool Failed);
80408040
void DiagnoseStaticAssertDetails(const Expr *E);
80418041

8042-
FriendDecl *CheckFriendTypeDecl(SourceLocation LocStart,
8043-
SourceLocation FriendLoc,
8044-
TypeSourceInfo *TSInfo);
80458042
Decl *ActOnFriendTypeDecl(Scope *S, const DeclSpec &DS,
80468043
MultiTemplateParamsArg TemplateParams);
80478044
NamedDecl *ActOnFriendFunctionDecl(Scope *S, Declarator &D,

clang/lib/Parse/ParseTentative.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ bool Parser::isCXXDeclarationStatement(
7979
getCurScope(), *II, Tok.getLocation(), SS, /*Template=*/nullptr);
8080
if (Actions.isCurrentClassName(*II, getCurScope(), &SS) ||
8181
isDeductionGuide) {
82-
if (isConstructorDeclarator(/*Unqualified=*/SS.isEmpty(),
83-
isDeductionGuide,
84-
DeclSpec::FriendSpecified::No))
82+
if (isConstructorDeclarator(
83+
/*Unqualified=*/SS.isEmpty(), isDeductionGuide,
84+
/*IsFriend=*/DeclSpec::FriendSpecified::No))
8585
return true;
8686
} else if (SS.isNotEmpty()) {
8787
// If the scope is not empty, it could alternatively be something like

clang/lib/Sema/DeclSpec.cpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,18 +1102,13 @@ bool DeclSpec::setFunctionSpecNoreturn(SourceLocation Loc,
11021102

11031103
bool DeclSpec::SetFriendSpec(SourceLocation Loc, const char *&PrevSpec,
11041104
unsigned &DiagID) {
1105-
if (Friend_specified) {
1105+
if (isFriendSpecified()) {
11061106
PrevSpec = "friend";
1107-
// Keep the later location, so that we can later diagnose ill-formed
1108-
// declarations like 'friend class X friend;'. Per [class.friend]p3,
1109-
// 'friend' must be the first token in a friend declaration that is
1110-
// not a function declaration.
1111-
FriendLoc = Loc;
11121107
DiagID = diag::warn_duplicate_declspec;
11131108
return true;
11141109
}
11151110

1116-
Friend_specified = true;
1111+
FriendSpecifiedFirst = isEmpty();
11171112
FriendLoc = Loc;
11181113
return false;
11191114
}

clang/lib/Sema/SemaDecl.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17264,6 +17264,26 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc,
1726417264
return true;
1726517265
}
1726617266

17267+
if (TUK == TUK_Friend && Kind == TagTypeKind::Enum) {
17268+
// C++23 [dcl.type.elab]p4:
17269+
// If an elaborated-type-specifier appears with the friend specifier as
17270+
// an entire member-declaration, the member-declaration shall have one
17271+
// of the following forms:
17272+
// friend class-key nested-name-specifier(opt) identifier ;
17273+
// friend class-key simple-template-id ;
17274+
// friend class-key nested-name-specifier template(opt)
17275+
// simple-template-id ;
17276+
//
17277+
// Since enum is not a class-key, so declarations like "friend enum E;"
17278+
// are ill-formed. Although CWG2363 reaffirms that such declarations are
17279+
// invalid, most implementations accept so we issue a pedantic warning.
17280+
Diag(KWLoc, diag::ext_enum_friend) << FixItHint::CreateRemoval(
17281+
ScopedEnum ? SourceRange(KWLoc, ScopedEnumKWLoc) : KWLoc);
17282+
assert(ScopedEnum || !ScopedEnumUsesClassTag);
17283+
Diag(KWLoc, diag::note_enum_friend)
17284+
<< (ScopedEnum + ScopedEnumUsesClassTag);
17285+
}
17286+
1726717287
// Figure out the underlying type if this a enum declaration. We need to do
1726817288
// this early, because it's needed to detect if this is an incompatible
1726917289
// redeclaration.

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 38 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -17545,79 +17545,6 @@ Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc,
1754517545
return Decl;
1754617546
}
1754717547

17548-
/// Perform semantic analysis of the given friend type declaration.
17549-
///
17550-
/// \returns A friend declaration that.
17551-
FriendDecl *Sema::CheckFriendTypeDecl(SourceLocation LocStart,
17552-
SourceLocation FriendLoc,
17553-
TypeSourceInfo *TSInfo) {
17554-
assert(TSInfo && "NULL TypeSourceInfo for friend type declaration");
17555-
17556-
QualType T = TSInfo->getType();
17557-
SourceRange TypeRange = TSInfo->getTypeLoc().getSourceRange();
17558-
17559-
// C++03 [class.friend]p2:
17560-
// An elaborated-type-specifier shall be used in a friend declaration
17561-
// for a class.*
17562-
//
17563-
// * The class-key of the elaborated-type-specifier is required.
17564-
if (!CodeSynthesisContexts.empty()) {
17565-
// Do not complain about the form of friend template types during any kind
17566-
// of code synthesis. For template instantiation, we will have complained
17567-
// when the template was defined.
17568-
} else {
17569-
if (!T->isElaboratedTypeSpecifier()) {
17570-
// If we evaluated the type to a record type, suggest putting
17571-
// a tag in front.
17572-
if (const RecordType *RT = T->getAs<RecordType>()) {
17573-
RecordDecl *RD = RT->getDecl();
17574-
17575-
SmallString<16> InsertionText(" ");
17576-
InsertionText += RD->getKindName();
17577-
17578-
Diag(TypeRange.getBegin(),
17579-
getLangOpts().CPlusPlus11 ?
17580-
diag::warn_cxx98_compat_unelaborated_friend_type :
17581-
diag::ext_unelaborated_friend_type)
17582-
<< (unsigned) RD->getTagKind()
17583-
<< T
17584-
<< FixItHint::CreateInsertion(getLocForEndOfToken(FriendLoc),
17585-
InsertionText);
17586-
} else {
17587-
Diag(FriendLoc,
17588-
getLangOpts().CPlusPlus11 ?
17589-
diag::warn_cxx98_compat_nonclass_type_friend :
17590-
diag::ext_nonclass_type_friend)
17591-
<< T
17592-
<< TypeRange;
17593-
}
17594-
} else if (T->getAs<EnumType>()) {
17595-
Diag(FriendLoc,
17596-
getLangOpts().CPlusPlus11 ?
17597-
diag::warn_cxx98_compat_enum_friend :
17598-
diag::ext_enum_friend)
17599-
<< T
17600-
<< TypeRange;
17601-
}
17602-
17603-
// C++11 [class.friend]p3:
17604-
// A friend declaration that does not declare a function shall have one
17605-
// of the following forms:
17606-
// friend elaborated-type-specifier ;
17607-
// friend simple-type-specifier ;
17608-
// friend typename-specifier ;
17609-
if (getLangOpts().CPlusPlus11 && LocStart != FriendLoc)
17610-
Diag(FriendLoc, diag::err_friend_not_first_in_declaration) << T;
17611-
}
17612-
17613-
// If the type specifier in a friend declaration designates a (possibly
17614-
// cv-qualified) class type, that class is declared as a friend; otherwise,
17615-
// the friend declaration is ignored.
17616-
return FriendDecl::Create(Context, CurContext,
17617-
TSInfo->getTypeLoc().getBeginLoc(), TSInfo,
17618-
FriendLoc);
17619-
}
17620-
1762117548
/// Handle a friend tag declaration where the scope specifier was
1762217549
/// templated.
1762317550
DeclResult Sema::ActOnTemplatedFriendTag(
@@ -17755,6 +17682,7 @@ DeclResult Sema::ActOnTemplatedFriendTag(
1775517682
Decl *Sema::ActOnFriendTypeDecl(Scope *S, const DeclSpec &DS,
1775617683
MultiTemplateParamsArg TempParams) {
1775717684
SourceLocation Loc = DS.getBeginLoc();
17685+
SourceLocation FriendLoc = DS.getFriendSpecLoc();
1775817686

1775917687
assert(DS.isFriendSpecified());
1776017688
assert(DS.getStorageClassSpec() == DeclSpec::SCS_unspecified);
@@ -17766,9 +17694,10 @@ Decl *Sema::ActOnFriendTypeDecl(Scope *S, const DeclSpec &DS,
1776617694
// friend simple-type-specifier ;
1776717695
// friend typename-specifier ;
1776817696
//
17769-
// Any declaration with a type qualifier does not have that form. (It's
17770-
// legal to specify a qualified type as a friend, you just can't write the
17771-
// keywords.)
17697+
// If the friend keyword isn't first, or if the declarations has any type
17698+
// qualifiers, then the declaration doesn't have that form.
17699+
if (getLangOpts().CPlusPlus11 && !DS.isFriendSpecifiedFirst())
17700+
Diag(FriendLoc, diag::err_friend_not_first_in_declaration);
1777217701
if (DS.getTypeQualifiers()) {
1777317702
if (DS.getTypeQualifiers() & DeclSpec::TQ_const)
1777417703
Diag(DS.getConstSpecLoc(), diag::err_friend_decl_spec) << "const";
@@ -17795,24 +17724,35 @@ Decl *Sema::ActOnFriendTypeDecl(Scope *S, const DeclSpec &DS,
1779517724
if (DiagnoseUnexpandedParameterPack(Loc, TSI, UPPC_FriendDeclaration))
1779617725
return nullptr;
1779717726

17798-
// This is definitely an error in C++98. It's probably meant to
17799-
// be forbidden in C++0x, too, but the specification is just
17800-
// poorly written.
17801-
//
17802-
// The problem is with declarations like the following:
17803-
// template <T> friend A<T>::foo;
17804-
// where deciding whether a class C is a friend or not now hinges
17805-
// on whether there exists an instantiation of A that causes
17806-
// 'foo' to equal C. There are restrictions on class-heads
17807-
// (which we declare (by fiat) elaborated friend declarations to
17808-
// be) that makes this tractable.
17809-
//
17810-
// FIXME: handle "template <> friend class A<T>;", which
17811-
// is possibly well-formed? Who even knows?
17812-
if (TempParams.size() && !T->isElaboratedTypeSpecifier()) {
17813-
Diag(Loc, diag::err_tagless_friend_type_template)
17814-
<< DS.getSourceRange();
17815-
return nullptr;
17727+
if (!T->isElaboratedTypeSpecifier()) {
17728+
if (TempParams.size()) {
17729+
// C++23 [dcl.pre]p5:
17730+
// In a simple-declaration, the optional init-declarator-list can be
17731+
// omitted only when declaring a class or enumeration, that is, when
17732+
// the decl-specifier-seq contains either a class-specifier, an
17733+
// elaborated-type-specifier with a class-key, or an enum-specifier.
17734+
//
17735+
// The declaration of a template-declaration or explicit-specialization
17736+
// is never a member-declaration, so this must be a simple-declaration
17737+
// with no init-declarator-list. Therefore, this is ill-formed.
17738+
Diag(Loc, diag::err_tagless_friend_type_template) << DS.getSourceRange();
17739+
return nullptr;
17740+
} else if (const RecordDecl *RD = T->getAsRecordDecl()) {
17741+
SmallString<16> InsertionText(" ");
17742+
InsertionText += RD->getKindName();
17743+
17744+
Diag(Loc, getLangOpts().CPlusPlus11
17745+
? diag::warn_cxx98_compat_unelaborated_friend_type
17746+
: diag::ext_unelaborated_friend_type)
17747+
<< (unsigned)RD->getTagKind() << T
17748+
<< FixItHint::CreateInsertion(getLocForEndOfToken(FriendLoc),
17749+
InsertionText);
17750+
} else {
17751+
Diag(FriendLoc, getLangOpts().CPlusPlus11
17752+
? diag::warn_cxx98_compat_nonclass_type_friend
17753+
: diag::ext_nonclass_type_friend)
17754+
<< T << DS.getSourceRange();
17755+
}
1781617756
}
1781717757

1781817758
// C++98 [class.friend]p1: A friend of a class is a function
@@ -17828,12 +17768,11 @@ Decl *Sema::ActOnFriendTypeDecl(Scope *S, const DeclSpec &DS,
1782817768

1782917769
Decl *D;
1783017770
if (!TempParams.empty())
17831-
D = FriendTemplateDecl::Create(Context, CurContext, Loc,
17832-
TempParams,
17833-
TSI,
17834-
DS.getFriendSpecLoc());
17771+
D = FriendTemplateDecl::Create(Context, CurContext, Loc, TempParams, TSI,
17772+
FriendLoc);
1783517773
else
17836-
D = CheckFriendTypeDecl(Loc, DS.getFriendSpecLoc(), TSI);
17774+
D = FriendDecl::Create(Context, CurContext, TSI->getTypeLoc().getBeginLoc(),
17775+
TSI, FriendLoc);
1783717776

1783817777
if (!D)
1783917778
return nullptr;

clang/lib/Sema/SemaTemplateInstantiateDecl.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,11 +1407,8 @@ Decl *TemplateDeclInstantiator::VisitFriendDecl(FriendDecl *D) {
14071407
if (!InstTy)
14081408
return nullptr;
14091409

1410-
FriendDecl *FD = SemaRef.CheckFriendTypeDecl(D->getBeginLoc(),
1411-
D->getFriendLoc(), InstTy);
1412-
if (!FD)
1413-
return nullptr;
1414-
1410+
FriendDecl *FD = FriendDecl::Create(
1411+
SemaRef.Context, Owner, D->getLocation(), InstTy, D->getFriendLoc());
14151412
FD->setAccess(AS_public);
14161413
FD->setUnsupportedFriend(D->isUnsupportedFriend());
14171414
Owner->addDecl(FD);

clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.type.elab/p3.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ class A1 {
1616
friend union A; // expected-error {{use of 'A' with tag type that does not match previous declaration}}
1717

1818
friend enum A; // expected-error {{use of 'A' with tag type that does not match previous declaration}}
19-
friend enum E;
20-
#if __cplusplus <= 199711L // C++03 or earlier modes
21-
// expected-warning@-2 {{befriending enumeration type 'enum E' is a C++11 extension}}
22-
#endif
19+
// expected-warning@-1 {{cannot be declared as a friend}}
20+
// expected-note@-2 {{remove 'enum' to befriend an enum}}
21+
friend enum E; // expected-warning {{cannot be declared as a friend}}
22+
// expected-note@-1 {{remove 'enum' to befriend an enum}}
2323
};
2424

2525
template <class T> struct B { // expected-note {{previous use is here}}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// RUN: %clang_cc1 -verify %s -std=c++11 -pedantic-errors
2+
3+
enum class E;
4+
5+
template<typename T>
6+
struct A {
7+
enum class F;
8+
};
9+
10+
struct B {
11+
template<typename T>
12+
friend enum A<T>::F; // expected-error {{elaborated enum specifier cannot be declared as a friend}}
13+
// expected-note@-1 {{remove 'enum' to befriend an enum}}
14+
15+
// FIXME: Per [temp.expl.spec]p19, a friend declaration cannot be an explicit specialization
16+
template<>
17+
friend enum A<int>::F; // expected-error {{elaborated enum specifier cannot be declared as a friend}}
18+
// expected-note@-1 {{remove 'enum' to befriend an enum}}
19+
20+
enum class G;
21+
22+
friend enum E; // expected-error {{elaborated enum specifier cannot be declared as a friend}}
23+
// expected-note@-1 {{remove 'enum' to befriend an enum}}
24+
};
25+
26+
template<typename T>
27+
struct C {
28+
friend enum T::G; // expected-error {{elaborated enum specifier cannot be declared as a friend}}
29+
// expected-note@-1 {{remove 'enum' to befriend an enum}}
30+
friend enum A<T>::G; // expected-error {{elaborated enum specifier cannot be declared as a friend}}
31+
// expected-note@-1 {{remove 'enum' to befriend an enum}}
32+
};
33+
34+
struct D {
35+
friend enum B::G; // expected-error {{elaborated enum specifier cannot be declared as a friend}}
36+
// expected-note@-1 {{remove 'enum' to befriend an enum}}
37+
friend enum class B::G; // expected-error {{elaborated enum specifier cannot be declared as a friend}}
38+
// expected-note@-1 {{remove 'enum class' to befriend an enum}}
39+
// expected-error@-2 {{reference to enumeration must use 'enum' not 'enum class'}}
40+
};

0 commit comments

Comments
 (0)