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" @@ -76,6 +77,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,40 @@ +//===--- 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" + +namespace clang::tidy::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); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus14; + } + std::optional getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + const bool IgnoreMacros; +}; + +} // namespace clang::tidy::modernize + +#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,316 @@ +//===--- 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" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::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", +}; + +static DeclarationName getName(const DependentScopeDeclRefExpr &D) { + return D.getDeclName(); +} + +static DeclarationName getName(const DeclRefExpr &D) { + return D.getDecl()->getDeclName(); +} + +static bool isNamedType(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; +} + +static bool isNamedType(const DependentNameTypeLoc &DTL) { + return DTL.getTypePtr()->getIdentifier()->getName() == "type"; +} + +namespace { +AST_POLYMORPHIC_MATCHER(isValue, AST_POLYMORPHIC_SUPPORTED_TYPES( + DeclRefExpr, DependentScopeDeclRefExpr)) { + const IdentifierInfo *Ident = getName(Node).getAsIdentifierInfo(); + return Ident && Ident->isStr("value"); +} + +AST_POLYMORPHIC_MATCHER(isType, + AST_POLYMORPHIC_SUPPORTED_TYPES(ElaboratedTypeLoc, + DependentNameTypeLoc)) { + return Node.getBeginLoc().isValid() && isNamedType(Node); +} +} // namespace + +static constexpr char Bind[] = ""; + +void TypeTraitsCheck::registerMatchers(MatchFinder *Finder) { + const ast_matchers::internal::VariadicDynCastAllOfMatcher< + Stmt, + DependentScopeDeclRefExpr> + dependentScopeDeclRefExpr; // NOLINT(readability-identifier-naming) + const ast_matchers::internal::VariadicDynCastAllOfMatcher< + TypeLoc, + DependentNameTypeLoc> + dependentNameTypeLoc; // NOLINT(readability-identifier-naming) + + // 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; +} + +TypeTraitsCheck::TypeTraitsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", false)) {} + +void TypeTraitsCheck::check(const MatchFinder::MatchResult &Result) { + auto EmitValueWarning = [this, &Result](const NestedNameSpecifierLoc &QualLoc, + SourceLocation EndLoc) { + SourceLocation TemplateNameEndLoc; + if (auto TSTL = QualLoc.getTypeLoc().getAs(); + !TSTL.isNull()) + TemplateNameEndLoc = Lexer::getLocForEndOfToken( + TSTL.getTemplateNameLoc(), 0, *Result.SourceManager, + Result.Context->getLangOpts()); + else + return; + + if (EndLoc.isMacroID() || QualLoc.getEndLoc().isMacroID() || + TemplateNameEndLoc.isMacroID()) { + if (IgnoreMacros) + return; + diag(QualLoc.getBeginLoc(), "use c++17 style variable templates"); + return; + } + diag(QualLoc.getBeginLoc(), "use c++17 style variable templates") + << FixItHint::CreateInsertion(TemplateNameEndLoc, "_v") + << FixItHint::CreateRemoval({QualLoc.getEndLoc(), EndLoc}); + }; + + auto EmitTypeWarning = [this, &Result](const NestedNameSpecifierLoc &QualLoc, + SourceLocation EndLoc, + SourceLocation TypenameLoc) { + SourceLocation TemplateNameEndLoc; + if (auto TSTL = QualLoc.getTypeLoc().getAs(); + !TSTL.isNull()) + TemplateNameEndLoc = Lexer::getLocForEndOfToken( + TSTL.getTemplateNameLoc(), 0, *Result.SourceManager, + Result.Context->getLangOpts()); + else + return; + + if (EndLoc.isMacroID() || QualLoc.getEndLoc().isMacroID() || + TemplateNameEndLoc.isMacroID() || TypenameLoc.isMacroID()) { + if (IgnoreMacros) + return; + diag(QualLoc.getBeginLoc(), "use c++14 style type templates"); + return; + } + auto Diag = diag(QualLoc.getBeginLoc(), "use c++14 style type templates"); + + if (TypenameLoc.isValid()) + Diag << FixItHint::CreateRemoval(TypenameLoc); + Diag << FixItHint::CreateInsertion(TemplateNameEndLoc, "_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; + } +} + +void TypeTraitsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IgnoreMacros", IgnoreMacros); +} +} // namespace clang::tidy::modernize 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 @@ -133,6 +133,12 @@ Checks that all implicit and explicit inline functions in header files are tagged with the ``LIBC_INLINE`` macro. +- 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 :doc:`readability-avoid-unconditional-preprocessor-if ` check. 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 @@ -284,6 +284,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,40 @@ +.. 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 + +Options +------- + +.. option:: IgnoreMacros + + If `true` don't diagnose traits defined in macros. + + Note: Fixes will never be emitted for code inside of macros. + + .. code-block:: c++ + + #define IS_SIGNED(T) std::is_signed::value + + Defaults to `false`. 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,119 @@ +// RUN: %check_clang_tidy -std=c++14 %s modernize-type-traits %t -check-suffixes=',MACRO' +// RUN: %check_clang_tidy -std=c++14 %s modernize-type-traits %t -- \ +// RUN: -config='{CheckOptions: {modernize-type-traits.IgnoreMacros: true}}' +// RUN: %check_clang_tidy -std=c++17 %s modernize-type-traits %t -check-suffixes=',CXX17,MACRO,CXX17MACRO' + +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; + }; + +inline namespace __std_lib_version1 { + template + struct add_const { + using type = T; + }; +} // namespace __std_lib_version1 + +namespace ext { + template + struct add_const { + using type = T; + }; +} // namespace ext + +} // 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; + +using UsingSpace = std::enable_if ::type; +// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use c++14 style type templates +// CHECK-FIXES: using UsingSpace = std::enable_if_t ; + +template +using UsingSpaceTemplate = typename std::enable_if ::type; +// CHECK-MESSAGES: :[[@LINE-1]]:37: warning: use c++14 style type templates +// CHECK-FIXES: using UsingSpaceTemplate = std::enable_if_t ; + +bool NoTemplateSpace = std::is_const ::value; +// CHECK-MESSAGES-CXX17: :[[@LINE-1]]:24: warning: use c++17 style variable templates +// CHECK-FIXES-CXX17: bool NoTemplateSpace = std::is_const_v ; + +template +constexpr bool InTemplateSpace = std::is_const ::value; +// CHECK-MESSAGES-CXX17: :[[@LINE-1]]:34: warning: use c++17 style variable templates +// CHECK-FIXES-CXX17: constexpr bool InTemplateSpace = std::is_const_v ; + +// For macros, no diagnostics if IgnoreMacros is set, +// No fixes emitted even if IgnoreMacros is unset. + +#define VALUE_MACRO std::is_same::value +bool MacroValue = VALUE_MACRO; +// CHECK-MESSAGES-CXX17MACRO: :[[@LINE-1]]:19: warning: use c++17 style variable templates +// CHECK-FIXES-CXX17MACRO: #define VALUE_MACRO std::is_same::value + +#define TYPE_MACRO typename std::enable_if::type +using MacroType = TYPE_MACRO; +// CHECK-MESSAGES-MACRO: :[[@LINE-1]]:19: warning: use c++14 style type templates +// CHECK-FIXES-MACRO: #define TYPE_MACRO typename std::enable_if::type + + +// Names defined and accessed inside an inline namespace should be converted. +// Whether or not the inline namespace is specified + +using InlineUnspecified = std::add_const::type; +// CHECK-MESSAGES: :[[@LINE-1]]:27: warning: use c++14 style type templates +// CHECK-FIXES: using InlineUnspecified = std::add_const_t; + +using Inline = std::__std_lib_version1::add_const::type; +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: use c++14 style type templates +// CHECK-FIXES: using Inline = std::__std_lib_version1::add_const_t; + +// Don't try to offer any fix if the name is an extension to the standard library +using Ext = std::ext::add_const::type; + +namespace my_std = std; + +using Alias = my_std::add_const::type; +// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: use c++14 style type templates +// CHECK-FIXES: using Alias = my_std::add_const_t;