clang-tools 22.0.0git
FixItHintUtils.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
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
6//
7//===----------------------------------------------------------------------===//
8
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/Sema/DeclSpec.h"
15#include "clang/Tooling/FixIt.h"
16#include <optional>
17
19
20FixItHint changeVarDeclToReference(const VarDecl &Var, ASTContext &Context) {
21 SourceLocation AmpLocation = Var.getLocation();
23 AmpLocation, Context.getSourceManager(), Context.getLangOpts());
24
25 // For parameter packs the '&' must go before the '...' token
26 if (Token.is(tok::ellipsis))
27 return FixItHint::CreateInsertion(Token.getLocation(), "&");
28
29 if (!Token.is(tok::unknown))
30 AmpLocation = Lexer::getLocForEndOfToken(Token.getLocation(), 0,
31 Context.getSourceManager(),
32 Context.getLangOpts());
33 return FixItHint::CreateInsertion(AmpLocation, "&");
34}
35
36static bool isValueType(const Type *T) {
37 return !(isa<PointerType>(T) || isa<ReferenceType>(T) || isa<ArrayType>(T) ||
38 isa<MemberPointerType>(T) || isa<ObjCObjectPointerType>(T));
39}
40static bool isValueType(QualType QT) { return isValueType(QT.getTypePtr()); }
41static bool isMemberOrFunctionPointer(QualType QT) {
42 return (QT->isPointerType() && QT->isFunctionPointerType()) ||
43 isa<MemberPointerType>(QT.getTypePtr());
44}
45
46static bool locDangerous(SourceLocation S) {
47 return S.isInvalid() || S.isMacroID();
48}
49
50static std::optional<SourceLocation>
51skipLParensBackwards(SourceLocation Start, const ASTContext &Context) {
52 if (locDangerous(Start))
53 return std::nullopt;
54
55 auto PreviousTokenLParen = [&Start, &Context]() {
56 Token T;
57 T = lexer::getPreviousToken(Start, Context.getSourceManager(),
58 Context.getLangOpts());
59 return T.is(tok::l_paren);
60 };
61
62 while (Start.isValid() && PreviousTokenLParen())
63 Start = lexer::findPreviousTokenStart(Start, Context.getSourceManager(),
64 Context.getLangOpts());
65
66 if (locDangerous(Start))
67 return std::nullopt;
68 return Start;
69}
70
71static std::optional<FixItHint> fixIfNotDangerous(SourceLocation Loc,
72 StringRef Text) {
73 if (locDangerous(Loc))
74 return std::nullopt;
75 return FixItHint::CreateInsertion(Loc, Text);
76}
77
78// Build a string that can be emitted as FixIt with either a space in before
79// or after the qualifier, either ' const' or 'const '.
80static std::string buildQualifier(Qualifiers::TQ Qualifier,
81 bool WhitespaceBefore = false) {
82 if (WhitespaceBefore)
83 return (llvm::Twine(' ') + Qualifiers::fromCVRMask(Qualifier).getAsString())
84 .str();
85 return (llvm::Twine(Qualifiers::fromCVRMask(Qualifier).getAsString()) + " ")
86 .str();
87}
88
89static std::optional<FixItHint> changeValue(const VarDecl &Var,
90 Qualifiers::TQ Qualifier,
91 QualifierTarget QualTarget,
92 QualifierPolicy QualPolicy,
93 const ASTContext &Context) {
94 switch (QualPolicy) {
96 return fixIfNotDangerous(Var.getTypeSpecStartLoc(),
97 buildQualifier(Qualifier));
99 std::optional<SourceLocation> IgnoredParens =
100 skipLParensBackwards(Var.getLocation(), Context);
101
102 if (IgnoredParens)
103 return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier));
104 return std::nullopt;
105 }
106 llvm_unreachable("Unknown QualifierPolicy enum");
107}
108
109static std::optional<FixItHint> changePointerItself(const VarDecl &Var,
110 Qualifiers::TQ Qualifier,
111 const ASTContext &Context) {
112 if (locDangerous(Var.getLocation()))
113 return std::nullopt;
114
115 std::optional<SourceLocation> IgnoredParens =
116 skipLParensBackwards(Var.getLocation(), Context);
117 if (IgnoredParens)
118 return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier));
119 return std::nullopt;
120}
121
122static std::optional<FixItHint>
123changePointer(const VarDecl &Var, Qualifiers::TQ Qualifier, const Type *Pointee,
124 QualifierTarget QualTarget, QualifierPolicy QualPolicy,
125 const ASTContext &Context) {
126 // The pointer itself shall be marked as `const`. This is always to the right
127 // of the '*' or in front of the identifier.
128 if (QualTarget == QualifierTarget::Value)
129 return changePointerItself(Var, Qualifier, Context);
130
131 // Mark the pointee `const` that is a normal value (`int* p = nullptr;`).
132 if (QualTarget == QualifierTarget::Pointee && isValueType(Pointee)) {
133 // Adding the `const` on the left side is just the beginning of the type
134 // specification. (`const int* p = nullptr;`)
135 if (QualPolicy == QualifierPolicy::Left)
136 return fixIfNotDangerous(Var.getTypeSpecStartLoc(),
137 buildQualifier(Qualifier));
138
139 // Adding the `const` on the right side of the value type requires finding
140 // the `*` token and placing the `const` left of it.
141 // (`int const* p = nullptr;`)
142 if (QualPolicy == QualifierPolicy::Right) {
143 const SourceLocation BeforeStar = lexer::findPreviousTokenKind(
144 Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(),
145 tok::star);
146 if (locDangerous(BeforeStar))
147 return std::nullopt;
148
149 std::optional<SourceLocation> IgnoredParens =
150 skipLParensBackwards(BeforeStar, Context);
151
152 if (IgnoredParens)
153 return fixIfNotDangerous(*IgnoredParens,
154 buildQualifier(Qualifier, true));
155 return std::nullopt;
156 }
157 }
158
159 if (QualTarget == QualifierTarget::Pointee && Pointee->isPointerType()) {
160 // Adding the `const` to the pointee if the pointee is a pointer
161 // is the same as 'QualPolicy == Right && isValueType(Pointee)'.
162 // The `const` must be left of the last `*` token.
163 // (`int * const* p = nullptr;`)
164 const SourceLocation BeforeStar = lexer::findPreviousTokenKind(
165 Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(),
166 tok::star);
167 return fixIfNotDangerous(BeforeStar, buildQualifier(Qualifier, true));
168 }
169
170 return std::nullopt;
171}
172
173static std::optional<FixItHint>
174changeReferencee(const VarDecl &Var, Qualifiers::TQ Qualifier, QualType Pointee,
175 QualifierTarget QualTarget, QualifierPolicy QualPolicy,
176 const ASTContext &Context) {
177 if (QualPolicy == QualifierPolicy::Left && isValueType(Pointee))
178 return fixIfNotDangerous(Var.getTypeSpecStartLoc(),
179 buildQualifier(Qualifier));
180
181 const SourceLocation BeforeRef = lexer::findPreviousAnyTokenKind(
182 Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(),
183 tok::amp, tok::ampamp);
184 std::optional<SourceLocation> IgnoredParens =
185 skipLParensBackwards(BeforeRef, Context);
186 if (IgnoredParens)
187 return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier, true));
188
189 return std::nullopt;
190}
191
192std::optional<FixItHint> addQualifierToVarDecl(const VarDecl &Var,
193 const ASTContext &Context,
194 Qualifiers::TQ Qualifier,
195 QualifierTarget QualTarget,
196 QualifierPolicy QualPolicy) {
197 assert((QualPolicy == QualifierPolicy::Left ||
198 QualPolicy == QualifierPolicy::Right) &&
199 "Unexpected Insertion Policy");
200 assert((QualTarget == QualifierTarget::Pointee ||
201 QualTarget == QualifierTarget::Value) &&
202 "Unexpected Target");
203
204 const QualType ParenStrippedType = Var.getType().IgnoreParens();
205 if (isValueType(ParenStrippedType))
206 return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context);
207
208 if (ParenStrippedType->isReferenceType())
209 return changeReferencee(Var, Qualifier, Var.getType()->getPointeeType(),
210 QualTarget, QualPolicy, Context);
211
212 if (isMemberOrFunctionPointer(ParenStrippedType))
213 return changePointerItself(Var, Qualifier, Context);
214
215 if (ParenStrippedType->isPointerType())
216 return changePointer(Var, Qualifier,
217 ParenStrippedType->getPointeeType().getTypePtr(),
218 QualTarget, QualPolicy, Context);
219
220 if (ParenStrippedType->isArrayType()) {
221 const Type *AT = ParenStrippedType->getBaseElementTypeUnsafe();
222 assert(AT && "Did not retrieve array element type for an array.");
223
224 if (isValueType(AT))
225 return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context);
226
227 if (AT->isPointerType())
228 return changePointer(Var, Qualifier, AT->getPointeeType().getTypePtr(),
229 QualTarget, QualPolicy, Context);
230 }
231
232 return std::nullopt;
233}
234
235bool areParensNeededForStatement(const Stmt &Node) {
236 if (isa<ParenExpr>(&Node))
237 return false;
238
239 if (isa<clang::BinaryOperator>(&Node) || isa<UnaryOperator>(&Node))
240 return true;
241
242 if (isa<clang::ConditionalOperator>(&Node) ||
243 isa<BinaryConditionalOperator>(&Node))
244 return true;
245
246 if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(&Node)) {
247 switch (Op->getOperator()) {
248 case OO_PlusPlus:
249 [[fallthrough]];
250 case OO_MinusMinus:
251 return Op->getNumArgs() != 2;
252 case OO_Call:
253 [[fallthrough]];
254 case OO_Subscript:
255 [[fallthrough]];
256 case OO_Arrow:
257 return false;
258 default:
259 return true;
260 };
261 }
262
263 if (isa<CStyleCastExpr>(&Node))
264 return true;
265
266 return false;
267}
268
269// Return true if expr needs to be put in parens when it is an argument of a
270// prefix unary operator, e.g. when it is a binary or ternary operator
271// syntactically.
272static bool needParensAfterUnaryOperator(const Expr &ExprNode) {
273 if (isa<clang::BinaryOperator>(&ExprNode) ||
274 isa<clang::ConditionalOperator>(&ExprNode)) {
275 return true;
276 }
277 if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(&ExprNode)) {
278 return Op->getNumArgs() == 2 && Op->getOperator() != OO_PlusPlus &&
279 Op->getOperator() != OO_MinusMinus && Op->getOperator() != OO_Call &&
280 Op->getOperator() != OO_Subscript;
281 }
282 return false;
283}
284
285// Format a pointer to an expression: prefix with '*' but simplify
286// when it already begins with '&'. Return empty string on failure.
287std::string formatDereference(const Expr &ExprNode, const ASTContext &Context) {
288 if (const auto *Op = dyn_cast<clang::UnaryOperator>(&ExprNode)) {
289 if (Op->getOpcode() == UO_AddrOf) {
290 // Strip leading '&'.
291 return std::string(
292 tooling::fixit::getText(*Op->getSubExpr()->IgnoreParens(), Context));
293 }
294 }
295 StringRef Text = tooling::fixit::getText(ExprNode, Context);
296
297 if (Text.empty())
298 return {};
299
300 // Remove remaining '->' from overloaded operator call
301 Text.consume_back("->");
302
303 // Add leading '*'.
304 if (needParensAfterUnaryOperator(ExprNode)) {
305 return (llvm::Twine("*(") + Text + ")").str();
306 }
307 return (llvm::Twine("*") + Text).str();
308}
309
310} // namespace clang::tidy::utils::fixit
static std::string buildQualifier(Qualifiers::TQ Qualifier, bool WhitespaceBefore=false)
bool areParensNeededForStatement(const Stmt &Node)
static std::optional< FixItHint > changePointer(const VarDecl &Var, Qualifiers::TQ Qualifier, const Type *Pointee, QualifierTarget QualTarget, QualifierPolicy QualPolicy, const ASTContext &Context)
static std::optional< FixItHint > changePointerItself(const VarDecl &Var, Qualifiers::TQ Qualifier, const ASTContext &Context)
static std::optional< FixItHint > fixIfNotDangerous(SourceLocation Loc, StringRef Text)
static std::optional< FixItHint > changeValue(const VarDecl &Var, Qualifiers::TQ Qualifier, QualifierTarget QualTarget, QualifierPolicy QualPolicy, const ASTContext &Context)
static bool isMemberOrFunctionPointer(QualType QT)
QualifierTarget
This enum defines which entity is the target for adding the qualifier. This makes only a difference f...
@ Value
Transforming a pointer attaches to the pointee and not the pointer itself. For references and normal ...
static std::optional< FixItHint > changeReferencee(const VarDecl &Var, Qualifiers::TQ Qualifier, QualType Pointee, QualifierTarget QualTarget, QualifierPolicy QualPolicy, const ASTContext &Context)
QualifierPolicy
This enum defines where the qualifier shall be preferably added.
FixItHint changeVarDeclToReference(const VarDecl &Var, ASTContext &Context)
Creates fix to make VarDecl a reference by adding &.
std::optional< FixItHint > addQualifierToVarDecl(const VarDecl &Var, const ASTContext &Context, Qualifiers::TQ Qualifier, QualifierTarget QualTarget, QualifierPolicy QualPolicy)
Creates fix to qualify VarDecl with the specified Qualifier. Requires that Var is isolated in written...
static std::optional< SourceLocation > skipLParensBackwards(SourceLocation Start, const ASTContext &Context)
static bool needParensAfterUnaryOperator(const Expr &ExprNode)
static bool locDangerous(SourceLocation S)
std::string formatDereference(const Expr &ExprNode, const ASTContext &Context)
static bool isValueType(const Type *T)
Token getPreviousToken(SourceLocation Location, const SourceManager &SM, const LangOptions &LangOpts, bool SkipComments)
Returns previous token or tok::unknown if not found.
SourceLocation findPreviousTokenStart(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
SourceLocation findPreviousTokenKind(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts, tok::TokenKind TK)
SourceLocation findPreviousAnyTokenKind(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts, TokenKind TK, TokenKinds... TKs)
Definition LexerUtils.h:44