[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-14 Thread kadir çetinkaya via cfe-commits


@@ -538,11 +564,222 @@ std::optional checkName(const NamedDecl 
,
 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 , const syntax::Token ) {
+  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 , const syntax::Token 
,
+const SourceManager ,
+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 , 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-14 Thread kadir çetinkaya via cfe-commits


@@ -538,11 +564,222 @@ std::optional checkName(const NamedDecl 
,
 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 , const syntax::Token ) {
+  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 , const syntax::Token 
,
+const SourceManager ,
+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 , 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  = 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


@@ -696,7 +982,7 @@ renameOutsideFile(const NamedDecl , 
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 
,
 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 , const syntax::Token ) {
+  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 , const syntax::Token 
,
+const SourceManager ,
+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 , 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

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 
,
 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 , const syntax::Token ) {
+  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 , const syntax::Token 
,
+const SourceManager ,
+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 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 
,
 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 , const syntax::Token ) {
+  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-12 Thread Alex Hoppen via cfe-commits


@@ -538,11 +564,222 @@ std::optional checkName(const NamedDecl 
,
 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 , const syntax::Token ) {
+  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 , const syntax::Token 
,
+const SourceManager ,
+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 , 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  = 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 , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto  : 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 
,
 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 , const syntax::Token ) {
+  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 , const syntax::Token 
,
+const SourceManager ,
+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

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

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-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-02-08 Thread David Goldman via cfe-commits


@@ -569,8 +840,13 @@ renameWithinFile(ParsedAST , const NamedDecl 
,
 //   }
 if (!isInsideMainFile(RenameLoc, SM))
   continue;
+Locs.push_back(RenameLoc);
+  }
+  if (const auto *MD = dyn_cast())
+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


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


@@ -538,11 +564,205 @@ std::optional checkName(const NamedDecl 
,
 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 , const syntax::Token ) {
+  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 , const syntax::Token 
,
+const SourceManager ,
+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 kadir çetinkaya via cfe-commits


@@ -538,11 +564,205 @@ std::optional checkName(const NamedDecl 
,
 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 , const syntax::Token ) {
+  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 , const syntax::Token 
,
+const SourceManager ,
+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 kadir çetinkaya via cfe-commits


@@ -538,11 +564,205 @@ std::optional checkName(const NamedDecl 
,
 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 , const syntax::Token ) {
+  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 , const syntax::Token 
,
+const SourceManager ,
+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 
,
 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 , const syntax::Token ) {
+  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 , const syntax::Token 
,
+const SourceManager ,
+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 , 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  = 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 , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto  : 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  = 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  = 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) &&
+isMatchingSelectorName(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


@@ -681,12 +957,22 @@ renameOutsideFile(const NamedDecl , 
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()) {

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 
,
 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 , const syntax::Token ) {
+  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 , const syntax::Token 
,
+const SourceManager ,
+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 , 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  = 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 , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto  : 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  = 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


@@ -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 ) {
+  if (const auto *MD = dyn_cast())
+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 ,
- llvm::StringRef NewName) {
+std::optional checkName(const NamedDecl ,
+ 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()) {
+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


@@ -696,7 +982,7 @@ renameOutsideFile(const NamedDecl , 
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


@@ -538,11 +564,205 @@ std::optional checkName(const NamedDecl 
,
 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 , const syntax::Token ) {
+  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 , const syntax::Token 
,
+const SourceManager ,
+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 , 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  = 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 , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto  : 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  = 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  = 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) &&
+isMatchingSelectorName(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

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 
,
 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 , const syntax::Token ) {
+  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 , const syntax::Token 
,
+const SourceManager ,
+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 , 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  = 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


@@ -538,11 +564,205 @@ std::optional checkName(const NamedDecl 
,
 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 , const syntax::Token ) {
+  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 , const syntax::Token 
,
+const SourceManager ,
+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 , 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  = 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

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 
,
 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 , const syntax::Token ) {
+  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 , const syntax::Token 
,
+const SourceManager ,
+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-07 Thread David Goldman via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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 , const syntax::Token ) {
+  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 , unsigned Index,
+unsigned Last, Selector Sel,
+std::vector ) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto  = 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 , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto  : 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  = 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  = Tokens[Index];
+

DavidGoldman wrote:

Done. This lexer logic runs independent of those 

[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 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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 , const syntax::Token ) {
+  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 , unsigned Index,
+unsigned Last, Selector Sel,
+std::vector ) {
+
+  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




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/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 ) {
   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  = ND.getASTContext();
   const auto  = AST.getSourceManager();
@@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) {
   if (clang::Lexer::getRawToken(Loc, Tok, SM, LO))
 return false;
   auto StrName = Name.getAsString();
+  if (const auto *MD = dyn_cast())
+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(Symbols, 

[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 ) {
   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  = ND.getASTContext();
   const auto  = AST.getSourceManager();
@@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) {
   if (clang::Lexer::getRawToken(Loc, Tok, SM, LO))
 return false;
   auto StrName = Name.getAsString();
+  if (const auto *MD = dyn_cast())
+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(Symbols, 

[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 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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-05 Thread Alex Hoppen via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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 , const syntax::Token ) {
+  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 , unsigned Index,
+unsigned Last, Selector Sel,
+std::vector ) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto  = 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 , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto  : 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  = 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 `collectRenameIdentifierRanges` at the moment, one of which already 
has the tokens and one 

[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 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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 , const syntax::Token ) {
+  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 , unsigned Index,
+unsigned Last, Selector Sel,
+std::vector ) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto  = 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 , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto  : 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  = 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  = Tokens[Index];
+
+if (BraceCount == 0 && ParenCount == 0) {
+  auto PieceCount = 

[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 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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 , const syntax::Token ) {
+  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 , unsigned Index,
+unsigned Last, Selector Sel,
+std::vector ) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto  = 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 , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto  : 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  = 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  = Tokens[Index];
+

ahoppen wrote:

But shouldn’t we know based on the index data that `- 

[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 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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 , const syntax::Token ) {
+  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 , unsigned Index,
+unsigned Last, Selector Sel,
+std::vector ) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto  = 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 , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto  : 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  = 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 the caller. I could pass an optional Array ref 
of tokens to use to 

[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 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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 , const syntax::Token ) {
+  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 , unsigned Index,
+unsigned Last, Selector Sel,
+std::vector ) {

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 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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 , const syntax::Token ) {
+  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 , unsigned Index,
+unsigned Last, Selector Sel,
+std::vector ) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto  = 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 , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto  : 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  = 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  = Tokens[Index];
+
+if (BraceCount == 0 && ParenCount == 0) {
+  auto PieceCount = 

[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 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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 , const syntax::Token ) {
+  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 , unsigned Index,
+unsigned Last, Selector Sel,
+std::vector ) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto  = 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 , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto  : 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  = 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  = Tokens[Index];
+

DavidGoldman wrote:

The main edge case I mentioned is having one 

[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 ) {
+  if (const auto *MD = dyn_cast())
+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 ,
- llvm::StringRef NewName) {
+std::optional checkName(const NamedDecl ,

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-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-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 Alex Hoppen via cfe-commits


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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 , const syntax::Token ) {
+  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 , unsigned Index,
+unsigned Last, Selector Sel,
+std::vector ) {
+
+  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


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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


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


@@ -569,8 +840,13 @@ renameWithinFile(ParsedAST , const NamedDecl 
,
 //   }
 if (!isInsideMainFile(RenameLoc, SM))
   continue;
+Locs.push_back(RenameLoc);
+  }
+  if (const auto *MD = dyn_cast())
+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 == ':')
+  continue;
 if (llvm::isASCII(C) && !isAsciiIdentifierContinue(C, AllowDollar))
   return false;
   }
   return true;
 }
 
+std::string getName(const NamedDecl ) {
+  if (const auto *MD = dyn_cast())
+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 ,
- llvm::StringRef NewName) {
+std::optional checkName(const NamedDecl ,
+ 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()) {
+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


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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 , const syntax::Token ) {
+  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 , unsigned Index,
+unsigned Last, Selector Sel,
+std::vector ) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto  = 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 , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto  : 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  = 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 we shouldn’t need to re-parse the file.


[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 ) {
+  if (const auto *MD = dyn_cast())
+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 ,
- llvm::StringRef NewName) {
+std::optional checkName(const NamedDecl ,
+ 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()) {
+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


@@ -681,12 +957,22 @@ renameOutsideFile(const NamedDecl , 
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




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 , 
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()) {

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


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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 , const syntax::Token ) {
+  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


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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 , const syntax::Token ) {
+  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 , unsigned Index,
+unsigned Last, Selector Sel,
+std::vector ) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto  = 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 , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto  : 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  = 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  = Tokens[Index];
+
+if (BraceCount == 0 && ParenCount == 0) {
+  auto PieceCount = 

[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 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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 , const syntax::Token ) {
+  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 , unsigned Index,
+unsigned Last, Selector Sel,
+std::vector ) {

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 kadir çetinkaya via cfe-commits


@@ -696,7 +982,7 @@ renameOutsideFile(const NamedDecl , 
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


@@ -543,6 +550,45 @@ std::optional checkName(const NamedDecl 
,
   return Result;
 }
 
+clangd::Range tokenRangeForLoc(ParsedAST , SourceLocation TokLoc,
+   const SourceManager ,
+   const LangOptions ) {
+  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 , const ObjCMethodDecl *MD,
+   llvm::StringRef NewName,
+   std::vector Locs) {
+  const SourceManager  = 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  = MD->getASTContext().getLangOpts();
+  for (const auto  : 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


@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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 , const syntax::Token ) {
+  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 , unsigned Index,
+unsigned Last, Selector Sel,
+std::vector ) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto  = 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 , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto  : 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  = 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  = Tokens[Index];
+

kadircet wrote:

Sorry, i am still having a lot of trouble following 

[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 , 
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()) {

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 
,
 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 , const syntax::Token 
,
+const SourceManager ,
+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 , const syntax::Token ) {
+  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 , unsigned Index,
+unsigned Last, Selector Sel,
+std::vector ) {
+
+  unsigned NumArgs = Sel.getNumArgs();
+  llvm::SmallVector Closes;
+  SelectorPieces.clear();
+  while (Index < Last) {
+const auto  = 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 , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+auto IdentifierRanges =
+collectIdentifierRanges(Identifier, Content, LangOpts);
+for (const auto  : 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  = 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  = Tokens[Index];
+

kadircet wrote:

this might be missing your concerns around having 

[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 ) {
+  if (const auto *MD = dyn_cast())
+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 ,
- llvm::StringRef NewName) {
+std::optional checkName(const NamedDecl ,

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-01-25 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 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 ) {
   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  = ND.getASTContext();
   const auto  = AST.getSourceManager();
@@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) {
   if (clang::Lexer::getRawToken(Loc, Tok, SM, LO))
 return false;
   auto StrName = Name.getAsString();
+  if (const auto *MD = dyn_cast())
+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/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 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(findSymbol(Symbols, 

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

2024-01-25 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 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 ) {
   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  = ND.getASTContext();
   const auto  = AST.getSourceManager();
@@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) {
   if (clang::Lexer::getRawToken(Loc, Tok, SM, LO))
 return false;
   auto StrName = Name.getAsString();
+  if (const auto *MD = dyn_cast())
+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/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 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(findSymbol(Symbols, 

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

2024-01-25 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 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 ) {
   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  = ND.getASTContext();
   const auto  = AST.getSourceManager();
@@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) {
   if (clang::Lexer::getRawToken(Loc, Tok, SM, LO))
 return false;
   auto StrName = Name.getAsString();
+  if (const auto *MD = dyn_cast())
+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/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 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(findSymbol(Symbols, 

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

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


@@ -38,6 +38,11 @@
 
 namespace clang {
 namespace clangd {
+
+std::vector collectRenameIdentifierRanges(

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-25 Thread David Goldman via cfe-commits


@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef 
Indexed, ArrayRef Lexed,
   return Cost;
 }
 
+static bool isMatchingSelectorName(const syntax::Token ,
+   const syntax::Token ,
+   const SourceManager ,
+   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.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+static bool isSelectorLike(const syntax::Token ,
+   const syntax::Token ) {
+  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();
+}
+
+static void
+lex(llvm::StringRef Code, const LangOptions ,
+llvm::function_ref
+Action) {
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Code.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+  for (const auto  : syntax::tokenize(SM.getMainFileID(), SM, LangOpts))
+Action(Tok, SM);
+}
+
+std::vector collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+lex(Content, LangOpts,
+[&](const syntax::Token , const SourceManager ) {
+  if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier)
+return;
+  Ranges.emplace_back(
+  halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+});
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+
+  // One parser state for top level and each `[]` pair, can be nested.
+  // Technically we should have a state or recursion for each ()/{} as well,
+  // but since we're expecting well formed code it shouldn't matter in 
practice.
+  struct ParserState {
+unsigned ParenCount = 0;
+unsigned BraceCount = 0;
+std::vector Pieces;
+  };
+
+  // We have to track square brackets, parens and braces as we want to skip the
+  // tokens inside them. This ensures that we don't use identical selector
+  // pieces in inner message sends, blocks, lambdas and @selector expressions.
+  std::vector States = {ParserState()};
+  unsigned NumPieces = Selector->getNumArgs();
+
+  for (unsigned Index = 0; Index < Last; ++Index) {

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-25 Thread David Goldman via cfe-commits


@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef 
Indexed, ArrayRef Lexed,
   return Cost;
 }
 
+static bool isMatchingSelectorName(const syntax::Token ,
+   const syntax::Token ,
+   const SourceManager ,
+   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.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+static bool isSelectorLike(const syntax::Token ,
+   const syntax::Token ) {
+  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();
+}
+
+static void
+lex(llvm::StringRef Code, const LangOptions ,
+llvm::function_ref
+Action) {
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Code.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+  for (const auto  : syntax::tokenize(SM.getMainFileID(), SM, LangOpts))
+Action(Tok, SM);
+}
+
+std::vector collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+lex(Content, LangOpts,
+[&](const syntax::Token , const SourceManager ) {
+  if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier)
+return;
+  Ranges.emplace_back(
+  halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+});
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+
+  // One parser state for top level and each `[]` pair, can be nested.
+  // Technically we should have a state or recursion for each ()/{} as well,
+  // but since we're expecting well formed code it shouldn't matter in 
practice.

DavidGoldman wrote:

Removed

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-25 Thread David Goldman via cfe-commits


@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef 
Indexed, ArrayRef Lexed,
   return Cost;
 }
 
+static bool isMatchingSelectorName(const syntax::Token ,
+   const syntax::Token ,
+   const SourceManager ,
+   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.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+static bool isSelectorLike(const syntax::Token ,
+   const syntax::Token ) {
+  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();
+}
+
+static void
+lex(llvm::StringRef Code, const LangOptions ,
+llvm::function_ref
+Action) {
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Code.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+  for (const auto  : syntax::tokenize(SM.getMainFileID(), SM, LangOpts))
+Action(Tok, SM);
+}
+
+std::vector collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+lex(Content, LangOpts,
+[&](const syntax::Token , const SourceManager ) {
+  if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier)
+return;
+  Ranges.emplace_back(
+  halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+});
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+
+  // One parser state for top level and each `[]` pair, can be nested.
+  // Technically we should have a state or recursion for each ()/{} as well,
+  // but since we're expecting well formed code it shouldn't matter in 
practice.
+  struct ParserState {

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-25 Thread David Goldman via cfe-commits


@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef 
Indexed, ArrayRef Lexed,
   return Cost;
 }
 
+static bool isMatchingSelectorName(const syntax::Token ,
+   const syntax::Token ,
+   const SourceManager ,
+   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.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+static bool isSelectorLike(const syntax::Token ,
+   const syntax::Token ) {
+  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();
+}
+
+static void
+lex(llvm::StringRef Code, const LangOptions ,
+llvm::function_ref
+Action) {
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Code.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+  for (const auto  : syntax::tokenize(SM.getMainFileID(), SM, LangOpts))
+Action(Tok, SM);
+}
+
+std::vector collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+lex(Content, LangOpts,
+[&](const syntax::Token , const SourceManager ) {
+  if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier)
+return;
+  Ranges.emplace_back(
+  halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+});
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+
+  // One parser state for top level and each `[]` pair, can be nested.
+  // Technically we should have a state or recursion for each ()/{} as well,
+  // but since we're expecting well formed code it shouldn't matter in 
practice.
+  struct ParserState {
+unsigned ParenCount = 0;
+unsigned BraceCount = 0;
+std::vector Pieces;
+  };
+
+  // We have to track square brackets, parens and braces as we want to skip the
+  // tokens inside them. This ensures that we don't use identical selector
+  // pieces in inner message sends, blocks, lambdas and @selector expressions.
+  std::vector States = {ParserState()};
+  unsigned NumPieces = Selector->getNumArgs();
+
+  for (unsigned Index = 0; Index < Last; ++Index) {
+auto  = States.back();
+auto  = State.Pieces;
+const auto  = Tokens[Index];
+const auto Kind = Tok.kind();
+auto PieceCount = Pieces.size();
+
+if (State.ParenCount == 0) {
+  // Check for matches until we find all selector pieces.
+  if (PieceCount < NumPieces &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Selector->getNameForSlot(PieceCount))) {
+if (!Selector->getNameForSlot(PieceCount).empty()) {
+  // Skip the ':' after the name. This ensures that it won't match a
+  // follow-up selector piece with an empty name.
+  ++Index;
+}
+Pieces.push_back(halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces, we still need to try to consume more pieces
+  // as it's possible the selector being renamed is a prefix of this method
+  // name.

DavidGoldman wrote:

Updated, parseMessageExpression can return early but the top level parse can't.

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-25 Thread David Goldman via cfe-commits


@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef 
Indexed, ArrayRef Lexed,
   return Cost;
 }
 
+static bool isMatchingSelectorName(const syntax::Token ,
+   const syntax::Token ,
+   const SourceManager ,
+   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.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+static bool isSelectorLike(const syntax::Token ,
+   const syntax::Token ) {
+  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();
+}
+
+static void
+lex(llvm::StringRef Code, const LangOptions ,
+llvm::function_ref
+Action) {
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Code.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+  for (const auto  : syntax::tokenize(SM.getMainFileID(), SM, LangOpts))
+Action(Tok, SM);
+}
+
+std::vector collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+lex(Content, LangOpts,
+[&](const syntax::Token , const SourceManager ) {
+  if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier)
+return;
+  Ranges.emplace_back(
+  halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+});
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+
+  // One parser state for top level and each `[]` pair, can be nested.
+  // Technically we should have a state or recursion for each ()/{} as well,
+  // but since we're expecting well formed code it shouldn't matter in 
practice.
+  struct ParserState {
+unsigned ParenCount = 0;
+unsigned BraceCount = 0;
+std::vector Pieces;
+  };
+
+  // We have to track square brackets, parens and braces as we want to skip the
+  // tokens inside them. This ensures that we don't use identical selector
+  // pieces in inner message sends, blocks, lambdas and @selector expressions.
+  std::vector States = {ParserState()};
+  unsigned NumPieces = Selector->getNumArgs();
+
+  for (unsigned Index = 0; Index < Last; ++Index) {
+auto  = States.back();
+auto  = State.Pieces;
+const auto  = Tokens[Index];
+const auto Kind = Tok.kind();
+auto PieceCount = Pieces.size();
+
+if (State.ParenCount == 0) {
+  // Check for matches until we find all selector pieces.
+  if (PieceCount < NumPieces &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Selector->getNameForSlot(PieceCount))) {
+if (!Selector->getNameForSlot(PieceCount).empty()) {

DavidGoldman wrote:

Invalid or empty selector piece (foo::)

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-25 Thread David Goldman via cfe-commits


@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef 
Indexed, ArrayRef Lexed,
   return Cost;
 }
 
+static bool isMatchingSelectorName(const syntax::Token ,
+   const syntax::Token ,
+   const SourceManager ,
+   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.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+static bool isSelectorLike(const syntax::Token ,
+   const syntax::Token ) {
+  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();
+}
+
+static void
+lex(llvm::StringRef Code, const LangOptions ,
+llvm::function_ref
+Action) {
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Code.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+  for (const auto  : syntax::tokenize(SM.getMainFileID(), SM, LangOpts))
+Action(Tok, SM);
+}
+
+std::vector collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+lex(Content, LangOpts,
+[&](const syntax::Token , const SourceManager ) {
+  if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier)
+return;
+  Ranges.emplace_back(
+  halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+});
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+
+  // One parser state for top level and each `[]` pair, can be nested.
+  // Technically we should have a state or recursion for each ()/{} as well,
+  // but since we're expecting well formed code it shouldn't matter in 
practice.
+  struct ParserState {
+unsigned ParenCount = 0;
+unsigned BraceCount = 0;
+std::vector Pieces;
+  };
+
+  // We have to track square brackets, parens and braces as we want to skip the
+  // tokens inside them. This ensures that we don't use identical selector
+  // pieces in inner message sends, blocks, lambdas and @selector expressions.
+  std::vector States = {ParserState()};
+  unsigned NumPieces = Selector->getNumArgs();
+
+  for (unsigned Index = 0; Index < Last; ++Index) {
+auto  = States.back();
+auto  = State.Pieces;
+const auto  = Tokens[Index];
+const auto Kind = Tok.kind();
+auto PieceCount = Pieces.size();
+
+if (State.ParenCount == 0) {
+  // Check for matches until we find all selector pieces.
+  if (PieceCount < NumPieces &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,

DavidGoldman wrote:

It's not, Last = Len -1 and we go until < Last, so Len - 2.

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-25 Thread David Goldman via cfe-commits


@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef 
Indexed, ArrayRef Lexed,
   return Cost;
 }
 
+static bool isMatchingSelectorName(const syntax::Token ,
+   const syntax::Token ,
+   const SourceManager ,
+   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.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+static bool isSelectorLike(const syntax::Token ,
+   const syntax::Token ) {
+  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();
+}
+
+static void
+lex(llvm::StringRef Code, const LangOptions ,
+llvm::function_ref
+Action) {
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Code.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+  for (const auto  : syntax::tokenize(SM.getMainFileID(), SM, LangOpts))
+Action(Tok, SM);
+}
+
+std::vector collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+lex(Content, LangOpts,
+[&](const syntax::Token , const SourceManager ) {
+  if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier)
+return;
+  Ranges.emplace_back(
+  halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+});
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+
+  // One parser state for top level and each `[]` pair, can be nested.
+  // Technically we should have a state or recursion for each ()/{} as well,
+  // but since we're expecting well formed code it shouldn't matter in 
practice.
+  struct ParserState {
+unsigned ParenCount = 0;
+unsigned BraceCount = 0;
+std::vector Pieces;

DavidGoldman wrote:

Updated with a comment, selector pieces/ranges as well as ( and { count

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 David Goldman via cfe-commits


@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef 
Indexed, ArrayRef Lexed,
   return Cost;
 }
 
+static bool isMatchingSelectorName(const syntax::Token ,

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 David Goldman 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;

DavidGoldman wrote:

This was formatted because it was changed, .start -> .range().start

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 David Goldman via cfe-commits


@@ -1007,7 +1148,8 @@ std::optional> 
getMappedRanges(ArrayRef Indexed,
 //   diff[0]: line + 1  <- insert a line before edit 0.
 //   diff[1]: column + 1 <- remove a line between edits 0 and 1, and insert a
 //   character on edit 1.
-size_t renameRangeAdjustmentCost(ArrayRef Indexed, ArrayRef 
Lexed,
+size_t renameRangeAdjustmentCost(ArrayRef Indexed,
+ ArrayRef Lexed,

DavidGoldman wrote:

These are formatted now because Range -> SymbolRange

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 David Goldman via cfe-commits


@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef 
Indexed, ArrayRef Lexed,
   return Cost;
 }
 
+static bool isMatchingSelectorName(const syntax::Token ,
+   const syntax::Token ,
+   const SourceManager ,
+   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.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+static bool isSelectorLike(const syntax::Token ,
+   const syntax::Token ) {
+  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();
+}
+
+static void
+lex(llvm::StringRef Code, const LangOptions ,
+llvm::function_ref
+Action) {
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Code.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+  for (const auto  : syntax::tokenize(SM.getMainFileID(), SM, LangOpts))
+Action(Tok, SM);
+}
+
+std::vector collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+lex(Content, LangOpts,
+[&](const syntax::Token , const SourceManager ) {
+  if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier)
+return;
+  Ranges.emplace_back(
+  halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+});
+return Ranges;

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 David Goldman via cfe-commits


@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef 
Indexed, ArrayRef Lexed,
   return Cost;
 }
 
+static bool isMatchingSelectorName(const syntax::Token ,
+   const syntax::Token ,
+   const SourceManager ,
+   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.
+ // e.g. support `foo:` but not `foo :`.

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 David Goldman via cfe-commits


@@ -778,12 +868,44 @@ llvm::Expected rename(const RenameInputs 
) {
 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  = **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()) {
+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();

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 David Goldman via cfe-commits


@@ -778,12 +868,44 @@ llvm::Expected rename(const RenameInputs 
) {
 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  = **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()) {
+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];
+  break;
+}
+  }
+}
+  } else {
+const auto *ID = RenameDecl.getIdentifier();
+if (!ID)
+  return makeError(ReasonToReject::UnsupportedSymbol);
+if (ID->getName() == RInputs.NewName)
+  return makeError(ReasonToReject::SameName);

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 David Goldman via cfe-commits


@@ -778,12 +868,44 @@ llvm::Expected rename(const RenameInputs 
) {
 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  = **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()) {
+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];
+  break;
+}
+  }
+}
+  } else {
+const auto *ID = RenameDecl.getIdentifier();
+if (!ID)
+  return makeError(ReasonToReject::UnsupportedSymbol);

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 David Goldman via cfe-commits


@@ -778,12 +868,44 @@ llvm::Expected rename(const RenameInputs 
) {
 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  = **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()) {
+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()});

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 David Goldman via cfe-commits


@@ -778,12 +868,44 @@ llvm::Expected rename(const RenameInputs 
) {
 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  = **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()) {
+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()});

DavidGoldman wrote:

Never mind, didn't see your comments below

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 David Goldman via cfe-commits


@@ -778,12 +868,44 @@ llvm::Expected rename(const RenameInputs 
) {
 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  = **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()) {
+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()});

DavidGoldman wrote:

Are you sure? I kept this separate since the else statement below performs 
similar logic outside of checkName. Should I move both into checkName?

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 David Goldman 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 , const SymbolRange ) {
+  return LHS.Ranges == RHS.Ranges;
+}
+bool operator!=(const SymbolRange , const SymbolRange ) {
+  return !(LHS == RHS);
+}
+bool operator<(const SymbolRange , const SymbolRange ) {
+  return LHS.range() < RHS.range();
+}
+
+std::vector symbolRanges(const std::vector Ranges) {

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 David Goldman via cfe-commits


@@ -543,6 +550,45 @@ std::optional checkName(const NamedDecl 
,
   return Result;
 }
 
+clangd::Range tokenRangeForLoc(ParsedAST , SourceLocation TokLoc,
+   const SourceManager ,
+   const LangOptions ) {
+  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 , const ObjCMethodDecl *MD,
+   llvm::StringRef NewName,
+   std::vector Locs) {
+  const SourceManager  = 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  = MD->getASTContext().getLangOpts();
+  for (const auto  : Locs)
+Ranges.push_back(tokenRangeForLoc(AST, Loc, SM, LangOpts));
+  auto FilePath = AST.tuPath();
+  auto RenameRanges = collectRenameIdentifierRanges(

DavidGoldman wrote:

Hmm, what do you think? we could call adjustRenameRanges to make sure what 
we've lexed matches. It's a bit more complex to guide lexing since we need to 
differentiate between method decls/exprs but maybe that could work. Just not 
sure it's worth adding much on top of this logic - we originally had a purely 
AST-visitor based approach but I'd prefer not to add more complexity 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-01-24 Thread David Goldman via cfe-commits


@@ -543,6 +550,45 @@ std::optional checkName(const NamedDecl 
,
   return Result;
 }
 
+clangd::Range tokenRangeForLoc(ParsedAST , SourceLocation TokLoc,
+   const SourceManager ,
+   const LangOptions ) {
+  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.

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 David Goldman via cfe-commits


@@ -543,6 +550,45 @@ std::optional checkName(const NamedDecl 
,
   return Result;
 }
 
+clangd::Range tokenRangeForLoc(ParsedAST , SourceLocation TokLoc,
+   const SourceManager ,
+   const LangOptions ) {
+  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 , const ObjCMethodDecl *MD,
+   llvm::StringRef NewName,
+   std::vector Locs) {
+  const SourceManager  = AST.getSourceManager();
+  auto Code = SM.getBufferData(SM.getMainFileID());
+  auto RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
+  llvm::SmallVector NewNames;
+  NewName.split(NewNames, ":");
+  if (NewNames.empty())

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 David Goldman via cfe-commits


@@ -543,6 +550,45 @@ std::optional checkName(const NamedDecl 
,
   return Result;
 }
 
+clangd::Range tokenRangeForLoc(ParsedAST , SourceLocation TokLoc,
+   const SourceManager ,
+   const LangOptions ) {
+  const auto *Token = AST.getTokens().spelledTokenAt(TokLoc);
+  assert(Token && "got inclusion at wrong offset");

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 David Goldman via cfe-commits


@@ -681,12 +718,26 @@ renameOutsideFile(const NamedDecl , 
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()) {
+  if (MD->getSelector().getNumArgs() > 1) {
+RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
+Selector = MD->getSelector();
+NewName.split(NewNames, ":");

DavidGoldman wrote:

Yep, you can have empty selector fragments.

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


@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef 
Indexed, ArrayRef Lexed,
   return Cost;
 }
 
+static bool isMatchingSelectorName(const syntax::Token ,
+   const syntax::Token ,
+   const SourceManager ,
+   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.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+static bool isSelectorLike(const syntax::Token ,
+   const syntax::Token ) {
+  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();
+}
+
+static void
+lex(llvm::StringRef Code, const LangOptions ,
+llvm::function_ref
+Action) {
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Code.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+  for (const auto  : syntax::tokenize(SM.getMainFileID(), SM, LangOpts))
+Action(Tok, SM);
+}
+
+std::vector collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+lex(Content, LangOpts,
+[&](const syntax::Token , const SourceManager ) {
+  if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier)
+return;
+  Ranges.emplace_back(
+  halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+});
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+
+  // One parser state for top level and each `[]` pair, can be nested.
+  // Technically we should have a state or recursion for each ()/{} as well,
+  // but since we're expecting well formed code it shouldn't matter in 
practice.
+  struct ParserState {
+unsigned ParenCount = 0;
+unsigned BraceCount = 0;
+std::vector Pieces;
+  };
+
+  // We have to track square brackets, parens and braces as we want to skip the
+  // tokens inside them. This ensures that we don't use identical selector
+  // pieces in inner message sends, blocks, lambdas and @selector expressions.
+  std::vector States = {ParserState()};
+  unsigned NumPieces = Selector->getNumArgs();
+
+  for (unsigned Index = 0; Index < Last; ++Index) {
+auto  = States.back();
+auto  = State.Pieces;
+const auto  = Tokens[Index];
+const auto Kind = Tok.kind();
+auto PieceCount = Pieces.size();
+
+if (State.ParenCount == 0) {
+  // Check for matches until we find all selector pieces.
+  if (PieceCount < NumPieces &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Selector->getNameForSlot(PieceCount))) {
+if (!Selector->getNameForSlot(PieceCount).empty()) {
+  // Skip the ':' after the name. This ensures that it won't match a
+  // follow-up selector piece with an empty name.
+  ++Index;
+}
+Pieces.push_back(halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+continue;
+  }
+  // If we've found all pieces, we still need to try to consume more pieces
+  // as it's possible the selector being renamed is a prefix of this method
+  // name.

kadircet wrote:

shouldn't we just fail in that case?

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 
,
   return Result;
 }
 
+clangd::Range tokenRangeForLoc(ParsedAST , SourceLocation TokLoc,
+   const SourceManager ,
+   const LangOptions ) {
+  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.

kadircet wrote:

can we also define what `Locs` are, with a better name (like 
`SelectorOccurences`)?

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


@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef 
Indexed, ArrayRef Lexed,
   return Cost;
 }
 
+static bool isMatchingSelectorName(const syntax::Token ,

kadircet wrote:

can you move all of these into anon namespace instead?

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


@@ -38,6 +38,11 @@
 
 namespace clang {
 namespace clangd {
+
+std::vector collectRenameIdentifierRanges(

kadircet wrote:

let's move this into anonymous namespace, and add some comments explaining what 
it 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-01-24 Thread kadir çetinkaya via cfe-commits


@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef 
Indexed, ArrayRef Lexed,
   return Cost;
 }
 
+static bool isMatchingSelectorName(const syntax::Token ,
+   const syntax::Token ,
+   const SourceManager ,
+   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.
+ // e.g. support `foo:` but not `foo :`.
+ Cur.endLocation() == Next.location();
+}
+
+static bool isSelectorLike(const syntax::Token ,
+   const syntax::Token ) {
+  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();
+}
+
+static void
+lex(llvm::StringRef Code, const LangOptions ,
+llvm::function_ref
+Action) {
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Code.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+  for (const auto  : syntax::tokenize(SM.getMainFileID(), SM, LangOpts))
+Action(Tok, SM);
+}
+
+std::vector collectRenameIdentifierRanges(
+llvm::StringRef Identifier, llvm::StringRef Content,
+const LangOptions , std::optional Selector) {
+  std::vector Ranges;
+  if (!Selector) {
+lex(Content, LangOpts,
+[&](const syntax::Token , const SourceManager ) {
+  if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier)
+return;
+  Ranges.emplace_back(
+  halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
+});
+return Ranges;
+  }
+  // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated!
+  std::string NullTerminatedCode = Content.str();
+  SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
+  auto  = FileSM.get();
+
+  auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
+  unsigned Last = Tokens.size() - 1;
+
+  // One parser state for top level and each `[]` pair, can be nested.
+  // Technically we should have a state or recursion for each ()/{} as well,
+  // but since we're expecting well formed code it shouldn't matter in 
practice.
+  struct ParserState {
+unsigned ParenCount = 0;
+unsigned BraceCount = 0;
+std::vector Pieces;
+  };
+
+  // We have to track square brackets, parens and braces as we want to skip the
+  // tokens inside them. This ensures that we don't use identical selector
+  // pieces in inner message sends, blocks, lambdas and @selector expressions.
+  std::vector States = {ParserState()};
+  unsigned NumPieces = Selector->getNumArgs();
+
+  for (unsigned Index = 0; Index < Last; ++Index) {
+auto  = States.back();
+auto  = State.Pieces;
+const auto  = Tokens[Index];
+const auto Kind = Tok.kind();
+auto PieceCount = Pieces.size();
+
+if (State.ParenCount == 0) {
+  // Check for matches until we find all selector pieces.
+  if (PieceCount < NumPieces &&
+  isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
+ Selector->getNameForSlot(PieceCount))) {
+if (!Selector->getNameForSlot(PieceCount).empty()) {

kadircet wrote:

what does it mean for this selector slot to be 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


  1   2   >