From 46a395d8c41f6009a7fbae51f408c3c6ea2399d3 Mon Sep 17 00:00:00 2001 From: antangelo Date: Tue, 16 Jan 2024 21:06:02 -0500 Subject: [PATCH] [clang] Emit error for invalid friend functions under [temp.friend]p9 (#78083) Emits an error for friend FunctionDecls that either: * are not templates and have a requires clause * are templates, and have a constrained parameter that depends on a template parameter from an enclosing template and are not a definition. For a non-template friend function with a requires clause, if the function is not templated then the original error message indicating that such a function is disallowed is shown instead, as the function will still be rejected if a definition is added. --- clang/docs/ReleaseNotes.rst | 3 ++ .../clang/Basic/DiagnosticSemaKinds.td | 5 +++ clang/lib/Sema/SemaDecl.cpp | 41 +++++++++++++++---- .../CXX/dcl.decl/dcl.decl.general/p4-20.cpp | 2 +- clang/test/SemaTemplate/GH71595.cpp | 8 ++-- clang/test/SemaTemplate/concepts-friends.cpp | 13 ++++++ 6 files changed, 59 insertions(+), 13 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 36d9da9b89bab0..1eba8ab5590c52 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -560,6 +560,9 @@ Improvements to Clang's diagnostics - Clang now diagnoses unexpanded packs within the template argument lists of function template specializations. - Clang now diagnoses attempts to bind a bitfield to an NTTP of a reference type as erroneous converted constant expression and not as a reference to subobject. +- Clang now diagnoses the requirement that non-template friend declarations with requires clauses + and template friend declarations with a constraint that depends on a template parameter from an + enclosing template must be a definition. Improvements to Clang's time-trace diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 414779a7970ab8..991c72cad33cad 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7006,6 +7006,11 @@ def err_member_decl_does_not_match : Error< "does not match any declaration in %1">; def err_friend_decl_with_def_arg_must_be_def : Error< "friend declaration specifying a default argument must be a definition">; +def err_friend_decl_with_enclosing_temp_constraint_must_be_def : Error< + "friend declaration with a constraint that depends on an enclosing " + "template parameter must be a definition">; +def err_non_temp_friend_decl_with_requires_clause_must_be_def : Error< + "non-template friend declaration with a requires clause must be a definition">; def err_friend_decl_with_def_arg_redeclared : Error< "friend declaration specifying a default argument must be the only declaration">; def err_friend_decl_does_not_match : Error< diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index c6d00ddf5c1088..bd38298a1481fa 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -10846,9 +10846,19 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC, // Precalculate whether this is a friend function template with a constraint // that depends on an enclosing template, per [temp.friend]p9. if (isFriend && FunctionTemplate && - FriendConstraintsDependOnEnclosingTemplate(NewFD)) + FriendConstraintsDependOnEnclosingTemplate(NewFD)) { NewFD->setFriendConstraintRefersToEnclosingTemplate(true); + // C++ [temp.friend]p9: + // A friend function template with a constraint that depends on a + // template parameter from an enclosing template shall be a definition. + if (!D.isFunctionDefinition()) { + Diag(NewFD->getBeginLoc(), + diag::err_friend_decl_with_enclosing_temp_constraint_must_be_def); + NewFD->setInvalidDecl(); + } + } + if (FunctionTemplate) { if (NewFD->isInvalidDecl()) FunctionTemplate->setInvalidDecl(); @@ -12065,11 +12075,12 @@ bool Sema::CheckFunctionDeclaration(Scope *S, FunctionDecl *NewFD, checkThisInStaticMemberFunctionType(Method); } - // C++20: dcl.decl.general p4: - // The optional requires-clause ([temp.pre]) in an init-declarator or - // member-declarator shall be present only if the declarator declares a - // templated function ([dcl.fct]). if (Expr *TRC = NewFD->getTrailingRequiresClause()) { + // C++20: dcl.decl.general p4: + // The optional requires-clause ([temp.pre]) in an init-declarator or + // member-declarator shall be present only if the declarator declares a + // templated function ([dcl.fct]). + // // [temp.pre]/8: // An entity is templated if it is // - a template, @@ -12087,15 +12098,29 @@ bool Sema::CheckFunctionDeclaration(Scope *S, FunctionDecl *NewFD, // templated. A templated variable is a variable template or a variable // that is templated. - if (!NewFD->getDescribedFunctionTemplate() && // -a template - // defined... in a templated entity + bool IsTemplate = NewFD->getDescribedFunctionTemplate(); + bool IsFriend = NewFD->getFriendObjectKind(); + if (!IsTemplate && // -a template + // defined... in a templated entity !(DeclIsDefn && NewFD->isTemplated()) && // a member of a templated entity !(isa(NewFD) && NewFD->isTemplated()) && // Don't complain about instantiations, they've already had these // rules + others enforced. - !NewFD->isTemplateInstantiation()) { + !NewFD->isTemplateInstantiation() && + // If the function violates [temp.friend]p9 because it is missing + // a definition, and adding a definition would make it templated, + // then let that error take precedence. + !(!DeclIsDefn && IsFriend && NewFD->isTemplated())) { Diag(TRC->getBeginLoc(), diag::err_constrained_non_templated_function); + } else if (!DeclIsDefn && !IsTemplate && IsFriend && + !NewFD->isTemplateInstantiation()) { + // C++ [temp.friend]p9: + // A non-template friend declaration with a requires-clause shall be a + // definition. + Diag(NewFD->getBeginLoc(), + diag::err_non_temp_friend_decl_with_requires_clause_must_be_def); + NewFD->setInvalidDecl(); } } diff --git a/clang/test/CXX/dcl.decl/dcl.decl.general/p4-20.cpp b/clang/test/CXX/dcl.decl/dcl.decl.general/p4-20.cpp index 83ec78dac9fe3f..c7596218db5379 100644 --- a/clang/test/CXX/dcl.decl/dcl.decl.general/p4-20.cpp +++ b/clang/test/CXX/dcl.decl/dcl.decl.general/p4-20.cpp @@ -27,7 +27,7 @@ auto *p = new void(*)(char) namespace GH61748 { template struct S { - // expected-error@+1 {{non-templated function cannot have a requires clause}} + // expected-error@+1 {{non-template friend declaration with a requires clause must be a definition}} friend void declared_friend() requires(sizeof(T) > 1); // OK, is a definition. friend void defined_friend() requires(sizeof(T) > 1){} diff --git a/clang/test/SemaTemplate/GH71595.cpp b/clang/test/SemaTemplate/GH71595.cpp index 7d34d1bf054e4b..daec9410e547a6 100644 --- a/clang/test/SemaTemplate/GH71595.cpp +++ b/clang/test/SemaTemplate/GH71595.cpp @@ -18,17 +18,17 @@ void f() { template class temp { template T> - friend void g(); + friend void g(); // expected-error {{friend declaration with a constraint that depends on an enclosing template parameter must be a definition}} - temp(); // expected-note {{implicitly declared private here}} + temp(); }; template> T> void g() { - auto v = temp(); // expected-error {{calling a private constructor of class 'temp'}} + auto v = temp(); } void h() { f(); - g(); // expected-note {{in instantiation of function template specialization 'g' requested here}} + g(); } diff --git a/clang/test/SemaTemplate/concepts-friends.cpp b/clang/test/SemaTemplate/concepts-friends.cpp index 5c4609520a3c7e..255b0858917fb6 100644 --- a/clang/test/SemaTemplate/concepts-friends.cpp +++ b/clang/test/SemaTemplate/concepts-friends.cpp @@ -2,6 +2,7 @@ template concept constraint = false; + namespace temp_friend_9 { // A non-template friend declaration with a requires-clause shall be a // definition. ...Such a constrained friend function ... does not declare the @@ -11,6 +12,14 @@ struct NonTemplateFriend { friend void foo() requires true {} + + friend void baz() // expected-error {{non-template friend declaration with a requires clause must be a definition}} + requires true; +}; + +struct TempP9NotShownIfFunctionWouldBeInvalidAnyway { + friend void foo() + requires true; // expected-error {{non-templated function cannot have a requires clause}} }; // A friend function template with a constraint that depends on a template @@ -19,6 +28,10 @@ struct NonTemplateFriend { // function template as a declaration in any other scope. template struct TemplateFromEnclosing { + template + friend void bar2() // expected-error {{friend declaration with a constraint that depends on an enclosing template parameter must be a definition}} + requires constraint; + template friend void foo() requires constraint