-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Fix #3324: add isInstanceOf
check
#4045
Conversation
b42414e
to
3dbb2fa
Compare
enum Result[+T, +E] { | ||
case OK [T](x: T) extends Result[T, Nothing] | ||
case Err[E](e: E) extends Result[Nothing, E] | ||
} |
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.
Add a comment why this test?
@@ -152,7 +153,7 @@ class SyntheticMethods(thisPhase: DenotTransformer) { | |||
* def equals(that: Any): Boolean = | |||
* (this eq that) || { | |||
* that match { | |||
* case x$0 @ (_: C) => this.x == this$0.x && this.y == x$0.y | |||
* case x$0 @ (_: C @unchecked) => this.x == this$0.x && this.y == x$0.y |
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.
Add comment warning maintainers this code is fragile, since unchecked might hide type errors...
def eval[T](e: Exp[T]) = e match { | ||
case Fun(x: Fun[Int, Double]) => ??? // error | ||
case Fun(x: Exp[Int => String]) => ??? // error | ||
} |
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.
Looking at this with @liufengyun, we realize this complication has to do with compiling a match like:
def eval[T](e: Exp[T]) = e match {
case f @ Fun(x: Fun[Int, Double]) => ???
}
The correct desugaring is a bit surprising to me, because we want to ensure f: Fun[Int, Double]
, but this is not justified where f
is bound but only after the inner match.
def eval[T](e: Exp[T]) = e match {
case f1: Fun[a1, b1] =>
f1.f match {
case f2: Fun[Int, Double] =>
//***Here*** we learn that a1 = Int and b1 = Double
//So only ***here*** we can bind `f` with the correct type.
val f: Fun[Int, Double] = f1
}
}
We suspect the typechecker currently refines type variables a1
and b1
too soon, which has been fine until now, but strictly speaking means (I think) the current typechecker output is not well-typed in a sound typesystem.
EDIT: as usual, up to misunderstandings.
Haven't looked at what this PR does in details, but one thing to note is that right now the GADT bounds are stored separately in typer and not preserved in subsequent phases. There used to be code in PostTyper to do this that got removed (I don't know why), you may need to bring it back: eb21d24#diff-21d08d8a179119a483e67f9f0f994e99L62 |
6a14322
to
4bdcaa8
Compare
Thanks for the tip @smarter . Upon reflection, it seems to correctly handle GADTs in patterns, we need some context-sensitive typing, the type of a term name or type name could be refined in a context depending on type test. The refinement should be in sync with the type test. In TypeScript, there's a related feature called type guards, which can refine the type of a variable in a context: // Type is 'SpaceRepeatingPadder | StringPadder'
let padder: Padder = getRandomPadder();
if (padder instanceof SpaceRepeatingPadder) {
padder; // type narrowed to 'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
padder; // type narrowed to 'StringPadder'
} I've no idea how this works in theory. In our case, we need to refine the type of types names (as @Blaisorblade points out in the example) instead of / (in addition to) term names. |
An example related to GADT typing of Dotty trees: class Test {
trait Type
class Tree[-T]
case class Ident[-T](x: String) extends Tree[T]
trait Base extends Tree[Type]
class DummyIdent extends Ident[Null]("hello") with Base
def foo(tree: Tree[Type]): Ident[Type] = tree match {
case id @ Ident(x) => id // should we emit warnings here?
}
foo(new DummyIdent)
} |
@liufengyun
TypeScript has flow-sensitive typing; in Haskell's internal language used for GADTs, IIUC, matches produce evidence that some type is equal or smaller than another (=:= and <:<). Doing that properly would require adapting the theory though, which isn't an immediate step. |
I just confirmed that https://gist.github.com/Blaisorblade/a0eebb6a4f35344e48c4c60dc2a14ce6 |
And people pointed me to scala/bug#9565 and https://groups.google.com/d/msg/scala-internals/KYZACmy8_Qg/Wh1T0coqBYYJ. For this PR, I suspect |
@Blaisorblade If we always warn for a ClassTag-based test like I think soundness is not the only concern here. Giving that it happens rarely, I think we should optimize for usability as Scalac does. |
It's not great, but one can always use |
@@ -12,7 +12,7 @@ class Test { | |||
} | |||
|
|||
def quux[T](a: A[T]): Unit = a match { | |||
case _: B[T] => // error | |||
case _: B[T] => // err-or: cannot handle this for now |
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 replace err-or
with should be an error
or such?
For
However, the above specification cannot explain why we should not produce a warning in the following case: sealed trait A[T]
class B[T] extends A[T]
def g(x: A[Int]) = x match { case _: B[Int] => } The specification in this PR can be seen as making the obscurity in Scalac specification clear by resorting to type inference. Otherwise, the two are equivalent in spirit. |
test performance please |
performance test scheduled: 1 job(s) in queue, 0 running. |
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.
Otherwise LGTM
case fn: Select if fn.symbol == defn.Any_typeTest => | ||
ensureCheckable(fn.qualifier, tree.args.head) | ||
case fn: Select if fn.symbol == defn.Any_isInstanceOf => | ||
ensureCheckable(fn.qualifier, tree.args.head) |
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 not merge the first two cases:
case fn: Select if fn.symbol == defn.Any_typeTest || fn.symbol == defn.Any_isInstanceOf =>
ensureCheckable(fn.qualifier, tree.args.head)
def replaceP(implicit ctx: Context) = new TypeMap { | ||
def apply(tp: Type) = tp match { | ||
case tref: TypeRef | ||
if !tref.typeSymbol.isClass && tref.symbol.is(Case) => WildcardType |
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 you explain what this test does?
* 1. if `X <:< P`, TRUE | ||
* 2. if `P` is a singleton type, TRUE | ||
* 3. if `P` refers to an abstract type member or type parameter, FALSE | ||
* 4. if `P = Array[T]`, checkable(E, T) where `E` is the element type of `X`, defaults to `Any`. |
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 you explain why there is a special treatment for Array?
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 @allanrenucci for the helpful feedback, I've addressed them in the latest commit.
For Array
, we need a special treatment, because JVM carries type information of array elements, it's possible to test the element type at runtime [1]
:
scala> Array(1, 2, 3).getClass
res2: Class[_ <: Array[Int]] = class [I
// where the string "[I" is the run-time type signature for the class
// object "array with component type int".
[1]
: https://docs.oracle.com/javase/specs/jls/se7/html/jls-10.html
* (a) replace `Ts` with fresh type variables `Xs` | ||
* (b) constrain `Xs` with `pre.F[Xs] <:< X` | ||
* (c) instantiate Xs and check `pre.F[Xs] <:< P` | ||
* 6. if `P = T1 | T2` or `P = T1 & T2`, checkable(X, T1) && checkable(X, T2). |
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 you add a test for &
types. For example:
trait Marker
def foo[T](x: T) = x match {
case _: T & Marker => // no warning
// case _: T with Marker => // scalac emits a warning
case _ =>
}
Scalac emits a warning but I believe it is erroneous.
Use case:
def foo[T](xs: List[T]): List[T] = xs.collect { case x: T with Marker => x }
def replaceX(implicit ctx: Context) = new TypeMap { | ||
def apply(tp: Type) = tp match { | ||
case tref: TypeRef | ||
if !tref.typeSymbol.isClass && tref.symbol.is(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.
Could be isPatternTypeSymbol(tref.typeSymbol)
trait Marker | ||
def foo[T](x: T) = x match { | ||
case _: (T & Marker) => // no warning | ||
// case _: T with Marker => // scalac emits a warning |
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 should probably tested too
case _ => | ||
} | ||
|
||
def bar(x: Any) = x.isInstanceOf[List[String]] // error |
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 remove this line and it pos
test. This already tested in i3324.scala
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 catch. This line is intended to make the test a neg test. Make it a pos test will not work, as we want to check warnings here, -fatal-warning
is required. And it's nice to have all related tests of a feature under one folder.
|
||
def g(x: A[Int]) = x match { case _: B[Int] => } | ||
|
||
def foo(x: Any) = x.isInstanceOf[List[String]] // error |
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.
Remove this line. Tested in i3324.scala
?
} | ||
|
||
def foo(x: Any): Boolean = | ||
x.isInstanceOf[List[String]] // error |
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.
Remove this line. Tested in i3324.scala
?
} | ||
|
||
def foo(x: Any): Boolean = | ||
x.isInstanceOf[List[String]] // error |
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.
Remove this line. Tested in i3324.scala
?
// test parametric case classes, which synthesis `canEqual` and `equals` | ||
enum Result[+T, +E] { | ||
case OK [T](x: T) extends Result[T, Nothing] | ||
case Err[E](e: E) extends Result[Nothing, E] |
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.
What is tested here? There is no pattern 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.
Here the synthesized code will generate pattern match in canEqual
and equals
.
} | ||
|
||
def foo(x: Any): Boolean = | ||
x.isInstanceOf[List[String]] // error |
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.
Remove this line. Tested in i3324.scala
?
eval(exp)(Env.empty) | ||
|
||
def foo(x: Any): Boolean = | ||
x.isInstanceOf[List[String]] // error |
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.
Remove this line. Tested in i3324.scala
?
} | ||
|
||
def foo(x: Any): Boolean = | ||
x.isInstanceOf[List[String]] // error |
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.
Remove this line. Tested in i3324.scala
?
Merge now, so that we can fix bugs related to this feature. |
Fix #3324: add
isInstanceOf
check.This specification is different from the scalac compiler implementation. I hope it's more understandable.