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

Skip to content

[clang-refactor] Add Matcher Edit refactoring rule #123782

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 3 commits into
base: main
Choose a base branch
from

Conversation

IgnatSergeev
Copy link

Modified capabilities of refactoring engine and clang-refactor tool:

  • Added source location argument for clang-refactor tool
  • Added corresponding source location requirement, that requests SourceLocation
  • Added AST matcher(hasPointWithin), that checks that location is inside result node SourceRange
  • Added AST location match requirement, that requests ast_matcher::DeclarationMatcher or StatementMatcher, SourceLocation(it is source location requirement), wrappes given matcher with hasPointWithin matcher using requested location, matches AST and provides last match result
  • Added AST edit requirement, that requests and provides transformer::ASTEdit
  • Added Edit Match Refactoring rule, that edits given match result with given ASTEdit

Игнат Сергеев added 3 commits January 21, 2025 19:55
Added source location requirement for refactoring engine
Added source location argument for clang-refactor cli
Added matcher requirement, edit generator requirement, and edit match rule
Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jan 21, 2025
@llvmbot
Copy link
Member

llvmbot commented Jan 21, 2025

@llvm/pr-subscribers-clang

Author: Сергеев Игнатий (IgnatSergeev)

Changes

Modified capabilities of refactoring engine and clang-refactor tool:

  • Added source location argument for clang-refactor tool
  • Added corresponding source location requirement, that requests SourceLocation
  • Added AST matcher(hasPointWithin), that checks that location is inside result node SourceRange
  • Added AST location match requirement, that requests ast_matcher::DeclarationMatcher or StatementMatcher, SourceLocation(it is source location requirement), wrappes given matcher with hasPointWithin matcher using requested location, matches AST and provides last match result
  • Added AST edit requirement, that requests and provides transformer::ASTEdit
  • Added Edit Match Refactoring rule, that edits given match result with given ASTEdit

Patch is 22.38 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/123782.diff

9 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticRefactoringKinds.td (+6)
  • (added) clang/include/clang/Tooling/Refactoring/Edit/EditMatchRule.h (+49)
  • (modified) clang/include/clang/Tooling/Refactoring/RefactoringActionRule.h (+4)
  • (modified) clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h (+101-1)
  • (modified) clang/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h (+5)
  • (modified) clang/include/clang/Tooling/Refactoring/RefactoringRuleContext.h (+12)
  • (modified) clang/lib/Tooling/Refactoring/CMakeLists.txt (+2)
  • (added) clang/lib/Tooling/Refactoring/Edit/EditMatchRule.cpp (+74)
  • (modified) clang/tools/clang-refactor/ClangRefactor.cpp (+143-6)
diff --git a/clang/include/clang/Basic/DiagnosticRefactoringKinds.td b/clang/include/clang/Basic/DiagnosticRefactoringKinds.td
index e060fffc7280a7..9eacb76cd9dadc 100644
--- a/clang/include/clang/Basic/DiagnosticRefactoringKinds.td
+++ b/clang/include/clang/Basic/DiagnosticRefactoringKinds.td
@@ -28,6 +28,12 @@ def err_refactor_extract_simple_expression : Error<"the selected expression "
 def err_refactor_extract_prohibited_expression : Error<"the selected "
   "expression cannot be extracted">;
 
+def err_refactor_no_location : Error<"refactoring action can't be initiated "
+  "without a location">;
+def err_refactor_no_location_match : Error<"refactoring action can't be initiated "
+  "without a matching location">;
+def err_refactor_invalid_edit_generator : Error<"refactoring action can't be initiated "
+  "without a correct edit generator">;
 }
 
 } // end of Refactoring diagnostics
