| //===--- DefineInline.cpp ----------------------------------------*- C++-*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "AST.h" |
| #include "FindTarget.h" |
| #include "Selection.h" |
| #include "SourceCode.h" |
| #include "XRefs.h" |
| #include "refactor/Tweak.h" |
| #include "support/Logger.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/ASTTypeTraits.h" |
| #include "clang/AST/Decl.h" |
| #include "clang/AST/DeclBase.h" |
| #include "clang/AST/DeclCXX.h" |
| #include "clang/AST/DeclTemplate.h" |
| #include "clang/AST/Expr.h" |
| #include "clang/AST/ExprCXX.h" |
| #include "clang/AST/NestedNameSpecifier.h" |
| #include "clang/AST/PrettyPrinter.h" |
| #include "clang/AST/RecursiveASTVisitor.h" |
| #include "clang/AST/Stmt.h" |
| #include "clang/AST/TemplateBase.h" |
| #include "clang/AST/Type.h" |
| #include "clang/AST/TypeLoc.h" |
| #include "clang/Basic/LangOptions.h" |
| #include "clang/Basic/SourceLocation.h" |
| #include "clang/Basic/SourceManager.h" |
| #include "clang/Basic/TokenKinds.h" |
| #include "clang/Driver/Types.h" |
| #include "clang/Index/IndexDataConsumer.h" |
| #include "clang/Index/IndexSymbol.h" |
| #include "clang/Index/IndexingAction.h" |
| #include "clang/Lex/Lexer.h" |
| #include "clang/Lex/Preprocessor.h" |
| #include "clang/Lex/Token.h" |
| #include "clang/Sema/Lookup.h" |
| #include "clang/Sema/Sema.h" |
| #include "clang/Tooling/Core/Replacement.h" |
| #include "llvm/ADT/DenseMap.h" |
| #include "llvm/ADT/DenseSet.h" |
| #include "llvm/ADT/None.h" |
| #include "llvm/ADT/Optional.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Support/Casting.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Support/FormatAdapters.h" |
| #include "llvm/Support/FormatVariadic.h" |
| #include "llvm/Support/Signals.h" |
| #include "llvm/Support/raw_ostream.h" |
| #include <cstddef> |
| #include <set> |
| #include <string> |
| #include <unordered_map> |
| #include <utility> |
| #include <vector> |
| |
| namespace clang { |
| namespace clangd { |
| namespace { |
| |
| // Returns semicolon location for the given FD. Since AST doesn't contain that |
| // information, searches for a semicolon by lexing from end of function decl |
| // while skipping comments. |
| llvm::Optional<SourceLocation> getSemicolonForDecl(const FunctionDecl *FD) { |
| const SourceManager &SM = FD->getASTContext().getSourceManager(); |
| const LangOptions &LangOpts = FD->getASTContext().getLangOpts(); |
| |
| SourceLocation CurLoc = FD->getEndLoc(); |
| auto NextTok = Lexer::findNextToken(CurLoc, SM, LangOpts); |
| if (!NextTok || !NextTok->is(tok::semi)) |
| return llvm::None; |
| return NextTok->getLocation(); |
| } |
| |
| // Deduces the FunctionDecl from a selection. Requires either the function body |
| // or the function decl to be selected. Returns null if none of the above |
| // criteria is met. |
| const FunctionDecl *getSelectedFunction(const SelectionTree::Node *SelNode) { |
| const DynTypedNode &AstNode = SelNode->ASTNode; |
| if (const FunctionDecl *FD = AstNode.get<FunctionDecl>()) |
| return FD; |
| if (AstNode.get<CompoundStmt>() && |
| SelNode->Selected == SelectionTree::Complete) { |
| if (const SelectionTree::Node *P = SelNode->Parent) |
| return P->ASTNode.get<FunctionDecl>(); |
| } |
| return nullptr; |
| } |
| |
| // Checks the decls mentioned in Source are visible in the context of Target. |
| // Achieves that by checking declarations occur before target location in |
| // translation unit or declared in the same class. |
| bool checkDeclsAreVisible(const llvm::DenseSet<const Decl *> &DeclRefs, |
| const FunctionDecl *Target, const SourceManager &SM) { |
| SourceLocation TargetLoc = Target->getLocation(); |
| // To be used in visibility check below, decls in a class are visible |
| // independent of order. |
| const RecordDecl *Class = nullptr; |
| if (const auto *MD = llvm::dyn_cast<CXXMethodDecl>(Target)) |
| Class = MD->getParent(); |
| |
| for (const auto *DR : DeclRefs) { |
| // Use canonical decl, since having one decl before target is enough. |
| const Decl *D = DR->getCanonicalDecl(); |
| if (D == Target) |
| continue; |
| SourceLocation DeclLoc = D->getLocation(); |
| |
| // FIXME: Allow declarations from different files with include insertion. |
| if (!SM.isWrittenInSameFile(DeclLoc, TargetLoc)) |
| return false; |
| |
| // If declaration is before target, then it is visible. |
| if (SM.isBeforeInTranslationUnit(DeclLoc, TargetLoc)) |
| continue; |
| |
| // Otherwise they need to be in same class |
| if (!Class) |
| return false; |
| const RecordDecl *Parent = nullptr; |
| if (const auto *MD = llvm::dyn_cast<CXXMethodDecl>(D)) |
| Parent = MD->getParent(); |
| else if (const auto *FD = llvm::dyn_cast<FieldDecl>(D)) |
| Parent = FD->getParent(); |
| if (Parent != Class) |
| return false; |
| } |
| return true; |
| } |
| |
| // Rewrites body of FD by re-spelling all of the names to make sure they are |
| // still valid in context of Target. |
| llvm::Expected<std::string> qualifyAllDecls(const FunctionDecl *FD, |
| const FunctionDecl *Target, |
| const HeuristicResolver *Resolver) { |
| // There are three types of spellings that needs to be qualified in a function |
| // body: |
| // - Types: Foo -> ns::Foo |
| // - DeclRefExpr: ns2::foo() -> ns1::ns2::foo(); |
| // - UsingDecls: |
| // using ns2::foo -> using ns1::ns2::foo |
| // using namespace ns2 -> using namespace ns1::ns2 |
| // using ns3 = ns2 -> using ns3 = ns1::ns2 |
| // |
| // Go over all references inside a function body to generate replacements that |
| // will qualify those. So that body can be moved into an arbitrary file. |
| // We perform the qualification by qualifying the first type/decl in a |
| // (un)qualified name. e.g: |
| // namespace a { namespace b { class Bar{}; void foo(); } } |
| // b::Bar x; -> a::b::Bar x; |
| // foo(); -> a::b::foo(); |
| |
| auto *TargetContext = Target->getLexicalDeclContext(); |
| const SourceManager &SM = FD->getASTContext().getSourceManager(); |
| |
| tooling::Replacements Replacements; |
| bool HadErrors = false; |
| findExplicitReferences( |
| FD->getBody(), |
| [&](ReferenceLoc Ref) { |
| // Since we want to qualify only the first qualifier, skip names with a |
| // qualifier. |
| if (Ref.Qualifier) |
| return; |
| // There might be no decl in dependent contexts, there's nothing much we |
| // can do in such cases. |
| if (Ref.Targets.empty()) |
| return; |
| // Do not qualify names introduced by macro expansions. |
| if (Ref.NameLoc.isMacroID()) |
| return; |
| |
| for (const NamedDecl *ND : Ref.Targets) { |
| if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) { |
| elog("define inline: Targets from multiple contexts: {0}, {1}", |
| printQualifiedName(*Ref.Targets.front()), |
| printQualifiedName(*ND)); |
| HadErrors = true; |
| return; |
| } |
| } |
| // All Targets are in the same scope, so we can safely chose first one. |
| const NamedDecl *ND = Ref.Targets.front(); |
| // Skip anything from a non-namespace scope, these can be: |
| // - Function or Method scopes, which means decl is local and doesn't |
| // need |
| // qualification. |
| // - From Class/Struct/Union scope, which again doesn't need any |
| // qualifiers, |
| // rather the left side of it requires qualification, like: |
| // namespace a { class Bar { public: static int x; } } |
| // void foo() { Bar::x; } |
| // ~~~~~ -> we need to qualify Bar not x. |
| if (!ND->getDeclContext()->isNamespace()) |
| return; |
| |
| const std::string Qualifier = getQualification( |
| FD->getASTContext(), TargetContext, Target->getBeginLoc(), ND); |
| if (auto Err = Replacements.add( |
| tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier))) { |
| HadErrors = true; |
| elog("define inline: Failed to add quals: {0}", std::move(Err)); |
| } |
| }, |
| Resolver); |
| |
| if (HadErrors) |
| return error( |
| "define inline: Failed to compute qualifiers. See logs for details."); |
| |
| // Get new begin and end positions for the qualified body. |
| auto OrigBodyRange = toHalfOpenFileRange( |
| SM, FD->getASTContext().getLangOpts(), FD->getBody()->getSourceRange()); |
| if (!OrigBodyRange) |
| return error("Couldn't get range func body."); |
| |
| unsigned BodyBegin = SM.getFileOffset(OrigBodyRange->getBegin()); |
| unsigned BodyEnd = Replacements.getShiftedCodePosition( |
| SM.getFileOffset(OrigBodyRange->getEnd())); |
| |
| // Trim the result to function body. |
| auto QualifiedFunc = tooling::applyAllReplacements( |
| SM.getBufferData(SM.getFileID(OrigBodyRange->getBegin())), Replacements); |
| if (!QualifiedFunc) |
| return QualifiedFunc.takeError(); |
| return QualifiedFunc->substr(BodyBegin, BodyEnd - BodyBegin + 1); |
| } |
| |
| /// Generates Replacements for changing template and function parameter names in |
| /// \p Dest to be the same as in \p Source. |
| llvm::Expected<tooling::Replacements> |
| renameParameters(const FunctionDecl *Dest, const FunctionDecl *Source, |
| const HeuristicResolver *Resolver) { |
| llvm::DenseMap<const Decl *, std::string> ParamToNewName; |
| llvm::DenseMap<const NamedDecl *, std::vector<SourceLocation>> RefLocs; |
| auto HandleParam = [&](const NamedDecl *DestParam, |
| const NamedDecl *SourceParam) { |
| // No need to rename if parameters already have the same name. |
| if (DestParam->getName() == SourceParam->getName()) |
| return; |
| std::string NewName; |
| // Unnamed parameters won't be visited in findExplicitReferences. So add |
| // them here. |
| if (DestParam->getName().empty()) { |
| RefLocs[DestParam].push_back(DestParam->getLocation()); |
| // If decl is unnamed in destination we pad the new name to avoid gluing |
| // with previous token, e.g. foo(int^) shouldn't turn into foo(intx). |
| NewName = " "; |
| } |
| NewName.append(std::string(SourceParam->getName())); |
| ParamToNewName[DestParam->getCanonicalDecl()] = std::move(NewName); |
| }; |
| |
| // Populate mapping for template parameters. |
| auto *DestTempl = Dest->getDescribedFunctionTemplate(); |
| auto *SourceTempl = Source->getDescribedFunctionTemplate(); |
| assert(bool(DestTempl) == bool(SourceTempl)); |
| if (DestTempl) { |
| const auto *DestTPL = DestTempl->getTemplateParameters(); |
| const auto *SourceTPL = SourceTempl->getTemplateParameters(); |
| assert(DestTPL->size() == SourceTPL->size()); |
| |
| for (size_t I = 0, EP = DestTPL->size(); I != EP; ++I) |
| HandleParam(DestTPL->getParam(I), SourceTPL->getParam(I)); |
| } |
| |
| // Populate mapping for function params. |
| assert(Dest->param_size() == Source->param_size()); |
| for (size_t I = 0, E = Dest->param_size(); I != E; ++I) |
| HandleParam(Dest->getParamDecl(I), Source->getParamDecl(I)); |
| |
| const SourceManager &SM = Dest->getASTContext().getSourceManager(); |
| const LangOptions &LangOpts = Dest->getASTContext().getLangOpts(); |
| // Collect other references in function signature, i.e parameter types and |
| // default arguments. |
| findExplicitReferences( |
| // Use function template in case of templated functions to visit template |
| // parameters. |
| DestTempl ? llvm::dyn_cast<Decl>(DestTempl) : llvm::dyn_cast<Decl>(Dest), |
| [&](ReferenceLoc Ref) { |
| if (Ref.Targets.size() != 1) |
| return; |
| const auto *Target = |
| llvm::cast<NamedDecl>(Ref.Targets.front()->getCanonicalDecl()); |
| auto It = ParamToNewName.find(Target); |
| if (It == ParamToNewName.end()) |
| return; |
| RefLocs[Target].push_back(Ref.NameLoc); |
| }, |
| Resolver); |
| |
| // Now try to generate edits for all the refs. |
| tooling::Replacements Replacements; |
| for (auto &Entry : RefLocs) { |
| const auto *OldDecl = Entry.first; |
| llvm::StringRef OldName = OldDecl->getName(); |
| llvm::StringRef NewName = ParamToNewName[OldDecl]; |
| for (SourceLocation RefLoc : Entry.second) { |
| CharSourceRange ReplaceRange; |
| // In case of unnamed parameters, we have an empty char range, whereas we |
| // have a tokenrange at RefLoc with named parameters. |
| if (OldName.empty()) |
| ReplaceRange = CharSourceRange::getCharRange(RefLoc, RefLoc); |
| else |
| ReplaceRange = CharSourceRange::getTokenRange(RefLoc, RefLoc); |
| // If occurrence is coming from a macro expansion, try to get back to the |
| // file range. |
| if (RefLoc.isMacroID()) { |
| ReplaceRange = Lexer::makeFileCharRange(ReplaceRange, SM, LangOpts); |
| // Bail out if we need to replace macro bodies. |
| if (ReplaceRange.isInvalid()) { |
| auto Err = error("Cant rename parameter inside macro body."); |
| elog("define inline: {0}", Err); |
| return std::move(Err); |
| } |
| } |
| |
| if (auto Err = Replacements.add( |
| tooling::Replacement(SM, ReplaceRange, NewName))) { |
| elog("define inline: Couldn't replace parameter name for {0} to {1}: " |
| "{2}", |
| OldName, NewName, Err); |
| return std::move(Err); |
| } |
| } |
| } |
| return Replacements; |
| } |
| |
| // Returns the canonical declaration for the given FunctionDecl. This will |
| // usually be the first declaration in current translation unit with the |
| // exception of template specialization. |
| // For those we return first declaration different than the canonical one. |
| // Because canonical declaration points to template decl instead of |
| // specialization. |
| const FunctionDecl *findTarget(const FunctionDecl *FD) { |
| auto CanonDecl = FD->getCanonicalDecl(); |
| if (!FD->isFunctionTemplateSpecialization() || CanonDecl == FD) |
| return CanonDecl; |
| // For specializations CanonicalDecl is the TemplatedDecl, which is not the |
| // target we want to inline into. Instead we traverse previous decls to find |
| // the first forward decl for this specialization. |
| auto PrevDecl = FD; |
| while (PrevDecl->getPreviousDecl() != CanonDecl) { |
| PrevDecl = PrevDecl->getPreviousDecl(); |
| assert(PrevDecl && "Found specialization without template decl"); |
| } |
| return PrevDecl; |
| } |
| |
| // Returns the beginning location for a FunctionDecl. Returns location of |
| // template keyword for templated functions. |
| const SourceLocation getBeginLoc(const FunctionDecl *FD) { |
| // Include template parameter list. |
| if (auto *FTD = FD->getDescribedFunctionTemplate()) |
| return FTD->getBeginLoc(); |
| return FD->getBeginLoc(); |
| } |
| |
| llvm::Optional<tooling::Replacement> |
| addInlineIfInHeader(const FunctionDecl *FD) { |
| // This includes inline functions and constexpr functions. |
| if (FD->isInlined() || llvm::isa<CXXMethodDecl>(FD)) |
| return llvm::None; |
| // Primary template doesn't need inline. |
| if (FD->isTemplated() && !FD->isFunctionTemplateSpecialization()) |
| return llvm::None; |
| |
| const SourceManager &SM = FD->getASTContext().getSourceManager(); |
| llvm::StringRef FileName = SM.getFilename(FD->getLocation()); |
| |
| // If it is not a header we don't need to mark function as "inline". |
| if (!isHeaderFile(FileName, FD->getASTContext().getLangOpts())) |
| return llvm::None; |
| |
| return tooling::Replacement(SM, FD->getInnerLocStart(), 0, "inline "); |
| } |
| |
| /// Moves definition of a function/method to its declaration location. |
| /// Before: |
| /// a.h: |
| /// void foo(); |
| /// |
| /// a.cc: |
| /// void foo() { return; } |
| /// |
| /// ------------------------ |
| /// After: |
| /// a.h: |
| /// void foo() { return; } |
| /// |
| /// a.cc: |
| /// |
| class DefineInline : public Tweak { |
| public: |
| const char *id() const override final; |
| |
| llvm::StringLiteral kind() const override { |
| return CodeAction::REFACTOR_KIND; |
| } |
| std::string title() const override { |
| return "Move function body to declaration"; |
| } |
| |
| // Returns true when selection is on a function definition that does not |
| // make use of any internal symbols. |
| bool prepare(const Selection &Sel) override { |
| const SelectionTree::Node *SelNode = Sel.ASTSelection.commonAncestor(); |
| if (!SelNode) |
| return false; |
| Source = getSelectedFunction(SelNode); |
| if (!Source || !Source->hasBody()) |
| return false; |
| // Only the last level of template parameter locations are not kept in AST, |
| // so if we are inlining a method that is in a templated class, there is no |
| // way to verify template parameter names. Therefore we bail out. |
| if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source)) { |
| if (MD->getParent()->isTemplated()) |
| return false; |
| } |
| // If function body starts or ends inside a macro, we refuse to move it into |
| // declaration location. |
| if (Source->getBody()->getBeginLoc().isMacroID() || |
| Source->getBody()->getEndLoc().isMacroID()) |
| return false; |
| |
| Target = findTarget(Source); |
| if (Target == Source) { |
| // The only declaration is Source. No other declaration to move function |
| // body. |
| // FIXME: If we are in an implementation file, figure out a suitable |
| // location to put declaration. Possibly using other declarations in the |
| // AST. |
| return false; |
| } |
| |
| // Check if the decls referenced in function body are visible in the |
| // declaration location. |
| if (!checkDeclsAreVisible(getNonLocalDeclRefs(*Sel.AST, Source), Target, |
| Sel.AST->getSourceManager())) |
| return false; |
| |
| return true; |
| } |
| |
| Expected<Effect> apply(const Selection &Sel) override { |
| const auto &AST = Sel.AST->getASTContext(); |
| const auto &SM = AST.getSourceManager(); |
| |
| auto Semicolon = getSemicolonForDecl(Target); |
| if (!Semicolon) |
| return error("Couldn't find semicolon for target declaration."); |
| |
| auto AddInlineIfNecessary = addInlineIfInHeader(Target); |
| auto ParamReplacements = |
| renameParameters(Target, Source, Sel.AST->getHeuristicResolver()); |
| if (!ParamReplacements) |
| return ParamReplacements.takeError(); |
| |
| auto QualifiedBody = |
| qualifyAllDecls(Source, Target, Sel.AST->getHeuristicResolver()); |
| if (!QualifiedBody) |
| return QualifiedBody.takeError(); |
| |
| const tooling::Replacement SemicolonToFuncBody(SM, *Semicolon, 1, |
| *QualifiedBody); |
| tooling::Replacements TargetFileReplacements(SemicolonToFuncBody); |
| TargetFileReplacements = TargetFileReplacements.merge(*ParamReplacements); |
| if (AddInlineIfNecessary) { |
| if (auto Err = TargetFileReplacements.add(*AddInlineIfNecessary)) |
| return std::move(Err); |
| } |
| |
| auto DefRange = toHalfOpenFileRange( |
| SM, AST.getLangOpts(), |
| SM.getExpansionRange(CharSourceRange::getCharRange(getBeginLoc(Source), |
| Source->getEndLoc())) |
| .getAsRange()); |
| if (!DefRange) |
| return error("Couldn't get range for the source."); |
| unsigned int SourceLen = SM.getFileOffset(DefRange->getEnd()) - |
| SM.getFileOffset(DefRange->getBegin()); |
| const tooling::Replacement DeleteFuncBody(SM, DefRange->getBegin(), |
| SourceLen, ""); |
| |
| llvm::SmallVector<std::pair<std::string, Edit>> Edits; |
| // Edit for Target. |
| auto FE = Effect::fileEdit(SM, SM.getFileID(*Semicolon), |
| std::move(TargetFileReplacements)); |
| if (!FE) |
| return FE.takeError(); |
| Edits.push_back(std::move(*FE)); |
| |
| // Edit for Source. |
| if (!SM.isWrittenInSameFile(DefRange->getBegin(), |
| SM.getExpansionLoc(Target->getBeginLoc()))) { |
| // Generate a new edit if the Source and Target are in different files. |
| auto FE = Effect::fileEdit(SM, SM.getFileID(Sel.Cursor), |
| tooling::Replacements(DeleteFuncBody)); |
| if (!FE) |
| return FE.takeError(); |
| Edits.push_back(std::move(*FE)); |
| } else { |
| // Merge with previous edit if they are in the same file. |
| if (auto Err = Edits.front().second.Replacements.add(DeleteFuncBody)) |
| return std::move(Err); |
| } |
| |
| Effect E; |
| for (auto &Pair : Edits) |
| E.ApplyEdits.try_emplace(std::move(Pair.first), std::move(Pair.second)); |
| return E; |
| } |
| |
| private: |
| const FunctionDecl *Source = nullptr; |
| const FunctionDecl *Target = nullptr; |
| }; |
| |
| REGISTER_TWEAK(DefineInline) |
| |
| } // namespace |
| } // namespace clangd |
| } // namespace clang |