Index: clang-tidy/modernize/UseDefaultCheck.cpp =================================================================== --- clang-tidy/modernize/UseDefaultCheck.cpp +++ clang-tidy/modernize/UseDefaultCheck.cpp @@ -10,6 +10,7 @@ #include "UseDefaultCheck.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" using namespace clang::ast_matchers; @@ -17,45 +18,347 @@ namespace tidy { namespace modernize { -static const char CtorDtor[] = "CtorDtorDecl"; +static const char SpecialFunction[] = "SpecialFunction"; + +/// \brief Finds the SourceLocation of the colon ':' before the initialization +/// list in the definition of a constructor. +static SourceLocation getColonLoc(const ASTContext *Context, + const CXXConstructorDecl *Ctor) { + // FIXME: First init is the first initialization that is going to be + // performed, no matter what was the real order in the source code. If the + // order of the inits is wrong in the code, it may result in a false negative. + SourceLocation FirstInit = (*Ctor->init_begin())->getSourceLocation(); + SourceLocation LastArg = + Ctor->getParamDecl(Ctor->getNumParams() - 1)->getLocEnd(); + // We need to find the colon between the ')' and the first initializer. + bool Invalid = false; + StringRef Text = Lexer::getSourceText( + CharSourceRange::getCharRange(LastArg, FirstInit), + Context->getSourceManager(), Context->getLangOpts(), &Invalid); + if (Invalid) + return SourceLocation(); + + size_t ColonPos = Text.rfind(':'); + if (ColonPos == StringRef::npos) + return SourceLocation(); + + Text = Text.drop_front(ColonPos + 1); + if (std::strspn(Text.data(), " \t\r\n") != Text.size()) { + // If there are comments, preprocessor directives or anything, abort. + return SourceLocation(); + } + // FIXME: don't remove comments in the middle of the initializers. + return LastArg.getLocWithOffset(ColonPos); +} + +/// \brief Returns true if \p E is a DeclRefExpr to the variable declared with +/// \p Var. +static bool exprRefersToVariable(const Expr *E, const ValueDecl *Var) { + const auto *DeclRef = dyn_cast(E->IgnoreParenImpCasts()); + return DeclRef && DeclRef->getDecl() && + DeclRef->getDecl()->getCanonicalDecl() == Var->getCanonicalDecl(); +} + +/// \brief Returns true if \p E is a member expression, where the base is the +/// variable declared with \p Var and the accessed member is the one declared +/// as \p Field. +/// +/// FIXME: always returns false in class templates. +static bool exprIsAccessToFieldInVar(const Expr *E, const FieldDecl *Field, + const ValueDecl *Var) { + // Ensure that the expression is a member expression. + const auto *Access = dyn_cast(E->IgnoreParenImpCasts()); + if (!Access) + return false; + + // It must be an access to the specified field. + if (dyn_cast(Access->getMemberDecl()) != Field) + return false; + + // The base must be a DeclRefExpr to Var. + return exprRefersToVariable(Access->getBase(), Var); +} + +/// \brief Finds all the named non-static fields of \p Record. +static std::set +getAllNamedFields(const CXXRecordDecl *Record) { + std::set Result; + for (const auto *Field : Record->fields()) { + // Static data members are not in this range. + if (Field->isUnnamedBitfield()) + continue; + Result.insert(Field); + } + return Result; +} + +/// \brief Returns the names of the direct bases of \p Record, both virtual and +/// non-virtual. +static std::set getAllDirectBases(const CXXRecordDecl *Record) { + std::set Result; + for (auto Base : Record->bases()) { + // CXXBaseSpecifier. + const auto *BaseType = Base.getTypeSourceInfo()->getType().getTypePtr(); + Result.insert(BaseType); + } + return Result; +} + +/// \brief Checks that the given method receives as only parameter a lvalue +/// reference to a variable with the type that has this method as a member. +static bool hasCopySignature(const ASTContext *Context, + const CXXMethodDecl *Method) { + if (Method->getNumParams() != 1) + return false; + QualType ArgType = Method->getParamDecl(0)->getType(); + + const auto *Record = Method->getParent(); + QualType RecordType = Context->getTypeDeclType(Record); + + return !ArgType.isNull() && ArgType->isLValueReferenceType() && + ArgType.getNonReferenceType().getUnqualifiedType() == RecordType; +} + +/// \brief Check that the given constructor has copy signature and that it +/// copy-initializes all its bases and members. +static bool isCopyConstructorAndCanBeDefaulted(const ASTContext *Context, + const CXXConstructorDecl *Ctor) { + // An explicitly-defaulted constructor cannot have default arguments. + if (!hasCopySignature(Context, Ctor) || Ctor->getMinRequiredArguments() != 1) + return false; + + // Base classes and members that have to be copied. + const auto *Record = Ctor->getParent(); + auto BasesToInit = getAllDirectBases(Record); + auto FieldsToInit = getAllNamedFields(Record); + + for (const CXXCtorInitializer *Initializer : Ctor->inits()) { + if (Initializer->isIndirectMemberInitializer() || + Initializer->isInClassMemberInitializer() || + Initializer->isDelegatingInitializer()) + return false; + + const auto *Init = Initializer->getInit(); + if (Initializer->isBaseInitializer()) { + // Initialization of a base class. It should be a call to a copy + // constructor of the base. + const auto *Construct = dyn_cast(Init); + if (!BasesToInit.count(Initializer->getBaseClass()) || !Construct || + !Construct->getConstructor()->isCopyConstructor() || + !exprRefersToVariable(Construct->getArg(0), Ctor->getParamDecl(0))) + return false; + BasesToInit.erase(Initializer->getBaseClass()); + } else if (Initializer->isMemberInitializer()) { + const auto *Field = Initializer->getMember(); + // The initialization is a CXXConstructExpr only for class types. + if (const auto *Construct = dyn_cast(Init)) { + if (!Construct->getConstructor()->isCopyConstructor()) + return false; + Init = Construct->getArg(0); + } + if (!FieldsToInit.count(Field) || + !exprIsAccessToFieldInVar(Init, Field, Ctor->getParamDecl(0))) { + return false; + } + FieldsToInit.erase(Field); + } else { + llvm_unreachable("Unknown CXXCtorInitializer"); + } + } + return BasesToInit.empty() && FieldsToInit.empty(); +} + +/// \brief Checks that the given method is an overloading of the assignment +/// operator, has copy signature, returns a reference to "*this" and copies +/// all its members and subobjects. +static bool isCopyAssignmentAndCanBeDefaulted(const ASTContext *Context, + const CXXMethodDecl *Operator) { + if (!Operator->isCopyAssignmentOperator() || + !hasCopySignature(Context, Operator)) + return false; + + const auto *Record = Operator->getParent(); + auto BasesToInit = getAllDirectBases(Record); + auto FieldsToInit = getAllNamedFields(Record); + bool ReturnsThis = false; + + for (const auto *Stmt : cast(Operator->getBody())->body()) { + if (const auto *Call = dyn_cast(Stmt)) { + const auto *Object = Call->getImplicitObjectArgument(); + const auto *Method = Call->getMethodDecl(); + if (!Object || !Method) + return false; + // Assignment operator of a base class: + // Base::operator=(Other); + // + // Clang translates this into: + // ((Base*)this)->operator=((Base)Other); + // + // So we are looking for a member call that fulfills: + + // - The object is an implicit cast of 'this' to a pointer to a base + // class. + const auto *Cast = cast(Object); + if (!Cast || Cast->getCastKind() != CK_UncheckedDerivedToBase || + !Cast->getSubExpr()->isImplicitCXXThis()) + return false; + // - The called method is the operator=. + if (!Method->isCopyAssignmentOperator()) + return false; + // - The argument is (an implicit cast to a Base of) the argument taken by + // "Operator". + if (Call->getNumArgs() != 1 || + !exprRefersToVariable(Call->getArg(0), Operator->getParamDecl(0))) + return false; + + // Get the type of the base class. + const Type *Base = Cast->getType()->getPointeeType().getTypePtrOrNull(); + if (!BasesToInit.count(Base)) + return false; + BasesToInit.erase(Base); + } else if (const auto *Return = dyn_cast(Stmt)) { + // Returns statement of the assignment operator. Has to be: + // return *this; + const auto *Deref = dyn_cast(Return->getRetValue()); + if (!Deref || Deref->getOpcode() != UO_Deref || + !isa(Deref->getSubExpr())) + return false; + ReturnsThis = true; + break; + } else { + // The assignment of members: + // Field = Other.Field; + // Is a BinaryOperator in non-class types, and a CXXOperatorCallExpr + // otherwise. + const auto *BinOp = dyn_cast(Stmt); + const auto *OpCall = dyn_cast(Stmt); + const Expr *LHS, *RHS; + if (BinOp && BinOp->getOpcode() == BO_Assign) { + LHS = BinOp->getLHS(); + RHS = BinOp->getRHS(); + } else if (OpCall && OpCall->getOperator() == OO_Equal) { + assert(OpCall->getNumArgs() == 2 && + "operator= doesn't receive 2 arguments"); + LHS = OpCall->getArg(0); + RHS = OpCall->getArg(1); + } else { + // Unknown statement. + return false; + } + // Left side has to be a data member of 'this'. + const auto *Member = dyn_cast(LHS); + if (!Member || !Member->isImplicitAccess()) + return false; + const auto *Field = dyn_cast(Member->getMemberDecl()); + // getMemberDecl() will return either a FieldDecl or a CXXMethodDecl. + if (!Field) + return false; + + // Right side has to be an access to the same field in the parameter. + if (!FieldsToInit.count(Field) || + !exprIsAccessToFieldInVar(RHS, Field, Operator->getParamDecl(0))) + return false; + FieldsToInit.erase(Field); + } + } + + return BasesToInit.empty() && FieldsToInit.empty() && ReturnsThis; +} + +/// \brief Returns false if the body has any non-whitespace character. +static bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body) { + bool Invalid = false; + StringRef Text = Lexer::getSourceText( + CharSourceRange::getCharRange(Body->getLBracLoc().getLocWithOffset(1), + Body->getRBracLoc()), + Context->getSourceManager(), Context->getLangOpts(), &Invalid); + return !Invalid && std::strspn(Text.data(), " \t\r\n") == Text.size(); +} + +/// \brief Matcher for method declarations that are overloading the +/// copy-assignment operator. +AST_MATCHER(CXXMethodDecl, isCopyAssignmentOperator) { + return Node.isCopyAssignmentOperator(); +} void UseDefaultCheck::registerMatchers(MatchFinder *Finder) { if (getLangOpts().CPlusPlus) { + // Destructor. + Finder->addMatcher(cxxDestructorDecl(isDefinition()).bind(SpecialFunction), + this); Finder->addMatcher( - cxxConstructorDecl(isDefinition(), - unless(hasAnyConstructorInitializer(anything())), - parameterCountIs(0)) - .bind(CtorDtor), + cxxConstructorDecl( + isDefinition(), + anyOf( + // Default constructor. + allOf(unless(hasAnyConstructorInitializer(anything())), + parameterCountIs(0)), + // Copy constructor. + allOf(parameterCountIs(1), + hasParameter(0, hasType(lValueReferenceType()))))) + .bind(SpecialFunction), this); - Finder->addMatcher(cxxDestructorDecl(isDefinition()).bind(CtorDtor), this); + // Copy-assignment operator. + Finder->addMatcher(cxxMethodDecl(isDefinition(), isCopyAssignmentOperator()) + .bind(SpecialFunction), + this); } } void UseDefaultCheck::check(const MatchFinder::MatchResult &Result) { + std::string SpecialFunctionName; + SourceLocation StartLoc, EndLoc; + // Both CXXConstructorDecl and CXXDestructorDecl inherit from CXXMethodDecl. - const auto *CtorDtorDecl = Result.Nodes.getNodeAs(CtorDtor); + const auto *SpecialFunctionDecl = + Result.Nodes.getNodeAs(SpecialFunction); + + // Discard explicitly deleted/defaulted special member functions and those + // that are not user-provided (automatically generated). + if (SpecialFunctionDecl->isDeleted() || + SpecialFunctionDecl->isExplicitlyDefaulted() || + !SpecialFunctionDecl->isUserProvided() || !SpecialFunctionDecl->hasBody()) + return; + + const auto *Body = dyn_cast(SpecialFunctionDecl->getBody()); + if (!Body) + return; - // Discard explicitly deleted/defaulted constructors/destructors, those that - // are not user-provided (automatically generated constructor/destructor), and - // those with non-empty bodies. - if (CtorDtorDecl->isDeleted() || CtorDtorDecl->isExplicitlyDefaulted() || - !CtorDtorDecl->isUserProvided() || !CtorDtorDecl->hasTrivialBody()) + // Default locations. + StartLoc = Body->getLBracLoc(); + EndLoc = Body->getRBracLoc(); + + // If there are comments inside the body, don't do the change. + if (!SpecialFunctionDecl->isCopyAssignmentOperator() && + !bodyEmpty(Result.Context, Body)) return; - const auto *Body = dyn_cast(CtorDtorDecl->getBody()); - // This should never happen, since 'hasTrivialBody' checks that this is - // actually a CompoundStmt. - assert(Body && "Definition body is not a CompoundStmt"); - - diag(CtorDtorDecl->getLocStart(), - "use '= default' to define a trivial " + - std::string(dyn_cast(CtorDtorDecl) - ? "default constructor" - : "destructor")) + if (const auto *Ctor = dyn_cast(SpecialFunctionDecl)) { + if (Ctor->getNumParams() == 0) { + SpecialFunctionName = "default constructor"; + } else { + if (!isCopyConstructorAndCanBeDefaulted(Result.Context, Ctor)) + return; + SpecialFunctionName = "copy constructor"; + } + // If there are constructor initializers, they must be removed. + if (Ctor->getNumCtorInitializers() != 0) { + StartLoc = getColonLoc(Result.Context, Ctor); + if (!StartLoc.isValid()) + return; + } + } else if (dyn_cast(SpecialFunctionDecl)) { + SpecialFunctionName = "destructor"; + } else { + if (!isCopyAssignmentAndCanBeDefaulted(Result.Context, SpecialFunctionDecl)) + return; + SpecialFunctionName = "copy-assignment operator"; + } + + diag(SpecialFunctionDecl->getLocStart(), + "use '= default' to define a trivial " + SpecialFunctionName) << FixItHint::CreateReplacement( - CharSourceRange::getTokenRange(Body->getLBracLoc(), - Body->getRBracLoc()), - "= default;"); + CharSourceRange::getTokenRange(StartLoc, EndLoc), "= default;"); } } // namespace modernize Index: test/clang-tidy/modernize-use-default-copy.cpp =================================================================== --- /dev/null +++ test/clang-tidy/modernize-use-default-copy.cpp @@ -0,0 +1,422 @@ +// RUN: %check_clang_tidy %s modernize-use-default %t -- -- -std=c++11 -fno-delayed-template-parsing + +// Out of line definition. +struct OL { + OL(const OL &); + OL &operator=(const OL &); + int Field; +}; +OL::OL(const OL &Other) : Field(Other.Field) {} +// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: use '= default' to define a trivial copy constructor [modernize-use-default] +// CHECK-FIXES: OL::OL(const OL &Other) = default; +OL &OL::operator=(const OL &Other) { + Field = Other.Field; + return *this; +} +// CHECK-MESSAGES: :[[@LINE-4]]:1: warning: use '= default' to define a trivial copy-assignment operator [modernize-use-default] +// CHECK-FIXES: OL &OL::operator=(const OL &Other) = default; + +// Inline. +struct IL { + IL(const IL &Other) : Field(Other.Field) {} + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' + // CHECK-FIXES: IL(const IL &Other) = default; + IL &operator=(const IL &Other) { + Field = Other.Field; + return *this; + } + // CHECK-MESSAGES: :[[@LINE-4]]:3: warning: use '= default' + // CHECK-FIXES: IL &operator=(const IL &Other) = default; + int Field; +}; + +// Wrong type. +struct WT { + WT(const IL &Other) {} + WT &operator=(const IL &); +}; +WT &WT::operator=(const IL &Other) { return *this; } + +// Qualifiers. +struct Qual { + Qual(const Qual &Other) : Field(Other.Field), Volatile(Other.Volatile), + Mutable(Other.Mutable), Reference(Other.Reference), + Const(Other.Const) {} + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: use '= default' + // CHECK-FIXES: Qual(const Qual &Other) = default; + + int Field; + volatile char Volatile; + mutable bool Mutable; + const OL &Reference; // This makes this class non-assignable. + const IL Const; // This also makes this class non-assignable. + static int Static; +}; + +// Wrong init arguments. +struct WI { + WI(const WI &Other) : Field1(Other.Field1), Field2(Other.Field1) {} + WI &operator=(const WI &); + int Field1, Field2; +}; +WI &WI::operator=(const WI &Other) { + Field1 = Other.Field1; + Field2 = Other.Field1; + return *this; +} + +// Missing field. +struct MF { + MF(const MF &Other) : Field1(Other.Field1), Field2(Other.Field2) {} + MF &operator=(const MF &); + int Field1, Field2, Field3; +}; +MF &MF::operator=(const MF &Other) { + Field1 = Other.Field1; + Field2 = Other.Field2; + return *this; +} + +struct Comments { + Comments(const Comments &Other) + /* don't delete */ : /* this comment */ Field(Other.Field) {} + int Field; +}; + +struct MoreComments { + MoreComments(const MoreComments &Other) /* this comment is OK */ + : Field(Other.Field) {} + // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: use '= default' + // CHECK-FIXES: MoreComments(const MoreComments &Other) /* this comment is OK */ + // CHECK-FIXES-NEXT: = default; + int Field; +}; + +struct ColonInComment { + ColonInComment(const ColonInComment &Other) /* : */ : Field(Other.Field) {} + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' + // CHECK-FIXES: ColonInComment(const ColonInComment &Other) /* : */ = default; + int Field; +}; + +// No members or bases (in particular, no colon). +struct Empty { + Empty(const Empty &Other) {} + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' + // CHECK-FIXES: Empty(const Empty &Other) = default; + Empty &operator=(const Empty &); +}; +Empty &Empty::operator=(const Empty &Other) { return *this; } +// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: use '= default' +// CHECK-FIXES: Empty &Empty::operator=(const Empty &Other) = default; + +// Bit fields. +struct BF { + BF() = default; + BF(const BF &Other) : Field1(Other.Field1), Field2(Other.Field2), Field3(Other.Field3), + Field4(Other.Field4) {} + // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: use '= default' + // CHECK-FIXES: BF(const BF &Other) = default; + BF &operator=(const BF &); + + unsigned Field1 : 3; + int : 7; + char Field2 : 6; + int : 0; + int Field3 : 24; + unsigned char Field4; +}; +BF &BF::operator=(const BF &Other) { + Field1 = Other.Field1; + Field2 = Other.Field2; + Field3 = Other.Field3; + Field4 = Other.Field4; + return *this; +} +// CHECK-MESSAGES: :[[@LINE-7]]:1: warning: use '= default' +// CHECK-FIXES: BF &BF::operator=(const BF &Other) = default; + +// Base classes. +struct BC : IL, OL, BF { + BC(const BC &Other) : IL(Other), OL(Other), BF(Other) {} + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' + // CHECK-FIXES: BC(const BC &Other) = default; + BC &operator=(const BC &Other); +}; +BC &BC::operator=(const BC &Other) { + IL::operator=(Other); + OL::operator=(Other); + BF::operator=(Other); + return *this; +} +// CHECK-MESSAGES: :[[@LINE-6]]:1: warning: use '= default' +// CHECK-FIXES: BC &BC::operator=(const BC &Other) = default; + +// Base classes with member. +struct BCWM : IL, OL { + BCWM(const BCWM &Other) : IL(Other), OL(Other), Bf(Other.Bf) {} + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' + // CHECK-FIXES: BCWM(const BCWM &Other) = default; + BCWM &operator=(const BCWM &); + BF Bf; +}; +BCWM &BCWM::operator=(const BCWM &Other) { + IL::operator=(Other); + OL::operator=(Other); + Bf = Other.Bf; + return *this; +} +// CHECK-MESSAGES: :[[@LINE-6]]:1: warning: use '= default' +// CHECK-FIXES: BCWM &BCWM::operator=(const BCWM &Other) = default; + +// Missing base class. +struct MBC : IL, OL, BF { + MBC(const MBC &Other) : IL(Other), OL(Other) {} + MBC &operator=(const MBC &); +}; +MBC &MBC::operator=(const MBC &Other) { + IL::operator=(Other); + OL::operator=(Other); + return *this; +} + +// Base classes, incorrect parameter. +struct BCIP : BCWM, BF { + BCIP(const BCIP &Other) : BCWM(Other), BF(Other.Bf) {} + BCIP &operator=(const BCIP &); +}; +BCIP &BCIP::operator=(const BCIP &Other) { + BCWM::operator=(Other); + BF::operator=(Other.Bf); + return *this; +} + +// Virtual base classes. +struct VA : virtual OL {}; +struct VB : virtual OL {}; +struct VBC : VA, VB, virtual OL { + // OL is the first thing that is going to be initialized, despite the fact + // that it is the last in the list of bases, because it is virtual and there + // is a virtual OL at the beginning of VA (which is the same). + VBC(const VBC &Other) : OL(Other), VA(Other), VB(Other) {} + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' + // CHECK-FIXES: VBC(const VBC &Other) = default; + VBC &operator=(const VBC &Other); +}; +VBC &VBC::operator=(const VBC &Other) { + OL::operator=(Other); + VA::operator=(Other); + VB::operator=(Other); + return *this; +} +// CHECK-MESSAGES: :[[@LINE-6]]:1: warning: use '= default' +// CHECK-FIXES: VBC &VBC::operator=(const VBC &Other) = default; + +// Indirect base. +struct IB : VBC { + IB(const IB &Other) : OL(Other), VBC(Other) {} + IB &operator=(const IB &); +}; +IB &IB::operator=(const IB &Other) { + OL::operator=(Other); + VBC::operator=(Other); + return *this; +} + +// Class template. +template +struct Template { + Template() = default; + Template(const Template &Other) : Field(Other.Field) {} + Template &operator=(const Template &Other); + void foo(const T &t); + int Field; +}; +template +Template &Template::operator=(const Template &Other) { + Field = Other.Field; + return *this; +} +Template T1; + +// Dependent types. +template +struct DT1 { + DT1() = default; + DT1(const DT1 &Other) : Field(Other.Field) {} + DT1 &operator=(const DT1 &); + T Field; +}; +template +DT1 &DT1::operator=(const DT1 &Other) { + Field = Other.Field; + return *this; +} +DT1 Dt1; + +template +struct DT2 { + DT2() = default; + DT2(const DT2 &Other) : Field(Other.Field), Dependent(Other.Dependent) {} + DT2 &operator=(const DT2 &); + T Field; + typename T::TT Dependent; +}; +template +DT2 &DT2::operator=(const DT2 &Other) { + Field = Other.Field; + Dependent = Other.Dependent; + return *this; +} +struct T { + typedef int TT; +}; +DT2 Dt2; + +// Default arguments. +struct DA { + DA(int Int); + DA(const DA &Other = DA(0)) : Field1(Other.Field1), Field2(Other.Field2) {} + DA &operator=(const DA &); + int Field1; + char Field2; +}; +// Overloaded operator= cannot have a default argument. +DA &DA::operator=(const DA &Other) { + Field1 = Other.Field1; + Field2 = Other.Field2; + return *this; +} +// CHECK-MESSAGES: :[[@LINE-5]]:1: warning: use '= default' +// CHECK-FIXES: DA &DA::operator=(const DA &Other) = default; + +// Default initialization. +struct DI { + DI(const DI &Other) : Field1(Other.Field1), Field2(Other.Field2) {} + int Field1; + int Field2 = 0; + int Fiedl3; +}; + +// Statement inside body. +void foo(); +struct SIB { + SIB(const SIB &Other) : Field(Other.Field) { foo(); } + SIB &operator=(const SIB &); + int Field; +}; +SIB &SIB::operator=(const SIB &Other) { + Field = Other.Field; + foo(); + return *this; +} + +// Comment inside body. +struct CIB { + CIB(const CIB &Other) : Field(Other.Field) { /* Don't erase this */ + } + CIB &operator=(const CIB &); + int Field; +}; +CIB &CIB::operator=(const CIB &Other) { + Field = Other.Field; + // FIXME: don't erase this comment. + return *this; +} +// CHECK-MESSAGES: :[[@LINE-5]]:1: warning: use '= default' +// CHECK-FIXES: CIB &CIB::operator=(const CIB &Other) = default; + +// Take non-const reference as argument. +struct NCRef { + NCRef(NCRef &Other) : Field1(Other.Field1), Field2(Other.Field2) {} + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' + // CHECK-FIXES: NCRef(NCRef &Other) = default; + NCRef &operator=(NCRef &); + int Field1, Field2; +}; +NCRef &NCRef::operator=(NCRef &Other) { + Field1 = Other.Field1; + Field2 = Other.Field2; + return *this; +} +// CHECK-MESSAGES: :[[@LINE-5]]:1: warning: use '= default' +// CHECK-FIXES: NCRef &NCRef::operator=(NCRef &Other) = default; + +// Already defaulted. +struct IAD { + IAD(const IAD &Other) = default; + IAD &operator=(const IAD &Other) = default; +}; + +struct OAD { + OAD(const OAD &Other); + OAD &operator=(const OAD &); +}; +OAD::OAD(const OAD &Other) = default; +OAD &OAD::operator=(const OAD &Other) = default; + +// Deleted. +struct ID { + ID(const ID &Other) = delete; + ID &operator=(const ID &Other) = delete; +}; + +// Non-reference parameter. +struct NRef { + NRef &operator=(NRef Other); + int Field1; +}; +NRef &NRef::operator=(NRef Other) { + Field1 = Other.Field1; + return *this; +} + +// RValue reference parameter. +struct RVR { + RVR(RVR &&Other) {} + RVR &operator=(RVR &&); +}; +RVR &RVR::operator=(RVR &&Other) { return *this; } + +// Similar function. +struct SF { + SF &foo(const SF &); + int Field1; +}; +SF &SF::foo(const SF &Other) { + Field1 = Other.Field1; + return *this; +} + +// No return. +struct NR { + NR &operator=(const NR &); +}; +NR &NR::operator=(const NR &Other) {} + +// Return misplaced. +struct RM { + RM &operator=(const RM &); + int Field; +}; +RM &RM::operator=(const RM &Other) { + return *this; + Field = Other.Field; +} + +// Wrong return value. +struct WRV { + WRV &operator=(WRV &); +}; +WRV &WRV::operator=(WRV &Other) { + return Other; +} + +// Wrong return type. +struct WRT : IL { + IL &operator=(const WRT &); +}; +IL &WRT::operator=(const WRT &Other) { + return *this; +} + Index: test/clang-tidy/modernize-use-default.cpp =================================================================== --- test/clang-tidy/modernize-use-default.cpp +++ test/clang-tidy/modernize-use-default.cpp @@ -135,3 +135,13 @@ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' // CHECK-FIXES: ~N() override = default; }; + +struct O { + O() { + // Don't erase comments inside the body. + } + ~O() { + // Don't erase comments inside the body. + } +}; +