Yikes. This is one of a long line of problems with comparison (and other) operators that support heterogeneous types.
These are supposed to have been stamped out by brute force for concrete values using additional concretely typed overloads (the problem has never been solved in generic code), but something must have caused a regression. As a result, != is preferring heterogeneous generic comparison to the default integer type over homogeneous (same-type) comparison.
It is more than a performance annotation issue but an outright correctness issue:
that different comparison ops choose different paths (heterogeneous vs homogeneous)
that one of the paths (or both?!) contains a bug
personally I am surprised that comparison ops between different types are even allowed (I still can't do that, say, with an addition / subtraction, and inconsistencies always strike me odd).
What you describe in the first bullet point is the bug.
@dnadoba linked to the relevant proposal. Comparison and other operators which support heterogeneous types can be expressed without implicit integer promotion: addition and subtraction cannot.
Right, to expand on what Xiaodi said, the bug is in the type assigned to expression 1 << 31. The actual comparisons are fine.
Without doing any real investigation, I expect that there's a same-type overload for == on each concrete integer type, but not for !=, so it gets the heterogeneous definition on BinaryInteger, which leads to 1 << 31 having type Int, and on a 64b platform that is (correctly!) not equal to Int32.min. This is quite easy to fix ("simply" add an overload for != as well), but we should be pretty careful about introducing this change, as it can change the behavior of existing programs in very subtle ways.
It's worth noting that it would probably be genuinely useful to provide explicitly typed heterogeneous arithmetic as functions rather than operators, because sometimes you really do need to add a T1 and T2 to produce a T3, and rolling it yourself is a notorious source of bugs in most low-level languages. Writing T3(a) + T3(b) in Swift is safe (it cannot produce an incorrect result value), but will be conservative in its checking (one of the conversions to T3 might fail even though the final result would have been representable).
This is fairly niche however, and probably rightly goes in numerics or a low-level support library, rather than the stdlib.
As I surmised above, I can ascertain that this is a regression in Swift 5 after some spelunking using godbolt.org. @moiseev added the original overloads and wouldn't have just missed one, and my expectation is that (at some point) one was removed inadvertently (or altered) without adequate testing.
Input:
let x = Int32.min == 1 << 31
let y = Int32.min != 1 << 31
let z = !(Int32.min == 1 << 31)
print(x, y, z)
let a = Int32.max < (1 << 31) // false
let b = Int32.max < Int(1 << 31) // true
let c = Int32.max > (1 << 31) // true
let d = Int32.max > Int(1 << 31) // false
let e = Int32.min < (1 << 31) // false
let f = Int32.min < Int(1 << 31) // true
let g = Int32.min > (1 << 31) // false
let h = Int32.min > Int(1 << 31) // false
print(a, b, c, d, e, f, g, h)
I can see how we land there, i.e. 1 << 31 being treated as Int32.min when treated as Int32 so not sure if this is a bug or a feature. Also:
let a = Int32.max < (1 << 32) // false
let b = Int32.max < Int(1 << 32) // true
let c = Int32.max > (1 << 32) // true
let d = Int32.max > Int(1 << 32) // false
Yes, all of these fall out from the fact that (1 << 31), when used in a comparison with Int32, is inferred to have type Int32 and value โ2ยณยน, and (1 << 32) has type Int32 and value 0 in the same context.
Pretty much. You ought to be able to work around it in the short term by using !(a == b) instead of a != b (or by explicitly providing a type on both sides of the inequality).