1 | //===--- ConstReturnTypeCheck.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 "ConstReturnTypeCheck.h" |
10 | #include "../utils/LexerUtils.h" |
11 | #include "clang/AST/ASTContext.h" |
12 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
13 | #include "clang/Basic/SourceLocation.h" |
14 | #include "clang/Lex/Lexer.h" |
15 | #include <optional> |
16 | |
17 | using namespace clang::ast_matchers; |
18 | |
19 | namespace clang::tidy::readability { |
20 | |
21 | // Finds the location of the qualifying `const` token in the `FunctionDecl`'s |
22 | // return type. Returns `std::nullopt` when the return type is not |
23 | // `const`-qualified or `const` does not appear in `Def`'s source, like when the |
24 | // type is an alias or a macro. |
25 | static std::optional<Token> |
26 | findConstToRemove(const FunctionDecl *Def, |
27 | const MatchFinder::MatchResult &Result) { |
28 | if (!Def->getReturnType().isLocalConstQualified()) |
29 | return std::nullopt; |
30 | |
31 | // Get the begin location for the function name, including any qualifiers |
32 | // written in the source (for out-of-line declarations). A FunctionDecl's |
33 | // "location" is the start of its name, so, when the name is unqualified, we |
34 | // use `getLocation()`. |
35 | SourceLocation NameBeginLoc = Def->getQualifier() |
36 | ? Def->getQualifierLoc().getBeginLoc() |
37 | : Def->getLocation(); |
38 | // Since either of the locs can be in a macro, use `makeFileCharRange` to be |
39 | // sure that we have a consistent `CharSourceRange`, located entirely in the |
40 | // source file. |
41 | CharSourceRange FileRange = Lexer::makeFileCharRange( |
42 | Range: CharSourceRange::getCharRange(Def->getBeginLoc(), NameBeginLoc), |
43 | SM: *Result.SourceManager, LangOpts: Result.Context->getLangOpts()); |
44 | |
45 | if (FileRange.isInvalid()) |
46 | return std::nullopt; |
47 | |
48 | return utils::lexer::getQualifyingToken( |
49 | TK: tok::kw_const, Range: FileRange, Context: *Result.Context, SM: *Result.SourceManager); |
50 | } |
51 | |
52 | namespace { |
53 | |
54 | AST_MATCHER(QualType, isLocalConstQualified) { |
55 | return Node.isLocalConstQualified(); |
56 | } |
57 | |
58 | AST_MATCHER(QualType, isTypeOfType) { |
59 | return isa<TypeOfType>(Val: Node.getTypePtr()); |
60 | } |
61 | |
62 | AST_MATCHER(QualType, isTypeOfExprType) { |
63 | return isa<TypeOfExprType>(Val: Node.getTypePtr()); |
64 | } |
65 | |
66 | struct CheckResult { |
67 | // Source range of the relevant `const` token in the definition being checked. |
68 | CharSourceRange ConstRange; |
69 | |
70 | // FixItHints associated with the definition being checked. |
71 | llvm::SmallVector<clang::FixItHint, 4> Hints; |
72 | |
73 | // Locations of any declarations that could not be fixed. |
74 | llvm::SmallVector<clang::SourceLocation, 4> DeclLocs; |
75 | }; |
76 | |
77 | } // namespace |
78 | |
79 | // Does the actual work of the check. |
80 | static CheckResult checkDef(const clang::FunctionDecl *Def, |
81 | const MatchFinder::MatchResult &MatchResult) { |
82 | CheckResult Result; |
83 | std::optional<Token> Tok = findConstToRemove(Def, Result: MatchResult); |
84 | if (!Tok) |
85 | return Result; |
86 | |
87 | Result.ConstRange = |
88 | CharSourceRange::getCharRange(B: Tok->getLocation(), E: Tok->getEndLoc()); |
89 | Result.Hints.push_back(Elt: FixItHint::CreateRemoval(RemoveRange: Result.ConstRange)); |
90 | |
91 | // Fix the definition and any visible declarations, but don't warn |
92 | // separately for each declaration. Instead, associate all fixes with the |
93 | // single warning at the definition. |
94 | for (const FunctionDecl *Decl = Def->getPreviousDecl(); Decl != nullptr; |
95 | Decl = Decl->getPreviousDecl()) { |
96 | if (std::optional<Token> T = findConstToRemove(Def: Decl, Result: MatchResult)) |
97 | Result.Hints.push_back(Elt: FixItHint::CreateRemoval( |
98 | RemoveRange: CharSourceRange::getCharRange(B: T->getLocation(), E: T->getEndLoc()))); |
99 | else |
100 | // `getInnerLocStart` gives the start of the return type. |
101 | Result.DeclLocs.push_back(Elt: Decl->getInnerLocStart()); |
102 | } |
103 | return Result; |
104 | } |
105 | |
106 | void ConstReturnTypeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
107 | Options.store(Options&: Opts, LocalName: "IgnoreMacros" , Value: IgnoreMacros); |
108 | } |
109 | |
110 | void ConstReturnTypeCheck::registerMatchers(MatchFinder *Finder) { |
111 | // Find all function definitions for which the return types are `const` |
112 | // qualified, ignoring decltype types. |
113 | auto NonLocalConstType = |
114 | qualType(unless(isLocalConstQualified()), |
115 | anyOf(decltypeType(), autoType(), isTypeOfType(), |
116 | isTypeOfExprType(), substTemplateTypeParmType())); |
117 | Finder->addMatcher( |
118 | NodeMatch: functionDecl( |
119 | returns(InnerMatcher: allOf(isConstQualified(), unless(NonLocalConstType))), |
120 | anyOf(isDefinition(), cxxMethodDecl(isPure())), |
121 | // Overridden functions are not actionable. |
122 | unless(cxxMethodDecl(isOverride()))) |
123 | .bind(ID: "func" ), |
124 | Action: this); |
125 | } |
126 | |
127 | void ConstReturnTypeCheck::check(const MatchFinder::MatchResult &Result) { |
128 | const auto *Def = Result.Nodes.getNodeAs<FunctionDecl>(ID: "func" ); |
129 | // Suppress the check if macros are involved. |
130 | if (IgnoreMacros && |
131 | (Def->getBeginLoc().isMacroID() || Def->getEndLoc().isMacroID())) |
132 | return; |
133 | |
134 | CheckResult CR = checkDef(Def, MatchResult: Result); |
135 | { |
136 | // Clang only supports one in-flight diagnostic at a time. So, delimit the |
137 | // scope of `Diagnostic` to allow further diagnostics after the scope. We |
138 | // use `getInnerLocStart` to get the start of the return type. |
139 | DiagnosticBuilder Diagnostic = |
140 | diag(Def->getInnerLocStart(), |
141 | "return type %0 is 'const'-qualified at the top level, which may " |
142 | "reduce code readability without improving const correctness" ) |
143 | << Def->getReturnType(); |
144 | if (CR.ConstRange.isValid()) |
145 | Diagnostic << CR.ConstRange; |
146 | |
147 | // Do not propose fixes for virtual function. |
148 | const auto *Method = dyn_cast<CXXMethodDecl>(Val: Def); |
149 | if (Method && Method->isVirtual()) |
150 | return; |
151 | |
152 | for (auto &Hint : CR.Hints) |
153 | Diagnostic << Hint; |
154 | } |
155 | for (auto Loc : CR.DeclLocs) |
156 | diag(Loc, Description: "could not transform this declaration" , Level: DiagnosticIDs::Note); |
157 | } |
158 | |
159 | } // namespace clang::tidy::readability |
160 | |