Skip to content

Use jl.Class'es instead of ClassTag's in structural calls. #10388

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

Merged
merged 1 commit into from
Nov 20, 2020
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
72 changes: 31 additions & 41 deletions compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3199,26 +3199,24 @@ class JSCodeGen()(using genCtx: Context) {
}

/** Gen JS code for an asInstanceOf cast (for reference types only) */
private def genAsInstanceOf(value: js.Tree, to: Type)(
implicit pos: Position): js.Tree = {

val sym = to.typeSymbol
private def genAsInstanceOf(value: js.Tree, to: Type)(implicit pos: Position): js.Tree =
genAsInstanceOf(value, toIRType(to))

if (sym == defn.ObjectClass || sym.isJSType) {
/* asInstanceOf[Object] always succeeds, and
* asInstanceOf to a raw JS type is completely erased.
*/
value
} else if (sym == defn.NullClass) {
js.If(
js.BinaryOp(js.BinaryOp.===, value, js.Null()),
js.Null(),
genThrowClassCastException())(
jstpe.NullType)
} else if (sym == defn.NothingClass) {
js.Block(value, genThrowClassCastException())
} else {
js.AsInstanceOf(value, toIRType(to))
/** Gen JS code for an asInstanceOf cast (for reference types only) */
private def genAsInstanceOf(value: js.Tree, to: jstpe.Type)(implicit pos: Position): js.Tree = {
to match {
case jstpe.AnyType =>
value
case jstpe.NullType =>
js.If(
js.BinaryOp(js.BinaryOp.===, value, js.Null()),
js.Null(),
genThrowClassCastException())(
jstpe.NullType)
case jstpe.NothingType =>
js.Block(value, genThrowClassCastException())
case _ =>
js.AsInstanceOf(value, to)
}
}

Expand Down Expand Up @@ -3558,7 +3556,7 @@ class JSCodeGen()(using genCtx: Context) {
* {{{
* reflectiveSelectable(structural).selectDynamic("foo")
* reflectiveSelectable(structural).applyDynamic("bar",
* ClassTag(classOf[Int]), ClassTag(classOf[String])
* classOf[Int], classOf[String]
* )(
* 6, "hello"
* )
Expand All @@ -3569,7 +3567,7 @@ class JSCodeGen()(using genCtx: Context) {
* {{{
* reflectiveSelectable(structural).selectDynamic("foo") // same as above
* reflectiveSelectable(structural).applyDynamic("bar",
* wrapRefArray([ ClassTag(classOf[Int]), ClassTag(classOf[String]) : ClassTag ]
* wrapRefArray([ classOf[Int], classOf[String] : jl.Class ]
* )(
* genericWrapArray([ Int.box(6), "hello" : Object ])
* )
Expand Down Expand Up @@ -3599,7 +3597,7 @@ class JSCodeGen()(using genCtx: Context) {
*
* - the original receiver `structural`
* - the method name as a compile-time string `foo` or `bar`
* - the `tp: Type`s that have been wrapped in `ClassTag(classOf[tp])`, as a
* - the `tp: Type`s that have been wrapped in `classOf[tp]`, as a
* compile-time List[Type], from which we'll derive `jstpe.Type`s for the
* `asInstanceOf`s and `jstpe.TypeRef`s for the `MethodName.reflectiveProxy`
* - the actual arguments as a compile-time `List[Tree]`
Expand Down Expand Up @@ -3648,38 +3646,30 @@ class JSCodeGen()(using genCtx: Context) {
} else {
// Extract the param type refs and actual args from the 2nd and 3rd argument to applyDynamic
args.tail match {
case WrapArray(classTagsArray: JavaSeqLiteral) :: WrapArray(actualArgsAnyArray: JavaSeqLiteral) :: Nil =>
// Extract jstpe.Type's and jstpe.TypeRef's from the ClassTag.apply(_) trees
val formalParamTypesAndTypeRefs = classTagsArray.elems.map {
// ClassTag.apply(classOf[tp]) -> tp
case Apply(fun, Literal(const) :: Nil)
if fun.symbol == defn.ClassTagModule_apply && const.tag == Constants.ClazzTag =>
case WrapArray(classOfsArray: JavaSeqLiteral) :: WrapArray(actualArgsAnyArray: JavaSeqLiteral) :: Nil =>
// Extract jstpe.Type's and jstpe.TypeRef's from the classOf[_] trees
val formalParamTypesAndTypeRefs = classOfsArray.elems.map {
// classOf[tp] -> tp
case Literal(const) if const.tag == Constants.ClazzTag =>
toIRTypeAndTypeRef(const.typeValue)
// ClassTag.SpecialType -> erasure(SepecialType.typeRef) (e.g., ClassTag.Any -> Object)
case Apply(Select(classTagModule, name), Nil)
if classTagModule.symbol == defn.ClassTagModule &&
defn.SpecialClassTagClasses.exists(_.name == name.toTypeName) =>
toIRTypeAndTypeRef(TypeErasure.erasure(
defn.SpecialClassTagClasses.find(_.name == name.toTypeName).get.typeRef))
// Anything else is invalid
case classTag =>
case otherTree =>
report.error(
"The ClassTags passed to Selectable.applyDynamic must be " +
"literal ClassTag(classOf[T]) expressions " +
"(typically compiler-generated). " +
"The java.lang.Class[_] arguments passed to Selectable.applyDynamic must be " +
"literal classOf[T] expressions (typically compiler-generated). " +
"Other uses are not supported in Scala.js.",
classTag.sourcePos)
otherTree.sourcePos)
(jstpe.AnyType, jstpe.ClassRef(jsNames.ObjectClass))
}

// Gen the actual args, downcasting them to the formal param types
val actualArgs = actualArgsAnyArray.elems.zip(formalParamTypesAndTypeRefs).map {
(actualArgAny, formalParamTypeAndTypeRef) =>
val genActualArgAny = genExpr(actualArgAny)
js.AsInstanceOf(genActualArgAny, formalParamTypeAndTypeRef._1)(genActualArgAny.pos)
genAsInstanceOf(genActualArgAny, formalParamTypeAndTypeRef._1)(genActualArgAny.pos)
}

(formalParamTypesAndTypeRefs.map(_._2), actualArgs)
(formalParamTypesAndTypeRefs.map(pair => toParamOrResultTypeRef(pair._2)), actualArgs)

case _ =>
report.error(
Expand Down
18 changes: 11 additions & 7 deletions compiler/src/dotty/tools/backend/sjs/JSEncoding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -231,13 +231,8 @@ object JSEncoding {
}

/** Computes the type ref for a type, to be used in a method signature. */
private def paramOrResultTypeRef(tpe: Type)(using Context): jstpe.TypeRef = {
toTypeRef(tpe) match {
case jstpe.ClassRef(ScalaRuntimeNullClassName) => jstpe.NullRef
case jstpe.ClassRef(ScalaRuntimeNothingClassName) => jstpe.NothingRef
case otherTypeRef => otherTypeRef
}
}
private def paramOrResultTypeRef(tpe: Type)(using Context): jstpe.TypeRef =
toParamOrResultTypeRef(toTypeRef(tpe))

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

/** Converts a general TypeRef to a TypeRef to be used in a method signature. */
def toParamOrResultTypeRef(typeRef: jstpe.TypeRef): jstpe.TypeRef = {
typeRef match {
case jstpe.ClassRef(ScalaRuntimeNullClassName) => jstpe.NullRef
case jstpe.ClassRef(ScalaRuntimeNothingClassName) => jstpe.NothingRef
case _ => typeRef
}
}

def toIRTypeAndTypeRef(tp: Type)(using Context): (jstpe.Type, jstpe.TypeRef) = {
val typeRefInternal = toTypeRefInternal(tp)
(toIRTypeInternal(typeRefInternal), typeRefInternal._1)
Expand Down
46 changes: 25 additions & 21 deletions compiler/src/dotty/tools/dotc/typer/Dynamic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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.TypeErasure
import util.Spans._
import core.Symbols._
import core.Definitions
Expand Down Expand Up @@ -149,22 +150,22 @@ trait Dynamic {
* x1.applyDynamic("a")(a11, ..., a1n, ..., aN1, ..., aNn)
* .asInstanceOf[R]
* ```
* If this call resolves to an `applyDynamic` method that takes a `ClassTag[?]*` as second
* If this call resolves to an `applyDynamic` method that takes a `Class[?]*` as second
* parameter, we further rewrite this call to
* scala```
* x1.applyDynamic("a", CT11, ..., CT1n, ..., CTN1, ... CTNn)
* x1.applyDynamic("a", c11, ..., c1n, ..., cN1, ... cNn)
* (a11, ..., a1n, ..., aN1, ..., aNn)
* .asInstanceOf[R]
* ```
* where CT11, ..., CTNn are the class tags representing the erasure of T11, ..., TNn.
* where c11, ..., cNn are the classOf constants representing the erasures of T11, ..., TNn.
*
* It's an error if U is neither a value nor a method type, or a dependent method
* type.
*/
def handleStructural(tree: Tree)(using Context): Tree = {
val (fun @ Select(qual, name), targs, vargss) = decomposeCall(tree)

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

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

// If function is an `applyDynamic` that takes a ClassTag* parameter,
// add `ctags`.
def addClassTags(tree: Tree): Tree = tree match
// If function is an `applyDynamic` that takes a Class* parameter,
// add `classOfs`.
def addClassOfs(tree: Tree): Tree = tree match
case Apply(fn: Apply, args) =>
cpy.Apply(tree)(addClassTags(fn), args)
cpy.Apply(tree)(addClassOfs(fn), args)
case Apply(fn @ Select(_, nme.applyDynamic), nameArg :: _ :: Nil) =>
fn.tpe.widen match
case mt: MethodType => mt.paramInfos match
case _ :: ctagsParam :: Nil
if ctagsParam.isRepeatedParam
&& ctagsParam.argInfos.head.isRef(defn.ClassTagClass) =>
val ctagType = defn.ClassTagClass.typeRef.appliedTo(TypeBounds.empty)
case _ :: classOfsParam :: Nil
if classOfsParam.isRepeatedParam
&& classOfsParam.argInfos.head.isRef(defn.ClassClass) =>
val jlClassType = defn.ClassClass.typeRef.appliedTo(TypeBounds.empty)
cpy.Apply(tree)(fn,
nameArg :: seqToRepeated(SeqLiteral(ctags, TypeTree(ctagType))) :: Nil)
nameArg :: seqToRepeated(SeqLiteral(classOfs, TypeTree(jlClassType))) :: Nil)
case _ => tree
case other => tree
case _ => tree
addClassTags(typed(scall))
addClassOfs(typed(scall))
}

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

fun.tpe.widen match {
Expand All @@ -215,18 +216,21 @@ trait Dynamic {
}

if (isDependentMethod(tpe))
fail(name, i"has a method type with inter-parameter dependencies")
fail(i"has a method type with inter-parameter dependencies")
else {
def ctags = tpe.paramInfoss.flatten.map(pt =>
implicitArgTree(defn.ClassTagClass.typeRef.appliedTo(pt.widenDealias :: Nil), fun.span.endPos))
structuralCall(nme.applyDynamic, ctags).cast(tpe.finalResultType)
def classOfs =
if tpe.paramInfoss.nestedExists(!TypeErasure.hasStableErasure(_)) then
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)
}

// (@allanrenucci) I think everything below is dead code
case _: PolyType =>
fail(name, "is polymorphic")
fail("is polymorphic")
case tpe =>
fail(name, i"has an unsupported type: $tpe")
fail(i"has an unsupported type: $tpe")
}
}
}
16 changes: 10 additions & 6 deletions docs/docs/reference/changed-features/structural-types-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,17 @@ def selectDynamic(name: String): T
```
Often, the return type `T` is `Any`.

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.
Unlike `scala.Dynamic`, there is no special meaning for an `updateDynamic` method.
However, we reserve the right to give it meaning in the future.
Consequently, it is recommended not to define any member called `updateDynamic` in `Selectable`s.

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.
Its signature should be of one of the two following forms:
```scala
def applyDynamic(name: String)(args: Any*): T
def applyDynamic(name: String, ctags: ClassTag[?]*)(args: Any*): T
def applyDynamic(name: String, ctags: Class[?]*)(args: Any*): T
```
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
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
if `applyDynamic` is implemented using Java reflection, but it could be
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.

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

- If `U` is neither a value nor a method type, or a dependent method
type, an error is emitted.
Expand Down
6 changes: 3 additions & 3 deletions docs/docs/reference/changed-features/structural-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ differences.
`Selectable` is a trait which declares the access operations.

- Two access operations, `selectDynamic` and `applyDynamic` are shared
between both approaches. In `Selectable`, `applyDynamic` also takes
`ClassTag` indicating the method's formal parameter types. `Dynamic`
comes with `updateDynamic`.
between both approaches. In `Selectable`, `applyDynamic` also may also take
`java.lang.Class` arguments indicating the method's formal parameter types.
`Dynamic` comes with `updateDynamic`.

[More details](structural-types-spec.md)
9 changes: 5 additions & 4 deletions library/src/scala/Selectable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ package scala
* `applyDynamic` is invoked for selections with arguments `v.m(...)`.
* If there's only one kind of selection, the method supporting the
* other may be omitted. The `applyDynamic` can also have a second parameter
* list of class tag arguments, i.e. it may alternatively have the signature
* list of `java.lang.Class` arguments, i.e. it may alternatively have the
* signature
*
* def applyDynamic(name: String, paramClasses: ClassTag[_]*)(args: Any*): Any
* def applyDynamic(name: String, paramClasses: Class[_]*)(args: Any*): Any
*
* In this case the call will synthesize `ClassTag` arguments for all formal parameter
* types of the method in the structural type.
* In this case the call will synthesize `Class` arguments for the erasure of
* all formal parameter types of the method in the structural type.
*/
trait Selectable extends Any

Expand Down
5 changes: 2 additions & 3 deletions library/src/scala/reflect/Selectable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,9 @@ trait Selectable extends scala.Selectable:
* @param paramTypes The class tags of the selected method's formal parameter types
* @param args The arguments to pass to the selected method
*/
def applyDynamic(name: String, paramTypes: ClassTag[_]*)(args: Any*): Any =
def applyDynamic(name: String, paramTypes: Class[_]*)(args: Any*): Any =
val rcls = selectedValue.getClass
val paramClasses = paramTypes.map(_.runtimeClass)
val mth = rcls.getMethod(name, paramClasses: _*)
val mth = rcls.getMethod(name, paramTypes: _*)
ensureAccessible(mth)
mth.invoke(selectedValue, args.asInstanceOf[Seq[AnyRef]]: _*)

Expand Down
16 changes: 8 additions & 8 deletions tests/neg-scalajs/reflective-calls.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ object Test {
val receiver: Any = ???
reflectiveSelectable(receiver).selectDynamic("foo") // OK
reflectiveSelectable(receiver).applyDynamic("foo")() // OK
reflectiveSelectable(receiver).applyDynamic("foo", ClassTag(classOf[String]), ClassTag(classOf[List[_]]))("bar", Nil) // OK
reflectiveSelectable(receiver).applyDynamic("foo", classOf[String], classOf[List[_]])("bar", Nil) // OK
}

def badReceider(): Unit = {
Expand All @@ -25,21 +25,21 @@ object Test {
reflectiveSelectable(receiver).applyDynamic(methodName)() // error
}

def nonLiteralClassTag(): Unit = {
def nonLiteralClassOf(): Unit = {
val receiver: Any = ???
val myClassTag: ClassTag[String] = ClassTag(classOf[String])
reflectiveSelectable(receiver).applyDynamic("foo", myClassTag, ClassTag(classOf[List[_]]))("bar", Nil) // error
val myClassOf: Class[String] = classOf[String]
reflectiveSelectable(receiver).applyDynamic("foo", myClassOf, classOf[List[_]])("bar", Nil) // error
}

def classTagVarArgs(): Unit = {
def classOfVarArgs(): Unit = {
val receiver: Any = ???
val classTags: List[ClassTag[_]] = List(ClassTag(classOf[String]), ClassTag(classOf[List[_]]))
reflectiveSelectable(receiver).applyDynamic("foo", classTags: _*)("bar", Nil) // error
val classOfs: List[Class[_]] = List(classOf[String], classOf[List[_]])
reflectiveSelectable(receiver).applyDynamic("foo", classOfs: _*)("bar", Nil) // error
}

def argsVarArgs(): Unit = {
val receiver: Any = ???
val args: List[Any] = List("bar", Nil)
reflectiveSelectable(receiver).applyDynamic("foo", ClassTag(classOf[String]), ClassTag(classOf[List[_]]))(args: _*) // error
reflectiveSelectable(receiver).applyDynamic("foo", classOf[String], classOf[List[_]])(args: _*) // error
}
}
2 changes: 1 addition & 1 deletion tests/neg/structural.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
object Test3 {
import scala.reflect.Selectable.reflectiveSelectable
def g(x: { type T ; def t: T ; def f(a: T): Boolean }) = x.f(x.t) // error: no ClassTag for x.T
def g(x: { type T ; def t: T ; def f(a: T): Boolean }) = x.f(x.t) // error: it has a parameter type with an unstable erasure
g(new { type T = Int; def t = 4; def f(a:T) = true })
g(new { type T = Any; def t = 4; def f(a:T) = true })
val y: { type T = Int; def t = 4; def f(a:T) = true } // error: illegal refinement // error: illegal refinement
Expand Down
2 changes: 1 addition & 1 deletion tests/semanticdb/expect/Advanced.expect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ object Test/*<-advanced::Test.*/ {
val s2/*<-advanced::Test.s2.*/ = s/*->advanced::Test.s.*/.s2/*->advanced::Structural#s2().*/
val s2x/*<-advanced::Test.s2x.*/ = /*->scala::reflect::Selectable.reflectiveSelectable().*/s/*->advanced::Test.s.*/.s2/*->advanced::Structural#s2().*//*->scala::reflect::Selectable#selectDynamic().*/.x
val s3/*<-advanced::Test.s3.*/ = s/*->advanced::Test.s.*/.s3/*->advanced::Structural#s3().*/
val s3x/*<-advanced::Test.s3x.*/ = /*->scala::reflect::Selectable.reflectiveSelectable().*/s/*->advanced::Test.s.*/.s3/*->advanced::Structural#s3().*//*->scala::reflect::Selectable#applyDynamic().*/.m/*->scala::reflect::ClassTag.apply().*/(???/*->scala::Predef.`???`().*/)
val s3x/*<-advanced::Test.s3x.*/ = /*->scala::reflect::Selectable.reflectiveSelectable().*/s/*->advanced::Test.s.*/.s3/*->advanced::Structural#s3().*//*->scala::reflect::Selectable#applyDynamic().*/.m(???/*->scala::Predef.`???`().*/)

val e/*<-advanced::Test.e.*/ = new Wildcards/*->advanced::Wildcards#*/
val e1/*<-advanced::Test.e1.*/ = e/*->advanced::Test.e.*/.e1/*->advanced::Wildcards#e1().*/
Expand Down
Loading