[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2023-12-27 Thread David Goldman via cfe-commits

https://github.com/DavidGoldman created 
https://github.com/llvm/llvm-project/pull/76466

This is based on  top of #76410 

>From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001
From: David Goldman 
Date: Tue, 26 Dec 2023 15:59:01 -0500
Subject: [PATCH 1/4] [clangd][SymbolCollector] Treat ObjC methods as spelled

We'll treat multi-arg methods as spelled once we have full rename
support for them.
---
 .../clangd/index/SymbolCollector.cpp  |  6 ++-
 .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++
 2 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp 
b/clang-tools-extra/clangd/index/SymbolCollector.cpp
index 7ef4b15febad22..336bc3506bb360 100644
--- a/clang-tools-extra/clangd/index/SymbolCollector.cpp
+++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp
@@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
   auto Name = ND.getDeclName();
   const auto NameKind = Name.getNameKind();
   if (NameKind != DeclarationName::Identifier &&
-  NameKind != DeclarationName::CXXConstructorName)
+  NameKind != DeclarationName::CXXConstructorName &&
+  NameKind != DeclarationName::ObjCZeroArgSelector &&
+  NameKind != DeclarationName::ObjCOneArgSelector)
 return false;
   const auto &AST = ND.getASTContext();
   const auto &SM = AST.getSourceManager();
@@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
   if (clang::Lexer::getRawToken(Loc, Tok, SM, LO))
 return false;
   auto StrName = Name.getAsString();
+  if (const auto *MD = dyn_cast(&ND))
+StrName = MD->getSelector().getNameForSlot(0).str();
   return clang::Lexer::getSpelling(Tok, SM, LO) == StrName;
 }
 } // namespace
diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp 
b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 9cdc57ec01f327..1d4e1c1d75ea23 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -105,6 +105,9 @@ MATCHER(refRange, "") {
 MATCHER_P2(OverriddenBy, Subject, Object, "") {
   return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID};
 }
+MATCHER(isSpelled, "") {
+  return static_cast(arg.Kind & RefKind::Spelled);
+}
 ::testing::Matcher &>
 haveRanges(const std::vector Ranges) {
   return ::testing::UnorderedPointwise(refRange(), Ranges);
@@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) {
  forCodeCompletion(false);
 }
 
