Skip to content

Commit cc3da61

Browse files
authored
Merge pull request #10393 from dotty-staging/final-reflect-selectable-methods
Make the dynamic methods of reflect.Selectable final.
2 parents dda6be5 + da39683 commit cc3da61

File tree

13 files changed

+177
-154
lines changed

13 files changed

+177
-154
lines changed

compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala

Lines changed: 61 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -3199,26 +3199,24 @@ class JSCodeGen()(using genCtx: Context) {
31993199
}
32003200

32013201
/** Gen JS code for an asInstanceOf cast (for reference types only) */
3202-
private def genAsInstanceOf(value: js.Tree, to: Type)(
3203-
implicit pos: Position): js.Tree = {
3204-
3205-
val sym = to.typeSymbol
3202+
private def genAsInstanceOf(value: js.Tree, to: Type)(implicit pos: Position): js.Tree =
3203+
genAsInstanceOf(value, toIRType(to))
32063204

3207-
if (sym == defn.ObjectClass || sym.isJSType) {
3208-
/* asInstanceOf[Object] always succeeds, and
3209-
* asInstanceOf to a raw JS type is completely erased.
3210-
*/
3211-
value
3212-
} else if (sym == defn.NullClass) {
3213-
js.If(
3214-
js.BinaryOp(js.BinaryOp.===, value, js.Null()),
3215-
js.Null(),
3216-
genThrowClassCastException())(
3217-
jstpe.NullType)
3218-
} else if (sym == defn.NothingClass) {
3219-
js.Block(value, genThrowClassCastException())
3220-
} else {
3221-
js.AsInstanceOf(value, toIRType(to))
3205+
/** Gen JS code for an asInstanceOf cast (for reference types only) */
3206+
private def genAsInstanceOf(value: js.Tree, to: jstpe.Type)(implicit pos: Position): js.Tree = {
3207+
to match {
3208+
case jstpe.AnyType =>
3209+
value
3210+
case jstpe.NullType =>
3211+
js.If(
3212+
js.BinaryOp(js.BinaryOp.===, value, js.Null()),
3213+
js.Null(),
3214+
genThrowClassCastException())(
3215+
jstpe.NullType)
3216+
case jstpe.NothingType =>
3217+
js.Block(value, genThrowClassCastException())
3218+
case _ =>
3219+
js.AsInstanceOf(value, to)
32223220
}
32233221
}
32243222

@@ -3558,48 +3556,56 @@ class JSCodeGen()(using genCtx: Context) {
35583556
* {{{
35593557
* reflectiveSelectable(structural).selectDynamic("foo")
35603558
* reflectiveSelectable(structural).applyDynamic("bar",
3561-
* ClassTag(classOf[Int]), ClassTag(classOf[String])
3559+
* classOf[Int], classOf[String]
35623560
* )(
35633561
* 6, "hello"
35643562
* )
35653563
* }}}
35663564
*
3567-
* and eventually reach the back-end as
3565+
* When the original `structural` value is already of a subtype of
3566+
* `scala.reflect.Selectable`, there is no conversion involved. There could
3567+
* also be any other arbitrary conversion, such as the deprecated bridge for
3568+
* Scala 2's `import scala.language.reflectiveCalls`. In general, the shape
3569+
* is therefore the following, for some `selectable: reflect.Selectable`:
35683570
*
35693571
* {{{
3570-
* reflectiveSelectable(structural).selectDynamic("foo") // same as above
3571-
* reflectiveSelectable(structural).applyDynamic("bar",
3572-
* wrapRefArray([ ClassTag(classOf[Int]), ClassTag(classOf[String]) : ClassTag ]
3572+
* selectable.selectDynamic("foo")
3573+
* selectable.applyDynamic("bar",
3574+
* classOf[Int], classOf[String]
35733575
* )(
3574-
* genericWrapArray([ Int.box(6), "hello" : Object ])
3576+
* 6, "hello"
35753577
* )
35763578
* }}}
35773579
*
3578-
* If we use the deprecated `import scala.language.reflectiveCalls`, the
3579-
* wrapper for the receiver `structural` are the following instead:
3580+
* and eventually reaches the back-end as
35803581
*
35813582
* {{{
3582-
* reflectiveSelectableFromLangReflectiveCalls(structural)(
3583-
* using scala.languageFeature.reflectiveCalls)
3583+
* selectable.selectDynamic("foo") // same as above
3584+
* selectable.applyDynamic("bar",
3585+
* wrapRefArray([ classOf[Int], classOf[String] : jl.Class ]
3586+
* )(
3587+
* genericWrapArray([ Int.box(6), "hello" : Object ])
3588+
* )
35843589
* }}}
35853590
*
3586-
* (in which case we don't care about the contextual argument).
3587-
*
35883591
* In SJSIR, they must be encoded as follows:
35893592
*
35903593
* {{{
3591-
* structural.foo;R()
3592-
* structural.bar;I;Ljava.lang.String;R(
3594+
* selectable.selectedValue;O().foo;R()
3595+
* selectable.selectedValue;O().bar;I;Ljava.lang.String;R(
35933596
* Int.box(6).asInstanceOf[int],
35943597
* "hello".asInstanceOf[java.lang.String]
35953598
* )
35963599
* }}}
35973600
*
3601+
* where `selectedValue;O()` is declared in `scala.reflect.Selectable` and
3602+
* holds the actual instance on which to perform the reflective operations.
3603+
* For the typical use case from the first snippet, it returns `structural`.
3604+
*
35983605
* This means that we must deconstruct the elaborated calls to recover:
35993606
*
3600-
* - the original receiver `structural`
36013607
* - the method name as a compile-time string `foo` or `bar`
3602-
* - the `tp: Type`s that have been wrapped in `ClassTag(classOf[tp])`, as a
3608+
* - the `tp: Type`s that have been wrapped in `classOf[tp]`, as a
36033609
* compile-time List[Type], from which we'll derive `jstpe.Type`s for the
36043610
* `asInstanceOf`s and `jstpe.TypeRef`s for the `MethodName.reflectiveProxy`
36053611
* - the actual arguments as a compile-time `List[Tree]`
@@ -3609,26 +3615,10 @@ class JSCodeGen()(using genCtx: Context) {
36093615
*/
36103616
private def genReflectiveCall(tree: Apply, isSelectDynamic: Boolean): js.Tree = {
36113617
implicit val pos = tree.span
3612-
val Apply(fun @ Select(receiver0, _), args) = tree
3618+
val Apply(fun @ Select(receiver, _), args) = tree
36133619

3614-
/* Extract the real receiver, which is the first argument to one of the
3615-
* implicit conversions scala.reflect.Selectable.reflectiveSelectable or
3616-
* scala.Selectable.reflectiveSelectableFromLangReflectiveCalls.
3617-
*/
3618-
val receiver = receiver0 match {
3619-
case Apply(fun1, receiver :: _)
3620-
if fun1.symbol == jsdefn.ReflectSelectable_reflectiveSelectable ||
3621-
fun1.symbol == jsdefn.Selectable_reflectiveSelectableFromLangReflectiveCalls =>
3622-
genExpr(receiver)
3623-
3624-
case _ =>
3625-
report.error(
3626-
"The receiver of Selectable.selectDynamic or Selectable.applyDynamic " +
3627-
"must be a call to the (implicit) method scala.reflect.Selectable.reflectiveSelectable. " +
3628-
"Other uses are not supported in Scala.js.",
3629-
tree.sourcePos)
3630-
js.Undefined()
3631-
}
3620+
val selectedValueTree = js.Apply(js.ApplyFlags.empty, genExpr(receiver),
3621+
js.MethodIdent(selectedValueMethodName), Nil)(jstpe.AnyType)
36323622

36333623
// Extract the method name as a String
36343624
val methodNameStr = args.head match {
@@ -3648,38 +3638,30 @@ class JSCodeGen()(using genCtx: Context) {
36483638
} else {
36493639
// Extract the param type refs and actual args from the 2nd and 3rd argument to applyDynamic
36503640
args.tail match {
3651-
case WrapArray(classTagsArray: JavaSeqLiteral) :: WrapArray(actualArgsAnyArray: JavaSeqLiteral) :: Nil =>
3652-
// Extract jstpe.Type's and jstpe.TypeRef's from the ClassTag.apply(_) trees
3653-
val formalParamTypesAndTypeRefs = classTagsArray.elems.map {
3654-
// ClassTag.apply(classOf[tp]) -> tp
3655-
case Apply(fun, Literal(const) :: Nil)
3656-
if fun.symbol == defn.ClassTagModule_apply && const.tag == Constants.ClazzTag =>
3641+
case WrapArray(classOfsArray: JavaSeqLiteral) :: WrapArray(actualArgsAnyArray: JavaSeqLiteral) :: Nil =>
3642+
// Extract jstpe.Type's and jstpe.TypeRef's from the classOf[_] trees
3643+
val formalParamTypesAndTypeRefs = classOfsArray.elems.map {
3644+
// classOf[tp] -> tp
3645+
case Literal(const) if const.tag == Constants.ClazzTag =>
36573646
toIRTypeAndTypeRef(const.typeValue)
3658-
// ClassTag.SpecialType -> erasure(SepecialType.typeRef) (e.g., ClassTag.Any -> Object)
3659-
case Apply(Select(classTagModule, name), Nil)
3660-
if classTagModule.symbol == defn.ClassTagModule &&
3661-
defn.SpecialClassTagClasses.exists(_.name == name.toTypeName) =>
3662-
toIRTypeAndTypeRef(TypeErasure.erasure(
3663-
defn.SpecialClassTagClasses.find(_.name == name.toTypeName).get.typeRef))
36643647
// Anything else is invalid
3665-
case classTag =>
3648+
case otherTree =>
36663649
report.error(
3667-
"The ClassTags passed to Selectable.applyDynamic must be " +
3668-
"literal ClassTag(classOf[T]) expressions " +
3669-
"(typically compiler-generated). " +
3650+
"The java.lang.Class[_] arguments passed to Selectable.applyDynamic must be " +
3651+
"literal classOf[T] expressions (typically compiler-generated). " +
36703652
"Other uses are not supported in Scala.js.",
3671-
classTag.sourcePos)
3653+
otherTree.sourcePos)
36723654
(jstpe.AnyType, jstpe.ClassRef(jsNames.ObjectClass))
36733655
}
36743656

36753657
// Gen the actual args, downcasting them to the formal param types
36763658
val actualArgs = actualArgsAnyArray.elems.zip(formalParamTypesAndTypeRefs).map {
36773659
(actualArgAny, formalParamTypeAndTypeRef) =>
36783660
val genActualArgAny = genExpr(actualArgAny)
3679-
js.AsInstanceOf(genActualArgAny, formalParamTypeAndTypeRef._1)(genActualArgAny.pos)
3661+
genAsInstanceOf(genActualArgAny, formalParamTypeAndTypeRef._1)(genActualArgAny.pos)
36803662
}
36813663

3682-
(formalParamTypesAndTypeRefs.map(_._2), actualArgs)
3664+
(formalParamTypesAndTypeRefs.map(pair => toParamOrResultTypeRef(pair._2)), actualArgs)
36833665

36843666
case _ =>
36853667
report.error(
@@ -3692,7 +3674,7 @@ class JSCodeGen()(using genCtx: Context) {
36923674

36933675
val methodName = MethodName.reflectiveProxy(methodNameStr, formalParamTypeRefs)
36943676

3695-
js.Apply(js.ApplyFlags.empty, receiver, js.MethodIdent(methodName), actualArgs)(jstpe.AnyType)
3677+
js.Apply(js.ApplyFlags.empty, selectedValueTree, js.MethodIdent(methodName), actualArgs)(jstpe.AnyType)
36963678
}
36973679

36983680
/** Gen actual actual arguments to Scala method call.
@@ -4306,10 +4288,13 @@ object JSCodeGen {
43064288
private val NullPointerExceptionClass = ClassName("java.lang.NullPointerException")
43074289
private val JSObjectClassName = ClassName("scala.scalajs.js.Object")
43084290

4291+
private val ObjectClassRef = jstpe.ClassRef(ir.Names.ObjectClass)
4292+
43094293
private val newSimpleMethodName = SimpleMethodName("new")
43104294

4311-
private val ObjectArgConstructorName =
4312-
MethodName.constructor(List(jstpe.ClassRef(ir.Names.ObjectClass)))
4295+
private val selectedValueMethodName = MethodName("selectedValue", Nil, ObjectClassRef)
4296+
4297+
private val ObjectArgConstructorName = MethodName.constructor(List(ObjectClassRef))
43134298

43144299
private val thisOriginalName = OriginalName("this")
43154300

compiler/src/dotty/tools/backend/sjs/JSEncoding.scala

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -231,13 +231,8 @@ object JSEncoding {
231231
}
232232

233233
/** Computes the type ref for a type, to be used in a method signature. */
234-
private def paramOrResultTypeRef(tpe: Type)(using Context): jstpe.TypeRef = {
235-
toTypeRef(tpe) match {
236-
case jstpe.ClassRef(ScalaRuntimeNullClassName) => jstpe.NullRef
237-
case jstpe.ClassRef(ScalaRuntimeNothingClassName) => jstpe.NothingRef
238-
case otherTypeRef => otherTypeRef
239-
}
240-
}
234+
private def paramOrResultTypeRef(tpe: Type)(using Context): jstpe.TypeRef =
235+
toParamOrResultTypeRef(toTypeRef(tpe))
241236

242237
def encodeLocalSym(sym: Symbol)(
243238
implicit ctx: Context, pos: ir.Position, localNames: LocalNameGenerator): js.LocalIdent = {
@@ -284,6 +279,15 @@ object JSEncoding {
284279
ClassName(sym1.javaClassName)
285280
}
286281

282+
/** Converts a general TypeRef to a TypeRef to be used in a method signature. */
283+
def toParamOrResultTypeRef(typeRef: jstpe.TypeRef): jstpe.TypeRef = {
284+
typeRef match {
285+
case jstpe.ClassRef(ScalaRuntimeNullClassName) => jstpe.NullRef
286+
case jstpe.ClassRef(ScalaRuntimeNothingClassName) => jstpe.NothingRef
287+
case _ => typeRef
288+
}
289+
}
290+
287291
def toIRTypeAndTypeRef(tp: Type)(using Context): (jstpe.Type, jstpe.TypeRef) = {
288292
val typeRefInternal = toTypeRefInternal(tp)
289293
(toIRTypeInternal(typeRefInternal), typeRefInternal._1)

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

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import dotty.tools.dotc.core.Names.{Name, TermName}
1111
import dotty.tools.dotc.core.StdNames._
1212
import dotty.tools.dotc.core.Types._
1313
import dotty.tools.dotc.core.Decorators._
14+
import dotty.tools.dotc.core.TypeErasure
1415
import util.Spans._
1516
import core.Symbols._
1617
import core.Definitions
@@ -149,22 +150,22 @@ trait Dynamic {
149150
* x1.applyDynamic("a")(a11, ..., a1n, ..., aN1, ..., aNn)
150151
* .asInstanceOf[R]
151152
* ```
152-
* If this call resolves to an `applyDynamic` method that takes a `ClassTag[?]*` as second
153+
* If this call resolves to an `applyDynamic` method that takes a `Class[?]*` as second
153154
* parameter, we further rewrite this call to
154155
* scala```
155-
* x1.applyDynamic("a", CT11, ..., CT1n, ..., CTN1, ... CTNn)
156+
* x1.applyDynamic("a", c11, ..., c1n, ..., cN1, ... cNn)
156157
* (a11, ..., a1n, ..., aN1, ..., aNn)
157158
* .asInstanceOf[R]
158159
* ```
159-
* where CT11, ..., CTNn are the class tags representing the erasure of T11, ..., TNn.
160+
* where c11, ..., cNn are the classOf constants representing the erasures of T11, ..., TNn.
160161
*
161162
* It's an error if U is neither a value nor a method type, or a dependent method
162163
* type.
163164
*/
164165
def handleStructural(tree: Tree)(using Context): Tree = {
165166
val (fun @ Select(qual, name), targs, vargss) = decomposeCall(tree)
166167

167-
def structuralCall(selectorName: TermName, ctags: => List[Tree]) = {
168+
def structuralCall(selectorName: TermName, classOfs: => List[Tree]) = {
168169
val selectable = adapt(qual, defn.SelectableClass.typeRef)
169170

170171
// ($qual: Selectable).$selectorName("$name")
@@ -177,27 +178,27 @@ trait Dynamic {
177178
if (vargss.isEmpty) base
178179
else untpd.Apply(base, vargss.flatten.map(untpd.TypedSplice(_)))
179180

180-
// If function is an `applyDynamic` that takes a ClassTag* parameter,
181-
// add `ctags`.
182-
def addClassTags(tree: Tree): Tree = tree match
181+
// If function is an `applyDynamic` that takes a Class* parameter,
182+
// add `classOfs`.
183+
def addClassOfs(tree: Tree): Tree = tree match
183184
case Apply(fn: Apply, args) =>
184-
cpy.Apply(tree)(addClassTags(fn), args)
185+
cpy.Apply(tree)(addClassOfs(fn), args)
185186
case Apply(fn @ Select(_, nme.applyDynamic), nameArg :: _ :: Nil) =>
186187
fn.tpe.widen match
187188
case mt: MethodType => mt.paramInfos match
188-
case _ :: ctagsParam :: Nil
189-
if ctagsParam.isRepeatedParam
190-
&& ctagsParam.argInfos.head.isRef(defn.ClassTagClass) =>
191-
val ctagType = defn.ClassTagClass.typeRef.appliedTo(TypeBounds.empty)
189+
case _ :: classOfsParam :: Nil
190+
if classOfsParam.isRepeatedParam
191+
&& classOfsParam.argInfos.head.isRef(defn.ClassClass) =>
192+
val jlClassType = defn.ClassClass.typeRef.appliedTo(TypeBounds.empty)
192193
cpy.Apply(tree)(fn,
193-
nameArg :: seqToRepeated(SeqLiteral(ctags, TypeTree(ctagType))) :: Nil)
194+
nameArg :: seqToRepeated(SeqLiteral(classOfs, TypeTree(jlClassType))) :: Nil)
194195
case _ => tree
195196
case other => tree
196197
case _ => tree
197-
addClassTags(typed(scall))
198+
addClassOfs(typed(scall))
198199
}
199200

200-
def fail(name: Name, reason: String) =
201+
def fail(reason: String): Tree =
201202
errorTree(tree, em"Structural access not allowed on method $name because it $reason")
202203

203204
fun.tpe.widen match {
@@ -215,18 +216,21 @@ trait Dynamic {
215216
}
216217

217218
if (isDependentMethod(tpe))
218-
fail(name, i"has a method type with inter-parameter dependencies")
219+
fail(i"has a method type with inter-parameter dependencies")
219220
else {
220-
def ctags = tpe.paramInfoss.flatten.map(pt =>
221-
implicitArgTree(defn.ClassTagClass.typeRef.appliedTo(pt.widenDealias :: Nil), fun.span.endPos))
222-
structuralCall(nme.applyDynamic, ctags).cast(tpe.finalResultType)
221+
def classOfs =
222+
if tpe.paramInfoss.nestedExists(!TypeErasure.hasStableErasure(_)) then
223+
fail(i"has a parameter type with an unstable erasure") :: Nil
224+
else
225+
TypeErasure.erasure(tpe).asInstanceOf[MethodType].paramInfos.map(clsOf(_))
226+
structuralCall(nme.applyDynamic, classOfs).cast(tpe.finalResultType)
223227
}
224228

225229
// (@allanrenucci) I think everything below is dead code
226230
case _: PolyType =>
227-
fail(name, "is polymorphic")
231+
fail("is polymorphic")
228232
case tpe =>
229-
fail(name, i"has an unsupported type: $tpe")
233+
fail(i"has an unsupported type: $tpe")
230234
}
231235
}
232236
}

docs/docs/reference/changed-features/structural-types-spec.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,17 @@ def selectDynamic(name: String): T
3535
```
3636
Often, the return type `T` is `Any`.
3737

38-
The `applyDynamic` method is used for selections that are applied to arguments. It takes a method name and possibly `ClassTag`s representing its parameters types as well as the arguments to pass to the function.
38+
Unlike `scala.Dynamic`, there is no special meaning for an `updateDynamic` method.
39+
However, we reserve the right to give it meaning in the future.
40+
Consequently, it is recommended not to define any member called `updateDynamic` in `Selectable`s.
41+
42+
The `applyDynamic` method is used for selections that are applied to arguments. It takes a method name and possibly `Class`es representing its parameters types as well as the arguments to pass to the function.
3943
Its signature should be of one of the two following forms:
4044
```scala
4145
def applyDynamic(name: String)(args: Any*): T
42-
def applyDynamic(name: String, ctags: ClassTag[?]*)(args: Any*): T
46+
def applyDynamic(name: String, ctags: Class[?]*)(args: Any*): T
4347
```
44-
Both versions are passed the actual arguments in the `args` parameter. The second version takes in addition a vararg argument of class tags that identify the method's parameter classes. Such an argument is needed
48+
Both versions are passed the actual arguments in the `args` parameter. The second version takes in addition a vararg argument of `java.lang.Class`es that identify the method's parameter classes. Such an argument is needed
4549
if `applyDynamic` is implemented using Java reflection, but it could be
4650
useful in other cases as well. `selectDynamic` and `applyDynamic` can also take additional context parameters in using clauses. These are resolved in the normal way at the callsite.
4751

@@ -58,13 +62,13 @@ and `Rs` are structural refinement declarations, and given `v.a` of type `U`, we
5862
v.applyDynamic("a")(a11, ..., a1n, ..., aN1, ..., aNn)
5963
.asInstanceOf[R]
6064
```
61-
If this call resolves to an `applyDynamic` method of the second form that takes a `ClassTag[?]*` argument, we further rewrite this call to
65+
If this call resolves to an `applyDynamic` method of the second form that takes a `Class[?]*` argument, we further rewrite this call to
6266
```scala
63-
v.applyDynamic("a", CT11, ..., CT1n, ..., CTN1, ... CTNn)(
67+
v.applyDynamic("a", c11, ..., c1n, ..., cN1, ... cNn)(
6468
a11, ..., a1n, ..., aN1, ..., aNn)
6569
.asInstanceOf[R]
6670
```
67-
where each `CT_ij` is the class tag of the type of the formal parameter `Tij`
71+
where each `c_ij` is the literal `java.lang.Class[?]` of the type of the formal parameter `Tij`, i.e., `classOf[Tij]`.
6872

6973
- If `U` is neither a value nor a method type, or a dependent method
7074
type, an error is emitted.

0 commit comments

Comments
 (0)