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
17using namespace clang::ast_matchers;
18
19namespace 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.
25static std::optional<Token>
26findConstToRemove(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
52namespace {
53
54AST_MATCHER(QualType, isLocalConstQualified) {
55 return Node.isLocalConstQualified();
56}
57
58AST_MATCHER(QualType, isTypeOfType) {
59 return isa<TypeOfType>(Val: Node.getTypePtr());
60}
61
62AST_MATCHER(QualType, isTypeOfExprType) {
63 return isa<TypeOfExprType>(Val: Node.getTypePtr());
64}
65
66struct 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.
80static 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
106void ConstReturnTypeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
107 Options.store(Options&: Opts, LocalName: "IgnoreMacros", Value: IgnoreMacros);
108}
109
110void 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
127void 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

source code of clang-tools-extra/clang-tidy/readability/ConstReturnTypeCheck.cpp