[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 08:00:59 PDT 2023


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

…ation

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.

>From 34b4e7939c88627ce433c96cdcc5e54d71ada94d 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 the
 class 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               | 21 -----
 clang/unittests/Tooling/QualTypeNamesTest.cpp | 83 +++++++++----------
 2 files changed, 40 insertions(+), 64 deletions(-)

diff --git a/clang/lib/AST/QualTypeNames.cpp b/clang/lib/AST/QualTypeNames.cpp
index 7557336f0aafa88..32fba2ab887d27c 100644
--- a/clang/lib/AST/QualTypeNames.cpp
+++ b/clang/lib/AST/QualTypeNames.cpp
@@ -272,27 +272,6 @@ 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)) {
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