Skip to content

[Clang][Sema] Diagnose declarative nested-name-specifiers naming alias templates #80842

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 20, 2024

Conversation

sdkrystian
Copy link
Member

@sdkrystian sdkrystian commented Feb 6, 2024

According to [expr.prim.id.qual] p3:

The nested-name-specifier ​:: nominates the global namespace. A nested-name-specifier with a computed-type-specifier nominates the type denoted by the computed-type-specifier, which shall be a class or enumeration type. If a nested-name-specifier N is declarative and has a simple-template-id with a template argument list A that involves a template parameter, let T be the template nominated by N without A. T shall be a class template.

Meaning, the out-of-line definition of A::f in the following example is ill-formed:

template<typename T>
struct A 
{ 
    void f(); 
};

template<typename T>
using B = A<T>;

template<typename T>
void B<T>::f() { } // error: a declarative nested name specifier cannot name an alias template

This patch diagnoses such cases as an extension (in group alias-template-in-declaration-name).

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Feb 6, 2024
@sdkrystian sdkrystian requested a review from erichkeane February 6, 2024 14:55
@llvmbot
Copy link
Member

llvmbot commented Feb 6, 2024

@llvm/pr-subscribers-clang

Author: Krystian Stasiowski (sdkrystian)

Changes

According to [[expr.prim.id.qual] p3](http://eel.is/c++draft/expr.prim.id.qual#3):
> The nested-name-specifier ​::​ nominates the global namespace. A nested-name-specifier with a computed-type-specifier nominates the type denoted by the computed-type-specifier, which shall be a class or enumeration type. If a nested-name-specifier N is declarative and has a simple-template-id with a template argument list A that involves a template parameter, let T be the template nominated by N without A. T shall be a class template.

Meaning, the out-of-line definition of A::f in the following example is ill-formed:

template&lt;typename T&gt;
struct A 
{ 
    void f(); 
};

template&lt;typename T&gt;
using B = A&lt;T&gt;;

template&lt;typename T&gt;
void B&lt;T&gt;::f() { } // error: a declarative nested name specifier cannot name a type alias template

This patch diagnoses such cases as an extension (in group alias-template-in-declaration-name).


Full diff: https://github.com/llvm/llvm-project/pull/80842.diff

4 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+3)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+26-10)
  • (added) clang/test/CXX/expr/expr.prim/expr.prim.id/expr.prim.id.qual/p3.cpp (+29)
  • (modified) clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p1.cpp (+1-1)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index f76e7a3392183..d02fddd069341 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -8250,6 +8250,9 @@ def err_not_tag_in_scope : Error<
 def ext_template_after_declarative_nns : ExtWarn<
     "'template' cannot be used after a declarative nested name specifier">,
     InGroup<DiagGroup<"template-in-declaration-name">>;
