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
17namespace clang::tidy::utils::fixit {
18
19FixItHint changeVarDeclToReference(const VarDecl &Var, ASTContext &Context) {
20 SourceLocation AmpLocation = Var.getLocation();
21 auto Token = utils::lexer::getPreviousToken(
22 Location: AmpLocation, SM: Context.getSourceManager(), LangOpts: Context.getLangOpts());
23 if (!Token.is(tok::unknown))
24 AmpLocation = Lexer::getLocForEndOfToken(Loc: Token.getLocation(), Offset: 0,
25 SM: Context.getSourceManager(),
26 LangOpts: Context.getLangOpts());
27 return FixItHint::CreateInsertion(InsertionLoc: AmpLocation, Code: "&");
28}
29
30static bool isValueType(const Type *T) {
31 return !(isa<PointerType>(Val: T) || isa<ReferenceType>(Val: T) || isa<ArrayType>(Val: T) ||
32 isa<MemberPointerType>(Val: T) || isa<ObjCObjectPointerType>(Val: T));
33}
34static bool isValueType(QualType QT) { return isValueType(T: QT.getTypePtr()); }
35static bool isMemberOrFunctionPointer(QualType QT) {
36 return (QT->isPointerType() && QT->isFunctionPointerType()) ||
37 isa<MemberPointerType>(Val: 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(S: Start))
47 return std::nullopt;
48
49 auto PreviousTokenLParen = [&Start, &Context]() {
50 Token T;
51 T = lexer::getPreviousToken(Location: Start, SM: Context.getSourceManager(),
52 LangOpts: Context.getLangOpts());
53 return T.is(K: tok::l_paren);
54 };
55
56 while (Start.isValid() && PreviousTokenLParen())
57 Start = lexer::findPreviousTokenStart(Start, SM: Context.getSourceManager(),
58 LangOpts: Context.getLangOpts());
59
60 if (locDangerous(S: Start))
61 return std::nullopt;
62 return Start;
63}
64
65static std::optional<FixItHint> fixIfNotDangerous(SourceLocation Loc,
66 StringRef Text) {
67 if (locDangerous(S: Loc))
68 return std::nullopt;
69 return FixItHint::CreateInsertion(InsertionLoc: Loc, Code: 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(Q: Qualifier)).str();
78 return (llvm::Twine(DeclSpec::getSpecifierName(Q: 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) {
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);
93
94 if (IgnoredParens)
95 return fixIfNotDangerous(Loc: *IgnoredParens, Text: 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(Loc: *IgnoredParens, Text: 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(T: 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 Start: Var.getLocation(), SM: Context.getSourceManager(), LangOpts: Context.getLangOpts(),
137 TK: tok::star);
138 if (locDangerous(S: BeforeStar))
139 return std::nullopt;
140
141 std::optional<SourceLocation> IgnoredParens =
142 skipLParensBackwards(Start: BeforeStar, Context);
143
144 if (IgnoredParens)
145 return fixIfNotDangerous(Loc: *IgnoredParens,
146 Text: buildQualifier(Qualifier, WhitespaceBefore: 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 Start: Var.getLocation(), SM: Context.getSourceManager(), LangOpts: Context.getLangOpts(),
158 TK: tok::star);
159 return fixIfNotDangerous(Loc: BeforeStar, Text: buildQualifier(Qualifier, WhitespaceBefore: 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(QT: 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(Start: BeforeRef, Context);
178 if (IgnoredParens)
179 return fixIfNotDangerous(Loc: *IgnoredParens, Text: buildQualifier(Qualifier, WhitespaceBefore: 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(QT: 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(QT: ParenStrippedType))
205 return changePointerItself(Var, Qualifier, Context);
206
207 if (ParenStrippedType->isPointerType())
208 return changePointer(Var, Qualifier,
209 Pointee: 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(T: AT))
217 return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context);
218
219 if (AT->isPointerType())
220 return changePointer(Var, Qualifier, Pointee: AT->getPointeeType().getTypePtr(),
221 QualTarget, QualPolicy, Context);
222 }
223
224 return std::nullopt;
225}
226
227bool areParensNeededForStatement(const Stmt &Node) {
228 if (isa<ParenExpr>(Val: &Node))
229 return false;
230
231 if (isa<clang::BinaryOperator>(Val: &Node) || isa<UnaryOperator>(Val: &Node))
232 return true;
233
234 if (isa<clang::ConditionalOperator>(Val: &Node) ||
235 isa<BinaryConditionalOperator>(Val: &Node))
236 return true;
237
238 if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(Val: &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>(Val: &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>(Val: &ExprNode) ||
266 isa<clang::ConditionalOperator>(Val: &ExprNode)) {
267 return true;
268 }
269 if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(Val: &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>(Val: &ExprNode)) {
281 if (Op->getOpcode() == UO_AddrOf) {
282 // Strip leading '&'.
283 return std::string(
284 tooling::fixit::getText(Node: *Op->getSubExpr()->IgnoreParens(), Context));
285 }
286 }
287 StringRef Text = tooling::fixit::getText(Node: ExprNode, Context);
288
289 if (Text.empty())
290 return {};
291
292 // Remove remaining '->' from overloaded operator call
293 Text.consume_back(Suffix: "->");
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
303

source code of clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp