Skip to content

Commit

Permalink
Merge pull request scala#6304 from sjrd/scalajs-junit-tests
Browse files Browse the repository at this point in the history
Emit Scala.js JUnit bootstrappers for JUnit test classes.
  • Loading branch information
sjrd authored May 3, 2019
2 parents 2869bb3 + e5bd1fe commit e1385a3
Show file tree
Hide file tree
Showing 14 changed files with 586 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pipeline:
image: lampepfl/dotty:2019-04-22
commands:
- cp -R . /tmp/2/ && cd /tmp/2/
- ./project/scripts/sbt ";dotty-bootstrapped/compile ;dotty-bootstrapped/test; dotty-semanticdb/compile; dotty-semanticdb/test:compile;sjsSandbox/run"
- ./project/scripts/sbt ";dotty-bootstrapped/compile ;dotty-bootstrapped/test; dotty-semanticdb/compile; dotty-semanticdb/test:compile;sjsSandbox/run;sjsSandbox/test"
- ./project/scripts/bootstrapCmdTests

community_build:
Expand Down
13 changes: 8 additions & 5 deletions compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import interface._
// Here used to be an `assert(!classSym.isDelambdafyFunction)`: delambdafy lambda classes are
// always top-level. However, SI-8900 shows an example where the weak name-based implementation
// of isDelambdafyFunction failed (for a function declared in a package named "lambda").
classSym.isAnonymousClass || (classSym.originalOwner != NoSymbol && !classSym.originalOwner.isClass)
classSym.isAnonymousClass || {
val originalOwnerLexicallyEnclosingClass = classSym.originalOwner.originalLexicallyEnclosingClass
originalOwnerLexicallyEnclosingClass != NoSymbol && !originalOwnerLexicallyEnclosingClass.isClass
}
}

