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,307 @@ 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 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 Returns a matcher that matches member expressions where the base is +/// the variable declared as \p Var and the accessed member is the one declared +/// as \p Field. +internal::Matcher accessToFieldInVar(const FieldDecl *Field, + const ValueDecl *Var) { + return ignoringImpCasts( + memberExpr(hasObjectExpression(declRefExpr(to(varDecl(equalsNode(Var))))), + member(fieldDecl(equalsNode(Field))))); +} + +/// \brief Check that the given constructor has copy signature and that it +/// copy-initializes all its bases and members. +static bool isCopyConstructorAndCanBeDefaulted(ASTContext *Context, + const CXXConstructorDecl *Ctor) { + // An explicitly-defaulted constructor cannot have default arguments. + if (Ctor->getMinRequiredArguments() != 1) + return false; + + const auto *Record = Ctor->getParent(); + const auto *Param = Ctor->getParamDecl(0); + + // Base classes and members that have to be copied. + auto BasesToInit = getAllDirectBases(Record); + auto FieldsToInit = getAllNamedFields(Record); + + // Ensure that all the bases are copied. + for (const auto *Base : BasesToInit) { + // The initialization of a base class should be a call to a copy + // constructor of the base. + if (match( + cxxConstructorDecl(forEachConstructorInitializer(cxxCtorInitializer( + isBaseInitializer(), + withInitializer(cxxConstructExpr(allOf( + hasType(equalsNode(Base)), + hasDeclaration(cxxConstructorDecl(isCopyConstructor())), + argumentCountIs(1), + hasArgument( + 0, declRefExpr(to(varDecl(equalsNode(Param))))))))))), + *Ctor, *Context) + .empty()) + return false; + } + + // Ensure that all the members are copied. + for (const auto *Field : FieldsToInit) { + auto AccessToFieldInParam = accessToFieldInVar(Field, Param); + // The initialization is a CXXConstructExpr for class types. + if (match( + cxxConstructorDecl(forEachConstructorInitializer(cxxCtorInitializer( + isMemberInitializer(), forField(equalsNode(Field)), + withInitializer(anyOf( + AccessToFieldInParam, + cxxConstructExpr(allOf( + hasDeclaration(cxxConstructorDecl(isCopyConstructor())), + argumentCountIs(1), + hasArgument(0, AccessToFieldInParam)))))))), + *Ctor, *Context) + .empty()) + return false; + } + + // Ensure that we don't do anything else, like initializing an indirect base. + return Ctor->getNumCtorInitializers() == + BasesToInit.size() + FieldsToInit.size(); +} + +/// \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(ASTContext *Context, + const CXXMethodDecl *Operator) { + Operator->dumpColor(); + Operator->getReturnType()->dump(); + Operator->getCallResultType()->dump(); + //if (dyn_cast(Operator->getReturnType().getTypePtr())->hasExceptionSpec()) + //return false; + const auto *Record = Operator->getParent(); + const auto *Param = Operator->getParamDecl(0); + + // Base classes and members that have to be copied. + auto BasesToInit = getAllDirectBases(Record); + auto FieldsToInit = getAllNamedFields(Record); + + const auto *Compound = cast(Operator->getBody()); + + // The assignment operator definition has to end with the following return + // statement: + // return *this; + if (Compound->body_empty() || + match(returnStmt(has(unaryOperator(hasOperatorName("*"), + hasUnaryOperand(cxxThisExpr())))), + *Compound->body_back(), *Context) + .empty()) + return false; + + // Ensure that all the bases are copied. + for (const auto *Base : BasesToInit) { + // 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: + if (match( + compoundStmt(has(cxxMemberCallExpr(allOf( + // - The object is an implicit cast of 'this' to a pointer to + // a base class. + onImplicitObjectArgument( + implicitCastExpr(hasImplicitDestinationType( + pointsTo(type(equalsNode(Base)))), + hasSourceExpression(cxxThisExpr()))), + // - The called method is the operator=. + callee(cxxMethodDecl(isCopyAssignmentOperator())), + // - The argument is (an implicit cast to a Base of) the + // argument taken by "Operator". + argumentCountIs(1), + hasArgument(0, declRefExpr(to(varDecl(equalsNode(Param))))))))), + *Compound, *Context) + .empty()) + return false; + } + + // Ensure that all the members are copied. + for (const auto *Field : FieldsToInit) { + // The assignment of data members: + // Field = Other.Field; + // Is a BinaryOperator in non-class types, and a CXXOperatorCallExpr + // otherwise. + auto LHS = memberExpr(hasObjectExpression(cxxThisExpr()), + member(fieldDecl(equalsNode(Field)))); + auto RHS = accessToFieldInVar(Field, Param); + if (match( + compoundStmt(has(stmt(anyOf( + binaryOperator(hasOperatorName("="), hasLHS(LHS), hasRHS(RHS)), + cxxOperatorCallExpr(hasOverloadedOperatorName("="), + argumentCountIs(2), hasArgument(0, LHS), + hasArgument(1, RHS)))))), + *Compound, *Context) + .empty()) + return false; + } + + // Ensure that we don't do anything else. + return Compound->size() == BasesToInit.size() + FieldsToInit.size() + 1; +} + +/// \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(); +} void UseDefaultCheck::registerMatchers(MatchFinder *Finder) { if (getLangOpts().CPlusPlus) { + // Destructor. + Finder->addMatcher(cxxDestructorDecl(isDefinition()).bind(SpecialFunction), + this); + Finder->addMatcher( + cxxConstructorDecl( + isDefinition(), + anyOf( + // Default constructor. + allOf(unless(hasAnyConstructorInitializer(anything())), + parameterCountIs(0)), + // Copy constructor. + allOf(isCopyConstructor(), + // Discard constructors that can be used as a copy + // constructor because all the other arguments have + // default values. + parameterCountIs(1)))) + .bind(SpecialFunction), + this); + // Copy-assignment operator. Finder->addMatcher( - cxxConstructorDecl(isDefinition(), - unless(hasAnyConstructorInitializer(anything())), - parameterCountIs(0)) - .bind(CtorDtor), + cxxMethodDecl(isDefinition(), isCopyAssignmentOperator(), + // isCopyAssignmentOperator() allows the parameter to be + // passed by value, and in this case it cannot be + // defaulted. + hasParameter(0, hasType(lValueReferenceType()))) + .bind(SpecialFunction), this); - Finder->addMatcher(cxxDestructorDecl(isDefinition()).bind(CtorDtor), 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; - // 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()) + const auto *Body = dyn_cast(SpecialFunctionDecl->getBody()); + if (!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")) + // 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; + + 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,469 @@ +// 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; + +struct DA2 { + // Can be used as copy-constructor but cannot be explicitly defaulted. + DA2(const DA &Other, int Def = 0) {} +}; + +// 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; +} + +// Try-catch. +struct ITC { + ITC(const ITC &Other) + try : Field(Other.Field) { + } catch (...) { + } + ITC &operator=(const ITC &Other) try { + Field = Other.Field; + } catch (...) { + } + int Field; +}; + +struct OTC { + OTC(const OTC &); + OTC &operator=(const OTC &); + int Field; +}; +OTC::OTC(const OTC &Other) try : Field(Other.Field) { +} catch (...) { +} +OTC &OTC::operator=(const OTC &Other) try { + Field = Other.Field; +} catch (...) { +} + +// FIXME: the check is not able to detect exception specification. +// noexcept(true). +struct NET { + // This is the default. + //NET(const NET &Other) noexcept {} + NET &operator=(const NET &Other) noexcept; +}; +//NET &NET::operator=(const NET &Other) noexcept { return *this; } + +// noexcept(false). +struct NEF { + // This is the default. + //NEF(const NEF &Other) noexcept(false) {} + NEF &operator=(const NEF &Other) noexcept(false); +}; +//NEF &NEF::operator=(const NEF &Other) noexcept(false) { return *this; } Index: test/clang-tidy/modernize-use-default.cpp =================================================================== --- test/clang-tidy/modernize-use-default.cpp +++ test/clang-tidy/modernize-use-default.cpp @@ -1,137 +1,159 @@ // RUN: %check_clang_tidy %s modernize-use-default %t -- -- -std=c++11 -fno-delayed-template-parsing -class A { +// Out of line definition. +class OL { public: - A(); - ~A(); + OL(); + ~OL(); }; -A::A() {} +OL::OL() {} // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: use '= default' to define a trivial default constructor [modernize-use-default] -// CHECK-FIXES: A::A() = default; -A::~A() {} +// CHECK-FIXES: OL::OL() = default; +OL::~OL() {} // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: use '= default' to define a trivial destructor [modernize-use-default] -// CHECK-FIXES: A::~A() = default; +// CHECK-FIXES: OL::~OL() = default; // Inline definitions. -class B { +class IL { public: - B() {} + IL() {} // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' - // CHECK-FIXES: B() = default; - ~B() {} + // CHECK-FIXES: IL() = default; + ~IL() {} // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' - // CHECK-FIXES: ~B() = default; + // CHECK-FIXES: ~IL() = default; }; +// Non-empty body. void f(); - -class C { +class NE { public: - // Non-empty constructor body. - C() { f(); } - // Non-empty destructor body. - ~C() { f(); } + NE() { f(); } + ~NE() { f(); } }; -class D { +// Initializer or arguments. +class IA { public: // Constructor with initializer. - D() : Field(5) {} + IA() : Field(5) {} // Constructor with arguments. - D(int Arg1, int Arg2) {} + IA(int Arg1, int Arg2) {} int Field; }; // Private constructor/destructor. -class E { - E() {} +class Priv { + Priv() {} // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' - // CHECK-FIXES: E() = default; - ~E() {} + // CHECK-FIXES: Priv() = default; + ~Priv() {} // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' - // CHECK-FIXES: ~E() = default; + // CHECK-FIXES: ~Priv() = default; }; // struct. -struct F { - F() {} +struct ST { + ST() {} // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' - // CHECK-FIXES: F() = default; - ~F() {} + // CHECK-FIXES: ST() = default; + ~ST() {} // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' - // CHECK-FIXES: F() = default; + // CHECK-FIXES: ST() = default; }; // Deleted constructor/destructor. -class G { +class Del { public: - G() = delete; - ~G() = delete; + Del() = delete; + ~Del() = delete; }; // Do not remove other keywords. -class H { +class KW { public: - explicit H() {} + explicit KW() {} // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' - // CHECK-FIXES: explicit H() = default; - virtual ~H() {} + // CHECK-FIXES: explicit KW() = default; + virtual ~KW() {} // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' - // CHECK-FIXES: virtual ~H() = default; + // CHECK-FIXES: virtual ~KW() = default; }; // Nested class. -struct I { - struct II { - II() {} +struct N { + struct NN { + NN() {} // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use '= default' - // CHECK-FIXES: II() = default; - ~II() {} + // CHECK-FIXES: NN() = default; + ~NN() {} // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use '= default' - // CHECK-FIXES: ~II() = default; + // CHECK-FIXES: ~NN() = default; }; int Int; }; // Class template. template -class J { +class Temp { public: - J() {} + Temp() {} // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' - // CHECK-FIXES: J() = default; - ~J() {} + // CHECK-FIXES: Temp() = default; + ~Temp() {} // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' - // CHECK-FIXES: ~J() = default; + // CHECK-FIXES: ~Temp() = default; }; // Non user-provided constructor/destructor. -struct K { +struct Imp { int Int; }; void g() { - K *PtrK = new K(); - PtrK->~K(); - delete PtrK; + Imp *PtrImp = new Imp(); + PtrImp->~Imp(); + delete PtrImp; } // Already using default. -struct L { - L() = default; - ~L() = default; -}; -struct M { - M(); - ~M(); +struct IDef { + IDef() = default; + ~IDef() = default; +}; +struct ODef { + ODef(); + ~ODef(); }; -M::M() = default; -M::~M() = default; +ODef::ODef() = default; +ODef::~ODef() = default; // Delegating constructor and overriden destructor. -struct N : H { - N() : H() {} - ~N() override {} +struct DC : KW { + DC() : KW() {} + ~DC() override {} // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default' - // CHECK-FIXES: ~N() override = default; + // CHECK-FIXES: ~DC() override = default; +}; + +struct Comments { + Comments() { + // Don't erase comments inside the body. + } + ~Comments() { + // Don't erase comments inside the body. + } +}; + +// Try-catch. +struct ITC { + ITC() try {} catch(...) {} + ~ITC() try {} catch(...) {} +}; + +struct OTC { + OTC(); + ~OTC(); }; +OTC::OTC() try {} catch(...) {} +OTC::~OTC() try {} catch(...) {}