New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SR-13755] Convenience operators on BinaryInteger causing subtle bugs due to unintuitive type inference #56152
Comments
@swift-ci create |
Swift has |
It looks like the type checker does find a solution that infers the literal type as `T`, but it's ranked lower than the solution that infers the type to be `Int` even though there's an implicit conversion in that solution. It seems to me like a solution with the default literal type + implicit conversion is given higher priority than a solution with non-default literal type, which IMO makes literals without type annotations kind of unusable in a generic context like this... |
Oh whoops I misread the description, it's not an implicit conversion - `Self(truncatingIfNeeded: rhs)` happens in the body of the operator. That makes a lot more sense. I do still think it's unexpected that this overload is preferred. I think this means you can never use any of the comparison operators with literals and rely on the other side of the expression providing context for the literal. |
I could swear that I filed a bug for this years ago, but it appears not. I described it here in my review of Swift numeric types and protocols. The issue was originally encountered in the standard library itself. The bug is very subtle to debug; it is rarely observable because this PR added concrete overloads so that you don't encounter the same problem if you're not using generics. When it happens though, it is very bad. I tried a couple of times to address this with standard library changes, but it's really not possible. @rudkx's designated type proposal for operators would have fixed the issue, but the design (which I thought was fantastic) never advanced and the change was backed out of the compiler. Hopefully @hborla can push this forward with her upcoming changes in this area! |
@regexident That PR improves performance in some common cases of expressions with operators, but it does not change the behavior of operator overload resolution. The issue here is partially due to the overload set on |
I see. Thanks for the clarification, @hborla. Much appreciated! |
Additional Detail from JIRA
md5: 75125d4cd126a1caa0e7e05edfa98e00
relates to:
Issue Description:
The existence of this method on
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 therhs
…… makes the following code behave in rather unexpected ways:
One would expect both calls to succeed (and most importantly: behave identical!).
Alas the second one has its second precondition fail:
Why? Let's investigate.
First we'll add the following helper function to help in inspecting
rhs
's type:Then we wrap
rhs
withspyType(…)
:… to find this printed on the console:
The type-checker seems to infer
rhs
to beT: BinaryInteger
, and then picks the default:Int
, which then overflows onSelf(truncatingIfNeeded: rhs)
.The reason we get the correct
UInt8
in there at all is thatInt
does not have an~
operator, forcing the type-checker to infer the correct type ofUInt8
, 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!):
One needs to actually promote the rvalues to explicitly typed lvalues to make the above code behave as expected:
Which now FINALLY produces the expected behavior …
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!)
The text was updated successfully, but these errors were encountered: