| 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" | 
| Kirill Bobyrev | ec61882 | 2019-12-12 13:10:59 +0100 | [diff] [blame] | 21 | #include "clang/Tooling/Syntax/Tokens.h" | 
| Haojian Wu | 891f822 | 2019-12-09 17:00:51 +0100 | [diff] [blame] | 22 | #include "llvm/ADT/None.h" | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 23 | #include "llvm/ADT/STLExtras.h" | 
| Kirill Bobyrev | ec61882 | 2019-12-12 13:10:59 +0100 | [diff] [blame] | 24 | #include "llvm/Support/Casting.h" | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 25 | #include "llvm/Support/Error.h" | 
| Haojian Wu | 8805316 | 2019-11-19 15:23:36 +0100 | [diff] [blame] | 26 | #include "llvm/Support/FormatVariadic.h" | 
| Haojian Wu | 891f822 | 2019-12-09 17:00:51 +0100 | [diff] [blame] | 27 | #include <algorithm> | 
| Sam McCall | c094912 | 2019-05-07 07:11:56 +0000 | [diff] [blame] | 28 |  | 
|  | 29 | namespace clang { | 
|  | 30 | namespace clangd { | 
|  | 31 | namespace { | 
|  | 32 |  | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 33 | llvm::Optional<std::string> filePath(const SymbolLocation &Loc, | 
|  | 34 | llvm::StringRef HintFilePath) { | 
|  | 35 | if (!Loc) | 
|  | 36 | return None; | 
| Haojian Wu | f821ab8 | 2019-10-29 10:49:27 +0100 | [diff] [blame] | 37 | auto Path = URI::resolve(Loc.FileURI, HintFilePath); | 
|  | 38 | if (!Path) { | 
|  | 39 | elog("Could not resolve URI {0}: {1}", Loc.FileURI, Path.takeError()); | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 40 | return None; | 
|  | 41 | } | 
| Haojian Wu | f821ab8 | 2019-10-29 10:49:27 +0100 | [diff] [blame] | 42 |  | 
|  | 43 | return *Path; | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 44 | } | 
|  | 45 |  | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 46 | // Returns true if the given location is expanded from any macro body. | 
|  | 47 | bool isInMacroBody(const SourceManager &SM, SourceLocation Loc) { | 
|  | 48 | while (Loc.isMacroID()) { | 
|  | 49 | if (SM.isMacroBodyExpansion(Loc)) | 
|  | 50 | return true; | 
|  | 51 | Loc = SM.getImmediateMacroCallerLoc(Loc); | 
|  | 52 | } | 
|  | 53 |  | 
|  | 54 | return false; | 
|  | 55 | } | 
|  | 56 |  | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 57 | // 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] | 58 | llvm::Optional<std::string> getOtherRefFile(const Decl &D, StringRef MainFile, | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 59 | const SymbolIndex &Index) { | 
|  | 60 | RefsRequest Req; | 
|  | 61 | // We limit the number of results, this is a correctness/performance | 
|  | 62 | // tradeoff. We expect the number of symbol references in the current file | 
|  | 63 | // is smaller than the limit. | 
|  | 64 | Req.Limit = 100; | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 65 | Req.IDs.insert(*getSymbolID(&D)); | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 66 | llvm::Optional<std::string> OtherFile; | 
|  | 67 | Index.refs(Req, [&](const Ref &R) { | 
|  | 68 | if (OtherFile) | 
|  | 69 | return; | 
|  | 70 | if (auto RefFilePath = filePath(R.Location, /*HintFilePath=*/MainFile)) { | 
|  | 71 | if (*RefFilePath != MainFile) | 
|  | 72 | OtherFile = *RefFilePath; | 
|  | 73 | } | 
|  | 74 | }); | 
|  | 75 | return OtherFile; | 
|  | 76 | } | 
|  | 77 |  | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 78 | llvm::DenseSet<const Decl *> locateDeclAt(ParsedAST &AST, | 
|  | 79 | SourceLocation TokenStartLoc) { | 
|  | 80 | unsigned Offset = | 
|  | 81 | AST.getSourceManager().getDecomposedSpellingLoc(TokenStartLoc).second; | 
|  | 82 |  | 
| Sam McCall | 6af1ad2 | 2019-12-16 19:07:49 +0100 | [diff] [blame] | 83 | SelectionTree Selection(AST.getASTContext(), AST.getTokens(), Offset); | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 84 | const SelectionTree::Node *SelectedNode = Selection.commonAncestor(); | 
|  | 85 | if (!SelectedNode) | 
|  | 86 | return {}; | 
|  | 87 |  | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 88 | llvm::DenseSet<const Decl *> Result; | 
| Sam McCall | f06f439 | 2020-01-03 17:26:33 +0100 | [diff] [blame^] | 89 | for (const NamedDecl *D : | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 90 | targetDecl(SelectedNode->ASTNode, | 
| Kirill Bobyrev | ec61882 | 2019-12-12 13:10:59 +0100 | [diff] [blame] | 91 | DeclRelation::Alias | DeclRelation::TemplatePattern)) { | 
| Kirill Bobyrev | ec61882 | 2019-12-12 13:10:59 +0100 | [diff] [blame] | 92 | // Get to CXXRecordDecl from constructor or destructor. | 
| Sam McCall | f06f439 | 2020-01-03 17:26:33 +0100 | [diff] [blame^] | 93 | D = tooling::getCanonicalSymbolDeclaration(D); | 
|  | 94 | Result.insert(D); | 
| Kirill Bobyrev | ec61882 | 2019-12-12 13:10:59 +0100 | [diff] [blame] | 95 | } | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 96 | return Result; | 
|  | 97 | } | 
|  | 98 |  | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 99 | enum ReasonToReject { | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 100 | NoSymbolFound, | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 101 | NoIndexProvided, | 
|  | 102 | NonIndexable, | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 103 | UsedOutsideFile, // for within-file rename only. | 
| Haojian Wu | 442a120 | 2019-06-26 08:10:26 +0000 | [diff] [blame] | 104 | UnsupportedSymbol, | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 105 | AmbiguousSymbol, | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 106 | }; | 
|  | 107 |  | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 108 | llvm::Optional<ReasonToReject> renameable(const Decl &RenameDecl, | 
|  | 109 | StringRef MainFilePath, | 
|  | 110 | const SymbolIndex *Index, | 
|  | 111 | bool CrossFile) { | 
|  | 112 | // Filter out symbols that are unsupported in both rename modes. | 
| Haojian Wu | 93a825c | 2019-06-27 13:24:10 +0000 | [diff] [blame] | 113 | if (llvm::isa<NamespaceDecl>(&RenameDecl)) | 
| Haojian Wu | 442a120 | 2019-06-26 08:10:26 +0000 | [diff] [blame] | 114 | return ReasonToReject::UnsupportedSymbol; | 
| Haojian Wu | af28bb6 | 2019-09-16 10:16:56 +0000 | [diff] [blame] | 115 | if (const auto *FD = llvm::dyn_cast<FunctionDecl>(&RenameDecl)) { | 
|  | 116 | if (FD->isOverloadedOperator()) | 
|  | 117 | return ReasonToReject::UnsupportedSymbol; | 
|  | 118 | } | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 119 | // function-local symbols is safe to rename. | 
| Haojian Wu | 93a825c | 2019-06-27 13:24:10 +0000 | [diff] [blame] | 120 | if (RenameDecl.getParentFunctionOrMethod()) | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 121 | return None; | 
|  | 122 |  | 
| Haojian Wu | 902dc6c | 2019-11-29 14:58:44 +0100 | [diff] [blame] | 123 | // Check whether the symbol being rename is indexable. | 
|  | 124 | auto &ASTCtx = RenameDecl.getASTContext(); | 
|  | 125 | bool MainFileIsHeader = isHeaderFile(MainFilePath, ASTCtx.getLangOpts()); | 
|  | 126 | bool DeclaredInMainFile = | 
|  | 127 | isInsideMainFile(RenameDecl.getBeginLoc(), ASTCtx.getSourceManager()); | 
|  | 128 | bool IsMainFileOnly = true; | 
|  | 129 | if (MainFileIsHeader) | 
|  | 130 | // main file is a header, the symbol can't be main file only. | 
|  | 131 | IsMainFileOnly = false; | 
|  | 132 | else if (!DeclaredInMainFile) | 
|  | 133 | IsMainFileOnly = false; | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 134 | bool IsIndexable = | 
|  | 135 | isa<NamedDecl>(RenameDecl) && | 
|  | 136 | SymbolCollector::shouldCollectSymbol( | 
|  | 137 | cast<NamedDecl>(RenameDecl), RenameDecl.getASTContext(), | 
| Haojian Wu | 902dc6c | 2019-11-29 14:58:44 +0100 | [diff] [blame] | 138 | SymbolCollector::Options(), IsMainFileOnly); | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 139 | if (!IsIndexable) // If the symbol is not indexable, we disallow rename. | 
|  | 140 | return ReasonToReject::NonIndexable; | 
|  | 141 |  | 
|  | 142 | if (!CrossFile) { | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 143 | if (!DeclaredInMainFile) | 
|  | 144 | // We are sure the symbol is used externally, bail out early. | 
|  | 145 | return ReasonToReject::UsedOutsideFile; | 
|  | 146 |  | 
|  | 147 | // If the symbol is declared in the main file (which is not a header), we | 
|  | 148 | // rename it. | 
|  | 149 | if (!MainFileIsHeader) | 
|  | 150 | return None; | 
|  | 151 |  | 
|  | 152 | if (!Index) | 
|  | 153 | return ReasonToReject::NoIndexProvided; | 
|  | 154 |  | 
|  | 155 | auto OtherFile = getOtherRefFile(RenameDecl, MainFilePath, *Index); | 
|  | 156 | // If the symbol is indexable and has no refs from other files in the index, | 
|  | 157 | // we rename it. | 
|  | 158 | if (!OtherFile) | 
|  | 159 | return None; | 
|  | 160 | // If the symbol is indexable and has refs from other files in the index, | 
|  | 161 | // we disallow rename. | 
|  | 162 | return ReasonToReject::UsedOutsideFile; | 
|  | 163 | } | 
|  | 164 |  | 
|  | 165 | assert(CrossFile); | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 166 | if (!Index) | 
|  | 167 | return ReasonToReject::NoIndexProvided; | 
|  | 168 |  | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 169 | // Blacklist symbols that are not supported yet in cross-file mode due to the | 
|  | 170 | // limitations of our index. | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame] | 171 | // FIXME: Renaming templates requires to rename all related specializations, | 
|  | 172 | // our index doesn't have this information. | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 173 | if (RenameDecl.getDescribedTemplate()) | 
|  | 174 | return ReasonToReject::UnsupportedSymbol; | 
|  | 175 |  | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame] | 176 | // FIXME: Renaming virtual methods requires to rename all overridens in | 
|  | 177 | // subclasses, our index doesn't have this information. | 
|  | 178 | // Note: Within-file rename does support this through the AST. | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 179 | if (const auto *S = llvm::dyn_cast<CXXMethodDecl>(&RenameDecl)) { | 
|  | 180 | if (S->isVirtual()) | 
|  | 181 | return ReasonToReject::UnsupportedSymbol; | 
|  | 182 | } | 
|  | 183 | return None; | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 184 | } | 
|  | 185 |  | 
| Haojian Wu | 9d34f45 | 2019-07-01 09:26:48 +0000 | [diff] [blame] | 186 | llvm::Error makeError(ReasonToReject Reason) { | 
|  | 187 | auto Message = [](ReasonToReject Reason) { | 
|  | 188 | switch (Reason) { | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 189 | case ReasonToReject::NoSymbolFound: | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 190 | return "there is no symbol at the given location"; | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 191 | case ReasonToReject::NoIndexProvided: | 
| Haojian Wu | 08cce03 | 2019-11-28 11:39:48 +0100 | [diff] [blame] | 192 | return "no index provided"; | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 193 | case ReasonToReject::UsedOutsideFile: | 
| Haojian Wu | 9d34f45 | 2019-07-01 09:26:48 +0000 | [diff] [blame] | 194 | return "the symbol is used outside main file"; | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 195 | case ReasonToReject::NonIndexable: | 
| Haojian Wu | 9d34f45 | 2019-07-01 09:26:48 +0000 | [diff] [blame] | 196 | return "symbol may be used in other files (not eligible for indexing)"; | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 197 | case ReasonToReject::UnsupportedSymbol: | 
| Haojian Wu | 9d34f45 | 2019-07-01 09:26:48 +0000 | [diff] [blame] | 198 | return "symbol is not a supported kind (e.g. namespace, macro)"; | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 199 | case AmbiguousSymbol: | 
|  | 200 | return "there are multiple symbols at the given location"; | 
| Haojian Wu | 9d34f45 | 2019-07-01 09:26:48 +0000 | [diff] [blame] | 201 | } | 
|  | 202 | llvm_unreachable("unhandled reason kind"); | 
|  | 203 | }; | 
|  | 204 | return llvm::make_error<llvm::StringError>( | 
|  | 205 | llvm::formatv("Cannot rename symbol: {0}", Message(Reason)), | 
|  | 206 | llvm::inconvertibleErrorCode()); | 
|  | 207 | } | 
|  | 208 |  | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 209 | // Return all rename occurrences in the main file. | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 210 | std::vector<SourceLocation> findOccurrencesWithinFile(ParsedAST &AST, | 
|  | 211 | const NamedDecl &ND) { | 
| Kirill Bobyrev | ec61882 | 2019-12-12 13:10:59 +0100 | [diff] [blame] | 212 | // If the cursor is at the underlying CXXRecordDecl of the | 
|  | 213 | // ClassTemplateDecl, ND will be the CXXRecordDecl. In this case, we need to | 
|  | 214 | // get the primary template maunally. | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 215 | // getUSRsForDeclaration will find other related symbols, e.g. virtual and its | 
|  | 216 | // overriddens, primary template and all explicit specializations. | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame] | 217 | // FIXME: Get rid of the remaining tooling APIs. | 
| Kirill Bobyrev | ec61882 | 2019-12-12 13:10:59 +0100 | [diff] [blame] | 218 | const auto RenameDecl = | 
|  | 219 | ND.getDescribedTemplate() ? ND.getDescribedTemplate() : &ND; | 
|  | 220 | std::vector<std::string> RenameUSRs = | 
|  | 221 | tooling::getUSRsForDeclaration(RenameDecl, AST.getASTContext()); | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 222 | llvm::DenseSet<SymbolID> TargetIDs; | 
|  | 223 | for (auto &USR : RenameUSRs) | 
|  | 224 | TargetIDs.insert(SymbolID(USR)); | 
|  | 225 |  | 
|  | 226 | std::vector<SourceLocation> Results; | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 227 | for (Decl *TopLevelDecl : AST.getLocalTopLevelDecls()) { | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 228 | findExplicitReferences(TopLevelDecl, [&](ReferenceLoc Ref) { | 
|  | 229 | if (Ref.Targets.empty()) | 
|  | 230 | return; | 
|  | 231 | for (const auto *Target : Ref.Targets) { | 
|  | 232 | auto ID = getSymbolID(Target); | 
|  | 233 | if (!ID || TargetIDs.find(*ID) == TargetIDs.end()) | 
|  | 234 | return; | 
|  | 235 | } | 
|  | 236 | Results.push_back(Ref.NameLoc); | 
|  | 237 | }); | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 238 | } | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 239 |  | 
|  | 240 | return Results; | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 241 | } | 
|  | 242 |  | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 243 | // AST-based rename, it renames all occurrences in the main file. | 
| Sam McCall | c094912 | 2019-05-07 07:11:56 +0000 | [diff] [blame] | 244 | llvm::Expected<tooling::Replacements> | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 245 | renameWithinFile(ParsedAST &AST, const NamedDecl &RenameDecl, | 
|  | 246 | llvm::StringRef NewName) { | 
| Sam McCall | b2a984c0 | 2019-09-04 10:15:27 +0000 | [diff] [blame] | 247 | const SourceManager &SM = AST.getSourceManager(); | 
| Haojian Wu | 7276a44 | 2019-06-25 08:43:17 +0000 | [diff] [blame] | 248 |  | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 249 | tooling::Replacements FilteredChanges; | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 250 | for (SourceLocation Loc : findOccurrencesWithinFile(AST, RenameDecl)) { | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 251 | SourceLocation RenameLoc = Loc; | 
|  | 252 | // We don't rename in any macro bodies, but we allow rename the symbol | 
|  | 253 | // spelled in a top-level macro argument in the main file. | 
|  | 254 | if (RenameLoc.isMacroID()) { | 
|  | 255 | if (isInMacroBody(SM, RenameLoc)) | 
|  | 256 | continue; | 
|  | 257 | RenameLoc = SM.getSpellingLoc(Loc); | 
|  | 258 | } | 
|  | 259 | // Filter out locations not from main file. | 
|  | 260 | // We traverse only main file decls, but locations could come from an | 
|  | 261 | // non-preamble #include file e.g. | 
|  | 262 | //   void test() { | 
|  | 263 | //     int f^oo; | 
|  | 264 | //     #include "use_foo.inc" | 
|  | 265 | //   } | 
|  | 266 | if (!isInsideMainFile(RenameLoc, SM)) | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 267 | continue; | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 268 | if (auto Err = FilteredChanges.add(tooling::Replacement( | 
| Haojian Wu | 7db1230 | 2019-11-19 10:10:43 +0100 | [diff] [blame] | 269 | SM, CharSourceRange::getTokenRange(RenameLoc), NewName))) | 
| Haojian Wu | 8b49173 | 2019-08-09 10:55:22 +0000 | [diff] [blame] | 270 | return std::move(Err); | 
| Sam McCall | c094912 | 2019-05-07 07:11:56 +0000 | [diff] [blame] | 271 | } | 
|  | 272 | return FilteredChanges; | 
|  | 273 | } | 
|  | 274 |  | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 275 | Range toRange(const SymbolLocation &L) { | 
|  | 276 | Range R; | 
|  | 277 | R.start.line = L.Start.line(); | 
|  | 278 | R.start.character = L.Start.column(); | 
|  | 279 | R.end.line = L.End.line(); | 
|  | 280 | R.end.character = L.End.column(); | 
|  | 281 | return R; | 
| Bill Wendling | 936de1c | 2019-12-02 14:05:28 -0800 | [diff] [blame] | 282 | } | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 283 |  | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame] | 284 | // Return all rename occurrences (using the index) outside of the main file, | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 285 | // grouped by the absolute file path. | 
| Haojian Wu | 3c3aca2 | 2019-11-28 12:47:32 +0100 | [diff] [blame] | 286 | llvm::Expected<llvm::StringMap<std::vector<Range>>> | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 287 | findOccurrencesOutsideFile(const NamedDecl &RenameDecl, | 
|  | 288 | llvm::StringRef MainFile, const SymbolIndex &Index) { | 
|  | 289 | RefsRequest RQuest; | 
|  | 290 | RQuest.IDs.insert(*getSymbolID(&RenameDecl)); | 
|  | 291 |  | 
| Haojian Wu | 3c3aca2 | 2019-11-28 12:47:32 +0100 | [diff] [blame] | 292 | // Absolute file path => rename occurrences in that file. | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 293 | llvm::StringMap<std::vector<Range>> AffectedFiles; | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame] | 294 | // FIXME: Make the limit customizable. | 
| Haojian Wu | 3c3aca2 | 2019-11-28 12:47:32 +0100 | [diff] [blame] | 295 | static constexpr size_t MaxLimitFiles = 50; | 
|  | 296 | bool HasMore = Index.refs(RQuest, [&](const Ref &R) { | 
|  | 297 | if (AffectedFiles.size() > MaxLimitFiles) | 
|  | 298 | return; | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 299 | if (auto RefFilePath = filePath(R.Location, /*HintFilePath=*/MainFile)) { | 
|  | 300 | if (*RefFilePath != MainFile) | 
|  | 301 | AffectedFiles[*RefFilePath].push_back(toRange(R.Location)); | 
|  | 302 | } | 
|  | 303 | }); | 
| Haojian Wu | 3c3aca2 | 2019-11-28 12:47:32 +0100 | [diff] [blame] | 304 |  | 
|  | 305 | if (AffectedFiles.size() > MaxLimitFiles) | 
|  | 306 | return llvm::make_error<llvm::StringError>( | 
|  | 307 | llvm::formatv("The number of affected files exceeds the max limit {0}", | 
|  | 308 | MaxLimitFiles), | 
|  | 309 | llvm::inconvertibleErrorCode()); | 
|  | 310 | if (HasMore) { | 
|  | 311 | return llvm::make_error<llvm::StringError>( | 
|  | 312 | llvm::formatv("The symbol {0} has too many occurrences", | 
|  | 313 | RenameDecl.getQualifiedNameAsString()), | 
|  | 314 | llvm::inconvertibleErrorCode()); | 
|  | 315 | } | 
| Haojian Wu | f0004aa | 2019-12-10 22:15:29 +0100 | [diff] [blame] | 316 | // Sort and deduplicate the results, in case that index returns duplications. | 
|  | 317 | for (auto &FileAndOccurrences : AffectedFiles) { | 
|  | 318 | auto &Ranges = FileAndOccurrences.getValue(); | 
|  | 319 | llvm::sort(Ranges); | 
|  | 320 | Ranges.erase(std::unique(Ranges.begin(), Ranges.end()), Ranges.end()); | 
|  | 321 | } | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 322 | return AffectedFiles; | 
|  | 323 | } | 
|  | 324 |  | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 325 | // Index-based rename, it renames all occurrences outside of the main file. | 
|  | 326 | // | 
|  | 327 | // The cross-file rename is purely based on the index, as we don't want to | 
|  | 328 | // build all ASTs for affected files, which may cause a performance hit. | 
|  | 329 | // We choose to trade off some correctness for performance and scalability. | 
|  | 330 | // | 
|  | 331 | // Clangd builds a dynamic index for all opened files on top of the static | 
|  | 332 | // index of the whole codebase. Dynamic index is up-to-date (respects dirty | 
|  | 333 | // buffers) as long as clangd finishes processing opened files, while static | 
|  | 334 | // index (background index) is relatively stale. We choose the dirty buffers | 
|  | 335 | // as the file content we rename on, and fallback to file content on disk if | 
|  | 336 | // there is no dirty buffer. | 
|  | 337 | // | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame] | 338 | // FIXME: Add range patching heuristics to detect staleness of the index, and | 
|  | 339 | // report to users. | 
|  | 340 | // FIXME: Our index may return implicit references, which are not eligible for | 
|  | 341 | // rename, we should filter out these references. | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 342 | llvm::Expected<FileEdits> renameOutsideFile( | 
|  | 343 | const NamedDecl &RenameDecl, llvm::StringRef MainFilePath, | 
|  | 344 | llvm::StringRef NewName, const SymbolIndex &Index, | 
|  | 345 | llvm::function_ref<llvm::Expected<std::string>(PathRef)> GetFileContent) { | 
|  | 346 | auto AffectedFiles = | 
|  | 347 | findOccurrencesOutsideFile(RenameDecl, MainFilePath, Index); | 
| Haojian Wu | 3c3aca2 | 2019-11-28 12:47:32 +0100 | [diff] [blame] | 348 | if (!AffectedFiles) | 
|  | 349 | return AffectedFiles.takeError(); | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 350 | FileEdits Results; | 
| Haojian Wu | 3c3aca2 | 2019-11-28 12:47:32 +0100 | [diff] [blame] | 351 | for (auto &FileAndOccurrences : *AffectedFiles) { | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 352 | llvm::StringRef FilePath = FileAndOccurrences.first(); | 
|  | 353 |  | 
|  | 354 | auto AffectedFileCode = GetFileContent(FilePath); | 
|  | 355 | if (!AffectedFileCode) { | 
|  | 356 | elog("Fail to read file content: {0}", AffectedFileCode.takeError()); | 
|  | 357 | continue; | 
|  | 358 | } | 
| Haojian Wu | 891f822 | 2019-12-09 17:00:51 +0100 | [diff] [blame] | 359 | auto RenameRanges = | 
|  | 360 | adjustRenameRanges(*AffectedFileCode, RenameDecl.getNameAsString(), | 
|  | 361 | std::move(FileAndOccurrences.second), | 
|  | 362 | RenameDecl.getASTContext().getLangOpts()); | 
|  | 363 | if (!RenameRanges) { | 
| Kirill Bobyrev | 3b9715c | 2019-12-16 10:33:56 +0100 | [diff] [blame] | 364 | // Our heuristics fails to adjust rename ranges to the current state of | 
| Haojian Wu | 891f822 | 2019-12-09 17:00:51 +0100 | [diff] [blame] | 365 | // the file, it is most likely the index is stale, so we give up the | 
|  | 366 | // entire rename. | 
|  | 367 | return llvm::make_error<llvm::StringError>( | 
|  | 368 | llvm::formatv("Index results don't match the content of file {0} " | 
|  | 369 | "(the index may be stale)", | 
|  | 370 | FilePath), | 
|  | 371 | llvm::inconvertibleErrorCode()); | 
|  | 372 | } | 
| Haojian Wu | 66ab932 | 2019-11-28 16:48:49 +0100 | [diff] [blame] | 373 | auto RenameEdit = | 
| Haojian Wu | 891f822 | 2019-12-09 17:00:51 +0100 | [diff] [blame] | 374 | buildRenameEdit(FilePath, *AffectedFileCode, *RenameRanges, NewName); | 
| Haojian Wu | 8805316 | 2019-11-19 15:23:36 +0100 | [diff] [blame] | 375 | if (!RenameEdit) { | 
|  | 376 | return llvm::make_error<llvm::StringError>( | 
|  | 377 | llvm::formatv("fail to build rename edit for file {0}: {1}", FilePath, | 
|  | 378 | llvm::toString(RenameEdit.takeError())), | 
|  | 379 | llvm::inconvertibleErrorCode()); | 
|  | 380 | } | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 381 | if (!RenameEdit->Replacements.empty()) | 
|  | 382 | Results.insert({FilePath, std::move(*RenameEdit)}); | 
|  | 383 | } | 
|  | 384 | return Results; | 
|  | 385 | } | 
|  | 386 |  | 
| Kirill Bobyrev | 3b9715c | 2019-12-16 10:33:56 +0100 | [diff] [blame] | 387 | // A simple edit is either changing line or column, but not both. | 
| Haojian Wu | 891f822 | 2019-12-09 17:00:51 +0100 | [diff] [blame] | 388 | bool impliesSimpleEdit(const Position &LHS, const Position &RHS) { | 
|  | 389 | return LHS.line == RHS.line || LHS.character == RHS.character; | 
|  | 390 | } | 
|  | 391 |  | 
|  | 392 | // Performs a DFS to enumerate all possible near-miss matches. | 
|  | 393 | // It finds the locations where the indexed occurrences are now spelled in | 
|  | 394 | // Lexed occurrences, a near miss is defined as: | 
|  | 395 | //   - a near miss maps all of the **name** occurrences from the index onto a | 
|  | 396 | //     *subset* of lexed occurrences (we allow a single name refers to more | 
|  | 397 | //     than one symbol) | 
|  | 398 | //   - all indexed occurrences must be mapped, and Result must be distinct and | 
|  | 399 | //     preseve order (only support detecting simple edits to ensure a | 
|  | 400 | //     robust mapping) | 
|  | 401 | //   - each indexed -> lexed occurrences mapping correspondence may change the | 
|  | 402 | //     *line* or *column*, but not both (increases chance of a robust mapping) | 
|  | 403 | void findNearMiss( | 
|  | 404 | std::vector<size_t> &PartialMatch, ArrayRef<Range> IndexedRest, | 
|  | 405 | ArrayRef<Range> LexedRest, int LexedIndex, int &Fuel, | 
|  | 406 | llvm::function_ref<void(const std::vector<size_t> &)> MatchedCB) { | 
|  | 407 | if (--Fuel < 0) | 
|  | 408 | return; | 
|  | 409 | if (IndexedRest.size() > LexedRest.size()) | 
|  | 410 | return; | 
|  | 411 | if (IndexedRest.empty()) { | 
|  | 412 | MatchedCB(PartialMatch); | 
|  | 413 | return; | 
|  | 414 | } | 
|  | 415 | if (impliesSimpleEdit(IndexedRest.front().start, LexedRest.front().start)) { | 
|  | 416 | PartialMatch.push_back(LexedIndex); | 
|  | 417 | findNearMiss(PartialMatch, IndexedRest.drop_front(), LexedRest.drop_front(), | 
|  | 418 | LexedIndex + 1, Fuel, MatchedCB); | 
|  | 419 | PartialMatch.pop_back(); | 
|  | 420 | } | 
|  | 421 | findNearMiss(PartialMatch, IndexedRest, LexedRest.drop_front(), | 
|  | 422 | LexedIndex + 1, Fuel, MatchedCB); | 
|  | 423 | } | 
|  | 424 |  | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 425 | } // namespace | 
|  | 426 |  | 
|  | 427 | llvm::Expected<FileEdits> rename(const RenameInputs &RInputs) { | 
|  | 428 | ParsedAST &AST = RInputs.AST; | 
|  | 429 | const SourceManager &SM = AST.getSourceManager(); | 
|  | 430 | llvm::StringRef MainFileCode = SM.getBufferData(SM.getMainFileID()); | 
|  | 431 | auto GetFileContent = [&RInputs, | 
|  | 432 | &SM](PathRef AbsPath) -> llvm::Expected<std::string> { | 
|  | 433 | llvm::Optional<std::string> DirtyBuffer; | 
|  | 434 | if (RInputs.GetDirtyBuffer && | 
|  | 435 | (DirtyBuffer = RInputs.GetDirtyBuffer(AbsPath))) | 
|  | 436 | return std::move(*DirtyBuffer); | 
|  | 437 |  | 
|  | 438 | auto Content = | 
|  | 439 | SM.getFileManager().getVirtualFileSystem().getBufferForFile(AbsPath); | 
|  | 440 | if (!Content) | 
|  | 441 | return llvm::createStringError( | 
|  | 442 | llvm::inconvertibleErrorCode(), | 
|  | 443 | llvm::formatv("Fail to open file {0}: {1}", AbsPath, | 
|  | 444 | Content.getError().message())); | 
|  | 445 | if (!*Content) | 
|  | 446 | return llvm::createStringError( | 
|  | 447 | llvm::inconvertibleErrorCode(), | 
|  | 448 | llvm::formatv("Got no buffer for file {0}", AbsPath)); | 
|  | 449 |  | 
|  | 450 | return (*Content)->getBuffer().str(); | 
|  | 451 | }; | 
| Kirill Bobyrev | ec61882 | 2019-12-12 13:10:59 +0100 | [diff] [blame] | 452 | // Try to find the tokens adjacent to the cursor position. | 
|  | 453 | auto Loc = sourceLocationInMainFile(SM, RInputs.Pos); | 
|  | 454 | if (!Loc) | 
|  | 455 | return Loc.takeError(); | 
|  | 456 | const syntax::Token *IdentifierToken = | 
|  | 457 | spelledIdentifierTouching(*Loc, AST.getTokens()); | 
|  | 458 | // Renames should only triggered on identifiers. | 
|  | 459 | if (!IdentifierToken) | 
|  | 460 | return makeError(ReasonToReject::NoSymbolFound); | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame] | 461 | // FIXME: Renaming macros is not supported yet, the macro-handling code should | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 462 | // be moved to rename tooling library. | 
| Kirill Bobyrev | ec61882 | 2019-12-12 13:10:59 +0100 | [diff] [blame] | 463 | if (locateMacroAt(IdentifierToken->location(), AST.getPreprocessor())) | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 464 | return makeError(ReasonToReject::UnsupportedSymbol); | 
|  | 465 |  | 
| Kirill Bobyrev | ec61882 | 2019-12-12 13:10:59 +0100 | [diff] [blame] | 466 | auto DeclsUnderCursor = locateDeclAt(AST, IdentifierToken->location()); | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 467 | if (DeclsUnderCursor.empty()) | 
|  | 468 | return makeError(ReasonToReject::NoSymbolFound); | 
|  | 469 | if (DeclsUnderCursor.size() > 1) | 
|  | 470 | return makeError(ReasonToReject::AmbiguousSymbol); | 
|  | 471 |  | 
|  | 472 | const auto *RenameDecl = llvm::dyn_cast<NamedDecl>(*DeclsUnderCursor.begin()); | 
|  | 473 | if (!RenameDecl) | 
|  | 474 | return makeError(ReasonToReject::UnsupportedSymbol); | 
|  | 475 |  | 
|  | 476 | auto Reject = | 
|  | 477 | renameable(*RenameDecl->getCanonicalDecl(), RInputs.MainFilePath, | 
|  | 478 | RInputs.Index, RInputs.AllowCrossFile); | 
|  | 479 | if (Reject) | 
|  | 480 | return makeError(*Reject); | 
|  | 481 |  | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame] | 482 | // We have two implementations of the rename: | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 483 | //   - AST-based rename: used for renaming local symbols, e.g. variables | 
|  | 484 | //     defined in a function body; | 
|  | 485 | //   - index-based rename: used for renaming non-local symbols, and not | 
|  | 486 | //     feasible for local symbols (as by design our index don't index these | 
|  | 487 | //     symbols by design; | 
|  | 488 | // To make cross-file rename work for local symbol, we use a hybrid solution: | 
|  | 489 | //   - run AST-based rename on the main file; | 
|  | 490 | //   - run index-based rename on other affected files; | 
|  | 491 | auto MainFileRenameEdit = renameWithinFile(AST, *RenameDecl, RInputs.NewName); | 
|  | 492 | if (!MainFileRenameEdit) | 
|  | 493 | return MainFileRenameEdit.takeError(); | 
|  | 494 |  | 
|  | 495 | if (!RInputs.AllowCrossFile) { | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame] | 496 | // Within-file rename: just return the main file results. | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 497 | return FileEdits( | 
|  | 498 | {std::make_pair(RInputs.MainFilePath, | 
|  | 499 | Edit{MainFileCode, std::move(*MainFileRenameEdit)})}); | 
|  | 500 | } | 
|  | 501 |  | 
|  | 502 | FileEdits Results; | 
| Kirill Bobyrev | 9091f06 | 2019-12-03 10:12:17 +0100 | [diff] [blame] | 503 | // Renameable safely guards us that at this point we are renaming a local | 
|  | 504 | // symbol if we don't have index. | 
| Haojian Wu | 852bafa | 2019-10-23 14:40:20 +0200 | [diff] [blame] | 505 | if (RInputs.Index) { | 
|  | 506 | auto OtherFilesEdits = | 
|  | 507 | renameOutsideFile(*RenameDecl, RInputs.MainFilePath, RInputs.NewName, | 
|  | 508 | *RInputs.Index, GetFileContent); | 
|  | 509 | if (!OtherFilesEdits) | 
|  | 510 | return OtherFilesEdits.takeError(); | 
|  | 511 | Results = std::move(*OtherFilesEdits); | 
|  | 512 | } | 
|  | 513 | // Attach the rename edits for the main file. | 
|  | 514 | Results.try_emplace(RInputs.MainFilePath, MainFileCode, | 
|  | 515 | std::move(*MainFileRenameEdit)); | 
|  | 516 | return Results; | 
|  | 517 | } | 
|  | 518 |  | 
| Haojian Wu | 66ab932 | 2019-11-28 16:48:49 +0100 | [diff] [blame] | 519 | llvm::Expected<Edit> buildRenameEdit(llvm::StringRef AbsFilePath, | 
|  | 520 | llvm::StringRef InitialCode, | 
| Haojian Wu | 8805316 | 2019-11-19 15:23:36 +0100 | [diff] [blame] | 521 | std::vector<Range> Occurrences, | 
|  | 522 | llvm::StringRef NewName) { | 
| Haojian Wu | f0004aa | 2019-12-10 22:15:29 +0100 | [diff] [blame] | 523 | assert(std::is_sorted(Occurrences.begin(), Occurrences.end())); | 
|  | 524 | assert(std::unique(Occurrences.begin(), Occurrences.end()) == | 
|  | 525 | Occurrences.end() && | 
|  | 526 | "Occurrences must be unique"); | 
|  | 527 |  | 
| Haojian Wu | 8805316 | 2019-11-19 15:23:36 +0100 | [diff] [blame] | 528 | // These two always correspond to the same position. | 
|  | 529 | Position LastPos{0, 0}; | 
|  | 530 | size_t LastOffset = 0; | 
|  | 531 |  | 
|  | 532 | auto Offset = [&](const Position &P) -> llvm::Expected<size_t> { | 
|  | 533 | assert(LastPos <= P && "malformed input"); | 
|  | 534 | Position Shifted = { | 
|  | 535 | P.line - LastPos.line, | 
|  | 536 | P.line > LastPos.line ? P.character : P.character - LastPos.character}; | 
|  | 537 | auto ShiftedOffset = | 
|  | 538 | positionToOffset(InitialCode.substr(LastOffset), Shifted); | 
|  | 539 | if (!ShiftedOffset) | 
|  | 540 | return llvm::make_error<llvm::StringError>( | 
|  | 541 | llvm::formatv("fail to convert the position {0} to offset ({1})", P, | 
|  | 542 | llvm::toString(ShiftedOffset.takeError())), | 
|  | 543 | llvm::inconvertibleErrorCode()); | 
|  | 544 | LastPos = P; | 
|  | 545 | LastOffset += *ShiftedOffset; | 
|  | 546 | return LastOffset; | 
|  | 547 | }; | 
|  | 548 |  | 
|  | 549 | std::vector<std::pair</*start*/ size_t, /*end*/ size_t>> OccurrencesOffsets; | 
|  | 550 | for (const auto &R : Occurrences) { | 
|  | 551 | auto StartOffset = Offset(R.start); | 
|  | 552 | if (!StartOffset) | 
|  | 553 | return StartOffset.takeError(); | 
|  | 554 | auto EndOffset = Offset(R.end); | 
|  | 555 | if (!EndOffset) | 
|  | 556 | return EndOffset.takeError(); | 
|  | 557 | OccurrencesOffsets.push_back({*StartOffset, *EndOffset}); | 
|  | 558 | } | 
|  | 559 |  | 
|  | 560 | tooling::Replacements RenameEdit; | 
|  | 561 | for (const auto &R : OccurrencesOffsets) { | 
|  | 562 | auto ByteLength = R.second - R.first; | 
|  | 563 | if (auto Err = RenameEdit.add( | 
| Haojian Wu | 66ab932 | 2019-11-28 16:48:49 +0100 | [diff] [blame] | 564 | tooling::Replacement(AbsFilePath, R.first, ByteLength, NewName))) | 
| Haojian Wu | 8805316 | 2019-11-19 15:23:36 +0100 | [diff] [blame] | 565 | return std::move(Err); | 
|  | 566 | } | 
|  | 567 | return Edit(InitialCode, std::move(RenameEdit)); | 
|  | 568 | } | 
|  | 569 |  | 
| Haojian Wu | 891f822 | 2019-12-09 17:00:51 +0100 | [diff] [blame] | 570 | // Details: | 
|  | 571 | //  - lex the draft code to get all rename candidates, this yields a superset | 
|  | 572 | //    of candidates. | 
|  | 573 | //  - apply range patching heuristics to generate "authoritative" occurrences, | 
|  | 574 | //    cases we consider: | 
|  | 575 | //      (a) index returns a subset of candidates, we use the indexed results. | 
|  | 576 | //        - fully equal, we are sure the index is up-to-date | 
|  | 577 | //        - proper subset, index is correct in most cases? there may be false | 
|  | 578 | //          positives (e.g. candidates got appended), but rename is still safe | 
|  | 579 | //      (b) index returns non-candidate results, we attempt to map the indexed | 
|  | 580 | //          ranges onto candidates in a plausible way (e.g. guess that lines | 
|  | 581 | //          were inserted). If such a "near miss" is found, the rename is still | 
|  | 582 | //          possible | 
|  | 583 | llvm::Optional<std::vector<Range>> | 
|  | 584 | adjustRenameRanges(llvm::StringRef DraftCode, llvm::StringRef Identifier, | 
|  | 585 | std::vector<Range> Indexed, const LangOptions &LangOpts) { | 
|  | 586 | assert(!Indexed.empty()); | 
| Haojian Wu | f0004aa | 2019-12-10 22:15:29 +0100 | [diff] [blame] | 587 | assert(std::is_sorted(Indexed.begin(), Indexed.end())); | 
| Haojian Wu | 891f822 | 2019-12-09 17:00:51 +0100 | [diff] [blame] | 588 | std::vector<Range> Lexed = | 
|  | 589 | collectIdentifierRanges(Identifier, DraftCode, LangOpts); | 
| Haojian Wu | 891f822 | 2019-12-09 17:00:51 +0100 | [diff] [blame] | 590 | llvm::sort(Lexed); | 
|  | 591 | return getMappedRanges(Indexed, Lexed); | 
|  | 592 | } | 
|  | 593 |  | 
|  | 594 | llvm::Optional<std::vector<Range>> getMappedRanges(ArrayRef<Range> Indexed, | 
|  | 595 | ArrayRef<Range> Lexed) { | 
|  | 596 | assert(!Indexed.empty()); | 
|  | 597 | assert(std::is_sorted(Indexed.begin(), Indexed.end())); | 
|  | 598 | assert(std::is_sorted(Lexed.begin(), Lexed.end())); | 
|  | 599 |  | 
|  | 600 | if (Indexed.size() > Lexed.size()) { | 
|  | 601 | vlog("The number of lexed occurrences is less than indexed occurrences"); | 
|  | 602 | return llvm::None; | 
|  | 603 | } | 
|  | 604 | // Fast check for the special subset case. | 
|  | 605 | if (std::includes(Indexed.begin(), Indexed.end(), Lexed.begin(), Lexed.end())) | 
|  | 606 | return Indexed.vec(); | 
|  | 607 |  | 
|  | 608 | std::vector<size_t> Best; | 
|  | 609 | size_t BestCost = std::numeric_limits<size_t>::max(); | 
|  | 610 | bool HasMultiple = 0; | 
|  | 611 | std::vector<size_t> ResultStorage; | 
|  | 612 | int Fuel = 10000; | 
|  | 613 | findNearMiss(ResultStorage, Indexed, Lexed, 0, Fuel, | 
|  | 614 | [&](const std::vector<size_t> &Matched) { | 
|  | 615 | size_t MCost = | 
|  | 616 | renameRangeAdjustmentCost(Indexed, Lexed, Matched); | 
|  | 617 | if (MCost < BestCost) { | 
|  | 618 | BestCost = MCost; | 
|  | 619 | Best = std::move(Matched); | 
|  | 620 | HasMultiple = false; // reset | 
|  | 621 | return; | 
|  | 622 | } | 
|  | 623 | if (MCost == BestCost) | 
|  | 624 | HasMultiple = true; | 
|  | 625 | }); | 
|  | 626 | if (HasMultiple) { | 
|  | 627 | vlog("The best near miss is not unique."); | 
|  | 628 | return llvm::None; | 
|  | 629 | } | 
|  | 630 | if (Best.empty()) { | 
|  | 631 | vlog("Didn't find a near miss."); | 
|  | 632 | return llvm::None; | 
|  | 633 | } | 
|  | 634 | std::vector<Range> Mapped; | 
|  | 635 | for (auto I : Best) | 
|  | 636 | Mapped.push_back(Lexed[I]); | 
|  | 637 | return Mapped; | 
|  | 638 | } | 
|  | 639 |  | 
|  | 640 | // The cost is the sum of the implied edit sizes between successive diffs, only | 
|  | 641 | // simple edits are considered: | 
|  | 642 | //   - insert/remove a line (change line offset) | 
|  | 643 | //   - insert/remove a character on an existing line (change column offset) | 
|  | 644 | // | 
|  | 645 | // Example I, total result is 1 + 1 = 2. | 
|  | 646 | //   diff[0]: line + 1 <- insert a line before edit 0. | 
|  | 647 | //   diff[1]: line + 1 | 
|  | 648 | //   diff[2]: line + 1 | 
|  | 649 | //   diff[3]: line + 2 <- insert a line before edits 2 and 3. | 
|  | 650 | // | 
|  | 651 | // Example II, total result is 1 + 1 + 1 = 3. | 
|  | 652 | //   diff[0]: line + 1  <- insert a line before edit 0. | 
|  | 653 | //   diff[1]: column + 1 <- remove a line between edits 0 and 1, and insert a | 
|  | 654 | //   character on edit 1. | 
|  | 655 | size_t renameRangeAdjustmentCost(ArrayRef<Range> Indexed, ArrayRef<Range> Lexed, | 
|  | 656 | ArrayRef<size_t> MappedIndex) { | 
|  | 657 | assert(Indexed.size() == MappedIndex.size()); | 
|  | 658 | assert(std::is_sorted(Indexed.begin(), Indexed.end())); | 
|  | 659 | assert(std::is_sorted(Lexed.begin(), Lexed.end())); | 
|  | 660 |  | 
|  | 661 | int LastLine = -1; | 
|  | 662 | int LastDLine = 0, LastDColumn = 0; | 
|  | 663 | int Cost = 0; | 
|  | 664 | for (size_t I = 0; I < Indexed.size(); ++I) { | 
|  | 665 | int DLine = Indexed[I].start.line - Lexed[MappedIndex[I]].start.line; | 
|  | 666 | int DColumn = | 
|  | 667 | Indexed[I].start.character - Lexed[MappedIndex[I]].start.character; | 
|  | 668 | int Line = Indexed[I].start.line; | 
|  | 669 | if (Line != LastLine) | 
|  | 670 | LastDColumn = 0; // colmun offsets don't carry cross lines. | 
|  | 671 | Cost += abs(DLine - LastDLine) + abs(DColumn - LastDColumn); | 
|  | 672 | std::tie(LastLine, LastDLine, LastDColumn) = std::tie(Line, DLine, DColumn); | 
|  | 673 | } | 
|  | 674 | return Cost; | 
|  | 675 | } | 
|  | 676 |  | 
| Sam McCall | c094912 | 2019-05-07 07:11:56 +0000 | [diff] [blame] | 677 | } // namespace clangd | 
|  | 678 | } // namespace clang |