Skip to content

feat(refactor): Implement ConvertToSwitchStmt (SourceKit-LSP #2424)#3264

Open
rickhohler wants to merge 4 commits intoswiftlang:mainfrom
rickhohler:feat/convert-to-switch-stmt
Open

feat(refactor): Implement ConvertToSwitchStmt (SourceKit-LSP #2424)#3264
rickhohler wants to merge 4 commits intoswiftlang:mainfrom
rickhohler:feat/convert-to-switch-stmt

Conversation

@rickhohler
Copy link
Copy Markdown

This PR implements the ConvertToSwitchStmt refactoring action as requested in SourceKit-LSP #2424.

It supports converting if/else if/else chains into switch statements when simpler == comparisons are used against a common subject.

Includes comprehensive unit tests to verify correctness and safety (e.g. exhaustiveness checks via implicit default: break insertion).

…g#2424)

- Implements ConvertToSwitchStmt refactoring action.
- Supports converting if/else if/else chains with == conditions on a common subject.
- Adds comprehensive unit tests including failure scenarios.
- Ensures generated switch statements are exhaustive (implicit default: break).
Comment thread Sources/SwiftRefactor/CollapseNestedIfStmt.swift Outdated
Comment thread Sources/SwiftRefactor/ConvertToSwitchStmt.swift

// 1. Identify Subject and Pattern from the first `if`.
guard let (subject, firstPattern) = extractSubjectAndPattern(from: ifExpr) else {
throw RefactoringNotApplicableError("First condition invalid")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw RefactoringNotApplicableError("First condition invalid")
throw RefactoringNotApplicableError("First condition does not of the format x == y")

// Else If block
// Must match subject and operator
guard let (nextSubject, nextPattern) = extractSubjectAndPattern(from: nextIf) else {
throw RefactoringNotApplicableError("Subsequent condition invalid")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar comment as for the first condition.

// Ensure subject is structurally identical (ignoring trivia)
// We use trimmedDescription for comparison.
guard nextSubject.trimmedDescription == subject.trimmedDescription else {
throw RefactoringNotApplicableError("Subject mismatch")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be a little more descriptive here about what mismatches.

Comment on lines +111 to +116
// Add default: break
let breakStmt = CodeBlockItemSyntax(
leadingTrivia: .newlines(1) + .spaces(2),
item: .stmt(StmtSyntax(BreakStmtSyntax()))
)
cases.append(createDefaultCase(body: CodeBlockSyntax(statements: [breakStmt])))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you run swift-format as described in CONTRIBUTING.md

// Therefore, if there is no `else`, we MUST likely abort OR add `default: break`.
// Adding `default: break` is safe.

if ifExpr.elseBody == nil || cases.last?.label.is(SwitchDefaultLabelSyntax.self) == false {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally wouldn’t add a default case. If the switch isn’t exhaustive, the compiler will add a diagnostic for it and then the user can add a default case if necessary.

Comment on lines +87 to +93
return SwitchExprSyntax(
switchKeyword: .keyword(.switch, trailingTrivia: .space),
subject: subject.trimmed,
leftBrace: .leftBraceToken(leadingTrivia: .space),
cases: caseList,
rightBrace: .rightBraceToken(leadingTrivia: .newlines(1))
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to just break here, so we can have a single return constructing a SwitchExprSyntax for the function.

)
}

private static func createDefaultCase(body: CodeBlockSyntax) -> SwitchCaseSyntax {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this ends up being called from only a single place, we can inline it and don’t need a function for it anymore.

}
}

// Private helper to avoid redeclaration conflicts
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar comment as in your other PR, ExprSyntax already conforms to ExpressibleByStringLiteral.

@ahoppen
Copy link
Copy Markdown
Member

ahoppen commented Feb 21, 2026

Looks like you pushed a new commit without addressing all of my comments. Please let me know when this is ready for another review.

@ahoppen
Copy link
Copy Markdown
Member

ahoppen commented Apr 4, 2026

@rickhohler Are you planning to continue working on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants