| //===----------------------------------------------------------------------===// |
| // |
| // 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<TypedefTypeLoc>()) { |
| const auto *TD = TL.getDecl(); |
| return TD->getDeclName().isIdentifier() && TD->getName() == "type"; |
| } |
| if (auto TL = Node.getAs<DependentNameTypeLoc>()) |
| 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<TemplateSpecializationType>(); |
| 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<TemplateSpecializationTypeLoc>()) |
| 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<TemplateSpecializationTypeLoc>()) |
| 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<DeclRefExpr>(Bind)) { |
| if (!DRE->hasQualifier()) |
| return; |
| if (const auto *CTSD = dyn_cast_if_present<ClassTemplateSpecializationDecl>( |
| DRE->getQualifier().getAsRecordDecl())) { |
| if (isNamedDeclInStdTraitsSet(CTSD, ValueTraits)) |
| EmitValueWarning(DRE->getQualifierLoc(), DRE->getEndLoc()); |
| } |
| return; |
| } |
| |
| if (const auto *TL = Result.Nodes.getNodeAs<TypedefTypeLoc>(Bind)) { |
| const NestedNameSpecifierLoc QualLoc = TL->getQualifierLoc(); |
| NestedNameSpecifier NNS = QualLoc.getNestedNameSpecifier(); |
| if (const auto *CTSD = dyn_cast_if_present<ClassTemplateSpecializationDecl>( |
| NNS.getAsRecordDecl())) { |
| if (isNamedDeclInStdTraitsSet(CTSD, TypeTraits)) |
| EmitTypeWarning(TL->getQualifierLoc(), TL->getEndLoc(), |
| TL->getElaboratedKeywordLoc()); |
| } |
| return; |
| } |
| |
| if (const auto *DSDRE = |
| Result.Nodes.getNodeAs<DependentScopeDeclRefExpr>(Bind)) { |
| if (checkTemplatedDecl(DSDRE->getQualifier(), ValueTraits)) |
| EmitValueWarning(DSDRE->getQualifierLoc(), DSDRE->getEndLoc()); |
| return; |
| } |
| |
| if (const auto *DNTL = Result.Nodes.getNodeAs<DependentNameTypeLoc>(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 |