1010 lines · cpp
1//===-- ChangeNamespace.cpp - Change namespace implementation -------------===//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-exception6//7//===----------------------------------------------------------------------===//8#include "ChangeNamespace.h"9#include "clang/AST/ASTContext.h"10#include "clang/Format/Format.h"11#include "clang/Lex/Lexer.h"12#include "llvm/Support/Casting.h"13#include "llvm/Support/ErrorHandling.h"14 15using namespace clang::ast_matchers;16 17namespace clang {18namespace change_namespace {19 20namespace {21 22inline std::string joinNamespaces(ArrayRef<StringRef> Namespaces) {23 return llvm::join(Namespaces, "::");24}25 26// Given "a::b::c", returns {"a", "b", "c"}.27llvm::SmallVector<llvm::StringRef, 4> splitSymbolName(llvm::StringRef Name) {28 llvm::SmallVector<llvm::StringRef, 4> Splitted;29 Name.split(Splitted, "::", /*MaxSplit=*/-1,30 /*KeepEmpty=*/false);31 return Splitted;32}33 34SourceLocation endLocationForType(TypeLoc TLoc) {35 if (auto QTL = TLoc.getAs<QualifiedTypeLoc>())36 TLoc = QTL.getUnqualifiedLoc();37 38 // The location for template specializations (e.g. Foo<int>) includes the39 // templated types in its location range. We want to restrict this to just40 // before the `<` character.41 if (TLoc.getTypeLocClass() == TypeLoc::TemplateSpecialization)42 return TLoc.castAs<TemplateSpecializationTypeLoc>()43 .getLAngleLoc()44 .getLocWithOffset(-1);45 return TLoc.getEndLoc();46}47 48// Returns the containing namespace of `InnerNs` by skipping `PartialNsName`.49// If the `InnerNs` does not have `PartialNsName` as suffix, or `PartialNsName`50// is empty, nullptr is returned.51// For example, if `InnerNs` is "a::b::c" and `PartialNsName` is "b::c", then52// the NamespaceDecl of namespace "a" will be returned.53const NamespaceDecl *getOuterNamespace(const NamespaceDecl *InnerNs,54 llvm::StringRef PartialNsName) {55 if (!InnerNs || PartialNsName.empty())56 return nullptr;57 const auto *CurrentContext = llvm::cast<DeclContext>(InnerNs);58 const auto *CurrentNs = InnerNs;59 auto PartialNsNameSplitted = splitSymbolName(PartialNsName);60 while (!PartialNsNameSplitted.empty()) {61 // Get the inner-most namespace in CurrentContext.62 while (CurrentContext && !llvm::isa<NamespaceDecl>(CurrentContext))63 CurrentContext = CurrentContext->getParent();64 if (!CurrentContext)65 return nullptr;66 CurrentNs = llvm::cast<NamespaceDecl>(CurrentContext);67 if (PartialNsNameSplitted.back() != CurrentNs->getNameAsString())68 return nullptr;69 PartialNsNameSplitted.pop_back();70 CurrentContext = CurrentContext->getParent();71 }72 return CurrentNs;73}74 75static std::unique_ptr<Lexer>76getLexerStartingFromLoc(SourceLocation Loc, const SourceManager &SM,77 const LangOptions &LangOpts) {78 if (Loc.isMacroID() &&79 !Lexer::isAtEndOfMacroExpansion(Loc, SM, LangOpts, &Loc))80 return nullptr;81 // Break down the source location.82 std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc);83 // Try to load the file buffer.84 bool InvalidTemp = false;85 llvm::StringRef File = SM.getBufferData(LocInfo.first, &InvalidTemp);86 if (InvalidTemp)87 return nullptr;88 89 const char *TokBegin = File.data() + LocInfo.second;90 // Lex from the start of the given location.91 return std::make_unique<Lexer>(SM.getLocForStartOfFile(LocInfo.first),92 LangOpts, File.begin(), TokBegin, File.end());93}94 95// FIXME: get rid of this helper function if this is supported in clang-refactor96// library.97static SourceLocation getStartOfNextLine(SourceLocation Loc,98 const SourceManager &SM,99 const LangOptions &LangOpts) {100 std::unique_ptr<Lexer> Lex = getLexerStartingFromLoc(Loc, SM, LangOpts);101 if (!Lex)102 return SourceLocation();103 llvm::SmallVector<char, 16> Line;104 // FIXME: this is a bit hacky to get ReadToEndOfLine work.105 Lex->setParsingPreprocessorDirective(true);106 Lex->ReadToEndOfLine(&Line);107 auto End = Loc.getLocWithOffset(Line.size());108 return SM.getLocForEndOfFile(SM.getDecomposedLoc(Loc).first) == End109 ? End110 : End.getLocWithOffset(1);111}112 113// Returns `R` with new range that refers to code after `Replaces` being114// applied.115tooling::Replacement116getReplacementInChangedCode(const tooling::Replacements &Replaces,117 const tooling::Replacement &R) {118 unsigned NewStart = Replaces.getShiftedCodePosition(R.getOffset());119 unsigned NewEnd =120 Replaces.getShiftedCodePosition(R.getOffset() + R.getLength());121 return tooling::Replacement(R.getFilePath(), NewStart, NewEnd - NewStart,122 R.getReplacementText());123}124 125// Adds a replacement `R` into `Replaces` or merges it into `Replaces` by126// applying all existing Replaces first if there is conflict.127void addOrMergeReplacement(const tooling::Replacement &R,128 tooling::Replacements *Replaces) {129 auto Err = Replaces->add(R);130 if (Err) {131 llvm::consumeError(std::move(Err));132 auto Replace = getReplacementInChangedCode(*Replaces, R);133 *Replaces = Replaces->merge(tooling::Replacements(Replace));134 }135}136 137tooling::Replacement createReplacement(SourceLocation Start, SourceLocation End,138 llvm::StringRef ReplacementText,139 const SourceManager &SM) {140 if (!Start.isValid() || !End.isValid()) {141 llvm::errs() << "start or end location were invalid\n";142 return tooling::Replacement();143 }144 if (SM.getDecomposedLoc(Start).first != SM.getDecomposedLoc(End).first) {145 llvm::errs()146 << "start or end location were in different macro expansions\n";147 return tooling::Replacement();148 }149 Start = SM.getSpellingLoc(Start);150 End = SM.getSpellingLoc(End);151 if (SM.getFileID(Start) != SM.getFileID(End)) {152 llvm::errs() << "start or end location were in different files\n";153 return tooling::Replacement();154 }155 return tooling::Replacement(156 SM, CharSourceRange::getTokenRange(SM.getSpellingLoc(Start),157 SM.getSpellingLoc(End)),158 ReplacementText);159}160 161void addReplacementOrDie(162 SourceLocation Start, SourceLocation End, llvm::StringRef ReplacementText,163 const SourceManager &SM,164 std::map<std::string, tooling::Replacements> *FileToReplacements) {165 const auto R = createReplacement(Start, End, ReplacementText, SM);166 auto Err = (*FileToReplacements)[std::string(R.getFilePath())].add(R);167 if (Err)168 llvm_unreachable(llvm::toString(std::move(Err)).c_str());169}170 171tooling::Replacement createInsertion(SourceLocation Loc,172 llvm::StringRef InsertText,173 const SourceManager &SM) {174 if (Loc.isInvalid()) {175 llvm::errs() << "insert Location is invalid.\n";176 return tooling::Replacement();177 }178 Loc = SM.getSpellingLoc(Loc);179 return tooling::Replacement(SM, Loc, 0, InsertText);180}181 182// Returns the shortest qualified name for declaration `DeclName` in the183// namespace `NsName`. For example, if `DeclName` is "a::b::X" and `NsName`184// is "a::c::d", then "b::X" will be returned.185// Note that if `DeclName` is `::b::X` and `NsName` is `::a::b`, this returns186// "::b::X" instead of "b::X" since there will be a name conflict otherwise.187// \param DeclName A fully qualified name, "::a::b::X" or "a::b::X".188// \param NsName A fully qualified name, "::a::b" or "a::b". Global namespace189// will have empty name.190std::string getShortestQualifiedNameInNamespace(llvm::StringRef DeclName,191 llvm::StringRef NsName) {192 DeclName = DeclName.ltrim(':');193 NsName = NsName.ltrim(':');194 if (!DeclName.contains(':'))195 return std::string(DeclName);196 197 auto NsNameSplitted = splitSymbolName(NsName);198 auto DeclNsSplitted = splitSymbolName(DeclName);199 llvm::StringRef UnqualifiedDeclName = DeclNsSplitted.pop_back_val();200 // If the Decl is in global namespace, there is no need to shorten it.201 if (DeclNsSplitted.empty())202 return std::string(UnqualifiedDeclName);203 // If NsName is the global namespace, we can simply use the DeclName sans204 // leading "::".205 if (NsNameSplitted.empty())206 return std::string(DeclName);207 208 if (NsNameSplitted.front() != DeclNsSplitted.front()) {209 // The DeclName must be fully-qualified, but we still need to decide if a210 // leading "::" is necessary. For example, if `NsName` is "a::b::c" and the211 // `DeclName` is "b::X", then the reference must be qualified as "::b::X"212 // to avoid conflict.213 if (llvm::is_contained(NsNameSplitted, DeclNsSplitted.front()))214 return ("::" + DeclName).str();215 return std::string(DeclName);216 }217 // Since there is already an overlap namespace, we know that `DeclName` can be218 // shortened, so we reduce the longest common prefix.219 auto DeclI = DeclNsSplitted.begin();220 auto DeclE = DeclNsSplitted.end();221 auto NsI = NsNameSplitted.begin();222 auto NsE = NsNameSplitted.end();223 for (; DeclI != DeclE && NsI != NsE && *DeclI == *NsI; ++DeclI, ++NsI) {224 }225 return (DeclI == DeclE)226 ? UnqualifiedDeclName.str()227 : (llvm::join(DeclI, DeclE, "::") + "::" + UnqualifiedDeclName)228 .str();229}230 231std::string wrapCodeInNamespace(StringRef NestedNs, std::string Code) {232 if (Code.back() != '\n')233 Code += "\n";234 auto NsSplitted = splitSymbolName(NestedNs);235 while (!NsSplitted.empty()) {236 // FIXME: consider code style for comments.237 Code = ("namespace " + NsSplitted.back() + " {\n" + Code +238 "} // namespace " + NsSplitted.back() + "\n")239 .str();240 NsSplitted.pop_back();241 }242 return Code;243}244 245// Returns true if \p D is a nested DeclContext in \p Context246bool isNestedDeclContext(const DeclContext *D, const DeclContext *Context) {247 while (D) {248 if (D == Context)249 return true;250 D = D->getParent();251 }252 return false;253}254 255// Returns true if \p D is visible at \p Loc with DeclContext \p DeclCtx.256bool isDeclVisibleAtLocation(const SourceManager &SM, const Decl *D,257 const DeclContext *DeclCtx, SourceLocation Loc) {258 SourceLocation DeclLoc = SM.getSpellingLoc(D->getBeginLoc());259 Loc = SM.getSpellingLoc(Loc);260 return SM.isBeforeInTranslationUnit(DeclLoc, Loc) &&261 (SM.getFileID(DeclLoc) == SM.getFileID(Loc) &&262 isNestedDeclContext(DeclCtx, D->getDeclContext()));263}264 265// Given a qualified symbol name, returns true if the symbol will be266// incorrectly qualified without leading "::". For example, a symbol267// "nx::ny::Foo" in namespace "na::nx::ny" without leading "::"; a symbol268// "util::X" in namespace "na" can potentially conflict with "na::util" (if this269// exists).270bool conflictInNamespace(const ASTContext &AST, llvm::StringRef QualifiedSymbol,271 llvm::StringRef Namespace) {272 auto SymbolSplitted = splitSymbolName(QualifiedSymbol.trim(":"));273 assert(!SymbolSplitted.empty());274 SymbolSplitted.pop_back(); // We are only interested in namespaces.275 276 if (SymbolSplitted.size() >= 1 && !Namespace.empty()) {277 auto SymbolTopNs = SymbolSplitted.front();278 auto NsSplitted = splitSymbolName(Namespace.trim(":"));279 assert(!NsSplitted.empty());280 281 auto LookupDecl = [&AST](const Decl &Scope,282 llvm::StringRef Name) -> const NamedDecl * {283 const auto *DC = llvm::dyn_cast<DeclContext>(&Scope);284 if (!DC)285 return nullptr;286 auto LookupRes = DC->lookup(DeclarationName(&AST.Idents.get(Name)));287 if (LookupRes.empty())288 return nullptr;289 return LookupRes.front();290 };291 // We do not check the outermost namespace since it would not be a292 // conflict if it equals to the symbol's outermost namespace and the293 // symbol name would have been shortened.294 const NamedDecl *Scope =295 LookupDecl(*AST.getTranslationUnitDecl(), NsSplitted.front());296 for (const auto &I : llvm::drop_begin(NsSplitted)) {297 if (I == SymbolTopNs) // Handles "::ny" in "::nx::ny" case.298 return true;299 // Handles "::util" and "::nx::util" conflicts.300 if (Scope) {301 if (LookupDecl(*Scope, SymbolTopNs))302 return true;303 Scope = LookupDecl(*Scope, I);304 }305 }306 if (Scope && LookupDecl(*Scope, SymbolTopNs))307 return true;308 }309 return false;310}311 312bool isTemplateParameter(TypeLoc Type) {313 while (!Type.isNull()) {314 if (Type.getTypeLocClass() == TypeLoc::SubstTemplateTypeParm)315 return true;316 Type = Type.getNextTypeLoc();317 }318 return false;319}320 321} // anonymous namespace322 323ChangeNamespaceTool::ChangeNamespaceTool(324 llvm::StringRef OldNs, llvm::StringRef NewNs, llvm::StringRef FilePattern,325 llvm::ArrayRef<std::string> AllowedSymbolPatterns,326 std::map<std::string, tooling::Replacements> *FileToReplacements,327 llvm::StringRef FallbackStyle)328 : FallbackStyle(FallbackStyle), FileToReplacements(*FileToReplacements),329 OldNamespace(OldNs.ltrim(':')), NewNamespace(NewNs.ltrim(':')),330 FilePattern(FilePattern), FilePatternRE(FilePattern) {331 FileToReplacements->clear();332 auto OldNsSplitted = splitSymbolName(OldNamespace);333 auto NewNsSplitted = splitSymbolName(NewNamespace);334 // Calculates `DiffOldNamespace` and `DiffNewNamespace`.335 while (!OldNsSplitted.empty() && !NewNsSplitted.empty() &&336 OldNsSplitted.front() == NewNsSplitted.front()) {337 OldNsSplitted.erase(OldNsSplitted.begin());338 NewNsSplitted.erase(NewNsSplitted.begin());339 }340 DiffOldNamespace = joinNamespaces(OldNsSplitted);341 DiffNewNamespace = joinNamespaces(NewNsSplitted);342 343 for (const auto &Pattern : AllowedSymbolPatterns)344 AllowedSymbolRegexes.emplace_back(Pattern);345}346 347void ChangeNamespaceTool::registerMatchers(ast_matchers::MatchFinder *Finder) {348 std::string FullOldNs = "::" + OldNamespace;349 // Prefix is the outer-most namespace in DiffOldNamespace. For example, if the350 // OldNamespace is "a::b::c" and DiffOldNamespace is "b::c", then Prefix will351 // be "a::b". Declarations in this namespace will not be visible in the new352 // namespace. If DiffOldNamespace is empty, Prefix will be a invalid name "-".353 llvm::SmallVector<llvm::StringRef, 4> DiffOldNsSplitted;354 llvm::StringRef(DiffOldNamespace)355 .split(DiffOldNsSplitted, "::", /*MaxSplit=*/-1,356 /*KeepEmpty=*/false);357 std::string Prefix = "-";358 if (!DiffOldNsSplitted.empty())359 Prefix = (StringRef(FullOldNs).drop_back(DiffOldNamespace.size()) +360 DiffOldNsSplitted.front())361 .str();362 auto IsInMovedNs =363 allOf(hasAncestor(namespaceDecl(hasName(FullOldNs)).bind("ns_decl")),364 isExpansionInFileMatching(FilePattern));365 auto IsVisibleInNewNs = anyOf(366 IsInMovedNs, unless(hasAncestor(namespaceDecl(hasName(Prefix)))));367 // Match using declarations.368 Finder->addMatcher(369 usingDecl(isExpansionInFileMatching(FilePattern), IsVisibleInNewNs)370 .bind("using"),371 this);372 // Match using namespace declarations.373 Finder->addMatcher(usingDirectiveDecl(isExpansionInFileMatching(FilePattern),374 IsVisibleInNewNs)375 .bind("using_namespace"),376 this);377 // Match namespace alias declarations.378 Finder->addMatcher(namespaceAliasDecl(isExpansionInFileMatching(FilePattern),379 IsVisibleInNewNs)380 .bind("namespace_alias"),381 this);382 383 // Match old namespace blocks.384 Finder->addMatcher(385 namespaceDecl(hasName(FullOldNs), isExpansionInFileMatching(FilePattern))386 .bind("old_ns"),387 this);388 389 // Match class forward-declarations in the old namespace.390 // Note that forward-declarations in classes are not matched.391 Finder->addMatcher(cxxRecordDecl(unless(anyOf(isImplicit(), isDefinition())),392 IsInMovedNs, hasParent(namespaceDecl()))393 .bind("class_fwd_decl"),394 this);395 396 // Match template class forward-declarations in the old namespace.397 Finder->addMatcher(398 classTemplateDecl(unless(hasDescendant(cxxRecordDecl(isDefinition()))),399 IsInMovedNs, hasParent(namespaceDecl()))400 .bind("template_class_fwd_decl"),401 this);402 403 // Match references to types that are not defined in the old namespace.404 // Forward-declarations in the old namespace are also matched since they will405 // be moved back to the old namespace.406 auto DeclMatcher = namedDecl(407 hasAncestor(namespaceDecl()),408 unless(anyOf(409 isImplicit(), hasAncestor(namespaceDecl(isAnonymous())),410 hasAncestor(cxxRecordDecl()),411 allOf(IsInMovedNs, unless(cxxRecordDecl(unless(isDefinition())))))));412 413 // Using shadow declarations in classes always refers to base class, which414 // does not need to be qualified since it can be inferred from inheritance.415 // Note that this does not match using alias declarations.416 auto UsingShadowDeclInClass =417 usingDecl(hasAnyUsingShadowDecl(decl()), hasParent(cxxRecordDecl()));418 419 // Match TypeLocs on the declaration. Carefully match only the outermost420 // TypeLoc and template specialization arguments (which are not outermost)421 // that are directly linked to types matching `DeclMatcher`. Nested name422 // specifier locs are handled separately below.423 Finder->addMatcher(424 typeLoc(IsInMovedNs,425 loc(qualType(hasDeclaration(DeclMatcher.bind("from_decl")))),426 unless(anyOf(hasParent(typeLoc(loc(qualType(427 hasDeclaration(DeclMatcher),428 unless(templateSpecializationType()))))),429 hasParent(nestedNameSpecifierLoc()),430 hasAncestor(decl(isImplicit())),431 hasAncestor(UsingShadowDeclInClass),432 hasAncestor(functionDecl(isDefaulted())))),433 hasAncestor(decl().bind("dc")))434 .bind("type"),435 this);436 437 // Types in `UsingShadowDecl` is not matched by `typeLoc` above, so we need to438 // special case it.439 // Since using declarations inside classes must have the base class in the440 // nested name specifier, we leave it to the nested name specifier matcher.441 Finder->addMatcher(usingDecl(IsInMovedNs, hasAnyUsingShadowDecl(decl()),442 unless(UsingShadowDeclInClass))443 .bind("using_with_shadow"),444 this);445 446 // Handle types in nested name specifier. Specifiers that are in a TypeLoc447 // matched above are not matched, e.g. "A::" in "A::A" is not matched since448 // "A::A" would have already been fixed.449 Finder->addMatcher(450 nestedNameSpecifierLoc(451 hasAncestor(decl(IsInMovedNs).bind("dc")),452 loc(nestedNameSpecifier(453 specifiesType(hasDeclaration(DeclMatcher.bind("from_decl"))))),454 unless(anyOf(hasAncestor(decl(isImplicit())),455 hasAncestor(UsingShadowDeclInClass),456 hasAncestor(functionDecl(isDefaulted())),457 hasAncestor(typeLoc(loc(qualType(hasDeclaration(458 decl(equalsBoundNode("from_decl"))))))))))459 .bind("nested_specifier_loc"),460 this);461 462 // Matches base class initializers in constructors. TypeLocs of base class463 // initializers do not need to be fixed. For example,464 // class X : public a::b::Y {465 // public:466 // X() : Y::Y() {} // Y::Y do not need namespace specifier.467 // };468 Finder->addMatcher(469 cxxCtorInitializer(isBaseInitializer()).bind("base_initializer"), this);470 471 // Handle function.472 // Only handle functions that are defined in a namespace excluding member473 // function, static methods (qualified by nested specifier), and functions474 // defined in the global namespace.475 // Note that the matcher does not exclude calls to out-of-line static method476 // definitions, so we need to exclude them in the callback handler.477 auto FuncMatcher =478 functionDecl(unless(anyOf(cxxMethodDecl(), IsInMovedNs,479 hasAncestor(namespaceDecl(isAnonymous())),480 hasAncestor(cxxRecordDecl()))),481 hasParent(namespaceDecl()));482 Finder->addMatcher(expr(hasAncestor(decl().bind("dc")), IsInMovedNs,483 unless(hasAncestor(decl(isImplicit()))),484 anyOf(callExpr(callee(FuncMatcher)).bind("call"),485 declRefExpr(to(FuncMatcher.bind("func_decl")))486 .bind("func_ref"))),487 this);488 489 auto GlobalVarMatcher = varDecl(490 hasGlobalStorage(), hasParent(namespaceDecl()),491 unless(anyOf(IsInMovedNs, hasAncestor(namespaceDecl(isAnonymous())))));492 Finder->addMatcher(declRefExpr(IsInMovedNs, hasAncestor(decl().bind("dc")),493 to(GlobalVarMatcher.bind("var_decl")))494 .bind("var_ref"),495 this);496 497 // Handle unscoped enum constant.498 auto UnscopedEnumMatcher = enumConstantDecl(hasParent(enumDecl(499 hasParent(namespaceDecl()),500 unless(anyOf(isScoped(), IsInMovedNs, hasAncestor(cxxRecordDecl()),501 hasAncestor(namespaceDecl(isAnonymous())))))));502 Finder->addMatcher(503 declRefExpr(IsInMovedNs, hasAncestor(decl().bind("dc")),504 to(UnscopedEnumMatcher.bind("enum_const_decl")))505 .bind("enum_const_ref"),506 this);507}508 509void ChangeNamespaceTool::run(510 const ast_matchers::MatchFinder::MatchResult &Result) {511 if (const auto *Using = Result.Nodes.getNodeAs<UsingDecl>("using")) {512 UsingDecls.insert(Using);513 } else if (const auto *UsingNamespace =514 Result.Nodes.getNodeAs<UsingDirectiveDecl>(515 "using_namespace")) {516 UsingNamespaceDecls.insert(UsingNamespace);517 } else if (const auto *NamespaceAlias =518 Result.Nodes.getNodeAs<NamespaceAliasDecl>(519 "namespace_alias")) {520 NamespaceAliasDecls.insert(NamespaceAlias);521 } else if (const auto *NsDecl =522 Result.Nodes.getNodeAs<NamespaceDecl>("old_ns")) {523 moveOldNamespace(Result, NsDecl);524 } else if (const auto *FwdDecl =525 Result.Nodes.getNodeAs<CXXRecordDecl>("class_fwd_decl")) {526 moveClassForwardDeclaration(Result, cast<NamedDecl>(FwdDecl));527 } else if (const auto *TemplateFwdDecl =528 Result.Nodes.getNodeAs<ClassTemplateDecl>(529 "template_class_fwd_decl")) {530 moveClassForwardDeclaration(Result, cast<NamedDecl>(TemplateFwdDecl));531 } else if (const auto *UsingWithShadow =532 Result.Nodes.getNodeAs<UsingDecl>("using_with_shadow")) {533 fixUsingShadowDecl(Result, UsingWithShadow);534 } else if (const auto *Specifier =535 Result.Nodes.getNodeAs<NestedNameSpecifierLoc>(536 "nested_specifier_loc")) {537 SourceLocation Start = Specifier->getBeginLoc();538 SourceLocation End = endLocationForType(Specifier->castAsTypeLoc());539 fixTypeLoc(Result, Start, End, Specifier->castAsTypeLoc());540 } else if (const auto *BaseInitializer =541 Result.Nodes.getNodeAs<CXXCtorInitializer>(542 "base_initializer")) {543 BaseCtorInitializerTypeLocs.push_back(544 BaseInitializer->getTypeSourceInfo()->getTypeLoc());545 } else if (const auto *TLoc = Result.Nodes.getNodeAs<TypeLoc>("type")) {546 // This avoids fixing types with record types as qualifier, which is not547 // filtered by matchers in some cases, e.g. the type is templated. We should548 // handle the record type qualifier instead.549 TypeLoc Loc = *TLoc;550 if (auto QTL = Loc.getAs<QualifiedTypeLoc>())551 Loc = QTL.getUnqualifiedLoc();552 // FIXME: avoid changing injected class names.553 if (NestedNameSpecifier NestedNameSpecifier =554 Loc.getPrefix().getNestedNameSpecifier();555 NestedNameSpecifier.getKind() == NestedNameSpecifier::Kind::Type &&556 NestedNameSpecifier.getAsType()->isRecordType())557 return;558 fixTypeLoc(Result, Loc.getNonElaboratedBeginLoc(), endLocationForType(Loc),559 Loc);560 } else if (const auto *VarRef =561 Result.Nodes.getNodeAs<DeclRefExpr>("var_ref")) {562 const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var_decl");563 assert(Var);564 if (Var->getCanonicalDecl()->isStaticDataMember())565 return;566 const auto *Context = Result.Nodes.getNodeAs<Decl>("dc");567 assert(Context && "Empty decl context.");568 fixDeclRefExpr(Result, Context->getDeclContext(),569 llvm::cast<NamedDecl>(Var), VarRef);570 } else if (const auto *EnumConstRef =571 Result.Nodes.getNodeAs<DeclRefExpr>("enum_const_ref")) {572 // Do not rename the reference if it is already scoped by the EnumDecl name.573 if (NestedNameSpecifier Qualifier = EnumConstRef->getQualifier();574 Qualifier.getKind() == NestedNameSpecifier::Kind::Type &&575 Qualifier.getAsType()->isEnumeralType())576 return;577 const auto *EnumConstDecl =578 Result.Nodes.getNodeAs<EnumConstantDecl>("enum_const_decl");579 assert(EnumConstDecl);580 const auto *Context = Result.Nodes.getNodeAs<Decl>("dc");581 assert(Context && "Empty decl context.");582 // FIXME: this would qualify "ns::VALUE" as "ns::EnumValue::VALUE". Fix it583 // if it turns out to be an issue.584 fixDeclRefExpr(Result, Context->getDeclContext(),585 llvm::cast<NamedDecl>(EnumConstDecl), EnumConstRef);586 } else if (const auto *FuncRef =587 Result.Nodes.getNodeAs<DeclRefExpr>("func_ref")) {588 // If this reference has been processed as a function call, we do not589 // process it again.590 if (!ProcessedFuncRefs.insert(FuncRef).second)591 return;592 const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func_decl");593 assert(Func);594 const auto *Context = Result.Nodes.getNodeAs<Decl>("dc");595 assert(Context && "Empty decl context.");596 fixDeclRefExpr(Result, Context->getDeclContext(),597 llvm::cast<NamedDecl>(Func), FuncRef);598 } else {599 const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");600 assert(Call != nullptr && "Expecting callback for CallExpr.");601 const auto *CalleeFuncRef =602 llvm::cast<DeclRefExpr>(Call->getCallee()->IgnoreImplicit());603 ProcessedFuncRefs.insert(CalleeFuncRef);604 const FunctionDecl *Func = Call->getDirectCallee();605 assert(Func != nullptr);606 // FIXME: ignore overloaded operators. This would miss cases where operators607 // are called by qualified names (i.e. "ns::operator <"). Ignore such608 // cases for now.609 if (Func->isOverloadedOperator())610 return;611 // Ignore out-of-line static methods since they will be handled by nested612 // name specifiers.613 if (Func->getCanonicalDecl()->getStorageClass() ==614 StorageClass::SC_Static &&615 Func->isOutOfLine())616 return;617 const auto *Context = Result.Nodes.getNodeAs<Decl>("dc");618 assert(Context && "Empty decl context.");619 SourceRange CalleeRange = Call->getCallee()->getSourceRange();620 replaceQualifiedSymbolInDeclContext(621 Result, Context->getDeclContext(), CalleeRange.getBegin(),622 CalleeRange.getEnd(), llvm::cast<NamedDecl>(Func));623 }624}625 626static SourceLocation getLocAfterNamespaceLBrace(const NamespaceDecl *NsDecl,627 const SourceManager &SM,628 const LangOptions &LangOpts) {629 std::unique_ptr<Lexer> Lex =630 getLexerStartingFromLoc(NsDecl->getBeginLoc(), SM, LangOpts);631 assert(Lex && "Failed to create lexer from the beginning of namespace.");632 if (!Lex)633 return SourceLocation();634 Token Tok;635 while (!Lex->LexFromRawLexer(Tok) && Tok.isNot(tok::TokenKind::l_brace)) {636 }637 return Tok.isNot(tok::TokenKind::l_brace)638 ? SourceLocation()639 : Tok.getEndLoc().getLocWithOffset(1);640}641 642// Stores information about a moved namespace in `MoveNamespaces` and leaves643// the actual movement to `onEndOfTranslationUnit()`.644void ChangeNamespaceTool::moveOldNamespace(645 const ast_matchers::MatchFinder::MatchResult &Result,646 const NamespaceDecl *NsDecl) {647 // If the namespace is empty, do nothing.648 if (Decl::castToDeclContext(NsDecl)->decls_empty())649 return;650 651 const SourceManager &SM = *Result.SourceManager;652 // Get the range of the code in the old namespace.653 SourceLocation Start =654 getLocAfterNamespaceLBrace(NsDecl, SM, Result.Context->getLangOpts());655 assert(Start.isValid() && "Can't find l_brace for namespace.");656 MoveNamespace MoveNs;657 MoveNs.Offset = SM.getFileOffset(Start);658 // The range of the moved namespace is from the location just past the left659 // brace to the location right before the right brace.660 MoveNs.Length = SM.getFileOffset(NsDecl->getRBraceLoc()) - MoveNs.Offset;661 662 // Insert the new namespace after `DiffOldNamespace`. For example, if663 // `OldNamespace` is "a::b::c" and `NewNamespace` is `a::x::y`, then664 // "x::y" will be inserted inside the existing namespace "a" and after "a::b".665 // `OuterNs` is the first namespace in `DiffOldNamespace`, e.g. "namespace b"666 // in the above example.667 // If there is no outer namespace (i.e. DiffOldNamespace is empty), the new668 // namespace will be a nested namespace in the old namespace.669 const NamespaceDecl *OuterNs = getOuterNamespace(NsDecl, DiffOldNamespace);670 SourceLocation InsertionLoc = Start;671 if (OuterNs) {672 SourceLocation LocAfterNs = getStartOfNextLine(673 OuterNs->getRBraceLoc(), SM, Result.Context->getLangOpts());674 assert(LocAfterNs.isValid() &&675 "Failed to get location after DiffOldNamespace");676 InsertionLoc = LocAfterNs;677 }678 MoveNs.InsertionOffset = SM.getFileOffset(SM.getSpellingLoc(InsertionLoc));679 MoveNs.FID = SM.getFileID(Start);680 MoveNs.SourceMgr = Result.SourceManager;681 MoveNamespaces[std::string(SM.getFilename(Start))].push_back(MoveNs);682}683 684// Removes a class forward declaration from the code in the moved namespace and685// creates an `InsertForwardDeclaration` to insert the forward declaration back686// into the old namespace after moving code from the old namespace to the new687// namespace.688// For example, changing "a" to "x":689// Old code:690// namespace a {691// class FWD;692// class A { FWD *fwd; }693// } // a694// New code:695// namespace a {696// class FWD;697// } // a698// namespace x {699// class A { a::FWD *fwd; }700// } // x701void ChangeNamespaceTool::moveClassForwardDeclaration(702 const ast_matchers::MatchFinder::MatchResult &Result,703 const NamedDecl *FwdDecl) {704 SourceLocation Start = FwdDecl->getBeginLoc();705 SourceLocation End = FwdDecl->getEndLoc();706 const SourceManager &SM = *Result.SourceManager;707 SourceLocation AfterSemi = Lexer::findLocationAfterToken(708 End, tok::semi, SM, Result.Context->getLangOpts(),709 /*SkipTrailingWhitespaceAndNewLine=*/true);710 if (AfterSemi.isValid())711 End = AfterSemi.getLocWithOffset(-1);712 // Delete the forward declaration from the code to be moved.713 addReplacementOrDie(Start, End, "", SM, &FileToReplacements);714 llvm::StringRef Code = Lexer::getSourceText(715 CharSourceRange::getTokenRange(SM.getSpellingLoc(Start),716 SM.getSpellingLoc(End)),717 SM, Result.Context->getLangOpts());718 // Insert the forward declaration back into the old namespace after moving the719 // code from old namespace to new namespace.720 // Insertion information is stored in `InsertFwdDecls` and actual721 // insertion will be performed in `onEndOfTranslationUnit`.722 // Get the (old) namespace that contains the forward declaration.723 const auto *NsDecl = Result.Nodes.getNodeAs<NamespaceDecl>("ns_decl");724 // The namespace contains the forward declaration, so it must not be empty.725 assert(!NsDecl->decls_empty());726 const auto Insertion = createInsertion(727 getLocAfterNamespaceLBrace(NsDecl, SM, Result.Context->getLangOpts()),728 Code, SM);729 InsertForwardDeclaration InsertFwd;730 InsertFwd.InsertionOffset = Insertion.getOffset();731 InsertFwd.ForwardDeclText = Insertion.getReplacementText().str();732 InsertFwdDecls[std::string(Insertion.getFilePath())].push_back(InsertFwd);733}734 735// Replaces a qualified symbol (in \p DeclCtx) that refers to a declaration \p736// FromDecl with the shortest qualified name possible when the reference is in737// `NewNamespace`.738void ChangeNamespaceTool::replaceQualifiedSymbolInDeclContext(739 const ast_matchers::MatchFinder::MatchResult &Result,740 const DeclContext *DeclCtx, SourceLocation Start, SourceLocation End,741 const NamedDecl *FromDecl) {742 const auto *NsDeclContext = DeclCtx->getEnclosingNamespaceContext();743 if (llvm::isa<TranslationUnitDecl>(NsDeclContext)) {744 // This should not happen in usual unless the TypeLoc is in function type745 // parameters, e.g `std::function<void(T)>`. In this case, DeclContext of746 // `T` will be the translation unit. We simply use fully-qualified name747 // here.748 // Note that `FromDecl` must not be defined in the old namespace (according749 // to `DeclMatcher`), so its fully-qualified name will not change after750 // changing the namespace.751 addReplacementOrDie(Start, End, FromDecl->getQualifiedNameAsString(),752 *Result.SourceManager, &FileToReplacements);753 return;754 }755 const auto *NsDecl = llvm::cast<NamespaceDecl>(NsDeclContext);756 // Calculate the name of the `NsDecl` after it is moved to new namespace.757 std::string OldNs = NsDecl->getQualifiedNameAsString();758 llvm::StringRef Postfix = OldNs;759 bool Consumed = Postfix.consume_front(OldNamespace);760 assert(Consumed && "Expect OldNS to start with OldNamespace.");761 (void)Consumed;762 const std::string NewNs = (NewNamespace + Postfix).str();763 764 llvm::StringRef NestedName = Lexer::getSourceText(765 CharSourceRange::getTokenRange(766 Result.SourceManager->getSpellingLoc(Start),767 Result.SourceManager->getSpellingLoc(End)),768 *Result.SourceManager, Result.Context->getLangOpts());769 std::string FromDeclName = FromDecl->getQualifiedNameAsString();770 for (llvm::Regex &RE : AllowedSymbolRegexes)771 if (RE.match(FromDeclName))772 return;773 std::string ReplaceName =774 getShortestQualifiedNameInNamespace(FromDeclName, NewNs);775 // Checks if there is any using namespace declarations that can shorten the776 // qualified name.777 for (const auto *UsingNamespace : UsingNamespaceDecls) {778 if (!isDeclVisibleAtLocation(*Result.SourceManager, UsingNamespace, DeclCtx,779 Start))780 continue;781 StringRef FromDeclNameRef = FromDeclName;782 if (FromDeclNameRef.consume_front(UsingNamespace->getNominatedNamespace()783 ->getQualifiedNameAsString())) {784 FromDeclNameRef = FromDeclNameRef.drop_front(2);785 if (FromDeclNameRef.size() < ReplaceName.size())786 ReplaceName = std::string(FromDeclNameRef);787 }788 }789 // Checks if there is any namespace alias declarations that can shorten the790 // qualified name.791 for (const auto *NamespaceAlias : NamespaceAliasDecls) {792 if (!isDeclVisibleAtLocation(*Result.SourceManager, NamespaceAlias, DeclCtx,793 Start))794 continue;795 StringRef FromDeclNameRef = FromDeclName;796 if (FromDeclNameRef.consume_front(797 NamespaceAlias->getNamespace()->getQualifiedNameAsString() +798 "::")) {799 std::string AliasName = NamespaceAlias->getNameAsString();800 std::string AliasQualifiedName =801 NamespaceAlias->getQualifiedNameAsString();802 // We only consider namespace aliases define in the global namespace or803 // in namespaces that are directly visible from the reference, i.e.804 // ancestor of the `OldNs`. Note that declarations in ancestor namespaces805 // but not visible in the new namespace is filtered out by806 // "IsVisibleInNewNs" matcher.807 if (AliasQualifiedName != AliasName) {808 // The alias is defined in some namespace.809 assert(StringRef(AliasQualifiedName).ends_with("::" + AliasName));810 llvm::StringRef AliasNs =811 StringRef(AliasQualifiedName).drop_back(AliasName.size() + 2);812 if (!llvm::StringRef(OldNs).starts_with(AliasNs))813 continue;814 }815 std::string NameWithAliasNamespace =816 (AliasName + "::" + FromDeclNameRef).str();817 if (NameWithAliasNamespace.size() < ReplaceName.size())818 ReplaceName = NameWithAliasNamespace;819 }820 }821 // Checks if there is any using shadow declarations that can shorten the822 // qualified name.823 bool Matched = false;824 for (const UsingDecl *Using : UsingDecls) {825 if (Matched)826 break;827 if (isDeclVisibleAtLocation(*Result.SourceManager, Using, DeclCtx, Start)) {828 for (const auto *UsingShadow : Using->shadows()) {829 const auto *TargetDecl = UsingShadow->getTargetDecl();830 if (TargetDecl->getQualifiedNameAsString() ==831 FromDecl->getQualifiedNameAsString()) {832 ReplaceName = FromDecl->getNameAsString();833 Matched = true;834 break;835 }836 }837 }838 }839 bool Conflict = conflictInNamespace(DeclCtx->getParentASTContext(),840 ReplaceName, NewNamespace);841 // If the new nested name in the new namespace is the same as it was in the842 // old namespace, we don't create replacement unless there can be ambiguity.843 if ((NestedName == ReplaceName && !Conflict) ||844 (NestedName.starts_with("::") && NestedName.drop_front(2) == ReplaceName))845 return;846 // If the reference need to be fully-qualified, add a leading "::" unless847 // NewNamespace is the global namespace.848 if (ReplaceName == FromDeclName && !NewNamespace.empty() && Conflict)849 ReplaceName = "::" + ReplaceName;850 addReplacementOrDie(Start, End, ReplaceName, *Result.SourceManager,851 &FileToReplacements);852}853 854// Replace the [Start, End] of `Type` with the shortest qualified name when the855// `Type` is in `NewNamespace`.856void ChangeNamespaceTool::fixTypeLoc(857 const ast_matchers::MatchFinder::MatchResult &Result, SourceLocation Start,858 SourceLocation End, TypeLoc Type) {859 // FIXME: do not rename template parameter.860 if (Start.isInvalid() || End.isInvalid())861 return;862 // Types of CXXCtorInitializers do not need to be fixed.863 if (llvm::is_contained(BaseCtorInitializerTypeLocs, Type))864 return;865 if (isTemplateParameter(Type))866 return;867 // The declaration which this TypeLoc refers to.868 const auto *FromDecl = Result.Nodes.getNodeAs<NamedDecl>("from_decl");869 // `hasDeclaration` gives underlying declaration, but if the type is870 // a typedef type, we need to use the typedef type instead.871 auto IsInMovedNs = [&](const NamedDecl *D) {872 if (!llvm::StringRef(D->getQualifiedNameAsString())873 .starts_with(OldNamespace + "::"))874 return false;875 auto ExpansionLoc = Result.SourceManager->getExpansionLoc(D->getBeginLoc());876 if (ExpansionLoc.isInvalid())877 return false;878 llvm::StringRef Filename = Result.SourceManager->getFilename(ExpansionLoc);879 return FilePatternRE.match(Filename);880 };881 // Make `FromDecl` the immediate declaration that `Type` refers to, i.e. if882 // `Type` is an alias type, we make `FromDecl` the type alias declaration.883 // Also, don't fix the \p Type if it refers to a type alias decl in the moved884 // namespace since the alias decl will be moved along with the type reference.885 if (auto *Typedef = Type.getType()->getAs<TypedefType>()) {886 FromDecl = Typedef->getDecl();887 if (IsInMovedNs(FromDecl))888 return;889 } else if (auto *TemplateType =890 Type.getType()->getAs<TemplateSpecializationType>()) {891 if (TemplateType->isTypeAlias()) {892 FromDecl = TemplateType->getTemplateName().getAsTemplateDecl();893 if (IsInMovedNs(FromDecl))894 return;895 }896 }897 const auto *DeclCtx = Result.Nodes.getNodeAs<Decl>("dc");898 assert(DeclCtx && "Empty decl context.");899 replaceQualifiedSymbolInDeclContext(Result, DeclCtx->getDeclContext(), Start,900 End, FromDecl);901}902 903void ChangeNamespaceTool::fixUsingShadowDecl(904 const ast_matchers::MatchFinder::MatchResult &Result,905 const UsingDecl *UsingDeclaration) {906 SourceLocation Start = UsingDeclaration->getBeginLoc();907 SourceLocation End = UsingDeclaration->getEndLoc();908 if (Start.isInvalid() || End.isInvalid())909 return;910 911 assert(UsingDeclaration->shadow_size() > 0);912 // FIXME: it might not be always accurate to use the first using-decl.913 const NamedDecl *TargetDecl =914 UsingDeclaration->shadow_begin()->getTargetDecl();915 std::string TargetDeclName = TargetDecl->getQualifiedNameAsString();916 // FIXME: check if target_decl_name is in moved ns, which doesn't make much917 // sense. If this happens, we need to use name with the new namespace.918 // Use fully qualified name in UsingDecl for now.919 addReplacementOrDie(Start, End, "using ::" + TargetDeclName,920 *Result.SourceManager, &FileToReplacements);921}922 923void ChangeNamespaceTool::fixDeclRefExpr(924 const ast_matchers::MatchFinder::MatchResult &Result,925 const DeclContext *UseContext, const NamedDecl *From,926 const DeclRefExpr *Ref) {927 SourceRange RefRange = Ref->getSourceRange();928 replaceQualifiedSymbolInDeclContext(Result, UseContext, RefRange.getBegin(),929 RefRange.getEnd(), From);930}931 932void ChangeNamespaceTool::onEndOfTranslationUnit() {933 // Move namespace blocks and insert forward declaration to old namespace.934 for (const auto &FileAndNsMoves : MoveNamespaces) {935 auto &NsMoves = FileAndNsMoves.second;936 if (NsMoves.empty())937 continue;938 const std::string &FilePath = FileAndNsMoves.first;939 auto &Replaces = FileToReplacements[FilePath];940 auto &SM = *NsMoves.begin()->SourceMgr;941 llvm::StringRef Code = SM.getBufferData(NsMoves.begin()->FID);942 auto ChangedCode = tooling::applyAllReplacements(Code, Replaces);943 if (!ChangedCode) {944 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";945 continue;946 }947 // Replacements on the changed code for moving namespaces and inserting948 // forward declarations to old namespaces.949 tooling::Replacements NewReplacements;950 // Cut the changed code from the old namespace and paste the code in the new951 // namespace.952 for (const auto &NsMove : NsMoves) {953 // Calculate the range of the old namespace block in the changed954 // code.955 const unsigned NewOffset = Replaces.getShiftedCodePosition(NsMove.Offset);956 const unsigned NewLength =957 Replaces.getShiftedCodePosition(NsMove.Offset + NsMove.Length) -958 NewOffset;959 tooling::Replacement Deletion(FilePath, NewOffset, NewLength, "");960 std::string MovedCode = ChangedCode->substr(NewOffset, NewLength);961 std::string MovedCodeWrappedInNewNs =962 wrapCodeInNamespace(DiffNewNamespace, MovedCode);963 // Calculate the new offset at which the code will be inserted in the964 // changed code.965 unsigned NewInsertionOffset =966 Replaces.getShiftedCodePosition(NsMove.InsertionOffset);967 tooling::Replacement Insertion(FilePath, NewInsertionOffset, 0,968 MovedCodeWrappedInNewNs);969 addOrMergeReplacement(Deletion, &NewReplacements);970 addOrMergeReplacement(Insertion, &NewReplacements);971 }972 // After moving namespaces, insert forward declarations back to old973 // namespaces.974 const auto &FwdDeclInsertions = InsertFwdDecls[FilePath];975 for (const auto &FwdDeclInsertion : FwdDeclInsertions) {976 unsigned NewInsertionOffset =977 Replaces.getShiftedCodePosition(FwdDeclInsertion.InsertionOffset);978 tooling::Replacement Insertion(FilePath, NewInsertionOffset, 0,979 FwdDeclInsertion.ForwardDeclText);980 addOrMergeReplacement(Insertion, &NewReplacements);981 }982 // Add replacements referring to the changed code to existing replacements,983 // which refers to the original code.984 Replaces = Replaces.merge(NewReplacements);985 auto Style =986 format::getStyle(format::DefaultFormatStyle, FilePath, FallbackStyle);987 if (!Style) {988 llvm::errs() << llvm::toString(Style.takeError()) << "\n";989 continue;990 }991 // Clean up old namespaces if there is nothing in it after moving.992 auto CleanReplacements =993 format::cleanupAroundReplacements(Code, Replaces, *Style);994 if (!CleanReplacements) {995 llvm::errs() << llvm::toString(CleanReplacements.takeError()) << "\n";996 continue;997 }998 FileToReplacements[FilePath] = *CleanReplacements;999 }1000 1001 // Make sure we don't generate replacements for files that do not match1002 // FilePattern.1003 for (auto &Entry : FileToReplacements)1004 if (!FilePatternRE.match(Entry.first))1005 Entry.second.clear();1006}1007 1008} // namespace change_namespace1009} // namespace clang1010