//===--- 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 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::getConstQualifyingToken(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 Hints; // Locations of any declarations that could not be fixed. llvm::SmallVector DeclLocs; }; } // namespace // Does the actual work of the check. static CheckResult checkDef(const clang::FunctionDecl *Def, const MatchFinder::MatchResult &MatchResult) { CheckResult Result; llvm::Optional 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 // seperately 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 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("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