Skip to content

Commit 1156bbc

Browse files
authored
[Clang][Sema] Diagnose use of template keyword after declarative nested-name-specifiers (#78595)
According to [temp.names] p5: > The keyword template shall not appear immediately after a declarative nested-name-specifier. [expr.prim.id.qual] p2 defines a declarative nested-name-specifier as follows: > A nested-name-specifier is declarative if it is part of > - a class-head-name, > - an enum-head-name, > - a qualified-id that is the id-expression of a declarator-id, or > - a declarative nested-name-specifier. Note: I believe this definition is defective as it doesn't include _nested-name-specifiers_ appearing in _elaborated-type-specifiers_ that declare partial/explicit specializations and explicit instantiations. See my post to the core reflector. Minus a few bugs that are addressed by this PR, this is how we implement it. This means that declarations like: ``` template<typename> struct A { template<typename> struct B { void f(); }; }; template<typename T> template<typename U> void A<T>::template B<U>::f() { } // error: 'template' cannot be used after a declarative nested name specifier ``` are ill-formed. This PR add diagnostics for such declarations. The name of the diagnostic group is `template-in-declaration-name`. Regarding the aforementioned "few bugs that are addressed by this PR" in order to correctly implement this: - `CheckClassTemplate` did not call `diagnoseQualifiedDeclaration` when the semantic context was dependent. This allowed for constructs like: ``` struct A { template<typename T> struct B { template<typename U> struct C; }; }; template<typename T> template<typename U> struct decltype(A())::B<T>::C { }; ``` - `ActOnClassTemplateSpecialization` did not call `diagnoseQualifiedDeclaration` at all, allowing for qualified partial/explicit specializations at class scope and other related nonsense - `TreeTransform::TransformNestedNameSpecifierLoc` would rebuild a `NestedNameSpecifier::TypeSpecWithTemplate` as a `NestedNameSpecifier::TypeSpec` - `TemplateSpecializationTypeLoc::initializeLocal` would set the `template` keyword `SourceLocation` to the provided `Loc` parameter, which would result in a `TemplateSpecializationTypeLoc` obtained via `ASTContext::getTrivialTypeSourceInfo` being displayed as always having a `template` prefix (since the presence of the keyword is not stored anywhere else).
1 parent faeb3d1 commit 1156bbc

File tree

17 files changed

+306
-32
lines changed

17 files changed

+306
-32
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ Improvements to Clang's diagnostics
149149
prints.
150150

151151
- Clang now diagnoses member template declarations with multiple declarators.
152+
- Clang now diagnoses use of the ``template`` keyword after declarative nested name specifiers.
152153

153154
Improvements to Clang's time-trace
154155
----------------------------------

clang/include/clang/AST/TypeLoc.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ class TypeLoc {
189189
/// pointer types, but not through decltype or typedefs.
190190
AutoTypeLoc getContainedAutoTypeLoc() const;
191191

192+
/// Get the SourceLocation of the template keyword (if any).
193+
SourceLocation getTemplateKeywordLoc() const;
194+
192195
/// Initializes this to state that every location in this
193196
/// type is the given location.
194197
///
@@ -1691,7 +1694,7 @@ class TemplateSpecializationTypeLoc :
16911694
}
16921695

16931696
void initializeLocal(ASTContext &Context, SourceLocation Loc) {
1694-
setTemplateKeywordLoc(Loc);
1697+
setTemplateKeywordLoc(SourceLocation());
16951698
setTemplateNameLoc(Loc);
16961699
setLAngleLoc(Loc);
16971700
setRAngleLoc(Loc);

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8247,6 +8247,9 @@ def err_invalid_declarator_in_block : Error<
82478247
"definition or redeclaration of %0 not allowed inside a block">;
82488248
def err_not_tag_in_scope : Error<
82498249
"no %select{struct|interface|union|class|enum}0 named %1 in %2">;
8250+
def ext_template_after_declarative_nns : ExtWarn<
8251+
"'template' cannot be used after a declarative nested name specifier">,
8252+
InGroup<DiagGroup<"template-in-declaration-name">>;
82508253

82518254
def err_no_typeid_with_fno_rtti : Error<
82528255
"use of typeid requires -frtti">;

clang/include/clang/Sema/Sema.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2960,7 +2960,8 @@ class Sema final {
29602960
bool DiagnoseClassNameShadow(DeclContext *DC, DeclarationNameInfo Info);
29612961
bool diagnoseQualifiedDeclaration(CXXScopeSpec &SS, DeclContext *DC,
29622962
DeclarationName Name, SourceLocation Loc,
2963-
bool IsTemplateId);
2963+
TemplateIdAnnotation *TemplateId,
2964+
bool IsMemberSpecialization);
29642965
void
29652966
diagnoseIgnoredQualifiers(unsigned DiagID, unsigned Quals,
29662967
SourceLocation FallbackLoc,

clang/lib/AST/TypeLoc.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,3 +738,12 @@ AutoTypeLoc TypeLoc::getContainedAutoTypeLoc() const {
738738
return AutoTypeLoc();
739739
return Res.getAs<AutoTypeLoc>();
740740
}
741+
742+
SourceLocation TypeLoc::getTemplateKeywordLoc() const {
743+
if (const auto TSTL = getAsAdjusted<TemplateSpecializationTypeLoc>())
744+
return TSTL.getTemplateKeywordLoc();
745+
if (const auto DTSTL =
746+
getAsAdjusted<DependentTemplateSpecializationTypeLoc>())
747+
return DTSTL.getTemplateKeywordLoc();
748+
return SourceLocation();
749+
}

clang/lib/Parse/ParseDecl.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6663,12 +6663,14 @@ void Parser::ParseDirectDeclarator(Declarator &D) {
66636663
}
66646664

66656665
bool HadScope = D.getCXXScopeSpec().isValid();
6666+
SourceLocation TemplateKWLoc;
66666667
if (ParseUnqualifiedId(D.getCXXScopeSpec(),
66676668
/*ObjectType=*/nullptr,
66686669
/*ObjectHadErrors=*/false,
66696670
/*EnteringContext=*/true,
66706671
/*AllowDestructorName=*/true, AllowConstructorName,
6671-
AllowDeductionGuide, nullptr, D.getName()) ||
6672+
AllowDeductionGuide, &TemplateKWLoc,
6673+
D.getName()) ||
66726674
// Once we're past the identifier, if the scope was bad, mark the
66736675
// whole declarator bad.
66746676
D.getCXXScopeSpec().isInvalid()) {

clang/lib/Sema/SemaDecl.cpp

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6196,13 +6196,17 @@ bool Sema::DiagnoseClassNameShadow(DeclContext *DC,
61966196
///
61976197
/// \param Loc The location of the name of the entity being declared.
61986198
///
6199-
/// \param IsTemplateId Whether the name is a (simple-)template-id, and thus
6200-
/// we're declaring an explicit / partial specialization / instantiation.
6199+
/// \param IsMemberSpecialization Whether we are declaring a member
6200+
/// specialization.
6201+
///
6202+
/// \param TemplateId The template-id, if any.
62016203
///
62026204
/// \returns true if we cannot safely recover from this error, false otherwise.
62036205
bool Sema::diagnoseQualifiedDeclaration(CXXScopeSpec &SS, DeclContext *DC,
62046206
DeclarationName Name,
6205-
SourceLocation Loc, bool IsTemplateId) {
6207+
SourceLocation Loc,
6208+
TemplateIdAnnotation *TemplateId,
6209+
bool IsMemberSpecialization) {
62066210
DeclContext *Cur = CurContext;
62076211
while (isa<LinkageSpecDecl>(Cur) || isa<CapturedDecl>(Cur))
62086212
Cur = Cur->getParent();
@@ -6231,7 +6235,7 @@ bool Sema::diagnoseQualifiedDeclaration(CXXScopeSpec &SS, DeclContext *DC,
62316235
// Check whether the qualifying scope encloses the scope of the original
62326236
// declaration. For a template-id, we perform the checks in
62336237
// CheckTemplateSpecializationScope.
6234-
if (!Cur->Encloses(DC) && !IsTemplateId) {
6238+
if (!Cur->Encloses(DC) && !(TemplateId || IsMemberSpecialization)) {
62356239
if (Cur->isRecord())
62366240
Diag(Loc, diag::err_member_qualification)
62376241
<< Name << SS.getRange();
@@ -6277,12 +6281,32 @@ bool Sema::diagnoseQualifiedDeclaration(CXXScopeSpec &SS, DeclContext *DC,
62776281
return false;
62786282
}
62796283

6284+
// C++23 [temp.names]p5:
6285+
// The keyword template shall not appear immediately after a declarative
6286+
// nested-name-specifier.
6287+
//
6288+
// First check the template-id (if any), and then check each component of the
6289+
// nested-name-specifier in reverse order.
6290+
//
6291+
// FIXME: nested-name-specifiers in friend declarations are declarative,
6292+
// but we don't call diagnoseQualifiedDeclaration for them. We should.
6293+
if (TemplateId && TemplateId->TemplateKWLoc.isValid())
6294+
Diag(Loc, diag::ext_template_after_declarative_nns)
6295+
<< FixItHint::CreateRemoval(TemplateId->TemplateKWLoc);
6296+
6297+
NestedNameSpecifierLoc SpecLoc(SS.getScopeRep(), SS.location_data());
6298+
while (SpecLoc.getPrefix()) {
6299+
if (SpecLoc.getNestedNameSpecifier()->getKind() ==
6300+
NestedNameSpecifier::TypeSpecWithTemplate)
6301+
Diag(Loc, diag::ext_template_after_declarative_nns)
6302+
<< FixItHint::CreateRemoval(
6303+
SpecLoc.getTypeLoc().getTemplateKeywordLoc());
6304+
6305+
SpecLoc = SpecLoc.getPrefix();
6306+
}
62806307
// C++11 [dcl.meaning]p1:
62816308
// [...] "The nested-name-specifier of the qualified declarator-id shall
62826309
// not begin with a decltype-specifer"
6283-
NestedNameSpecifierLoc SpecLoc(SS.getScopeRep(), SS.location_data());
6284-
while (SpecLoc.getPrefix())
6285-
SpecLoc = SpecLoc.getPrefix();
62866310
if (isa_and_nonnull<DecltypeType>(
62876311
SpecLoc.getNestedNameSpecifier()->getAsType()))
62886312
Diag(Loc, diag::err_decltype_in_declarator)
@@ -6350,9 +6374,13 @@ NamedDecl *Sema::HandleDeclarator(Scope *S, Declarator &D,
63506374
return nullptr;
63516375
}
63526376
if (!D.getDeclSpec().isFriendSpecified()) {
6353-
if (diagnoseQualifiedDeclaration(
6354-
D.getCXXScopeSpec(), DC, Name, D.getIdentifierLoc(),
6355-
D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId)) {
6377+
TemplateIdAnnotation *TemplateId =
6378+
D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId
6379+
? D.getName().TemplateId
6380+
: nullptr;
6381+
if (diagnoseQualifiedDeclaration(D.getCXXScopeSpec(), DC, Name,
6382+
D.getIdentifierLoc(), TemplateId,
6383+
/*IsMemberSpecialization=*/false)) {
63566384
if (DC->isRecord())
63576385
return nullptr;
63586386

@@ -17957,6 +17985,7 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc,
1795717985
// nested-name-specifier against the current context.
1795817986
if ((TUK == TUK_Definition || TUK == TUK_Declaration) &&
1795917987
diagnoseQualifiedDeclaration(SS, DC, OrigName, Loc,
17988+
/*TemplateId=*/nullptr,
1796017989
isMemberSpecialization))
1796117990
Invalid = true;
1796217991

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3621,14 +3621,18 @@ Sema::ActOnCXXMemberDeclarator(Scope *S, AccessSpecifier AS, Declarator &D,
36213621
// class X {
36223622
// int X::member;
36233623
// };
3624-
if (DeclContext *DC = computeDeclContext(SS, false))
3624+
if (DeclContext *DC = computeDeclContext(SS, false)) {
3625+
TemplateIdAnnotation *TemplateId =
3626+
D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId
3627+
? D.getName().TemplateId
3628+
: nullptr;
36253629
diagnoseQualifiedDeclaration(SS, DC, Name, D.getIdentifierLoc(),
3626-
D.getName().getKind() ==
3627-
UnqualifiedIdKind::IK_TemplateId);
3628-
else
3630+
TemplateId,
3631+
/*IsMemberSpecialization=*/false);
3632+
} else {
36293633
Diag(D.getIdentifierLoc(), diag::err_member_qualification)
36303634
<< Name << SS.getRange();
3631-
3635+
}
36323636
SS.clear();
36333637
}
36343638

clang/lib/Sema/SemaTemplate.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1890,8 +1890,12 @@ DeclResult Sema::CheckClassTemplate(
18901890
ContextRAII SavedContext(*this, SemanticContext);
18911891
if (RebuildTemplateParamsInCurrentInstantiation(TemplateParams))
18921892
Invalid = true;
1893-
} else if (TUK != TUK_Friend && TUK != TUK_Reference)
1894-
diagnoseQualifiedDeclaration(SS, SemanticContext, Name, NameLoc, false);
1893+
}
1894+
1895+
if (TUK != TUK_Friend && TUK != TUK_Reference)
1896+
diagnoseQualifiedDeclaration(SS, SemanticContext, Name, NameLoc,
1897+
/*TemplateId-*/ nullptr,
1898+
/*IsMemberSpecialization*/ false);
18951899

18961900
LookupQualifiedName(Previous, SemanticContext);
18971901
} else {
@@ -8826,6 +8830,15 @@ DeclResult Sema::ActOnClassTemplateSpecialization(
88268830
bool isMemberSpecialization = false;
88278831
bool isPartialSpecialization = false;
88288832

8833+
if (SS.isSet()) {
8834+
if (TUK != TUK_Reference && TUK != TUK_Friend &&
8835+
diagnoseQualifiedDeclaration(SS, ClassTemplate->getDeclContext(),
8836+
ClassTemplate->getDeclName(),
8837+
TemplateNameLoc, &TemplateId,
8838+
/*IsMemberSpecialization=*/false))
8839+
return true;
8840+
}
8841+
88298842
// Check the validity of the template headers that introduce this
88308843
// template.
88318844
// FIXME: We probably shouldn't complain about these headers for

clang/lib/Sema/TreeTransform.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4394,7 +4394,8 @@ NestedNameSpecifierLoc TreeTransform<Derived>::TransformNestedNameSpecifierLoc(
43944394
SS.Adopt(ETL.getQualifierLoc());
43954395
TL = ETL.getNamedTypeLoc();
43964396
}
4397-
SS.Extend(SemaRef.Context, /*FIXME:*/ SourceLocation(), TL,
4397+
4398+
SS.Extend(SemaRef.Context, TL.getTemplateKeywordLoc(), TL,
43984399
Q.getLocalEndLoc());
43994400
break;
44004401
}

clang/test/CXX/drs/dr23xx.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,8 @@ struct Bad2 { int a, b; };
182182
} // namespace dr2386
183183
namespace std {
184184
template <typename T> struct tuple_size;
185-
template <> struct std::tuple_size<dr2386::Bad1> {};
186-
template <> struct std::tuple_size<dr2386::Bad2> {
185+
template <> struct tuple_size<dr2386::Bad1> {};
186+
template <> struct tuple_size<dr2386::Bad2> {
187187
static const int value = 42;
188188
};
189189
} // namespace std

clang/test/CXX/drs/dr7xx.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ namespace dr727 { // dr727: partial
105105
// expected-note@#dr727-N {{explicitly specialized declaration is here}}
106106

107107
template<> struct A::C<double>;
108-
// expected-error@-1 {{class template specialization of 'C' not in class 'A' or an enclosing namespace}}
108+
// expected-error@-1 {{non-friend class member 'C' cannot have a qualified name}}
109+
// expected-error@-2 {{class template specialization of 'C' not in class 'A' or an enclosing namespace}}
109110
// expected-note@#dr727-C {{explicitly specialized declaration is here}}
110111
template<> void A::f<double>();
111112
// expected-error@-1 {{o function template matches function template specialization 'f'}}
@@ -116,7 +117,8 @@ namespace dr727 { // dr727: partial
116117
// expected-note@#dr727-N {{explicitly specialized declaration is here}}
117118

118119
template<typename T> struct A::C<T***>;
119-
// expected-error@-1 {{class template partial specialization of 'C' not in class 'A' or an enclosing namespace}}
120+
// expected-error@-1 {{non-friend class member 'C' cannot have a qualified name}}
121+
// expected-error@-2 {{class template partial specialization of 'C' not in class 'A' or an enclosing namespace}}
120122
// expected-note@#dr727-C {{explicitly specialized declaration is here}}
121123
template<typename T> static int A::N<T***>;
122124
// expected-error@-1 {{non-friend class member 'N' cannot have a qualified name}}

clang/test/CXX/temp/temp.decls/temp.class/temp.mem.func/p1.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ struct X1 {
7575

7676
template<typename T>
7777
template<typename U>
78-
void X1<T>::template B<U>::f() { }
78+
void X1<T>::template B<U>::f() { } // expected-warning{{'template' cannot be used after a declarative}}
7979

8080
// PR5527
8181
template <template <class> class T>

0 commit comments

Comments
 (0)