//===--- 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(T) || isa(T) || isa(T) || isa(T) || isa(T)); } static bool isValueType(QualType QT) { return isValueType(QT.getTypePtr()); } static bool isMemberOrFunctionPointer(QualType QT) { return (QT->isPointerType() && QT->isFunctionPointerType()) || isa(QT.getTypePtr()); } static bool locDangerous(SourceLocation S) { return S.isInvalid() || S.isMacroID(); } static Optional 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 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 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 IgnoredParens = skipLParensBackwards(Var.getLocation(), Context); if (IgnoredParens) return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier)); return None; } llvm_unreachable("Unknown QualifierPolicy enum"); } static Optional changePointerItself(const VarDecl &Var, DeclSpec::TQ Qualifier, const ASTContext &Context) { if (locDangerous(Var.getLocation())) return None; Optional IgnoredParens = skipLParensBackwards(Var.getLocation(), Context); if (IgnoredParens) return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier)); return None; } static Optional 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 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 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 IgnoredParens = skipLParensBackwards(BeforeRef, Context); if (IgnoredParens) return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier, true)); return None; } Optional 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