diff --git a/clang/include/clang/Tooling/Refactoring/Edit/EditMatchRule.h b/clang/include/clang/Tooling/Refactoring/Edit/EditMatchRule.h
new file mode 100644
index 00000000000000..46e5aa54c0bee5
--- /dev/null
+++ b/clang/include/clang/Tooling/Refactoring/Edit/EditMatchRule.h
@@ -0,0 +1,49 @@
+//===--- EditMatchRule.h - Clang refactoring library ----------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLING_REFACTORING_EDIT_EDITMATCHRULE_H
+#define LLVM_CLANG_TOOLING_REFACTORING_EDIT_EDITMATCHRULE_H
+
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Tooling/Refactoring/RefactoringActionRules.h"
+#include "clang/Tooling/Transformer/RewriteRule.h"
+
+namespace clang {
+namespace tooling {
+
+/// A "Edit Match" refactoring rule edits code around matches according to
+/// EditGenerator.
+class EditMatchRule final : public SourceChangeRefactoringRule {
+public:
+  /// Initiates the delete match refactoring operation.
+  ///
+  /// \param R    MatchResult  Match result to edit.
+  /// \param EG    EditGenerator  Edit to perform.
+  static Expected<EditMatchRule>
+  initiate(RefactoringRuleContext &Context,
+           ast_matchers::MatchFinder::MatchResult R,
+           transformer::EditGenerator EG);
+
+  static const RefactoringDescriptor &describe();
+
+private:
+  EditMatchRule(ast_matchers::MatchFinder::MatchResult R,
+                transformer::EditGenerator EG)
+      : Result(std::move(R)), EditGenerator(std::move(EG)) {}
+
+  Expected<AtomicChanges>
+  createSourceReplacements(RefactoringRuleContext &Context) override;
+
+  ast_matchers::MatchFinder::MatchResult Result;
+  transformer::EditGenerator EditGenerator;
+};
+
+} // end namespace tooling
+} // end namespace clang
+
+#endif // LLVM_CLANG_TOOLING_REFACTORING_EDIT_EDITMATCHRULE_H
diff --git a/clang/include/clang/Tooling/Refactoring/RefactoringActionRule.h b/clang/include/clang/Tooling/Refactoring/RefactoringActionRule.h
index c6a6c4f6093a34..374f19d6d82338 100644
--- a/clang/include/clang/Tooling/Refactoring/RefactoringActionRule.h
+++ b/clang/include/clang/Tooling/Refactoring/RefactoringActionRule.h
@@ -56,6 +56,10 @@ class RefactoringActionRule : public RefactoringActionRuleBase {
   /// to be fulfilled before refactoring can be performed.
   virtual bool hasSelectionRequirement() = 0;
 
+  /// Returns true when the rule has a source location requirement that has
+  /// to be fulfilled before refactoring can be performed.
+  virtual bool hasLocationRequirement() = 0;
+
   /// Traverses each refactoring option used by the rule and invokes the
   /// \c visit callback in the consumer for each option.
   ///
diff --git a/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h b/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h
index 1a318da3acca19..4ad7fbbef31620 100644
--- a/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h
+++ b/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h
@@ -9,13 +9,18 @@
 #ifndef LLVM_CLANG_TOOLING_REFACTORING_REFACTORINGACTIONRULEREQUIREMENTS_H
 #define LLVM_CLANG_TOOLING_REFACTORING_REFACTORINGACTIONRULEREQUIREMENTS_H
 
+#include "clang/AST/ASTTypeTraits.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/ASTMatchers/ASTMatchersMacros.h"
 #include "clang/Basic/LLVM.h"
+#include "clang/Basic/SourceLocation.h"
 #include "clang/Tooling/Refactoring/ASTSelection.h"
 #include "clang/Tooling/Refactoring/RefactoringDiagnostic.h"
 #include "clang/Tooling/Refactoring/RefactoringOption.h"
 #include "clang/Tooling/Refactoring/RefactoringRuleContext.h"
+#include "clang/Tooling/Transformer/RewriteRule.h"
 #include "llvm/Support/Error.h"
-#include <type_traits>
 
 namespace clang {
 namespace tooling {
@@ -77,6 +82,101 @@ class CodeRangeASTSelectionRequirement : public ASTSelectionRequirement {
   evaluate(RefactoringRuleContext &Context) const;
 };
 
+/// A base class for any requirement that expects source code position (or the
+/// refactoring tool with the -location option).
+class SourceLocationRequirement : public RefactoringActionRuleRequirement {
+public:
+  Expected<SourceLocation> evaluate(RefactoringRuleContext &Context) const {
+    if (Context.getLocation().isValid())
+      return Context.getLocation();
+    return Context.createDiagnosticError(diag::err_refactor_no_location);
+  }
+};
+
+AST_POLYMORPHIC_MATCHER_P(hasPointWithin,
+                          AST_POLYMORPHIC_SUPPORTED_TYPES(Stmt, Decl),
+                          FullSourceLoc, L) {
+  if (!L.hasManager()) {
+    return false;
+  }
+  const SourceRange &SR = Node.getSourceRange();
+  return L.getManager().isPointWithin(L, SR.getBegin(), SR.getEnd());
+}
+
+/// An AST location match is satisfied when there is match around given
+/// location. In case of several matches inner one is taken.
+///
+/// The requirement will be evaluated only once during the initiation and
+/// search of matching refactoring action rules.
+template <typename MatcherType>
+class ASTLocMatchRequirement : public SourceLocationRequirement {
+public:
+  static_assert(
+      std::is_same<ast_matchers::StatementMatcher, MatcherType>::value ||
+          std::is_same<ast_matchers::DeclarationMatcher, MatcherType>::value,
+      "Expected a Statement or Declaration matcher");
+
+  class LocMatchCallback : public ast_matchers::MatchFinder::MatchCallback {
+  public:
+    void run(const clang::ast_matchers::MatchFinder::MatchResult &R) override {
+      Result = std::make_unique<ast_matchers::MatchFinder::MatchResult>(R);
+    }
+    std::unique_ptr<ast_matchers::MatchFinder::MatchResult> Result;
+  };
+
+  Expected<ast_matchers::MatchFinder::MatchResult>
+  evaluate(RefactoringRuleContext &Context) const {
+    Expected<SourceLocation> Location =
+        SourceLocationRequirement::evaluate(Context);
+    if (!Location)
+      return Location.takeError();
+    MatcherType M = createWrapperMatcher(
+        FullSourceLoc(*Location, Context.getASTContext().getSourceManager()),
+        Matcher);
+
+    ast_matchers::MatchFinder MF;
+    LocMatchCallback Callback;
+    MF.addMatcher(M, &Callback);
+    MF.matchAST(Context.getASTContext());
+    if (!Callback.Result)
+      return Context.createDiagnosticError(
+          diag::err_refactor_no_location_match);
+    return *Callback.Result;
+  }
+
+  ASTLocMatchRequirement(MatcherType M) : Matcher(M) {}
+
+private:
+  ast_matchers::StatementMatcher
+  createWrapperMatcher(FullSourceLoc L,
+                       ast_matchers::StatementMatcher M) const {
+    return ast_matchers::stmt(M, hasPointWithin(L)).bind(transformer::RootID);
+  }
+
+  ast_matchers::DeclarationMatcher
+  createWrapperMatcher(FullSourceLoc L,
+                       ast_matchers::DeclarationMatcher M) const {
+    return ast_matchers::decl(M, hasPointWithin(L)).bind(transformer::RootID);
+  }
+
+  MatcherType Matcher;
+};
+
+/// Requirement that evaluates to the EditGenerator value given at its creation.
+class EditGeneratorRequirement : public RefactoringActionRuleRequirement {
+public:
+  Expected<transformer::EditGenerator>
+  evaluate(RefactoringRuleContext &Context) const {
+    return EditGenerator;
+  }
+
+  EditGeneratorRequirement(transformer::EditGenerator EG)
+      : EditGenerator(std::move(EG)) {}
+
+private:
+  transformer::EditGenerator EditGenerator;
+};
+
 /// A base class for any requirement that requires some refactoring options.
 class RefactoringOptionsRequirement : public RefactoringActionRuleRequirement {
 public:
diff --git a/clang/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h b/clang/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h
index 33194c401ea143..52afb012f4874c 100644
--- a/clang/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h
+++ b/clang/include/clang/Tooling/Refactoring/RefactoringActionRulesInternal.h
@@ -139,6 +139,11 @@ createRefactoringActionRule(const RequirementTypes &... Requirements) {
                                  RequirementTypes...>::value;
     }
 
+    bool hasLocationRequirement() override {
+      return internal::HasBaseOf<SourceLocationRequirement,
+                                 RequirementTypes...>::value;
+    }
+
     void visitRefactoringOptions(RefactoringOptionVisitor &Visitor) override {
       internal::visitRefactoringOptions(
           Visitor, Requirements,
diff --git a/clang/include/clang/Tooling/Refactoring/RefactoringRuleContext.h b/clang/include/clang/Tooling/Refactoring/RefactoringRuleContext.h
index 7d97f811f024e0..85bba662afcd28 100644
--- a/clang/include/clang/Tooling/Refactoring/RefactoringRuleContext.h
+++ b/clang/include/clang/Tooling/Refactoring/RefactoringRuleContext.h
@@ -30,6 +30,9 @@ namespace tooling {
 ///
 ///   - SelectionRange: an optional source selection ranges that can be used
 ///     to represent a selection in an editor.
+///
+///   - Location: an optional source location that can be used
+///     to represent a cursor in an editor.
 class RefactoringRuleContext {
 public:
   RefactoringRuleContext(const SourceManager &SM) : SM(SM) {}
@@ -40,8 +43,14 @@ class RefactoringRuleContext {
   /// refactoring engine. Can be invalid.
   SourceRange getSelectionRange() const { return SelectionRange; }
 
+  /// Returns the current source location as set by the
+  /// refactoring engine. Can be invalid.
+  SourceLocation getLocation() const { return Location; }
+
   void setSelectionRange(SourceRange R) { SelectionRange = R; }
 
+  void setLocation(SourceLocation L) { Location = L; }
+
   bool hasASTContext() const { return AST; }
 
   ASTContext &getASTContext() const {
@@ -73,6 +82,9 @@ class RefactoringRuleContext {
   /// An optional source selection range that's commonly used to represent
   /// a selection in an editor.
   SourceRange SelectionRange;
+  /// An optional source location that's commonly used to represent
+  /// a cursor in an editor.
+  SourceLocation Location;
   /// An optional AST for the translation unit on which a refactoring action
   /// might operate on.
   ASTContext *AST = nullptr;
diff --git a/clang/lib/Tooling/Refactoring/CMakeLists.txt b/clang/lib/Tooling/Refactoring/CMakeLists.txt
index d3077be8810aad..97023b6d6b97bb 100644
--- a/clang/lib/Tooling/Refactoring/CMakeLists.txt
+++ b/clang/lib/Tooling/Refactoring/CMakeLists.txt
@@ -13,6 +13,7 @@ add_clang_library(clangToolingRefactoring
   Rename/USRFinder.cpp
   Rename/USRFindingAction.cpp
   Rename/USRLocFinder.cpp
+  Edit/EditMatchRule.cpp
 
   LINK_LIBS
   clangAST
@@ -23,6 +24,7 @@ add_clang_library(clangToolingRefactoring
   clangLex
   clangRewrite
   clangToolingCore
+  clangTransformer
 
   DEPENDS
   omp_gen
diff --git a/clang/lib/Tooling/Refactoring/Edit/EditMatchRule.cpp b/clang/lib/Tooling/Refactoring/Edit/EditMatchRule.cpp
new file mode 100644
index 00000000000000..55c94b39237778
--- /dev/null
+++ b/clang/lib/Tooling/Refactoring/Edit/EditMatchRule.cpp
@@ -0,0 +1,74 @@
+//===--- EditMatchRule.cpp - Clang refactoring library --------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implements the "edit-match" refactoring rule that can edit matcher results
+///
+//===----------------------------------------------------------------------===//
+
+#include "clang/Tooling/Refactoring/Edit/EditMatchRule.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Basic/DiagnosticRefactoring.h"
+#include "clang/Rewrite/Core/Rewriter.h"
+#include "clang/Tooling/Refactoring/AtomicChange.h"
+#include "clang/Tooling/Transformer/RewriteRule.h"
+#include "clang/Tooling/Transformer/SourceCode.h"
+
+using namespace clang;
+using namespace tooling;
+using namespace transformer;
+
+Expected<EditMatchRule>
+EditMatchRule::initiate(RefactoringRuleContext &Context,
+                        ast_matchers::MatchFinder::MatchResult R,
+                        transformer::EditGenerator EG) {
+  return EditMatchRule(std::move(R), std::move(EG));
+}
+
+const RefactoringDescriptor &EditMatchRule::describe() {
+  static const RefactoringDescriptor Descriptor = {
+      "edit-match",
+      "Edit Match",
+      "Edits match result source code",
+  };
+  return Descriptor;
+}
+
+Expected<AtomicChanges>
+EditMatchRule::createSourceReplacements(RefactoringRuleContext &Context) {
+  ASTContext &AST = Context.getASTContext();
+  SourceManager &SM = AST.getSourceManager();
+  Expected<SmallVector<transformer::Edit, 1>> Edits = EditGenerator(Result);
+
+  if (!Edits) {
+    return std::move(Edits.takeError());
+  }
+  if (Edits->empty())
+    return Context.createDiagnosticError(
+        diag::err_refactor_invalid_edit_generator);
+
+  AtomicChange Change(SM, Edits->front().Range.getBegin());
+  {
+    for (const auto &Edit : *Edits) {
+      switch (Edit.Kind) {
+      case EditKind::Range:
+        if (auto Err = Change.replace(SM, std::move(Edit.Range),
+                                      std::move(Edit.Replacement))) {
+          return std::move(Err);
+        }
+        break;
+      case EditKind::AddInclude:
+        Change.addHeader(std::move(Edit.Replacement));
+        break;
+      }
+    }
+  }
+
+  return AtomicChanges{std::move(Change)};
+}
diff --git a/clang/tools/clang-refactor/ClangRefactor.cpp b/clang/tools/clang-refactor/ClangRefactor.cpp
index 968f0594085d40..937e605a3dafbb 100644
--- a/clang/tools/clang-refactor/ClangRefactor.cpp
+++ b/clang/tools/clang-refactor/ClangRefactor.cpp
@@ -164,6 +164,85 @@ SourceSelectionArgument::fromString(StringRef Value) {
   return nullptr;
 }
 
+/// Stores the parsed `-location` argument.
+class AbstractSourceLocationArgument {
+public:
+  virtual ~AbstractSourceLocationArgument() {}
+
+  /// Parse the `-location` argument.
+  ///
+  /// \returns A valid argument when the parse succedeed, null otherwise.
+  static std::unique_ptr<AbstractSourceLocationArgument>
+  fromString(StringRef Value);
+
+  /// Prints any additional state associated with the location argument to
+  /// the given output stream.
+  virtual void print(raw_ostream &OS) {}
+
+  /// Returns a replacement refactoring result consumer (if any) that should
+  /// consume the results of a refactoring operation.
+  ///
+  /// The replacement refactoring result consumer is used by \c
+  /// TestSourceLocationArgument to inject a test-specific result handling
+  /// logic into the refactoring operation. The test-specific consumer
+  /// ensures that the individual results in a particular test group are
+  /// identical.
+  virtual std::unique_ptr<ClangRefactorToolConsumerInterface>
+  createCustomConsumer() {
+    return nullptr;
+  }
+
+  /// Runs the given refactoring function for each specified location.
+  ///
+  /// \returns true if an error occurred, false otherwise.
+  virtual bool
+  forAllLocations(const SourceManager &SM,
+                  llvm::function_ref<void(SourceLocation L)> Callback) = 0;
+};
+
+/// Stores the parsed -location=filename:line:column option.
+class SourceLocationArgument final : public AbstractSourceLocationArgument {
+public:
+  SourceLocationArgument(ParsedSourceLocation Location)
+      : Location(std::move(Location)) {}
+
+  bool forAllLocations(
+      const SourceManager &SM,
+      llvm::function_ref<void(SourceLocation L)> Callback) override {
+    auto FE = SM.getFileManager().getFile(Location.FileName);
+    FileID FID = FE ? SM.translateFile(*FE) : FileID();
+    if (!FE || FID.isInvalid()) {
+      llvm::errs() << "error: -location=" << Location.FileName
+                   << ":... : given file is not in the target TU\n";
+      return true;
+    }
+
+    SourceLocation Loc = SM.getMacroArgExpandedLocation(
+        SM.translateLineCol(FID, Location.Line, Location.Column));
+    if (Loc.isInvalid()) {
+      llvm::errs() << "error: -location=" << Location.FileName << ':'
+                   << Location.Line << ':' << Location.Column
+                   << " : invalid source location\n";
+      return true;
+    }
+    Callback(Loc);
+    return false;
+  }
+
+private:
+  ParsedSourceLocation Location;
+};
+
+std::unique_ptr<AbstractSourceLocationArgument>
+AbstractSourceLocationArgument::fromString(StringRef Value) {
+  ParsedSourceLocation Location = ParsedSourceLocation::FromString(Value);
+  if (Location.FileName != "")
+    return std::make_unique<SourceLocationArgument>(std::move(Location));
+  llvm::errs() << "error: '-location' option must be specified using "
+                  "<file>:<line>:<column>\n";
+  return nullptr;
+}
+
 /// A container that stores the command-line options used by a single
 /// refactoring option.
 class RefactoringActionCommandLineOptions {
@@ -272,6 +351,17 @@ class RefactoringActionSubcommand : public cl::SubCommand {
         break;
       }
     }
+    // Check if the location option is supported.
+    for (const auto &Rule : this->ActionRules) {
+      if (Rule->hasLocationRequirement()) {
+        Location = std::make_unique<cl::opt<std::string>>(
+            "location",
+            cl::desc("Location where refactoring should "
+                     "be initiated( <file>:<line>:<column>)"),
+            cl::cat(Category), cl::sub(*this));
+        break;
+      }
+    }
     // Create the refactoring options.
     for (const auto &Rule : this->ActionRules) {
       CommandLineRefactoringOptionCreator OptionCreator(Category, *this,
@@ -296,11 +386,28 @@ class RefactoringActionSubcommand : public cl::SubCommand {
     return false;
   }
 
+  /// Parses the "-location" command-line argument.
+  ///
+  /// \returns true on error, false otherwise.
+  bool parseLocationArgument() {
+    if (Location) {
+      ParsedLocation = AbstractSourceLocationArgument::fromString(*Location);
+      if (!ParsedLocation)
+        return true;
+    }
+    return false;
+  }
+
   SourceSelectionArgument *getSelection() const {
     assert(Selection && "selection not supported!");
     return ParsedSelection.get();
   }
 
+  AbstractSourceLocationArgument *getLocation() const {
+    assert(Location && "location not supported!");
+    return ParsedLocation.get();
+  }
+
   const RefactoringActionCommandLineOptions &getOptions() const {
     return Options;
   }
@@ -309,7 +416,9 @@ class RefactoringActionSubcommand : public cl::SubCommand {
   std::unique_ptr<RefactoringAction> Action;
   RefactoringActionRules ActionRules;
   std::unique_ptr<cl::opt<std::string>> Selection;
+  std::unique_ptr<cl::opt<std::string>> Location;
   std::unique_ptr<SourceSelectionArgument> ParsedSelection;
+  std::unique_ptr<AbstractSourceLocationArgument> ParsedLocation;
   RefactoringActionCommandLineOptions Options;
 };
 
@@ -399,6 +508,7 @@ class ClangRefactorTool {
     // consumer.
     std::unique_ptr<ClangRefactorToolConsumerInterface> TestConsumer;
     bool HasSelection = MatchingRule->hasSelectionRequirement();
+    bool HasLocation = MatchingRule->hasLocationRequirement();
     if (HasSelection)
       TestConsumer = Selected...
[truncated]

@IgnatSergeev IgnatSergeev mentioned this pull request Jan 21, 2025
@bartlettroscoe
Copy link

@IgnatSergeev, thanks for posting this PR!

First question, where is this new code tested?

Do you know the status of automated testing for this LLVM refactoring support software and the clang-refactor tool?

According to:

unit, integration and regression tests can be added under llvm/unittests/ and llvm/test/. Is there a way to add tests for new code like this?

I don't see any existing tests for the clang-refactor tool itself looking there:

$ cd llvm-project/

$ git log-short --name-status -1 HEAD
3d08fa25824c "[libc++] Another _LIBCPP_NODEBUG fix"
Author: Louis Dionne <[email protected]>
Date:   Mon Jan 20 14:15:50 2025 -0500 (25 hours ago)

$ cd llvm/

M       libcxx/include/future

$ find unittests test -type f -exec grep -nHi clang-refactor {} \;
[empty]

But do see some hits under clang/test/Refactor/:

$ find . -type f -exec grep -nHi 'clang-refactor' {} \;

...
./clang/test/Refactor/Extract/ExtractExprIntoFunction.cpp:1:// RUN: clang-refactor extract -selection=test:%s %s -- -std=c++14 2>&1 | grep -v CHECK | FileCheck %s                                                 
./clang/test/Refactor/Extract/ExtractionSemicolonPolicy.cpp:1:// RUN: clang-refactor extract -selection=test:%s %s -- -std=c++11 -fcxx-exceptions | grep -v CHECK | FileCheck %s                                   
./clang/test/Refactor/Extract/ExtractionSemicolonPolicy.m:1:// RUN: clang-refactor extract -selection=test:%s %s -- 2>&1 | grep -v CHECK | FileCheck %s                                                            
./clang/test/Refactor/Extract/FromMethodToFunction.cpp:1:// RUN: clang-refactor extract -selection=test:%s %s -- -std=c++11 2>&1 | grep -v CHECK | FileCheck --check-prefixes=CHECK,CHECK-INNER %s                 
./clang/test/Refactor/Extract/FromMethodToFunction.cpp:2:// RUN: clang-refactor extract -selection=test:%s %s -- -std=c++11 -DMULTIPLE 2>&1 | grep -v CHECK | FileCheck --check-prefixes=CHECK,CHECK-OUTER %s      
./clang/test/Refactor/Extract/ObjCProperty.m:1:// RUN: clang-refactor extract -selection=test:%s %s -- 2>&1 | grep -v CHECK | FileCheck %s                                                                         
./clang/test/Refactor/LocalRename/BuiltinOffsetof.cpp:1:// RUN: clang-refactor local-rename -selection=test:%s -new-name=bar %s -- | grep -v CHECK | FileCheck %s                                                  
./clang/test/Refactor/LocalRename/Field.cpp:1:// RUN: clang-refactor local-rename -selection=test:%s -new-name=Bar %s -- | grep -v CHECK | FileCheck %s                                                            
./clang/test/Refactor/LocalRename/NoSymbolSelectedError.cpp:1:// RUN: not clang-refactor local-rename -selection=%s:4:1 -new-name=Bar %s -- 2>&1 | grep -v CHECK | FileCheck %s                                    
./clang/test/Refactor/LocalRename/NoSymbolSelectedError.cpp:2:// RUN: clang-refactor local-rename -selection=test:%s -new-name=Bar %s -- 2>&1 | grep -v CHECK | FileCheck --check-prefix=TESTCHECK %s              
./clang/test/Refactor/LocalRename/QualifiedRename.cpp:1:// RUN: clang-refactor local-rename -old-qualified-name="foo::A" -new-qualified-name="bar::B" %s -- -std=c++11 2>&1 | grep -v CHECK | FileCheck %s         
./clang/test/Refactor/tool-apply-replacements.cpp:2:// RUN: clang-refactor local-rename -selection=%t.cpp:7:7 -new-name=test %t.cpp -- | FileCheck %s                                                              
./clang/test/Refactor/tool-apply-replacements.cpp:3:// RUN: clang-refactor local-rename -selection=%t.cpp:7:7-7:15 -new-name=test %t.cpp -- | FileCheck %s                                                         
./clang/test/Refactor/tool-apply-replacements.cpp:4:// RUN: clang-refactor local-rename -i -selection=%t.cpp:7:7 -new-name=test %t.cpp --                                                                          
./clang/test/Refactor/tool-common-options.c:1:// RUN: not clang-refactor 2>&1 | FileCheck --check-prefix=MISSING_ACTION %s                                                                                         
./clang/test/Refactor/tool-selection-option.c:3:// RUN: clang-refactor local-rename -selection=%t.cp.c:6:5 -new-name=test -v %t.cp.c -- | FileCheck --check-prefix=CHECK1 %s                                       
./clang/test/Refactor/tool-selection-option.c:4:// RUN: clang-refactor local-rename -selection=%t.cp.c:6:5-6:9 -new-name=test -v %t.cp.c -- | FileCheck --check-prefix=CHECK2 %s                                   
./clang/test/Refactor/tool-selection-option.c:14:// RUN: not clang-refactor local-rename -selection=%s:6:5 -new-name=test -v %t.cp.c -- 2>&1 | FileCheck --check-prefix=CHECK-FILE-ERR %s                          
./clang/test/Refactor/tool-test-support.c:1:// RUN: clang-refactor local-rename -selection=test:%s -new-name=test -v %s -- | FileCheck %s                                                                          
...

There is no mention at all about tests under clang/test/Refactor/ in https://llvm.org/docs/TestingGuide.html (or anywhere in a Google search that I can find).

Looking at the GitHub Actions defined for this GitHub repo at:

and it is not clear which of these GHAs run run actual tests. (Looks like most of those GHAs are just doing some PR automation and not really testing anything.)

@IgnatSergeev
Copy link
Author

IgnatSergeev commented Jan 26, 2025

@IgnatSergeev, thanks for posting this PR!

First question, where is this new code tested?

Do you know the status of automated testing for this LLVM refactoring support software and the clang-refactor tool?

According to:

unit, integration and regression tests can be added under llvm/unittests/ and llvm/test/. Is there a way to add tests for new code like this?

I don't see any existing tests for the clang-refactor tool itself looking there:

$ cd llvm-project/

$ git log-short --name-status -1 HEAD
3d08fa25824c "[libc++] Another _LIBCPP_NODEBUG fix"
Author: Louis Dionne <[email protected]>
Date:   Mon Jan 20 14:15:50 2025 -0500 (25 hours ago)

$ cd llvm/

M       libcxx/include/future

$ find unittests test -type f -exec grep -nHi clang-refactor {} \;
[empty]

But do see some hits under clang/test/Refactor/:

$ find . -type f -exec grep -nHi 'clang-refactor' {} \;

...
./clang/test/Refactor/Extract/ExtractExprIntoFunction.cpp:1:// RUN: clang-refactor extract -selection=test:%s %s -- -std=c++14 2>&1 | grep -v CHECK | FileCheck %s                                                 
./clang/test/Refactor/Extract/ExtractionSemicolonPolicy.cpp:1:// RUN: clang-refactor extract -selection=test:%s %s -- -std=c++11 -fcxx-exceptions | grep -v CHECK | FileCheck %s                                   
./clang/test/Refactor/Extract/ExtractionSemicolonPolicy.m:1:// RUN: clang-refactor extract -selection=test:%s %s -- 2>&1 | grep -v CHECK | FileCheck %s                                                            
./clang/test/Refactor/Extract/FromMethodToFunction.cpp:1:// RUN: clang-refactor extract -selection=test:%s %s -- -std=c++11 2>&1 | grep -v CHECK | FileCheck --check-prefixes=CHECK,CHECK-INNER %s                 
./clang/test/Refactor/Extract/FromMethodToFunction.cpp:2:// RUN: clang-refactor extract -selection=test:%s %s -- -std=c++11 -DMULTIPLE 2>&1 | grep -v CHECK | FileCheck --check-prefixes=CHECK,CHECK-OUTER %s      
./clang/test/Refactor/Extract/ObjCProperty.m:1:// RUN: clang-refactor extract -selection=test:%s %s -- 2>&1 | grep -v CHECK | FileCheck %s                                                                         
./clang/test/Refactor/LocalRename/BuiltinOffsetof.cpp:1:// RUN: clang-refactor local-rename -selection=test:%s -new-name=bar %s -- | grep -v CHECK | FileCheck %s                                                  
./clang/test/Refactor/LocalRename/Field.cpp:1:// RUN: clang-refactor local-rename -selection=test:%s -new-name=Bar %s -- | grep -v CHECK | FileCheck %s                                                            
./clang/test/Refactor/LocalRename/NoSymbolSelectedError.cpp:1:// RUN: not clang-refactor local-rename -selection=%s:4:1 -new-name=Bar %s -- 2>&1 | grep -v CHECK | FileCheck %s                                    
./clang/test/Refactor/LocalRename/NoSymbolSelectedError.cpp:2:// RUN: clang-refactor local-rename -selection=test:%s -new-name=Bar %s -- 2>&1 | grep -v CHECK | FileCheck --check-prefix=TESTCHECK %s              
./clang/test/Refactor/LocalRename/QualifiedRename.cpp:1:// RUN: clang-refactor local-rename -old-qualified-name="foo::A" -new-qualified-name="bar::B" %s -- -std=c++11 2>&1 | grep -v CHECK | FileCheck %s         
./clang/test/Refactor/tool-apply-replacements.cpp:2:// RUN: clang-refactor local-rename -selection=%t.cpp:7:7 -new-name=test %t.cpp -- | FileCheck %s                                                              
./clang/test/Refactor/tool-apply-replacements.cpp:3:// RUN: clang-refactor local-rename -selection=%t.cpp:7:7-7:15 -new-name=test %t.cpp -- | FileCheck %s                                                         
./clang/test/Refactor/tool-apply-replacements.cpp:4:// RUN: clang-refactor local-rename -i -selection=%t.cpp:7:7 -new-name=test %t.cpp --                                                                          
./clang/test/Refactor/tool-common-options.c:1:// RUN: not clang-refactor 2>&1 | FileCheck --check-prefix=MISSING_ACTION %s                                                                                         
./clang/test/Refactor/tool-selection-option.c:3:// RUN: clang-refactor local-rename -selection=%t.cp.c:6:5 -new-name=test -v %t.cp.c -- | FileCheck --check-prefix=CHECK1 %s                                       
./clang/test/Refactor/tool-selection-option.c:4:// RUN: clang-refactor local-rename -selection=%t.cp.c:6:5-6:9 -new-name=test -v %t.cp.c -- | FileCheck --check-prefix=CHECK2 %s                                   
./clang/test/Refactor/tool-selection-option.c:14:// RUN: not clang-refactor local-rename -selection=%s:6:5 -new-name=test -v %t.cp.c -- 2>&1 | FileCheck --check-prefix=CHECK-FILE-ERR %s                          
./clang/test/Refactor/tool-test-support.c:1:// RUN: clang-refactor local-rename -selection=test:%s -new-name=test -v %s -- | FileCheck %s                                                                          
...

There is no mention at all about tests under clang/test/Refactor/ in https://llvm.org/docs/TestingGuide.html (or anywhere in a Google search that I can find).

Looking at the GitHub Actions defined for this GitHub repo at:

and it is not clear which of these GHAs run run actual tests. (Looks like most of those GHAs are just doing some PR automation and not really testing anything.)

Speaking about testing infrastructure, as you pointed out clang-refactor has tests under clang/test (and actually clang/unittests), tests for clang and tools that are based on it (including clang-refactor) are separated from llvm tests. They could be run with these cmake targets: check-clang and check-clang-unit (or you could trigger both llvm and clang tests with check-all target).
As for GitHub Actions, I think build and test CI should be triggered manually by a maintainer.

Speaking about test's content, as much as I understand, clang-refactor tool has tests for:

  • complex CLI options (clang/test/Refactor/tool-selection-option.c, ...)
  • every Refactoring Action (clang/test/Refactor/LocalRename and clang/test/Refactor/Extract)

It has some unit tests, but they mostly test ability to create Refactoring Rules, rather than test existing ones (clang/unittests/Tooling/RefactoringActionRulesTest.cpp)

CLI options are tested through Refactoring Actions, that use them, with requirement of specific test support in the tool itself (clang/tools/clang-refactor/TestSupport.cpp).

In current state, this PR adds new CLI option and Refactoring Rules, but has no new Refactoring Actions (so there is no way to test anything).

But as I mentioned in closed PR, I have some dummy Refactoring Actions, that are based on new Refactoring Rules, with tests covering them, and test support for new CLI option.

I could add every piece of code related to those Refactoring Actions and tests here, and later remove them if needed.

P. S. I'm using some Refactoring Engine terms here, if you are not sure, I recommend reading these docs https://clang.llvm.org/docs/RefactoringEngine.html

@IgnatSergeev
Copy link
Author

IgnatSergeev commented Jan 26, 2025

According to PR workflows, llvm uses external CI platform (buildkite).
buildkite/github-pull-requests/linux-linux-x64 workflow seems like the one that builds and tests changes on linux

@bartlettroscoe
Copy link

@IgnatSergeev, thanks for the detailed information on the clang-refactor testing infrastructure. We will try to run these tests and dig in deeper to see how the testing system works.

I could add every piece of code related to those Refactoring Actions and tests here, and later remove them if needed.

No need for unnecessary work. We just appreciate your willingness to help answer our questions 😊

I have been looking for other PRs that extend clang-refactor, but it is hard to find them. There is not even a GitHub Issue label clang-refactor, so it is hard to find PRs and Issues specific to that tool. (Unlike for the tools with labels clang-tidy and clang-format.)

P. S. I'm using some Refactoring Engine terms here, if you are not sure, I recommend reading these docs clang.llvm.org/docs/RefactoringEngine.html

Thanks! We have been looking that over.

Do you know why so few refactorings are supported in the clang-refactor tool or the clangd tool? For example, the extract function refactoring seems to only extracts a new function from a given range of code in a larger function. It does not seem refactor the larger original function to call the new extracted function (which is one of the most important parts of the classic "Extract Method" refactoring). (We can't seem to find any examples of the usage of extract function. We determined this gap by experimentation.)

It seems that LLMV/Clang LibTool could support an endless set of refactorings (due to arbitrary AST changes), but there seems to be very limited user-ready refactorings supported by tools in the llvm-project repo itself. I know that major companies are using LLVM/Clang LibTool to do large refctorings. Are these companies mostly just developing their own proprietary in-house refactoring tools based on LLVM/Clang LibTool?

@IgnatSergeev
Copy link
Author

@bartlettroscoe, sorry for not responding, at some point, i forgot checking my github notifications

You were right about the amount of refactorings that LibTool could support
And actually AST transformations are already supported in clang-transformer tool and its library, so the clang-refactor tool is abandoned as i could tell
Furthermore, transformer is the main tool (or actually library) for transformations in clang-tidy

So i would recommend reading about them, and searching through their code, maybe you could find some of the refactorings already implemented for those tools
And i could be wrong, but everyone mostly uses transformer to implement their own refactorings privately, as sad for open source as it may sound

@bartlettroscoe
Copy link

@IgnatSergeev, thanks for the reply!

You were right about the amount of refactorings that LibTool could support And actually AST transformations are already supported in clang-transformer tool and its library

So is this really transforming the AST? According to:

it says:

The astute reader may have noticed that we’ve been somewhat vague in our explanation of what the rewrite rules are actually rewriting. We’ve referred to “code”, but code can be represented both as raw source text and as an abstract syntax tree. So, which one is it?

Ideally, we’d be rewriting the input AST to a new AST, but clang’s AST is not terribly amenable to this kind of transformation. So, we compromise: we express our patterns and the names that they bind in terms of the AST, but our changes in terms of source code text. We’ve designed Transformer’s language to bridge the gap between the two representations, in an attempt to minimize the user’s need to reason about source code locations and other, low-level syntactic details.

so the clang-refactor tool is abandoned as i could tell Furthermore, transformer is the main tool (or actually library) for transformations in clang-tidy

Just an FYI, we have found that the Apple fork of the llvm-project repo appears to have some additional C++ refactoring tooling that is used by the XCode IDE:

For example, there appears to be an "Extract Function" refactor that might actually be able to refactor real C++ code the exectuable:

We have not done an in-depth evaluation of this capability yet, but it looks promising.

So i would recommend reading about them, and searching through their code, maybe you could find some of the refactorings already implemented for those tools And i could be wrong, but everyone mostly uses transformer to implement their own refactorings privately, as sad for open source as it may sound

Are people using clang-transformer for more than just clang-tidy "FixIt" checks? I have done some initial searches and I can't seem to find anything other than just clang-tidy FixIts.

The question is how his does this clang transformer mapping of AST to source code work for larger chucks of C++ code, like for "Extract Function"?

FYI: We are starting to look at the LLNL ROSE Outliner tool:

which is supposed to be able to implement a robust C++ "Extract Function" refactoring.

Or, perhaps everyone is waiting for better AI to be able to refactor C++ automatically?

@IgnatSergeev
Copy link
Author

IgnatSergeev commented Apr 24, 2025

@bartlettroscoe,

So is this really transforming the AST?

Well yeah, you are right, it replaces it with text, but what i ment, is that it supports replacements with EditGenerators, which are really expressive (a few words about them https://clang.llvm.org/docs/ClangTransformerTutorial.html#editgenerators-advanced)

But i would say that even clang-refactor uses source code transformations, it doesn't change AST

For example, there appears to be an "Extract Function" refactor that might actually be able to refactor real C++ code

The link you provided is to a test tool, but i see how you can adapt this to some tool
And speaking about "Extract Function", i searched through that file, and found only a rename symbol refactor

And after a brief look of that file, i think it uses source code transformations too
main -> calls rename (https://github.com/swiftlang/llvm-project/blob/next/clang/tools/clang-refactor-test/ClangRefactorTest.cpp#L520) -> calls clang_Refactoring_initiateAction and after uses i's action (https://github.com/swiftlang/llvm-project/blob/next/clang/tools/libclang/CRefactor.cpp#L1384) -> creates RefactoringAction (https://github.com/swiftlang/llvm-project/blob/next/clang/tools/libclang/CRefactor.cpp#L750) that uses RefactoringOperation and RenamingAction for refactoring

RenamingAction is based on Refactoring library (the same that clang-refactor uses) and it uses the same source code transformations (https://github.com/swiftlang/llvm-project/blob/next/clang/lib/Tooling/Refactoring/Rename/RenamingAction.cpp)

RefactoringOperation (https://github.com/swiftlang/llvm-project/blob/next/clang/include/clang/Tooling/Refactor/RefactoringOperation.h) uses RefactoringResult that also uses source code transformations through RefactoringReplacement (https://github.com/swiftlang/llvm-project/blob/next/clang/include/clang/Tooling/Refactor/RefactoringReplacement.h#L37)

I hope it makes sense
So at the end it is still a source code transformation, these functions don't allow you to get transformed AST
But that was a brief look, i could be wrong

LLNL ROSE Outliner tool

At first glance could be what you want

The question is how his does this clang transformer mapping of AST to source code work for larger chucks of C++ code, like for "Extract Function"?

Well, you could write a matcher that matches if current node is in specific source range
After that you need a rule that uses functionDecl matcher with that custom matcher for binding nodes in that range, in ASTEdit you replace the whole matched function with some source code that the defines the exctracted fuction, contatenate this code with captured nodes, and the whole function

That just added extracted function
The second rule should do the same, but match this range, and replace it with call
That theoretically could work

But this tool is mostly used for local transformations, as there are much more of those, and they are commonly met

Or, perhaps everyone is waiting for better AI to be able to refactor C++ automatically?

Actually, that's an interesting idea

But to be honest, i think that there are't many "users" for large refactoring tools, so when they are needed people write small portion of their own transformations, thus they don't get to open source

@bartlettroscoe
Copy link

Hello @IgnatSergeev, thanks for your response!

I am looping in my colleague @achauphan who has been working with the LLVM Clang refactoring tooling ...

But to be honest, i think that there are't many "users" for large refactoring tools, so when they are needed people write small portion of their own transformations, thus they don't get to open source

I just find that so surprising. It is so hard to even safely change the name of an overloaded function across a large C++ code base. And performing Extract Function can be very tedious to do it correctly with minimal inputs/outputs, dealing with multiple returns, etc.

Is this a chicken and the egg problem? I mean, do they not use the refactoring tools because the refactoring tools don't exist or are not reliable so they don't use them? Or is it because they don't do refactoring as a regular practice?

I asked ChatGPT 4o the question:

Why do software developers not use automated source code refactoring tools more?

and it provided the following summary response (see here):

Developers often avoid automated refactoring tools due to lack of trust, poor integration, workflow disruption, or organizational inertia. For tools to gain adoption, they must be safe, fast, unobtrusive, and deeply integrated into the developer's natural environment.

All it would take is one or two bad refactors from a refactoring tool and I think many people will just give up.

I have never had access to a working refactoring tool for C++ so I have zero experience with such tooling.

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
None yet
Development

Successfully merging this pull request may close these issues.

3 participants