Skip to content

Commit 29bd78b

Browse files
authored
[Clang][Sema] Diagnose friend function specialization definitions (#72863)
Per [[class.friend]p6](http://eel.is/c++draft/class.friend#6) a friend function shall not be defined if its name isn't unqualified. A _template-id_ is not a name, meaning that a friend function specialization does not meet this criteria and cannot be defined. GCC, MSVC, and EDG all consider friend function specialization definitions to be invalid de facto explicit specializations and diagnose them as such. Instantiating a dependent friend function specialization definition [currently sets off an assert](https://godbolt.org/z/Krqdq95hY) in `FunctionDecl::setFunctionTemplateSpecialization`. This happens because we do not set the `TemplateSpecializationKind` of the `FunctionDecl` created by template argument deduction to `TSK_ExplicitSpecialization` for friend functions [here](https://github.com/llvm/llvm-project/blob/main/clang/lib/Sema/SemaTemplate.cpp#L9600). I changed the assert condition because I believe this is the correct behavior.
1 parent 9071a15 commit 29bd78b

File tree

7 files changed

+55
-41
lines changed

7 files changed

+55
-41
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,7 @@ Improvements to Clang's diagnostics
513513
48 | static_assert(1 << 4 == 15);
514514
| ~~~~~~~^~~~~
515515
516+
- Clang now diagnoses definitions of friend function specializations, e.g. ``friend void f<>(int) {}``.
516517

517518
Improvements to Clang's time-trace
518519
----------------------------------

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,6 +1669,8 @@ def err_qualified_friend_def : Error<
16691669
"friend function definition cannot be qualified with '%0'">;
16701670
def err_friend_def_in_local_class : Error<
16711671
"friend function cannot be defined in a local class">;
1672+
def err_friend_specialization_def : Error<
1673+
"friend function specialization cannot be defined">;
16721674
def err_friend_not_first_in_declaration : Error<
16731675
"'friend' must appear first in a non-function declaration">;
16741676
def err_using_decl_friend : Error<

clang/lib/AST/Decl.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4150,6 +4150,7 @@ FunctionDecl::setFunctionTemplateSpecialization(ASTContext &C,
41504150
assert(TSK != TSK_Undeclared &&
41514151
"Must specify the type of function template specialization");
41524152
assert((TemplateOrSpecialization.isNull() ||
4153+
getFriendObjectKind() != FOK_None ||
41534154
TSK == TSK_ExplicitSpecialization) &&
41544155
"Member specialization must be an explicit specialization");
41554156
FunctionTemplateSpecializationInfo *Info =

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17879,6 +17879,8 @@ NamedDecl *Sema::ActOnFriendFunctionDecl(Scope *S, Declarator &D,
1787917879
LookupResult Previous(*this, NameInfo, LookupOrdinaryName,
1788017880
ForExternalRedeclaration);
1788117881

17882+
bool isTemplateId = D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId;
17883+
1788217884
// There are five cases here.
1788317885
// - There's no scope specifier and we're in a local class. Only look
1788417886
// for functions declared in the immediately-enclosing block scope.
@@ -17916,14 +17918,6 @@ NamedDecl *Sema::ActOnFriendFunctionDecl(Scope *S, Declarator &D,
1791617918
}
1791717919
adjustContextForLocalExternDecl(DC);
1791817920

17919-
// C++ [class.friend]p6:
17920-
// A function can be defined in a friend declaration of a class if and
17921-
// only if the class is a non-local class (9.8), the function name is
17922-
// unqualified, and the function has namespace scope.
17923-
if (D.isFunctionDefinition()) {
17924-
Diag(NameInfo.getBeginLoc(), diag::err_friend_def_in_local_class);
17925-
}
17926-
1792717921
// - There's no scope specifier, in which case we just go to the
1792817922
// appropriate scope and look for a function or function template
1792917923
// there as appropriate.
@@ -17934,8 +17928,6 @@ NamedDecl *Sema::ActOnFriendFunctionDecl(Scope *S, Declarator &D,
1793417928
// elaborated-type-specifier, the lookup to determine whether
1793517929
// the entity has been previously declared shall not consider
1793617930
// any scopes outside the innermost enclosing namespace.
17937-
bool isTemplateId =
17938-
D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId;
1793917931

1794017932
// Find the appropriate context according to the above.
1794117933
DC = CurContext;
@@ -17988,39 +17980,12 @@ NamedDecl *Sema::ActOnFriendFunctionDecl(Scope *S, Declarator &D,
1798817980
diag::warn_cxx98_compat_friend_is_member :
1798917981
diag::err_friend_is_member);
1799017982

17991-
if (D.isFunctionDefinition()) {
17992-
// C++ [class.friend]p6:
17993-
// A function can be defined in a friend declaration of a class if and
17994-
// only if the class is a non-local class (9.8), the function name is
17995-
// unqualified, and the function has namespace scope.
17996-
//
17997-
// FIXME: We should only do this if the scope specifier names the
17998-
// innermost enclosing namespace; otherwise the fixit changes the
17999-
// meaning of the code.
18000-
SemaDiagnosticBuilder DB
18001-
= Diag(SS.getRange().getBegin(), diag::err_qualified_friend_def);
18002-
18003-
DB << SS.getScopeRep();
18004-
if (DC->isFileContext())
18005-
DB << FixItHint::CreateRemoval(SS.getRange());
18006-
SS.clear();
18007-
}
18008-
1800917983
// - There's a scope specifier that does not match any template
1801017984
// parameter lists, in which case we use some arbitrary context,
1801117985
// create a method or method template, and wait for instantiation.
1801217986
// - There's a scope specifier that does match some template
1801317987
// parameter lists, which we don't handle right now.
1801417988
} else {
18015-
if (D.isFunctionDefinition()) {
18016-
// C++ [class.friend]p6:
18017-
// A function can be defined in a friend declaration of a class if and
18018-
// only if the class is a non-local class (9.8), the function name is
18019-
// unqualified, and the function has namespace scope.
18020-
Diag(SS.getRange().getBegin(), diag::err_qualified_friend_def)
18021-
<< SS.getScopeRep();
18022-
}
18023-
1802417989
DC = CurContext;
1802517990
assert(isa<CXXRecordDecl>(DC) && "friend declaration not in class?");
1802617991
}
@@ -18105,6 +18070,38 @@ NamedDecl *Sema::ActOnFriendFunctionDecl(Scope *S, Declarator &D,
1810518070
else
1810618071
FD = cast<FunctionDecl>(ND);
1810718072

18073+
// C++ [class.friend]p6:
18074+
// A function may be defined in a friend declaration of a class if and
18075+
// only if the class is a non-local class, and the function name is
18076+
// unqualified.
18077+
if (D.isFunctionDefinition()) {
18078+
// Qualified friend function definition.
18079+
if (SS.isNotEmpty()) {
18080+
// FIXME: We should only do this if the scope specifier names the
18081+
// innermost enclosing namespace; otherwise the fixit changes the
18082+
// meaning of the code.
18083+
SemaDiagnosticBuilder DB =
18084+
Diag(SS.getRange().getBegin(), diag::err_qualified_friend_def);
18085+
18086+
DB << SS.getScopeRep();
18087+
if (DC->isFileContext())
18088+
DB << FixItHint::CreateRemoval(SS.getRange());
18089+
18090+
// Friend function defined in a local class.
18091+
} else if (FunctionContainingLocalClass) {
18092+
Diag(NameInfo.getBeginLoc(), diag::err_friend_def_in_local_class);
18093+
18094+
// Per [basic.pre]p4, a template-id is not a name. Therefore, if we have
18095+
// a template-id, the function name is not unqualified because these is
18096+
// no name. While the wording requires some reading in-between the
18097+
// lines, GCC, MSVC, and EDG all consider a friend function
18098+
// specialization definitions // to be de facto explicit specialization
18099+
// and diagnose them as such.
18100+
} else if (isTemplateId) {
18101+
Diag(NameInfo.getBeginLoc(), diag::err_friend_specialization_def);
18102+
}
18103+
}
18104+
1810818105
// C++11 [dcl.fct.default]p4: If a friend declaration specifies a
1810918106
// default argument expression, that declaration shall be a definition
1811018107
// and shall be the only declaration of the function or function

clang/test/CXX/class.access/class.friend/p6.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,16 @@ void local() {
2222
friend void f() { } // expected-error{{friend function cannot be defined in a local class}}
2323
};
2424
}
25+
26+
template<typename T> void f3(T);
27+
28+
namespace N {
29+
template<typename T> void f4(T);
30+
}
31+
32+
template<typename T> struct A {
33+
friend void f3(T) {}
34+
friend void f3<T>(T) {} // expected-error{{friend function specialization cannot be defined}}
35+
friend void N::f4(T) {} // expected-error{{friend function definition cannot be qualified with 'N::'}}
36+
friend void N::f4<T>(T) {} // expected-error{{friend function definition cannot be qualified with 'N::'}}
37+
};

clang/test/CXX/temp/temp.decls/temp.friend/p1.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ template <typename T> struct Num {
1717
for (U count = n.count_; count; --count)
1818
x += a;
1919
return x;
20-
}
20+
}
2121
};
2222

2323
friend Num operator+(const Num &a, const Num &b) {
@@ -145,7 +145,7 @@ namespace test5 {
145145

146146
namespace Dependent {
147147
template<typename T, typename Traits> class X;
148-
template<typename T, typename Traits>
148+
template<typename T, typename Traits>
149149
X<T, Traits> operator+(const X<T, Traits>&, const T*);
150150

151151
template<typename T, typename Traits> class X {
@@ -249,7 +249,7 @@ namespace test11 {
249249
};
250250

251251
template struct Foo::IteratorImpl<int>;
252-
template struct Foo::IteratorImpl<long>;
252+
template struct Foo::IteratorImpl<long>;
253253
}
254254

255255
// PR6827

clang/test/SemaCXX/friend.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ namespace test9 {
162162
class C {
163163
};
164164
struct A {
165-
friend void C::f(int, int, int) {} // expected-error {{friend function definition cannot be qualified with 'C::'}}
165+
friend void C::f(int, int, int) {} // expected-error {{friend declaration of 'f' does not match any declaration in 'test9::C'}}
166166
};
167167
}
168168

0 commit comments

Comments
 (0)