blob: c564b5adcbbf8a890bd2137f5198a27a035fc9c7 [file] [log] [blame]
//===--- QualifiedAutoCheck.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 "QualifiedAutoCheck.h"
#include "../utils/LexerUtils.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/SmallVector.h"
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace readability {
namespace {
// FIXME move to ASTMatchers
AST_MATCHER_P(QualType, hasUnqualifiedType,
ast_matchers::internal::Matcher<QualType>, InnerMatcher) {
return InnerMatcher.matches(Node.getUnqualifiedType(), Finder, Builder);
}
enum class Qualifier { Const, Volatile, Restrict };
llvm::Optional<Token> findQualToken(const VarDecl *Decl, Qualifier Qual,
const MatchFinder::MatchResult &Result) {
// 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.
assert((Qual == Qualifier::Const || Qual == Qualifier::Volatile ||
Qual == Qualifier::Restrict) &&
"Invalid Qualifier");
SourceLocation BeginLoc = Decl->getQualifierLoc().getBeginLoc();
if (BeginLoc.isInvalid())
BeginLoc = Decl->getBeginLoc();
SourceLocation EndLoc = Decl->getLocation();
CharSourceRange FileRange = Lexer::makeFileCharRange(
CharSourceRange::getCharRange(BeginLoc, EndLoc), *Result.SourceManager,
Result.Context->getLangOpts());
if (FileRange.isInvalid())
return llvm::None;
tok::TokenKind Tok =
Qual == Qualifier::Const
? tok::kw_const
: Qual == Qualifier::Volatile ? tok::kw_volatile : tok::kw_restrict;
return utils::lexer::getQualifyingToken(Tok, FileRange, *Result.Context,
*Result.SourceManager);
}
llvm::Optional<SourceRange>
getTypeSpecifierLocation(const VarDecl *Var,
const MatchFinder::MatchResult &Result) {
SourceRange TypeSpecifier(
Var->getTypeSpecStartLoc(),
Var->getTypeSpecEndLoc().getLocWithOffset(Lexer::MeasureTokenLength(
Var->getTypeSpecEndLoc(), *Result.SourceManager,
Result.Context->getLangOpts())));
if (TypeSpecifier.getBegin().isMacroID() ||
TypeSpecifier.getEnd().isMacroID())
return llvm::None;
return TypeSpecifier;
}
llvm::Optional<SourceRange> mergeReplacementRange(SourceRange &TypeSpecifier,
const Token &ConstToken) {
if (TypeSpecifier.getBegin().getLocWithOffset(-1) == ConstToken.getEndLoc()) {
TypeSpecifier.setBegin(ConstToken.getLocation());
return llvm::None;
}
if (TypeSpecifier.getEnd().getLocWithOffset(1) == ConstToken.getLocation()) {
TypeSpecifier.setEnd(ConstToken.getEndLoc());
return llvm::None;
}
return SourceRange(ConstToken.getLocation(), ConstToken.getEndLoc());
}
bool isPointerConst(QualType QType) {
QualType Pointee = QType->getPointeeType();
assert(!Pointee.isNull() && "can't have a null Pointee");
return Pointee.isConstQualified();
}
bool isAutoPointerConst(QualType QType) {
QualType Pointee =
cast<AutoType>(QType->getPointeeType().getTypePtr())->desugar();
assert(!Pointee.isNull() && "can't have a null Pointee");
return Pointee.isConstQualified();
}
} // namespace
void QualifiedAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "AddConstToQualified", AddConstToQualified);
}
void QualifiedAutoCheck::registerMatchers(MatchFinder *Finder) {
auto ExplicitSingleVarDecl =
[](const ast_matchers::internal::Matcher<VarDecl> &InnerMatcher,
llvm::StringRef ID) {
return declStmt(
unless(isInTemplateInstantiation()),
hasSingleDecl(
varDecl(unless(isImplicit()), InnerMatcher).bind(ID)));
};
auto ExplicitSingleVarDeclInTemplate =
[](const ast_matchers::internal::Matcher<VarDecl> &InnerMatcher,
llvm::StringRef ID) {
return declStmt(
isInTemplateInstantiation(),
hasSingleDecl(
varDecl(unless(isImplicit()), InnerMatcher).bind(ID)));
};
auto IsBoundToType = refersToType(equalsBoundNode("type"));
auto UnlessFunctionType = unless(hasUnqualifiedDesugaredType(functionType()));
auto IsAutoDeducedToPointer = [](const auto &...InnerMatchers) {
return autoType(hasDeducedType(
hasUnqualifiedDesugaredType(pointerType(pointee(InnerMatchers...)))));
};
Finder->addMatcher(
ExplicitSingleVarDecl(hasType(IsAutoDeducedToPointer(UnlessFunctionType)),
"auto"),
this);
Finder->addMatcher(
ExplicitSingleVarDeclInTemplate(
allOf(hasType(IsAutoDeducedToPointer(
hasUnqualifiedType(qualType().bind("type")),
UnlessFunctionType)),
anyOf(hasAncestor(
functionDecl(hasAnyTemplateArgument(IsBoundToType))),
hasAncestor(classTemplateSpecializationDecl(
hasAnyTemplateArgument(IsBoundToType))))),
"auto"),
this);
if (!AddConstToQualified)
return;
Finder->addMatcher(ExplicitSingleVarDecl(
hasType(pointerType(pointee(autoType()))), "auto_ptr"),
this);
Finder->addMatcher(
ExplicitSingleVarDecl(hasType(lValueReferenceType(pointee(autoType()))),
"auto_ref"),
this);
}
void QualifiedAutoCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto")) {
SourceRange TypeSpecifier;
if (llvm::Optional<SourceRange> TypeSpec =
getTypeSpecifierLocation(Var, Result)) {
TypeSpecifier = *TypeSpec;
} else
return;
llvm::SmallVector<SourceRange, 4> RemoveQualifiersRange;
auto CheckQualifier = [&](bool IsPresent, Qualifier Qual) {
if (IsPresent) {
llvm::Optional<Token> Token = findQualToken(Var, Qual, Result);
if (!Token || Token->getLocation().isMacroID())
return true; // Disregard this VarDecl.
if (llvm::Optional<SourceRange> Result =
mergeReplacementRange(TypeSpecifier, *Token))
RemoveQualifiersRange.push_back(*Result);
}
return false;
};
bool IsLocalConst = Var->getType().isLocalConstQualified();
bool IsLocalVolatile = Var->getType().isLocalVolatileQualified();
bool IsLocalRestrict = Var->getType().isLocalRestrictQualified();
if (CheckQualifier(IsLocalConst, Qualifier::Const) ||
CheckQualifier(IsLocalVolatile, Qualifier::Volatile) ||
CheckQualifier(IsLocalRestrict, Qualifier::Restrict))
return;
// Check for bridging the gap between the asterisk and name.
if (Var->getLocation() == TypeSpecifier.getEnd().getLocWithOffset(1))
TypeSpecifier.setEnd(TypeSpecifier.getEnd().getLocWithOffset(1));
CharSourceRange FixItRange = CharSourceRange::getCharRange(TypeSpecifier);
if (FixItRange.isInvalid())
return;
SourceLocation FixitLoc = FixItRange.getBegin();
for (SourceRange &Range : RemoveQualifiersRange) {
if (Range.getBegin() < FixitLoc)
FixitLoc = Range.getBegin();
}
std::string ReplStr = [&] {
llvm::StringRef PtrConst = isPointerConst(Var->getType()) ? "const " : "";
llvm::StringRef LocalConst = IsLocalConst ? "const " : "";
llvm::StringRef LocalVol = IsLocalVolatile ? "volatile " : "";
llvm::StringRef LocalRestrict = IsLocalRestrict ? "__restrict " : "";
return (PtrConst + "auto *" + LocalConst + LocalVol + LocalRestrict)
.str();
}();
DiagnosticBuilder Diag =
diag(FixitLoc,
"'%select{|const }0%select{|volatile }1%select{|__restrict }2auto "
"%3' can be declared as '%4%3'")
<< IsLocalConst << IsLocalVolatile << IsLocalRestrict << Var->getName()
<< ReplStr;
for (SourceRange &Range : RemoveQualifiersRange) {
Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(Range));
}
Diag << FixItHint::CreateReplacement(FixItRange, ReplStr);
return;
}
if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto_ptr")) {
if (!isPointerConst(Var->getType()))
return; // Pointer isn't const, no need to add const qualifier.
if (!isAutoPointerConst(Var->getType()))
return; // Const isn't wrapped in the auto type, so must be declared
// explicitly.
if (Var->getType().isLocalConstQualified()) {
llvm::Optional<Token> Token =
findQualToken(Var, Qualifier::Const, Result);
if (!Token || Token->getLocation().isMacroID())
return;
}
if (Var->getType().isLocalVolatileQualified()) {
llvm::Optional<Token> Token =
findQualToken(Var, Qualifier::Volatile, Result);
if (!Token || Token->getLocation().isMacroID())
return;
}
if (Var->getType().isLocalRestrictQualified()) {
llvm::Optional<Token> Token =
findQualToken(Var, Qualifier::Restrict, Result);
if (!Token || Token->getLocation().isMacroID())
return;
}
if (llvm::Optional<SourceRange> TypeSpec =
getTypeSpecifierLocation(Var, Result)) {
if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() ||
TypeSpec->getEnd().isMacroID())
return;
SourceLocation InsertPos = TypeSpec->getBegin();
diag(InsertPos,
"'auto *%select{|const }0%select{|volatile }1%2' can be declared as "
"'const auto *%select{|const }0%select{|volatile }1%2'")
<< Var->getType().isLocalConstQualified()
<< Var->getType().isLocalVolatileQualified() << Var->getName()
<< FixItHint::CreateInsertion(InsertPos, "const ");
}
return;
}
if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto_ref")) {
if (!isPointerConst(Var->getType()))
return; // Pointer isn't const, no need to add const qualifier.
if (!isAutoPointerConst(Var->getType()))
// Const isn't wrapped in the auto type, so must be declared explicitly.
return;
if (llvm::Optional<SourceRange> TypeSpec =
getTypeSpecifierLocation(Var, Result)) {
if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() ||
TypeSpec->getEnd().isMacroID())
return;
SourceLocation InsertPos = TypeSpec->getBegin();
diag(InsertPos, "'auto &%0' can be declared as 'const auto &%0'")
<< Var->getName() << FixItHint::CreateInsertion(InsertPos, "const ");
}
return;
}
}
} // namespace readability
} // namespace tidy
} // namespace clang