modernize-use-default supports copy constructor and copy-assignment operator.

Summary: the check will now warn when the user provided definitions of this functions is equivalent to the explicitly defaulted ones.

Reviewers: klimek

Subscribers: klimek, cfe-commits, alexfh

Differential Revision: http://reviews.llvm.org/D14145

llvm-svn: 251788
This commit is contained in:
Angel Garcia Gomez 2015-11-02 10:34:19 +00:00
parent f423c1200f
commit f2bc2f001a
3 changed files with 840 additions and 91 deletions

View File

@ -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,302 @@ namespace clang {
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<const FieldDecl *>
getAllNamedFields(const CXXRecordDecl *Record) {
std::set<const FieldDecl *> 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<const Type *> getAllDirectBases(const CXXRecordDecl *Record) {
std::set<const Type *> 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<Expr> 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) {
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<CompoundStmt>(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(),
unless(hasAnyConstructorInitializer(anything())),
parameterCountIs(0))
.bind(CtorDtor),
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(
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) {
// Both CXXConstructorDecl and CXXDestructorDecl inherit from CXXMethodDecl.
const auto *CtorDtorDecl = Result.Nodes.getNodeAs<CXXMethodDecl>(CtorDtor);
std::string SpecialFunctionName;
SourceLocation StartLoc, EndLoc;
// 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())
// Both CXXConstructorDecl and CXXDestructorDecl inherit from CXXMethodDecl.
const auto *SpecialFunctionDecl =
Result.Nodes.getNodeAs<CXXMethodDecl>(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<CompoundStmt>(CtorDtorDecl->getBody());
// This should never happen, since 'hasTrivialBody' checks that this is
// actually a CompoundStmt.
assert(Body && "Definition body is not a CompoundStmt");
const auto *Body = dyn_cast<CompoundStmt>(SpecialFunctionDecl->getBody());
if (!Body)
return;
diag(CtorDtorDecl->getLocStart(),
"use '= default' to define a trivial " +
std::string(dyn_cast<CXXConstructorDecl>(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<CXXConstructorDecl>(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<CXXDestructorDecl>(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

View File

@ -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 <class T>
struct Template {
Template() = default;
Template(const Template &Other) : Field(Other.Field) {}
Template &operator=(const Template &Other);
void foo(const T &t);
int Field;
};
template <class T>
Template<T> &Template<T>::operator=(const Template<T> &Other) {
Field = Other.Field;
return *this;
}
Template<int> T1;
// Dependent types.
template <class T>
struct DT1 {
DT1() = default;
DT1(const DT1 &Other) : Field(Other.Field) {}
DT1 &operator=(const DT1 &);
T Field;
};
template <class T>
DT1<T> &DT1<T>::operator=(const DT1<T> &Other) {
Field = Other.Field;
return *this;
}
DT1<int> Dt1;
template <class T>
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 <class T>
DT2<T> &DT2<T>::operator=(const DT2<T> &Other) {
Field = Other.Field;
Dependent = Other.Dependent;
return *this;
}
struct T {
typedef int TT;
};
DT2<T> 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; }

View File

@ -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 T>
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 IDef {
IDef() = default;
~IDef() = default;
};
struct M {
M();
~M();
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(...) {}