diff options
Diffstat (limited to 'clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp')
-rw-r--r-- | clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp | 196 |
1 files changed, 194 insertions, 2 deletions
diff --git a/clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp b/clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp index c30a59e99ba..a9c95e9f381 100644 --- a/clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp +++ b/clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp @@ -9,6 +9,7 @@ #include "FixItHintUtils.h" #include "LexerUtils.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/Type.h" namespace clang { namespace tidy { @@ -26,10 +27,201 @@ FixItHint changeVarDeclToReference(const VarDecl &Var, ASTContext &Context) { return FixItHint::CreateInsertion(AmpLocation, "&"); } -FixItHint changeVarDeclToConst(const VarDecl &Var) { - return FixItHint::CreateInsertion(Var.getTypeSpecStartLoc(), "const "); +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; + } +} + +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 |