+def ext_alias_template_in_declarative_nns : ExtWarn<
+  "a declarative nested name specifier cannot name an alias template">,
+  InGroup<DiagGroup<"alias-template-in-declaration-name">>;
 
 def err_no_typeid_with_fno_rtti : Error<
   "use of typeid requires -frtti">;
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 481d952d2389b..464f3738b0ba5 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -6207,6 +6207,8 @@ bool Sema::diagnoseQualifiedDeclaration(CXXScopeSpec &SS, DeclContext *DC,
                                         SourceLocation Loc,
                                         TemplateIdAnnotation *TemplateId,
                                         bool IsMemberSpecialization) {
+  assert(SS.isValid() && "diagnoseQualifiedDeclaration called for declaration "
+                         "without nested-name-specifier");
   DeclContext *Cur = CurContext;
   while (isa<LinkageSpecDecl>(Cur) || isa<CapturedDecl>(Cur))
     Cur = Cur->getParent();
@@ -6295,22 +6297,36 @@ bool Sema::diagnoseQualifiedDeclaration(CXXScopeSpec &SS, DeclContext *DC,
         << FixItHint::CreateRemoval(TemplateId->TemplateKWLoc);
 
   NestedNameSpecifierLoc SpecLoc(SS.getScopeRep(), SS.location_data());
-  while (SpecLoc.getPrefix()) {
+  do {
     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"
-  if (isa_and_nonnull<DecltypeType>(
-          SpecLoc.getNestedNameSpecifier()->getAsType()))
-    Diag(Loc, diag::err_decltype_in_declarator)
-      << SpecLoc.getTypeLoc().getSourceRange();
+    if (const Type *T = SpecLoc.getNestedNameSpecifier()->getAsType()) {
+      if (const auto *TST = T->getAsAdjusted<TemplateSpecializationType>()) {
+        // C++23 [expr.prim.id.qual]p3:
+        //   [...] If a nested-name-specifier N is declarative and has a
+        //   simple-template-id with a template argument list A that involves a
+        //   template parameter, let T be the template nominated by N without A.
+        //   T shall be a class template.
+        if (TST->isDependentType() && TST->isTypeAlias())
+          Diag(Loc, diag::ext_alias_template_in_declarative_nns)
+              << SpecLoc.getLocalSourceRange();
+      } else if (T->isDecltypeType()) {
+        // C++23 [expr.prim.id.qual]p2:
+        //   [...] A declarative nested-name-specifier shall not have a
+        //   decltype-specifier.
+        //
+        // FIXME: This wording appears to be defective, as it does not
+        // forbid declarative nested-name-specifiers that begin with a
+        // pack-index-specifier.
+        Diag(Loc, diag::err_decltype_in_declarator)
+            << SpecLoc.getTypeLoc().getSourceRange();
+      }
+    }
+  } while ((SpecLoc = SpecLoc.getPrefix()));
 
   return false;
 }
diff --git a/clang/test/CXX/expr/expr.prim/expr.prim.id/expr.prim.id.qual/p3.cpp b/clang/test/CXX/expr/expr.prim/expr.prim.id/expr.prim.id.qual/p3.cpp
new file mode 100644
index 0000000000000..ba89d2c96c7bb
--- /dev/null
+++ b/clang/test/CXX/expr/expr.prim/expr.prim.id/expr.prim.id.qual/p3.cpp
@@ -0,0 +1,29 @@
+// RUN: %clang_cc1 -verify %s
+
+template<typename T>
+struct A {
+  void f();
+};
+
+template<typename T>
+using B = A<T>;
+
+template<typename T>
+void B<T>::f() { } // expected-warning {{a declarative nested name specifier cannot name an alias template}}
+
+template<>
+void B<int>::f() { } // ok, template argument list of simple-template-id doesn't involve template parameters
+
+namespace N {
+
+  template<typename T>
+  struct D {
+    void f();
+  };
+
+  template<typename T>
+  using E = C<T>;
+}
+
+template<typename T>
+void N::E<T>::f() { } // expected-warning {{a declarative nested name specifier cannot name an alias template}}
diff --git a/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p1.cpp b/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p1.cpp
index 82983f05fe878..910dab11ee5e1 100644
--- a/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p1.cpp
+++ b/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p1.cpp
@@ -26,5 +26,5 @@ namespace Example2 {
     void g();
   };
   template<class T> using B = A<T>;
-  template<class T> void B<T>::g() {} // ok.
+  template<class T> void B<T>::g() {} // // expected-warning {{a declarative nested name specifier cannot name an alias template}}
 }

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added other reviewers, this part of naming is something that I need other eyes on.

@sdkrystian sdkrystian force-pushed the declarative-nns-alias-template branch 3 times, most recently from 13abf4c to c7457d7 Compare February 6, 2024 16:21
@sdkrystian
Copy link
Member Author

@erichkeane Should i wait for additional reviews, or can this be merged?

@erichkeane
Copy link
Collaborator

I think we're good, feel free to resolve your conflict and commit.

@sdkrystian sdkrystian force-pushed the declarative-nns-alias-template branch from c7457d7 to 3cef590 Compare February 20, 2024 18:14
@sdkrystian sdkrystian merged commit fb615cf into llvm:main Feb 20, 2024
@sdkrystian sdkrystian deleted the declarative-nns-alias-template branch February 26, 2024 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants