Assignment Operator Does Not Update Its Left Operand

ID

swift.assignment_operator_updates_left

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

Swift

Tags

reliability, suspicious-code

Description

Reports a custom infix operator whose name ends in =, so every call site reads it as a compound assignment, but whose implementation cannot or does not update its left operand.

Rationale

struct Counter {
    var value: Int
}

func += (lhs: Counter, rhs: Int) -> Counter {
    return Counter(value: lhs.value + rhs)
}

var counter = Counter(value: 1)
counter += 5        // counter.value is still 1

a += b carries one meaning wherever it appears: update a. A reader does not consult the operator’s definition before believing that, and neither does a reviewer looking at the diff. An implementation that takes its left operand by value cannot update anything the caller can observe, so the statement is silently a no-op. One that takes the operand inout and never writes to it is the same no-op with the type system’s blessing.

Either way the caller’s variable keeps its old value, and nothing at the call site says so. The defect surfaces later and elsewhere, as a total that never grew or a buffer that stayed empty, with the accumulating loop looking perfectly correct.

Notes

There are two triggers, both unambiguous:

  • The left parameter is not inout. Swift value semantics then make the update physically impossible, whatever the body does — the clearest positive there is.

  • The left parameter is inout, and nothing in the body can write to it. A write is any assignment or compound assignment whose left side names the parameter, any method call on it (which may be mutating), and any &lhs handed to another function. Plain reads — lhs.value, lhs[0], lhs + rhs — are not writes.

The second check is deliberately generous about what counts as a write, so an operator that mutates through a route the rule does not model stays silent rather than being reported as a defect.

The comparison operators end in = without being assignments and never fire: ==, !=, ⇐, >=, ===, !==, the pattern-match operator ~=, and their point-wise .== forms are all excluded by name. A bare = is excluded too — Swift does not allow it to be overloaded.

Those names are the standard library’s, and a program may spell a comparison any way it likes. So the file’s own infix operator declaration is read first and outranks the name: infix operator ?== : ComparisonPrecedence says the operator compares, and the overload below it is not reported however it treats its left operand. Only a declaration naming AssignmentPrecedence, or no declaration in the file at all, leaves the spelling to answer.

Remediation

Take the left operand inout and assign to it. A compound-assignment operator returns nothing; the update is its result:

func += (lhs: inout Counter, rhs: Int) {
    lhs.value += rhs
}

If the operation is genuinely a pure function that computes a new value, it should not be named as an assignment. Drop the = and return the result:

func + (lhs: Counter, rhs: Int) -> Counter {
    return Counter(value: lhs.value + rhs)
}

For a type that already implements the non-assigning form, the compound form is one line:

func += (lhs: inout Counter, rhs: Int) {
    lhs = lhs + rhs
}

© 2026 Xygeni. All rights reserved