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