Aye, and this isn't even theoretical, it's a very real issue that comes up often with Sorbet, a static type checker for Ruby.
Ruby doesn't have properties. An object's instance variables are only exposed through methods. To the outside world, there's no way to distinguish between a method that's just a dumb reader (that returns consistent values between calls) vs a method that does more complicated things (and returns different values on each call).
Thus, Sorbet is forced to pessimistically assume all method calls can return a different value from one call to the next, making this code fail type-checking:
x = !maybe_int.nil? && (2 * maybe_int)
(Ruby lets you omit the parens, so maybe_int
is a method call, as if you wrote maybe_int().nil?
)
The fix is to introduce a new local variable:
tmp = maybe_int
y = !tmp.nil? && (2 * tmp)
Where Sorbet can be confident that the value of tmp
stays consistent from one read to the next.