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