Thanks to visit codestin.com
Credit goes to github.com

Skip to content

release/20.x: [clang] Forward TPL of NestedNameSpecifier #137806

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

Open
wants to merge 1 commit into
base: release/20.x
Choose a base branch
from

Conversation

hahnjo
Copy link
Member

@hahnjo hahnjo commented Apr 29, 2025

This avoids type suffixes for integer constants when the type can be inferred from the template parameter, such as the unsigned parameter of A<1> and A<2> in the added test.


Note: This issue does not exist anymore in main, see #137804. However, I don't think dc17429 should be backported to release/20.x, so I'm proposing a targeted fix.

This avoids type suffixes for integer constants when the type can be
inferred from the template parameter, such as the unsigned parameter
of A<1> and A<2> in the added test.
@hahnjo hahnjo added this to the LLVM 20.X Release milestone Apr 29, 2025
@hahnjo hahnjo self-assigned this Apr 29, 2025
@github-project-automation github-project-automation bot moved this to Needs Triage in LLVM Release Status Apr 29, 2025
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Apr 29, 2025
@llvmbot
Copy link
Member

llvmbot commented Apr 29, 2025

@llvm/pr-subscribers-clang

Author: Jonas Hahnfeld (hahnjo)

Changes

This avoids type suffixes for integer constants when the type can be inferred from the template parameter, such as the unsigned parameter of A&lt;1&gt; and A&lt;2&gt; in the added test.


Note: This issue does not exist anymore in main, see #137804. However, I don't think dc17429 should be backported to release/20.x, so I'm proposing a targeted fix.


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

2 Files Affected:

  • (modified) clang/lib/AST/NestedNameSpecifier.cpp (+10-7)
  • (modified) clang/unittests/Tooling/QualTypeNamesTest.cpp (+96)
