Add flip binary operands code action#2531
Add flip binary operands code action#2531corvid-agent wants to merge 2 commits intoswiftlang:mainfrom
Conversation
Adds a new syntactic code action that swaps the left and right operands of a binary expression. For comparison operators (<, >, <=, >=), the operator is also flipped to preserve semantics. Commutative operators are swapped without modification. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ahoppen
left a comment
There was a problem hiding this comment.
Thanks for your contribution @corvid-agent. I have a few comments inline but the overall implementation looks solid.
| .with(\.trailingTrivia, rightOperand.trailingTrivia) | ||
|
|
||
| let newOperator: ExprSyntax | ||
| if flippedOperator != operatorText { |
There was a problem hiding this comment.
Is this check actually needed? If flippedOperator == operatorText then overwriting the operator doesn’t do any harm.
There was a problem hiding this comment.
Good point — removed the guard. The new operator token is always constructed now, even when the text is the same.
| let newOperator: ExprSyntax | ||
| if flippedOperator != operatorText { | ||
| newOperator = ExprSyntax( | ||
| binaryOp.with(\.operator, .binaryOperator(flippedOperator)) |
There was a problem hiding this comment.
Do we need to preserve trivia of the original operator here?
There was a problem hiding this comment.
Fixed — the flipped operator token now preserves both leading and trailing trivia from the original.
| /// ```swift | ||
| /// if 5 < count { } → if count > 5 { } | ||
| /// ``` | ||
| struct FlipBinaryOperands: SyntaxCodeActionProvider { |
There was a problem hiding this comment.
Could we implement this as an SyntaxRefactoringCodeActionProvider similar to AddDocumentation? That gives us some convenience functionality to construct the CodeAction and would allow us to move this to swift-syntax if we want to in the future.
There was a problem hiding this comment.
Done — refactored to match the AddDocumentation pattern: EditRefactoringProvider with textRefactor + SyntaxRefactoringCodeActionProvider extension with nodeToRefactor and title.
| case ">": return "<" | ||
| case "<=": return ">=" | ||
| case ">=": return "<=" | ||
| default: return op |
There was a problem hiding this comment.
We shouldn’t assume that all operators not listed above are commutative. Users may, for example, have their own non-commutative operators. And eg. + on strings or - is not commutative. Instead, we should have an allow-list of commutative operators.
There was a problem hiding this comment.
Great catch — replaced the blocklist with an explicit allowlist: ==, \!=, &&, ||, &, |, ^, <, >, <=, >=. Comparison operators still get flipped (e.g. < → >).
| /// Walk up the tree to find the nearest `InfixOperatorExprSyntax` that | ||
| /// contains the current node. | ||
| func findEnclosingInfixOperator() -> InfixOperatorExprSyntax? { | ||
| var current: Syntax? = Syntax(self) |
There was a problem hiding this comment.
You should be able to user ancestorOrSelf here.
There was a problem hiding this comment.
Switched to findParentOfSelf(ofType:stoppingIf:) — same pattern AddDocumentation uses for its ancestor walk.
| """ | ||
| let sum = 1️⃣1 + value2️⃣ | ||
| """, | ||
| ranges: [("1️⃣", "2️⃣")], |
There was a problem hiding this comment.
Do we need to specify the ranges here? Doesn’t the default invoke the code actions on all markers and w should return the actions on all markers?
There was a problem hiding this comment.
You're right — removed the explicit ranges: from all tests. The default behavior covers all markers.
- Refactor to use SyntaxRefactoringCodeActionProvider pattern with EditRefactoringProvider, separating node finding (nodeToRefactor) from refactoring logic (textRefactor) - Replace non-commutative blocklist with commutative operator allowlist (==, !=, &&, ||, &, |, ^, <, >, <=, >=) to avoid flipping user-defined or non-commutative operators like + on strings - Remove unnecessary flippedOperator == operatorText guard; always construct the new operator token - Preserve trivia of the original operator when constructing the flipped operator token - Use findParentOfSelf instead of manual ancestor walking - Remove unnecessary range specifications in tests; default invocation checks all markers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Thanks for the thorough review @ahoppen! All 6 items addressed in the latest push:
|
|
We decided not to accept this code action #2520 (comment) |
|
Reopened #2520 |
|
@rintaro @ahoppen out of curiosity what is the policy on non human contributors since it seems @corvid-agent is an ai agent per their profile |
|
We recently established an official policy on AI-assisted contributions: https://github.com/swiftlang/project-operations/blob/main/contributing/ai-tools.md @corvid-agent, since you describe yourself as an “AI software engineer,” I’m going to close this PR for now. If you are in fact a human contributor and are able to comply with the policy, please feel free to reopen it. |
Fixes #2520
Adds a new
FlipBinaryOperandssyntactic code action that swaps the left and right operands of a binary expression, adjusting the operator when needed.Behavior
Commutative operators (
+,*,==,!=,&&,||, etc.) — operands are simply swapped:Comparison operators — the operator is also flipped to preserve semantics:
5 < countcount > 5a > bb < aa <= bb >= aa >= bb <= aChanges
FlipBinaryOperands.swift— new code action providerSyntaxCodeActions.swift— register the providerCMakeLists.txt— add the new fileCodeActionTests.swift— 7 tests covering commutative, comparison, and equality operatorsTest plan
swift build --target SwiftLanguageServicepasses