xref: /llvm-project/clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp (revision 29ffafb5754100502da70171b47ee8a0f722c994)
15ab9a850SKadir Cetinkaya //===--- DefineInline.cpp ----------------------------------------*- C++-*-===//
25ab9a850SKadir Cetinkaya //
35ab9a850SKadir Cetinkaya // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
45ab9a850SKadir Cetinkaya // See https://llvm.org/LICENSE.txt for license information.
55ab9a850SKadir Cetinkaya // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
65ab9a850SKadir Cetinkaya //
75ab9a850SKadir Cetinkaya //===----------------------------------------------------------------------===//
85ab9a850SKadir Cetinkaya 
95ab9a850SKadir Cetinkaya #include "AST.h"
103d65def1SKadir Cetinkaya #include "FindTarget.h"
115ab9a850SKadir Cetinkaya #include "Selection.h"
125ab9a850SKadir Cetinkaya #include "SourceCode.h"
135ab9a850SKadir Cetinkaya #include "XRefs.h"
145ab9a850SKadir Cetinkaya #include "refactor/Tweak.h"
15ad97ccf6SSam McCall #include "support/Logger.h"
165ab9a850SKadir Cetinkaya #include "clang/AST/ASTContext.h"
175ab9a850SKadir Cetinkaya #include "clang/AST/ASTTypeTraits.h"
185ab9a850SKadir Cetinkaya #include "clang/AST/Decl.h"
195ab9a850SKadir Cetinkaya #include "clang/AST/DeclBase.h"
205ab9a850SKadir Cetinkaya #include "clang/AST/DeclCXX.h"
215ab9a850SKadir Cetinkaya #include "clang/AST/DeclTemplate.h"
225ab9a850SKadir Cetinkaya #include "clang/AST/NestedNameSpecifier.h"
235ab9a850SKadir Cetinkaya #include "clang/AST/Stmt.h"
245ab9a850SKadir Cetinkaya #include "clang/Basic/LangOptions.h"
255ab9a850SKadir Cetinkaya #include "clang/Basic/SourceLocation.h"
265ab9a850SKadir Cetinkaya #include "clang/Basic/SourceManager.h"
275ab9a850SKadir Cetinkaya #include "clang/Basic/TokenKinds.h"
285ab9a850SKadir Cetinkaya #include "clang/Lex/Lexer.h"
295ab9a850SKadir Cetinkaya #include "clang/Lex/Token.h"
305ab9a850SKadir Cetinkaya #include "clang/Sema/Lookup.h"
315ab9a850SKadir Cetinkaya #include "clang/Sema/Sema.h"
325ab9a850SKadir Cetinkaya #include "clang/Tooling/Core/Replacement.h"
335ab9a850SKadir Cetinkaya #include "llvm/ADT/DenseMap.h"
345ab9a850SKadir Cetinkaya #include "llvm/ADT/DenseSet.h"
353d65def1SKadir Cetinkaya #include "llvm/ADT/SmallVector.h"
365ab9a850SKadir Cetinkaya #include "llvm/ADT/StringRef.h"
375ab9a850SKadir Cetinkaya #include "llvm/Support/Casting.h"
385ab9a850SKadir Cetinkaya #include "llvm/Support/Error.h"
395ab9a850SKadir Cetinkaya #include "llvm/Support/raw_ostream.h"
405ab9a850SKadir Cetinkaya #include <cstddef>
4171f55735SKazu Hirata #include <optional>
425ab9a850SKadir Cetinkaya #include <set>
435ab9a850SKadir Cetinkaya #include <string>
445ab9a850SKadir Cetinkaya #include <unordered_map>
453d65def1SKadir Cetinkaya #include <utility>
465ab9a850SKadir Cetinkaya #include <vector>
475ab9a850SKadir Cetinkaya 
485ab9a850SKadir Cetinkaya namespace clang {
495ab9a850SKadir Cetinkaya namespace clangd {
505ab9a850SKadir Cetinkaya namespace {
515ab9a850SKadir Cetinkaya 
523d65def1SKadir Cetinkaya // Returns semicolon location for the given FD. Since AST doesn't contain that
533d65def1SKadir Cetinkaya // information, searches for a semicolon by lexing from end of function decl
543d65def1SKadir Cetinkaya // while skipping comments.
getSemicolonForDecl(const FunctionDecl * FD)55*f71ffd3bSKazu Hirata std::optional<SourceLocation> getSemicolonForDecl(const FunctionDecl *FD) {
563d65def1SKadir Cetinkaya   const SourceManager &SM = FD->getASTContext().getSourceManager();
573d65def1SKadir Cetinkaya   const LangOptions &LangOpts = FD->getASTContext().getLangOpts();
583d65def1SKadir Cetinkaya 
593d65def1SKadir Cetinkaya   SourceLocation CurLoc = FD->getEndLoc();
603d65def1SKadir Cetinkaya   auto NextTok = Lexer::findNextToken(CurLoc, SM, LangOpts);
613d65def1SKadir Cetinkaya   if (!NextTok || !NextTok->is(tok::semi))
62059a23c0SKazu Hirata     return std::nullopt;
633d65def1SKadir Cetinkaya   return NextTok->getLocation();
643d65def1SKadir Cetinkaya }
653d65def1SKadir Cetinkaya 
665ab9a850SKadir Cetinkaya // Deduces the FunctionDecl from a selection. Requires either the function body
675ab9a850SKadir Cetinkaya // or the function decl to be selected. Returns null if none of the above
685ab9a850SKadir Cetinkaya // criteria is met.
getSelectedFunction(const SelectionTree::Node * SelNode)695ab9a850SKadir Cetinkaya const FunctionDecl *getSelectedFunction(const SelectionTree::Node *SelNode) {
70027899daSAlexander Kornienko   const DynTypedNode &AstNode = SelNode->ASTNode;
715ab9a850SKadir Cetinkaya   if (const FunctionDecl *FD = AstNode.get<FunctionDecl>())
725ab9a850SKadir Cetinkaya     return FD;
735ab9a850SKadir Cetinkaya   if (AstNode.get<CompoundStmt>() &&
745ab9a850SKadir Cetinkaya       SelNode->Selected == SelectionTree::Complete) {
755ab9a850SKadir Cetinkaya     if (const SelectionTree::Node *P = SelNode->Parent)
765ab9a850SKadir Cetinkaya       return P->ASTNode.get<FunctionDecl>();
775ab9a850SKadir Cetinkaya   }
785ab9a850SKadir Cetinkaya   return nullptr;
795ab9a850SKadir Cetinkaya }
805ab9a850SKadir Cetinkaya 
815ab9a850SKadir Cetinkaya // Checks the decls mentioned in Source are visible in the context of Target.
82b7ecf1c1SKazuaki Ishizaki // Achieves that by checking declarations occur before target location in
835ab9a850SKadir Cetinkaya // translation unit or declared in the same class.
checkDeclsAreVisible(const llvm::DenseSet<const Decl * > & DeclRefs,const FunctionDecl * Target,const SourceManager & SM)845ab9a850SKadir Cetinkaya bool checkDeclsAreVisible(const llvm::DenseSet<const Decl *> &DeclRefs,
855ab9a850SKadir Cetinkaya                           const FunctionDecl *Target, const SourceManager &SM) {
865ab9a850SKadir Cetinkaya   SourceLocation TargetLoc = Target->getLocation();
875ab9a850SKadir Cetinkaya   // To be used in visibility check below, decls in a class are visible
885ab9a850SKadir Cetinkaya   // independent of order.
895ab9a850SKadir Cetinkaya   const RecordDecl *Class = nullptr;
905ab9a850SKadir Cetinkaya   if (const auto *MD = llvm::dyn_cast<CXXMethodDecl>(Target))
915ab9a850SKadir Cetinkaya     Class = MD->getParent();
925ab9a850SKadir Cetinkaya 
935ab9a850SKadir Cetinkaya   for (const auto *DR : DeclRefs) {
945ab9a850SKadir Cetinkaya     // Use canonical decl, since having one decl before target is enough.
955ab9a850SKadir Cetinkaya     const Decl *D = DR->getCanonicalDecl();
965ab9a850SKadir Cetinkaya     if (D == Target)
975ab9a850SKadir Cetinkaya       continue;
985ab9a850SKadir Cetinkaya     SourceLocation DeclLoc = D->getLocation();
995ab9a850SKadir Cetinkaya 
1005ab9a850SKadir Cetinkaya     // FIXME: Allow declarations from different files with include insertion.
1015ab9a850SKadir Cetinkaya     if (!SM.isWrittenInSameFile(DeclLoc, TargetLoc))
1025ab9a850SKadir Cetinkaya       return false;
1035ab9a850SKadir Cetinkaya 
1045ab9a850SKadir Cetinkaya     // If declaration is before target, then it is visible.
1055ab9a850SKadir Cetinkaya     if (SM.isBeforeInTranslationUnit(DeclLoc, TargetLoc))
1065ab9a850SKadir Cetinkaya       continue;
1075ab9a850SKadir Cetinkaya 
1085ab9a850SKadir Cetinkaya     // Otherwise they need to be in same class
1095ab9a850SKadir Cetinkaya     if (!Class)
1105ab9a850SKadir Cetinkaya       return false;
1115ab9a850SKadir Cetinkaya     const RecordDecl *Parent = nullptr;
1125ab9a850SKadir Cetinkaya     if (const auto *MD = llvm::dyn_cast<CXXMethodDecl>(D))
1135ab9a850SKadir Cetinkaya       Parent = MD->getParent();
1145ab9a850SKadir Cetinkaya     else if (const auto *FD = llvm::dyn_cast<FieldDecl>(D))
1155ab9a850SKadir Cetinkaya       Parent = FD->getParent();
1165ab9a850SKadir Cetinkaya     if (Parent != Class)
1175ab9a850SKadir Cetinkaya       return false;
1185ab9a850SKadir Cetinkaya   }
1195ab9a850SKadir Cetinkaya   return true;
1205ab9a850SKadir Cetinkaya }
1215ab9a850SKadir Cetinkaya 
1225075c682SKadir Cetinkaya // Rewrites body of FD by re-spelling all of the names to make sure they are
1235075c682SKadir Cetinkaya // still valid in context of Target.
qualifyAllDecls(const FunctionDecl * FD,const FunctionDecl * Target,const HeuristicResolver * Resolver)1245075c682SKadir Cetinkaya llvm::Expected<std::string> qualifyAllDecls(const FunctionDecl *FD,
1259510b094SNathan Ridge                                             const FunctionDecl *Target,
1269510b094SNathan Ridge                                             const HeuristicResolver *Resolver) {
1273d65def1SKadir Cetinkaya   // There are three types of spellings that needs to be qualified in a function
1283d65def1SKadir Cetinkaya   // body:
1293d65def1SKadir Cetinkaya   // - Types:       Foo                 -> ns::Foo
1303d65def1SKadir Cetinkaya   // - DeclRefExpr: ns2::foo()          -> ns1::ns2::foo();
1313d65def1SKadir Cetinkaya   // - UsingDecls:
1323d65def1SKadir Cetinkaya   //    using ns2::foo      -> using ns1::ns2::foo
1333d65def1SKadir Cetinkaya   //    using namespace ns2 -> using namespace ns1::ns2
1343d65def1SKadir Cetinkaya   //    using ns3 = ns2     -> using ns3 = ns1::ns2
1353d65def1SKadir Cetinkaya   //
1363d65def1SKadir Cetinkaya   // Go over all references inside a function body to generate replacements that
1375075c682SKadir Cetinkaya   // will qualify those. So that body can be moved into an arbitrary file.
138dd5571d5SKazuaki Ishizaki   // We perform the qualification by qualifying the first type/decl in a
1393d65def1SKadir Cetinkaya   // (un)qualified name. e.g:
1403d65def1SKadir Cetinkaya   //    namespace a { namespace b { class Bar{}; void foo(); } }
1413d65def1SKadir Cetinkaya   //    b::Bar x; -> a::b::Bar x;
1423d65def1SKadir Cetinkaya   //    foo(); -> a::b::foo();
1433d65def1SKadir Cetinkaya 
1445075c682SKadir Cetinkaya   auto *TargetContext = Target->getLexicalDeclContext();
1453d65def1SKadir Cetinkaya   const SourceManager &SM = FD->getASTContext().getSourceManager();
1465075c682SKadir Cetinkaya 
1473d65def1SKadir Cetinkaya   tooling::Replacements Replacements;
1483d65def1SKadir Cetinkaya   bool HadErrors = false;
1499510b094SNathan Ridge   findExplicitReferences(
1509510b094SNathan Ridge       FD->getBody(),
1519510b094SNathan Ridge       [&](ReferenceLoc Ref) {
1523d65def1SKadir Cetinkaya         // Since we want to qualify only the first qualifier, skip names with a
1533d65def1SKadir Cetinkaya         // qualifier.
1543d65def1SKadir Cetinkaya         if (Ref.Qualifier)
1553d65def1SKadir Cetinkaya           return;
1569510b094SNathan Ridge         // There might be no decl in dependent contexts, there's nothing much we
1579510b094SNathan Ridge         // can do in such cases.
1583d65def1SKadir Cetinkaya         if (Ref.Targets.empty())
1593d65def1SKadir Cetinkaya           return;
1603d65def1SKadir Cetinkaya         // Do not qualify names introduced by macro expansions.
1613d65def1SKadir Cetinkaya         if (Ref.NameLoc.isMacroID())
1623d65def1SKadir Cetinkaya           return;
1633d65def1SKadir Cetinkaya 
1643d65def1SKadir Cetinkaya         for (const NamedDecl *ND : Ref.Targets) {
1653d65def1SKadir Cetinkaya           if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) {
1663d65def1SKadir Cetinkaya             elog("define inline: Targets from multiple contexts: {0}, {1}",
1679510b094SNathan Ridge                  printQualifiedName(*Ref.Targets.front()),
1689510b094SNathan Ridge                  printQualifiedName(*ND));
1693d65def1SKadir Cetinkaya             HadErrors = true;
1703d65def1SKadir Cetinkaya             return;
1713d65def1SKadir Cetinkaya           }
1723d65def1SKadir Cetinkaya         }
1733d65def1SKadir Cetinkaya         // All Targets are in the same scope, so we can safely chose first one.
1743d65def1SKadir Cetinkaya         const NamedDecl *ND = Ref.Targets.front();
1753d65def1SKadir Cetinkaya         // Skip anything from a non-namespace scope, these can be:
1769510b094SNathan Ridge         // - Function or Method scopes, which means decl is local and doesn't
1779510b094SNathan Ridge         // need
1783d65def1SKadir Cetinkaya         //   qualification.
1799510b094SNathan Ridge         // - From Class/Struct/Union scope, which again doesn't need any
1809510b094SNathan Ridge         // qualifiers,
1813d65def1SKadir Cetinkaya         //   rather the left side of it requires qualification, like:
1823d65def1SKadir Cetinkaya         //   namespace a { class Bar { public: static int x; } }
1833d65def1SKadir Cetinkaya         //   void foo() { Bar::x; }
1843d65def1SKadir Cetinkaya         //                ~~~~~ -> we need to qualify Bar not x.
1853d65def1SKadir Cetinkaya         if (!ND->getDeclContext()->isNamespace())
1863d65def1SKadir Cetinkaya           return;
1873d65def1SKadir Cetinkaya 
1885075c682SKadir Cetinkaya         const std::string Qualifier = getQualification(
1895075c682SKadir Cetinkaya             FD->getASTContext(), TargetContext, Target->getBeginLoc(), ND);
1903d65def1SKadir Cetinkaya         if (auto Err = Replacements.add(
1913d65def1SKadir Cetinkaya                 tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier))) {
1923d65def1SKadir Cetinkaya           HadErrors = true;
1933d65def1SKadir Cetinkaya           elog("define inline: Failed to add quals: {0}", std::move(Err));
1943d65def1SKadir Cetinkaya         }
1959510b094SNathan Ridge       },
1969510b094SNathan Ridge       Resolver);
1973d65def1SKadir Cetinkaya 
198687e1d71SSam McCall   if (HadErrors)
199687e1d71SSam McCall     return error(
200687e1d71SSam McCall         "define inline: Failed to compute qualifiers. See logs for details.");
2013d65def1SKadir Cetinkaya 
2023d65def1SKadir Cetinkaya   // Get new begin and end positions for the qualified body.
2033d65def1SKadir Cetinkaya   auto OrigBodyRange = toHalfOpenFileRange(
2043d65def1SKadir Cetinkaya       SM, FD->getASTContext().getLangOpts(), FD->getBody()->getSourceRange());
2053d65def1SKadir Cetinkaya   if (!OrigBodyRange)
206687e1d71SSam McCall     return error("Couldn't get range func body.");
2073d65def1SKadir Cetinkaya 
2083d65def1SKadir Cetinkaya   unsigned BodyBegin = SM.getFileOffset(OrigBodyRange->getBegin());
2093d65def1SKadir Cetinkaya   unsigned BodyEnd = Replacements.getShiftedCodePosition(
2103d65def1SKadir Cetinkaya       SM.getFileOffset(OrigBodyRange->getEnd()));
2113d65def1SKadir Cetinkaya 
2123d65def1SKadir Cetinkaya   // Trim the result to function body.
2133d65def1SKadir Cetinkaya   auto QualifiedFunc = tooling::applyAllReplacements(
2143d65def1SKadir Cetinkaya       SM.getBufferData(SM.getFileID(OrigBodyRange->getBegin())), Replacements);
2153d65def1SKadir Cetinkaya   if (!QualifiedFunc)
2163d65def1SKadir Cetinkaya     return QualifiedFunc.takeError();
2173d65def1SKadir Cetinkaya   return QualifiedFunc->substr(BodyBegin, BodyEnd - BodyBegin + 1);
2183d65def1SKadir Cetinkaya }
2193d65def1SKadir Cetinkaya 
22071aa3f7bSKadir Cetinkaya /// Generates Replacements for changing template and function parameter names in
22171aa3f7bSKadir Cetinkaya /// \p Dest to be the same as in \p Source.
22271aa3f7bSKadir Cetinkaya llvm::Expected<tooling::Replacements>
renameParameters(const FunctionDecl * Dest,const FunctionDecl * Source,const HeuristicResolver * Resolver)2239510b094SNathan Ridge renameParameters(const FunctionDecl *Dest, const FunctionDecl *Source,
2249510b094SNathan Ridge                  const HeuristicResolver *Resolver) {
22571aa3f7bSKadir Cetinkaya   llvm::DenseMap<const Decl *, std::string> ParamToNewName;
22671aa3f7bSKadir Cetinkaya   llvm::DenseMap<const NamedDecl *, std::vector<SourceLocation>> RefLocs;
22771aa3f7bSKadir Cetinkaya   auto HandleParam = [&](const NamedDecl *DestParam,
22871aa3f7bSKadir Cetinkaya                          const NamedDecl *SourceParam) {
22971aa3f7bSKadir Cetinkaya     // No need to rename if parameters already have the same name.
23071aa3f7bSKadir Cetinkaya     if (DestParam->getName() == SourceParam->getName())
23171aa3f7bSKadir Cetinkaya       return;
23271aa3f7bSKadir Cetinkaya     std::string NewName;
23371aa3f7bSKadir Cetinkaya     // Unnamed parameters won't be visited in findExplicitReferences. So add
23471aa3f7bSKadir Cetinkaya     // them here.
23571aa3f7bSKadir Cetinkaya     if (DestParam->getName().empty()) {
23671aa3f7bSKadir Cetinkaya       RefLocs[DestParam].push_back(DestParam->getLocation());
23771aa3f7bSKadir Cetinkaya       // If decl is unnamed in destination we pad the new name to avoid gluing
23871aa3f7bSKadir Cetinkaya       // with previous token, e.g. foo(int^) shouldn't turn into foo(intx).
23971aa3f7bSKadir Cetinkaya       NewName = " ";
24071aa3f7bSKadir Cetinkaya     }
241adcd0268SBenjamin Kramer     NewName.append(std::string(SourceParam->getName()));
24271aa3f7bSKadir Cetinkaya     ParamToNewName[DestParam->getCanonicalDecl()] = std::move(NewName);
24371aa3f7bSKadir Cetinkaya   };
24471aa3f7bSKadir Cetinkaya 
24571aa3f7bSKadir Cetinkaya   // Populate mapping for template parameters.
24671aa3f7bSKadir Cetinkaya   auto *DestTempl = Dest->getDescribedFunctionTemplate();
24771aa3f7bSKadir Cetinkaya   auto *SourceTempl = Source->getDescribedFunctionTemplate();
24871aa3f7bSKadir Cetinkaya   assert(bool(DestTempl) == bool(SourceTempl));
24971aa3f7bSKadir Cetinkaya   if (DestTempl) {
25071aa3f7bSKadir Cetinkaya     const auto *DestTPL = DestTempl->getTemplateParameters();
25171aa3f7bSKadir Cetinkaya     const auto *SourceTPL = SourceTempl->getTemplateParameters();
25271aa3f7bSKadir Cetinkaya     assert(DestTPL->size() == SourceTPL->size());
25371aa3f7bSKadir Cetinkaya 
25471aa3f7bSKadir Cetinkaya     for (size_t I = 0, EP = DestTPL->size(); I != EP; ++I)
25571aa3f7bSKadir Cetinkaya       HandleParam(DestTPL->getParam(I), SourceTPL->getParam(I));
25671aa3f7bSKadir Cetinkaya   }
25771aa3f7bSKadir Cetinkaya 
25871aa3f7bSKadir Cetinkaya   // Populate mapping for function params.
25971aa3f7bSKadir Cetinkaya   assert(Dest->param_size() == Source->param_size());
26071aa3f7bSKadir Cetinkaya   for (size_t I = 0, E = Dest->param_size(); I != E; ++I)
26171aa3f7bSKadir Cetinkaya     HandleParam(Dest->getParamDecl(I), Source->getParamDecl(I));
26271aa3f7bSKadir Cetinkaya 
26371aa3f7bSKadir Cetinkaya   const SourceManager &SM = Dest->getASTContext().getSourceManager();
26471aa3f7bSKadir Cetinkaya   const LangOptions &LangOpts = Dest->getASTContext().getLangOpts();
26571aa3f7bSKadir Cetinkaya   // Collect other references in function signature, i.e parameter types and
26671aa3f7bSKadir Cetinkaya   // default arguments.
26771aa3f7bSKadir Cetinkaya   findExplicitReferences(
26871aa3f7bSKadir Cetinkaya       // Use function template in case of templated functions to visit template
26971aa3f7bSKadir Cetinkaya       // parameters.
27071aa3f7bSKadir Cetinkaya       DestTempl ? llvm::dyn_cast<Decl>(DestTempl) : llvm::dyn_cast<Decl>(Dest),
27171aa3f7bSKadir Cetinkaya       [&](ReferenceLoc Ref) {
27271aa3f7bSKadir Cetinkaya         if (Ref.Targets.size() != 1)
27371aa3f7bSKadir Cetinkaya           return;
27471aa3f7bSKadir Cetinkaya         const auto *Target =
27571aa3f7bSKadir Cetinkaya             llvm::cast<NamedDecl>(Ref.Targets.front()->getCanonicalDecl());
27671aa3f7bSKadir Cetinkaya         auto It = ParamToNewName.find(Target);
27771aa3f7bSKadir Cetinkaya         if (It == ParamToNewName.end())
27871aa3f7bSKadir Cetinkaya           return;
27971aa3f7bSKadir Cetinkaya         RefLocs[Target].push_back(Ref.NameLoc);
2809510b094SNathan Ridge       },
2819510b094SNathan Ridge       Resolver);
28271aa3f7bSKadir Cetinkaya 
28371aa3f7bSKadir Cetinkaya   // Now try to generate edits for all the refs.
28471aa3f7bSKadir Cetinkaya   tooling::Replacements Replacements;
28571aa3f7bSKadir Cetinkaya   for (auto &Entry : RefLocs) {
28671aa3f7bSKadir Cetinkaya     const auto *OldDecl = Entry.first;
28771aa3f7bSKadir Cetinkaya     llvm::StringRef OldName = OldDecl->getName();
28871aa3f7bSKadir Cetinkaya     llvm::StringRef NewName = ParamToNewName[OldDecl];
28971aa3f7bSKadir Cetinkaya     for (SourceLocation RefLoc : Entry.second) {
29071aa3f7bSKadir Cetinkaya       CharSourceRange ReplaceRange;
29171aa3f7bSKadir Cetinkaya       // In case of unnamed parameters, we have an empty char range, whereas we
29271aa3f7bSKadir Cetinkaya       // have a tokenrange at RefLoc with named parameters.
29371aa3f7bSKadir Cetinkaya       if (OldName.empty())
29471aa3f7bSKadir Cetinkaya         ReplaceRange = CharSourceRange::getCharRange(RefLoc, RefLoc);
29571aa3f7bSKadir Cetinkaya       else
29671aa3f7bSKadir Cetinkaya         ReplaceRange = CharSourceRange::getTokenRange(RefLoc, RefLoc);
297dd5571d5SKazuaki Ishizaki       // If occurrence is coming from a macro expansion, try to get back to the
29871aa3f7bSKadir Cetinkaya       // file range.
29971aa3f7bSKadir Cetinkaya       if (RefLoc.isMacroID()) {
30071aa3f7bSKadir Cetinkaya         ReplaceRange = Lexer::makeFileCharRange(ReplaceRange, SM, LangOpts);
30171aa3f7bSKadir Cetinkaya         // Bail out if we need to replace macro bodies.
30271aa3f7bSKadir Cetinkaya         if (ReplaceRange.isInvalid()) {
303687e1d71SSam McCall           auto Err = error("Cant rename parameter inside macro body.");
30471aa3f7bSKadir Cetinkaya           elog("define inline: {0}", Err);
30571aa3f7bSKadir Cetinkaya           return std::move(Err);
30671aa3f7bSKadir Cetinkaya         }
30771aa3f7bSKadir Cetinkaya       }
30871aa3f7bSKadir Cetinkaya 
30971aa3f7bSKadir Cetinkaya       if (auto Err = Replacements.add(
31071aa3f7bSKadir Cetinkaya               tooling::Replacement(SM, ReplaceRange, NewName))) {
31171aa3f7bSKadir Cetinkaya         elog("define inline: Couldn't replace parameter name for {0} to {1}: "
31271aa3f7bSKadir Cetinkaya              "{2}",
31371aa3f7bSKadir Cetinkaya              OldName, NewName, Err);
31471aa3f7bSKadir Cetinkaya         return std::move(Err);
31571aa3f7bSKadir Cetinkaya       }
31671aa3f7bSKadir Cetinkaya     }
31771aa3f7bSKadir Cetinkaya   }
31871aa3f7bSKadir Cetinkaya   return Replacements;
31971aa3f7bSKadir Cetinkaya }
32071aa3f7bSKadir Cetinkaya 
3215ab9a850SKadir Cetinkaya // Returns the canonical declaration for the given FunctionDecl. This will
3225ab9a850SKadir Cetinkaya // usually be the first declaration in current translation unit with the
3235ab9a850SKadir Cetinkaya // exception of template specialization.
3245ab9a850SKadir Cetinkaya // For those we return first declaration different than the canonical one.
3255ab9a850SKadir Cetinkaya // Because canonical declaration points to template decl instead of
3265ab9a850SKadir Cetinkaya // specialization.
findTarget(const FunctionDecl * FD)3275ab9a850SKadir Cetinkaya const FunctionDecl *findTarget(const FunctionDecl *FD) {
328c0e3c893SChristian Kühnel   auto *CanonDecl = FD->getCanonicalDecl();
32901173285SKadir Cetinkaya   if (!FD->isFunctionTemplateSpecialization() || CanonDecl == FD)
3305ab9a850SKadir Cetinkaya     return CanonDecl;
3315ab9a850SKadir Cetinkaya   // For specializations CanonicalDecl is the TemplatedDecl, which is not the
3325ab9a850SKadir Cetinkaya   // target we want to inline into. Instead we traverse previous decls to find
3335ab9a850SKadir Cetinkaya   // the first forward decl for this specialization.
334c0e3c893SChristian Kühnel   auto *PrevDecl = FD;
3355ab9a850SKadir Cetinkaya   while (PrevDecl->getPreviousDecl() != CanonDecl) {
3365ab9a850SKadir Cetinkaya     PrevDecl = PrevDecl->getPreviousDecl();
3375ab9a850SKadir Cetinkaya     assert(PrevDecl && "Found specialization without template decl");
3385ab9a850SKadir Cetinkaya   }
3395ab9a850SKadir Cetinkaya   return PrevDecl;
3405ab9a850SKadir Cetinkaya }
3415ab9a850SKadir Cetinkaya 
342dd5571d5SKazuaki Ishizaki // Returns the beginning location for a FunctionDecl. Returns location of
3433d65def1SKadir Cetinkaya // template keyword for templated functions.
getBeginLoc(const FunctionDecl * FD)3443d65def1SKadir Cetinkaya const SourceLocation getBeginLoc(const FunctionDecl *FD) {
3453d65def1SKadir Cetinkaya   // Include template parameter list.
3463d65def1SKadir Cetinkaya   if (auto *FTD = FD->getDescribedFunctionTemplate())
3473d65def1SKadir Cetinkaya     return FTD->getBeginLoc();
3483d65def1SKadir Cetinkaya   return FD->getBeginLoc();
3493d65def1SKadir Cetinkaya }
3503d65def1SKadir Cetinkaya 
351*f71ffd3bSKazu Hirata std::optional<tooling::Replacement>
addInlineIfInHeader(const FunctionDecl * FD)352087528a3SKadir Cetinkaya addInlineIfInHeader(const FunctionDecl *FD) {
353087528a3SKadir Cetinkaya   // This includes inline functions and constexpr functions.
354087528a3SKadir Cetinkaya   if (FD->isInlined() || llvm::isa<CXXMethodDecl>(FD))
355059a23c0SKazu Hirata     return std::nullopt;
356087528a3SKadir Cetinkaya   // Primary template doesn't need inline.
357087528a3SKadir Cetinkaya   if (FD->isTemplated() && !FD->isFunctionTemplateSpecialization())
358059a23c0SKazu Hirata     return std::nullopt;
359087528a3SKadir Cetinkaya 
360087528a3SKadir Cetinkaya   const SourceManager &SM = FD->getASTContext().getSourceManager();
361087528a3SKadir Cetinkaya   llvm::StringRef FileName = SM.getFilename(FD->getLocation());
362087528a3SKadir Cetinkaya 
363087528a3SKadir Cetinkaya   // If it is not a header we don't need to mark function as "inline".
364087528a3SKadir Cetinkaya   if (!isHeaderFile(FileName, FD->getASTContext().getLangOpts()))
365059a23c0SKazu Hirata     return std::nullopt;
366087528a3SKadir Cetinkaya 
367087528a3SKadir Cetinkaya   return tooling::Replacement(SM, FD->getInnerLocStart(), 0, "inline ");
368087528a3SKadir Cetinkaya }
369087528a3SKadir Cetinkaya 
3705ab9a850SKadir Cetinkaya /// Moves definition of a function/method to its declaration location.
3715ab9a850SKadir Cetinkaya /// Before:
3725ab9a850SKadir Cetinkaya /// a.h:
3735ab9a850SKadir Cetinkaya ///   void foo();
3745ab9a850SKadir Cetinkaya ///
3755ab9a850SKadir Cetinkaya /// a.cc:
3765ab9a850SKadir Cetinkaya ///   void foo() { return; }
3775ab9a850SKadir Cetinkaya ///
3785ab9a850SKadir Cetinkaya /// ------------------------
3795ab9a850SKadir Cetinkaya /// After:
3805ab9a850SKadir Cetinkaya /// a.h:
3815ab9a850SKadir Cetinkaya ///   void foo() { return; }
3825ab9a850SKadir Cetinkaya ///
3835ab9a850SKadir Cetinkaya /// a.cc:
3845ab9a850SKadir Cetinkaya ///
3855ab9a850SKadir Cetinkaya class DefineInline : public Tweak {
3865ab9a850SKadir Cetinkaya public:
38795a932fbSKazu Hirata   const char *id() const final;
3885ab9a850SKadir Cetinkaya 
kind() const38917747d2eSSam McCall   llvm::StringLiteral kind() const override {
39017747d2eSSam McCall     return CodeAction::REFACTOR_KIND;
39117747d2eSSam McCall   }
title() const3925ab9a850SKadir Cetinkaya   std::string title() const override {
3935ab9a850SKadir Cetinkaya     return "Move function body to declaration";
3945ab9a850SKadir Cetinkaya   }
3955ab9a850SKadir Cetinkaya 
3965ab9a850SKadir Cetinkaya   // Returns true when selection is on a function definition that does not
3975ab9a850SKadir Cetinkaya   // make use of any internal symbols.
prepare(const Selection & Sel)3985ab9a850SKadir Cetinkaya   bool prepare(const Selection &Sel) override {
3995ab9a850SKadir Cetinkaya     const SelectionTree::Node *SelNode = Sel.ASTSelection.commonAncestor();
4005ab9a850SKadir Cetinkaya     if (!SelNode)
4015ab9a850SKadir Cetinkaya       return false;
4025ab9a850SKadir Cetinkaya     Source = getSelectedFunction(SelNode);
403b9213dfeSSam McCall     if (!Source || !Source->hasBody())
4045ab9a850SKadir Cetinkaya       return false;
4055ab9a850SKadir Cetinkaya     // Only the last level of template parameter locations are not kept in AST,
4065ab9a850SKadir Cetinkaya     // so if we are inlining a method that is in a templated class, there is no
4075ab9a850SKadir Cetinkaya     // way to verify template parameter names. Therefore we bail out.
4085ab9a850SKadir Cetinkaya     if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source)) {
4095ab9a850SKadir Cetinkaya       if (MD->getParent()->isTemplated())
4105ab9a850SKadir Cetinkaya         return false;
4115ab9a850SKadir Cetinkaya     }
4123d65def1SKadir Cetinkaya     // If function body starts or ends inside a macro, we refuse to move it into
4133d65def1SKadir Cetinkaya     // declaration location.
4143d65def1SKadir Cetinkaya     if (Source->getBody()->getBeginLoc().isMacroID() ||
4153d65def1SKadir Cetinkaya         Source->getBody()->getEndLoc().isMacroID())
4163d65def1SKadir Cetinkaya       return false;
4175ab9a850SKadir Cetinkaya 
4185ab9a850SKadir Cetinkaya     Target = findTarget(Source);
4195ab9a850SKadir Cetinkaya     if (Target == Source) {
4205ab9a850SKadir Cetinkaya       // The only declaration is Source. No other declaration to move function
4215ab9a850SKadir Cetinkaya       // body.
4225ab9a850SKadir Cetinkaya       // FIXME: If we are in an implementation file, figure out a suitable
4235ab9a850SKadir Cetinkaya       // location to put declaration. Possibly using other declarations in the
4245ab9a850SKadir Cetinkaya       // AST.
4255ab9a850SKadir Cetinkaya       return false;
4265ab9a850SKadir Cetinkaya     }
4275ab9a850SKadir Cetinkaya 
4285ab9a850SKadir Cetinkaya     // Check if the decls referenced in function body are visible in the
4295ab9a850SKadir Cetinkaya     // declaration location.
4307dc388bdSSam McCall     if (!checkDeclsAreVisible(getNonLocalDeclRefs(*Sel.AST, Source), Target,
4317dc388bdSSam McCall                               Sel.AST->getSourceManager()))
4325ab9a850SKadir Cetinkaya       return false;
4335ab9a850SKadir Cetinkaya 
4345ab9a850SKadir Cetinkaya     return true;
4355ab9a850SKadir Cetinkaya   }
4365ab9a850SKadir Cetinkaya 
apply(const Selection & Sel)4375ab9a850SKadir Cetinkaya   Expected<Effect> apply(const Selection &Sel) override {
4387dc388bdSSam McCall     const auto &AST = Sel.AST->getASTContext();
4393d65def1SKadir Cetinkaya     const auto &SM = AST.getSourceManager();
4403d65def1SKadir Cetinkaya 
4413d65def1SKadir Cetinkaya     auto Semicolon = getSemicolonForDecl(Target);
442687e1d71SSam McCall     if (!Semicolon)
443687e1d71SSam McCall       return error("Couldn't find semicolon for target declaration.");
4443d65def1SKadir Cetinkaya 
445087528a3SKadir Cetinkaya     auto AddInlineIfNecessary = addInlineIfInHeader(Target);
4469510b094SNathan Ridge     auto ParamReplacements =
4479510b094SNathan Ridge         renameParameters(Target, Source, Sel.AST->getHeuristicResolver());
44871aa3f7bSKadir Cetinkaya     if (!ParamReplacements)
44971aa3f7bSKadir Cetinkaya       return ParamReplacements.takeError();
45071aa3f7bSKadir Cetinkaya 
4519510b094SNathan Ridge     auto QualifiedBody =
4529510b094SNathan Ridge         qualifyAllDecls(Source, Target, Sel.AST->getHeuristicResolver());
4533d65def1SKadir Cetinkaya     if (!QualifiedBody)
4543d65def1SKadir Cetinkaya       return QualifiedBody.takeError();
4553d65def1SKadir Cetinkaya 
4563d65def1SKadir Cetinkaya     const tooling::Replacement SemicolonToFuncBody(SM, *Semicolon, 1,
4573d65def1SKadir Cetinkaya                                                    *QualifiedBody);
458087528a3SKadir Cetinkaya     tooling::Replacements TargetFileReplacements(SemicolonToFuncBody);
459087528a3SKadir Cetinkaya     TargetFileReplacements = TargetFileReplacements.merge(*ParamReplacements);
460087528a3SKadir Cetinkaya     if (AddInlineIfNecessary) {
461087528a3SKadir Cetinkaya       if (auto Err = TargetFileReplacements.add(*AddInlineIfNecessary))
462087528a3SKadir Cetinkaya         return std::move(Err);
463087528a3SKadir Cetinkaya     }
464087528a3SKadir Cetinkaya 
4653d65def1SKadir Cetinkaya     auto DefRange = toHalfOpenFileRange(
4663d65def1SKadir Cetinkaya         SM, AST.getLangOpts(),
4673d65def1SKadir Cetinkaya         SM.getExpansionRange(CharSourceRange::getCharRange(getBeginLoc(Source),
4683d65def1SKadir Cetinkaya                                                            Source->getEndLoc()))
4693d65def1SKadir Cetinkaya             .getAsRange());
470687e1d71SSam McCall     if (!DefRange)
471687e1d71SSam McCall       return error("Couldn't get range for the source.");
4723d65def1SKadir Cetinkaya     unsigned int SourceLen = SM.getFileOffset(DefRange->getEnd()) -
4733d65def1SKadir Cetinkaya                              SM.getFileOffset(DefRange->getBegin());
4743d65def1SKadir Cetinkaya     const tooling::Replacement DeleteFuncBody(SM, DefRange->getBegin(),
4753d65def1SKadir Cetinkaya                                               SourceLen, "");
4763d65def1SKadir Cetinkaya 
477ee02e20cSKirill Bobyrev     llvm::SmallVector<std::pair<std::string, Edit>> Edits;
4783d65def1SKadir Cetinkaya     // Edit for Target.
479087528a3SKadir Cetinkaya     auto FE = Effect::fileEdit(SM, SM.getFileID(*Semicolon),
480087528a3SKadir Cetinkaya                                std::move(TargetFileReplacements));
4813d65def1SKadir Cetinkaya     if (!FE)
4823d65def1SKadir Cetinkaya       return FE.takeError();
4833d65def1SKadir Cetinkaya     Edits.push_back(std::move(*FE));
4843d65def1SKadir Cetinkaya 
4853d65def1SKadir Cetinkaya     // Edit for Source.
4863d65def1SKadir Cetinkaya     if (!SM.isWrittenInSameFile(DefRange->getBegin(),
4873d65def1SKadir Cetinkaya                                 SM.getExpansionLoc(Target->getBeginLoc()))) {
4883d65def1SKadir Cetinkaya       // Generate a new edit if the Source and Target are in different files.
4893d65def1SKadir Cetinkaya       auto FE = Effect::fileEdit(SM, SM.getFileID(Sel.Cursor),
4903d65def1SKadir Cetinkaya                                  tooling::Replacements(DeleteFuncBody));
4913d65def1SKadir Cetinkaya       if (!FE)
4923d65def1SKadir Cetinkaya         return FE.takeError();
4933d65def1SKadir Cetinkaya       Edits.push_back(std::move(*FE));
4943d65def1SKadir Cetinkaya     } else {
4953d65def1SKadir Cetinkaya       // Merge with previous edit if they are in the same file.
4963d65def1SKadir Cetinkaya       if (auto Err = Edits.front().second.Replacements.add(DeleteFuncBody))
4973d65def1SKadir Cetinkaya         return std::move(Err);
4983d65def1SKadir Cetinkaya     }
4993d65def1SKadir Cetinkaya 
5003d65def1SKadir Cetinkaya     Effect E;
5013d65def1SKadir Cetinkaya     for (auto &Pair : Edits)
5023d65def1SKadir Cetinkaya       E.ApplyEdits.try_emplace(std::move(Pair.first), std::move(Pair.second));
5033d65def1SKadir Cetinkaya     return E;
5045ab9a850SKadir Cetinkaya   }
5055ab9a850SKadir Cetinkaya 
5065ab9a850SKadir Cetinkaya private:
5075ab9a850SKadir Cetinkaya   const FunctionDecl *Source = nullptr;
5085ab9a850SKadir Cetinkaya   const FunctionDecl *Target = nullptr;
5095ab9a850SKadir Cetinkaya };
5105ab9a850SKadir Cetinkaya 
51193a3128aSSimon Pilgrim REGISTER_TWEAK(DefineInline)
5125ab9a850SKadir Cetinkaya 
5135ab9a850SKadir Cetinkaya } // namespace
5145ab9a850SKadir Cetinkaya } // namespace clangd
5155ab9a850SKadir Cetinkaya } // namespace clang
516