From 60b6ff04d0d25415840a9f1de88ea8518f0973a3 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 15 Mar 2023 11:35:57 +0100 Subject: [PATCH 1/3] Allow selectDynamic and applyDynamic to be an extension methods Allow selectDynamic and applyDynamic to be an extension methods when dispatching structurally. Fixes #17100 --- compiler/src/dotty/tools/dotc/typer/Dynamic.scala | 8 ++++---- tests/pos/i17100.scala | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 tests/pos/i17100.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index 8ec4e9416151..1513872cf47a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -186,7 +186,7 @@ trait Dynamic { // ($qual: Selectable).$selectorName("$name") val base = untpd.Apply( - untpd.TypedSplice(selectable.select(selectorName)).withSpan(fun.span), + untpd.Select(untpd.TypedSplice(selectable), selectorName).withSpan(fun.span), (Literal(Constant(name.encode.toString)) :: Nil).map(untpd.TypedSplice(_))) val scall = @@ -219,19 +219,19 @@ trait Dynamic { 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 = + 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 diff --git a/tests/pos/i17100.scala b/tests/pos/i17100.scala new file mode 100644 index 000000000000..2fd01bcbe5ff --- /dev/null +++ b/tests/pos/i17100.scala @@ -0,0 +1,10 @@ +trait Sel extends Selectable + +extension (s: Sel) + def selectDynamic(name: String) = ??? + def applyDynamic(name: String)(x: Int) = ??? + +val sel = (new Sel {}).asInstanceOf[Sel{ def foo: String; def bar(x: Int): Int }] +val foo = sel.selectDynamic("foo") +val foo2 = sel.foo +val foo3 = sel.bar(2) From a3242e82b20d9df8b8d48ba9e50b96f93009e5cb Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 15 Mar 2023 13:04:55 +0100 Subject: [PATCH 2/3] Update semanticDB expect files --- tests/pos/i17100.scala | 6 +++++- tests/semanticdb/expect/Advanced.expect.scala | 8 ++++---- tests/semanticdb/metac.expect | 6 +++++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/pos/i17100.scala b/tests/pos/i17100.scala index 2fd01bcbe5ff..1858e0383f8d 100644 --- a/tests/pos/i17100.scala +++ b/tests/pos/i17100.scala @@ -3,8 +3,12 @@ trait Sel extends Selectable extension (s: Sel) def selectDynamic(name: String) = ??? def applyDynamic(name: String)(x: Int) = ??? + def applyDynamic(name: String)() = ??? -val sel = (new Sel {}).asInstanceOf[Sel{ def foo: String; def bar(x: Int): Int }] +val sel = (new Sel {}).asInstanceOf[Sel{ def foo: String; def bar(x: Int): Int; def baz(): Int }] val foo = sel.selectDynamic("foo") val foo2 = sel.foo val foo3 = sel.bar(2) +val foo4 = sel.baz() + + diff --git a/tests/semanticdb/expect/Advanced.expect.scala b/tests/semanticdb/expect/Advanced.expect.scala index c701821a09b8..d36fcd611eef 100644 --- a/tests/semanticdb/expect/Advanced.expect.scala +++ b/tests/semanticdb/expect/Advanced.expect.scala @@ -25,11 +25,11 @@ class Wildcards/*<-advanced::Wildcards#*/ { object Test/*<-advanced::Test.*/ { val s/*<-advanced::Test.s.*/ = new Structural/*->advanced::Structural#*/ val s1/*<-advanced::Test.s1.*/ = s/*->advanced::Test.s.*/.s1/*->advanced::Structural#s1().*/ - val s1x/*<-advanced::Test.s1x.*/ = s/*->advanced::Test.s.*/.s1/*->advanced::Structural#s1().*/.x + val s1x/*<-advanced::Test.s1x.*/ = s/*->advanced::Test.s.*/.s1/*->advanced::Structural#s1().*/.x/*->scala::reflect::Selectable#selectDynamic().*/ val s2/*<-advanced::Test.s2.*/ = s/*->advanced::Test.s.*/.s2/*->advanced::Structural#s2().*/ - val s2x/*<-advanced::Test.s2x.*/ = s/*->advanced::Test.s.*/.s2/*->advanced::Structural#s2().*/.x + val s2x/*<-advanced::Test.s2x.*/ = s/*->advanced::Test.s.*/.s2/*->advanced::Structural#s2().*/.x/*->scala::reflect::Selectable#selectDynamic().*/ val s3/*<-advanced::Test.s3.*/ = s/*->advanced::Test.s.*/.s3/*->advanced::Structural#s3().*/ - val s3x/*<-advanced::Test.s3x.*/ = s/*->advanced::Test.s.*/.s3/*->advanced::Structural#s3().*/.m(???/*->scala::Predef.`???`().*/) + val s3x/*<-advanced::Test.s3x.*/ = s/*->advanced::Test.s.*/.s3/*->advanced::Structural#s3().*/.m/*->scala::reflect::Selectable#applyDynamic().*/(???/*->scala::Predef.`???`().*/) val e/*<-advanced::Test.e.*/ = new Wildcards/*->advanced::Wildcards#*/ val e1/*<-advanced::Test.e1.*/ = e/*->advanced::Test.e.*/.e1/*->advanced::Wildcards#e1().*/ @@ -45,7 +45,7 @@ object Test/*<-advanced::Test.*/ { // see: https://github.com/lampepfl/dotty/pull/14608#discussion_r835642563 lazy val foo/*<-advanced::Test.foo.*/: (reflect.Selectable/*->scala::reflect::Selectable#*/ { type A/*<-local16*/ = Int/*->scala::Int#*/ }) &/*->scala::`&`#*/ (reflect.Selectable/*->scala::reflect::Selectable#*/ { type A/*<-local17*/ = Int/*->scala::Int#*/; val a/*<-local18*/: A/*->local17*/ }) = ???/*->scala::Predef.`???`().*/ - def bar/*<-advanced::Test.bar().*/: foo/*->advanced::Test.foo.*/.A/*->local17*/ = foo/*->advanced::Test.foo.*/.a + def bar/*<-advanced::Test.bar().*/: foo/*->advanced::Test.foo.*/.A/*->local17*/ = foo/*->advanced::Test.foo.*/.a/*->scala::reflect::Selectable#selectDynamic().*/ } diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index c78d30e00863..f5556e28bd1b 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -49,7 +49,7 @@ Uri => Advanced.scala Text => empty Language => Scala Symbols => 60 entries -Occurrences => 134 entries +Occurrences => 138 entries Synthetics => 3 entries Symbols: @@ -187,18 +187,21 @@ Occurrences: [27:6..27:9): s1x <- advanced/Test.s1x. [27:12..27:13): s -> advanced/Test.s. [27:14..27:16): s1 -> advanced/Structural#s1(). +[27:16..27:18): .x -> scala/reflect/Selectable#selectDynamic(). [28:6..28:8): s2 <- advanced/Test.s2. [28:11..28:12): s -> advanced/Test.s. [28:13..28:15): s2 -> advanced/Structural#s2(). [29:6..29:9): s2x <- advanced/Test.s2x. [29:12..29:13): s -> advanced/Test.s. [29:14..29:16): s2 -> advanced/Structural#s2(). +[29:16..29:18): .x -> scala/reflect/Selectable#selectDynamic(). [30:6..30:8): s3 <- advanced/Test.s3. [30:11..30:12): s -> advanced/Test.s. [30:13..30:15): s3 -> advanced/Structural#s3(). [31:6..31:9): s3x <- advanced/Test.s3x. [31:12..31:13): s -> advanced/Test.s. [31:14..31:16): s3 -> advanced/Structural#s3(). +[31:16..31:18): .m -> scala/reflect/Selectable#applyDynamic(). [31:19..31:22): ??? -> scala/Predef.`???`(). [33:6..33:7): e <- advanced/Test.e. [33:14..33:23): Wildcards -> advanced/Wildcards# @@ -233,6 +236,7 @@ Occurrences: [47:11..47:14): foo -> advanced/Test.foo. [47:15..47:16): A -> local17 [47:19..47:22): foo -> advanced/Test.foo. +[47:22..47:24): .a -> scala/reflect/Selectable#selectDynamic(). [52:6..52:13): HKClass <- advanced/HKClass# [52:14..52:15): F <- advanced/HKClass#[F] [52:20..52:21): T <- advanced/HKClass#``().[F][T] From fa29ba5c03da550123299611aa7f4c32028472ce Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 15 Mar 2023 13:41:31 +0100 Subject: [PATCH 3/3] Also allow structural dispatch on Dynamic Given ```scala trait Sel extends Dynamic extension (s: Sel) def selectDynamic(name: String) = ??? ``` the following worked: ```scala val sel = new Sel {} val foo = sel.foo ``` but the following didn't: ```scala val sel2 = (new Sel {}).asInstanceOf[Sel{ def foo: String }] val foo2 = sel2.foo ``` The problem was that we recognized a structural dispatch and then required the qualifier to be an instance of `Selectable`. But in fact, `Dynamic` works just as well, and the mechanism is the same. It's just that `Dynamic` is less type safe then `Selectable`. --- compiler/src/dotty/tools/dotc/typer/Dynamic.scala | 2 +- tests/pos/i17100a.scala | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i17100a.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index 1513872cf47a..717966923708 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -181,7 +181,7 @@ trait Dynamic { val vargss = termArgss(tree) def structuralCall(selectorName: TermName, classOfs: => List[Tree]) = { - val selectable = adapt(qual, defn.SelectableClass.typeRef) + val selectable = adapt(qual, defn.SelectableClass.typeRef | defn.DynamicClass.typeRef) // ($qual: Selectable).$selectorName("$name") val base = diff --git a/tests/pos/i17100a.scala b/tests/pos/i17100a.scala new file mode 100644 index 000000000000..abf74c80a4f5 --- /dev/null +++ b/tests/pos/i17100a.scala @@ -0,0 +1,12 @@ + +import scala.language.dynamics +trait Sel extends Dynamic + +extension (s: Sel) + def selectDynamic(name: String) = ??? + +val sel = new Sel {} +val foo = sel.foo +val sel2 = (new Sel {}).asInstanceOf[Sel{ def foo: String }] +val foo2 = sel2.foo +