218 lines · cpp
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-exception6//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 27namespace clang {28namespace clangd {29namespace {30/// Check whether it makes logical sense to swap operands to an operator.31/// Assignment or member access operators are rarely swappable32/// while keeping the meaning intact, whereas comparison operators, mathematical33/// operators, etc. are often desired to be swappable for readability, avoiding34/// bugs by assigning to nullptr when comparison was desired, etc.35bool 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 noncommutative59 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 their81/// operands82/// @param[out] Opcode the opcode to potentially swap83/// If the opcode does not need to be swapped or is not swappable, does nothing84BinaryOperatorKind 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 operator133/// Before:134/// x != nullptr135/// ^ ^^^^^^^136/// After:137/// nullptr != x138class SwapBinaryOperands : public Tweak {139public: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("Swap operands to {0}",146 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 153private:154 const BinaryOperator *Op;155};156 157REGISTER_TWEAK(SwapBinaryOperands)158 159bool 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 binary164 // operator, not from somewhere inside a lambda for example165 // (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 going170 if (Op && !isOpSwappable(Op->getOpcode())) {171 Op = nullptr;172 }173 }174 return Op != nullptr;175}176 177Expected<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 "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 "Could not obtain range of the 'rhs' of the operator. Macros?");191 const auto OpRng =192 toHalfOpenFileRange(SrcMgr, Ctx.getLangOpts(), Op->getOperatorLoc());193 if (!OpRng)194 return error("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(SrcMgr, *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(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));213}214 215} // namespace216} // namespace clangd217} // namespace clang218