From 0c159c8e4d288ba73f679e9d07627f3154d0f855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 23 Jul 2020 13:22:33 +0200 Subject: [PATCH] Scala 2 compat: enable reflectiveSelectable for language.reflectiveCalls. In Scala 2, calls to structural types that require reflective calls must be enabled with import scala.language.reflectiveCalls In Scala 3, that import has no effect, and reflective calls refuse to compile without the following import: import scala.reflect.Selectable.reflectiveSelectable However, since that import does not compile in Scala 2, there is no way to write code that cross-compiles between Scala 2 and Scala 3 and uses reflective calls to members of structural types. In this commit, we conditionally enable `reflectiveSelectable` if a given `scala.language.reflectiveCalls` is in scope. This straightforwardly allows code to cross-compile by using the Scala 2 language import, without any compiler intervention. --- library/src/scala/Selectable.scala | 13 ++- tests/run/structural-compat.scala | 133 +++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 tests/run/structural-compat.scala diff --git a/library/src/scala/Selectable.scala b/library/src/scala/Selectable.scala index 332cab87b662..01f8dd902549 100644 --- a/library/src/scala/Selectable.scala +++ b/library/src/scala/Selectable.scala @@ -1,5 +1,4 @@ package scala -import scala.reflect.ClassTag /** A marker trait for objects that support structural selection via * `selectDynamic` and `applyDynamic` @@ -22,3 +21,15 @@ import scala.reflect.ClassTag * types of the method in the structural type. */ trait Selectable extends Any + +object Selectable: + /* Scala 2 compat + allowing for cross-compilation: + * enable scala.reflect.Selectable.reflectiveSelectable when there is an + * import scala.language.reflectiveCalls in scope. + */ + @deprecated( + "import scala.reflect.Selectable.reflectiveSelectable instead of scala.language.reflectiveCalls", + since = "3.0") + implicit def reflectiveSelectableFromLangReflectiveCalls(x: Any)( + using scala.languageFeature.reflectiveCalls): scala.reflect.Selectable = + scala.reflect.Selectable.reflectiveSelectable(x) diff --git a/tests/run/structural-compat.scala b/tests/run/structural-compat.scala new file mode 100644 index 000000000000..600f3dd1cba1 --- /dev/null +++ b/tests/run/structural-compat.scala @@ -0,0 +1,133 @@ +// the old reflectiveCalls still works and is equivalent to +// import scala.reflect.Selectable.reflectiveSelectable +import scala.language.reflectiveCalls + +object Test { + class C { type S = String; type I } + class D extends C { type I = Int } + + type Foo = { + def sel0: Int + def sel1: Int => Int + def fun0(x: Int): Int + + def fun1(x: Int)(y: Int): Int + def fun2(x: Int): Int => Int + def fun3(a1: Int, a2: Int, a3: Int) + (a4: Int, a5: Int, a6: Int) + (a7: Int, a8: Int, a9: Int): Int + + def fun4(implicit x: Int): Int + def fun5(x: Int)(implicit y: Int): Int + + def fun6(x: C, y: x.S): Int + def fun7(x: C, y: x.I): Int + def fun8(y: C): y.S + def fun9(y: C): y.I + } + + class Foo1 { + def sel0: Int = 1 + def sel1: Int => Int = x => x + def fun0(x: Int): Int = x + + def fun1(x: Int)(y: Int): Int = x + y + def fun2(x: Int): Int => Int = y => x * y + def fun3(a1: Int, a2: Int, a3: Int) + (a4: Int, a5: Int, a6: Int) + (a7: Int, a8: Int, a9: Int): Int = -1 + + def fun4(implicit x: Int): Int = x + def fun5(x: Int)(implicit y: Int): Int = x + y + + def fun6(x: C, y: x.S): Int = 1 + def fun7(x: C, y: x.I): Int = 2 + def fun8(y: C): y.S = "Hello" + def fun9(y: C): y.I = 1.asInstanceOf[y.I] + } + + class Foo2 extends scala.Selectable { + def sel0: Int = 1 + def sel1: Int => Int = x => x + def fun0(x: Int): Int = x + + def fun1(x: Int)(y: Int): Int = x + y + def fun2(x: Int): Int => Int = y => x * y + def fun3(a1: Int, a2: Int, a3: Int) + (a4: Int, a5: Int, a6: Int) + (a7: Int, a8: Int, a9: Int): Int = -1 + + def fun4(implicit x: Int): Int = x + def fun5(x: Int)(implicit y: Int): Int = x + y + + def fun6(x: C, y: x.S): Int = 1 + def fun7(x: C, y: x.I): Int = 2 + def fun8(y: C): y.S = "Hello" + def fun9(y: C): y.I = 1.asInstanceOf[y.I] + } + + def basic(x: Foo): Unit ={ + assert(x.sel0 == 1) + assert(x.sel1(2) == 2) + assert(x.fun0(3) == 3) + + val f = x.sel1 + assert(f(3) == 3) + } + + def currying(x: Foo): Unit = { + assert(x.fun1(1)(2) == 3) + assert(x.fun2(1)(2) == 2) + assert(x.fun3(1, 2, 3)(4, 5, 6)(7, 8, 9) == -1) + } + + def etaExpansion(x: Foo): Unit = { + val f0 = x.fun0(_) + assert(f0(2) == 2) + + val f1 = x.fun0 _ + assert(f1(2) == 2) + + val f2 = x.fun1(1)(_) + assert(f2(2) == 3) + + val f3 = x.fun1(1) _ + assert(f3(2) == 3) + + val f4 = x.fun1(1) + assert(f4(2) == 3) + } + + def implicits(x: Foo) = { + implicit val y = 2 + assert(x.fun4 == 2) + assert(x.fun5(1) == 3) + } + + // Limited support for dependant methods + def dependent(x: Foo) = { + val y = new D + + assert(x.fun6(y, "Hello") == 1) + // assert(x.fun7(y, 1) == 2) // error: No ClassTag available for x.I + + val s = x.fun8(y) + assert((s: String) == "Hello") + + // val i = x.fun9(y) // error: rejected (blows up in pickler if not rejected) + // assert((i: String) == "Hello") // error: Type mismatch: found: y.S(i); required: String + } + + def main(args: Array[String]): Unit = { + basic(new Foo1) + currying(new Foo1) + etaExpansion(new Foo1) + implicits(new Foo1) + dependent(new Foo1) + basic(new Foo2) + currying(new Foo2) + etaExpansion(new Foo2) + implicits(new Foo2) + dependent(new Foo2) + } +}