https://github.com/ahoppen updated 
https://github.com/llvm/llvm-project/pull/82061

>From 1285081e8fc7d7ecd162ed8ec7201437dbd708da Mon Sep 17 00:00:00 2001
From: Alex Hoppen <ahop...@apple.com>
Date: Mon, 29 Jul 2024 17:33:43 -0700
Subject: [PATCH] [clangd] Use `RenameSymbolName` to represent Objective-C
 selectors
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This is a cleaner design than using identifier and an optional `Selector`. It 
also allows rename of Objective-C method names if no declaration is at hand and 
thus no `Selector` instance can be formed. For example, when finding the ranges 
to rename based on an index that’s not clangd’s built-in index.
---
 clang-tools-extra/clangd/refactor/Rename.cpp  | 101 ++++++++++++------
 clang-tools-extra/clangd/refactor/Rename.h    |  48 ++++++++-
 .../clangd/unittests/RenameTests.cpp          |   6 +-
 3 files changed, 118 insertions(+), 37 deletions(-)

diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp 
b/clang-tools-extra/clangd/refactor/Rename.cpp
index b7894b8918eed..26059167208aa 100644
--- a/clang-tools-extra/clangd/refactor/Rename.cpp
+++ b/clang-tools-extra/clangd/refactor/Rename.cpp
@@ -18,9 +18,11 @@
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/ASTTypeTraits.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/DeclBase.h"
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclObjC.h"
 #include "clang/AST/DeclTemplate.h"
+#include "clang/AST/DeclarationName.h"
 #include "clang/AST/ParentMapContext.h"
 #include "clang/AST/Stmt.h"
 #include "clang/Basic/CharInfo.h"
