[clang] Qualify non-dependent types of a class template with the class declar… (PR #67566)

Luca Di sera via cfe-commits cfe-commits at lists.llvm.org
Wed Sep 27 10:18:02 PDT 2023


https://github.com/diseraluca updated https://github.com/llvm/llvm-project/pull/67566

>From d19d716609034465bf6be814a322f26c1ea619c9 Mon Sep 17 00:00:00 2001
From: Luca Di Sera <luca.disera at qt.io>
Date: Wed, 27 Sep 2023 15:20:22 +0200
Subject: [PATCH] Qualify non-dependent types of a class template with its
 declaration

When
`clang::TypeName::getFullyQualifiedType`/`clang::TypeName::getFullyQualifiedName`
encounter a non-dependent type/type alias that is defined under a templated
class/struct, it qualifies the type/type alias under a specialization of
the templated class, if any was declared.

That is, in:

```
template<typename T>
class Foo {
    using Bar = T;
}

using Baz = Foo<int>;
```

Usages of `Foo::Bar` will be qualified as `Foo<int>::Bar`.

When the `using Baz = Foo<int>;` line is removed, as there are would be no
specialization encountered in the translation unit, usages of
`Foo::Bar` would instead be qualified as `Foo<T>::Bar`.

When multiple specializations are present, the one that is chosen is the
first one that is encountered; due to the current implementation of the
behavior and the fact that iterating the specialization of a
`ClassTemplateDecl` respects the order in which the specializations were
stored in.

That is, if we were to add a reference to some other specialization in
the above example, so that it would be parsed before the current `using
Baz = Foo<int>;`, say:

```
template<typename T>
class Foo {
    using Bar = T;
}

using Bat = Foo<double>;
using Baz = Foo<int>;
```

Then usages of `Foo::Bar` would be qualified as `Foo<double>::Bar`
instead of `Foo<int>::Bar`.

Should the same declaration be added, instead, after the `using Baz =
Foo<int>;` line, then the qualification would remain consistent with our
previous version.

To provide a more consistent behavior, that avoids changing the output
when relatively unrelated declarations are present in the translation
unit,
`clang::TypeName::getFullyQualifiedType`/`clang::TypeName::getFullyQualifiedName`
will now qualify such types using the original templated declaration
instead of one of the specializations.
---
 clang/lib/AST/QualTypeNames.cpp               | 49 ++++-------
 clang/unittests/Tooling/QualTypeNamesTest.cpp | 83 +++++++++----------
 2 files changed, 54 insertions(+), 78 deletions(-)

diff --git a/clang/lib/AST/QualTypeNames.cpp b/clang/lib/AST/QualTypeNames.cpp
index 7557336f0aafa88..6ed5c9c3b2dee24 100644
--- a/clang/lib/AST/QualTypeNames.cpp
+++ b/clang/lib/AST/QualTypeNames.cpp
@@ -272,43 +272,22 @@ static NestedNameSpecifier *createNestedNameSpecifierForScopeOf(
   const auto *Outer = dyn_cast_or_null<NamedDecl>(DC);
   const auto *OuterNS = dyn_cast_or_null<NamespaceDecl>(DC);
   if (Outer && !(OuterNS && OuterNS->isAnonymousNamespace())) {
-    if (const auto *CxxDecl = dyn_cast<CXXRecordDecl>(DC)) {
-      if (ClassTemplateDecl *ClassTempl =
-              CxxDecl->getDescribedClassTemplate()) {
-        // We are in the case of a type(def) that was declared in a
-        // class template but is *not* type dependent.  In clang, it
-        // gets attached to the class template declaration rather than
-        // any specific class template instantiation.  This result in
-        // 'odd' fully qualified typename:
-        //
-        //    vector<_Tp,_Alloc>::size_type
-        //
-        // Make the situation is 'useable' but looking a bit odd by
-        // picking a random instance as the declaring context.
-        if (ClassTempl->spec_begin() != ClassTempl->spec_end()) {
-          Decl = *(ClassTempl->spec_begin());
-          Outer = dyn_cast<NamedDecl>(Decl);
-          OuterNS = dyn_cast<NamespaceDecl>(Decl);
-        }
+      if (OuterNS) {
+        return createNestedNameSpecifier(Ctx, OuterNS, WithGlobalNsPrefix);
+      } else if (const auto *TD = dyn_cast<TagDecl>(Outer)) {
+        return createNestedNameSpecifier(Ctx, TD, FullyQualified,
+                                         WithGlobalNsPrefix);
+      } else if (isa<TranslationUnitDecl>(Outer)) {
+        // Context is the TU. Nothing needs to be done.
+        return nullptr;
+      } else {
+        // Decl's context was neither the TU, a namespace, nor a
+        // TagDecl, which means it is a type local to a scope, and not
+        // accessible at the end of the TU.
+        return nullptr;
       }
-    }
-
-    if (OuterNS) {
-      return createNestedNameSpecifier(Ctx, OuterNS, WithGlobalNsPrefix);
-    } else if (const auto *TD = dyn_cast<TagDecl>(Outer)) {
-      return createNestedNameSpecifier(
-          Ctx, TD, FullyQualified, WithGlobalNsPrefix);
-    } else if (isa<TranslationUnitDecl>(Outer)) {
-      // Context is the TU. Nothing needs to be done.
-      return nullptr;
-    } else {
-      // Decl's context was neither the TU, a namespace, nor a
-      // TagDecl, which means it is a type local to a scope, and not
-      // accessible at the end of the TU.
-      return nullptr;
-    }
   } else if (WithGlobalNsPrefix && DC->isTranslationUnit()) {
-    return NestedNameSpecifier::GlobalSpecifier(Ctx);
+      return NestedNameSpecifier::GlobalSpecifier(Ctx);
   }
   return nullptr;
 }
diff --git a/clang/unittests/Tooling/QualTypeNamesTest.cpp b/clang/unittests/Tooling/QualTypeNamesTest.cpp
index 686d189cf69eb2f..18286f9e74921dd 100644
--- a/clang/unittests/Tooling/QualTypeNamesTest.cpp
+++ b/clang/unittests/Tooling/QualTypeNamesTest.cpp
@@ -85,7 +85,7 @@ TEST(QualTypeNameTest, Simple) {
   // Namespace alias
   Visitor.ExpectedQualTypeNames["CheckL"] = "A::B::C::MyInt";
   Visitor.ExpectedQualTypeNames["non_dependent_type_var"] =
-      "Foo<X>::non_dependent_type";
+      "Foo<T>::non_dependent_type";
   Visitor.ExpectedQualTypeNames["AnEnumVar"] = "EnumScopeClass::AnEnum";
   Visitor.ExpectedQualTypeNames["AliasTypeVal"] = "A::B::C::InnerAlias<int>";
   Visitor.ExpectedQualTypeNames["AliasInnerTypeVal"] =
@@ -175,20 +175,19 @@ TEST(QualTypeNameTest, Simple) {
 TEST(QualTypeNameTest, Complex) {
   TypeNameVisitor Complex;
   Complex.ExpectedQualTypeNames["CheckTX"] = "B::TX";
-  Complex.runOver(
-      "namespace A {"
-      "  struct X {};"
-      "}"
-      "using A::X;"
-      "namespace fake_std {"
-      "  template<class... Types > class tuple {};"
-      "}"
-      "namespace B {"
-      "  using fake_std::tuple;"
-      "  typedef tuple<X> TX;"
-      "  TX CheckTX;"
-      "  struct A { typedef int X; };"
-      "}");
+  Complex.runOver("namespace A {"
+                  "  struct X {};"
+                  "}"
+                  "using A::X;"
+                  "namespace fake_std {"
+                  "  template<class... Types > class tuple {};"
+                  "}"
+                  "namespace B {"
+                  "  using fake_std::tuple;"
+                  "  typedef tuple<X> TX;"
+                  "  TX CheckTX;"
+                  "  struct A { typedef int X; };"
+                  "}");
 }
 
 TEST(QualTypeNameTest, DoubleUsing) {
@@ -223,33 +222,31 @@ TEST(QualTypeNameTest, GlobalNsPrefix) {
   GlobalNsPrefix.ExpectedQualTypeNames["GlobalZVal"] = "::Z";
   GlobalNsPrefix.ExpectedQualTypeNames["CheckK"] = "D::aStruct";
   GlobalNsPrefix.ExpectedQualTypeNames["YZMPtr"] = "::A::B::X ::A::B::Y::Z::*";
-  GlobalNsPrefix.runOver(
-      "namespace A {\n"
-      "  namespace B {\n"
-      "    int IntVal;\n"
-      "    bool BoolVal;\n"
-      "    struct X {};\n"
-      "    X XVal;\n"
-      "    template <typename T> class CCC { };\n"
-      "    template <typename T>\n"
-      "    using Alias = CCC<T>;\n"
-      "    Alias<int> IntAliasVal;\n"
-      "    struct Y { struct Z { X YZIPtr; }; };\n"
-      "    Y::Z ZVal;\n"
-      "    X Y::Z::*YZMPtr;\n"
-      "  }\n"
-      "}\n"
-      "struct Z {};\n"
-      "Z GlobalZVal;\n"
-      "namespace {\n"
-      "  namespace D {\n"
-      "    namespace {\n"
-      "      class aStruct {};\n"
-      "      aStruct CheckK;\n"
-      "    }\n"
-      "  }\n"
-      "}\n"
-  );
+  GlobalNsPrefix.runOver("namespace A {\n"
+                         "  namespace B {\n"
+                         "    int IntVal;\n"
+                         "    bool BoolVal;\n"
+                         "    struct X {};\n"
+                         "    X XVal;\n"
+                         "    template <typename T> class CCC { };\n"
+                         "    template <typename T>\n"
+                         "    using Alias = CCC<T>;\n"
+                         "    Alias<int> IntAliasVal;\n"
+                         "    struct Y { struct Z { X YZIPtr; }; };\n"
+                         "    Y::Z ZVal;\n"
+                         "    X Y::Z::*YZMPtr;\n"
+                         "  }\n"
+                         "}\n"
+                         "struct Z {};\n"
+                         "Z GlobalZVal;\n"
+                         "namespace {\n"
+                         "  namespace D {\n"
+                         "    namespace {\n"
+                         "      class aStruct {};\n"
+                         "      aStruct CheckK;\n"
+                         "    }\n"
+                         "  }\n"
+                         "}\n");
 }
 
 TEST(QualTypeNameTest, InlineNamespace) {
@@ -297,4 +294,4 @@ TEST(QualTypeNameTest, ConstUsing) {
                         using ::A::S;
                         void foo(const S& param1, const S param2);)");
 }
-}  // end anonymous namespace
+} // end anonymous namespace



More information about the cfe-commits mailing list