xref: /llvm-project/clang-tools-extra/clangd/refactor/tweaks/SpecialMembers.cpp (revision e26dad0a661e055076002d0de4ceb713b8ca6917)
1 //===--- SpecialMembers.cpp - Generate C++ special member functions -------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 #include "ParsedAST.h"
9 #include "refactor/InsertionPoint.h"
10 #include "refactor/Tweak.h"
11 #include "clang/AST/DeclCXX.h"
12 #include "clang/Sema/Sema.h"
13 #include "clang/Tooling/Core/Replacement.h"
14 #include "llvm/ADT/StringRef.h"
15 #include "llvm/Support/Casting.h"
16 #include "llvm/Support/Error.h"
17 
18 namespace clang {
19 namespace clangd {
20 namespace {
21 
22 // Returns code to declare missing copy/move constructors/assignment operators.
23 // They will be deleted or defaulted to match the class's current state.
buildSpecialMemberDeclarations(const CXXRecordDecl & Class)24 std::string buildSpecialMemberDeclarations(const CXXRecordDecl &Class) {
25   struct Members {
26     const CXXMethodDecl *Copy = nullptr;
27     const CXXMethodDecl *Move = nullptr;
28   } Ctor, Assign;
29 
30   for (const auto &M : Class.methods()) {
31     if (M->isCopyAssignmentOperator())
32       Assign.Copy = M;
33     else if (M->isMoveAssignmentOperator())
34       Assign.Move = M;
35     if (const auto *C = llvm::dyn_cast<CXXConstructorDecl>(M)) {
36       if (C->isCopyConstructor())
37         Ctor.Copy = C;
38       else if (C->isMoveConstructor())
39         Ctor.Move = C;
40     }
41   }
42 
43   std::string S;
44   llvm::raw_string_ostream OS(S);
45 
46   auto PrintMember = [&](const CXXMethodDecl *D, const char *MemberPattern,
47                          const char *ParmPattern) {
48     if (D && !D->isImplicit())
49       return;
50     bool Delete = !D || D->isDeleted();
51     OS << llvm::formatv(
52         "{0} = {1};\n",
53         llvm::formatv(MemberPattern, Class.getName(),
54                       llvm::formatv(ParmPattern, Class.getName())),
55         Delete ? "delete" : "default");
56   };
57   auto PrintMembers = [&](const Members &M, const char *MemberPattern) {
58     PrintMember(M.Copy, MemberPattern, /*ParmPattern=*/"const {0}&");
59     PrintMember(M.Move, MemberPattern, /*ParmPattern=*/"{0}&&");
60   };
61   PrintMembers(Ctor, /*MemberPattern=*/"{0}({1})");
62   PrintMembers(Assign, /*MemberPattern=*/"{0} &operator=({1})");
63 
64   return S;
65 }
66 
67 // A tweak that adds missing declarations of copy & move constructors.
68 //
69 // e.g. given `struct ^S{};`, produces:
70 //   struct S {
71 //     S(const S&) = default;
72 //     S(S&&) = default;
73 //     S &operator=(const S&) = default;
74 //     S &operator=(S&&) = default;
75 //   };
76 //
77 // Added members are defaulted or deleted to approximately preserve semantics.
78 // (May not be a strict no-op when they were not implicitly declared).
79 //
80 // Having these spelled out is useful:
81 //  - to understand the implicit behavior
82 //  - to avoid relying on the implicit behavior
83 //  - as a baseline for explicit modification
84 class SpecialMembers : public Tweak {
85 public:
86   const char *id() const final;
kind() const87   llvm::StringLiteral kind() const override {
88     return CodeAction::REFACTOR_KIND;
89   }
title() const90   std::string title() const override {
91     return llvm::formatv("Declare implicit {0} members",
92                          NeedCopy ? NeedMove ? "copy/move" : "copy" : "move");
93   }
94 
prepare(const Selection & Inputs)95   bool prepare(const Selection &Inputs) override {
96     // This tweak relies on =default and =delete.
97     if (!Inputs.AST->getLangOpts().CPlusPlus11)
98       return false;
99 
100     // Trigger only on class definitions.
101     if (auto *N = Inputs.ASTSelection.commonAncestor())
102       Class = const_cast<CXXRecordDecl *>(N->ASTNode.get<CXXRecordDecl>());
103     if (!Class || !Class->isThisDeclarationADefinition() || Class->isUnion())
104       return false;
105 
106     // Tweak is only available if some members are missing.
107     NeedCopy = !Class->hasUserDeclaredCopyConstructor() ||
108                !Class->hasUserDeclaredCopyAssignment();
109     NeedMove = !Class->hasUserDeclaredMoveAssignment() ||
110                !Class->hasUserDeclaredMoveConstructor();
111     return NeedCopy || NeedMove;
112   }
113 
apply(const Selection & Inputs)114   Expected<Effect> apply(const Selection &Inputs) override {
115     // Implicit special members are created lazily by clang.
116     // We need them so we can tell whether they should be =default or =delete.
117     Inputs.AST->getSema().ForceDeclarationOfImplicitMembers(Class);
118     std::string Code = buildSpecialMemberDeclarations(*Class);
119 
120     // Prefer to place the new members...
121     std::vector<Anchor> Anchors = {
122         // Below the default constructor
123         {[](const Decl *D) {
124            if (const auto *CCD = llvm::dyn_cast<CXXConstructorDecl>(D))
125              return CCD->isDefaultConstructor();
126            return false;
127          },
128          Anchor::Below},
129         // Above existing constructors
130         {[](const Decl *D) { return llvm::isa<CXXConstructorDecl>(D); },
131          Anchor::Above},
132         // At the top of the public section
133         {[](const Decl *D) { return true; }, Anchor::Above},
134     };
135     auto Edit = insertDecl(Code, *Class, std::move(Anchors), AS_public);
136     if (!Edit)
137       return Edit.takeError();
138     return Effect::mainFileEdit(Inputs.AST->getSourceManager(),
139                                 tooling::Replacements{std::move(*Edit)});
140   }
141 
142 private:
143   bool NeedCopy = false, NeedMove = false;
144   CXXRecordDecl *Class = nullptr;
145 };
146 REGISTER_TWEAK(SpecialMembers)
147 
148 } // namespace
149 } // namespace clangd
150 } // namespace clang
151