Skip to content

Emit Scala.js JUnit bootstrappers for JUnit test classes. #6304

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

Merged
merged 4 commits into from
May 3, 2019
Merged
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
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