llvm-project icon indicating copy to clipboard operation
llvm-project copied to clipboard

[clangd] fix extract-to-function for overloaded operators

Open 5chmidti opened this issue 1 year ago • 5 comments

When selecting code that contains the use of overloaded operators, the SelectionTree will attribute the operator to the operator declaration, not to the CXXOperatorCallExpr. To allow extract-to-function to work with these operators, make unselected CXXOperatorCallExprs valid root statements, just like DeclStmts.

Fixes clangd/clangd#1254

5chmidti avatar Feb 13 '24 18:02 5chmidti

@llvm/pr-subscribers-clang-tools-extra

@llvm/pr-subscribers-clangd

Author: Julian Schmidt (5chmidti)

Changes

When selecting code that contains the use of overloaded operators, the SelectionTree will attribute the operator to the operator declaration, not to the CXXOperatorCallExpr. To allow extract-to-function to work with these operators, make unselected CXXOperatorCallExprs valid root statements, just like DeclStmts.

Fixes clangd/clangd#1254


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

3 Files Affected:

  • (modified) clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp (+9-6)
  • (modified) clang-tools-extra/clangd/unittests/tweaks/ExtractFunctionTests.cpp (+47)
  • (modified) clang-tools-extra/docs/ReleaseNotes.rst (+3)
diff --git a/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp b/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp
index 0302839c58252e..aae480175b33f6 100644
--- a/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp
+++ b/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp
@@ -56,6 +56,7 @@
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclBase.h"
+#include "clang/AST/ExprCXX.h"
 #include "clang/AST/NestedNameSpecifier.h"
 #include "clang/AST/RecursiveASTVisitor.h"
 #include "clang/AST/Stmt.h"
@@ -70,7 +71,6 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/Error.h"
-#include "llvm/Support/raw_os_ostream.h"
 #include <optional>
 
 namespace clang {
@@ -104,9 +104,12 @@ bool isRootStmt(const Node *N) {
   // Root statement cannot be partially selected.
   if (N->Selected == SelectionTree::Partial)
     return false;
-  // Only DeclStmt can be an unselected RootStmt since VarDecls claim the entire
-  // selection range in selectionTree.
-  if (N->Selected == SelectionTree::Unselected && !N->ASTNode.get<DeclStmt>())
+  // A DeclStmt can be an unselected RootStmt since VarDecls claim the entire
+  // selection range in selectionTree. Additionally, an CXXOperatorCallExpr of a
+  // binary operation can be unselected because it's children claim the entire
+  // selection range in the selection tree (e.g. <<).
+  if (N->Selected == SelectionTree::Unselected && !N->ASTNode.get<DeclStmt>() &&
+      !N->ASTNode.get<CXXOperatorCallExpr>())
     return false;
   return true;
 }
@@ -913,8 +916,8 @@ Expected<Tweak::Effect> ExtractFunction::apply(const Selection &Inputs) {
 
       tooling::Replacements OtherEdit(
           createForwardDeclaration(*ExtractedFunc, SM));
-      if (auto PathAndEdit = Tweak::Effect::fileEdit(SM, SM.getFileID(*FwdLoc),
-                                                 OtherEdit))
+      if (auto PathAndEdit =
+              Tweak::Effect::fileEdit(SM, SM.getFileID(*FwdLoc), OtherEdit))
         MultiFileEffect->ApplyEdits.try_emplace(PathAndEdit->first,
                                                 PathAndEdit->second);
       else
diff --git a/clang-tools-extra/clangd/unittests/tweaks/ExtractFunctionTests.cpp b/clang-tools-extra/clangd/unittests/tweaks/ExtractFunctionTests.cpp
index dec63d454d52c6..8e347b516c6ffe 100644
--- a/clang-tools-extra/clangd/unittests/tweaks/ExtractFunctionTests.cpp
+++ b/clang-tools-extra/clangd/unittests/tweaks/ExtractFunctionTests.cpp
@@ -571,6 +571,53 @@ int getNum(bool Superstitious, int Min, int Max) {
   EXPECT_EQ(apply(Before), After);
 }
 
+TEST_F(ExtractFunctionTest, OverloadedOperators) {
+  Context = File;
+  std::string Before = R"cpp(struct A {
+                int operator+(int x) { return x; }
+              };
+              A &operator<<(A &, int);
+              A &operator|(A &, int);
+
+              A stream{};
+
+              void foo(int, int);
+
+              int main() {
+                [[foo(1, 2);
+                foo(3, 4);
+                stream << 42;
+                stream + 42;
+                stream | 42;
+                foo(1, 2);
+                foo(3, 4);]]
+              })cpp";
+  std::string After =
+      R"cpp(struct A {
+                int operator+(int x) { return x; }
+              };
+              A &operator<<(A &, int);
+              A &operator|(A &, int);
+
+              A stream{};
+
+              void foo(int, int);
+
+              void extracted() {
+foo(1, 2);
+                foo(3, 4);
+                stream << 42;
+                stream + 42;
+                stream | 42;
+                foo(1, 2);
+                foo(3, 4);
+}
+int main() {
+                extracted();
+              })cpp";
+  EXPECT_EQ(apply(Before), After);
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index f2fba9aa1450d6..01cd2278c1fb18 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -69,6 +69,9 @@ Code completion
 Code actions
 ^^^^^^^^^^^^
 
