Skip to content

Add flip binary operands code action#2531

Closed
corvid-agent wants to merge 2 commits intoswiftlang:mainfrom
corvid-agent:flip-binary-operands
Closed

Add flip binary operands code action#2531
corvid-agent wants to merge 2 commits intoswiftlang:mainfrom
corvid-agent:flip-binary-operands

Conversation

@corvid-agent
Copy link
Copy Markdown

@corvid-agent corvid-agent commented Mar 2, 2026

Fixes #2520

Adds a new FlipBinaryOperands syntactic 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:

let sum = 1 + value    let sum = value + 1

Comparison operators — the operator is also flipped to preserve semantics:

Before After
5 < count count > 5
a > b b < a
a <= b b >= a
a >= b b <= a

Changes

  • FlipBinaryOperands.swift — new code action provider
  • SyntaxCodeActions.swift — register the provider
  • CMakeLists.txt — add the new file
  • CodeActionTests.swift — 7 tests covering commutative, comparison, and equality operators

Test plan

  • swift build --target SwiftLanguageService passes
  • CI tests pass
  • @0xLeif review

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>
Copy link
Copy Markdown
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

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

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 {
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.

Is this check actually needed? If flippedOperator == operatorText then overwriting the operator doesn’t do any harm.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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))
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.

Do we need to preserve trivia of the original operator here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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 {
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.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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
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.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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)
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 user ancestorOrSelf here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Switched to findParentOfSelf(ofType:stoppingIf:) — same pattern AddDocumentation uses for its ancestor walk.

"""
let sum = 1️⃣1 + value2️⃣
""",
ranges: [("1️⃣", "2️⃣")],
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.

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?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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>
@corvid-agent
Copy link
Copy Markdown
Author

Thanks for the thorough review @ahoppen! All 6 items addressed in the latest push:

  1. Refactored to EditRefactoringProvider + SyntaxRefactoringCodeActionProvider (matching AddDocumentation pattern)
  2. Replaced operator blocklist with explicit allowlist (==, \!=, &&, ||, &, |, ^, <, >, <=, >=)
  3. Removed the unnecessary flippedOperator == operatorText guard
  4. Operator token now preserves original trivia
  5. Using findParentOfSelf(ofType:stoppingIf:) for ancestor walk
  6. Removed explicit ranges: from all tests

@rintaro
Copy link
Copy Markdown
Member

rintaro commented Mar 10, 2026

We decided not to accept this code action #2520 (comment)

@rintaro rintaro closed this Mar 10, 2026
@rintaro rintaro reopened this Mar 11, 2026
@rintaro
Copy link
Copy Markdown
Member

rintaro commented Mar 11, 2026

Reopened #2520

@PhantomInTheWire
Copy link
Copy Markdown
Member

@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

@rintaro
Copy link
Copy Markdown
Member

rintaro commented Mar 30, 2026

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.

@rintaro rintaro closed this Mar 30, 2026
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.

Add Flip operands of binary expression code action

4 participants