diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 174244b4a456..5416c0719467 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index 1630ce31e4c6..92d4f678b15c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -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 = @@ -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 { @@ -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 diff --git a/tests/run/i14340.check b/tests/run/i14340.check new file mode 100644 index 000000000000..e96afd98e6e9 --- /dev/null +++ b/tests/run/i14340.check @@ -0,0 +1,10 @@ +1 +1 +2 +2 +10 +10 +20 +20 +100 +100 \ No newline at end of file diff --git a/tests/run/i14340.scala b/tests/run/i14340.scala new file mode 100644 index 000000000000..0670c7e471ac --- /dev/null +++ b/tests/run/i14340.scala @@ -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)