//===--- SpecialMembers.cpp - Generate C++ special member functions -------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "ParsedAST.h" #include "refactor/InsertionPoint.h" #include "refactor/Tweak.h" #include "clang/AST/DeclCXX.h" #include "clang/Sema/Sema.h" #include "clang/Tooling/Core/Replacement.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Error.h" namespace clang { namespace clangd { namespace { // Returns code to declare missing copy/move constructors/assignment operators. // They will be deleted or defaulted to match the class's current state. std::string buildSpecialMemberDeclarations(const CXXRecordDecl &Class) { struct Members { const CXXMethodDecl *Copy = nullptr; const CXXMethodDecl *Move = nullptr; } Ctor, Assign; for (const auto &M : Class.methods()) { if (M->isCopyAssignmentOperator()) Assign.Copy = M; else if (M->isMoveAssignmentOperator()) Assign.Move = M; if (const auto *C = llvm::dyn_cast(M)) { if (C->isCopyConstructor()) Ctor.Copy = C; else if (C->isMoveConstructor()) Ctor.Move = C; } } std::string S; llvm::raw_string_ostream OS(S); auto PrintMember = [&](const CXXMethodDecl *D, const char *MemberPattern, const char *ParmPattern) { if (D && !D->isImplicit()) return; bool Delete = !D || D->isDeleted(); OS << llvm::formatv( "{0} = {1};\n", llvm::formatv(MemberPattern, Class.getName(), llvm::formatv(ParmPattern, Class.getName())), Delete ? "delete" : "default"); }; auto PrintMembers = [&](const Members &M, const char *MemberPattern) { PrintMember(M.Copy, MemberPattern, /*ParmPattern=*/"const {0}&"); PrintMember(M.Move, MemberPattern, /*ParmPattern=*/"{0}&&"); }; PrintMembers(Ctor, /*MemberPattern=*/"{0}({1})"); PrintMembers(Assign, /*MemberPattern=*/"{0} &operator=({1})"); return S; } // A tweak that adds missing declarations of copy & move constructors. // // e.g. given `struct ^S{};`, produces: // struct S { // S(const S&) = default; // S(S&&) = default; // S &operator=(const S&) = default; // S &operator=(S&&) = default; // }; // // Added members are defaulted or deleted to approximately preserve semantics. // (May not be a strict no-op when they were not implicitly declared). // // Having these spelled out is useful: // - to understand the implicit behavior // - to avoid relying on the implicit behavior // - as a baseline for explicit modification class SpecialMembers : public Tweak { public: const char *id() const final; llvm::StringLiteral kind() const override { return CodeAction::REFACTOR_KIND; } std::string title() const override { return llvm::formatv("Declare implicit {0} members", NeedCopy ? NeedMove ? "copy/move" : "copy" : "move"); } bool prepare(const Selection &Inputs) override { // This tweak relies on =default and =delete. if (!Inputs.AST->getLangOpts().CPlusPlus11) return false; // Trigger only on class definitions. if (auto *N = Inputs.ASTSelection.commonAncestor()) Class = const_cast(N->ASTNode.get()); if (!Class || !Class->isThisDeclarationADefinition() || Class->isUnion()) return false; // Tweak is only available if some members are missing. NeedCopy = !Class->hasUserDeclaredCopyConstructor() || !Class->hasUserDeclaredCopyAssignment(); NeedMove = !Class->hasUserDeclaredMoveAssignment() || !Class->hasUserDeclaredMoveConstructor(); return NeedCopy || NeedMove; } Expected apply(const Selection &Inputs) override { // Implicit special members are created lazily by clang. // We need them so we can tell whether they should be =default or =delete. Inputs.AST->getSema().ForceDeclarationOfImplicitMembers(Class); std::string Code = buildSpecialMemberDeclarations(*Class); // Prefer to place the new members... std::vector Anchors = { // Below the default constructor {[](const Decl *D) { if (const auto *CCD = llvm::dyn_cast(D)) return CCD->isDefaultConstructor(); return false; }, Anchor::Below}, // Above existing constructors {[](const Decl *D) { return llvm::isa(D); }, Anchor::Above}, // At the top of the public section {[](const Decl *D) { return true; }, Anchor::Above}, }; auto Edit = insertDecl(Code, *Class, std::move(Anchors), AS_public); if (!Edit) return Edit.takeError(); return Effect::mainFileEdit(Inputs.AST->getSourceManager(), tooling::Replacements{std::move(*Edit)}); } private: bool NeedCopy = false, NeedMove = false; CXXRecordDecl *Class = nullptr; }; REGISTER_TWEAK(SpecialMembers) } // namespace } // namespace clangd } // namespace clang