diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -25,6 +25,7 @@ ReplaceRandomShuffleCheck.cpp ReturnBracedInitListCheck.cpp ShrinkToFitCheck.cpp + TypeTraitsCheck.cpp UnaryStaticAssertCheck.cpp UseAutoCheck.cpp UseBoolLiteralsCheck.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -26,6 +26,7 @@ #include "ReplaceRandomShuffleCheck.h" #include "ReturnBracedInitListCheck.h" #include "ShrinkToFitCheck.h" +#include "TypeTraitsCheck.h" #include "UnaryStaticAssertCheck.h" #include "UseAutoCheck.h" #include "UseBoolLiteralsCheck.h" @@ -77,6 +78,7 @@ CheckFactories.registerCheck( "modernize-return-braced-init-list"); CheckFactories.registerCheck("modernize-shrink-to-fit"); + CheckFactories.registerCheck("modernize-type-traits"); CheckFactories.registerCheck( "modernize-unary-static-assert"); CheckFactories.registerCheck("modernize-use-auto"); diff --git a/clang-tools-extra/clang-tidy/modernize/TypeTraitsCheck.h b/clang-tools-extra/clang-tidy/modernize/TypeTraitsCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/TypeTraitsCheck.h @@ -0,0 +1,38 @@ +//===--- TypeTraitsCheck.h - clang-tidy -------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_TYPETRAITSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_TYPETRAITSCHECK_H + +#include "../ClangTidyCheck.h" +#include "clang/AST/ASTTypeTraits.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Converts standard library type traits of the form `traits<...>::type` and +/// `traits<...>::value` into `traits_t<...>` and `traits_v<...>` respectively. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/type-traits.html +class TypeTraitsCheck : public ClangTidyCheck { +public: + TypeTraitsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override; + llvm::Optional getCheckTraversalKind() const override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_TYPETRAITSCHECK_H diff --git a/clang-tools-extra/clang-tidy/modernize/TypeTraitsCheck.cpp b/clang-tools-extra/clang-tidy/modernize/TypeTraitsCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/TypeTraitsCheck.cpp @@ -0,0 +1,308 @@ +//===--- TypeTraitsCheck.cpp - clang-tidy ---------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "TypeTraitsCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +static const llvm::StringSet<> ValueTraits = { + "alignment_of", + "conjunction", + "disjunction", + "extent", + "has_unique_object_representations", + "has_virtual_destructor", + "is_abstract", + "is_aggregate", + "is_arithmetic", + "is_array", + "is_assignable", + "is_base_of", + "is_bounded_array", + "is_class", + "is_compound", + "is_const", + "is_constructible", + "is_convertible", + "is_copy_assignable", + "is_copy_constructible", + "is_default_constructible", + "is_destructible", + "is_empty", + "is_enum", + "is_final", + "is_floating_point", + "is_function", + "is_fundamental", + "is_integral", + "is_invocable", + "is_invocable_r", + "is_layout_compatible", + "is_lvalue_reference", + "is_member_function_pointer", + "is_member_object_pointer", + "is_member_pointer", + "is_move_assignable", + "is_move_constructible", + "is_nothrow_assignable", + "is_nothrow_constructible", + "is_nothrow_convertible", + "is_nothrow_copy_assignable", + "is_nothrow_copy_constructible", + "is_nothrow_default_constructible", + "is_nothrow_destructible", + "is_nothrow_invocable", + "is_nothrow_invocable_r", + "is_nothrow_move_assignable", + "is_nothrow_move_constructible", + "is_nothrow_swappable", + "is_nothrow_swappable_with", + "is_null_pointer", + "is_object", + "is_pointer", + "is_pointer_interconvertible_base_of", + "is_polymorphic", + "is_reference", + "is_rvalue_reference", + "is_same", + "is_scalar", + "is_scoped_enum", + "is_signed", + "is_standard_layout", + "is_swappable", + "is_swappable_with", + "is_trivial", + "is_trivially_assignable", + "is_trivially_constructible", + "is_trivially_copy_assignable", + "is_trivially_copy_constructible", + "is_trivially_copyable", + "is_trivially_default_constructible", + "is_trivially_destructible", + "is_trivially_move_assignable", + "is_trivially_move_constructible", + "is_unbounded_array", + "is_union", + "is_unsigned", + "is_void", + "is_volatile", + "negation", + "rank", + "reference_constructs_from_temporary", + "reference_converts_from_temporary", +}; + +static const llvm::StringSet<> TypeTraits = { + "remove_cv", + "remove_const", + "remove_volatile", + "add_cv", + "add_const", + "add_volatile", + "remove_reference", + "add_lvalue_reference", + "add_rvalue_reference", + "remove_pointer", + "add_pointer", + "make_signed", + "make_unsigned", + "remove_extent", + "remove_all_extents", + "aligned_storage", + "aligned_union", + "decay", + "remove_cvref", + "enable_if", + "conditional", + "common_type", + "common_reference", + "underlying_type", + "result_of", + "invoke_result", + "type_identity", +}; + +namespace { +const internal::VariadicDynCastAllOfMatcher + dependentScopeDeclRefExpr; // NOLINT(readability-identifier-naming) +const internal::VariadicDynCastAllOfMatcher + dependentNameTypeLoc; // NOLINT(readability-identifier-naming) + +namespace internal { +DeclarationName getName(const DependentScopeDeclRefExpr &D) { + return D.getDeclName(); +} +DeclarationName getName(const DeclRefExpr &D) { + return D.getDecl()->getDeclName(); +} + +bool refersToType(const ElaboratedTypeLoc &ETL) { + if (const auto *TFT = + ETL.getNamedTypeLoc().getTypePtr()->getAs()) { + const TypedefNameDecl *Decl = TFT->getDecl(); + return Decl->getDeclName().isIdentifier() && Decl->getName() == "type"; + } + return false; +} + +bool refersToType(const DependentNameTypeLoc &DTL) { + return DTL.getTypePtr()->getIdentifier()->getName() == "type"; +} +} // namespace internal + +AST_POLYMORPHIC_MATCHER(isValue, AST_POLYMORPHIC_SUPPORTED_TYPES( + DeclRefExpr, DependentScopeDeclRefExpr)) { + DeclarationName DeclNode = internal::getName(Node); + return DeclNode.isIdentifier() && + DeclNode.getAsIdentifierInfo()->getName() == "value"; +} + +AST_POLYMORPHIC_MATCHER(isType, + AST_POLYMORPHIC_SUPPORTED_TYPES(ElaboratedTypeLoc, + DependentNameTypeLoc)) { + return internal::refersToType(Node); +} +} // namespace + +static constexpr char Bind[] = ""; + +void TypeTraitsCheck::registerMatchers(MatchFinder *Finder) { + // Only register matchers for trait<...>::value in c++17 mode. + if (getLangOpts().CPlusPlus17) { + Finder->addMatcher(mapAnyOf(declRefExpr, dependentScopeDeclRefExpr) + .with(isValue()) + .bind(Bind), + this); + } + Finder->addMatcher(mapAnyOf(elaboratedTypeLoc, dependentNameTypeLoc) + .with(isType()) + .bind(Bind), + this); +} + +static bool isNamedDeclInStdTraitsSet(const NamedDecl *ND, + const llvm::StringSet<> &Set) { + return ND->isInStdNamespace() && ND->getDeclName().isIdentifier() && + Set.contains(ND->getName()); +} + +static bool checkTemplatedDecl(const NestedNameSpecifier *NNS, + const llvm::StringSet<> &Set) { + if (!NNS) + return false; + const Type *NNST = NNS->getAsType(); + if (!NNST) + return false; + const auto *TST = NNST->getAs(); + if (!TST) + return false; + if (const TemplateDecl *TD = TST->getTemplateName().getAsTemplateDecl()) { + return isNamedDeclInStdTraitsSet(TD, Set); + } + return false; +} + +void TypeTraitsCheck::check(const MatchFinder::MatchResult &Result) { + auto EmitValueWarning = [this](const NestedNameSpecifierLoc &QualLoc, + SourceLocation EndLoc) { + SourceLocation LAngleLoc; + if (auto TSTL = QualLoc.getTypeLoc().getAs(); + !TSTL.isNull()) + LAngleLoc = TSTL.getLAngleLoc(); + else + return; + auto Diag = + diag(QualLoc.getBeginLoc(), "use c++17 style variable templates"); + if (EndLoc.isMacroID() || QualLoc.getEndLoc().isMacroID() || + LAngleLoc.isMacroID()) + return; + Diag << FixItHint::CreateInsertion(LAngleLoc, "_v") + << FixItHint::CreateRemoval({QualLoc.getEndLoc(), EndLoc}); + }; + + auto EmitTypeWarning = [this](const NestedNameSpecifierLoc &QualLoc, + SourceLocation EndLoc, + SourceLocation TypenameLoc) { + SourceLocation LAngleLoc; + if (auto TSTL = QualLoc.getTypeLoc().getAs(); + !TSTL.isNull()) + LAngleLoc = TSTL.getLAngleLoc(); + else + return; + auto Diag = diag(QualLoc.getBeginLoc(), "use c++14 style type templates"); + if (EndLoc.isMacroID() || QualLoc.getEndLoc().isMacroID() || + LAngleLoc.isMacroID()) + return; + if (TypenameLoc.isValid()) { + if (TypenameLoc.isMacroID()) + return; + Diag << FixItHint::CreateRemoval(TypenameLoc); + } + Diag << FixItHint::CreateInsertion(LAngleLoc, "_t") + << FixItHint::CreateRemoval({QualLoc.getEndLoc(), EndLoc}); + }; + + if (const auto *DRE = Result.Nodes.getNodeAs(Bind)) { + if (!DRE->hasQualifier()) + return; + if (const auto *CTSD = dyn_cast_if_present( + DRE->getQualifier()->getAsRecordDecl())) { + if (isNamedDeclInStdTraitsSet(CTSD, ValueTraits)) + EmitValueWarning(DRE->getQualifierLoc(), DRE->getEndLoc()); + } + return; + } + + if (const auto *ETL = Result.Nodes.getNodeAs(Bind)) { + const NestedNameSpecifierLoc QualLoc = ETL->getQualifierLoc(); + const auto *NNS = QualLoc.getNestedNameSpecifier(); + if (!NNS) + return; + if (const auto *CTSD = dyn_cast_if_present( + NNS->getAsRecordDecl())) { + if (isNamedDeclInStdTraitsSet(CTSD, TypeTraits)) + EmitTypeWarning(ETL->getQualifierLoc(), ETL->getEndLoc(), + ETL->getElaboratedKeywordLoc()); + } + return; + } + + if (const auto *DSDRE = + Result.Nodes.getNodeAs(Bind)) { + if (checkTemplatedDecl(DSDRE->getQualifier(), ValueTraits)) + EmitValueWarning(DSDRE->getQualifierLoc(), DSDRE->getEndLoc()); + return; + } + + if (const auto *DNTL = Result.Nodes.getNodeAs(Bind)) { + NestedNameSpecifierLoc QualLoc = DNTL->getQualifierLoc(); + if (checkTemplatedDecl(QualLoc.getNestedNameSpecifier(), TypeTraits)) + EmitTypeWarning(QualLoc, DNTL->getEndLoc(), + DNTL->getElaboratedKeywordLoc()); + return; + } +} + +bool TypeTraitsCheck::isLanguageVersionSupported( + const LangOptions &LangOpts) const { + return LangOpts.CPlusPlus14; +} + +llvm::Optional TypeTraitsCheck::getCheckTraversalKind() const { + return TK_IgnoreUnlessSpelledInSource; +} + +} // namespace modernize +} // namespace tidy +} // namespace clang 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 @@ -115,6 +115,12 @@ Warns when using ``do-while`` loops. +- New :doc:`modernize-type-traits + ` check. + + Converts standard library type traits of the form ``traits<...>::type`` and + ``traits<...>::value`` into ``traits_t<...>`` and ``traits_v<...>`` respectively. + New check aliases ^^^^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -276,6 +276,7 @@ `modernize-replace-random-shuffle `_, "Yes" `modernize-return-braced-init-list `_, "Yes" `modernize-shrink-to-fit `_, "Yes" + `modernize-type-traits `_, "Yes" `modernize-unary-static-assert `_, "Yes" `modernize-use-auto `_, "Yes" `modernize-use-bool-literals `_, "Yes" diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/type-traits.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/type-traits.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/type-traits.rst @@ -0,0 +1,25 @@ +.. title:: clang-tidy - modernize-type-traits + +modernize-type-traits +===================== + +Converts standard library type traits of the form ``traits<...>::type`` and +``traits<...>::value`` into ``traits_t<...>`` and ``traits_v<...>`` respectively. + +For example: + +.. code-block:: c++ + + std::is_integral::value + std::is_same::value + typename std::add_const::type + std::make_signed::type + +Would be converted into: + +.. code-block:: c++ + + std::is_integral_v + std::is_same_v + std::add_const_t + std::make_signed_t diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/type-traits.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/type-traits.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/type-traits.cpp @@ -0,0 +1,62 @@ +// RUN: %check_clang_tidy -std=c++14 %s modernize-type-traits %t +// RUN: %check_clang_tidy -std=c++17 %s modernize-type-traits %t -check-suffixes=',CXX17' + +namespace std{ + template + struct is_const { + static constexpr bool value = true; + }; + + template + struct is_same { + static constexpr bool value = true; + }; + + template + struct enable_if { + using type = T; + }; +} // namespace std + +bool NoTemplate = std::is_const::value; +// CHECK-MESSAGES-CXX17: :[[@LINE-1]]:19: warning: use c++17 style variable templates +// CHeCK-FIXES-CXX17: bool NoTemplate = std::is_const_v + +template +constexpr bool InTemplate = std::is_const::value; +// CHECK-MESSAGES-CXX17: :[[@LINE-1]]:29: warning: use c++17 style variable templates +// CHECK-FIXES-CXX17: constexpr bool InTemplate = std::is_const_v; + +template +constexpr bool Template2Params = std::is_same::value; +// CHECK-MESSAGES-CXX17: :[[@LINE-1]]:34: warning: use c++17 style variable templates +// CHECK-FIXES-CXX17: constexpr bool Template2Params = std::is_same_v; + +template +typename std::enable_if::type inTemplate(); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use c++14 style type templates +// CHECK-FIXES: std::enable_if_tinTemplate(); + +typename std::enable_if::type noTemplate(); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use c++14 style type templates +// CHECK-FIXES: std::enable_if_tnoTemplate(); + +std::enable_if::type noTemplateOrTypename(); +// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: use c++14 style type templates +// CHECK-FIXES: std::enable_if_tnoTemplateOrTypename(); + +using UsingNoTypename = std::enable_if::type; +// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: use c++14 style type templates +// CHECK-FIXES: using UsingNoTypename = std::enable_if_t; + +// For macros we don't want any fixes to be emitted + +#define VALUE_MACRO std::is_same::value +bool MacroValue = VALUE_MACRO; +// CHECK-MESSAGES-CXX17: :[[@LINE-1]]:19: warning: use c++17 style variable templates +// CHECK-FIXES-CXX17: #define VALUE_MACRO std::is_same::value + +#define TYPE_MACRO typename std::enable_if::type +using MacroType = TYPE_MACRO; +// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use c++14 style type templates +// CHECK-FIXES: #define TYPE_MACRO typename std::enable_if::type