//===----------------------------------------------------------------------===// // // 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 { // FIXME: Add chrono::treat_as_floating_point_v and chrono::is_clock_v. // This will require restructuring the code to handle type traits not // defined directly in std. 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_bind_expression", "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_error_code_enum", "is_error_condition_enum", "is_execution_policy", "is_final", "is_floating_point", "is_function", "is_fundamental", "is_implicit_lifetime", "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_relocatable", "is_nothrow_swappable", "is_nothrow_swappable_with", "is_null_pointer", "is_object", "is_placeholder", "is_pointer", "is_pointer_interconvertible_base_of", "is_polymorphic", "is_reference", "is_replaceable", "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_trivially_relocatable", "is_unbounded_array", "is_union", "is_unsigned", "is_virtual_base_of", "is_void", "is_volatile", "negation", "rank", "ratio_equal", "ratio_greater_equal", "ratio_greater", "ratio_less_equal", "ratio_less", "ratio_not_equal", "reference_constructs_from_temporary", "reference_converts_from_temporary", "tuple_size", "uses_allocator", "variant_size", }; 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", "compare_three_way_result", "common_comparison_category", "unwrap_ref_decay", "unwrap_reference", "tuple_element", "variant_alternative", }; static DeclarationName getName(const DependentScopeDeclRefExpr &D) { return D.getDeclName(); } static DeclarationName getName(const DeclRefExpr &D) { return D.getDecl()->getDeclName(); } namespace { AST_POLYMORPHIC_MATCHER(isValue, AST_POLYMORPHIC_SUPPORTED_TYPES( DeclRefExpr, DependentScopeDeclRefExpr)) { const IdentifierInfo *Ident = getName(Node).getAsIdentifierInfo(); return Ident && Ident->isStr("value"); } AST_MATCHER(TypeLoc, isType) { if (auto TL = Node.getAs()) { const auto *TD = TL.getDecl(); return TD->getDeclName().isIdentifier() && TD->getName() == "type"; } if (auto TL = Node.getAs()) return TL.getTypePtr()->getIdentifier()->getName() == "type"; return false; } } // 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(typeLoc(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(NestedNameSpecifier NNS, const llvm::StringSet<> &Set) { if (NNS.getKind() != NestedNameSpecifier::Kind::Type) return false; const auto *TST = NNS.getAsType()->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.get("IgnoreMacros", false)) {} void TypeTraitsCheck::check(const MatchFinder::MatchResult &Result) { auto EmitValueWarning = [this, &Result](const NestedNameSpecifierLoc &QualLoc, SourceLocation EndLoc) { SourceLocation TemplateNameEndLoc; if (auto TSTL = QualLoc.getAsTypeLoc().getAs()) 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.getAsTypeLoc().getAs()) 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 *TL = Result.Nodes.getNodeAs(Bind)) { const NestedNameSpecifierLoc QualLoc = TL->getQualifierLoc(); const NestedNameSpecifier NNS = QualLoc.getNestedNameSpecifier(); if (const auto *CTSD = dyn_cast_if_present( NNS.getAsRecordDecl())) { if (isNamedDeclInStdTraitsSet(CTSD, TypeTraits)) EmitTypeWarning(TL->getQualifierLoc(), TL->getEndLoc(), TL->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)) { const 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