1 //===--- FixItHintUtils.cpp - clang-tidy-----------------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "FixItHintUtils.h"
10 #include "LexerUtils.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/AST/ExprCXX.h"
13 #include "clang/AST/Type.h"
14 #include "clang/Tooling/FixIt.h"
17 namespace clang::tidy::utils::fixit
{
19 FixItHint
changeVarDeclToReference(const VarDecl
&Var
, ASTContext
&Context
) {
20 SourceLocation AmpLocation
= Var
.getLocation();
21 auto Token
= utils::lexer::getPreviousToken(
22 AmpLocation
, Context
.getSourceManager(), Context
.getLangOpts());
23 if (!Token
.is(tok::unknown
))
24 AmpLocation
= Lexer::getLocForEndOfToken(Token
.getLocation(), 0,
25 Context
.getSourceManager(),
26 Context
.getLangOpts());
27 return FixItHint::CreateInsertion(AmpLocation
, "&");
30 static bool isValueType(const Type
*T
) {
31 return !(isa
<PointerType
>(T
) || isa
<ReferenceType
>(T
) || isa
<ArrayType
>(T
) ||
32 isa
<MemberPointerType
>(T
) || isa
<ObjCObjectPointerType
>(T
));
34 static bool isValueType(QualType QT
) { return isValueType(QT
.getTypePtr()); }
35 static bool isMemberOrFunctionPointer(QualType QT
) {
36 return (QT
->isPointerType() && QT
->isFunctionPointerType()) ||
37 isa
<MemberPointerType
>(QT
.getTypePtr());
40 static bool locDangerous(SourceLocation S
) {
41 return S
.isInvalid() || S
.isMacroID();
44 static std::optional
<SourceLocation
>
45 skipLParensBackwards(SourceLocation Start
, const ASTContext
&Context
) {
46 if (locDangerous(Start
))
49 auto PreviousTokenLParen
= [&Start
, &Context
]() {
51 T
= lexer::getPreviousToken(Start
, Context
.getSourceManager(),
52 Context
.getLangOpts());
53 return T
.is(tok::l_paren
);
56 while (Start
.isValid() && PreviousTokenLParen())
57 Start
= lexer::findPreviousTokenStart(Start
, Context
.getSourceManager(),
58 Context
.getLangOpts());
60 if (locDangerous(Start
))
65 static std::optional
<FixItHint
> fixIfNotDangerous(SourceLocation Loc
,
67 if (locDangerous(Loc
))
69 return FixItHint::CreateInsertion(Loc
, Text
);
72 // Build a string that can be emitted as FixIt with either a space in before
73 // or after the qualifier, either ' const' or 'const '.
74 static std::string
buildQualifier(DeclSpec::TQ Qualifier
,
75 bool WhitespaceBefore
= false) {
77 return (llvm::Twine(' ') + DeclSpec::getSpecifierName(Qualifier
)).str();
78 return (llvm::Twine(DeclSpec::getSpecifierName(Qualifier
)) + " ").str();
81 static std::optional
<FixItHint
> changeValue(const VarDecl
&Var
,
82 DeclSpec::TQ Qualifier
,
83 QualifierTarget QualTarget
,
84 QualifierPolicy QualPolicy
,
85 const ASTContext
&Context
) {
87 case QualifierPolicy::Left
:
88 return fixIfNotDangerous(Var
.getTypeSpecStartLoc(),
89 buildQualifier(Qualifier
));
90 case QualifierPolicy::Right
:
91 std::optional
<SourceLocation
> IgnoredParens
=
92 skipLParensBackwards(Var
.getLocation(), Context
);
95 return fixIfNotDangerous(*IgnoredParens
, buildQualifier(Qualifier
));
98 llvm_unreachable("Unknown QualifierPolicy enum");
101 static std::optional
<FixItHint
> changePointerItself(const VarDecl
&Var
,
102 DeclSpec::TQ Qualifier
,
103 const ASTContext
&Context
) {
104 if (locDangerous(Var
.getLocation()))
107 std::optional
<SourceLocation
> IgnoredParens
=
108 skipLParensBackwards(Var
.getLocation(), Context
);
110 return fixIfNotDangerous(*IgnoredParens
, buildQualifier(Qualifier
));
114 static std::optional
<FixItHint
>
115 changePointer(const VarDecl
&Var
, DeclSpec::TQ Qualifier
, const Type
*Pointee
,
116 QualifierTarget QualTarget
, QualifierPolicy QualPolicy
,
117 const ASTContext
&Context
) {
118 // The pointer itself shall be marked as `const`. This is always to the right
119 // of the '*' or in front of the identifier.
120 if (QualTarget
== QualifierTarget::Value
)
121 return changePointerItself(Var
, Qualifier
, Context
);
123 // Mark the pointee `const` that is a normal value (`int* p = nullptr;`).
124 if (QualTarget
== QualifierTarget::Pointee
&& isValueType(Pointee
)) {
125 // Adding the `const` on the left side is just the beginning of the type
126 // specification. (`const int* p = nullptr;`)
127 if (QualPolicy
== QualifierPolicy::Left
)
128 return fixIfNotDangerous(Var
.getTypeSpecStartLoc(),
129 buildQualifier(Qualifier
));
131 // Adding the `const` on the right side of the value type requires finding
132 // the `*` token and placing the `const` left of it.
133 // (`int const* p = nullptr;`)
134 if (QualPolicy
== QualifierPolicy::Right
) {
135 SourceLocation BeforeStar
= lexer::findPreviousTokenKind(
136 Var
.getLocation(), Context
.getSourceManager(), Context
.getLangOpts(),
138 if (locDangerous(BeforeStar
))
141 std::optional
<SourceLocation
> IgnoredParens
=
142 skipLParensBackwards(BeforeStar
, Context
);
145 return fixIfNotDangerous(*IgnoredParens
,
146 buildQualifier(Qualifier
, true));
151 if (QualTarget
== QualifierTarget::Pointee
&& Pointee
->isPointerType()) {
152 // Adding the `const` to the pointee if the pointee is a pointer
153 // is the same as 'QualPolicy == Right && isValueType(Pointee)'.
154 // The `const` must be left of the last `*` token.
155 // (`int * const* p = nullptr;`)
156 SourceLocation BeforeStar
= lexer::findPreviousTokenKind(
157 Var
.getLocation(), Context
.getSourceManager(), Context
.getLangOpts(),
159 return fixIfNotDangerous(BeforeStar
, buildQualifier(Qualifier
, true));
165 static std::optional
<FixItHint
>
166 changeReferencee(const VarDecl
&Var
, DeclSpec::TQ Qualifier
, QualType Pointee
,
167 QualifierTarget QualTarget
, QualifierPolicy QualPolicy
,
168 const ASTContext
&Context
) {
169 if (QualPolicy
== QualifierPolicy::Left
&& isValueType(Pointee
))
170 return fixIfNotDangerous(Var
.getTypeSpecStartLoc(),
171 buildQualifier(Qualifier
));
173 SourceLocation BeforeRef
= lexer::findPreviousAnyTokenKind(
174 Var
.getLocation(), Context
.getSourceManager(), Context
.getLangOpts(),
175 tok::amp
, tok::ampamp
);
176 std::optional
<SourceLocation
> IgnoredParens
=
177 skipLParensBackwards(BeforeRef
, Context
);
179 return fixIfNotDangerous(*IgnoredParens
, buildQualifier(Qualifier
, true));
184 std::optional
<FixItHint
> addQualifierToVarDecl(const VarDecl
&Var
,
185 const ASTContext
&Context
,
186 DeclSpec::TQ Qualifier
,
187 QualifierTarget QualTarget
,
188 QualifierPolicy QualPolicy
) {
189 assert((QualPolicy
== QualifierPolicy::Left
||
190 QualPolicy
== QualifierPolicy::Right
) &&
191 "Unexpected Insertion Policy");
192 assert((QualTarget
== QualifierTarget::Pointee
||
193 QualTarget
== QualifierTarget::Value
) &&
194 "Unexpected Target");
196 QualType ParenStrippedType
= Var
.getType().IgnoreParens();
197 if (isValueType(ParenStrippedType
))
198 return changeValue(Var
, Qualifier
, QualTarget
, QualPolicy
, Context
);
200 if (ParenStrippedType
->isReferenceType())
201 return changeReferencee(Var
, Qualifier
, Var
.getType()->getPointeeType(),
202 QualTarget
, QualPolicy
, Context
);
204 if (isMemberOrFunctionPointer(ParenStrippedType
))
205 return changePointerItself(Var
, Qualifier
, Context
);
207 if (ParenStrippedType
->isPointerType())
208 return changePointer(Var
, Qualifier
,
209 ParenStrippedType
->getPointeeType().getTypePtr(),
210 QualTarget
, QualPolicy
, Context
);
212 if (ParenStrippedType
->isArrayType()) {
213 const Type
*AT
= ParenStrippedType
->getBaseElementTypeUnsafe();
214 assert(AT
&& "Did not retrieve array element type for an array.");
217 return changeValue(Var
, Qualifier
, QualTarget
, QualPolicy
, Context
);
219 if (AT
->isPointerType())
220 return changePointer(Var
, Qualifier
, AT
->getPointeeType().getTypePtr(),
221 QualTarget
, QualPolicy
, Context
);
227 // Return true if expr needs to be put in parens when it is an argument of a
228 // prefix unary operator, e.g. when it is a binary or ternary operator
230 static bool needParensAfterUnaryOperator(const Expr
&ExprNode
) {
231 if (isa
<clang::BinaryOperator
>(&ExprNode
) ||
232 isa
<clang::ConditionalOperator
>(&ExprNode
)) {
235 if (const auto *Op
= dyn_cast
<CXXOperatorCallExpr
>(&ExprNode
)) {
236 return Op
->getNumArgs() == 2 && Op
->getOperator() != OO_PlusPlus
&&
237 Op
->getOperator() != OO_MinusMinus
&& Op
->getOperator() != OO_Call
&&
238 Op
->getOperator() != OO_Subscript
;
243 // Format a pointer to an expression: prefix with '*' but simplify
244 // when it already begins with '&'. Return empty string on failure.
245 std::string
formatDereference(const Expr
&ExprNode
, const ASTContext
&Context
) {
246 if (const auto *Op
= dyn_cast
<clang::UnaryOperator
>(&ExprNode
)) {
247 if (Op
->getOpcode() == UO_AddrOf
) {
248 // Strip leading '&'.
250 tooling::fixit::getText(*Op
->getSubExpr()->IgnoreParens(), Context
));
253 StringRef Text
= tooling::fixit::getText(ExprNode
, Context
);
256 return std::string();
258 // Remove remaining '->' from overloaded operator call
259 Text
.consume_back("->");
262 if (needParensAfterUnaryOperator(ExprNode
)) {
263 return (llvm::Twine("*(") + Text
+ ")").str();
265 return (llvm::Twine("*") + Text
).str();
268 } // namespace clang::tidy::utils::fixit