+TEST_F(SymbolCollectorTest, ObjCRefs) {
+  Annotations Header(R"(
+  @interface Person
+  - (void)$talk[[talk]];
+  - (void)$say[[say]]:(id)something;
+  @end
+  @interface Person (Category)
+  - (void)categoryMethod;
+  - (void)multiArg:(id)a method:(id)b;
+  @end
+  )");
+  Annotations Main(R"(
+  @implementation Person
+  - (void)$talk[[talk]] {}
+  - (void)$say[[say]]:(id)something {}
+  @end
+
+  void fff(Person *p) {
+[p $talk[[talk]]];
+[p $say[[say]]:0];
+[p categoryMethod];
+[p multiArg:0 method:0];
+  }
+  )");
+  CollectorOpts.RefFilter = RefKind::All;
+  CollectorOpts.CollectMainFileRefs = true;
+  TestFileName = testPath("test.m");
+  runSymbolCollector(Header.code(), Main.code(),
+ {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"});
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID,
+  haveRanges(Main.ranges("talk");
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID,
+  haveRanges(Main.ranges("say");
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, 
"Person::categoryMethod").ID,
+  ElementsAre(isSpelled();
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, 
"Person::multiArg:method:").ID,
+  ElementsAre(Not(isSpelled());
+}
+
 TEST_F(SymbolCollectorTest, ObjCSymbols) {
   const std::string Header = R"(
 @interface Person

>From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001
From: David Goldman 
Date: Tue, 26 Dec 2023 16:12:03 -0500
Subject: [PATCH 2/4] Run clang-format

---
 .../clangd/unittests/SymbolCollectorTests.cpp  | 10 ++
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp 
b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 1d4e1c1d75ea23..5c20b950e4eac0 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) {
   haveRanges(Main.ranges("talk");
   EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID,
   haveRanges(Main.ranges("say");
-  EXPECT_T

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2023-12-27 Thread via cfe-commits

llvmbot wrote:




@llvm/pr-subscribers-clangd

Author: David Goldman (DavidGoldman)


Changes

This is based on  top of #76410 

---

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


11 Files Affected:

- (modified) clang-tools-extra/clangd/ClangdLSPServer.cpp (+5-2) 
- (modified) clang-tools-extra/clangd/ClangdLSPServer.h (+1-1) 
- (modified) clang-tools-extra/clangd/Protocol.cpp (+10) 
- (modified) clang-tools-extra/clangd/Protocol.h (+8) 
- (modified) clang-tools-extra/clangd/SourceCode.cpp (+8) 
- (modified) clang-tools-extra/clangd/SourceCode.h (+4) 
- (modified) clang-tools-extra/clangd/index/SymbolCollector.cpp (+6-1) 
- (modified) clang-tools-extra/clangd/refactor/Rename.cpp (+283-38) 
- (modified) clang-tools-extra/clangd/refactor/Rename.h (+37-7) 
- (modified) clang-tools-extra/clangd/unittests/RenameTests.cpp (+34-14) 
- (modified) clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp (+44) 


``diff
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp 
b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index a87da252b7a7e9..0e628cfc71c2de 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -844,14 +844,17 @@ void ClangdLSPServer::onWorkspaceSymbol(
 }
 
 void ClangdLSPServer::onPrepareRename(const TextDocumentPositionParams &Params,
-  Callback> Reply) {
+  
Callback> Reply) {
   Server->prepareRename(
   Params.textDocument.uri.file(), Params.position, /*NewName*/ 
std::nullopt,
   Opts.Rename,
   [Reply = std::move(Reply)](llvm::Expected Result) mutable {
 if (!Result)
   return Reply(Result.takeError());
-return Reply(std::move(Result->Target));
+PrepareRenameResult PrepareResult;
+PrepareResult.range = Result->Target;
+PrepareResult.placeholder = Result->Placeholder;
+return Reply(std::move(PrepareResult));
   });
 }
 
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h 
b/clang-tools-extra/clangd/ClangdLSPServer.h
index 79579c22b788a9..3a60b6e5db86bc 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -134,7 +134,7 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
   void onWorkspaceSymbol(const WorkspaceSymbolParams &,
  Callback>);
   void onPrepareRename(const TextDocumentPositionParams &,
-   Callback>);
+   Callback>);
   void onRename(const RenameParams &, Callback);
   void onHover(const TextDocumentPositionParams &,
Callback>);
diff --git a/clang-tools-extra/clangd/Protocol.cpp 
b/clang-tools-extra/clangd/Protocol.cpp
index a6370649f5ad1c..29b99da27101a8 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -1187,6 +1187,16 @@ bool fromJSON(const llvm::json::Value &Params, 
RenameParams &R,
  O.map("position", R.position) && O.map("newName", R.newName);
 }
 
+llvm::json::Value toJSON(const PrepareRenameResult &PRR) {
+  if (!PRR.placeholder) {
+return toJSON(PRR.range);
+  }
+  return llvm::json::Object{
+  {"range", toJSON(PRR.range)},
+  {"placeholder", PRR.placeholder},
+  };
+}
+
 llvm::json::Value toJSON(const DocumentHighlight &DH) {
   return llvm::json::Object{
   {"range", toJSON(DH.range)},
diff --git a/clang-tools-extra/clangd/Protocol.h 
b/clang-tools-extra/clangd/Protocol.h
index e88c804692568f..18ac530e9c1a0f 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -1436,6 +1436,14 @@ struct RenameParams {
 };
 bool fromJSON(const llvm::json::Value &, RenameParams &, llvm::json::Path);
 
+struct PrepareRenameResult {
+  /// Range of the string to rename.
+  Range range;
+  /// Placeholder text to use in the editor, if set.
+  std::optional placeholder;
+};
+llvm::json::Value toJSON(const PrepareRenameResult &PRR);
+
 enum class DocumentHighlightKind { Text = 1, Read = 2, Write = 3 };
 
 /// A document highlight is a range inside a text document which deserves
diff --git a/clang-tools-extra/clangd/SourceCode.cpp 
b/clang-tools-extra/clangd/SourceCode.cpp
index 835038423fdf37..73e12fc7ebde51 100644
--- a/clang-tools-extra/clangd/SourceCode.cpp
+++ b/clang-tools-extra/clangd/SourceCode.cpp
@@ -1243,6 +1243,14 @@ SourceLocation 
translatePreamblePatchLocation(SourceLocation Loc,
   return Loc;
 }
 
+clangd::Range tokenRangeForLoc(SourceLocation TokLoc, const SourceManager &SM, 
const LangOptions &LangOpts) {
+  auto TokenLength = clang::Lexer::MeasureTokenLength(TokLoc, SM, LangOpts);
+  clangd::Range Result;
+  Result.start = sourceLocToPosition(SM, TokLoc);
+  Result.end = sourceLocToPosition(SM, TokLoc.getLocWithOffset(TokenLength));
+  return Result;
+}
+
 clangd::Range rangeTillEOL(llvm::StringRef Code, unsigned HashOffset) {
   c

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2023-12-27 Thread David Goldman via cfe-commits

https://github.com/DavidGoldman updated 
https://github.com/llvm/llvm-project/pull/76466

>From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001
From: David Goldman 
Date: Tue, 26 Dec 2023 15:59:01 -0500
Subject: [PATCH 1/5] [clangd][SymbolCollector] Treat ObjC methods as spelled

We'll treat multi-arg methods as spelled once we have full rename
support for them.
---
 .../clangd/index/SymbolCollector.cpp  |  6 ++-
 .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++
 2 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp 
b/clang-tools-extra/clangd/index/SymbolCollector.cpp
index 7ef4b15febad22..336bc3506bb360 100644
--- a/clang-tools-extra/clangd/index/SymbolCollector.cpp
+++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp
@@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
   auto Name = ND.getDeclName();
   const auto NameKind = Name.getNameKind();
   if (NameKind != DeclarationName::Identifier &&
-  NameKind != DeclarationName::CXXConstructorName)
+  NameKind != DeclarationName::CXXConstructorName &&
+  NameKind != DeclarationName::ObjCZeroArgSelector &&
+  NameKind != DeclarationName::ObjCOneArgSelector)
 return false;
   const auto &AST = ND.getASTContext();
   const auto &SM = AST.getSourceManager();
@@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
   if (clang::Lexer::getRawToken(Loc, Tok, SM, LO))
 return false;
   auto StrName = Name.getAsString();
+  if (const auto *MD = dyn_cast(&ND))
+StrName = MD->getSelector().getNameForSlot(0).str();
   return clang::Lexer::getSpelling(Tok, SM, LO) == StrName;
 }
 } // namespace
diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp 
b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 9cdc57ec01f327..1d4e1c1d75ea23 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -105,6 +105,9 @@ MATCHER(refRange, "") {
 MATCHER_P2(OverriddenBy, Subject, Object, "") {
   return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID};
 }
+MATCHER(isSpelled, "") {
+  return static_cast(arg.Kind & RefKind::Spelled);
+}
 ::testing::Matcher &>
 haveRanges(const std::vector Ranges) {
   return ::testing::UnorderedPointwise(refRange(), Ranges);
@@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) {
  forCodeCompletion(false);
 }
 
+TEST_F(SymbolCollectorTest, ObjCRefs) {
+  Annotations Header(R"(
+  @interface Person
+  - (void)$talk[[talk]];
+  - (void)$say[[say]]:(id)something;
+  @end
+  @interface Person (Category)
+  - (void)categoryMethod;
+  - (void)multiArg:(id)a method:(id)b;
+  @end
+  )");
+  Annotations Main(R"(
+  @implementation Person
+  - (void)$talk[[talk]] {}
+  - (void)$say[[say]]:(id)something {}
+  @end
+
+  void fff(Person *p) {
+[p $talk[[talk]]];
+[p $say[[say]]:0];
+[p categoryMethod];
+[p multiArg:0 method:0];
+  }
+  )");
+  CollectorOpts.RefFilter = RefKind::All;
+  CollectorOpts.CollectMainFileRefs = true;
+  TestFileName = testPath("test.m");
+  runSymbolCollector(Header.code(), Main.code(),
+ {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"});
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID,
+  haveRanges(Main.ranges("talk");
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID,
+  haveRanges(Main.ranges("say");
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, 
"Person::categoryMethod").ID,
+  ElementsAre(isSpelled();
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, 
"Person::multiArg:method:").ID,
+  ElementsAre(Not(isSpelled());
+}
+
 TEST_F(SymbolCollectorTest, ObjCSymbols) {
   const std::string Header = R"(
 @interface Person

>From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001
From: David Goldman 
Date: Tue, 26 Dec 2023 16:12:03 -0500
Subject: [PATCH 2/5] Run clang-format

---
 .../clangd/unittests/SymbolCollectorTests.cpp  | 10 ++
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp 
b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 1d4e1c1d75ea23..5c20b950e4eac0 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) {
   haveRanges(Main.ranges("talk");
   EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID,
   haveRanges(Main.ranges("say");
-  EXPECT_THAT(Refs, Contains(Pair(findSymbol

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2023-12-27 Thread via cfe-commits

github-actions[bot] wrote:




:warning: C/C++ code formatter, clang-format found issues in your code. 
:warning:



You can test this locally with the following command:


``bash
git-clang-format --diff 8b485070844d03cda467e75aa8c924184ba671cf 
76385b51db0e33a25df9646eb6d59f04a4f65af7 -- 
clang-tools-extra/clangd/ClangdLSPServer.cpp 
clang-tools-extra/clangd/ClangdLSPServer.h 
clang-tools-extra/clangd/Protocol.cpp clang-tools-extra/clangd/Protocol.h 
clang-tools-extra/clangd/SourceCode.cpp clang-tools-extra/clangd/SourceCode.h 
clang-tools-extra/clangd/index/SymbolCollector.cpp 
clang-tools-extra/clangd/refactor/Rename.cpp 
clang-tools-extra/clangd/refactor/Rename.h 
clang-tools-extra/clangd/unittests/RenameTests.cpp 
clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
``





View the diff from clang-format here.


``diff
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp 
b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 0e628cfc71..641266b76c 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -843,8 +843,9 @@ void ClangdLSPServer::onWorkspaceSymbol(
   });
 }
 
-void ClangdLSPServer::onPrepareRename(const TextDocumentPositionParams &Params,
-  
Callback> Reply) {
+void ClangdLSPServer::onPrepareRename(
+const TextDocumentPositionParams &Params,
+Callback> Reply) {
   Server->prepareRename(
   Params.textDocument.uri.file(), Params.position, /*NewName*/ 
std::nullopt,
   Opts.Rename,
diff --git a/clang-tools-extra/clangd/SourceCode.cpp 
b/clang-tools-extra/clangd/SourceCode.cpp
index 73e12fc7eb..ac20ed095b 100644
--- a/clang-tools-extra/clangd/SourceCode.cpp
+++ b/clang-tools-extra/clangd/SourceCode.cpp
@@ -1243,7 +1243,8 @@ SourceLocation 
translatePreamblePatchLocation(SourceLocation Loc,
   return Loc;
 }
 
-clangd::Range tokenRangeForLoc(SourceLocation TokLoc, const SourceManager &SM, 
const LangOptions &LangOpts) {
+clangd::Range tokenRangeForLoc(SourceLocation TokLoc, const SourceManager &SM,
+   const LangOptions &LangOpts) {
   auto TokenLength = clang::Lexer::MeasureTokenLength(TokLoc, SM, LangOpts);
   clangd::Range Result;
   Result.start = sourceLocToPosition(SM, TokLoc);
diff --git a/clang-tools-extra/clangd/SourceCode.h 
b/clang-tools-extra/clangd/SourceCode.h
index d671e05aed..efe6d66c16 100644
--- a/clang-tools-extra/clangd/SourceCode.h
+++ b/clang-tools-extra/clangd/SourceCode.h
@@ -338,8 +338,7 @@ inline bool isReservedName(llvm::StringRef Name) {
 SourceLocation translatePreamblePatchLocation(SourceLocation Loc,
   const SourceManager &SM);
 
-clangd::Range tokenRangeForLoc(SourceLocation TokLoc,
-   const SourceManager &SM,
+clangd::Range tokenRangeForLoc(SourceLocation TokLoc, const SourceManager &SM,
const LangOptions &LangOpts);
 
 /// Returns the range starting at offset and spanning the whole line. Escaped
diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp 
b/clang-tools-extra/clangd/refactor/Rename.cpp
index 4f945f74e8..e5686c963a 100644
--- a/clang-tools-extra/clangd/refactor/Rename.cpp
+++ b/clang-tools-extra/clangd/refactor/Rename.cpp
@@ -585,11 +585,9 @@ renameWithinFile(ParsedAST &AST, const NamedDecl 
&RenameDecl,
 for (const auto &Loc : Locs)
   Ranges.push_back(tokenRangeForLoc(Loc, SM, LangOpts));
 auto FilePath = AST.tuPath();
-auto RenameRanges =
-adjustRenameRanges(Code, RenameIdentifier,
-   std::move(Ranges),
-   RenameDecl.getASTContext().getLangOpts(),
-   MD->getSelector());
+auto RenameRanges = adjustRenameRanges(
+Code, RenameIdentifier, std::move(Ranges),
+RenameDecl.getASTContext().getLangOpts(), MD->getSelector());
 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
@@ -598,8 +596,7 @@ renameWithinFile(ParsedAST &AST, const NamedDecl 
&RenameDecl,
"(selector handling may be incorrect)",
FilePath);
 }
-auto RenameEdit =
-buildRenameEdit(FilePath, Code, *RenameRanges, NewNames);
+auto RenameEdit = buildRenameEdit(FilePath, Code, *RenameRanges, NewNames);
 if (!RenameEdit)
   return error("failed to rename in file {0}: {1}", FilePath,
RenameEdit.takeError());
@@ -736,8 +733,7 @@ renameOutsideFile(const NamedDecl &RenameDecl, 
llvm::StringRef MainFilePath,
 auto RenameRanges =
 adjustRenameRanges(AffectedFileCode, RenameIdentifier,
std::move(FileAndOccurrences.second),
-   RenameDecl.getASTContext().getLangOpts(),
-   Selector);
+

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2023-12-27 Thread David Goldman via cfe-commits

https://github.com/DavidGoldman updated 
https://github.com/llvm/llvm-project/pull/76466

>From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001
From: David Goldman 
Date: Tue, 26 Dec 2023 15:59:01 -0500
Subject: [PATCH 1/6] [clangd][SymbolCollector] Treat ObjC methods as spelled

We'll treat multi-arg methods as spelled once we have full rename
support for them.
---
 .../clangd/index/SymbolCollector.cpp  |  6 ++-
 .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++
 2 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp 
b/clang-tools-extra/clangd/index/SymbolCollector.cpp
index 7ef4b15febad22..336bc3506bb360 100644
--- a/clang-tools-extra/clangd/index/SymbolCollector.cpp
+++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp
@@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
   auto Name = ND.getDeclName();
   const auto NameKind = Name.getNameKind();
   if (NameKind != DeclarationName::Identifier &&
-  NameKind != DeclarationName::CXXConstructorName)
+  NameKind != DeclarationName::CXXConstructorName &&
+  NameKind != DeclarationName::ObjCZeroArgSelector &&
+  NameKind != DeclarationName::ObjCOneArgSelector)
 return false;
   const auto &AST = ND.getASTContext();
   const auto &SM = AST.getSourceManager();
@@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
   if (clang::Lexer::getRawToken(Loc, Tok, SM, LO))
 return false;
   auto StrName = Name.getAsString();
+  if (const auto *MD = dyn_cast(&ND))
+StrName = MD->getSelector().getNameForSlot(0).str();
   return clang::Lexer::getSpelling(Tok, SM, LO) == StrName;
 }
 } // namespace
diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp 
b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 9cdc57ec01f327..1d4e1c1d75ea23 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -105,6 +105,9 @@ MATCHER(refRange, "") {
 MATCHER_P2(OverriddenBy, Subject, Object, "") {
   return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID};
 }
+MATCHER(isSpelled, "") {
+  return static_cast(arg.Kind & RefKind::Spelled);
+}
 ::testing::Matcher &>
 haveRanges(const std::vector Ranges) {
   return ::testing::UnorderedPointwise(refRange(), Ranges);
@@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) {
  forCodeCompletion(false);
 }
 
+TEST_F(SymbolCollectorTest, ObjCRefs) {
+  Annotations Header(R"(
+  @interface Person
+  - (void)$talk[[talk]];
+  - (void)$say[[say]]:(id)something;
+  @end
+  @interface Person (Category)
+  - (void)categoryMethod;
+  - (void)multiArg:(id)a method:(id)b;
+  @end
+  )");
+  Annotations Main(R"(
+  @implementation Person
+  - (void)$talk[[talk]] {}
+  - (void)$say[[say]]:(id)something {}
+  @end
+
+  void fff(Person *p) {
+[p $talk[[talk]]];
+[p $say[[say]]:0];
+[p categoryMethod];
+[p multiArg:0 method:0];
+  }
+  )");
+  CollectorOpts.RefFilter = RefKind::All;
+  CollectorOpts.CollectMainFileRefs = true;
+  TestFileName = testPath("test.m");
+  runSymbolCollector(Header.code(), Main.code(),
+ {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"});
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID,
+  haveRanges(Main.ranges("talk");
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID,
+  haveRanges(Main.ranges("say");
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, 
"Person::categoryMethod").ID,
+  ElementsAre(isSpelled();
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, 
"Person::multiArg:method:").ID,
+  ElementsAre(Not(isSpelled());
+}
+
 TEST_F(SymbolCollectorTest, ObjCSymbols) {
   const std::string Header = R"(
 @interface Person

>From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001
From: David Goldman 
Date: Tue, 26 Dec 2023 16:12:03 -0500
Subject: [PATCH 2/6] Run clang-format

---
 .../clangd/unittests/SymbolCollectorTests.cpp  | 10 ++
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp 
b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 1d4e1c1d75ea23..5c20b950e4eac0 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) {
   haveRanges(Main.ranges("talk");
   EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID,
   haveRanges(Main.ranges("say");
-  EXPECT_THAT(Refs, Contains(Pair(findSymbol

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-02 Thread David Goldman via cfe-commits

https://github.com/DavidGoldman updated 
https://github.com/llvm/llvm-project/pull/76466

>From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001
From: David Goldman 
Date: Tue, 26 Dec 2023 15:59:01 -0500
Subject: [PATCH 1/7] [clangd][SymbolCollector] Treat ObjC methods as spelled

We'll treat multi-arg methods as spelled once we have full rename
support for them.
---
 .../clangd/index/SymbolCollector.cpp  |  6 ++-
 .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++
 2 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp 
b/clang-tools-extra/clangd/index/SymbolCollector.cpp
index 7ef4b15febad22..336bc3506bb360 100644
--- a/clang-tools-extra/clangd/index/SymbolCollector.cpp
+++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp
@@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
   auto Name = ND.getDeclName();
   const auto NameKind = Name.getNameKind();
   if (NameKind != DeclarationName::Identifier &&
-  NameKind != DeclarationName::CXXConstructorName)
+  NameKind != DeclarationName::CXXConstructorName &&
+  NameKind != DeclarationName::ObjCZeroArgSelector &&
+  NameKind != DeclarationName::ObjCOneArgSelector)
 return false;
   const auto &AST = ND.getASTContext();
   const auto &SM = AST.getSourceManager();
@@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
   if (clang::Lexer::getRawToken(Loc, Tok, SM, LO))
 return false;
   auto StrName = Name.getAsString();
+  if (const auto *MD = dyn_cast(&ND))
+StrName = MD->getSelector().getNameForSlot(0).str();
   return clang::Lexer::getSpelling(Tok, SM, LO) == StrName;
 }
 } // namespace
diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp 
b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 9cdc57ec01f327..1d4e1c1d75ea23 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -105,6 +105,9 @@ MATCHER(refRange, "") {
 MATCHER_P2(OverriddenBy, Subject, Object, "") {
   return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID};
 }
+MATCHER(isSpelled, "") {
+  return static_cast(arg.Kind & RefKind::Spelled);
+}
 ::testing::Matcher &>
 haveRanges(const std::vector Ranges) {
   return ::testing::UnorderedPointwise(refRange(), Ranges);
@@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) {
  forCodeCompletion(false);
 }
 
+TEST_F(SymbolCollectorTest, ObjCRefs) {
+  Annotations Header(R"(
+  @interface Person
+  - (void)$talk[[talk]];
+  - (void)$say[[say]]:(id)something;
+  @end
+  @interface Person (Category)
+  - (void)categoryMethod;
+  - (void)multiArg:(id)a method:(id)b;
+  @end
+  )");
+  Annotations Main(R"(
+  @implementation Person
+  - (void)$talk[[talk]] {}
+  - (void)$say[[say]]:(id)something {}
+  @end
+
+  void fff(Person *p) {
+[p $talk[[talk]]];
+[p $say[[say]]:0];
+[p categoryMethod];
+[p multiArg:0 method:0];
+  }
+  )");
+  CollectorOpts.RefFilter = RefKind::All;
+  CollectorOpts.CollectMainFileRefs = true;
+  TestFileName = testPath("test.m");
+  runSymbolCollector(Header.code(), Main.code(),
+ {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"});
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID,
+  haveRanges(Main.ranges("talk");
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID,
+  haveRanges(Main.ranges("say");
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, 
"Person::categoryMethod").ID,
+  ElementsAre(isSpelled();
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, 
"Person::multiArg:method:").ID,
+  ElementsAre(Not(isSpelled());
+}
+
 TEST_F(SymbolCollectorTest, ObjCSymbols) {
   const std::string Header = R"(
 @interface Person

>From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001
From: David Goldman 
Date: Tue, 26 Dec 2023 16:12:03 -0500
Subject: [PATCH 2/7] Run clang-format

---
 .../clangd/unittests/SymbolCollectorTests.cpp  | 10 ++
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp 
b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 1d4e1c1d75ea23..5c20b950e4eac0 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) {
   haveRanges(Main.ranges("talk");
   EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID,
   haveRanges(Main.ranges("say");
-  EXPECT_THAT(Refs, Contains(Pair(findSymbol

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread kadir çetinkaya via cfe-commits

https://github.com/kadircet edited 
https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread kadir çetinkaya via cfe-commits

https://github.com/kadircet commented:

thanks, this LG in direction. only reviewed the pieces leading to in-file 
rename so far.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread kadir çetinkaya via cfe-commits


@@ -1187,6 +1187,16 @@ bool fromJSON(const llvm::json::Value &Params, 
RenameParams &R,
  O.map("position", R.position) && O.map("newName", R.newName);
 }
 
+llvm::json::Value toJSON(const PrepareRenameResult &PRR) {
+  if (!PRR.placeholder) {

kadircet wrote:

nit: braces

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread kadir çetinkaya via cfe-commits


@@ -1436,6 +1436,14 @@ struct RenameParams {
 };
 bool fromJSON(const llvm::json::Value &, RenameParams &, llvm::json::Path);
 
+struct PrepareRenameResult {
+  /// Range of the string to rename.
+  Range range;
+  /// Placeholder text to use in the editor, if set.
+  std::optional placeholder;

kadircet wrote:

let's drop `optional` to prevent confusion here, there isn't any difference 
between an empty placeholder and nullopt, right?

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread kadir çetinkaya via cfe-commits


@@ -53,13 +55,38 @@ struct RenameInputs {
 struct RenameResult {
   // The range of the symbol that the user can attempt to rename.
   Range Target;
+  // Placeholder text for the rename operation, if set.
+  std::optional Placeholder;
   // Rename occurrences for the current main file.
   std::vector LocalChanges;
   // Complete edits for the rename, including LocalChanges.
   // If the full set of changes is unknown, this field is empty.
   FileEdits GlobalChanges;
 };
 
+/// Represents a symbol range where the symbol can potentially have multiple
+/// tokens.
+struct SymbolRange {
+  /// All ranges. If multiple, corresponds to an ObjC selector.

kadircet wrote:

maybe also talk about the general case?
```
Ranges for the tokens that make up the symbol's name.
Usually a single range, there can be multiple ranges if the tokens for symbol 
are split, e.g. ObjC selectors.
```

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread kadir çetinkaya via cfe-commits


@@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
   if (clang::Lexer::getRawToken(Loc, Tok, SM, LO))
 return false;
   auto StrName = Name.getAsString();
+  if (const auto *MD = dyn_cast(&ND))
+StrName = MD->getSelector().getNameForSlot(0).str();

kadircet wrote:

nit: we can rewrite as:
```
auto TokSpelling = clang::Lexer::getSpelling(Tok, SM, LO);
if (const auto *MD = dyn_cast(&ND))
  return TokSpelling == MD->getSelector().getNameForSlot(0);
return TokSpelling == Name.getAsString();
```

(to prevent a string construction)

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread kadir çetinkaya via cfe-commits


@@ -569,8 +571,43 @@ renameWithinFile(ParsedAST &AST, const NamedDecl 
&RenameDecl,
 //   }
 if (!isInsideMainFile(RenameLoc, SM))
   continue;
+Locs.push_back(RenameLoc);
+  }
+  if (const auto *MD = dyn_cast(&RenameDecl)) {

kadircet wrote:

let's extract this branch into a separate function?

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread kadir çetinkaya via cfe-commits


@@ -134,7 +134,7 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
   void onWorkspaceSymbol(const WorkspaceSymbolParams &,
  Callback>);
   void onPrepareRename(const TextDocumentPositionParams &,
-   Callback>);
+   Callback>);

kadircet wrote:

you can drop the optional now, we either return an Error or PrepareRenameResult

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread kadir çetinkaya via cfe-commits


@@ -508,7 +508,8 @@ static bool mayBeValidIdentifier(llvm::StringRef Ident) {
   !isAsciiIdentifierStart(Ident.front(), AllowDollar))
 return false;
   for (char C : Ident) {
-if (llvm::isASCII(C) && !isAsciiIdentifierContinue(C, AllowDollar))
+if (llvm::isASCII(C) && !isAsciiIdentifierContinue(C, AllowDollar) &&

kadircet wrote:

nit:
```cpp
if (AllowColon && C == ':')
  continue;
if (llvm::isASCII ...)
```

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread kadir çetinkaya via cfe-commits


@@ -338,6 +338,10 @@ inline bool isReservedName(llvm::StringRef Name) {
 SourceLocation translatePreamblePatchLocation(SourceLocation Loc,
   const SourceManager &SM);
 
+clangd::Range tokenRangeForLoc(SourceLocation TokLoc,

kadircet wrote:

this isn't used outside rename.cpp, let's move it there?

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread kadir çetinkaya via cfe-commits


@@ -53,13 +55,38 @@ struct RenameInputs {
 struct RenameResult {
   // The range of the symbol that the user can attempt to rename.
   Range Target;
+  // Placeholder text for the rename operation, if set.
+  std::optional Placeholder;

kadircet wrote:

same here, let's drop optional

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread kadir çetinkaya via cfe-commits


@@ -71,8 +98,8 @@ llvm::Expected rename(const RenameInputs 
&RInputs);
 /// REQUIRED: Occurrences is sorted and doesn't have duplicated ranges.
 llvm::Expected buildRenameEdit(llvm::StringRef AbsFilePath,
  llvm::StringRef InitialCode,
- std::vector Occurrences,
- llvm::StringRef NewName);
+ std::vector Occurrences,
+ llvm::SmallVectorImpl 
&NewNames);

kadircet wrote:

why not `llvm::ArrayRef NewNames` ?

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread kadir çetinkaya via cfe-commits


@@ -569,8 +571,43 @@ renameWithinFile(ParsedAST &AST, const NamedDecl 
&RenameDecl,
 //   }
 if (!isInsideMainFile(RenameLoc, SM))
   continue;
+Locs.push_back(RenameLoc);
+  }
+  if (const auto *MD = dyn_cast(&RenameDecl)) {
+auto Code = SM.getBufferData(SM.getMainFileID());
+auto RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
+llvm::SmallVector NewNames;
+NewName.split(NewNames, ":");
+if (NewNames.empty())
+  NewNames.push_back(NewName);
+std::vector Ranges;
+const auto &LangOpts = RenameDecl.getASTContext().getLangOpts();
+for (const auto &Loc : Locs)
+  Ranges.push_back(tokenRangeForLoc(Loc, SM, LangOpts));
+auto FilePath = AST.tuPath();
+auto RenameRanges =
+adjustRenameRanges(Code, RenameIdentifier,

kadircet wrote:

we don't need to adjust ranges again in here. we're already using ranges from 
the AST (not from index)

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread kadir çetinkaya via cfe-commits


@@ -893,22 +964,36 @@ llvm::Expected buildRenameEdit(llvm::StringRef 
AbsFilePath,
 return LastOffset;
   };
 
-  std::vector> OccurrencesOffsets;
-  for (const auto &R : Occurrences) {
-auto StartOffset = Offset(R.start);
-if (!StartOffset)
-  return StartOffset.takeError();
-auto EndOffset = Offset(R.end);
-if (!EndOffset)
-  return EndOffset.takeError();
-OccurrencesOffsets.push_back({*StartOffset, *EndOffset});
+  struct OccurrenceOffset {
+size_t Start;
+size_t End;
+llvm::StringRef NewName;
+
+OccurrenceOffset(size_t Start, size_t End, llvm::StringRef NewName) :
+  Start(Start), End(End), NewName(NewName) {}
+  };
+
+  std::vector OccurrencesOffsets;
+  for (const auto &SR : Occurrences) {
+for (auto It = SR.Ranges.begin(); It != SR.Ranges.end(); ++It) {

kadircet wrote:

nit: `for (auto [Range, NewName] : llvm::zip(SR.Ranges, NewNames))`

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread kadir çetinkaya via cfe-commits


@@ -53,13 +55,38 @@ struct RenameInputs {
 struct RenameResult {
   // The range of the symbol that the user can attempt to rename.
   Range Target;
+  // Placeholder text for the rename operation, if set.
+  std::optional Placeholder;
   // Rename occurrences for the current main file.
   std::vector LocalChanges;
   // Complete edits for the rename, including LocalChanges.
   // If the full set of changes is unknown, this field is empty.
   FileEdits GlobalChanges;
 };
 
+/// Represents a symbol range where the symbol can potentially have multiple
+/// tokens.
+struct SymbolRange {
+  /// All ranges. If multiple, corresponds to an ObjC selector.
+  std::vector Ranges;
+
+  /// Returns the first range.
+  Range range() const { return Ranges.front(); }
+
+  SymbolRange(Range R) : Ranges({R}) {}
+  SymbolRange(std::vector Ranges) : Ranges(std::move(Ranges)) {}
+
+  friend bool operator==(const SymbolRange &LHS, const SymbolRange &RHS) {
+return LHS.Ranges == RHS.Ranges;
+  }
+  friend bool operator!=(const SymbolRange &LHS, const SymbolRange &RHS) {
+return !(LHS == RHS);
+  }
+  friend bool operator<(const SymbolRange &LHS, const SymbolRange &RHS) {
+return LHS.range() < RHS.range();
+  }
+};

kadircet wrote:

can you move function definitions to implementation file?

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread kadir çetinkaya via cfe-commits


@@ -569,8 +571,43 @@ renameWithinFile(ParsedAST &AST, const NamedDecl 
&RenameDecl,
 //   }
 if (!isInsideMainFile(RenameLoc, SM))
   continue;
+Locs.push_back(RenameLoc);
+  }
+  if (const auto *MD = dyn_cast(&RenameDecl)) {
+auto Code = SM.getBufferData(SM.getMainFileID());
+auto RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
+llvm::SmallVector NewNames;
+NewName.split(NewNames, ":");
+if (NewNames.empty())
+  NewNames.push_back(NewName);
+std::vector Ranges;
+const auto &LangOpts = RenameDecl.getASTContext().getLangOpts();
+for (const auto &Loc : Locs)
+  Ranges.push_back(tokenRangeForLoc(Loc, SM, LangOpts));

kadircet wrote:

instead of re-lexing, you can use tokenbuffer inside AST and call 
`spelledTokenAt`, then convert it into a range

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread David Goldman via cfe-commits


@@ -1436,6 +1436,14 @@ struct RenameParams {
 };
 bool fromJSON(const llvm::json::Value &, RenameParams &, llvm::json::Path);
 
+struct PrepareRenameResult {
+  /// Range of the string to rename.
+  Range range;
+  /// Placeholder text to use in the editor, if set.
+  std::optional placeholder;

DavidGoldman wrote:

I think it'll be encoded slightly different but VS Code did appear to handle an 
empty placeholder properly, but I'll have clangd omit encoding it if empty to 
be safe.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread David Goldman via cfe-commits


@@ -893,22 +964,36 @@ llvm::Expected buildRenameEdit(llvm::StringRef 
AbsFilePath,
 return LastOffset;
   };
 
-  std::vector> OccurrencesOffsets;
-  for (const auto &R : Occurrences) {
-auto StartOffset = Offset(R.start);
-if (!StartOffset)
-  return StartOffset.takeError();
-auto EndOffset = Offset(R.end);
-if (!EndOffset)
-  return EndOffset.takeError();
-OccurrencesOffsets.push_back({*StartOffset, *EndOffset});
+  struct OccurrenceOffset {
+size_t Start;
+size_t End;
+llvm::StringRef NewName;
+
+OccurrenceOffset(size_t Start, size_t End, llvm::StringRef NewName) :
+  Start(Start), End(End), NewName(NewName) {}
+  };
+
+  std::vector OccurrencesOffsets;
+  for (const auto &SR : Occurrences) {
+for (auto It = SR.Ranges.begin(); It != SR.Ranges.end(); ++It) {

DavidGoldman wrote:

Not sure I follow, the loop construction doesn't use NewNames at all, you might 
have misread it?

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-05 Thread David Goldman via cfe-commits

https://github.com/DavidGoldman updated 
https://github.com/llvm/llvm-project/pull/76466

>From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001
From: David Goldman 
Date: Tue, 26 Dec 2023 15:59:01 -0500
Subject: [PATCH 1/3] [clangd][SymbolCollector] Treat ObjC methods as spelled

We'll treat multi-arg methods as spelled once we have full rename
support for them.
---
 .../clangd/index/SymbolCollector.cpp  |  6 ++-
 .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++
 2 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp 
b/clang-tools-extra/clangd/index/SymbolCollector.cpp
index 7ef4b15febad22..336bc3506bb360 100644
--- a/clang-tools-extra/clangd/index/SymbolCollector.cpp
+++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp
@@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
   auto Name = ND.getDeclName();
   const auto NameKind = Name.getNameKind();
   if (NameKind != DeclarationName::Identifier &&
-  NameKind != DeclarationName::CXXConstructorName)
+  NameKind != DeclarationName::CXXConstructorName &&
+  NameKind != DeclarationName::ObjCZeroArgSelector &&
+  NameKind != DeclarationName::ObjCOneArgSelector)
 return false;
   const auto &AST = ND.getASTContext();
   const auto &SM = AST.getSourceManager();
@@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
   if (clang::Lexer::getRawToken(Loc, Tok, SM, LO))
 return false;
   auto StrName = Name.getAsString();
+  if (const auto *MD = dyn_cast(&ND))
+StrName = MD->getSelector().getNameForSlot(0).str();
   return clang::Lexer::getSpelling(Tok, SM, LO) == StrName;
 }
 } // namespace
diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp 
b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 9cdc57ec01f327..1d4e1c1d75ea23 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -105,6 +105,9 @@ MATCHER(refRange, "") {
 MATCHER_P2(OverriddenBy, Subject, Object, "") {
   return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID};
 }
+MATCHER(isSpelled, "") {
+  return static_cast(arg.Kind & RefKind::Spelled);
+}
 ::testing::Matcher &>
 haveRanges(const std::vector Ranges) {
   return ::testing::UnorderedPointwise(refRange(), Ranges);
@@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) {
  forCodeCompletion(false);
 }
 
+TEST_F(SymbolCollectorTest, ObjCRefs) {
+  Annotations Header(R"(
+  @interface Person
+  - (void)$talk[[talk]];
+  - (void)$say[[say]]:(id)something;
+  @end
+  @interface Person (Category)
+  - (void)categoryMethod;
+  - (void)multiArg:(id)a method:(id)b;
+  @end
+  )");
+  Annotations Main(R"(
+  @implementation Person
+  - (void)$talk[[talk]] {}
+  - (void)$say[[say]]:(id)something {}
+  @end
+
+  void fff(Person *p) {
+[p $talk[[talk]]];
+[p $say[[say]]:0];
+[p categoryMethod];
+[p multiArg:0 method:0];
+  }
+  )");
+  CollectorOpts.RefFilter = RefKind::All;
+  CollectorOpts.CollectMainFileRefs = true;
+  TestFileName = testPath("test.m");
+  runSymbolCollector(Header.code(), Main.code(),
+ {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"});
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID,
+  haveRanges(Main.ranges("talk");
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID,
+  haveRanges(Main.ranges("say");
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, 
"Person::categoryMethod").ID,
+  ElementsAre(isSpelled();
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, 
"Person::multiArg:method:").ID,
+  ElementsAre(Not(isSpelled());
+}
+
 TEST_F(SymbolCollectorTest, ObjCSymbols) {
   const std::string Header = R"(
 @interface Person

>From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001
From: David Goldman 
Date: Tue, 26 Dec 2023 16:12:03 -0500
Subject: [PATCH 2/3] Run clang-format

---
 .../clangd/unittests/SymbolCollectorTests.cpp  | 10 ++
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp 
b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 1d4e1c1d75ea23..5c20b950e4eac0 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) {
   haveRanges(Main.ranges("talk");
   EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID,
   haveRanges(Main.ranges("say");
-  EXPECT_THAT(Refs, Contains(Pair(findSymbol

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-12 Thread Alex Hoppen via cfe-commits

https://github.com/ahoppen commented:

The outstanding comments from last review are:
- Use `SymbolName` instead of `StringRef`
  - https://github.com/llvm/llvm-project/pull/76466#discussion_r1476409221
  - https://github.com/llvm/llvm-project/pull/76466#discussion_r1476427027
- Don’t re-lex the source file in `collectRenameIdentifierRanges`
  - https://github.com/llvm/llvm-project/pull/76466#discussion_r1476471971
- Use index or AST data to find edits in the current file. AFAICT we do use 
index-data for the multi-file rename case in `adjustRenameRanges` but I AFAICT 
no semantic information is used for the current file.
  - https://github.com/llvm/llvm-project/pull/76466/files#r1479029824
  - I think a good test case to add would be
```cpp
// Input
R"cpp(
  @interface Foo
  - (void)performA^ction:(int)action with:(int)value;
  @end
  @implementation Foo
  - (void)performAction:(int)action with:(int)value {
[self performAction:action with:value];
  }
  @end
  @interface Bar
  - (void)performAction:(int)action with:(int)value;
  @end
  @implementation Bar
  - (void)performAction:(int)action with:(int)value {
  }
  @end
)cpp",
// New name
"performNewAction:by:",
// Expected
R"cpp(
  @interface Foo
  - (void)performNewAction:(int)action by:(int)value;
  @end
  @implementation Foo
  - (void)performNewAction:(int)action by:(int)value {
[self performNewAction:action by:value];
  }
  @end
  @interface Bar
  - (void)performAction:(int)action with:(int)value;
  @end
  @implementation Bar
  - (void)performAction:(int)action with:(int)value {
  }
)cpp",
```

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-12 Thread Alex Hoppen via cfe-commits

https://github.com/ahoppen edited 
https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-12 Thread Alex Hoppen via cfe-commits


@@ -538,11 +564,222 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return llvm::Error::success();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
+}
+
+// Scan through Tokens to find ranges for each selector fragment in Sel at the
+// top level (not nested in any () or {} or []). The search will terminate upon

ahoppen wrote:

I think the `not nested in any () or {} or []` is quite misleading. Based on 
this comment, I would have expected `doStuff` in `[someObject someArgLabel: 
[foo doStuff: 1]]` or 

```objectivec
- (void) test {
  [foo doStuff: 1];
}
```

to not be found because it’s nested in `{}` or `[]`.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-12 Thread Alex Hoppen via cfe-commits


@@ -538,11 +564,222 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return llvm::Error::success();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
+}
+
+// Scan through Tokens to find ranges for each selector fragment in Sel at the
+// top level (not nested in any () or {} or []). The search will terminate upon
+// seeing Terminator or a ; at the top level.
+std::optional
+findAllSelectorPieces(llvm::ArrayRef Tokens,
+  const SourceManager &SM, Selector Sel,
+  tok::TokenKind Terminator) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  std::vector SelectorPieces;
+  unsigned Last = Tokens.size() - 1;
+  for (unsigned Index = 0; Index < Last; ++Index) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
+// token after the name.
+if (!Sel.getNameForSlot(PieceCount).empty())
+  ++Index;
+SelectorPieces.push_back(
+halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces but the current token looks like another
+  // selector piece, it means the method being renamed is a strict prefix 
of
+  // the selector we've found - should be skipped.
+  if (SelectorPieces.size() >= NumArgs &&
+  isSelectorLike(Tok, Tokens[Index + 1]))
+return std::nullopt;
+}
+
+if (Closes.empty() && Tok.kind() == Terminator)
+  return SelectorPieces.size() == NumArgs
+ ? std::optional(SymbolRange(SelectorPieces))
+ : std::nullopt;
+
+switch (Tok.kind()) {
+case tok::l_square:
+  Closes.push_back(tok::r_square);
+  break;
+case tok::l_paren:
+  Closes.push_back(tok::r_paren);
+  break;
+case tok::l_brace:
+  Closes.push_back(tok::r_brace);
+  break;
+case tok::r_square:
+case tok::r_paren:
+case tok::r_brace:
+  if (Closes.empty() || Closes.back() != Tok.kind())
+return std::nullopt;
+  Closes.pop_back();
+  break;
+case tok::semi:
+  // top level ; terminates all statements.
+  if (Closes.empty())
+return SelectorPieces.size() == NumArgs
+   ? std::optional(SymbolRange(SelectorPieces))
+   : std::nullopt;
+  break;
+default:
+  break;
+}
+  }
+  return std::nullopt;
+}
+
+/// 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 collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions &LangOpts, std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto &R : IdentifierRanges)
+  Ranges.emplace_back(R);
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!

ahoppen wrote:

In my rename PR I found that this is no longer an issue. See 
https://github.com/apple/llvm-project/pull/7915#discussion_r1442402332

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-12 Thread Alex Hoppen via cfe-commits


@@ -538,11 +564,222 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return llvm::Error::success();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.

ahoppen wrote:

Why don’t we support `foo : `? Or what am I missing here? `[bar foo : 1]` is 
perfectly valid AFAICT.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-14 Thread kadir çetinkaya via cfe-commits

https://github.com/kadircet edited 
https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-14 Thread kadir çetinkaya via cfe-commits


@@ -538,11 +564,222 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return llvm::Error::success();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
+}
+
+// Scan through Tokens to find ranges for each selector fragment in Sel at the
+// top level (not nested in any () or {} or []). The search will terminate upon

kadircet wrote:

but that's true for this function, as it's achieved by the outer loop that 
calls this function in `collectRenameIdentifierRanges`.

in here we're only trying to match `Sel` assuming its first segment is located 
at `Tokens.front()`.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-14 Thread kadir çetinkaya via cfe-commits

https://github.com/kadircet approved this pull request.

thanks, lgtm!

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-14 Thread kadir çetinkaya via cfe-commits


@@ -538,11 +564,222 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return llvm::Error::success();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
+}
+
+// Scan through Tokens to find ranges for each selector fragment in Sel at the
+// top level (not nested in any () or {} or []). The search will terminate upon
+// seeing Terminator or a ; at the top level.
+std::optional
+findAllSelectorPieces(llvm::ArrayRef Tokens,
+  const SourceManager &SM, Selector Sel,
+  tok::TokenKind Terminator) {
+

kadircet wrote:

`assert(!Tokens.empty())`

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-14 Thread kadir çetinkaya via cfe-commits


@@ -696,7 +982,7 @@ renameOutsideFile(const NamedDecl &RenameDecl, 
llvm::StringRef MainFilePath,
FilePath);
 }
 auto RenameEdit =
-buildRenameEdit(FilePath, AffectedFileCode, *RenameRanges, NewName);
+buildRenameEdit(FilePath, AffectedFileCode, *RenameRanges, NewNames);

kadircet wrote:

as discussed yesterday, i was suggesting to move:
```
llvm::SmallVector NewNames;
NewName.split(NewNames, ":");
```

from lines 928/936 to right before this call to `buildRenameEdit`

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-14 Thread kadir çetinkaya via cfe-commits


@@ -538,11 +564,222 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return llvm::Error::success();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
+}
+
+// Scan through Tokens to find ranges for each selector fragment in Sel at the
+// top level (not nested in any () or {} or []). The search will terminate upon
+// seeing Terminator or a ; at the top level.
+std::optional
+findAllSelectorPieces(llvm::ArrayRef Tokens,
+  const SourceManager &SM, Selector Sel,
+  tok::TokenKind Terminator) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  std::vector SelectorPieces;
+  unsigned Last = Tokens.size() - 1;
+  for (unsigned Index = 0; Index < Last; ++Index) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'

kadircet wrote:

as discussed we aren't actually handling this case probably (we won't have a 
token for an empty selector name).

can you just leave a comment explaining what & why it doesn't work (i don't 
necessarily see it as a TODO, as utility/complexity ratio is pretty low)?

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-14 Thread kadir çetinkaya via cfe-commits


@@ -538,11 +564,222 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return llvm::Error::success();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
+}
+
+// Scan through Tokens to find ranges for each selector fragment in Sel at the
+// top level (not nested in any () or {} or []). The search will terminate upon
+// seeing Terminator or a ; at the top level.
+std::optional
+findAllSelectorPieces(llvm::ArrayRef Tokens,
+  const SourceManager &SM, Selector Sel,
+  tok::TokenKind Terminator) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  std::vector SelectorPieces;
+  unsigned Last = Tokens.size() - 1;

kadircet wrote:

nit: `Last` is not used outside the loop, and it's still confusing me to see 
loop condition below as `Index < Last`. what about

`for (unsigned Index = 0, Last = Tokens.size(); Index < Last - 1; ++Index)`

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-15 Thread David Goldman via cfe-commits

https://github.com/DavidGoldman closed 
https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread kadir çetinkaya via cfe-commits


@@ -508,24 +513,46 @@ static bool mayBeValidIdentifier(llvm::StringRef Ident) {
   !isAsciiIdentifierStart(Ident.front(), AllowDollar))
 return false;
   for (char C : Ident) {
+if (AllowColon && C == ':')
+  continue;
 if (llvm::isASCII(C) && !isAsciiIdentifierContinue(C, AllowDollar))
   return false;
   }
   return true;
 }
 
+std::string getName(const NamedDecl &RenameDecl) {
+  if (const auto *MD = dyn_cast(&RenameDecl))
+return MD->getSelector().getAsString();
+  if (const auto *ID = RenameDecl.getIdentifier())
+return ID->getName().str();
+  return "";
+}
+
 // Check if we can rename the given RenameDecl into NewName.
 // Return details if the rename would produce a conflict.
-std::optional checkName(const NamedDecl &RenameDecl,
- llvm::StringRef NewName) {
+std::optional checkName(const NamedDecl &RenameDecl,

kadircet wrote:

no need for optional, you can just return `Error::success()` in the happy path

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread kadir çetinkaya via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool parseMessageExpression(llvm::ArrayRef Tokens,
+const SourceManager &SM, unsigned Index,
+unsigned Last, Selector Sel,
+std::vector &SelectorPieces) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
+// token after the name.
+if (!Sel.getNameForSlot(PieceCount).empty()) {
+  ++Index;
+}
+SelectorPieces.push_back(
+halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces but the current token looks like another
+  // selector piece, it means the method being renamed is a strict prefix 
of
+  // the selector we've found - should be skipped.
+  if (SelectorPieces.size() >= NumArgs &&
+  isSelectorLike(Tok, Tokens[Index + 1]))
+return false;
+}
+
+switch (Tok.kind()) {
+case tok::l_square:
+  Closes.push_back(']');
+  break;
+case tok::l_paren:
+  Closes.push_back(')');
+  break;
+case tok::l_brace:
+  Closes.push_back('}');
+  break;
+case tok::r_square:
+  if (Closes.empty())
+return SelectorPieces.size() == NumArgs;
+
+  if (Closes.back() != ']')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_paren:
+  if (Closes.empty() || Closes.back() != ')')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_brace:
+  if (Closes.empty() || Closes.back() != '}')
+return false;
+  Closes.pop_back();
+  break;
+case tok::semi:
+  // top level ; ends all statements.
+  if (Closes.empty())
+return false;
+  break;
+default:
+  break;
+}
+
+++Index;
+  }
+  return false;
+}
+
+/// 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 collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions &LangOpts, std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto &R : IdentifierRanges)
+  Ranges.emplace_back(R);
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto &SM = FileSM.get();
+
+  // We track parens and braces to ensure that we don't accidentally try 
parsing
+  // a method declaration or definition which isn't at the top level or similar
+  // looking expressions (e.g. an @selector() expression).
+  unsigned ParenCount = 0;
+  unsigned BraceCount = 0;
+  unsigned NumArgs = Selector->getNumArgs();
+
+  std::vector SelectorPieces;
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+  for (unsigned Index = 0; Index < Last; ++Index) {
+const auto &Tok = Tokens[Index];
+


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread kadir çetinkaya via cfe-commits


@@ -681,12 +957,22 @@ renameOutsideFile(const NamedDecl &RenameDecl, 
llvm::StringRef MainFilePath,
ExpBuffer.getError().message());
   continue;
 }
+std::string RenameIdentifier = RenameDecl.getNameAsString();
+std::optional Selector = std::nullopt;
+llvm::SmallVector NewNames;
+if (const auto *MD = dyn_cast(&RenameDecl)) {

kadircet wrote:

can we just push this logic all the way into `collectRenameIdentifierRanges`?

it already performs a different logic when we have a selector set, we can just 
tart by setting `Identifier = Selector.getNameForSlot(0)`

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread kadir çetinkaya via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool parseMessageExpression(llvm::ArrayRef Tokens,
+const SourceManager &SM, unsigned Index,
+unsigned Last, Selector Sel,
+std::vector &SelectorPieces) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
+// token after the name.
+if (!Sel.getNameForSlot(PieceCount).empty()) {
+  ++Index;
+}
+SelectorPieces.push_back(
+halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces but the current token looks like another
+  // selector piece, it means the method being renamed is a strict prefix 
of
+  // the selector we've found - should be skipped.
+  if (SelectorPieces.size() >= NumArgs &&
+  isSelectorLike(Tok, Tokens[Index + 1]))
+return false;
+}
+
+switch (Tok.kind()) {
+case tok::l_square:
+  Closes.push_back(']');
+  break;
+case tok::l_paren:
+  Closes.push_back(')');
+  break;
+case tok::l_brace:
+  Closes.push_back('}');
+  break;
+case tok::r_square:
+  if (Closes.empty())
+return SelectorPieces.size() == NumArgs;
+
+  if (Closes.back() != ']')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_paren:
+  if (Closes.empty() || Closes.back() != ')')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_brace:
+  if (Closes.empty() || Closes.back() != '}')
+return false;
+  Closes.pop_back();
+  break;
+case tok::semi:
+  // top level ; ends all statements.
+  if (Closes.empty())
+return false;
+  break;
+default:
+  break;
+}
+
+++Index;
+  }
+  return false;
+}
+
+/// 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 collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions &LangOpts, std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto &R : IdentifierRanges)
+  Ranges.emplace_back(R);
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto &SM = FileSM.get();
+
+  // We track parens and braces to ensure that we don't accidentally try 
parsing
+  // a method declaration or definition which isn't at the top level or similar
+  // looking expressions (e.g. an @selector() expression).
+  unsigned ParenCount = 0;
+  unsigned BraceCount = 0;
+  unsigned NumArgs = Selector->getNumArgs();
+
+  std::vector SelectorPieces;
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+  for (unsigned Index = 0; Index < Last; ++Index) {
+const auto &Tok = Tokens[Index];
+


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread kadir çetinkaya via cfe-commits


@@ -543,6 +550,45 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
   return Result;
 }
 
+clangd::Range tokenRangeForLoc(ParsedAST &AST, SourceLocation TokLoc,
+   const SourceManager &SM,
+   const LangOptions &LangOpts) {
+  const auto *Token = AST.getTokens().spelledTokenAt(TokLoc);
+  assert(Token && "got inclusion at wrong offset");
+  clangd::Range Result;
+  Result.start = sourceLocToPosition(SM, Token->location());
+  Result.end = sourceLocToPosition(SM, Token->endLocation());
+  return Result;
+}
+
+// AST-based ObjC method rename, it renames all occurrences in the main file
+// even for selectors which may have multiple tokens.
+llvm::Expected
+renameObjCMethodWithinFile(ParsedAST &AST, const ObjCMethodDecl *MD,
+   llvm::StringRef NewName,
+   std::vector Locs) {
+  const SourceManager &SM = AST.getSourceManager();
+  auto Code = SM.getBufferData(SM.getMainFileID());
+  auto RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
+  llvm::SmallVector NewNames;
+  NewName.split(NewNames, ":");
+  if (NewNames.empty())
+NewNames.push_back(NewName);
+
+  std::vector Ranges;
+  const auto &LangOpts = MD->getASTContext().getLangOpts();
+  for (const auto &Loc : Locs)
+Ranges.push_back(tokenRangeForLoc(AST, Loc, SM, LangOpts));
+  auto FilePath = AST.tuPath();
+  auto RenameRanges = collectRenameIdentifierRanges(

kadircet wrote:

well you know how I hate complexity :)) so I am definitely in favor of leaving 
it as-is.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread kadir çetinkaya via cfe-commits


@@ -696,7 +982,7 @@ renameOutsideFile(const NamedDecl &RenameDecl, 
llvm::StringRef MainFilePath,
FilePath);
 }
 auto RenameEdit =
-buildRenameEdit(FilePath, AffectedFileCode, *RenameRanges, NewName);
+buildRenameEdit(FilePath, AffectedFileCode, *RenameRanges, NewNames);

kadircet wrote:

can you also move the logic that splits `NewName` into `NewNames` here? with a 
comment explaining why it's done for objc selectors?

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread kadir çetinkaya via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool parseMessageExpression(llvm::ArrayRef Tokens,
+const SourceManager &SM, unsigned Index,
+unsigned Last, Selector Sel,
+std::vector &SelectorPieces) {

kadircet wrote:

can we return `std::vector` instead? indicating failure with an empty 
vector?

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread Alex Hoppen via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool parseMessageExpression(llvm::ArrayRef Tokens,
+const SourceManager &SM, unsigned Index,
+unsigned Last, Selector Sel,
+std::vector &SelectorPieces) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
+// token after the name.
+if (!Sel.getNameForSlot(PieceCount).empty()) {
+  ++Index;
+}
+SelectorPieces.push_back(
+halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces but the current token looks like another
+  // selector piece, it means the method being renamed is a strict prefix 
of
+  // the selector we've found - should be skipped.
+  if (SelectorPieces.size() >= NumArgs &&
+  isSelectorLike(Tok, Tokens[Index + 1]))
+return false;
+}
+
+switch (Tok.kind()) {
+case tok::l_square:
+  Closes.push_back(']');
+  break;
+case tok::l_paren:
+  Closes.push_back(')');
+  break;
+case tok::l_brace:
+  Closes.push_back('}');
+  break;
+case tok::r_square:
+  if (Closes.empty())
+return SelectorPieces.size() == NumArgs;
+
+  if (Closes.back() != ']')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_paren:
+  if (Closes.empty() || Closes.back() != ')')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_brace:
+  if (Closes.empty() || Closes.back() != '}')
+return false;
+  Closes.pop_back();
+  break;
+case tok::semi:
+  // top level ; ends all statements.
+  if (Closes.empty())
+return false;
+  break;
+default:
+  break;
+}
+
+++Index;
+  }
+  return false;
+}
+
+/// 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 collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions &LangOpts, std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto &R : IdentifierRanges)
+  Ranges.emplace_back(R);
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto &SM = FileSM.get();
+
+  // We track parens and braces to ensure that we don't accidentally try 
parsing
+  // a method declaration or definition which isn't at the top level or similar
+  // looking expressions (e.g. an @selector() expression).
+  unsigned ParenCount = 0;
+  unsigned BraceCount = 0;
+  unsigned NumArgs = Selector->getNumArgs();
+
+  std::vector SelectorPieces;
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+  for (unsigned Index = 0; Index < Last; ++Index) {
+const auto &Tok = Tokens[Index];
+
+if (BraceCou

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread Alex Hoppen via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool parseMessageExpression(llvm::ArrayRef Tokens,

ahoppen wrote:

I think a doc comment of what this method does on a high level would be very 
helpful.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread Alex Hoppen via cfe-commits


@@ -681,12 +957,22 @@ renameOutsideFile(const NamedDecl &RenameDecl, 
llvm::StringRef MainFilePath,
ExpBuffer.getError().message());
   continue;
 }
+std::string RenameIdentifier = RenameDecl.getNameAsString();
+std::optional Selector = std::nullopt;
+llvm::SmallVector NewNames;
+if (const auto *MD = dyn_cast(&RenameDecl)) {

ahoppen wrote:

A high-level comment. Instead of dealing with a vector of `StringRef` for 
`NewNames`, I think we should use `SymbolName`. It is already designed to be 
able to represent Objective-C selectors and that way you could do the name 
splitting in `SymbolName`.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread Alex Hoppen via cfe-commits




ahoppen wrote:

I think we could also include my tests from 
https://github.com/llvm/llvm-project/pull/78872/files#diff-26ff7c74af8a1d882abbad43625d3cd4bf8d024ef5c7064993f5ede3eec8752eR834

More tests never hurt.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread Alex Hoppen via cfe-commits


@@ -681,12 +957,22 @@ renameOutsideFile(const NamedDecl &RenameDecl, 
llvm::StringRef MainFilePath,
ExpBuffer.getError().message());
   continue;
 }
+std::string RenameIdentifier = RenameDecl.getNameAsString();

ahoppen wrote:

Technically `RenameIdentifier` isn’t correct here because this can be an 
Objective-C selector name, right? Which would be multiple identifiers and 
colons.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread Alex Hoppen via cfe-commits


@@ -508,24 +513,46 @@ static bool mayBeValidIdentifier(llvm::StringRef Ident) {
   !isAsciiIdentifierStart(Ident.front(), AllowDollar))
 return false;
   for (char C : Ident) {
+if (AllowColon && C == ':')
+  continue;
 if (llvm::isASCII(C) && !isAsciiIdentifierContinue(C, AllowDollar))
   return false;
   }
   return true;
 }
 
+std::string getName(const NamedDecl &RenameDecl) {
+  if (const auto *MD = dyn_cast(&RenameDecl))
+return MD->getSelector().getAsString();
+  if (const auto *ID = RenameDecl.getIdentifier())
+return ID->getName().str();
+  return "";
+}
+
 // Check if we can rename the given RenameDecl into NewName.
 // Return details if the rename would produce a conflict.
-std::optional checkName(const NamedDecl &RenameDecl,
- llvm::StringRef NewName) {
+std::optional checkName(const NamedDecl &RenameDecl,
+ llvm::StringRef NewName,
+ llvm::StringRef OldName) {
   trace::Span Tracer("CheckName");
   static constexpr trace::Metric InvalidNameMetric(
   "rename_name_invalid", trace::Metric::Counter, "invalid_kind");
+
+  if (OldName == NewName)
+return makeError(ReasonToReject::SameName);
+
+  if (const auto *MD = dyn_cast(&RenameDecl)) {
+const auto Sel = MD->getSelector();
+if (Sel.getNumArgs() != NewName.count(':') &&
+NewName != "__clangd_rename_placeholder")
+  return makeError(InvalidName{InvalidName::BadIdentifier, NewName.str()});

ahoppen wrote:

This seems awfully ad-hoc to check for `__clangd_rename_placeholder`. I would 
prefer to change the prepare rename request to just change one of the selector 
pieces to `__clangd_rename_placeholder`.

I’ve been doing this here

https://github.com/llvm/llvm-project/pull/78872/files#diff-26ff7c74af8a1d882abbad43625d3cd4bf8d024ef5c7064993f5ede3eec8752eR817-R829

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread Alex Hoppen via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool parseMessageExpression(llvm::ArrayRef Tokens,
+const SourceManager &SM, unsigned Index,
+unsigned Last, Selector Sel,
+std::vector &SelectorPieces) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
+// token after the name.
+if (!Sel.getNameForSlot(PieceCount).empty()) {
+  ++Index;
+}
+SelectorPieces.push_back(
+halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces but the current token looks like another
+  // selector piece, it means the method being renamed is a strict prefix 
of
+  // the selector we've found - should be skipped.
+  if (SelectorPieces.size() >= NumArgs &&
+  isSelectorLike(Tok, Tokens[Index + 1]))
+return false;
+}
+
+switch (Tok.kind()) {
+case tok::l_square:
+  Closes.push_back(']');
+  break;
+case tok::l_paren:
+  Closes.push_back(')');
+  break;
+case tok::l_brace:
+  Closes.push_back('}');
+  break;
+case tok::r_square:
+  if (Closes.empty())
+return SelectorPieces.size() == NumArgs;
+
+  if (Closes.back() != ']')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_paren:
+  if (Closes.empty() || Closes.back() != ')')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_brace:
+  if (Closes.empty() || Closes.back() != '}')
+return false;
+  Closes.pop_back();
+  break;
+case tok::semi:
+  // top level ; ends all statements.
+  if (Closes.empty())
+return false;
+  break;
+default:
+  break;
+}
+
+++Index;
+  }
+  return false;
+}
+
+/// 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 collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions &LangOpts, std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto &R : IdentifierRanges)
+  Ranges.emplace_back(R);
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto &SM = FileSM.get();
+
+  // We track parens and braces to ensure that we don't accidentally try 
parsing
+  // a method declaration or definition which isn't at the top level or similar
+  // looking expressions (e.g. an @selector() expression).
+  unsigned ParenCount = 0;
+  unsigned BraceCount = 0;
+  unsigned NumArgs = Selector->getNumArgs();
+
+  std::vector SelectorPieces;
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);

ahoppen wrote:

When calling `collectRenameIdentifierRanges` from `renameObjCMethodWithinFile`, 
we already have a `ParsedAST` and 

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread Alex Hoppen via cfe-commits


@@ -508,24 +513,46 @@ static bool mayBeValidIdentifier(llvm::StringRef Ident) {
   !isAsciiIdentifierStart(Ident.front(), AllowDollar))
 return false;
   for (char C : Ident) {
+if (AllowColon && C == ':')
+  continue;
 if (llvm::isASCII(C) && !isAsciiIdentifierContinue(C, AllowDollar))
   return false;
   }
   return true;
 }
 
+std::string getName(const NamedDecl &RenameDecl) {
+  if (const auto *MD = dyn_cast(&RenameDecl))
+return MD->getSelector().getAsString();
+  if (const auto *ID = RenameDecl.getIdentifier())
+return ID->getName().str();
+  return "";
+}
+
 // Check if we can rename the given RenameDecl into NewName.
 // Return details if the rename would produce a conflict.
-std::optional checkName(const NamedDecl &RenameDecl,
- llvm::StringRef NewName) {
+std::optional checkName(const NamedDecl &RenameDecl,
+ llvm::StringRef NewName,
+ llvm::StringRef OldName) {
   trace::Span Tracer("CheckName");
   static constexpr trace::Metric InvalidNameMetric(
   "rename_name_invalid", trace::Metric::Counter, "invalid_kind");
+
+  if (OldName == NewName)
+return makeError(ReasonToReject::SameName);
+
+  if (const auto *MD = dyn_cast(&RenameDecl)) {
+const auto Sel = MD->getSelector();
+if (Sel.getNumArgs() != NewName.count(':') &&

ahoppen wrote:

Instead of effectively doing a stripped-down selector parsing here, I would 
make `checkName` take a `SymbolName`.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread Alex Hoppen via cfe-commits


@@ -569,8 +840,13 @@ renameWithinFile(ParsedAST &AST, const NamedDecl 
&RenameDecl,
 //   }
 if (!isInsideMainFile(RenameLoc, SM))
   continue;
+Locs.push_back(RenameLoc);
+  }
+  if (const auto *MD = dyn_cast(&RenameDecl))
+return renameObjCMethodWithinFile(AST, MD, NewName, std::move(Locs));

ahoppen wrote:

If it is a zero-arg or one-arg Objective-C selector, I think we can still use 
the old, faster path that doesn’t need to scan the source file for Objective-C 
selectors using `renameObjCMethodWithinFile`.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread Alex Hoppen via cfe-commits


@@ -508,24 +513,46 @@ static bool mayBeValidIdentifier(llvm::StringRef Ident) {
   !isAsciiIdentifierStart(Ident.front(), AllowDollar))
 return false;
   for (char C : Ident) {
+if (AllowColon && C == ':')

ahoppen wrote:

Should we also allow spaces here if you spell the new method name as 
`doSomething: with:`?

--- 

Should we also check that the first selector piece is not empty? Ie. that `: 
with:` is not a valid selector.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread Alex Hoppen via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();

ahoppen wrote:

Could this be simplified to 

```suggestion
  return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
```

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread Alex Hoppen via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool parseMessageExpression(llvm::ArrayRef Tokens,
+const SourceManager &SM, unsigned Index,
+unsigned Last, Selector Sel,
+std::vector &SelectorPieces) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;

ahoppen wrote:

Wouldn’t it be cleaner if `Closes` is a vector of token kinds? I think then you 
could also just check something like the following instead of repeating it in 
the `switch`.

```cpp
if (!Closes.empty() && Closes.back() == Tok.kind()) {
  Closes.pop_back();
  continue;
}

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread Alex Hoppen via cfe-commits


@@ -53,13 +55,34 @@ struct RenameInputs {
 struct RenameResult {
   // The range of the symbol that the user can attempt to rename.
   Range Target;
+  // Placeholder text for the rename operation if non-empty.
+  std::string Placeholder;
   // Rename occurrences for the current main file.
   std::vector LocalChanges;
   // Complete edits for the rename, including LocalChanges.
   // If the full set of changes is unknown, this field is empty.
   FileEdits GlobalChanges;
 };
 
+/// Represents a symbol range where the symbol can potentially have multiple
+/// tokens.
+struct SymbolRange {
+  /// Ranges for the tokens that make up the symbol's name.
+  /// Usually a single range, but there can be multiple ranges if the tokens 
for
+  /// the symbol are split, e.g. ObjC selectors.
+  std::vector Ranges;

ahoppen wrote:

I think it would be good to clarify if 
- These ranges are only the selector piece names or also the colons
- If an empty selector piece is represented by an empty range in here

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-02 Thread Ben Barham via cfe-commits

https://github.com/bnbarham commented:

Cool stuff @DavidGoldman! Nice to see ObjC getting some love. It's a bummer we 
duplicated here, but seems like both you and @ahoppen took fairly similar 
approaches. The main difference looks to be the use of `SymbolName` in the 
other PR.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-05 Thread David Goldman via cfe-commits


@@ -508,24 +513,46 @@ static bool mayBeValidIdentifier(llvm::StringRef Ident) {
   !isAsciiIdentifierStart(Ident.front(), AllowDollar))
 return false;
   for (char C : Ident) {
+if (AllowColon && C == ':')
+  continue;
 if (llvm::isASCII(C) && !isAsciiIdentifierContinue(C, AllowDollar))
   return false;
   }
   return true;
 }
 
+std::string getName(const NamedDecl &RenameDecl) {
+  if (const auto *MD = dyn_cast(&RenameDecl))
+return MD->getSelector().getAsString();
+  if (const auto *ID = RenameDecl.getIdentifier())
+return ID->getName().str();
+  return "";
+}
+
 // Check if we can rename the given RenameDecl into NewName.
 // Return details if the rename would produce a conflict.
-std::optional checkName(const NamedDecl &RenameDecl,
- llvm::StringRef NewName) {
+std::optional checkName(const NamedDecl &RenameDecl,

DavidGoldman wrote:

Done

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-05 Thread David Goldman via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool parseMessageExpression(llvm::ArrayRef Tokens,
+const SourceManager &SM, unsigned Index,
+unsigned Last, Selector Sel,
+std::vector &SelectorPieces) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
+// token after the name.
+if (!Sel.getNameForSlot(PieceCount).empty()) {
+  ++Index;
+}
+SelectorPieces.push_back(
+halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces but the current token looks like another
+  // selector piece, it means the method being renamed is a strict prefix 
of
+  // the selector we've found - should be skipped.
+  if (SelectorPieces.size() >= NumArgs &&
+  isSelectorLike(Tok, Tokens[Index + 1]))
+return false;
+}
+
+switch (Tok.kind()) {
+case tok::l_square:
+  Closes.push_back(']');
+  break;
+case tok::l_paren:
+  Closes.push_back(')');
+  break;
+case tok::l_brace:
+  Closes.push_back('}');
+  break;
+case tok::r_square:
+  if (Closes.empty())
+return SelectorPieces.size() == NumArgs;
+
+  if (Closes.back() != ']')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_paren:
+  if (Closes.empty() || Closes.back() != ')')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_brace:
+  if (Closes.empty() || Closes.back() != '}')
+return false;
+  Closes.pop_back();
+  break;
+case tok::semi:
+  // top level ; ends all statements.
+  if (Closes.empty())
+return false;
+  break;
+default:
+  break;
+}
+
+++Index;
+  }
+  return false;
+}
+
+/// 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 collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions &LangOpts, std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto &R : IdentifierRanges)
+  Ranges.emplace_back(R);
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto &SM = FileSM.get();
+
+  // We track parens and braces to ensure that we don't accidentally try 
parsing
+  // a method declaration or definition which isn't at the top level or similar
+  // looking expressions (e.g. an @selector() expression).
+  unsigned ParenCount = 0;
+  unsigned BraceCount = 0;
+  unsigned NumArgs = Selector->getNumArgs();
+
+  std::vector SelectorPieces;
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+  for (unsigned Index = 0; Index < Last; ++Index) {
+const auto &Tok = Tokens[Index];
+


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-05 Thread David Goldman via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool parseMessageExpression(llvm::ArrayRef Tokens,
+const SourceManager &SM, unsigned Index,
+unsigned Last, Selector Sel,
+std::vector &SelectorPieces) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
+// token after the name.
+if (!Sel.getNameForSlot(PieceCount).empty()) {
+  ++Index;
+}
+SelectorPieces.push_back(
+halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces but the current token looks like another
+  // selector piece, it means the method being renamed is a strict prefix 
of
+  // the selector we've found - should be skipped.
+  if (SelectorPieces.size() >= NumArgs &&
+  isSelectorLike(Tok, Tokens[Index + 1]))
+return false;
+}
+
+switch (Tok.kind()) {
+case tok::l_square:
+  Closes.push_back(']');
+  break;
+case tok::l_paren:
+  Closes.push_back(')');
+  break;
+case tok::l_brace:
+  Closes.push_back('}');
+  break;
+case tok::r_square:
+  if (Closes.empty())
+return SelectorPieces.size() == NumArgs;
+
+  if (Closes.back() != ']')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_paren:
+  if (Closes.empty() || Closes.back() != ')')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_brace:
+  if (Closes.empty() || Closes.back() != '}')
+return false;
+  Closes.pop_back();
+  break;
+case tok::semi:
+  // top level ; ends all statements.
+  if (Closes.empty())
+return false;
+  break;
+default:
+  break;
+}
+
+++Index;
+  }
+  return false;
+}
+
+/// 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 collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions &LangOpts, std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto &R : IdentifierRanges)
+  Ranges.emplace_back(R);
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto &SM = FileSM.get();
+
+  // We track parens and braces to ensure that we don't accidentally try 
parsing
+  // a method declaration or definition which isn't at the top level or similar
+  // looking expressions (e.g. an @selector() expression).
+  unsigned ParenCount = 0;
+  unsigned BraceCount = 0;
+  unsigned NumArgs = Selector->getNumArgs();
+
+  std::vector SelectorPieces;
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+  for (unsigned Index = 0; Index < Last; ++Index) {
+const auto &Tok = Tokens[Index];
+
+if (BraceCou

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-05 Thread David Goldman via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool parseMessageExpression(llvm::ArrayRef Tokens,
+const SourceManager &SM, unsigned Index,
+unsigned Last, Selector Sel,
+std::vector &SelectorPieces) {

DavidGoldman wrote:

Done

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-05 Thread David Goldman via cfe-commits


@@ -508,24 +513,46 @@ static bool mayBeValidIdentifier(llvm::StringRef Ident) {
   !isAsciiIdentifierStart(Ident.front(), AllowDollar))
 return false;
   for (char C : Ident) {
+if (AllowColon && C == ':')

DavidGoldman wrote:

This doesn't check the entire selector - only a fragment, so I don't think 
there's a need to check spaces here. AFAIK selectors can have an empty first 
fragment too - any idea where that restriction is from?

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-05 Thread David Goldman via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool parseMessageExpression(llvm::ArrayRef Tokens,
+const SourceManager &SM, unsigned Index,
+unsigned Last, Selector Sel,
+std::vector &SelectorPieces) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
+// token after the name.
+if (!Sel.getNameForSlot(PieceCount).empty()) {
+  ++Index;
+}
+SelectorPieces.push_back(
+halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces but the current token looks like another
+  // selector piece, it means the method being renamed is a strict prefix 
of
+  // the selector we've found - should be skipped.
+  if (SelectorPieces.size() >= NumArgs &&
+  isSelectorLike(Tok, Tokens[Index + 1]))
+return false;
+}
+
+switch (Tok.kind()) {
+case tok::l_square:
+  Closes.push_back(']');
+  break;
+case tok::l_paren:
+  Closes.push_back(')');
+  break;
+case tok::l_brace:
+  Closes.push_back('}');
+  break;
+case tok::r_square:
+  if (Closes.empty())
+return SelectorPieces.size() == NumArgs;
+
+  if (Closes.back() != ']')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_paren:
+  if (Closes.empty() || Closes.back() != ')')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_brace:
+  if (Closes.empty() || Closes.back() != '}')
+return false;
+  Closes.pop_back();
+  break;
+case tok::semi:
+  // top level ; ends all statements.
+  if (Closes.empty())
+return false;
+  break;
+default:
+  break;
+}
+
+++Index;
+  }
+  return false;
+}
+
+/// 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 collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions &LangOpts, std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto &R : IdentifierRanges)
+  Ranges.emplace_back(R);
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto &SM = FileSM.get();
+
+  // We track parens and braces to ensure that we don't accidentally try 
parsing
+  // a method declaration or definition which isn't at the top level or similar
+  // looking expressions (e.g. an @selector() expression).
+  unsigned ParenCount = 0;
+  unsigned BraceCount = 0;
+  unsigned NumArgs = Selector->getNumArgs();
+
+  std::vector SelectorPieces;
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);

DavidGoldman wrote:

That's true, although a bit annoying since we need a SM to fetch this and I 
wouldn't want to force that into 

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-05 Thread Alex Hoppen via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool parseMessageExpression(llvm::ArrayRef Tokens,
+const SourceManager &SM, unsigned Index,
+unsigned Last, Selector Sel,
+std::vector &SelectorPieces) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
+// token after the name.
+if (!Sel.getNameForSlot(PieceCount).empty()) {
+  ++Index;
+}
+SelectorPieces.push_back(
+halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces but the current token looks like another
+  // selector piece, it means the method being renamed is a strict prefix 
of
+  // the selector we've found - should be skipped.
+  if (SelectorPieces.size() >= NumArgs &&
+  isSelectorLike(Tok, Tokens[Index + 1]))
+return false;
+}
+
+switch (Tok.kind()) {
+case tok::l_square:
+  Closes.push_back(']');
+  break;
+case tok::l_paren:
+  Closes.push_back(')');
+  break;
+case tok::l_brace:
+  Closes.push_back('}');
+  break;
+case tok::r_square:
+  if (Closes.empty())
+return SelectorPieces.size() == NumArgs;
+
+  if (Closes.back() != ']')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_paren:
+  if (Closes.empty() || Closes.back() != ')')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_brace:
+  if (Closes.empty() || Closes.back() != '}')
+return false;
+  Closes.pop_back();
+  break;
+case tok::semi:
+  // top level ; ends all statements.
+  if (Closes.empty())
+return false;
+  break;
+default:
+  break;
+}
+
+++Index;
+  }
+  return false;
+}
+
+/// 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 collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions &LangOpts, std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto &R : IdentifierRanges)
+  Ranges.emplace_back(R);
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto &SM = FileSM.get();
+
+  // We track parens and braces to ensure that we don't accidentally try 
parsing
+  // a method declaration or definition which isn't at the top level or similar
+  // looking expressions (e.g. an @selector() expression).
+  unsigned ParenCount = 0;
+  unsigned BraceCount = 0;
+  unsigned NumArgs = Selector->getNumArgs();
+
+  std::vector SelectorPieces;
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+  for (unsigned Index = 0; Index < Last; ++Index) {
+const auto &Tok = Tokens[Index];
+


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-05 Thread Alex Hoppen via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool parseMessageExpression(llvm::ArrayRef Tokens,
+const SourceManager &SM, unsigned Index,
+unsigned Last, Selector Sel,
+std::vector &SelectorPieces) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
+// token after the name.
+if (!Sel.getNameForSlot(PieceCount).empty()) {
+  ++Index;
+}
+SelectorPieces.push_back(
+halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces but the current token looks like another
+  // selector piece, it means the method being renamed is a strict prefix 
of
+  // the selector we've found - should be skipped.
+  if (SelectorPieces.size() >= NumArgs &&
+  isSelectorLike(Tok, Tokens[Index + 1]))
+return false;
+}
+
+switch (Tok.kind()) {
+case tok::l_square:
+  Closes.push_back(']');
+  break;
+case tok::l_paren:
+  Closes.push_back(')');
+  break;
+case tok::l_brace:
+  Closes.push_back('}');
+  break;
+case tok::r_square:
+  if (Closes.empty())
+return SelectorPieces.size() == NumArgs;
+
+  if (Closes.back() != ']')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_paren:
+  if (Closes.empty() || Closes.back() != ')')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_brace:
+  if (Closes.empty() || Closes.back() != '}')
+return false;
+  Closes.pop_back();
+  break;
+case tok::semi:
+  // top level ; ends all statements.
+  if (Closes.empty())
+return false;
+  break;
+default:
+  break;
+}
+
+++Index;
+  }
+  return false;
+}
+
+/// 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 collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions &LangOpts, std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto &R : IdentifierRanges)
+  Ranges.emplace_back(R);
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto &SM = FileSM.get();
+
+  // We track parens and braces to ensure that we don't accidentally try 
parsing
+  // a method declaration or definition which isn't at the top level or similar
+  // looking expressions (e.g. an @selector() expression).
+  unsigned ParenCount = 0;
+  unsigned BraceCount = 0;
+  unsigned NumArgs = Selector->getNumArgs();
+
+  std::vector SelectorPieces;
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+  for (unsigned Index = 0; Index < Last; ++Index) {
+const auto &Tok = Tokens[Index];
+
+if (BraceCou

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-05 Thread Alex Hoppen via cfe-commits


@@ -508,24 +513,46 @@ static bool mayBeValidIdentifier(llvm::StringRef Ident) {
   !isAsciiIdentifierStart(Ident.front(), AllowDollar))
 return false;
   for (char C : Ident) {
+if (AllowColon && C == ':')

ahoppen wrote:

Oh, I just assumed that the first selector argument had to be named. I never 
checked if it can be unnamed.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-05 Thread Alex Hoppen via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool parseMessageExpression(llvm::ArrayRef Tokens,
+const SourceManager &SM, unsigned Index,
+unsigned Last, Selector Sel,
+std::vector &SelectorPieces) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
+// token after the name.
+if (!Sel.getNameForSlot(PieceCount).empty()) {
+  ++Index;
+}
+SelectorPieces.push_back(
+halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces but the current token looks like another
+  // selector piece, it means the method being renamed is a strict prefix 
of
+  // the selector we've found - should be skipped.
+  if (SelectorPieces.size() >= NumArgs &&
+  isSelectorLike(Tok, Tokens[Index + 1]))
+return false;
+}
+
+switch (Tok.kind()) {
+case tok::l_square:
+  Closes.push_back(']');
+  break;
+case tok::l_paren:
+  Closes.push_back(')');
+  break;
+case tok::l_brace:
+  Closes.push_back('}');
+  break;
+case tok::r_square:
+  if (Closes.empty())
+return SelectorPieces.size() == NumArgs;
+
+  if (Closes.back() != ']')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_paren:
+  if (Closes.empty() || Closes.back() != ')')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_brace:
+  if (Closes.empty() || Closes.back() != '}')
+return false;
+  Closes.pop_back();
+  break;
+case tok::semi:
+  // top level ; ends all statements.
+  if (Closes.empty())
+return false;
+  break;
+default:
+  break;
+}
+
+++Index;
+  }
+  return false;
+}
+
+/// 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 collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions &LangOpts, std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto &R : IdentifierRanges)
+  Ranges.emplace_back(R);
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto &SM = FileSM.get();
+
+  // We track parens and braces to ensure that we don't accidentally try 
parsing
+  // a method declaration or definition which isn't at the top level or similar
+  // looking expressions (e.g. an @selector() expression).
+  unsigned ParenCount = 0;
+  unsigned BraceCount = 0;
+  unsigned NumArgs = Selector->getNumArgs();
+
+  std::vector SelectorPieces;
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);

ahoppen wrote:

I don’t think it’s too bad to force that onto the caller. AFAICT we have two 
calls to `collectRenameIdentifierRang

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-06 Thread David Goldman via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();

DavidGoldman wrote:

Done

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-06 Thread David Goldman via cfe-commits

https://github.com/DavidGoldman updated 
https://github.com/llvm/llvm-project/pull/76466

>From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001
From: David Goldman 
Date: Tue, 26 Dec 2023 15:59:01 -0500
Subject: [PATCH 1/8] [clangd][SymbolCollector] Treat ObjC methods as spelled

We'll treat multi-arg methods as spelled once we have full rename
support for them.
---
 .../clangd/index/SymbolCollector.cpp  |  6 ++-
 .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++
 2 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp 
b/clang-tools-extra/clangd/index/SymbolCollector.cpp
index 7ef4b15febad22..336bc3506bb360 100644
--- a/clang-tools-extra/clangd/index/SymbolCollector.cpp
+++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp
@@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
   auto Name = ND.getDeclName();
   const auto NameKind = Name.getNameKind();
   if (NameKind != DeclarationName::Identifier &&
-  NameKind != DeclarationName::CXXConstructorName)
+  NameKind != DeclarationName::CXXConstructorName &&
+  NameKind != DeclarationName::ObjCZeroArgSelector &&
+  NameKind != DeclarationName::ObjCOneArgSelector)
 return false;
   const auto &AST = ND.getASTContext();
   const auto &SM = AST.getSourceManager();
@@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
   if (clang::Lexer::getRawToken(Loc, Tok, SM, LO))
 return false;
   auto StrName = Name.getAsString();
+  if (const auto *MD = dyn_cast(&ND))
+StrName = MD->getSelector().getNameForSlot(0).str();
   return clang::Lexer::getSpelling(Tok, SM, LO) == StrName;
 }
 } // namespace
diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp 
b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 9cdc57ec01f327..1d4e1c1d75ea23 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -105,6 +105,9 @@ MATCHER(refRange, "") {
 MATCHER_P2(OverriddenBy, Subject, Object, "") {
   return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID};
 }
+MATCHER(isSpelled, "") {
+  return static_cast(arg.Kind & RefKind::Spelled);
+}
 ::testing::Matcher &>
 haveRanges(const std::vector Ranges) {
   return ::testing::UnorderedPointwise(refRange(), Ranges);
@@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) {
  forCodeCompletion(false);
 }
 
+TEST_F(SymbolCollectorTest, ObjCRefs) {
+  Annotations Header(R"(
+  @interface Person
+  - (void)$talk[[talk]];
+  - (void)$say[[say]]:(id)something;
+  @end
+  @interface Person (Category)
+  - (void)categoryMethod;
+  - (void)multiArg:(id)a method:(id)b;
+  @end
+  )");
+  Annotations Main(R"(
+  @implementation Person
+  - (void)$talk[[talk]] {}
+  - (void)$say[[say]]:(id)something {}
+  @end
+
+  void fff(Person *p) {
+[p $talk[[talk]]];
+[p $say[[say]]:0];
+[p categoryMethod];
+[p multiArg:0 method:0];
+  }
+  )");
+  CollectorOpts.RefFilter = RefKind::All;
+  CollectorOpts.CollectMainFileRefs = true;
+  TestFileName = testPath("test.m");
+  runSymbolCollector(Header.code(), Main.code(),
+ {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"});
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID,
+  haveRanges(Main.ranges("talk");
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID,
+  haveRanges(Main.ranges("say");
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, 
"Person::categoryMethod").ID,
+  ElementsAre(isSpelled();
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, 
"Person::multiArg:method:").ID,
+  ElementsAre(Not(isSpelled());
+}
+
 TEST_F(SymbolCollectorTest, ObjCSymbols) {
   const std::string Header = R"(
 @interface Person

>From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001
From: David Goldman 
Date: Tue, 26 Dec 2023 16:12:03 -0500
Subject: [PATCH 2/8] Run clang-format

---
 .../clangd/unittests/SymbolCollectorTests.cpp  | 10 ++
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp 
b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 1d4e1c1d75ea23..5c20b950e4eac0 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) {
   haveRanges(Main.ranges("talk");
   EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID,
   haveRanges(Main.ranges("say");
-  EXPECT_THAT(Refs, Contains(Pair(findSymbol

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-06 Thread David Goldman via cfe-commits

https://github.com/DavidGoldman updated 
https://github.com/llvm/llvm-project/pull/76466

>From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001
From: David Goldman 
Date: Tue, 26 Dec 2023 15:59:01 -0500
Subject: [PATCH 1/9] [clangd][SymbolCollector] Treat ObjC methods as spelled

We'll treat multi-arg methods as spelled once we have full rename
support for them.
---
 .../clangd/index/SymbolCollector.cpp  |  6 ++-
 .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++
 2 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp 
b/clang-tools-extra/clangd/index/SymbolCollector.cpp
index 7ef4b15febad2..336bc3506bb36 100644
--- a/clang-tools-extra/clangd/index/SymbolCollector.cpp
+++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp
@@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
   auto Name = ND.getDeclName();
   const auto NameKind = Name.getNameKind();
   if (NameKind != DeclarationName::Identifier &&
-  NameKind != DeclarationName::CXXConstructorName)
+  NameKind != DeclarationName::CXXConstructorName &&
+  NameKind != DeclarationName::ObjCZeroArgSelector &&
+  NameKind != DeclarationName::ObjCOneArgSelector)
 return false;
   const auto &AST = ND.getASTContext();
   const auto &SM = AST.getSourceManager();
@@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
   if (clang::Lexer::getRawToken(Loc, Tok, SM, LO))
 return false;
   auto StrName = Name.getAsString();
+  if (const auto *MD = dyn_cast(&ND))
+StrName = MD->getSelector().getNameForSlot(0).str();
   return clang::Lexer::getSpelling(Tok, SM, LO) == StrName;
 }
 } // namespace
diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp 
b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 9cdc57ec01f32..1d4e1c1d75ea2 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -105,6 +105,9 @@ MATCHER(refRange, "") {
 MATCHER_P2(OverriddenBy, Subject, Object, "") {
   return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID};
 }
+MATCHER(isSpelled, "") {
+  return static_cast(arg.Kind & RefKind::Spelled);
+}
 ::testing::Matcher &>
 haveRanges(const std::vector Ranges) {
   return ::testing::UnorderedPointwise(refRange(), Ranges);
@@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) {
  forCodeCompletion(false);
 }
 
+TEST_F(SymbolCollectorTest, ObjCRefs) {
+  Annotations Header(R"(
+  @interface Person
+  - (void)$talk[[talk]];
+  - (void)$say[[say]]:(id)something;
+  @end
+  @interface Person (Category)
+  - (void)categoryMethod;
+  - (void)multiArg:(id)a method:(id)b;
+  @end
+  )");
+  Annotations Main(R"(
+  @implementation Person
+  - (void)$talk[[talk]] {}
+  - (void)$say[[say]]:(id)something {}
+  @end
+
+  void fff(Person *p) {
+[p $talk[[talk]]];
+[p $say[[say]]:0];
+[p categoryMethod];
+[p multiArg:0 method:0];
+  }
+  )");
+  CollectorOpts.RefFilter = RefKind::All;
+  CollectorOpts.CollectMainFileRefs = true;
+  TestFileName = testPath("test.m");
+  runSymbolCollector(Header.code(), Main.code(),
+ {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"});
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID,
+  haveRanges(Main.ranges("talk");
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID,
+  haveRanges(Main.ranges("say");
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, 
"Person::categoryMethod").ID,
+  ElementsAre(isSpelled();
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, 
"Person::multiArg:method:").ID,
+  ElementsAre(Not(isSpelled());
+}
+
 TEST_F(SymbolCollectorTest, ObjCSymbols) {
   const std::string Header = R"(
 @interface Person

>From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001
From: David Goldman 
Date: Tue, 26 Dec 2023 16:12:03 -0500
Subject: [PATCH 2/9] Run clang-format

---
 .../clangd/unittests/SymbolCollectorTests.cpp  | 10 ++
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp 
b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 1d4e1c1d75ea2..5c20b950e4eac 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) {
   haveRanges(Main.ranges("talk");
   EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID,
   haveRanges(Main.ranges("say");
-  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbo

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-07 Thread David Goldman via cfe-commits




DavidGoldman wrote:

Done

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-07 Thread David Goldman via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool parseMessageExpression(llvm::ArrayRef Tokens,
+const SourceManager &SM, unsigned Index,
+unsigned Last, Selector Sel,
+std::vector &SelectorPieces) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;

DavidGoldman wrote:

Done

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-07 Thread David Goldman via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return std::nullopt;
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ Cur.text(SM) == SelectorName &&
+ // We require the selector name and : to be contiguous to avoid
+ // potential conflicts with ternary expression.
+ //
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool parseMessageExpression(llvm::ArrayRef Tokens,
+const SourceManager &SM, unsigned Index,
+unsigned Last, Selector Sel,
+std::vector &SelectorPieces) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
+// token after the name.
+if (!Sel.getNameForSlot(PieceCount).empty()) {
+  ++Index;
+}
+SelectorPieces.push_back(
+halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces but the current token looks like another
+  // selector piece, it means the method being renamed is a strict prefix 
of
+  // the selector we've found - should be skipped.
+  if (SelectorPieces.size() >= NumArgs &&
+  isSelectorLike(Tok, Tokens[Index + 1]))
+return false;
+}
+
+switch (Tok.kind()) {
+case tok::l_square:
+  Closes.push_back(']');
+  break;
+case tok::l_paren:
+  Closes.push_back(')');
+  break;
+case tok::l_brace:
+  Closes.push_back('}');
+  break;
+case tok::r_square:
+  if (Closes.empty())
+return SelectorPieces.size() == NumArgs;
+
+  if (Closes.back() != ']')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_paren:
+  if (Closes.empty() || Closes.back() != ')')
+return false;
+  Closes.pop_back();
+  break;
+case tok::r_brace:
+  if (Closes.empty() || Closes.back() != '}')
+return false;
+  Closes.pop_back();
+  break;
+case tok::semi:
+  // top level ; ends all statements.
+  if (Closes.empty())
+return false;
+  break;
+default:
+  break;
+}
+
+++Index;
+  }
+  return false;
+}
+
+/// 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 collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions &LangOpts, std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto &R : IdentifierRanges)
+  Ranges.emplace_back(R);
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto &SM = FileSM.get();
+
+  // We track parens and braces to ensure that we don't accidentally try 
parsing
+  // a method declaration or definition which isn't at the top level or similar
+  // looking expressions (e.g. an @selector() expression).
+  unsigned ParenCount = 0;
+  unsigned BraceCount = 0;
+  unsigned NumArgs = Selector->getNumArgs();
+
+  std::vector SelectorPieces;
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+  for (unsigned Index = 0; Index < Last; ++Index) {
+const auto &Tok = Tokens[Index];
+


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-08 Thread kadir çetinkaya via cfe-commits


@@ -538,11 +564,205 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return llvm::Error::success();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
+}
+
+std::vector findAllSelectorPieces(llvm::ArrayRef Tokens,

kadircet wrote:

can we just return a `std::optional` here?

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-08 Thread kadir çetinkaya via cfe-commits

https://github.com/kadircet commented:

thanks a lot for bearing with me @DavidGoldman !

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-08 Thread kadir çetinkaya via cfe-commits


@@ -538,11 +564,205 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return llvm::Error::success();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
+}
+
+std::vector findAllSelectorPieces(llvm::ArrayRef Tokens,
+ const SourceManager &SM, Selector Sel,
+ tok::TokenKind Terminator) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  std::vector SelectorPieces;
+  unsigned Last = Tokens.size() - 1;
+  for (unsigned Index = 0; Index < Last; ++Index) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
+// token after the name.
+if (!Sel.getNameForSlot(PieceCount).empty()) {

kadircet wrote:

nit: braces

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-08 Thread kadir çetinkaya via cfe-commits


@@ -538,11 +564,205 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return llvm::Error::success();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
+}
+
+std::vector findAllSelectorPieces(llvm::ArrayRef Tokens,
+ const SourceManager &SM, Selector Sel,
+ tok::TokenKind Terminator) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  std::vector SelectorPieces;
+  unsigned Last = Tokens.size() - 1;
+  for (unsigned Index = 0; Index < Last; ++Index) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
+// token after the name.
+if (!Sel.getNameForSlot(PieceCount).empty()) {
+  ++Index;
+}
+SelectorPieces.push_back(
+halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces but the current token looks like another
+  // selector piece, it means the method being renamed is a strict prefix 
of
+  // the selector we've found - should be skipped.
+  if (SelectorPieces.size() >= NumArgs &&
+  isSelectorLike(Tok, Tokens[Index + 1]))
+return std::vector();
+}
+
+if (Tok.kind() == Terminator)

kadircet wrote:

shouldn't this also be checked only when `Closes.empty()`?

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-08 Thread kadir çetinkaya via cfe-commits

https://github.com/kadircet edited 
https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-08 Thread kadir çetinkaya via cfe-commits


@@ -538,11 +564,205 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return llvm::Error::success();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
+}
+
+std::vector findAllSelectorPieces(llvm::ArrayRef Tokens,
+ const SourceManager &SM, Selector Sel,
+ tok::TokenKind Terminator) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  std::vector SelectorPieces;
+  unsigned Last = Tokens.size() - 1;
+  for (unsigned Index = 0; Index < Last; ++Index) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
+// token after the name.
+if (!Sel.getNameForSlot(PieceCount).empty()) {
+  ++Index;
+}
+SelectorPieces.push_back(
+halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces but the current token looks like another
+  // selector piece, it means the method being renamed is a strict prefix 
of
+  // the selector we've found - should be skipped.
+  if (SelectorPieces.size() >= NumArgs &&
+  isSelectorLike(Tok, Tokens[Index + 1]))
+return std::vector();
+}
+
+if (Tok.kind() == Terminator)
+  return SelectorPieces.size() == NumArgs ? SelectorPieces
+  : std::vector();
+
+switch (Tok.kind()) {
+case tok::l_square:
+  Closes.push_back(tok::r_square);
+  break;
+case tok::l_paren:
+  Closes.push_back(tok::r_paren);
+  break;
+case tok::l_brace:
+  Closes.push_back(tok::r_brace);
+  break;
+case tok::r_square:
+case tok::r_paren:
+case tok::r_brace:
+  if (Closes.empty() || Closes.back() != Tok.kind())
+return std::vector();
+  Closes.pop_back();
+  break;
+case tok::semi:
+  // top level ; terminates all statements.
+  if (Closes.empty())
+return SelectorPieces.size() == NumArgs ? SelectorPieces
+: std::vector();
+  break;
+default:
+  break;
+}
+  }
+  return std::vector();
+}
+
+/// 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 collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions &LangOpts, std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto &R : IdentifierRanges)
+  Ranges.emplace_back(R);
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto &SM = FileSM.get();
+
+  // We track parens and brackets to ensure that we don't accidentally try
+  // parsing a method declaration or definition which isn't at the top level or
+  // similar looking expressions (e.g. an @selector() expression).
+  llvm::SmallVector Closes;
+  llvm::StringRef FirstSelPiece = Selector->getNameForSlot(0);
+
+  std::vector SelectorPieces;
+  std::vector MsgExprPieces;
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+  for (unsigned Index = 0; Index < Last; ++Index) {
+const auto &Tok = Tokens[Index];
+
+// Search for the first selector piece to begin a match, but make sure 
we're
+// not in () to avoid the @selector(foo:bar:) case.
+if ((Closes.empty() || Closes.back() == tok::r_square) 

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-08 Thread kadir çetinkaya via cfe-commits


@@ -696,7 +982,7 @@ renameOutsideFile(const NamedDecl &RenameDecl, 
llvm::StringRef MainFilePath,
FilePath);
 }
 auto RenameEdit =
-buildRenameEdit(FilePath, AffectedFileCode, *RenameRanges, NewName);
+buildRenameEdit(FilePath, AffectedFileCode, *RenameRanges, NewNames);

kadircet wrote:

up

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-08 Thread kadir çetinkaya via cfe-commits


@@ -508,24 +513,46 @@ static bool mayBeValidIdentifier(llvm::StringRef Ident) {
   !isAsciiIdentifierStart(Ident.front(), AllowDollar))
 return false;
   for (char C : Ident) {
+if (AllowColon && C == ':')
+  continue;
 if (llvm::isASCII(C) && !isAsciiIdentifierContinue(C, AllowDollar))
   return false;
   }
   return true;
 }
 
+std::string getName(const NamedDecl &RenameDecl) {
+  if (const auto *MD = dyn_cast(&RenameDecl))
+return MD->getSelector().getAsString();
+  if (const auto *ID = RenameDecl.getIdentifier())
+return ID->getName().str();
+  return "";
+}
+
 // Check if we can rename the given RenameDecl into NewName.
 // Return details if the rename would produce a conflict.
-std::optional checkName(const NamedDecl &RenameDecl,
- llvm::StringRef NewName) {
+std::optional checkName(const NamedDecl &RenameDecl,
+ llvm::StringRef NewName,
+ llvm::StringRef OldName) {
   trace::Span Tracer("CheckName");
   static constexpr trace::Metric InvalidNameMetric(
   "rename_name_invalid", trace::Metric::Counter, "invalid_kind");
+
+  if (OldName == NewName)
+return makeError(ReasonToReject::SameName);
+
+  if (const auto *MD = dyn_cast(&RenameDecl)) {
+const auto Sel = MD->getSelector();
+if (Sel.getNumArgs() != NewName.count(':') &&
+NewName != "__clangd_rename_placeholder")
+  return makeError(InvalidName{InvalidName::BadIdentifier, NewName.str()});

kadircet wrote:

if we want a proper solution, i think it's better to signal that this is a 
prepare-rename request via `RenameInputs`. (but this change is already hard to 
review, so i'd rather see that as a follow up)

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-08 Thread kadir çetinkaya via cfe-commits


@@ -538,11 +564,205 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return llvm::Error::success();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
+}
+
+std::vector findAllSelectorPieces(llvm::ArrayRef Tokens,
+ const SourceManager &SM, Selector Sel,
+ tok::TokenKind Terminator) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  std::vector SelectorPieces;
+  unsigned Last = Tokens.size() - 1;
+  for (unsigned Index = 0; Index < Last; ++Index) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
+// token after the name.
+if (!Sel.getNameForSlot(PieceCount).empty()) {
+  ++Index;
+}
+SelectorPieces.push_back(
+halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces but the current token looks like another
+  // selector piece, it means the method being renamed is a strict prefix 
of
+  // the selector we've found - should be skipped.
+  if (SelectorPieces.size() >= NumArgs &&
+  isSelectorLike(Tok, Tokens[Index + 1]))
+return std::vector();
+}
+
+if (Tok.kind() == Terminator)
+  return SelectorPieces.size() == NumArgs ? SelectorPieces
+  : std::vector();
+
+switch (Tok.kind()) {
+case tok::l_square:
+  Closes.push_back(tok::r_square);
+  break;
+case tok::l_paren:
+  Closes.push_back(tok::r_paren);
+  break;
+case tok::l_brace:
+  Closes.push_back(tok::r_brace);
+  break;
+case tok::r_square:
+case tok::r_paren:
+case tok::r_brace:
+  if (Closes.empty() || Closes.back() != Tok.kind())
+return std::vector();
+  Closes.pop_back();
+  break;
+case tok::semi:
+  // top level ; terminates all statements.
+  if (Closes.empty())
+return SelectorPieces.size() == NumArgs ? SelectorPieces
+: std::vector();
+  break;
+default:
+  break;
+}
+  }
+  return std::vector();
+}
+
+/// 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 collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions &LangOpts, std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto &R : IdentifierRanges)
+  Ranges.emplace_back(R);
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto &SM = FileSM.get();
+
+  // We track parens and brackets to ensure that we don't accidentally try
+  // parsing a method declaration or definition which isn't at the top level or
+  // similar looking expressions (e.g. an @selector() expression).
+  llvm::SmallVector Closes;
+  llvm::StringRef FirstSelPiece = Selector->getNameForSlot(0);
+
+  std::vector SelectorPieces;
+  std::vector MsgExprPieces;

kadircet wrote:

this is not used, right?

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-08 Thread kadir çetinkaya via cfe-commits


@@ -681,12 +957,22 @@ renameOutsideFile(const NamedDecl &RenameDecl, 
llvm::StringRef MainFilePath,
ExpBuffer.getError().message());
   continue;
 }
+std::string RenameIdentifier = RenameDecl.getNameAsString();
+std::optional Selector = std::nullopt;
+llvm::SmallVector NewNames;
+if (const auto *MD = dyn_cast(&RenameDecl)) {

kadircet wrote:

i'd rather leave that to a follow up change so that we can close the loop here, 
if that's OK.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-08 Thread kadir çetinkaya via cfe-commits


@@ -538,11 +564,205 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return llvm::Error::success();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
+}
+
+std::vector findAllSelectorPieces(llvm::ArrayRef Tokens,
+ const SourceManager &SM, Selector Sel,
+ tok::TokenKind Terminator) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  std::vector SelectorPieces;
+  unsigned Last = Tokens.size() - 1;
+  for (unsigned Index = 0; Index < Last; ++Index) {
+const auto &Tok = Tokens[Index];
+
+if (Closes.empty()) {
+  auto PieceCount = SelectorPieces.size();
+  if (PieceCount < NumArgs &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Sel.getNameForSlot(PieceCount))) {
+// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
+// token after the name.
+if (!Sel.getNameForSlot(PieceCount).empty()) {
+  ++Index;
+}
+SelectorPieces.push_back(
+halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces but the current token looks like another
+  // selector piece, it means the method being renamed is a strict prefix 
of
+  // the selector we've found - should be skipped.
+  if (SelectorPieces.size() >= NumArgs &&
+  isSelectorLike(Tok, Tokens[Index + 1]))
+return std::vector();
+}
+
+if (Tok.kind() == Terminator)
+  return SelectorPieces.size() == NumArgs ? SelectorPieces
+  : std::vector();
+
+switch (Tok.kind()) {
+case tok::l_square:
+  Closes.push_back(tok::r_square);
+  break;
+case tok::l_paren:
+  Closes.push_back(tok::r_paren);
+  break;
+case tok::l_brace:
+  Closes.push_back(tok::r_brace);
+  break;
+case tok::r_square:
+case tok::r_paren:
+case tok::r_brace:
+  if (Closes.empty() || Closes.back() != Tok.kind())
+return std::vector();
+  Closes.pop_back();
+  break;
+case tok::semi:
+  // top level ; terminates all statements.
+  if (Closes.empty())
+return SelectorPieces.size() == NumArgs ? SelectorPieces
+: std::vector();
+  break;
+default:
+  break;
+}
+  }
+  return std::vector();
+}
+
+/// 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 collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions &LangOpts, std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto &R : IdentifierRanges)
+  Ranges.emplace_back(R);
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto &SM = FileSM.get();
+
+  // We track parens and brackets to ensure that we don't accidentally try
+  // parsing a method declaration or definition which isn't at the top level or
+  // similar looking expressions (e.g. an @selector() expression).
+  llvm::SmallVector Closes;
+  llvm::StringRef FirstSelPiece = Selector->getNameForSlot(0);
+
+  std::vector SelectorPieces;
+  std::vector MsgExprPieces;
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+  for (unsigned Index = 0; Index < Last; ++Index) {
+const auto &Tok = Tokens[Index];
+
+// Search for the first selector piece to begin a match, but make sure 
we're
+// not in () to avoid the @selector(foo:bar:) case.
+if ((Closes.empty() || Closes.back() == tok::r_square) 

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-08 Thread kadir çetinkaya via cfe-commits


@@ -538,11 +564,205 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return llvm::Error::success();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
+}
+
+std::vector findAllSelectorPieces(llvm::ArrayRef Tokens,

kadircet wrote:

can we just return a `std::optional` here?

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-08 Thread kadir çetinkaya via cfe-commits


@@ -538,11 +564,205 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return llvm::Error::success();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
+}
+
+std::vector findAllSelectorPieces(llvm::ArrayRef Tokens,

kadircet wrote:

can you add some comments describing parameters and what the function does?

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-08 Thread David Goldman via cfe-commits


@@ -538,11 +564,205 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
 }
   }
-  if (Result)
+  if (Result) {
 InvalidNameMetric.record(1, toString(Result->K));
+return makeError(*Result);
+  }
+  return llvm::Error::success();
+}
+
+bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
+  return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
+ // We require the selector name and : to be contiguous.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token 
&Next,
+const SourceManager &SM,
+llvm::StringRef SelectorName) {
+  if (SelectorName.empty())
+return Cur.kind() == tok::colon;
+  return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
+}
+
+std::vector findAllSelectorPieces(llvm::ArrayRef Tokens,

DavidGoldman wrote:

Done

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-08 Thread David Goldman via cfe-commits


@@ -53,13 +55,34 @@ struct RenameInputs {
 struct RenameResult {
   // The range of the symbol that the user can attempt to rename.
   Range Target;
+  // Placeholder text for the rename operation if non-empty.
+  std::string Placeholder;
   // Rename occurrences for the current main file.
   std::vector LocalChanges;
   // Complete edits for the rename, including LocalChanges.
   // If the full set of changes is unknown, this field is empty.
   FileEdits GlobalChanges;
 };
 
+/// Represents a symbol range where the symbol can potentially have multiple
+/// tokens.
+struct SymbolRange {
+  /// Ranges for the tokens that make up the symbol's name.
+  /// Usually a single range, but there can be multiple ranges if the tokens 
for
+  /// the symbol are split, e.g. ObjC selectors.
+  std::vector Ranges;

DavidGoldman wrote:

That makes sense, I think we might need to improve the empty selector case - 
I'll do that in a follow up.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-08 Thread David Goldman via cfe-commits


@@ -569,8 +840,13 @@ renameWithinFile(ParsedAST &AST, const NamedDecl 
&RenameDecl,
 //   }
 if (!isInsideMainFile(RenameLoc, SM))
   continue;
+Locs.push_back(RenameLoc);
+  }
+  if (const auto *MD = dyn_cast(&RenameDecl))
+return renameObjCMethodWithinFile(AST, MD, NewName, std::move(Locs));

DavidGoldman wrote:

SGTM, I'll look into this in a follow up (+ add some tests for that).

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-02-08 Thread David Goldman via cfe-commits

DavidGoldman wrote:

Thanks, PTAL, I'll save the remaining comments for follow ups.

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-23 Thread David Goldman via cfe-commits

https://github.com/DavidGoldman updated 
https://github.com/llvm/llvm-project/pull/76466

>From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001
From: David Goldman 
Date: Tue, 26 Dec 2023 15:59:01 -0500
Subject: [PATCH 1/4] [clangd][SymbolCollector] Treat ObjC methods as spelled

We'll treat multi-arg methods as spelled once we have full rename
support for them.
---
 .../clangd/index/SymbolCollector.cpp  |  6 ++-
 .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++
 2 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp 
b/clang-tools-extra/clangd/index/SymbolCollector.cpp
index 7ef4b15febad22f..336bc3506bb3608 100644
--- a/clang-tools-extra/clangd/index/SymbolCollector.cpp
+++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp
@@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
   auto Name = ND.getDeclName();
   const auto NameKind = Name.getNameKind();
   if (NameKind != DeclarationName::Identifier &&
-  NameKind != DeclarationName::CXXConstructorName)
+  NameKind != DeclarationName::CXXConstructorName &&
+  NameKind != DeclarationName::ObjCZeroArgSelector &&
+  NameKind != DeclarationName::ObjCOneArgSelector)
 return false;
   const auto &AST = ND.getASTContext();
   const auto &SM = AST.getSourceManager();
@@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
   if (clang::Lexer::getRawToken(Loc, Tok, SM, LO))
 return false;
   auto StrName = Name.getAsString();
+  if (const auto *MD = dyn_cast(&ND))
+StrName = MD->getSelector().getNameForSlot(0).str();
   return clang::Lexer::getSpelling(Tok, SM, LO) == StrName;
 }
 } // namespace
diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp 
b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 9cdc57ec01f3276..1d4e1c1d75ea230 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -105,6 +105,9 @@ MATCHER(refRange, "") {
 MATCHER_P2(OverriddenBy, Subject, Object, "") {
   return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID};
 }
+MATCHER(isSpelled, "") {
+  return static_cast(arg.Kind & RefKind::Spelled);
+}
 ::testing::Matcher &>
 haveRanges(const std::vector Ranges) {
   return ::testing::UnorderedPointwise(refRange(), Ranges);
@@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) {
  forCodeCompletion(false);
 }
 
+TEST_F(SymbolCollectorTest, ObjCRefs) {
+  Annotations Header(R"(
+  @interface Person
+  - (void)$talk[[talk]];
+  - (void)$say[[say]]:(id)something;
+  @end
+  @interface Person (Category)
+  - (void)categoryMethod;
+  - (void)multiArg:(id)a method:(id)b;
+  @end
+  )");
+  Annotations Main(R"(
+  @implementation Person
+  - (void)$talk[[talk]] {}
+  - (void)$say[[say]]:(id)something {}
+  @end
+
+  void fff(Person *p) {
+[p $talk[[talk]]];
+[p $say[[say]]:0];
+[p categoryMethod];
+[p multiArg:0 method:0];
+  }
+  )");
+  CollectorOpts.RefFilter = RefKind::All;
+  CollectorOpts.CollectMainFileRefs = true;
+  TestFileName = testPath("test.m");
+  runSymbolCollector(Header.code(), Main.code(),
+ {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"});
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID,
+  haveRanges(Main.ranges("talk");
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID,
+  haveRanges(Main.ranges("say");
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, 
"Person::categoryMethod").ID,
+  ElementsAre(isSpelled();
+  EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, 
"Person::multiArg:method:").ID,
+  ElementsAre(Not(isSpelled());
+}
+
 TEST_F(SymbolCollectorTest, ObjCSymbols) {
   const std::string Header = R"(
 @interface Person

>From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001
From: David Goldman 
Date: Tue, 26 Dec 2023 16:12:03 -0500
Subject: [PATCH 2/4] Run clang-format

---
 .../clangd/unittests/SymbolCollectorTests.cpp  | 10 ++
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp 
b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 1d4e1c1d75ea230..5c20b950e4eac0d 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) {
   haveRanges(Main.ranges("talk");
   EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID,
   haveRanges(Main.ranges("say");
-  EXPECT_THAT(Refs, Contains(Pair(find

[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-23 Thread David Goldman via cfe-commits


@@ -893,22 +964,36 @@ llvm::Expected buildRenameEdit(llvm::StringRef 
AbsFilePath,
 return LastOffset;
   };
 
-  std::vector> OccurrencesOffsets;
-  for (const auto &R : Occurrences) {
-auto StartOffset = Offset(R.start);
-if (!StartOffset)
-  return StartOffset.takeError();
-auto EndOffset = Offset(R.end);
-if (!EndOffset)
-  return EndOffset.takeError();
-OccurrencesOffsets.push_back({*StartOffset, *EndOffset});
+  struct OccurrenceOffset {
+size_t Start;
+size_t End;
+llvm::StringRef NewName;
+
+OccurrenceOffset(size_t Start, size_t End, llvm::StringRef NewName) :
+  Start(Start), End(End), NewName(NewName) {}
+  };
+
+  std::vector OccurrencesOffsets;
+  for (const auto &SR : Occurrences) {
+for (auto It = SR.Ranges.begin(); It != SR.Ranges.end(); ++It) {

DavidGoldman wrote:

Done

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-24 Thread kadir çetinkaya via cfe-commits


@@ -681,12 +718,26 @@ renameOutsideFile(const NamedDecl &RenameDecl, 
llvm::StringRef MainFilePath,
ExpBuffer.getError().message());
   continue;
 }
+std::string RenameIdentifier = RenameDecl.getNameAsString();
+std::optional Selector = std::nullopt;
+llvm::SmallVector NewNames;
+if (const auto *MD = dyn_cast(&RenameDecl)) {
+  if (MD->getSelector().getNumArgs() > 1) {
+RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
+Selector = MD->getSelector();
+NewName.split(NewNames, ":");

kadircet wrote:

you can perform the split unconditionally ,rather than pushing to `NewNames` 
below.

also is it possible to have "empty" selector segments? (e.g. is a NewName like 
`foo::bar` valid for a selector with 3 segments? if not we should probably 
raise an error when validating)

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-24 Thread kadir çetinkaya via cfe-commits


@@ -543,6 +550,45 @@ std::optional checkName(const NamedDecl 
&RenameDecl,
   return Result;
 }
 
+clangd::Range tokenRangeForLoc(ParsedAST &AST, SourceLocation TokLoc,
+   const SourceManager &SM,
+   const LangOptions &LangOpts) {
+  const auto *Token = AST.getTokens().spelledTokenAt(TokLoc);
+  assert(Token && "got inclusion at wrong offset");
+  clangd::Range Result;
+  Result.start = sourceLocToPosition(SM, Token->location());
+  Result.end = sourceLocToPosition(SM, Token->endLocation());
+  return Result;
+}
+
+// AST-based ObjC method rename, it renames all occurrences in the main file
+// even for selectors which may have multiple tokens.
+llvm::Expected
+renameObjCMethodWithinFile(ParsedAST &AST, const ObjCMethodDecl *MD,
+   llvm::StringRef NewName,
+   std::vector Locs) {
+  const SourceManager &SM = AST.getSourceManager();
+  auto Code = SM.getBufferData(SM.getMainFileID());
+  auto RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
+  llvm::SmallVector NewNames;
+  NewName.split(NewNames, ":");
+  if (NewNames.empty())
+NewNames.push_back(NewName);
+
+  std::vector Ranges;
+  const auto &LangOpts = MD->getASTContext().getLangOpts();
+  for (const auto &Loc : Locs)
+Ranges.push_back(tokenRangeForLoc(AST, Loc, SM, LangOpts));
+  auto FilePath = AST.tuPath();
+  auto RenameRanges = collectRenameIdentifierRanges(

kadircet wrote:

hmm we don't re-use the `Ranges`/`Locs` here at all. let's strip it all (or use 
it to guide lexing, instead of searching for selector identifier, just check if 
selectors at these locations are what we need ?)

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-24 Thread kadir çetinkaya via cfe-commits


@@ -817,7 +939,8 @@ llvm::Expected rename(const RenameInputs 
&RInputs) {
 return StartOffset.takeError();
   if (!EndOffset)
 return EndOffset.takeError();
-  if (llvm::none_of(
+  if (RenamingCurToken &&

kadircet wrote:

as mentioned above, let's just check with 
`shouldRenameTriggeringLocation(RenameDecl)`

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-24 Thread kadir çetinkaya via cfe-commits


@@ -778,12 +868,44 @@ llvm::Expected rename(const RenameInputs 
&RInputs) {
 return makeError(ReasonToReject::NoSymbolFound);
   if (DeclsUnderCursor.size() > 1)
 return makeError(ReasonToReject::AmbiguousSymbol);
+  std::string Placeholder;
+  // We expect the token under the cursor to be changed unless the user is
+  // renaming an Objective-C selector with multiple pieces and only renames
+  // some of the selector piece(s).
+  bool RenamingCurToken = true;
   const auto &RenameDecl = **DeclsUnderCursor.begin();
-  const auto *ID = RenameDecl.getIdentifier();
-  if (!ID)
-return makeError(ReasonToReject::UnsupportedSymbol);
-  if (ID->getName() == RInputs.NewName)
-return makeError(ReasonToReject::SameName);
+  if (const auto *MD = dyn_cast(&RenameDecl)) {
+const auto Sel = MD->getSelector();
+if (Sel.getAsString() == RInputs.NewName)
+  return makeError(ReasonToReject::SameName);
+if (Sel.getNumArgs() != RInputs.NewName.count(':') &&
+RInputs.NewName != "__clangd_rename_placeholder")
+  return makeError(
+  InvalidName{InvalidName::BadIdentifier, RInputs.NewName.str()});
+if (Sel.getNumArgs() > 1)
+  Placeholder = Sel.getAsString();
+
+// See if the token under the cursor should actually be renamed.
+if (RInputs.NewName != "__clangd_rename_placeholder") {
+  llvm::StringRef NewName = RInputs.NewName;
+  llvm::SmallVector NewNames;
+  NewName.split(NewNames, ":");
+
+  unsigned NumSelectorLocs = MD->getNumSelectorLocs();
+  for (unsigned I = 0; I < NumSelectorLocs; ++I) {
+if (MD->getSelectorLoc(I) == IdentifierToken->location()) {
+  RenamingCurToken = Sel.getNameForSlot(I) != NewNames[I];

kadircet wrote:

i don't think it's worth all this trouble to leave things in a semi-working 
state. we might rely on `CurrentIdentifier` or `IdentifierRange` in other 
places, and until we change this logic to actually amend the identifer, it's 
easier to assume that it'll always be invalid for objc.

let's leave a TODO to figure out the relevant selector range using 
`ObjCMessageExpr` under the cursor and change the safety check to not happen 
for ObjCMethodDecls for now?

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-24 Thread kadir çetinkaya via cfe-commits


@@ -1017,9 +1159,10 @@ size_t renameRangeAdjustmentCost(ArrayRef 
Indexed, ArrayRef Lexed,
   int LastDLine = 0, LastDColumn = 0;
   int Cost = 0;
   for (size_t I = 0; I < Indexed.size(); ++I) {
-int DLine = Indexed[I].start.line - Lexed[MappedIndex[I]].start.line;
-int DColumn =
-Indexed[I].start.character - Lexed[MappedIndex[I]].start.character;
+int DLine =
+Indexed[I].start.line - Lexed[MappedIndex[I]].range().start.line;
+int DColumn = Indexed[I].start.character -
+  Lexed[MappedIndex[I]].range().start.character;

kadircet wrote:

looks like just formatting change, can you revert?

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)

2024-01-24 Thread kadir çetinkaya via cfe-commits


@@ -746,6 +812,30 @@ void findNearMiss(
 
 } // namespace
 
+SymbolRange::SymbolRange(Range R) : Ranges({R}) {}
+
+SymbolRange::SymbolRange(std::vector Ranges)
+: Ranges(std::move(Ranges)) {}
+
+Range SymbolRange::range() const { return Ranges.front(); }
+
+bool operator==(const SymbolRange &LHS, const SymbolRange &RHS) {
+  return LHS.Ranges == RHS.Ranges;
+}
+bool operator!=(const SymbolRange &LHS, const SymbolRange &RHS) {
+  return !(LHS == RHS);
+}
+bool operator<(const SymbolRange &LHS, const SymbolRange &RHS) {
+  return LHS.range() < RHS.range();
+}
+
+std::vector symbolRanges(const std::vector Ranges) {

kadircet wrote:

can we take in `llvm::ArrayRef Ranges`, also move it to renametests, i 
don't think it's used elswhere

https://github.com/llvm/llvm-project/pull/76466
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


  1   2   >