+- Improved the extract-to-function code action to allow extracting statements
+  with overloaded operators like ``<<`` of ``std::ostream``.
+
 Signature help
 ^^^^^^^^^^^^^^
 

llvmbot avatar Feb 13 '24 18:02 llvmbot

Ping

5chmidti avatar Feb 24 '24 13:02 5chmidti

Rebase & Ping

5chmidti avatar Mar 24 '24 13:03 5chmidti

:white_check_mark: With the latest revision this PR passed the C/C++ code formatter.

github-actions[bot] avatar Mar 24 '24 13:03 github-actions[bot]

:white_check_mark: With the latest revision this PR passed the Python code formatter.

github-actions[bot] avatar Mar 24 '24 13:03 github-actions[bot]

Would be great to see this merged. This bug prevents basic function extractions from working.

BenBlumer avatar Jul 17 '24 21:07 BenBlumer

I can confirm this builds and fixes the issue. For example, in:

#include <iostream>

#include <ostream>




int main() {

int a = 5;

int b = 7;

int c;

c =  a+b;

std::cout <<  a+b << std::endl;

return 0;

}

Without this patch, there is no refactor available for:

c =  a+b;

std::cout <<  a+b << std::endl

With the patch, it can be extracted

BenBlumer avatar Jul 17 '24 23:07 BenBlumer

Needs rebase and another typo fix. Looks good to me otherwise.

ckandeler avatar Oct 17 '24 10:10 ckandeler

@5chmidti I'm excited for this to be mainlined. Give me a thumbs up if you want me to fix the typo and rebase for you -- happy to do it this weekend.

BenBlumer avatar Oct 25 '24 22:10 BenBlumer

I'm a bit of a outsider to the LLVM project, but I'd say a fix to a broken, already shipped, feature now is better than a general fix down the line.

On Sat, Oct 26, 2024, 3:23 AM Julian Schmidt @.***> wrote:

@.**** commented on this pull request.

CC @HighCommander4 https://github.com/HighCommander4 @kadircet https://github.com/kadircet

Technically, Sam told me last EuroLLVM that he had some refactor of the SelectionTree that would resolve issues like this in the general case, but as it is unclear that those changes will get in someday/in the 'near' future, I think that this PR makes sense to merge. Even though this is kind of a band-aid fix. Thoughts?

In clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp https://github.com/llvm/llvm-project/pull/81640#discussion_r1817768904:

  • // selection range in selectionTree. Additionally, a CXXOperatorCallExpr of a
  • // binary operation can be unselected because it's children claim the entire

⬇️ Suggested change

  • // selection range in selectionTree. Additionally, a CXXOperatorCallExpr of a
  • // binary operation can be unselected because it's children claim the entire
  • // selection range in selectionTree. Additionally, a CXXOperatorCallExpr of a
  • // binary operation can be unselected because its children claim the entire

— Reply to this email directly, view it on GitHub https://github.com/llvm/llvm-project/pull/81640#pullrequestreview-2397141474, or unsubscribe https://github.com/notifications/unsubscribe-auth/AA3J4MJEX4VEB24H7HVXXWTZ5NURZAVCNFSM6AAAAABDG6LIWCVHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHMZDGOJXGE2DCNBXGQ . You are receiving this because you commented.Message ID: @.***>

BenBlumer avatar Oct 26 '24 18:10 BenBlumer

Do I understand correctly that this is a partial fix for https://github.com/clangd/clangd/issues/1254 in that it addresses the issue with overloaded operators in particular, but it still leaves in place the limitation that a single expression statement (of any kind) cannot be extracted?

If so, would you mind adding an "unavailable" test case for the latter case, with some sort of "TODO, we'd like to relax this limitation" comment?

HighCommander4 avatar Oct 28 '24 07:10 HighCommander4

Do I understand correctly that this is a partial fix for clangd/clangd#1254 in that it addresses the issue with overloaded operators in particular, but it still leaves in place the limitation that a single expression statement (of any kind) cannot be extracted?

Exactly

If so, would you mind adding an "unavailable" test case for the latter case, with some sort of "TODO, we'd like to relax this limitation" comment?

Done

5chmidti avatar Oct 31 '24 09:10 5chmidti

Thanks for your work on this, Julian! I'm excited to have this tool work properly!

BenBlumer avatar Nov 01 '24 16:11 BenBlumer