-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Description
Previous ID | SR-13755 |
Radar | rdar://problem/70485231 |
Original Reporter | @regexident |
Type | Bug |
Additional Detail from JIRA
Votes | 2 |
Component/s | |
Labels | Bug |
Assignee | None |
Priority | Medium |
md5: 75125d4cd126a1caa0e7e05edfa98e00
relates to:
- SR-4984 Integer literal not inferred as unsigned in != comparison
Issue Description:
The existence of this method on BinaryInteger
…
static func == <Other>(lhs: Self, rhs: Other) -> Bool where Other : BinaryInteger
… has caused me quite some headache today, and wasted several hours spent debugging.
The fact that this method (and its brothers & sisters <=
, >=
, etc.) performs the following conversion on the rhs
…
let rhsAsSelf = Self(truncatingIfNeeded: rhs)
… makes the following code behave in rather unexpected ways:
func nonGeneric(_ t: UInt8) {
precondition(t != 0b0)
precondition(t == ~0b0)
}
func generic<T: BinaryInteger>(_ t: T) {
precondition(t != 0b0)
precondition(t == ~0b0)
}
let t: UInt8 = ~0b0
nonGeneric(t)
generic(t)
One would expect both calls to succeed (and most importantly: behave identical!).
Alas the second one has its second precondition fail:
Terminated due to signal: ILLEGAL INSTRUCTION (4)
Precondition failed: file Untitled.swift, line 8
Why? Let's investigate.
First we'll add the following helper function to help in inspecting rhs
's type:
func spyType<T>(_ t: T) -> T {
print("-", type(of: t))
return t
}
Then we wrap rhs
with spyType(…)
:
func nonGeneric(_ t: UInt8) {
print(#function)
precondition(t != spyType(0b0))
precondition(t == spyType(~0b0))
}
func generic<T: BinaryInteger>(_ t: T) {
print(#function)
precondition(t != spyType(0b0))
precondition(t == spyType(~0b0))
}
… to find this printed on the console:
nonGeneric(_:)
- Int
- UInt8
generic(_:)
- Int
- Int
The type-checker seems to infer rhs
to be T: BinaryInteger
, and then picks the default: Int
, which then overflows on Self(truncatingIfNeeded: rhs)
.
The reason we get the correct UInt8
in there at all is that Int
does not have an ~
operator, forcing the type-checker to infer the correct type of UInt8
, which does.
While this is correct behavior from the perspective of the type-checker (thanks to the existence of above operator on BinaryInteger
), it leads to subtle and utterly unexpected bugs and thus should be considered a bug, I think.
This is bad. So bad I wonder if above (convenience!) operator should exist at all, as it's the cause for all of this. There are just too many ways this could lead to unexpected behavior.
At the very least the compiler should emit a warning à la "The type of rhs is ambiguous, use an explicit type".
But even then naïvely wrapping the literal in a `UInt8(…)` to make it explicit doesn't solve the issue either and actually causes a fatal error now (but hey, at least it crashes now!):
func nonGeneric(_ t: UInt8) {
print(#function)
precondition(t != UInt8(spyType(0b0)))
precondition(t == UInt8(spyType(~0b0)))
}
func generic<T: BinaryInteger>(_ t: T) {
print(#function)
precondition(t != T(spyType(0b0)))
precondition(t == T(spyType(~0b0)))
}
nonGeneric(_:)
- Int
- Int
Fatal error: Negative value is not representable: file Swift/Integers.swift, line 3439
One needs to actually promote the rvalues to explicitly typed lvalues to make the above code behave as expected:
func nonGeneric(_ t: UInt8) {
print(#function)
let emptyMask: UInt8 = 0b0
precondition(t != spyType(emptyMask))
let fullMask: UInt8 = ~0b0
precondition(t == spyType(fullMask))
}
func generic<T: BinaryInteger>(_ t: T) {
print(#function)
let emptyMask: T = 0b0
precondition(t != spyType(emptyMask))
let fullMask: T = ~0b0
precondition(t == spyType(fullMask))
}
Which now FINALLY produces the expected behavior …
nonGeneric(_:)
- UInt8
- UInt8
generic(_:)
- UInt8
- UInt8
As a developer this is not an acceptable workaround to me, tbh.
Any user who does not already have a solid understanding, of how type-inference in Swift works, would be utterly lost here. And even those who do will have a hard time finding the culprit.
It should not behave this way and it should definitely should not require me to bend over backwards to make it work at all.
(ALSO, I CAN HAZ TYPE ASCRIPTION? KTHXBAI!)