clang-tools 19.0.0git
FixItHintUtils.cpp
Go to the documentation of this file.
1//===--- FixItHintUtils.cpp - clang-tidy-----------------------------------===//
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/Tooling/FixIt.h"
15#include <optional>
16
18
19FixItHint changeVarDeclToReference(const VarDecl &Var, ASTContext &Context) {
20 SourceLocation AmpLocation = Var.getLocation();
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, "&");
28}
29
30static bool isValueType(const Type *T) {
31 return !(isa<PointerType>(T) || isa<ReferenceType>(T) || isa<ArrayType>(T) ||
32 isa<MemberPointerType>(T) || isa<ObjCObjectPointerType>(T));
33}
34static bool isValueType(QualType QT) { return isValueType(QT.getTypePtr()); }
35static bool isMemberOrFunctionPointer(QualType QT) {
36 return (QT->isPointerType() && QT->isFunctionPointerType()) ||
37 isa<MemberPointerType>(QT.getTypePtr());
38}
39
40static bool locDangerous(SourceLocation S) {
41 return S.isInvalid() || S.isMacroID();
42}
43
44static std::optional<SourceLocation>
45skipLParensBackwards(SourceLocation Start, const ASTContext &Context) {
46 if (locDangerous(Start))
47 return std::nullopt;
48
49 auto PreviousTokenLParen = [&Start, &Context]() {
50 Token T;
51 T = lexer::getPreviousToken(Start, Context.getSourceManager(),
52 Context.getLangOpts());
53 return T.is(tok::l_paren);
54 };
55
56 while (Start.isValid() && PreviousTokenLParen())
57 Start = lexer::findPreviousTokenStart(Start, Context.getSourceManager(),
58 Context.getLangOpts());
59
60 if (locDangerous(Start))
61 return std::nullopt;
62 return Start;
63}
64
65static std::optional<FixItHint> fixIfNotDangerous(SourceLocation Loc,
66 StringRef Text) {
67 if (locDangerous(Loc))
68 return std::nullopt;
69 return FixItHint::CreateInsertion(Loc, Text);
70}
71
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 '.
74static std::string buildQualifier(DeclSpec::TQ Qualifier,
75 bool WhitespaceBefore = false) {
76 if (WhitespaceBefore)
77 return (llvm::Twine(' ') + DeclSpec::getSpecifierName(Qualifier)).str();
78 return (llvm::Twine(DeclSpec::getSpecifierName(Qualifier)) + " ").str();
79}
80
81static std::optional<FixItHint> changeValue(const VarDecl &Var,
82 DeclSpec::TQ Qualifier,
83 QualifierTarget QualTarget,
84 QualifierPolicy QualPolicy,
85 const ASTContext &Context) {
86 switch (QualPolicy) {
88 return fixIfNotDangerous(Var.getTypeSpecStartLoc(),
89 buildQualifier(Qualifier));
91 std::optional<SourceLocation> IgnoredParens =
92 skipLParensBackwards(Var.getLocation(), Context);
93
94 if (IgnoredParens)
95 return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier));
96 return std::nullopt;
97 }
98 llvm_unreachable("Unknown QualifierPolicy enum");
99}
100
101static std::optional<FixItHint> changePointerItself(const VarDecl &Var,
102 DeclSpec::TQ Qualifier,
103 const ASTContext &Context) {
104 if (locDangerous(Var.getLocation()))
105 return std::nullopt;
106
107 std::optional<SourceLocation> IgnoredParens =
108 skipLParensBackwards(Var.getLocation(), Context);
109 if (IgnoredParens)
110 return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier));
111 return std::nullopt;
112}
113
114static std::optional<FixItHint>
115changePointer(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);
122
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));
130
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(),
137 tok::star);
138 if (locDangerous(BeforeStar))
139 return std::nullopt;
140
141 std::optional<SourceLocation> IgnoredParens =
142 skipLParensBackwards(BeforeStar, Context);
143
144 if (IgnoredParens)
145 return fixIfNotDangerous(*IgnoredParens,
146 buildQualifier(Qualifier, true));
147 return std::nullopt;
148 }
149 }
150
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(),
158 tok::star);
159 return fixIfNotDangerous(BeforeStar, buildQualifier(Qualifier, true));
160 }
161
162 return std::nullopt;
163}
164
165static std::optional<FixItHint>
166changeReferencee(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));
172
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);
178 if (IgnoredParens)
179 return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier, true));
180
181 return std::nullopt;
182}
183
184std::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");
195
196 QualType ParenStrippedType = Var.getType().IgnoreParens();
197 if (isValueType(ParenStrippedType))
198 return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context);
199
200 if (ParenStrippedType->isReferenceType())
201 return changeReferencee(Var, Qualifier, Var.getType()->getPointeeType(),
202 QualTarget, QualPolicy, Context);
203
204 if (isMemberOrFunctionPointer(ParenStrippedType))
205 return changePointerItself(Var, Qualifier, Context);
206
207 if (ParenStrippedType->isPointerType())
208 return changePointer(Var, Qualifier,
209 ParenStrippedType->getPointeeType().getTypePtr(),
210 QualTarget, QualPolicy, Context);
211
212 if (ParenStrippedType->isArrayType()) {
213 const Type *AT = ParenStrippedType->getBaseElementTypeUnsafe();
214 assert(AT && "Did not retrieve array element type for an array.");
215
216 if (isValueType(AT))
217 return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context);
218
219 if (AT->isPointerType())
220 return changePointer(Var, Qualifier, AT->getPointeeType().getTypePtr(),
221 QualTarget, QualPolicy, Context);
222 }
223
224 return std::nullopt;
225}
226
227bool areParensNeededForStatement(const Stmt &Node) {
228 if (isa<ParenExpr>(&Node))
229 return false;
230
231 if (isa<clang::BinaryOperator>(&Node) || isa<UnaryOperator>(&Node))
232 return true;
233
234 if (isa<clang::ConditionalOperator>(&Node) ||
235 isa<BinaryConditionalOperator>(&Node))
236 return true;
237
238 if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(&Node)) {
239 switch (Op->getOperator()) {
240 case OO_PlusPlus:
241 [[fallthrough]];
242 case OO_MinusMinus:
243 return Op->getNumArgs() != 2;
244 case OO_Call:
245 [[fallthrough]];
246 case OO_Subscript:
247 [[fallthrough]];
248 case OO_Arrow:
249 return false;
250 default:
251 return true;
252 };
253 }
254
255 if (isa<CStyleCastExpr>(&Node))
256 return true;
257
258 return false;
259}
260
261// Return true if expr needs to be put in parens when it is an argument of a
262// prefix unary operator, e.g. when it is a binary or ternary operator
263// syntactically.
264static bool needParensAfterUnaryOperator(const Expr &ExprNode) {
265 if (isa<clang::BinaryOperator>(&ExprNode) ||
266 isa<clang::ConditionalOperator>(&ExprNode)) {
267 return true;
268 }
269 if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(&ExprNode)) {
270 return Op->getNumArgs() == 2 && Op->getOperator() != OO_PlusPlus &&
271 Op->getOperator() != OO_MinusMinus && Op->getOperator() != OO_Call &&
272 Op->getOperator() != OO_Subscript;
273 }
274 return false;
275}
276
277// Format a pointer to an expression: prefix with '*' but simplify
278// when it already begins with '&'. Return empty string on failure.
279std::string formatDereference(const Expr &ExprNode, const ASTContext &Context) {
280 if (const auto *Op = dyn_cast<clang::UnaryOperator>(&ExprNode)) {
281 if (Op->getOpcode() == UO_AddrOf) {
282 // Strip leading '&'.
283 return std::string(
284 tooling::fixit::getText(*Op->getSubExpr()->IgnoreParens(), Context));
285 }
286 }
287 StringRef Text = tooling::fixit::getText(ExprNode, Context);
288
289 if (Text.empty())
290 return {};
291
292 // Remove remaining '->' from overloaded operator call
293 Text.consume_back("->");
294
295 // Add leading '*'.
296 if (needParensAfterUnaryOperator(ExprNode)) {
297 return (llvm::Twine("*(") + Text + ")").str();
298 }
299 return (llvm::Twine("*") + Text).str();
300}
301
302} // namespace clang::tidy::utils::fixit
NodeType Type
std::string Text
SourceLocation Loc
::clang::DynTypedNode Node
bool areParensNeededForStatement(const Stmt &Node)
static std::optional< FixItHint > changePointerItself(const VarDecl &Var, DeclSpec::TQ Qualifier, const ASTContext &Context)
static std::optional< FixItHint > fixIfNotDangerous(SourceLocation Loc, StringRef Text)
static std::string buildQualifier(DeclSpec::TQ Qualifier, bool WhitespaceBefore=false)
static std::optional< FixItHint > changePointer(const VarDecl &Var, DeclSpec::TQ Qualifier, const Type *Pointee, 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.
@ Value
Transforming a pointer attaches to the pointee and not the pointer itself.
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 &.
static std::optional< FixItHint > changeValue(const VarDecl &Var, DeclSpec::TQ Qualifier, QualifierTarget QualTarget, QualifierPolicy QualPolicy, const ASTContext &Context)
static std::optional< SourceLocation > skipLParensBackwards(SourceLocation Start, const ASTContext &Context)
static std::optional< FixItHint > changeReferencee(const VarDecl &Var, DeclSpec::TQ Qualifier, QualType Pointee, QualifierTarget QualTarget, QualifierPolicy QualPolicy, const ASTContext &Context)
static bool needParensAfterUnaryOperator(const Expr &ExprNode)
static bool locDangerous(SourceLocation S)
std::optional< FixItHint > addQualifierToVarDecl(const VarDecl &Var, const ASTContext &Context, DeclSpec::TQ Qualifier, QualifierTarget QualTarget, QualifierPolicy QualPolicy)
Creates fix to qualify VarDecl with the specified Qualifier.
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.
Definition: LexerUtils.cpp:39
SourceLocation findPreviousTokenStart(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition: LexerUtils.cpp:46
SourceLocation findPreviousTokenKind(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts, tok::TokenKind TK)
Definition: LexerUtils.cpp:59
SourceLocation findPreviousAnyTokenKind(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts, TokenKind TK, TokenKinds... TKs)
Definition: LexerUtils.h:44