Skip to content

Conversation

@5chmidti
Copy link
Contributor

@5chmidti 5chmidti commented Feb 13, 2024

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.

Partially fixes clangd/clangd#1254

@llvmbot
Copy link
Member

llvmbot commented Feb 13, 2024

@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
 ^^^^^^^^^^^^^^
 

@5chmidti
Copy link
Contributor Author

Ping

@5chmidti 5chmidti force-pushed the clangd-extract-function-operators branch from 6efc30e to 6b62ed1 Compare March 24, 2024 13:09
@5chmidti
Copy link
Contributor Author

Rebase & Ping

@github-actions
Copy link

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

@github-actions
Copy link

✅ With the latest revision this PR passed the Python code formatter.

@BenBlumer
Copy link
Contributor

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

@BenBlumer
Copy link
Contributor

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

@ckandeler
Copy link
Member

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

@BenBlumer
Copy link
Contributor

@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.

Copy link
Contributor Author

@5chmidti 5chmidti left a comment

Choose a reason for hiding this comment

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

CC @HighCommander4 @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?

@BenBlumer
Copy link
Contributor

BenBlumer commented Oct 26, 2024 via email

@HighCommander4
Copy link
Collaborator

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?

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?

@5chmidti 5chmidti force-pushed the clangd-extract-function-operators branch from 9d67aa7 to a8f90e7 Compare October 31, 2024 09:37
@5chmidti
Copy link
Contributor Author

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

@BenBlumer
Copy link
Contributor

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

// selection range in selectionTree. Additionally, a 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>() &&
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need to make a corresponding change here? The comment suggests that it's special-casing DeclStmt for the same reason as this code.

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 don't think that would have any effect, because to hit this case, the common ancestor of our root statements needs to be completely selected, but the CXXOperatorCallExpr will be unselected as shown in this if here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess the difference is that a DeclStmt can have a single VarDecl child which could be completely selected, leaving the DeclStmt unselected. There is no analogous situation for a CXXOperatorCallExpr which has multiple children.

// FIXME: Expressions are currently not extracted
EXPECT_EQ(apply(R"cpp(
void sink(int);
void call() { sink([[1+1]]); }
Copy link
Collaborator

@HighCommander4 HighCommander4 Nov 4, 2024

Choose a reason for hiding this comment

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

I was thinking more specifically about entire expression-statements, e.g. [[stream << 42;]].

Extraction of arbitrary subexpressions would be nice as well, but likely more involved; there is a work in progress patch here.

(We can keep this test case too.)

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've added another testcase for extracting a single expression

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
`CXXOperatorCallExpr`s valid root statements, just like `DeclStmt`s.

Partially fixes clangd/clangd#1254
@5chmidti 5chmidti force-pushed the clangd-extract-function-operators branch from a8f90e7 to 38003e3 Compare November 10, 2024 18:05
Copy link
Collaborator

@HighCommander4 HighCommander4 left a comment

Choose a reason for hiding this comment

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

Thanks. +1 to taking this targeted fix without waiting for a larger refactor.

// selection range in selectionTree. Additionally, a 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>() &&
Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess the difference is that a DeclStmt can have a single VarDecl child which could be completely selected, leaving the DeclStmt unselected. There is no analogous situation for a CXXOperatorCallExpr which has multiple children.

@HighCommander4 HighCommander4 merged commit 9ae21b0 into llvm:main Nov 15, 2024
9 checks passed
@5chmidti 5chmidti deleted the clangd-extract-function-operators branch November 26, 2024 19:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"Extract to Function" tweak often not offered where it should be

5 participants