Skip to content
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

Fix #2681: Module Splitting Support #4198

Merged
merged 7 commits into from Oct 4, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
59 changes: 51 additions & 8 deletions Jenkinsfile
Expand Up @@ -118,16 +118,32 @@ def Tasks = [
helloworld$v/run \
helloworld$v/clean &&
sbtretry ++$scala \
'set artifactPath in (helloworld.v$v, Compile, fastOptJS) := (crossTarget in helloworld.v$v).value / "helloworld-fastopt.mjs"' \
'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
helloworld$v/run \
helloworld$v/clean &&
sbtretry ++$scala \
'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs")))' \
'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
helloworld$v/run &&
sbtretry ++$scala \
'set artifactPath in (helloworld.v$v, Compile, fullOptJS) := (crossTarget in helloworld.v$v).value / "helloworld-opt.mjs"' \
'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs")))' \
'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
helloworld$v/run &&
sbtretry ++$scala \
'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs")))' \
'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
'set scalaJSStage in Global := FullOptStage' \
helloworld$v/run \
helloworld$v/clean &&
sbtretry ++$scala testingExample$v/testHtmlJSDom &&
sbtretry ++$scala \
'set scalaJSLinkerConfig in testingExample.v$v ~= (_.withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs")))' \
'set scalaJSLinkerConfig in testingExample.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
'set scalaJSLinkerConfig in testingExample.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
testingExample$v/testHtml \
testingExample$v/clean &&
sbtretry 'set scalaJSStage in Global := FullOptStage' \
++$scala testingExample$v/testHtmlJSDom \
testingExample$v/clean &&
Expand All @@ -136,7 +152,14 @@ def Tasks = [
sbtretry ++$scala testSuiteEx$v/test &&
sbtretry 'set scalaJSStage in Global := FullOptStage' \
++$scala testSuiteEx$v/test &&
sbtretry ++$scala testSuite$v/test:doc library$v/test compiler$v/test reversi$v/fastOptJS reversi$v/fullOptJS &&
sjrd marked this conversation as resolved.
Show resolved Hide resolved
sbtretry ++$scala testSuite$v/test:doc library$v/test compiler$v/test &&
sbtretry ++$scala \
'set scalaJSLinkerConfig in reversi.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
'set scalaJSLinkerConfig in reversi.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
reversi$v/fastLinkJS \
reversi$v/fullLinkJS \
reversi$v/clean &&
sbtretry ++$scala reversi$v/fastLinkJS reversi$v/fullLinkJS &&
sbtretry ++$scala compiler$v/compile:doc library$v/compile:doc \
testInterface$v/compile:doc testBridge$v/compile:doc &&
sbtretry ++$scala headerCheck &&
Expand All @@ -154,7 +177,7 @@ def Tasks = [
sbtretry ++$scala $testSuite$v/test $testSuite$v/testHtmlJSDom &&
sbtretry 'set scalaJSStage in Global := FullOptStage' \
++$scala $testSuite$v/test \
$testSuite$v/testHtmlJSDom \
$testSuite$v/testHtmlJSDom \
$testSuite$v/clean &&
sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \
++$scala $testSuite$v/test &&
Expand Down Expand Up @@ -185,14 +208,23 @@ def Tasks = [
++$scala $testSuite$v/test &&
sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
++$scala $testSuite$v/test &&
sbtretry \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
++$scala $testSuite$v/test &&
sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
'set scalaJSStage in Global := FullOptStage' \
++$scala $testSuite$v/test \
$testSuite$v/clean &&
sbtretry 'set artifactPath in ($testSuite.v$v, Test, fastOptJS) := (crossTarget in $testSuite.v$v).value / "testsuite-fastopt.mjs"' \
sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs")))' \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
++$scala $testSuite$v/test &&
sbtretry \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs")))' \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
++$scala $testSuite$v/test &&
sbtretry 'set artifactPath in ($testSuite.v$v, Test, fullOptJS) := (crossTarget in $testSuite.v$v).value / "testsuite-opt.mjs"' \
sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs")))' \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
'set scalaJSStage in Global := FullOptStage' \
++$scala $testSuite$v/test
Expand Down Expand Up @@ -271,17 +303,28 @@ def Tasks = [
sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
++$scala $testSuite$v/test &&
sbtretry \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
++$scala $testSuite$v/test &&
sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
'set scalaJSStage in Global := FullOptStage' \
++$scala $testSuite$v/test \
$testSuite$v/clean &&
sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
'set artifactPath in ($testSuite.v$v, Test, fastOptJS) := (crossTarget in $testSuite.v$v).value / "testsuite-fastopt.mjs"' \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs")))' \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
++$scala $testSuite$v/test &&
sbtretry \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs")))' \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
++$scala $testSuite$v/test &&
sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
'set artifactPath in ($testSuite.v$v, Test, fullOptJS) := (crossTarget in $testSuite.v$v).value / "testsuite-opt.mjs"' \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs")))' \
'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
'set scalaJSStage in Global := FullOptStage' \
++$scala $testSuite$v/test
Expand Down
4 changes: 2 additions & 2 deletions ci/checksizes.sh
Expand Up @@ -20,8 +20,8 @@ case $FULLVER in
;;
esac

REVERSI_PREOPT="$BASEDIR/examples/reversi/.$VER/target/scala-$VER/reversi-fastopt.js"
REVERSI_OPT="$BASEDIR/examples/reversi/.$VER/target/scala-$VER/reversi-opt.js"
REVERSI_PREOPT="$BASEDIR/examples/reversi/.$VER/target/scala-$VER/reversi-dev/main.js"
REVERSI_OPT="$BASEDIR/examples/reversi/.$VER/target/scala-$VER/reversi-prod/main.js"

REVERSI_PREOPT_SIZE=$(stat '-c%s' "$REVERSI_PREOPT")
REVERSI_OPT_SIZE=$(stat '-c%s' "$REVERSI_OPT")
Expand Down
Expand Up @@ -143,11 +143,11 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {

kind match {
case Module =>
js.TopLevelModuleExportDef(info.jsName)
js.TopLevelModuleExportDef(info.moduleID, info.jsName)

case JSClass =>
assert(isNonNativeJSClass(classSym), "found export on non-JS class")
js.TopLevelJSClassExportDef(info.jsName)
js.TopLevelJSClassExportDef(info.moduleID, info.jsName)

case Constructor | Method =>
val exported = tups.map(t => ExportedSymbol(t._2))
Expand All @@ -156,14 +156,14 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
genExportMethod(exported, JSName.Literal(info.jsName), static = true)
}

js.TopLevelMethodExportDef(methodDef)
js.TopLevelMethodExportDef(info.moduleID, methodDef)

case Property =>
throw new AssertionError("found top-level exported property")

case Field =>
val sym = checkSingleField(tups)
js.TopLevelFieldExportDef(info.jsName, encodeFieldSym(sym))
js.TopLevelFieldExportDef(info.moduleID, info.jsName, encodeFieldSym(sym))
}
}).toList
}
Expand Down
Expand Up @@ -110,8 +110,8 @@ trait JSGlobalAddons extends JSDefinitions
/* Not final because it causes the follwing compile warning:
* "The outer reference in this type test cannot be checked at run time."
*/
case class TopLevelExportInfo(jsName: String)(val pos: Position)
extends ExportInfo
case class TopLevelExportInfo(moduleID: String, jsName: String)(
val pos: Position) extends ExportInfo
case class StaticExportInfo(jsName: String)(val pos: Position)
extends ExportInfo

Expand Down
54 changes: 36 additions & 18 deletions compiler/src/main/scala/org/scalajs/nscplugin/PrepJSExports.scala
Expand Up @@ -16,6 +16,7 @@ import scala.collection.mutable

import scala.tools.nsc.Global

import org.scalajs.ir.Names.DefaultModuleID
import org.scalajs.ir.Trees.TopLevelExportDef.isValidTopLevelExportName

/**
Expand All @@ -41,7 +42,7 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] =>
case object Normal extends ExportDestination

/** Export at the top-level. */
case object TopLevel extends ExportDestination
case class TopLevel(moduleID: String) extends ExportDestination

/** Export as a static member of the companion class. */
case object Static extends ExportDestination
Expand Down Expand Up @@ -142,8 +143,8 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] =>
private def registerStaticAndTopLevelExports(sym: Symbol,
exports: List[ExportInfo]): Unit = {
val topLevel = exports.collect {
case info @ ExportInfo(jsName, ExportDestination.TopLevel) =>
jsInterop.TopLevelExportInfo(jsName)(info.pos)
case info @ ExportInfo(jsName, ExportDestination.TopLevel(moduleID)) =>
jsInterop.TopLevelExportInfo(moduleID, jsName)(info.pos)
}

if (topLevel.nonEmpty)
Expand Down Expand Up @@ -248,23 +249,40 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] =>
assert(!isTopLevelExport || hasExplicitName,
"Found a top-level export without an explicit name at " + annot.pos)

def explicitName = annot.stringArg(0).getOrElse {
reporter.error(annot.pos,
s"The argument to ${annot.symbol.name} must be a literal string")
"dummy"
}

val name = {
if (hasExplicitName) explicitName
else if (sym.isConstructor) decodedFullName(sym.owner)
else if (sym.isClass) decodedFullName(sym)
else sym.unexpandedName.decoded.stripSuffix("_=")
if (hasExplicitName) {
annot.stringArg(0).getOrElse {
reporter.error(annot.args(0).pos,
s"The argument to ${annot.symbol.name} must be a literal string")
"dummy"
}
} else if (sym.isConstructor) {
decodedFullName(sym.owner)
} else if (sym.isClass) {
decodedFullName(sym)
} else {
sym.unexpandedName.decoded.stripSuffix("_=")
}
}

val destination = {
if (isTopLevelExport) ExportDestination.TopLevel
else if (isStaticExport) ExportDestination.Static
else ExportDestination.Normal
if (isTopLevelExport) {
val moduleID = if (annot.args.size == 1) {
DefaultModuleID
} else {
annot.stringArg(1).getOrElse {
reporter.error(annot.args(1).pos,
"moduleID must be a literal string")
DefaultModuleID
}
}

ExportDestination.TopLevel(moduleID)
} else if (isStaticExport) {
ExportDestination.Static
} else {
ExportDestination.Normal
}
}

// Enforce proper setter signature
Expand Down Expand Up @@ -304,7 +322,7 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] =>
"name apply.")
}

case ExportDestination.TopLevel =>
case _: ExportDestination.TopLevel =>
throw new AssertionError(
"Found a top-level export without an explicit name at " +
annot.pos)
Expand Down Expand Up @@ -341,7 +359,7 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] =>
"Use @JSExportTopLevel instead.")
}

case ExportDestination.TopLevel =>
case _: ExportDestination.TopLevel =>
if (sym.isLazy) {
reporter.error(annot.pos,
"You may not export a lazy val to the top level")
Expand Down
Expand Up @@ -944,7 +944,31 @@ class JSExportTest extends DirectTest with TestHelpers {
"""
|newSource1.scala:9: error: The argument to JSExport must be a literal string
| @JSExport(A.a)
| ^
| ^
"""

}

@Test
def noNonLiteralModuleID: Unit = {

"""
object A {
val a = "Hello"
final val b = "World"
}

object B {
@JSExportTopLevel("foo", A.a)
def foo() = 1
@JSExportTopLevel("foo", A.b)
def bar() = 1
}
""" hasErrors
"""
|newSource1.scala:9: error: moduleID must be a literal string
| @JSExportTopLevel("foo", A.a)
| ^
"""

}
Expand Down
3 changes: 3 additions & 0 deletions ir/src/main/scala/org/scalajs/ir/Names.scala
Expand Up @@ -518,6 +518,9 @@ object Names {
final val ClassInitializerName: MethodName =
MethodName(ClassInitializerSimpleName, Nil, VoidRef)

/** ModuleID of the default module */
final val DefaultModuleID: String = "main"

// ---------------------------------------------------
// ----- Private helpers for validation of names -----
// ---------------------------------------------------
Expand Down
25 changes: 14 additions & 11 deletions ir/src/main/scala/org/scalajs/ir/Printers.scala
Expand Up @@ -978,27 +978,30 @@ object Printers {
}

def print(topLevelExportDef: TopLevelExportDef): Unit = {
print("export top[moduleID=\"")
printEscapeJS(topLevelExportDef.moduleID, out)
print("\"] ")

topLevelExportDef match {
case TopLevelJSClassExportDef(exportName) =>
print("export top class \"")
case TopLevelJSClassExportDef(_, exportName) =>
print("class \"")
printEscapeJS(exportName, out)
print('\"')
print("\"")

case TopLevelModuleExportDef(exportName) =>
print("export top module \"")
case TopLevelModuleExportDef(_, exportName) =>
print("module \"")
printEscapeJS(exportName, out)
print('\"')
print("\"")

case TopLevelMethodExportDef(methodDef) =>
print("export top ")
case TopLevelMethodExportDef(_, methodDef) =>
print(methodDef)

case TopLevelFieldExportDef(exportName, field) =>
print("export top static field ")
case TopLevelFieldExportDef(_, exportName, field) =>
print("static field ")
print(field)
print(" as \"")
printEscapeJS(exportName, out)
print('\"')
print("\"")
}
}

Expand Down
4 changes: 2 additions & 2 deletions ir/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala
Expand Up @@ -17,8 +17,8 @@ import java.util.concurrent.ConcurrentHashMap
import scala.util.matching.Regex

object ScalaJSVersions extends VersionChecks(
current = "1.2.1-SNAPSHOT",
binaryEmitted = "1.2"
current = "1.3.0-SNAPSHOT",
binaryEmitted = "1.3-SNAPSHOT"
)

/** Helper class to allow for testing of logic. */
Expand Down