xref: /llvm-project/clang-tools-extra/clang-tidy/readability/ConvertMemberFunctionsToStatic.cpp (revision 11a411a49b62c129bba551df4587dd446fcdc660)
1ffca3222SMatthias Gehre //===--- ConvertMemberFunctionsToStatic.cpp - clang-tidy ------------------===//
2ffca3222SMatthias Gehre //
3c874dd53SChristopher Di Bella // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4c874dd53SChristopher Di Bella // See https://llvm.org/LICENSE.txt for license information.
5c874dd53SChristopher Di Bella // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6ffca3222SMatthias Gehre //
7ffca3222SMatthias Gehre //===----------------------------------------------------------------------===//
8ffca3222SMatthias Gehre 
9ffca3222SMatthias Gehre #include "ConvertMemberFunctionsToStatic.h"
10ffca3222SMatthias Gehre #include "clang/AST/ASTContext.h"
11ffca3222SMatthias Gehre #include "clang/AST/DeclCXX.h"
12ffca3222SMatthias Gehre #include "clang/AST/RecursiveASTVisitor.h"
13ffca3222SMatthias Gehre #include "clang/ASTMatchers/ASTMatchFinder.h"
14ffca3222SMatthias Gehre #include "clang/Basic/SourceLocation.h"
15860aefd0SNathan James #include "clang/Lex/Lexer.h"
16ffca3222SMatthias Gehre 
17ffca3222SMatthias Gehre using namespace clang::ast_matchers;
18ffca3222SMatthias Gehre 
197d2ea6c4SCarlos Galvez namespace clang::tidy::readability {
20ffca3222SMatthias Gehre 
AST_MATCHER(CXXMethodDecl,isStatic)21*11a411a4SPiotr Zegar AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); }
22*11a411a4SPiotr Zegar 
AST_MATCHER(CXXMethodDecl,hasTrivialBody)23*11a411a4SPiotr Zegar AST_MATCHER(CXXMethodDecl, hasTrivialBody) { return Node.hasTrivialBody(); }
24ffca3222SMatthias Gehre 
AST_MATCHER(CXXMethodDecl,isOverloadedOperator)25ffca3222SMatthias Gehre AST_MATCHER(CXXMethodDecl, isOverloadedOperator) {
26ffca3222SMatthias Gehre   return Node.isOverloadedOperator();
27ffca3222SMatthias Gehre }
28ffca3222SMatthias Gehre 
AST_MATCHER(CXXRecordDecl,hasAnyDependentBases)29*11a411a4SPiotr Zegar AST_MATCHER(CXXRecordDecl, hasAnyDependentBases) {
30*11a411a4SPiotr Zegar   return Node.hasAnyDependentBases();
31*11a411a4SPiotr Zegar }
32*11a411a4SPiotr Zegar 
AST_MATCHER(CXXMethodDecl,isTemplate)33*11a411a4SPiotr Zegar AST_MATCHER(CXXMethodDecl, isTemplate) {
34*11a411a4SPiotr Zegar   return Node.getTemplatedKind() != FunctionDecl::TK_NonTemplate;
35*11a411a4SPiotr Zegar }
36*11a411a4SPiotr Zegar 
AST_MATCHER(CXXMethodDecl,isDependentContext)37*11a411a4SPiotr Zegar AST_MATCHER(CXXMethodDecl, isDependentContext) {
38*11a411a4SPiotr Zegar   return Node.isDependentContext();
39*11a411a4SPiotr Zegar }
40*11a411a4SPiotr Zegar 
AST_MATCHER(CXXMethodDecl,isInsideMacroDefinition)41*11a411a4SPiotr Zegar AST_MATCHER(CXXMethodDecl, isInsideMacroDefinition) {
42*11a411a4SPiotr Zegar   const ASTContext &Ctxt = Finder->getASTContext();
43*11a411a4SPiotr Zegar   return clang::Lexer::makeFileCharRange(
44*11a411a4SPiotr Zegar              clang::CharSourceRange::getCharRange(
45*11a411a4SPiotr Zegar                  Node.getTypeSourceInfo()->getTypeLoc().getSourceRange()),
46*11a411a4SPiotr Zegar              Ctxt.getSourceManager(), Ctxt.getLangOpts())
47*11a411a4SPiotr Zegar       .isInvalid();
48*11a411a4SPiotr Zegar }
49*11a411a4SPiotr Zegar 
AST_MATCHER_P(CXXMethodDecl,hasCanonicalDecl,ast_matchers::internal::Matcher<CXXMethodDecl>,InnerMatcher)50*11a411a4SPiotr Zegar AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl,
51*11a411a4SPiotr Zegar               ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) {
52*11a411a4SPiotr Zegar   return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder);
53*11a411a4SPiotr Zegar }
54*11a411a4SPiotr Zegar 
AST_MATCHER(CXXMethodDecl,usesThis)55ffca3222SMatthias Gehre AST_MATCHER(CXXMethodDecl, usesThis) {
56ffca3222SMatthias Gehre   class FindUsageOfThis : public RecursiveASTVisitor<FindUsageOfThis> {
57ffca3222SMatthias Gehre   public:
58ffca3222SMatthias Gehre     bool Used = false;
59ffca3222SMatthias Gehre 
60ffca3222SMatthias Gehre     bool VisitCXXThisExpr(const CXXThisExpr *E) {
61ffca3222SMatthias Gehre       Used = true;
62ffca3222SMatthias Gehre       return false; // Stop traversal.
63ffca3222SMatthias Gehre     }
6467e05d38SNathan James 
6567e05d38SNathan James     // If we enter a class declaration, don't traverse into it as any usages of
6667e05d38SNathan James     // `this` will correspond to the nested class.
6767e05d38SNathan James     bool TraverseCXXRecordDecl(CXXRecordDecl *RD) { return true; }
6867e05d38SNathan James 
69ffca3222SMatthias Gehre   } UsageOfThis;
70ffca3222SMatthias Gehre 
71ffca3222SMatthias Gehre   // TraverseStmt does not modify its argument.
72ffca3222SMatthias Gehre   UsageOfThis.TraverseStmt(const_cast<Stmt *>(Node.getBody()));
73ffca3222SMatthias Gehre 
74ffca3222SMatthias Gehre   return UsageOfThis.Used;
75ffca3222SMatthias Gehre }
76ffca3222SMatthias Gehre 
registerMatchers(MatchFinder * Finder)77ffca3222SMatthias Gehre void ConvertMemberFunctionsToStatic::registerMatchers(MatchFinder *Finder) {
78ffca3222SMatthias Gehre   Finder->addMatcher(
79ffca3222SMatthias Gehre       cxxMethodDecl(
80ffca3222SMatthias Gehre           isDefinition(), isUserProvided(),
81ffca3222SMatthias Gehre           unless(anyOf(
82*11a411a4SPiotr Zegar               isExpansionInSystemHeader(), isVirtual(), isStatic(),
83*11a411a4SPiotr Zegar               hasTrivialBody(), isOverloadedOperator(), cxxConstructorDecl(),
84*11a411a4SPiotr Zegar               cxxDestructorDecl(), cxxConversionDecl(), isTemplate(),
85*11a411a4SPiotr Zegar               isDependentContext(),
86ffca3222SMatthias Gehre               ofClass(anyOf(
87ffca3222SMatthias Gehre                   isLambda(),
88*11a411a4SPiotr Zegar                   hasAnyDependentBases()) // Method might become virtual
89*11a411a4SPiotr Zegar                                           // depending on template base class.
90ffca3222SMatthias Gehre                       ),
91*11a411a4SPiotr Zegar               isInsideMacroDefinition(),
92*11a411a4SPiotr Zegar               hasCanonicalDecl(isInsideMacroDefinition()), usesThis())))
93ffca3222SMatthias Gehre           .bind("x"),
94ffca3222SMatthias Gehre       this);
95ffca3222SMatthias Gehre }
96ffca3222SMatthias Gehre 
97282dc72cSDmitri Gribenko /// Obtain the original source code text from a SourceRange.
getStringFromRange(SourceManager & SourceMgr,const LangOptions & LangOpts,SourceRange Range)98ffca3222SMatthias Gehre static StringRef getStringFromRange(SourceManager &SourceMgr,
99ffca3222SMatthias Gehre                                     const LangOptions &LangOpts,
100ffca3222SMatthias Gehre                                     SourceRange Range) {
101ffca3222SMatthias Gehre   if (SourceMgr.getFileID(Range.getBegin()) !=
102ffca3222SMatthias Gehre       SourceMgr.getFileID(Range.getEnd()))
103ffca3222SMatthias Gehre     return {};
104ffca3222SMatthias Gehre 
105ffca3222SMatthias Gehre   return Lexer::getSourceText(CharSourceRange(Range, true), SourceMgr,
106ffca3222SMatthias Gehre                               LangOpts);
107ffca3222SMatthias Gehre }
108ffca3222SMatthias Gehre 
getLocationOfConst(const TypeSourceInfo * TSI,SourceManager & SourceMgr,const LangOptions & LangOpts)109ffca3222SMatthias Gehre static SourceRange getLocationOfConst(const TypeSourceInfo *TSI,
110ffca3222SMatthias Gehre                                       SourceManager &SourceMgr,
111ffca3222SMatthias Gehre                                       const LangOptions &LangOpts) {
112ffca3222SMatthias Gehre   assert(TSI);
113ffca3222SMatthias Gehre   const auto FTL = TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>();
114ffca3222SMatthias Gehre   assert(FTL);
115ffca3222SMatthias Gehre 
116ffca3222SMatthias Gehre   SourceRange Range{FTL.getRParenLoc().getLocWithOffset(1),
117ffca3222SMatthias Gehre                     FTL.getLocalRangeEnd()};
118ffca3222SMatthias Gehre   // Inside Range, there might be other keywords and trailing return types.
119ffca3222SMatthias Gehre   // Find the exact position of "const".
120ffca3222SMatthias Gehre   StringRef Text = getStringFromRange(SourceMgr, LangOpts, Range);
121ffca3222SMatthias Gehre   size_t Offset = Text.find("const");
122ffca3222SMatthias Gehre   if (Offset == StringRef::npos)
123ffca3222SMatthias Gehre     return {};
124ffca3222SMatthias Gehre 
125ffca3222SMatthias Gehre   SourceLocation Start = Range.getBegin().getLocWithOffset(Offset);
126ffca3222SMatthias Gehre   return {Start, Start.getLocWithOffset(strlen("const") - 1)};
127ffca3222SMatthias Gehre }
128ffca3222SMatthias Gehre 
check(const MatchFinder::MatchResult & Result)129ffca3222SMatthias Gehre void ConvertMemberFunctionsToStatic::check(
130ffca3222SMatthias Gehre     const MatchFinder::MatchResult &Result) {
131ffca3222SMatthias Gehre   const auto *Definition = Result.Nodes.getNodeAs<CXXMethodDecl>("x");
132ffca3222SMatthias Gehre 
133ffca3222SMatthias Gehre   // TODO: For out-of-line declarations, don't modify the source if the header
134ffca3222SMatthias Gehre   // is excluded by the -header-filter option.
135ffca3222SMatthias Gehre   DiagnosticBuilder Diag =
136ffca3222SMatthias Gehre       diag(Definition->getLocation(), "method %0 can be made static")
137ffca3222SMatthias Gehre       << Definition;
138ffca3222SMatthias Gehre 
139ffca3222SMatthias Gehre   // TODO: Would need to remove those in a fix-it.
140ffca3222SMatthias Gehre   if (Definition->getMethodQualifiers().hasVolatile() ||
141ffca3222SMatthias Gehre       Definition->getMethodQualifiers().hasRestrict() ||
142ffca3222SMatthias Gehre       Definition->getRefQualifier() != RQ_None)
143ffca3222SMatthias Gehre     return;
144ffca3222SMatthias Gehre 
145ffca3222SMatthias Gehre   const CXXMethodDecl *Declaration = Definition->getCanonicalDecl();
146ffca3222SMatthias Gehre 
147ffca3222SMatthias Gehre   if (Definition->isConst()) {
148ffca3222SMatthias Gehre     // Make sure that we either remove 'const' on both declaration and
149ffca3222SMatthias Gehre     // definition or emit no fix-it at all.
150ffca3222SMatthias Gehre     SourceRange DefConst = getLocationOfConst(Definition->getTypeSourceInfo(),
151ffca3222SMatthias Gehre                                               *Result.SourceManager,
152ffca3222SMatthias Gehre                                               Result.Context->getLangOpts());
153ffca3222SMatthias Gehre 
154ffca3222SMatthias Gehre     if (DefConst.isInvalid())
155ffca3222SMatthias Gehre       return;
156ffca3222SMatthias Gehre 
157ffca3222SMatthias Gehre     if (Declaration != Definition) {
158ffca3222SMatthias Gehre       SourceRange DeclConst = getLocationOfConst(
159ffca3222SMatthias Gehre           Declaration->getTypeSourceInfo(), *Result.SourceManager,
160ffca3222SMatthias Gehre           Result.Context->getLangOpts());
161ffca3222SMatthias Gehre 
162ffca3222SMatthias Gehre       if (DeclConst.isInvalid())
163ffca3222SMatthias Gehre         return;
164ffca3222SMatthias Gehre       Diag << FixItHint::CreateRemoval(DeclConst);
165ffca3222SMatthias Gehre     }
166ffca3222SMatthias Gehre 
167ffca3222SMatthias Gehre     // Remove existing 'const' from both declaration and definition.
168ffca3222SMatthias Gehre     Diag << FixItHint::CreateRemoval(DefConst);
169ffca3222SMatthias Gehre   }
170ffca3222SMatthias Gehre   Diag << FixItHint::CreateInsertion(Declaration->getBeginLoc(), "static ");
171ffca3222SMatthias Gehre }
172ffca3222SMatthias Gehre 
1737d2ea6c4SCarlos Galvez } // namespace clang::tidy::readability
174