Skip to content

Commit 5a688dc

Browse files
committed
[clangd] Use RenameSymbolName to represent Objective-C selectors
This is a cleaner design than using identifier and an optional `Selector`. It also allows rename of Objective-C method names if no declaration is at hand and thus no `Selector` instance can be formed. For example, when finding the ranges to rename based on an index that’s not clangd’s built-in index.
1 parent 42326c7 commit 5a688dc

File tree

3 files changed

+127
-40
lines changed

3 files changed

+127
-40
lines changed

clang-tools-extra/clangd/refactor/Rename.cpp

Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818
#include "clang/AST/ASTContext.h"
1919
#include "clang/AST/ASTTypeTraits.h"
2020
#include "clang/AST/Decl.h"
21+
#include "clang/AST/DeclBase.h"
2122
#include "clang/AST/DeclCXX.h"
2223
#include "clang/AST/DeclObjC.h"
2324
#include "clang/AST/DeclTemplate.h"
25+
#include "clang/AST/DeclarationName.h"
2426
#include "clang/AST/ParentMapContext.h"
2527
#include "clang/AST/Stmt.h"
2628
#include "clang/Basic/CharInfo.h"
@@ -591,11 +593,11 @@ bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token &Next,
591593
// The search will terminate upon seeing Terminator or a ; at the top level.
592594
std::optional<SymbolRange>
593595
findAllSelectorPieces(llvm::ArrayRef<syntax::Token> Tokens,
594-
const SourceManager &SM, Selector Sel,
596+
const SourceManager &SM, const RenameSymbolName &Name,
595597
tok::TokenKind Terminator) {
596598
assert(!Tokens.empty());
597599

598-
unsigned NumArgs = Sel.getNumArgs();
600+
unsigned NumArgs = Name.getNamePieces().size();
599601
llvm::SmallVector<tok::TokenKind, 8> Closes;
600602
std::vector<Range> SelectorPieces;
601603
for (unsigned Index = 0, Last = Tokens.size(); Index < Last - 1; ++Index) {
@@ -605,12 +607,12 @@ findAllSelectorPieces(llvm::ArrayRef<syntax::Token> Tokens,
605607
auto PieceCount = SelectorPieces.size();
606608
if (PieceCount < NumArgs &&
607609
isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
608-
Sel.getNameForSlot(PieceCount))) {
610+
Name.getNamePieces()[PieceCount])) {
609611
// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
610612
// token after the name. We don't currently properly support empty
611613
// selectors since we may lex them improperly due to ternary statements
612614
// as well as don't properly support storing their ranges for edits.
613-
if (!Sel.getNameForSlot(PieceCount).empty())
615+
if (!Name.getNamePieces()[PieceCount].empty())
614616
++Index;
615617
SelectorPieces.push_back(
616618
halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
@@ -662,16 +664,17 @@ findAllSelectorPieces(llvm::ArrayRef<syntax::Token> Tokens,
662664

663665
/// Collects all ranges of the given identifier/selector in the source code.
664666
///
665-
/// If a selector is given, this does a full lex of the given source code in
666-
/// order to identify all selector fragments (e.g. in method exprs/decls) since
667-
/// they are non-contiguous.
668-
std::vector<SymbolRange> collectRenameIdentifierRanges(
669-
llvm::StringRef Identifier, llvm::StringRef Content,
670-
const LangOptions &LangOpts, std::optional<Selector> Selector) {
667+
/// If `Name` is an Objective-C symbol name, this does a full lex of the given
668+
/// source code in order to identify all selector fragments (e.g. in method
669+
/// exprs/decls) since they are non-contiguous.
670+
std::vector<SymbolRange>
671+
collectRenameIdentifierRanges(const RenameSymbolName &Name,
672+
llvm::StringRef Content,
673+
const LangOptions &LangOpts) {
671674
std::vector<SymbolRange> Ranges;
672-
if (!Selector) {
675+
if (auto SinglePiece = Name.getSinglePiece()) {
673676
auto IdentifierRanges =
674-
collectIdentifierRanges(Identifier, Content, LangOpts);
677+
collectIdentifierRanges(*SinglePiece, Content, LangOpts);
675678
for (const auto &R : IdentifierRanges)
676679
Ranges.emplace_back(R);
677680
return Ranges;
@@ -685,7 +688,7 @@ std::vector<SymbolRange> collectRenameIdentifierRanges(
685688
// parsing a method declaration or definition which isn't at the top level or
686689
// similar looking expressions (e.g. an @selector() expression).
687690
llvm::SmallVector<tok::TokenKind, 8> Closes;
688-
llvm::StringRef FirstSelPiece = Selector->getNameForSlot(0);
691+
llvm::StringRef FirstSelPiece = Name.getNamePieces()[0];
689692

690693
auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
691694
unsigned Last = Tokens.size() - 1;
@@ -717,7 +720,7 @@ std::vector<SymbolRange> collectRenameIdentifierRanges(
717720
// Check if we can find all the relevant selector peices starting from
718721
// this token
719722
auto SelectorRanges =
720-
findAllSelectorPieces(ArrayRef(Tokens).slice(Index), SM, *Selector,
723+
findAllSelectorPieces(ArrayRef(Tokens).slice(Index), SM, Name,
721724
Closes.empty() ? tok::l_brace : Closes.back());
722725
if (SelectorRanges)
723726
Ranges.emplace_back(std::move(*SelectorRanges));
@@ -764,7 +767,6 @@ renameObjCMethodWithinFile(ParsedAST &AST, const ObjCMethodDecl *MD,
764767
std::vector<SourceLocation> SelectorOccurences) {
765768
const SourceManager &SM = AST.getSourceManager();
766769
auto Code = SM.getBufferData(SM.getMainFileID());
767-
auto RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
768770
llvm::SmallVector<llvm::StringRef, 8> NewNames;
769771
NewName.split(NewNames, ":");
770772

@@ -774,7 +776,7 @@ renameObjCMethodWithinFile(ParsedAST &AST, const ObjCMethodDecl *MD,
774776
Ranges.push_back(tokenRangeForLoc(AST, Loc, SM, LangOpts));
775777
auto FilePath = AST.tuPath();
776778
auto RenameRanges = collectRenameIdentifierRanges(
777-
RenameIdentifier, Code, LangOpts, MD->getSelector());
779+
RenameSymbolName(MD->getDeclName()), Code, LangOpts);
778780
auto RenameEdit = buildRenameEdit(FilePath, Code, RenameRanges, NewNames);
779781
if (!RenameEdit)
780782
return error("failed to rename in file {0}: {1}", FilePath,
@@ -936,21 +938,14 @@ renameOutsideFile(const NamedDecl &RenameDecl, llvm::StringRef MainFilePath,
936938
ExpBuffer.getError().message());
937939
continue;
938940
}
939-
std::string RenameIdentifier = RenameDecl.getNameAsString();
940-
std::optional<Selector> Selector = std::nullopt;
941+
RenameSymbolName RenameName(RenameDecl.getDeclName());
941942
llvm::SmallVector<llvm::StringRef, 8> NewNames;
942-
if (const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl)) {
943-
RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
944-
if (MD->getSelector().getNumArgs() > 1)
945-
Selector = MD->getSelector();
946-
}
947943
NewName.split(NewNames, ":");
948944

949945
auto AffectedFileCode = (*ExpBuffer)->getBuffer();
950-
auto RenameRanges =
951-
adjustRenameRanges(AffectedFileCode, RenameIdentifier,
952-
std::move(FileAndOccurrences.second),
953-
RenameDecl.getASTContext().getLangOpts(), Selector);
946+
auto RenameRanges = adjustRenameRanges(
947+
AffectedFileCode, RenameName, std::move(FileAndOccurrences.second),
948+
RenameDecl.getASTContext().getLangOpts());
954949
if (!RenameRanges) {
955950
// Our heuristics fails to adjust rename ranges to the current state of
956951
// the file, it is most likely the index is stale, so we give up the
@@ -1011,6 +1006,50 @@ void findNearMiss(
10111006

10121007
} // namespace
10131008

1009+
RenameSymbolName::RenameSymbolName() : NamePieces({}) {}
1010+
1011+
namespace {
1012+
std::vector<std::string> extractNamePieces(const DeclarationName &DeclName) {
1013+
if (DeclName.getNameKind() ==
1014+
DeclarationName::NameKind::ObjCMultiArgSelector ||
1015+
DeclName.getNameKind() == DeclarationName::NameKind::ObjCOneArgSelector) {
1016+
const Selector &Sel = DeclName.getObjCSelector();
1017+
std::vector<std::string> Result;
1018+
for (unsigned Slot = 0; Slot < Sel.getNumArgs(); ++Slot) {
1019+
Result.push_back(Sel.getNameForSlot(Slot).str());
1020+
}
1021+
return Result;
1022+
}
1023+
return {DeclName.getAsString()};
1024+
}
1025+
} // namespace
1026+
1027+
RenameSymbolName::RenameSymbolName(const DeclarationName &DeclName)
1028+
: RenameSymbolName(extractNamePieces(DeclName)) {}
1029+
1030+
RenameSymbolName::RenameSymbolName(ArrayRef<std::string> NamePieces) {
1031+
for (const auto &Piece : NamePieces)
1032+
this->NamePieces.push_back(Piece);
1033+
}
1034+
1035+
std::optional<std::string> RenameSymbolName::getSinglePiece() const {
1036+
if (getNamePieces().size() == 1) {
1037+
return NamePieces.front();
1038+
}
1039+
return std::nullopt;
1040+
}
1041+
1042+
std::string RenameSymbolName::getAsString() const {
1043+
std::string Result;
1044+
llvm::raw_string_ostream OS(Result);
1045+
this->print(OS);
1046+
return Result;
1047+
}
1048+
1049+
void RenameSymbolName::print(raw_ostream &OS) const {
1050+
llvm::interleave(NamePieces, OS, ":");
1051+
}
1052+
10141053
SymbolRange::SymbolRange(Range R) : Ranges({R}) {}
10151054

10161055
SymbolRange::SymbolRange(std::vector<Range> Ranges)
@@ -1238,14 +1277,13 @@ llvm::Expected<Edit> buildRenameEdit(llvm::StringRef AbsFilePath,
12381277
// were inserted). If such a "near miss" is found, the rename is still
12391278
// possible
12401279
std::optional<std::vector<SymbolRange>>
1241-
adjustRenameRanges(llvm::StringRef DraftCode, llvm::StringRef Identifier,
1242-
std::vector<Range> Indexed, const LangOptions &LangOpts,
1243-
std::optional<Selector> Selector) {
1280+
adjustRenameRanges(llvm::StringRef DraftCode, const RenameSymbolName &Name,
1281+
std::vector<Range> Indexed, const LangOptions &LangOpts) {
12441282
trace::Span Tracer("AdjustRenameRanges");
12451283
assert(!Indexed.empty());
12461284
assert(llvm::is_sorted(Indexed));
12471285
std::vector<SymbolRange> Lexed =
1248-
collectRenameIdentifierRanges(Identifier, DraftCode, LangOpts, Selector);
1286+
collectRenameIdentifierRanges(Name, DraftCode, LangOpts);
12491287
llvm::sort(Lexed);
12501288
return getMappedRanges(Indexed, Lexed);
12511289
}

clang-tools-extra/clangd/refactor/Rename.h

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,56 @@ struct RenameOptions {
3535
bool RenameVirtual = true;
3636
};
3737

38+
/// A name of a symbol that should be renamed.
39+
///
40+
/// Symbol's name can be composed of multiple strings. For example, Objective-C
41+
/// methods can contain multiple argument labels:
42+
///
43+
/// \code
44+
/// - (void) myMethodNamePiece: (int)x anotherNamePieces:(int)y;
45+
/// // ^~ string 0 ~~~~~ ^~ string 1 ~~~~~
46+
/// \endcode
47+
class RenameSymbolName {
48+
llvm::SmallVector<std::string, 1> NamePieces;
49+
50+
public:
51+
RenameSymbolName();
52+
53+
/// Create a new \c SymbolName with the specified pieces.
54+
explicit RenameSymbolName(ArrayRef<std::string> NamePieces);
55+
56+
explicit RenameSymbolName(const DeclarationName &Name);
57+
58+
// /// Creates a \c SymbolName from the given string representation.
59+
// ///
60+
// /// For Objective-C symbol names, this splits a selector into multiple
61+
// pieces
62+
// /// on `:`. For all other languages the name is used as the symbol name.
63+
// SymbolName(StringRef Name, bool IsObjectiveCSelector);
64+
65+
ArrayRef<std::string> getNamePieces() const { return NamePieces; }
66+
67+
/// If this symbol consists of a single piece return it, otherwise return
68+
/// `None`.
69+
///
70+
/// Only symbols in Objective-C can consist of multiple pieces, so this
71+
/// function always returns a value for non-Objective-C symbols.
72+
std::optional<std::string> getSinglePiece() const;
73+
74+
/// Returns a human-readable version of this symbol name.
75+
///
76+
/// If the symbol consists of multiple pieces (aka. it is an Objective-C
77+
/// selector/method name), the pieces are separated by `:`, otherwise just an
78+
/// identifier name.
79+
std::string getAsString() const;
80+
81+
void print(raw_ostream &OS) const;
82+
83+
bool operator==(const RenameSymbolName &Other) const {
84+
return NamePieces == Other.NamePieces;
85+
}
86+
};
87+
3888
struct RenameInputs {
3989
Position Pos; // the position triggering the rename
4090
llvm::StringRef NewName;
@@ -108,9 +158,8 @@ llvm::Expected<Edit> buildRenameEdit(llvm::StringRef AbsFilePath,
108158
/// occurrence has the same length).
109159
/// REQUIRED: Indexed is sorted.
110160
std::optional<std::vector<SymbolRange>>
111-
adjustRenameRanges(llvm::StringRef DraftCode, llvm::StringRef Identifier,
112-
std::vector<Range> Indexed, const LangOptions &LangOpts,
113-
std::optional<Selector> Selector);
161+
adjustRenameRanges(llvm::StringRef DraftCode, const RenameSymbolName &Name,
162+
std::vector<Range> Indexed, const LangOptions &LangOpts);
114163

115164
/// Calculates the lexed occurrences that the given indexed occurrences map to.
116165
/// Returns std::nullopt if we don't find a mapping.

clang-tools-extra/clangd/unittests/RenameTests.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,7 @@ TEST(RenameTest, WithinFileRename) {
827827

828828
// Issue 170: Rename symbol introduced by UsingDecl
829829
R"cpp(
830-
namespace ns { void [[f^oo]](); }
830+
namespace ns { void [[f^oo]](); }
831831
832832
using ns::[[f^oo]];
833833
@@ -1307,7 +1307,7 @@ TEST(RenameTest, Renameable) {
13071307
"no symbol", false},
13081308

13091309
{R"cpp(// FIXME we probably want to rename both overloads here,
1310-
// but renaming currently assumes there's only a
1310+
// but renaming currently assumes there's only a
13111311
// single canonical declaration.
13121312
namespace ns { int foo(int); char foo(char); }
13131313
using ns::^foo;
@@ -1764,7 +1764,7 @@ TEST(CrossFileRenameTests, WithUpToDateIndex) {
17641764
void [[foo]]() override {};
17651765
};
17661766
1767-
void func(Base* b, Derived1* d1,
1767+
void func(Base* b, Derived1* d1,
17681768
Derived2* d2, NotDerived* nd) {
17691769
b->[[foo]]();
17701770
d1->[[foo]]();
@@ -2178,9 +2178,9 @@ TEST(CrossFileRenameTests, adjustRenameRanges) {
21782178
for (const auto &T : Tests) {
21792179
SCOPED_TRACE(T.DraftCode);
21802180
Annotations Draft(T.DraftCode);
2181-
auto ActualRanges = adjustRenameRanges(Draft.code(), "x",
2182-
Annotations(T.IndexedCode).ranges(),
2183-
LangOpts, std::nullopt);
2181+
auto ActualRanges = adjustRenameRanges(
2182+
Draft.code(), RenameSymbolName(ArrayRef<std::string>{"x"}),
2183+
Annotations(T.IndexedCode).ranges(), LangOpts);
21842184
if (!ActualRanges)
21852185
EXPECT_THAT(Draft.ranges(), testing::IsEmpty());
21862186
else

0 commit comments

Comments
 (0)