Standard Operator Precedence Changed
ID |
swift.standard_operator_precedence_changed |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Swift |
Tags |
reliability, suspicious-code |
Description
Reports a declaration that changes the precedence or associativity of an operator the
Swift standard library already defines. Two shapes do that: an infix operator
declaration that re-declares a standard operator under a different precedence group, and a
precedencegroup declaration that re-declares one of the standard library’s own groups.
Rationale
infix operator + : MultiplicationPrecedence
precedencegroup AdditionPrecedence {
associativity: right
higherThan: MultiplicationPrecedence
}
Precedence and associativity are what let a reader parse an expression without looking
anything up: a + b * c means a + (b * c) in every Swift file ever written, and
a - b - c means (a - b) - c. Moving a standard operator to another precedence group
re-parses every existing use of it in the module — silently, with not one call site
changed. Code that was correct yesterday now computes something else, and a reviewer
looking at the call site has no reason to suspect the grouping is not the one everybody
knows.
Re-declaring a standard precedence group does the same thing to every operator that group
holds at once, and switching its associativity from left to right changes the meaning
of every chained subtraction and division in the module.
The damage is invisible in review because the diff that causes it is one line, far from any of the expressions it changes.
Notes
Legitimate operator work never fires:
-
Declaring a genuinely new operator is correct Swift and is not reported. That is exactly how a custom operator is introduced, and it changes nothing about the existing ones. Only an operator name the standard library already defines is a candidate.
-
Re-declaring a standard operator under its own standard group is not reported either. It is redundant, but it moves nothing and breaks nothing.
-
Declaring a new precedence group is not reported, however it relates itself to the standard ones —
higherThan/lowerThanon a new group orders the new group; it does not reorder the standard operators. -
prefixandpostfixdeclarations are out of scope. Swift gives prefix and postfix operators neither a precedence group nor an associativity, so no such declaration can change either one.
An infix operator declaration with no group clause is not reported either, and for the same
reason as re-declaring an operator under its own group: a bare infix operator < written above
a file’s own overload of < names no destination group, so there is no move for a reader of a
call site to be caught out by. The default group a groupless declaration lands in applies to an
operator being introduced, and one the standard library already defines is not being introduced.
Remediation
To add behaviour for a standard operator on your own type, implement the operator — do not re-declare it. The declaration already exists in the standard library, and the implementation inherits its precedence:
struct Vector {
let x: Double
let y: Double
}
extension Vector {
static func + (lhs: Vector, rhs: Vector) -> Vector {
return Vector(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
}
When you genuinely need different grouping, introduce a new operator and a new precedence group. Nothing that already exists changes meaning:
precedencegroup ExponentiationPrecedence {
associativity: right
higherThan: MultiplicationPrecedence
}
infix operator ** : ExponentiationPrecedence