-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #5494: Spurious unchecked warning when type testing an alias #8808
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
555bce3
to
7722fa9
Compare
Previously, the check relied used `maximizeType`, which means that depending on which bound got picked by type inference, the check might fail. Instead, we only need to check if there is a possible constraint solution by making sure both subtype tests return true. I'm not sure if this is always a safe type test, but it at least avoids spurious warnings. This is necessary to bootstrap dotty after the previous commit which added a `dealias` to `recur`, this probably means that there are other situations where `recur` is missing checks.
7722fa9
to
3991198
Compare
There's still some bootstrapping errors indicating more issues with the checking logic: [error] -- Error: /home/smarter/opt/dotty/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala:95:15
[error] 95 | case p: WithLazyField[?] =>
[error] | ^^^^^^^^^^^^^^^^^^^
[error] |the type test for dotty.tools.dotc.ast.Trees.WithLazyField[_] @_ cannot be checked at runtime
[error] -- Error: /home/smarter/opt/dotty/compiler/src/dotty/tools/dotc/core/Annotations.scala:89:59
[error] 89 | override def isEvaluated: Boolean = myTree.isInstanceOf[Tree]
[error] | ^^^^^^^^^^^^^^^^^^^^^^^^^
[error] |the type test for dotty.tools.dotc.ast.tpd.Tree cannot be checked at runtime
[error] -- Error: /home/smarter/opt/dotty/compiler/src/dotty/tools/dotc/core/Annotations.scala:122:59
[error] 122 | override def isEvaluated: Boolean = myTree.isInstanceOf[Tree]
[error] | ^^^^^^^^^^^^^^^^^^^^^^^^^
[error] |the type test for dotty.tools.dotc.ast.tpd.Tree cannot be checked at runtime
[error] -- Error: /home/smarter/opt/dotty/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala:58:10
[error] 58 | _: WithLazyField[?]
[error] | ^^^^^^^^^^^^^^^^^^^
[error] |the type test for dotty.tools.dotc.ast.Trees.WithLazyField[_] @_ cannot be checked at runtime
@liufengyun do you think you could have a look at those ? |
Hmm actually maybe this one is correct: |
`Tree` here is `tpd.Tree` and we're checking against `tpd.Tree | (Context => tpd.Tree)`. Because `Function1` is a trait, we cannot rule out that there exists some subclass of `Tree` which extends it and is not a `tpd.Tree`. The unchecked warning started appearing after the first commit in this PR.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Sure, I'll take a look at it. Thanks for the diagnosis. |
OK, so the real issue with P1 <:< X // constraint P1
// use fromScala2x to avoid generating pattern bound symbols
maximizeType(P1, span, fromScala2x = true)
val res = P1 <:< P which succeeds even if By the way, the logic in X.classSymbol.exists && P.classSymbol.exists && !X.classSymbol.asClass.mayHaveCommonChild(P.classSymbol.asClass) || ... Meaning that if class A
class B[T]
object Test {
def foo(x: A) = x match {
case x: B[Int] =>
case _ =>
}
} |
Maybe we should consider redoing TypeTestsCasts based on the scala 2 implementation which is much more battle-tested: https://github.com/scala/scala/blob/2.13.x/src/compiler/scala/tools/nsc/typechecker/Checkable.scala |
@smarter The warning for always false test and runtime checkable are different. The always false test warning is actually issued: scala> class A
| class B[T]
|
| object Test {
| def foo(x: A) = x match {
| case x: B[Int] =>
| case _ =>
| }
| }
6 | case x: B[Int] =>
| ^
| this case is unreachable since type A and class B are unrelated |
I had a look at our implementation, I don't think it's a good idea to go to Scala 2 implementation, I believe our approach is more principled. We have a spec, and the bugs so far found can be easily fixed without changing the spec nor complicating the implementation. The As far as I know, the Scala 2 implementation does not resort to type inference, thus it cannot cover the cases that the Dotty implementation can handle. For example, Scala 2 does not issue any warning for the following code, while our check do: trait A[+T]
class B[T] extends A[T]
class C
class D extends C
def quux(a: A[C]): Unit = a match {
case _: B[C] => // error!!
}
quux(new B[D]) Another evidence for the correctness of the new implementation is that it found many true positives in the compiler itself, catching mistakes from missing variance annotation on tree definitions (58f506b), missing prefix such as It also catches the subtle errors demonstrated in the following code: trait Tree[-T]
trait Context
trait Type
def foo1(myTree: Tree[Type] | (Context => Tree[Type])) =
println(myTree.isInstanceOf[Tree[Type]]) // error
/* class DummyTree extends Tree[Nothing] with (Context => Tree[Type]) */ So I'd suggest we stay with the current scheme until a better solution emerges. |
test performance please |
performance test scheduled: 3 job(s) in queue, 1 running. |
@@ -579,7 +579,7 @@ object Trees { | |||
} | |||
|
|||
/** Array(elems) */ | |||
class JavaSeqLiteral[T >: Untyped] private[ast] (elems: List[Tree[T]], elemtpt: Tree[T])(implicit @constructorOnly src: SourceFile) | |||
class JavaSeqLiteral[-T >: Untyped] private[ast] (elems: List[Tree[T]], elemtpt: Tree[T])(implicit @constructorOnly src: SourceFile) |
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.
This is good for uniformity but why would this make any difference for type test safety warnings ?
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 following code shows why it matters:
trait Tree[-T]
class JavaSeqLiteral[T] extends Tree[T]
trait Type
class DummyTree extends JavaSeqLiteral[Any]
def foo1(tree: Tree[Type]) =
tree.isInstanceOf[JavaSeqLiteral[Type]] // error
foo1(new DummyTree)
@@ -117,7 +117,7 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend | |||
def isInstanceOfPackageClause(using ctx: Context): IsInstanceOf[PackageClause] = new { | |||
def runtimeClass: Class[?] = classOf[PackageClause] | |||
override def unapply(x: Any): Option[PackageClause] = x 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.
Can we avoid all these unchecked warnings by replacing x: Any
by x: tpd.Tree
here and below? /cc @nicolasstucki
Performance test finished successfully: Visit http://dotty-bench.epfl.ch/8808/ to see the changes. Benchmarks is based on merging with master (2466d97) |
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 a lot for the detailed comments! I have a couple of suggestions but otherwise LGTM.
X.classSymbol.exists && P.classSymbol.exists && !X.classSymbol.asClass.mayHaveCommonChild(P.classSymbol.asClass) || | ||
isClassDetermined(X, tpe)(ctx.fresh.setNewTyperState()) || | ||
isClassDetermined(stripTypeParam(X), tpe)(ctx.fresh.setNewTyperState()) | ||
isClassDetermined(X, tpe)(ctx.fresh.setNewTyperState().setFreshGADTBounds) || |
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.
This method is called isClassDetermined
but actually I don't think it's specific to classes: P
could be an application of a type lambda for example.
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 case for non-class type constructors is tested in the case TypeProxy
, where we assume it cannot be checked at runtime. It seems the assumption is practical enough to cover use cases we know of. When the need emerges, we may relax the check a little bit to make it more expressive.
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 case for non-class type constructors is tested in the case TypeProxy, where we assume it cannot be checked at runtime.
Ah I see, but this is incomplete: it doesn't handle situation where a type lambda is directly applied (or appears behind an alias):
trait A[T]
trait B[T] extends A[T]
object Test {
def foo(x: ([X] =>> A[X])[Any]) = x match {
case x: ([X] =>> B[X])[Any] =>
case _ =>
}
}
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.
Yes, we haven't seen use cases like this (though the example above type checks). When valid use cases emerge we can easily support them using the method typeArgsTrivial
.
No description provided.