No Useless Increment
ID |
kotlin.no_useless_increment |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Kotlin |
Tags |
dead-code, reliability, suspicious-assignment |
Description
Reports a postfix ++ or -- whose effect is thrown away, in the two shapes where the mistake is
unambiguous:
-
return i`, the expression body `fun bump(): Int = i, and a lambda whose last expression isi++— the postfix form yields the value from before the update, and thevarit just changed dies with the call. -
i = i++— the right-hand side is the old value ofi, written straight back over the incremented one.
Rationale
fun bumpLocal(start: Int): Int {
var local = start
return local++ // returns `start`, and the increment is lost with the frame
}
fun rewind() {
cursor = cursor++ // reads cursor, increments it, then overwrites it with the old value
}
Both compile and run. return local` hands the caller the value from before the increment, and
the increment lands in storage that vanishes when the function returns, so it is unobservable — the
author meant `return local + 1` or `return local. cursor = cursor++ is a complete no-op: the
increment happens and is then overwritten by the value read before it.
The returned-value shape is reported only when the operand is a local var declared in the same
function. Storage that survives the call is not a defect and is not reported:
class IdGenerator {
private var next = 0
fun nextId(): Int = next++ // OK — the canonical id generator; the property keeps the value
}
fun makeCounter(): () -> Int {
var i = 0
return { i++ } // OK — Kotlin captures a `var` by reference, so `i` survives
}
A prefix return i` is correct, since the returned value *is* the updated one. A bare `i
statement is the ordinary way to count and is never reported, and neither is a postfix whose value
something consumes — j = i`, `println(i), return i + 1`. A member or indexed target
(`box.count = box.count, values[0] = values[0]++) is left alone rather than guessing whether
the two sides denote the same storage.
Note that a Kotlin parameter is a val and cannot be incremented at all, so the only operand this
rule ever reports on is a local var.