From 710db931913e27ab728d4d44d16b9be8c39a796b Mon Sep 17 00:00:00 2001 From: G1ng3r Date: Sun, 10 Sep 2023 20:49:01 +0300 Subject: [PATCH] add support for curried applyDynamic methods --- .../src/dotty/tools/dotc/typer/Dynamic.scala | 31 ++++++++++--------- .../changed-features/structural-types-spec.md | 12 +++++++ tests/run/i16995.check | 3 +- tests/run/i16995.scala | 8 ++++- 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index c2515efed332..d99744bc7783 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -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} @@ -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) @@ -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`. diff --git a/docs/_docs/reference/changed-features/structural-types-spec.md b/docs/_docs/reference/changed-features/structural-types-spec.md index 18d0f31ee6fe..1ef40dd06b08 100644 --- a/docs/_docs/reference/changed-features/structural-types-spec.md +++ b/docs/_docs/reference/changed-features/structural-types-spec.md @@ -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: diff --git a/tests/run/i16995.check b/tests/run/i16995.check index 2329c0150b56..700f1b325fac 100644 --- a/tests/run/i16995.check +++ b/tests/run/i16995.check @@ -4,4 +4,5 @@ check 7 5 3 -3 \ No newline at end of file +3 +7 \ No newline at end of file diff --git a/tests/run/i16995.scala b/tests/run/i16995.scala index 90cd7cedb6cf..6994a308ddf4 100644 --- a/tests/run/i16995.scala +++ b/tests/run/i16995.scala @@ -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 = @@ -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)) @@ -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))) \ No newline at end of file + println(cont2.varargs(1, Foo(1), Foo(1))) + + println(cont2.curried(Foo(1))(Argument("123"))(3)) \ No newline at end of file