Skip to content

Commit 90172ea

Browse files
committed
Scala.js: Implement the @EnableReflectiveInstantiation treatment.
1 parent e7708c6 commit 90172ea

File tree

3 files changed

+133
-1
lines changed

3 files changed

+133
-1
lines changed

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

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ class JSCodeGen()(implicit ctx: Context) {
6868
private val thisLocalVarIdent = new ScopedVar[Option[js.Ident]]
6969
private val undefinedDefaultParams = new ScopedVar[mutable.Set[Symbol]]
7070

71+
private def withNewLocalNameScope[A](body: => A): A = {
72+
withScopedVars(localNames := new LocalNameGenerator) {
73+
body
74+
}
75+
}
76+
7177
/** Implicitly materializes the current local name generator. */
7278
private implicit def implicitLocalNames: LocalNameGenerator = localNames.get
7379

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

95+
/** Returns a new fresh local identifier. */
96+
private def freshLocalIdent(base: TermName)(implicit pos: Position): js.Ident =
97+
localNames.get.freshLocalIdent(base)
98+
8999
// Compilation unit --------------------------------------------------------
90100

91101
def run(): Unit = {
@@ -287,9 +297,31 @@ class JSCodeGen()(implicit ctx: Context) {
287297
Nil
288298
}
289299

300+
// Static initializer
301+
val optStaticInitializer = {
302+
// Initialization of reflection data, if required
303+
val reflectInit = {
304+
val enableReflectiveInstantiation = {
305+
sym.baseClasses.exists { ancestor =>
306+
ancestor.hasAnnotation(jsdefn.EnableReflectiveInstantiationAnnot)
307+
}
308+
}
309+
if (enableReflectiveInstantiation)
310+
genRegisterReflectiveInstantiation(sym)
311+
else
312+
None
313+
}
314+
315+
val staticInitializerStats = reflectInit.toList
316+
if (staticInitializerStats.nonEmpty)
317+
Some(genStaticInitializerWithStats(js.Block(staticInitializerStats)))
318+
else
319+
None
320+
}
321+
290322
// Hashed definitions of the class
291323
val hashedDefs =
292-
ir.Hashers.hashMemberDefs(generatedMembers ++ exports)
324+
ir.Hashers.hashMemberDefs(generatedMembers ++ exports ++ optStaticInitializer)
293325

294326
// The complete class definition
295327
val kind =
@@ -461,6 +493,93 @@ class JSCodeGen()(implicit ctx: Context) {
461493
}).toList
462494
}
463495

