1 | //===-- Move.cpp - Implement ClangMove functationalities --------*- 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 | |
9 | #include "Move.h" |
10 | #include "HelperDeclRefGraph.h" |
11 | #include "clang/ASTMatchers/ASTMatchers.h" |
12 | #include "clang/Basic/SourceManager.h" |
13 | #include "clang/Format/Format.h" |
14 | #include "clang/Frontend/CompilerInstance.h" |
15 | #include "clang/Lex/Lexer.h" |
16 | #include "clang/Lex/Preprocessor.h" |
17 | #include "clang/Rewrite/Core/Rewriter.h" |
18 | #include "clang/Tooling/Core/Replacement.h" |
19 | #include "llvm/Support/Debug.h" |
20 | #include "llvm/Support/Path.h" |
21 | |
22 | #define DEBUG_TYPE "clang-move" |
23 | |
24 | using namespace clang::ast_matchers; |
25 | |
26 | namespace clang { |
27 | namespace move { |
28 | namespace { |
29 | |
30 | // FIXME: Move to ASTMatchers. |
31 | AST_MATCHER(VarDecl, isStaticDataMember) { return Node.isStaticDataMember(); } |
32 | |
33 | AST_MATCHER(NamedDecl, notInMacro) { return !Node.getLocation().isMacroID(); } |
34 | |
35 | AST_MATCHER_P(Decl, hasOutermostEnclosingClass, |
36 | ast_matchers::internal::Matcher<Decl>, InnerMatcher) { |
37 | const auto *Context = Node.getDeclContext(); |
38 | if (!Context) |
39 | return false; |
40 | while (const auto *NextContext = Context->getParent()) { |
41 | if (isa<NamespaceDecl>(Val: NextContext) || |
42 | isa<TranslationUnitDecl>(Val: NextContext)) |
43 | break; |
44 | Context = NextContext; |
45 | } |
46 | return InnerMatcher.matches(Node: *Decl::castFromDeclContext(Context), Finder, |
47 | Builder); |
48 | } |
49 | |
50 | AST_MATCHER_P(CXXMethodDecl, ofOutermostEnclosingClass, |
51 | ast_matchers::internal::Matcher<CXXRecordDecl>, InnerMatcher) { |
52 | const CXXRecordDecl *Parent = Node.getParent(); |
53 | if (!Parent) |
54 | return false; |
55 | while (const auto *NextParent = |
56 | dyn_cast<CXXRecordDecl>(Parent->getParent())) { |
57 | Parent = NextParent; |
58 | } |
59 | |
60 | return InnerMatcher.matches(Node: *Parent, Finder, Builder); |
61 | } |
62 | |
63 | std::string CleanPath(StringRef PathRef) { |
64 | llvm::SmallString<128> Path(PathRef); |
65 | llvm::sys::path::remove_dots(path&: Path, /*remove_dot_dot=*/true); |
66 | // FIXME: figure out why this is necessary. |
67 | llvm::sys::path::native(path&: Path); |
68 | return std::string(Path); |
69 | } |
70 | |
71 | // Make the Path absolute using the CurrentDir if the Path is not an absolute |
72 | // path. An empty Path will result in an empty string. |
73 | std::string MakeAbsolutePath(StringRef CurrentDir, StringRef Path) { |
74 | if (Path.empty()) |
75 | return "" ; |
76 | llvm::SmallString<128> InitialDirectory(CurrentDir); |
77 | llvm::SmallString<128> AbsolutePath(Path); |
78 | llvm::sys::fs::make_absolute(current_directory: InitialDirectory, path&: AbsolutePath); |
79 | return CleanPath(PathRef: std::move(AbsolutePath)); |
80 | } |
81 | |
82 | // Make the Path absolute using the current working directory of the given |
83 | // SourceManager if the Path is not an absolute path. |
84 | // |
85 | // The Path can be a path relative to the build directory, or retrieved from |
86 | // the SourceManager. |
87 | std::string MakeAbsolutePath(const SourceManager &SM, StringRef Path) { |
88 | llvm::SmallString<128> AbsolutePath(Path); |
89 | if (std::error_code EC = |
90 | SM.getFileManager().getVirtualFileSystem().makeAbsolute(Path&: AbsolutePath)) |
91 | llvm::errs() << "Warning: could not make absolute file: '" << EC.message() |
92 | << '\n'; |
93 | // Handle symbolic link path cases. |
94 | // We are trying to get the real file path of the symlink. |
95 | auto Dir = SM.getFileManager().getOptionalDirectoryRef( |
96 | DirName: llvm::sys::path::parent_path(path: AbsolutePath.str())); |
97 | if (Dir) { |
98 | StringRef DirName = SM.getFileManager().getCanonicalName(Dir: *Dir); |
99 | // FIXME: getCanonicalName might fail to get real path on VFS. |
100 | if (llvm::sys::path::is_absolute(path: DirName)) { |
101 | SmallString<128> AbsoluteFilename; |
102 | llvm::sys::path::append(path&: AbsoluteFilename, a: DirName, |
103 | b: llvm::sys::path::filename(path: AbsolutePath.str())); |
104 | return CleanPath(PathRef: AbsoluteFilename); |
105 | } |
106 | } |
107 | return CleanPath(PathRef: AbsolutePath); |
108 | } |
109 | |
110 | // Matches AST nodes that are expanded within the given AbsoluteFilePath. |
111 | AST_POLYMORPHIC_MATCHER_P(isExpansionInFile, |
112 | AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt, TypeLoc), |
113 | std::string, AbsoluteFilePath) { |
114 | auto &SourceManager = Finder->getASTContext().getSourceManager(); |
115 | auto ExpansionLoc = SourceManager.getExpansionLoc(Loc: Node.getBeginLoc()); |
116 | if (ExpansionLoc.isInvalid()) |
117 | return false; |
118 | auto FileEntry = |
119 | SourceManager.getFileEntryRefForID(FID: SourceManager.getFileID(ExpansionLoc)); |
120 | if (!FileEntry) |
121 | return false; |
122 | return MakeAbsolutePath(SourceManager, FileEntry->getName()) == |
123 | AbsoluteFilePath; |
124 | } |
125 | |
126 | class FindAllIncludes : public PPCallbacks { |
127 | public: |
128 | explicit FindAllIncludes(SourceManager *SM, ClangMoveTool *const MoveTool) |
129 | : SM(*SM), MoveTool(MoveTool) {} |
130 | |
131 | void InclusionDirective(SourceLocation HashLoc, const Token & /*IncludeTok*/, |
132 | StringRef FileName, bool IsAngled, |
133 | CharSourceRange FilenameRange, |
134 | OptionalFileEntryRef /*File*/, StringRef SearchPath, |
135 | StringRef /*RelativePath*/, |
136 | const Module * /*SuggestedModule*/, |
137 | bool /*ModuleImported*/, |
138 | SrcMgr::CharacteristicKind /*FileType*/) override { |
139 | if (auto FileEntry = SM.getFileEntryRefForID(FID: SM.getFileID(SpellingLoc: HashLoc))) |
140 | MoveTool->addIncludes(IncludeHeader: FileName, IsAngled, SearchPath, |
141 | FileName: FileEntry->getName(), IncludeFilenameRange: FilenameRange, SM); |
142 | } |
143 | |
144 | private: |
145 | const SourceManager &SM; |
146 | ClangMoveTool *const MoveTool; |
147 | }; |
148 | |
149 | /// Add a declaration being moved to new.h/cc. Note that the declaration will |
150 | /// also be deleted in old.h/cc. |
151 | void MoveDeclFromOldFileToNewFile(ClangMoveTool *MoveTool, const NamedDecl *D) { |
152 | MoveTool->getMovedDecls().push_back(x: D); |
153 | MoveTool->addRemovedDecl(Decl: D); |
154 | MoveTool->getUnremovedDeclsInOldHeader().erase(Ptr: D); |
155 | } |
156 | |
157 | class FunctionDeclarationMatch : public MatchFinder::MatchCallback { |
158 | public: |
159 | explicit FunctionDeclarationMatch(ClangMoveTool *MoveTool) |
160 | : MoveTool(MoveTool) {} |
161 | |
162 | void run(const MatchFinder::MatchResult &Result) override { |
163 | const auto *FD = Result.Nodes.getNodeAs<FunctionDecl>(ID: "function" ); |
164 | assert(FD); |
165 | const NamedDecl *D = FD; |
166 | if (const auto *FTD = FD->getDescribedFunctionTemplate()) |
167 | D = FTD; |
168 | MoveDeclFromOldFileToNewFile(MoveTool, D); |
169 | } |
170 | |
171 | private: |
172 | ClangMoveTool *MoveTool; |
173 | }; |
174 | |
175 | class VarDeclarationMatch : public MatchFinder::MatchCallback { |
176 | public: |
177 | explicit VarDeclarationMatch(ClangMoveTool *MoveTool) |
178 | : MoveTool(MoveTool) {} |
179 | |
180 | void run(const MatchFinder::MatchResult &Result) override { |
181 | const auto *VD = Result.Nodes.getNodeAs<VarDecl>(ID: "var" ); |
182 | assert(VD); |
183 | MoveDeclFromOldFileToNewFile(MoveTool, VD); |
184 | } |
185 | |
186 | private: |
187 | ClangMoveTool *MoveTool; |
188 | }; |
189 | |
190 | class TypeAliasMatch : public MatchFinder::MatchCallback { |
191 | public: |
192 | explicit TypeAliasMatch(ClangMoveTool *MoveTool) |
193 | : MoveTool(MoveTool) {} |
194 | |
195 | void run(const MatchFinder::MatchResult &Result) override { |
196 | if (const auto *TD = Result.Nodes.getNodeAs<TypedefDecl>(ID: "typedef" )) |
197 | MoveDeclFromOldFileToNewFile(MoveTool, TD); |
198 | else if (const auto *TAD = |
199 | Result.Nodes.getNodeAs<TypeAliasDecl>(ID: "type_alias" )) { |
200 | const NamedDecl * D = TAD; |
201 | if (const auto * TD = TAD->getDescribedAliasTemplate()) |
202 | D = TD; |
203 | MoveDeclFromOldFileToNewFile(MoveTool, D); |
204 | } |
205 | } |
206 | |
207 | private: |
208 | ClangMoveTool *MoveTool; |
209 | }; |
210 | |
211 | class EnumDeclarationMatch : public MatchFinder::MatchCallback { |
212 | public: |
213 | explicit EnumDeclarationMatch(ClangMoveTool *MoveTool) |
214 | : MoveTool(MoveTool) {} |
215 | |
216 | void run(const MatchFinder::MatchResult &Result) override { |
217 | const auto *ED = Result.Nodes.getNodeAs<EnumDecl>(ID: "enum" ); |
218 | assert(ED); |
219 | MoveDeclFromOldFileToNewFile(MoveTool, ED); |
220 | } |
221 | |
222 | private: |
223 | ClangMoveTool *MoveTool; |
224 | }; |
225 | |
226 | class ClassDeclarationMatch : public MatchFinder::MatchCallback { |
227 | public: |
228 | explicit ClassDeclarationMatch(ClangMoveTool *MoveTool) |
229 | : MoveTool(MoveTool) {} |
230 | void run(const MatchFinder::MatchResult &Result) override { |
231 | SourceManager *SM = &Result.Context->getSourceManager(); |
232 | if (const auto *CMD = Result.Nodes.getNodeAs<CXXMethodDecl>(ID: "class_method" )) |
233 | MatchClassMethod(CMD, SM); |
234 | else if (const auto *VD = |
235 | Result.Nodes.getNodeAs<VarDecl>(ID: "class_static_var_decl" )) |
236 | MatchClassStaticVariable(VD, SM); |
237 | else if (const auto *CD = |
238 | Result.Nodes.getNodeAs<CXXRecordDecl>(ID: "moved_class" )) |
239 | MatchClassDeclaration(CD, SM); |
240 | } |
241 | |
242 | private: |
243 | void MatchClassMethod(const CXXMethodDecl *CMD, SourceManager *SM) { |
244 | // Skip inline class methods. isInline() ast matcher doesn't ignore this |
245 | // case. |
246 | if (!CMD->isInlined()) { |
247 | MoveTool->getMovedDecls().push_back(CMD); |
248 | MoveTool->addRemovedDecl(CMD); |
249 | // Get template class method from its method declaration as |
250 | // UnremovedDecls stores template class method. |
251 | if (const auto *FTD = CMD->getDescribedFunctionTemplate()) |
252 | MoveTool->getUnremovedDeclsInOldHeader().erase(Ptr: FTD); |
253 | else |
254 | MoveTool->getUnremovedDeclsInOldHeader().erase(CMD); |
255 | } |
256 | } |
257 | |
258 | void MatchClassStaticVariable(const NamedDecl *VD, SourceManager *SM) { |
259 | MoveDeclFromOldFileToNewFile(MoveTool, D: VD); |
260 | } |
261 | |
262 | void MatchClassDeclaration(const CXXRecordDecl *CD, SourceManager *SM) { |
263 | // Get class template from its class declaration as UnremovedDecls stores |
264 | // class template. |
265 | if (const auto *TC = CD->getDescribedClassTemplate()) |
266 | MoveTool->getMovedDecls().push_back(TC); |
267 | else |
268 | MoveTool->getMovedDecls().push_back(CD); |
269 | MoveTool->addRemovedDecl(Decl: MoveTool->getMovedDecls().back()); |
270 | MoveTool->getUnremovedDeclsInOldHeader().erase( |
271 | Ptr: MoveTool->getMovedDecls().back()); |
272 | } |
273 | |
274 | ClangMoveTool *MoveTool; |
275 | }; |
276 | |
277 | // Expand to get the end location of the line where the EndLoc of the given |
278 | // Decl. |
279 | SourceLocation getLocForEndOfDecl(const Decl *D, |
280 | const LangOptions &LangOpts = LangOptions()) { |
281 | const auto &SM = D->getASTContext().getSourceManager(); |
282 | // If the expansion range is a character range, this is the location of |
283 | // the first character past the end. Otherwise it's the location of the |
284 | // first character in the final token in the range. |
285 | auto EndExpansionLoc = SM.getExpansionRange(Loc: D->getEndLoc()).getEnd(); |
286 | std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc: EndExpansionLoc); |
287 | // Try to load the file buffer. |
288 | bool InvalidTemp = false; |
289 | llvm::StringRef File = SM.getBufferData(FID: LocInfo.first, Invalid: &InvalidTemp); |
290 | if (InvalidTemp) |
291 | return SourceLocation(); |
292 | |
293 | const char *TokBegin = File.data() + LocInfo.second; |
294 | // Lex from the start of the given location. |
295 | Lexer Lex(SM.getLocForStartOfFile(FID: LocInfo.first), LangOpts, File.begin(), |
296 | TokBegin, File.end()); |
297 | |
298 | llvm::SmallVector<char, 16> Line; |
299 | // FIXME: this is a bit hacky to get ReadToEndOfLine work. |
300 | Lex.setParsingPreprocessorDirective(true); |
301 | Lex.ReadToEndOfLine(Result: &Line); |
302 | SourceLocation EndLoc = EndExpansionLoc.getLocWithOffset(Offset: Line.size()); |
303 | // If we already reach EOF, just return the EOF SourceLocation; |
304 | // otherwise, move 1 offset ahead to include the trailing newline character |
305 | // '\n'. |
306 | return SM.getLocForEndOfFile(FID: LocInfo.first) == EndLoc |
307 | ? EndLoc |
308 | : EndLoc.getLocWithOffset(Offset: 1); |
309 | } |
310 | |
311 | // Get full range of a Decl including the comments associated with it. |
312 | CharSourceRange getFullRange(const Decl *D, |
313 | const LangOptions &options = LangOptions()) { |
314 | const auto &SM = D->getASTContext().getSourceManager(); |
315 | SourceRange Full(SM.getExpansionLoc(Loc: D->getBeginLoc()), getLocForEndOfDecl(D)); |
316 | // Expand to comments that are associated with the Decl. |
317 | if (const auto * = D->getASTContext().getRawCommentForDeclNoCache(D)) { |
318 | if (SM.isBeforeInTranslationUnit(LHS: Full.getEnd(), RHS: Comment->getEndLoc())) |
319 | Full.setEnd(Comment->getEndLoc()); |
320 | // FIXME: Don't delete a preceding comment, if there are no other entities |
321 | // it could refer to. |
322 | if (SM.isBeforeInTranslationUnit(LHS: Comment->getBeginLoc(), RHS: Full.getBegin())) |
323 | Full.setBegin(Comment->getBeginLoc()); |
324 | } |
325 | |
326 | return CharSourceRange::getCharRange(R: Full); |
327 | } |
328 | |
329 | std::string getDeclarationSourceText(const Decl *D) { |
330 | const auto &SM = D->getASTContext().getSourceManager(); |
331 | llvm::StringRef SourceText = |
332 | Lexer::getSourceText(Range: getFullRange(D), SM, LangOpts: LangOptions()); |
333 | return SourceText.str(); |
334 | } |
335 | |
336 | bool (const Decl *D, llvm::StringRef OriginalRunningDirectory, |
337 | llvm::StringRef ) { |
338 | const auto &SM = D->getASTContext().getSourceManager(); |
339 | if (OldHeader.empty()) |
340 | return false; |
341 | auto ExpansionLoc = SM.getExpansionLoc(Loc: D->getBeginLoc()); |
342 | if (ExpansionLoc.isInvalid()) |
343 | return false; |
344 | |
345 | if (auto FE = SM.getFileEntryRefForID(FID: SM.getFileID(SpellingLoc: ExpansionLoc))) { |
346 | return MakeAbsolutePath(SM, Path: FE->getName()) == |
347 | MakeAbsolutePath(CurrentDir: OriginalRunningDirectory, Path: OldHeader); |
348 | } |
349 | |
350 | return false; |
351 | } |
352 | |
353 | std::vector<std::string> getNamespaces(const Decl *D) { |
354 | std::vector<std::string> Namespaces; |
355 | for (const auto *Context = D->getDeclContext(); Context; |
356 | Context = Context->getParent()) { |
357 | if (llvm::isa<TranslationUnitDecl>(Val: Context) || |
358 | llvm::isa<LinkageSpecDecl>(Val: Context)) |
359 | break; |
360 | |
361 | if (const auto *ND = llvm::dyn_cast<NamespaceDecl>(Val: Context)) |
362 | Namespaces.push_back(ND->getName().str()); |
363 | } |
364 | std::reverse(first: Namespaces.begin(), last: Namespaces.end()); |
365 | return Namespaces; |
366 | } |
367 | |
368 | tooling::Replacements |
369 | createInsertedReplacements(const std::vector<std::string> &Includes, |
370 | const std::vector<const NamedDecl *> &Decls, |
371 | llvm::StringRef FileName, bool = false, |
372 | StringRef = "" ) { |
373 | std::string NewCode; |
374 | std::string GuardName(FileName); |
375 | if (IsHeader) { |
376 | for (size_t i = 0; i < GuardName.size(); ++i) { |
377 | if (!isAlphanumeric(c: GuardName[i])) |
378 | GuardName[i] = '_'; |
379 | } |
380 | GuardName = StringRef(GuardName).upper(); |
381 | NewCode += "#ifndef " + GuardName + "\n" ; |
382 | NewCode += "#define " + GuardName + "\n\n" ; |
383 | } |
384 | |
385 | NewCode += OldHeaderInclude; |
386 | // Add #Includes. |
387 | for (const auto &Include : Includes) |
388 | NewCode += Include; |
389 | |
390 | if (!Includes.empty()) |
391 | NewCode += "\n" ; |
392 | |
393 | // Add moved class definition and its related declarations. All declarations |
394 | // in same namespace are grouped together. |
395 | // |
396 | // Record namespaces where the current position is in. |
397 | std::vector<std::string> CurrentNamespaces; |
398 | for (const auto *MovedDecl : Decls) { |
399 | // The namespaces of the declaration being moved. |
400 | std::vector<std::string> DeclNamespaces = getNamespaces(MovedDecl); |
401 | auto CurrentIt = CurrentNamespaces.begin(); |
402 | auto DeclIt = DeclNamespaces.begin(); |
403 | // Skip the common prefix. |
404 | while (CurrentIt != CurrentNamespaces.end() && |
405 | DeclIt != DeclNamespaces.end()) { |
406 | if (*CurrentIt != *DeclIt) |
407 | break; |
408 | ++CurrentIt; |
409 | ++DeclIt; |
410 | } |
411 | // Calculate the new namespaces after adding MovedDecl in CurrentNamespace, |
412 | // which is used for next iteration of this loop. |
413 | std::vector<std::string> NextNamespaces(CurrentNamespaces.begin(), |
414 | CurrentIt); |
415 | NextNamespaces.insert(NextNamespaces.end(), DeclIt, DeclNamespaces.end()); |
416 | |
417 | |
418 | // End with CurrentNamespace. |
419 | bool HasEndCurrentNamespace = false; |
420 | auto RemainingSize = CurrentNamespaces.end() - CurrentIt; |
421 | for (auto It = CurrentNamespaces.rbegin(); RemainingSize > 0; |
422 | --RemainingSize, ++It) { |
423 | assert(It < CurrentNamespaces.rend()); |
424 | NewCode += "} // namespace " + *It + "\n" ; |
425 | HasEndCurrentNamespace = true; |
426 | } |
427 | // Add trailing '\n' after the nested namespace definition. |
428 | if (HasEndCurrentNamespace) |
429 | NewCode += "\n" ; |
430 | |
431 | // If the moved declaration is not in CurrentNamespace, add extra namespace |
432 | // definitions. |
433 | bool IsInNewNamespace = false; |
434 | while (DeclIt != DeclNamespaces.end()) { |
435 | NewCode += "namespace " + *DeclIt + " {\n" ; |
436 | IsInNewNamespace = true; |
437 | ++DeclIt; |
438 | } |
439 | // If the moved declaration is in same namespace CurrentNamespace, add |
440 | // a preceeding `\n' before the moved declaration. |
441 | // FIXME: Don't add empty lines between using declarations. |
442 | if (!IsInNewNamespace) |
443 | NewCode += "\n" ; |
444 | NewCode += getDeclarationSourceText(MovedDecl); |
445 | CurrentNamespaces = std::move(NextNamespaces); |
446 | } |
447 | std::reverse(first: CurrentNamespaces.begin(), last: CurrentNamespaces.end()); |
448 | for (const auto &NS : CurrentNamespaces) |
449 | NewCode += "} // namespace " + NS + "\n" ; |
450 | |
451 | if (IsHeader) |
452 | NewCode += "\n#endif // " + GuardName + "\n" ; |
453 | return tooling::Replacements(tooling::Replacement(FileName, 0, 0, NewCode)); |
454 | } |
455 | |
456 | // Return a set of all decls which are used/referenced by the given Decls. |
457 | // Specifically, given a class member declaration, this method will return all |
458 | // decls which are used by the whole class. |
459 | llvm::DenseSet<const Decl *> |
460 | getUsedDecls(const HelperDeclRefGraph *RG, |
461 | const std::vector<const NamedDecl *> &Decls) { |
462 | assert(RG); |
463 | llvm::DenseSet<const CallGraphNode *> Nodes; |
464 | for (const auto *D : Decls) { |
465 | auto Result = RG->getReachableNodes( |
466 | D: HelperDeclRGBuilder::getOutmostClassOrFunDecl(D)); |
467 | Nodes.insert(Result.begin(), Result.end()); |
468 | } |
469 | llvm::DenseSet<const Decl *> Results; |
470 | for (const auto *Node : Nodes) |
471 | Results.insert(V: Node->getDecl()); |
472 | return Results; |
473 | } |
474 | |
475 | } // namespace |
476 | |
477 | std::unique_ptr<ASTConsumer> |
478 | ClangMoveAction::CreateASTConsumer(CompilerInstance &Compiler, |
479 | StringRef /*InFile*/) { |
480 | Compiler.getPreprocessor().addPPCallbacks(C: std::make_unique<FindAllIncludes>( |
481 | args: &Compiler.getSourceManager(), args: &MoveTool)); |
482 | return MatchFinder.newASTConsumer(); |
483 | } |
484 | |
485 | ClangMoveTool::ClangMoveTool(ClangMoveContext *const Context, |
486 | DeclarationReporter *const Reporter) |
487 | : Context(Context), Reporter(Reporter) { |
488 | if (!Context->Spec.NewHeader.empty()) |
489 | CCIncludes.push_back(x: "#include \"" + Context->Spec.NewHeader + "\"\n" ); |
490 | } |
491 | |
492 | void ClangMoveTool::addRemovedDecl(const NamedDecl *Decl) { |
493 | const auto &SM = Decl->getASTContext().getSourceManager(); |
494 | auto Loc = Decl->getLocation(); |
495 | StringRef FilePath = SM.getFilename(Loc); |
496 | FilePathToFileID[FilePath] = SM.getFileID(Loc); |
497 | RemovedDecls.push_back(x: Decl); |
498 | } |
499 | |
500 | void ClangMoveTool::registerMatchers(ast_matchers::MatchFinder *Finder) { |
501 | auto = |
502 | isExpansionInFile(AbsoluteFilePath: makeAbsolutePath(Path: Context->Spec.OldHeader)); |
503 | auto InOldCC = isExpansionInFile(AbsoluteFilePath: makeAbsolutePath(Path: Context->Spec.OldCC)); |
504 | auto InOldFiles = anyOf(InOldHeader, InOldCC); |
505 | auto classTemplateForwardDecls = |
506 | classTemplateDecl(unless(has(cxxRecordDecl(isDefinition())))); |
507 | auto ForwardClassDecls = namedDecl( |
508 | anyOf(cxxRecordDecl(unless(anyOf(isImplicit(), isDefinition()))), |
509 | classTemplateForwardDecls)); |
510 | auto TopLevelDecl = |
511 | hasDeclContext(InnerMatcher: anyOf(namespaceDecl(), translationUnitDecl())); |
512 | |
513 | //============================================================================ |
514 | // Matchers for old header |
515 | //============================================================================ |
516 | // Match all top-level named declarations (e.g. function, variable, enum) in |
517 | // old header, exclude forward class declarations and namespace declarations. |
518 | // |
519 | // We consider declarations inside a class belongs to the class. So these |
520 | // declarations will be ignored. |
521 | auto = namedDecl( |
522 | unless(ForwardClassDecls), unless(namespaceDecl()), |
523 | unless(usingDirectiveDecl()), // using namespace decl. |
524 | notInMacro(), |
525 | InOldHeader, |
526 | hasParent(decl(anyOf(namespaceDecl(), translationUnitDecl()))), |
527 | hasDeclContext(InnerMatcher: decl(anyOf(namespaceDecl(), translationUnitDecl())))); |
528 | Finder->addMatcher(NodeMatch: AllDeclsInHeader.bind(ID: "decls_in_header" ), Action: this); |
529 | |
530 | // Don't register other matchers when dumping all declarations in header. |
531 | if (Context->DumpDeclarations) |
532 | return; |
533 | |
534 | // Match forward declarations in old header. |
535 | Finder->addMatcher(NodeMatch: namedDecl(ForwardClassDecls, InOldHeader).bind(ID: "fwd_decl" ), |
536 | Action: this); |
537 | |
538 | //============================================================================ |
539 | // Matchers for old cc |
540 | //============================================================================ |
541 | auto IsOldCCTopLevelDecl = allOf( |
542 | hasParent(decl(anyOf(namespaceDecl(), translationUnitDecl()))), InOldCC); |
543 | // Matching using decls/type alias decls which are in named/anonymous/global |
544 | // namespace, these decls are always copied to new.h/cc. Those in classes, |
545 | // functions are covered in other matchers. |
546 | Finder->addMatcher(NodeMatch: namedDecl(anyOf(usingDecl(IsOldCCTopLevelDecl), |
547 | usingDirectiveDecl(unless(isImplicit()), |
548 | IsOldCCTopLevelDecl), |
549 | typeAliasDecl(IsOldCCTopLevelDecl)), |
550 | notInMacro()) |
551 | .bind(ID: "using_decl" ), |
552 | Action: this); |
553 | |
554 | // Match static functions/variable definitions which are defined in named |
555 | // namespaces. |
556 | SmallVector<std::string, 4> QualNames; |
557 | QualNames.reserve(N: Context->Spec.Names.size()); |
558 | for (StringRef SymbolName : Context->Spec.Names) { |
559 | QualNames.push_back(Elt: ("::" + SymbolName.trim().ltrim(Char: ':')).str()); |
560 | } |
561 | |
562 | if (QualNames.empty()) { |
563 | llvm::errs() << "No symbols being moved.\n" ; |
564 | return; |
565 | } |
566 | |
567 | ast_matchers::internal::Matcher<NamedDecl> HasAnySymbolNames = |
568 | hasAnyName(SmallVector<StringRef, 4>(QualNames.begin(), QualNames.end())); |
569 | |
570 | auto InMovedClass = |
571 | hasOutermostEnclosingClass(InnerMatcher: cxxRecordDecl(HasAnySymbolNames)); |
572 | |
573 | // Matchers for helper declarations in old.cc. |
574 | auto InAnonymousNS = hasParent(namespaceDecl(isAnonymous())); |
575 | auto NotInMovedClass= allOf(unless(InMovedClass), InOldCC); |
576 | auto IsOldCCHelper = |
577 | allOf(NotInMovedClass, anyOf(isStaticStorageClass(), InAnonymousNS)); |
578 | // Match helper classes separately with helper functions/variables since we |
579 | // want to reuse these matchers in finding helpers usage below. |
580 | // |
581 | // There could be forward declarations usage for helpers, especially for |
582 | // classes and functions. We need include these forward declarations. |
583 | // |
584 | // Forward declarations for variable helpers will be excluded as these |
585 | // declarations (with "extern") are not supposed in cpp file. |
586 | auto HelperFuncOrVar = |
587 | namedDecl(notInMacro(), anyOf(functionDecl(IsOldCCHelper), |
588 | varDecl(isDefinition(), IsOldCCHelper))); |
589 | auto HelperClasses = |
590 | cxxRecordDecl(notInMacro(), NotInMovedClass, InAnonymousNS); |
591 | // Save all helper declarations in old.cc. |
592 | Finder->addMatcher( |
593 | NodeMatch: namedDecl(anyOf(HelperFuncOrVar, HelperClasses)).bind(ID: "helper_decls" ), |
594 | Action: this); |
595 | |
596 | // Construct an AST-based call graph of helper declarations in old.cc. |
597 | // In the following matcheres, "dc" is a caller while "helper_decls" and |
598 | // "used_class" is a callee, so a new edge starting from caller to callee will |
599 | // be add in the graph. |
600 | // |
601 | // Find helper function/variable usages. |
602 | Finder->addMatcher( |
603 | NodeMatch: declRefExpr(to(InnerMatcher: HelperFuncOrVar), hasAncestor(decl().bind(ID: "dc" ))) |
604 | .bind(ID: "func_ref" ), |
605 | Action: &RGBuilder); |
606 | // Find helper class usages. |
607 | Finder->addMatcher( |
608 | NodeMatch: typeLoc(loc(InnerMatcher: recordType(hasDeclaration(InnerMatcher: HelperClasses.bind(ID: "used_class" )))), |
609 | hasAncestor(decl().bind(ID: "dc" ))), |
610 | Action: &RGBuilder); |
611 | |
612 | //============================================================================ |
613 | // Matchers for old files, including old.h/old.cc |
614 | //============================================================================ |
615 | // Create a MatchCallback for class declarations. |
616 | MatchCallbacks.push_back(x: std::make_unique<ClassDeclarationMatch>(args: this)); |
617 | // Match moved class declarations. |
618 | auto MovedClass = |
619 | cxxRecordDecl(InOldFiles, HasAnySymbolNames, isDefinition(), TopLevelDecl) |
620 | .bind(ID: "moved_class" ); |
621 | Finder->addMatcher(NodeMatch: MovedClass, Action: MatchCallbacks.back().get()); |
622 | // Match moved class methods (static methods included) which are defined |
623 | // outside moved class declaration. |
624 | Finder->addMatcher(NodeMatch: cxxMethodDecl(InOldFiles, |
625 | ofOutermostEnclosingClass(InnerMatcher: HasAnySymbolNames), |
626 | isDefinition()) |
627 | .bind(ID: "class_method" ), |
628 | Action: MatchCallbacks.back().get()); |
629 | // Match static member variable definition of the moved class. |
630 | Finder->addMatcher( |
631 | NodeMatch: varDecl(InMovedClass, InOldFiles, isDefinition(), isStaticDataMember()) |
632 | .bind(ID: "class_static_var_decl" ), |
633 | Action: MatchCallbacks.back().get()); |
634 | |
635 | MatchCallbacks.push_back(x: std::make_unique<FunctionDeclarationMatch>(args: this)); |
636 | Finder->addMatcher(NodeMatch: functionDecl(InOldFiles, HasAnySymbolNames, TopLevelDecl) |
637 | .bind(ID: "function" ), |
638 | Action: MatchCallbacks.back().get()); |
639 | |
640 | MatchCallbacks.push_back(x: std::make_unique<VarDeclarationMatch>(args: this)); |
641 | Finder->addMatcher( |
642 | NodeMatch: varDecl(InOldFiles, HasAnySymbolNames, TopLevelDecl).bind(ID: "var" ), |
643 | Action: MatchCallbacks.back().get()); |
644 | |
645 | // Match enum definition in old.h. Enum helpers (which are defined in old.cc) |
646 | // will not be moved for now no matter whether they are used or not. |
647 | MatchCallbacks.push_back(x: std::make_unique<EnumDeclarationMatch>(args: this)); |
648 | Finder->addMatcher( |
649 | NodeMatch: enumDecl(InOldHeader, HasAnySymbolNames, isDefinition(), TopLevelDecl) |
650 | .bind(ID: "enum" ), |
651 | Action: MatchCallbacks.back().get()); |
652 | |
653 | // Match type alias in old.h, this includes "typedef" and "using" type alias |
654 | // declarations. Type alias helpers (which are defined in old.cc) will not be |
655 | // moved for now no matter whether they are used or not. |
656 | MatchCallbacks.push_back(x: std::make_unique<TypeAliasMatch>(args: this)); |
657 | Finder->addMatcher(NodeMatch: namedDecl(anyOf(typedefDecl().bind(ID: "typedef" ), |
658 | typeAliasDecl().bind(ID: "type_alias" )), |
659 | InOldHeader, HasAnySymbolNames, TopLevelDecl), |
660 | Action: MatchCallbacks.back().get()); |
661 | } |
662 | |
663 | void ClangMoveTool::run(const ast_matchers::MatchFinder::MatchResult &Result) { |
664 | if (const auto *D = Result.Nodes.getNodeAs<NamedDecl>(ID: "decls_in_header" )) { |
665 | UnremovedDeclsInOldHeader.insert(Ptr: D); |
666 | } else if (const auto *FWD = |
667 | Result.Nodes.getNodeAs<CXXRecordDecl>(ID: "fwd_decl" )) { |
668 | // Skip all forward declarations which appear after moved class declaration. |
669 | if (RemovedDecls.empty()) { |
670 | if (const auto *DCT = FWD->getDescribedClassTemplate()) |
671 | MovedDecls.push_back(DCT); |
672 | else |
673 | MovedDecls.push_back(FWD); |
674 | } |
675 | } else if (const auto *ND = |
676 | Result.Nodes.getNodeAs<NamedDecl>(ID: "helper_decls" )) { |
677 | MovedDecls.push_back(x: ND); |
678 | HelperDeclarations.push_back(x: ND); |
679 | LLVM_DEBUG(llvm::dbgs() |
680 | << "Add helper : " << ND->getDeclName() << " (" << ND << ")\n" ); |
681 | } else if (const auto *UD = Result.Nodes.getNodeAs<NamedDecl>(ID: "using_decl" )) { |
682 | MovedDecls.push_back(x: UD); |
683 | } |
684 | } |
685 | |
686 | std::string ClangMoveTool::makeAbsolutePath(StringRef Path) { |
687 | return MakeAbsolutePath(CurrentDir: Context->OriginalRunningDirectory, Path); |
688 | } |
689 | |
690 | void ClangMoveTool::addIncludes(llvm::StringRef , bool IsAngled, |
691 | llvm::StringRef SearchPath, |
692 | llvm::StringRef FileName, |
693 | CharSourceRange IncludeFilenameRange, |
694 | const SourceManager &SM) { |
695 | SmallString<128> ; |
696 | llvm::sys::path::append(path&: HeaderWithSearchPath, a: SearchPath, b: IncludeHeader); |
697 | std::string = |
698 | MakeAbsolutePath(SM, Path: HeaderWithSearchPath); |
699 | std::string IncludeLine = |
700 | IsAngled ? ("#include <" + IncludeHeader + ">\n" ).str() |
701 | : ("#include \"" + IncludeHeader + "\"\n" ).str(); |
702 | |
703 | std::string = makeAbsolutePath(Path: Context->Spec.OldHeader); |
704 | std::string AbsoluteCurrentFile = MakeAbsolutePath(SM, Path: FileName); |
705 | if (AbsoluteOldHeader == AbsoluteCurrentFile) { |
706 | // Find old.h includes "old.h". |
707 | if (AbsoluteOldHeader == AbsoluteIncludeHeader) { |
708 | OldHeaderIncludeRangeInHeader = IncludeFilenameRange; |
709 | return; |
710 | } |
711 | HeaderIncludes.push_back(x: IncludeLine); |
712 | } else if (makeAbsolutePath(Path: Context->Spec.OldCC) == AbsoluteCurrentFile) { |
713 | // Find old.cc includes "old.h". |
714 | if (AbsoluteOldHeader == AbsoluteIncludeHeader) { |
715 | OldHeaderIncludeRangeInCC = IncludeFilenameRange; |
716 | return; |
717 | } |
718 | CCIncludes.push_back(x: IncludeLine); |
719 | } |
720 | } |
721 | |
722 | void ClangMoveTool::removeDeclsInOldFiles() { |
723 | if (RemovedDecls.empty()) return; |
724 | |
725 | // If old_header is not specified (only move declarations from old.cc), remain |
726 | // all the helper function declarations in old.cc as UnremovedDeclsInOldHeader |
727 | // is empty in this case, there is no way to verify unused/used helpers. |
728 | if (!Context->Spec.OldHeader.empty()) { |
729 | std::vector<const NamedDecl *> UnremovedDecls; |
730 | for (const auto *D : UnremovedDeclsInOldHeader) |
731 | UnremovedDecls.push_back(x: D); |
732 | |
733 | auto UsedDecls = getUsedDecls(RG: RGBuilder.getGraph(), Decls: UnremovedDecls); |
734 | |
735 | // We remove the helper declarations which are not used in the old.cc after |
736 | // moving the given declarations. |
737 | for (const auto *D : HelperDeclarations) { |
738 | LLVM_DEBUG(llvm::dbgs() << "Check helper is used: " << D->getDeclName() |
739 | << " (" << D << ")\n" ); |
740 | if (!UsedDecls.count(V: HelperDeclRGBuilder::getOutmostClassOrFunDecl( |
741 | D: D->getCanonicalDecl()))) { |
742 | LLVM_DEBUG(llvm::dbgs() << "Helper removed in old.cc: " |
743 | << D->getDeclName() << " (" << D << ")\n" ); |
744 | RemovedDecls.push_back(x: D); |
745 | } |
746 | } |
747 | } |
748 | |
749 | for (const auto *RemovedDecl : RemovedDecls) { |
750 | const auto &SM = RemovedDecl->getASTContext().getSourceManager(); |
751 | auto Range = getFullRange(RemovedDecl); |
752 | tooling::Replacement RemoveReplacement( |
753 | SM, CharSourceRange::getCharRange(Range.getBegin(), Range.getEnd()), |
754 | "" ); |
755 | std::string FilePath = RemoveReplacement.getFilePath().str(); |
756 | auto Err = Context->FileToReplacements[FilePath].add(R: RemoveReplacement); |
757 | if (Err) |
758 | llvm::errs() << llvm::toString(std::move(Err)) << "\n" ; |
759 | } |
760 | const auto &SM = RemovedDecls[0]->getASTContext().getSourceManager(); |
761 | |
762 | // Post process of cleanup around all the replacements. |
763 | for (auto &FileAndReplacements : Context->FileToReplacements) { |
764 | StringRef FilePath = FileAndReplacements.first; |
765 | // Add #include of new header to old header. |
766 | if (Context->Spec.OldDependOnNew && |
767 | MakeAbsolutePath(SM, FilePath) == |
768 | makeAbsolutePath(Path: Context->Spec.OldHeader)) { |
769 | // FIXME: Minimize the include path like clang-include-fixer. |
770 | std::string IncludeNewH = |
771 | "#include \"" + Context->Spec.NewHeader + "\"\n" ; |
772 | // This replacement for inserting header will be cleaned up at the end. |
773 | auto Err = FileAndReplacements.second.add( |
774 | R: tooling::Replacement(FilePath, UINT_MAX, 0, IncludeNewH)); |
775 | if (Err) |
776 | llvm::errs() << llvm::toString(E: std::move(Err)) << "\n" ; |
777 | } |
778 | |
779 | auto SI = FilePathToFileID.find(Key: FilePath); |
780 | // Ignore replacements for new.h/cc. |
781 | if (SI == FilePathToFileID.end()) continue; |
782 | llvm::StringRef Code = SM.getBufferData(SI->second); |
783 | auto Style = format::getStyle(StyleName: format::DefaultFormatStyle, FileName: FilePath, |
784 | FallbackStyle: Context->FallbackStyle); |
785 | if (!Style) { |
786 | llvm::errs() << llvm::toString(E: Style.takeError()) << "\n" ; |
787 | continue; |
788 | } |
789 | auto CleanReplacements = format::cleanupAroundReplacements( |
790 | Code, Replaces: Context->FileToReplacements[std::string(FilePath)], Style: *Style); |
791 | |
792 | if (!CleanReplacements) { |
793 | llvm::errs() << llvm::toString(CleanReplacements.takeError()) << "\n" ; |
794 | continue; |
795 | } |
796 | Context->FileToReplacements[std::string(FilePath)] = *CleanReplacements; |
797 | } |
798 | } |
799 | |
800 | void ClangMoveTool::moveDeclsToNewFiles() { |
801 | std::vector<const NamedDecl *> ; |
802 | std::vector<const NamedDecl *> NewCCDecls; |
803 | for (const auto *MovedDecl : MovedDecls) { |
804 | if (isInHeaderFile(MovedDecl, Context->OriginalRunningDirectory, |
805 | Context->Spec.OldHeader)) |
806 | NewHeaderDecls.push_back(x: MovedDecl); |
807 | else |
808 | NewCCDecls.push_back(x: MovedDecl); |
809 | } |
810 | |
811 | auto UsedDecls = getUsedDecls(RG: RGBuilder.getGraph(), Decls: RemovedDecls); |
812 | std::vector<const NamedDecl *> ActualNewCCDecls; |
813 | |
814 | // Filter out all unused helpers in NewCCDecls. |
815 | // We only move the used helpers (including transitively used helpers) and the |
816 | // given symbols being moved. |
817 | for (const auto *D : NewCCDecls) { |
818 | if (llvm::is_contained(Range&: HelperDeclarations, Element: D) && |
819 | !UsedDecls.count(V: HelperDeclRGBuilder::getOutmostClassOrFunDecl( |
820 | D: D->getCanonicalDecl()))) |
821 | continue; |
822 | |
823 | LLVM_DEBUG(llvm::dbgs() << "Helper used in new.cc: " << D->getDeclName() |
824 | << " " << D << "\n" ); |
825 | ActualNewCCDecls.push_back(x: D); |
826 | } |
827 | |
828 | if (!Context->Spec.NewHeader.empty()) { |
829 | std::string = |
830 | Context->Spec.NewDependOnOld |
831 | ? "#include \"" + Context->Spec.OldHeader + "\"\n" |
832 | : "" ; |
833 | Context->FileToReplacements[Context->Spec.NewHeader] = |
834 | createInsertedReplacements(Includes: HeaderIncludes, Decls: NewHeaderDecls, |
835 | FileName: Context->Spec.NewHeader, /*IsHeader=*/true, |
836 | OldHeaderInclude); |
837 | } |
838 | if (!Context->Spec.NewCC.empty()) |
839 | Context->FileToReplacements[Context->Spec.NewCC] = |
840 | createInsertedReplacements(Includes: CCIncludes, Decls: ActualNewCCDecls, |
841 | FileName: Context->Spec.NewCC); |
842 | } |
843 | |
844 | // Move all contents from OldFile to NewFile. |
845 | void ClangMoveTool::moveAll(SourceManager &SM, StringRef OldFile, |
846 | StringRef NewFile) { |
847 | auto FE = SM.getFileManager().getOptionalFileRef(Filename: makeAbsolutePath(Path: OldFile)); |
848 | if (!FE) { |
849 | llvm::errs() << "Failed to get file: " << OldFile << "\n" ; |
850 | return; |
851 | } |
852 | FileID ID = SM.getOrCreateFileID(SourceFile: *FE, FileCharacter: SrcMgr::C_User); |
853 | auto Begin = SM.getLocForStartOfFile(FID: ID); |
854 | auto End = SM.getLocForEndOfFile(FID: ID); |
855 | tooling::Replacement RemoveAll(SM, CharSourceRange::getCharRange(B: Begin, E: End), |
856 | "" ); |
857 | std::string FilePath = RemoveAll.getFilePath().str(); |
858 | Context->FileToReplacements[FilePath] = tooling::Replacements(RemoveAll); |
859 | |
860 | StringRef Code = SM.getBufferData(FID: ID); |
861 | if (!NewFile.empty()) { |
862 | auto AllCode = |
863 | tooling::Replacements(tooling::Replacement(NewFile, 0, 0, Code)); |
864 | auto ReplaceOldInclude = [&](CharSourceRange ) { |
865 | AllCode = AllCode.merge(Replaces: tooling::Replacements(tooling::Replacement( |
866 | SM, OldHeaderIncludeRange, '"' + Context->Spec.NewHeader + '"'))); |
867 | }; |
868 | // Fix the case where old.h/old.cc includes "old.h", we replace the |
869 | // `#include "old.h"` with `#include "new.h"`. |
870 | if (Context->Spec.NewCC == NewFile && OldHeaderIncludeRangeInCC.isValid()) |
871 | ReplaceOldInclude(OldHeaderIncludeRangeInCC); |
872 | else if (Context->Spec.NewHeader == NewFile && |
873 | OldHeaderIncludeRangeInHeader.isValid()) |
874 | ReplaceOldInclude(OldHeaderIncludeRangeInHeader); |
875 | Context->FileToReplacements[std::string(NewFile)] = std::move(AllCode); |
876 | } |
877 | } |
878 | |
879 | void ClangMoveTool::onEndOfTranslationUnit() { |
880 | if (Context->DumpDeclarations) { |
881 | assert(Reporter); |
882 | for (const auto *Decl : UnremovedDeclsInOldHeader) { |
883 | auto Kind = Decl->getKind(); |
884 | bool Templated = Decl->isTemplated(); |
885 | const std::string QualifiedName = Decl->getQualifiedNameAsString(); |
886 | if (Kind == Decl::Kind::Var) |
887 | Reporter->reportDeclaration(DeclarationName: QualifiedName, Type: "Variable" , Templated); |
888 | else if (Kind == Decl::Kind::Function || |
889 | Kind == Decl::Kind::FunctionTemplate) |
890 | Reporter->reportDeclaration(DeclarationName: QualifiedName, Type: "Function" , Templated); |
891 | else if (Kind == Decl::Kind::ClassTemplate || |
892 | Kind == Decl::Kind::CXXRecord) |
893 | Reporter->reportDeclaration(DeclarationName: QualifiedName, Type: "Class" , Templated); |
894 | else if (Kind == Decl::Kind::Enum) |
895 | Reporter->reportDeclaration(DeclarationName: QualifiedName, Type: "Enum" , Templated); |
896 | else if (Kind == Decl::Kind::Typedef || Kind == Decl::Kind::TypeAlias || |
897 | Kind == Decl::Kind::TypeAliasTemplate) |
898 | Reporter->reportDeclaration(DeclarationName: QualifiedName, Type: "TypeAlias" , Templated); |
899 | } |
900 | return; |
901 | } |
902 | |
903 | if (RemovedDecls.empty()) |
904 | return; |
905 | // Ignore symbols that are not supported when checking if there is unremoved |
906 | // symbol in old header. This makes sure that we always move old files to new |
907 | // files when all symbols produced from dump_decls are moved. |
908 | auto IsSupportedKind = [](const NamedDecl *Decl) { |
909 | switch (Decl->getKind()) { |
910 | case Decl::Kind::Function: |
911 | case Decl::Kind::FunctionTemplate: |
912 | case Decl::Kind::ClassTemplate: |
913 | case Decl::Kind::CXXRecord: |
914 | case Decl::Kind::Enum: |
915 | case Decl::Kind::Typedef: |
916 | case Decl::Kind::TypeAlias: |
917 | case Decl::Kind::TypeAliasTemplate: |
918 | case Decl::Kind::Var: |
919 | return true; |
920 | default: |
921 | return false; |
922 | } |
923 | }; |
924 | if (llvm::none_of(Range&: UnremovedDeclsInOldHeader, P: IsSupportedKind) && |
925 | !Context->Spec.OldHeader.empty()) { |
926 | auto &SM = RemovedDecls[0]->getASTContext().getSourceManager(); |
927 | moveAll(SM&: SM, OldFile: Context->Spec.OldHeader, NewFile: Context->Spec.NewHeader); |
928 | moveAll(SM&: SM, OldFile: Context->Spec.OldCC, NewFile: Context->Spec.NewCC); |
929 | return; |
930 | } |
931 | LLVM_DEBUG(RGBuilder.getGraph()->dump()); |
932 | moveDeclsToNewFiles(); |
933 | removeDeclsInOldFiles(); |
934 | } |
935 | |
936 | } // namespace move |
937 | } // namespace clang |
938 | |