Skip to content

Commit 97a2bcf

Browse files
authored
Properly handle AnyVals as refinement members of Selectables (#16286)
- add dynamic access to value classes field
2 parents ab19081 + 1a6224b commit 97a2bcf

File tree

4 files changed

+106
-11
lines changed

4 files changed

+106
-11
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,8 @@ class Definitions {
817817
@tu lazy val ClassTagModule: Symbol = ClassTagClass.companionModule
818818
@tu lazy val ClassTagModule_apply: Symbol = ClassTagModule.requiredMethod(nme.apply)
819819

820+
@tu lazy val ReflectSelectableTypeRef: TypeRef = requiredClassRef("scala.reflect.Selectable")
821+
820822
@tu lazy val TypeTestClass: ClassSymbol = requiredClass("scala.reflect.TypeTest")
821823
@tu lazy val TypeTest_unapply: Symbol = TypeTestClass.requiredMethod(nme.unapply)
822824
@tu lazy val TypeTestModule_identity: Symbol = TypeTestClass.companionModule.requiredMethod(nme.identity)

compiler/src/dotty/tools/dotc/typer/Dynamic.scala

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,22 @@ package dotty.tools
22
package dotc
33
package typer
44

5-
import dotty.tools.dotc.ast.Trees._
5+
import dotty.tools.dotc.ast.Trees.*
66
import dotty.tools.dotc.ast.tpd
77
import dotty.tools.dotc.ast.untpd
88
import dotty.tools.dotc.core.Constants.Constant
9-
import dotty.tools.dotc.core.Contexts._
9+
import dotty.tools.dotc.core.Contexts.*
1010
import dotty.tools.dotc.core.Names.{Name, TermName}
11-
import dotty.tools.dotc.core.StdNames._
12-
import dotty.tools.dotc.core.Types._
13-
import dotty.tools.dotc.core.Decorators._
11+
import dotty.tools.dotc.core.StdNames.*
12+
import dotty.tools.dotc.core.Types.*
13+
import dotty.tools.dotc.core.Decorators.*
1414
import dotty.tools.dotc.core.TypeErasure
15-
import util.Spans._
16-
import core.Symbols._
17-
import ErrorReporting._
18-
import reporting._
15+
import util.Spans.*
16+
import core.Symbols.*
17+
import ErrorReporting.*
18+
import dotty.tools.dotc.transform.ValueClasses
19+
import dotty.tools.dotc.transform.TypeUtils.isPrimitiveValueType
20+
import reporting.*
1921

2022
object Dynamic {
2123
private def isDynamicMethod(name: Name): Boolean =
@@ -214,9 +216,33 @@ trait Dynamic {
214216
def fail(reason: String): Tree =
215217
errorTree(tree, em"Structural access not allowed on method $name because it $reason")
216218

219+
extension (tree: Tree)
220+
/** The implementations of `selectDynamic` and `applyDynamic` in `scala.reflect.SelectDynamic` have no information about the expected return type of a value/method which was declared in the refinement,
221+
* only the JVM type after erasure can be obtained through reflection, e.g.
222+
*
223+
* class Foo(val i: Int) extends AnyVal
224+
* class Reflective extends reflect.Selectable
225+
* val reflective = new Reflective {
226+
* def foo = Foo(1) // Foo at compile time, java.lang.Integer in reflection
227+
* }
228+
*
229+
* Because of that reflective access cannot be implemented properly in `scala.reflect.SelectDynamic` itself
230+
* because it's not known there if the value should be wrapped in a value class constructor call or not.
231+
* Hence the logic of wrapping is performed here, relying on the fact that the implementations of `selectDynamic` and `applyDynamic` in `scala.reflect.SelectDynamic` are final.
232+
*/
233+
def maybeBoxingCast(tpe: Type) =
234+
val maybeBoxed =
235+
if ValueClasses.isDerivedValueClass(tpe.classSymbol) && qual.tpe <:< defn.ReflectSelectableTypeRef then
236+
val genericUnderlying = ValueClasses.valueClassUnbox(tpe.classSymbol.asClass)
237+
val underlying = tpe.select(genericUnderlying).widen.resultType
238+
New(tpe, tree.cast(underlying) :: Nil)
239+
else
240+
tree
241+
maybeBoxed.cast(tpe)
242+
217243
fun.tpe.widen match {
218244
case tpe: ValueType =>
219-
structuralCall(nme.selectDynamic, Nil).cast(tpe)
245+
structuralCall(nme.selectDynamic, Nil).maybeBoxingCast(tpe)
220246

221247
case tpe: MethodType =>
222248
def isDependentMethod(tpe: Type): Boolean = tpe match {
@@ -236,7 +262,7 @@ trait Dynamic {
236262
fail(i"has a parameter type with an unstable erasure") :: Nil
237263
else
238264
TypeErasure.erasure(tpe).asInstanceOf[MethodType].paramInfos.map(clsOf(_))
239-
structuralCall(nme.applyDynamic, classOfs).cast(tpe.finalResultType)
265+
structuralCall(nme.applyDynamic, classOfs).maybeBoxingCast(tpe.finalResultType)
240266
}
241267

242268
// (@allanrenucci) I think everything below is dead code

tests/run/i14340.check

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
1
2+
1
3+
2
4+
2
5+
10
6+
10
7+
20
8+
20
9+
100
10+
100

tests/run/i14340.scala

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
class Container1 extends reflect.Selectable
2+
3+
class Container2(values: Map[String, Any], methods: Map[String, Int => Any]) extends Selectable:
4+
def selectDynamic(name: String) = values(name)
5+
def applyDynamic(name: String)(arg: Int) = methods(name)(arg)
6+
7+
class Foo(val value: Int) extends AnyVal
8+
class Bar[A](val value: A) extends AnyVal
9+
10+
object Helpers:
11+
def foo = Foo(1)
12+
def bar = Bar(Foo(2))
13+
def qux1 = Bar(new Container1 { def foo = Foo(10) })
14+
def qux2 = Bar(new Container2(Map("foo" -> Foo(20)), Map.empty).asInstanceOf[Container2 { def foo: Foo }])
15+
16+
@main def Test: Unit =
17+
val cont1 = new Container1:
18+
def foo = Helpers.foo
19+
val bar = Helpers.bar
20+
def qux1 = Helpers.qux1
21+
def qux2 = Helpers.qux2
22+
def fooFromInt(i: Int) = Foo(i)
23+
24+
val cont2values = Map(
25+
"foo" -> Helpers.foo,
26+
"bar" -> Helpers.bar,
27+
"qux1" -> Helpers.qux1,
28+
"qux2" -> Helpers.qux2
29+
)
30+
31+
val cont2methods = Map(
32+
"fooFromInt" -> { (i: Int) => Foo(i) }
33+
)
34+
35+
val cont2 = Container2(cont2values, cont2methods).asInstanceOf[Container2 {
36+
def foo: Foo
37+
def bar: Bar[Foo]
38+
def qux1: Bar[Container1 { def foo: Foo }]
39+
def qux2: Bar[Container2 { def foo: Foo }]
40+
def fooFromInt(i: Int): Foo
41+
}]
42+
43+
44+
println(cont1.foo.value)
45+
println(cont2.foo.value)
46+
47+
println(cont1.bar.value.value)
48+
println(cont2.bar.value.value)
49+
50+
println(cont1.qux1.value.foo.value)
51+
println(cont2.qux1.value.foo.value)
52+
53+
println(cont1.qux2.value.foo.value)
54+
println(cont2.qux2.value.foo.value)
55+
56+
println(cont1.fooFromInt(100).value)
57+
println(cont2.fooFromInt(100).value)

0 commit comments

Comments
 (0)