[clang] [refactoring] idea: convert a switch statement to an if #34352 FIXED (PR #167142)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Nov 8 05:41:52 PST 2025
https://github.com/krishnateja2314 created https://github.com/llvm/llvm-project/pull/167142
None
>From 09f494a850addf0ded229f61b9aa51281e4be547 Mon Sep 17 00:00:00 2001
From: krishnateja2314 <cs23btech11028 at iith.ac.in>
Date: Sat, 8 Nov 2025 18:56:29 +0530
Subject: [PATCH 1/2] added code for switch to if
---
.../Refactoring/SwitchToIf/SwitchToIf.h | 46 +++
clang/lib/Tooling/Refactoring/CMakeLists.txt | 1 +
.../Refactoring/RefactoringActions.cpp | 20 ++
.../Refactoring/SwitchToIf/SwitchToIf.cpp | 316 ++++++++++++++++++
4 files changed, 383 insertions(+)
create mode 100644 clang/include/clang/Tooling/Refactoring/SwitchToIf/SwitchToIf.h
create mode 100644 clang/lib/Tooling/Refactoring/SwitchToIf/SwitchToIf.cpp
diff --git a/clang/include/clang/Tooling/Refactoring/SwitchToIf/SwitchToIf.h b/clang/include/clang/Tooling/Refactoring/SwitchToIf/SwitchToIf.h
new file mode 100644
index 0000000000000..aa46e7a9b52bd
--- /dev/null
+++ b/clang/include/clang/Tooling/Refactoring/SwitchToIf/SwitchToIf.h
@@ -0,0 +1,46 @@
+//===--- SwitchToIf.h - Switch to if refactoring -------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLING_REFACTORING_SWITCHTOIF_SWITCHTOIF_H
+#define LLVM_CLANG_TOOLING_REFACTORING_SWITCHTOIF_SWITCHTOIF_H
+
+#include "clang/Tooling/Refactoring/ASTSelection.h"
+#include "clang/Tooling/Refactoring/RefactoringActionRules.h"
+
+namespace clang {
+class SwitchStmt;
+
+namespace tooling {
+
+/// A "Switch to If" refactoring converts a switch statement into an if-else
+/// chain.
+class SwitchToIf final : public SourceChangeRefactoringRule {
+public:
+ /// Initiates the switch-to-if refactoring operation.
+ ///
+ /// \param Selection The selected AST node, which should be a switch statement.
+ static Expected<SwitchToIf>
+ initiate(RefactoringRuleContext &Context,
+ SelectedASTNode Selection);
+
+ static const RefactoringDescriptor &describe();
+
+private:
+ SwitchToIf(const SwitchStmt *Switch) : TheSwitch(Switch) {}
+
+ Expected<AtomicChanges>
+ createSourceReplacements(RefactoringRuleContext &Context) override;
+
+ const SwitchStmt *TheSwitch;
+};
+
+} // end namespace tooling
+} // end namespace clang
+
+#endif // LLVM_CLANG_TOOLING_REFACTORING_SWITCHTOIF_SWITCHTOIF_H
+
diff --git a/clang/lib/Tooling/Refactoring/CMakeLists.txt b/clang/lib/Tooling/Refactoring/CMakeLists.txt
index d3077be8810aa..35b806177fb7e 100644
--- a/clang/lib/Tooling/Refactoring/CMakeLists.txt
+++ b/clang/lib/Tooling/Refactoring/CMakeLists.txt
@@ -13,6 +13,7 @@ add_clang_library(clangToolingRefactoring
Rename/USRFinder.cpp
Rename/USRFindingAction.cpp
Rename/USRLocFinder.cpp
+ SwitchToIf/SwitchToIf.cpp
LINK_LIBS
clangAST
diff --git a/clang/lib/Tooling/Refactoring/RefactoringActions.cpp b/clang/lib/Tooling/Refactoring/RefactoringActions.cpp
index bf98941f568b3..1f6f3f641aa8c 100644
--- a/clang/lib/Tooling/Refactoring/RefactoringActions.cpp
+++ b/clang/lib/Tooling/Refactoring/RefactoringActions.cpp
@@ -10,6 +10,7 @@
#include "clang/Tooling/Refactoring/RefactoringAction.h"
#include "clang/Tooling/Refactoring/RefactoringOptions.h"
#include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
+#include "clang/Tooling/Refactoring/SwitchToIf/SwitchToIf.h"
namespace clang {
namespace tooling {
@@ -93,6 +94,24 @@ class LocalRename final : public RefactoringAction {
}
};
+class SwitchToIfRefactoring final : public RefactoringAction {
+public:
+ StringRef getCommand() const override { return "switch-to-if"; }
+
+ StringRef getDescription() const override {
+ return "Converts a switch statement into an if-else chain";
+ }
+
+ /// Returns a set of refactoring actions rules that are defined by this
+ /// action.
+ RefactoringActionRules createActionRules() const override {
+ RefactoringActionRules Rules;
+ Rules.push_back(createRefactoringActionRule<SwitchToIf>(
+ ASTSelectionRequirement()));
+ return Rules;
+ }
+};
+
} // end anonymous namespace
std::vector<std::unique_ptr<RefactoringAction>> createRefactoringActions() {
@@ -100,6 +119,7 @@ std::vector<std::unique_ptr<RefactoringAction>> createRefactoringActions() {
Actions.push_back(std::make_unique<LocalRename>());
Actions.push_back(std::make_unique<ExtractRefactoring>());
+ Actions.push_back(std::make_unique<SwitchToIfRefactoring>());
return Actions;
}
diff --git a/clang/lib/Tooling/Refactoring/SwitchToIf/SwitchToIf.cpp b/clang/lib/Tooling/Refactoring/SwitchToIf/SwitchToIf.cpp
new file mode 100644
index 0000000000000..cc6d30dc6d4ce
--- /dev/null
+++ b/clang/lib/Tooling/Refactoring/SwitchToIf/SwitchToIf.cpp
@@ -0,0 +1,316 @@
+//===--- SwitchToIf.cpp - Switch to if refactoring ------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Tooling/Refactoring/SwitchToIf/SwitchToIf.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/Stmt.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Tooling/Refactoring/AtomicChange.h"
+#include "clang/Tooling/Refactoring/RefactoringDiagnostic.h"
+#include "clang/Tooling/Refactoring/RefactoringRuleContext.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/raw_ostream.h"
+#include <functional>
+#include <optional>
+
+using namespace clang;
+using namespace tooling;
+
+namespace {
+
+/// Returns the source text for the given expression.
+std::string getExprText(const Expr *E, const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ SourceRange Range = E->getSourceRange();
+ return Lexer::getSourceText(CharSourceRange::getTokenRange(Range), SM,
+ LangOpts)
+ .str();
+}
+
+/// Returns the source text for a range.
+std::string getSourceText(SourceRange Range, const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ return Lexer::getSourceText(CharSourceRange::getTokenRange(Range), SM,
+ LangOpts)
+ .str();
+}
+
+/// Returns true if the statement is a break statement.
+bool isBreakStmt(const Stmt *S) {
+ return isa<BreakStmt>(S);
+}
+
+/// Recursively collects all statements from a statement, removing breaks.
+/// This handles compound statements and stops at the first break.
+void collectStatementsWithoutBreaks(const Stmt *S,
+ SmallVector<const Stmt *, 16> &Result,
+ const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ if (!S)
+ return;
+
+ if (isBreakStmt(S))
+ return;
+
+ if (const CompoundStmt *CS = dyn_cast<CompoundStmt>(S)) {
+ // Process each statement in the compound statement
+ for (const Stmt *Child : CS->body()) {
+ if (isBreakStmt(Child)) {
+ // Stop at first break
+ break;
+ }
+ collectStatementsWithoutBreaks(Child, Result, SM, LangOpts);
+ }
+ } else {
+ // For non-compound statements, add them directly
+ Result.push_back(S);
+ }
+}
+
+/// Gets the statements from a case/default, removing breaks.
+SmallVector<const Stmt *, 16> getCaseStatements(const SwitchCase *SC,
+ const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ SmallVector<const Stmt *, 16> Result;
+ const Stmt *SubStmt = SC->getSubStmt();
+ if (!SubStmt)
+ return Result;
+
+ collectStatementsWithoutBreaks(SubStmt, Result, SM, LangOpts);
+ return Result;
+}
+
+} // end anonymous namespace
+
+const RefactoringDescriptor &SwitchToIf::describe() {
+ static const RefactoringDescriptor Descriptor = {
+ "switch-to-if",
+ "Switch to If",
+ "Converts a switch statement into an if-else chain",
+ };
+ return Descriptor;
+}
+
+Expected<SwitchToIf>
+SwitchToIf::initiate(RefactoringRuleContext &Context,
+ SelectedASTNode Selection) {
+ // Find the SwitchStmt in the selection
+ const SwitchStmt *Switch = nullptr;
+
+ // Helper lambda to recursively search for SwitchStmt
+ std::function<const SwitchStmt *(const SelectedASTNode &)> findSwitch =
+ [&](const SelectedASTNode &Node) -> const SwitchStmt * {
+ if (const SwitchStmt *S = Node.Node.get<SwitchStmt>()) {
+ return S;
+ }
+ // Search in children
+ for (const SelectedASTNode &Child : Node.Children) {
+ if (const SwitchStmt *S = findSwitch(Child)) {
+ return S;
+ }
+ }
+ return nullptr;
+ };
+
+ Switch = findSwitch(Selection);
+
+ if (!Switch) {
+ return Context.createDiagnosticError(
+ Context.getSelectionRange().getBegin(),
+ diag::err_refactor_selection_invalid_ast);
+ }
+
+ // Validate that the switch has at least one case
+ if (!Switch->getSwitchCaseList()) {
+ return Context.createDiagnosticError(
+ Switch->getSwitchLoc(),
+ diag::err_refactor_selection_invalid_ast);
+ }
+
+ return SwitchToIf(Switch);
+}
+
+Expected<AtomicChanges>
+SwitchToIf::createSourceReplacements(RefactoringRuleContext &Context) {
+ ASTContext &AST = Context.getASTContext();
+ SourceManager &SM = AST.getSourceManager();
+ const LangOptions &LangOpts = AST.getLangOpts();
+
+ const SwitchStmt *Switch = TheSwitch;
+ const Expr *Cond = Switch->getCond();
+
+ // Get the full source range of the switch statement
+ SourceLocation StartLoc = Switch->getBeginLoc();
+ SourceLocation EndLoc = Switch->getEndLoc();
+
+ // Find the actual end location (closing brace)
+ if (const Stmt *Body = Switch->getBody()) {
+ EndLoc = Body->getEndLoc();
+ }
+
+ SourceRange SwitchRange(StartLoc, EndLoc);
+
+ // Build the if-else chain
+ std::string Replacement;
+ llvm::raw_string_ostream OS(Replacement);
+
+ std::string CondText = getExprText(Cond, SM, LangOpts);
+
+ // Handle init statement if present
+ if (Switch->getInit()) {
+ std::string InitText = getSourceText(Switch->getInit()->getSourceRange(),
+ SM, LangOpts);
+ OS << InitText << " ";
+ }
+
+ // Handle condition variable if present
+ if (Switch->getConditionVariableDeclStmt()) {
+ std::string VarText = getSourceText(
+ Switch->getConditionVariableDeclStmt()->getSourceRange(), SM, LangOpts);
+ OS << VarText << " ";
+ }
+
+ bool First = true;
+ const SwitchCase *DefaultCase = nullptr;
+ SmallVector<const SwitchCase *, 16> Cases;
+
+ // Collect all cases and find default
+ for (const SwitchCase *SC = Switch->getSwitchCaseList(); SC;
+ SC = SC->getNextSwitchCase()) {
+ if (isa<DefaultStmt>(SC)) {
+ DefaultCase = SC;
+ } else {
+ Cases.push_back(SC);
+ }
+ }
+
+ // Process cases
+ for (const SwitchCase *Case : Cases) {
+ if (First) {
+ OS << "if (";
+ First = false;
+ } else {
+ OS << " else if (";
+ }
+
+ const CaseStmt *CS = cast<CaseStmt>(Case);
+ const Expr *LHS = CS->getLHS();
+
+ // Handle GNU case ranges
+ if (CS->caseStmtIsGNURange()) {
+ const Expr *RHS = CS->getRHS();
+ std::string LHSText = getExprText(LHS, SM, LangOpts);
+ std::string RHSText = getExprText(RHS, SM, LangOpts);
+ OS << CondText << " >= " << LHSText << " && " << CondText << " <= "
+ << RHSText;
+ } else {
+ std::string CaseValue = getExprText(LHS, SM, LangOpts);
+ OS << CondText << " == " << CaseValue;
+ }
+
+ OS << ") {\n";
+
+ // Get statements from this case (without breaks)
+ SmallVector<const Stmt *, 16> Statements = getCaseStatements(Case, SM, LangOpts);
+
+ // Print statements
+ if (Statements.empty()) {
+ // Empty case - just add a blank line or comment
+ OS << " // empty case\n";
+ } else {
+ for (const Stmt *S : Statements) {
+ SourceRange StmtRange = S->getSourceRange();
+ std::string StmtText = getSourceText(StmtRange, SM, LangOpts);
+
+ // Indent the statement
+ OS << " " << StmtText;
+
+ // For compound statements, they already have their own braces
+ // For other statements, ensure proper termination
+ if (!isa<CompoundStmt>(S) && !isa<IfStmt>(S) && !isa<ForStmt>(S) &&
+ !isa<WhileStmt>(S) && !isa<SwitchStmt>(S) && !isa<DoStmt>(S) &&
+ !isa<BreakStmt>(S) && !isa<ReturnStmt>(S) && !isa<GotoStmt>(S)) {
+ // Check if statement already ends with semicolon by looking at the
+ // source text
+ if (!StmtText.empty() && StmtText.back() != ';') {
+ // Try to get the token after the statement
+ SourceLocation AfterEnd = Lexer::getLocForEndOfToken(
+ StmtRange.getEnd(), 0, SM, LangOpts);
+ Token Tok;
+ if (Lexer::getRawToken(AfterEnd, Tok, SM, LangOpts, false) ||
+ !Tok.is(tok::semi)) {
+ OS << ";";
+ }
+ }
+ }
+ OS << "\n";
+ }
+ }
+
+ OS << "}";
+ }
+
+ // Process default case
+ if (DefaultCase) {
+ if (First) {
+ OS << "if (1) { // default case\n";
+ First = false;
+ } else {
+ OS << " else { // default case\n";
+ }
+
+ SmallVector<const Stmt *, 16> Statements = getCaseStatements(DefaultCase, SM, LangOpts);
+
+ if (Statements.empty()) {
+ OS << " // empty default case\n";
+ } else {
+ for (const Stmt *S : Statements) {
+ SourceRange StmtRange = S->getSourceRange();
+ std::string StmtText = getSourceText(StmtRange, SM, LangOpts);
+
+ OS << " " << StmtText;
+
+ if (!isa<CompoundStmt>(S) && !isa<IfStmt>(S) && !isa<ForStmt>(S) &&
+ !isa<WhileStmt>(S) && !isa<SwitchStmt>(S) && !isa<DoStmt>(S) &&
+ !isa<BreakStmt>(S) && !isa<ReturnStmt>(S) && !isa<GotoStmt>(S)) {
+ if (!StmtText.empty() && StmtText.back() != ';') {
+ SourceLocation AfterEnd = Lexer::getLocForEndOfToken(
+ StmtRange.getEnd(), 0, SM, LangOpts);
+ Token Tok;
+ if (Lexer::getRawToken(AfterEnd, Tok, SM, LangOpts, false) ||
+ !Tok.is(tok::semi)) {
+ OS << ";";
+ }
+ }
+ }
+ OS << "\n";
+ }
+ }
+
+ OS << "}";
+ }
+
+ // Flush the stream to ensure all content is written to Replacement
+ OS.flush();
+
+ // Create the atomic change
+ AtomicChange Change(SM, StartLoc);
+
+ // Replace the entire switch statement
+ auto Err = Change.replace(SM, CharSourceRange::getTokenRange(SwitchRange),
+ Replacement);
+ if (Err)
+ return std::move(Err);
+
+ return AtomicChanges{std::move(Change)};
+}
+
>From 02a470e54dce51f1ee7b8efff088ab985c4c53bc Mon Sep 17 00:00:00 2001
From: krishnateja2314 <cs23btech11028 at iith.ac.in>
Date: Sat, 8 Nov 2025 19:10:54 +0530
Subject: [PATCH 2/2] added test cases
---
clang/test/Refactor/SwitchToIf/basic.cpp | 22 ++++++++++++++++++
.../test/Refactor/SwitchToIf/fallthrough.cpp | 23 +++++++++++++++++++
clang/test/Refactor/SwitchToIf/multi_case.cpp | 23 +++++++++++++++++++
clang/test/Refactor/SwitchToIf/no_default.cpp | 18 +++++++++++++++
4 files changed, 86 insertions(+)
create mode 100644 clang/test/Refactor/SwitchToIf/basic.cpp
create mode 100644 clang/test/Refactor/SwitchToIf/fallthrough.cpp
create mode 100644 clang/test/Refactor/SwitchToIf/multi_case.cpp
create mode 100644 clang/test/Refactor/SwitchToIf/no_default.cpp
diff --git a/clang/test/Refactor/SwitchToIf/basic.cpp b/clang/test/Refactor/SwitchToIf/basic.cpp
new file mode 100644
index 0000000000000..82a4ac64f603b
--- /dev/null
+++ b/clang/test/Refactor/SwitchToIf/basic.cpp
@@ -0,0 +1,22 @@
+// RUN: clang-refactor -action switch-to-if %s -- 2>&1 | FileCheck %s
+
+void foo(int x) {
+ switch (x) { // CHECK: Start refactoring here
+ case 1:
+ bar();
+ break;
+ case 2:
+ baz();
+ break;
+ default:
+ qux();
+ }
+}
+
+// CHECK: if (x == 1) {
+// CHECK-NEXT: bar();
+// CHECK-NEXT: } else if (x == 2) {
+// CHECK-NEXT: baz();
+// CHECK-NEXT: } else {
+// CHECK-NEXT: qux();
+// CHECK-NEXT: }
diff --git a/clang/test/Refactor/SwitchToIf/fallthrough.cpp b/clang/test/Refactor/SwitchToIf/fallthrough.cpp
new file mode 100644
index 0000000000000..8b02b44e9226b
--- /dev/null
+++ b/clang/test/Refactor/SwitchToIf/fallthrough.cpp
@@ -0,0 +1,23 @@
+// RUN: clang-refactor -action switch-to-if %s -- 2>&1 | FileCheck %s
+
+void g(int v) {
+ switch (v) {
+ case 3:
+ alpha();
+ [[fallthrough]];
+ case 4:
+ beta();
+ break;
+ default:
+ gamma();
+ }
+}
+
+// CHECK: if (v == 3) {
+// CHECK-NEXT: alpha();
+// CHECK-NEXT: beta();
+// CHECK-NEXT: } else if (v == 4) {
+// CHECK-NEXT: beta();
+// CHECK-NEXT: } else {
+// CHECK-NEXT: gamma();
+// CHECK-NEXT: }
diff --git a/clang/test/Refactor/SwitchToIf/multi_case.cpp b/clang/test/Refactor/SwitchToIf/multi_case.cpp
new file mode 100644
index 0000000000000..aa8dfba9b0c97
--- /dev/null
+++ b/clang/test/Refactor/SwitchToIf/multi_case.cpp
@@ -0,0 +1,23 @@
+// RUN: clang-refactor -action switch-to-if %s -- 2>&1 | FileCheck %s
+
+void test(int n) {
+ switch (n) {
+ case 1:
+ case 2:
+ handleSmall();
+ break;
+ case 10:
+ handleLarge();
+ break;
+ default:
+ handleOther();
+ }
+}
+
+// CHECK: if (n == 1 || n == 2) {
+// CHECK-NEXT: handleSmall();
+// CHECK-NEXT: } else if (n == 10) {
+// CHECK-NEXT: handleLarge();
+// CHECK-NEXT: } else {
+// CHECK-NEXT: handleOther();
+// CHECK-NEXT: }
diff --git a/clang/test/Refactor/SwitchToIf/no_default.cpp b/clang/test/Refactor/SwitchToIf/no_default.cpp
new file mode 100644
index 0000000000000..1f75de88a4c4e
--- /dev/null
+++ b/clang/test/Refactor/SwitchToIf/no_default.cpp
@@ -0,0 +1,18 @@
+// RUN: clang-refactor -action switch-to-if %s -- 2>&1 | FileCheck %s
+
+void f(int k) {
+ switch (k) {
+ case 5:
+ ping();
+ break;
+ case 7:
+ pong();
+ break;
+ }
+}
+
+// CHECK: if (k == 5) {
+// CHECK-NEXT: ping();
+// CHECK-NEXT: } else if (k == 7) {
+// CHECK-NEXT: pong();
+// CHECK-NEXT: }
More information about the cfe-commits
mailing list