1 | //===--- SwapBinaryOperands.cpp ----------------------------------*- C++-*-===// |
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 | #include "ParsedAST.h" |
9 | #include "Protocol.h" |
10 | #include "Selection.h" |
11 | #include "SourceCode.h" |
12 | #include "refactor/Tweak.h" |
13 | #include "support/Logger.h" |
14 | #include "clang/AST/ASTContext.h" |
15 | #include "clang/AST/Expr.h" |
16 | #include "clang/AST/OperationKinds.h" |
17 | #include "clang/AST/Stmt.h" |
18 | #include "clang/Basic/LLVM.h" |
19 | #include "clang/Basic/SourceLocation.h" |
20 | #include "clang/Tooling/Core/Replacement.h" |
21 | #include "llvm/ADT/StringRef.h" |
22 | #include "llvm/Support/Casting.h" |
23 | #include "llvm/Support/FormatVariadic.h" |
24 | #include <string> |
25 | #include <utility> |
26 | |
27 | namespace clang { |
28 | namespace clangd { |
29 | namespace { |
30 | /// Check whether it makes logical sense to swap operands to an operator. |
31 | /// Assignment or member access operators are rarely swappable |
32 | /// while keeping the meaning intact, whereas comparison operators, mathematical |
33 | /// operators, etc. are often desired to be swappable for readability, avoiding |
34 | /// bugs by assigning to nullptr when comparison was desired, etc. |
35 | bool isOpSwappable(const BinaryOperatorKind Opcode) { |
36 | switch (Opcode) { |
37 | case BinaryOperatorKind::BO_Mul: |
38 | case BinaryOperatorKind::BO_Add: |
39 | case BinaryOperatorKind::BO_LT: |
40 | case BinaryOperatorKind::BO_GT: |
41 | case BinaryOperatorKind::BO_LE: |
42 | case BinaryOperatorKind::BO_GE: |
43 | case BinaryOperatorKind::BO_EQ: |
44 | case BinaryOperatorKind::BO_NE: |
45 | case BinaryOperatorKind::BO_And: |
46 | case BinaryOperatorKind::BO_Xor: |
47 | case BinaryOperatorKind::BO_Or: |
48 | case BinaryOperatorKind::BO_LAnd: |
49 | case BinaryOperatorKind::BO_LOr: |
50 | case BinaryOperatorKind::BO_Comma: |
51 | return true; |
52 | // Noncommutative operators: |
53 | case BinaryOperatorKind::BO_Div: |
54 | case BinaryOperatorKind::BO_Sub: |
55 | case BinaryOperatorKind::BO_Shl: |
56 | case BinaryOperatorKind::BO_Shr: |
57 | case BinaryOperatorKind::BO_Rem: |
58 | // <=> is noncommutative |
59 | case BinaryOperatorKind::BO_Cmp: |
60 | // Member access: |
61 | case BinaryOperatorKind::BO_PtrMemD: |
62 | case BinaryOperatorKind::BO_PtrMemI: |
63 | // Assignment: |
64 | case BinaryOperatorKind::BO_Assign: |
65 | case BinaryOperatorKind::BO_MulAssign: |
66 | case BinaryOperatorKind::BO_DivAssign: |
67 | case BinaryOperatorKind::BO_RemAssign: |
68 | case BinaryOperatorKind::BO_AddAssign: |
69 | case BinaryOperatorKind::BO_SubAssign: |
70 | case BinaryOperatorKind::BO_ShlAssign: |
71 | case BinaryOperatorKind::BO_ShrAssign: |
72 | case BinaryOperatorKind::BO_AndAssign: |
73 | case BinaryOperatorKind::BO_XorAssign: |
74 | case BinaryOperatorKind::BO_OrAssign: |
75 | return false; |
76 | } |
77 | return false; |
78 | } |
79 | |
80 | /// Some operators are asymmetric and need to be flipped when swapping their |
81 | /// operands |
82 | /// @param[out] Opcode the opcode to potentially swap |
83 | /// If the opcode does not need to be swapped or is not swappable, does nothing |
84 | BinaryOperatorKind swapOperator(const BinaryOperatorKind Opcode) { |
85 | switch (Opcode) { |
86 | case BinaryOperatorKind::BO_LT: |
87 | return BinaryOperatorKind::BO_GT; |
88 | |
89 | case BinaryOperatorKind::BO_GT: |
90 | return BinaryOperatorKind::BO_LT; |
91 | |
92 | case BinaryOperatorKind::BO_LE: |
93 | return BinaryOperatorKind::BO_GE; |
94 | |
95 | case BinaryOperatorKind::BO_GE: |
96 | return BinaryOperatorKind::BO_LE; |
97 | |
98 | case BinaryOperatorKind::BO_Mul: |
99 | case BinaryOperatorKind::BO_Add: |
100 | case BinaryOperatorKind::BO_Cmp: |
101 | case BinaryOperatorKind::BO_EQ: |
102 | case BinaryOperatorKind::BO_NE: |
103 | case BinaryOperatorKind::BO_And: |
104 | case BinaryOperatorKind::BO_Xor: |
105 | case BinaryOperatorKind::BO_Or: |
106 | case BinaryOperatorKind::BO_LAnd: |
107 | case BinaryOperatorKind::BO_LOr: |
108 | case BinaryOperatorKind::BO_Comma: |
109 | case BinaryOperatorKind::BO_Div: |
110 | case BinaryOperatorKind::BO_Sub: |
111 | case BinaryOperatorKind::BO_Shl: |
112 | case BinaryOperatorKind::BO_Shr: |
113 | case BinaryOperatorKind::BO_Rem: |
114 | case BinaryOperatorKind::BO_PtrMemD: |
115 | case BinaryOperatorKind::BO_PtrMemI: |
116 | case BinaryOperatorKind::BO_Assign: |
117 | case BinaryOperatorKind::BO_MulAssign: |
118 | case BinaryOperatorKind::BO_DivAssign: |
119 | case BinaryOperatorKind::BO_RemAssign: |
120 | case BinaryOperatorKind::BO_AddAssign: |
121 | case BinaryOperatorKind::BO_SubAssign: |
122 | case BinaryOperatorKind::BO_ShlAssign: |
123 | case BinaryOperatorKind::BO_ShrAssign: |
124 | case BinaryOperatorKind::BO_AndAssign: |
125 | case BinaryOperatorKind::BO_XorAssign: |
126 | case BinaryOperatorKind::BO_OrAssign: |
127 | return Opcode; |
128 | } |
129 | llvm_unreachable("Unknown BinaryOperatorKind enum" ); |
130 | } |
131 | |
132 | /// Swaps the operands to a binary operator |
133 | /// Before: |
134 | /// x != nullptr |
135 | /// ^ ^^^^^^^ |
136 | /// After: |
137 | /// nullptr != x |
138 | class SwapBinaryOperands : public Tweak { |
139 | public: |
140 | const char *id() const final; |
141 | |
142 | bool prepare(const Selection &Inputs) override; |
143 | Expected<Effect> apply(const Selection &Inputs) override; |
144 | std::string title() const override { |
145 | return llvm::formatv(Fmt: "Swap operands to {0}" , |
146 | Vals: Op ? Op->getOpcodeStr() : "binary operator" ); |
147 | } |
148 | llvm::StringLiteral kind() const override { |
149 | return CodeAction::REFACTOR_KIND; |
150 | } |
151 | bool hidden() const override { return false; } |
152 | |
153 | private: |
154 | const BinaryOperator *Op; |
155 | }; |
156 | |
157 | REGISTER_TWEAK(SwapBinaryOperands) |
158 | |
159 | bool SwapBinaryOperands::prepare(const Selection &Inputs) { |
160 | for (const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor(); |
161 | N && !Op; N = N->Parent) { |
162 | // Stop once we hit a block, e.g. a lambda in one of the operands. |
163 | // This makes sure that the selection point is in the 'scope' of the binary |
164 | // operator, not from somewhere inside a lambda for example |
165 | // (5 < [](){ ^return 1; }) |
166 | if (llvm::isa_and_nonnull<CompoundStmt>(N->ASTNode.get<Stmt>())) |
167 | return false; |
168 | Op = dyn_cast_or_null<BinaryOperator>(N->ASTNode.get<Stmt>()); |
169 | // If we hit upon a nonswappable binary operator, ignore and keep going |
170 | if (Op && !isOpSwappable(Opcode: Op->getOpcode())) { |
171 | Op = nullptr; |
172 | } |
173 | } |
174 | return Op != nullptr; |
175 | } |
176 | |
177 | Expected<Tweak::Effect> SwapBinaryOperands::apply(const Selection &Inputs) { |
178 | const auto &Ctx = Inputs.AST->getASTContext(); |
179 | const auto &SrcMgr = Inputs.AST->getSourceManager(); |
180 | |
181 | const auto LHSRng = toHalfOpenFileRange(SrcMgr, Ctx.getLangOpts(), |
182 | Op->getLHS()->getSourceRange()); |
183 | if (!LHSRng) |
184 | return error( |
185 | Fmt: "Could not obtain range of the 'lhs' of the operator. Macros?" ); |
186 | const auto RHSRng = toHalfOpenFileRange(SrcMgr, Ctx.getLangOpts(), |
187 | Op->getRHS()->getSourceRange()); |
188 | if (!RHSRng) |
189 | return error( |
190 | Fmt: "Could not obtain range of the 'rhs' of the operator. Macros?" ); |
191 | const auto OpRng = |
192 | toHalfOpenFileRange(Mgr: SrcMgr, LangOpts: Ctx.getLangOpts(), R: Op->getOperatorLoc()); |
193 | if (!OpRng) |
194 | return error(Fmt: "Could not obtain range of the operator itself. Macros?" ); |
195 | |
196 | const auto LHSCode = toSourceCode(SrcMgr, *LHSRng); |
197 | const auto RHSCode = toSourceCode(SrcMgr, *RHSRng); |
198 | const auto OperatorCode = toSourceCode(SM: SrcMgr, R: *OpRng); |
199 | |
200 | tooling::Replacements Result; |
201 | if (auto Err = Result.add(tooling::Replacement( |
202 | Ctx.getSourceManager(), LHSRng->getBegin(), LHSCode.size(), RHSCode))) |
203 | return std::move(Err); |
204 | if (auto Err = Result.add(tooling::Replacement( |
205 | Ctx.getSourceManager(), RHSRng->getBegin(), RHSCode.size(), LHSCode))) |
206 | return std::move(Err); |
207 | const auto SwappedOperator = swapOperator(Opcode: Op->getOpcode()); |
208 | if (auto Err = Result.add(R: tooling::Replacement( |
209 | Ctx.getSourceManager(), OpRng->getBegin(), OperatorCode.size(), |
210 | Op->getOpcodeStr(Op: SwappedOperator)))) |
211 | return std::move(Err); |
212 | return Effect::mainFileEdit(SM: SrcMgr, Replacements: std::move(Result)); |
213 | } |
214 | |
215 | } // namespace |
216 | } // namespace clangd |
217 | } // namespace clang |
218 | |