diff --git a/clang/lib/AST/NestedNameSpecifier.cpp b/clang/lib/AST/NestedNameSpecifier.cpp
index 76c77569da9fd..c043996f1ada3 100644
--- a/clang/lib/AST/NestedNameSpecifier.cpp
+++ b/clang/lib/AST/NestedNameSpecifier.cpp
@@ -283,13 +283,16 @@ void NestedNameSpecifier::print(raw_ostream &OS, const PrintingPolicy &Policy,
   case TypeSpec: {
     const auto *Record =
             dyn_cast_or_null<ClassTemplateSpecializationDecl>(getAsRecordDecl());
-    if (ResolveTemplateArguments && Record) {
+    const TemplateParameterList *TPL = nullptr;
+    if (Record) {
+      TPL = Record->getSpecializedTemplate()->getTemplateParameters();
+      if (ResolveTemplateArguments) {
         // Print the type trait with resolved template parameters.
         Record->printName(OS, Policy);
-        printTemplateArgumentList(
-            OS, Record->getTemplateArgs().asArray(), Policy,
-            Record->getSpecializedTemplate()->getTemplateParameters());
+        printTemplateArgumentList(OS, Record->getTemplateArgs().asArray(),
+                                  Policy, TPL);
         break;
+      }
     }
     const Type *T = getAsType();
 
@@ -313,8 +316,8 @@ void NestedNameSpecifier::print(raw_ostream &OS, const PrintingPolicy &Policy,
                                         TemplateName::Qualified::None);
 
       // Print the template argument list.
-      printTemplateArgumentList(OS, SpecType->template_arguments(),
-                                InnerPolicy);
+      printTemplateArgumentList(OS, SpecType->template_arguments(), InnerPolicy,
+                                TPL);
     } else if (const auto *DepSpecType =
                    dyn_cast<DependentTemplateSpecializationType>(T)) {
       // Print the template name without its corresponding
@@ -322,7 +325,7 @@ void NestedNameSpecifier::print(raw_ostream &OS, const PrintingPolicy &Policy,
       OS << DepSpecType->getIdentifier()->getName();
       // Print the template argument list.
       printTemplateArgumentList(OS, DepSpecType->template_arguments(),
-                                InnerPolicy);
+                                InnerPolicy, TPL);
     } else {
       // Print the type normally
       QualType(T, 0).print(OS, InnerPolicy);
diff --git a/clang/unittests/Tooling/QualTypeNamesTest.cpp b/clang/unittests/Tooling/QualTypeNamesTest.cpp
index 5ded64d4fcc8c..49c40d633ad4b 100644
--- a/clang/unittests/Tooling/QualTypeNamesTest.cpp
+++ b/clang/unittests/Tooling/QualTypeNamesTest.cpp
@@ -265,6 +265,102 @@ TEST(QualTypeNameTest, InlineNamespace) {
                           TypeNameVisitor::Lang_CXX11);
 }
 
+TEST(QualTypeNameTest, TemplatedClass) {
+  std::unique_ptr<ASTUnit> AST =
+      tooling::buildASTFromCode("template <unsigned U1> struct A {\n"
+                                "  template <unsigned U2> struct B {};\n"
+                                "};\n"
+                                "template struct A<1>;\n"
+                                "template struct A<2u>;\n"
+                                "template struct A<1>::B<3>;\n"
+                                "template struct A<2u>::B<4u>;\n");
+
+  auto &Context = AST->getASTContext();
+  auto &Policy = Context.getPrintingPolicy();
+  auto getFullyQualifiedName = [&](QualType QT) {
+    return TypeName::getFullyQualifiedName(QT, Context, Policy);
+  };
+
+  auto *A = Context.getTranslationUnitDecl()
+                ->lookup(&Context.Idents.get("A"))
+                .find_first<ClassTemplateDecl>();
+  ASSERT_NE(A, nullptr);
+
+  // A has two explicit instantiations: A<1> and A<2u>
+  auto ASpec = A->spec_begin();
+  ASSERT_NE(ASpec, A->spec_end());
+  auto *A1 = *ASpec;
+  ASpec++;
+  ASSERT_NE(ASpec, A->spec_end());
+  auto *A2 = *ASpec;
+
+  // Their type names follow the records.
+  QualType A1RecordTy = Context.getRecordType(A1);
+  EXPECT_EQ(getFullyQualifiedName(A1RecordTy), "A<1>");
+  QualType A2RecordTy = Context.getRecordType(A2);
+  EXPECT_EQ(getFullyQualifiedName(A2RecordTy), "A<2U>");
+
+  // getTemplateSpecializationType() gives types that print the integral
+  // argument directly.
+  TemplateArgument Args1[] = {
+      {Context, llvm::APSInt::getUnsigned(1u), Context.UnsignedIntTy}};
+  QualType A1TemplateSpecTy =
+      Context.getTemplateSpecializationType(TemplateName(A), Args1, A1RecordTy);
+  EXPECT_EQ(A1TemplateSpecTy.getAsString(), "A<1>");
+
+  TemplateArgument Args2[] = {
+      {Context, llvm::APSInt::getUnsigned(2u), Context.UnsignedIntTy}};
+  QualType A2TemplateSpecTy =
+      Context.getTemplateSpecializationType(TemplateName(A), Args2, A2RecordTy);
+  EXPECT_EQ(A2TemplateSpecTy.getAsString(), "A<2>");
+
+  // Find A<1>::B and its specialization B<3>.
+  auto *A1B =
+      A1->lookup(&Context.Idents.get("B")).find_first<ClassTemplateDecl>();
+  ASSERT_NE(A1B, nullptr);
+  auto A1BSpec = A1B->spec_begin();
+  ASSERT_NE(A1BSpec, A1B->spec_end());
+  auto *A1B3 = *A1BSpec;
+  QualType A1B3RecordTy = Context.getRecordType(A1B3);
+  EXPECT_EQ(getFullyQualifiedName(A1B3RecordTy), "A<1>::B<3>");
+
+  // Construct A<1>::B<3> and check name.
+  TemplateArgument Args3[] = {
+      {Context, llvm::APSInt::getUnsigned(3u), Context.UnsignedIntTy}};
+  QualType A1B3TemplateSpecTy = Context.getTemplateSpecializationType(
+      TemplateName(A1B), Args3, A1B3RecordTy);
+  EXPECT_EQ(A1B3TemplateSpecTy.getAsString(), "B<3>");
+
+  NestedNameSpecifier *A1Nested = NestedNameSpecifier::Create(
+      Context, nullptr, false, A1TemplateSpecTy.getTypePtr());
+  QualType A1B3ElaboratedTy = Context.getElaboratedType(
+      ElaboratedTypeKeyword::None, A1Nested, A1B3TemplateSpecTy);
+  EXPECT_EQ(A1B3ElaboratedTy.getAsString(), "A<1>::B<3>");
+
+  // Find A<2u>::B and its specialization B<4u>.
+  auto *A2B =
+      A2->lookup(&Context.Idents.get("B")).find_first<ClassTemplateDecl>();
+  ASSERT_NE(A2B, nullptr);
+  auto A2BSpec = A2B->spec_begin();
+  ASSERT_NE(A2BSpec, A2B->spec_end());
+  auto *A2B4 = *A2BSpec;
+  QualType A2B4RecordTy = Context.getRecordType(A2B4);
+  EXPECT_EQ(getFullyQualifiedName(A2B4RecordTy), "A<2U>::B<4U>");
+
+  // Construct A<2>::B<4> and check name.
+  TemplateArgument Args4[] = {
+      {Context, llvm::APSInt::getUnsigned(4u), Context.UnsignedIntTy}};
+  QualType A2B4TemplateSpecTy = Context.getTemplateSpecializationType(
+      TemplateName(A2B), Args4, A2B4RecordTy);
+  EXPECT_EQ(A2B4TemplateSpecTy.getAsString(), "B<4>");
+
+  NestedNameSpecifier *A2Nested = NestedNameSpecifier::Create(
+      Context, nullptr, false, A2TemplateSpecTy.getTypePtr());
+  QualType A2B4ElaboratedTy = Context.getElaboratedType(
+      ElaboratedTypeKeyword::None, A2Nested, A2B4TemplateSpecTy);
+  EXPECT_EQ(A2B4ElaboratedTy.getAsString(), "A<2>::B<4>");
+}
+
 TEST(QualTypeNameTest, AnonStrucs) {
   TypeNameVisitor AnonStrucs;
   AnonStrucs.ExpectedQualTypeNames["a"] = "short";

@github-project-automation github-project-automation bot moved this from Needs Triage to Needs Merge in LLVM Release Status Apr 29, 2025
Copy link
Contributor

@vgvassilev vgvassilev left a comment

Choose a reason for hiding this comment

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

This is great, @hahnjo. We need this to unbreak llvm20 for downstream clients. LGTM!

@shafik
Copy link
Collaborator

shafik commented Apr 29, 2025

This is great, @hahnjo. We need this to unbreak llvm20 for downstream clients. LGTM!

Is there a bug report for this, I would like to understand how this is breaking someone? IF there is not one, can we please file one.

Without understanding the problem better, I don't see why we would want to backport this.

Copy link
Collaborator

@shafik shafik left a comment

Choose a reason for hiding this comment

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

The summary should also explain the problem being fixed and how exactly this fixes the problem. The motivation for backporting is not clear to me from what is provided.

@hahnjo
Copy link
Member Author

hahnjo commented Apr 30, 2025

Hi @shafik, the issue is an inconsistency in the treatment of template specialization types depending on whether they are the prefix or the "final" type. To stay with the example of the test, the result without this patch is A<1> and A<2>, but A<1U>::B<3> and A<2U>::B<4>. This is affecting us downstream (ROOT, Cling, LHC experiments) where we try to normalize user types to a canonical string, irrespective of what the code may have spelled in different occasions. The latter means we cannot use the Decl, but we have to recursively normalize all scopes, class names, and template arguments. The inconsistency now causes a problem because we also have a "cheap" normalization routine based on string manipulation that just looks at the integer constant and keeps it without type suffix.

There is no issue for this because the problem is not present anymore in main, see #137804 (and it actually took me quite some time to understand how I could trigger the problem upstream without copying thousands of lines from the name normalization). If you insist, I can create an issue and immediately close it (?) but I personally don't see the advantage. The commit message and PR summary already explain that the change forwards the TPL and this avoids "avoids type suffixes for integer constants when the type can be inferred from the template parameter." I don't know how to improve this, but I'm open for suggestions.

@hahnjo hahnjo requested a review from shafik May 6, 2025 12:52
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
Status: Needs Merge
Development

Successfully merging this pull request may close these issues.

5 participants