-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #9776: Don't issue a @switch warning when fewer than 4 cases #9852
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello, and thank you for opening this PR! 🎉
All contributors have signed the CLA, thank you! ❤️
Have an awesome day! ☀️
Any reason this is still a draft? |
Almost done, found some issues related to value classes. |
365f5ad
to
b59eced
Compare
So, it turns out that the naive fix for this broke Wait, what? Switches on value classes? Yes, it turns out that Before this PR, simple case class IntAnyVal(x: Int) extends AnyVal
def testS(x: IntAnyVal) = (x: @switch) match {
case IntAnyVal(1) => 0
case IntAnyVal(10) => 1
case IntAnyVal(100) => 2
case IntAnyVal(1000) => 3
case IntAnyVal(10000) => 4
} [warn] -- [E115] Syntax Warning: /src/dotty-issues/i9776/src/main/scala/testS.scala:31:41
[warn] 31 | def testS(x: IntAnyVal) = (x: @switch) match {
[warn] | ^
[warn] |Could not emit switch for @switch annotated match since there are not enough cases
[warn] 32 | case IntAnyVal(1) => 0
[warn] 33 | case IntAnyVal(10) => 1
[warn] 34 | case IntAnyVal(100) => 2
[warn] 35 | case IntAnyVal(1000) => 3
[warn] 36 | case IntAnyVal(10000) => 4
[warn] 37 | } This warning is spurious, since a lookupswitch is actually emitted. However, it will also issue a warning in cases where a switch is not generated, such as: val Ten = IntAnyVal(10)
def testBad(x: IntAnyVal) = (x: @switch) match {
case IntAnyVal(1) => 0
case Ten => 1
case IntAnyVal(100) => 2
case IntAnyVal(1000) => 3
case IntAnyVal(10000) => 4
} [warn] -- [E115] Syntax Warning: /src/dotty-issues/i9776/src/main/scala/testS.scala:61:43
[warn] 61 | def testBad(x: IntAnyVal) = (x: @switch) match {
[warn] | ^
[warn] |Could not emit switch for @switch annotated match since there are not enough cases
[warn] 62 | case IntAnyVal(1) => 0
[warn] 63 | case Ten => 1
[warn] 64 | case IntAnyVal(100) => 2
[warn] 65 | case IntAnyVal(1000) => 3
[warn] 66 | case IntAnyVal(10000) => 4
[warn] 67 | } In this instance, the warning is valid, although it is issued for the wrong reason. There are sufficient cases, but the use of a val rather than an extractor breaks the switch generation. The "too few cases" message is issued because the shape of the result tree when matching on value classes is different than, say, matching on an Int. This prevents any of the result cases of the generated switch from being picked up by the warning logic, and so every (I think) When the original version of this PR simply disabled the issuance of I have added an additional commit to address this. I am not particularly pleased with the implementation, it feels like a bit of a brittle hack to me. Suggestions are welcomed. I have kept it is a separate commit for independent review and so it can be easily reverted if desired. |
Updated to reflect the recently merged changes to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR, and sorry for the very long delay before someone looked at it.
I have two comments. Also, could you rebase on top of the latest master, even if there is no conflict, to make sure the CI runs against a recent version of the codebase? Thank you.
val caseThreshold = | ||
if ValueClasses.isDerivedValueClass(tpt.tpe.typeSymbol) then 1 | ||
else MinSwitchCases |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we have this branch? Shouldn't it always be 1?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The general idea is to limit the warnings to when numTypes(original.cases) >= MinSwitchCases
However value classes are special cased as an attempt at "best-effort" support. Consider:
case class IntAnyVal(x: Int) extends AnyVal
final val Ten = IntAnyVal(10)
def test(x: IntAnyVal) = (x: @switch) match {
case IntAnyVal(1) => 0
case Ten => 1
case IntAnyVal(100) => 2
case IntAnyVal(1000) => 3
case IntAnyVal(10000) => 4
}
which results in
Item | Value | Notes |
---|---|---|
numTypes(original.cases) |
2 |
{ IntAnyVal, Ten.type } |
numTypes(resultCases) |
0 |
|
emitted bytecode | if-then |
We want to warn here since the switch
was not emitted despite a sufficient number of cases, thus we use a lower caseThreshold
for value classes.
In the spirit of scala/scala#4418, which fixed SI-8731. The consensus seems to be that the intent of the @switch annotation is to warn when the generated bytecode may be poor, not merely if the compiler elects to not emit a tableswitch/lookupswitch. Note that the case threshold for determining whether a switch is emitted is implementation dependent, and currently varies between scalac and dotc. Also note that this implementation will not warn in instances where a switch will never be emitted (e.g. because the match is on a non-integral type) but the number of cases is below the warning threshold. This behavior is consistent with scalac, but may be surprising to the user if another case is added later and a warning suddenly appears. Not all spurious @switch warnings are addressed by this commit, see issue scala#5070 for an example.
Matches on value classes that have an underlying switchable type may be emitted as tableswitch or lookupswitch, but the shape of the result tree differs from ordinary switch-compiled matches. Previously, this caused a spurious warning to be issued if such a match was annotated with @switch, as none of the result cases were discovered by the warning logic, and hence the match was warned as having too few cases. With the warning for too few cases now removed, we have the opposite issue: in no circumstance is a switch warning issued for @switch annotated matches on value classes. This commit attempts to address this issue and restore the @switch warnings for those matches on value classes where a tableswitch or lookupswitch is not emitted. A complicating factor is that the original case types for matches on value class extractors are not singleton types, and so counting the number of unique types is not useful for determining the number of original cases.
Rebased on current master and incorporated review suggestion. |
Ping @sjrd, is this good to merge? |
In the spirit of scala/scala#4418, which fixed SI-8731.
The consensus seems to be that the intent of the
@switch
annotation is to warn when the generated bytecode may be poor, not merely if the compiler elects to not emit a tableswitch/lookupswitch.Note that the case threshold for determining whether a switch is emitted is implementation dependent, and currently varies between
scalac
anddotc
.Also note that this implementation will not warn in instances where a switch will never be emitted (e.g. because the match is on a non-integral type) but the number of cases is below the warning threshold. This behavior is consistent with
scalac
, but may be surprising to the user if another case is added later and a warning suddenly appears.Not all spurious
@switch
warnings are addressed by this PR, see #5070 for an example.Fixes #9776.