Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properly handle AnyVals as refinement members of Selectables #16286

Merged
merged 2 commits into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -808,6 +808,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)