xref: /llvm-project/clang-tools-extra/clangd/refactor/tweaks/MemberwiseConstructor.cpp (revision 95a932fb15960e66fdc43dc9821685addd5ee44d)
1afa94306SSam McCall //===--- MemberwiseConstructor.cpp - Generate C++ constructor -------------===//
2afa94306SSam McCall //
3afa94306SSam McCall // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4afa94306SSam McCall // See https://llvm.org/LICENSE.txt for license information.
5afa94306SSam McCall // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6afa94306SSam McCall //
7afa94306SSam McCall //===----------------------------------------------------------------------===//
8afa94306SSam McCall #include "AST.h"
9afa94306SSam McCall #include "ParsedAST.h"
10afa94306SSam McCall #include "refactor/InsertionPoint.h"
11afa94306SSam McCall #include "refactor/Tweak.h"
12afa94306SSam McCall #include "support/Logger.h"
13afa94306SSam McCall #include "clang/AST/DeclCXX.h"
14afa94306SSam McCall #include "clang/AST/TypeVisitor.h"
15afa94306SSam McCall #include "clang/Basic/SourceManager.h"
16afa94306SSam McCall #include "clang/Tooling/Core/Replacement.h"
17afa94306SSam McCall #include "llvm/ADT/StringRef.h"
18afa94306SSam McCall #include "llvm/Support/Casting.h"
19afa94306SSam McCall #include "llvm/Support/Error.h"
20afa94306SSam McCall 
21afa94306SSam McCall namespace clang {
22afa94306SSam McCall namespace clangd {
23afa94306SSam McCall namespace {
24afa94306SSam McCall 
25afa94306SSam McCall // A tweak that adds a C++ constructor which initializes each member.
26afa94306SSam McCall //
27afa94306SSam McCall // Given:
28afa94306SSam McCall //   struct S{ int x; unique_ptr<double> y; };
29afa94306SSam McCall // the tweak inserts the constructor:
30afa94306SSam McCall //   S(int x, unique_ptr<double> y) : x(x), y(std::move(y)) {}
31afa94306SSam McCall //
32afa94306SSam McCall // We place the constructor inline, other tweaks are available to outline it.
33afa94306SSam McCall class MemberwiseConstructor : public Tweak {
34afa94306SSam McCall public:
35*95a932fbSKazu Hirata   const char *id() const final;
kind() const36afa94306SSam McCall   llvm::StringLiteral kind() const override {
37afa94306SSam McCall     return CodeAction::REFACTOR_KIND;
38afa94306SSam McCall   }
title() const39afa94306SSam McCall   std::string title() const override {
40afa94306SSam McCall     return llvm::formatv("Define constructor");
41afa94306SSam McCall   }
42afa94306SSam McCall 
prepare(const Selection & Inputs)43afa94306SSam McCall   bool prepare(const Selection &Inputs) override {
44afa94306SSam McCall     // This tweak assumes move semantics.
45afa94306SSam McCall     if (!Inputs.AST->getLangOpts().CPlusPlus11)
46afa94306SSam McCall       return false;
47afa94306SSam McCall 
48afa94306SSam McCall     // Trigger only on class definitions.
49afa94306SSam McCall     if (auto *N = Inputs.ASTSelection.commonAncestor())
50afa94306SSam McCall       Class = N->ASTNode.get<CXXRecordDecl>();
51afa94306SSam McCall     if (!Class || !Class->isThisDeclarationADefinition() || Class->isUnion() ||
52afa94306SSam McCall         Class->getDeclName().isEmpty())
53afa94306SSam McCall       return false;
54afa94306SSam McCall 
55afa94306SSam McCall     dlog("MemberwiseConstructor for {0}?", Class->getName());
56afa94306SSam McCall     // For now, don't support nontrivial initialization of bases.
57afa94306SSam McCall     for (const CXXBaseSpecifier &Base : Class->bases()) {
58afa94306SSam McCall       const auto *BaseClass = Base.getType()->getAsCXXRecordDecl();
59afa94306SSam McCall       if (!BaseClass || !BaseClass->hasDefaultConstructor()) {
60afa94306SSam McCall         dlog("  can't construct base {0}", Base.getType().getAsString());
61afa94306SSam McCall         return false;
62afa94306SSam McCall       }
63afa94306SSam McCall     }
64afa94306SSam McCall 
65afa94306SSam McCall     // We don't want to offer the tweak if there's a similar constructor.
66afa94306SSam McCall     // For now, only offer it if all constructors are special members.
67afa94306SSam McCall     for (const CXXConstructorDecl *CCD : Class->ctors()) {
68afa94306SSam McCall       if (!CCD->isDefaultConstructor() && !CCD->isCopyOrMoveConstructor()) {
69afa94306SSam McCall         dlog("  conflicting constructor");
70afa94306SSam McCall         return false;
71afa94306SSam McCall       }
72afa94306SSam McCall     }
73afa94306SSam McCall 
74afa94306SSam McCall     // Examine the fields to see which ones we should initialize.
75afa94306SSam McCall     for (const FieldDecl *D : Class->fields()) {
76afa94306SSam McCall       switch (FieldAction A = considerField(D)) {
77afa94306SSam McCall       case Fail:
78afa94306SSam McCall         dlog("  difficult field {0}", D->getName());
79afa94306SSam McCall         return false;
80afa94306SSam McCall       case Skip:
81afa94306SSam McCall         dlog("  (skipping field {0})", D->getName());
82afa94306SSam McCall         break;
83afa94306SSam McCall       default:
84afa94306SSam McCall         Fields.push_back({D, A});
85afa94306SSam McCall         break;
86afa94306SSam McCall       }
87afa94306SSam McCall     }
88afa94306SSam McCall     // Only offer the tweak if we have some fields to initialize.
89afa94306SSam McCall     if (Fields.empty()) {
90afa94306SSam McCall       dlog("  no fields to initialize");
91afa94306SSam McCall       return false;
92afa94306SSam McCall     }
93afa94306SSam McCall 
94afa94306SSam McCall     return true;
95afa94306SSam McCall   }
96afa94306SSam McCall 
apply(const Selection & Inputs)97afa94306SSam McCall   Expected<Effect> apply(const Selection &Inputs) override {
98afa94306SSam McCall     std::string Code = buildCode();
99afa94306SSam McCall     // Prefer to place the new constructor...
100afa94306SSam McCall     std::vector<Anchor> Anchors = {
101afa94306SSam McCall         // Below special constructors.
102afa94306SSam McCall         {[](const Decl *D) {
103afa94306SSam McCall            if (const auto *CCD = llvm::dyn_cast<CXXConstructorDecl>(D))
104afa94306SSam McCall              return CCD->isDefaultConstructor();
105afa94306SSam McCall            return false;
106afa94306SSam McCall          },
107afa94306SSam McCall          Anchor::Below},
108afa94306SSam McCall         // Above other constructors
109afa94306SSam McCall         {[](const Decl *D) { return llvm::isa<CXXConstructorDecl>(D); },
110afa94306SSam McCall          Anchor::Above},
111afa94306SSam McCall         // At the top of the public section
112afa94306SSam McCall         {[](const Decl *D) { return true; }, Anchor::Above},
113afa94306SSam McCall     };
114afa94306SSam McCall     auto Edit = insertDecl(Code, *Class, std::move(Anchors), AS_public);
115afa94306SSam McCall     if (!Edit)
116afa94306SSam McCall       return Edit.takeError();
117afa94306SSam McCall     return Effect::mainFileEdit(Inputs.AST->getSourceManager(),
118afa94306SSam McCall                                 tooling::Replacements{std::move(*Edit)});
119afa94306SSam McCall   }
120afa94306SSam McCall 
121afa94306SSam McCall private:
122afa94306SSam McCall   enum FieldAction {
123afa94306SSam McCall     Fail,    // Disallow the tweak, we can't handle this field.
124afa94306SSam McCall     Skip,    // Do not initialize this field, but allow the tweak anyway.
125afa94306SSam McCall     Move,    // Pass by value and std::move into place
126afa94306SSam McCall     Copy,    // Pass by value and copy into place
127afa94306SSam McCall     CopyRef, // Pass by const ref and copy into place
128afa94306SSam McCall   };
considerField(const FieldDecl * Field) const129afa94306SSam McCall   FieldAction considerField(const FieldDecl *Field) const {
130afa94306SSam McCall     if (Field->hasInClassInitializer())
131afa94306SSam McCall       return Skip;
132afa94306SSam McCall     if (!Field->getIdentifier())
133afa94306SSam McCall       return Fail;
134afa94306SSam McCall 
135afa94306SSam McCall     // Decide what to do based on the field type.
136afa94306SSam McCall     class Visitor : public TypeVisitor<Visitor, FieldAction> {
137afa94306SSam McCall     public:
138afa94306SSam McCall       Visitor(const ASTContext &Ctx) : Ctx(Ctx) {}
139afa94306SSam McCall       const ASTContext &Ctx;
140afa94306SSam McCall 
141afa94306SSam McCall       // If we don't understand the type, assume we can't handle it.
142afa94306SSam McCall       FieldAction VisitType(const Type *T) { return Fail; }
143afa94306SSam McCall       FieldAction VisitRecordType(const RecordType *T) {
144afa94306SSam McCall         if (const auto *D = T->getAsCXXRecordDecl())
145afa94306SSam McCall           return considerClassValue(*D);
146afa94306SSam McCall         return Fail;
147afa94306SSam McCall       }
148afa94306SSam McCall       FieldAction VisitBuiltinType(const BuiltinType *T) {
149afa94306SSam McCall         if (T->isInteger() || T->isFloatingPoint() || T->isNullPtrType())
150afa94306SSam McCall           return Copy;
151afa94306SSam McCall         return Fail;
152afa94306SSam McCall       }
153afa94306SSam McCall       FieldAction VisitObjCObjectPointerType(const ObjCObjectPointerType *) {
154afa94306SSam McCall         return Ctx.getLangOpts().ObjCAutoRefCount ? Copy : Fail;
155afa94306SSam McCall       }
156afa94306SSam McCall       FieldAction VisitAttributedType(const AttributedType *T) {
157afa94306SSam McCall         return Visit(T->getModifiedType().getCanonicalType().getTypePtr());
158afa94306SSam McCall       }
159afa94306SSam McCall #define ALWAYS(T, Action)                                                      \
160afa94306SSam McCall   FieldAction Visit##T##Type(const T##Type *) { return Action; }
161afa94306SSam McCall       // Trivially copyable types (pointers and numbers).
162afa94306SSam McCall       ALWAYS(Pointer, Copy);
163afa94306SSam McCall       ALWAYS(MemberPointer, Copy);
164afa94306SSam McCall       ALWAYS(Reference, Copy);
165afa94306SSam McCall       ALWAYS(Complex, Copy);
166afa94306SSam McCall       ALWAYS(Enum, Copy);
167afa94306SSam McCall       // These types are dependent (when canonical) and likely to be classes.
168afa94306SSam McCall       // Move is a reasonable generic option.
169afa94306SSam McCall       ALWAYS(DependentName, Move);
170afa94306SSam McCall       ALWAYS(UnresolvedUsing, Move);
171afa94306SSam McCall       ALWAYS(TemplateTypeParm, Move);
172afa94306SSam McCall       ALWAYS(TemplateSpecialization, Move);
173afa94306SSam McCall     };
174afa94306SSam McCall #undef ALWAYS
175afa94306SSam McCall     return Visitor(Class->getASTContext())
176afa94306SSam McCall         .Visit(Field->getType().getCanonicalType().getTypePtr());
177afa94306SSam McCall   }
178afa94306SSam McCall 
179afa94306SSam McCall   // Decide what to do with a field of type C.
considerClassValue(const CXXRecordDecl & C)180afa94306SSam McCall   static FieldAction considerClassValue(const CXXRecordDecl &C) {
18184051d82SHaojian Wu     if (!C.hasDefinition())
18284051d82SHaojian Wu       return Skip;
183afa94306SSam McCall     // We can't always tell if C is copyable/movable without doing Sema work.
184afa94306SSam McCall     // We assume operations are possible unless we can prove not.
185afa94306SSam McCall     bool CanCopy = C.hasUserDeclaredCopyConstructor() ||
186afa94306SSam McCall                    C.needsOverloadResolutionForCopyConstructor() ||
187afa94306SSam McCall                    !C.defaultedCopyConstructorIsDeleted();
188afa94306SSam McCall     bool CanMove = C.hasUserDeclaredMoveConstructor() ||
189afa94306SSam McCall                    (C.needsOverloadResolutionForMoveConstructor() ||
190afa94306SSam McCall                     !C.defaultedMoveConstructorIsDeleted());
191afa94306SSam McCall     bool CanDefaultConstruct = C.hasDefaultConstructor();
192afa94306SSam McCall     if (C.hasUserDeclaredCopyConstructor() ||
193afa94306SSam McCall         C.hasUserDeclaredMoveConstructor()) {
194afa94306SSam McCall       for (const CXXConstructorDecl *CCD : C.ctors()) {
195afa94306SSam McCall         bool IsUsable = !CCD->isDeleted() && CCD->getAccess() == AS_public;
196afa94306SSam McCall         if (CCD->isCopyConstructor())
197afa94306SSam McCall           CanCopy = CanCopy && IsUsable;
198afa94306SSam McCall         if (CCD->isMoveConstructor())
199afa94306SSam McCall           CanMove = CanMove && IsUsable;
200afa94306SSam McCall         if (CCD->isDefaultConstructor())
201afa94306SSam McCall           CanDefaultConstruct = IsUsable;
202afa94306SSam McCall       }
203afa94306SSam McCall     }
204afa94306SSam McCall     dlog("  {0} CanCopy={1} CanMove={2} TriviallyCopyable={3}", C.getName(),
205afa94306SSam McCall          CanCopy, CanMove, C.isTriviallyCopyable());
206afa94306SSam McCall     if (CanCopy && C.isTriviallyCopyable())
207afa94306SSam McCall       return Copy;
208afa94306SSam McCall     if (CanMove)
209afa94306SSam McCall       return Move;
210afa94306SSam McCall     if (CanCopy)
211afa94306SSam McCall       return CopyRef;
212afa94306SSam McCall     // If it's neither copyable nor movable, then default construction is
213afa94306SSam McCall     // likely to make sense (example: std::mutex).
214afa94306SSam McCall     if (CanDefaultConstruct)
215afa94306SSam McCall       return Skip;
216afa94306SSam McCall     return Fail;
217afa94306SSam McCall   }
218afa94306SSam McCall 
buildCode() const219afa94306SSam McCall   std::string buildCode() const {
220afa94306SSam McCall     std::string S;
221afa94306SSam McCall     llvm::raw_string_ostream OS(S);
222afa94306SSam McCall 
223afa94306SSam McCall     if (Fields.size() == 1)
224afa94306SSam McCall       OS << "explicit ";
225afa94306SSam McCall     OS << Class->getName() << "(";
226afa94306SSam McCall     const char *Sep = "";
227afa94306SSam McCall     for (const FieldInfo &Info : Fields) {
228afa94306SSam McCall       OS << Sep;
229afa94306SSam McCall       QualType ParamType = Info.Field->getType().getLocalUnqualifiedType();
230afa94306SSam McCall       if (Info.Action == CopyRef)
231afa94306SSam McCall         ParamType = Class->getASTContext().getLValueReferenceType(
232afa94306SSam McCall             ParamType.withConst());
233afa94306SSam McCall       OS << printType(ParamType, *Class,
234afa94306SSam McCall                       /*Placeholder=*/paramName(Info.Field));
235afa94306SSam McCall       Sep = ", ";
236afa94306SSam McCall     }
237afa94306SSam McCall     OS << ")";
238afa94306SSam McCall     Sep = " : ";
239afa94306SSam McCall     for (const FieldInfo &Info : Fields) {
240afa94306SSam McCall       OS << Sep << Info.Field->getName() << "(";
241afa94306SSam McCall       if (Info.Action == Move)
242afa94306SSam McCall         OS << "std::move("; // FIXME: #include <utility> too
243afa94306SSam McCall       OS << paramName(Info.Field);
244afa94306SSam McCall       if (Info.Action == Move)
245afa94306SSam McCall         OS << ")";
246afa94306SSam McCall       OS << ")";
247afa94306SSam McCall       Sep = ", ";
248afa94306SSam McCall     }
249afa94306SSam McCall     OS << " {}\n";
250afa94306SSam McCall 
251afa94306SSam McCall     return S;
252afa94306SSam McCall   }
253afa94306SSam McCall 
paramName(const FieldDecl * Field) const254afa94306SSam McCall   llvm::StringRef paramName(const FieldDecl *Field) const {
255afa94306SSam McCall     return Field->getName().trim("_");
256afa94306SSam McCall   }
257afa94306SSam McCall 
258afa94306SSam McCall   const CXXRecordDecl *Class = nullptr;
259afa94306SSam McCall   struct FieldInfo {
260afa94306SSam McCall     const FieldDecl *Field;
261afa94306SSam McCall     FieldAction Action;
262afa94306SSam McCall   };
263afa94306SSam McCall   std::vector<FieldInfo> Fields;
264afa94306SSam McCall };
265afa94306SSam McCall REGISTER_TWEAK(MemberwiseConstructor)
266afa94306SSam McCall 
267afa94306SSam McCall } // namespace
268afa94306SSam McCall } // namespace clangd
269afa94306SSam McCall } // namespace clang
270