496+
// Static initializers -----------------------------------------------------
497+
498+
private def genStaticInitializerWithStats(stats: js.Tree)(
499+
implicit pos: Position): js.MethodDef = {
500+
js.MethodDef(
501+
js.MemberFlags.empty.withNamespace(js.MemberNamespace.StaticConstructor),
502+
js.Ident(ir.Definitions.StaticInitializerName),
503+
Nil,
504+
jstpe.NoType,
505+
Some(stats))(
506+
OptimizerHints.empty, None)
507+
}
508+
509+
private def genRegisterReflectiveInstantiation(sym: Symbol)(
510+
implicit pos: Position): Option[js.Tree] = {
511+
if (isStaticModule(sym))
512+
genRegisterReflectiveInstantiationForModuleClass(sym)
513+
else if (sym.is(ModuleClass))
514+
None // scala-js#3228
515+
// TODO What is `originalOwner` in dotc?
516+
//else if (sym.is(Lifted) && !sym.originalOwner.isClass)
517+
// None // scala-js#3227
518+
else
519+
genRegisterReflectiveInstantiationForNormalClass(sym)
520+
}
521+
522+
private def genRegisterReflectiveInstantiationForModuleClass(sym: Symbol)(
523+
implicit pos: Position): Option[js.Tree] = {
524+
val fqcnArg = js.StringLiteral(sym.fullName.toString)
525+
val runtimeClassArg = js.ClassOf(toTypeRef(sym.info))
526+
val loadModuleFunArg =
527+
js.Closure(arrow = true, Nil, Nil, genLoadModule(sym), Nil)
528+
529+
val stat = genApplyMethod(
530+
genLoadModule(jsdefn.ReflectModule),
531+
jsdefn.Reflect_registerLoadableModuleClass,
532+
List(fqcnArg, runtimeClassArg, loadModuleFunArg))
533+
534+
Some(stat)
535+
}
536+
537+
private def genRegisterReflectiveInstantiationForNormalClass(sym: Symbol)(
538+
implicit pos: Position): Option[js.Tree] = {
539+
val ctors =
540+
if (sym.is(Abstract)) Nil
541+
else sym.info.member(nme.CONSTRUCTOR).alternatives.map(_.symbol).filter(m => !m.is(Private | Protected))
542+
543+
if (ctors.isEmpty) {
544+
None
545+
} else {
546+
val constructorsInfos = for {
547+
ctor <- ctors
548+
} yield {
549+
withNewLocalNameScope {
550+
val (parameterTypes, formalParams, actualParams) = (for {
551+
(paramName, paramInfo) <- ctor.info.paramNamess.flatten.zip(ctor.info.paramInfoss.flatten)
552+
} yield {
553+
val paramType = js.ClassOf(toTypeRef(paramInfo))
554+
val paramDef = js.ParamDef(freshLocalIdent(paramName), jstpe.AnyType,
555+
mutable = false, rest = false)
556+
val actualParam = unbox(paramDef.ref, paramInfo)
557+
(paramType, paramDef, actualParam)
558+
}).unzip3
559+
560+
val paramTypesArray = js.JSArrayConstr(parameterTypes)
561+
562+
val newInstanceFun = js.Closure(arrow = true, Nil, formalParams, {
563+
js.New(encodeClassRef(sym), encodeMethodSym(ctor), actualParams)
564+
}, Nil)
565+
566+
js.JSArrayConstr(List(paramTypesArray, newInstanceFun))
567+
}
568+
}
569+
570+
val fqcnArg = js.StringLiteral(sym.fullName.toString)
571+
val runtimeClassArg = js.ClassOf(toTypeRef(sym.info))
572+
val ctorsInfosArg = js.JSArrayConstr(constructorsInfos)
573+
574+
val stat = genApplyMethod(
575+
genLoadModule(jsdefn.ReflectModule),
576+
jsdefn.Reflect_registerInstantiatableClass,
577+
List(fqcnArg, runtimeClassArg, ctorsInfosArg))
578+
579+
Some(stat)
580+
}
581+
}
582+
464583
// Generate a method -------------------------------------------------------
465584

466585
private def genMethod(dd: DefDef): Option[js.MethodDef] = {

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,16 @@ final class JSDefinitions()(implicit ctx: Context) {
158158
lazy val BoxesRunTime_unboxToCharR = defn.BoxesRunTimeModule.requiredMethodRef("unboxToChar")
159159
def BoxesRunTime_unboxToChar(implicit ctx: Context): Symbol = BoxesRunTime_unboxToCharR.symbol
160160

161+
lazy val EnableReflectiveInstantiationAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.reflect.annotation.EnableReflectiveInstantiation")
162+
def EnableReflectiveInstantiationAnnot(implicit ctx: Context) = EnableReflectiveInstantiationAnnotType.symbol.asClass
163+
164+
lazy val ReflectModuleRef = ctx.requiredModuleRef("scala.scalajs.reflect.Reflect")
165+
def ReflectModule(implicit ctx: Context) = ReflectModuleRef.symbol
166+
lazy val Reflect_registerLoadableModuleClassR = ReflectModule.requiredMethodRef("registerLoadableModuleClass")
167+
def Reflect_registerLoadableModuleClass(implicit ctx: Context) = Reflect_registerLoadableModuleClassR.symbol
168+
lazy val Reflect_registerInstantiatableClassR = ReflectModule.requiredMethodRef("registerInstantiatableClass")
169+
def Reflect_registerInstantiatableClass(implicit ctx: Context) = Reflect_registerInstantiatableClassR.symbol
170+
161171
/** If `cls` is a class in the scala package, its name, otherwise EmptyTypeName */
162172
private def scalajsClassName(cls: Symbol)(implicit ctx: Context): TypeName =
163173
if (cls.isClass && cls.owner == ScalaJSJSPackageClass) cls.asClass.name

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ object JSEncoding {
6666
def freshLocalIdent(base: String)(implicit pos: ir.Position): js.Ident =
6767
js.Ident(freshName(base), Some(base))
6868

69+
def freshLocalIdent(base: TermName)(implicit pos: ir.Position): js.Ident =
70+
js.Ident(freshName(base.toString), Some(base.unexpandedName.toString))
71+
6972
private def freshName(base: String = "x"): String = {
7073
var suffix = 1
7174
var longName = base

0 commit comments

Comments
 (0)