1 //===--- SwapBinaryOperands.cpp ----------------------------------*- C++-*-===//
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
7 //===----------------------------------------------------------------------===//
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"
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
) {
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
:
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
:
61 case BinaryOperatorKind::BO_PtrMemD
:
62 case BinaryOperatorKind::BO_PtrMemI
:
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
:
80 /// Some operators are asymmetric and need to be flipped when swapping their
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
) {
86 case BinaryOperatorKind::BO_LT
:
87 return BinaryOperatorKind::BO_GT
;
89 case BinaryOperatorKind::BO_GT
:
90 return BinaryOperatorKind::BO_LT
;
92 case BinaryOperatorKind::BO_LE
:
93 return BinaryOperatorKind::BO_GE
;
95 case BinaryOperatorKind::BO_GE
:
96 return BinaryOperatorKind::BO_LE
;
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
:
129 llvm_unreachable("Unknown BinaryOperatorKind enum");
132 /// Swaps the operands to a binary operator
138 class SwapBinaryOperands
: public Tweak
{
140 const char *id() const final
;
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("Swap operands to {0}",
146 Op
? Op
->getOpcodeStr() : "binary operator");
148 llvm::StringLiteral
kind() const override
{
149 return CodeAction::REFACTOR_KIND
;
151 bool hidden() const override
{ return false; }
154 const BinaryOperator
*Op
;
157 REGISTER_TWEAK(SwapBinaryOperands
)
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
>()))
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(Op
->getOpcode())) {
174 return Op
!= nullptr;
177 Expected
<Tweak::Effect
> SwapBinaryOperands::apply(const Selection
&Inputs
) {
178 const auto &Ctx
= Inputs
.AST
->getASTContext();
179 const auto &SrcMgr
= Inputs
.AST
->getSourceManager();
181 const auto LHSRng
= toHalfOpenFileRange(SrcMgr
, Ctx
.getLangOpts(),
182 Op
->getLHS()->getSourceRange());
185 "Could not obtain range of the 'lhs' of the operator. Macros?");
186 const auto RHSRng
= toHalfOpenFileRange(SrcMgr
, Ctx
.getLangOpts(),
187 Op
->getRHS()->getSourceRange());
190 "Could not obtain range of the 'rhs' of the operator. Macros?");
192 toHalfOpenFileRange(SrcMgr
, Ctx
.getLangOpts(), Op
->getOperatorLoc());
194 return error("Could not obtain range of the operator itself. Macros?");
196 const auto LHSCode
= toSourceCode(SrcMgr
, *LHSRng
);
197 const auto RHSCode
= toSourceCode(SrcMgr
, *RHSRng
);
198 const auto OperatorCode
= toSourceCode(SrcMgr
, *OpRng
);
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(Op
->getOpcode());
208 if (auto Err
= Result
.add(tooling::Replacement(
209 Ctx
.getSourceManager(), OpRng
->getBegin(), OperatorCode
.size(),
210 Op
->getOpcodeStr(SwappedOperator
))))
211 return std::move(Err
);
212 return Effect::mainFileEdit(SrcMgr
, std::move(Result
));
216 } // namespace clangd