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