Skip to content
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

Fix the double space and double attribute printing of the final keyword. #88600

Merged
merged 2 commits into from
Apr 18, 2024

Conversation

vgvassilev
Copy link
Contributor

Fixes #56517.

cc: @kimgr

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" HLSL HLSL Language Support labels Apr 13, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Apr 13, 2024

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-hlsl

Author: Vassil Vassilev (vgvassilev)

Changes

Fixes #56517.

cc: @kimgr


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

4 Files Affected:

  • (modified) clang/lib/AST/DeclPrinter.cpp (+18-13)
  • (modified) clang/test/SemaCXX/cxx11-attr-print.cpp (+5)
  • (modified) clang/test/SemaHLSL/BuiltIns/RWBuffers.hlsl (+2-2)
  • (modified) clang/utils/TableGen/ClangAttrEmitter.cpp (+1-1)
diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp
index 6afdb6cfccb142..c758f792b04a59 100644
--- a/clang/lib/AST/DeclPrinter.cpp
+++ b/clang/lib/AST/DeclPrinter.cpp
@@ -119,7 +119,7 @@ namespace {
     void printTemplateArguments(llvm::ArrayRef<TemplateArgumentLoc> Args,
                                 const TemplateParameterList *Params);
     enum class AttrPosAsWritten { Default = 0, Left, Right };
-    void
+    bool
     prettyPrintAttributes(const Decl *D,
                           AttrPosAsWritten Pos = AttrPosAsWritten::Default);
     void prettyPrintPragmas(Decl *D);
@@ -252,10 +252,12 @@ static DeclPrinter::AttrPosAsWritten getPosAsWritten(const Attr *A,
   return DeclPrinter::AttrPosAsWritten::Right;
 }
 
-void DeclPrinter::prettyPrintAttributes(const Decl *D,
+// returns true if an attribute was printed.
+bool DeclPrinter::prettyPrintAttributes(const Decl *D,
                                         AttrPosAsWritten Pos /*=Default*/) {
+  bool hasPrinted = false;
   if (Policy.PolishForDeclaration)
-    return;
+    return hasPrinted;
 
   if (D->hasAttrs()) {
     const AttrVec &Attrs = D->getAttrs();
@@ -275,6 +277,7 @@ void DeclPrinter::prettyPrintAttributes(const Decl *D,
           if (Pos != AttrPosAsWritten::Left)
             Out << ' ';
           A->printPretty(Out, Policy);
+          hasPrinted = true;
           if (Pos == AttrPosAsWritten::Left)
             Out << ' ';
         }
@@ -282,6 +285,7 @@ void DeclPrinter::prettyPrintAttributes(const Decl *D,
       }
     }
   }
+  return hasPrinted;
 }
 
 void DeclPrinter::prettyPrintPragmas(Decl *D) {
@@ -1060,12 +1064,14 @@ void DeclPrinter::VisitCXXRecordDecl(CXXRecordDecl *D) {
   // FIXME: add printing of pragma attributes if required.
   if (!Policy.SuppressSpecifiers && D->isModulePrivate())
     Out << "__module_private__ ";
-  Out << D->getKindName();
 
-  prettyPrintAttributes(D);
+  Out << D->getKindName() << ' ';
+
+  // FIXME: Move before printing the decl kind to match the behavior of the
+  // attribute printing for variables and function where they are printed first.
+  prettyPrintAttributes(D, AttrPosAsWritten::Left);
 
   if (D->getIdentifier()) {
-    Out << ' ';
     if (auto *NNS = D->getQualifier())
       NNS->print(Out, Policy);
     Out << *D;
@@ -1082,11 +1088,8 @@ void DeclPrinter::VisitCXXRecordDecl(CXXRecordDecl *D) {
     }
   }
 
-  if (D->hasDefinition()) {
-    if (D->hasAttr<FinalAttr>()) {
-      Out << " final";
-    }
-  }
+  if (prettyPrintAttributes(D, AttrPosAsWritten::Right))
+    Out << ' ';
 
   if (D->isCompleteDefinition()) {
     // Print the base classes
@@ -1114,10 +1117,12 @@ void DeclPrinter::VisitCXXRecordDecl(CXXRecordDecl *D) {
 
     // Print the class definition
     // FIXME: Doesn't print access specifiers, e.g., "public:"
+    //if (!D->getIdentifier())
+      Out << ' ';
     if (Policy.TerseOutput) {
-      Out << " {}";
+      Out << "{}";
     } else {
-      Out << " {\n";
+      Out << "{\n";
       VisitDeclContext(D);
       Indent() << "}";
     }
diff --git a/clang/test/SemaCXX/cxx11-attr-print.cpp b/clang/test/SemaCXX/cxx11-attr-print.cpp
index a169d1b4409b4d..2b084018bc0662 100644
--- a/clang/test/SemaCXX/cxx11-attr-print.cpp
+++ b/clang/test/SemaCXX/cxx11-attr-print.cpp
@@ -87,3 +87,8 @@ template struct S<int>;
 
 // CHECK: using Small2 {{\[}}[gnu::mode(byte)]] = int;
 using Small2 [[gnu::mode(byte)]] = int;
+
+class FinalNonTemplate final {};
+// CHECK: class FinalNonTemplate final {
+template <typename T> class FinalTemplate final {};
+// CHECK: template <typename T> class FinalTemplate final {
diff --git a/clang/test/SemaHLSL/BuiltIns/RWBuffers.hlsl b/clang/test/SemaHLSL/BuiltIns/RWBuffers.hlsl
index 7e79ae3bf005fc..b1a15c43191829 100644
--- a/clang/test/SemaHLSL/BuiltIns/RWBuffers.hlsl
+++ b/clang/test/SemaHLSL/BuiltIns/RWBuffers.hlsl
@@ -6,11 +6,11 @@ typedef vector<float, 3> float3;
 RWBuffer<float3> Buffer;
 
 // expected-error@+2 {{class template 'RWBuffer' requires template arguments}}
-// expected-note@*:* {{template declaration from hidden source: template <class element_type> class RWBuffer final}}
+// expected-note@*:* {{template declaration from hidden source: template <class element_type> class RWBuffer}}
 RWBuffer BufferErr1;
 
 // expected-error@+2 {{too few template arguments for class template 'RWBuffer'}}
-// expected-note@*:* {{template declaration from hidden source: template <class element_type> class RWBuffer final}}
+// expected-note@*:* {{template declaration from hidden source: template <class element_type> class RWBuffer}}
 RWBuffer<> BufferErr2;
 
 [numthreads(1,1,1)]
diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp
index 6c56f99f503df4..765cbbf3b04bcf 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -1608,7 +1608,7 @@ writePrettyPrintFunction(const Record &R,
       Prefix = "[";
       Suffix = "]";
     } else if (Variety == "Keyword") {
-      Prefix = " ";
+      Prefix = "";
       Suffix = "";
     } else if (Variety == "Pragma") {
       Prefix = "#pragma ";

@@ -6,11 +6,11 @@ typedef vector<float, 3> float3;
RWBuffer<float3> Buffer;

// expected-error@+2 {{class template 'RWBuffer' requires template arguments}}
// expected-note@*:* {{template declaration from hidden source: template <class element_type> class RWBuffer final}}
// expected-note@*:* {{template declaration from hidden source: template <class element_type> class RWBuffer}}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We remove the final keyword here as it is modeled as an attribute and HLSL calls FinalAttr::CreateImplicit to constrain the users from inheriting from the builtin classes. We do not print implicit attributes. This used to work because we double printed final once as an attribute and once as part of the regular printing of CXXRecordDecls...

@kimgr
Copy link
Contributor

kimgr commented Apr 13, 2024

Thanks!

Could you add this DeclPrinterTest unittest for regression?

TEST(DeclPrinter, TestTemplateFinal) {
  ASSERT_TRUE(PrintedDeclCXX11Matches(
    "template<typename T>"
    "class FinalTemplate final {};",
    classTemplateDecl(hasName("FinalTemplate")).bind("id"),
    "template <typename T> class FinalTemplate final {}"));
}

This is my expectation for correct output, but the current branch seems to disagree -- now there's two spaces between final and {}. Not sure if that's a problem. IWYU has enough post-processing that it doesn't break us either way.

EDIT: Hmm, I see your -ast-print test does not have double space, so I guess it has something to do with the printing policy. Here's what I've seen so far:

  • With .PolishForDeclaration=true, there are NO final specifiers (which is what we want to produce forward decls in IWYU)
  • In the DeclPrinter unittests I guess the default policy applies; they print ... X final {} with double space after final
  • In your end-to-end test, there are no double spaces, so I guess the compiler sets up a printing policy that masks it?

@kimgr
Copy link
Contributor

kimgr commented Apr 13, 2024

With .PolishForDeclaration=true, there are NO final specifiers (which is what we want to produce forward decls in IWYU)

This is actually a regression in this PR, and it breaks the clangd test added here: 9f57b65 (the patch that originally led to double finals).

EDIT: It looks like prettyPrintAttributes is only intended for semantic attributes, whereas clangd wanted to preserve as-written final keyword. So I wonder if this change needs to be tweaked a little: https://github.com/llvm/llvm-project/pull/88600/files#diff-81d69bc555945d6582a758e0c094ff870cbc38697501ad7415694ee30c567dbfL1085

@vgvassilev
Copy link
Contributor Author

With .PolishForDeclaration=true, there are NO final specifiers (which is what we want to produce forward decls in IWYU)

This is actually a regression in this PR, and it breaks the clangd test added here: 9f57b65 (the patch that originally led to double finals).

EDIT: It looks like prettyPrintAttributes is only intended for semantic attributes, whereas clangd wanted to preserve as-written final keyword. So I wonder if this change needs to be tweaked a little: https://github.com/llvm/llvm-project/pull/88600/files#diff-81d69bc555945d6582a758e0c094ff870cbc38697501ad7415694ee30c567dbfL1085

I've added a fix for your example. Can you provide a test case for the clangd use-case?

@kimgr
Copy link
Contributor

kimgr commented Apr 13, 2024

Thank you.

I believe this should cover both cases, added some attempt at rationale in comments:

diff --git a/clang/unittests/AST/DeclPrinterTest.cpp b/clang/unittests/AST/DeclPrinterTest.cpp
index f2b027a25621..8691a6c38f16 100644
--- a/clang/unittests/AST/DeclPrinterTest.cpp
+++ b/clang/unittests/AST/DeclPrinterTest.cpp
@@ -86,16 +86,13 @@ PrintedDeclCXX98Matches(StringRef Code, const DeclarationMatcher &NodeMatch,
                             ExpectedPrinted, "input.cc");
 }
 
-::testing::AssertionResult PrintedDeclCXX11Matches(
-                                  StringRef Code,
-                                  const DeclarationMatcher &NodeMatch,
-                                  StringRef ExpectedPrinted) {
+::testing::AssertionResult
+PrintedDeclCXX11Matches(StringRef Code, const DeclarationMatcher &NodeMatch,
+                        StringRef ExpectedPrinted,
+                        PrintingPolicyAdjuster PolicyModifier = nullptr) {
   std::vector<std::string> Args(1, "-std=c++11");
-  return PrintedDeclMatches(Code,
-                            Args,
-                            NodeMatch,
-                            ExpectedPrinted,
-                            "input.cc");
+  return PrintedDeclMatches(Code, Args, NodeMatch, ExpectedPrinted, "input.cc",
+                            PolicyModifier);
 }
 
 ::testing::AssertionResult PrintedDeclCXX11nonMSCMatches(
@@ -1556,3 +1553,25 @@ TEST(DeclPrinter, VarDeclWithInitializer) {
       PrintedDeclCXX17Matches("void foo() {int arr[42]; for(int a : arr);}",
                               namedDecl(hasName("a")).bind("id"), "int a"));
 }
+
+TEST(DeclPrinter, TestTemplateFinal) {
+  // By default we should print 'final' keyword whether class is implicitly or
+  // explicitly marked final.
+  ASSERT_TRUE(PrintedDeclCXX11Matches(
+      "template<typename T>\n"
+      "class FinalTemplate final {};",
+      classTemplateDecl(hasName("FinalTemplate")).bind("id"),
+      "template <typename T> class FinalTemplate final {}"));
+}
+
+TEST(DeclPrinter, TestTemplateFinalWithPolishForDecl) {
+  // clangd relies on the 'final' keyword being printed when
+  // PolishForDeclaration is enabled, so make sure it is even if implicit attrs
+  // are disabled.
+  ASSERT_TRUE(PrintedDeclCXX11Matches(
+      "template<typename T>\n"
+      "class FinalTemplate final {};",
+      classTemplateDecl(hasName("FinalTemplate")).bind("id"),
+      "template <typename T> class FinalTemplate final {}",
+      [](PrintingPolicy &Policy) { Policy.PolishForDeclaration = true; }));
+}

@vgvassilev
Copy link
Contributor Author

IIUC, the PolishForDeclaration option is supposed to When true, do certain refinement needed for producing proper declaration tag; such as, do not print attributes attached to the declaration. . If the intent is to produce a forward declaration the final keyword cannot be attached to a forward declaration. So I am not sure what's the "right" fix here...

@vgvassilev
Copy link
Contributor Author

I've put a fix that fixes the cases that you mentioned...

@kimgr
Copy link
Contributor

kimgr commented Apr 13, 2024

If the intent is to produce a forward declaration the final keyword cannot be attached to a forward declaration. So I am not sure what's the "right" fix here...

I don't believe that's the intent of DeclPrinter or PolishForDeclaration --

  • Clangd uses PolishForDeclaration to print an informational "hover text" for the declaration (and I guess that's why they want to include final -- it's something that's good for an interactive user to know about the decl)
  • IWYU uses PolishForDeclaration to get a valid declaration, and then does some very hacky heuristic post-processing to turn it into a forward-decl.

Sorry for stirring confusion into this, my primary focus is IWYU, but I remember that the original double-final came from a change intended for Clangd.

Hopefully this helps clarify.


if (D->hasAttrs()) {
const AttrVec &Attrs = D->getAttrs();
for (auto *A : Attrs) {
if (A->isInherited() || A->isImplicit())
continue;
// Don't strip out the keyword attributes, they aren't regular attributes.
if (Policy.PolishForDeclaration && !A->isKeywordAttribute())
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice, I was about to suggest something like this, but didn't know isKeywordAttribute existed.

@vgvassilev
Copy link
Contributor Author

The ROOT usecase of the Cling interpreter has similar infrastructure and it works at scale couple of million lines of scientific codes: https://github.com/root-project/root/blob/master/interpreter/cling/lib/Interpreter/ForwardDeclPrinter.cpp. There are some tests that one can look at: https://github.com/root-project/root/tree/master/interpreter/cling/test/Autoloading

I am wondering if that'd be interesting and if so, maybe we can share it between both projects via upstreaming to Clang...

@kimgr
Copy link
Contributor

kimgr commented Apr 13, 2024

Current PR passes all my tests, both Clang (ninja ASTTests), Clangd (ninja ClangdTests) and IWYU end-to-end tests -- thanks!

@kimgr
Copy link
Contributor

kimgr commented Apr 13, 2024

I am wondering if that'd be interesting and if so, maybe we can share it between both projects via upstreaming to Clang...

That sounds fantastic, but mostly because I don't have anything to offer, and everything to benefit :)

I was just thinking about adding a .ForwardDeclaration policy to DeclPrinter this morning -- the current PolishForDeclaration output is so close. But it seemed weird to add a mode for which IWYU (a non-LLVM project) was the only user.

I suspect your ForwardDeclarePrinter is more principled. I guess it might be hard to coordinate forward-decl style between users, but ultimately that's just printing policy (or chaining libFormat, or something).

Let's maybe continue this discussion somewhere else?

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.

2 nits, else LGTM. Once you and @kimgr are in agreement with what to do next (and have looked into my nits), feel free to commit.


if (D->hasAttrs()) {
const AttrVec &Attrs = D->getAttrs();
for (auto *A : Attrs) {
if (A->isInherited() || A->isImplicit())
continue;
// Don't strip out the keyword attributes, they aren't regular attributes.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// Don't strip out the keyword attributes, they aren't regular attributes.
// Don't print out the keyword attributes, they aren't regular attributes.

??

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We should word it as Print out the keyword attributes, they aren't regular attributes., I think.

if (Pos == AttrPosAsWritten::Left)
Out << ' ';
}
break;
}
}
}
return hasPrinted;
Copy link
Collaborator

Choose a reason for hiding this comment

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

see Out->tell() here, it is perhaps less error-prone here (that is, store the uint64_t value above, and see if it changed).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was thinking to use something like that but I believe the boolean flag is more readable. I'd like to keep it the way it is.

@vgvassilev vgvassilev merged commit 71c0784 into llvm:main Apr 18, 2024
4 checks passed
@vgvassilev vgvassilev deleted the fix-attr-print-final branch April 18, 2024 06:32
@kimgr
Copy link
Contributor

kimgr commented Apr 18, 2024

@vgvassilev Thank you!

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 HLSL HLSL Language Support
Projects
None yet
Development

Successfully merging this pull request may close these issues.

DeclPrinter prints 'final' twice
4 participants