blob: dd7c24d0c7ef717898c935ad2af9ff7ae8a22bb4 [file] [log] [blame]
//===--- 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/Type.h"
namespace clang {
namespace tidy {
namespace utils {
namespace 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 Optional<SourceLocation>
skipLParensBackwards(SourceLocation Start, const ASTContext &Context) {
if (locDangerous(Start))
return None;
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 None;
return Start;
}
static Optional<FixItHint> fixIfNotDangerous(SourceLocation Loc,
StringRef Text) {
if (locDangerous(Loc))
return None;
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 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:
Optional<SourceLocation> IgnoredParens =
skipLParensBackwards(Var.getLocation(), Context);
if (IgnoredParens)
return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier));
return None;
}
llvm_unreachable("Unknown QualifierPolicy enum");
}
static Optional<FixItHint> changePointerItself(const VarDecl &Var,
DeclSpec::TQ Qualifier,
const ASTContext &Context) {
if (locDangerous(Var.getLocation()))
return None;
Optional<SourceLocation> IgnoredParens =
skipLParensBackwards(Var.getLocation(), Context);
if (IgnoredParens)
return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier));
return None;
}
static 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 None;
Optional<SourceLocation> IgnoredParens =
skipLParensBackwards(BeforeStar, Context);
if (IgnoredParens)
return fixIfNotDangerous(*IgnoredParens,
buildQualifier(Qualifier, true));
return None;
}
}
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 None;
}
static 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);
Optional<SourceLocation> IgnoredParens =
skipLParensBackwards(BeforeRef, Context);
if (IgnoredParens)
return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier, true));
return None;
}
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 None;
}
} // namespace fixit
} // namespace utils
} // namespace tidy
} // namespace clang