Skip to content

Introduce a Scala-agnostic API for Reflect. #5183

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
136 changes: 112 additions & 24 deletions compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1417,13 +1417,18 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
implicit pos: Position): Option[js.Tree] = {
val fqcnArg = js.StringLiteral(sym.fullName + "$")
val runtimeClassArg = js.ClassOf(toTypeRef(sym.info))
val loadModuleFunArg =
js.Closure(js.ClosureFlags.arrow, Nil, Nil, None, jstpe.AnyType, genLoadModule(sym), Nil)

val stat = genApplyMethod(
genLoadModule(ReflectModule),
Reflect_registerLoadableModuleClass,
List(fqcnArg, runtimeClassArg, loadModuleFunArg))
val loadModuleFunArg = js.NewLambda(
JSupplierClassDescriptor,
js.Closure(js.ClosureFlags.typed, Nil, Nil, None, jstpe.AnyType, genLoadModule(sym), Nil)
)(jstpe.ClassType(JSupplierClassName, nullable = false))

val stat = js.ApplyStatic(
js.ApplyFlags.empty,
JavalibIntfReflectClassName,
js.MethodIdent(Reflect_registerLoadableModuleClassMethodName),
List(fqcnArg, runtimeClassArg, loadModuleFunArg)
)(jstpe.VoidType)

Some(stat)
}
Expand All @@ -1440,9 +1445,22 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val constructorsInfos = for {
ctor <- ctors
} yield {
withNewLocalNameScope {
val (parameterTypes, formalParams, actualParams) = (for {
param <- ctor.tpe.params
val paramTypesArray = js.ArrayValue(ClassArrayRef,
ctor.tpe.params.map(p => js.ClassOf(toTypeRef(p.tpe))))

val newInstanceClosure = {
// param args: Object
val argsParamDef = js.ParamDef(js.LocalIdent(LocalName("args")),
NoOriginalName, jstpe.AnyType, mutable = false)

// val argsArray: Object[] = args.asInstanceOf[Object[]]
val argsArrayVarDef = js.VarDef(js.LocalIdent(LocalName("argsArray")),
NoOriginalName, ObjectArrayType, mutable = false,
js.AsInstanceOf(argsParamDef.ref, ObjectArrayType))

// argsArray[i].asInstanceOf[Ti] for every parameter of the constructor
val actualParams = for {
(param, index) <- ctor.tpe.params.zipWithIndex
} yield {
/* Note that we do *not* use `param.tpe` entering posterasure
* (neither to compute `paramType` nor to give to `fromAny`).
Expand All @@ -1460,29 +1478,38 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* parameter types is `List(classOf[Int])`, and when invoked
* reflectively, it must be given an `Int` (or `Integer`).
*/
val paramType = js.ClassOf(toTypeRef(param.tpe))
val paramDef = genParamDef(param, jstpe.AnyType)
val actualParam = fromAny(paramDef.ref, param.tpe)
(paramType, paramDef, actualParam)
}).unzip3
fromAny(
js.ArraySelect(argsArrayVarDef.ref, js.IntLiteral(index))(jstpe.AnyType),
param.tpe)
}

val paramTypesArray = js.JSArrayConstr(parameterTypes)
/* typed-lambda<>(args: Object): any = {
* val argsArray: Object[] = args.asInstanceOf[Object[]]
* new MyClass(...argsArray[i].asInstanceOf[Ti])
* }
*/
js.Closure(js.ClosureFlags.typed, Nil, argsParamDef :: Nil, None, jstpe.AnyType, {
js.Block(argsArrayVarDef, genNew(sym, ctor, actualParams))
}, Nil)
}

val newInstanceFun = js.Closure(js.ClosureFlags.arrow, Nil,
formalParams, None, jstpe.AnyType, genNew(sym, ctor, actualParams), Nil)
val newInstanceFun = js.NewLambda(JFunctionClassDescriptor, newInstanceClosure)(
jstpe.ClassType(JFunctionClassName, nullable = false))

js.JSArrayConstr(List(paramTypesArray, newInstanceFun))
}
js.New(SimpleMapEntryClassName, js.MethodIdent(TwoObjectArgsConstructorName),
List(paramTypesArray, newInstanceFun))
}

