| Haojian Wu | 488c509 | 2019-05-31 14:38:16 +0000 | [diff] [blame] | 1 | //===--- Rename.cpp - Symbol-rename refactorings -----------------*- C++-*-===// | 
|  | 2 | // | 
|  | 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | 4 | // See https://llvm.org/LICENSE.txt for license information. | 
|  | 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | 6 | // | 
|  | 7 | //===----------------------------------------------------------------------===// | 
|  | 8 |  | 
| Sam McCall | c094912 | 2019-05-07 07:11:56 +0000 | [diff] [blame] | 9 | #include "refactor/Rename.h" | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 10 | #include "AST.h" | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 11 | #include "FindTarget.h" | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 12 | #include "Logger.h" | 
| Sam McCall | 915f978 | 2019-09-04 09:46:06 +0000 | [diff] [blame] | 13 | #include "ParsedAST.h" | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 14 | #include "Selection.h" | 
| Sam McCall | 19cefc2 | 2019-09-03 15:34:47 +0000 | [diff] [blame] | 15 | #include "SourceCode.h" | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 16 | #include "index/SymbolCollector.h" | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 17 | #include "clang/AST/DeclCXX.h" | 
|  | 18 | #include "clang/AST/DeclTemplate.h" | 
|  | 19 | #include "clang/Basic/SourceLocation.h" | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 20 | #include "clang/Tooling/Refactoring/Rename/USRFindingAction.h" | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 21 | #include "llvm/ADT/STLExtras.h" | 
|  | 22 | #include "llvm/Support/Error.h" | 
| Haojian Wu | 8805316 | 2019-11-19 15:23:36 +0100 | [diff] [blame] | 23 | #include "llvm/Support/FormatVariadic.h" | 
| Sam McCall | c094912 | 2019-05-07 07:11:56 +0000 | [diff] [blame] | 24 |  | 
|  | 25 | namespace clang { | 
|  | 26 | namespace clangd { | 
|  | 27 | namespace { | 
|  | 28 |  | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 29 | llvm::Optional<std::string> filePath(const SymbolLocation &Loc, | 
|  | 30 | llvm::StringRef HintFilePath) { | 
|  | 31 | if (!Loc) | 
|  | 32 | return None; | 
| Haojian Wu | f821ab8 | 2019-10-29 10:49:27 +0100 | [diff] [blame] | 33 | auto Path = URI::resolve(Loc.FileURI, HintFilePath); | 
|  | 34 | if (!Path) { | 
|  | 35 | elog("Could not resolve URI {0}: {1}", Loc.FileURI, Path.takeError()); | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 36 | return None; | 
|  | 37 | } | 
| Haojian Wu | f821ab8 | 2019-10-29 10:49:27 +0100 | [diff] [blame] | 38 |  | 
|  | 39 | return *Path; | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 40 | } | 
|  | 41 |  | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 42 | // Returns true if the given location is expanded from any macro body. | 
|  | 43 | bool isInMacroBody(const SourceManager &SM, SourceLocation Loc) { | 
|  | 44 | while (Loc.isMacroID()) { | 
|  | 45 | if (SM.isMacroBodyExpansion(Loc)) | 
|  | 46 | return true; | 
|  | 47 | Loc = SM.getImmediateMacroCallerLoc(Loc); | 
|  | 48 | } | 
|  | 49 |  | 
|  | 50 | return false; | 
|  | 51 | } | 
|  | 52 |  | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 53 | // Query the index to find some other files where the Decl is referenced. | 
| Haojian Wu | 93a825c | 2019-06-27 13:24:10 +0000 | [diff] [blame] | 54 | llvm::Optional<std::string> getOtherRefFile(const Decl &D, StringRef MainFile, | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 55 | const SymbolIndex &Index) { | 
|  | 56 | RefsRequest Req; | 
|  | 57 | // We limit the number of results, this is a correctness/performance | 
|  | 58 | // tradeoff. We expect the number of symbol references in the current file | 
|  | 59 | // is smaller than the limit. | 
|  | 60 | Req.Limit = 100; | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 61 | Req.IDs.insert(*getSymbolID(&D)); | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 62 | llvm::Optional<std::string> OtherFile; | 
|  | 63 | Index.refs(Req, [&](const Ref &R) { | 
|  | 64 | if (OtherFile) | 
|  | 65 | return; | 
|  | 66 | if (auto RefFilePath = filePath(R.Location, /*HintFilePath=*/MainFile)) { | 
|  | 67 | if (*RefFilePath != MainFile) | 
|  | 68 | OtherFile = *RefFilePath; | 
|  | 69 | } | 
|  | 70 | }); | 
|  | 71 | return OtherFile; | 
|  | 72 | } | 
|  | 73 |  | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 74 | llvm::DenseSet<const Decl *> locateDeclAt(ParsedAST &AST, | 
|  | 75 | SourceLocation TokenStartLoc) { | 
|  | 76 | unsigned Offset = | 
|  | 77 | AST.getSourceManager().getDecomposedSpellingLoc(TokenStartLoc).second; | 
|  | 78 |  | 
|  | 79 | SelectionTree Selection(AST.getASTContext(), AST.getTokens(), Offset); | 
|  | 80 | const SelectionTree::Node *SelectedNode = Selection.commonAncestor(); | 
|  | 81 | if (!SelectedNode) | 
|  | 82 | return {}; | 
|  | 83 |  | 
|  | 84 | // If the location points to a Decl, we check it is actually on the name | 
|  | 85 | // range of the Decl. This would avoid allowing rename on unrelated tokens. | 
|  | 86 | //   ^class Foo {} // SelectionTree returns CXXRecordDecl, | 
|  | 87 | //                 // we don't attempt to trigger rename on this position. | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame^] | 88 | // FIXME: Make this work on destructors, e.g. "~F^oo()". | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 89 | if (const auto *D = SelectedNode->ASTNode.get<Decl>()) { | 
|  | 90 | if (D->getLocation() != TokenStartLoc) | 
|  | 91 | return {}; | 
|  | 92 | } | 
|  | 93 |  | 
|  | 94 | llvm::DenseSet<const Decl *> Result; | 
|  | 95 | for (const auto *D : | 
|  | 96 | targetDecl(SelectedNode->ASTNode, | 
|  | 97 | DeclRelation::Alias | DeclRelation::TemplatePattern)) | 
|  | 98 | Result.insert(D); | 
|  | 99 | return Result; | 
|  | 100 | } | 
|  | 101 |  | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 102 | enum ReasonToReject { | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 103 | NoSymbolFound, | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 104 | NoIndexProvided, | 
|  | 105 | NonIndexable, | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 106 | UsedOutsideFile, // for within-file rename only. | 
| Haojian Wu | 442a120 | 2019-06-26 08:10:26 +0000 | [diff] [blame] | 107 | UnsupportedSymbol, | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 108 | AmbiguousSymbol, | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 109 | }; | 
|  | 110 |  | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 111 | llvm::Optional<ReasonToReject> renameable(const Decl &RenameDecl, | 
|  | 112 | StringRef MainFilePath, | 
|  | 113 | const SymbolIndex *Index, | 
|  | 114 | bool CrossFile) { | 
|  | 115 | // Filter out symbols that are unsupported in both rename modes. | 
| Haojian Wu | 93a825c | 2019-06-27 13:24:10 +0000 | [diff] [blame] | 116 | if (llvm::isa<NamespaceDecl>(&RenameDecl)) | 
| Haojian Wu | 442a120 | 2019-06-26 08:10:26 +0000 | [diff] [blame] | 117 | return ReasonToReject::UnsupportedSymbol; | 
| Haojian Wu | af28bb6 | 2019-09-16 10:16:56 +0000 | [diff] [blame] | 118 | if (const auto *FD = llvm::dyn_cast<FunctionDecl>(&RenameDecl)) { | 
|  | 119 | if (FD->isOverloadedOperator()) | 
|  | 120 | return ReasonToReject::UnsupportedSymbol; | 
|  | 121 | } | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 122 | // function-local symbols is safe to rename. | 
| Haojian Wu | 93a825c | 2019-06-27 13:24:10 +0000 | [diff] [blame] | 123 | if (RenameDecl.getParentFunctionOrMethod()) | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 124 | return None; | 
|  | 125 |  | 
| Haojian Wu | 902dc6c | 2019-11-29 14:58:44 +0100 | [diff] [blame] | 126 | // Check whether the symbol being rename is indexable. | 
|  | 127 | auto &ASTCtx = RenameDecl.getASTContext(); | 
|  | 128 | bool MainFileIsHeader = isHeaderFile(MainFilePath, ASTCtx.getLangOpts()); | 
|  | 129 | bool DeclaredInMainFile = | 
|  | 130 | isInsideMainFile(RenameDecl.getBeginLoc(), ASTCtx.getSourceManager()); | 
|  | 131 | bool IsMainFileOnly = true; | 
|  | 132 | if (MainFileIsHeader) | 
|  | 133 | // main file is a header, the symbol can't be main file only. | 
|  | 134 | IsMainFileOnly = false; | 
|  | 135 | else if (!DeclaredInMainFile) | 
|  | 136 | IsMainFileOnly = false; | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 137 | bool IsIndexable = | 
|  | 138 | isa<NamedDecl>(RenameDecl) && | 
|  | 139 | SymbolCollector::shouldCollectSymbol( | 
|  | 140 | cast<NamedDecl>(RenameDecl), RenameDecl.getASTContext(), | 
| Haojian Wu | 902dc6c | 2019-11-29 14:58:44 +0100 | [diff] [blame] | 141 | SymbolCollector::Options(), IsMainFileOnly); | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 142 | if (!IsIndexable) // If the symbol is not indexable, we disallow rename. | 
|  | 143 | return ReasonToReject::NonIndexable; | 
|  | 144 |  | 
|  | 145 | if (!CrossFile) { | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 146 | if (!DeclaredInMainFile) | 
|  | 147 | // We are sure the symbol is used externally, bail out early. | 
|  | 148 | return ReasonToReject::UsedOutsideFile; | 
|  | 149 |  | 
|  | 150 | // If the symbol is declared in the main file (which is not a header), we | 
|  | 151 | // rename it. | 
|  | 152 | if (!MainFileIsHeader) | 
|  | 153 | return None; | 
|  | 154 |  | 
|  | 155 | if (!Index) | 
|  | 156 | return ReasonToReject::NoIndexProvided; | 
|  | 157 |  | 
|  | 158 | auto OtherFile = getOtherRefFile(RenameDecl, MainFilePath, *Index); | 
|  | 159 | // If the symbol is indexable and has no refs from other files in the index, | 
|  | 160 | // we rename it. | 
|  | 161 | if (!OtherFile) | 
|  | 162 | return None; | 
|  | 163 | // If the symbol is indexable and has refs from other files in the index, | 
|  | 164 | // we disallow rename. | 
|  | 165 | return ReasonToReject::UsedOutsideFile; | 
|  | 166 | } | 
|  | 167 |  | 
|  | 168 | assert(CrossFile); | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 169 | if (!Index) | 
|  | 170 | return ReasonToReject::NoIndexProvided; | 
|  | 171 |  | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 172 | // Blacklist symbols that are not supported yet in cross-file mode due to the | 
|  | 173 | // limitations of our index. | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame^] | 174 | // FIXME: Renaming templates requires to rename all related specializations, | 
|  | 175 | // our index doesn't have this information. | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 176 | if (RenameDecl.getDescribedTemplate()) | 
|  | 177 | return ReasonToReject::UnsupportedSymbol; | 
|  | 178 |  | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame^] | 179 | // FIXME: Renaming virtual methods requires to rename all overridens in | 
|  | 180 | // subclasses, our index doesn't have this information. | 
|  | 181 | // Note: Within-file rename does support this through the AST. | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 182 | if (const auto *S = llvm::dyn_cast<CXXMethodDecl>(&RenameDecl)) { | 
|  | 183 | if (S->isVirtual()) | 
|  | 184 | return ReasonToReject::UnsupportedSymbol; | 
|  | 185 | } | 
|  | 186 | return None; | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 187 | } | 
|  | 188 |  | 
| Haojian Wu | 9d34f45 | 2019-07-01 09:26:48 +0000 | [diff] [blame] | 189 | llvm::Error makeError(ReasonToReject Reason) { | 
|  | 190 | auto Message = [](ReasonToReject Reason) { | 
|  | 191 | switch (Reason) { | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 192 | case ReasonToReject::NoSymbolFound: | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 193 | return "there is no symbol at the given location"; | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 194 | case ReasonToReject::NoIndexProvided: | 
| Haojian Wu | 08cce03 | 2019-11-28 11:39:48 +0100 | [diff] [blame] | 195 | return "no index provided"; | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 196 | case ReasonToReject::UsedOutsideFile: | 
| Haojian Wu | 9d34f45 | 2019-07-01 09:26:48 +0000 | [diff] [blame] | 197 | return "the symbol is used outside main file"; | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 198 | case ReasonToReject::NonIndexable: | 
| Haojian Wu | 9d34f45 | 2019-07-01 09:26:48 +0000 | [diff] [blame] | 199 | return "symbol may be used in other files (not eligible for indexing)"; | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 200 | case ReasonToReject::UnsupportedSymbol: | 
| Haojian Wu | 9d34f45 | 2019-07-01 09:26:48 +0000 | [diff] [blame] | 201 | return "symbol is not a supported kind (e.g. namespace, macro)"; | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 202 | case AmbiguousSymbol: | 
|  | 203 | return "there are multiple symbols at the given location"; | 
| Haojian Wu | 9d34f45 | 2019-07-01 09:26:48 +0000 | [diff] [blame] | 204 | } | 
|  | 205 | llvm_unreachable("unhandled reason kind"); | 
|  | 206 | }; | 
|  | 207 | return llvm::make_error<llvm::StringError>( | 
|  | 208 | llvm::formatv("Cannot rename symbol: {0}", Message(Reason)), | 
|  | 209 | llvm::inconvertibleErrorCode()); | 
|  | 210 | } | 
|  | 211 |  | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 212 | // Return all rename occurrences in the main file. | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 213 | std::vector<SourceLocation> findOccurrencesWithinFile(ParsedAST &AST, | 
|  | 214 | const NamedDecl &ND) { | 
|  | 215 | // In theory, locateDeclAt should return the primary template. However, if the | 
|  | 216 | // cursor is under the underlying CXXRecordDecl of the ClassTemplateDecl, ND | 
|  | 217 | // will be the CXXRecordDecl, for this case, we need to get the primary | 
|  | 218 | // template maunally. | 
|  | 219 | const auto &RenameDecl = | 
|  | 220 | ND.getDescribedTemplate() ? *ND.getDescribedTemplate() : ND; | 
|  | 221 | // getUSRsForDeclaration will find other related symbols, e.g. virtual and its | 
|  | 222 | // overriddens, primary template and all explicit specializations. | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame^] | 223 | // FIXME: Get rid of the remaining tooling APIs. | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 224 | std::vector<std::string> RenameUSRs = tooling::getUSRsForDeclaration( | 
|  | 225 | tooling::getCanonicalSymbolDeclaration(&RenameDecl), AST.getASTContext()); | 
|  | 226 | llvm::DenseSet<SymbolID> TargetIDs; | 
|  | 227 | for (auto &USR : RenameUSRs) | 
|  | 228 | TargetIDs.insert(SymbolID(USR)); | 
|  | 229 |  | 
|  | 230 | std::vector<SourceLocation> Results; | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 231 | for (Decl *TopLevelDecl : AST.getLocalTopLevelDecls()) { | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 232 | findExplicitReferences(TopLevelDecl, [&](ReferenceLoc Ref) { | 
|  | 233 | if (Ref.Targets.empty()) | 
|  | 234 | return; | 
|  | 235 | for (const auto *Target : Ref.Targets) { | 
|  | 236 | auto ID = getSymbolID(Target); | 
|  | 237 | if (!ID || TargetIDs.find(*ID) == TargetIDs.end()) | 
|  | 238 | return; | 
|  | 239 | } | 
|  | 240 | Results.push_back(Ref.NameLoc); | 
|  | 241 | }); | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 242 | } | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 243 |  | 
|  | 244 | return Results; | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 245 | } | 
|  | 246 |  | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 247 | // AST-based rename, it renames all occurrences in the main file. | 
| Sam McCall | c094912 | 2019-05-07 07:11:56 +0000 | [diff] [blame] | 248 | llvm::Expected<tooling::Replacements> | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 249 | renameWithinFile(ParsedAST &AST, const NamedDecl &RenameDecl, | 
|  | 250 | llvm::StringRef NewName) { | 
| Sam McCall | b2a984c0 | 2019-09-04 10:15:27 +0000 | [diff] [blame] | 251 | const SourceManager &SM = AST.getSourceManager(); | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 252 |  | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 253 | tooling::Replacements FilteredChanges; | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 254 | for (SourceLocation Loc : findOccurrencesWithinFile(AST, RenameDecl)) { | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 255 | SourceLocation RenameLoc = Loc; | 
|  | 256 | // We don't rename in any macro bodies, but we allow rename the symbol | 
|  | 257 | // spelled in a top-level macro argument in the main file. | 
|  | 258 | if (RenameLoc.isMacroID()) { | 
|  | 259 | if (isInMacroBody(SM, RenameLoc)) | 
|  | 260 | continue; | 
|  | 261 | RenameLoc = SM.getSpellingLoc(Loc); | 
|  | 262 | } | 
|  | 263 | // Filter out locations not from main file. | 
|  | 264 | // We traverse only main file decls, but locations could come from an | 
|  | 265 | // non-preamble #include file e.g. | 
|  | 266 | //   void test() { | 
|  | 267 | //     int f^oo; | 
|  | 268 | //     #include "use_foo.inc" | 
|  | 269 | //   } | 
|  | 270 | if (!isInsideMainFile(RenameLoc, SM)) | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 271 | continue; | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 272 | if (auto Err = FilteredChanges.add(tooling::Replacement( | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 273 | SM, CharSourceRange::getTokenRange(RenameLoc), NewName))) | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 274 | return std::move(Err); | 
| Sam McCall | c094912 | 2019-05-07 07:11:56 +0000 | [diff] [blame] | 275 | } | 
|  | 276 | return FilteredChanges; | 
|  | 277 | } | 
|  | 278 |  | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 279 | Range toRange(const SymbolLocation &L) { | 
|  | 280 | Range R; | 
|  | 281 | R.start.line = L.Start.line(); | 
|  | 282 | R.start.character = L.Start.column(); | 
|  | 283 | R.end.line = L.End.line(); | 
|  | 284 | R.end.character = L.End.column(); | 
|  | 285 | return R; | 
| Bill Wendling | 936de1c | 2019-12-02 14:05:28 -0800 | [diff] [blame] | 286 | } | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 287 |  | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame^] | 288 | // Return all rename occurrences (using the index) outside of the main file, | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 289 | // grouped by the absolute file path. | 
| Haojian Wu | 3c3aca2 | 2019-11-28 12:47:32 +0100 | [diff] [blame] | 290 | llvm::Expected<llvm::StringMap<std::vector<Range>>> | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 291 | findOccurrencesOutsideFile(const NamedDecl &RenameDecl, | 
|  | 292 | llvm::StringRef MainFile, const SymbolIndex &Index) { | 
|  | 293 | RefsRequest RQuest; | 
|  | 294 | RQuest.IDs.insert(*getSymbolID(&RenameDecl)); | 
|  | 295 |  | 
| Haojian Wu | 3c3aca2 | 2019-11-28 12:47:32 +0100 | [diff] [blame] | 296 | // Absolute file path => rename occurrences in that file. | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 297 | llvm::StringMap<std::vector<Range>> AffectedFiles; | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame^] | 298 | // FIXME: Make the limit customizable. | 
| Haojian Wu | 3c3aca2 | 2019-11-28 12:47:32 +0100 | [diff] [blame] | 299 | static constexpr size_t MaxLimitFiles = 50; | 
|  | 300 | bool HasMore = Index.refs(RQuest, [&](const Ref &R) { | 
|  | 301 | if (AffectedFiles.size() > MaxLimitFiles) | 
|  | 302 | return; | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 303 | if (auto RefFilePath = filePath(R.Location, /*HintFilePath=*/MainFile)) { | 
|  | 304 | if (*RefFilePath != MainFile) | 
|  | 305 | AffectedFiles[*RefFilePath].push_back(toRange(R.Location)); | 
|  | 306 | } | 
|  | 307 | }); | 
| Haojian Wu | 3c3aca2 | 2019-11-28 12:47:32 +0100 | [diff] [blame] | 308 |  | 
|  | 309 | if (AffectedFiles.size() > MaxLimitFiles) | 
|  | 310 | return llvm::make_error<llvm::StringError>( | 
|  | 311 | llvm::formatv("The number of affected files exceeds the max limit {0}", | 
|  | 312 | MaxLimitFiles), | 
|  | 313 | llvm::inconvertibleErrorCode()); | 
|  | 314 | if (HasMore) { | 
|  | 315 | return llvm::make_error<llvm::StringError>( | 
|  | 316 | llvm::formatv("The symbol {0} has too many occurrences", | 
|  | 317 | RenameDecl.getQualifiedNameAsString()), | 
|  | 318 | llvm::inconvertibleErrorCode()); | 
|  | 319 | } | 
|  | 320 |  | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 321 | return AffectedFiles; | 
|  | 322 | } | 
|  | 323 |  | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 324 | // Index-based rename, it renames all occurrences outside of the main file. | 
|  | 325 | // | 
|  | 326 | // The cross-file rename is purely based on the index, as we don't want to | 
|  | 327 | // build all ASTs for affected files, which may cause a performance hit. | 
|  | 328 | // We choose to trade off some correctness for performance and scalability. | 
|  | 329 | // | 
|  | 330 | // Clangd builds a dynamic index for all opened files on top of the static | 
|  | 331 | // index of the whole codebase. Dynamic index is up-to-date (respects dirty | 
|  | 332 | // buffers) as long as clangd finishes processing opened files, while static | 
|  | 333 | // index (background index) is relatively stale. We choose the dirty buffers | 
|  | 334 | // as the file content we rename on, and fallback to file content on disk if | 
|  | 335 | // there is no dirty buffer. | 
|  | 336 | // | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame^] | 337 | // FIXME: Add range patching heuristics to detect staleness of the index, and | 
|  | 338 | // report to users. | 
|  | 339 | // FIXME: Our index may return implicit references, which are not eligible for | 
|  | 340 | // rename, we should filter out these references. | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 341 | llvm::Expected<FileEdits> renameOutsideFile( | 
|  | 342 | const NamedDecl &RenameDecl, llvm::StringRef MainFilePath, | 
|  | 343 | llvm::StringRef NewName, const SymbolIndex &Index, | 
|  | 344 | llvm::function_ref<llvm::Expected<std::string>(PathRef)> GetFileContent) { | 
|  | 345 | auto AffectedFiles = | 
|  | 346 | findOccurrencesOutsideFile(RenameDecl, MainFilePath, Index); | 
| Haojian Wu | 3c3aca2 | 2019-11-28 12:47:32 +0100 | [diff] [blame] | 347 | if (!AffectedFiles) | 
|  | 348 | return AffectedFiles.takeError(); | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 349 | FileEdits Results; | 
| Haojian Wu | 3c3aca2 | 2019-11-28 12:47:32 +0100 | [diff] [blame] | 350 | for (auto &FileAndOccurrences : *AffectedFiles) { | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 351 | llvm::StringRef FilePath = FileAndOccurrences.first(); | 
|  | 352 |  | 
|  | 353 | auto AffectedFileCode = GetFileContent(FilePath); | 
|  | 354 | if (!AffectedFileCode) { | 
|  | 355 | elog("Fail to read file content: {0}", AffectedFileCode.takeError()); | 
|  | 356 | continue; | 
|  | 357 | } | 
| Haojian Wu | 66ab932 | 2019-11-28 16:48:49 +0100 | [diff] [blame] | 358 | auto RenameEdit = | 
|  | 359 | buildRenameEdit(FilePath, *AffectedFileCode, | 
|  | 360 | std::move(FileAndOccurrences.second), NewName); | 
| Haojian Wu | 8805316 | 2019-11-19 15:23:36 +0100 | [diff] [blame] | 361 | if (!RenameEdit) { | 
|  | 362 | return llvm::make_error<llvm::StringError>( | 
|  | 363 | llvm::formatv("fail to build rename edit for file {0}: {1}", FilePath, | 
|  | 364 | llvm::toString(RenameEdit.takeError())), | 
|  | 365 | llvm::inconvertibleErrorCode()); | 
|  | 366 | } | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 367 | if (!RenameEdit->Replacements.empty()) | 
|  | 368 | Results.insert({FilePath, std::move(*RenameEdit)}); | 
|  | 369 | } | 
|  | 370 | return Results; | 
|  | 371 | } | 
|  | 372 |  | 
|  | 373 | } // namespace | 
|  | 374 |  | 
|  | 375 | llvm::Expected<FileEdits> rename(const RenameInputs &RInputs) { | 
|  | 376 | ParsedAST &AST = RInputs.AST; | 
|  | 377 | const SourceManager &SM = AST.getSourceManager(); | 
|  | 378 | llvm::StringRef MainFileCode = SM.getBufferData(SM.getMainFileID()); | 
|  | 379 | auto GetFileContent = [&RInputs, | 
|  | 380 | &SM](PathRef AbsPath) -> llvm::Expected<std::string> { | 
|  | 381 | llvm::Optional<std::string> DirtyBuffer; | 
|  | 382 | if (RInputs.GetDirtyBuffer && | 
|  | 383 | (DirtyBuffer = RInputs.GetDirtyBuffer(AbsPath))) | 
|  | 384 | return std::move(*DirtyBuffer); | 
|  | 385 |  | 
|  | 386 | auto Content = | 
|  | 387 | SM.getFileManager().getVirtualFileSystem().getBufferForFile(AbsPath); | 
|  | 388 | if (!Content) | 
|  | 389 | return llvm::createStringError( | 
|  | 390 | llvm::inconvertibleErrorCode(), | 
|  | 391 | llvm::formatv("Fail to open file {0}: {1}", AbsPath, | 
|  | 392 | Content.getError().message())); | 
|  | 393 | if (!*Content) | 
|  | 394 | return llvm::createStringError( | 
|  | 395 | llvm::inconvertibleErrorCode(), | 
|  | 396 | llvm::formatv("Got no buffer for file {0}", AbsPath)); | 
|  | 397 |  | 
|  | 398 | return (*Content)->getBuffer().str(); | 
|  | 399 | }; | 
|  | 400 | SourceLocation SourceLocationBeg = | 
|  | 401 | SM.getMacroArgExpandedLocation(getBeginningOfIdentifier( | 
|  | 402 | RInputs.Pos, SM, AST.getASTContext().getLangOpts())); | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame^] | 403 | // FIXME: Renaming macros is not supported yet, the macro-handling code should | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 404 | // be moved to rename tooling library. | 
|  | 405 | if (locateMacroAt(SourceLocationBeg, AST.getPreprocessor())) | 
|  | 406 | return makeError(ReasonToReject::UnsupportedSymbol); | 
|  | 407 |  | 
|  | 408 | auto DeclsUnderCursor = locateDeclAt(AST, SourceLocationBeg); | 
|  | 409 | if (DeclsUnderCursor.empty()) | 
|  | 410 | return makeError(ReasonToReject::NoSymbolFound); | 
|  | 411 | if (DeclsUnderCursor.size() > 1) | 
|  | 412 | return makeError(ReasonToReject::AmbiguousSymbol); | 
|  | 413 |  | 
|  | 414 | const auto *RenameDecl = llvm::dyn_cast<NamedDecl>(*DeclsUnderCursor.begin()); | 
|  | 415 | if (!RenameDecl) | 
|  | 416 | return makeError(ReasonToReject::UnsupportedSymbol); | 
|  | 417 |  | 
|  | 418 | auto Reject = | 
|  | 419 | renameable(*RenameDecl->getCanonicalDecl(), RInputs.MainFilePath, | 
|  | 420 | RInputs.Index, RInputs.AllowCrossFile); | 
|  | 421 | if (Reject) | 
|  | 422 | return makeError(*Reject); | 
|  | 423 |  | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame^] | 424 | // We have two implementations of the rename: | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 425 | //   - AST-based rename: used for renaming local symbols, e.g. variables | 
|  | 426 | //     defined in a function body; | 
|  | 427 | //   - index-based rename: used for renaming non-local symbols, and not | 
|  | 428 | //     feasible for local symbols (as by design our index don't index these | 
|  | 429 | //     symbols by design; | 
|  | 430 | // To make cross-file rename work for local symbol, we use a hybrid solution: | 
|  | 431 | //   - run AST-based rename on the main file; | 
|  | 432 | //   - run index-based rename on other affected files; | 
|  | 433 | auto MainFileRenameEdit = renameWithinFile(AST, *RenameDecl, RInputs.NewName); | 
|  | 434 | if (!MainFileRenameEdit) | 
|  | 435 | return MainFileRenameEdit.takeError(); | 
|  | 436 |  | 
|  | 437 | if (!RInputs.AllowCrossFile) { | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame^] | 438 | // Within-file rename: just return the main file results. | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 439 | return FileEdits( | 
|  | 440 | {std::make_pair(RInputs.MainFilePath, | 
|  | 441 | Edit{MainFileCode, std::move(*MainFileRenameEdit)})}); | 
|  | 442 | } | 
|  | 443 |  | 
|  | 444 | FileEdits Results; | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame^] | 445 | // Renameable safely guards us that at this point we are renaming a local | 
|  | 446 | // symbol if we don't have index. | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 447 | if (RInputs.Index) { | 
|  | 448 | auto OtherFilesEdits = | 
|  | 449 | renameOutsideFile(*RenameDecl, RInputs.MainFilePath, RInputs.NewName, | 
|  | 450 | *RInputs.Index, GetFileContent); | 
|  | 451 | if (!OtherFilesEdits) | 
|  | 452 | return OtherFilesEdits.takeError(); | 
|  | 453 | Results = std::move(*OtherFilesEdits); | 
|  | 454 | } | 
|  | 455 | // Attach the rename edits for the main file. | 
|  | 456 | Results.try_emplace(RInputs.MainFilePath, MainFileCode, | 
|  | 457 | std::move(*MainFileRenameEdit)); | 
|  | 458 | return Results; | 
|  | 459 | } | 
|  | 460 |  | 
| Haojian Wu | 66ab932 | 2019-11-28 16:48:49 +0100 | [diff] [blame] | 461 | llvm::Expected<Edit> buildRenameEdit(llvm::StringRef AbsFilePath, | 
|  | 462 | llvm::StringRef InitialCode, | 
| Haojian Wu | 8805316 | 2019-11-19 15:23:36 +0100 | [diff] [blame] | 463 | std::vector<Range> Occurrences, | 
|  | 464 | llvm::StringRef NewName) { | 
|  | 465 | llvm::sort(Occurrences); | 
|  | 466 | // These two always correspond to the same position. | 
|  | 467 | Position LastPos{0, 0}; | 
|  | 468 | size_t LastOffset = 0; | 
|  | 469 |  | 
|  | 470 | auto Offset = [&](const Position &P) -> llvm::Expected<size_t> { | 
|  | 471 | assert(LastPos <= P && "malformed input"); | 
|  | 472 | Position Shifted = { | 
|  | 473 | P.line - LastPos.line, | 
|  | 474 | P.line > LastPos.line ? P.character : P.character - LastPos.character}; | 
|  | 475 | auto ShiftedOffset = | 
|  | 476 | positionToOffset(InitialCode.substr(LastOffset), Shifted); | 
|  | 477 | if (!ShiftedOffset) | 
|  | 478 | return llvm::make_error<llvm::StringError>( | 
|  | 479 | llvm::formatv("fail to convert the position {0} to offset ({1})", P, | 
|  | 480 | llvm::toString(ShiftedOffset.takeError())), | 
|  | 481 | llvm::inconvertibleErrorCode()); | 
|  | 482 | LastPos = P; | 
|  | 483 | LastOffset += *ShiftedOffset; | 
|  | 484 | return LastOffset; | 
|  | 485 | }; | 
|  | 486 |  | 
|  | 487 | std::vector<std::pair</*start*/ size_t, /*end*/ size_t>> OccurrencesOffsets; | 
|  | 488 | for (const auto &R : Occurrences) { | 
|  | 489 | auto StartOffset = Offset(R.start); | 
|  | 490 | if (!StartOffset) | 
|  | 491 | return StartOffset.takeError(); | 
|  | 492 | auto EndOffset = Offset(R.end); | 
|  | 493 | if (!EndOffset) | 
|  | 494 | return EndOffset.takeError(); | 
|  | 495 | OccurrencesOffsets.push_back({*StartOffset, *EndOffset}); | 
|  | 496 | } | 
|  | 497 |  | 
|  | 498 | tooling::Replacements RenameEdit; | 
|  | 499 | for (const auto &R : OccurrencesOffsets) { | 
|  | 500 | auto ByteLength = R.second - R.first; | 
|  | 501 | if (auto Err = RenameEdit.add( | 
| Haojian Wu | 66ab932 | 2019-11-28 16:48:49 +0100 | [diff] [blame] | 502 | tooling::Replacement(AbsFilePath, R.first, ByteLength, NewName))) | 
| Haojian Wu | 8805316 | 2019-11-19 15:23:36 +0100 | [diff] [blame] | 503 | return std::move(Err); | 
|  | 504 | } | 
|  | 505 | return Edit(InitialCode, std::move(RenameEdit)); | 
|  | 506 | } | 
|  | 507 |  | 
| Sam McCall | c094912 | 2019-05-07 07:11:56 +0000 | [diff] [blame] | 508 | } // namespace clangd | 
|  | 509 | } // namespace clang |