[clang-tidy] added AllowedTypes option to readability-qualified-auto check (#136571)

Added `AllowedTypes` option to `readability-qualified-auto` check

Fixes https://github.com/llvm/llvm-project/issues/63461.
This commit is contained in:
Baranov Victor 2025-05-26 13:25:35 +03:00 committed by GitHub
parent 5e0c390160
commit ac0a880f4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 188 additions and 9 deletions

View File

@ -8,6 +8,8 @@
#include "QualifiedAutoCheck.h"
#include "../utils/LexerUtils.h"
#include "../utils/Matchers.h"
#include "../utils/OptionsUtils.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "llvm/ADT/SmallVector.h"
#include <optional>
@ -100,8 +102,17 @@ bool isAutoPointerConst(QualType QType) {
} // namespace
QualifiedAutoCheck::QualifiedAutoCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
AddConstToQualified(Options.get("AddConstToQualified", true)),
AllowedTypes(
utils::options::parseStringList(Options.get("AllowedTypes", ""))) {}
void QualifiedAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "AddConstToQualified", AddConstToQualified);
Options.store(Opts, "AllowedTypes",
utils::options::serializeStringList(AllowedTypes));
}
void QualifiedAutoCheck::registerMatchers(MatchFinder *Finder) {
@ -124,20 +135,26 @@ void QualifiedAutoCheck::registerMatchers(MatchFinder *Finder) {
auto IsBoundToType = refersToType(equalsBoundNode("type"));
auto UnlessFunctionType = unless(hasUnqualifiedDesugaredType(functionType()));
auto IsAutoDeducedToPointer = [](const auto &...InnerMatchers) {
auto IsAutoDeducedToPointer = [](const std::vector<StringRef> &AllowedTypes,
const auto &...InnerMatchers) {
return autoType(hasDeducedType(
hasUnqualifiedDesugaredType(pointerType(pointee(InnerMatchers...)))));
hasUnqualifiedDesugaredType(pointerType(pointee(InnerMatchers...))),
unless(hasUnqualifiedType(
matchers::matchesAnyListedTypeName(AllowedTypes, false))),
unless(pointerType(pointee(hasUnqualifiedType(
matchers::matchesAnyListedTypeName(AllowedTypes, false)))))));
};
Finder->addMatcher(
ExplicitSingleVarDecl(hasType(IsAutoDeducedToPointer(UnlessFunctionType)),
"auto"),
ExplicitSingleVarDecl(
hasType(IsAutoDeducedToPointer(AllowedTypes, UnlessFunctionType)),
"auto"),
this);
Finder->addMatcher(
ExplicitSingleVarDeclInTemplate(
allOf(hasType(IsAutoDeducedToPointer(
hasUnqualifiedType(qualType().bind("type")),
AllowedTypes, hasUnqualifiedType(qualType().bind("type")),
UnlessFunctionType)),
anyOf(hasAncestor(
functionDecl(hasAnyTemplateArgument(IsBoundToType))),

View File

@ -21,9 +21,7 @@ namespace clang::tidy::readability {
/// http://clang.llvm.org/extra/clang-tidy/checks/readability/qualified-auto.html
class QualifiedAutoCheck : public ClangTidyCheck {
public:
QualifiedAutoCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
AddConstToQualified(Options.get("AddConstToQualified", true)) {}
QualifiedAutoCheck(StringRef Name, ClangTidyContext *Context);
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus11;
}
@ -33,6 +31,7 @@ public:
private:
const bool AddConstToQualified;
const std::vector<StringRef> AllowedTypes;
};
} // namespace clang::tidy::readability

View File

@ -239,6 +239,10 @@ Changes in existing checks
tolerating fix-it breaking compilation when functions is used as pointers
to avoid matching usage of functions within the current compilation unit.
- Improved :doc:`readability-qualified-auto
<clang-tidy/checks/readability/qualified-auto>` check by adding the option
`AllowedTypes`, that excludes specified types from adding qualifiers.
Removed checks
^^^^^^^^^^^^^^

View File

@ -82,3 +82,17 @@ Otherwise it will be transformed into:
const auto &Foo3 = cast<const int &>(Bar3);
Note in the LLVM alias, the default value is `false`.
.. option:: AllowedTypes
A semicolon-separated list of names of types to ignore when ``auto`` is
deduced to that type or a pointer to that type. Note that this distinguishes
type aliases from the original type, so specifying e.g. ``my_int`` will not
suppress reports about ``int`` even if it is defined as a ``typedef`` alias
for ``int``. Regular expressions are accepted, e.g. ``[Rr]ef(erence)?$``
matches every type with suffix ``Ref``, ``ref``, ``Reference`` and
``reference``. If a name in the list contains the sequence `::` it is matched
against the qualified type name (i.e. ``namespace::Type``), otherwise it is
matched against only the type name (i.e. ``Type``). E.g. to suppress reports
for ``std::array`` iterators use `std::array<.*>::(const_)?iterator` string.
The default is an empty string.

View File

@ -1,4 +1,7 @@
// RUN: %check_clang_tidy %s readability-qualified-auto %t
// RUN: %check_clang_tidy %s readability-qualified-auto %t \
// RUN: -config='{CheckOptions: { \
// RUN: readability-qualified-auto.AllowedTypes: "[iI]terator$;my::ns::Ignored1;std::array<.*>::Ignored2;MyIgnoredPtr" \
// RUN: }}'
namespace typedefs {
typedef int *MyPtr;
@ -238,3 +241,145 @@ void baz() {
auto &MyFunctionRef2 = *getPtrFunction();
}
namespace std {
template<typename T, int N>
struct array {
typedef T value_type;
typedef value_type* iterator;
typedef value_type* Iterator;
using using_iterator = T*;
typedef const value_type* const_iterator;
typedef const value_type* constIterator;
struct Ignored2 {};
using NotIgnored2 = Ignored2;
iterator begin() { return nullptr; }
const_iterator begin() const { return nullptr; }
iterator end() { return nullptr; }
const_iterator end() const { return nullptr; }
};
struct Iterator {};
struct Ignored2 {}; // should not be ignored
} // namespace std
typedef std::Iterator iterator;
namespace my {
namespace ns {
struct Ignored1 {};
using NotIgnored1 = Ignored1;
typedef Ignored1 NotIgnored2;
} // namespace ns
struct Ignored1 {}; // should not be ignored
} // namespace my
typedef int *MyIgnoredPtr;
MyIgnoredPtr getIgnoredPtr();
void ignored_types() {
auto ignored_ptr = getIgnoredPtr();
// CHECK-MESSAGES-NOT: warning: 'auto ignored_ptr' can be declared as 'auto *ignored_ptr'
// CHECK-FIXES-NOT: auto *ignored_ptr = getIgnoredPtr();
std::array<int, 4> arr;
std::array<int, 4> carr;
auto it1 = arr.begin();
// CHECK-MESSAGES-NOT: warning: 'auto it' can be declared as 'auto *it'
// CHECK-FIXES-NOT: auto *it = vec.it_begin();
auto it2 = carr.begin();
// CHECK-MESSAGES-NOT: warning: 'auto it2' can be declared as 'auto *it2'
// CHECK-FIXES-NOT: auto *it2 = carr.begin();
auto it3 = std::array<int, 4>::iterator{};
// CHECK-MESSAGES-NOT: warning: 'auto it3' can be declared as 'auto *it3'
// CHECK-FIXES-NOT: auto *it3 = std::array<int, 4>::iterator{};
auto it4 = std::array<int, 4>::Iterator{};
// CHECK-MESSAGES-NOT: warning: 'auto it4' can be declared as 'auto *it4'
// CHECK-FIXES-NOT: auto *it4 = std::array<int, 4>::Iterator{};
auto it5 = std::array<int, 4>::using_iterator{};
// CHECK-MESSAGES-NOT: warning: 'auto it5' can be declared as 'auto *it5'
// CHECK-FIXES-NOT: auto *it5 = std::array<int, 4>::using_iterator{};
auto it6 = std::array<int, 4>::const_iterator{};
// CHECK-MESSAGES-NOT: warning: 'auto it6' can be declared as 'auto *it6'
// CHECK-FIXES-NOT: auto *it6 = std::array<int, 4>::const_iterator{};
auto it7 = std::array<int, 4>::constIterator{};
// CHECK-MESSAGES-NOT: warning: 'auto it7' can be declared as 'auto *it7'
// CHECK-FIXES-NOT: auto *it7 = std::array<int, 4>::constIterator{};
auto it8 = new std::Iterator();
// CHECK-MESSAGES-NOT: warning: 'auto it8' can be declared as 'auto *it8'
// CHECK-FIXES-NOT: auto *it8 = new std::Iterator();
auto it9 = new iterator();
// CHECK-MESSAGES-NOT: warning: 'auto it9' can be declared as 'auto *it9'
// CHECK-FIXES-NOT: auto *it9 = new iterator();
auto arr_ignored2 = new std::array<int, 4>::Ignored2();
// CHECK-MESSAGES-NOT: warning: 'auto arr_ignored2' can be declared as 'auto *arr_ignored2'
// CHECK-FIXES-NOT: auto *arr_ignored2 = new std::array<int, 4>::Ignored2();
auto arr_not_ignored2 = new std::array<int, 4>::NotIgnored2();
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'auto arr_not_ignored2' can be declared as 'auto *arr_not_ignored2'
// CHECK-FIXES: auto *arr_not_ignored2 = new std::array<int, 4>::NotIgnored2();
auto not_ignored2 = new std::Ignored2();
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'auto not_ignored2' can be declared as 'auto *not_ignored2'
// CHECK-FIXES: auto *not_ignored2 = new std::Ignored2();
auto ignored1 = new my::ns::Ignored1();
// CHECK-MESSAGES-NOT: warning: 'auto ignored1' can be declared as 'auto *ignored1'
// CHECK-FIXES-NOT: auto *ignored1 = new my::ns::Ignored1();
auto not_ignored1 = new my::ns::NotIgnored1();
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'auto not_ignored1' can be declared as 'auto *not_ignored1'
// CHECK-FIXES: auto *not_ignored1 = new my::ns::NotIgnored1();
auto not2_ignored1 = new my::ns::NotIgnored2();
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'auto not2_ignored1' can be declared as 'auto *not2_ignored1'
// CHECK-FIXES: auto *not2_ignored1 = new my::ns::NotIgnored2();
auto not3_ignored1 = new my::Ignored1();
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'auto not3_ignored1' can be declared as 'auto *not3_ignored1'
// CHECK-FIXES: auto *not3_ignored1 = new my::Ignored1();
}
template <typename T>
void ignored_types_template(std::array<T, 4> arr, const std::array<T, 4>& carr) {
auto it1 = arr.begin();
// CHECK-MESSAGES-NOT: warning: 'auto it' can be declared as 'auto *it'
// CHECK-FIXES-NOT: auto *it = arr.it_begin();
auto it2 = carr.begin();
// CHECK-MESSAGES-NOT: warning: 'auto it2' can be declared as 'auto *it2'
// CHECK-FIXES-NOT: auto *it2 = carr.begin();
for (auto Data : arr) {
// CHECK-MESSAGES-NOT: warning: 'auto Data' can be declared as 'auto *Data'
// CHECK-FIXES-NOT: {{^}} for (auto *Data : MClassTemplate) {
change(*Data);
}
for (auto Data : carr) {
// CHECK-MESSAGES-NOT: warning: 'auto Data' can be declared as 'const auto *Data'
// CHECK-FIXES-NOT: {{^}} for (const auto *Data : MClassTemplate) {
change(*Data);
}
}