Skip to content
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

Unbox value classes in arguments on dynamic function call #17564

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1592,6 +1592,11 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def repeated(trees: List[Tree], tpt: Tree)(using Context): Tree =
ctx.typeAssigner.arrayToRepeated(JavaSeqLiteral(trees, tpt))

/** Convert a list of trees to a vararg-compatible tree represented as SeqLiteral.
*/
def repeatedSeq(trees: List[Tree], tpt: Tree)(using Context): Tree =
ctx.typeAssigner.seqToRepeated(SeqLiteral(trees, tpt))

/** Create a tree representing a list containing all
* the elements of the argument list. A "list of tree to
* tree of list" conversion.
Expand Down
40 changes: 34 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Dynamic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ package dotc
package typer

import dotty.tools.dotc.ast.Trees.*
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.ast.untpd
import dotty.tools.dotc.ast.{tpd, untpd}
import dotty.tools.dotc.core.Constants.Constant
import dotty.tools.dotc.core.Contexts.*
import dotty.tools.dotc.core.Flags.*
Expand Down Expand Up @@ -149,6 +148,9 @@ trait Dynamic {
untpd.Apply(selectWithTypes, Literal(Constant(name.toString)))
}

extension (tpe: Type)
def isReflectSelectableTypeRef(using Context): Boolean = tpe <:< defn.ReflectSelectableTypeRef

/** Handle reflection-based dispatch for members of structural types.
*
* Given `x.a`, where `x` is of (widened) type `T` (a value type or a nullary method type),
Expand Down Expand Up @@ -178,10 +180,27 @@ trait Dynamic {
* It's an error if U is neither a value nor a method type, or a dependent method
* type
*/
def handleStructural(tree: Tree)(using Context): Tree = {
def handleStructural(tree: Tree)(using Context) = {
val fun @ Select(qual, name) = funPart(tree): @unchecked
val vargss = termArgss(tree)

def handleRepeated(base: untpd.Tree, possiblyCurried: List[List[Tree]], isReflectSelectable: Boolean) = {
val args = possiblyCurried.flatten.map { t =>
val clzSym = t.tpe.resultType.classSymbol.asClass
if clzSym.isDerivedValueClass && isReflectSelectable then
val underlying = ValueClasses.valueClassUnbox(clzSym).asTerm
tpd.Select(t, underlying.name)
else
t
}
val handledFlat =
if isReflectSelectable then List(untpd.TypedSplice(tpd.repeatedSeq(args, TypeTree(defn.AnyType))))
else if args.isEmpty then List()
else List(untpd.TypedSplice(tpd.repeatedSeq(args, TypeTree(args.map(_.tpe).reduce(_ | _)))))

untpd.Apply(base, handledFlat)
}

def structuralCall(selectorName: TermName, classOfs: => List[Tree]) = {
val selectable = adapt(qual, defn.SelectableClass.typeRef | defn.DynamicClass.typeRef)

Expand All @@ -193,7 +212,11 @@ trait Dynamic {

val scall =
if (vargss.isEmpty) base
else untpd.Apply(base, vargss.flatten.map(untpd.TypedSplice(_)))
else handleRepeated(
base,
vargss,
qual.tpe.isReflectSelectableTypeRef || selectable.tpe.isReflectSelectableTypeRef
)

// If function is an `applyDynamic` that takes a Class* parameter,
// add `classOfs`.
Expand Down Expand Up @@ -239,7 +262,7 @@ trait Dynamic {
*/
def maybeBoxingCast(tpe: Type) =
val maybeBoxed =
if tpe.classSymbol.isDerivedValueClass && qual.tpe <:< defn.ReflectSelectableTypeRef then
if tpe.classSymbol.isDerivedValueClass && qual.tpe.isReflectSelectableTypeRef then
val genericUnderlying = ValueClasses.valueClassUnbox(tpe.classSymbol.asClass)
val underlying = tpe.select(genericUnderlying).widen.resultType
New(tpe.widen, tree.cast(underlying) :: Nil)
Expand Down Expand Up @@ -268,7 +291,12 @@ trait Dynamic {
if tpe.paramInfoss.nestedExists(!TypeErasure.hasStableErasure(_)) then
fail(i"has a parameter type with an unstable erasure") :: Nil
else
TypeErasure.erasure(tpe).asInstanceOf[MethodType].paramInfos.map(clsOf(_))
TypeErasure.erasure(tpe).asInstanceOf[MethodType].paramInfos.map { tpe =>
if tpe.widen.classSymbol.isDerivedValueClass then
clsOf(ValueClasses.valueClassUnbox(tpe.classSymbol.asClass).info)
else
clsOf(tpe)
}
structuralCall(nme.applyDynamic, classOfs).maybeBoxingCast(tpe.finalResultType)
}

Expand Down
12 changes: 12 additions & 0 deletions docs/_docs/reference/changed-features/structural-types-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ Both versions are passed the actual arguments in the `args` parameter. The secon
if `applyDynamic` is implemented using Java reflection, but it could be
useful in other cases as well. `selectDynamic` and `applyDynamic` can also take additional context parameters in using clauses. These are resolved in the normal way at the callsite.

Typically, vararg arguments of a function at callsite are represented as an `Array(elems)`. But in case of `scala.Selectable` implementation relied on Java reflection `scala.reflect.Selectable` when vararg arguments passed to `applyDynamic` method, they get interpreted as single parameter instead of multiple.
To overcome this they should be wrapped in `Seq(elems)` to be considered as multiple parameters at callsite of `scala.reflect.Selectable.applyDynamic`.
```scala
class Reflective extends reflect.Selectable
class Foo(val i: Int) extends AnyVal
val reflective = new Reflective {
def varargs(x: Int, foo: Foo*) = foo.map(_.i).sum + x
}
val varargs = List(Foo(1), Foo(2), Foo(3))
reflective.varargs(1, varargs:_*)
```

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`, we consider three distinct cases:

Expand Down
2 changes: 1 addition & 1 deletion tests/pos/i17100.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ trait Sel extends Selectable

extension (s: Sel)
def selectDynamic(name: String) = ???
def applyDynamic(name: String)(x: Int) = ???
def applyDynamic(name: String)(x: Int*) = ???
def applyDynamic(name: String)() = ???

val sel = (new Sel {}).asInstanceOf[Sel{ def foo: String; def bar(x: Int): Int; def baz(): Int }]
Expand Down
10 changes: 5 additions & 5 deletions tests/run/i14340.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
class Container1 extends reflect.Selectable

class Container2(values: Map[String, Any], methods: Map[String, Int => Any]) extends Selectable:
class Container2(values: Map[String, Any], methods: Map[String, Seq[Int] => Any]) extends Selectable:
def selectDynamic(name: String) = values(name)
def applyDynamic(name: String)(arg: Int) = methods(name)(arg)
def applyDynamic(name: String)(arg: Int*) = methods(name)(arg)

class Foo(val value: Int) extends AnyVal
class Bar[A](val value: A) extends AnyVal
Expand All @@ -29,7 +29,7 @@ object Helpers:
)

val cont2methods = Map(
"fooFromInt" -> { (i: Int) => Foo(i) }
"fooFromInt" -> { (i: Seq[Int]) => Foo(i.head) }
)

val cont2 = Container2(cont2values, cont2methods).asInstanceOf[Container2 {
Expand All @@ -39,7 +39,7 @@ object Helpers:
def qux2: Bar[Container2 { def foo: Foo }]
def fooFromInt(i: Int): Foo
}]


println(cont1.foo.value)
println(cont2.foo.value)
Expand All @@ -49,7 +49,7 @@ object Helpers:

println(cont1.qux1.value.foo.value)
println(cont2.qux1.value.foo.value)

println(cont1.qux2.value.foo.value)
println(cont2.qux2.value.foo.value)

Expand Down
62 changes: 62 additions & 0 deletions tests/run/i16995.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
foo
foo0
fooI 1
fooN 1
fooII 1 2
fooNN 1 2
fooIvarargs 1 2
fooNvarargs 1 2
fooIseq 1 2
fooNseq 1 2
fooIIvarargs 1 2
fooNNvarargs 1 2
fooI_I 1 2
fooN_N 1 2
foo0_II 1 2
foo0_NN 1 2
foo0_Ivarargs 1 2
foo0_Nvarargs 1 2
foo0_I_I 1 2
foo0_N_N 1 2

foo
foo0
fooI 1
fooN 1
fooII 1 2
fooNN 1 2
fooIvarargs 1 2
fooNvarargs 1 2
fooIseq 1 2
fooNseq 1 2
fooIIvarargs 1 2 3
fooNNvarargs 1 2
fooI_I 1 2
fooN_N 1 2
foo0_II 1 2
foo0_NN 1 2
foo0_Ivarargs 1 2
foo0_Nvarargs 1 2
foo0_I_I 1 2
foo0_N_N 1 2

foo
foo0
fooI 1
fooN 1
fooII 1 2
fooNN 1 2
fooIvarargs 1 2
fooNvarargs 1 2
fooIseq 1 2
fooNseq 1 2
fooIIvarargs 1 2
fooNNvarargs 1 2
fooI_I 1 2
fooN_N 1 2
foo0_II 1 2
foo0_NN 1 2
foo0_Ivarargs 1 2
foo0_Nvarargs 1 2
foo0_I_I 1 2
foo0_N_N 1 2
154 changes: 154 additions & 0 deletions tests/run/i16995.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import scala.language.dynamics
import scala.reflect.Selectable.reflectiveSelectable

object Test {
class Num(val i: Int) extends AnyVal

def show(x: Int | Num | Seq[Int | Num]): String = x match
case i: Int => i.toString
case num: Num => num.i.toString
case seq: Seq[Int | Num] => seq.map(show).mkString(" ")

trait Nonreflective extends Selectable:
def selectDynamic(name: String): String = name
def applyDynamic(name: String)(args: (Int | Num | Seq[Int | Num])*): String =
val argsString = args.map(show).mkString(" ", " ", "")
s"${name}${argsString}"

trait Dynamic0 extends Dynamic:
def selectDynamic(name: String): String = name

trait Dynamic1 extends Dynamic:
def applyDynamic(name: String)(args1: (Int | Num | Seq[Int | Num])*): String =
val argsString = args1.map(show).mkString(" ", " ", "")
s"${name}${argsString}"

trait Dynamic2 extends Dynamic:
def applyDynamic(name: String)(args1: (Int | Num)*)(args2: (Int | Num)*): String =
val argsString = (args1 ++ args2).map(show).mkString(" ", " ", "")
s"${name}${argsString}"

trait Dynamic3 extends Dynamic:
def applyDynamic(name: String)(args1: (Int | Num)*)(args2: (Int | Num)*)(args3: (Int | Num)*): String =
val argsString = (args1 ++ args2 ++ args3).map(show).mkString(" ", " ", "")
s"${name}${argsString}"

type Api = {
def foo: String
def foo0(): String
def fooI(i: Int): String
def fooN(n: Num): String
def fooII(i1: Int, i2: Int): String
def fooNN(n1: Num, n2: Num): String
def fooIvarargs(is: Int*): String
def fooNvarargs(ns: Num*): String
def fooIseq(is: Seq[Int]): String
def fooNseq(ns: Seq[Num]): String
def fooIIvarargs(i1: Int, is: Int*): String
def fooNNvarargs(n1: Num, ns: Num*): String
def fooI_I(i1: Int)(i2: Int): String
def fooN_N(n1: Num)(n2: Num): String
def foo0_II()(i1: Int, i2: Int): String
def foo0_NN()(n1: Num, n2: Num): String
def foo0_Ivarargs()(is: Int*): String
def foo0_Nvarargs()(ns: Num*): String
def foo0_I_I()(i1: Int)(i2: Int): String
def foo0_N_N()(n1: Num)(n2: Num): String
}

class ClassImpl {
def foo: String = "foo"
def foo0(): String = "foo0"
def fooI(i: Int): String = s"fooI ${i}"
def fooN(n: Num): String = s"fooN ${n.i}"
def fooII(i1: Int, i2: Int): String = s"fooII ${i1} ${i2}"
def fooNN(n1: Num, n2: Num): String = s"fooNN ${n1.i} ${n2.i}"
def fooIvarargs(is: Int*): String = s"fooIvarargs${is.mkString(" ", " ", "")}"
def fooNvarargs(ns: Num*): String = s"fooNvarargs${ns.map(_.i).mkString(" ", " ", "")}"
def fooIseq(is: Seq[Int]): String = s"fooIseq${is.mkString(" ", " ", "")}"
def fooNseq(ns: Seq[Num]): String = s"fooNseq${ns.map(_.i).mkString(" ", " ", "")}"
def fooIIvarargs(i1: Int, is: Int*): String = s"fooIIvarargs ${i1}${is.mkString(" ", " ", "")}"
def fooNNvarargs(n1: Num, ns: Num*): String = s"fooNNvarargs ${n1.i}${ns.map(_.i).mkString(" ", " ", "")}"
def fooI_I(i1: Int)(i2: Int): String = s"fooI_I ${i1} ${i2}"
def fooN_N(n1: Num)(n2: Num): String = s"fooN_N ${n1.i} ${n2.i}"
def foo0_II()(i1: Int, i2: Int): String = s"foo0_II ${i1} ${i2}"
def foo0_NN()(n1: Num, n2: Num): String = s"foo0_NN ${n1.i} ${n2.i}"
def foo0_Ivarargs()(is: Int*): String = s"foo0_Ivarargs${is.mkString(" ", " ", "")}"
def foo0_Nvarargs()(ns: Num*): String = s"foo0_Nvarargs${ns.map(_.i).mkString(" ", " ", "")}"
def foo0_I_I()(i1: Int)(i2: Int): String = s"foo0_I_I ${i1} ${i2}"
def foo0_N_N()(n1: Num)(n2: Num): String = s"foo0_N_N ${n1.i} ${n2.i}"
}


def main(args: Array[String]): Unit = {
val reflective: Api = new ClassImpl()
val nonreflective: Nonreflective & Api = (new Nonreflective {}).asInstanceOf[Nonreflective & Api]
val dynamic0 = new Dynamic0 {}
val dynamic1 = new Dynamic1 {}
val dynamic2 = new Dynamic2 {}
val dynamic3 = new Dynamic3 {}

println(reflective.foo)
println(reflective.foo0())
println(reflective.fooI(1))
println(reflective.fooN(new Num(1)))
println(reflective.fooII(1, 2))
println(reflective.fooNN(new Num(1), new Num(2)))
println(reflective.fooIvarargs(1, 2))
println(reflective.fooNvarargs(new Num(1), new Num(2)))
println(reflective.fooIseq(Seq(1, 2)))
println(reflective.fooNseq(Seq(new Num(1), new Num(2))))
println(reflective.fooIIvarargs(1, 2))
println(reflective.fooNNvarargs(new Num(1), new Num(2)))
println(reflective.fooI_I(1)(2))
println(reflective.fooN_N(new Num(1))(new Num(2)))
println(reflective.foo0_II()(1, 2))
println(reflective.foo0_NN()(new Num(1), new Num(2)))
println(reflective.foo0_Ivarargs()(1, 2))
println(reflective.foo0_Nvarargs()(new Num(1), new Num(2)))
println(reflective.foo0_I_I()(1)(2))
println(reflective.foo0_N_N()(new Num(1))(new Num(2)))
println()
println(nonreflective.foo)
println(nonreflective.foo0())
println(nonreflective.fooI(1))
println(nonreflective.fooN(new Num(1)))
println(nonreflective.fooII(1, 2))
println(nonreflective.fooNN(new Num(1), new Num(2)))
println(nonreflective.fooIvarargs(1, 2))
println(nonreflective.fooNvarargs(new Num(1), new Num(2)))
println(nonreflective.fooIseq(Seq(1, 2)))
println(nonreflective.fooNseq(Seq(new Num(1), new Num(2))))
println(nonreflective.fooIIvarargs(1, 2, 3))
println(nonreflective.fooNNvarargs(new Num(1), new Num(2)))
println(nonreflective.fooI_I(1)(2))
println(nonreflective.fooN_N(new Num(1))(new Num(2)))
println(nonreflective.foo0_II()(1, 2))
println(nonreflective.foo0_NN()(new Num(1), new Num(2)))
println(nonreflective.foo0_Ivarargs()(1, 2))
println(nonreflective.foo0_Nvarargs()(new Num(1), new Num(2)))
println(nonreflective.foo0_I_I()(1)(2))
println(nonreflective.foo0_N_N()(new Num(1))(new Num(2)))
println()
println(dynamic0.foo)
println(dynamic1.foo0())
println(dynamic1.fooI(1))
println(dynamic1.fooN(new Num(1)))
println(dynamic1.fooII(1, 2))
println(dynamic1.fooNN(new Num(1), new Num(2)))
println(dynamic1.fooIvarargs(1, 2))
println(dynamic1.fooNvarargs(new Num(1), new Num(2)))
println(dynamic1.fooIseq(Seq(1, 2)))
println(dynamic1.fooNseq(Seq(new Num(1), new Num(2))))
println(dynamic1.fooIIvarargs(1, 2))
println(dynamic1.fooNNvarargs(new Num(1), new Num(2)))
println(dynamic2.fooI_I(1)(2))
println(dynamic2.fooN_N(new Num(1))(new Num(2)))
println(dynamic2.foo0_II()(1, 2))
println(dynamic2.foo0_NN()(new Num(1), new Num(2)))
println(dynamic2.foo0_Ivarargs()(1, 2))
println(dynamic2.foo0_Nvarargs()(new Num(1), new Num(2)))
println(dynamic3.foo0_I_I()(1)(2))
println(dynamic3.foo0_N_N()(new Num(1))(new Num(2)))
}
}
Loading