Skip to content

Commit

Permalink
Properly handle AnyVals as refinement members of Selectables (#16286
Browse files Browse the repository at this point in the history
)

- add dynamic access to value classes field
  • Loading branch information
prolativ committed Feb 22, 2023
2 parents ab19081 + 1a6224b commit 97a2bcf
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 11 deletions.
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,8 @@ class Definitions {
@tu lazy val ClassTagModule: Symbol = ClassTagClass.companionModule
@tu lazy val ClassTagModule_apply: Symbol = ClassTagModule.requiredMethod(nme.apply)

@tu lazy val ReflectSelectableTypeRef: TypeRef = requiredClassRef("scala.reflect.Selectable")

@tu lazy val TypeTestClass: ClassSymbol = requiredClass("scala.reflect.TypeTest")
@tu lazy val TypeTest_unapply: Symbol = TypeTestClass.requiredMethod(nme.unapply)
@tu lazy val TypeTestModule_identity: Symbol = TypeTestClass.companionModule.requiredMethod(nme.identity)
Expand Down
48 changes: 37 additions & 11 deletions compiler/src/dotty/tools/dotc/typer/Dynamic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@ package dotty.tools
package dotc
package typer

import dotty.tools.dotc.ast.Trees._
import dotty.tools.dotc.ast.Trees.*
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.ast.untpd
import dotty.tools.dotc.core.Constants.Constant
import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.core.Contexts.*
import dotty.tools.dotc.core.Names.{Name, TermName}
import dotty.tools.dotc.core.StdNames._
import dotty.tools.dotc.core.Types._
import dotty.tools.dotc.core.Decorators._
import dotty.tools.dotc.core.StdNames.*
import dotty.tools.dotc.core.Types.*
import dotty.tools.dotc.core.Decorators.*
import dotty.tools.dotc.core.TypeErasure
import util.Spans._
import core.Symbols._
import ErrorReporting._
import reporting._
import util.Spans.*
import core.Symbols.*
import ErrorReporting.*
import dotty.tools.dotc.transform.ValueClasses
import dotty.tools.dotc.transform.TypeUtils.isPrimitiveValueType
import reporting.*

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

extension (tree: Tree)
/** 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,
* only the JVM type after erasure can be obtained through reflection, e.g.
*
* class Foo(val i: Int) extends AnyVal
* class Reflective extends reflect.Selectable
* val reflective = new Reflective {
* def foo = Foo(1) // Foo at compile time, java.lang.Integer in reflection
* }
*
* Because of that reflective access cannot be implemented properly in `scala.reflect.SelectDynamic` itself
* because it's not known there if the value should be wrapped in a value class constructor call or not.
* 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.
*/
def maybeBoxingCast(tpe: Type) =
val maybeBoxed =
if ValueClasses.isDerivedValueClass(tpe.classSymbol) && qual.tpe <:< defn.ReflectSelectableTypeRef then
val genericUnderlying = ValueClasses.valueClassUnbox(tpe.classSymbol.asClass)
val underlying = tpe.select(genericUnderlying).widen.resultType
New(tpe, tree.cast(underlying) :: Nil)
else
tree
maybeBoxed.cast(tpe)

fun.tpe.widen match {
case tpe: ValueType =>
structuralCall(nme.selectDynamic, Nil).cast(tpe)
structuralCall(nme.selectDynamic, Nil).maybeBoxingCast(tpe)

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

// (@allanrenucci) I think everything below is dead code
Expand Down
10 changes: 10 additions & 0 deletions tests/run/i14340.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
1
1
2
2
10
10
20
20
100
100
57 changes: 57 additions & 0 deletions tests/run/i14340.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
class Container1 extends reflect.Selectable

class Container2(values: Map[String, Any], methods: Map[String, Int => Any]) extends Selectable:
def selectDynamic(name: String) = values(name)
def applyDynamic(name: String)(arg: Int) = methods(name)(arg)

class Foo(val value: Int) extends AnyVal
class Bar[A](val value: A) extends AnyVal

object Helpers:
def foo = Foo(1)
def bar = Bar(Foo(2))
def qux1 = Bar(new Container1 { def foo = Foo(10) })
def qux2 = Bar(new Container2(Map("foo" -> Foo(20)), Map.empty).asInstanceOf[Container2 { def foo: Foo }])

@main def Test: Unit =
val cont1 = new Container1:
def foo = Helpers.foo
val bar = Helpers.bar
def qux1 = Helpers.qux1
def qux2 = Helpers.qux2
def fooFromInt(i: Int) = Foo(i)

val cont2values = Map(
"foo" -> Helpers.foo,
"bar" -> Helpers.bar,
"qux1" -> Helpers.qux1,
"qux2" -> Helpers.qux2
)

val cont2methods = Map(
"fooFromInt" -> { (i: Int) => Foo(i) }
)

val cont2 = Container2(cont2values, cont2methods).asInstanceOf[Container2 {
def foo: Foo
def bar: Bar[Foo]
def qux1: Bar[Container1 { def foo: Foo }]
def qux2: Bar[Container2 { def foo: Foo }]
def fooFromInt(i: Int): Foo
}]


println(cont1.foo.value)
println(cont2.foo.value)

println(cont1.bar.value.value)
println(cont2.bar.value.value)

println(cont1.qux1.value.foo.value)
println(cont2.qux1.value.foo.value)

println(cont1.qux2.value.foo.value)
println(cont2.qux2.value.foo.value)

println(cont1.fooFromInt(100).value)
println(cont2.fooFromInt(100).value)

0 comments on commit 97a2bcf

Please sign in to comment.