Description
Compiler version
3.3.2-RC1-bin-20230615-916d4e7-NIGHTLY and before
Minimized code
class Sel1 extends Selectable {
def applyDynamic(name: String)(args: Int*): Int = args.sum
}
class Sel2 extends Selectable {
def applyDynamic(name: String)(args: Int*)(arg: Int): Int = args.sum + arg
}
object Main {
def main(args: Array[String]) = {
val sel1 = (new Sel1).asInstanceOf[Sel1 {
def foo(x: Int)(y: Int): Int
def bar(xs: Int*)(y: Int): Int
}]
// println(sel1.foo(1)(2))
// println(sel1.bar(1)(2))
val sel2 = (new Sel2).asInstanceOf[Sel2 {
def foo(x: Int)(y: Int): Int
def bar(xs: Int*)(y: Int): Int
}]
// println(sel2.foo(1)(2))
// println(sel2.bar(1)(2))
}
}
Output
When one tries to compile and run the snippet from above after uncommenting only one commented line at a time, here's what happens in each case:
sel1.foo
:
Compilation succeeds, prints3
.
Relevant-Xprint:typer
output:
sel1.applyDynamic("foo")([1,2 : Int]*).$asInstanceOf[Int]
sel1.bar
:
Compilation error
[error] Sel.scala:15:22
[error] Sequence argument type annotation `*` cannot be used here:
[error] it is not the only argument to be passed to the corresponding repeated parameter Int*
[error] println(sel1.bar(1)(2))
[error] ^
Relevant -Xprint:typer
output:
sel1.applyDynamic("bar")([[1 : Int]*,2 : Int]*).$asInstanceOf[Int]
sel2.foo
:
Runtime exception
Exception in thread "main" java.lang.ClassCastException: Main$$$Lambda$3/1929600551 cannot be cast to java.lang.Integer
at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:99)
at Main$.main(Sel.scala:21)
at Main.main(Sel.scala)
Relevant -Xprint:typer
output:
{
val args$1: Int* = ([1,2 : Int]*)
{
def $anonfun(arg: Int): Int =
sel2.applyDynamic("foo")(args$1)(arg)
closure($anonfun)
}
}.$asInstanceOf[Int]
sel2.bar
:
Compilation error
[error] Sel.scala:22:22
[error] Sequence argument type annotation `*` cannot be used here:
[error] it is not the only argument to be passed to the corresponding repeated parameter Int*
[error] println(sel2.bar(1)(2))
[error] ^
Relevant -Xprint:typer
output:
sel2.applyDynamic("bar")([[1 : Int]*,2 : Int]*).$asInstanceOf[Int]
Expectation
The specification says:
Given a value v
of type C { Rs }
, where C
is a class reference and Rs
are structural refinement declarations, and given v.a
of type U
[...]
- If
U
is a method type(T11, ..., T1n)...(TN1, ..., TNn): R
and it is not a dependent method type, we mapv.a(a11, ..., a1n)...(aN1, ..., aNn)
to:
v.applyDynamic("a")(a11, ..., a1n, ..., aN1, ..., aNn)
.asInstanceOf[R]
Thus, according to the spec, the 4 cases should get desugared as follows:
sel1.foo(1)(2)
->sel1.applyDynamic("foo")(1, 2).asInstanceOf[Int]
sel1.bar(1)(2)
->sel1.applyDynamic("bar")(1, 2).asInstanceOf[Int]
sel2.foo(1)(2)
->sel2.applyDynamic("foo")(1, 2).asInstanceOf[Int]
sel2.bar(1)(2)
->sel2.applyDynamic("bar")(1, 2).asInstanceOf[Int]
The first two cases should compile successfully and return3
then, while the latter two should either perform eta-expansion and fail at runtime with an exception on casting a function toInt
(we should try to avoid this situation) or complain about a missing parameter.
On the other hand, as a user I would expect code using Selectable
to behave analogously to code using Dynamic
instead.
Thus, given the snippet below
import scala.language.dynamics
class Dyn1 extends Dynamic {
def applyDynamic(name: String)(args: Int*): Int = args.sum
}
class Dyn2 extends Dynamic {
def applyDynamic(name: String)(args: Int*)(arg: Int): Int = args.sum + arg
}
object Main {
def main(args: Array[String]) = {
val dyn1 = new Dyn1
// println(dyn1.foo(1)(2))
// println(dyn1.bar(1)(2))
val dyn2 = new Dyn2
// println(dyn2.foo(1)(2))
// println(dyn2.bar(1)(2))
}
}
and uncommenting a single line at a time, I get:
dyn1.foo
:
[error] Dyn.scala:13:13
[error] method applyDynamic in class Dyn1 does not take more parameters
[error] println(dyn1.foo(1)(2))
[error] ^^^^^^^^^^
dyn1.bar
:
[error] Dyn.scala:14:13
[error] method applyDynamic in class Dyn1 does not take more parameters
[error] println(dyn1.bar(1)(2))
[error] ^^^^^^^^^^
-
dyn2.foo
:
Compiles, prints3
-
dyn2.bar
:
Compiles, prints3
For Dynamic
s the behaviour in scala 2 is basically the same, though the compilation errors for dyn1.foo
and dyn1.bar
are slightly different:
[error] Dyn.scala:13:13
[error] Int does not take parameters
[error] println(dyn1.foo(1)(2))
[error] ^^^^^^^^^^^^^^
[error] Dyn.scala:14:13
[error] Int does not take parameters
[error] println(dyn1.bar(1)(2))
[error] ^^^^^^^^^^^^^^
Summing this up, the behaviour for Dynamic
s in this case is the opposite to the specified behaviour for Selectable
, which in turn is different to the current actual (definitely buggy in some way) behaviour for Selectable
.