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
Incorrect match inexhaustive warnings in Scala 2.13.4 #12232
Comments
Minimised: $ m Test.scala
object Test {
sealed trait Parsed2[+T]
object Parsed2 {
class Success[+T](val tree: T) extends Parsed2[T]
class Error(val pos: Int, val message: String, val details: Exception) extends Parsed2[Nothing]
object Success {
def apply[T](tree: T): Success[T] = new Success[T](tree)
def unapply[T](x: Success[T]): Option[T] = if (x == null) None else Some(x.tree)
}
object Error {
def apply(pos: Int, message: String, details: Exception): Error = new Error(pos, message, details)
def unapply(x: Error): Option[(Int, String, Exception)] = if (x == null) None else Some((x.pos, x.message, x.details))
}
}
def m[Source](p: Parsed2[Source]) = p match {
case Parsed2.Success(_) => println("Success")
case Parsed2.Error(_, _, _) => println("Error")
}
} $ scalac -2.13.4 Test.scala
Test.scala:19: warning: match may not be exhaustive.
It would fail on the following inputs: Error(), Success()
def m[Source](p: Parsed2[Source]) = p match {
^
1 warning The problem is that Success and Error aren't case classes, which means that the pattern matcher uses the custom Basically the compiler is saying that the unhandled cases are those where One way this could be avoided is by changing the custom extractors to return I no longer think this is a regression and I'm not even sure it's a bug any more... Everything's pretty much working as intended, given what is provided to the compiler. Making this better would required enhancing things in some way... |
|
That's what I said, except it makes it unsafe for random other unapply calls. Perhaps that's acceptable (I wouldn't hesitate to do that). Could be the macro implementation is from before that change. |
Ah ok I misunderstood, yeah I don't think null-safety of explicitly called unapply is something anyone really cares about. |
Just to confirm myself: $ m Test.scala
object Test {
sealed trait Parsed2[+T]
object Parsed2 {
class Success[+T](val tree: T) extends Parsed2[T]
class Error(val pos: Int, val message: String, val details: Exception) extends Parsed2[Nothing]
object Success {
def apply[T](tree: T): Success[T] = new Success[T](tree)
def unapply[T](x: Success[T]): Some[T] = Some(x.tree)
}
object Error {
def apply(pos: Int, message: String, details: Exception): Error = new Error(pos, message, details)
def unapply(x: Error): Some[(Int, String, Exception)] = Some((x.pos, x.message, x.details))
}
}
def m[Source](p: Parsed2[Source]) = p match {
case Parsed2.Success(_) => println("Success")
case Parsed2.Error(_, _, _) => println("Error")
}
}
$ scalac Test.scala
$ Given that, I'm going to call this one not a bug. |
How to avoid the warning for extractors returning object Test {
sealed trait Foo
final class Bar extends Foo
object Bar {
def unapply(o: Bar) /* : true */ = true
}
def f(foo: Foo) = foo match {
case Bar() => println("Bar")
}
}
Uncommenting the true literal return type gives the following error:
|
That came up in #12240, which @martijnhoekstra is trying to get working in scala/scala#9343. |
It's close, but not quite: 9343 is doing irrefutable name-based extractors. This is an irrefutable boolean extractor that's not working. Irrefutable extractors (whether the |
It was so close, that it's now quite. I made a separate issue for it but included the fix in the same PR |
I'm still seeing an issue with this in 2.13.6 Minimal case: abstract class LA
class SL extends LA
class LanguageParameter[L <: LA](
val in: Boolean
)
object LanguageParameter {
def unapply[L <: LA](param: LanguageParameter[L]): Option[Boolean] =
Some(param.in)
}
object App {
def param: Option[LanguageParameter[SL]] = Some(new LanguageParameter[SL](true))
val z: List[Boolean] =
param.toList.flatMap {
case LanguageParameter(in) =>
Some(in)
}
} yields
|
@blast-hardcheese the example says to use |
@som-snytt My apologies, I falsely presumed this was related to some type bounded |
Follow-up, for those curious and or following the trail. The change that introduced this behaviour is scala/scala@c54081e#diff-2215b486f4bacbe3dcddde189788c570d51345473b306e22ced6cce6ca4c11edL508-L517 diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala
index 301d2dc32a..cca38266e4 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala
@@ -505,7 +505,6 @@ trait MatchAnalysis extends MatchApproximation {
// - back off (to avoid crying exhaustive too often) in unhandled cases
val start = if (StatisticsStatics.areSomeColdStatsEnabled) statistics.startTimer(statistics.patmatAnaExhaust) else null
var backoff = false
- val strict = settings.XstrictPatmatAnalysis.value
val approx = new TreeMakersToProps(prevBinder)
val symbolicCases = approx.approximateMatch(cases, approx.onUnknown { tm =>
@@ -514,8 +513,9 @@ trait MatchAnalysis extends MatchApproximation {
case ExtractorTreeMaker(_, _, _)
| ProductExtractorTreeMaker(_, _)
| GuardTreeMaker(_) =>
- if (strict) False else True
+ False // <-- This used to be True by default, you had to opt-in to this "strict" behaviour.
- case _ => // debug.patmat("backing off due to "+ tm)
+ case _ =>
+ debug.patmat("backing off due to "+ tm)
backoff = true
False
}) Setting that value back to There does appear to be a bit of a bit of incorrect reporting around this, fwiw, an example from scalameta's codebase: private def fqRef(fqName: String, isTerm: Boolean): Tree = {
def loop(parts: List[String]): Tree = parts match {
case Nil :+ part => q"${TermName(part)}"
case rest :+ part => q"${loop(rest)}.${TermName(part)}"
}
val prefix :+ last = fqName.split("\\.").toList
if (isTerm) q"${loop(prefix)}.${TermName(last)}" else tq"${loop(prefix)}.${TypeName(last)}"
} reports
despite def loop(parts: List[String]): Tree = parts match {
case part :: Nil => q"${TermName(part)}"
case part :: rest => q"${loop(rest)}.${TermName(part)}"
} correctly identifies
If there are no objections, I think it makes sense to report these kinds of edge cases in this issue, despite it already being closed, as there's already so much context here already. If there are objections, I'm happy to open a new issue. |
Amusingly, scalameta does, up until now I hadn't realized this whole issue pertained to scalameta in the first place. Perhaps macro/compiler internals are an edge case due to their more... eccentric requirements? |
I've also got something of a usability question around this; is there a way, without Previously, if we were not strict in Given the warning
a user may be inclined to trust the construction suggested by the compiler: @@ -65,6 +66,7 @@ class SpringMvcServerGenerator private (implicit Cl: CollectionsLibTerms[JavaLan
case OctetStream => new FieldAccessExpr(new NameExpr("MediaType"), "APPLICATION_OCTET_STREAM_VALUE")
case TextContent(name) => new StringLiteralExpr(name)
case BinaryContent(name) => new StringLiteralExpr(name)
+ case BinaryContent() => ??? // TODO: What do we do if we get here?
} we're greeted by the (expected)...
Is there a way to address the looseness of |
Extractors don't need to deal with |
@blast-hardcheese You have something like the following. The revised unapply does not warn, TIL. Apparently, the narrower param type suffices to express "I cover this case". There are other discussions about adding an annotation to say what an extractor covers, but I did not follow them. Again, you'd get a wider and smarter audience on the forum.
but wait, you want a warning if cases are added, so better would be
but that actually warns about |
this is also doesn't warn and is closer to the sample. I guess if you want separate extractors for Y and Z that would also work.
Edit: I just went, wait did I accidentally close the issue? before remembering we're chatting on a closed ticket. Probably any of discord, discourse, gitter, or a separate ticket (for buggy behavior) would be better, putting on my Dale hat and Seth t-shirt. |
The difference between |
There are open links for the extractor coverage issue on dotty. |
Found another issue in 2.13.6 (and presumably in 2.13.4) which works fine in 2.13.3: def foo(bool: Boolean): Boolean = {
val bar: Boolean = true
bar match {
case false => false
case true if bool => true
case true if !bool => true
}
} This match is clearly exhaustive, but the compiler errors (with fatal warnings on):
Is it appropriate to discuss this here or should I make a new issue? |
@coreywoodfield my naive expectation is that the guards break exhaustiveness, but I also expect the breakage to be "false negative" (no warn) instead of false positive (warn incorrectly). I saw a similar false pos on another ticket. (I'm not following the latest work here.) I would be surprised if it accurately warned (or not) if two guards are identical except for negation, although that would be clever. To answer the direct question, I think a discussion on the forum would be most productive as a first step, as it is not clearly a bug. |
I checked 2.13.3 again, and it looks like the false negative route was taken there (so def foo(bool: Boolean): Boolean = {
val bar: Boolean = true
bar match {
case false => false
case true if bool => true
}
} doesn't warn even though it's not exhaustive) so it seems like 2.13.4 just switched to the false positive, which is overall safer so a good change. It would be nice if it could recognize negated guards though. Further discussion here: https://users.scala-lang.org/t/exhaustive-match-checking-warns-incorrectly-when-cases-are-exhaustive-but-include-guards/7916 |
I've responded on Discourse. Please use Discourse for questions. (There's also Stack Overflow, Discord, and so on.) These questions are perfectly good questions, but we are adamant about keeping scala/bug tickets focused on specific individual bugs, not free-for-alls for discussing entire general areas of functionality (for example, exhaustiveness checking). |
reproduction steps
using Scala 2.13.4 and scalameta 4.3.24,
problem
The Scala 2.13.4 compiler produces the following message:
The Scala 2.13.3 compiler does not produce this warning.
This might be related to issue #12186.
Attached is a zip file to ease reproducing the issue.
scala-2.13.4-match-issue.zip
The text was updated successfully, but these errors were encountered: