This Scala 3 code compiles and links using ScalaJS 1.3.1 and the latest Dotty nightly build, but fails to link using ScalaJS 1.4.0:
package problem
object Other:
lazy val a = "hello"
// this line causes linking to fail with ScalaJS 1.4.0, but not with 1.3.1
lazy val b = ???
object Main:
val view = Other.a
def main(args: Array[String]): Unit = ()
```
Output of Sbt build using ScalaJS 1.4.0 under Windows 10 20H2:
```
[info] welcome to sbt 1.4.6 (AdoptOpenJDK Java 11.0.9)
[info] loading global plugins from C:\Users\djn\.sbt\1.0\plugins
[info] loading settings for project muve-build from plugins.sbt ...
[info] loading project definition from C:\Git\Muve\project
[info] loading settings for project muve from build.sbt ...
Fetching latest Dotty nightly version...
Latest Dotty nightly build version: 3.0.0-RC1-bin-20210111-0f1d350-NIGHTLY
[info] set current project to ScalaJS Linker Problem (in build file:/C:/Git/Muve/)
[info] sbt server started at local:sbt-server-23257b54281723eb2d64
[info] started sbt server
sbt:ScalaJS Linker Problem> clean
[success] Total time: 0 s, completed 13 Jan 2021, 00:32:35
sbt:ScalaJS Linker Problem> fastLinkJS
[warn] There may be incompatibilities among your library dependencies; run 'evicted' to see detailed eviction warnings.
[info] compiling 1 Scala source to C:\Git\Muve\target\scala-3.0.0-RC1\classes ...
[info] Fast optimizing C:\Git\Muve\target\scala-3.0.0-RC1\scalajs-linker-problem-fastopt
[error] stack trace is suppressed; run last Compile / fastLinkJS for the full output
[error] (Compile / fastLinkJS) java.lang.IllegalArgumentException: cannot generate a zero for NothingType
[error] Total time: 6 s, completed 13 Jan 2021, 00:32:43
Full exception output:
sbt:ScalaJS Linker Problem> last Compile / fastLinkJS
[info] Fast optimizing C:\Git\Muve\target\scala-3.0.0-RC1\scalajs-linker-problem-fastopt
[debug] Linker: Compute reachability: 130613 us
[debug] Linker: Assemble LinkedClasses: 14873 us
[debug] Linker: 189468 us
[debug] Optimizer: Batch mode: true
[debug] Optimizer: Incremental part: 52768 us
[debug] Optimizer: Optimizing 211 methods.
[debug] Optimizer: Optimizer part: 144326 us
[debug] Optimizer: 201273 us
[debug] Refiner: Compute reachability: 17781 us
[debug] Refiner: Assemble LinkedClasses: 2146 us
[debug] Refiner: 103948 us
[debug] Module Splitter: Calculate Dependency Info: 3563 us
[debug] Module Splitter: Analyze Modules: 793 us
[debug] Module Splitter: Assemble Modules: 7452 us
[debug] Module Splitter: 13887 us
[debug] Emitter: Class tree cache stats: reused: 0 -- invalidated: 9
[debug] Emitter: Method tree cache stats: reused: 0 -- invalidated: 11
[error] java.lang.IllegalArgumentException: cannot generate a zero for NothingType
[error] at org.scalajs.linker.backend.emitter.SJSGen.genZeroOfPrim(SJSGen.scala:79)
[error] at org.scalajs.linker.backend.emitter.SJSGen.genZeroOf(SJSGen.scala:48)
[error] at org.scalajs.linker.backend.emitter.ClassEmitter.$anonfun$genFieldDefsOfScalaClass$2(ClassEmitter.scala:407)
[error] at scala.collection.TraversableLike$WithFilter.$anonfun$map$2(TraversableLike.scala:874)
[error] at scala.collection.immutable.List.foreach(List.scala:431)
[error] at scala.collection.TraversableLike$WithFilter.map(TraversableLike.scala:873)
[error] at org.scalajs.linker.backend.emitter.ClassEmitter.genFieldDefsOfScalaClass(ClassEmitter.scala:401)
[error] at org.scalajs.linker.backend.emitter.ClassEmitter.$anonfun$genJSConstructorFun$1(ClassEmitter.scala:351)
[error] at scala.collection.immutable.List.flatMap(List.scala:366)
[error] at org.scalajs.linker.backend.emitter.ClassEmitter.genJSConstructorFun(ClassEmitter.scala:350)
[error] at org.scalajs.linker.backend.emitter.ClassEmitter.genES5Constructor(ClassEmitter.scala:225)
[error] at org.scalajs.linker.backend.emitter.ClassEmitter.genConstructor(ClassEmitter.scala:186)
[error] at org.scalajs.linker.backend.emitter.Emitter.$anonfun$genClass$10(Emitter.scala:438)
[error] at org.scalajs.linker.backend.emitter.Emitter$MethodCache.getOrElseUpdate(Emitter.scala:668)
[error] at org.scalajs.linker.backend.emitter.Emitter.genClass(Emitter.scala:438)
[error] at org.scalajs.linker.backend.emitter.Emitter.$anonfun$emitOnce$4(Emitter.scala:180)
[error] at scala.collection.immutable.List.map(List.scala:297)
[error] at org.scalajs.linker.backend.emitter.Emitter.$anonfun$emitOnce$2(Emitter.scala:180)
[error] at scala.collection.immutable.List.map(List.scala:293)
[error] at org.scalajs.linker.backend.emitter.Emitter.$anonfun$emitOnce$1(Emitter.scala:177)
[error] at org.scalajs.logging.Logger.time(Logger.scala:42)
[error] at org.scalajs.logging.Logger.time$(Logger.scala:40)
[error] at org.scalajs.sbtplugin.Loggers$SbtLoggerWrapper.time(Loggers.scala:19)
[error] at org.scalajs.linker.backend.emitter.Emitter.emitOnce(Emitter.scala:181)
[error] at org.scalajs.linker.backend.emitter.Emitter.emitAvoidGlobalClash(Emitter.scala:151)
[error] at org.scalajs.linker.backend.emitter.Emitter.emitInternal(Emitter.scala:127)
[error] at org.scalajs.linker.backend.emitter.Emitter.emit(Emitter.scala:79)
[error] at org.scalajs.linker.backend.BasicLinkerBackend.$anonfun$emit$1(BasicLinkerBackend.scala:57)
[error] at org.scalajs.logging.Logger.time(Logger.scala:42)
[error] at org.scalajs.logging.Logger.time$(Logger.scala:40)
[error] at org.scalajs.sbtplugin.Loggers$SbtLoggerWrapper.time(Loggers.scala:19)
[error] at org.scalajs.linker.backend.BasicLinkerBackend.emit(BasicLinkerBackend.scala:57)
[error] at org.scalajs.linker.standard.StandardLinkerImpl.$anonfun$link$2(StandardLinkerImpl.scala:47)
[error] at scala.concurrent.Future.$anonfun$flatMap$1(Future.scala:307)
[error] at scala.concurrent.impl.Promise.$anonfun$transformWith$1(Promise.scala:41)
[error] at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:64)
[error] at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1426)
[error] at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
[error] at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
[error] at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
[error] at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
[error] at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)
[error] (Compile / fastLinkJS) java.lang.IllegalArgumentException: cannot generate a zero for NothingType
Sbt, JDK, and Dotty versions are shown in the above output. Other relevant files are shown below:
plugins.sbt:
```scala
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.1")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.4.0")
`build.sbt`:
```scala
enablePlugins(ScalaJSPlugin)
name := "ScalaJS Linker Problem"
scalaVersion := dottyLatestNightlyBuild.get
scalaJSUseMainModuleInitializer := true
Thank you for the report!
I can reproduce this with Scala 2 as well, in 2.11.12, 2.12.12 and 2.13.4. Of course that requires using Scala 2 syntax:
object Other {
lazy val a = "hello"
// this line causes linking to fail with ScalaJS 1.4.0, but not with 1.3.1
lazy val b = ???
}
object Main {
val view = Other.a
def main(args: Array[String]): Unit = ()
}
In all cases, the type of the field b in the IR is nothing (NothingType). This is the Scala 2.12.12 output:
module class test.Other$ extends java.lang.Object {
var a: java.lang.String
var b: nothing
var bitmap$0: byte
private def a$lzycompute;Ljava.lang.String(): java.lang.String = {
if ((((int)((byte)(((int)this.test.Other$::bitmap$0) &[int] 1))) ==[int] 0)) {
this.test.Other$::a = "hello";
this.test.Other$::bitmap$0 = ((byte)(((int)this.test.Other$::bitmap$0) |[int] 1))
};
this.test.Other$::a
}
def a;Ljava.lang.String(): java.lang.String = {
if ((((int)((byte)(((int)this.test.Other$::bitmap$0) &[int] 1))) ==[int] 0)) {
this.test.Other$::private::a$lzycompute;Ljava.lang.String()
} else {
this.test.Other$::a
}
}
private def b$lzycompute;E(): nothing = {
if ((((int)((byte)(((int)this.test.Other$::bitmap$0) &[int] 2))) ==[int] 0)) {
this.test.Other$::b = mod:scala.Predef$.$qmark$qmark$qmark;E();
this.test.Other$::bitmap$0 = ((byte)(((int)this.test.Other$::bitmap$0) |[int] 2))
};
this.test.Other$::b
}
def b;E(): nothing = {
if ((((int)((byte)(((int)this.test.Other$::bitmap$0) &[int] 2))) ==[int] 0)) {
this.test.Other$::private::b$lzycompute;E()
} else {
this.test.Other$::b
}
}
constructor def <init>;V() {
this.java.lang.Object::<init>;V();
mod:test.Other$<-this
}
}
The IR checker lets that through, but then the emitter chokes on it at
https://github.com/scala-js/scala-js/blob/c6c82e80f56bd2008ff8273088bbbbbbbc30f777/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala#L78-L79
nothing shouldn't be a valid field type, since a field needs an initial value, and no value can fit in a nothing. On the JVM, the type of the field would be scala.runtime.Nothing$ instead.
We need to decide whether:
scala.runtime.Nothing$ for field types (like in arrays).nothing in the linker.Backward compat would like to demand that we pick the second option, but I don't see how we can soundly process a field of type nothing. We would have to throw upon construction, but that's not compatible either. So I would go with making it invalid IR anyway.
Give an appropriate explicit type to the lazy val if its rhs makes it infer as Nothing:
lazy val b: String = ???
I agree, this should be invalid IR. I'm wondering if we can apply a deser hack to emulate pre 1.4.0 behavior.
I think we can, but the deser hack would have to be Scala-aware, which the linker generally isn't (except for a couple of intrinsics). We would need to:
FieldDef(..., NothingType, ...) -> FieldDef(..., ClassType("scala.runtime.Nothing$"), ...)Assign(Select(..., f)(NothingType), ...) -> Assign(Select(..., f)(ClassType("scala.runtime.Nothing$")), ...)Select(..., f)(NothingType) -> AsInstanceOf(Select(..., f)(ClassType("scala.runtime.Nothing$")), NothingType)But clearly that means knowing about scala.runtime.Nothing$.
Hum, but wouldn't the assignment cause invalid IR?
I was more thinking something like:
FieldDef(..., NothingType, ...)Assign(Select(..., f)(NothingType), rhs) -> rhsSelect(..., f)(NothingType) -> AsInstanceOf(Null(), NothingType) (essentially pre 1.3.0 behavior IIUC).Hum, but wouldn't the assignment cause invalid IR?
No because the rhs of the assignment already has type nothing. We can assign something of type nothing to a field of type scala.runtime.Nothing$.
That said, your proposal should work as well, and does not need to be Scala-aware, so that's nice :)