brintos

brintos / llvm-project-archived public Read only

0
0
Text · 9.2 KiB · 9bb8ca9 Raw
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