blob: 1647cb1f4c940709f0181eac8b7a1e989419b875 [file] [log] [blame]
//===--- ConstReturnTypeCheck.cpp - clang-tidy ---------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "ConstReturnTypeCheck.h"
#include "../utils/LexerUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/Optional.h"
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace readability {
// Finds the location of the qualifying `const` token in the `FunctionDecl`'s
// return type. Returns `None` when the return type is not `const`-qualified or
// `const` does not appear in `Def`'s source, like when the type is an alias or
// a macro.
static llvm::Optional<Token>
findConstToRemove(const FunctionDecl *Def,
const MatchFinder::MatchResult &Result) {
if (!Def->getReturnType().isLocalConstQualified())
return None;
// Get the begin location for the function name, including any qualifiers
// written in the source (for out-of-line declarations). A FunctionDecl's
// "location" is the start of its name, so, when the name is unqualified, we
// use `getLocation()`.
SourceLocation NameBeginLoc = Def->getQualifier()
? Def->getQualifierLoc().getBeginLoc()
: Def->getLocation();
// Since either of the locs can be in a macro, use `makeFileCharRange` to be
// sure that we have a consistent `CharSourceRange`, located entirely in the
// source file.
CharSourceRange FileRange = Lexer::makeFileCharRange(
CharSourceRange::getCharRange(Def->getBeginLoc(), NameBeginLoc),
*Result.SourceManager, Result.Context->getLangOpts());
if (FileRange.isInvalid())
return None;
return utils::lexer::getQualifyingToken(
tok::kw_const, FileRange, *Result.Context, *Result.SourceManager);
}
namespace {
struct CheckResult {
// Source range of the relevant `const` token in the definition being checked.
CharSourceRange ConstRange;
// FixItHints associated with the definition being checked.
llvm::SmallVector<clang::FixItHint, 4> Hints;
// Locations of any declarations that could not be fixed.
llvm::SmallVector<clang::SourceLocation, 4> DeclLocs;
};
} // namespace
// Does the actual work of the check.
static CheckResult checkDef(const clang::FunctionDecl *Def,
const MatchFinder::MatchResult &MatchResult) {
CheckResult Result;
llvm::Optional<Token> Tok = findConstToRemove(Def, MatchResult);
if (!Tok)
return Result;
Result.ConstRange =
CharSourceRange::getCharRange(Tok->getLocation(), Tok->getEndLoc());
Result.Hints.push_back(FixItHint::CreateRemoval(Result.ConstRange));
// Fix the definition and any visible declarations, but don't warn
// separately for each declaration. Instead, associate all fixes with the
// single warning at the definition.
for (const FunctionDecl *Decl = Def->getPreviousDecl(); Decl != nullptr;
Decl = Decl->getPreviousDecl()) {
if (llvm::Optional<Token> T = findConstToRemove(Decl, MatchResult))
Result.Hints.push_back(FixItHint::CreateRemoval(
CharSourceRange::getCharRange(T->getLocation(), T->getEndLoc())));
else
// `getInnerLocStart` gives the start of the return type.
Result.DeclLocs.push_back(Decl->getInnerLocStart());
}
return Result;
}
void ConstReturnTypeCheck::registerMatchers(MatchFinder *Finder) {
// Find all function definitions for which the return types are `const`
// qualified.
Finder->addMatcher(
functionDecl(returns(isConstQualified()), isDefinition()).bind("func"),
this);
}
void ConstReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Def = Result.Nodes.getNodeAs<FunctionDecl>("func");
CheckResult CR = checkDef(Def, Result);
{
// Clang only supports one in-flight diagnostic at a time. So, delimit the
// scope of `Diagnostic` to allow further diagnostics after the scope. We
// use `getInnerLocStart` to get the start of the return type.
DiagnosticBuilder Diagnostic =
diag(Def->getInnerLocStart(),
"return type %0 is 'const'-qualified at the top level, which may "
"reduce code readability without improving const correctness")
<< Def->getReturnType();
if (CR.ConstRange.isValid())
Diagnostic << CR.ConstRange;
for (auto &Hint : CR.Hints)
Diagnostic << Hint;
}
for (auto Loc : CR.DeclLocs)
diag(Loc, "could not transform this declaration", DiagnosticIDs::Note);
}
} // namespace readability
} // namespace tidy
} // namespace clang