xref: /llvm-project/clang-tools-extra/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp (revision 11a411a49b62c129bba551df4587dd446fcdc660)
1 //===--- UseTransparentFunctorsCheck.cpp - clang-tidy----------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "UseTransparentFunctorsCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 
13 using namespace clang::ast_matchers;
14 
15 namespace clang::tidy::modernize {
16 
UseTransparentFunctorsCheck(StringRef Name,ClangTidyContext * Context)17 UseTransparentFunctorsCheck::UseTransparentFunctorsCheck(
18     StringRef Name, ClangTidyContext *Context)
19     : ClangTidyCheck(Name, Context), SafeMode(Options.get("SafeMode", false)) {}
20 
storeOptions(ClangTidyOptions::OptionMap & Opts)21 void UseTransparentFunctorsCheck::storeOptions(
22     ClangTidyOptions::OptionMap &Opts) {
23   Options.store(Opts, "SafeMode", SafeMode);
24 }
25 
registerMatchers(MatchFinder * Finder)26 void UseTransparentFunctorsCheck::registerMatchers(MatchFinder *Finder) {
27   const auto TransparentFunctors =
28       classTemplateSpecializationDecl(
29           unless(hasAnyTemplateArgument(refersToType(voidType()))),
30           hasAnyName("::std::plus", "::std::minus", "::std::multiplies",
31                      "::std::divides", "::std::modulus", "::std::negate",
32                      "::std::equal_to", "::std::not_equal_to", "::std::greater",
33                      "::std::less", "::std::greater_equal", "::std::less_equal",
34                      "::std::logical_and", "::std::logical_or",
35                      "::std::logical_not", "::std::bit_and", "::std::bit_or",
36                      "::std::bit_xor", "::std::bit_not"))
37           .bind("FunctorClass");
38 
39   // Non-transparent functor mentioned as a template parameter. FIXIT.
40   Finder->addMatcher(
41       loc(qualType(
42               unless(elaboratedType()),
43               hasDeclaration(classTemplateSpecializationDecl(
44                   unless(hasAnyTemplateArgument(templateArgument(refersToType(
45                       qualType(pointsTo(qualType(isAnyCharacter()))))))),
46                   hasAnyTemplateArgument(
47                       templateArgument(refersToType(qualType(hasDeclaration(
48                                            TransparentFunctors))))
49                           .bind("Functor"))))))
50           .bind("FunctorParentLoc"),
51       this);
52 
53   if (SafeMode)
54     return;
55 
56   // Non-transparent functor constructed. No FIXIT. There is no easy way
57   // to rule out the problematic char* vs string case.
58   Finder->addMatcher(cxxConstructExpr(hasDeclaration(cxxMethodDecl(
59                                           ofClass(TransparentFunctors))),
60                                       unless(isInTemplateInstantiation()))
61                          .bind("FuncInst"),
62                      this);
63 }
64 
65 static const StringRef Message = "prefer transparent functors '%0<>'";
66 
getInnerTypeLocAs(TypeLoc Loc)67 template <typename T> static T getInnerTypeLocAs(TypeLoc Loc) {
68   T Result;
69   while (Result.isNull() && !Loc.isNull()) {
70     Result = Loc.getAs<T>();
71     Loc = Loc.getNextTypeLoc();
72   }
73   return Result;
74 }
75 
check(const MatchFinder::MatchResult & Result)76 void UseTransparentFunctorsCheck::check(
77     const MatchFinder::MatchResult &Result) {
78   const auto *FuncClass =
79       Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("FunctorClass");
80   if (const auto *FuncInst =
81           Result.Nodes.getNodeAs<CXXConstructExpr>("FuncInst")) {
82     diag(FuncInst->getBeginLoc(), Message) << FuncClass->getName();
83     return;
84   }
85 
86   const auto *Functor = Result.Nodes.getNodeAs<TemplateArgument>("Functor");
87   const auto FunctorParentLoc =
88       Result.Nodes.getNodeAs<TypeLoc>("FunctorParentLoc")
89           ->getAs<TemplateSpecializationTypeLoc>();
90 
91   if (!FunctorParentLoc)
92     return;
93 
94   unsigned ArgNum = 0;
95   const auto *FunctorParentType =
96       FunctorParentLoc.getType()->castAs<TemplateSpecializationType>();
97   for (; ArgNum < FunctorParentType->template_arguments().size(); ++ArgNum) {
98     const TemplateArgument &Arg =
99         FunctorParentType->template_arguments()[ArgNum];
100     if (Arg.getKind() != TemplateArgument::Type)
101       continue;
102     QualType ParentArgType = Arg.getAsType();
103     if (ParentArgType->isRecordType() &&
104         ParentArgType->getAsCXXRecordDecl() ==
105             Functor->getAsType()->getAsCXXRecordDecl())
106       break;
107   }
108   // Functor is a default template argument.
109   if (ArgNum == FunctorParentType->template_arguments().size())
110     return;
111   TemplateArgumentLoc FunctorLoc = FunctorParentLoc.getArgLoc(ArgNum);
112   auto FunctorTypeLoc = getInnerTypeLocAs<TemplateSpecializationTypeLoc>(
113       FunctorLoc.getTypeSourceInfo()->getTypeLoc());
114   if (FunctorTypeLoc.isNull())
115     return;
116 
117   SourceLocation ReportLoc = FunctorLoc.getLocation();
118   if (ReportLoc.isInvalid())
119     return;
120   diag(ReportLoc, Message) << FuncClass->getName()
121                            << FixItHint::CreateRemoval(
122                                   FunctorTypeLoc.getArgLoc(0).getSourceRange());
123 }
124 
125 } // namespace clang::tidy::modernize
126