/**
Expand Down Expand Up @@ -51,9 +54,9 @@ import interface._
def enclosingMethod(sym: Symbol): Option[Symbol] = {
if (sym.isClass || sym == NoSymbol) None
else if (sym.isMethod) Some(sym)
else enclosingMethod(sym.originalOwner)
else enclosingMethod(sym.originalOwner.originalLexicallyEnclosingClass)
}
enclosingMethod(classSym.originalOwner)
enclosingMethod(classSym.originalOwner.originalLexicallyEnclosingClass)
}

/**
Expand All @@ -64,9 +67,9 @@ import interface._
assert(classSym.isClass, classSym)
def enclosingClass(sym: Symbol): Symbol = {
if (sym.isClass) sym
else enclosingClass(sym.originalOwner)
else enclosingClass(sym.originalOwner.originalLexicallyEnclosingClass)
}
enclosingClass(classSym.originalOwner)
enclosingClass(classSym.originalOwner.originalLexicallyEnclosingClass)
}

/*final*/ case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class BTypesFromSymbols[I <: BackendInterface](val int: I) extends BTypes {
if (!isNested) None
else {
// See comment in BTypes, when is a class marked static in the InnerClass table.
val isStaticNestedClass = innerClassSym.originalOwner.isOriginallyStaticOwner
val isStaticNestedClass = innerClassSym.originalOwner.originalLexicallyEnclosingClass.isOriginallyStaticOwner

// After lambdalift (which is where we are), the rawowoner field contains the enclosing class.
val enclosingClassSym = innerClassSym.enclosingClassSym
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/backend/jvm/BackendInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,7 @@ abstract class BackendInterface extends BackendInterfaceDefinitions {
def companionSymbol: Symbol
def moduleClass: Symbol
def enclosingClassSym: Symbol
def originalLexicallyEnclosingClass: Symbol
def nextOverriddenSymbol: Symbol


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

def samMethod(): Symbol

Expand Down
21 changes: 10 additions & 11 deletions compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -727,18 +727,9 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
// navigation
def owner: Symbol = toDenot(sym).owner
def rawowner: Symbol = {
originalOwner
originalOwner.originalLexicallyEnclosingClass
}
def originalOwner: Symbol =
// used to populate the EnclosingMethod attribute.
// it is very tricky in presence of classes(and annonymous classes) defined inside supper calls.
if (sym.exists) {
val original = toDenot(sym).initial
val validity = original.validFor
val shiftedContext = ctx.withPhase(validity.phaseId)
val r = toDenot(sym)(shiftedContext).maybeOwner.lexicallyEnclosingClass(shiftedContext)
r
} else NoSymbol
def originalOwner: Symbol = toDenot(sym).originalOwner
def parentSymbols: List[Symbol] = toDenot(sym).info.parents.map(_.typeSymbol)
def superClass: Symbol = {
val t = toDenot(sym).asClass.superClass
Expand All @@ -765,6 +756,14 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
}
else sym.enclosingClass(ctx.withPhase(ctx.flattenPhase.prev))
} //todo is handled specially for JavaDefined symbols in scalac
def originalLexicallyEnclosingClass: Symbol =
// used to populate the EnclosingMethod attribute.
// it is very tricky in presence of classes(and annonymous classes) defined inside supper calls.
if (sym.exists) {
val validity = toDenot(sym).initial.validFor
val shiftedContext = ctx.withPhase(validity.phaseId)
toDenot(sym)(shiftedContext).lexicallyEnclosingClass(shiftedContext)
} else NoSymbol
def nextOverriddenSymbol: Symbol = toDenot(sym).nextOverriddenSymbol

// members
Expand Down
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
46 changes: 46 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 All @@ -179,4 +189,40 @@ final class JSDefinitions()(implicit ctx: Context) {
def isJSThisFunctionClass(cls: Symbol): Boolean =
isScalaJSVarArityClass(cls, "ThisFunction")

/** Definitions related to the treatment of JUnit boostrappers. */
object junit {
lazy val TestAnnotType: TypeRef = ctx.requiredClassRef("org.junit.Test")
def TestAnnotClass(implicit ctx: Context): ClassSymbol = TestAnnotType.symbol.asClass

lazy val BeforeAnnotType: TypeRef = ctx.requiredClassRef("org.junit.Before")
def BeforeAnnotClass(implicit ctx: Context): ClassSymbol = BeforeAnnotType.symbol.asClass

lazy val AfterAnnotType: TypeRef = ctx.requiredClassRef("org.junit.After")
def AfterAnnotClass(implicit ctx: Context): ClassSymbol = AfterAnnotType.symbol.asClass

lazy val BeforeClassAnnotType: TypeRef = ctx.requiredClassRef("org.junit.BeforeClass")
def BeforeClassAnnotClass(implicit ctx: Context): ClassSymbol = BeforeClassAnnotType.symbol.asClass

lazy val AfterClassAnnotType: TypeRef = ctx.requiredClassRef("org.junit.AfterClass")
def AfterClassAnnotClass(implicit ctx: Context): ClassSymbol = AfterClassAnnotType.symbol.asClass

lazy val IgnoreAnnotType: TypeRef = ctx.requiredClassRef("org.junit.Ignore")
def IgnoreAnnotClass(implicit ctx: Context): ClassSymbol = IgnoreAnnotType.symbol.asClass

lazy val BootstrapperType: TypeRef = ctx.requiredClassRef("org.scalajs.junit.Bootstrapper")

lazy val TestMetadataType: TypeRef = ctx.requiredClassRef("org.scalajs.junit.TestMetadata")

lazy val NoSuchMethodExceptionType: TypeRef = ctx.requiredClassRef("java.lang.NoSuchMethodException")

lazy val FutureType: TypeRef = ctx.requiredClassRef("scala.concurrent.Future")
def FutureClass(implicit ctx: Context): ClassSymbol = FutureType.symbol.asClass

private lazy val FutureModule_successfulR = ctx.requiredModule("scala.concurrent.Future").requiredMethodRef("successful")
def FutureModule_successful(implicit ctx: Context): Symbol = FutureModule_successfulR.symbol

private lazy val SuccessModule_applyR = ctx.requiredModule("scala.util.Success").requiredMethodRef(nme.apply)
def SuccessModule_apply(implicit ctx: Context): Symbol = SuccessModule_applyR.symbol
}

}
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
Loading

0 comments on commit e1385a3

Please sign in to comment.