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.dis...@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 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits