1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
|
//===--- Rename.cpp - Symbol-rename refactorings -----------------*- C++-*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "refactor/Rename.h"
#include "AST.h"
#include "Logger.h"
#include "index/SymbolCollector.h"
#include "clang/Tooling/Refactoring/RefactoringResultConsumer.h"
#include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
namespace clang {
namespace clangd {
namespace {
class RefactoringResultCollector final
: public tooling::RefactoringResultConsumer {
public:
void handleError(llvm::Error Err) override {
assert(!Result.hasValue());
Result = std::move(Err);
}
// Using the handle(SymbolOccurrences) from parent class.
using tooling::RefactoringResultConsumer::handle;
void handle(tooling::AtomicChanges SourceReplacements) override {
assert(!Result.hasValue());
Result = std::move(SourceReplacements);
}
llvm::Optional<llvm::Expected<tooling::AtomicChanges>> Result;
};
// Expand a DiagnosticError to make it print-friendly (print the detailed
// message, rather than "clang diagnostic").
llvm::Error expandDiagnostics(llvm::Error Err, DiagnosticsEngine &DE) {
if (auto Diag = DiagnosticError::take(Err)) {
llvm::cantFail(std::move(Err));
SmallVector<char, 128> DiagMessage;
Diag->second.EmitToString(DE, DiagMessage);
return llvm::make_error<llvm::StringError>(DiagMessage,
llvm::inconvertibleErrorCode());
}
return Err;
}
llvm::Optional<std::string> filePath(const SymbolLocation &Loc,
llvm::StringRef HintFilePath) {
if (!Loc)
return None;
auto Uri = URI::parse(Loc.FileURI);
if (!Uri) {
elog("Could not parse URI {0}: {1}", Loc.FileURI, Uri.takeError());
return None;
}
auto U = URIForFile::fromURI(*Uri, HintFilePath);
if (!U) {
elog("Could not resolve URI {0}: {1}", Loc.FileURI, U.takeError());
return None;
}
return U->file().str();
}
// Query the index to find some other files where the Decl is referenced.
llvm::Optional<std::string> getOtherRefFile(const Decl &D, StringRef MainFile,
const SymbolIndex &Index) {
RefsRequest Req;
// We limit the number of results, this is a correctness/performance
// tradeoff. We expect the number of symbol references in the current file
// is smaller than the limit.
Req.Limit = 100;
if (auto ID = getSymbolID(&D))
Req.IDs.insert(*ID);
llvm::Optional<std::string> OtherFile;
Index.refs(Req, [&](const Ref &R) {
if (OtherFile)
return;
if (auto RefFilePath = filePath(R.Location, /*HintFilePath=*/MainFile)) {
if (*RefFilePath != MainFile)
OtherFile = *RefFilePath;
}
});
return OtherFile;
}
enum ReasonToReject {
NoIndexProvided,
NonIndexable,
UsedOutsideFile,
UnsupportedSymbol,
};
// Check the symbol Decl is renameable (per the index) within the file.
llvm::Optional<ReasonToReject> renamableWithinFile(const Decl &RenameDecl,
StringRef MainFile,
const SymbolIndex *Index) {
if (llvm::isa<NamespaceDecl>(&RenameDecl))
return ReasonToReject::UnsupportedSymbol;
auto &ASTCtx = RenameDecl.getASTContext();
const auto &SM = ASTCtx.getSourceManager();
bool MainFileIsHeader = ASTCtx.getLangOpts().IsHeaderFile;
bool DeclaredInMainFile = isInsideMainFile(RenameDecl.getBeginLoc(), SM);
// If the symbol is declared in the main file (which is not a header), we
// rename it.
if (DeclaredInMainFile && !MainFileIsHeader)
return None;
// Below are cases where the symbol is declared in the header.
// If the symbol is function-local, we rename it.
if (RenameDecl.getParentFunctionOrMethod())
return None;
if (!Index)
return ReasonToReject::NoIndexProvided;
bool IsIndexable = isa<NamedDecl>(RenameDecl) &&
SymbolCollector::shouldCollectSymbol(
cast<NamedDecl>(RenameDecl), ASTCtx, {}, false);
// If the symbol is not indexable, we disallow rename.
if (!IsIndexable)
return ReasonToReject::NonIndexable;
auto OtherFile = getOtherRefFile(RenameDecl, MainFile, *Index);
// If the symbol is indexable and has no refs from other files in the index,
// we rename it.
if (!OtherFile)
return None;
// If the symbol is indexable and has refs from other files in the index,
// we disallow rename.
return ReasonToReject::UsedOutsideFile;
}
llvm::Error makeError(ReasonToReject Reason) {
auto Message = [](ReasonToReject Reason) {
switch (Reason) {
case NoIndexProvided:
return "symbol may be used in other files (no index available)";
case UsedOutsideFile:
return "the symbol is used outside main file";
case NonIndexable:
return "symbol may be used in other files (not eligible for indexing)";
case UnsupportedSymbol:
return "symbol is not a supported kind (e.g. namespace, macro)";
}
llvm_unreachable("unhandled reason kind");
};
return llvm::make_error<llvm::StringError>(
llvm::formatv("Cannot rename symbol: {0}", Message(Reason)),
llvm::inconvertibleErrorCode());
}
} // namespace
llvm::Expected<tooling::Replacements>
renameWithinFile(ParsedAST &AST, llvm::StringRef File, Position Pos,
llvm::StringRef NewName, const SymbolIndex *Index) {
RefactoringResultCollector ResultCollector;
ASTContext &ASTCtx = AST.getASTContext();
SourceLocation SourceLocationBeg = clangd::getBeginningOfIdentifier(
AST, Pos, AST.getSourceManager().getMainFileID());
// FIXME: renaming macros is not supported yet, the macro-handling code should
// be moved to rename tooling library.
if (locateMacroAt(SourceLocationBeg, AST.getPreprocessor()))
return makeError(UnsupportedSymbol);
tooling::RefactoringRuleContext Context(AST.getSourceManager());
Context.setASTContext(ASTCtx);
auto Rename = clang::tooling::RenameOccurrences::initiate(
Context, SourceRange(SourceLocationBeg), NewName);
if (!Rename)
return expandDiagnostics(Rename.takeError(), ASTCtx.getDiagnostics());
const auto *RenameDecl = Rename->getRenameDecl();
assert(RenameDecl && "symbol must be found at this point");
if (auto Reject =
renamableWithinFile(*RenameDecl->getCanonicalDecl(), File, Index))
return makeError(*Reject);
Rename->invoke(ResultCollector, Context);
assert(ResultCollector.Result.hasValue());
if (!ResultCollector.Result.getValue())
return expandDiagnostics(ResultCollector.Result->takeError(),
ASTCtx.getDiagnostics());
tooling::Replacements FilteredChanges;
// Right now we only support renaming the main file, so we
// drop replacements not for the main file. In the future, we might
// also support rename with wider scope.
// Rename sometimes returns duplicate edits (which is a bug). A side-effect of
// adding them to a single Replacements object is these are deduplicated.
for (const tooling::AtomicChange &Change : ResultCollector.Result->get()) {
for (const auto &Rep : Change.getReplacements()) {
if (Rep.getFilePath() == File)
cantFail(FilteredChanges.add(Rep));
}
}
return FilteredChanges;
}
} // namespace clangd
} // namespace clang
|