Skip to content

Commit e1385a3

Browse files
authored
Merge pull request #6304 from sjrd/scalajs-junit-tests
Emit Scala.js JUnit bootstrappers for JUnit test classes.
2 parents 2869bb3 + e5bd1fe commit e1385a3

14 files changed

+586
-20
lines changed

.drone.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pipeline:
3131
image: lampepfl/dotty:2019-04-22
3232
commands:
3333
- cp -R . /tmp/2/ && cd /tmp/2/
34-
- ./project/scripts/sbt ";dotty-bootstrapped/compile ;dotty-bootstrapped/test; dotty-semanticdb/compile; dotty-semanticdb/test:compile;sjsSandbox/run"
34+
- ./project/scripts/sbt ";dotty-bootstrapped/compile ;dotty-bootstrapped/test; dotty-semanticdb/compile; dotty-semanticdb/test:compile;sjsSandbox/run;sjsSandbox/test"
3535
- ./project/scripts/bootstrapCmdTests
3636

3737
community_build:

compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import interface._
2020
// Here used to be an `assert(!classSym.isDelambdafyFunction)`: delambdafy lambda classes are
2121
// always top-level. However, SI-8900 shows an example where the weak name-based implementation
2222
// of isDelambdafyFunction failed (for a function declared in a package named "lambda").
23-
classSym.isAnonymousClass || (classSym.originalOwner != NoSymbol && !classSym.originalOwner.isClass)
23+
classSym.isAnonymousClass || {
24+
val originalOwnerLexicallyEnclosingClass = classSym.originalOwner.originalLexicallyEnclosingClass
25+
originalOwnerLexicallyEnclosingClass != NoSymbol && !originalOwnerLexicallyEnclosingClass.isClass
26+
}
2427
}
2528

2629
/**
@@ -51,9 +54,9 @@ import interface._
5154
def enclosingMethod(sym: Symbol): Option[Symbol] = {
5255
if (sym.isClass || sym == NoSymbol) None
5356
else if (sym.isMethod) Some(sym)
54-
else enclosingMethod(sym.originalOwner)
57+
else enclosingMethod(sym.originalOwner.originalLexicallyEnclosingClass)
5558
}
56-
enclosingMethod(classSym.originalOwner)
59+
enclosingMethod(classSym.originalOwner.originalLexicallyEnclosingClass)
5760
}
5861

5962
/**
@@ -64,9 +67,9 @@ import interface._
6467
assert(classSym.isClass, classSym)
6568
def enclosingClass(sym: Symbol): Symbol = {
6669
if (sym.isClass) sym
67-
else enclosingClass(sym.originalOwner)
70+
else enclosingClass(sym.originalOwner.originalLexicallyEnclosingClass)
6871
}
69-
enclosingClass(classSym.originalOwner)
72+
enclosingClass(classSym.originalOwner.originalLexicallyEnclosingClass)
7073
}
7174

7275
/*final*/ case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String)

compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ class BTypesFromSymbols[I <: BackendInterface](val int: I) extends BTypes {
147147
if (!isNested) None
148148
else {
149149
// See comment in BTypes, when is a class marked static in the InnerClass table.
150-
val isStaticNestedClass = innerClassSym.originalOwner.isOriginallyStaticOwner
150+
val isStaticNestedClass = innerClassSym.originalOwner.originalLexicallyEnclosingClass.isOriginallyStaticOwner
151151

152152
// After lambdalift (which is where we are), the rawowoner field contains the enclosing class.
153153
val enclosingClassSym = innerClassSym.enclosingClassSym

compiler/src/dotty/tools/backend/jvm/BackendInterface.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ abstract class BackendInterface extends BackendInterfaceDefinitions {
537537
def companionSymbol: Symbol
538538
def moduleClass: Symbol
539539
def enclosingClassSym: Symbol
540+
def originalLexicallyEnclosingClass: Symbol
540541
def nextOverriddenSymbol: Symbol
541542

542543

@@ -584,7 +585,7 @@ abstract class BackendInterface extends BackendInterfaceDefinitions {
584585
* the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here.
585586
*/
586587
def isOriginallyStaticOwner: Boolean =
587-
isPackageClass || isModuleClass && originalOwner.isOriginallyStaticOwner
588+
isPackageClass || isModuleClass && originalOwner.originalLexicallyEnclosingClass.isOriginallyStaticOwner
588589

589590
def samMethod(): Symbol
590591

compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -727,18 +727,9 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
727727
// navigation
728728
def owner: Symbol = toDenot(sym).owner
729729
def rawowner: Symbol = {
730-
originalOwner
730+
originalOwner.originalLexicallyEnclosingClass
731731
}
732-
def originalOwner: Symbol =
733-
// used to populate the EnclosingMethod attribute.
734-
// it is very tricky in presence of classes(and annonymous classes) defined inside supper calls.
735-
if (sym.exists) {
736-
val original = toDenot(sym).initial
737-
val validity = original.validFor
738-
val shiftedContext = ctx.withPhase(validity.phaseId)
739-
val r = toDenot(sym)(shiftedContext).maybeOwner.lexicallyEnclosingClass(shiftedContext)
740-
r
741-
} else NoSymbol
732+
def originalOwner: Symbol = toDenot(sym).originalOwner
742733
def parentSymbols: List[Symbol] = toDenot(sym).info.parents.map(_.typeSymbol)
743734
def superClass: Symbol = {
744735
val t = toDenot(sym).asClass.superClass
@@ -765,6 +756,14 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
765756
}
766757
else sym.enclosingClass(ctx.withPhase(ctx.flattenPhase.prev))
767758
} //todo is handled specially for JavaDefined symbols in scalac
759+
def originalLexicallyEnclosingClass: Symbol =
760+
// used to populate the EnclosingMethod attribute.
761+
// it is very tricky in presence of classes(and annonymous classes) defined inside supper calls.
762+
if (sym.exists) {
763+
val validity = toDenot(sym).initial.validFor
764+
val shiftedContext = ctx.withPhase(validity.phaseId)
765+
toDenot(sym)(shiftedContext).lexicallyEnclosingClass(shiftedContext)
766+
} else NoSymbol
768767
def nextOverriddenSymbol: Symbol = toDenot(sym).nextOverriddenSymbol
769768

770769
// members

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

Lines changed: 119 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,92 @@ 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+
else if (sym.is(Lifted) && !sym.originalOwner.isClass)
516+
None // scala-js#3227
517+
else
518+
genRegisterReflectiveInstantiationForNormalClass(sym)
519+
}
520+
521+
private def genRegisterReflectiveInstantiationForModuleClass(sym: Symbol)(
522+
implicit pos: Position): Option[js.Tree] = {
523+
val fqcnArg = js.StringLiteral(sym.fullName.toString)
524+
val runtimeClassArg = js.ClassOf(toTypeRef(sym.info))
525+
val loadModuleFunArg =
526+
js.Closure(arrow = true, Nil, Nil, genLoadModule(sym), Nil)
527+
528+
val stat = genApplyMethod(
529+
genLoadModule(jsdefn.ReflectModule),
530+
jsdefn.Reflect_registerLoadableModuleClass,
531+
List(fqcnArg, runtimeClassArg, loadModuleFunArg))
532+
533+
Some(stat)
534+
}
535+
536+
private def genRegisterReflectiveInstantiationForNormalClass(sym: Symbol)(
537+
implicit pos: Position): Option[js.Tree] = {
538+
val ctors =
539+
if (sym.is(Abstract)) Nil
540+
else sym.info.member(nme.CONSTRUCTOR).alternatives.map(_.symbol).filter(m => !m.is(Private | Protected))
541+
542+
if (ctors.isEmpty) {
543+
None
544+
} else {
545+
val constructorsInfos = for {
546+
ctor <- ctors
547+
} yield {
548+
withNewLocalNameScope {
549+
val (parameterTypes, formalParams, actualParams) = (for {
550+
(paramName, paramInfo) <- ctor.info.paramNamess.flatten.zip(ctor.info.paramInfoss.flatten)
551+
} yield {
552+
val paramType = js.ClassOf(toTypeRef(paramInfo))
553+
val paramDef = js.ParamDef(freshLocalIdent(paramName), jstpe.AnyType,
554+
mutable = false, rest = false)
555+
val actualParam = unbox(paramDef.ref, paramInfo)
556+
(paramType, paramDef, actualParam)
557+
}).unzip3
558+
559+
val paramTypesArray = js.JSArrayConstr(parameterTypes)
560+
561+
val newInstanceFun = js.Closure(arrow = true, Nil, formalParams, {
562+
js.New(encodeClassRef(sym), encodeMethodSym(ctor), actualParams)
563+
}, Nil)
564+
565+
js.JSArrayConstr(List(paramTypesArray, newInstanceFun))
566+
}
567+
}
568+
569+
val fqcnArg = js.StringLiteral(sym.fullName.toString)
570+
val runtimeClassArg = js.ClassOf(toTypeRef(sym.info))
571+
val ctorsInfosArg = js.JSArrayConstr(constructorsInfos)
572+
573+
val stat = genApplyMethod(
574+
genLoadModule(jsdefn.ReflectModule),
575+
jsdefn.Reflect_registerInstantiatableClass,
576+
List(fqcnArg, runtimeClassArg, ctorsInfosArg))
577+
578+
Some(stat)
579+
}
580+
}
581+
464582
// Generate a method -------------------------------------------------------
465583

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

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

Lines changed: 46 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
@@ -179,4 +189,40 @@ final class JSDefinitions()(implicit ctx: Context) {
179189
def isJSThisFunctionClass(cls: Symbol): Boolean =
180190
isScalaJSVarArityClass(cls, "ThisFunction")
181191

192+
/** Definitions related to the treatment of JUnit boostrappers. */
193+
object junit {
194+
lazy val TestAnnotType: TypeRef = ctx.requiredClassRef("org.junit.Test")
195+
def TestAnnotClass(implicit ctx: Context): ClassSymbol = TestAnnotType.symbol.asClass
196+
197+
lazy val BeforeAnnotType: TypeRef = ctx.requiredClassRef("org.junit.Before")
198+
def BeforeAnnotClass(implicit ctx: Context): ClassSymbol = BeforeAnnotType.symbol.asClass
199+
200+
lazy val AfterAnnotType: TypeRef = ctx.requiredClassRef("org.junit.After")
201+
def AfterAnnotClass(implicit ctx: Context): ClassSymbol = AfterAnnotType.symbol.asClass
202+
203+
lazy val BeforeClassAnnotType: TypeRef = ctx.requiredClassRef("org.junit.BeforeClass")
204+
def BeforeClassAnnotClass(implicit ctx: Context): ClassSymbol = BeforeClassAnnotType.symbol.asClass
205+
206+
lazy val AfterClassAnnotType: TypeRef = ctx.requiredClassRef("org.junit.AfterClass")
207+
def AfterClassAnnotClass(implicit ctx: Context): ClassSymbol = AfterClassAnnotType.symbol.asClass
208+
209+
lazy val IgnoreAnnotType: TypeRef = ctx.requiredClassRef("org.junit.Ignore")
210+
def IgnoreAnnotClass(implicit ctx: Context): ClassSymbol = IgnoreAnnotType.symbol.asClass
211+
212+
lazy val BootstrapperType: TypeRef = ctx.requiredClassRef("org.scalajs.junit.Bootstrapper")
213+
214+
lazy val TestMetadataType: TypeRef = ctx.requiredClassRef("org.scalajs.junit.TestMetadata")
215+
216+
lazy val NoSuchMethodExceptionType: TypeRef = ctx.requiredClassRef("java.lang.NoSuchMethodException")
217+
218+
lazy val FutureType: TypeRef = ctx.requiredClassRef("scala.concurrent.Future")
219+
def FutureClass(implicit ctx: Context): ClassSymbol = FutureType.symbol.asClass
220+
221+
private lazy val FutureModule_successfulR = ctx.requiredModule("scala.concurrent.Future").requiredMethodRef("successful")
222+
def FutureModule_successful(implicit ctx: Context): Symbol = FutureModule_successfulR.symbol
223+
224+
private lazy val SuccessModule_applyR = ctx.requiredModule("scala.util.Success").requiredMethodRef(nme.apply)
225+
def SuccessModule_apply(implicit ctx: Context): Symbol = SuccessModule_applyR.symbol
226+
}
227+
182228
}

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)