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