1 //===--- ConvertMemberFunctionsToStatic.cpp - clang-tidy ------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "ConvertMemberFunctionsToStatic.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/AST/DeclCXX.h"
12 #include "clang/AST/RecursiveASTVisitor.h"
13 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 #include "clang/Basic/SourceLocation.h"
15 #include "clang/Lex/Lexer.h"
17 using namespace clang::ast_matchers
;
19 namespace clang::tidy::readability
{
21 AST_MATCHER(CXXMethodDecl
, isStatic
) { return Node
.isStatic(); }
23 AST_MATCHER(CXXMethodDecl
, hasTrivialBody
) { return Node
.hasTrivialBody(); }
25 AST_MATCHER(CXXMethodDecl
, isOverloadedOperator
) {
26 return Node
.isOverloadedOperator();
29 AST_MATCHER(CXXRecordDecl
, hasAnyDependentBases
) {
30 return Node
.hasAnyDependentBases();
33 AST_MATCHER(CXXMethodDecl
, isTemplate
) {
34 return Node
.getTemplatedKind() != FunctionDecl::TK_NonTemplate
;
37 AST_MATCHER(CXXMethodDecl
, isDependentContext
) {
38 return Node
.isDependentContext();
41 AST_MATCHER(CXXMethodDecl
, isInsideMacroDefinition
) {
42 const ASTContext
&Ctxt
= Finder
->getASTContext();
43 return clang::Lexer::makeFileCharRange(
44 clang::CharSourceRange::getCharRange(
45 Node
.getTypeSourceInfo()->getTypeLoc().getSourceRange()),
46 Ctxt
.getSourceManager(), Ctxt
.getLangOpts())
50 AST_MATCHER_P(CXXMethodDecl
, hasCanonicalDecl
,
51 ast_matchers::internal::Matcher
<CXXMethodDecl
>, InnerMatcher
) {
52 return InnerMatcher
.matches(*Node
.getCanonicalDecl(), Finder
, Builder
);
55 AST_MATCHER(CXXMethodDecl
, usesThis
) {
56 class FindUsageOfThis
: public RecursiveASTVisitor
<FindUsageOfThis
> {
60 bool VisitCXXThisExpr(const CXXThisExpr
*E
) {
62 return false; // Stop traversal.
65 // If we enter a class declaration, don't traverse into it as any usages of
66 // `this` will correspond to the nested class.
67 bool TraverseCXXRecordDecl(CXXRecordDecl
*RD
) { return true; }
71 // TraverseStmt does not modify its argument.
72 UsageOfThis
.TraverseStmt(const_cast<Stmt
*>(Node
.getBody()));
74 return UsageOfThis
.Used
;
77 void ConvertMemberFunctionsToStatic::registerMatchers(MatchFinder
*Finder
) {
80 isDefinition(), isUserProvided(),
82 isExpansionInSystemHeader(), isVirtual(), isStatic(),
83 hasTrivialBody(), isOverloadedOperator(), cxxConstructorDecl(),
84 cxxDestructorDecl(), cxxConversionDecl(), isTemplate(),
88 hasAnyDependentBases()) // Method might become virtual
89 // depending on template base class.
91 isInsideMacroDefinition(),
92 hasCanonicalDecl(isInsideMacroDefinition()), usesThis())))
97 /// Obtain the original source code text from a SourceRange.
98 static StringRef
getStringFromRange(SourceManager
&SourceMgr
,
99 const LangOptions
&LangOpts
,
101 if (SourceMgr
.getFileID(Range
.getBegin()) !=
102 SourceMgr
.getFileID(Range
.getEnd()))
105 return Lexer::getSourceText(CharSourceRange(Range
, true), SourceMgr
,
109 static SourceRange
getLocationOfConst(const TypeSourceInfo
*TSI
,
110 SourceManager
&SourceMgr
,
111 const LangOptions
&LangOpts
) {
113 const auto FTL
= TSI
->getTypeLoc().IgnoreParens().getAs
<FunctionTypeLoc
>();
116 SourceRange Range
{FTL
.getRParenLoc().getLocWithOffset(1),
117 FTL
.getLocalRangeEnd()};
118 // Inside Range, there might be other keywords and trailing return types.
119 // Find the exact position of "const".
120 StringRef Text
= getStringFromRange(SourceMgr
, LangOpts
, Range
);
121 size_t Offset
= Text
.find("const");
122 if (Offset
== StringRef::npos
)
125 SourceLocation Start
= Range
.getBegin().getLocWithOffset(Offset
);
126 return {Start
, Start
.getLocWithOffset(strlen("const") - 1)};
129 void ConvertMemberFunctionsToStatic::check(
130 const MatchFinder::MatchResult
&Result
) {
131 const auto *Definition
= Result
.Nodes
.getNodeAs
<CXXMethodDecl
>("x");
133 // TODO: For out-of-line declarations, don't modify the source if the header
134 // is excluded by the -header-filter option.
135 DiagnosticBuilder Diag
=
136 diag(Definition
->getLocation(), "method %0 can be made static")
139 // TODO: Would need to remove those in a fix-it.
140 if (Definition
->getMethodQualifiers().hasVolatile() ||
141 Definition
->getMethodQualifiers().hasRestrict() ||
142 Definition
->getRefQualifier() != RQ_None
)
145 const CXXMethodDecl
*Declaration
= Definition
->getCanonicalDecl();
147 if (Definition
->isConst()) {
148 // Make sure that we either remove 'const' on both declaration and
149 // definition or emit no fix-it at all.
150 SourceRange DefConst
= getLocationOfConst(Definition
->getTypeSourceInfo(),
151 *Result
.SourceManager
,
152 Result
.Context
->getLangOpts());
154 if (DefConst
.isInvalid())
157 if (Declaration
!= Definition
) {
158 SourceRange DeclConst
= getLocationOfConst(
159 Declaration
->getTypeSourceInfo(), *Result
.SourceManager
,
160 Result
.Context
->getLangOpts());
162 if (DeclConst
.isInvalid())
164 Diag
<< FixItHint::CreateRemoval(DeclConst
);
167 // Remove existing 'const' from both declaration and definition.
168 Diag
<< FixItHint::CreateRemoval(DefConst
);
170 Diag
<< FixItHint::CreateInsertion(Declaration
->getBeginLoc(), "static ");
173 } // namespace clang::tidy::readability