val fqcnArg = js.StringLiteral(sym.fullName)
val runtimeClassArg = js.ClassOf(toTypeRef(sym.info))
val ctorsInfosArg = js.JSArrayConstr(constructorsInfos)
val ctorsInfosArg = js.ArrayValue(MapEntryArrayRef, constructorsInfos)

val stat = genApplyMethod(
genLoadModule(ReflectModule),
Reflect_registerInstantiatableClass,
List(fqcnArg, runtimeClassArg, ctorsInfosArg))
val stat = js.ApplyStatic(
js.ApplyFlags.empty,
JavalibIntfReflectClassName,
js.MethodIdent(Reflect_registerInstantiatableClassMethodName),
List(fqcnArg, runtimeClassArg, ctorsInfosArg)
)(jstpe.VoidType)

Some(stat)
}
Expand Down Expand Up @@ -7336,10 +7363,71 @@ private object GenJSCode {
private val JSObjectClassName = ClassName("scala.scalajs.js.Object")
private val JavaScriptExceptionClassName = ClassName("scala.scalajs.js.JavaScriptException")

private val JavalibIntfReflectClassName = ClassName("org.scalajs.javalibintf.Reflect")

private val JFunctionClassName = ClassName("java.util.function.Function")
private val JSupplierClassName = ClassName("java.util.function.Supplier")
private val MapEntryClassName = ClassName("java.util.Map$Entry")
private val SimpleMapEntryClassName = ClassName("java.util.AbstractMap$SimpleImmutableEntry")

private val ObjectArrayType =
jstpe.ArrayType(jstpe.ArrayTypeRef(jswkn.ObjectRef, 1), nullable = true)
private val ClassArrayRef =
jstpe.ArrayTypeRef(jstpe.ClassRef(jswkn.ClassClass), 1)

private val MapEntryArrayRef =
jstpe.ArrayTypeRef(jstpe.ClassRef(MapEntryClassName), 1)

private val newSimpleMethodName = SimpleMethodName("new")

private val Reflect_registerLoadableModuleClassMethodName = {
MethodName(
"registerLoadableModuleClass",
List(
jstpe.ClassRef(jswkn.BoxedStringClass),
jstpe.ClassRef(jswkn.ClassClass),
jstpe.ClassRef(JSupplierClassName)
),
jstpe.VoidRef
)
}

private val Reflect_registerInstantiatableClassMethodName = {
MethodName(
"registerInstantiatableClass",
List(
jstpe.ClassRef(jswkn.BoxedStringClass),
jstpe.ClassRef(jswkn.ClassClass),
MapEntryArrayRef
),
jstpe.VoidRef
)
}

private val ObjectArgConstructorName =
MethodName.constructor(List(jswkn.ObjectRef))
private val TwoObjectArgsConstructorName =
MethodName.constructor(List(jswkn.ObjectRef, jswkn.ObjectRef))

private val JSupplierClassDescriptor = {
js.NewLambda.Descriptor(
jswkn.ObjectClass,
List(JSupplierClassName),
MethodName("get", Nil, jswkn.ObjectRef),
Nil,
jstpe.AnyType
)
}

private val JFunctionClassDescriptor = {
js.NewLambda.Descriptor(
jswkn.ObjectClass,
List(JFunctionClassName),
MethodName("apply", List(jswkn.ObjectRef), jswkn.ObjectRef),
List(jstpe.AnyType),
jstpe.AnyType
)
}

private val thisOriginalName = OriginalName("this")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,6 @@ trait JSDefinitions {
// (rather than definitions) and we weren't sure if it is safe to make this a lazy val
def ScalaRunTime_isArray: Symbol = getMemberMethod(ScalaRunTimeModule, newTermName("isArray")).suchThat(_.tpe.params.size == 2)

lazy val ReflectModule = getRequiredModule("scala.scalajs.reflect.Reflect")
lazy val Reflect_registerLoadableModuleClass = getMemberMethod(ReflectModule, newTermName("registerLoadableModuleClass"))
lazy val Reflect_registerInstantiatableClass = getMemberMethod(ReflectModule, newTermName("registerInstantiatableClass"))

lazy val EnableReflectiveInstantiationAnnotation = getRequiredClass("scala.scalajs.reflect.annotation.EnableReflectiveInstantiation")

lazy val ExecutionContextModule = getRequiredModule("scala.concurrent.ExecutionContext")
Expand Down
109 changes: 109 additions & 0 deletions javalib/src/main/scala/org/scalajs/javalibintf/Reflect.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Scala.js (https://www.scala-js.org/)
*
* Copyright EPFL.
*
* Licensed under Apache License 2.0
* (https://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/

package org.scalajs.javalibintf

import java.util.Map.Entry
import java.util.Optional
import java.util.function.{Function, Supplier}

object Reflect {
private val loadableModuleClasses =
new java.util.HashMap[String, LoadableModuleClass[_]]()

private val instantiatableClasses =
new java.util.HashMap[String, InstantiatableClass[_]]()

// Public API (documented in javalibintf/.../Reflect.java)

def registerLoadableModuleClass[T](fqcn: String, runtimeClass: Class[T],
moduleSupplier: Supplier[T]): Unit = {
loadableModuleClasses.put(fqcn,
new LoadableModuleClassImpl(runtimeClass, moduleSupplier))
}

def registerInstantiatableClass[T](fqcn: String, runtimeClass: Class[T],
constructors: Array[Entry[Array[Class[_]], Function[Array[Object], T]]]): Unit = {
val ctorLen = constructors.length
val invokableConstructors = new Array[InvokableConstructor[T]](ctorLen)
var i = 0
while (i != ctorLen) {
val entry = constructors(i)
invokableConstructors(i) =
new InvokableConstructorImpl(entry.getKey().clone(), entry.getValue())
i += 1
}
instantiatableClasses.put(fqcn,
new InstantiatableClassImpl(runtimeClass, invokableConstructors))
}

def lookupLoadableModuleClass(fqcn: String): Optional[LoadableModuleClass[_]] =
Optional.ofNullable(loadableModuleClasses.get(fqcn))

def lookupInstantiatableClass(fqcn: String): Optional[InstantiatableClass[_]] =
Optional.ofNullable(instantiatableClasses.get(fqcn))

trait LoadableModuleClass[T] {
def getRuntimeClass(): Class[T]

def loadModule(): T
}

trait InstantiatableClass[T] {
def getRuntimeClass(): Class[T]

def getDeclaredConstructors(): Array[InvokableConstructor[T]]
}

trait InvokableConstructor[T] {
def getParameterTypes(): Array[Class[_]]

def newInstance(args: Array[Object]): T
}

// Private implementation

private final class LoadableModuleClassImpl[T](
runtimeClass: Class[T],
moduleSupplier: Supplier[T]
) extends LoadableModuleClass[T] {
def getRuntimeClass(): Class[T] = runtimeClass

def loadModule(): T = moduleSupplier.get()
}

private final class InstantiatableClassImpl[T](
runtimeClass: Class[T],
declaredConstructors: Array[InvokableConstructor[T]]
) extends InstantiatableClass[T] {
def getRuntimeClass(): Class[T] = runtimeClass

def getDeclaredConstructors(): Array[InvokableConstructor[T]] =
declaredConstructors.clone()
}

private final class InvokableConstructorImpl[T](
parameterTypes: Array[Class[_]],
newInstanceFun: Function[Array[Object], T]
) extends InvokableConstructor[T] {
def getParameterTypes(): Array[Class[_]] = parameterTypes.clone()

def newInstance(args: Array[Object]): T = {
/* Check the number of actual arguments. We let the casts and unbox
* operations inside `newInstanceFun` take care of the rest.
*/
if (args.length != parameterTypes.length)
throw new IllegalArgumentException(s"Argument count mismatch: ${args.length}")
newInstanceFun.apply(args)
}
}
}
Loading