Skip to content

Commit

Permalink
Scala.js: Implement the @EnableReflectiveInstantiation treatment.
Browse files Browse the repository at this point in the history
  • Loading branch information
sjrd committed May 2, 2019
1 parent f79679f commit d51356b
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 1 deletion.
120 changes: 119 additions & 1 deletion compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ class JSCodeGen()(implicit ctx: Context) {
private val thisLocalVarIdent = new ScopedVar[Option[js.Ident]]
private val undefinedDefaultParams = new ScopedVar[mutable.Set[Symbol]]

private def withNewLocalNameScope[A](body: => A): A = {
withScopedVars(localNames := new LocalNameGenerator) {
body
}
}

/** Implicitly materializes the current local name generator. */
private implicit def implicitLocalNames: LocalNameGenerator = localNames.get

Expand All @@ -86,6 +92,10 @@ class JSCodeGen()(implicit ctx: Context) {
private def freshLocalIdent(base: String)(implicit pos: Position): js.Ident =
localNames.get.freshLocalIdent(base)

/** Returns a new fresh local identifier. */
private def freshLocalIdent(base: TermName)(implicit pos: Position): js.Ident =
localNames.get.freshLocalIdent(base)

// Compilation unit --------------------------------------------------------

def run(): Unit = {
Expand Down Expand Up @@ -287,9 +297,31 @@ class JSCodeGen()(implicit ctx: Context) {
Nil
}

// Static initializer
val optStaticInitializer = {
// Initialization of reflection data, if required
val reflectInit = {
val enableReflectiveInstantiation = {
sym.baseClasses.exists { ancestor =>
ancestor.hasAnnotation(jsdefn.EnableReflectiveInstantiationAnnot)
}
}
if (enableReflectiveInstantiation)
genRegisterReflectiveInstantiation(sym)
else
None
}

val staticInitializerStats = reflectInit.toList
if (staticInitializerStats.nonEmpty)
Some(genStaticInitializerWithStats(js.Block(staticInitializerStats)))
else
None
}

// Hashed definitions of the class
val hashedDefs =
ir.Hashers.hashMemberDefs(generatedMembers ++ exports)
ir.Hashers.hashMemberDefs(generatedMembers ++ exports ++ optStaticInitializer)

// The complete class definition
val kind =
Expand Down Expand Up @@ -461,6 +493,92 @@ class JSCodeGen()(implicit ctx: Context) {
}).toList
}

// Static initializers -----------------------------------------------------

private def genStaticInitializerWithStats(stats: js.Tree)(
implicit pos: Position): js.MethodDef = {
js.MethodDef(
js.MemberFlags.empty.withNamespace(js.MemberNamespace.StaticConstructor),
js.Ident(ir.Definitions.StaticInitializerName),
Nil,
jstpe.NoType,
Some(stats))(
OptimizerHints.empty, None)
}

private def genRegisterReflectiveInstantiation(sym: Symbol)(
implicit pos: Position): Option[js.Tree] = {
if (isStaticModule(sym))
genRegisterReflectiveInstantiationForModuleClass(sym)
else if (sym.is(ModuleClass))
None // scala-js#3228
else if (sym.is(Lifted) && !sym.originalOwner.isClass)
None // scala-js#3227
else
genRegisterReflectiveInstantiationForNormalClass(sym)
}

private def genRegisterReflectiveInstantiationForModuleClass(sym: Symbol)(
implicit pos: Position): Option[js.Tree] = {
val fqcnArg = js.StringLiteral(sym.fullName.toString)
val runtimeClassArg = js.ClassOf(toTypeRef(sym.info))
val loadModuleFunArg =
js.Closure(arrow = true, Nil, Nil, genLoadModule(sym), Nil)

val stat = genApplyMethod(
genLoadModule(jsdefn.ReflectModule),
jsdefn.Reflect_registerLoadableModuleClass,
List(fqcnArg, runtimeClassArg, loadModuleFunArg))

Some(stat)
}

private def genRegisterReflectiveInstantiationForNormalClass(sym: Symbol)(
implicit pos: Position): Option[js.Tree] = {
val ctors =
if (sym.is(Abstract)) Nil
else sym.info.member(nme.CONSTRUCTOR).alternatives.map(_.symbol).filter(m => !m.is(Private | Protected))

if (ctors.isEmpty) {
None
} else {
val constructorsInfos = for {
ctor <- ctors
} yield {
withNewLocalNameScope {
val (parameterTypes, formalParams, actualParams) = (for {
(paramName, paramInfo) <- ctor.info.paramNamess.flatten.zip(ctor.info.paramInfoss.flatten)
} yield {
val paramType = js.ClassOf(toTypeRef(paramInfo))
val paramDef = js.ParamDef(freshLocalIdent(paramName), jstpe.AnyType,
mutable = false, rest = false)
val actualParam = unbox(paramDef.ref, paramInfo)
(paramType, paramDef, actualParam)
}).unzip3

val paramTypesArray = js.JSArrayConstr(parameterTypes)

val newInstanceFun = js.Closure(arrow = true, Nil, formalParams, {
js.New(encodeClassRef(sym), encodeMethodSym(ctor), actualParams)
}, Nil)

js.JSArrayConstr(List(paramTypesArray, newInstanceFun))
}
}

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

val stat = genApplyMethod(
genLoadModule(jsdefn.ReflectModule),
jsdefn.Reflect_registerInstantiatableClass,
List(fqcnArg, runtimeClassArg, ctorsInfosArg))

Some(stat)
}
}

// Generate a method -------------------------------------------------------

private def genMethod(dd: DefDef): Option[js.MethodDef] = {
Expand Down
10 changes: 10 additions & 0 deletions compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,16 @@ final class JSDefinitions()(implicit ctx: Context) {
lazy val BoxesRunTime_unboxToCharR = defn.BoxesRunTimeModule.requiredMethodRef("unboxToChar")
def BoxesRunTime_unboxToChar(implicit ctx: Context): Symbol = BoxesRunTime_unboxToCharR.symbol

lazy val EnableReflectiveInstantiationAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.reflect.annotation.EnableReflectiveInstantiation")
def EnableReflectiveInstantiationAnnot(implicit ctx: Context) = EnableReflectiveInstantiationAnnotType.symbol.asClass

lazy val ReflectModuleRef = ctx.requiredModuleRef("scala.scalajs.reflect.Reflect")
def ReflectModule(implicit ctx: Context) = ReflectModuleRef.symbol
lazy val Reflect_registerLoadableModuleClassR = ReflectModule.requiredMethodRef("registerLoadableModuleClass")
def Reflect_registerLoadableModuleClass(implicit ctx: Context) = Reflect_registerLoadableModuleClassR.symbol
lazy val Reflect_registerInstantiatableClassR = ReflectModule.requiredMethodRef("registerInstantiatableClass")
def Reflect_registerInstantiatableClass(implicit ctx: Context) = Reflect_registerInstantiatableClassR.symbol

/** If `cls` is a class in the scala package, its name, otherwise EmptyTypeName */
private def scalajsClassName(cls: Symbol)(implicit ctx: Context): TypeName =
if (cls.isClass && cls.owner == ScalaJSJSPackageClass) cls.asClass.name
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/backend/sjs/JSEncoding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ object JSEncoding {
def freshLocalIdent(base: String)(implicit pos: ir.Position): js.Ident =
js.Ident(freshName(base), Some(base))

def freshLocalIdent(base: TermName)(implicit pos: ir.Position): js.Ident =
js.Ident(freshName(base.toString), Some(base.unexpandedName.toString))

private def freshName(base: String = "x"): String = {
var suffix = 1
var longName = base
Expand Down

0 comments on commit d51356b

Please sign in to comment.