-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #2363: better handle extractors in exhaustivity check #2424
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
@@ -23,7 +23,7 @@ trait UniqueMessagePositions extends Reporter { | |||
for (pos <- m.pos.start to m.pos.end) { | |||
positions get (ctx.source, pos) match { | |||
case Some(level) if level >= m.level => shouldHide = true | |||
case _ => positions((ctx.source, pos)) = m.level | |||
case _ => // positions((ctx.source, pos)) = m.level |
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 CI fails because the change of this line. There's some bug hidden in isHidden
, I have to comment it out to temporarily make sure all the exhaustivity/unreachability warnings are shown. Otherwise, the unreachable warning in the PR example will be incorrectly hidden.
1. Better handle exactors 2. Translate the pattern `List(pat1, pat2, c:_*)` to space semantically 3. Deal with extractors that return `Some[_]` Previously we approximate extractor either to the extracted type or Empty, which is too coarse, and it causes some false positives about unreachable case. Introducing the concept extractor into the theory enables us to inspect the patterns inside extractors and check more unreachable cases, which is not supported by Scalac. Scalac is silent with following code, but Dotty reports both unexhaustive and unreachable warnings. sealed trait Node case class NodeA(i: Int) extends Node case class NodeB(b: Boolean) extends Node case class NodeC(s: String) extends Node object Node { def unapply(node: Node): Option[(Node, Node)] = ??? } object Test { def foo(x: Node): Boolean = x match { // unexhaustive case Node(NodeA(_), NodeB(_)) => true case Node(NodeA(4), NodeB(false)) => true // unreachable code } }
@liufengyun Not directly related to this PR, but while you're working on this code, could you have a look at the exhaustiveness warnings reported against dotty itself when bootstrapping? See http://dotty-ci.epfl.ch/lampepfl/dotty/2267/4 for example. You can reproduce that locally by running |
@smarter Sure, I'll examine log to see if there're still false positives. |
@felixmulder The last commit 31ae57b contains some changes to the doc-tool, could you please confirm if the changes make sense? |
@@ -77,6 +77,7 @@ trait MemberLookup { | |||
case (x :: xs, _) => | |||
if (xs.nonEmpty) globalLookup | |||
else lookup(entity, packages, "scala." + query) | |||
case (Nil, _) => ??? // impossible as long as query is not empty |
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.
let's do something better than ???
, otherwise LGTM
@@ -426,14 +509,14 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { | |||
case space => List(space) | |||
} | |||
case OrType(tp1, tp2) => List(Typ(tp1, true), Typ(tp2, true)) | |||
case _ if tp =:= ctx.definitions.BooleanType => | |||
case tp if tp =:= ctx.definitions.BooleanType => |
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.
tp.isRef(defn.BooleanClass)
@@ -549,6 +632,8 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { | |||
|
|||
if (ctx.definitions.isTupleType(tp)) | |||
signature(tp).map(_ => "_").mkString("(", ", ", ")") | |||
else if (sym.showFullName == "scala.collection.immutable.List") |
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.
We should never do string comparisons to determine the type of something. Instead something like sym == defn.ListClass
should be used, if ListClass
is not defined in Definitions.scala
then it should be added.
else Empty | ||
}.reduce((a, b) => Or(List(a, b))) | ||
|
||
val curr = project(cases(i).pat) | ||
|
||
debug.println(s"---------------reachable? ${show(curr)}") |
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.
I suggest adding a Printer
for the exhaustiveness checker in https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/config/Printers.scala instead of using debug
here.
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 debug
is just a rename of the exhaustivity printer :)
@@ -98,19 +102,31 @@ trait SpaceLogic { | |||
def show(sp: Space): String | |||
|
|||
/** Simplify space using the laws, there's no nested union after simplify */ | |||
def simplify(space: Space): Space = space match { | |||
def simplify(space: Space, aggressive: Boolean = false): Space = space match { |
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.
Could you update the documentation of this method to explain what the aggressive
parameter is for?
isSubType(tp1, tp2) || tryDecompose1(tp1) || tryDecompose2(tp2) | ||
case (Typ(tp1, _), Or(ss)) => | ||
isSubType(tp1, tp2) | ||
case (Typ(tp1, _), Or(ss)) => // optimization |
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.
Maybe improve this comment to explain what this is an optimization of?
@@ -386,7 +469,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { | |||
/** Is `tp1` a subtype of `tp2`? */ | |||
def isSubType(tp1: Type, tp2: Type): Boolean = { | |||
// check SI-9657 and tests/patmat/gadt.scala | |||
val res = erase(tp1) <:< erase(tp2) | |||
val res = tp1 <:< erase(tp2) |
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.
Could you add a comment explaining why it's correct to erase tp2
but not tp1
?
@@ -420,7 +420,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { | |||
Kon(pat.tpe.stripAnnots, pats.map(pat => project(pat))) | |||
else if (fun.symbol.owner == scalaSeqFactoryClass && fun.symbol.name == nme.unapplySeq) | |||
projectList(pats) | |||
else if (fun.symbol.info.resultType.isRef(scalaSomeClass)) | |||
else if (!fun.symbol.info.finalResultType.isRef(scalaOptionClass)) |
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.
I don't think this is correct in general, the return type of an option-less patmat may define a def isEmpty: Boolean
method, if this method return false
, then the case will not be taken.
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.
Sorry, if isEmpty
returns true
, not false
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.
Ah, I see, then it means we cannot tell from types whether an option-less is exhaustive or not. I'll retract the last commit.
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.
Well, if the method is defined as def isEmpty: false
then we know it will always return false :). But we should probably not encode this logic in the exhaustiveness checker until we have a real spec for option-less pattern-matching that explains all cases.
@@ -469,6 +475,19 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { | |||
/** Is `tp1` a subtype of `tp2`? */ | |||
def isSubType(tp1: Type, tp2: Type): Boolean = { | |||
// check SI-9657 and tests/patmat/gadt.scala | |||
// | |||
// `erase` is a walkaround to make the following code pass the check: |
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.
typo: walkaround -> workaround
private val scalaSeqFactoryClass = ctx.requiredClass("scala.collection.generic.SeqFactory".toTypeName) | ||
private val scalaListType = ctx.requiredClassRef("scala.collection.immutable.List".toTypeName) | ||
private val scalaNilType = ctx.requiredModuleRef("scala.collection.immutable.Nil".toTermName) | ||
private val scalaConType = ctx.requiredClassRef("scala.collection.immutable.::".toTypeName) | ||
private val scalaConsType = ctx.requiredClassRef("scala.collection.immutable.::".toTypeName) |
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.
You could get rid of all the .toTypeName
here if Decorators.PreNamedString
is imported.
Fix #2363: better handle extractors in exhaustivity check
List(pat1, pat2, c:_*)
to space semanticallySome[_]
or option-less typePreviously we approximate extractor either to the extracted type space or Empty,
which is too coarse, and it causes some false positives about unreachable case.
Introducing the concept extractor into the theory enables us to inspect the
patterns inside extractors and check more unreachable cases, which is not
supported by Scalac.
Scalac is silent with following code, but Dotty correctly reports both unexhaustive and
unreachable warnings.