302 lines
11 KiB
C++
302 lines
11 KiB
C++
//===--- FixItHintUtils.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 "FixItHintUtils.h"
|
|
#include "LexerUtils.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/AST/ExprCXX.h"
|
|
#include "clang/AST/Type.h"
|
|
#include "clang/Tooling/FixIt.h"
|
|
#include <optional>
|
|
|
|
namespace clang::tidy::utils::fixit {
|
|
|
|
FixItHint changeVarDeclToReference(const VarDecl &Var, ASTContext &Context) {
|
|
SourceLocation AmpLocation = Var.getLocation();
|
|
auto Token = utils::lexer::getPreviousToken(
|
|
AmpLocation, Context.getSourceManager(), Context.getLangOpts());
|
|
if (!Token.is(tok::unknown))
|
|
AmpLocation = Lexer::getLocForEndOfToken(Token.getLocation(), 0,
|
|
Context.getSourceManager(),
|
|
Context.getLangOpts());
|
|
return FixItHint::CreateInsertion(AmpLocation, "&");
|
|
}
|
|
|
|
static bool isValueType(const Type *T) {
|
|
return !(isa<PointerType>(T) || isa<ReferenceType>(T) || isa<ArrayType>(T) ||
|
|
isa<MemberPointerType>(T) || isa<ObjCObjectPointerType>(T));
|
|
}
|
|
static bool isValueType(QualType QT) { return isValueType(QT.getTypePtr()); }
|
|
static bool isMemberOrFunctionPointer(QualType QT) {
|
|
return (QT->isPointerType() && QT->isFunctionPointerType()) ||
|
|
isa<MemberPointerType>(QT.getTypePtr());
|
|
}
|
|
|
|
static bool locDangerous(SourceLocation S) {
|
|
return S.isInvalid() || S.isMacroID();
|
|
}
|
|
|
|
static std::optional<SourceLocation>
|
|
skipLParensBackwards(SourceLocation Start, const ASTContext &Context) {
|
|
if (locDangerous(Start))
|
|
return std::nullopt;
|
|
|
|
auto PreviousTokenLParen = [&Start, &Context]() {
|
|
Token T;
|
|
T = lexer::getPreviousToken(Start, Context.getSourceManager(),
|
|
Context.getLangOpts());
|
|
return T.is(tok::l_paren);
|
|
};
|
|
|
|
while (Start.isValid() && PreviousTokenLParen())
|
|
Start = lexer::findPreviousTokenStart(Start, Context.getSourceManager(),
|
|
Context.getLangOpts());
|
|
|
|
if (locDangerous(Start))
|
|
return std::nullopt;
|
|
return Start;
|
|
}
|
|
|
|
static std::optional<FixItHint> fixIfNotDangerous(SourceLocation Loc,
|
|
StringRef Text) {
|
|
if (locDangerous(Loc))
|
|
return std::nullopt;
|
|
return FixItHint::CreateInsertion(Loc, Text);
|
|
}
|
|
|
|
// Build a string that can be emitted as FixIt with either a space in before
|
|
// or after the qualifier, either ' const' or 'const '.
|
|
static std::string buildQualifier(DeclSpec::TQ Qualifier,
|
|
bool WhitespaceBefore = false) {
|
|
if (WhitespaceBefore)
|
|
return (llvm::Twine(' ') + DeclSpec::getSpecifierName(Qualifier)).str();
|
|
return (llvm::Twine(DeclSpec::getSpecifierName(Qualifier)) + " ").str();
|
|
}
|
|
|
|
static std::optional<FixItHint> changeValue(const VarDecl &Var,
|
|
DeclSpec::TQ Qualifier,
|
|
QualifierTarget QualTarget,
|
|
QualifierPolicy QualPolicy,
|
|
const ASTContext &Context) {
|
|
switch (QualPolicy) {
|
|
case QualifierPolicy::Left:
|
|
return fixIfNotDangerous(Var.getTypeSpecStartLoc(),
|
|
buildQualifier(Qualifier));
|
|
case QualifierPolicy::Right:
|
|
std::optional<SourceLocation> IgnoredParens =
|
|
skipLParensBackwards(Var.getLocation(), Context);
|
|
|
|
if (IgnoredParens)
|
|
return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier));
|
|
return std::nullopt;
|
|
}
|
|
llvm_unreachable("Unknown QualifierPolicy enum");
|
|
}
|
|
|
|
static std::optional<FixItHint> changePointerItself(const VarDecl &Var,
|
|
DeclSpec::TQ Qualifier,
|
|
const ASTContext &Context) {
|
|
if (locDangerous(Var.getLocation()))
|
|
return std::nullopt;
|
|
|
|
std::optional<SourceLocation> IgnoredParens =
|
|
skipLParensBackwards(Var.getLocation(), Context);
|
|
if (IgnoredParens)
|
|
return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier));
|
|
return std::nullopt;
|
|
}
|
|
|
|
static std::optional<FixItHint>
|
|
changePointer(const VarDecl &Var, DeclSpec::TQ Qualifier, const Type *Pointee,
|
|
QualifierTarget QualTarget, QualifierPolicy QualPolicy,
|
|
const ASTContext &Context) {
|
|
// The pointer itself shall be marked as `const`. This is always to the right
|
|
// of the '*' or in front of the identifier.
|
|
if (QualTarget == QualifierTarget::Value)
|
|
return changePointerItself(Var, Qualifier, Context);
|
|
|
|
// Mark the pointee `const` that is a normal value (`int* p = nullptr;`).
|
|
if (QualTarget == QualifierTarget::Pointee && isValueType(Pointee)) {
|
|
// Adding the `const` on the left side is just the beginning of the type
|
|
// specification. (`const int* p = nullptr;`)
|
|
if (QualPolicy == QualifierPolicy::Left)
|
|
return fixIfNotDangerous(Var.getTypeSpecStartLoc(),
|
|
buildQualifier(Qualifier));
|
|
|
|
// Adding the `const` on the right side of the value type requires finding
|
|
// the `*` token and placing the `const` left of it.
|
|
// (`int const* p = nullptr;`)
|
|
if (QualPolicy == QualifierPolicy::Right) {
|
|
SourceLocation BeforeStar = lexer::findPreviousTokenKind(
|
|
Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(),
|
|
tok::star);
|
|
if (locDangerous(BeforeStar))
|
|
return std::nullopt;
|
|
|
|
std::optional<SourceLocation> IgnoredParens =
|
|
skipLParensBackwards(BeforeStar, Context);
|
|
|
|
if (IgnoredParens)
|
|
return fixIfNotDangerous(*IgnoredParens,
|
|
buildQualifier(Qualifier, true));
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
if (QualTarget == QualifierTarget::Pointee && Pointee->isPointerType()) {
|
|
// Adding the `const` to the pointee if the pointee is a pointer
|
|
// is the same as 'QualPolicy == Right && isValueType(Pointee)'.
|
|
// The `const` must be left of the last `*` token.
|
|
// (`int * const* p = nullptr;`)
|
|
SourceLocation BeforeStar = lexer::findPreviousTokenKind(
|
|
Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(),
|
|
tok::star);
|
|
return fixIfNotDangerous(BeforeStar, buildQualifier(Qualifier, true));
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
static std::optional<FixItHint>
|
|
changeReferencee(const VarDecl &Var, DeclSpec::TQ Qualifier, QualType Pointee,
|
|
QualifierTarget QualTarget, QualifierPolicy QualPolicy,
|
|
const ASTContext &Context) {
|
|
if (QualPolicy == QualifierPolicy::Left && isValueType(Pointee))
|
|
return fixIfNotDangerous(Var.getTypeSpecStartLoc(),
|
|
buildQualifier(Qualifier));
|
|
|
|
SourceLocation BeforeRef = lexer::findPreviousAnyTokenKind(
|
|
Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(),
|
|
tok::amp, tok::ampamp);
|
|
std::optional<SourceLocation> IgnoredParens =
|
|
skipLParensBackwards(BeforeRef, Context);
|
|
if (IgnoredParens)
|
|
return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier, true));
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<FixItHint> addQualifierToVarDecl(const VarDecl &Var,
|
|
const ASTContext &Context,
|
|
DeclSpec::TQ Qualifier,
|
|
QualifierTarget QualTarget,
|
|
QualifierPolicy QualPolicy) {
|
|
assert((QualPolicy == QualifierPolicy::Left ||
|
|
QualPolicy == QualifierPolicy::Right) &&
|
|
"Unexpected Insertion Policy");
|
|
assert((QualTarget == QualifierTarget::Pointee ||
|
|
QualTarget == QualifierTarget::Value) &&
|
|
"Unexpected Target");
|
|
|
|
QualType ParenStrippedType = Var.getType().IgnoreParens();
|
|
if (isValueType(ParenStrippedType))
|
|
return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context);
|
|
|
|
if (ParenStrippedType->isReferenceType())
|
|
return changeReferencee(Var, Qualifier, Var.getType()->getPointeeType(),
|
|
QualTarget, QualPolicy, Context);
|
|
|
|
if (isMemberOrFunctionPointer(ParenStrippedType))
|
|
return changePointerItself(Var, Qualifier, Context);
|
|
|
|
if (ParenStrippedType->isPointerType())
|
|
return changePointer(Var, Qualifier,
|
|
ParenStrippedType->getPointeeType().getTypePtr(),
|
|
QualTarget, QualPolicy, Context);
|
|
|
|
if (ParenStrippedType->isArrayType()) {
|
|
const Type *AT = ParenStrippedType->getBaseElementTypeUnsafe();
|
|
assert(AT && "Did not retrieve array element type for an array.");
|
|
|
|
if (isValueType(AT))
|
|
return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context);
|
|
|
|
if (AT->isPointerType())
|
|
return changePointer(Var, Qualifier, AT->getPointeeType().getTypePtr(),
|
|
QualTarget, QualPolicy, Context);
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
bool areParensNeededForStatement(const Stmt &Node) {
|
|
if (isa<ParenExpr>(&Node))
|
|
return false;
|
|
|
|
if (isa<clang::BinaryOperator>(&Node) || isa<UnaryOperator>(&Node))
|
|
return true;
|
|
|
|
if (isa<clang::ConditionalOperator>(&Node) ||
|
|
isa<BinaryConditionalOperator>(&Node))
|
|
return true;
|
|
|
|
if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(&Node)) {
|
|
switch (Op->getOperator()) {
|
|
case OO_PlusPlus:
|
|
[[fallthrough]];
|
|
case OO_MinusMinus:
|
|
return Op->getNumArgs() != 2;
|
|
case OO_Call:
|
|
[[fallthrough]];
|
|
case OO_Subscript:
|
|
[[fallthrough]];
|
|
case OO_Arrow:
|
|
return false;
|
|
default:
|
|
return true;
|
|
};
|
|
}
|
|
|
|
if (isa<CStyleCastExpr>(&Node))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Return true if expr needs to be put in parens when it is an argument of a
|
|
// prefix unary operator, e.g. when it is a binary or ternary operator
|
|
// syntactically.
|
|
static bool needParensAfterUnaryOperator(const Expr &ExprNode) {
|
|
if (isa<clang::BinaryOperator>(&ExprNode) ||
|
|
isa<clang::ConditionalOperator>(&ExprNode)) {
|
|
return true;
|
|
}
|
|
if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(&ExprNode)) {
|
|
return Op->getNumArgs() == 2 && Op->getOperator() != OO_PlusPlus &&
|
|
Op->getOperator() != OO_MinusMinus && Op->getOperator() != OO_Call &&
|
|
Op->getOperator() != OO_Subscript;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Format a pointer to an expression: prefix with '*' but simplify
|
|
// when it already begins with '&'. Return empty string on failure.
|
|
std::string formatDereference(const Expr &ExprNode, const ASTContext &Context) {
|
|
if (const auto *Op = dyn_cast<clang::UnaryOperator>(&ExprNode)) {
|
|
if (Op->getOpcode() == UO_AddrOf) {
|
|
// Strip leading '&'.
|
|
return std::string(
|
|
tooling::fixit::getText(*Op->getSubExpr()->IgnoreParens(), Context));
|
|
}
|
|
}
|
|
StringRef Text = tooling::fixit::getText(ExprNode, Context);
|
|
|
|
if (Text.empty())
|
|
return {};
|
|
|
|
// Remove remaining '->' from overloaded operator call
|
|
Text.consume_back("->");
|
|
|
|
// Add leading '*'.
|
|
if (needParensAfterUnaryOperator(ExprNode)) {
|
|
return (llvm::Twine("*(") + Text + ")").str();
|
|
}
|
|
return (llvm::Twine("*") + Text).str();
|
|
}
|
|
|
|
} // namespace clang::tidy::utils::fixit
|