diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index b5c6fbd32cc91..cd8a82f281f52 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -149,6 +149,7 @@ Improvements to Clang's diagnostics prints. - Clang now diagnoses member template declarations with multiple declarators. +- Clang now diagnoses use of the ``template`` keyword after declarative nested name specifiers. Improvements to Clang's time-trace ---------------------------------- diff --git a/clang/include/clang/AST/TypeLoc.h b/clang/include/clang/AST/TypeLoc.h index b1b38f882d19f..32028e877fc8d 100644 --- a/clang/include/clang/AST/TypeLoc.h +++ b/clang/include/clang/AST/TypeLoc.h @@ -189,6 +189,9 @@ class TypeLoc { /// pointer types, but not through decltype or typedefs. AutoTypeLoc getContainedAutoTypeLoc() const; + /// Get the SourceLocation of the template keyword (if any). + SourceLocation getTemplateKeywordLoc() const; + /// Initializes this to state that every location in this /// type is the given location. /// @@ -1691,7 +1694,7 @@ class TemplateSpecializationTypeLoc : } void initializeLocal(ASTContext &Context, SourceLocation Loc) { - setTemplateKeywordLoc(Loc); + setTemplateKeywordLoc(SourceLocation()); setTemplateNameLoc(Loc); setLAngleLoc(Loc); setRAngleLoc(Loc); diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 21a767cfc5e5c..f76e7a3392183 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -8247,6 +8247,9 @@ def err_invalid_declarator_in_block : Error< "definition or redeclaration of %0 not allowed inside a block">; def err_not_tag_in_scope : Error< "no %select{struct|interface|union|class|enum}0 named %1 in %2">; +def ext_template_after_declarative_nns : ExtWarn< + "'template' cannot be used after a declarative nested name specifier">, + InGroup>; def err_no_typeid_with_fno_rtti : Error< "use of typeid requires -frtti">; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 7885cf6728b54..3c26003b5bda7 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2960,7 +2960,8 @@ class Sema final { bool DiagnoseClassNameShadow(DeclContext *DC, DeclarationNameInfo Info); bool diagnoseQualifiedDeclaration(CXXScopeSpec &SS, DeclContext *DC, DeclarationName Name, SourceLocation Loc, - bool IsTemplateId); + TemplateIdAnnotation *TemplateId, + bool IsMemberSpecialization); void diagnoseIgnoredQualifiers(unsigned DiagID, unsigned Quals, SourceLocation FallbackLoc, diff --git a/clang/lib/AST/TypeLoc.cpp b/clang/lib/AST/TypeLoc.cpp index 66732bba18e2d..b0acb18230875 100644 --- a/clang/lib/AST/TypeLoc.cpp +++ b/clang/lib/AST/TypeLoc.cpp @@ -738,3 +738,12 @@ AutoTypeLoc TypeLoc::getContainedAutoTypeLoc() const { return AutoTypeLoc(); return Res.getAs(); } + +SourceLocation TypeLoc::getTemplateKeywordLoc() const { + if (const auto TSTL = getAsAdjusted()) + return TSTL.getTemplateKeywordLoc(); + if (const auto DTSTL = + getAsAdjusted()) + return DTSTL.getTemplateKeywordLoc(); + return SourceLocation(); +} diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index a186253954f68..94696d8e482e5 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -6663,12 +6663,14 @@ void Parser::ParseDirectDeclarator(Declarator &D) { } bool HadScope = D.getCXXScopeSpec().isValid(); + SourceLocation TemplateKWLoc; if (ParseUnqualifiedId(D.getCXXScopeSpec(), /*ObjectType=*/nullptr, /*ObjectHadErrors=*/false, /*EnteringContext=*/true, /*AllowDestructorName=*/true, AllowConstructorName, - AllowDeductionGuide, nullptr, D.getName()) || + AllowDeductionGuide, &TemplateKWLoc, + D.getName()) || // Once we're past the identifier, if the scope was bad, mark the // whole declarator bad. D.getCXXScopeSpec().isInvalid()) { diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index de6067ba13cbe..481d952d2389b 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -6196,13 +6196,17 @@ bool Sema::DiagnoseClassNameShadow(DeclContext *DC, /// /// \param Loc The location of the name of the entity being declared. /// -/// \param IsTemplateId Whether the name is a (simple-)template-id, and thus -/// we're declaring an explicit / partial specialization / instantiation. +/// \param IsMemberSpecialization Whether we are declaring a member +/// specialization. +/// +/// \param TemplateId The template-id, if any. /// /// \returns true if we cannot safely recover from this error, false otherwise. bool Sema::diagnoseQualifiedDeclaration(CXXScopeSpec &SS, DeclContext *DC, DeclarationName Name, - SourceLocation Loc, bool IsTemplateId) { + SourceLocation Loc, + TemplateIdAnnotation *TemplateId, + bool IsMemberSpecialization) { DeclContext *Cur = CurContext; while (isa(Cur) || isa(Cur)) Cur = Cur->getParent(); @@ -6231,7 +6235,7 @@ bool Sema::diagnoseQualifiedDeclaration(CXXScopeSpec &SS, DeclContext *DC, // Check whether the qualifying scope encloses the scope of the original // declaration. For a template-id, we perform the checks in // CheckTemplateSpecializationScope. - if (!Cur->Encloses(DC) && !IsTemplateId) { + if (!Cur->Encloses(DC) && !(TemplateId || IsMemberSpecialization)) { if (Cur->isRecord()) Diag(Loc, diag::err_member_qualification) << Name << SS.getRange(); @@ -6277,12 +6281,32 @@ bool Sema::diagnoseQualifiedDeclaration(CXXScopeSpec &SS, DeclContext *DC, return false; } + // C++23 [temp.names]p5: + // The keyword template shall not appear immediately after a declarative + // nested-name-specifier. + // + // First check the template-id (if any), and then check each component of the + // nested-name-specifier in reverse order. + // + // FIXME: nested-name-specifiers in friend declarations are declarative, + // but we don't call diagnoseQualifiedDeclaration for them. We should. + if (TemplateId && TemplateId->TemplateKWLoc.isValid()) + Diag(Loc, diag::ext_template_after_declarative_nns) + << FixItHint::CreateRemoval(TemplateId->TemplateKWLoc); + + NestedNameSpecifierLoc SpecLoc(SS.getScopeRep(), SS.location_data()); + while (SpecLoc.getPrefix()) { + if (SpecLoc.getNestedNameSpecifier()->getKind() == + NestedNameSpecifier::TypeSpecWithTemplate) + Diag(Loc, diag::ext_template_after_declarative_nns) + << FixItHint::CreateRemoval( + SpecLoc.getTypeLoc().getTemplateKeywordLoc()); + + SpecLoc = SpecLoc.getPrefix(); + } // C++11 [dcl.meaning]p1: // [...] "The nested-name-specifier of the qualified declarator-id shall // not begin with a decltype-specifer" - NestedNameSpecifierLoc SpecLoc(SS.getScopeRep(), SS.location_data()); - while (SpecLoc.getPrefix()) - SpecLoc = SpecLoc.getPrefix(); if (isa_and_nonnull( SpecLoc.getNestedNameSpecifier()->getAsType())) Diag(Loc, diag::err_decltype_in_declarator) @@ -6350,9 +6374,13 @@ NamedDecl *Sema::HandleDeclarator(Scope *S, Declarator &D, return nullptr; } if (!D.getDeclSpec().isFriendSpecified()) { - if (diagnoseQualifiedDeclaration( - D.getCXXScopeSpec(), DC, Name, D.getIdentifierLoc(), - D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId)) { + TemplateIdAnnotation *TemplateId = + D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId + ? D.getName().TemplateId + : nullptr; + if (diagnoseQualifiedDeclaration(D.getCXXScopeSpec(), DC, Name, + D.getIdentifierLoc(), TemplateId, + /*IsMemberSpecialization=*/false)) { if (DC->isRecord()) return nullptr; @@ -17957,6 +17985,7 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc, // nested-name-specifier against the current context. if ((TUK == TUK_Definition || TUK == TUK_Declaration) && diagnoseQualifiedDeclaration(SS, DC, OrigName, Loc, + /*TemplateId=*/nullptr, isMemberSpecialization)) Invalid = true; diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 5adc262cd6bc9..ab8a967b06a45 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -3621,14 +3621,18 @@ Sema::ActOnCXXMemberDeclarator(Scope *S, AccessSpecifier AS, Declarator &D, // class X { // int X::member; // }; - if (DeclContext *DC = computeDeclContext(SS, false)) + if (DeclContext *DC = computeDeclContext(SS, false)) { + TemplateIdAnnotation *TemplateId = + D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId + ? D.getName().TemplateId + : nullptr; diagnoseQualifiedDeclaration(SS, DC, Name, D.getIdentifierLoc(), - D.getName().getKind() == - UnqualifiedIdKind::IK_TemplateId); - else + TemplateId, + /*IsMemberSpecialization=*/false); + } else { Diag(D.getIdentifierLoc(), diag::err_member_qualification) << Name << SS.getRange(); - + } SS.clear(); } diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index ed4201bc6b1f7..cf781e0e1bf3f 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -1890,8 +1890,12 @@ DeclResult Sema::CheckClassTemplate( ContextRAII SavedContext(*this, SemanticContext); if (RebuildTemplateParamsInCurrentInstantiation(TemplateParams)) Invalid = true; - } else if (TUK != TUK_Friend && TUK != TUK_Reference) - diagnoseQualifiedDeclaration(SS, SemanticContext, Name, NameLoc, false); + } + + if (TUK != TUK_Friend && TUK != TUK_Reference) + diagnoseQualifiedDeclaration(SS, SemanticContext, Name, NameLoc, + /*TemplateId-*/ nullptr, + /*IsMemberSpecialization*/ false); LookupQualifiedName(Previous, SemanticContext); } else { @@ -8826,6 +8830,15 @@ DeclResult Sema::ActOnClassTemplateSpecialization( bool isMemberSpecialization = false; bool isPartialSpecialization = false; + if (SS.isSet()) { + if (TUK != TUK_Reference && TUK != TUK_Friend && + diagnoseQualifiedDeclaration(SS, ClassTemplate->getDeclContext(), + ClassTemplate->getDeclName(), + TemplateNameLoc, &TemplateId, + /*IsMemberSpecialization=*/false)) + return true; + } + // Check the validity of the template headers that introduce this // template. // FIXME: We probably shouldn't complain about these headers for diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 4dc36a8a5f4f0..3ed17c3360a83 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -4394,7 +4394,8 @@ NestedNameSpecifierLoc TreeTransform::TransformNestedNameSpecifierLoc( SS.Adopt(ETL.getQualifierLoc()); TL = ETL.getNamedTypeLoc(); } - SS.Extend(SemaRef.Context, /*FIXME:*/ SourceLocation(), TL, + + SS.Extend(SemaRef.Context, TL.getTemplateKeywordLoc(), TL, Q.getLocalEndLoc()); break; } diff --git a/clang/test/CXX/drs/dr23xx.cpp b/clang/test/CXX/drs/dr23xx.cpp index 265b51aff8ae6..3f8c476427eba 100644 --- a/clang/test/CXX/drs/dr23xx.cpp +++ b/clang/test/CXX/drs/dr23xx.cpp @@ -182,8 +182,8 @@ struct Bad2 { int a, b; }; } // namespace dr2386 namespace std { template struct tuple_size; -template <> struct std::tuple_size {}; -template <> struct std::tuple_size { +template <> struct tuple_size {}; +template <> struct tuple_size { static const int value = 42; }; } // namespace std diff --git a/clang/test/CXX/drs/dr7xx.cpp b/clang/test/CXX/drs/dr7xx.cpp index 926bff1cc479c..2cbdc218ab7b5 100644 --- a/clang/test/CXX/drs/dr7xx.cpp +++ b/clang/test/CXX/drs/dr7xx.cpp @@ -105,7 +105,8 @@ namespace dr727 { // dr727: partial // expected-note@#dr727-N {{explicitly specialized declaration is here}} template<> struct A::C; - // expected-error@-1 {{class template specialization of 'C' not in class 'A' or an enclosing namespace}} + // expected-error@-1 {{non-friend class member 'C' cannot have a qualified name}} + // expected-error@-2 {{class template specialization of 'C' not in class 'A' or an enclosing namespace}} // expected-note@#dr727-C {{explicitly specialized declaration is here}} template<> void A::f(); // expected-error@-1 {{o function template matches function template specialization 'f'}} @@ -116,7 +117,8 @@ namespace dr727 { // dr727: partial // expected-note@#dr727-N {{explicitly specialized declaration is here}} template struct A::C; - // expected-error@-1 {{class template partial specialization of 'C' not in class 'A' or an enclosing namespace}} + // expected-error@-1 {{non-friend class member 'C' cannot have a qualified name}} + // expected-error@-2 {{class template partial specialization of 'C' not in class 'A' or an enclosing namespace}} // expected-note@#dr727-C {{explicitly specialized declaration is here}} template static int A::N; // expected-error@-1 {{non-friend class member 'N' cannot have a qualified name}} diff --git a/clang/test/CXX/temp/temp.decls/temp.class/temp.mem.func/p1.cpp b/clang/test/CXX/temp/temp.decls/temp.class/temp.mem.func/p1.cpp index 17645639fb82f..8eeb610f79469 100644 --- a/clang/test/CXX/temp/temp.decls/temp.class/temp.mem.func/p1.cpp +++ b/clang/test/CXX/temp/temp.decls/temp.class/temp.mem.func/p1.cpp @@ -75,7 +75,7 @@ struct X1 { template template -void X1::template B::f() { } +void X1::template B::f() { } // expected-warning{{'template' cannot be used after a declarative}} // PR5527 template