Description
Minimized code
final class JSTuple2[+T1, +T2](val _1: T1, val _2: T2)
object JSTuple2 {
def apply[T1, T2](_1: T1, _2: T2): JSTuple2[T1, T2] =
new JSTuple2(_1, _2)
def unapply[T1, T2](t: JSTuple2[T1, T2]): Option[(T1, T2)] =
Some((t._1, t._2))
}
object Test {
def main(args: Array[String]): Unit = {
val ts: List[JSTuple2[Int, String]] = List(JSTuple2(42, "foo"))
ts(0) match { // note ts(0) here
case JSTuple2(x, y) => println(s"$x $y")
}
val JSTuple2(a, b) = ts(0) // and ts(0) here
println(s"$a $b")
}
}
Compile with -Xprint:typer
:
sbt:dotty> dotc -Xprint:typer tests/run/hello.scala
[warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list
[info] running (fork) dotty.tools.dotc.Main -classpath C:\Users\sjrdo\AppData\Local\Coursier\cache\v1\https\repo1.maven.org\maven2\org\scala-lang\scala-library\2.13.3\scala-library-2.13.3.jar;C:\Users\sjrdo\Documents\Projets\dotty\library\..\out\bootstrap\dotty-library-bootstrapped\scala-0.27\dotty-library_0.27-0.27.0-bin-SNAPSHOT.jar -Xprint:typer tests/run/hello.scala
result of tests\run\hello.scala after typer:
package <empty> {
final class JSTuple2[T1 >: Nothing <: Any, T2 >: Nothing <: Any](_1: T1,
_2: T2
) extends Object() {
+T1
+T2
val _1: T1
val _2: T2
}
final lazy module val JSTuple2: JSTuple2$ = new JSTuple2$()
final module class JSTuple2$() extends Object(), _root_.scala.Serializable {
this: JSTuple2.type =>
def apply[T1 >: Nothing <: Any, T2 >: Nothing <: Any](_1: T1, _2: T2):
JSTuple2[T1, T2]
= new JSTuple2[T1, T2](_1, _2)
def unapply[T1 >: Nothing <: Any, T2 >: Nothing <: Any](t: JSTuple2[T1, T2])
:
Option[Tuple2[T1, T2]] =
Some.apply[(T1, T2)](Tuple2.apply[T1, T2](t._1, t._2))
}
final lazy module val Test: Test$ = new Test$()
final module class Test$() extends Object(), _root_.scala.Serializable {
this: Test.type =>
def main(args: Array[String]): Unit =
{
val ts: List[JSTuple2[Int, String]] =
List.apply[JSTuple2[Int, String]](
[JSTuple2.apply[Int, String](42, "foo") : JSTuple2[Int, String]]:
JSTuple2[Int, String]*
)
ts.apply(0) match
{
case JSTuple2.unapply[Int, String](x @ _, y @ _) => // no : JSTuple2[Int, String] here
println(
_root_.scala.StringContext.apply([""," ","" : String]:String*).s
(
[x,y : Any]:Any*)
)
}
val $1$: (Int, String) =
ts.apply(0):JSTuple2[Int, String] @unchecked match
{
case
JSTuple2.unapply[Int, String](a @ _, b @ _):
JSTuple2[Int, String] // Note the : JSTuple2[Int, String] here, it is spurious
=> Tuple2.apply[Int, String](a, b)
}
val a: Int = $1$._1
val b: String = $1$._2
println(
_root_.scala.StringContext.apply([""," ","" : String]:String*).s(
[a,b : Any]:Any*
)
)
}
}
}
In the extractor case, the typer inserts a spurious Typed
node, printed as : JSTuple2[Int, String]
. That Typed
is not inserted when we do an equivalent extraction but using a match
, as illustrated in the same example.
Funnier, this requires that the thing being extracted be something like ts(0)
. I can work around the problem by assigning val t = ts(0)
first, and then use t
:
final class JSTuple2[+T1, +T2](val _1: T1, val _2: T2)
object JSTuple2 {
def apply[T1, T2](_1: T1, _2: T2): JSTuple2[T1, T2] =
new JSTuple2(_1, _2)
def unapply[T1, T2](t: JSTuple2[T1, T2]): Option[(T1, T2)] =
Some((t._1, t._2))
}
object Test {
def main(args: Array[String]): Unit = {
val ts: List[JSTuple2[Int, String]] = List(JSTuple2(42, "foo"))
val t = ts(0)
t match { // note `t` here
case JSTuple2(x, y) => println(s"$x $y")
}
val JSTuple2(a, b) = t // note `t` here
println(s"$a $b")
}
}
With that changed, printing after typer shows that neither occurrence has a Typed
node:
sbt:dotty> dotc -Xprint:typer tests/run/hello.scala
[warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list
[info] running (fork) dotty.tools.dotc.Main -classpath C:\Users\sjrdo\AppData\Local\Coursier\cache\v1\https\repo1.maven.org\maven2\org\scala-lang\scala-library\2.13.3\scala-library-2.13.3.jar;C:\Users\sjrdo\Documents\Projets\dotty\library\..\out\bootstrap\dotty-library-bootstrapped\scala-0.27\dotty-library_0.27-0.27.0-bin-SNAPSHOT.jar -Xprint:typer tests/run/hello.scala
result of tests\run\hello.scala after typer:
package <empty> {
final class JSTuple2[T1 >: Nothing <: Any, T2 >: Nothing <: Any](_1: T1,
_2: T2
) extends Object() {
+T1
+T2
val _1: T1
val _2: T2
}
final lazy module val JSTuple2: JSTuple2$ = new JSTuple2$()
final module class JSTuple2$() extends Object(), _root_.scala.Serializable {
this: JSTuple2.type =>
def apply[T1 >: Nothing <: Any, T2 >: Nothing <: Any](_1: T1, _2: T2):
JSTuple2[T1, T2]
= new JSTuple2[T1, T2](_1, _2)
def unapply[T1 >: Nothing <: Any, T2 >: Nothing <: Any](t: JSTuple2[T1, T2])
:
Option[Tuple2[T1, T2]] =
Some.apply[(T1, T2)](Tuple2.apply[T1, T2](t._1, t._2))
}
final lazy module val Test: Test$ = new Test$()
final module class Test$() extends Object(), _root_.scala.Serializable {
this: Test.type =>
def main(args: Array[String]): Unit =
{
val ts: List[JSTuple2[Int, String]] =
List.apply[JSTuple2[Int, String]](
[JSTuple2.apply[Int, String](42, "foo") : JSTuple2[Int, String]]:
JSTuple2[Int, String]*
)
val t: JSTuple2[Int, String] = ts.apply(0)
t match
{
case JSTuple2.unapply[Int, String](x @ _, y @ _) => // No : JSTuple2[Int, String] here
println(
_root_.scala.StringContext.apply([""," ","" : String]:String*).s
(
[x,y : Any]:Any*)
)
}
val $1$: (Int, String) =
t:(t : JSTuple2[Int, String]) @unchecked match
{
case JSTuple2.unapply[Int, String](a @ _, b @ _) => // and no : JSTuple2[Int, String] here either!
Tuple2.apply[Int, String](a, b)
}
val a: Int = $1$._1
val b: String = $1$._2
println(
_root_.scala.StringContext.apply([""," ","" : String]:String*).s(
[a,b : Any]:Any*
)
)
}
}
}
There's clearly something funny that the type checker is doing here.
Expectation
I expect the type checker not to insert the Typed
node in any of the above examples. scalac doesn't do it.
The insertion of the Typed
node has a consequence: it means that PatternMatcher inserts an isInstanceOf[JSTuple[Int, String]]
for that Typed
, even though it is not necessary.
This is a mild consequence for the JVM, but it is critical for Scala.js if the type is a JS trait, like all js.TupleN
s are. Because doing isInstanceOf[SomeJSTrait]
is not valid and cannot be implemented.
Ultimately, this issue causes the test org.scalajs.testsuite.library.ObjectTest.entries_from_object()
not to compile, with compile errors at lines
https://github.com/scala-js/scala-js/blob/e25e45bf55564fb9fc972dc7a9dbc34bcec9e5f4/test-suite/js/src/test/scala/org/scalajs/testsuite/library/ObjectTest.scala#L66
and
https://github.com/scala-js/scala-js/blob/e25e45bf55564fb9fc972dc7a9dbc34bcec9e5f4/test-suite/js/src/test/scala/org/scalajs/testsuite/library/ObjectTest.scala#L70