Skip to content

Varargs and currying in Selectable's applyDynamic #18009

Open
@prolativ

Description

@prolativ

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, prints 3.
    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 map v.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 return 3 then, while the latter two should either perform eta-expansion and fail at runtime with an exception on casting a function to Int (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, prints 3

  • dyn2.bar:
    Compiles, prints 3

For Dynamics 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 Dynamics 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.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions