diff --git a/clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.h b/clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.h --- a/clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.h +++ b/clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.h @@ -10,6 +10,7 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERSIZEEMPTYCHECK_H #include "../ClangTidyCheck.h" +#include namespace clang::tidy::readability { @@ -31,9 +32,13 @@ } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; std::optional getCheckTraversalKind() const override { return TK_IgnoreUnlessSpelledInSource; } + +private: + std::vector ExcludedComparisonTypes; }; } // namespace clang::tidy::readability diff --git a/clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp b/clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp --- a/clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp @@ -8,6 +8,7 @@ #include "ContainerSizeEmptyCheck.h" #include "../utils/ASTUtils.h" #include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Lex/Lexer.h" @@ -17,6 +18,7 @@ namespace clang { namespace ast_matchers { + AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam, AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr, CXXConstructExpr), @@ -83,12 +85,15 @@ }); return Result; } + AST_MATCHER(CXXConstructExpr, isDefaultConstruction) { return Node.getConstructor()->isDefaultConstructor(); } + AST_MATCHER(QualType, isIntegralType) { return Node->isIntegralType(Finder->getASTContext()); } + } // namespace ast_matchers namespace tidy::readability { @@ -96,7 +101,14 @@ ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name, ClangTidyContext *Context) - : ClangTidyCheck(Name, Context) {} + : ClangTidyCheck(Name, Context), + ExcludedComparisonTypes(utils::options::parseStringList( + Options.get("ExcludedComparisonTypes", "::std::array"))) {} + +void ContainerSizeEmptyCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ExcludedComparisonTypes", + utils::options::serializeStringList(ExcludedComparisonTypes)); +} void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) { const auto ValidContainerRecord = cxxRecordDecl(isSameOrDerivedFrom( @@ -164,12 +176,26 @@ hasUnaryOperand( expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))), expr(hasType(ValidContainer)).bind("STLObject")); + + const auto ExcludedComparisonTypesMatcher = qualType(anyOf( + hasDeclaration( + cxxRecordDecl(matchers::matchesAnyListedName(ExcludedComparisonTypes)) + .bind("excluded")), + hasCanonicalType(hasDeclaration( + cxxRecordDecl(matchers::matchesAnyListedName(ExcludedComparisonTypes)) + .bind("excluded"))))); + const auto SameExcludedComparisonTypesMatcher = + qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode("excluded"))), + hasCanonicalType(hasDeclaration( + cxxRecordDecl(equalsBoundNode("excluded")))))); + Finder->addMatcher( - binaryOperation(hasAnyOperatorName("==", "!="), - hasOperands(WrongComparend, - STLArg), - unless(hasAncestor(cxxMethodDecl( - ofClass(equalsBoundNode("container")))))) + binaryOperation( + hasAnyOperatorName("==", "!="), hasOperands(WrongComparend, STLArg), + unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher)), + hasRHS(hasType(SameExcludedComparisonTypesMatcher)))), + unless(hasAncestor( + cxxMethodDecl(ofClass(equalsBoundNode("container")))))) .bind("BinCmp"), this); } diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -173,6 +173,11 @@ :doc:`readability-identifier-naming ` check. +- Fixed a false positive in :doc:`readability-container-size-empty + ` check when comparing + ``std::array`` objects to default constructed ones. The behavior for this and + other relevant classes can now be configured with a new option. + Removed checks ^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/container-size-empty.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/container-size-empty.rst --- a/clang-tools-extra/docs/clang-tidy/checks/readability/container-size-empty.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/readability/container-size-empty.rst @@ -24,3 +24,11 @@ bool empty() const; `size_type` can be any kind of integer type. + +.. option:: ExcludedComparisonTypes + + A semicolon-separated list of class names for which the check will ignore + comparisons of objects with default-constructed objects of the same type. + If a class is listed here, the check will not suggest using ``empty()`` + instead of such comparisons for objects of that class. + Default value is: `::std::array`. diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/container-size-empty.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/container-size-empty.cpp --- a/clang-tools-extra/test/clang-tidy/checkers/readability/container-size-empty.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/readability/container-size-empty.cpp @@ -1,4 +1,6 @@ -// RUN: %check_clang_tidy %s readability-container-size-empty %t -- -- -fno-delayed-template-parsing +// RUN: %check_clang_tidy %s readability-container-size-empty %t -- \ +// RUN: -config="{CheckOptions: [{key: readability-container-size-empty.ExcludedComparisonTypes , value: '::std::array;::IgnoredDummyType'}]}" \ +// RUN: -- -fno-delayed-template-parsing namespace std { template struct vector { @@ -123,12 +125,12 @@ ; // 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: :32:8: note: method 'set'::empty() defined here + // CHECK-MESSAGES: :34:8: note: method 'set'::empty() defined here if (intSet == std::set()) ; // 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'::empty() defined here + // CHECK-MESSAGES: :34:8: note: method 'set'::empty() defined here if (s_func() == "") ; // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used @@ -450,7 +452,7 @@ public: ConstructWithBoolField(const std::vector &C) : B(C.size()) {} // CHECK-MESSAGES: :[[@LINE-1]]:57: warning: the 'empty' method should be used -// CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here +// CHECK-MESSAGES: :11:8: note: method 'vector'::empty() defined here // CHECK-FIXES: {{^ }}ConstructWithBoolField(const std::vector &C) : B(!C.empty()) {} }; @@ -458,21 +460,21 @@ std::vector C; bool B = C.size(); // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: the 'empty' method should be used -// CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here +// CHECK-MESSAGES: :11:8: note: method 'vector'::empty() defined here // CHECK-FIXES: {{^ }}bool B = !C.empty(); }; int func(const std::vector &C) { return C.size() ? 0 : 1; // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: the 'empty' method should be used -// CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here +// CHECK-MESSAGES: :11:8: note: method 'vector'::empty() defined here // CHECK-FIXES: {{^ }}return !C.empty() ? 0 : 1; } constexpr Lazy L; static_assert(!L.size(), ""); // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: the 'empty' method should be used -// CHECK-MESSAGES: :101:18: note: method 'Lazy'::empty() defined here +// CHECK-MESSAGES: :103:18: note: method 'Lazy'::empty() defined here // CHECK-FIXES: {{^}}static_assert(L.empty(), ""); struct StructWithLazyNoexcept { @@ -487,7 +489,7 @@ if (v.size()) ; // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty] - // CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here + // CHECK-MESSAGES: :11:8: note: method 'vector'::empty() defined here // CHECK-FIXES: {{^ }}if (!v.empty()){{$}} if (v == std::vector()) ; @@ -496,24 +498,24 @@ // CHECK-FIXES-NEXT: ; CHECKSIZE(v); // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: the 'empty' method should be used - // CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here + // CHECK-MESSAGES: :11:8: note: method 'vector'::empty() defined here // CHECK-FIXES: CHECKSIZE(v); TemplatedContainer templated_container; if (templated_container.size()) ; // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used - // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here + // CHECK-MESSAGES: :46:8: note: method 'TemplatedContainer'::empty() defined here // CHECK-FIXES: {{^ }}if (!templated_container.empty()){{$}} if (templated_container != TemplatedContainer()) ; // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used - // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here + // CHECK-MESSAGES: :46:8: note: method 'TemplatedContainer'::empty() defined here // CHECK-FIXES: {{^ }}if (!templated_container.empty()){{$}} // CHECK-FIXES-NEXT: ; CHECKSIZE(templated_container); // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: the 'empty' method should be used - // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here + // CHECK-MESSAGES: :46:8: note: method 'TemplatedContainer'::empty() defined here // CHECK-FIXES: CHECKSIZE(templated_container); } @@ -529,7 +531,7 @@ if (v.size()) ; // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty] - // CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here + // CHECK-MESSAGES: :11:8: note: method 'vector'::empty() defined here // CHECK-FIXES: {{^ }}if (!v.empty()){{$}} if (v == std::vector()) @@ -540,22 +542,22 @@ if (v.size() == 0) ; // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty] - // CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here + // CHECK-MESSAGES: :11:8: note: method 'vector'::empty() defined here // CHECK-FIXES: {{^ }}if (v.empty()){{$}} if (v.size() != 0) ; // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty] - // CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here + // CHECK-MESSAGES: :11:8: note: method 'vector'::empty() defined here // CHECK-FIXES: {{^ }}if (!v.empty()){{$}} if (v.size() < 1) ; // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty] - // CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here + // CHECK-MESSAGES: :11:8: note: method 'vector'::empty() defined here // CHECK-FIXES: {{^ }}if (v.empty()){{$}} if (v.size() > 0) ; // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty] - // CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here + // CHECK-MESSAGES: :11:8: note: method 'vector'::empty() defined here // CHECK-FIXES: {{^ }}if (!v.empty()){{$}} if (v.size() == 1) ; @@ -569,91 +571,91 @@ if (static_cast(v.size())) ; // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty] - // CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here + // CHECK-MESSAGES: :11:8: note: method 'vector'::empty() defined here // CHECK-FIXES: {{^ }}if (static_cast(!v.empty())){{$}} if (v.size() && false) ; // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty] - // CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here + // CHECK-MESSAGES: :11:8: note: method 'vector'::empty() defined here // CHECK-FIXES: {{^ }}if (!v.empty() && false){{$}} if (!v.size()) ; // CHECK-MESSAGES: :[[@LINE-2]]:8: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty] - // CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here + // CHECK-MESSAGES: :11:8: note: method 'vector'::empty() defined here // CHECK-FIXES: {{^ }}if (v.empty()){{$}} TemplatedContainer templated_container; if (templated_container.size()) ; // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used - // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here + // CHECK-MESSAGES: :46:8: note: method 'TemplatedContainer'::empty() defined here // CHECK-FIXES: {{^ }}if (!templated_container.empty()){{$}} if (templated_container != TemplatedContainer()) ; // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used - // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here + // CHECK-MESSAGES: :46:8: note: method 'TemplatedContainer'::empty() defined here // CHECK-FIXES: {{^ }}if (!templated_container.empty()){{$}} // CHECK-FIXES-NEXT: ; while (templated_container.size()) ; // CHECK-MESSAGES: :[[@LINE-2]]:10: warning: the 'empty' method should be used - // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here + // CHECK-MESSAGES: :46:8: note: method 'TemplatedContainer'::empty() defined here // CHECK-FIXES: {{^ }}while (!templated_container.empty()){{$}} do { } while (templated_container.size()); // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: the 'empty' method should be used - // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here + // CHECK-MESSAGES: :46:8: note: method 'TemplatedContainer'::empty() defined here // CHECK-FIXES: {{^ }}while (!templated_container.empty()); for (; templated_container.size();) ; // CHECK-MESSAGES: :[[@LINE-2]]:10: warning: the 'empty' method should be used - // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here + // CHECK-MESSAGES: :46:8: note: method 'TemplatedContainer'::empty() defined here // CHECK-FIXES: {{^ }}for (; !templated_container.empty();){{$}} if (true && templated_container.size()) ; // CHECK-MESSAGES: :[[@LINE-2]]:15: warning: the 'empty' method should be used - // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here + // CHECK-MESSAGES: :46:8: note: method 'TemplatedContainer'::empty() defined here // CHECK-FIXES: {{^ }}if (true && !templated_container.empty()){{$}} if (true || templated_container.size()) ; // CHECK-MESSAGES: :[[@LINE-2]]:15: warning: the 'empty' method should be used - // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here + // CHECK-MESSAGES: :46:8: note: method 'TemplatedContainer'::empty() defined here // CHECK-FIXES: {{^ }}if (true || !templated_container.empty()){{$}} if (!templated_container.size()) ; // CHECK-MESSAGES: :[[@LINE-2]]:8: warning: the 'empty' method should be used - // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here + // CHECK-MESSAGES: :46:8: note: method 'TemplatedContainer'::empty() defined here // CHECK-FIXES: {{^ }}if (templated_container.empty()){{$}} bool b1 = templated_container.size(); // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: the 'empty' method should be used - // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here + // CHECK-MESSAGES: :46:8: note: method 'TemplatedContainer'::empty() defined here // CHECK-FIXES: {{^ }}bool b1 = !templated_container.empty(); bool b2(templated_container.size()); // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: the 'empty' method should be used - // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here + // CHECK-MESSAGES: :46:8: note: method 'TemplatedContainer'::empty() defined here // CHECK-FIXES: {{^ }}bool b2(!templated_container.empty()); auto b3 = static_cast(templated_container.size()); // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: the 'empty' method should be used - // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here + // CHECK-MESSAGES: :46:8: note: method 'TemplatedContainer'::empty() defined here // CHECK-FIXES: {{^ }}auto b3 = static_cast(!templated_container.empty()); auto b4 = (bool)templated_container.size(); // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: the 'empty' method should be used - // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here + // CHECK-MESSAGES: :46:8: note: method 'TemplatedContainer'::empty() defined here // CHECK-FIXES: {{^ }}auto b4 = (bool)!templated_container.empty(); auto b5 = bool(templated_container.size()); // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: the 'empty' method should be used - // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here + // CHECK-MESSAGES: :46:8: note: method 'TemplatedContainer'::empty() defined here // CHECK-FIXES: {{^ }}auto b5 = bool(!templated_container.empty()); takesBool(templated_container.size()); @@ -662,7 +664,7 @@ return templated_container.size(); // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: the 'empty' method should be used - // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here + // CHECK-MESSAGES: :46:8: note: method 'TemplatedContainer'::empty() defined here // CHECK-FIXES: {{^ }}return !templated_container.empty(); } @@ -708,14 +710,14 @@ bool call_through_unique_ptr(const std::unique_ptr> &ptr) { return ptr->size() > 0; // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: the 'empty' method should be used - // CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here + // CHECK-MESSAGES: :11:8: note: method 'vector'::empty() defined here // CHECK-FIXES: {{^ }}return !ptr->empty(); } bool call_through_unique_ptr_deref(const std::unique_ptr> &ptr) { return (*ptr).size() > 0; // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: the 'empty' method should be used - // CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here + // CHECK-MESSAGES: :11:8: note: method 'vector'::empty() defined here // CHECK-FIXES: {{^ }}return !(*ptr).empty(); } @@ -724,10 +726,60 @@ size_type size() const; bool empty() const; }; -void test() { + +void testTypedefSize() { TypedefSize ts; if (ts.size() == 0) ; // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used // CHECK-FIXES: {{^ }}if (ts.empty()){{$}} } + +namespace std { + +template struct array { + bool operator==(const array& other) const; + bool operator!=(const array& other) const; + unsigned long size() const { return N; } + bool empty() const { return N != 0U; } + + T data[N]; +}; + +} + +struct DummyType { + bool operator==(const DummyType&) const; + unsigned long size() const; + bool empty() const; +}; + +struct IgnoredDummyType { + bool operator==(const IgnoredDummyType&) const; + unsigned long size() const; + bool empty() const; +}; + +typedef std::array Array; + +bool testArraySize(const Array& value) { + return value.size() == 0U; +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty] +// CHECK-FIXES: {{^ }}return value.empty();{{$}} +// CHECK-MESSAGES: :744:8: note: method 'array'::empty() defined here +} + +bool testArrayCompareToEmpty(const Array& value) { + return value == std::array(); +} + +bool testDummyType(const DummyType& value) { + return value == DummyType(); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: the 'empty' method should be used to check for emptiness instead of comparing to an empty object [readability-container-size-empty] +// CHECK-FIXES: {{^ }}return value.empty();{{$}} +// CHECK-MESSAGES: :754:8: note: method 'DummyType'::empty() defined here +} + +bool testIgnoredDummyType(const IgnoredDummyType& value) { + return value == IgnoredDummyType(); +}