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 "llvm/ADT/Optional.h"
16
17 using namespace clang::ast_matchers;
18
19 namespace clang {
20 namespace tidy {
21 namespace readability {
22
23 // Finds the location of the qualifying `const` token in the `FunctionDecl`'s
24 // return type. Returns `None` when the return type is not `const`-qualified or
25 // `const` does not appear in `Def`'s source, like when the type is an alias or
26 // a macro.
27 static llvm::Optional<Token>
findConstToRemove(const FunctionDecl * Def,const MatchFinder::MatchResult & Result)28 findConstToRemove(const FunctionDecl *Def,
29 const MatchFinder::MatchResult &Result) {
30 if (!Def->getReturnType().isLocalConstQualified())
31 return None;
32
33 // Get the begin location for the function name, including any qualifiers
34 // written in the source (for out-of-line declarations). A FunctionDecl's
35 // "location" is the start of its name, so, when the name is unqualified, we
36 // use `getLocation()`.
37 SourceLocation NameBeginLoc = Def->getQualifier()
38 ? Def->getQualifierLoc().getBeginLoc()
39 : Def->getLocation();
40 // Since either of the locs can be in a macro, use `makeFileCharRange` to be
41 // sure that we have a consistent `CharSourceRange`, located entirely in the
42 // source file.
43 CharSourceRange FileRange = Lexer::makeFileCharRange(
44 CharSourceRange::getCharRange(Def->getBeginLoc(), NameBeginLoc),
45 *Result.SourceManager, Result.Context->getLangOpts());
46
47 if (FileRange.isInvalid())
48 return None;
49
50 return utils::lexer::getQualifyingToken(
51 tok::kw_const, FileRange, *Result.Context, *Result.SourceManager);
52 }
53
54 namespace {
55
56 struct CheckResult {
57 // Source range of the relevant `const` token in the definition being checked.
58 CharSourceRange ConstRange;
59
60 // FixItHints associated with the definition being checked.
61 llvm::SmallVector<clang::FixItHint, 4> Hints;
62
63 // Locations of any declarations that could not be fixed.
64 llvm::SmallVector<clang::SourceLocation, 4> DeclLocs;
65 };
66
67 } // namespace
68
69 // Does the actual work of the check.
checkDef(const clang::FunctionDecl * Def,const MatchFinder::MatchResult & MatchResult)70 static CheckResult checkDef(const clang::FunctionDecl *Def,
71 const MatchFinder::MatchResult &MatchResult) {
72 CheckResult Result;
73 llvm::Optional<Token> Tok = findConstToRemove(Def, MatchResult);
74 if (!Tok)
75 return Result;
76
77 Result.ConstRange =
78 CharSourceRange::getCharRange(Tok->getLocation(), Tok->getEndLoc());
79 Result.Hints.push_back(FixItHint::CreateRemoval(Result.ConstRange));
80
81 // Fix the definition and any visible declarations, but don't warn
82 // separately for each declaration. Instead, associate all fixes with the
83 // single warning at the definition.
84 for (const FunctionDecl *Decl = Def->getPreviousDecl(); Decl != nullptr;
85 Decl = Decl->getPreviousDecl()) {
86 if (llvm::Optional<Token> T = findConstToRemove(Decl, MatchResult))
87 Result.Hints.push_back(FixItHint::CreateRemoval(
88 CharSourceRange::getCharRange(T->getLocation(), T->getEndLoc())));
89 else
90 // `getInnerLocStart` gives the start of the return type.
91 Result.DeclLocs.push_back(Decl->getInnerLocStart());
92 }
93 return Result;
94 }
95
registerMatchers(MatchFinder * Finder)96 void ConstReturnTypeCheck::registerMatchers(MatchFinder *Finder) {
97 // Find all function definitions for which the return types are `const`
98 // qualified.
99 Finder->addMatcher(
100 functionDecl(returns(isConstQualified()), isDefinition()).bind("func"),
101 this);
102 }
103
check(const MatchFinder::MatchResult & Result)104 void ConstReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
105 const auto *Def = Result.Nodes.getNodeAs<FunctionDecl>("func");
106 CheckResult CR = checkDef(Def, Result);
107 {
108 // Clang only supports one in-flight diagnostic at a time. So, delimit the
109 // scope of `Diagnostic` to allow further diagnostics after the scope. We
110 // use `getInnerLocStart` to get the start of the return type.
111 DiagnosticBuilder Diagnostic =
112 diag(Def->getInnerLocStart(),
113 "return type %0 is 'const'-qualified at the top level, which may "
114 "reduce code readability without improving const correctness")
115 << Def->getReturnType();
116 if (CR.ConstRange.isValid())
117 Diagnostic << CR.ConstRange;
118 for (auto &Hint : CR.Hints)
119 Diagnostic << Hint;
120 }
121 for (auto Loc : CR.DeclLocs)
122 diag(Loc, "could not transform this declaration", DiagnosticIDs::Note);
123 }
124
125 } // namespace readability
126 } // namespace tidy
127 } // namespace clang
128