blob: 46de805ed50857d8f0cf5565ae68d1b1c73cf9c4 [file] [log] [blame]
//===- RedundantVoidArgCheck.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 "RedundantVoidArgCheck.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/Lexer.h"
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace modernize {
namespace {
// Determine if the given QualType is a nullary function or pointer to same.
bool protoTypeHasNoParms(QualType QT) {
if (auto PT = QT->getAs<PointerType>()) {
QT = PT->getPointeeType();
}
if (auto *MPT = QT->getAs<MemberPointerType>()) {
QT = MPT->getPointeeType();
}
if (auto FP = QT->getAs<FunctionProtoType>()) {
return FP->getNumParams() == 0;
}
return false;
}
const char FunctionId[] = "function";
const char TypedefId[] = "typedef";
const char FieldId[] = "field";
const char VarId[] = "var";
const char NamedCastId[] = "named-cast";
const char CStyleCastId[] = "c-style-cast";
const char ExplicitCastId[] = "explicit-cast";
const char LambdaId[] = "lambda";
} // namespace
void RedundantVoidArgCheck::registerMatchers(MatchFinder *Finder) {
if (!getLangOpts().CPlusPlus)
return;
Finder->addMatcher(functionDecl(parameterCountIs(0), unless(isImplicit()),
unless(isInstantiated()), unless(isExternC()))
.bind(FunctionId),
this);
Finder->addMatcher(typedefNameDecl().bind(TypedefId), this);
auto ParenFunctionType = parenType(innerType(functionType()));
auto PointerToFunctionType = pointee(ParenFunctionType);
auto FunctionOrMemberPointer =
anyOf(hasType(pointerType(PointerToFunctionType)),
hasType(memberPointerType(PointerToFunctionType)));
Finder->addMatcher(fieldDecl(FunctionOrMemberPointer).bind(FieldId), this);
Finder->addMatcher(varDecl(FunctionOrMemberPointer).bind(VarId), this);
auto CastDestinationIsFunction =
hasDestinationType(pointsTo(ParenFunctionType));
Finder->addMatcher(
cStyleCastExpr(CastDestinationIsFunction).bind(CStyleCastId), this);
Finder->addMatcher(
cxxStaticCastExpr(CastDestinationIsFunction).bind(NamedCastId), this);
Finder->addMatcher(
cxxReinterpretCastExpr(CastDestinationIsFunction).bind(NamedCastId),
this);
Finder->addMatcher(
cxxConstCastExpr(CastDestinationIsFunction).bind(NamedCastId), this);
Finder->addMatcher(lambdaExpr().bind(LambdaId), this);
}
void RedundantVoidArgCheck::check(const MatchFinder::MatchResult &Result) {
const BoundNodes &Nodes = Result.Nodes;
if (const auto *Function = Nodes.getNodeAs<FunctionDecl>(FunctionId)) {
processFunctionDecl(Result, Function);
} else if (const auto *TypedefName =
Nodes.getNodeAs<TypedefNameDecl>(TypedefId)) {
processTypedefNameDecl(Result, TypedefName);
} else if (const auto *Member = Nodes.getNodeAs<FieldDecl>(FieldId)) {
processFieldDecl(Result, Member);
} else if (const auto *Var = Nodes.getNodeAs<VarDecl>(VarId)) {
processVarDecl(Result, Var);
} else if (const auto *NamedCast =
Nodes.getNodeAs<CXXNamedCastExpr>(NamedCastId)) {
processNamedCastExpr(Result, NamedCast);
} else if (const auto *CStyleCast =
Nodes.getNodeAs<CStyleCastExpr>(CStyleCastId)) {
processExplicitCastExpr(Result, CStyleCast);
} else if (const auto *ExplicitCast =
Nodes.getNodeAs<ExplicitCastExpr>(ExplicitCastId)) {
processExplicitCastExpr(Result, ExplicitCast);
} else if (const auto *Lambda = Nodes.getNodeAs<LambdaExpr>(LambdaId)) {
processLambdaExpr(Result, Lambda);
}
}
void RedundantVoidArgCheck::processFunctionDecl(
const MatchFinder::MatchResult &Result, const FunctionDecl *Function) {
if (Function->isThisDeclarationADefinition()) {
SourceLocation Start = Function->getBeginLoc();
SourceLocation End = Function->getEndLoc();
if (const Stmt *Body = Function->getBody()) {
End = Body->getBeginLoc();
if (End.isMacroID() &&
Result.SourceManager->isAtStartOfImmediateMacroExpansion(End))
End = Result.SourceManager->getExpansionLoc(End);
End = End.getLocWithOffset(-1);
}
removeVoidArgumentTokens(Result, SourceRange(Start, End),
"function definition");
} else {
removeVoidArgumentTokens(Result, Function->getSourceRange(),
"function declaration");
}
}
void RedundantVoidArgCheck::removeVoidArgumentTokens(
const ast_matchers::MatchFinder::MatchResult &Result, SourceRange Range,
StringRef GrammarLocation) {
CharSourceRange CharRange =
Lexer::makeFileCharRange(CharSourceRange::getTokenRange(Range),
*Result.SourceManager, getLangOpts());
std::string DeclText =
Lexer::getSourceText(CharRange, *Result.SourceManager, getLangOpts())
.str();
Lexer PrototypeLexer(CharRange.getBegin(), getLangOpts(), DeclText.data(),
DeclText.data(), DeclText.data() + DeclText.size());
enum TokenState {
NothingYet,
SawLeftParen,
SawVoid,
};
TokenState State = NothingYet;
Token VoidToken;
Token ProtoToken;
std::string Diagnostic =
("redundant void argument list in " + GrammarLocation).str();
while (!PrototypeLexer.LexFromRawLexer(ProtoToken)) {
switch (State) {
case NothingYet:
if (ProtoToken.is(tok::TokenKind::l_paren)) {
State = SawLeftParen;
}
break;
case SawLeftParen:
if (ProtoToken.is(tok::TokenKind::raw_identifier) &&
ProtoToken.getRawIdentifier() == "void") {
State = SawVoid;
VoidToken = ProtoToken;
} else if (ProtoToken.is(tok::TokenKind::l_paren)) {
State = SawLeftParen;
} else {
State = NothingYet;
}
break;
case SawVoid:
State = NothingYet;
if (ProtoToken.is(tok::TokenKind::r_paren)) {
removeVoidToken(VoidToken, Diagnostic);
} else if (ProtoToken.is(tok::TokenKind::l_paren)) {
State = SawLeftParen;
}
break;
}
}
if (State == SawVoid && ProtoToken.is(tok::TokenKind::r_paren)) {
removeVoidToken(VoidToken, Diagnostic);
}
}
void RedundantVoidArgCheck::removeVoidToken(Token VoidToken,
StringRef Diagnostic) {
SourceLocation VoidLoc = VoidToken.getLocation();
diag(VoidLoc, Diagnostic) << FixItHint::CreateRemoval(VoidLoc);
}
void RedundantVoidArgCheck::processTypedefNameDecl(
const MatchFinder::MatchResult &Result,
const TypedefNameDecl *TypedefName) {
if (protoTypeHasNoParms(TypedefName->getUnderlyingType())) {
removeVoidArgumentTokens(Result, TypedefName->getSourceRange(),
isa<TypedefDecl>(TypedefName) ? "typedef"
: "type alias");
}
}
void RedundantVoidArgCheck::processFieldDecl(
const MatchFinder::MatchResult &Result, const FieldDecl *Member) {
if (protoTypeHasNoParms(Member->getType())) {
removeVoidArgumentTokens(Result, Member->getSourceRange(),
"field declaration");
}
}
void RedundantVoidArgCheck::processVarDecl(
const MatchFinder::MatchResult &Result, const VarDecl *Var) {
if (protoTypeHasNoParms(Var->getType())) {
SourceLocation Begin = Var->getBeginLoc();
if (Var->hasInit()) {
SourceLocation InitStart =
Result.SourceManager->getExpansionLoc(Var->getInit()->getBeginLoc())
.getLocWithOffset(-1);
removeVoidArgumentTokens(Result, SourceRange(Begin, InitStart),
"variable declaration with initializer");
} else {
removeVoidArgumentTokens(Result, Var->getSourceRange(),
"variable declaration");
}
}
}
void RedundantVoidArgCheck::processNamedCastExpr(
const MatchFinder::MatchResult &Result, const CXXNamedCastExpr *NamedCast) {
if (protoTypeHasNoParms(NamedCast->getTypeAsWritten())) {
removeVoidArgumentTokens(
Result,
NamedCast->getTypeInfoAsWritten()->getTypeLoc().getSourceRange(),
"named cast");
}
}
void RedundantVoidArgCheck::processExplicitCastExpr(
const MatchFinder::MatchResult &Result,
const ExplicitCastExpr *ExplicitCast) {
if (protoTypeHasNoParms(ExplicitCast->getTypeAsWritten())) {
removeVoidArgumentTokens(Result, ExplicitCast->getSourceRange(),
"cast expression");
}
}
void RedundantVoidArgCheck::processLambdaExpr(
const MatchFinder::MatchResult &Result, const LambdaExpr *Lambda) {
if (Lambda->getLambdaClass()->getLambdaCallOperator()->getNumParams() == 0 &&
Lambda->hasExplicitParameters()) {
SourceManager *SM = Result.SourceManager;
TypeLoc TL = Lambda->getLambdaClass()->getLambdaTypeInfo()->getTypeLoc();
removeVoidArgumentTokens(Result,
{SM->getSpellingLoc(TL.getBeginLoc()),
SM->getSpellingLoc(TL.getEndLoc())},
"lambda expression");
}
}
} // namespace modernize
} // namespace tidy
} // namespace clang