@@ -597,11 +599,12 @@ bool isMatchingSelectorName(const syntax::Token &Cur, 
const syntax::Token &Next,
 // The search will terminate upon seeing Terminator or a ; at the top level.
 std::optional<SymbolRange>
 findAllSelectorPieces(llvm::ArrayRef<syntax::Token> Tokens,
-                      const SourceManager &SM, Selector Sel,
+                      const SourceManager &SM, const RenameSymbolName &Name,
                       tok::TokenKind Terminator) {
   assert(!Tokens.empty());
 
-  unsigned NumArgs = Sel.getNumArgs();
+  ArrayRef<std::string> NamePieces = Name.getNamePieces();
+  unsigned NumArgs = NamePieces.size();
   llvm::SmallVector<tok::TokenKind, 8> Closes;
   std::vector<Range> SelectorPieces;
   for (unsigned Index = 0, Last = Tokens.size(); Index < Last - 1; ++Index) {
@@ -611,12 +614,12 @@ findAllSelectorPieces(llvm::ArrayRef<syntax::Token> 
Tokens,
       auto PieceCount = SelectorPieces.size();
       if (PieceCount < NumArgs &&
           isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
-                                 Sel.getNameForSlot(PieceCount))) {
+                                 NamePieces[PieceCount])) {
         // If 'foo:' instead of ':' (empty selector), we need to skip the ':'
         // token after the name. We don't currently properly support empty
         // selectors since we may lex them improperly due to ternary statements
         // as well as don't properly support storing their ranges for edits.
-        if (!Sel.getNameForSlot(PieceCount).empty())
+        if (!NamePieces[PieceCount].empty())
           ++Index;
         SelectorPieces.push_back(
             halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
@@ -668,16 +671,17 @@ findAllSelectorPieces(llvm::ArrayRef<syntax::Token> 
Tokens,
 
 /// Collects all ranges of the given identifier/selector in the source code.
 ///
-/// If a selector is given, this does a full lex of the given source code in
-/// order to identify all selector fragments (e.g. in method exprs/decls) since
-/// they are non-contiguous.
-std::vector<SymbolRange> collectRenameIdentifierRanges(
-    llvm::StringRef Identifier, llvm::StringRef Content,
-    const LangOptions &LangOpts, std::optional<Selector> Selector) {
+/// If `Name` is an Objective-C symbol name, this does a full lex of the given
+/// source code in order to identify all selector fragments (e.g. in method
+/// exprs/decls) since they are non-contiguous.
+std::vector<SymbolRange>
+collectRenameIdentifierRanges(const RenameSymbolName &Name,
+                              llvm::StringRef Content,
+                              const LangOptions &LangOpts) {
   std::vector<SymbolRange> Ranges;
-  if (!Selector) {
+  if (auto SinglePiece = Name.getSinglePiece()) {
     auto IdentifierRanges =
-        collectIdentifierRanges(Identifier, Content, LangOpts);
+        collectIdentifierRanges(*SinglePiece, Content, LangOpts);
     for (const auto &R : IdentifierRanges)
       Ranges.emplace_back(R);
     return Ranges;
@@ -691,7 +695,7 @@ std::vector<SymbolRange> collectRenameIdentifierRanges(
   // parsing a method declaration or definition which isn't at the top level or
   // similar looking expressions (e.g. an @selector() expression).
   llvm::SmallVector<tok::TokenKind, 8> Closes;
-  llvm::StringRef FirstSelPiece = Selector->getNameForSlot(0);
+  llvm::StringRef FirstSelPiece = Name.getNamePieces()[0];
 
   auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
   unsigned Last = Tokens.size() - 1;
@@ -723,7 +727,7 @@ std::vector<SymbolRange> collectRenameIdentifierRanges(
       // Check if we can find all the relevant selector peices starting from
       // this token
       auto SelectorRanges =
-          findAllSelectorPieces(ArrayRef(Tokens).slice(Index), SM, *Selector,
+          findAllSelectorPieces(ArrayRef(Tokens).slice(Index), SM, Name,
                                 Closes.empty() ? tok::l_brace : Closes.back());
       if (SelectorRanges)
         Ranges.emplace_back(std::move(*SelectorRanges));
@@ -770,7 +774,6 @@ renameObjCMethodWithinFile(ParsedAST &AST, const 
ObjCMethodDecl *MD,
                            std::vector<SourceLocation> SelectorOccurences) {
   const SourceManager &SM = AST.getSourceManager();
   auto Code = SM.getBufferData(SM.getMainFileID());
-  auto RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
   llvm::SmallVector<llvm::StringRef, 8> NewNames;
   NewName.split(NewNames, ":");
 
@@ -780,7 +783,7 @@ renameObjCMethodWithinFile(ParsedAST &AST, const 
ObjCMethodDecl *MD,
     Ranges.push_back(tokenRangeForLoc(AST, Loc, SM, LangOpts));
   auto FilePath = AST.tuPath();
   auto RenameRanges = collectRenameIdentifierRanges(
-      RenameIdentifier, Code, LangOpts, MD->getSelector());
+      RenameSymbolName(MD->getDeclName()), Code, LangOpts);
   auto RenameEdit = buildRenameEdit(FilePath, Code, RenameRanges, NewNames);
   if (!RenameEdit)
     return error("failed to rename in file {0}: {1}", FilePath,
@@ -942,21 +945,14 @@ renameOutsideFile(const NamedDecl &RenameDecl, 
llvm::StringRef MainFilePath,
            ExpBuffer.getError().message());
       continue;
     }
-    std::string RenameIdentifier = RenameDecl.getNameAsString();
-    std::optional<Selector> Selector = std::nullopt;
+    RenameSymbolName RenameName(RenameDecl.getDeclName());
     llvm::SmallVector<llvm::StringRef, 8> NewNames;
-    if (const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl)) {
-      RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
-      if (MD->getSelector().getNumArgs() > 1)
-        Selector = MD->getSelector();
-    }
     NewName.split(NewNames, ":");
 
     auto AffectedFileCode = (*ExpBuffer)->getBuffer();
-    auto RenameRanges =
-        adjustRenameRanges(AffectedFileCode, RenameIdentifier,
-                           std::move(FileAndOccurrences.second),
-                           RenameDecl.getASTContext().getLangOpts(), Selector);
+    auto RenameRanges = adjustRenameRanges(
+        AffectedFileCode, RenameName, std::move(FileAndOccurrences.second),
+        RenameDecl.getASTContext().getLangOpts());
     if (!RenameRanges) {
       // Our heuristics fails to adjust rename ranges to the current state of
       // the file, it is most likely the index is stale, so we give up the
@@ -1017,6 +1013,50 @@ void findNearMiss(
 
 } // namespace
 
+RenameSymbolName::RenameSymbolName() : NamePieces({}) {}
+
+namespace {
+std::vector<std::string> extractNamePieces(const DeclarationName &DeclName) {
+  if (DeclName.getNameKind() ==
+          DeclarationName::NameKind::ObjCMultiArgSelector ||
+      DeclName.getNameKind() == DeclarationName::NameKind::ObjCOneArgSelector) 
{
+    const Selector &Sel = DeclName.getObjCSelector();
+    std::vector<std::string> Result;
+    for (unsigned Slot = 0; Slot < Sel.getNumArgs(); ++Slot) {
+      Result.push_back(Sel.getNameForSlot(Slot).str());
+    }
+    return Result;
+  }
+  return {DeclName.getAsString()};
+}
+} // namespace
+
+RenameSymbolName::RenameSymbolName(const DeclarationName &DeclName)
+    : RenameSymbolName(extractNamePieces(DeclName)) {}
+
+RenameSymbolName::RenameSymbolName(ArrayRef<std::string> NamePieces) {
+  for (const auto &Piece : NamePieces)
+    this->NamePieces.push_back(Piece);
+}
+
+std::optional<std::string> RenameSymbolName::getSinglePiece() const {
+  if (getNamePieces().size() == 1) {
+    return NamePieces.front();
+  }
+  return std::nullopt;
+}
+
+std::string RenameSymbolName::getAsString() const {
+  std::string Result;
+  llvm::raw_string_ostream OS(Result);
+  this->print(OS);
+  return Result;
+}
+
+void RenameSymbolName::print(raw_ostream &OS) const {
+  llvm::interleave(NamePieces, OS, ":");
+}
+
 SymbolRange::SymbolRange(Range R) : Ranges({R}) {}
 
 SymbolRange::SymbolRange(std::vector<Range> Ranges)
@@ -1244,14 +1284,13 @@ llvm::Expected<Edit> buildRenameEdit(llvm::StringRef 
AbsFilePath,
 //          were inserted). If such a "near miss" is found, the rename is still
 //          possible
 std::optional<std::vector<SymbolRange>>
-adjustRenameRanges(llvm::StringRef DraftCode, llvm::StringRef Identifier,
-                   std::vector<Range> Indexed, const LangOptions &LangOpts,
-                   std::optional<Selector> Selector) {
+adjustRenameRanges(llvm::StringRef DraftCode, const RenameSymbolName &Name,
+                   std::vector<Range> Indexed, const LangOptions &LangOpts) {
   trace::Span Tracer("AdjustRenameRanges");
   assert(!Indexed.empty());
   assert(llvm::is_sorted(Indexed));
   std::vector<SymbolRange> Lexed =
-      collectRenameIdentifierRanges(Identifier, DraftCode, LangOpts, Selector);
+      collectRenameIdentifierRanges(Name, DraftCode, LangOpts);
   llvm::sort(Lexed);
   return getMappedRanges(Indexed, Lexed);
 }
diff --git a/clang-tools-extra/clangd/refactor/Rename.h 
b/clang-tools-extra/clangd/refactor/Rename.h
index be5c346ae150f..0f61d9647e7c8 100644
--- a/clang-tools-extra/clangd/refactor/Rename.h
+++ b/clang-tools-extra/clangd/refactor/Rename.h
@@ -35,6 +35,49 @@ struct RenameOptions {
   bool RenameVirtual = true;
 };
 
+/// A name of a symbol that should be renamed.
+///
+/// Symbol's name can be composed of multiple strings. For example, Objective-C
+/// methods can contain multiple argument labels:
+///
+/// \code
+/// - (void) myMethodNamePiece: (int)x anotherNamePieces:(int)y;
+/// //       ^~ string 0 ~~~~~         ^~ string 1 ~~~~~
+/// \endcode
+class RenameSymbolName {
+  llvm::SmallVector<std::string, 1> NamePieces;
+
+public:
+  RenameSymbolName();
+
+  /// Create a new \c SymbolName with the specified pieces.
+  explicit RenameSymbolName(ArrayRef<std::string> NamePieces);
+
+  explicit RenameSymbolName(const DeclarationName &Name);
+
+  ArrayRef<std::string> getNamePieces() const { return NamePieces; }
+
+  /// If this symbol consists of a single piece return it, otherwise return
+  /// `None`.
+  ///
+  /// Only symbols in Objective-C can consist of multiple pieces, so this
+  /// function always returns a value for non-Objective-C symbols.
+  std::optional<std::string> getSinglePiece() const;
+
+  /// Returns a human-readable version of this symbol name.
+  ///
+  /// If the symbol consists of multiple pieces (aka. it is an Objective-C
+  /// selector/method name), the pieces are separated by `:`, otherwise just an
+  /// identifier name.
+  std::string getAsString() const;
+
+  void print(raw_ostream &OS) const;
+
+  bool operator==(const RenameSymbolName &Other) const {
+    return NamePieces == Other.NamePieces;
+  }
+};
+
 struct RenameInputs {
   Position Pos; // the position triggering the rename
   llvm::StringRef NewName;
@@ -108,9 +151,8 @@ llvm::Expected<Edit> buildRenameEdit(llvm::StringRef 
AbsFilePath,
 /// occurrence has the same length).
 /// REQUIRED: Indexed is sorted.
 std::optional<std::vector<SymbolRange>>
-adjustRenameRanges(llvm::StringRef DraftCode, llvm::StringRef Identifier,
-                   std::vector<Range> Indexed, const LangOptions &LangOpts,
-                   std::optional<Selector> Selector);
+adjustRenameRanges(llvm::StringRef DraftCode, const RenameSymbolName &Name,
+                   std::vector<Range> Indexed, const LangOptions &LangOpts);
 
 /// Calculates the lexed occurrences that the given indexed occurrences map to.
 /// Returns std::nullopt if we don't find a mapping.
diff --git a/clang-tools-extra/clangd/unittests/RenameTests.cpp 
b/clang-tools-extra/clangd/unittests/RenameTests.cpp
index 65558440e36e3..2cb0722f7f285 100644
--- a/clang-tools-extra/clangd/unittests/RenameTests.cpp
+++ b/clang-tools-extra/clangd/unittests/RenameTests.cpp
@@ -2223,9 +2223,9 @@ TEST(CrossFileRenameTests, adjustRenameRanges) {
   for (const auto &T : Tests) {
     SCOPED_TRACE(T.DraftCode);
     Annotations Draft(T.DraftCode);
-    auto ActualRanges = adjustRenameRanges(Draft.code(), "x",
-                                           Annotations(T.IndexedCode).ranges(),
-                                           LangOpts, std::nullopt);
+    auto ActualRanges = adjustRenameRanges(
+        Draft.code(), RenameSymbolName(ArrayRef<std::string>{"x"}),
+        Annotations(T.IndexedCode).ranges(), LangOpts);
     if (!ActualRanges)
        EXPECT_THAT(Draft.ranges(), testing::IsEmpty());
     else

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to