joshz updated this revision to Diff 95488.
joshz marked an inline comment as done.
joshz edited the summary of this revision.
joshz added a comment.
Resolved the bug, with a slightly modified version of Aaron's suggestion. (It
will suggest parens for anything that wasn't just a DeclRefExpr, which may be a
bit over-exuberant, but not as much as always suggesting them.)
Repository:
rL LLVM
https://reviews.llvm.org/D31542
Files:
clang-tidy/readability/ContainerSizeEmptyCheck.cpp
test/clang-tidy/readability-container-size-empty.cpp
Index: test/clang-tidy/readability-container-size-empty.cpp
===================================================================
--- test/clang-tidy/readability-container-size-empty.cpp
+++ test/clang-tidy/readability-container-size-empty.cpp
@@ -3,6 +3,8 @@
namespace std {
template <typename T> struct vector {
vector();
+ bool operator==(const vector<T>& other) const;
+ bool operator!=(const vector<T>& other) const;
unsigned long size() const;
bool empty() const;
};
@@ -9,6 +11,11 @@
template <typename T> struct basic_string {
basic_string();
+ bool operator==(const basic_string<T>& other) const;
+ bool operator!=(const basic_string<T>& other) const;
+ bool operator==(const char *) const;
+ bool operator!=(const char *) const;
+ basic_string<T> operator+(const basic_string<T>& other) const;
unsigned long size() const;
bool empty() const;
};
@@ -19,6 +26,8 @@
inline namespace __v2 {
template <typename T> struct set {
set();
+ bool operator==(const set<T>& other) const;
+ bool operator!=(const set<T>& other) const;
unsigned long size() const;
bool empty() const;
};
@@ -29,6 +38,8 @@
template <typename T>
class TemplatedContainer {
public:
+ bool operator==(const TemplatedContainer<T>& other) const;
+ bool operator!=(const TemplatedContainer<T>& other) const;
int size() const;
bool empty() const;
};
@@ -36,6 +47,8 @@
template <typename T>
class PrivateEmpty {
public:
+ bool operator==(const PrivateEmpty<T>& other) const;
+ bool operator!=(const PrivateEmpty<T>& other) const;
int size() const;
private:
bool empty() const;
@@ -54,6 +67,7 @@
class Container {
public:
+ bool operator==(const Container& other) const;
int size() const;
bool empty() const;
};
@@ -75,9 +89,17 @@
bool Container3::empty() const { return this->size() == 0; }
+class Container4 {
+public:
+ bool operator==(const Container4& rhs) const;
+ int size() const;
+ bool empty() const { return *this == Container4(); }
+};
+
int main() {
std::set<int> intSet;
std::string str;
+ std::string str2;
std::wstring wstr;
str.size() + 0;
str.size() - 0;
@@ -87,24 +109,53 @@
;
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty]
// CHECK-FIXES: {{^ }}if (intSet.empty()){{$}}
- // CHECK-MESSAGES: :23:8: note: method 'set<int>'::empty() defined here
+ // CHECK-MESSAGES: :32:8: note: method 'set<int>'::empty() defined here
+ if (intSet == std::set<int>())
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used to check for emptiness
+ // CHECK-FIXES: {{^ }}if (intSet.empty()){{$}}
+ // CHECK-MESSAGES: :32:8: note: method 'set<int>'::empty() defined here
if (str.size() == 0)
;
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
// CHECK-FIXES: {{^ }}if (str.empty()){{$}}
+ if ((str + str2).size() == 0)
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+ // CHECK-FIXES: {{^ }}if ((str + str2).empty()){{$}}
+ if (str == "")
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+ // CHECK-FIXES: {{^ }}if (str.empty()){{$}}
+ if (str + str2 == "")
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+ // CHECK-FIXES: {{^ }}if ((str + str2).empty()){{$}}
if (wstr.size() == 0)
;
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
// CHECK-FIXES: {{^ }}if (wstr.empty()){{$}}
+ if (wstr == "")
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+ // CHECK-FIXES: {{^ }}if (wstr.empty()){{$}}
std::vector<int> vect;
if (vect.size() == 0)
;
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
// CHECK-FIXES: {{^ }}if (vect.empty()){{$}}
+ if (vect == std::vector<int>())
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+ // CHECK-FIXES: {{^ }}if (vect.empty()){{$}}
if (vect.size() != 0)
;
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
// CHECK-FIXES: {{^ }}if (!vect.empty()){{$}}
+ if (vect != std::vector<int>())
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+ // CHECK-FIXES: {{^ }}if (!vect.empty()){{$}}
if (0 == vect.size())
;
// CHECK-MESSAGES: :[[@LINE-2]]:12: warning: the 'empty' method should be used
@@ -113,6 +164,14 @@
;
// CHECK-MESSAGES: :[[@LINE-2]]:12: warning: the 'empty' method should be used
// CHECK-FIXES: {{^ }}if (!vect.empty()){{$}}
+ if (std::vector<int>() == vect)
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+ // CHECK-FIXES: {{^ }}if (vect.empty()){{$}}
+ if (std::vector<int>() != vect)
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+ // CHECK-FIXES: {{^ }}if (!vect.empty()){{$}}
if (vect.size() > 0)
;
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
@@ -172,6 +231,14 @@
;
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
// CHECK-FIXES: {{^ }}if ((*vect3).empty()){{$}}
+ if ((*vect3) == std::vector<int>())
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+ // CHECK-FIXES: {{^ }}if ((vect3)->empty()){{$}}
+ if (*vect3 == std::vector<int>())
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+ // CHECK-FIXES: {{^ }}if ((vect3)->empty()){{$}}
delete vect3;
@@ -180,6 +247,10 @@
;
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
// CHECK-FIXES: {{^ }}if (vect4.empty()){{$}}
+ if (vect4 == std::vector<int>())
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+ // CHECK-FIXES: {{^ }}if (vect4.empty()){{$}}
TemplatedContainer<void> templated_container;
if (templated_container.size() == 0)
@@ -186,18 +257,34 @@
;
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
// CHECK-FIXES: {{^ }}if (templated_container.empty()){{$}}
+ if (templated_container == TemplatedContainer<void>())
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+ // CHECK-FIXES: {{^ }}if (templated_container.empty()){{$}}
if (templated_container.size() != 0)
;
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
// CHECK-FIXES: {{^ }}if (!templated_container.empty()){{$}}
+ if (templated_container != TemplatedContainer<void>())
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+ // CHECK-FIXES: {{^ }}if (!templated_container.empty()){{$}}
if (0 == templated_container.size())
;
// CHECK-MESSAGES: :[[@LINE-2]]:12: warning: the 'empty' method should be used
// CHECK-FIXES: {{^ }}if (templated_container.empty()){{$}}
+ if (TemplatedContainer<void>() == templated_container)
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+ // CHECK-FIXES: {{^ }}if (templated_container.empty()){{$}}
if (0 != templated_container.size())
;
// CHECK-MESSAGES: :[[@LINE-2]]:12: warning: the 'empty' method should be used
// CHECK-FIXES: {{^ }}if (!templated_container.empty()){{$}}
+ if (TemplatedContainer<void>() != templated_container)
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+ // CHECK-FIXES: {{^ }}if (!templated_container.empty()){{$}}
if (templated_container.size() > 0)
;
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
@@ -246,12 +333,20 @@
PrivateEmpty<void> private_empty;
if (private_empty.size() == 0)
;
+ if (private_empty == PrivateEmpty<void>())
+ ;
if (private_empty.size() != 0)
;
+ if (private_empty != PrivateEmpty<void>())
+ ;
if (0 == private_empty.size())
;
+ if (PrivateEmpty<void>() == private_empty)
+ ;
if (0 != private_empty.size())
;
+ if (PrivateEmpty<void>() != private_empty)
+ ;
if (private_empty.size() > 0)
;
if (0 < private_empty.size())
@@ -290,6 +385,10 @@
;
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
// CHECK-FIXES: {{^ }}if (derived.empty()){{$}}
+ if (derived == Derived())
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+ // CHECK-FIXES: {{^ }}if (derived.empty()){{$}}
}
#define CHECKSIZE(x) if (x.size())
@@ -301,6 +400,10 @@
;
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty]
// CHECK-FIXES: {{^ }}if (!v.empty()){{$}}
+ if (v == std::vector<T>())
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used to check for emptiness instead of comparing to an empty object. [readability-container-size-empty]
+ // CHECK-FIXES: {{^ }}if (v.empty()){{$}}
// CHECK-FIXES-NEXT: ;
CHECKSIZE(v);
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: the 'empty' method should be used
@@ -311,6 +414,10 @@
;
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
// CHECK-FIXES: {{^ }}if (!templated_container.empty()){{$}}
+ if (templated_container != TemplatedContainer<T>())
+ ;
+ // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+ // CHECK-FIXES: {{^ }}if (!templated_container.empty()){{$}}
// CHECK-FIXES-NEXT: ;
CHECKSIZE(templated_container);
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: the 'empty' method should be used
Index: clang-tidy/readability/ContainerSizeEmptyCheck.cpp
===================================================================
--- clang-tidy/readability/ContainerSizeEmptyCheck.cpp
+++ clang-tidy/readability/ContainerSizeEmptyCheck.cpp
@@ -62,23 +62,73 @@
ofClass(equalsBoundNode("container"))))))
.bind("SizeCallExpr"),
this);
+
+ // Empty constructor matcher.
+ const auto DefaultConstructor = cxxConstructExpr(
+ hasDeclaration(cxxConstructorDecl(isDefaultConstructor())));
+ // Comparison to empty string or empty constructor.
+ const auto WrongComparend = anyOf(
+ ignoringImpCasts(stringLiteral(hasSize(0))),
+ ignoringImpCasts(cxxBindTemporaryExpr(has(DefaultConstructor))),
+ ignoringImplicit(DefaultConstructor),
+ cxxConstructExpr(
+ hasDeclaration(cxxConstructorDecl(isCopyConstructor())),
+ has(expr(ignoringImpCasts(DefaultConstructor)))),
+ cxxConstructExpr(
+ hasDeclaration(cxxConstructorDecl(isMoveConstructor())),
+ has(expr(ignoringImpCasts(DefaultConstructor)))));
+ // Match the object being compared.
+ const auto STLArg =
+ anyOf(unaryOperator(
+ hasOperatorName("*"),
+ hasUnaryOperand(
+ expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))),
+ expr(hasType(ValidContainer)).bind("STLObject"));
+ const auto STLOuter = anyOf(allOf(ignoringImplicit(declRefExpr().bind("Decl")),
+ STLArg),
+ STLArg);
+ Finder->addMatcher(
+ cxxOperatorCallExpr(
+ anyOf(hasOverloadedOperatorName("=="),
+ hasOverloadedOperatorName("!=")),
+ anyOf(allOf(hasArgument(0, WrongComparend), hasArgument(1, STLOuter)),
+ allOf(hasArgument(0, STLOuter), hasArgument(1, WrongComparend))),
+ unless(hasAncestor(
+ cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
+ .bind("BinCmp"),
+ this);
}
-
void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
const auto *MemberCall =
Result.Nodes.getNodeAs<CXXMemberCallExpr>("SizeCallExpr");
+ const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");
const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
- const auto *E = MemberCall->getImplicitObjectArgument();
+ const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");
+ const auto *Decl = Result.Nodes.getNodeAs<DeclRefExpr>("Decl");
+ const auto *E =
+ MemberCall
+ ? MemberCall->getImplicitObjectArgument()
+ : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));
FixItHint Hint;
std::string ReplacementText =
Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()),
*Result.SourceManager, getLangOpts());
+ if (BinCmp && !Decl) {
+ // Not just a DeclRefExpr, so parenthesize to be on the safe side.
+ ReplacementText = "(" + ReplacementText + ")";
+ }
if (E->getType()->isPointerType())
ReplacementText += "->empty()";
else
ReplacementText += ".empty()";
- if (BinaryOp) { // Determine the correct transformation.
+ if (BinCmp) {
+ if (BinCmp->getOperator() == OO_ExclaimEqual) {
+ ReplacementText = "!" + ReplacementText;
+ }
+ Hint =
+ FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
+ } else if (BinaryOp) { // Determine the correct transformation.
bool Negation = false;
const bool ContainerIsLHS =
!llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
@@ -148,9 +198,17 @@
"!" + ReplacementText);
}
- diag(MemberCall->getLocStart(), "the 'empty' method should be used to check "
- "for emptiness instead of 'size'")
- << Hint;
+ if (MemberCall) {
+ diag(MemberCall->getLocStart(),
+ "the 'empty' method should be used to check "
+ "for emptiness instead of 'size'")
+ << Hint;
+ } else {
+ diag(BinCmp->getLocStart(),
+ "the 'empty' method should be used to check "
+ "for emptiness instead of comparing to an empty object.")
+ << Hint;
+ }
const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container");
const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty");
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits