Skip to content

Commit

Permalink
add support for curried applyDynamic methods
Browse files Browse the repository at this point in the history
  • Loading branch information
G1ng3r committed Sep 10, 2023
1 parent af62a89 commit 710db93
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 17 deletions.
31 changes: 16 additions & 15 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.Names.{Name, TermName}
Expand Down Expand Up @@ -180,18 +179,20 @@ trait Dynamic {
val fun @ Select(qual, name) = funPart(tree): @unchecked
val vargss = termArgss(tree)

def handleRepeated(args: List[List[Tree]]) =
val isRepeated = args.flatten.exists(_.tpe.widen.isRepeatedParam)
if isRepeated && qual.tpe <:< defn.ReflectSelectableTypeRef then
List(untpd.TypedSplice(tpd.repeatedSeq(args.flatten, TypeTree(defn.AnyType))))
else args.flatten.map { t =>
val clzSym = t.tpe.resultType.classSymbol.asClass
if ValueClasses.isDerivedValueClass(clzSym) then
val underlying = ValueClasses.valueClassUnbox(clzSym).asTerm
tpd.Select(t, underlying.name)
else
t
}.map(untpd.TypedSplice(_))
def handleRepeated(base: Tree, possiblyCurried: List[List[Tree]]) =
possiblyCurried.map { args =>
val isRepeated = args.exists(_.tpe.widen.isRepeatedParam)
if isRepeated && qual.tpe <:< defn.ReflectSelectableTypeRef then
List(untpd.TypedSplice(tpd.repeatedSeq(args, TypeTree(defn.AnyType))))
else args.map { t =>
val clzSym = t.tpe.resultType.classSymbol.asClass
if ValueClasses.isDerivedValueClass(clzSym) && qual.tpe <:< defn.ReflectSelectableTypeRef then
val underlying = ValueClasses.valueClassUnbox(clzSym).asTerm
tpd.Select(t, underlying.name)
else
t
}.map(untpd.TypedSplice(_))
}.foldLeft(base)((base, args) => untpd.Apply(base, args))

def structuralCall(selectorName: TermName, classOfs: => List[Tree]) = {
val selectable = adapt(qual, defn.SelectableClass.typeRef | defn.DynamicClass.typeRef)
Expand All @@ -204,7 +205,7 @@ trait Dynamic {

val scall =
if (vargss.isEmpty) base
else untpd.Apply(base, handleRepeated(vargss))
else handleRepeated(base, vargss)

// If function is an `applyDynamic` that takes a Class* parameter,
// add `classOfs`.
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
3 changes: 2 additions & 1 deletion tests/run/i16995.check
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ check
7
5
3
3
3
7
8 changes: 7 additions & 1 deletion tests/run/i16995.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class ScalaSelectable(values: Map[String, Any], methods: Map[String, (Int, Seq[F
def selectDynamic(name: String): Any = values(name)

def applyDynamic(name: String)(i: Int, foos: Foo*): Int = methods(name)(i, foos)

def applyDynamic(name: String)(foo: Foo)(argument: Argument)(someInt: Int): Int = foo.i + argument.x.length + someInt
}

@main def Test: Unit =
Expand All @@ -15,6 +17,7 @@ class ScalaSelectable(values: Map[String, Any], methods: Map[String, (Int, Seq[F
def manyArgs(argument: Argument, foo: Foo, someInt: Int) = foo.i + someInt + argument.x.length
def varargs(x: Int, foo: Foo*) = foo.map(_.i).sum + x
def letsHaveSeq(args: Seq[Argument]) = args.map(_.x.length).sum
def curried(foo: Foo)(arg1: Argument)(someInt: Int): Int = foo.i + arg1.x.length + someInt
}

val i = reflective.bar(Foo(1))
Expand Down Expand Up @@ -53,6 +56,9 @@ class ScalaSelectable(values: Map[String, Any], methods: Map[String, (Int, Seq[F

val cont2 = ScalaSelectable(cont2values, cont2methods).asInstanceOf[ScalaSelectable {
def varargs(i: Int, foos: Foo*): Int
def curried(foo: Foo)(argument: Argument)(someInt: Int): Int
}]

println(cont2.varargs(1, Foo(1), Foo(1)))
println(cont2.varargs(1, Foo(1), Foo(1)))

println(cont2.curried(Foo(1))(Argument("123"))(3))

0 comments on commit 710db93

Please sign in to comment.