Skip to content

Commit 0c159c8

Browse files
committed
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.
1 parent fabe7bb commit 0c159c8

File tree

2 files changed

+145
-1
lines changed

2 files changed

+145
-1
lines changed

library/src/scala/Selectable.scala

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
package scala
2-
import scala.reflect.ClassTag
32

43
/** A marker trait for objects that support structural selection via
54
* `selectDynamic` and `applyDynamic`
@@ -22,3 +21,15 @@ import scala.reflect.ClassTag
2221
* types of the method in the structural type.
2322
*/
2423
trait Selectable extends Any
24+
25+
object Selectable:
26+
/* Scala 2 compat + allowing for cross-compilation:
27+
* enable scala.reflect.Selectable.reflectiveSelectable when there is an
28+
* import scala.language.reflectiveCalls in scope.
29+
*/
30+
@deprecated(
31+
"import scala.reflect.Selectable.reflectiveSelectable instead of scala.language.reflectiveCalls",
32+
since = "3.0")
33+
implicit def reflectiveSelectableFromLangReflectiveCalls(x: Any)(
34+
using scala.languageFeature.reflectiveCalls): scala.reflect.Selectable =
35+
scala.reflect.Selectable.reflectiveSelectable(x)

tests/run/structural-compat.scala

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// the old reflectiveCalls still works and is equivalent to
2+
// import scala.reflect.Selectable.reflectiveSelectable
3+
import scala.language.reflectiveCalls
4+
5+
object Test {
6+
class C { type S = String; type I }
7+
class D extends C { type I = Int }
8+
9+
type Foo = {
10+
def sel0: Int
11+
def sel1: Int => Int
12+
def fun0(x: Int): Int
13+
14+
def fun1(x: Int)(y: Int): Int
15+
def fun2(x: Int): Int => Int
16+
def fun3(a1: Int, a2: Int, a3: Int)
17+
(a4: Int, a5: Int, a6: Int)
18+
(a7: Int, a8: Int, a9: Int): Int
19+
20+
def fun4(implicit x: Int): Int
21+
def fun5(x: Int)(implicit y: Int): Int
22+
23+
def fun6(x: C, y: x.S): Int
24+
def fun7(x: C, y: x.I): Int
25+
def fun8(y: C): y.S
26+
def fun9(y: C): y.I
27+
}
28+
29+
class Foo1 {
30+
def sel0: Int = 1
31+
def sel1: Int => Int = x => x
32+
def fun0(x: Int): Int = x
33+
34+
def fun1(x: Int)(y: Int): Int = x + y
35+
def fun2(x: Int): Int => Int = y => x * y
36+
def fun3(a1: Int, a2: Int, a3: Int)
37+
(a4: Int, a5: Int, a6: Int)
38+
(a7: Int, a8: Int, a9: Int): Int = -1
39+
40+
def fun4(implicit x: Int): Int = x
41+
def fun5(x: Int)(implicit y: Int): Int = x + y
42+
43+
def fun6(x: C, y: x.S): Int = 1
44+
def fun7(x: C, y: x.I): Int = 2
45+
def fun8(y: C): y.S = "Hello"
46+
def fun9(y: C): y.I = 1.asInstanceOf[y.I]
47+
}
48+
49+
class Foo2 extends scala.Selectable {
50+
def sel0: Int = 1
51+
def sel1: Int => Int = x => x
52+
def fun0(x: Int): Int = x
53+
54+
def fun1(x: Int)(y: Int): Int = x + y
55+
def fun2(x: Int): Int => Int = y => x * y
56+
def fun3(a1: Int, a2: Int, a3: Int)
57+
(a4: Int, a5: Int, a6: Int)
58+
(a7: Int, a8: Int, a9: Int): Int = -1
59+
60+
def fun4(implicit x: Int): Int = x
61+
def fun5(x: Int)(implicit y: Int): Int = x + y
62+
63+
def fun6(x: C, y: x.S): Int = 1
64+
def fun7(x: C, y: x.I): Int = 2
65+
def fun8(y: C): y.S = "Hello"
66+
def fun9(y: C): y.I = 1.asInstanceOf[y.I]
67+
}
68+
69+
def basic(x: Foo): Unit ={
70+
assert(x.sel0 == 1)
71+
assert(x.sel1(2) == 2)
72+
assert(x.fun0(3) == 3)
73+
74+
val f = x.sel1
75+
assert(f(3) == 3)
76+
}
77+
78+
def currying(x: Foo): Unit = {
79+
assert(x.fun1(1)(2) == 3)
80+
assert(x.fun2(1)(2) == 2)
81+
assert(x.fun3(1, 2, 3)(4, 5, 6)(7, 8, 9) == -1)
82+
}
83+
84+
def etaExpansion(x: Foo): Unit = {
85+
val f0 = x.fun0(_)
86+
assert(f0(2) == 2)
87+
88+
val f1 = x.fun0 _
89+
assert(f1(2) == 2)
90+
91+
val f2 = x.fun1(1)(_)
92+
assert(f2(2) == 3)
93+
94+
val f3 = x.fun1(1) _
95+
assert(f3(2) == 3)
96+
97+
val f4 = x.fun1(1)
98+
assert(f4(2) == 3)
99+
}
100+
101+
def implicits(x: Foo) = {
102+
implicit val y = 2
103+
assert(x.fun4 == 2)
104+
assert(x.fun5(1) == 3)
105+
}
106+
107+
// Limited support for dependant methods
108+
def dependent(x: Foo) = {
109+
val y = new D
110+
111+
assert(x.fun6(y, "Hello") == 1)
112+
// assert(x.fun7(y, 1) == 2) // error: No ClassTag available for x.I
113+
114+
val s = x.fun8(y)
115+
assert((s: String) == "Hello")
116+
117+
// val i = x.fun9(y) // error: rejected (blows up in pickler if not rejected)
118+
// assert((i: String) == "Hello") // error: Type mismatch: found: y.S(i); required: String
119+
}
120+
121+
def main(args: Array[String]): Unit = {
122+
basic(new Foo1)
123+
currying(new Foo1)
124+
etaExpansion(new Foo1)
125+
implicits(new Foo1)
126+
dependent(new Foo1)
127+
basic(new Foo2)
128+
currying(new Foo2)
129+
etaExpansion(new Foo2)
130+
implicits(new Foo2)
131+
dependent(new Foo2)
132+
}
133+
}

0 commit comments

Comments
 (0)