From 7c95972e30c9897440e4404515d05bf663639daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 27 Sep 2024 23:18:22 +0200 Subject: [PATCH 001/121] Towards 1.17.1. --- .../org/scalajs/ir/ScalaJSVersions.scala | 2 +- project/BinaryIncompatibilities.scala | 32 ------------------- project/Build.scala | 2 +- 3 files changed, 2 insertions(+), 34 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index 97162b8bed..cd695d6894 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.17.0", + current = "1.17.1-SNAPSHOT", binaryEmitted = "1.17" ) diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 8c27dca150..4713fe6bf8 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -5,39 +5,9 @@ import com.typesafe.tools.mima.core.ProblemFilters._ object BinaryIncompatibilities { val IR = Seq( - // !!! Breaking, OK in minor release - - ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.ir.Trees#*.tpe"), - - ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.scalajs.ir.Trees#NewArray.this"), - ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.scalajs.ir.Trees#NewArray.apply"), - ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.scalajs.ir.Trees#NewArray.copy"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.ir.Trees#NewArray.copy$default$2"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Trees#NewArray.lengths"), - - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Trees#UnaryOp.resultTypeOf"), - - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#ClassType.this"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#ClassType.apply"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#ClassType.copy"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#ArrayType.this"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#ArrayType.apply"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#ArrayType.copy"), - - ProblemFilters.exclude[MissingTypesProblem]("org.scalajs.ir.Types$ClassType$"), - ProblemFilters.exclude[MissingTypesProblem]("org.scalajs.ir.Types$ArrayType$"), - - // New abstract member in sealed hierarchy, not an issue - ProblemFilters.exclude[ReversedMissingMethodProblem]("org.scalajs.ir.Types#Type.toNonNullable"), ) val Linker = Seq( - // private, not an issue - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.CoreSpec.this"), - - // private[linker], not an issue - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.CoreSpec.apply"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.ModuleSet.this"), ) val LinkerInterface = Seq( @@ -50,8 +20,6 @@ object BinaryIncompatibilities { ) val Library = Seq( - // New abstract member in JS trait, not an issue - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.scalajs.runtime.LinkingInfo.isWebAssembly"), ) val TestInterface = Seq( diff --git a/project/Build.scala b/project/Build.scala index 8e5f1eba16..9ba80d3b93 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -394,7 +394,7 @@ object Build { val previousVersions = List("1.0.0", "1.0.1", "1.1.0", "1.1.1", "1.2.0", "1.3.0", "1.3.1", "1.4.0", "1.5.0", "1.5.1", "1.6.0", "1.7.0", "1.7.1", "1.8.0", "1.9.0", "1.10.0", "1.10.1", "1.11.0", "1.12.0", "1.13.0", - "1.13.1", "1.13.2", "1.14.0", "1.15.0", "1.16.0") + "1.13.1", "1.13.2", "1.14.0", "1.15.0", "1.16.0", "1.17.0") val previousVersion = previousVersions.last val previousBinaryCrossVersion = CrossVersion.binaryWith("sjs1_", "") From b442f016c7f22cd8a17788641300f77f183fb407 Mon Sep 17 00:00:00 2001 From: Rikito Taniguchi Date: Sun, 29 Sep 2024 12:20:54 +0900 Subject: [PATCH 002/121] Fix #5048: Wasm: Emit no files when no modules are defined. Remove any existing files if there are. The previous error message in `WebAssemblyLinkerBackend` is not clear for users not very familiar with Scala.js when no modules are defined. It said: The WebAssembly backend does not support multiple modules. Found: . This behavior was not aligned with the JS backend, and it could be confusing for users. This commit changes the behavior of the Wasm backend when no modules are defined: we now delete previous artifacts and emit no files. This change aligns the behavior with that of the JS backend. --- Jenkinsfile | 5 ++++- .../backend/WebAssemblyLinkerBackend.scala | 19 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 141b4c1a1d..43009dfd57 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -451,7 +451,10 @@ def Tasks = [ sbtretry ++$scala \ 'set Global/enableWasmEverywhere := true' \ 'set scalaJSStage in Global := FullOptStage' \ - testingExample$v/testHtml + testingExample$v/testHtml && + sbtretry ++$scala \ + 'set Global/enableWasmEverywhere := true' \ + irJS$v/fastLinkJS ''', /* For the bootstrap tests to be able to call diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/WebAssemblyLinkerBackend.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/WebAssemblyLinkerBackend.scala index 14f2a6de09..ba12a26b59 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/WebAssemblyLinkerBackend.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/WebAssemblyLinkerBackend.scala @@ -63,17 +63,30 @@ final class WebAssemblyLinkerBackend(config: LinkerBackendImpl.Config) def emit(moduleSet: ModuleSet, output: OutputDirectory, logger: Logger)( implicit ec: ExecutionContext): Future[Report] = { - val onlyModule = moduleSet.modules match { + moduleSet.modules match { + case Nil => + val outputImpl = OutputDirectoryImpl.fromOutputDirectory(output) + for { + currentFilesList <- outputImpl.listFiles() + _ <- Future.traverse(currentFilesList) { f => + outputImpl.delete(f) + } + } yield new ReportImpl(Nil) case onlyModule :: Nil => - onlyModule + emit(onlyModule, moduleSet.globalInfo, output, logger) case modules => throw new UnsupportedOperationException( "The WebAssembly backend does not support multiple modules. Found: " + modules.map(_.id.id).mkString(", ")) } + } + + private def emit(onlyModule: ModuleSet.Module, globalInfo: LinkedGlobalInfo, + output: OutputDirectory, logger: Logger)( + implicit ec: ExecutionContext): Future[Report] = { val moduleID = onlyModule.id.id - val emitterResult = emitter.emit(onlyModule, moduleSet.globalInfo, logger) + val emitterResult = emitter.emit(onlyModule, globalInfo, logger) val wasmModule = emitterResult.wasmModule val outputImpl = OutputDirectoryImpl.fromOutputDirectory(output) From 9f0d899be37830ca20dc5e2b41a9321c4fcc7fd6 Mon Sep 17 00:00:00 2001 From: Rikito Taniguchi Date: Tue, 1 Oct 2024 14:49:08 +0900 Subject: [PATCH 003/121] Bump the version to 1.18.0-SNAPSHOT for the upcoming changes. --- ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index cd695d6894..37521f5065 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,8 +17,8 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.17.1-SNAPSHOT", - binaryEmitted = "1.17" + current = "1.18.0-SNAPSHOT", + binaryEmitted = "1.18-SNAPSHOT" ) /** Helper class to allow for testing of logic. */ From 7bf410dd922662dabfc85f1d76d40431c1fe0910 Mon Sep 17 00:00:00 2001 From: Rikito Taniguchi Date: Thu, 5 Sep 2024 19:30:43 +0900 Subject: [PATCH 004/121] Introduce `LinkTimeProperty`. ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values. Also, this commit introduces validation for `LinkTimeProperty` during reachability analysis. The Analyzer now verifies that the `LinkTimeProperty` in the IR has a valid name and type pair, ensuring it can be resolved using the provided link-time information. If an invalid `LinkTimeProperty` is detected, an error will be recorded in the `Analysis` --- .../org/scalajs/nscplugin/GenJSCode.scala | 2 +- .../main/scala/org/scalajs/ir/Hashers.scala | 5 ++ .../main/scala/org/scalajs/ir/Printers.scala | 5 ++ .../scala/org/scalajs/ir/Serializers.scala | 54 +++++++++++++-- .../src/main/scala/org/scalajs/ir/Tags.scala | 3 + .../scala/org/scalajs/ir/Transformers.scala | 2 +- .../scala/org/scalajs/ir/Traversers.scala | 2 +- .../src/main/scala/org/scalajs/ir/Trees.scala | 23 ++++++- .../scala/org/scalajs/ir/PrintersTest.scala | 8 +++ .../scalajs/linker/analyzer/Analysis.scala | 8 +++ .../scalajs/linker/analyzer/Analyzer.scala | 8 +++ .../org/scalajs/linker/analyzer/Infos.scala | 33 +++++++-- .../linker/backend/emitter/ClassEmitter.scala | 1 + .../linker/backend/emitter/CoreJSLib.scala | 21 ++---- .../linker/backend/emitter/Emitter.scala | 36 +++------- .../backend/emitter/FunctionEmitter.scala | 23 +++++-- .../linker/backend/emitter/JSGen.scala | 1 + .../backend/emitter/KnowledgeGuardian.scala | 2 +- .../linker/backend/emitter/SJSGen.scala | 1 + .../linker/backend/emitter/VarField.scala | 4 +- .../linker/backend/emitter/VarGen.scala | 10 +-- .../backend/wasmemitter/CoreWasmLib.scala | 1 - .../backend/wasmemitter/FunctionEmitter.scala | 29 +++++--- .../linker/backend/wasmemitter/VarGen.scala | 1 - .../linker/checker/ClassDefChecker.scala | 5 ++ .../scalajs/linker/checker/IRChecker.scala | 2 + .../frontend/optimizer/OptimizerCore.scala | 32 ++------- .../scalajs/linker/standard/CoreSpec.scala | 3 + .../linker/standard/LinkTimeProperties.scala | 68 +++++++++++++++++++ .../org/scalajs/linker/AnalyzerTest.scala | 31 +++++++++ .../org/scalajs/linker/LibrarySizeTest.scala | 4 +- project/Build.scala | 6 +- .../testsuite/library/LinkingInfoTest.scala | 9 +++ 33 files changed, 328 insertions(+), 115 deletions(-) create mode 100644 linker/shared/src/main/scala/org/scalajs/linker/standard/LinkTimeProperties.scala diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index ef5d20f120..c3dff7493b 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -6758,7 +6758,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) implicit pos: Position): js.JSGlobalRef = { propName match { case js.StringLiteral(value) => - if (js.JSGlobalRef.isValidJSGlobalRefName(value)) { + if (js.JSGlobalRef.isValidJSGlobalRefName(value) && value != js.JSGlobalRef.FileLevelThis) { if (value == "await") { global.runReporting.warning(pos, s"$actionFull of the global scope with the name '$value' is deprecated.\n" + diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala index d37bf739e7..2ed30d9443 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala @@ -550,6 +550,11 @@ object Hashers { mixName(className) mixTrees(captureValues) + case LinkTimeProperty(name) => + mixTag(TagLinkTimeProperty) + mixString(name) + mixType(tree.tpe) + case Transient(value) => throw new InvalidIRException(tree, "Cannot hash a transient IR node (its value is of class " + diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala index 34f5743e69..16cb79b85d 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -901,6 +901,11 @@ object Printers { print(className) printRow(captureValues, "](", ", ", ")") + case LinkTimeProperty(name) => + print("(") + print(name) + print(")") + // Transient case Transient(value) => diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index ae2eec6b1f..ca58392661 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -25,6 +25,7 @@ import Names._ import OriginalName.NoOriginalName import Position._ import Trees._ +import LinkTimeProperty.{ProductionMode, ESVersion, UseECMAScript2015Semantics, IsWebAssembly, LinkerVersion} import Types._ import Tags._ import Version.Unversioned @@ -572,6 +573,11 @@ object Serializers { writeName(className) writeTrees(captureValues) + case LinkTimeProperty(name) => + writeTagAndPos(TagLinkTimeProperty) + writeString(name) + writeType(tree.tpe) + case Transient(value) => throw new InvalidIRException(tree, "Cannot serialize a transient IR node (its value is of class " + @@ -1290,9 +1296,32 @@ object Serializers { case TagUnwrapFromThrowable => UnwrapFromThrowable(readTree()) - case TagJSNew => JSNew(readTree(), readTreeOrJSSpreads()) - case TagJSPrivateSelect => JSPrivateSelect(readTree(), readFieldIdent()) - case TagJSSelect => JSSelect(readTree(), readTree()) + case TagJSNew => JSNew(readTree(), readTreeOrJSSpreads()) + case TagJSPrivateSelect => JSPrivateSelect(readTree(), readFieldIdent()) + + case TagJSSelect => + if (/* hacks.use17 */ true && buf.get(buf.position()) == TagJSLinkingInfo) { // scalastyle:ignore + val jsLinkingInfo = readTree() + readTree() match { + case StringLiteral("productionMode") => + LinkTimeProperty(ProductionMode)(BooleanType) + case StringLiteral("esVersion") => + LinkTimeProperty(ESVersion)(IntType) + case StringLiteral("assumingES6") => + LinkTimeProperty(UseECMAScript2015Semantics)(BooleanType) + case StringLiteral("isWebAssembly") => + LinkTimeProperty(IsWebAssembly)(BooleanType) + case StringLiteral("linkerVersion") => + LinkTimeProperty(LinkerVersion)(StringType) + case StringLiteral("fileLevelThis") => + JSGlobalRef(JSGlobalRef.FileLevelThis) + case otherItem => + JSSelect(jsLinkingInfo, otherItem) + } + } else { + JSSelect(readTree(), readTree()) + } + case TagJSFunctionApply => JSFunctionApply(readTree(), readTreeOrJSSpreads()) case TagJSMethodApply => JSMethodApply(readTree(), readTree(), readTreeOrJSSpreads()) case TagJSSuperSelect => JSSuperSelect(readTree(), readTree(), readTree()) @@ -1312,7 +1341,21 @@ object Serializers { JSObjectConstr(List.fill(readInt())((readTree(), readTree()))) case TagJSGlobalRef => JSGlobalRef(readString()) case TagJSTypeOfGlobalRef => JSTypeOfGlobalRef(readTree().asInstanceOf[JSGlobalRef]) - case TagJSLinkingInfo => JSLinkingInfo() + + case TagJSLinkingInfo => + if (/* hacks.use17 */ true) { // scalastyle:ignore + JSObjectConstr(List( + (StringLiteral("productionMode"), LinkTimeProperty(ProductionMode)(BooleanType)), + (StringLiteral("esVersion"), LinkTimeProperty(ESVersion)(IntType)), + (StringLiteral("assumingES6"), LinkTimeProperty(UseECMAScript2015Semantics)(BooleanType)), + (StringLiteral("isWebAssembly"), LinkTimeProperty(IsWebAssembly)(BooleanType)), + (StringLiteral("linkerVersion"), LinkTimeProperty(LinkerVersion)(StringType)), + (StringLiteral("fileLevelThis"), JSGlobalRef(JSGlobalRef.FileLevelThis)) + )) + } else { + throw new IOException( + s"Found invalid pre-1.18 JSLinkingInfo def at ${pos}") + } case TagUndefined => Undefined() case TagNull => Null() @@ -1355,6 +1398,9 @@ object Serializers { case TagCreateJSClass => CreateJSClass(readClassName(), readTrees()) + + case TagLinkTimeProperty => + LinkTimeProperty(readString())(readType()) } } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala b/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala index 3c3162245b..577a3ceca6 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala @@ -127,6 +127,9 @@ private[ir] object Tags { final val TagWrapAsThrowable = TagJSNewTarget + 1 final val TagUnwrapFromThrowable = TagWrapAsThrowable + 1 + // New in 1.18 + final val TagLinkTimeProperty = TagUnwrapFromThrowable + 1 + // Tags for member defs final val TagFieldDef = 1 diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala index 09b82b1757..99f16e8755 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala @@ -223,7 +223,7 @@ object Transformers { case _:Skip | _:Debugger | _:LoadModule | _:StoreModule | _:SelectStatic | _:SelectJSNativeMember | _:LoadJSConstructor | _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | _:JSLinkingInfo | - _:Literal | _:VarRef | _:This | _:JSGlobalRef => + _:Literal | _:VarRef | _:This | _:JSGlobalRef | _:LinkTimeProperty => tree } } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala index 2f68fac81f..4c6fd1a355 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala @@ -222,7 +222,7 @@ object Traversers { case _:Skip | _:Debugger | _:LoadModule | _:StoreModule | _:SelectStatic | _:SelectJSNativeMember | _:LoadJSConstructor | _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | _:JSLinkingInfo | - _:Literal | _:VarRef | _:This | _:JSGlobalRef => + _:Literal | _:VarRef | _:This | _:JSGlobalRef | _:LinkTimeProperty => } def traverseClassDef(tree: ClassDef): Unit = { diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index 6cc39f24a8..4b36f1808e 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -966,8 +966,15 @@ object Trees { * [[isJSIdentifierName]]) *and* it is not reserved (see * [[ReservedJSIdentifierNames]]). */ - def isValidJSGlobalRefName(name: String): Boolean = - isJSIdentifierName(name) && !ReservedJSIdentifierNames.contains(name) + def isValidJSGlobalRefName(name: String): Boolean = { + (isJSIdentifierName(name) && !ReservedJSIdentifierNames.contains(name)) || + name == FileLevelThis + } + + /** The JavaScript value that is an alias to `this` + * at the top-level of the generated file. + */ + final val FileLevelThis = "this" } sealed case class JSTypeOfGlobalRef(globalRef: JSGlobalRef)( @@ -1072,6 +1079,18 @@ object Trees { val tpe = ClassType(ClassClass, nullable = false) } + sealed case class LinkTimeProperty(name: String)(val tpe: Type)( + implicit val pos: Position) + extends Tree + + object LinkTimeProperty { + final val ProductionMode = "core/productionMode" + final val ESVersion = "core/esVersion" + final val UseECMAScript2015Semantics = "core/useECMAScript2015Semantics" + final val IsWebAssembly = "core/isWebAssembly" + final val LinkerVersion = "core/linkerVersion" + } + // Atomic expressions sealed case class VarRef(ident: LocalIdent)(val tpe: Type)( diff --git a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala index 57d80c3373..013dc6672e 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -937,6 +937,14 @@ class PrintersTest { CreateJSClass("Foo", List(ref("x", IntType), ref("y", AnyType)))) } + @Test def printLinkTimeProperty(): Unit = { + assertPrintEquals( + """ + |(foo) + """, + LinkTimeProperty("foo")(StringType)) + } + @Test def printTransient(): Unit = { class MyTransient(expr: Tree) extends Transient.Value { val tpe: Type = AnyType diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala index 3c1e6f251b..0c1b0118e5 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala @@ -208,6 +208,12 @@ object Analysis { final case class ExponentOperatorWithoutES2016Support(from: From) extends Error + final case class InvalidLinkTimeProperty( + linkTimePropertyName: String, + linkTimePropertyType: Type, + from: From + ) extends Error + sealed trait From final case class FromMethod(methodInfo: MethodInfo) extends From final case class FromDispatch(classInfo: ClassInfo, methodName: MethodName) extends From @@ -264,6 +270,8 @@ object Analysis { "Uses import.meta with a module kind other than ESModule" case ExponentOperatorWithoutES2016Support(_) => "Uses the ** operator with an ECMAScript version older than ES 2016" + case InvalidLinkTimeProperty(name, tpe, _) => + s"Uses invalid link-time property ${name} of type ${tpe}" } logger.log(level, headMsg) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala index 79f1ef432f..daf81ec531 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala @@ -1478,6 +1478,14 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean, _classSuperClassUsed.set(true) } } + + if (data.referencedLinkTimeProperties.nonEmpty) { + for ((name, tpe) <- data.referencedLinkTimeProperties) { + if (!config.coreSpec.linkTimeProperties.validate(name, tpe)) { + _errors ::= InvalidLinkTimeProperty(name, tpe, from) + } + } + } } @tailrec diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala index 6b622ae82a..3ff5ee059d 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala @@ -78,13 +78,14 @@ object Infos { val isAbstract: Boolean, version: Version, byClass: Array[ReachabilityInfoInClass], - globalFlags: ReachabilityInfo.Flags - ) extends ReachabilityInfo(version, byClass, globalFlags) + globalFlags: ReachabilityInfo.Flags, + referencedLinkTimeProperties: Array[(String, Type)] + ) extends ReachabilityInfo(version, byClass, globalFlags, referencedLinkTimeProperties) object MethodInfo { def apply(isAbstract: Boolean, reachabilityInfo: ReachabilityInfo): MethodInfo = { import reachabilityInfo._ - new MethodInfo(isAbstract, version, byClass, globalFlags) + new MethodInfo(isAbstract, version, byClass, globalFlags, referencedLinkTimeProperties) } } @@ -102,7 +103,8 @@ object Infos { */ val version: Version, val byClass: Array[ReachabilityInfoInClass], - val globalFlags: ReachabilityInfo.Flags + val globalFlags: ReachabilityInfo.Flags, + val referencedLinkTimeProperties: Array[(String, Type)] ) object ReachabilityInfo { @@ -196,8 +198,10 @@ object Infos { } final class ReachabilityInfoBuilder(version: Version) { + import ReachabilityInfoBuilder._ private val byClass = mutable.Map.empty[ClassName, ReachabilityInfoInClassBuilder] private var flags: ReachabilityInfo.Flags = 0 + private val linkTimeProperties = mutable.ListBuffer.empty[(String, Type)] private def forClass(cls: ClassName): ReachabilityInfoInClassBuilder = byClass.getOrElseUpdate(cls, new ReachabilityInfoInClassBuilder(cls)) @@ -390,8 +394,22 @@ object Infos { def addUsedClassSuperClass(): this.type = setFlag(ReachabilityInfo.FlagUsedClassSuperClass) - def result(): ReachabilityInfo = - new ReachabilityInfo(version, byClass.valuesIterator.map(_.result()).toArray, flags) + def addReferencedLinkTimeProperty(linkTimeProperty: LinkTimeProperty): this.type = { + linkTimeProperties.append((linkTimeProperty.name, linkTimeProperty.tpe)) + this + } + + def result(): ReachabilityInfo = { + val referencedLinkTimeProperties = + if (linkTimeProperties.isEmpty) emptyLinkTimePropertyArray + else linkTimeProperties.toArray + new ReachabilityInfo(version, byClass.valuesIterator.map(_.result()).toArray, flags, + referencedLinkTimeProperties) + } + } + + object ReachabilityInfoBuilder { + private val emptyLinkTimePropertyArray = new Array[(String, Type)](0) } final class ReachabilityInfoInClassBuilder(val className: ClassName) { @@ -744,6 +762,9 @@ object Infos { case VarDef(_, _, vtpe, _, _) => builder.maybeAddReferencedClass(vtpe) + case linkTimeProperty: LinkTimeProperty => + builder.addReferencedLinkTimeProperty(linkTimeProperty) + case _ => } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala index 0243d9f7bc..1b1751df60 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala @@ -40,6 +40,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { import sjsGen._ import jsGen._ import config._ + import coreSpec._ import nameGen._ import varGen._ diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala index 35e480f233..ffbea0e2f7 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala @@ -63,6 +63,7 @@ private[emitter] object CoreJSLib { import sjsGen._ import jsGen._ import config._ + import coreSpec._ import nameGen._ import varGen._ import esFeatures._ @@ -127,7 +128,7 @@ private[emitter] object CoreJSLib { } private def buildPreObjectDefinitions(): List[Tree] = { - defineLinkingInfo() ::: + defineFileLevelThis() ::: defineJSBuiltinsSnapshotsAndPolyfills() ::: declareCachedL0() ::: defineCharClass() ::: @@ -154,22 +155,8 @@ private[emitter] object CoreJSLib { assignCachedL0() } - private def defineLinkingInfo(): List[Tree] = { - // must be in sync with scala.scalajs.runtime.LinkingInfo - - def objectFreeze(tree: Tree): Tree = - Apply(genIdentBracketSelect(ObjectRef, "freeze"), tree :: Nil) - - val linkingInfo = objectFreeze(ObjectConstr(List( - str("esVersion") -> int(esVersion.edition), - str("assumingES6") -> bool(useECMAScript2015Semantics), // different name for historical reasons - str("isWebAssembly") -> bool(false), - str("productionMode") -> bool(productionMode), - str("linkerVersion") -> str(ScalaJSVersions.current), - str("fileLevelThis") -> This() - ))) - - extractWithGlobals(globalVarDef(VarField.linkingInfo, CoreVar, linkingInfo)) + private def defineFileLevelThis(): List[Tree] = { + extractWithGlobals(globalVarDef(VarField.fileLevelThis, CoreVar, This())) } private def defineJSBuiltinsSnapshotsAndPolyfills(): List[Tree] = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala index 07e4dee5f8..c6576f4f5d 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala @@ -37,6 +37,7 @@ final class Emitter(config: Emitter.Config, prePrinter: Emitter.PrePrinter) { import Emitter._ import config._ + import coreSpec._ require(!config.minify || prePrinter == PrePrinter.Off, "When using the 'minify' option, the prePrinter must be Off.") @@ -1088,23 +1089,16 @@ object Emitter { /** Configuration for the Emitter. */ final class Config private ( - val semantics: Semantics, - val moduleKind: ModuleKind, - val esFeatures: ESFeatures, + val coreSpec: CoreSpec, val jsHeader: String, val internalModulePattern: ModuleID => String, val optimizeBracketSelects: Boolean, val trackAllGlobalRefs: Boolean, val minify: Boolean ) { - private def this( - semantics: Semantics, - moduleKind: ModuleKind, - esFeatures: ESFeatures) = { + private def this(coreSpec: CoreSpec) = { this( - semantics, - moduleKind, - esFeatures, + coreSpec, jsHeader = "", internalModulePattern = "./" + _.id, optimizeBracketSelects = true, @@ -1117,14 +1111,8 @@ object Emitter { if (trackAllGlobalRefs) GlobalRefTracking.All else GlobalRefTracking.Dangerous - def withSemantics(f: Semantics => Semantics): Config = - copy(semantics = f(semantics)) - - def withModuleKind(moduleKind: ModuleKind): Config = - copy(moduleKind = moduleKind) - - def withESFeatures(f: ESFeatures => ESFeatures): Config = - copy(esFeatures = f(esFeatures)) + def withCoreSpec(coreSpec: CoreSpec): Config = + copy(coreSpec = coreSpec) def withJSHeader(jsHeader: String): Config = { require(StandardConfig.isValidJSHeader(jsHeader), jsHeader) @@ -1144,16 +1132,14 @@ object Emitter { copy(minify = minify) private def copy( - semantics: Semantics = semantics, - moduleKind: ModuleKind = moduleKind, - esFeatures: ESFeatures = esFeatures, + coreSpec: CoreSpec = coreSpec, jsHeader: String = jsHeader, internalModulePattern: ModuleID => String = internalModulePattern, optimizeBracketSelects: Boolean = optimizeBracketSelects, trackAllGlobalRefs: Boolean = trackAllGlobalRefs, minify: Boolean = minify ): Config = { - new Config(semantics, moduleKind, esFeatures, jsHeader, + new Config(coreSpec, jsHeader, internalModulePattern, optimizeBracketSelects, trackAllGlobalRefs, minify) } @@ -1161,7 +1147,7 @@ object Emitter { object Config { def apply(coreSpec: CoreSpec): Config = - new Config(coreSpec.semantics, coreSpec.moduleKind, coreSpec.esFeatures) + new Config(coreSpec) } sealed trait PrePrinter { @@ -1257,7 +1243,7 @@ object Emitter { ancestors: List[ClassName], moduleContext: ModuleContext) private def symbolRequirements(config: Config): SymbolRequirement = { - import config.semantics._ + import config.coreSpec.semantics._ import CheckedBehavior._ val factory = SymbolRequirement.factory("emitter") @@ -1313,7 +1299,7 @@ object Emitter { callMethod(BoxedDoubleClass, hashCodeMethodName), callMethod(BoxedStringClass, hashCodeMethodName), - cond(!config.esFeatures.allowBigIntsForLongs) { + cond(!config.coreSpec.esFeatures.allowBigIntsForLongs) { multiple( instanceTests(LongImpl.RuntimeLongClass), instantiateClass(LongImpl.RuntimeLongClass, LongImpl.AllConstructors.toList), diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index b8b802695f..18d4272cf1 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -251,6 +251,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { import sjsGen._ import jsGen._ import config._ + import coreSpec._ import nameGen._ import varGen._ @@ -1243,10 +1244,11 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { def test(tree: Tree): Boolean = tree match { // Atomic expressions - case _: Literal => true - case _: This => true - case _: JSNewTarget => true - case _: JSLinkingInfo => true + case _: Literal => true + case _: This => true + case _: JSNewTarget => true + case _: JSLinkingInfo => true + case _: LinkTimeProperty => true // Vars (side-effect free, pure if immutable) case VarRef(name) => @@ -2796,6 +2798,11 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { genSelect(newExpr, FieldIdent(exceptionFieldName)), genCheckNotNull(newExpr)) + case prop: LinkTimeProperty => + transformExpr( + config.coreSpec.linkTimeProperties.transformLinkTimeProperty(prop), + preserveChar) + // Transients case Transient(Cast(expr, tpe)) => @@ -2947,13 +2954,17 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { }) case JSGlobalRef(name) => - js.VarRef(transformGlobalVarIdent(name)) + if (name == JSGlobalRef.FileLevelThis) + globalVar(VarField.fileLevelThis, CoreVar) + else + js.VarRef(transformGlobalVarIdent(name)) case JSTypeOfGlobalRef(globalRef) => js.UnaryOp(JSUnaryOp.typeof, transformExprNoChar(globalRef)) case JSLinkingInfo() => - globalVar(VarField.linkingInfo, CoreVar) + throw new IllegalArgumentException( + "JSLinkingInfo was removed in 1.18.0.") // Literals diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala index 3676a947c8..ea9be90e76 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala @@ -25,6 +25,7 @@ import org.scalajs.linker.interface.ESVersion private[emitter] final class JSGen(val config: Emitter.Config) { import config._ + import coreSpec._ /** Should we use ECMAScript classes for JavaScript classes and Throwable * classes? diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala index 5a02604391..c7a94abfb9 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala @@ -152,7 +152,7 @@ private[emitter] final class KnowledgeGuardian(config: Emitter.Config) { private def computeStaticFieldMirrors( moduleSet: ModuleSet): Map[ClassName, Map[FieldName, List[String]]] = { - if (config.moduleKind != ModuleKind.NoModule) { + if (config.coreSpec.moduleKind != ModuleKind.NoModule) { Map.empty } else { var result = Map.empty[ClassName, Map[FieldName, List[String]]] diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala index 44f8708265..d16df3126a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala @@ -38,6 +38,7 @@ private[emitter] final class SJSGen( import jsGen._ import config._ + import coreSpec._ import nameGen._ import varGen._ diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala index a43a127f61..44193542b9 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala @@ -137,8 +137,8 @@ private[emitter] object VarField { // Core fields: Generated by the CoreJSLib - /** The linking info object. */ - final val linkingInfo = mk("$linkingInfo") + /** The alias to file level `this` in the generated JS file. */ + final val fileLevelThis = mk("$fileLevelThis") /** The TypeData class. */ final val TypeData = mk("$TypeData") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala index d867b347ed..cdcf20560f 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala @@ -97,7 +97,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, val ident = globalVarIdent(field, scope, origName) val varDef = genLet(ident, mutable = true, value) - if (config.moduleKind == ModuleKind.ESModule && !moduleContext.public) { + if (config.coreSpec.moduleKind == ModuleKind.ESModule && !moduleContext.public) { val setterIdent = globalVarIdent(setterField, scope) val x = Ident("x") val setter = FunctionDef(setterIdent, List(ParamDef(x)), None, { @@ -117,7 +117,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, def needToUseGloballyMutableVarSetter[T](scope: T)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, scopeType: Scope[T]): Boolean = { - config.moduleKind == ModuleKind.ESModule && + config.coreSpec.moduleKind == ModuleKind.ESModule && globalKnowledge.getModule(scopeType.reprClass(scope)) != moduleContext.moduleID } @@ -125,7 +125,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, origName: OriginalName = NoOriginalName)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { - assert(config.moduleKind == ModuleKind.ESModule) + assert(config.coreSpec.moduleKind == ModuleKind.ESModule) val ident = globalVarIdent(field, scope, origName) foldSameModule[T, Tree](scope) { @@ -163,7 +163,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, } { moduleID => val moduleName = config.internalModulePattern(moduleID) - val moduleTree = config.moduleKind match { + val moduleTree = config.coreSpec.moduleKind match { case ModuleKind.NoModule => /* If we get here, it means that what we are trying to import is in a * different module than the module we're currently generating @@ -279,7 +279,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, if (moduleContext.public) { WithGlobals(tree :: Nil) } else { - val exportStat = config.moduleKind match { + val exportStat = config.coreSpec.moduleKind match { case ModuleKind.NoModule => throw new AssertionError("non-public module in NoModule mode") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala index d74aa2d810..6a00aaab2c 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala @@ -311,7 +311,6 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { ) } - addGlobalHelperImport(genGlobalID.jsLinkingInfo, RefType.any) addGlobalHelperImport(genGlobalID.undef, RefType.any) addGlobalHelperImport(genGlobalID.bFalse, RefType.any) addGlobalHelperImport(genGlobalID.bTrue, RefType.any) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index 051fd64887..7babc4ee9c 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -575,6 +575,7 @@ private class FunctionEmitter private ( case t: IdentityHashCode => genIdentityHashCode(t) case t: WrapAsThrowable => genWrapAsThrowable(t) case t: UnwrapFromThrowable => genUnwrapFromThrowable(t) + case t: LinkTimeProperty => genLinkTimeProperty(t) // JavaScript expressions case t: JSNew => genJSNew(t) @@ -2702,6 +2703,12 @@ private class FunctionEmitter private ( AnyType } + private def genLinkTimeProperty(tree: LinkTimeProperty): Type = { + val lit = ctx.coreSpec.linkTimeProperties.transformLinkTimeProperty(tree) + genLiteral(lit, lit.tpe) + lit.tpe + } + private def genJSNew(tree: JSNew): Type = { val JSNew(ctor, args) = tree @@ -2938,14 +2945,19 @@ private class FunctionEmitter private ( val JSGlobalRef(name) = tree implicit val pos = tree.pos + markPosition(pos) - val builder = new CustomJSHelperBuilder() - val helperID = builder.build(AnyType) { - js.Return(builder.genGlobalRef(name)) - } + if (name == JSGlobalRef.FileLevelThis) { + // In ES modules global this is undefined, and Wasm backend only supports `ESModule` + fb += wa.GlobalGet(genGlobalID.undef) + } else { + val builder = new CustomJSHelperBuilder() + val helperID = builder.build(AnyType) { + js.Return(builder.genGlobalRef(name)) + } - markPosition(pos) - fb += wa.Call(helperID) + fb += wa.Call(helperID) + } AnyType } @@ -2965,9 +2977,8 @@ private class FunctionEmitter private ( } private def genJSLinkingInfo(tree: JSLinkingInfo): Type = { - markPosition(tree) - fb += wa.GlobalGet(genGlobalID.jsLinkingInfo) - AnyType + throw new IllegalArgumentException( + "JSLinkingInfo is deprecated as of 1.18.0.") } private def genArrayLength(tree: ArrayLength): Type = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala index f01d9f49e9..6a01f55cb6 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala @@ -53,7 +53,6 @@ object VarGen { */ sealed abstract class JSHelperGlobalID extends GlobalID - case object jsLinkingInfo extends JSHelperGlobalID case object undef extends JSHelperGlobalID case object bFalse extends JSHelperGlobalID case object bTrue extends JSHelperGlobalID diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index b569825470..ee5cbcd719 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -562,6 +562,9 @@ private final class ClassDefChecker(classDef: ClassDef, if (env.locals.get(name).exists(!_.mutable)) reportError(i"Assignment to immutable variable $name.") + case JSGlobalRef(JSGlobalRef.FileLevelThis) => + reportError(i"Assignment to global this.") + case _:Select | _:JSPrivateSelect | _:SelectStatic | _:ArraySelect | _:RecordSelect | _:JSSelect | _:JSSuperSelect | _:JSGlobalRef => @@ -727,6 +730,8 @@ private final class ClassDefChecker(classDef: ClassDef, case UnwrapFromThrowable(expr) => checkTree(expr, env) + case LinkTimeProperty(name) => + // JavaScript expressions case JSNew(ctor, args) => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index 5bf5e259c4..6f9aa2c12c 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -536,6 +536,8 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { case UnwrapFromThrowable(expr) => typecheckExpect(expr, env, ClassType(ThrowableClass, nullable = true)) + case LinkTimeProperty(name) => + // JavaScript expressions case JSNew(ctor, args) => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 2e49b2d40f..502af60ace 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -610,6 +610,9 @@ private[optimizer] abstract class OptimizerCore( pretransformExpr(tree)(finishTransform(isStat)) } + case prop: LinkTimeProperty => + config.coreSpec.linkTimeProperties.transformLinkTimeProperty(prop) + // JavaScript expressions case JSNew(ctor, args) => @@ -2393,7 +2396,7 @@ private[optimizer] abstract class OptimizerCore( val titem = optimizeJSBracketSelectItem(titem0) def default: TailRec[Tree] = { - cont(PreTransTree(foldJSSelect(finishTransformExpr(tqual), + cont(PreTransTree(JSSelect(finishTransformExpr(tqual), finishTransformExpr(titem)))) } @@ -5096,33 +5099,6 @@ private[optimizer] abstract class OptimizerCore( } } - private def foldJSSelect(qualifier: Tree, item: Tree)( - implicit pos: Position): Tree = { - // !!! Must be in sync with scala.scalajs.runtime.LinkingInfo - - import config.coreSpec.esFeatures - - (qualifier, item) match { - case (JSLinkingInfo(), StringLiteral("productionMode")) => - BooleanLiteral(semantics.productionMode) - - case (JSLinkingInfo(), StringLiteral("esVersion")) => - IntLiteral(esFeatures.esVersion.edition) - - case (JSLinkingInfo(), StringLiteral("assumingES6")) => - BooleanLiteral(esFeatures.useECMAScript2015Semantics) - - case (JSLinkingInfo(), StringLiteral("isWebAssembly")) => - BooleanLiteral(isWasm) - - case (JSLinkingInfo(), StringLiteral("version")) => - StringLiteral(ScalaJSVersions.current) - - case _ => - JSSelect(qualifier, item) - } - } - private def transformMethodDefBody(optTarget: Option[MethodID], thisType: Type, params: List[ParamDef], jsClassCaptures: List[ParamDef], resultType: Type, body: Tree, isNoArgCtor: Boolean): (List[ParamDef], Tree) = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/standard/CoreSpec.scala b/linker/shared/src/main/scala/org/scalajs/linker/standard/CoreSpec.scala index 3c4c979adc..e5e285268f 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/standard/CoreSpec.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/standard/CoreSpec.scala @@ -96,6 +96,9 @@ final class CoreSpec private ( targetIsWebAssembly ) } + + private[linker] lazy val linkTimeProperties = new LinkTimeProperties( + semantics, esFeatures, targetIsWebAssembly) } private[linker] object CoreSpec { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkTimeProperties.scala b/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkTimeProperties.scala new file mode 100644 index 0000000000..875196c736 --- /dev/null +++ b/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkTimeProperties.scala @@ -0,0 +1,68 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.linker.standard + +import org.scalajs.ir.{Types => jstpe, Trees => js} +import org.scalajs.ir.Trees.LinkTimeProperty._ +import org.scalajs.ir.ScalaJSVersions +import org.scalajs.ir.Position.NoPosition +import org.scalajs.linker.interface.{Semantics, ESFeatures} + +private[linker] final class LinkTimeProperties ( + semantics: Semantics, + esFeatures: ESFeatures, + targetIsWebAssembly: Boolean +) { + import LinkTimeProperties._ + + private val linkTimeProperties: Map[String, LinkTimeValue] = Map( + ESVersion -> + LinkTimeInt(esFeatures.esVersion.edition), + UseECMAScript2015Semantics -> + LinkTimeBoolean(esFeatures.useECMAScript2015Semantics), + IsWebAssembly -> + LinkTimeBoolean(targetIsWebAssembly), + ProductionMode -> + LinkTimeBoolean(semantics.productionMode), + LinkerVersion -> + LinkTimeString(ScalaJSVersions.current) + ) + + def validate(name: String, tpe: jstpe.Type): Boolean = { + linkTimeProperties.get(name).exists { + case _: LinkTimeBoolean => tpe == jstpe.BooleanType + case _: LinkTimeInt => tpe == jstpe.IntType + case _: LinkTimeString => tpe == jstpe.StringType + } + } + + def transformLinkTimeProperty(prop: js.LinkTimeProperty): js.Literal = { + val value = linkTimeProperties.getOrElse(prop.name, + throw new IllegalArgumentException(s"link time property not found: '${prop.name}' of type ${prop.tpe}")) + value match { + case LinkTimeBoolean(value) => + js.BooleanLiteral(value)(prop.pos) + case LinkTimeInt(value) => + js.IntLiteral(value)(prop.pos) + case LinkTimeString(value) => + js.StringLiteral(value)(prop.pos) + } + } +} + +private[linker] object LinkTimeProperties { + sealed abstract class LinkTimeValue + final case class LinkTimeInt(value: Int) extends LinkTimeValue + final case class LinkTimeBoolean(value: Boolean) extends LinkTimeValue + final case class LinkTimeString(value: String) extends LinkTimeValue +} diff --git a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala index f797ad25a1..1ae0a8d801 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala @@ -806,6 +806,37 @@ class AnalyzerTest { assertFalse(I2barMethodInfo.isAbstractReachable) } } + + @Test + def invalidLinkTimeProperty(): AsyncResult = await { + def test(invalidLinkTimeProperty: LinkTimeProperty): Future[Unit] = { + val classDefs = Seq( + classDef("A", + kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), + methods = List( + trivialCtor("A"), + mainMethodDef(invalidLinkTimeProperty) + ) + ) + ) + + val moduleInitializer = ModuleInitializer.mainMethodWithArgs("A", "main") + + val analysis = computeAnalysis(classDefs, + moduleInitializers = List(moduleInitializer)) + + assertContainsError(s"InvalidLinkTimeProperty(${invalidLinkTimeProperty.name})", analysis) { + case InvalidLinkTimeProperty(name, tpe, _) => + name == invalidLinkTimeProperty.name && tpe == invalidLinkTimeProperty.tpe + } + } + + val results = List( + test(LinkTimeProperty("not-found")(IntType)), + test(LinkTimeProperty(LinkTimeProperty.ESVersion)(BooleanType)) // ESVersion should be IntType + ) + Future.sequence(results) + } } object AnalyzerTest { diff --git a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala index 9ab95541de..a9fbf49876 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala @@ -70,9 +70,9 @@ class LibrarySizeTest { ) testLinkedSizes( - expectedFastLinkSize = 147629, + expectedFastLinkSize = 147459, expectedFullLinkSizeWithoutClosure = 85718, - expectedFullLinkSizeWithClosure = 21625, + expectedFullLinkSizeWithClosure = 21495, classDefs, moduleInitializers = MainTestModuleInitializers ) diff --git a/project/Build.scala b/project/Build.scala index 9ba80d3b93..b4cab25a64 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2048,7 +2048,7 @@ object Build { case `default212Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 623000 to 624000, + fastLink = 622000 to 623000, fullLink = 96000 to 97000, fastLinkGz = 75000 to 79000, fullLinkGz = 25000 to 26000, @@ -2056,7 +2056,7 @@ object Build { } else { Some(ExpectedSizes( fastLink = 423000 to 424000, - fullLink = 281000 to 282000, + fullLink = 280000 to 281000, fastLinkGz = 60000 to 61000, fullLinkGz = 43000 to 44000, )) @@ -2065,7 +2065,7 @@ object Build { case `default213Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 450000 to 451000, + fastLink = 449000 to 450000, fullLink = 95000 to 96000, fastLinkGz = 58000 to 59000, fullLinkGz = 25000 to 26000, diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkingInfoTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkingInfoTest.scala index 7dbcd44d22..50fc1fe8af 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkingInfoTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkingInfoTest.scala @@ -51,4 +51,13 @@ class LinkingInfoTest { assertEquals(11, ESVersion.ES2020) assertEquals(12, ESVersion.ES2021) } + + @Test def isolatedJSLinkingInfo(): Unit = { + val linkingInfo = scala.scalajs.runtime.linkingInfo + assertEquals(Platform.isInProductionMode, linkingInfo.productionMode) + assertEquals(Platform.assumedESVersion, linkingInfo.esVersion) + assertEquals(Platform.assumedESVersion >= ESVersion.ES2015, linkingInfo.assumingES6) + assertEquals(Platform.executingInWebAssembly, linkingInfo.isWebAssembly) + assertEquals(Platform.assumedESVersion, linkingInfo.esVersion) + } } From 3763e426d3accfa90dbe9e6a3def7e5b0de80b37 Mon Sep 17 00:00:00 2001 From: Rikito Taniguchi Date: Fri, 6 Sep 2024 13:27:10 +0900 Subject: [PATCH 005/121] Emit `LinkTimeProperty` via `LinkingInfo.linkTimeProperty` APIs TLDR: - Add `scala.scalajs.LinkingInfo.linkTimeProperty` APIs that emits `LinkTimeProperty` IRs. - Deprecate `scala.scalajs.runtime.linkingInfo` in favor of `scala.scalajs.LinkingInfo` and `scala.scalajs.js.special.fileLevelThis`. - Keeping `scala.scalajs.runtime.linkingInfo` because Scala3 compiler looks like depends on the symbol of `runtime.linkingInfo` https://github.com/scala/scala3/blob/67cd3ebe3f70bb9045cc966af5b96a623a7da973/compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala#L182-L183 This commit introduces new APIs under `scala.scalajs.LinkingInfo`: `linkTimeProperty(Int|String|Boolean)`. These APIs allow us to define link-time properties in a more optimized and JS-independent way, replacing the previous `scala.scalajs.runtime.linkingInfo`. For instance, `linkTimePropertyInt("core/esVersion")` emits the IR `LinkTimeProperty("core/esVersion")(IntType)`, which will later be transformed into an `IntLiteral` by the optimizer or linker backend. `scala.scalajs.runtime.linkingInfo` is now deprecated in favor of `scala.scalajs.LinkingInfo`. For cases where `scala.scalajs.runtime.linkingInfo.fileLevelThis` was used, users should migrate to `scala.scalajs.js.special.fileLevelThis`. --- .../org/scalajs/nscplugin/GenJSCode.scala | 44 +++++++++++++++---- .../org/scalajs/nscplugin/JSDefinitions.scala | 5 +++ .../org/scalajs/nscplugin/JSPrimitives.scala | 11 +++-- .../nscplugin/test/JSGlobalScopeTest.scala | 32 +++++++++++++- .../nscplugin/test/OptimizationTest.scala | 6 +-- .../main/scala/org/scalajs/ir/Hashers.scala | 3 -- .../main/scala/org/scalajs/ir/Printers.scala | 3 -- .../scala/org/scalajs/ir/Serializers.scala | 9 ++-- .../scala/org/scalajs/ir/Transformers.scala | 2 +- .../scala/org/scalajs/ir/Traversers.scala | 2 +- .../src/main/scala/org/scalajs/ir/Trees.scala | 4 -- .../scala/org/scalajs/ir/PrintersTest.scala | 4 -- .../src/main/scala/java/lang/Character.scala | 4 +- .../src/main/scala/java/lang/ClassValue.scala | 6 +-- javalib/src/main/scala/java/lang/Double.scala | 4 +- .../scala/java/lang/FloatingPointBits.scala | 6 +-- .../src/main/scala/java/lang/Integer.scala | 4 +- javalib/src/main/scala/java/lang/Math.scala | 4 +- javalib/src/main/scala/java/lang/System.scala | 4 +- .../src/main/scala/java/lang/_String.scala | 12 ++--- javalib/src/main/scala/java/util/Random.scala | 6 +-- .../java/util/regex/PatternCompiler.scala | 20 ++++----- .../util/regex/PatternSyntaxException.scala | 1 - .../scala/scala/scalajs/LinkingInfo.scala | 24 +++++++--- .../scala/scalajs/js/special/package.scala | 2 +- .../scala/scalajs/runtime/LinkingInfo.scala | 6 ++- .../scala/scala/scalajs/runtime/package.scala | 13 +++++- .../backend/emitter/FunctionEmitter.scala | 5 --- .../backend/wasmemitter/FunctionEmitter.scala | 6 --- .../linker/checker/ClassDefChecker.scala | 2 - .../scalajs/linker/checker/IRChecker.scala | 2 - .../frontend/optimizer/OptimizerCore.scala | 2 +- project/BinaryIncompatibilities.scala | 3 ++ project/JavalibIRCleaner.scala | 17 +++++++ .../collection/mutable/ArrayBuilder.scala | 4 +- .../collection/mutable/ArrayBuilder.scala | 4 +- .../compiler/InteroperabilityTest.scala | 24 ++++++++++ .../testsuite/javalib/lang/SystemJSTest.scala | 4 +- .../testsuite/jsinterop/MiscInteropTest.scala | 23 ++++++++++ 39 files changed, 233 insertions(+), 104 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index c3dff7493b..8dfacc7a37 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -27,7 +27,15 @@ import scala.reflect.internal.Flags import org.scalajs.ir import org.scalajs.ir.{Trees => js, Types => jstpe, ClassKind, Hashers, OriginalName} -import org.scalajs.ir.Names.{LocalName, SimpleFieldName, FieldName, SimpleMethodName, MethodName, ClassName} +import org.scalajs.ir.Names.{ + LocalName, + SimpleFieldName, + FieldName, + SimpleMethodName, + MethodName, + ClassName, + BoxedStringClass +} import org.scalajs.ir.OriginalName.NoOriginalName import org.scalajs.ir.Trees.OptimizerHints import org.scalajs.ir.Version.Unversioned @@ -5202,10 +5210,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) genStatOrExpr(args(1), isStat) } - case LINKING_INFO => - // runtime.linkingInfo - js.JSLinkingInfo() - case IDENTITY_HASH_CODE => // runtime.identityHashCode(arg) val arg = genArgs1 @@ -5390,6 +5394,22 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) case UNWRAP_FROM_THROWABLE => // js.special.unwrapFromThrowable(arg) js.UnwrapFromThrowable(genArgs1) + + case LINKTIME_PROPERTY => + // LinkingInfo.linkTimePropertyXXX("...") + val arg = genArgs1 + val tpe: jstpe.Type = toIRType(tree.tpe) match { + case jstpe.ClassType(BoxedStringClass, _) => jstpe.StringType + case irType => irType + } + arg match { + case js.StringLiteral(name) => + js.LinkTimeProperty(name)(tpe) + case _ => + reporter.error(args.head.pos, + "The argument of linkTimePropertyXXX must be a String literal: \"...\"") + js.LinkTimeProperty("erroneous")(tpe) + } } } @@ -5501,8 +5521,16 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) def genSelectGet(propName: js.Tree): js.Tree = genSuperReference(propName) - def genSelectSet(propName: js.Tree, value: js.Tree): js.Tree = - js.Assign(genSuperReference(propName), value) + def genSelectSet(propName: js.Tree, value: js.Tree): js.Tree = { + val lhs = genSuperReference(propName) + lhs match { + case js.JSGlobalRef(js.JSGlobalRef.FileLevelThis) => + reporter.error(pos, + "Illegal assignment to global this.") + case _ => + } + js.Assign(lhs, value) + } def genCall(methodName: js.Tree, args: List[js.TreeOrJSSpread]): js.Tree = { @@ -6758,7 +6786,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) implicit pos: Position): js.JSGlobalRef = { propName match { case js.StringLiteral(value) => - if (js.JSGlobalRef.isValidJSGlobalRefName(value) && value != js.JSGlobalRef.FileLevelThis) { + if (js.JSGlobalRef.isValidJSGlobalRefName(value)) { if (value == "await") { global.runReporting.warning(pos, s"$actionFull of the global scope with the name '$value' is deprecated.\n" + diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala index 8214985b6a..5a46388543 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala @@ -127,6 +127,11 @@ trait JSDefinitions { lazy val Runtime_identityHashCode = getMemberMethod(RuntimePackageModule, newTermName("identityHashCode")) lazy val Runtime_dynamicImport = getMemberMethod(RuntimePackageModule, newTermName("dynamicImport")) + lazy val LinkingInfoModule = getRequiredModule("scala.scalajs.LinkingInfo") + lazy val LinkingInfo_linkTimePropertyBoolean = getMemberMethod(LinkingInfoModule, newTermName("linkTimePropertyBoolean")) + lazy val LinkingInfo_linkTimePropertyInt = getMemberMethod(LinkingInfoModule, newTermName("linkTimePropertyInt")) + lazy val LinkingInfo_linkTimePropertyString = getMemberMethod(LinkingInfoModule, newTermName("linkTimePropertyString")) + lazy val DynamicImportThunkClass = getRequiredClass("scala.scalajs.runtime.DynamicImportThunk") lazy val DynamicImportThunkClass_apply = getMemberMethod(DynamicImportThunkClass, nme.apply) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala index df5ff293db..c93709363b 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala @@ -55,8 +55,7 @@ abstract class JSPrimitives { final val CREATE_INNER_JS_CLASS = CONSTRUCTOROF + 1 // runtime.createInnerJSClass final val CREATE_LOCAL_JS_CLASS = CREATE_INNER_JS_CLASS + 1 // runtime.createLocalJSClass final val WITH_CONTEXTUAL_JS_CLASS_VALUE = CREATE_LOCAL_JS_CLASS + 1 // runtime.withContextualJSClassValue - final val LINKING_INFO = WITH_CONTEXTUAL_JS_CLASS_VALUE + 1 // runtime.linkingInfo - final val IDENTITY_HASH_CODE = LINKING_INFO + 1 // runtime.identityHashCode + final val IDENTITY_HASH_CODE = WITH_CONTEXTUAL_JS_CLASS_VALUE + 1 // runtime.identityHashCode final val DYNAMIC_IMPORT = IDENTITY_HASH_CODE + 1 // runtime.dynamicImport final val STRICT_EQ = DYNAMIC_IMPORT + 1 // js.special.strictEquals @@ -69,8 +68,9 @@ abstract class JSPrimitives { final val WRAP_AS_THROWABLE = JS_TRY_CATCH + 1 // js.special.wrapAsThrowable final val UNWRAP_FROM_THROWABLE = WRAP_AS_THROWABLE + 1 // js.special.unwrapFromThrowable final val DEBUGGER = UNWRAP_FROM_THROWABLE + 1 // js.special.debugger + final val LINKTIME_PROPERTY = DEBUGGER + 1 // LinkingInfo.linkTimePropertyXXX - final val LastJSPrimitiveCode = DEBUGGER + final val LastJSPrimitiveCode = LINKTIME_PROPERTY /** Initialize the map of primitive methods (for GenJSCode) */ def init(): Unit = initWithPrimitives(addPrimitive) @@ -109,7 +109,6 @@ abstract class JSPrimitives { addPrimitive(Runtime_createLocalJSClass, CREATE_LOCAL_JS_CLASS) addPrimitive(Runtime_withContextualJSClassValue, WITH_CONTEXTUAL_JS_CLASS_VALUE) - addPrimitive(Runtime_linkingInfo, LINKING_INFO) addPrimitive(Runtime_identityHashCode, IDENTITY_HASH_CODE) addPrimitive(Runtime_dynamicImport, DYNAMIC_IMPORT) @@ -123,6 +122,10 @@ abstract class JSPrimitives { addPrimitive(Special_wrapAsThrowable, WRAP_AS_THROWABLE) addPrimitive(Special_unwrapFromThrowable, UNWRAP_FROM_THROWABLE) addPrimitive(Special_debugger, DEBUGGER) + + addPrimitive(LinkingInfo_linkTimePropertyBoolean, LINKTIME_PROPERTY) + addPrimitive(LinkingInfo_linkTimePropertyInt, LINKTIME_PROPERTY) + addPrimitive(LinkingInfo_linkTimePropertyString, LINKTIME_PROPERTY) } def isJavaScriptPrimitive(code: Int): Boolean = diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSGlobalScopeTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSGlobalScopeTest.scala index 6d5628abf2..19d3f0f8d3 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSGlobalScopeTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSGlobalScopeTest.scala @@ -342,7 +342,7 @@ class JSGlobalScopeTest extends DirectTest with TestHelpers { "extends", "false", "finally", "for", "function", "if", "implements", "import", "in", "instanceof", "interface", "let", "new", "null", "package", "private", "protected", "public", "return", "static", - "super", "switch", "this", "throw", "true", "try", "typeof", "var", + "super", "switch", "throw", "true", "try", "typeof", "var", "void", "while", "with", "yield") for (reservedIdentifier <- reservedIdentifiers) { @@ -497,4 +497,34 @@ class JSGlobalScopeTest extends DirectTest with TestHelpers { } } + @Test + def rejectAssignmentToGlobalThis(): Unit = { + """ + import scala.scalajs.js + import scala.scalajs.js.annotation._ + + object Main { + def main(): Unit = { + js.Dynamic.global.`this` = 0 + GlobalScope.globalThis = 0 + } + } + + @js.native + @JSGlobalScope + object GlobalScope extends js.Any { + @JSName("this") + var globalThis: Any = js.native + } + """ hasErrors + s""" + |newSource1.scala:44: error: Illegal assignment to global this. + | js.Dynamic.global.`this` = 0 + | ^ + |newSource1.scala:45: error: Illegal assignment to global this. + | GlobalScope.globalThis = 0 + | ^ + """ + } + } diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala index 7e140ebd38..47f843c696 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala @@ -170,14 +170,14 @@ class OptimizationTest extends JSASTTest { // Verify the optimized emitted code for 'new js.Object' and 'new js.Array' """ import scala.scalajs.js - class A { val o = new js.Object val a = new js.Array } """. - hasNot("any reference to the global scope") { - case js.JSLinkingInfo() => + hasNot("any reference to the global scope nor loading JS constructor") { + case js.JSGlobalRef(_) => + case js.LoadJSConstructor(_) => } } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala index 2ed30d9443..e866e32fb4 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala @@ -478,9 +478,6 @@ object Hashers { mixTag(TagJSTypeOfGlobalRef) mixTree(globalRef) - case JSLinkingInfo() => - mixTag(TagJSLinkingInfo) - case Undefined() => mixTag(TagUndefined) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala index 16cb79b85d..5895bf9773 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -778,9 +778,6 @@ object Printers { print(globalRef) print(")") - case JSLinkingInfo() => - print("") - // Literals case Undefined() => diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index ca58392661..0b3d7bd6a8 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -501,9 +501,6 @@ object Serializers { writeTagAndPos(TagJSTypeOfGlobalRef) writeTree(globalRef) - case JSLinkingInfo() => - writeTagAndPos(TagJSLinkingInfo) - case Undefined() => writeTagAndPos(TagUndefined) @@ -1300,7 +1297,7 @@ object Serializers { case TagJSPrivateSelect => JSPrivateSelect(readTree(), readFieldIdent()) case TagJSSelect => - if (/* hacks.use17 */ true && buf.get(buf.position()) == TagJSLinkingInfo) { // scalastyle:ignore + if (hacks.use17 && buf.get(buf.position()) == TagJSLinkingInfo) { val jsLinkingInfo = readTree() readTree() match { case StringLiteral("productionMode") => @@ -1343,7 +1340,7 @@ object Serializers { case TagJSTypeOfGlobalRef => JSTypeOfGlobalRef(readTree().asInstanceOf[JSGlobalRef]) case TagJSLinkingInfo => - if (/* hacks.use17 */ true) { // scalastyle:ignore + if (hacks.use17) { JSObjectConstr(List( (StringLiteral("productionMode"), LinkTimeProperty(ProductionMode)(BooleanType)), (StringLiteral("esVersion"), LinkTimeProperty(ESVersion)(IntType)), @@ -2475,6 +2472,8 @@ object Serializers { assert(sourceVersion != "1.15", "source version 1.15 does not exist") val use16: Boolean = use13 || sourceVersion == "1.16" + + val use17: Boolean = use16 || sourceVersion == "1.17" } /** Names needed for hacks. */ diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala index 99f16e8755..0b9719da0f 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala @@ -222,7 +222,7 @@ object Transformers { case _:Skip | _:Debugger | _:LoadModule | _:StoreModule | _:SelectStatic | _:SelectJSNativeMember | _:LoadJSConstructor | - _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | _:JSLinkingInfo | + _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | _:Literal | _:VarRef | _:This | _:JSGlobalRef | _:LinkTimeProperty => tree } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala index 4c6fd1a355..ec39bb1086 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala @@ -221,7 +221,7 @@ object Traversers { case _:Skip | _:Debugger | _:LoadModule | _:StoreModule | _:SelectStatic | _:SelectJSNativeMember | _:LoadJSConstructor | - _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | _:JSLinkingInfo | + _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | _:Literal | _:VarRef | _:This | _:JSGlobalRef | _:LinkTimeProperty => } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index 4b36f1808e..96145a12f5 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -982,10 +982,6 @@ object Trees { val tpe = AnyType } - sealed case class JSLinkingInfo()(implicit val pos: Position) extends Tree { - val tpe = AnyType - } - // Literals /** Marker for literals. Literals are always pure. diff --git a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala index 013dc6672e..1df89dc5d8 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -807,10 +807,6 @@ class PrintersTest { assertPrintEquals("(typeof global:Foo)", JSTypeOfGlobalRef(JSGlobalRef("Foo"))) } - @Test def printJSLinkingInfo(): Unit = { - assertPrintEquals("", JSLinkingInfo()) - } - @Test def printUndefined(): Unit = { assertPrintEquals("(void 0)", Undefined()) } diff --git a/javalib/src/main/scala/java/lang/Character.scala b/javalib/src/main/scala/java/lang/Character.scala index 50c38793ac..a085f427d7 100644 --- a/javalib/src/main/scala/java/lang/Character.scala +++ b/javalib/src/main/scala/java/lang/Character.scala @@ -15,7 +15,7 @@ package java.lang import scala.annotation.{tailrec, switch} import scala.scalajs.js -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import scala.scalajs.LinkingInfo.ESVersion import java.lang.constant.Constable @@ -128,7 +128,7 @@ object Character { if (!isValidCodePoint(codePoint)) throw new IllegalArgumentException() - if (linkingInfo.esVersion >= ESVersion.ES2015) { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { js.Dynamic.global.String.fromCodePoint(codePoint).asInstanceOf[String] } else { if (codePoint < MIN_SUPPLEMENTARY_CODE_POINT) { diff --git a/javalib/src/main/scala/java/lang/ClassValue.scala b/javalib/src/main/scala/java/lang/ClassValue.scala index 94f46965e4..74f91999ca 100644 --- a/javalib/src/main/scala/java/lang/ClassValue.scala +++ b/javalib/src/main/scala/java/lang/ClassValue.scala @@ -16,14 +16,14 @@ import java.util.HashMap import scala.scalajs.js import scala.scalajs.js.annotation._ -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import scala.scalajs.LinkingInfo.ESVersion import Utils._ abstract class ClassValue[T] protected () { private val jsMap: js.Map[Class[_], T] = { - if (linkingInfo.esVersion >= ESVersion.ES2015 || js.typeOf(js.Dynamic.global.Map) != "undefined") + if (LinkingInfo.esVersion >= ESVersion.ES2015 || js.typeOf(js.Dynamic.global.Map) != "undefined") new js.Map() else null @@ -35,7 +35,7 @@ abstract class ClassValue[T] protected () { * emitting ES 2015 code, which allows to dead-code-eliminate the branches * using `HashMap`s, and therefore `HashMap` itself. */ - linkingInfo.esVersion >= ESVersion.ES2015 || jsMap != null + LinkingInfo.esVersion >= ESVersion.ES2015 || jsMap != null } /* We use a HashMap instead of an IdentityHashMap because the latter is diff --git a/javalib/src/main/scala/java/lang/Double.scala b/javalib/src/main/scala/java/lang/Double.scala index e2768f8aca..aa6e3bc8d9 100644 --- a/javalib/src/main/scala/java/lang/Double.scala +++ b/javalib/src/main/scala/java/lang/Double.scala @@ -15,7 +15,7 @@ package java.lang import java.lang.constant.{Constable, ConstantDesc} import scala.scalajs.js -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import Utils._ @@ -365,7 +365,7 @@ object Double { !isNaN(d) && !isInfinite(d) @inline def hashCode(value: scala.Double): Int = { - if (linkingInfo.isWebAssembly) + if (LinkingInfo.isWebAssembly) hashCodeForWasm(value) else FloatingPointBits.numberHashCode(value) diff --git a/javalib/src/main/scala/java/lang/FloatingPointBits.scala b/javalib/src/main/scala/java/lang/FloatingPointBits.scala index 3b3f972346..fb9b89ff93 100644 --- a/javalib/src/main/scala/java/lang/FloatingPointBits.scala +++ b/javalib/src/main/scala/java/lang/FloatingPointBits.scala @@ -20,11 +20,11 @@ import scala.scalajs.LinkingInfo.ESVersion /** Manipulating the bits of floating point numbers. */ private[lang] object FloatingPointBits { - import scala.scalajs.runtime.linkingInfo + import scala.scalajs.LinkingInfo private[this] val _areTypedArraysSupported = { // Here we use the `esVersion` test to dce the 4 subsequent tests - linkingInfo.esVersion >= ESVersion.ES2015 || { + LinkingInfo.esVersion >= ESVersion.ES2015 || { js.typeOf(global.ArrayBuffer) != "undefined" && js.typeOf(global.Int32Array) != "undefined" && js.typeOf(global.Float32Array) != "undefined" && @@ -42,7 +42,7 @@ private[lang] object FloatingPointBits { * * If we emit ES5, replace `areTypedArraysSupported` by * `_areTypedArraysSupported` so we do not calculate it multiple times. */ - linkingInfo.esVersion >= ESVersion.ES2015 || _areTypedArraysSupported + LinkingInfo.esVersion >= ESVersion.ES2015 || _areTypedArraysSupported } private val arrayBuffer = diff --git a/javalib/src/main/scala/java/lang/Integer.scala b/javalib/src/main/scala/java/lang/Integer.scala index 78267337da..8ef617e7d0 100644 --- a/javalib/src/main/scala/java/lang/Integer.scala +++ b/javalib/src/main/scala/java/lang/Integer.scala @@ -15,7 +15,7 @@ package java.lang import java.lang.constant.{Constable, ConstantDesc} import scala.scalajs.js -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import scala.scalajs.LinkingInfo.ESVersion /* This is a hijacked class. Its instances are primitive numbers. @@ -279,7 +279,7 @@ object Integer { // Intrinsic, fallback on actual code for non-literal in JS @inline def numberOfLeadingZeros(i: scala.Int): scala.Int = { - if (linkingInfo.esVersion >= ESVersion.ES2015) js.Math.clz32(i) + if (LinkingInfo.esVersion >= ESVersion.ES2015) js.Math.clz32(i) else clz32Dynamic(i) } diff --git a/javalib/src/main/scala/java/lang/Math.scala b/javalib/src/main/scala/java/lang/Math.scala index 42262255f3..cb965bb56b 100644 --- a/javalib/src/main/scala/java/lang/Math.scala +++ b/javalib/src/main/scala/java/lang/Math.scala @@ -16,7 +16,7 @@ package lang import scala.scalajs.js import js.Dynamic.{ global => g } -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import scala.scalajs.LinkingInfo.ESVersion object Math { @@ -24,7 +24,7 @@ object Math { final val PI = 3.141592653589793 @inline private def assumingES6: scala.Boolean = - linkingInfo.esVersion >= ESVersion.ES2015 + LinkingInfo.esVersion >= ESVersion.ES2015 @inline def abs(a: scala.Int): scala.Int = if (a < 0) -a else a @inline def abs(a: scala.Long): scala.Long = if (a < 0) -a else a diff --git a/javalib/src/main/scala/java/lang/System.scala b/javalib/src/main/scala/java/lang/System.scala index c7424a218a..8075a6ac70 100644 --- a/javalib/src/main/scala/java/lang/System.scala +++ b/javalib/src/main/scala/java/lang/System.scala @@ -16,7 +16,7 @@ import java.io._ import scala.scalajs.js import scala.scalajs.js.Dynamic.global -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import java.{util => ju} @@ -200,7 +200,7 @@ object System { dictSet(result, "java.vm.specification.vendor", "Oracle Corporation") dictSet(result, "java.vm.specification.name", "Java Virtual Machine Specification") dictSet(result, "java.vm.name", "Scala.js") - dictSet(result, "java.vm.version", linkingInfo.linkerVersion) + dictSet(result, "java.vm.version", LinkingInfo.linkerVersion) dictSet(result, "java.specification.version", "1.8") dictSet(result, "java.specification.vendor", "Oracle Corporation") dictSet(result, "java.specification.name", "Java Platform API Specification") diff --git a/javalib/src/main/scala/java/lang/_String.scala b/javalib/src/main/scala/java/lang/_String.scala index d5b3e546fa..d89ada96dc 100644 --- a/javalib/src/main/scala/java/lang/_String.scala +++ b/javalib/src/main/scala/java/lang/_String.scala @@ -19,7 +19,7 @@ import java.util.Comparator import scala.scalajs.js import scala.scalajs.js.annotation._ import scala.scalajs.js.JSStringOps.enableJSStringOps -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import scala.scalajs.LinkingInfo.ESVersion import java.lang.constant.{Constable, ConstantDesc} @@ -56,7 +56,7 @@ final class _String private () // scalastyle:ignore // Wasm intrinsic def codePointAt(index: Int): Int = { - if (linkingInfo.esVersion >= ESVersion.ES2015) { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { charAt(index) // bounds check this.asInstanceOf[js.Dynamic].codePointAt(index).asInstanceOf[Int] } else { @@ -164,7 +164,7 @@ final class _String private () // scalastyle:ignore @inline def endsWith(suffix: String): scala.Boolean = { - if (linkingInfo.esVersion >= ESVersion.ES2015) { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { suffix.getClass() // null check thisString.asInstanceOf[js.Dynamic].endsWith(suffix).asInstanceOf[scala.Boolean] } else { @@ -270,7 +270,7 @@ final class _String private () // scalastyle:ignore def repeat(count: Int): String = { if (count < 0) { throw new IllegalArgumentException - } else if (linkingInfo.esVersion >= ESVersion.ES2015) { + } else if (LinkingInfo.esVersion >= ESVersion.ES2015) { /* This will throw a `js.RangeError` if `count` is too large, instead of * an `OutOfMemoryError`. That's fine because the behavior of `repeat` is * not specified for `count` too large. @@ -316,7 +316,7 @@ final class _String private () // scalastyle:ignore @inline def startsWith(prefix: String): scala.Boolean = { - if (linkingInfo.esVersion >= ESVersion.ES2015) { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { prefix.getClass() // null check thisString.asInstanceOf[js.Dynamic].startsWith(prefix).asInstanceOf[scala.Boolean] } else { @@ -326,7 +326,7 @@ final class _String private () // scalastyle:ignore @inline def startsWith(prefix: String, toffset: Int): scala.Boolean = { - if (linkingInfo.esVersion >= ESVersion.ES2015) { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { prefix.getClass() // null check (toffset <= length() && toffset >= 0 && thisString.asInstanceOf[js.Dynamic].startsWith(prefix, toffset).asInstanceOf[scala.Boolean]) diff --git a/javalib/src/main/scala/java/util/Random.scala b/javalib/src/main/scala/java/util/Random.scala index 8cd654cd1f..f0452ffb0d 100644 --- a/javalib/src/main/scala/java/util/Random.scala +++ b/javalib/src/main/scala/java/util/Random.scala @@ -15,7 +15,7 @@ package java.util import scala.annotation.tailrec import scala.scalajs.js -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo class Random(seed_in: Long) extends AnyRef with java.io.Serializable { /* This class has two different implementations of seeding and computing @@ -39,7 +39,7 @@ class Random(seed_in: Long) extends AnyRef with java.io.Serializable { def setSeed(seed_in: Long): Unit = { val seed = ((seed_in ^ 0x5DEECE66DL) & ((1L << 48) - 1)) // as documented - if (linkingInfo.isWebAssembly) { + if (LinkingInfo.isWebAssembly) { this.seed = seed } else { seedHi = (seed >>> 24).toInt @@ -50,7 +50,7 @@ class Random(seed_in: Long) extends AnyRef with java.io.Serializable { @noinline protected def next(bits: Int): Int = - if (linkingInfo.isWebAssembly) nextWasm(bits) + if (LinkingInfo.isWebAssembly) nextWasm(bits) else nextJS(bits) @inline diff --git a/javalib/src/main/scala/java/util/regex/PatternCompiler.scala b/javalib/src/main/scala/java/util/regex/PatternCompiler.scala index b2f001407f..93754f24b7 100644 --- a/javalib/src/main/scala/java/util/regex/PatternCompiler.scala +++ b/javalib/src/main/scala/java/util/regex/PatternCompiler.scala @@ -30,7 +30,7 @@ import java.util.ScalaOps._ import scala.scalajs.js import scala.scalajs.js.JSStringOps.enableJSStringOps -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import scala.scalajs.LinkingInfo.ESVersion /** Compiler from Java regular expressions to JavaScript regular expressions. @@ -83,15 +83,15 @@ private[regex] object PatternCompiler { /** Cache for `Support.supportsUnicode`. */ private val _supportsUnicode = - (linkingInfo.esVersion >= ESVersion.ES2015) || featureTest("u") + (LinkingInfo.esVersion >= ESVersion.ES2015) || featureTest("u") /** Cache for `Support.supportsSticky`. */ private val _supportsSticky = - (linkingInfo.esVersion >= ESVersion.ES2015) || featureTest("y") + (LinkingInfo.esVersion >= ESVersion.ES2015) || featureTest("y") /** Cache for `Support.supportsDotAll`. */ private val _supportsDotAll = - (linkingInfo.esVersion >= ESVersion.ES2018) || featureTest("us") + (LinkingInfo.esVersion >= ESVersion.ES2018) || featureTest("us") /** Cache for `Support.supportsIndices`. */ private val _supportsIndices = @@ -107,17 +107,17 @@ private[regex] object PatternCompiler { /** Tests whether the underlying JS RegExp supports the 'u' flag. */ @inline def supportsUnicode: Boolean = - (linkingInfo.esVersion >= ESVersion.ES2015) || _supportsUnicode + (LinkingInfo.esVersion >= ESVersion.ES2015) || _supportsUnicode /** Tests whether the underlying JS RegExp supports the 'y' flag. */ @inline def supportsSticky: Boolean = - (linkingInfo.esVersion >= ESVersion.ES2015) || _supportsSticky + (LinkingInfo.esVersion >= ESVersion.ES2015) || _supportsSticky /** Tests whether the underlying JS RegExp supports the 's' flag. */ @inline def supportsDotAll: Boolean = - (linkingInfo.esVersion >= ESVersion.ES2018) || _supportsDotAll + (LinkingInfo.esVersion >= ESVersion.ES2018) || _supportsDotAll /** Tests whether the underlying JS RegExp supports the 'd' flag. */ @inline @@ -131,7 +131,7 @@ private[regex] object PatternCompiler { */ @inline def enableUnicodeCaseInsensitive: Boolean = - linkingInfo.esVersion >= ESVersion.ES2015 + LinkingInfo.esVersion >= ESVersion.ES2015 /** Tests whether features requiring \p{} and/or look-behind assertions are enabled. * @@ -140,7 +140,7 @@ private[regex] object PatternCompiler { */ @inline def enableUnicodeCharacterClassesAndLookBehinds: Boolean = - linkingInfo.esVersion >= ESVersion.ES2018 + LinkingInfo.esVersion >= ESVersion.ES2018 } import Support._ @@ -215,7 +215,7 @@ private[regex] object PatternCompiler { import InlinedHelpers._ private def codePointToString(codePoint: Int): String = { - if (linkingInfo.esVersion >= ESVersion.ES2015) { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { js.Dynamic.global.String.fromCodePoint(codePoint).asInstanceOf[String] } else { if (isBmpCodePoint(codePoint)) { diff --git a/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala b/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala index 945753d91b..e0bd4e1223 100644 --- a/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala +++ b/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala @@ -13,7 +13,6 @@ package java.util.regex import scala.scalajs.js -import scala.scalajs.runtime.linkingInfo import scala.scalajs.LinkingInfo class PatternSyntaxException(desc: String, regex: String, index: Int) diff --git a/library/src/main/scala/scala/scalajs/LinkingInfo.scala b/library/src/main/scala/scala/scalajs/LinkingInfo.scala index bf1bfa9c00..ea9d6c1a2f 100644 --- a/library/src/main/scala/scala/scalajs/LinkingInfo.scala +++ b/library/src/main/scala/scala/scalajs/LinkingInfo.scala @@ -14,8 +14,6 @@ package scala.scalajs object LinkingInfo { - import scala.scalajs.runtime.linkingInfo - /** Returns true if we are linking for production, false otherwise. * * `productionMode` is always equal to `!developmentMode`. @@ -46,7 +44,7 @@ object LinkingInfo { */ @inline def productionMode: Boolean = - linkingInfo.productionMode + linkTimePropertyBoolean("core/productionMode") /** Returns true if we are linking for development, false otherwise. * @@ -124,7 +122,7 @@ object LinkingInfo { */ @inline def esVersion: Int = - linkingInfo.esVersion + linkTimePropertyInt("core/esVersion") /** Returns true if we are assuming that the target platform supports * ECMAScript 6, false otherwise. @@ -222,7 +220,7 @@ object LinkingInfo { */ @inline def useECMAScript2015Semantics: Boolean = - linkingInfo.assumingES6 // name mismatch for historical reasons + linkTimePropertyBoolean("core/useECMAScript2015Semantics") /** Whether we are linking to WebAssembly. * @@ -256,7 +254,12 @@ object LinkingInfo { */ @inline def isWebAssembly: Boolean = - linkingInfo.isWebAssembly + linkTimePropertyBoolean("core/isWebAssembly") + + /** Version of the linker. */ + @inline + def linkerVersion: String = + linkTimePropertyString("core/linkerVersion") /** Constants for the value of `esVersion`. */ object ESVersion { @@ -326,4 +329,13 @@ object LinkingInfo { */ final val ES2021 = 12 } + + private[scalajs] def linkTimePropertyInt(name: String): Int = + throw new java.lang.Error("stub") + + private[scalajs] def linkTimePropertyBoolean(name: String): Boolean = + throw new java.lang.Error("stub") + + private[scalajs] def linkTimePropertyString(name: String): String = + throw new java.lang.Error("stub") } diff --git a/library/src/main/scala/scala/scalajs/js/special/package.scala b/library/src/main/scala/scala/scalajs/js/special/package.scala index 2ad1da9ece..64f7edd6fb 100644 --- a/library/src/main/scala/scala/scalajs/js/special/package.scala +++ b/library/src/main/scala/scala/scalajs/js/special/package.scala @@ -214,7 +214,7 @@ package object special { */ @inline def fileLevelThis: scala.Any = - scala.scalajs.runtime.linkingInfo.fileLevelThis + js.Dynamic.global.`this` /** Exact equivalent of the `debugger` keyword of JavaScript. * diff --git a/library/src/main/scala/scala/scalajs/runtime/LinkingInfo.scala b/library/src/main/scala/scala/scalajs/runtime/LinkingInfo.scala index 3dd8395202..0645b93f0f 100644 --- a/library/src/main/scala/scala/scalajs/runtime/LinkingInfo.scala +++ b/library/src/main/scala/scala/scalajs/runtime/LinkingInfo.scala @@ -15,7 +15,11 @@ package scala.scalajs.runtime import scala.scalajs.js /** Information about link-time configuration of Scala.js. */ -sealed trait LinkingInfo extends js.Object { +@deprecated( + "Use scala.scalajs.LinkingInfo instead. " + + "For fileLevelThis, use scala.scalajs.js.special.fileLevelThis.", + since = "1.18.0") +trait LinkingInfo extends js.Object { /** Version (edition) of ECMAScript that is assumed to be supported by the * runtime. * diff --git a/library/src/main/scala/scala/scalajs/runtime/package.scala b/library/src/main/scala/scala/scalajs/runtime/package.scala index 151769c2b9..d3ba4f766f 100644 --- a/library/src/main/scala/scala/scalajs/runtime/package.scala +++ b/library/src/main/scala/scala/scalajs/runtime/package.scala @@ -97,7 +97,18 @@ package object runtime { * * See [[LinkingInfo]] for details. */ - def linkingInfo: LinkingInfo = throw new Error("stub") + @deprecated( + "Use scala.scalajs.LinkingInfo instead. " + + "For fileLevelThis, use scala.scalajs.js.special.fileLevelThis.", + since = "1.18.0") + def linkingInfo: LinkingInfo = new LinkingInfo { + override val esVersion: Int = scalajs.LinkingInfo.esVersion + override val assumingES6: Boolean = scalajs.LinkingInfo.assumingES6 + override val isWebAssembly: Boolean = scalajs.LinkingInfo.isWebAssembly + override val productionMode: Boolean = scalajs.LinkingInfo.productionMode + override val linkerVersion: String = scalajs.LinkingInfo.linkerVersion + override val fileLevelThis: Any = js.special.fileLevelThis + } /** Identity hash code of an object. */ def identityHashCode(x: Object): Int = throw new Error("stub") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index 18d4272cf1..dcd45d0533 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -1247,7 +1247,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case _: Literal => true case _: This => true case _: JSNewTarget => true - case _: JSLinkingInfo => true case _: LinkTimeProperty => true // Vars (side-effect free, pure if immutable) @@ -2962,10 +2961,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case JSTypeOfGlobalRef(globalRef) => js.UnaryOp(JSUnaryOp.typeof, transformExprNoChar(globalRef)) - case JSLinkingInfo() => - throw new IllegalArgumentException( - "JSLinkingInfo was removed in 1.18.0.") - // Literals case Undefined() => js.Undefined() diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index 7babc4ee9c..88d53a47e3 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -594,7 +594,6 @@ private class FunctionEmitter private ( case t: JSObjectConstr => genJSObjectConstr(t) case t: JSGlobalRef => genJSGlobalRef(t) case t: JSTypeOfGlobalRef => genJSTypeOfGlobalRef(t) - case t: JSLinkingInfo => genJSLinkingInfo(t) case t: Closure => genClosure(t) // array @@ -2976,11 +2975,6 @@ private class FunctionEmitter private ( AnyType } - private def genJSLinkingInfo(tree: JSLinkingInfo): Type = { - throw new IllegalArgumentException( - "JSLinkingInfo is deprecated as of 1.18.0.") - } - private def genArrayLength(tree: ArrayLength): Type = { val ArrayLength(array) = tree diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index ee5cbcd719..6c4c6d5426 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -806,8 +806,6 @@ private final class ClassDefChecker(classDef: ClassDef, case JSTypeOfGlobalRef(_) => - case JSLinkingInfo() => - // Literals case ClassOf(typeRef) => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index 6f9aa2c12c..4d388be2ea 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -645,8 +645,6 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { case JSTypeOfGlobalRef(_) => - case JSLinkingInfo() => - // Literals case _: Literal => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 502af60ace..3f80a981fa 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -698,7 +698,7 @@ private[optimizer] abstract class OptimizerCore( // Trees that need not be transformed case _:Skip | _:Debugger | _:StoreModule | - _:SelectStatic | _:JSNewTarget | _:JSImportMeta | _:JSLinkingInfo | + _:SelectStatic | _:JSNewTarget | _:JSImportMeta | _:JSGlobalRef | _:JSTypeOfGlobalRef | _:Literal => tree diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 4713fe6bf8..959802dff4 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -5,6 +5,9 @@ import com.typesafe.tools.mima.core.ProblemFilters._ object BinaryIncompatibilities { val IR = Seq( + // !!! Breaking, OK in minor release + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$JSLinkingInfo"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$JSLinkingInfo$") ) val Linker = Seq( diff --git a/project/JavalibIRCleaner.scala b/project/JavalibIRCleaner.scala index 4eab2d5a6f..035732d7c4 100644 --- a/project/JavalibIRCleaner.scala +++ b/project/JavalibIRCleaner.scala @@ -394,6 +394,17 @@ final class JavalibIRCleaner(baseDirectoryURI: URI) { result } + // LinkingInfo + // Must stay in sync with the definitions in `scala.scalajs.LinkingInfo` + case IntrinsicCall(LinkingInfoClass, `esVersionMethodName`, Nil) => + LinkTimeProperty(LinkTimeProperty.ESVersion)(IntType) + + case IntrinsicCall(LinkingInfoClass, `isWebAssemblyMethodName`, Nil) => + LinkTimeProperty(LinkTimeProperty.IsWebAssembly)(BooleanType) + + case IntrinsicCall(LinkingInfoClass, `linkerVersionMethodName`, Nil) => + LinkTimeProperty(LinkTimeProperty.LinkerVersion)(StringType) + case _ => tree } @@ -655,6 +666,7 @@ object JavalibIRCleaner { private val UnionType = ClassName("scala.scalajs.js.$bar") private val UnionTypeMod = ClassName("scala.scalajs.js.$bar$") private val UnionTypeEvidence = ClassName("scala.scalajs.js.$bar$Evidence") + private val LinkingInfoClass = ClassName("scala.scalajs.LinkingInfo$") private val FunctionNClasses: IndexedSeq[ClassName] = (0 to MaxFunctionArity).map(n => ClassName(s"scala.Function$n")) @@ -699,6 +711,11 @@ object JavalibIRCleaner { private val writeReplaceMethodName = MethodName("writeReplace", Nil, ClassRef(ObjectClass)) + // LinkingInfo + private val esVersionMethodName = MethodName("esVersion", Nil, IntRef) + private val isWebAssemblyMethodName = MethodName("isWebAssembly", Nil, BooleanRef) + private val linkerVersionMethodName = MethodName("linkerVersion", Nil, ClassRef(BoxedStringClass)) + private val functionApplyMethodNames: IndexedSeq[MethodName] = { (0 to MaxFunctionArity).map { n => MethodName("apply", (1 to n).toList.map(_ => ClassRef(ObjectClass)), ClassRef(ObjectClass)) diff --git a/scalalib/overrides-2.12/scala/collection/mutable/ArrayBuilder.scala b/scalalib/overrides-2.12/scala/collection/mutable/ArrayBuilder.scala index 0e1c43c45e..fc3e0ee4a6 100644 --- a/scalalib/overrides-2.12/scala/collection/mutable/ArrayBuilder.scala +++ b/scalalib/overrides-2.12/scala/collection/mutable/ArrayBuilder.scala @@ -14,7 +14,7 @@ import scala.reflect.ClassTag import scala.runtime.BoxedUnit import scala.scalajs.js -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo /** A builder class for arrays. * @@ -37,7 +37,7 @@ object ArrayBuilder { */ @inline def make[T: ClassTag](): ArrayBuilder[T] = - if (linkingInfo.isWebAssembly) makeForWasm() + if (LinkingInfo.isWebAssembly) makeForWasm() else makeForJS() /** Implementation of `make` for JS. */ diff --git a/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala b/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala index a49ae231c2..531a6438a2 100644 --- a/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala +++ b/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala @@ -17,7 +17,7 @@ import scala.reflect.ClassTag import scala.runtime.BoxedUnit import scala.scalajs.js -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo /** A builder class for arrays. * @@ -90,7 +90,7 @@ object ArrayBuilder { */ @inline def make[T: ClassTag]: ArrayBuilder[T] = - if (linkingInfo.isWebAssembly) makeForWasm + if (LinkingInfo.isWebAssembly) makeForWasm else makeForJS /** Implementation of `make` for JS. */ diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/InteroperabilityTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/InteroperabilityTest.scala index cc8f62fdcc..d69dd148f0 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/InteroperabilityTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/InteroperabilityTest.scala @@ -741,6 +741,22 @@ class InteroperabilityTest { assertEquals(6, InteroperabilityTestLetConstGlobals_method(5)) } + @Test def accessGlobalThis(): Unit = { + import InteroperabilityTestScalaObjectContainer._ + assumeTrue(isNoModule) + assertSame(js.Math, GlobalScope.globalThis.asInstanceOf[js.Dynamic].Math) + assertSame(js.Math, GlobalScope.`this`.asInstanceOf[js.Dynamic].Math) + assertSame(js.Math, js.Dynamic.global.`this`.Math) + } + + @Test def accessGlobalThisESModule(): Unit = { + import InteroperabilityTestScalaObjectContainer._ + assumeTrue(isESModule) + assertSame(js.undefined, GlobalScope.globalThis) + assertSame(js.undefined, GlobalScope.`this`) + assertSame(js.undefined, js.Dynamic.global.`this`) + } + } object InteroperabilityTest { @@ -1154,4 +1170,12 @@ object InteroperabilityTestScalaObjectContainer { var InteroperabilityTestLetConstGlobals_variable: String = js.native def InteroperabilityTestLetConstGlobals_method(x: Int): Int = js.native } + + @js.native + @JSGlobalScope + object GlobalScope extends js.Any { + def `this`: Any = js.native + @JSName("this") + def globalThis: Any = js.native + } } diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/javalib/lang/SystemJSTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/javalib/lang/SystemJSTest.scala index 12dc332f64..b57f6f25ce 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/javalib/lang/SystemJSTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/javalib/lang/SystemJSTest.scala @@ -15,7 +15,7 @@ package org.scalajs.testsuite.javalib.lang import org.scalajs.testsuite.utils.Platform import scala.scalajs.js -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import org.junit.Test import org.junit.Assert._ @@ -179,6 +179,6 @@ class SystemJSTest { assertEquals("/", get("file.separator")) assertEquals(":", get("path.separator")) assertEquals("\n", get("line.separator")) - assertEquals(linkingInfo.linkerVersion, get("java.vm.version")) + assertEquals(LinkingInfo.linkerVersion, get("java.vm.version")) } } diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/MiscInteropTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/MiscInteropTest.scala index bb422ac84a..6e5c97a7a4 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/MiscInteropTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/MiscInteropTest.scala @@ -194,6 +194,22 @@ class MiscInteropTest { assertEquals((), js.Dynamic.global.undefined) } + @Test def typeOfGlobalThis(): Unit = { + import MiscInteropTest._ + assumeTrue(isNoModule) + assertSame("object", js.typeOf(GlobalScope.globalThis)) + assertSame("object", js.typeOf(GlobalScope.`this`)) + assertSame("object", js.typeOf(js.Dynamic.global.`this`)) + } + + @Test def accessGlobalThisESModule(): Unit = { + import MiscInteropTest._ + assumeTrue(isESModule) + assertSame("undefined", js.typeOf(GlobalScope.globalThis)) + assertSame("undefined", js.typeOf(GlobalScope.`this`)) + assertSame("undefined", js.typeOf(js.Dynamic.global.`this`)) + } + // Emitted classes @Test def meaningfulNameProperty(): Unit = { @@ -234,4 +250,11 @@ object MiscInteropTest { class SomeJSClass extends js.Object + @js.native + @JSGlobalScope + object GlobalScope extends js.Any { + def `this`: Any = js.native + @JSName("this") + def globalThis: Any = js.native + } } From 9e4905ff87e35951a5aff2d5ed1acee97bd64271 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 14 Sep 2024 16:21:52 +0200 Subject: [PATCH 006/121] Remove unused ReplaceWithVarRef#longOpTree Discovered while attempting to run the IRChecker post optimizer. --- .../frontend/optimizer/OptimizerCore.scala | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 3f80a981fa..944c8ae079 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -423,7 +423,7 @@ private[optimizer] abstract class OptimizerCore( val (newName, newOriginalName) = freshLocalName(name, originalName, mutable = false) val localDef = LocalDef(RefinedType(AnyType), mutable = false, - ReplaceWithVarRef(newName, newSimpleState(UsedAtLeastOnce), None)) + ReplaceWithVarRef(newName, newSimpleState(UsedAtLeastOnce))) val bodyScope = scope.withEnv(scope.env.withLocalDef(name, localDef)) val newBody = if (isWasm) { @@ -448,7 +448,7 @@ private[optimizer] abstract class OptimizerCore( val (newName, newOriginalName) = freshLocalName(name, originalName, mutable = false) val localDef = LocalDef(RefinedType(AnyType), true, - ReplaceWithVarRef(newName, newSimpleState(UsedAtLeastOnce), None)) + ReplaceWithVarRef(newName, newSimpleState(UsedAtLeastOnce))) val newHandler = { val handlerScope = scope.withEnv(scope.env.withLocalDef(name, localDef)) transform(handler, isStat)(handlerScope) @@ -769,7 +769,7 @@ private[optimizer] abstract class OptimizerCore( def addCaptureParam(newName: LocalName): LocalDef = { val newOriginalName = originalNameForFresh(paramName, originalName, newName) - val replacement = ReplaceWithVarRef(newName, newSimpleState(Unused), None) + val replacement = ReplaceWithVarRef(newName, newSimpleState(Unused)) val localDef = LocalDef(tcaptureValue.tpe, mutable, replacement) val localIdent = LocalIdent(newName)(ident.pos) val newParamDef = ParamDef(localIdent, newOriginalName, tcaptureValue.tpe.base, mutable)(paramDef.pos) @@ -790,7 +790,7 @@ private[optimizer] abstract class OptimizerCore( case PreTransLit(literal) => captureParamLocalDefs += paramName -> LocalDef(tcaptureValue.tpe, false, ReplaceWithConstant(literal)) - case PreTransLocalDef(LocalDef(_, /* mutable = */ false, ReplaceWithVarRef(captureName, _, _))) => + case PreTransLocalDef(LocalDef(_, /* mutable = */ false, ReplaceWithVarRef(captureName, _))) => captureParamLocalDefsForVarRefs.get(captureName).fold[Unit] { captureParamLocalDefsForVarRefs += captureName -> addCaptureParam(captureName) } { prevLocalDef => @@ -1618,7 +1618,7 @@ private[optimizer] abstract class OptimizerCore( val LocalDef(tpe, mutable, replacement) = localDef val (name, used) = (replacement: @unchecked) match { - case ReplaceWithVarRef(name, used, _) => + case ReplaceWithVarRef(name, used) => (name, used) case ReplaceWithRecordVarRef(name, _, used, _) => (name, used) @@ -5111,7 +5111,7 @@ private[optimizer] abstract class OptimizerCore( */ localNameAllocator.reserve(name) - val replacement = ReplaceWithVarRef(name, newSimpleState(Unused), None) + val replacement = ReplaceWithVarRef(name, newSimpleState(Unused)) val localDef = LocalDef(RefinedType(ptpe), mutable, replacement) name -> localDef } @@ -5332,7 +5332,7 @@ private[optimizer] abstract class OptimizerCore( val (newName, newOriginalName) = freshLocalName(name, originalName, mutable) - val replacement = ReplaceWithVarRef(newName, newSimpleState(Unused), None) + val replacement = ReplaceWithVarRef(newName, newSimpleState(Unused)) val localDef = LocalDef(RefinedType(ptpe), mutable, replacement) val localIdent = LocalIdent(newName)(ident.pos) val newParamDef = ParamDef(localIdent, newOriginalName, ptpe, mutable)(paramDef.pos) @@ -5439,7 +5439,7 @@ private[optimizer] abstract class OptimizerCore( (ReplaceWithRecordVarRef(newName, recordType, used, cancelFun), value.tpe) case None => - (ReplaceWithVarRef(newName, used, None), tpe) + (ReplaceWithVarRef(newName, used), tpe) } val localDef = LocalDef(refinedType, mutable, replacement) @@ -5475,7 +5475,7 @@ private[optimizer] abstract class OptimizerCore( case PreTransTree(VarRef(LocalIdent(refName)), _) if !localIsMutable(refName) => buildInner(LocalDef(value.tpe, false, - ReplaceWithVarRef(refName, newSimpleState(UsedAtLeastOnce), None)), cont) + ReplaceWithVarRef(refName, newSimpleState(UsedAtLeastOnce))), cont) case _ => withDedicatedVar(value.tpe) @@ -5832,7 +5832,7 @@ private[optimizer] object OptimizerCore { } def newReplacement(implicit pos: Position): Tree = this.replacement match { - case ReplaceWithVarRef(name, used, _) => + case ReplaceWithVarRef(name, used) => used.value = used.value.inc VarRef(LocalIdent(name))(tpe.base) @@ -5936,8 +5936,7 @@ private[optimizer] object OptimizerCore { private sealed abstract class LocalDefReplacement private final case class ReplaceWithVarRef(name: LocalName, - used: SimpleState[IsUsed], - longOpTree: Option[() => Tree]) extends LocalDefReplacement + used: SimpleState[IsUsed]) extends LocalDefReplacement private final case class ReplaceWithRecordVarRef(name: LocalName, recordType: RecordType, @@ -6121,7 +6120,7 @@ private[optimizer] object OptimizerCore { localDef.replacement) def isAlreadyUsed: Boolean = (localDef.replacement: @unchecked) match { - case ReplaceWithVarRef(_, used, _) => used.value.isUsed + case ReplaceWithVarRef(_, used) => used.value.isUsed case ReplaceWithRecordVarRef(_, _, used, _) => used.value.isUsed } } From ee1015840400b12972bea6f0582ded654e8ca509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 24 Aug 2024 13:36:19 +0200 Subject: [PATCH 007/121] Check that constructor calls follow a chaining discipline. * The constructor of `jl.Object` cannot call any other constructor. * Every other constructor must call exactly one other constructor, on `this`, either from the same class or the direct superclass. There cannot be any other call to constructors. These restrictions are also enforced by the JVM. Technically we are more restrictive because the JVM allows different code paths, each having a single constructor call, whereas we require exactly one at the top level. This is fine because both Scala and Java, as languages, enforce that more restrictive property. Unfortunately, we used to generate an illegal super constructor call in the synthetic classes that extend `DynamicImportThunk`. We patch them with a deserialization hack. In this commit, we do not yet change the compiler to fix the above. --- .../scala/org/scalajs/ir/Serializers.scala | 33 ++++- .../linker/checker/ClassDefChecker.scala | 124 ++++++++++++++---- .../org/scalajs/linker/AnalyzerTest.scala | 10 +- .../org/scalajs/linker/BaseLinkerTest.scala | 2 +- .../org/scalajs/linker/IRCheckerTest.scala | 2 +- .../org/scalajs/linker/OptimizerTest.scala | 4 +- .../linker/checker/ClassDefCheckerTest.scala | 86 +++++++++++- .../linker/testutils/TestIRBuilder.scala | 23 ++-- 8 files changed, 241 insertions(+), 43 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index 0b3d7bd6a8..6d4c315b94 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -41,6 +41,10 @@ object Serializers { */ final val IRMagicNumber = 0xCAFE4A53 + // For deserialization hack + private final val DynamicImportThunkClass = + ClassName("scala.scalajs.runtime.DynamicImportThunk") + def serialize(stream: OutputStream, classDef: ClassDef): Unit = { new Serializer().serialize(stream, classDef) } @@ -1042,6 +1046,7 @@ object Serializers { private[this] var enclosingClassName: ClassName = _ private[this] var thisTypeForHack: Option[Type] = None + private[this] var patchDynamicImportThunkSuperCtorCall: Boolean = false def deserializeEntryPointsInfo(): EntryPointsInfo = { hacks = new Hacks(sourceVersion = readHeader()) @@ -1218,9 +1223,24 @@ object Serializers { case TagApply => Apply(readApplyFlags(), readTree(), readMethodIdent(), readTrees())( readType()) + case TagApplyStatically => - ApplyStatically(readApplyFlags(), readTree(), readClassName(), - readMethodIdent(), readTrees())(readType()) + val flags = readApplyFlags() + val receiver = readTree() + val className0 = readClassName() + val method = readMethodIdent() + val args = readTrees() + val tpe = readType() + + val className = { + if (patchDynamicImportThunkSuperCtorCall && method.name.isConstructor) + DynamicImportThunkClass + else + className0 + } + + ApplyStatically(flags, receiver, className, method, args)(tpe) + case TagApplyStatic => ApplyStatic(readApplyFlags(), readClassName(), readMethodIdent(), readTrees())(readType()) @@ -1510,6 +1530,15 @@ object Serializers { val superClass = readOptClassIdent() val parents = readClassIdents() + if (/* hacks.use17 &&*/ kind.isClass) { // scalastyle:ignore + /* In 1.18, we started enforcing the constructor chaining discipline. + * Unfortunately, we used to generate a wrong super constructor call in + * synthetic classes extending `DynamicImportThunk`, so we patch them. + */ + patchDynamicImportThunkSuperCtorCall = + superClass.exists(_.name == DynamicImportThunkClass) + } + /* jsSuperClass is not hacked like in readMemberDef.bodyHack5. The * compilers before 1.6 always use a simple VarRef() as jsSuperClass, * when there is one, so no hack is required. diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index 6c4c6d5426..958845a90d 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -236,7 +236,7 @@ private final class ClassDefChecker(classDef: ClassDef, } private def checkMethodDef(methodDef: MethodDef): Unit = withPerMethodState { - val MethodDef(flags, MethodIdent(name), _, params, _, body) = methodDef + val MethodDef(flags, MethodIdent(name), _, params, _, optBody) = methodDef implicit val ctx = ErrorContext(methodDef) val namespace = flags.namespace @@ -251,7 +251,7 @@ private final class ClassDefChecker(classDef: ClassDef, if (!methods(namespace.ordinal).add(name)) reportError(i"duplicate ${namespace.prefixString}method '$name'") - if (body.isEmpty && namespace != MemberNamespace.Public) + if (optBody.isEmpty && namespace != MemberNamespace.Public) reportError("Abstract methods may only be in the public namespace") // ClassInitializer @@ -299,11 +299,17 @@ private final class ClassDefChecker(classDef: ClassDef, } // Body - val thisType = if (static) NoType else instanceThisType - val bodyEnv = Env.fromParams(params) - .withThisType(thisType) - .withInConstructor(isConstructor) - body.foreach(checkTree(_, bodyEnv)) + for (body <- optBody) { + val thisType = if (static) NoType else instanceThisType + val bodyEnv = Env.fromParams(params) + .withThisType(thisType) + .withInConstructor(isConstructor) + + if (isConstructor) + checkConstructorBody(body, bodyEnv) + else + checkTree(body, bodyEnv) + } } private def checkJSConstructorDef(ctorDef: JSConstructorDef): Unit = withPerMethodState { @@ -324,14 +330,10 @@ private final class ClassDefChecker(classDef: ClassDef, .withHasNewTarget(true) .withInConstructor(true) - val envJustBeforeSuper = body.beforeSuper.foldLeft(startEnv) { (prevEnv, stat) => - checkTree(stat, prevEnv) - } + val envJustBeforeSuper = checkBlockStats(body.beforeSuper, startEnv) checkTreeOrSpreads(body.superCall.args, envJustBeforeSuper) val envJustAfterSuper = envJustBeforeSuper.withThisType(instanceThisType) - body.afterSuper.foldLeft(envJustAfterSuper) { (prevEnv, stat) => - checkTree(stat, prevEnv) - } + checkBlockStats(body.afterSuper, envJustAfterSuper) } private def checkJSMethodDef(methodDef: JSMethodDef): Unit = withPerMethodState { @@ -510,6 +512,80 @@ private final class ClassDefChecker(classDef: ClassDef, } } + private def checkConstructorBody(body: Tree, bodyEnv: Env): Unit = { + /* If the enclosing class is `jl.Object`, the `body` cannot contain any + * delegate constructor call. + * + * Otherwise: + * + * - Let `stats` be the list of flattened statements in `body`. + * - There exists a unique `stat` in the `stats` list that is an + * `ApplyStatically(_, This(), cls, someConstructor, args)`, called the + * delegate constructor call. + * - There is no such `ApplyStatically` anywhere else in the body. + * - `cls` must be the enclosing class or its direct superclass. + * + * After the optimizer, there may be no delegate constructor call at all. + * This frequently happens as the optimizer inlines super constructor + * calls. If there is one, `cls` can be any class (it must still be some + * class in the superclass chain for the types to align, but this is not + * checked here). + */ + + implicit val ctx = ErrorContext(body) + + val bodyStats = body match { + case Block(stats) => stats + case Skip() => Nil + case _ => body :: Nil + } + + if (isJLObject) { + checkBlockStats(bodyStats, bodyEnv) + } else { + val (beforeDelegateCtor, rest) = bodyStats.span { + case ApplyStatically(_, This(), _, MethodIdent(ctor), _) => + !ctor.isConstructor + case _ => + true + } + + if (rest.isEmpty) { + if (!postOptimizer) + reportError(i"Constructor must contain a delegate constructor call") + + checkBlockStats(bodyStats, bodyEnv) + } else { + val (delegateCtorCall: ApplyStatically) :: afterDelegateCtor = rest + val ApplyStatically(_, receiver, cls, MethodIdent(ctor), args) = delegateCtorCall + + val envJustBeforeDelegate = checkBlockStats(beforeDelegateCtor, bodyEnv) + + checkApplyArgs(ctor, args, envJustBeforeDelegate) + + checkTree(receiver, envJustBeforeDelegate) // check that the This itself is valid + + if (!postOptimizer) { + if (!(cls == classDef.className || classDef.superClass.exists(_.name == cls))) { + implicit val ctx = ErrorContext(delegateCtorCall) + reportError( + i"Invalid target class $cls for delegate constructor call; " + + i"expected ${classDef.className}" + + classDef.superClass.fold("")(s => i" or ${s.name}")) + } + } + + checkBlockStats(afterDelegateCtor, envJustBeforeDelegate) + } + } + } + + private def checkBlockStats(stats: List[Tree], env: Env): Env = { + stats.foldLeft(env) { (prevEnv, stat) => + checkTree(stat, prevEnv) + } + } + private def checkTreeOrSpreads(trees: List[TreeOrJSSpread], env: Env): Unit = { trees.foreach { case JSSpread(items) => checkTree(items, env) @@ -520,15 +596,19 @@ private final class ClassDefChecker(classDef: ClassDef, private def checkTrees(trees: List[Tree], env: Env): Unit = trees.foreach(checkTree(_, env)) + private def checkApplyArgs(methodName: MethodName, args: List[Tree], env: Env)( + implicit ctx: ErrorContext): Unit = { + val paramRefsCount = methodName.paramTypeRefs.size + if (args.size != paramRefsCount) + reportError(i"Arity mismatch: $paramRefsCount expected but ${args.size} found") + checkTrees(args, env) + } + private def checkTree(tree: Tree, env: Env): Env = { implicit val ctx = ErrorContext(tree) - def checkApplyGeneric(methodName: MethodName, args: List[Tree]): Unit = { - val paramRefsCount = methodName.paramTypeRefs.size - if (args.size != paramRefsCount) - reportError(i"Arity mismatch: $paramRefsCount expected but ${args.size} found") - checkTrees(args, env) - } + def checkApplyGeneric(methodName: MethodName, args: List[Tree]): Unit = + checkApplyArgs(methodName, args, env) val newEnv = tree match { case VarDef(ident, _, vtpe, mutable, _) => @@ -545,9 +625,7 @@ private final class ClassDefChecker(classDef: ClassDef, case Skip() => case Block(stats) => - stats.foldLeft(env) { (prevEnv, stat) => - checkTree(stat, prevEnv) - } + checkBlockStats(stats, env) case Labeled(label, _, body) => checkDeclareLabel(label) @@ -645,6 +723,8 @@ private final class ClassDefChecker(classDef: ClassDef, checkApplyGeneric(method, args) case ApplyStatically(_, receiver, _, MethodIdent(method), args) => + if (method.isConstructor) + reportError(i"Illegal constructor call") checkTree(receiver, env) checkApplyGeneric(method, args) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala index 1ae0a8d801..924cd3cabe 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala @@ -192,7 +192,7 @@ class AnalyzerTest { val classDefs = Seq( classDef("A", kind = kindSub, superClass = Some("B"), - methods = requiredMethods("A", kindSub), + methods = requiredMethods("A", kindSub, "B"), jsConstructor = requiredJSConstructor(kindSub)), classDef("B", kind = kindBase, superClass = validParentForKind(kindBase), @@ -321,7 +321,7 @@ class AnalyzerTest { )))(EOH, UNV) )), classDef("B", superClass = Some("A"), - methods = List(trivialCtor("B"))) + methods = List(trivialCtor("B", "A"))) ) val analysis = computeAnalysis(classDefs, @@ -350,7 +350,7 @@ class AnalyzerTest { methods = List(trivialCtor("A"))), classDef("B", superClass = Some("A"), methods = List( - trivialCtor("B"), + trivialCtor("B", "A"), MethodDef(EMF, fooMethodName, NON, Nil, IntType, Some(int(5)))(EOH, UNV) )) ) @@ -771,12 +771,12 @@ class AnalyzerTest { )), classDef("B", superClass = Some("A"), interfaces = List("I2"), methods = List( - trivialCtor("B"), + trivialCtor("B", "A"), MethodDef(EMF, fooMethodName, NON, Nil, IntType, Some(int(5)))(EOH, UNV) )), classDef("C", superClass = Some("B"), methods = List( - trivialCtor("C"), + trivialCtor("C", "B"), MethodDef(EMF, barMethodName, NON, Nil, IntType, Some(int(5)))(EOH, UNV) )) ) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/BaseLinkerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/BaseLinkerTest.scala index 2f4c74f00a..1997d0f789 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/BaseLinkerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/BaseLinkerTest.scala @@ -55,7 +55,7 @@ class BaseLinkerTest { "Sub", kind = ClassKind.Class, superClass = Some("Base"), - methods = List(trivialCtor("Sub")) + methods = List(trivialCtor("Sub", "Base")) ), mainTestClassDef( consoleLog(Apply(EAF, New("Sub", NoArgConstructorName, Nil), fooName, Nil)(IntType)) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala index 9b30403634..fd2faebe8b 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala @@ -133,7 +133,7 @@ class IRCheckerTest { superClass = Some("C"), interfaces = List("B"), methods = List( - trivialCtor("D"), + trivialCtor("D", "C"), MethodDef( EMF.withNamespace(MemberNamespace.PublicStatic), testMethodName, diff --git a/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala index d81ce35df5..3854cd89ad 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala @@ -503,10 +503,12 @@ class OptimizerTest { // def this() = { // this.x = null // this.y = 5 + // this.jl.Object::() // } MethodDef(EMF.withNamespace(Constructor), NoArgConstructorName, NON, Nil, NoType, Some(Block( Assign(Select(thisFor("Foo"), FieldName("Foo", "x"))(witnessType), Null()), - Assign(Select(thisFor("Foo"), FieldName("Foo", "y"))(IntType), int(5)) + Assign(Select(thisFor("Foo"), FieldName("Foo", "y"))(IntType), int(5)), + trivialSuperCtorCall("Foo") )))(EOH, UNV), // def method(): Int = this.y diff --git a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala index 62fbd15bff..db1d5ef955 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala @@ -109,7 +109,7 @@ class ClassDefCheckerTest { val name = if (kind == ClassKind.HijackedClass) BoxedIntegerClass else ClassName("A") assertError( classDef(name, kind = kind, - methods = requiredMethods(name, kind), + methods = requiredMethods(name, kind, parentClassName = name), jsConstructor = requiredJSConstructor(kind)), "missing superClass") } @@ -302,6 +302,87 @@ class ClassDefCheckerTest { ) } + @Test + def missingDelegateCtorCall(): Unit = { + val ctorFlags = EMF.withNamespace(MemberNamespace.Constructor) + + assertError( + classDef( + "Foo", superClass = Some(ObjectClass), + methods = List( + MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, NoType, + Some(int(5)))(EOH, UNV) + ) + ), + "Constructor must contain a delegate constructor call") + } + + @Test + def invalidDelegateCtorCallTarget(): Unit = { + val ctorFlags = EMF.withNamespace(MemberNamespace.Constructor) + + assertError( + classDef( + "Foo", superClass = Some(ObjectClass), + methods = List( + MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, NoType, Some { + ApplyStatically(EAF.withConstructor(true), thisFor("Foo"), + "Bar", NoArgConstructorName, Nil)(NoType) + })(EOH, UNV) + ) + ), + "Invalid target class Bar for delegate constructor call; " + + "expected Foo or java.lang.Object") + } + + @Test + def illegalCtorCall(): Unit = { + val ctorFlags = EMF.withNamespace(MemberNamespace.Constructor) + + def ctorCall(receiver: Tree): ApplyStatically = { + ApplyStatically(EAF.withConstructor(true), receiver, + ObjectClass, NoArgConstructorName, Nil)(NoType) + } + + val thiz = thisFor("Foo") + + assertError( + classDef( + "Foo", superClass = Some(ObjectClass), + methods = List( + MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, NoType, Some(Block( + ctorCall(thiz), + ctorCall(Null()) + )))(EOH, UNV) + ) + ), + "Illegal constructor call") + + assertError( + classDef( + "Foo", superClass = Some(ObjectClass), + methods = List( + MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, NoType, Some(Block( + ctorCall(thiz), + If(BooleanLiteral(true), ctorCall(thiz), Skip())(NoType) + )))(EOH, UNV) + ) + ), + "Illegal constructor call") + + assertError( + classDef( + "Foo", superClass = Some(ObjectClass), + methods = List( + trivialCtor("Foo"), + MethodDef(EMF, m("foo", Nil, V), NON, Nil, NoType, Some(Block( + ctorCall(thiz) + )))(EOH, UNV) + ) + ), + "Illegal constructor call") + } + @Test def thisType(): Unit = { def testThisTypeError(static: Boolean, expr: Tree, expectedMsg: String): Unit = { @@ -370,8 +451,7 @@ class ClassDefCheckerTest { def storeModule(): Unit = { val ctorFlags = EMF.withNamespace(MemberNamespace.Constructor) - val superCtorCall = ApplyStatically(EAF.withConstructor(true), thisFor("Foo"), - ObjectClass, NoArgConstructorName, Nil)(NoType) + val superCtorCall = trivialSuperCtorCall("Foo") assertError( classDef( diff --git a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala index de55615fcf..42c8736b02 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala @@ -89,13 +89,18 @@ object TestIRBuilder { def trivialCtor(enclosingClassName: ClassName, parentClassName: ClassName = ObjectClass): MethodDef = { val flags = MemberFlags.empty.withNamespace(MemberNamespace.Constructor) MethodDef(flags, MethodIdent(NoArgConstructorName), NON, Nil, NoType, - Some(ApplyStatically(EAF.withConstructor(true), - thisFor(enclosingClassName), - parentClassName, MethodIdent(NoArgConstructorName), - Nil)(NoType)))( + Some(trivialSuperCtorCall(enclosingClassName, parentClassName)))( EOH, UNV) } + def trivialSuperCtorCall(enclosingClassName: ClassName, + parentClassName: ClassName = ObjectClass): ApplyStatically = { + ApplyStatically(EAF.withConstructor(true), + thisFor(enclosingClassName), + parentClassName, MethodIdent(NoArgConstructorName), + Nil)(NoType) + } + def trivialJSCtor: JSConstructorDef = { JSConstructorDef(JSCtorFlags, Nil, None, JSConstructorBody(Nil, JSSuperConstructorCall(Nil), Undefined() :: Nil))( @@ -141,10 +146,12 @@ object TestIRBuilder { ) } - def requiredMethods(className: ClassName, - classKind: ClassKind): List[MethodDef] = { - if (classKind == ClassKind.ModuleClass) List(trivialCtor(className)) - else Nil + def requiredMethods(className: ClassName, classKind: ClassKind, + parentClassName: ClassName = ObjectClass): List[MethodDef] = { + if (classKind == ClassKind.ModuleClass) + List(trivialCtor(className, parentClassName)) + else + Nil } def requiredJSConstructor(classKind: ClassKind): Option[JSConstructorDef] = { From 20e334bfc5843ce64a1e3cdd973b592cd9756369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 24 Aug 2024 13:44:51 +0200 Subject: [PATCH 008/121] Fix the super constructor call to `DynamicImportThunk`. And only enable the corresponding deserialization hack for IR < 1.18. --- .../src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala | 2 +- ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala index 2a59a931bc..592b9aa381 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala @@ -475,7 +475,7 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) */ val superCtorCall = gen.mkMethodCall( Super(clsSym, tpnme.EMPTY), - ObjectClass.primaryConstructor, Nil, Nil) + DynamicImportThunkClass.primaryConstructor, Nil, Nil) // class $anon extends DynamicImportThunk val clsDef = ClassDef(clsSym, List( diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index 6d4c315b94..15a2e251ec 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -1530,7 +1530,7 @@ object Serializers { val superClass = readOptClassIdent() val parents = readClassIdents() - if (/* hacks.use17 &&*/ kind.isClass) { // scalastyle:ignore + if (hacks.use17 && kind.isClass) { /* In 1.18, we started enforcing the constructor chaining discipline. * Unfortunately, we used to generate a wrong super constructor call in * synthetic classes extending `DynamicImportThunk`, so we patch them. From 3bacf85541389b0f98a8263964770738faf8ca1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 7 Oct 2024 15:27:40 +0200 Subject: [PATCH 009/121] Emit lifted methods and lambda methods as static if possible. For all lifted methods and methods that contain the body of lambdas, we analyze whether they (transitively) refer to `this`. If not, we emit them as static. The JVM backend does a similar analysis in the `delambdafy` phase, which comes after our backend. As a side effect, this solves an issue with lifted local methods coming from arguments to a super constructor call. They would previously result in references to `this` from the super constructor call arguments, which we will not consider as valid IR anymore. Since Scala lexical scoping rules ensure that they do not actually refer to `this`, they will now always be compiled as static. --- .../org/scalajs/nscplugin/GenJSCode.scala | 147 ++++++++++++++++-- 1 file changed, 134 insertions(+), 13 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 8dfacc7a37..714fc7bd1b 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -248,6 +248,133 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) )(withNewLocalNameScope(body)) } + // Global state for tracking methods that reference `this` ----------------- + + /* scalac generates private instance methods for + * a) local defs and + * b) anonymous functions. + * + * In many cases, these methods do not need access to the enclosing `this` + * value. For every other local variable, `lambdalift` only adds them as + * arguments when they are needed; but `this` is always implicitly added + * because all such methods are instance methods. + * + * We run a separate analysis to figure out which of those methods actually + * need their `this` value. We compile those that do not as static methods, + * as if they were `isStaticMember`. + * + * The `delambdafy` phase of scalac performs a similar analysis, although + * as it runs after our backend (for other reasons), we do not benefit from + * it. Our analysis is much simpler because it deals with local defs and + * anonymous functions in a unified way. + * + * Performing this analysis is particularly important for lifted methods + * that appear within arguments to a super constructor call. The lexical + * scope of Scala guarantees that they cannot use `this`, but if they are + * compiled as instance methods, they force the constructor to leak the + * `this` value before the super constructor call, which is invalid. + * While most of the time this analysis is only an optimization, in those + * (rare) situations it is required for correctness. See for example + * the partest `run/t8733.scala`. + */ + + private var statifyCandidateMethodsThatReferenceThis: scala.collection.Set[Symbol] = null + + /** Is that given method a statify-candidate? + * + * If a method is a statify-candidate, we will analyze whether it actually + * needs its `this` value. If it does not, we will compile it as a static + * method in the IR. + * + * A method is a statify-candidate if it is a lifted method (a local def) + * or the target of an anonymous function. + * + * TODO Currently we also require that the method owner not be a JS type. + * We should relax that restriction in the future. + */ + private def isStatifyCandidate(sym: Symbol): Boolean = + (sym.isLiftedMethod || sym.isDelambdafyTarget) && !isJSType(sym.owner) + + /** Do we compile the given method as a static method in the IR? + * + * This is true if one of the following is true: + * + * - It is `isStaticMember`, or + * - It is a statify-candidate method and it does not reference `this`. + */ + private def compileAsStaticMethod(sym: Symbol): Boolean = { + sym.isStaticMember || { + isStatifyCandidate(sym) && + !statifyCandidateMethodsThatReferenceThis.contains(sym) + } + } + + /** Finds all statify-candidate methods that reference `this`, inspired by Delambdafy. */ + private final class ThisReferringMethodsTraverser extends Traverser { + // the set of statify-candidate methods that directly refer to `this` + private val roots = mutable.Set.empty[Symbol] + + // for each statify-candidate method `m`, the set of statify-candidate methods that call `m` + private val methodReferences = mutable.Map.empty[Symbol, mutable.Set[Symbol]] + + def methodReferencesThisIn(tree: Tree): collection.Set[Symbol] = { + traverse(tree) + propagateReferences() + } + + private def addRoot(symbol: Symbol): Unit = + roots += symbol + + private def addReference(from: Symbol, to: Symbol): Unit = + methodReferences.getOrElseUpdate(to, mutable.Set.empty) += from + + private def propagateReferences(): collection.Set[Symbol] = { + val result = mutable.Set.empty[Symbol] + + def rec(symbol: Symbol): Unit = { + // mark `symbol` as referring to `this` + if (result.add(symbol)) { + // `symbol` was not yet in the set; propagate further + methodReferences.getOrElse(symbol, Nil).foreach(rec(_)) + } + } + + roots.foreach(rec(_)) + + result + } + + private var currentMethod: Symbol = NoSymbol + + override def traverse(tree: Tree): Unit = tree match { + case _: DefDef => + if (isStatifyCandidate(tree.symbol)) { + currentMethod = tree.symbol + super.traverse(tree) + currentMethod = NoSymbol + } else { + // No need to traverse other methods; we always assume they refer to `this`. + } + case Function(_, Apply(target, _)) => + /* We don't drill into functions because at this phase they will always refer to `this`. + * They will be of the form {(args...) => this.anonfun(args...)} + * but we do need to make note of the lifted body method in case it refers to `this`. + */ + if (currentMethod.exists) + addReference(from = currentMethod, to = target.symbol) + case Apply(sel @ Select(This(_), _), args) if isStatifyCandidate(sel.symbol) => + if (currentMethod.exists) + addReference(from = currentMethod, to = sel.symbol) + super.traverseTrees(args) + case This(_) => + // Note: tree.symbol != enclClass is possible if it is a module loaded with genLoadModule + if (currentMethod.exists && tree.symbol == currentMethod.enclClass) + addRoot(currentMethod) + case _ => + super.traverse(tree) + } + } + // Global class generation state ------------------------------------------- private val lazilyGeneratedAnonClasses = mutable.Map.empty[Symbol, ClassDef] @@ -306,6 +433,9 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) */ override def apply(cunit: CompilationUnit): Unit = { try { + statifyCandidateMethodsThatReferenceThis = + new ThisReferringMethodsTraverser().methodReferencesThisIn(cunit.body) + def collectClassDefs(tree: Tree): List[ClassDef] = { tree match { case EmptyTree => Nil @@ -455,6 +585,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) lazilyGeneratedAnonClasses.clear() generatedStaticForwarderClasses.clear() generatedClasses.clear() + statifyCandidateMethodsThatReferenceThis = null pos2irPosCache.clear() } } @@ -1144,16 +1275,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) assert(moduleClass.isModuleClass, moduleClass) - val hasAnyExistingPublicStaticMethod = - existingMethods.exists(_.flags.namespace == js.MemberNamespace.PublicStatic) - if (hasAnyExistingPublicStaticMethod) { - reporter.error(pos, - "Unexpected situation: found existing public static methods in " + - s"the class ${moduleClass.fullName} while trying to generate " + - "static forwarders for its companion object. " + - "Please report this as a bug in Scala.js.") - } - def listMembersBasedOnFlags = { // Copy-pasted from BCodeHelpers. val ExcludedForwarderFlags: Long = { @@ -1964,7 +2085,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } else { val resultIRType = toIRType(sym.tpe.resultType) val namespace = { - if (sym.isStaticMember) { + if (compileAsStaticMethod(sym)) { if (sym.isPrivate) js.MemberNamespace.PrivateStatic else js.MemberNamespace.PublicStatic } else { @@ -3453,7 +3574,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) genApplyJSClassMethod(genExpr(receiver), sym, genActualArgs(sym, args)) } else if (sym.hasAnnotation(JSNativeAnnotation)) { genJSNativeMemberCall(tree, isStat) - } else if (sym.isStaticMember) { + } else if (compileAsStaticMethod(sym)) { if (sym.isMixinConstructor) { /* Do not emit a call to the $init$ method of JS traits. * This exception is necessary because optional JS fields cause the @@ -6333,7 +6454,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val formalArgs = params.map(genParamDef(_)) - val (allFormalCaptures, body, allActualCaptures) = if (!target.isStaticMember) { + val (allFormalCaptures, body, allActualCaptures) = if (!compileAsStaticMethod(target)) { val thisActualCapture = genExpr(receiver) val thisFormalCapture = js.ParamDef( freshLocalIdent("this")(receiver.pos), thisOriginalName, From 274713ecf587e529a632b0890751d28f687bb785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 23 Aug 2024 17:16:16 +0200 Subject: [PATCH 010/121] Check restrictions on how `this` values can be used in constructors. Essentially, `this` cannot be used before the super constructor call, except to assign fields (but not reading). These restrictions match those enforced by the JVM for bytecode. While we never intended `this` values to be restricted in this way, in practice we never produced IR that would violate those restrictions. There was one exception before the parent commit: we could have lifted methods coming from the arguments to a super constructor call. Since they were lifted as instance methods, they caused the constructor to refer to `this` before the super constructor call. This is in theory a backward breaking change in the IR! However, we audited the entire ecosystem of libraries published on Maven Central. None of them contained IR that violates the new restriction. In the future, we may leverage those restrictions to validate strict field initialization. If a field *needs* to be initialized before it is ever read (for example, because it has a non-nullable reference type), then we can do so: we only have to check that it is definitely initialized before the super constructor call. The restrictions implemented in this commit ensure that nobody can read the field before the super constructor call starts. --- .../linker/checker/ClassDefChecker.scala | 55 +++++++++++++--- .../linker/checker/ClassDefCheckerTest.scala | 66 +++++++++++++++++++ 2 files changed, 111 insertions(+), 10 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index 958845a90d..f2134214a7 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -524,6 +524,13 @@ private final class ClassDefChecker(classDef: ClassDef, * delegate constructor call. * - There is no such `ApplyStatically` anywhere else in the body. * - `cls` must be the enclosing class or its direct superclass. + * - In the statements before the delegate constructor call, and within + * `args`: + * - `This()` cannot be used except in `Assign(Select(This(), _), _)`, + * i.e., to assign to a field (but not read from one). + * - `StoreModule()` cannot be used. + * - After the delegate constructor call, `This` can be used without + * restriction. * * After the optimizer, there may be no delegate constructor call at all. * This frequently happens as the optimizer inlines super constructor @@ -559,11 +566,14 @@ private final class ClassDefChecker(classDef: ClassDef, val (delegateCtorCall: ApplyStatically) :: afterDelegateCtor = rest val ApplyStatically(_, receiver, cls, MethodIdent(ctor), args) = delegateCtorCall - val envJustBeforeDelegate = checkBlockStats(beforeDelegateCtor, bodyEnv) + val initEnv = bodyEnv.withIsThisRestricted(true) + val envJustBeforeDelegate = checkBlockStats(beforeDelegateCtor, initEnv) checkApplyArgs(ctor, args, envJustBeforeDelegate) - checkTree(receiver, envJustBeforeDelegate) // check that the This itself is valid + val unrestrictedEnv = envJustBeforeDelegate.withIsThisRestricted(false) + + checkTree(receiver, unrestrictedEnv) // check that the This itself is valid if (!postOptimizer) { if (!(cls == classDef.className || classDef.superClass.exists(_.name == cls))) { @@ -575,7 +585,7 @@ private final class ClassDefChecker(classDef: ClassDef, } } - checkBlockStats(afterDelegateCtor, envJustBeforeDelegate) + checkBlockStats(afterDelegateCtor, unrestrictedEnv) } } } @@ -632,7 +642,12 @@ private final class ClassDefChecker(classDef: ClassDef, checkTree(body, env.withLabel(label.name)) case Assign(lhs, rhs) => - checkTree(lhs, env) + lhs match { + case Select(This(), _) if env.isThisRestricted => + checkTree(lhs, env.withIsThisRestricted(false)) + case _ => + checkTree(lhs, env) + } checkTree(rhs, env) lhs match { @@ -708,6 +723,8 @@ private final class ClassDefChecker(classDef: ClassDef, reportError(i"Illegal StoreModule outside of constructor") if (env.thisType == NoType) // can happen before JSSuperConstructorCall in JSModuleClass reportError(i"Cannot find `this` in scope for StoreModule()") + if (env.isThisRestricted) + reportError(i"Restricted use of `this` for StoreModule() before super constructor call") case Select(qualifier, _) => checkTree(qualifier, env) @@ -915,6 +932,8 @@ private final class ClassDefChecker(classDef: ClassDef, reportError(i"Cannot find `this` in scope") else if (tree.tpe != env.thisType) reportError(i"`this` of type ${env.thisType} typed as ${tree.tpe}") + if (env.isThisRestricted) + reportError(i"Restricted use of `this` before the super constructor call") case Closure(arrow, captureParams, params, restParam, body, captureValues) => /* Check compliance of captureValues wrt. captureParams in the current @@ -1045,7 +1064,9 @@ object ClassDefChecker { /** Return types by label. */ val returnLabels: Set[LabelName], /** Whether we are in a constructor of the class. */ - val inConstructor: Boolean + val inConstructor: Boolean, + /** Whether usages of `this` are restricted in this scope. */ + val isThisRestricted: Boolean ) { import Env._ @@ -1064,20 +1085,33 @@ object ClassDefChecker { def withInConstructor(inConstructor: Boolean): Env = copy(inConstructor = inConstructor) + def withIsThisRestricted(isThisRestricted: Boolean): Env = + copy(isThisRestricted = isThisRestricted) + private def copy( hasNewTarget: Boolean = hasNewTarget, thisType: Type = thisType, locals: Map[LocalName, LocalDef] = locals, returnLabels: Set[LabelName] = returnLabels, - inConstructor: Boolean = inConstructor + inConstructor: Boolean = inConstructor, + isThisRestricted: Boolean = isThisRestricted ): Env = { - new Env(hasNewTarget, thisType, locals, returnLabels, inConstructor) + new Env(hasNewTarget, thisType, locals, returnLabels, inConstructor, + isThisRestricted) } } private object Env { - val empty: Env = - new Env(hasNewTarget = false, thisType = NoType, Map.empty, Set.empty, inConstructor = false) + val empty: Env = { + new Env( + hasNewTarget = false, + thisType = NoType, + locals = Map.empty, + returnLabels = Set.empty, + inConstructor = false, + isThisRestricted = false + ) + } def fromParams(params: List[ParamDef]): Env = { val paramLocalDefs = @@ -1089,7 +1123,8 @@ object ClassDefChecker { thisType = NoType, paramLocalDefs.toMap, Set.empty, - inConstructor = false + inConstructor = false, + isThisRestricted = false ) } } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala index db1d5ef955..d48a8f7c45 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala @@ -447,6 +447,55 @@ class ClassDefCheckerTest { "`this` of type any typed as Foo!") } + @Test + def restrictedThis(): Unit = { + val xParamDef = paramDef("x", IntType) + val xFieldName = FieldName("Foo", "x") + + def testRestrictedThisError(ctorStats: Tree*): Unit = { + val ctorFlags = EMF.withNamespace(MemberNamespace.Constructor) + + assertError( + classDef( + "Foo", superClass = Some(ObjectClass), + methods = List( + MethodDef(ctorFlags, MethodName.constructor(List(I)), NON, + List(xParamDef), NoType, Some(Block(ctorStats: _*)))(EOH, UNV) + ) + ), + "Restricted use of `this` before the super constructor call") + } + + val superCtorCall = trivialSuperCtorCall("Foo") + val thiz = thisFor("Foo") + + testRestrictedThisError( + thiz, + superCtorCall + ) + + testRestrictedThisError( + Select(thiz, xFieldName)(IntType), + superCtorCall + ) + + testRestrictedThisError( + Assign(Select(Select(thiz, xFieldName)(IntType), xFieldName)(IntType), int(5)), + superCtorCall + ) + + testRestrictedThisError( + Assign(Select(thiz, xFieldName)(IntType), Select(thiz, xFieldName)(IntType)), + superCtorCall + ) + + testRestrictedThisError( + ApplyStatically(EAF.withConstructor(true), thiz, ObjectClass, + MethodIdent(MethodName.constructor(List(O))), + List(thiz))(NoType) + ) + } + @Test def storeModule(): Unit = { val ctorFlags = EMF.withNamespace(MemberNamespace.Constructor) @@ -470,6 +519,23 @@ class ClassDefCheckerTest { "Illegal StoreModule inside class of kind Class" ) + assertError( + classDef( + "Foo", + kind = ClassKind.ModuleClass, + superClass = Some(ObjectClass), + methods = List( + MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, NoType, Some { + Block( + StoreModule(), + superCtorCall + ) + })(EOH, UNV) + ) + ), + "Restricted use of `this` for StoreModule() before super constructor call" + ) + assertError( classDef( "Foo", From f3244d8ff23dd82a91566a74c87225c55d2181e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 19 Oct 2024 10:36:02 +0000 Subject: [PATCH 011/121] Bump cookie and express Bumps [cookie](https://github.com/jshttp/cookie) to 0.7.1 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together. Updates `cookie` from 0.6.0 to 0.7.1 - [Release notes](https://github.com/jshttp/cookie/releases) - [Commits](https://github.com/jshttp/cookie/compare/v0.6.0...v0.7.1) Updates `express` from 4.21.0 to 4.21.1 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md) - [Commits](https://github.com/expressjs/express/compare/4.21.0...4.21.1) --- updated-dependencies: - dependency-name: cookie dependency-type: indirect - dependency-name: express dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- package-lock.json | 30 +++++++++++++++--------------- package.json | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6cf3197b4a..82d8f5dd50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "devDependencies": { - "express": "4.21.0", + "express": "4.21.1", "jsdom": "16.7.0", "jszip": "3.8.0", "source-map-support": "0.5.19" @@ -247,9 +247,9 @@ } }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, "engines": { "node": ">= 0.6" @@ -497,9 +497,9 @@ } }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dev": true, "dependencies": { "accepts": "~1.3.8", @@ -507,7 +507,7 @@ "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -1754,9 +1754,9 @@ "dev": true }, "cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true }, "cookie-signature": { @@ -1943,9 +1943,9 @@ "dev": true }, "express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dev": true, "requires": { "accepts": "~1.3.8", @@ -1953,7 +1953,7 @@ "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", diff --git a/package.json b/package.json index badeab7cd3..05186d318e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "devDependencies": { - "express": "4.21.0", + "express": "4.21.1", "jsdom": "16.7.0", "jszip": "3.8.0", "source-map-support": "0.5.19" From a7e023250fa5142512a0c9241d59f01ad8f993eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 21 Oct 2024 11:08:22 +0200 Subject: [PATCH 012/121] Only clean the IR loader after the result is completed in Refiner. Port to `Refiner` the way `BaseLinker` already handled `irLoader.cleanAfterRun()`. We need to wait for the `result` future to be completed before cleaning, otherwise we can leave `NullPointerException`s hanging. --- .../main/scala/org/scalajs/linker/frontend/Refiner.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala index df7b262a00..51da80fd6d 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala @@ -44,10 +44,10 @@ final class Refiner(config: CommonPhaseConfig, checkIR: Boolean) { analyzer.computeReachability(moduleInitializers, symbolRequirements, logger) } - for { + val result = for { analysis <- analysis } yield { - val result = logger.time("Refiner: Assemble LinkedClasses") { + logger.time("Refiner: Assemble LinkedClasses") { val assembled = for { (classDef, version) <- classDefs if analysis.classInfos.contains(classDef.className) @@ -65,10 +65,10 @@ final class Refiner(config: CommonPhaseConfig, checkIR: Boolean) { new LinkingUnit(config.coreSpec, linkedClassDefs.toList, linkedTopLevelExports.flatten.toList, moduleInitializers, globalInfo) } + } + result.andThen { case _ => irLoader.cleanAfterRun() - - result } } } From c399f63d9a938580aea73aeac1c1a95fec0db0f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 21 Oct 2024 11:42:24 +0200 Subject: [PATCH 013/121] Avoid self-suppression in `Analyzer.WorkTracker`. --- .../main/scala/org/scalajs/linker/analyzer/Analyzer.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala index daf81ec531..dc4dab1816 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala @@ -1581,7 +1581,10 @@ private object AnalyzerRun { case Nil => promise.success(()) case firstFailure :: moreFailures => - for (t <- moreFailures) + /* The same `Throwable` can be propagated to several tracked Futures. + * Since t.addSuppressed(t) is not allowed, we filter out duplicates. + */ + for (t <- moreFailures if t ne firstFailure) firstFailure.addSuppressed(t) promise.failure(firstFailure) } From 7840ce17318a22ba8708de4f55b6df89b1091e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 21 Oct 2024 11:25:07 +0200 Subject: [PATCH 014/121] Only allow `StoreModule` at the top-level of constructor bodies. This allows to get rid of `Env.inConstructor` in the `ClassDefChecker`. In theory, this is a breaking change. However, we never emitted IR with `StoreModule`s that would violate the new rule. In fact, all the IR we emit is even stricter: there is always a `StoreModule` *immediately after* the super constructor call of (JS) module class constructors, and never anywhere else. --- .../linker/checker/ClassDefChecker.scala | 52 +++++++++---------- .../linker/checker/ClassDefCheckerTest.scala | 40 ++++++++++++-- 2 files changed, 60 insertions(+), 32 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index f2134214a7..418a18b05c 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -303,7 +303,6 @@ private final class ClassDefChecker(classDef: ClassDef, val thisType = if (static) NoType else instanceThisType val bodyEnv = Env.fromParams(params) .withThisType(thisType) - .withInConstructor(isConstructor) if (isConstructor) checkConstructorBody(body, bodyEnv) @@ -328,12 +327,13 @@ private final class ClassDefChecker(classDef: ClassDef, val startEnv = Env.fromParams(classDef.jsClassCaptures.getOrElse(Nil) ++ params ++ restParam) .withHasNewTarget(true) - .withInConstructor(true) val envJustBeforeSuper = checkBlockStats(body.beforeSuper, startEnv) checkTreeOrSpreads(body.superCall.args, envJustBeforeSuper) val envJustAfterSuper = envJustBeforeSuper.withThisType(instanceThisType) - checkBlockStats(body.afterSuper, envJustAfterSuper) + val afterSuperStoreModulesHandled = + handleStoreModulesAfterSuperCtorCall(body.afterSuper) + checkBlockStats(afterSuperStoreModulesHandled, envJustAfterSuper) } private def checkJSMethodDef(methodDef: JSMethodDef): Unit = withPerMethodState { @@ -525,12 +525,10 @@ private final class ClassDefChecker(classDef: ClassDef, * - There is no such `ApplyStatically` anywhere else in the body. * - `cls` must be the enclosing class or its direct superclass. * - In the statements before the delegate constructor call, and within - * `args`: - * - `This()` cannot be used except in `Assign(Select(This(), _), _)`, - * i.e., to assign to a field (but not read from one). - * - `StoreModule()` cannot be used. + * `args`, `This()` cannot be used except in `Assign(Select(This(), _), _)`, + * i.e., to assign to a field (but not read from one). * - After the delegate constructor call, `This` can be used without - * restriction. + * restriction. Moreover, we can have `StoreModule`s at the top-level. * * After the optimizer, there may be no delegate constructor call at all. * This frequently happens as the optimizer inlines super constructor @@ -561,7 +559,9 @@ private final class ClassDefChecker(classDef: ClassDef, if (!postOptimizer) reportError(i"Constructor must contain a delegate constructor call") - checkBlockStats(bodyStats, bodyEnv) + val bodyStatsStoreModulesHandled = + handleStoreModulesAfterSuperCtorCall(bodyStats) + checkBlockStats(bodyStatsStoreModulesHandled, bodyEnv) } else { val (delegateCtorCall: ApplyStatically) :: afterDelegateCtor = rest val ApplyStatically(_, receiver, cls, MethodIdent(ctor), args) = delegateCtorCall @@ -585,11 +585,23 @@ private final class ClassDefChecker(classDef: ClassDef, } } - checkBlockStats(afterDelegateCtor, unrestrictedEnv) + val afterDelegateCtorStoreModulesHandled = + handleStoreModulesAfterSuperCtorCall(afterDelegateCtor) + checkBlockStats(afterDelegateCtorStoreModulesHandled, unrestrictedEnv) } } } + private def handleStoreModulesAfterSuperCtorCall(trees: List[Tree]): List[Tree] = { + /* If StoreModule's are allowed, there is nothing left to check about them, + * so we filter them out. + */ + if (classDef.kind.hasModuleAccessor) + trees.filter(!_.isInstanceOf[StoreModule]) + else + trees + } + private def checkBlockStats(stats: List[Tree], env: Env): Env = { stats.foldLeft(env) { (prevEnv, stat) => checkTree(stat, prevEnv) @@ -717,14 +729,7 @@ private final class ClassDefChecker(classDef: ClassDef, case _: LoadModule => case StoreModule() => - if (!classDef.kind.hasModuleAccessor) - reportError(i"Illegal StoreModule inside class of kind ${classDef.kind}") - if (!env.inConstructor) - reportError(i"Illegal StoreModule outside of constructor") - if (env.thisType == NoType) // can happen before JSSuperConstructorCall in JSModuleClass - reportError(i"Cannot find `this` in scope for StoreModule()") - if (env.isThisRestricted) - reportError(i"Restricted use of `this` for StoreModule() before super constructor call") + reportError(i"Illegal StoreModule") case Select(qualifier, _) => checkTree(qualifier, env) @@ -1063,8 +1068,6 @@ object ClassDefChecker { val locals: Map[LocalName, LocalDef], /** Return types by label. */ val returnLabels: Set[LabelName], - /** Whether we are in a constructor of the class. */ - val inConstructor: Boolean, /** Whether usages of `this` are restricted in this scope. */ val isThisRestricted: Boolean ) { @@ -1082,9 +1085,6 @@ object ClassDefChecker { def withLabel(label: LabelName): Env = copy(returnLabels = returnLabels + label) - def withInConstructor(inConstructor: Boolean): Env = - copy(inConstructor = inConstructor) - def withIsThisRestricted(isThisRestricted: Boolean): Env = copy(isThisRestricted = isThisRestricted) @@ -1093,11 +1093,9 @@ object ClassDefChecker { thisType: Type = thisType, locals: Map[LocalName, LocalDef] = locals, returnLabels: Set[LabelName] = returnLabels, - inConstructor: Boolean = inConstructor, isThisRestricted: Boolean = isThisRestricted ): Env = { - new Env(hasNewTarget, thisType, locals, returnLabels, inConstructor, - isThisRestricted) + new Env(hasNewTarget, thisType, locals, returnLabels, isThisRestricted) } } @@ -1108,7 +1106,6 @@ object ClassDefChecker { thisType = NoType, locals = Map.empty, returnLabels = Set.empty, - inConstructor = false, isThisRestricted = false ) } @@ -1123,7 +1120,6 @@ object ClassDefChecker { thisType = NoType, paramLocalDefs.toMap, Set.empty, - inConstructor = false, isThisRestricted = false ) } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala index d48a8f7c45..f1e17cc5f1 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala @@ -516,7 +516,7 @@ class ClassDefCheckerTest { })(EOH, UNV) ) ), - "Illegal StoreModule inside class of kind Class" + "Illegal StoreModule" ) assertError( @@ -533,7 +533,24 @@ class ClassDefCheckerTest { })(EOH, UNV) ) ), - "Restricted use of `this` for StoreModule() before super constructor call" + "Illegal StoreModule" + ) + + assertError( + classDef( + "Foo", + kind = ClassKind.ModuleClass, + superClass = Some(ObjectClass), + methods = List( + MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, NoType, Some { + Block( + superCtorCall, + If(BooleanLiteral(true), StoreModule(), Skip())(NoType) + ) + })(EOH, UNV) + ) + ), + "Illegal StoreModule" ) assertError( @@ -550,7 +567,7 @@ class ClassDefCheckerTest { })(EOH, UNV) ) ), - "Illegal StoreModule outside of constructor" + "Illegal StoreModule" ) assertError( @@ -564,7 +581,22 @@ class ClassDefCheckerTest { EOH, UNV) ) ), - "Cannot find `this` in scope for StoreModule()" + "Illegal StoreModule" + ) + + assertError( + classDef( + "Foo", + kind = ClassKind.JSModuleClass, + superClass = Some("scala.scalajs.js.Object"), + jsConstructor = Some( + JSConstructorDef(JSCtorFlags, Nil, None, + JSConstructorBody(Nil, JSSuperConstructorCall(Nil), + If(BooleanLiteral(true), StoreModule(), Skip())(NoType) :: Undefined() :: Nil))( + EOH, UNV) + ) + ), + "Illegal StoreModule" ) } From f3c0590cb9b18eeb6ab17d7c0127d8283ec6e72e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 21 Oct 2024 13:47:38 +0200 Subject: [PATCH 015/121] Under restrictedThis, do not allow assigning fields of super classes. We can only assign fields of `this` that are declared by the current class. This restriction matches a restriction on the JVM. --- .../scala/org/scalajs/linker/checker/ClassDefChecker.scala | 7 +++++-- .../org/scalajs/linker/checker/ClassDefCheckerTest.scala | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index f2134214a7..fbe01036dc 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -643,8 +643,11 @@ private final class ClassDefChecker(classDef: ClassDef, case Assign(lhs, rhs) => lhs match { - case Select(This(), _) if env.isThisRestricted => - checkTree(lhs, env.withIsThisRestricted(false)) + case Select(This(), field) if env.isThisRestricted => + if (postOptimizer || field.name.className == classDef.className) + checkTree(lhs, env.withIsThisRestricted(false)) + else + checkTree(lhs, env) case _ => checkTree(lhs, env) } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala index d48a8f7c45..32f394ebb9 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala @@ -479,6 +479,11 @@ class ClassDefCheckerTest { superCtorCall ) + testRestrictedThisError( + Assign(Select(thiz, FieldName("Bar", "y"))(IntType), int(5)), + superCtorCall + ) + testRestrictedThisError( Assign(Select(Select(thiz, xFieldName)(IntType), xFieldName)(IntType), int(5)), superCtorCall From f1732b16a19a7c6620904f55e21ecd822cc3a283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 26 Oct 2024 12:29:23 +0200 Subject: [PATCH 016/121] Switch to `try_table` to compile `TryCatch`. And remove the internal support for the legacy `try/catch` instruction pair. Now that Node.js 23 is released, we can always use `try_table`. The main browsers already have full support for `try_table`. --- .../backend/wasmemitter/FunctionEmitter.scala | 72 ++++++------------- .../backend/webassembly/FunctionBuilder.scala | 2 +- .../backend/webassembly/Instructions.scala | 11 --- .../backend/webassembly/TextWriter.scala | 8 +-- 4 files changed, 25 insertions(+), 68 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index 88d53a47e3..133dde2cde 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -40,22 +40,6 @@ import TypeTransformer._ object FunctionEmitter { - /** Whether to use the legacy `try` instruction to implement `TryCatch`. - * - * Support for catching JS exceptions was only added to `try_table` in V8 12.5 from April 2024. - * While waiting for Node.js to catch up with V8, we use `try` to implement our `TryCatch`. - * - * We use this "fixed configuration option" to keep the code that implements `TryCatch` using - * `try_table` in the codebase, as code that is actually compiled, so that refactorings apply to - * it as well. It also makes it easier to manually experiment with the new `try_table` encoding, - * which is available in Chrome since v125. - * - * Note that we use `try_table` regardless to implement `TryFinally`. Its `catch_all_ref` handler - * is perfectly happy to catch and rethrow JavaScript exception in Node.js 22. Duplicating that - * implementation for `try` would be a nightmare, given how complex it is already. - */ - private final val UseLegacyExceptionsForTryCatch = true - private val dotUTF8String = UTF8String(".") def emitFunction( @@ -2475,47 +2459,31 @@ private class FunctionEmitter private ( val resultType = transformResultType(expectedType) - if (UseLegacyExceptionsForTryCatch) { - markPosition(tree) - fb += wa.Try(fb.sigToBlockType(Sig(Nil, resultType))) - withNPEScope(resultType) { - genTree(block, expectedType) - } - markPosition(tree) - fb += wa.Catch(genTagID.exception) + markPosition(tree) + fb.block(resultType) { doneLabel => + fb.block(watpe.RefType.externref) { catchLabel => + /* We used to have `resultType` as result of the try_table, with the + * `wa.BR(doneLabel)` outside of the try_table. Unfortunately it seems + * V8 cannot handle try_table with a result type that is `(ref ...)`. + * The current encoding with `externref` as result type (to match the + * enclosing block) and the `br` *inside* the `try_table` works. + */ + fb.tryTable(watpe.RefType.externref)( + List(wa.CatchClause.Catch(genTagID.exception, catchLabel)) + ) { + withNPEScope(resultType) { + genTree(block, expectedType) + } + markPosition(tree) + fb += wa.Br(doneLabel) + } + } // end block $catch withNewLocal(errVarName, errVarOrigName, watpe.RefType.anyref) { exceptionLocal => fb += wa.AnyConvertExtern fb += wa.LocalSet(exceptionLocal) genTree(handler, expectedType) } - fb += wa.End - } else { - markPosition(tree) - fb.block(resultType) { doneLabel => - fb.block(watpe.RefType.externref) { catchLabel => - /* We used to have `resultType` as result of the try_table, with the - * `wa.BR(doneLabel)` outside of the try_table. Unfortunately it seems - * V8 cannot handle try_table with a result type that is `(ref ...)`. - * The current encoding with `externref` as result type (to match the - * enclosing block) and the `br` *inside* the `try_table` works. - */ - fb.tryTable(watpe.RefType.externref)( - List(wa.CatchClause.Catch(genTagID.exception, catchLabel)) - ) { - withNPEScope(resultType) { - genTree(block, expectedType) - } - markPosition(tree) - fb += wa.Br(doneLabel) - } - } // end block $catch - withNewLocal(errVarName, errVarOrigName, watpe.RefType.anyref) { exceptionLocal => - fb += wa.AnyConvertExtern - fb += wa.LocalSet(exceptionLocal) - genTree(handler, expectedType) - } - } // end block $done - } + } // end block $done if (expectedType == NothingType) fb += wa.Unreachable diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/FunctionBuilder.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/FunctionBuilder.scala index 25162bc9f9..5c31310cae 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/FunctionBuilder.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/FunctionBuilder.scala @@ -377,7 +377,7 @@ final class FunctionBuilder( while (nestingLevel >= 0 && iter.hasNext) { val deadCodeInstr = iter.next() deadCodeInstr match { - case End | Else | _: Catch if nestingLevel == 0 => + case End | Else if nestingLevel == 0 => /* We have reached the end of the original block of dead code. * Actually emit this END or ELSE and then drop `nestingLevel` * below 0 to end the dead code processing loop. diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/Instructions.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/Instructions.scala index 45ab60e830..26b9e02c9b 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/Instructions.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/Instructions.scala @@ -120,17 +120,6 @@ object Instructions { case object Else extends SimpleInstr("else", 0x05) - /** `try` in the legacy exception system. - * - * @see - * [[https://webassembly.github.io/exception-handling/legacy/exceptions/core/syntax.html]] - */ - final case class Try(i: BlockType, label: Option[LabelID] = None) - extends BlockTypeLabeledInstr("try", 0x06, i) - - /** `catch` in the legacy exception system. */ - final case class Catch(i: TagID) extends TagInstr("catch", 0x07, i) - final case class Throw(i: TagID) extends TagInstr("throw", 0x08, i) with StackPolymorphicInstr case object ThrowRef extends SimpleInstr("throw_ref", 0x0A) with StackPolymorphicInstr diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/TextWriter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/TextWriter.scala index 645bc72e52..d8adec9b9f 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/TextWriter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/TextWriter.scala @@ -411,8 +411,8 @@ private class TextWriter(module: Module) { case _ => instr match { - case End | Else | _: Catch => b.deindent() - case _ => // do nothing + case End | Else => b.deindent() + case _ => // do nothing } b.newLine() b.appendElement(instr.mnemonic) @@ -429,8 +429,8 @@ private class TextWriter(module: Module) { writeInstrImmediates(instr) instr match { - case _: StructuredLabeledInstr | Else | _: Catch => b.indent() - case _ => // do nothing + case _: StructuredLabeledInstr | Else => b.indent() + case _ => // do nothing } } } From 8998caf50902b09619bd1a4bcafabca820e0d7f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 2 Nov 2024 14:56:53 +0100 Subject: [PATCH 017/121] Make record types *not* subtypes of `any`. And clarify that `any` is not a universal top type. It is only the supertype of all values that can be passed to JavaScript. --- .../src/main/scala/org/scalajs/ir/Types.scala | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Types.scala b/ir/shared/src/main/scala/org/scalajs/ir/Types.scala index 000583a135..df0ee5b926 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Types.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Types.scala @@ -78,13 +78,17 @@ object Types { } } - /** Any type (the top type of this type system). - * A variable of this type can contain any value, including `undefined` - * and `null` and any JS value. This type supports a very limited set - * of Scala operations, the ones common to all values. Basically only - * reference equality tests and instance tests. It also supports all - * JavaScript operations, since all Scala objects are also genuine - * JavaScript objects. + /** Any type. + * + * This is the supertype of all value types that can be passed to JavaScript + * code. Record types are the canonical counter-example: they are not + * subtypes of `any` because their values cannot be given to JavaScript. + * + * This type supports a very limited set of Scala operations, the ones + * common to all values. Basically only reference equality tests and + * instance tests. It also supports all JavaScript operations, since all + * Scala objects are also genuine JavaScript values. + * * The type java.lang.Object in the back-end maps to [[AnyType]] because it * can hold JS values (not only instances of Scala.js classes). */ @@ -179,11 +183,19 @@ object Types { } /** Record type. + * * Used by the optimizer to inline classes as records with multiple fields. * They are desugared as several local variables by JSDesugaring. * Record types cannot cross method boundaries, so they cannot appear as * the type of fields or parameters, nor as result types of methods. * The compiler itself never generates record types. + * + * Record types currently do not feature any form of subtyping. For R1 to be + * a subtype of R2, it must have the same fields, in the same order, with + * equivalent types. + * + * Record types are not subtypes of `any`. As such, they can never be passed + * to JavaScript. */ final case class RecordType(fields: List[RecordType.Field]) extends Type { def findField(name: SimpleFieldName): RecordType.Field = @@ -393,12 +405,16 @@ object Types { (lhs == rhs) || ((lhs, rhs) match { + case (NothingType, _) => true case (_, NoType) => true case (NoType, _) => false - case (_, AnyType) => true - case (NothingType, _) => true - case (NullType, _) => rhs.isNullable + case (NullType, _) => rhs.isNullable + + case (_: RecordType, _) => false + case (_, _: RecordType) => false + + case (_, AnyType) => true case (_, AnyNotNullType) => !lhs.isNullable case (ClassType(lhsClass, lhsNullable), ClassType(rhsClass, rhsNullable)) => From ec936732befbd3bef2dc19e843834085029a9b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 21 Oct 2024 16:13:32 +0200 Subject: [PATCH 018/121] Print `Undefined()/UndefType` as `undefined/undef` rather than "void". The concept of "void" will be reserved for "statement types", not for expressions with the `undefined` value. --- ir/shared/src/main/scala/org/scalajs/ir/Printers.scala | 4 ++-- ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala index 5895bf9773..bd7e615a7f 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -781,7 +781,7 @@ object Printers { // Literals case Undefined() => - print("(void 0)") + print("undefined") case Null() => print("null") @@ -1093,7 +1093,7 @@ object Printers { case AnyType => print("any") case AnyNotNullType => print("any!") case NothingType => print("nothing") - case UndefType => print("void") + case UndefType => print("undef") case BooleanType => print("boolean") case CharType => print("char") case ByteType => print("byte") diff --git a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala index 1df89dc5d8..852535a9be 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -52,7 +52,7 @@ class PrintersTest { assertPrintEquals("any", AnyType) assertPrintEquals("any!", AnyNotNullType) assertPrintEquals("nothing", NothingType) - assertPrintEquals("void", UndefType) + assertPrintEquals("undef", UndefType) assertPrintEquals("boolean", BooleanType) assertPrintEquals("char", CharType) assertPrintEquals("byte", ByteType) @@ -808,7 +808,7 @@ class PrintersTest { } @Test def printUndefined(): Unit = { - assertPrintEquals("(void 0)", Undefined()) + assertPrintEquals("undefined", Undefined()) } @Test def printNull(): Unit = { @@ -1298,7 +1298,7 @@ class PrintersTest { |constructor def constructor(x: any): any = { | 5; | super(6); - | (void 0) + | undefined |} """, JSConstructorDef(MemberFlags.empty.withNamespace(Constructor), From cdcff9973579ed77e87343f3edd9429661b4a786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 21 Oct 2024 16:28:43 +0200 Subject: [PATCH 019/121] Rename NoType to VoidType and print it as "void". Also clarify that it is the real top type of our type system. We have been using `void` in the "theoretical" type system since Scala.js 1.0.0, but for historical reasons, `NoType` kept its old name. We now officially rename it to `VoidType`, and print it as `void` instead of ``, giving it a true `Type` status. --- .../org/scalajs/nscplugin/GenJSCode.scala | 68 +++++++++---------- .../org/scalajs/nscplugin/GenJSExports.scala | 4 +- .../scalajs/nscplugin/TypeConversions.scala | 2 +- .../main/scala/org/scalajs/ir/Hashers.scala | 4 +- .../src/main/scala/org/scalajs/ir/Names.scala | 2 +- .../main/scala/org/scalajs/ir/Printers.scala | 8 +-- .../scala/org/scalajs/ir/Serializers.scala | 30 ++++---- .../src/main/scala/org/scalajs/ir/Tags.scala | 7 +- .../scala/org/scalajs/ir/Transformers.scala | 2 +- .../src/main/scala/org/scalajs/ir/Trees.scala | 19 +++--- .../src/main/scala/org/scalajs/ir/Types.scala | 29 ++++---- .../scala/org/scalajs/ir/PrintersTest.scala | 20 +++--- .../scala/org/scalajs/linker/RunTest.scala | 2 +- .../org/scalajs/linker/analyzer/Infos.scala | 2 +- .../linker/backend/emitter/ClassEmitter.scala | 8 +-- .../linker/backend/emitter/CoreJSLib.scala | 2 +- .../backend/emitter/FunctionEmitter.scala | 6 +- .../linker/backend/emitter/NameGen.scala | 2 +- .../linker/backend/emitter/SJSGen.scala | 8 +-- .../linker/backend/emitter/Transients.scala | 2 +- .../linker/backend/emitter/VarGen.scala | 2 +- .../backend/wasmemitter/ClassEmitter.scala | 2 +- .../backend/wasmemitter/DerivedClasses.scala | 2 +- .../backend/wasmemitter/FunctionEmitter.scala | 58 ++++++++-------- .../linker/backend/wasmemitter/SWasmGen.scala | 2 +- .../backend/wasmemitter/TypeTransformer.scala | 4 +- .../linker/checker/ClassDefChecker.scala | 34 +++++----- .../scalajs/linker/checker/IRChecker.scala | 8 +-- .../frontend/optimizer/IncOptimizer.scala | 6 +- .../frontend/optimizer/OptimizerCore.scala | 30 ++++---- .../org/scalajs/linker/AnalyzerTest.scala | 12 ++-- .../org/scalajs/linker/IRCheckerTest.scala | 18 ++--- .../org/scalajs/linker/IncrementalTest.scala | 12 ++-- .../linker/LibraryReachabilityTest.scala | 4 +- .../org/scalajs/linker/OptimizerTest.scala | 12 ++-- .../linker/checker/ClassDefCheckerTest.scala | 58 ++++++++-------- .../linker/testutils/TestIRBuilder.scala | 8 +-- project/BinaryIncompatibilities.scala | 4 +- project/JavaLangObject.scala | 8 +-- 39 files changed, 258 insertions(+), 253 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 714fc7bd1b..5ac71f2c26 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -1414,7 +1414,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.MethodIdent(name), NoOriginalName, Nil, - jstpe.NoType, + jstpe.VoidType, Some(stats))( OptimizerHints.empty, Unversioned) } @@ -1842,7 +1842,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } - js.If(cond, body, js.Skip())(jstpe.NoType) + js.If(cond, body, js.Skip())(jstpe.VoidType) } /* preStats / postStats use pre/post order traversal respectively to @@ -2080,7 +2080,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val namespace = js.MemberNamespace.Constructor js.MethodDef( js.MemberFlags.empty.withNamespace(namespace), methodName, - originalName, jsParams, jstpe.NoType, Some(genStat(dd.rhs)))( + originalName, jsParams, jstpe.VoidType, Some(genStat(dd.rhs)))( optimizerHints, Unversioned) } else { val resultIRType = toIRType(sym.tpe.resultType) @@ -2180,7 +2180,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } val newBody = body.map( - b => transformer.transform(b, isStat = resultType == jstpe.NoType)) + b => transformer.transform(b, isStat = resultType == jstpe.VoidType)) js.MethodDef(flags, methodName, originalName, newParams, resultType, newBody)(methodDef.optimizerHints, Unversioned)(methodDef.pos) } @@ -2216,7 +2216,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } val newBody = body.map( - b => transformer.transform(b, isStat = resultType == jstpe.NoType)) + b => transformer.transform(b, isStat = resultType == jstpe.VoidType)) js.MethodDef(flags, methodName, originalName, newParams, resultType, newBody)(methodDef.optimizerHints, Unversioned)(methodDef.pos) } @@ -2248,7 +2248,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) optimizerHints: OptimizerHints): js.MethodDef = { implicit val pos = tree.pos - val bodyIsStat = resultIRType == jstpe.NoType + val bodyIsStat = resultIRType == jstpe.VoidType def genBodyWithinReturnableScope(): js.Tree = tree match { case Block( @@ -2439,8 +2439,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) */ def genExpr(tree: Tree): js.Tree = { val result = genStatOrExpr(tree, isStat = false) - assert(result.tpe != jstpe.NoType, - s"genExpr($tree) returned a tree with type NoType at pos ${tree.pos}") + assert(result.tpe != jstpe.VoidType, + s"genExpr($tree) returned a tree with type VoidType at pos ${tree.pos}") result } @@ -2517,7 +2517,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) case tree @ If(cond, thenp, elsep) => def default: js.Tree = { val tpe = - if (isStat) jstpe.NoType + if (isStat) jstpe.VoidType else toIRType(tree.tpe) js.If(genExpr(cond), genStatOrExpr(thenp, isStat), @@ -2559,8 +2559,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) case Return(expr) => js.Return(toIRType(expr.tpe) match { - case jstpe.NoType => js.Block(genStat(expr), js.Undefined()) - case _ => genExpr(expr) + case jstpe.VoidType => js.Block(genStat(expr), js.Undefined()) + case _ => genExpr(expr) }, getEnclosingReturnLabel()) case t: Try => @@ -2916,7 +2916,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) if (transformedRhs.tpe == jstpe.NothingType) { // In this case, we do not need the outer block label js.While(js.BooleanLiteral(true), { - js.Labeled(labelIdent, jstpe.NoType, { + js.Labeled(labelIdent, jstpe.VoidType, { transformedRhs match { // Eliminate a trailing return@lab case js.Block(stats :+ ReturnFromThisLabel(exprAsStat)) => @@ -2930,11 +2930,11 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) // When all else has failed, we need the full machinery val blockLabelIdent = freshLabelIdent("block") val bodyType = - if (isStat) jstpe.NoType + if (isStat) jstpe.VoidType else toIRType(tree.tpe) js.Labeled(blockLabelIdent, bodyType, { js.While(js.BooleanLiteral(true), { - js.Labeled(labelIdent, jstpe.NoType, { + js.Labeled(labelIdent, jstpe.VoidType, { if (isStat) js.Block(transformedRhs, js.Return(js.Undefined(), blockLabelIdent)) else @@ -3050,7 +3050,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val blockAST = genStatOrExpr(block, isStat) val resultType = - if (isStat) jstpe.NoType + if (isStat) jstpe.VoidType else toIRType(tree.tpe) val handled = @@ -3282,7 +3282,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val r = toIRType(to) def isValueType(tpe: jstpe.Type): Boolean = tpe match { - case jstpe.NoType | jstpe.BooleanType | jstpe.CharType | + case jstpe.VoidType | jstpe.BooleanType | jstpe.CharType | jstpe.ByteType | jstpe.ShortType | jstpe.IntType | jstpe.LongType | jstpe.FloatType | jstpe.DoubleType => true @@ -3625,7 +3625,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) .withNoinline(noinline) val methodIdent = encodeMethodSym(method) val resultType = - if (method.isClassConstructor) jstpe.NoType + if (method.isClassConstructor) jstpe.VoidType else toIRType(method.tpe.resultType) js.ApplyStatically(flags, receiver, encodeClassName(method.owner), methodIdent, arguments)(resultType) @@ -3891,7 +3891,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val genSelector = genExpr(selector) val resultType = - if (isStat) jstpe.NoType + if (isStat) jstpe.VoidType else toIRType(tree.tpe) val defaultLabelSym = cases.collectFirst { @@ -4035,8 +4035,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) (alts, newBody) } js.Labeled(matchResultLabel, resultType, js.Block(List( - js.Labeled(elseClauseLabel, jstpe.NoType, { - buildMatch(patchedClauses.reverse, js.Skip(), jstpe.NoType) + js.Labeled(elseClauseLabel, jstpe.VoidType, { + buildMatch(patchedClauses.reverse, js.Skip(), jstpe.VoidType) }), elseClause ))) @@ -4133,7 +4133,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val translatedMatch = genTranslatedMatch(cases, matchEnd) val genMore = genBlockWithCaseLabelDefs(more, isStat) val label = getEnclosingReturnLabel() - if (translatedMatch.tpe == jstpe.NoType) { + if (translatedMatch.tpe == jstpe.VoidType) { // Could not actually reproduce this, but better be safe than sorry translatedMatch :: js.Return(js.Undefined(), label) :: genMore } else { @@ -4217,7 +4217,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) tree match { case If(cond, thenp, elsep) => js.If(genExpr(cond), genCaseBody(thenp), genCaseBody(elsep))( - jstpe.NoType) + jstpe.VoidType) case Block(stats, Literal(Constant(()))) => // Generated a lot by the async transform @@ -4272,7 +4272,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) def genMatchEndBody(): js.Tree = { genStatOrExpr(matchEndBody, - isStat = toIRType(matchEndBody.tpe) == jstpe.NoType) + isStat = toIRType(matchEndBody.tpe) == jstpe.VoidType) } matchEnd.params match { @@ -4326,7 +4326,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) ) { genTranslatedCases } - val optimized = genOptimizedMatchEndLabeled(labelIdent, jstpe.NoType, + val optimized = genOptimizedMatchEndLabeled(labelIdent, jstpe.VoidType, translatedCases, info.generatedReturns) js.Block(varDefs ::: optimized :: genMatchEndBody() :: Nil) } @@ -4379,7 +4379,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) implicit pos: Position): js.Tree = { def default: js.Tree = - js.Labeled(label, jstpe.NoType, translatedBody) + js.Labeled(label, jstpe.VoidType, translatedBody) if (returnCount == 0) { translatedBody @@ -4403,7 +4403,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) case _ => js.UnaryOp(js.UnaryOp.Boolean_!, cond) } - js.Block(stats1 :+ js.If(notCond, js.Block(stats2), js.Skip())(jstpe.NoType)) + js.Block(stats1 :+ js.If(notCond, js.Block(stats2), js.Skip())(jstpe.VoidType)) case _ :: _ => throw new AssertionError("unreachable code") @@ -5219,7 +5219,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) def makePrimitiveBox(expr: js.Tree, tpe: Type)( implicit pos: Position): js.Tree = { toIRType(tpe) match { - case jstpe.NoType => // for JS interop cases + case jstpe.VoidType => // for JS interop cases js.Block(expr, js.Undefined()) case jstpe.BooleanType | jstpe.CharType | jstpe.ByteType | jstpe.ShortType | jstpe.IntType | jstpe.LongType | jstpe.FloatType | @@ -5234,8 +5234,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) def makePrimitiveUnbox(expr: js.Tree, tpe: Type)( implicit pos: Position): js.Tree = { toIRType(tpe) match { - case jstpe.NoType => expr // for JS interop cases - case irTpe => js.AsInstanceOf(expr, irTpe) + case jstpe.VoidType => expr // for JS interop cases + case irTpe => js.AsInstanceOf(expr, irTpe) } } @@ -6135,8 +6135,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * * we generate a function: * - * lambda[notype]( - * outer, capture1, ..., captureM, param1, ..., paramN) { + * arrow-lambda(param1, ..., paramN) { * * } * @@ -6206,8 +6205,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * * we generate a function: * - * lambda[notype]( - * outer, capture1, ..., captureM, param1, ..., paramN) { + * arrow-lambda(param1, ..., paramN) { * outer.lambdaImpl(param1, ..., paramN, capture1, ..., captureM) * } */ @@ -6540,7 +6538,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.MethodIdent(ObjectArgConstructorName), NoOriginalName, List(fParamDef), - jstpe.NoType, + jstpe.VoidType, Some(js.Block(List( js.Assign( js.Select(js.This()(thisType), fFieldIdent)(jstpe.AnyType), @@ -6549,7 +6547,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.This()(thisType), ir.Names.ObjectClass, js.MethodIdent(ir.Names.NoArgConstructorName), - Nil)(jstpe.NoType)))))( + Nil)(jstpe.VoidType)))))( js.OptimizerHints.empty, Unversioned) } diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala index beca736586..bf668424ae 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala @@ -870,7 +870,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { } // #4684 If the getter returns void, we must "box" it by returning undefined - if (callGetter.tpe == jstpe.NoType) + if (callGetter.tpe == jstpe.VoidType) js.Block(callGetter, js.Undefined()) else callGetter @@ -975,7 +975,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { (toIRType(tpe): @unchecked) match { case jstpe.AnyType | jstpe.AnyNotNullType => NoTypeTest - case jstpe.NoType => PrimitiveTypeTest(jstpe.UndefType, 0) + case jstpe.VoidType => PrimitiveTypeTest(jstpe.UndefType, 0) case jstpe.BooleanType => PrimitiveTypeTest(jstpe.BooleanType, 1) case jstpe.CharType => PrimitiveTypeTest(jstpe.CharType, 2) case jstpe.ByteType => PrimitiveTypeTest(jstpe.ByteType, 3) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/TypeConversions.scala b/compiler/src/main/scala/org/scalajs/nscplugin/TypeConversions.scala index 97db07f24f..eae31fdb14 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/TypeConversions.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/TypeConversions.scala @@ -25,7 +25,7 @@ trait TypeConversions[G <: Global with Singleton] extends SubComponent { private lazy val primitiveIRTypeMap: Map[Symbol, Types.Type] = { Map( - UnitClass -> Types.NoType, + UnitClass -> Types.VoidType, BooleanClass -> Types.BooleanType, CharClass -> Types.CharType, ByteClass -> Types.ByteType, diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala index e866e32fb4..1c159fe9b2 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala @@ -581,7 +581,7 @@ object Hashers { def mixTypeRef(typeRef: TypeRef): Unit = typeRef match { case PrimRef(tpe) => tpe match { - case NoType => mixTag(TagVoidRef) + case VoidType => mixTag(TagVoidRef) case BooleanType => mixTag(TagBooleanRef) case CharType => mixTag(TagCharRef) case ByteType => mixTag(TagByteRef) @@ -621,7 +621,7 @@ object Hashers { case DoubleType => mixTag(TagDoubleType) case StringType => mixTag(TagStringType) case NullType => mixTag(TagNullType) - case NoType => mixTag(TagNoType) + case VoidType => mixTag(TagVoidType) case ClassType(className, nullable) => mixTag(if (nullable) TagClassType else TagNonNullClassType) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Names.scala b/ir/shared/src/main/scala/org/scalajs/ir/Names.scala index 73ea0c8c7d..8193227be0 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Names.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Names.scala @@ -393,7 +393,7 @@ object Names { def appendTypeRef(typeRef: TypeRef): Unit = typeRef match { case PrimRef(tpe) => tpe match { - case NoType => builder.append('V') + case VoidType => builder.append('V') case BooleanType => builder.append('Z') case CharType => builder.append('C') case ByteType => builder.append('B') diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala index bd7e615a7f..e245cffeb4 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -106,7 +106,7 @@ object Printers { print(")") - if (resultType != NoType) { + if (resultType != VoidType) { print(": ") print(resultType) print(" = ") @@ -173,7 +173,7 @@ object Printers { case Labeled(label, tpe, body) => print(label) - if (tpe != NoType) { + if (tpe != VoidType) { print('[') print(tpe) print(']') @@ -1037,7 +1037,7 @@ object Printers { print(flags.namespace.prefixString) print("set ") printJSMemberName(name) - printSig(arg :: Nil, None, NoType) + printSig(arg :: Nil, None, VoidType) printBlock(body) } @@ -1104,7 +1104,7 @@ object Printers { case DoubleType => print("double") case StringType => print("string") case NullType => print("null") - case NoType => print("") + case VoidType => print("void") case ClassType(className, nullable) => print(className) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index 15a2e251ec..5a69b79b42 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -199,7 +199,7 @@ object Serializers { def writeTypeRef(typeRef: TypeRef): Unit = typeRef match { case PrimRef(tpe) => tpe match { - case NoType => s.writeByte(TagVoidRef) + case VoidType => s.writeByte(TagVoidRef) case BooleanType => s.writeByte(TagBooleanRef) case CharType => s.writeByte(TagCharRef) case ByteType => s.writeByte(TagByteRef) @@ -874,7 +874,7 @@ object Serializers { case DoubleType => buffer.write(TagDoubleType) case StringType => buffer.write(TagStringType) case NullType => buffer.write(TagNullType) - case NoType => buffer.write(TagNoType) + case VoidType => buffer.write(TagVoidType) case ClassType(className, nullable) => buffer.write(if (nullable) TagClassType else TagNonNullClassType) @@ -899,7 +899,7 @@ object Serializers { def writeTypeRef(typeRef: TypeRef): Unit = typeRef match { case PrimRef(tpe) => tpe match { - case NoType => buffer.writeByte(TagVoidRef) + case VoidType => buffer.writeByte(TagVoidRef) case BooleanType => buffer.writeByte(TagBooleanRef) case CharType => buffer.writeByte(TagCharRef) case ByteType => buffer.writeByte(TagByteRef) @@ -1615,9 +1615,9 @@ object Serializers { if (methodName.isConstructor) { val newName = MethodIdent(NoArgConstructorName)(method.name.pos) val newBody = ApplyStatically(ApplyFlags.empty.withConstructor(true), - thisJLClass, ObjectClass, newName, Nil)(NoType) + thisJLClass, ObjectClass, newName, Nil)(VoidType) MethodDef(method.flags, newName, method.originalName, - Nil, NoType, Some(newBody))( + Nil, VoidType, Some(newBody))( method.optimizerHints, method.version) } else { def argRef = method.args.head.ref @@ -1653,7 +1653,7 @@ object Serializers { super.transform(tree, isStat) } } - transformer.transform(method.body.get, isStat = method.resultType == NoType) + transformer.transform(method.body.get, isStat = method.resultType == VoidType) } val newOptimizerHints = @@ -1759,7 +1759,7 @@ object Serializers { ) }) ) - }, Skip())(NoType), + }, Skip())(VoidType), result.ref ) } @@ -1980,7 +1980,7 @@ object Serializers { MethodDef(flags, name, originalName, args, resultType, patchedBody)( patchedOptimizerHints, optHash) } else { - val patchedBody = body.map(bodyHack5(_, isStat = resultType == NoType)) + val patchedBody = body.map(bodyHack5(_, isStat = resultType == VoidType)) MethodDef(flags, name, originalName, args, resultType, patchedBody)( optimizerHints, optHash) } @@ -2058,7 +2058,7 @@ object Serializers { body } else { /* #4442 and #4601: Patch Labeled, If, Match and TryCatch nodes in - * statement position to have type NoType. These 4 nodes are the + * statement position to have type VoidType. These 4 nodes are the * control structures whose result type is explicitly specified (and * not derived from their children like Block or TryFinally, or * constant like While). @@ -2066,16 +2066,16 @@ object Serializers { new Transformers.Transformer { override def transform(tree: Tree, isStat: Boolean): Tree = { val newTree = super.transform(tree, isStat) - if (isStat && newTree.tpe != NoType) { + if (isStat && newTree.tpe != VoidType) { newTree match { case Labeled(label, _, body) => - Labeled(label, NoType, body)(newTree.pos) + Labeled(label, VoidType, body)(newTree.pos) case If(cond, thenp, elsep) => - If(cond, thenp, elsep)(NoType)(newTree.pos) + If(cond, thenp, elsep)(VoidType)(newTree.pos) case Match(selector, cases, default) => - Match(selector, cases, default)(NoType)(newTree.pos) + Match(selector, cases, default)(VoidType)(newTree.pos) case TryCatch(block, errVar, errVarOriginalName, handler) => - TryCatch(block, errVar, errVarOriginalName, handler)(NoType)(newTree.pos) + TryCatch(block, errVar, errVarOriginalName, handler)(VoidType)(newTree.pos) case _ => newTree } @@ -2223,7 +2223,7 @@ object Serializers { case TagDoubleType => DoubleType case TagStringType => StringType case TagNullType => NullType - case TagNoType => NoType + case TagVoidType => VoidType case TagClassType => ClassType(readClassName(), nullable = true) case TagArrayType => ArrayType(readArrayTypeRef(), nullable = true) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala b/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala index 577a3ceca6..1bc0a52f78 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala @@ -171,11 +171,14 @@ private[ir] object Tags { final val TagClassType = TagNullType + 1 final val TagArrayType = TagClassType + 1 final val TagRecordType = TagArrayType + 1 - final val TagNoType = TagRecordType + 1 + final val TagVoidType = TagRecordType + 1 + + @deprecated("Use TagVoidType instead", since = "1.18.0") + final val TagNoType = TagVoidType // New in 1.17 - final val TagAnyNotNullType = TagNoType + 1 + final val TagAnyNotNullType = TagVoidType + 1 final val TagNonNullClassType = TagAnyNotNullType + 1 final val TagNonNullArrayType = TagNonNullClassType + 1 diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala index 0b9719da0f..2fcc5bd1ee 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala @@ -246,7 +246,7 @@ object Transformers { def transformMethodDef(methodDef: MethodDef): MethodDef = { val MethodDef(flags, name, originalName, args, resultType, body) = methodDef - val newBody = body.map(transform(_, isStat = resultType == NoType)) + val newBody = body.map(transform(_, isStat = resultType == VoidType)) MethodDef(flags, name, originalName, args, resultType, newBody)( methodDef.optimizerHints, Unversioned)(methodDef.pos) } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index 96145a12f5..bf7eaff25b 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -102,7 +102,7 @@ object Trees { sealed case class VarDef(name: LocalIdent, originalName: OriginalName, vtpe: Type, mutable: Boolean, rhs: Tree)( implicit val pos: Position) extends Tree { - val tpe = NoType // cannot be in expression position + val tpe = VoidType def ref(implicit pos: Position): VarRef = VarRef(name)(vtpe) } @@ -116,7 +116,7 @@ object Trees { // Control flow constructs sealed case class Skip()(implicit val pos: Position) extends Tree { - val tpe = NoType // cannot be in expression position + val tpe = VoidType } sealed class Block private (val stats: List[Tree])( @@ -157,7 +157,7 @@ object Trees { sealed case class Assign(lhs: AssignLhs, rhs: Tree)( implicit val pos: Position) extends Tree { - val tpe = NoType // cannot be in expression position + val tpe = VoidType } sealed case class Return(expr: Tree, label: LabelIdent)( @@ -170,17 +170,16 @@ object Trees { sealed case class While(cond: Tree, body: Tree)( implicit val pos: Position) extends Tree { - // cannot be in expression position, unless it is infinite val tpe = cond match { case BooleanLiteral(true) => NothingType - case _ => NoType + case _ => VoidType } } sealed case class ForIn(obj: Tree, keyVar: LocalIdent, keyVarOriginalName: OriginalName, body: Tree)( implicit val pos: Position) extends Tree { - val tpe = NoType + val tpe = VoidType } sealed case class TryCatch(block: Tree, errVar: LocalIdent, @@ -224,7 +223,7 @@ object Trees { default: Tree)(val tpe: Type)(implicit val pos: Position) extends Tree sealed case class Debugger()(implicit val pos: Position) extends Tree { - val tpe = NoType // cannot be in expression position + val tpe = VoidType } // Scala expressions @@ -246,7 +245,7 @@ object Trees { } sealed case class StoreModule()(implicit val pos: Position) extends Tree { - val tpe = NoType // cannot be in expression position + val tpe = VoidType } sealed case class Select(qualifier: Tree, field: FieldIdent)(val tpe: Type)( @@ -734,7 +733,7 @@ object Trees { */ sealed case class JSSuperConstructorCall(args: List[TreeOrJSSpread])( implicit val pos: Position) extends Tree { - val tpe = NoType + val tpe = VoidType } /** JavaScript dynamic import of the form `import(arg)`. @@ -827,7 +826,7 @@ object Trees { implicit val pos: Position) extends Tree { - val tpe = NoType // cannot be in expression position + val tpe = VoidType } /** Unary operation (always preserves pureness). diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Types.scala b/ir/shared/src/main/scala/org/scalajs/ir/Types.scala index df0ee5b926..b801857290 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Types.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Types.scala @@ -46,9 +46,9 @@ object Types { } /** A type that accepts the same values as this type except `null`, unless - * this type is `NoType`. + * this type is `VoidType`. * - * If `this` is `NoType`, returns this type. + * If `this` is `VoidType`, returns this type. * * For all other types `tpe`, `tpe.toNonNullable.isNullable` is `false`. */ @@ -64,7 +64,7 @@ object Types { sealed abstract class PrimTypeWithRef extends PrimType { def primRef: PrimRef = this match { - case NoType => VoidRef + case VoidType => VoidRef case BooleanType => BooleanRef case CharType => CharRef case ByteType => ByteRef @@ -209,8 +209,11 @@ object Types { tpe: Type, mutable: Boolean) } - /** No type. */ - case object NoType extends PrimTypeWithRef + /** Void type, the top of type of our type system. */ + case object VoidType extends PrimTypeWithRef + + @deprecated("Use VoidType instead", since = "1.18.0") + lazy val NoType: VoidType.type = VoidType /** Type reference (allowed for classOf[], is/asInstanceOf[]). * @@ -276,7 +279,7 @@ object Types { * `"nothing"`, respectively. */ val displayName: String = tpe match { - case NoType => "void" + case VoidType => "void" case BooleanType => "boolean" case CharType => "char" case ByteType => "byte" @@ -300,7 +303,7 @@ object Types { * respectively. */ val charCode: Char = tpe match { - case NoType => 'V' + case VoidType => 'V' case BooleanType => 'Z' case CharType => 'C' case ByteType => 'B' @@ -315,15 +318,15 @@ object Types { } /* @unchecked for the initialization checker of Scala 3 - * When we get here, `NoType` is not yet considered fully initialized because + * When we get here, `VoidType` is not yet considered fully initialized because * its method `primRef` can access `VoidRef`. Since the constructor of - * `PrimRef` pattern-matches on its `tpe`, which is `NoType`, this is flagged + * `PrimRef` pattern-matches on its `tpe`, which is `VoidType`, this is flagged * by the init checker, although our usage is safe given that we do not call * `primRef`. The same reasoning applies to the other primitive types. * In the future, we may want to rearrange the initialization sequence of * this file to avoid this issue. */ - final val VoidRef = PrimRef(NoType: @unchecked) + final val VoidRef = PrimRef(VoidType: @unchecked) final val BooleanRef = PrimRef(BooleanType: @unchecked) final val CharRef = PrimRef(CharType: @unchecked) final val ByteRef = PrimRef(ByteType: @unchecked) @@ -372,7 +375,7 @@ object Types { case tpe: RecordType => RecordValue(tpe, tpe.fields.map(f => zeroOf(f.tpe))) - case NothingType | NoType | ClassType(_, false) | ArrayType(_, false) | + case NothingType | VoidType | ClassType(_, false) | ArrayType(_, false) | AnyNotNullType => throw new IllegalArgumentException(s"cannot generate a zero for $tpe") } @@ -406,8 +409,8 @@ object Types { (lhs == rhs) || ((lhs, rhs) match { case (NothingType, _) => true - case (_, NoType) => true - case (NoType, _) => false + case (_, VoidType) => true + case (VoidType, _) => false case (NullType, _) => rhs.isNullable diff --git a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala index 852535a9be..a2e7a2d51f 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -63,7 +63,7 @@ class PrintersTest { assertPrintEquals("double", DoubleType) assertPrintEquals("string", StringType) assertPrintEquals("null", NullType) - assertPrintEquals("", NoType) + assertPrintEquals("void", VoidType) assertPrintEquals("java.lang.Object", ClassType(ObjectClass, nullable = true)) assertPrintEquals("java.lang.String!", @@ -127,7 +127,7 @@ class PrintersTest { | 6 |} """, - Labeled("lab", NoType, i(6))) + Labeled("lab", VoidType, i(6))) assertPrintEquals( """ @@ -144,7 +144,7 @@ class PrintersTest { | 6 |} """, - Labeled("lab", NoType, Block(i(5), i(6)))) + Labeled("lab", VoidType, Block(i(5), i(6)))) } @Test def printAssign(): Unit = { @@ -173,7 +173,7 @@ class PrintersTest { | 5 |} """, - If(b(true), i(5), Skip())(NoType)) + If(b(true), i(5), Skip())(VoidType)) assertPrintEquals( """ @@ -322,7 +322,7 @@ class PrintersTest { @Test def printApply(): Unit = { assertPrintEquals("x.m;V()", - Apply(EAF, ref("x", "test.Test"), MethodName("m", Nil, V), Nil)(NoType)) + Apply(EAF, ref("x", "test.Test"), MethodName("m", Nil, V), Nil)(VoidType)) assertPrintEquals("x.m;I;I(5)", Apply(EAF, ref("x", "test.Test"), MethodName("m", List(I), I), List(i(5)))(IntType)) @@ -334,7 +334,7 @@ class PrintersTest { @Test def printApplyStatically(): Unit = { assertPrintEquals("x.test.Test::m;V()", ApplyStatically(EAF, ref("x", "test.Test"), "test.Test", - MethodName("m", Nil, V), Nil)(NoType)) + MethodName("m", Nil, V), Nil)(VoidType)) assertPrintEquals("x.test.Test::m;I;I(5)", ApplyStatically(EAF, ref("x", "test.Test"), "test.Test", MethodName("m", List(I), I), List(i(5)))(IntType)) @@ -344,12 +344,12 @@ class PrintersTest { assertPrintEquals("x.test.Test::private::m;V()", ApplyStatically(EAF.withPrivate(true), ref("x", "test.Test"), - "test.Test", MethodName("m", Nil, V), Nil)(NoType)) + "test.Test", MethodName("m", Nil, V), Nil)(VoidType)) } @Test def printApplyStatic(): Unit = { assertPrintEquals("test.Test::m;V()", - ApplyStatic(EAF, "test.Test", MethodName("m", Nil, V), Nil)(NoType)) + ApplyStatic(EAF, "test.Test", MethodName("m", Nil, V), Nil)(VoidType)) assertPrintEquals("test.Test::m;I;I(5)", ApplyStatic(EAF, "test.Test", MethodName("m", List(I), I), List(i(5)))(IntType)) @@ -359,7 +359,7 @@ class PrintersTest { assertPrintEquals("test.Test::private::m;V()", ApplyStatic(EAF.withPrivate(true), "test.Test", MethodName("m", Nil, V), - Nil)(NoType)) + Nil)(VoidType)) } @Test def printApplyDynamicImportStatic(): Unit = { @@ -1251,7 +1251,7 @@ class PrintersTest { """, MethodDef(MemberFlags.empty, mIVMethodName, NON, List(ParamDef("x", NON, IntType, mutable = false)), - NoType, Some(i(5)))(NoOptHints, UNV)) + VoidType, Some(i(5)))(NoOptHints, UNV)) assertPrintEquals( """ diff --git a/linker/jvm/src/test/scala/org/scalajs/linker/RunTest.scala b/linker/jvm/src/test/scala/org/scalajs/linker/RunTest.scala index 9adde311f8..0f7f6a628a 100644 --- a/linker/jvm/src/test/scala/org/scalajs/linker/RunTest.scala +++ b/linker/jvm/src/test/scala/org/scalajs/linker/RunTest.scala @@ -89,7 +89,7 @@ class RunTest { If(UnaryOp(UnaryOp.Boolean_!, test), Throw(JSNew(JSGlobalRef("Error"), List(str("Assertion failed")))), Skip())( - NoType) + VoidType) } private def testLinkAndRun(classDefs: Seq[ClassDef], diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala index 3ff5ee059d..84a8d8a630 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala @@ -254,7 +254,7 @@ object Infos { case NullType | NothingType => // Nothing to do - case NoType | RecordType(_) => + case VoidType | RecordType(_) => throw new IllegalArgumentException( s"Illegal receiver type: $receiverTpe") } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala index 1b1751df60..a27cb2dd1e 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala @@ -320,10 +320,10 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val initMethodBody = initMethodDef.body.getOrElse { throw new AssertionError("Cannot generate an abstract constructor") } - assert(initMethodDef.resultType == NoType, + assert(initMethodDef.resultType == VoidType, s"Found a constructor with type ${initMethodDef.resultType} at $pos") desugarToFunction(className, initMethodDef.args, initMethodBody, - resultType = NoType) + resultType = VoidType) } for (generatedInitMethodFun <- generatedInitMethodFunWithGlobals) yield { @@ -597,7 +597,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { // optional setter definition val optSetterWithGlobals = property.setterArgAndBody map { case (arg, body) => - desugarToFunction(className, arg :: Nil, body, resultType = NoType) + desugarToFunction(className, arg :: Nil, body, resultType = VoidType) } for { @@ -629,7 +629,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } val setterWithGlobals = property.setterArgAndBody.map { case (arg, body) => - for (fun <- desugarToFunction(className, arg :: Nil, body, resultType = NoType)) + for (fun <- desugarToFunction(className, arg :: Nil, body, resultType = VoidType)) yield js.SetterDef(static, propName, fun.args.head, fun.body) } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala index ffbea0e2f7..77e47c1fb4 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala @@ -2109,7 +2109,7 @@ private[emitter] object CoreJSLib { * `scala.collection.mutable.ArrayBuilder.genericArrayBuilderResult`. * This code is Scala-specific, and "unboxes" `null` as the zero of * primitive types. For `void`, it is even more special, as it produces - * a boxed Unit value, which is `undefined` (although `VoidRef`/`NoType` + * a boxed Unit value, which is `undefined` (although `VoidRef`/`VoidType` * doesn't have a zero value per se). */ val zero = primRef match { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index dcd45d0533..546bbb1e3b 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -284,7 +284,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, globalRefTracking: GlobalRefTracking, pos: Position): WithGlobals[js.Function] = { new JSDesugar(globalRefTracking).desugarToFunction( - params, restParam, body, isStat = resultType == NoType, + params, restParam, body, isStat = resultType == VoidType, Env.empty(resultType).withEnclosingClassName(Some(enclosingClassName))) } @@ -296,7 +296,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, globalRefTracking: GlobalRefTracking, pos: Position): WithGlobals[js.Function] = { new JSDesugar(globalRefTracking).desugarToFunctionWithExplicitThis( - params, body, isStat = resultType == NoType, + params, body, isStat = resultType == VoidType, Env.empty(resultType).withEnclosingClassName(Some(enclosingClassName))) } @@ -307,7 +307,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, globalRefTracking: GlobalRefTracking, pos: Position): WithGlobals[js.Function] = { new JSDesugar(globalRefTracking).desugarToFunction( - params, restParam, body, isStat = resultType == NoType, + params, restParam, body, isStat = resultType == VoidType, Env.empty(resultType)) } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameGen.scala index 4e26efd4b7..a6c6b720a0 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameGen.scala @@ -144,7 +144,7 @@ private[backend] final class NameGen { typeRef match { case PrimRef(tpe) => tpe match { - case NoType => builder.append('V') + case VoidType => builder.append('V') case BooleanType => builder.append('Z') case CharType => builder.append('C') case ByteType => builder.append('B') diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala index d16df3126a..38f73dfd8a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala @@ -213,7 +213,7 @@ private[emitter] final class SJSGen( case UndefType => Undefined() case NullType => Null() - case NoType | NothingType => + case VoidType | NothingType => throw new IllegalArgumentException(s"cannot generate a zero for $tpe") } } @@ -443,7 +443,7 @@ private[emitter] final class SJSGen( case AnyNotNullType => expr !== Null() - case NoType | NullType | NothingType | AnyType | + case VoidType | NullType | NothingType | AnyType | ClassType(_, true) | ArrayType(_, true) | _:RecordType => throw new AssertionError(s"Unexpected type $tpe in genIsInstanceOf") } @@ -527,7 +527,7 @@ private[emitter] final class SJSGen( if (semantics.strictFloats) genCallPolyfillableBuiltin(FroundBuiltin, expr) else wg(UnaryOp(irt.JSUnaryOp.+, expr)) - case NoType | NullType | NothingType | AnyNotNullType | + case VoidType | NullType | NothingType | AnyNotNullType | ClassType(_, false) | ArrayType(_, false) | _:RecordType => throw new AssertionError(s"Unexpected type $tpe in genAsInstanceOf") } @@ -553,7 +553,7 @@ private[emitter] final class SJSGen( case StringType => genCallHelper(VarField.uT, expr) case AnyType => expr - case NoType | NullType | NothingType | AnyNotNullType | + case VoidType | NullType | NothingType | AnyNotNullType | ClassType(_, false) | ArrayType(_, false) | _:RecordType => throw new AssertionError(s"Unexpected type $tpe in genAsInstanceOf") } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Transients.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Transients.scala index 5c199fa0be..dc772cfa1a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Transients.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Transients.scala @@ -62,7 +62,7 @@ object Transients { final case class SystemArrayCopy(src: Tree, srcPos: Tree, dest: Tree, destPos: Tree, length: Tree) extends Transient.Value { - val tpe: Type = NoType + val tpe: Type = VoidType def traverse(traverser: Traverser): Unit = { traverser.traverse(src) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala index cdcf20560f..1aab60acd6 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala @@ -379,7 +379,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, def subField(x: PrimRef): String = { // The mapping in this function is an implementation detail of the emitter x.tpe match { - case NoType => "V" + case VoidType => "V" case BooleanType => "Z" case CharType => "C" case ByteType => "B" diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala index c4be465718..43409fa133 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala @@ -1201,7 +1201,7 @@ class ClassEmitter(coreSpec: CoreSpec) { setterParamDef :: Nil, restParam = None, setterBody, - resultType = NoType + resultType = VoidType ) val setterRef = helperBuilder.addWasmInput("set", watpe.RefType.func) { fb += ctx.refFuncWithDeclaration(closureFuncID) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/DerivedClasses.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/DerivedClasses.scala index 02a3d34bea..1935e130fc 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/DerivedClasses.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/DerivedClasses.scala @@ -103,7 +103,7 @@ object DerivedClasses { MethodIdent(MethodName.constructor(List(primType.primRef))), NON, List(ctorParamDef), - NoType, + VoidType, Some(Assign(selectField, ctorParamDef.ref)) )(EOH, NOV) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index 133dde2cde..d8f18921b4 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -553,8 +553,8 @@ private class FunctionEmitter private ( case t: TryFinally => unwinding.genTryFinally(t, expectedType) case t: Throw => genThrow(t) case t: Match => genMatch(t, expectedType) - case t: Debugger => NoType // ignore - case t: Skip => NoType + case t: Debugger => VoidType // ignore + case t: Skip => VoidType case t: Clone => genClone(t) case t: IdentityHashCode => genIdentityHashCode(t) case t: WrapAsThrowable => genWrapAsThrowable(t) @@ -613,7 +613,7 @@ private class FunctionEmitter private ( () case (NothingType, _) => () - case (_, NoType) => + case (_, VoidType) => fb += wa.Drop case (primType: PrimTypeWithRef, _) => // box @@ -633,7 +633,7 @@ private class FunctionEmitter private ( genBox(watpe.Int32, SpecialNames.CharBoxClass) case LongType => genBox(watpe.Int64, SpecialNames.LongBoxClass) - case NoType | NothingType => + case VoidType | NothingType => throw new AssertionError(s"Unexpected adaptation from $primType to $expectedType") case _ => /* Calls a `bX` helper. Most of them are of the form @@ -756,7 +756,7 @@ private class FunctionEmitter private ( fb += wa.Call(genFunctionID.jsSelectSet) case JSSelect(qualifier, item) => - genThroughCustomJSHelper(List(qualifier, item, rhs), NoType) { allJSArgs => + genThroughCustomJSHelper(List(qualifier, item, rhs), VoidType) { allJSArgs => val List(jsQualifier, jsItem, jsRhs) = allJSArgs js.Assign(js.BracketSelect.makeOptimized(jsQualifier, jsItem), jsRhs) } @@ -772,7 +772,7 @@ private class FunctionEmitter private ( case lhs @ JSGlobalRef(name) => val builder = new CustomJSHelperBuilderWithTreeSupport() val rhsRef = builder.addInput(rhs) - val helperID = builder.build(NoType) { + val helperID = builder.build(VoidType) { js.Assign(builder.genGlobalRef(name), rhsRef) } markPosition(tree) @@ -789,7 +789,7 @@ private class FunctionEmitter private ( genWriteToStorage(lookupRecordSelect(lhs)) } - NoType + VoidType } private def genWriteToStorage(storage: VarStorage): Unit = { @@ -1269,7 +1269,7 @@ private class FunctionEmitter private ( } private def genLiteral(tree: Literal, expectedType: Type): Type = { - if (expectedType == NoType) { + if (expectedType == VoidType) { /* Since all literals are pure, we can always get rid of them. * This is mostly useful for the argument of `Return` nodes that target a * `Labeled` in statement position, since they must have a non-`void` @@ -1358,7 +1358,7 @@ private class FunctionEmitter private ( markPosition(tree) fb += wa.GlobalSet(genGlobalID.forModuleInstance(className)) - NoType + VoidType } private def genLoadModule(tree: LoadModule): Type = { @@ -1583,7 +1583,7 @@ private class FunctionEmitter private ( fb += wa.Call(genFunctionID.cast) AnyType } else { - genTree(lhs, NoType) + genTree(lhs, VoidType) genTreeAuto(rhs) rhs.tpe } @@ -1644,7 +1644,7 @@ private class FunctionEmitter private ( genTree(lhs, NothingType) NothingType } else if (rhsType == NothingType) { - genTree(lhs, NoType) + genTree(lhs, VoidType) genTree(rhs, NothingType) NothingType } else if (rhsType == NullType) { @@ -1652,7 +1652,7 @@ private class FunctionEmitter private ( * so testing for the `lhsType == NullType` is not as useful. */ genTree(lhs, AnyType) - genTree(rhs, NoType) // no-op if it is actually a Null() literal + genTree(rhs, VoidType) // no-op if it is actually a Null() literal markPosition(tree) fb += wa.RefIsNull maybeGenInvert() @@ -1880,7 +1880,7 @@ private class FunctionEmitter private ( fb += wa.Call(genFunctionID.jsValueToStringForConcat) case NothingType => () // unreachable - case NoType => + case VoidType => throw new AssertionError( s"Found expression of type void in String_+ at ${tree.pos}: $tree") } @@ -2035,7 +2035,7 @@ private class FunctionEmitter private ( case LongType => val structTypeID = genTypeID.forClass(SpecialNames.LongBoxClass) fb += wa.RefTest(watpe.RefType(structTypeID)) - case NoType | NothingType | NullType => + case VoidType | NothingType | NullType => throw new AssertionError(s"Illegal isInstanceOf[$testType]") case testType: PrimTypeWithRef => fb += wa.Call(genFunctionID.typeTest(testType.primRef)) @@ -2175,12 +2175,12 @@ private class FunctionEmitter private ( genTree(expr, NothingType) NothingType } else if (targetTpe == NothingType) { - genTree(expr, NoType) + genTree(expr, VoidType) fb += wa.Unreachable NothingType } else { /* At this point, neither sourceTpe nor targetTpe can be NothingType, - * NoType or RecordType, so we can use `transformSingleType`. + * VoidType or RecordType, so we can use `transformSingleType`. */ val sourceWasmType = transformSingleType(sourceTpe) @@ -2238,7 +2238,7 @@ private class FunctionEmitter private ( /** Unbox the `anyref` on the stack to the target `PrimType`. * - * `targetTpe` must not be `NothingType`, `NullType` nor `NoType`. + * `targetTpe` must not be `NothingType`, `NullType` nor `VoidType`. * * The type left on the stack is non-nullable. */ @@ -2279,7 +2279,7 @@ private class FunctionEmitter private ( fb += genZeroOf(targetTpe) } - case NothingType | NullType | NoType => + case NothingType | NullType | VoidType => throw new IllegalArgumentException(s"Illegal type in genUnbox: $targetTpe") case targetTpe: PrimTypeWithRef => @@ -2357,8 +2357,8 @@ private class FunctionEmitter private ( * Its scope is empty by construction, and therefore it need not be stored. */ val VarDef(_, _, _, _, rhs) = tree - genTree(rhs, NoType) - NoType + genTree(rhs, VoidType) + VoidType } private def genIf(tree: If, expectedType: Type): Type = { @@ -2371,7 +2371,7 @@ private class FunctionEmitter private ( elsep match { case Skip() => - assert(expectedType == NoType) + assert(expectedType == VoidType) fb.ifThen() { genTree(thenp, expectedType) } @@ -2397,7 +2397,7 @@ private class FunctionEmitter private ( // infinite loop that must be typed as `nothing`, i.e., unreachable markPosition(tree) fb.loop() { label => - genTree(body, NoType) + genTree(body, VoidType) markPosition(tree) fb += wa.Br(label) } @@ -2411,12 +2411,12 @@ private class FunctionEmitter private ( genTree(cond, BooleanType) markPosition(tree) fb.ifThen() { - genTree(body, NoType) + genTree(body, VoidType) markPosition(tree) fb += wa.Br(label) } } - NoType + VoidType } } @@ -2451,7 +2451,7 @@ private class FunctionEmitter private ( throw new NotImplementedError(s"Unsupported shape of ForIn node at ${tree.pos}: $tree") } - NoType + VoidType } private def genTryCatch(tree: TryCatch, expectedType: Type): Type = { @@ -2540,7 +2540,7 @@ private class FunctionEmitter private ( genWriteToStorage(storage) case _ => - genTree(stat, NoType) + genTree(stat, VoidType) } } @@ -2799,7 +2799,7 @@ private class FunctionEmitter private ( genTree(item, AnyType) markPosition(tree) fb += wa.Call(genFunctionID.jsDelete) - NoType + VoidType } private def genJSUnaryOp(tree: JSUnaryOp): Type = { @@ -3496,7 +3496,7 @@ private class FunctionEmitter private ( fb += wa.Call(genFunctionID.genericArrayCopy) } - NoType + VoidType } // Helpers to generate code going through custom JS helpers @@ -3993,7 +3993,7 @@ private class FunctionEmitter private ( } // end block $catch // finally block (during which we leave the `(ref null exn)` on the stack) - genTree(finalizer, NoType) + genTree(finalizer, VoidType) markPosition(tree) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SWasmGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SWasmGen.scala index d8566b503a..f13320065d 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SWasmGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SWasmGen.scala @@ -40,7 +40,7 @@ object SWasmGen { case AnyType | ClassType(_, true) | ArrayType(_, true) | NullType => RefNull(Types.HeapType.None) - case NothingType | NoType | ClassType(_, false) | ArrayType(_, false) | + case NothingType | VoidType | ClassType(_, false) | ArrayType(_, false) | AnyNotNullType | _:RecordType => throw new AssertionError(s"Unexpected type for field: ${tpe.show()}") } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/TypeTransformer.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/TypeTransformer.scala index 813dcdcd92..d38694e284 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/TypeTransformer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/TypeTransformer.scala @@ -68,7 +68,7 @@ object TypeTransformer { */ def transformResultType(tpe: Type)(implicit ctx: WasmContext): List[watpe.Type] = { tpe match { - case NoType => Nil + case VoidType => Nil case NothingType => Nil case RecordType(fields) => fields.flatMap(f => transformResultType(f.tpe)) case _ => List(transformSingleType(tpe)) @@ -134,7 +134,7 @@ object TypeTransformer { case StringType => watpe.RefType.extern case NullType => watpe.RefType.nullref - case NoType | NothingType => + case VoidType | NothingType => throw new IllegalArgumentException( s"${tpe.show()} does not have a corresponding Wasm type") } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index 718a710051..b23a83d5fc 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -152,8 +152,8 @@ private final class ClassDefChecker(classDef: ClassDef, val name = ident.name if (!alreadyDeclared.add(name)) reportError(i"Duplicate JS class capture '$name'") - if (tpe == NoType) - reportError(i"The JS class capture $name cannot have type NoType") + if (tpe == VoidType) + reportError(i"The JS class capture $name cannot have type VoidType") if (mutable) reportError(i"The JS class capture $name cannot be mutable") } @@ -227,7 +227,7 @@ private final class ClassDefChecker(classDef: ClassDef, } fieldDef.ftpe match { - case NoType | NothingType | AnyNotNullType | ClassType(_, false) | + case VoidType | NothingType | AnyNotNullType | ClassType(_, false) | ArrayType(_, false) | _:RecordType => reportError(i"FieldDef cannot have type ${fieldDef.ftpe}") case _ => @@ -294,13 +294,13 @@ private final class ClassDefChecker(classDef: ClassDef, // Params for (ParamDef(name, _, tpe, _) <- params) { checkDeclareLocalVar(name) - if (tpe == NoType) - reportError(i"Parameter $name has type NoType") + if (tpe == VoidType) + reportError(i"Parameter $name has type VoidType") } // Body for (body <- optBody) { - val thisType = if (static) NoType else instanceThisType + val thisType = if (static) VoidType else instanceThisType val bodyEnv = Env.fromParams(params) .withThisType(thisType) @@ -357,7 +357,7 @@ private final class ClassDefChecker(classDef: ClassDef, checkExportedPropertyName(pName) checkJSParamDefs(params, restParam) - val thisType = if (static) NoType else instanceThisType + val thisType = if (static) VoidType else instanceThisType val env = Env.fromParams(classDef.jsClassCaptures.getOrElse(Nil) ++ params ++ restParam).withThisType(thisType) @@ -383,7 +383,7 @@ private final class ClassDefChecker(classDef: ClassDef, checkExportedPropertyName(pName) val jsClassCaptures = classDef.jsClassCaptures.getOrElse(Nil) - val thisType = if (static) NoType else instanceThisType + val thisType = if (static) VoidType else instanceThisType getterBody.foreach { body => withPerMethodState { @@ -799,7 +799,7 @@ private final class ClassDefChecker(classDef: ClassDef, case IsInstanceOf(expr, testType) => checkTree(expr, env) testType match { - case NoType | NullType | NothingType | AnyType | + case VoidType | NullType | NothingType | AnyType | ClassType(_, true) | ArrayType(_, true) | _:RecordType => reportError(i"$testType is not a valid test type for IsInstanceOf") case testType: ArrayType => @@ -811,7 +811,7 @@ private final class ClassDefChecker(classDef: ClassDef, case AsInstanceOf(expr, tpe) => checkTree(expr, env) tpe match { - case NoType | NullType | NothingType | AnyNotNullType | + case VoidType | NullType | NothingType | AnyNotNullType | ClassType(_, false) | ArrayType(_, false) | _:RecordType => reportError(i"$tpe is not a valid target type for AsInstanceOf") case tpe: ArrayType => @@ -936,7 +936,7 @@ private final class ClassDefChecker(classDef: ClassDef, } case This() => - if (env.thisType == NoType) + if (env.thisType == VoidType) reportError(i"Cannot find `this` in scope") else if (tree.tpe != env.thisType) reportError(i"`this` of type ${env.thisType} typed as ${tree.tpe}") @@ -961,8 +961,8 @@ private final class ClassDefChecker(classDef: ClassDef, checkDeclareLocalVar(name) if (mutable) reportError(i"Capture parameter $name cannot be mutable") - if (ctpe == NoType) - reportError(i"Parameter $name has type NoType") + if (ctpe == VoidType) + reportError(i"Parameter $name has type VoidType") } checkJSParamDefs(params, restParam) @@ -970,7 +970,7 @@ private final class ClassDefChecker(classDef: ClassDef, val bodyEnv = Env .fromParams(captureParams ++ params ++ restParam) .withHasNewTarget(!arrow) - .withThisType(if (arrow) NoType else AnyType) + .withThisType(if (arrow) VoidType else AnyType) checkTree(body, bodyEnv) } @@ -1065,7 +1065,7 @@ object ClassDefChecker { private class Env( /** Whether there is a valid `new.target` in scope. */ val hasNewTarget: Boolean, - /** The type of `this` in scope, or `NoType` if there is no `this` in scope. */ + /** The type of `this` in scope, or `VoidType` if there is no `this` in scope. */ val thisType: Type, /** Local variables in scope (including through closures). */ val locals: Map[LocalName, LocalDef], @@ -1106,7 +1106,7 @@ object ClassDefChecker { val empty: Env = { new Env( hasNewTarget = false, - thisType = NoType, + thisType = VoidType, locals = Map.empty, returnLabels = Set.empty, isThisRestricted = false @@ -1120,7 +1120,7 @@ object ClassDefChecker { new Env( hasNewTarget = false, - thisType = NoType, + thisType = VoidType, paramLocalDefs.toMap, Set.empty, isThisRestricted = false diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index 4d388be2ea..f6df8de449 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -139,8 +139,8 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { body.superCall.args.foreach(typecheckExprOrSpread(_, bodyEnv)) body.afterSuper.foreach(typecheck(_, bodyEnv)) - val resultType = body.afterSuper.lastOption.fold[Type](NoType)(_.tpe) - if (resultType == NoType) + val resultType = body.afterSuper.lastOption.fold[Type](VoidType)(_.tpe) + if (resultType == VoidType) reportError(i"${AnyType} expected but $resultType found for JS constructor body") } @@ -257,7 +257,7 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { case Return(expr, label) => val returnType = env.returnTypes(label.name) - if (returnType == NoType) + if (returnType == VoidType) typecheckExpr(expr, env) else typecheckExpect(expr, env, returnType) @@ -312,7 +312,7 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { val clazz = lookupClass(className) if (clazz.kind != ClassKind.Class) reportError(i"new $className which is not a class") - checkApplyGeneric(className, ctor.name, args, NoType, isStatic = false) + checkApplyGeneric(className, ctor.name, args, VoidType, isStatic = false) case LoadModule(className) => val clazz = lookupClass(className) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index ab84765129..15818137e4 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -419,7 +419,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: val className: ClassName = linkedClass.className def untrackedThisType: Type = - if (namespace.isStatic) NoType + if (namespace.isStatic) VoidType else myInterface.untrackedInstanceThisType val methods = mutable.Map.empty[MethodName, MethodImpl] @@ -1162,7 +1162,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: def untrackedJSClassCaptures: List[ParamDef] = _jsClassCaptures def untrackedThisType(namespace: MemberNamespace): Type = - if (namespace.isStatic) NoType + if (namespace.isStatic) VoidType else myInterface.untrackedInstanceThisType def updateWith(linkedClass: LinkedClass): Unit = { @@ -1220,7 +1220,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: private[this] var methods = Map.empty[(String, String), (JSMethodImpl, Position)] val untrackedJSClassCaptures: List[ParamDef] = Nil - def untrackedThisType(namespace: MemberNamespace): Type = NoType + def untrackedThisType(namespace: MemberNamespace): Type = VoidType override def toString(): String = "" diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 944c8ae079..adaac12ee7 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -270,8 +270,8 @@ private[optimizer] abstract class OptimizerCore( private val isSubclassFun = isSubclass _ private def isSubtype(lhs: Type, rhs: Type): Boolean = { - assert(lhs != NoType) - assert(rhs != NoType) + assert(lhs != VoidType) + assert(rhs != VoidType) Types.isSubtype(lhs, rhs)(isSubclassFun) || { (lhs, rhs) match { @@ -337,7 +337,7 @@ private[optimizer] abstract class OptimizerCore( case Labeled(ident @ LabelIdent(label), tpe, body) => trampoline { - pretransformLabeled(label, if (isStat) NoType else tpe, body, isStat, + pretransformLabeled(label, if (isStat) VoidType else tpe, body, isStat, usePreTransform = false)(finishTransform(isStat)) } @@ -1157,13 +1157,13 @@ private[optimizer] abstract class OptimizerCore( pretransformExprs(thenp, elsep) { (tthenp, telsep) => if (tthenp.tpe.isNothingType) { cont(PreTransBlock( - If(newCond, finishTransformStat(tthenp), Skip())(NoType), + If(newCond, finishTransformStat(tthenp), Skip())(VoidType), telsep)) } else if (telsep.tpe.isNothingType) { val negCond = finishTransformExpr( foldUnaryOp(UnaryOp.Boolean_!, newCond.toPreTransform)) cont(PreTransBlock( - If(negCond, finishTransformStat(telsep), Skip())(NoType), + If(negCond, finishTransformStat(telsep), Skip())(VoidType), tthenp)) } else { (resolveLocalDef(tthenp), resolveLocalDef(telsep)) match { @@ -1687,7 +1687,7 @@ private[optimizer] abstract class OptimizerCore( case If(cond, thenp, elsep) => (keepOnlySideEffects(thenp), keepOnlySideEffects(elsep)) match { case (Skip(), Skip()) => keepOnlySideEffects(cond) - case (newThenp, newElsep) => If(cond, newThenp, newElsep)(NoType)(stat.pos) + case (newThenp, newElsep) => If(cond, newThenp, newElsep)(VoidType)(stat.pos) } case BinaryOp(op, lhs, rhs) => @@ -3089,7 +3089,7 @@ private[optimizer] abstract class OptimizerCore( cpLocalDef.newReplacement, IntLiteral(Character.MAX_CODE_POINT))), Throw(New(IllegalArgumentExceptionClass, MethodIdent(NoArgConstructorName), Nil)), Skip() - )(NoType), + )(VoidType), Transient(WasmStringFromCodePoint(cpLocalDef.newReplacement)) ))) } (cont) @@ -3166,7 +3166,7 @@ private[optimizer] abstract class OptimizerCore( */ tpe match { case CharType => IntLiteral(0) - case NoType => Undefined() + case VoidType => Undefined() case _ => zeroOf(tpe) } case ClassOf(_) => @@ -5119,7 +5119,7 @@ private[optimizer] abstract class OptimizerCore( val (paramLocalDefs, newParamDefs) = params.map(newParamReplacement(_)).unzip val thisLocalDef = - if (thisType == NoType) None + if (thisType == VoidType) None else Some(newThisLocalDef(thisType)) val env = OptEnv.Empty @@ -5140,7 +5140,7 @@ private[optimizer] abstract class OptimizerCore( } } - val newBody0 = transform(body, resultType == NoType)(scope) + val newBody0 = transform(body, resultType == VoidType)(scope) val newBody = if (isNoArgCtor) tryElimStoreModule(newBody0) @@ -5258,7 +5258,7 @@ private[optimizer] abstract class OptimizerCore( def tryDropReturn(body: Tree): Option[Tree] = body match { case BlockOrAlone(prep, Return(result, LabelIdent(`newLabelName`))) => val result1 = - if (refinedType == NoType) keepOnlySideEffects(result) + if (refinedType == VoidType) keepOnlySideEffects(result) else result Some(Block(prep, result1)(body.pos)) @@ -5537,7 +5537,7 @@ private[optimizer] abstract class OptimizerCore( */ private def constrainedLub(lhs: RefinedType, rhs: RefinedType, upperBound: Type): RefinedType = { - if (upperBound == NoType) RefinedType(upperBound) + if (upperBound == VoidType) RefinedType(upperBound) else if (lhs == rhs) lhs else if (lhs.isNothingType) rhs else if (rhs.isNothingType) lhs @@ -5550,7 +5550,7 @@ private[optimizer] abstract class OptimizerCore( */ private def constrainedLub(lhs: Type, rhs: Type, upperBound: Type): Type = { // TODO Improve this - if (upperBound == NoType) upperBound + if (upperBound == VoidType) upperBound else if (lhs == rhs) lhs else if (lhs == NothingType) rhs else if (rhs == NothingType) lhs @@ -5770,7 +5770,7 @@ private[optimizer] object OptimizerCore { def apply(tpe: Type): RefinedType = { val isExact = tpe match { case NullType | NothingType | UndefType | BooleanType | CharType | - LongType | StringType | NoType => + LongType | StringType | VoidType => true case _ => /* At run-time, a byte will answer true to `x.isInstanceOf[Int]`, @@ -5782,7 +5782,7 @@ private[optimizer] object OptimizerCore { RefinedType(tpe, isExact) } - val NoRefinedType = RefinedType(NoType) + val NoRefinedType = RefinedType(VoidType) val Nothing = RefinedType(NothingType) } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala index 924cd3cabe..da9e9658c0 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala @@ -315,9 +315,9 @@ class AnalyzerTest { classDef("A", superClass = Some(ObjectClass), methods = List( trivialCtor("A"), - MethodDef(EMF, barMethodName, NON, Nil, NoType, Some(Block( - Apply(EAF, thisFor("A"), fooMethodName, Nil)(NoType), - Apply(EAF, New("B", NoArgConstructorName, Nil), fooMethodName, Nil)(NoType) + MethodDef(EMF, barMethodName, NON, Nil, VoidType, Some(Block( + Apply(EAF, thisFor("A"), fooMethodName, Nil)(VoidType), + Apply(EAF, New("B", NoArgConstructorName, Nil), fooMethodName, Nil)(VoidType) )))(EOH, UNV) )), classDef("B", superClass = Some("A"), @@ -413,7 +413,7 @@ class AnalyzerTest { val testName = m("test", Nil, O) val method = MethodDef( EMF.withNamespace(MemberNamespace.PublicStatic), - mainName, NON, Nil, NoType, + mainName, NON, Nil, VoidType, Some(SelectJSNativeMember("A", testName)))(EOH, UNV) val classDefs = Seq( @@ -433,7 +433,7 @@ class AnalyzerTest { @Test def conflictingDefaultMethods(): AsyncResult = await { val defaultMethodDef = MethodDef(EMF, m("foo", Nil, V), NON, Nil, - NoType, Some(Skip()))(EOH, UNV) + VoidType, Some(Skip()))(EOH, UNV) val classDefs = Seq( classDef("I1", kind = ClassKind.Interface, methods = List(defaultMethodDef)), @@ -603,7 +603,7 @@ class AnalyzerTest { val mainMethod = MethodDef( EMF.withNamespace(MemberNamespace.PublicStatic), - mainName, NON, Nil, NoType, + mainName, NON, Nil, VoidType, Some(SelectJSNativeMember("A", testName)))(EOH, UNV) val nativeMember = JSNativeMemberDef( EMF.withNamespace(MemberNamespace.PublicStatic), testName, diff --git a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala index fd2faebe8b..8617ae460d 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala @@ -48,7 +48,7 @@ class IRCheckerTest { val nullBarMethodName = m("nullBar", Nil, ClassRef(BarClass)) def callMethOn(receiver: Tree): Tree = - Apply(EAF, receiver, methMethodName, List(Null()))(NoType) + Apply(EAF, receiver, methMethodName, List(Null()))(VoidType) val classDefs = Seq( // LFoo will be dropped by base linking @@ -63,7 +63,7 @@ class IRCheckerTest { * instances of `Bar`. It will therefore not make `Foo` reachable. */ MethodDef(EMF, methMethodName, NON, - List(paramDef("foo", ClassType("Foo", nullable = true))), NoType, + List(paramDef("foo", ClassType("Foo", nullable = true))), VoidType, Some(Skip()))( EOH, UNV) ) @@ -114,7 +114,7 @@ class IRCheckerTest { interfaces = Nil, methods = List( MethodDef(EMF, fooMethodName, NON, - List(paramDef("x", ClassType(B, nullable = true))), NoType, Some(Skip()))( + List(paramDef("x", ClassType(B, nullable = true))), VoidType, Some(Skip()))( EOH, UNV) ) ), @@ -143,19 +143,19 @@ class IRCheckerTest { paramDef("c", ClassType(C, nullable = true)), paramDef("d", ClassType(D, nullable = true)) ), - NoType, + VoidType, Some(Block( Apply(EAF, VarRef("x")(receiverType), fooMethodName, - List(VarRef("c")(ClassType(C, nullable = true))))(NoType), + List(VarRef("c")(ClassType(C, nullable = true))))(VoidType), Apply(EAF, VarRef("x")(receiverType), fooMethodName, - List(VarRef("d")(ClassType(D, nullable = true))))(NoType) + List(VarRef("d")(ClassType(D, nullable = true))))(VoidType) )) )(EOH, UNV) ) ), mainTestClassDef( - ApplyStatic(EAF, D, testMethodName, List(newD, newD, newD))(NoType) + ApplyStatic(EAF, D, testMethodName, List(newD, newD, newD))(VoidType) ) ) @@ -220,7 +220,7 @@ class IRCheckerTest { for (log <- testLinkIRErrors(classDefs, MainTestModuleInitializers)) yield { log.assertContainsError( - "any expected but found for JS constructor body") + "any expected but void found for JS constructor body") } } @@ -249,7 +249,7 @@ class IRCheckerTest { for (log <- testLinkIRErrors(classDefs, MainTestModuleInitializers)) yield { log.assertContainsError( - "any expected but found for JS constructor body") + "any expected but void found for JS constructor body") } } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/IncrementalTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/IncrementalTest.scala index 245d36b326..105ba204d0 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/IncrementalTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/IncrementalTest.scala @@ -205,10 +205,10 @@ class IncrementalTest { val meth2 = m("meth2", Nil, VoidRef) def methDef(name: MethodName, body: Tree): MethodDef = - MethodDef(EMF, name, NON, Nil, NoType, Some(body))(EOH.withNoinline(true), UNV) + MethodDef(EMF, name, NON, Nil, VoidType, Some(body))(EOH.withNoinline(true), UNV) def callMeth(targetMeth: MethodName): Tree = - Apply(EAF, LoadModule(FooClass), targetMeth, Nil)(NoType) + Apply(EAF, LoadModule(FooClass), targetMeth, Nil)(VoidType) def classDefs(step: Int) = { val stepDependentMembers = step match { @@ -263,12 +263,12 @@ class IncrementalTest { def methDef(name: MethodName, body: Tree): MethodDef = { MethodDef(EMF.withNamespace(MemberNamespace.PublicStatic), name, NON, Nil, - NoType, Some(body))( + VoidType, Some(body))( EOH.withNoinline(true), UNV) } def callMeth(targetMeth: MethodName): Tree = - ApplyStatic(EAF, FooClass, targetMeth, Nil)(NoType) + ApplyStatic(EAF, FooClass, targetMeth, Nil)(VoidType) def classDefs(step: Int) = { val stepDependentMembers = step match { @@ -317,7 +317,7 @@ class IncrementalTest { ApplyStatically(EAF.withConstructor(true), thisFor(FooModule), ObjectClass, MethodIdent(NoArgConstructorName), - Nil)(NoType) + Nil)(VoidType) } val body = @@ -325,7 +325,7 @@ class IncrementalTest { else Block(superCtor, consoleLog(str("bar"))) MethodDef(MemberFlags.empty.withNamespace(MemberNamespace.Constructor), - MethodIdent(NoArgConstructorName), NON, Nil, NoType, + MethodIdent(NoArgConstructorName), NON, Nil, VoidType, Some(body))(EOH, UNV) } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/LibraryReachabilityTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/LibraryReachabilityTest.scala index 0aedcc4906..8b08bcf860 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LibraryReachabilityTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibraryReachabilityTest.scala @@ -43,7 +43,7 @@ class LibraryReachabilityTest { val classDefs = Seq( classDef("A", superClass = Some(ObjectClass), methods = List( trivialCtor("A"), - MethodDef(EMF, m("test", Nil, V), NON, Nil, NoType, Some(Block( + MethodDef(EMF, m("test", Nil, V), NON, Nil, VoidType, Some(Block( Apply(EAF, systemMod, m("getProperty", List(T), T), List(emptyStr))(StringType), Apply(EAF, systemMod, m("getProperty", List(T, T), T), List(emptyStr, emptyStr))(StringType), Apply(EAF, systemMod, m("setProperty", List(T, T), T), List(emptyStr, emptyStr))(StringType), @@ -72,7 +72,7 @@ class LibraryReachabilityTest { val classDefs = Seq( classDef("A", superClass = Some(ObjectClass), methods = List( trivialCtor("A"), - MethodDef(EMF, m("test", Nil, V), NON, Nil, NoType, Some(Block( + MethodDef(EMF, m("test", Nil, V), NON, Nil, VoidType, Some(Block( ApplyStatic(EAF, BoxedStringClass, formatMethod, List(str("hello %d"), int(42)))(StringType) )))(EOH, UNV) )) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala index 3854cd89ad..0a77c1414c 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala @@ -274,16 +274,16 @@ class OptimizerTest { val classDefs = Seq( mainTestClassDef(Block( - Labeled(matchResult1, NoType, Block( + Labeled(matchResult1, VoidType, Block( VarDef(x1, NON, AnyType, mutable = false, Null()), - Labeled(matchAlts1, NoType, Block( - Labeled(matchAlts2, NoType, Block( + Labeled(matchAlts1, VoidType, Block( + Labeled(matchAlts2, VoidType, Block( If(IsInstanceOf(VarRef(x1)(AnyType), ClassType(BoxedIntegerClass, nullable = false)), { Return(Undefined(), matchAlts2) - }, Skip())(NoType), + }, Skip())(VoidType), If(IsInstanceOf(VarRef(x1)(AnyType), ClassType(BoxedStringClass, nullable = false)), { Return(Undefined(), matchAlts2) - }, Skip())(NoType), + }, Skip())(VoidType), Return(Undefined(), matchAlts1) )), Return(Undefined(), matchResult1) @@ -505,7 +505,7 @@ class OptimizerTest { // this.y = 5 // this.jl.Object::() // } - MethodDef(EMF.withNamespace(Constructor), NoArgConstructorName, NON, Nil, NoType, Some(Block( + MethodDef(EMF.withNamespace(Constructor), NoArgConstructorName, NON, Nil, VoidType, Some(Block( Assign(Select(thisFor("Foo"), FieldName("Foo", "x"))(witnessType), Null()), Assign(Select(thisFor("Foo"), FieldName("Foo", "y"))(IntType), int(5)), trivialSuperCtorCall("Foo") diff --git a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala index 899abc9857..df23c6b3f5 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala @@ -173,7 +173,7 @@ class ClassDefCheckerTest { ArrayType(ArrayTypeRef(I, 1), nullable = false), RecordType(List(RecordType.Field("I", NON, IntType, mutable = true))), NothingType, - NoType + VoidType ) for (fieldType <- badFieldTypes) { @@ -210,7 +210,7 @@ class ClassDefCheckerTest { val callPrimaryCtorBody: Tree = { ApplyStatically(EAF.withConstructor(true), thisFor(FooClass), - FooClass, NoArgConstructorName, Nil)(NoType) + FooClass, NoArgConstructorName, Nil)(VoidType) } assertError( @@ -219,11 +219,11 @@ class ClassDefCheckerTest { trivialCtor(FooClass), MethodDef(EMF.withNamespace(MemberNamespace.Constructor), stringCtorName, NON, List(paramDef("x", BoxedStringType)), - NoType, Some(callPrimaryCtorBody))( + VoidType, Some(callPrimaryCtorBody))( EOH, UNV), MethodDef(EMF.withNamespace(MemberNamespace.Constructor), stringCtorName, NON, List(paramDef("y", BoxedStringType)), - NoType, Some(callPrimaryCtorBody))( + VoidType, Some(callPrimaryCtorBody))( EOH, UNV) )), "duplicate constructor method '(java.lang.String)void'") @@ -293,7 +293,7 @@ class ClassDefCheckerTest { def noDuplicateVarDefTryCatch(): Unit = { val body = Block( VarDef("x", NoOriginalName, IntType, mutable = false, int(1)), - TryCatch(Skip(), "x", NoOriginalName, Skip())(NoType) + TryCatch(Skip(), "x", NoOriginalName, Skip())(VoidType) ) assertError( @@ -310,7 +310,7 @@ class ClassDefCheckerTest { classDef( "Foo", superClass = Some(ObjectClass), methods = List( - MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, NoType, + MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, VoidType, Some(int(5)))(EOH, UNV) ) ), @@ -325,9 +325,9 @@ class ClassDefCheckerTest { classDef( "Foo", superClass = Some(ObjectClass), methods = List( - MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, NoType, Some { + MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, VoidType, Some { ApplyStatically(EAF.withConstructor(true), thisFor("Foo"), - "Bar", NoArgConstructorName, Nil)(NoType) + "Bar", NoArgConstructorName, Nil)(VoidType) })(EOH, UNV) ) ), @@ -341,7 +341,7 @@ class ClassDefCheckerTest { def ctorCall(receiver: Tree): ApplyStatically = { ApplyStatically(EAF.withConstructor(true), receiver, - ObjectClass, NoArgConstructorName, Nil)(NoType) + ObjectClass, NoArgConstructorName, Nil)(VoidType) } val thiz = thisFor("Foo") @@ -350,7 +350,7 @@ class ClassDefCheckerTest { classDef( "Foo", superClass = Some(ObjectClass), methods = List( - MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, NoType, Some(Block( + MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, VoidType, Some(Block( ctorCall(thiz), ctorCall(Null()) )))(EOH, UNV) @@ -362,9 +362,9 @@ class ClassDefCheckerTest { classDef( "Foo", superClass = Some(ObjectClass), methods = List( - MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, NoType, Some(Block( + MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, VoidType, Some(Block( ctorCall(thiz), - If(BooleanLiteral(true), ctorCall(thiz), Skip())(NoType) + If(BooleanLiteral(true), ctorCall(thiz), Skip())(VoidType) )))(EOH, UNV) ) ), @@ -375,7 +375,7 @@ class ClassDefCheckerTest { "Foo", superClass = Some(ObjectClass), methods = List( trivialCtor("Foo"), - MethodDef(EMF, m("foo", Nil, V), NON, Nil, NoType, Some(Block( + MethodDef(EMF, m("foo", Nil, V), NON, Nil, VoidType, Some(Block( ctorCall(thiz) )))(EOH, UNV) ) @@ -394,7 +394,7 @@ class ClassDefCheckerTest { classDef( "Foo", superClass = Some(ObjectClass), methods = List( - MethodDef(methodFlags, m("bar", Nil, V), NON, Nil, NoType, Some({ + MethodDef(methodFlags, m("bar", Nil, V), NON, Nil, VoidType, Some({ consoleLog(expr) }))(EOH, UNV) ) @@ -403,7 +403,7 @@ class ClassDefCheckerTest { } testThisTypeError(static = true, - This()(NoType), + This()(VoidType), "Cannot find `this` in scope") testThisTypeError(static = true, @@ -411,8 +411,8 @@ class ClassDefCheckerTest { "Cannot find `this` in scope") testThisTypeError(static = false, - This()(NoType), - "`this` of type Foo! typed as ") + This()(VoidType), + "`this` of type Foo! typed as void") testThisTypeError(static = false, This()(AnyType), @@ -431,7 +431,7 @@ class ClassDefCheckerTest { "`this` of type Foo! typed as Foo") testThisTypeError(static = false, - Closure(arrow = true, Nil, Nil, None, This()(NoType), Nil), + Closure(arrow = true, Nil, Nil, None, This()(VoidType), Nil), "Cannot find `this` in scope") testThisTypeError(static = false, @@ -439,8 +439,8 @@ class ClassDefCheckerTest { "Cannot find `this` in scope") testThisTypeError(static = false, - Closure(arrow = false, Nil, Nil, None, This()(NoType), Nil), - "`this` of type any typed as ") + Closure(arrow = false, Nil, Nil, None, This()(VoidType), Nil), + "`this` of type any typed as void") testThisTypeError(static = false, Closure(arrow = false, Nil, Nil, None, This()(ClassType("Foo", nullable = false)), Nil), @@ -460,7 +460,7 @@ class ClassDefCheckerTest { "Foo", superClass = Some(ObjectClass), methods = List( MethodDef(ctorFlags, MethodName.constructor(List(I)), NON, - List(xParamDef), NoType, Some(Block(ctorStats: _*)))(EOH, UNV) + List(xParamDef), VoidType, Some(Block(ctorStats: _*)))(EOH, UNV) ) ), "Restricted use of `this` before the super constructor call") @@ -497,7 +497,7 @@ class ClassDefCheckerTest { testRestrictedThisError( ApplyStatically(EAF.withConstructor(true), thiz, ObjectClass, MethodIdent(MethodName.constructor(List(O))), - List(thiz))(NoType) + List(thiz))(VoidType) ) } @@ -513,7 +513,7 @@ class ClassDefCheckerTest { kind = ClassKind.Class, superClass = Some(ObjectClass), methods = List( - MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, NoType, Some { + MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, VoidType, Some { Block( superCtorCall, StoreModule() @@ -530,7 +530,7 @@ class ClassDefCheckerTest { kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), methods = List( - MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, NoType, Some { + MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, VoidType, Some { Block( StoreModule(), superCtorCall @@ -547,10 +547,10 @@ class ClassDefCheckerTest { kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), methods = List( - MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, NoType, Some { + MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, VoidType, Some { Block( superCtorCall, - If(BooleanLiteral(true), StoreModule(), Skip())(NoType) + If(BooleanLiteral(true), StoreModule(), Skip())(VoidType) ) })(EOH, UNV) ) @@ -565,7 +565,7 @@ class ClassDefCheckerTest { superClass = Some(ObjectClass), methods = List( trivialCtor("Foo"), - MethodDef(EMF, MethodName("foo", Nil, VoidRef), NON, Nil, NoType, Some { + MethodDef(EMF, MethodName("foo", Nil, VoidRef), NON, Nil, VoidType, Some { Block( StoreModule() ) @@ -597,7 +597,7 @@ class ClassDefCheckerTest { jsConstructor = Some( JSConstructorDef(JSCtorFlags, Nil, None, JSConstructorBody(Nil, JSSuperConstructorCall(Nil), - If(BooleanLiteral(true), StoreModule(), Skip())(NoType) :: Undefined() :: Nil))( + If(BooleanLiteral(true), StoreModule(), Skip())(VoidType) :: Undefined() :: Nil))( EOH, UNV) ) ), @@ -624,7 +624,7 @@ class ClassDefCheckerTest { testAsInstanceOfError(tpe) } - testIsAsInstanceOfError(NoType) + testIsAsInstanceOfError(VoidType) testIsAsInstanceOfError(NullType) testIsAsInstanceOfError(NothingType) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala index 42c8736b02..d17d8e21c0 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala @@ -88,7 +88,7 @@ object TestIRBuilder { def trivialCtor(enclosingClassName: ClassName, parentClassName: ClassName = ObjectClass): MethodDef = { val flags = MemberFlags.empty.withNamespace(MemberNamespace.Constructor) - MethodDef(flags, MethodIdent(NoArgConstructorName), NON, Nil, NoType, + MethodDef(flags, MethodIdent(NoArgConstructorName), NON, Nil, VoidType, Some(trivialSuperCtorCall(enclosingClassName, parentClassName)))( EOH, UNV) } @@ -98,7 +98,7 @@ object TestIRBuilder { ApplyStatically(EAF.withConstructor(true), thisFor(enclosingClassName), parentClassName, MethodIdent(NoArgConstructorName), - Nil)(NoType) + Nil)(VoidType) } def trivialJSCtor: JSConstructorDef = { @@ -112,7 +112,7 @@ object TestIRBuilder { def mainMethodDef(body: Tree): MethodDef = { val argsParamDef = paramDef("args", ArrayType(AT, nullable = true)) MethodDef(MemberFlags.empty.withNamespace(MemberNamespace.PublicStatic), - MainMethodName, NON, List(argsParamDef), NoType, Some(body))( + MainMethodName, NON, List(argsParamDef), VoidType, Some(body))( EOH, UNV) } @@ -126,7 +126,7 @@ object TestIRBuilder { val out = ApplyStatic(EAF, "java.lang.System", outMethodName, Nil)( ClassType(PrintStreamClass, nullable = true)) - Apply(EAF, out, printlnMethodName, List(expr))(NoType) + Apply(EAF, out, printlnMethodName, List(expr))(VoidType) } def paramDef(name: LocalName, ptpe: Type): ParamDef = diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 959802dff4..75c5dbb18a 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -7,7 +7,9 @@ object BinaryIncompatibilities { val IR = Seq( // !!! Breaking, OK in minor release ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$JSLinkingInfo"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$JSLinkingInfo$") + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$JSLinkingInfo$"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.ir.Trees#*.tpe"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Types$NoType$"), ) val Linker = Seq( diff --git a/project/JavaLangObject.scala b/project/JavaLangObject.scala index c316ecc7a0..789d57df73 100644 --- a/project/JavaLangObject.scala +++ b/project/JavaLangObject.scala @@ -51,7 +51,7 @@ object JavaLangObject { MethodIdent(NoArgConstructorName), NoOriginalName, Nil, - NoType, + VoidType, Some(Skip()))(OptimizerHints.empty, Unversioned), /* def getClass(): java.lang.Class[_] = (this) */ @@ -148,7 +148,7 @@ object JavaLangObject { MethodIdent(MethodName("notify", Nil, VoidRef)), NoOriginalName, Nil, - NoType, + VoidType, Some(Skip()))(OptimizerHints.empty, Unversioned), /* def notifyAll(): Unit = () */ @@ -157,7 +157,7 @@ object JavaLangObject { MethodIdent(MethodName("notifyAll", Nil, VoidRef)), NoOriginalName, Nil, - NoType, + VoidType, Some(Skip()))(OptimizerHints.empty, Unversioned), /* def finalize(): Unit = () */ @@ -166,7 +166,7 @@ object JavaLangObject { MethodIdent(MethodName("finalize", Nil, VoidRef)), NoOriginalName, Nil, - NoType, + VoidType, Some(Skip()))(OptimizerHints.empty, Unversioned), ), jsConstructor = None, From ff197d81228e94b6d86cb890dbb412bb27340748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 21 Oct 2024 13:26:23 +0200 Subject: [PATCH 020/121] Restrict usages of `StoreModule` even further. * Must appear exactly once, right after the super constructor call, in module classes. * Cannot appear anywhere else. This was not actually the case for JS module classes. We never emitted `StoreModule`s in JS constructors. We use a deserialization hack to introduce them, and add tests for the corner case that `StoreModule` is meant to solve. In this commit, we always enable the deserialization hack, and do not change the compiler yet. --- .../scala/org/scalajs/ir/Serializers.scala | 18 ++++- .../linker/checker/ClassDefChecker.scala | 30 +++++-- .../org/scalajs/linker/AnalyzerTest.scala | 19 ++--- .../org/scalajs/linker/IRCheckerTest.scala | 2 +- .../org/scalajs/linker/IncrementalTest.scala | 19 ++--- .../linker/checker/ClassDefCheckerTest.scala | 79 +++++++++++++++++-- .../linker/testutils/TestIRBuilder.scala | 24 ++++-- .../compiler/StoreModuleJSTest.scala | 42 ++++++++++ .../testsuite/compiler/StoreModuleTest.scala | 40 ++++++++++ 9 files changed, 229 insertions(+), 44 deletions(-) create mode 100644 test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/StoreModuleJSTest.scala create mode 100644 test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/StoreModuleTest.scala diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index 5a69b79b42..de1c74d161 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -1560,7 +1560,7 @@ object Serializers { case TagFieldDef => fieldsBuilder += readFieldDef() case TagJSFieldDef => fieldsBuilder += readJSFieldDef() case TagMethodDef => methodsBuilder += readMethodDef(cls, kind) - case TagJSConstructorDef => jsConstructorBuilder += readJSConstructorDef() + case TagJSConstructorDef => jsConstructorBuilder += readJSConstructorDef(kind) case TagJSMethodDef => jsMethodPropsBuilder += readJSMethodDef() case TagJSPropertyDef => jsMethodPropsBuilder += readJSPropertyDef() case TagJSNativeMemberDef => jsNativeMembersBuilder += readJSNativeMemberDef() @@ -1986,7 +1986,9 @@ object Serializers { } } - private def readJSConstructorDef()(implicit pos: Position): JSConstructorDef = { + private def readJSConstructorDef(ownerKind: ClassKind)( + implicit pos: Position): JSConstructorDef = { + val optHash = readOptHash() // read and discard the length val len = readInt() @@ -2001,7 +2003,17 @@ object Serializers { val bodyPos = readPosition() val beforeSuper = readTrees() val superCall = readTree().asInstanceOf[JSSuperConstructorCall] - val afterSuper = readTrees() + val afterSuper0 = readTrees() + + val afterSuper = if (true /*hacks.use17*/ && ownerKind == ClassKind.JSModuleClass) { // scalastyle:ignore + afterSuper0 match { + case StoreModule() :: _ => afterSuper0 + case _ => StoreModule()(superCall.pos) :: afterSuper0 + } + } else { + afterSuper0 + } + val body = JSConstructorBody(beforeSuper, superCall, afterSuper)(bodyPos) JSConstructorDef(flags, params, restParam, body)( OptimizerHints.fromBits(readInt()), optHash) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index b23a83d5fc..baf49343e3 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -592,14 +592,30 @@ private final class ClassDefChecker(classDef: ClassDef, } } - private def handleStoreModulesAfterSuperCtorCall(trees: List[Tree]): List[Tree] = { - /* If StoreModule's are allowed, there is nothing left to check about them, - * so we filter them out. - */ - if (classDef.kind.hasModuleAccessor) - trees.filter(!_.isInstanceOf[StoreModule]) - else + private def handleStoreModulesAfterSuperCtorCall(trees: List[Tree])( + implicit ctx: ErrorContext): List[Tree] = { + + if (classDef.kind.hasModuleAccessor) { + if (postOptimizer) { + /* If the super constructor call was inlined, the StoreModule can be anywhere. + * Moreover, the optimizer can remove StoreModules altogether in many cases. + */ + trees.filter(!_.isInstanceOf[StoreModule]) + } else { + /* Before the optimizer, there must be a StoreModule and it must come + * right after the super constructor call. + */ + trees match { + case StoreModule() :: rest => + rest + case _ => + reportError(i"Missing StoreModule right after the super constructor call") + trees + } + } + } else { trees + } } private def checkBlockStats(stats: List[Tree], env: Env): Env = { diff --git a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala index da9e9658c0..ad0aeb6655 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala @@ -467,7 +467,7 @@ class AnalyzerTest { "A", kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), - methods = List(trivialCtor("A")), + methods = List(trivialCtor("A", forModuleClass = true)), topLevelExportDefs = List( TopLevelMethodExportDef("main", JSMethodDef( EMF.withNamespace(MemberNamespace.PublicStatic), @@ -492,7 +492,7 @@ class AnalyzerTest { def singleDef(name: String) = { classDef(name, kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), - methods = List(trivialCtor(name)), + methods = List(trivialCtor(name, forModuleClass = true)), topLevelExportDefs = List(TopLevelModuleExportDef(name, "foo"))) } @@ -515,7 +515,7 @@ class AnalyzerTest { def singleDef(name: String) = { classDef(name, kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), - methods = List(trivialCtor(name)), + methods = List(trivialCtor(name, forModuleClass = true)), topLevelExportDefs = List(TopLevelModuleExportDef("main", "foo"))) } @@ -536,7 +536,7 @@ class AnalyzerTest { def degenerateConflictingTopLevelExports(): AsyncResult = await { val classDefs = Seq(classDef("A", kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), - methods = List(trivialCtor("A")), + methods = List(trivialCtor("A", forModuleClass = true)), topLevelExportDefs = List( TopLevelModuleExportDef("main", "foo"), TopLevelModuleExportDef("main", "foo")))) @@ -552,7 +552,7 @@ class AnalyzerTest { val classDefs = Seq(classDef("A", kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), methods = List( - trivialCtor("A"), + trivialCtor("A", forModuleClass = true), mainMethodDef(Skip()) ), topLevelExportDefs = List(TopLevelModuleExportDef("A", "foo")))) @@ -630,9 +630,8 @@ class AnalyzerTest { val classDefs = Seq( classDef("A", - kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), + kind = ClassKind.Class, superClass = Some(ObjectClass), methods = List( - trivialCtor("A"), mainMethodDef(ApplyDynamicImport(EAF, "B", dynName, Nil))) ), classDef("B", @@ -688,9 +687,8 @@ class AnalyzerTest { def importMetaWithoutESModule(): AsyncResult = await { val classDefs = Seq( classDef("A", - kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), + kind = ClassKind.Class, superClass = Some(ObjectClass), methods = List( - trivialCtor("A"), mainMethodDef(JSImportMeta()) ) ) @@ -812,9 +810,8 @@ class AnalyzerTest { def test(invalidLinkTimeProperty: LinkTimeProperty): Future[Unit] = { val classDefs = Seq( classDef("A", - kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), + kind = ClassKind.Class, superClass = Some(ObjectClass), methods = List( - trivialCtor("A"), mainMethodDef(invalidLinkTimeProperty) ) ) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala index 8617ae460d..66455360d1 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala @@ -176,7 +176,7 @@ class IRCheckerTest { classDef("B", kind = ClassKind.NativeJSClass, superClass = Some(ObjectClass)), classDef("C", kind = ClassKind.NativeJSModuleClass, superClass = Some(ObjectClass)), - classDef("D", kind = ClassKind.JSClass, superClass = Some("A"), jsConstructor = Some(trivialJSCtor)), + classDef("D", kind = ClassKind.JSClass, superClass = Some("A"), jsConstructor = Some(trivialJSCtor())), mainTestClassDef(Block( LoadJSConstructor("B"), diff --git a/linker/shared/src/test/scala/org/scalajs/linker/IncrementalTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/IncrementalTest.scala index 105ba204d0..7129402959 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/IncrementalTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/IncrementalTest.scala @@ -57,7 +57,7 @@ class IncrementalTest { kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), methods = List( - trivialCtor(FooClass), + trivialCtor(FooClass, forModuleClass = true), MethodDef(EMF.withNamespace(MemberNamespace.PublicStatic), staticMethodName, NON, Nil, IntType, Some(int(6)))(EOH, UNV) ), @@ -238,7 +238,7 @@ class IncrementalTest { List( v -> classDef(FooClass, kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), - methods = trivialCtor(FooClass) :: stepDependentMembers), + methods = trivialCtor(FooClass, forModuleClass = true) :: stepDependentMembers), v -> mainTestClassDef(Block(stepDependentMainStats)) ) @@ -313,12 +313,13 @@ class IncrementalTest { val FooModule = ClassName("Foo") def fooCtor(pre: Boolean) = { - val superCtor = { + val superCtor = Block( ApplyStatically(EAF.withConstructor(true), thisFor(FooModule), ObjectClass, MethodIdent(NoArgConstructorName), - Nil)(VoidType) - } + Nil)(VoidType), + StoreModule() + ) val body = if (pre) superCtor @@ -362,7 +363,7 @@ class IncrementalTest { kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), methods = List( - trivialCtor(AModule) + trivialCtor(AModule, forModuleClass = true) ), jsMethodProps = List( JSMethodDef(EMF, str("foo"), Nil, None, @@ -374,7 +375,7 @@ class IncrementalTest { kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), methods = List( - trivialCtor(BModule), + trivialCtor(BModule, forModuleClass = true), MethodDef(EMF, targetMethodName, NON, Nil, IntType, Some(int(if (pre) 1 else 2)))(EOH.withInline(true), UNV) ) @@ -419,7 +420,7 @@ class IncrementalTest { kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), methods = List( - trivialCtor(BModule), + trivialCtor(BModule, forModuleClass = true), MethodDef(EMF, targetMethodName, NON, Nil, IntType, Some(int(if (pre) 1 else 2)))(EOH.withInline(true), UNV) ) @@ -451,7 +452,7 @@ class IncrementalTest { kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), methods = List( - trivialCtor(BModule), + trivialCtor(BModule, forModuleClass = true), MethodDef(EMF, targetMethodName, NON, Nil, IntType, Some(int(if (pre) 1 else 2)))(EOH.withInline(true), UNV) ) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala index df23c6b3f5..1d338034d8 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala @@ -145,7 +145,7 @@ class ClassDefCheckerTest { fields = List( FieldDef(EMF.withNamespace(MemberNamespace.PublicStatic), FieldName("A", "foo"), NON, IntType) ), - methods = List(trivialCtor("A")), + methods = List(trivialCtor("A", forModuleClass = true)), topLevelExportDefs = List( TopLevelFieldExportDef("main", "foo", FieldName("B", "foo")) ) @@ -533,7 +533,8 @@ class ClassDefCheckerTest { MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, VoidType, Some { Block( StoreModule(), - superCtorCall + superCtorCall, + StoreModule() ) })(EOH, UNV) ) @@ -541,6 +542,39 @@ class ClassDefCheckerTest { "Illegal StoreModule" ) + assertError( + classDef( + "Foo", + kind = ClassKind.ModuleClass, + superClass = Some(ObjectClass), + methods = List( + MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, VoidType, Some { + Block( + superCtorCall + ) + })(EOH, UNV) + ) + ), + "Missing StoreModule right after the super constructor call" + ) + + assertError( + classDef( + "Foo", + kind = ClassKind.ModuleClass, + superClass = Some(ObjectClass), + methods = List( + MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, VoidType, Some { + Block( + superCtorCall, + IntLiteral(1) + ) + })(EOH, UNV) + ) + ), + "Missing StoreModule right after the super constructor call" + ) + assertError( classDef( "Foo", @@ -550,6 +584,7 @@ class ClassDefCheckerTest { MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, VoidType, Some { Block( superCtorCall, + StoreModule(), If(BooleanLiteral(true), StoreModule(), Skip())(VoidType) ) })(EOH, UNV) @@ -564,7 +599,25 @@ class ClassDefCheckerTest { kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), methods = List( - trivialCtor("Foo"), + MethodDef(ctorFlags, NoArgConstructorName, NON, Nil, VoidType, Some { + Block( + superCtorCall, + StoreModule(), + StoreModule() + ) + })(EOH, UNV) + ) + ), + "Illegal StoreModule" + ) + + assertError( + classDef( + "Foo", + kind = ClassKind.ModuleClass, + superClass = Some(ObjectClass), + methods = List( + trivialCtor("Foo", forModuleClass = true), MethodDef(EMF, MethodName("foo", Nil, VoidRef), NON, Nil, VoidType, Some { Block( StoreModule() @@ -582,7 +635,8 @@ class ClassDefCheckerTest { superClass = Some("scala.scalajs.js.Object"), jsConstructor = Some( JSConstructorDef(JSCtorFlags, Nil, None, - JSConstructorBody(StoreModule() :: Nil, JSSuperConstructorCall(Nil), Undefined() :: Nil))( + JSConstructorBody(StoreModule() :: Nil, JSSuperConstructorCall(Nil), + StoreModule() :: Undefined() :: Nil))( EOH, UNV) ) ), @@ -597,7 +651,22 @@ class ClassDefCheckerTest { jsConstructor = Some( JSConstructorDef(JSCtorFlags, Nil, None, JSConstructorBody(Nil, JSSuperConstructorCall(Nil), - If(BooleanLiteral(true), StoreModule(), Skip())(VoidType) :: Undefined() :: Nil))( + Undefined() :: Nil))( + EOH, UNV) + ) + ), + "Missing StoreModule right after the super constructor call" + ) + + assertError( + classDef( + "Foo", + kind = ClassKind.JSModuleClass, + superClass = Some("scala.scalajs.js.Object"), + jsConstructor = Some( + JSConstructorDef(JSCtorFlags, Nil, None, + JSConstructorBody(Nil, JSSuperConstructorCall(Nil), + StoreModule() :: StoreModule() :: Undefined() :: Nil))( EOH, UNV) ) ), diff --git a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala index d17d8e21c0..295d78fd29 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala @@ -77,7 +77,7 @@ object TestIRBuilder { def mainTestClassDef(mainBody: Tree): ClassDef = { classDef( MainTestClassName, - kind = ClassKind.ModuleClass, + kind = ClassKind.Class, superClass = Some(ObjectClass), methods = List( trivialCtor(MainTestClassName), @@ -86,10 +86,15 @@ object TestIRBuilder { ) } - def trivialCtor(enclosingClassName: ClassName, parentClassName: ClassName = ObjectClass): MethodDef = { + def trivialCtor(enclosingClassName: ClassName, + parentClassName: ClassName = ObjectClass, + forModuleClass: Boolean = false): MethodDef = { val flags = MemberFlags.empty.withNamespace(MemberNamespace.Constructor) - MethodDef(flags, MethodIdent(NoArgConstructorName), NON, Nil, VoidType, - Some(trivialSuperCtorCall(enclosingClassName, parentClassName)))( + val superCtorCall = trivialSuperCtorCall(enclosingClassName, parentClassName) + val body = + if (forModuleClass) Block(superCtorCall, StoreModule()) + else superCtorCall + MethodDef(flags, MethodIdent(NoArgConstructorName), NON, Nil, VoidType, Some(body))( EOH, UNV) } @@ -101,9 +106,12 @@ object TestIRBuilder { Nil)(VoidType) } - def trivialJSCtor: JSConstructorDef = { + def trivialJSCtor(forModuleClass: Boolean = false): JSConstructorDef = { + val afterSuper = + if (forModuleClass) StoreModule() :: Undefined() :: Nil + else Undefined() :: Nil JSConstructorDef(JSCtorFlags, Nil, None, - JSConstructorBody(Nil, JSSuperConstructorCall(Nil), Undefined() :: Nil))( + JSConstructorBody(Nil, JSSuperConstructorCall(Nil), afterSuper))( EOH, UNV) } @@ -149,13 +157,13 @@ object TestIRBuilder { def requiredMethods(className: ClassName, classKind: ClassKind, parentClassName: ClassName = ObjectClass): List[MethodDef] = { if (classKind == ClassKind.ModuleClass) - List(trivialCtor(className, parentClassName)) + List(trivialCtor(className, parentClassName, forModuleClass = true)) else Nil } def requiredJSConstructor(classKind: ClassKind): Option[JSConstructorDef] = { - if (classKind.isJSClass) Some(trivialJSCtor) + if (classKind.isJSClass) Some(trivialJSCtor(forModuleClass = classKind.hasModuleAccessor)) else None } diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/StoreModuleJSTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/StoreModuleJSTest.scala new file mode 100644 index 0000000000..677b81161b --- /dev/null +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/StoreModuleJSTest.scala @@ -0,0 +1,42 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.testsuite.compiler + +import scala.scalajs.js + +import org.junit.Test +import org.junit.Assert._ + +class StoreModuleJSTest { + import StoreModuleJSTest._ + + @Test def jsModuleClass(): Unit = { + val a = JSObjA + val b = JSObjB + + assertNotNull(a) + assertNotNull(b) + assertSame(a, b.a) + assertSame(b, a.b) + } +} + +object StoreModuleJSTest { + object JSObjA extends js.Object { + val b = JSObjB + } + + object JSObjB extends js.Object { + val a = JSObjA + } +} diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/StoreModuleTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/StoreModuleTest.scala new file mode 100644 index 0000000000..e47906845f --- /dev/null +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/StoreModuleTest.scala @@ -0,0 +1,40 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.testsuite.compiler + +import org.junit.Test +import org.junit.Assert._ + +class StoreModuleTest { + import StoreModuleTest._ + + @Test def scalaModuleClass(): Unit = { + val a = ScalaObjA + val b = ScalaObjB + + assertNotNull(a) + assertNotNull(b) + assertSame(a, b.a) + assertSame(b, a.b) + } +} + +object StoreModuleTest { + object ScalaObjA { + val b = ScalaObjB + } + + object ScalaObjB { + val a = ScalaObjA + } +} From d36cba3eaf7482e8e4b66cd0ad2adb24533e6b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 2 Nov 2024 19:24:38 +0100 Subject: [PATCH 021/121] Insert `StoreModule`s for JS module classes in the compiler. And only enable the deserialization when reading IR < 1.18. --- .../src/main/scala/org/scalajs/nscplugin/GenJSCode.scala | 9 ++++++++- .../src/main/scala/org/scalajs/ir/Serializers.scala | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 5ac71f2c26..81cfa6df15 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -1610,7 +1610,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) var preSuperStats = List.newBuilder[js.Tree] var jsSuperCall: Option[js.JSSuperConstructorCall] = None - val postSuperStats = List.newBuilder[js.Tree] + val postSuperStats = mutable.ListBuffer.empty[js.Tree] /* Move param accessor initializers and early initializers after the * super constructor call since JS cannot access `this` before the super @@ -1714,6 +1714,13 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) assert(jsSuperCall.isDefined, "Did not find Super call in primary JS " + s"construtor at ${dd.pos}") + /* Insert a StoreModule if required. + * Do this now so we have the pos of the super ctor call. + * +=: prepends to the ListBuffer in O(1) -- yes, it's a cryptic name. + */ + if (isStaticModule(currentClassSym)) + js.StoreModule()(jsSuperCall.get.pos) +=: postSuperStats + new PrimaryJSCtor(sym, genParamsAndInfo(sym, vparamss), js.JSConstructorBody(preSuperStats.result(), jsSuperCall.get, postSuperStats.result())(dd.pos)) } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index de1c74d161..0a366a12b5 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -2005,7 +2005,7 @@ object Serializers { val superCall = readTree().asInstanceOf[JSSuperConstructorCall] val afterSuper0 = readTrees() - val afterSuper = if (true /*hacks.use17*/ && ownerKind == ClassKind.JSModuleClass) { // scalastyle:ignore + val afterSuper = if (hacks.use17 && ownerKind == ClassKind.JSModuleClass) { afterSuper0 match { case StoreModule() :: _ => afterSuper0 case _ => StoreModule()(superCall.pos) :: afterSuper0 From ab0f29b4cc68864ad192ddef76491e3b4cd5c6d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 7 Nov 2024 13:47:37 +0100 Subject: [PATCH 022/121] Wasm: Implement full support of `ForIn` using a JS generator function. We can abuse a JS generator function (`function*`) to turn the "push-based" `for..in` loop of JavaScript into a "pull-based" generator. This way we can call it from Wasm and keep the loop body inline, without needing to pass a callback function to JS. This allows to implement any shape of `ForIn` loop. We can therefore remove the optimizer limitation for those loops in Wasm. --- .../backend/wasmemitter/CoreWasmLib.scala | 3 +- .../backend/wasmemitter/FunctionEmitter.scala | 50 +++++++++---------- .../backend/wasmemitter/LoaderContent.scala | 3 +- .../linker/backend/wasmemitter/VarGen.scala | 3 +- .../frontend/optimizer/OptimizerCore.scala | 16 +----- 5 files changed, 32 insertions(+), 43 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala index 6a00aaab2c..73bf05e3d6 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala @@ -414,7 +414,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { addHelperImport(genFunctionID.jsImportCall, List(anyref), List(anyref)) addHelperImport(genFunctionID.jsImportMeta, Nil, List(anyref)) addHelperImport(genFunctionID.jsDelete, List(anyref, anyref), Nil) - addHelperImport(genFunctionID.jsForInSimple, List(anyref, anyref), Nil) + addHelperImport(genFunctionID.jsForInStart, List(anyref), List(anyref)) + addHelperImport(genFunctionID.jsForInNext, List(anyref), List(anyref, Int32)) addHelperImport(genFunctionID.jsIsTruthy, List(anyref), List(Int32)) addHelperImport(genFunctionID.newSymbol, Nil, List(anyref)) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index d8f18921b4..83a6854719 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -2421,34 +2421,34 @@ private class FunctionEmitter private ( } private def genForIn(tree: ForIn): Type = { - /* This is tricky. In general, the body of a ForIn can be an arbitrary - * statement, which can refer to the enclosing scope and its locals, - * including for mutations. Unfortunately, there is no way to implement a - * ForIn other than actually doing a JS `for (var key in obj) { body }` - * loop. That means we need to pass the `body` as a JS closure. - * - * That is problematic for our backend because we basically need to perform - * lambda lifting: identifying captures ourselves, and turn references to - * local variables into accessing the captured environment. - * - * We side-step this issue for now by exploiting the known shape of `ForIn` - * generated by the Scala.js compiler. This is fine as long as we do not - * support the Scala.js optimizer. We will have to revisit this code when - * we add that support. - */ + val ForIn(obj, LocalIdent(keyVarName), keyVarOrigName, body) = tree - val ForIn(obj, LocalIdent(keyVarName), _, body) = tree + val generatorLocal = addSyntheticLocal(watpe.RefType.anyref) - body match { - case JSFunctionApply(fVarRef: VarRef, List(VarRef(argIdent))) - if fVarRef.ident.name != keyVarName && argIdent.name == keyVarName => - genTree(obj, AnyType) - genTree(fVarRef, AnyType) - markPosition(tree) - fb += wa.Call(genFunctionID.jsForInSimple) + // Evaluate the obj and create the for..in generator + genTree(obj, AnyType) + markPosition(tree) + fb += wa.Call(genFunctionID.jsForInStart) + fb += wa.LocalSet(generatorLocal) - case _ => - throw new NotImplementedError(s"Unsupported shape of ForIn node at ${tree.pos}: $tree") + // Loop over the keys returned by the generator + fb.block() { doneLabel => + fb.loop() { loopLabel => + fb += wa.LocalGet(generatorLocal) + fb += wa.Call(genFunctionID.jsForInNext) // returns [value, done] + + // if done, jump to done + fb += wa.BrIf(doneLabel) + + // otherwise, store the value and execute the body + withNewLocal(keyVarName, keyVarOrigName, watpe.RefType.anyref) { keyLocal => + fb += wa.LocalSet(keyLocal) + genTree(body, VoidType) + } + + // and loop back + fb += wa.Br(loopLabel) + } } VoidType diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala index a94e132626..a096a66c5d 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala @@ -158,7 +158,8 @@ const scalaJSHelpers = { jsImportCall: (s) => import(s), jsImportMeta: () => import.meta, jsDelete: (o, p) => { delete o[p]; }, - jsForInSimple: (o, f) => { for (var k in o) f(k); }, + jsForInStart: function*(o) { for (var k in o) yield k; }, + jsForInNext: (g) => { var r = g.next(); return [r.value, r.done]; }, jsIsTruthy: (x) => !!x, // Non-native JS class support diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala index 6a01f55cb6..c71230948d 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala @@ -155,7 +155,8 @@ object VarGen { case object jsImportCall extends JSHelperFunctionID case object jsImportMeta extends JSHelperFunctionID case object jsDelete extends JSHelperFunctionID - case object jsForInSimple extends JSHelperFunctionID + case object jsForInStart extends JSHelperFunctionID + case object jsForInNext extends JSHelperFunctionID case object jsIsTruthy extends JSHelperFunctionID case object newSymbol extends JSHelperFunctionID diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index adaac12ee7..d69da3d2e4 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -425,21 +425,7 @@ private[optimizer] abstract class OptimizerCore( val localDef = LocalDef(RefinedType(AnyType), mutable = false, ReplaceWithVarRef(newName, newSimpleState(UsedAtLeastOnce))) val bodyScope = scope.withEnv(scope.env.withLocalDef(name, localDef)) - - val newBody = if (isWasm) { - // Avoid destroying the only shape of ForIn that the Wasm backend can handle - body match { - case JSFunctionApply(f: VarRef, List(arg: VarRef)) => - JSFunctionApply(transformExpr(f)(bodyScope), - List(transformExpr(arg)(bodyScope)))(body.pos) - case _ => - // Wasm will not be able to deal with anything else, but we cannot do anything about it - transformStat(body)(bodyScope) - } - } else { - transformStat(body)(bodyScope) - } - + val newBody = transformStat(body)(bodyScope) ForIn(newObj, LocalIdent(newName)(keyVar.pos), newOriginalName, newBody) case TryCatch(block, errVar @ LocalIdent(name), originalName, handler) => From 41e9e118d8ccb29ca1b98320992c2a93e883ff85 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 13 Oct 2024 09:19:07 +0200 Subject: [PATCH 023/121] IRChecker: Allow subtypes of ArrayType in array position --- .../org/scalajs/linker/checker/IRChecker.scala | 6 +++++- .../scala/org/scalajs/linker/IRCheckerTest.scala | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index f6df8de449..3ab776070b 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -499,13 +499,17 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { case ArrayLength(array) => typecheckExpr(array, env) - if (!array.tpe.isInstanceOf[ArrayType]) + if (array.tpe != NullType && + array.tpe != NothingType && + !array.tpe.isInstanceOf[ArrayType]) reportError(i"Array type expected but ${array.tpe} found") case ArraySelect(array, index) => typecheckExpect(index, env, IntType) typecheckExpr(array, env) array.tpe match { + case NothingType => // ok + case NullType => // will NPE, but allowed. case arrayType: ArrayType => if (tree.tpe != arrayElemType(arrayType)) reportError(i"Array select of array type $arrayType typed as ${tree.tpe}") diff --git a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala index 66455360d1..cc4847d684 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala @@ -253,6 +253,22 @@ class IRCheckerTest { } } + @Test + def arrayOpsNullOrNothing(): AsyncResult = await { + val classDefs = Seq( + mainTestClassDef( + Block( + ArraySelect(Null(), int(1))(NothingType), + ArrayLength(Null()), + ArraySelect(Throw(Null()), int(1))(NothingType), + ArrayLength(Throw(Null())) + ) + ) + ) + + testLinkNoIRError(classDefs, MainTestModuleInitializers) + } + } object IRCheckerTest { From d5a3baf0b4741179986edd11ad4a07acb6f988a7 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 10 Nov 2024 10:25:09 +0100 Subject: [PATCH 024/121] Optimizer: Add `fieldsReadAskers` to `unregisterDependee` Forgotten in #4681 / d50b69fc52f8559aea33f9c4dd2de74eac91ea74. --- .../org/scalajs/linker/frontend/optimizer/IncOptimizer.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index 15818137e4..16c576a6ac 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -1479,6 +1479,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: dynamicCallers.forEachValue(Long.MaxValue, _.remove(dependee)) staticCallers.foreach(_.forEachValue(Long.MaxValue, _.remove(dependee))) jsNativeImportsAskers.remove(dependee) + fieldsReadAskers.remove(dependee) } private def computeJSNativeImports(linkedClass: LinkedClass): JSNativeImports = { From f0aa3dff02aa34032525e40024f67ec9f7b64572 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 10 Nov 2024 10:59:28 +0100 Subject: [PATCH 025/121] Optimizer: Use constrainedLub to re-type `Match` This is cleaner in general, but we specifically do this to profit from the `isStat` fix in the next commit. --- .../linker/frontend/optimizer/OptimizerCore.scala | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index adaac12ee7..591ded9e55 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -475,9 +475,13 @@ private[optimizer] abstract class OptimizerCore( }.getOrElse(default) transform(body, isStat) case _ => - Match(newSelector, - cases map (c => (c._1, transform(c._2, isStat))), - transform(default, isStat))(tree.tpe) + val newCases = cases.map(c => (c._1, transform(c._2, isStat))) + val newDefault = transform(default, isStat) + + val refinedType = (newDefault.tpe :: newCases.map(_._2.tpe)) + .reduce(constrainedLub(_, _, tree.tpe)) + + Match(newSelector, newCases, newDefault)(refinedType) } // Scala expressions From b46a0942ab24e3a72503700616fc1fb19be94d10 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 10 Nov 2024 10:52:36 +0100 Subject: [PATCH 026/121] Optimizer: Add isStat flag to constrainedLub Split out from #5066. This is to ensure that we correctly raise the overall tree type in case we are in statement position. Before this commit, the inner trees were typed as `VoidType` (due to the `isStat` flag on `transform`), but the outer (aggregate) tree kept its original (expression) type. This lead to ill-typed IR. --- .../frontend/optimizer/OptimizerCore.scala | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 591ded9e55..58f245d4a6 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -407,7 +407,7 @@ private[optimizer] abstract class OptimizerCore( val newThenp = transform(thenp, isStat) val newElsep = transform(elsep, isStat) val refinedType = - constrainedLub(newThenp.tpe, newElsep.tpe, tree.tpe) + constrainedLub(newThenp.tpe, newElsep.tpe, tree.tpe, isStat) foldIf(newCond, newThenp, newElsep)(refinedType) } @@ -454,7 +454,7 @@ private[optimizer] abstract class OptimizerCore( transform(handler, isStat)(handlerScope) } - val refinedType = constrainedLub(newBlock.tpe, newHandler.tpe, tree.tpe) + val refinedType = constrainedLub(newBlock.tpe, newHandler.tpe, tree.tpe, isStat) TryCatch(newBlock, LocalIdent(newName)(errVar.pos), newOriginalName, newHandler)(refinedType) @@ -479,7 +479,7 @@ private[optimizer] abstract class OptimizerCore( val newDefault = transform(default, isStat) val refinedType = (newDefault.tpe :: newCases.map(_._2.tpe)) - .reduce(constrainedLub(_, _, tree.tpe)) + .reduce(constrainedLub(_, _, tree.tpe, isStat)) Match(newSelector, newCases, newDefault)(refinedType) } @@ -1190,7 +1190,7 @@ private[optimizer] abstract class OptimizerCore( val newThenp = finishTransformExpr(tthenpNoLocalDef) val newElsep = finishTransformExpr(telsepNoLocalDef) val refinedType = - constrainedLub(newThenp.tpe, newElsep.tpe, tree.tpe) + constrainedLub(newThenp.tpe, newElsep.tpe, tree.tpe, isStat = false) cont(foldIf(newCond, newThenp, newElsep)( refinedType).toPreTransform) } @@ -1200,7 +1200,7 @@ private[optimizer] abstract class OptimizerCore( val newThenp = transformExpr(thenp) val newElsep = transformExpr(elsep) val refinedType = - constrainedLub(newThenp.tpe, newElsep.tpe, tree.tpe) + constrainedLub(newThenp.tpe, newElsep.tpe, tree.tpe, isStat = false) cont(foldIf(newCond, newThenp, newElsep)( refinedType).toPreTransform) } @@ -5161,7 +5161,7 @@ private[optimizer] abstract class OptimizerCore( def doMakeTree(newBody: Tree, returnedTypes: List[Type]): Tree = { val refinedType = - returnedTypes.reduce(constrainedLub(_, _, resultType)) + returnedTypes.reduce(constrainedLub(_, _, resultType, isStat)) val returnCount = returnedTypes.size - 1 tryOptimizePatternMatch(oldLabelName, newLabel, refinedType, @@ -5538,23 +5538,26 @@ private[optimizer] abstract class OptimizerCore( /** Finds a type as precise as possible which is a supertype of lhs and rhs * but still a subtype of upperBound. * Requires that lhs and rhs be subtypes of upperBound, obviously. + * + * The RefinedType version does not have an `isStat` flag, since RefinedTypes + * only exist in a PreTransform context, which is always an expression context. */ private def constrainedLub(lhs: RefinedType, rhs: RefinedType, upperBound: Type): RefinedType = { - if (upperBound == VoidType) RefinedType(upperBound) + if (upperBound == VoidType) RefinedType(VoidType) else if (lhs == rhs) lhs else if (lhs.isNothingType) rhs else if (rhs.isNothingType) lhs - else RefinedType(constrainedLub(lhs.base, rhs.base, upperBound)) + else RefinedType(constrainedLub(lhs.base, rhs.base, upperBound, isStat = false)) } /** Finds a type as precise as possible which is a supertype of lhs and rhs * but still a subtype of upperBound. * Requires that lhs and rhs be subtypes of upperBound, obviously. */ - private def constrainedLub(lhs: Type, rhs: Type, upperBound: Type): Type = { + private def constrainedLub(lhs: Type, rhs: Type, upperBound: Type, isStat: Boolean): Type = { // TODO Improve this - if (upperBound == VoidType) upperBound + if (isStat || upperBound == VoidType) VoidType else if (lhs == rhs) lhs else if (lhs == NothingType) rhs else if (rhs == NothingType) lhs From 30098dd790a9af38e675fd78ad067e6c9c2b5a8c Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 2 Nov 2024 19:31:47 +0100 Subject: [PATCH 027/121] Optimizer: Fix types of ArraySelect trees To do this, we need to introduce a isJSType knowledge query to convert from type refs to types. --- .../frontend/optimizer/IncOptimizer.scala | 19 +++++++ .../frontend/optimizer/OptimizerCore.scala | 57 ++++++++++++++----- project/Build.scala | 2 +- 3 files changed, 64 insertions(+), 14 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index 16c576a6ac..38a02b54ad 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -1275,6 +1275,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: private val jsNativeImportsAskers = new ConcurrentHashMap[Processable, Unit] private val fieldsReadAskers = new ConcurrentHashMap[Processable, Unit] + private val isJSTypeAskers = new ConcurrentHashMap[Processable, Unit] private var _ancestors: List[ClassName] = linkedClass.ancestors @@ -1305,6 +1306,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: private var fieldsRead: Set[FieldName] = linkedClass.fieldsRead private var staticFieldsRead: Set[FieldName] = linkedClass.staticFieldsRead + private var isJSType = linkedClass.kind.isJSType /** The type of instances of this interface. * @@ -1399,6 +1401,12 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: staticFieldsRead.contains(name) } + def askIsJSType(asker: Processable): Boolean = { + isJSTypeAskers.put(asker, ()) + asker.registerTo(this) + isJSType + } + @inline def staticLike(namespace: MemberNamespace): StaticLikeNamespace = staticLikes(namespace.ordinal) @@ -1435,6 +1443,13 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: fieldsReadAskers.clear() } + // Update isJSType + if (isJSType != linkedClass.kind.isJSType) { + isJSType = linkedClass.kind.isJSType + isJSTypeAskers.forEachKey(Long.MaxValue, _.tag()) + isJSTypeAskers.clear() + } + // Update static likes for (staticLike <- staticLikes) { val (_, changed, _) = staticLike.updateWith(linkedClass) @@ -1480,6 +1495,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: staticCallers.foreach(_.forEachValue(Long.MaxValue, _.remove(dependee))) jsNativeImportsAskers.remove(dependee) fieldsReadAskers.remove(dependee) + isJSTypeAskers.remove(dependee) } private def computeJSNativeImports(linkedClass: LinkedClass): JSNativeImports = { @@ -1798,6 +1814,9 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: else clazz.askInlineableFieldBodies(asker) } + protected def isJSType(className: ClassName): Boolean = + getInterface(className).askIsJSType(asker) + protected def tryNewInlineableClass( className: ClassName): Option[OptimizerCore.InlineableClassStructure] = { classes.get(className).tryNewInlineable diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index adaac12ee7..ba87c3c38d 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -105,6 +105,9 @@ private[optimizer] abstract class OptimizerCore( /** Returns true if the given static field is ever read. */ protected def isStaticFieldRead(fieldName: FieldName): Boolean + /** Whether the given class is a JS type */ + protected def isJSType(className: ClassName): Boolean + private val localNameAllocator = new FreshNameAllocator.Local /** An allocated local variable name is mutable iff it belongs to this set. */ @@ -540,7 +543,26 @@ private[optimizer] abstract class OptimizerCore( ArrayLength(transformExpr(array)) case ArraySelect(array, index) => - ArraySelect(transformExpr(array), transformExpr(index))(tree.tpe) + val newArray = transformExpr(array) + + val newElemType = newArray.tpe match { + case NothingType | NullType => + /* Will throw / NPE / UB. + * + * Note that we cannot easily replace the ArraySelect with, say a + * checkNotNullStatement, because it might be used as lhs of an Assign node. + */ + NothingType + + case tpe: ArrayType => + arrayElemType(tpe) + + case _ => + throw new AssertionError( + s"got non-array type after transforming ArraySelect at ${tree.pos}") + } + + ArraySelect(newArray, transformExpr(index))(newElemType) case RecordValue(tpe, elems) => RecordValue(tpe, elems map transformExpr) @@ -2778,14 +2800,6 @@ private[optimizer] abstract class OptimizerCore( @inline def StringClassType = ClassType(BoxedStringClass, nullable = true) - def cursoryArrayElemType(tpe: ArrayType): Type = { - if (tpe.arrayTypeRef.dimensions != 1) AnyType - else (tpe.arrayTypeRef.base match { - case PrimRef(elemType) => elemType - case ClassRef(_) => AnyType - }) - } - def longToInt(longExpr: Tree): Tree = UnaryOp(UnaryOp.LongToInt, longExpr) def wasmUnaryOp(op: WasmUnaryOp.Code, lhs: PreTransform): Tree = @@ -2855,7 +2869,7 @@ private[optimizer] abstract class OptimizerCore( * code path, the semantics of `ArraySelect` are equivalent to the * intrinsic. */ - val elemType = cursoryArrayElemType(arrayTpe) + val elemType = arrayElemType(arrayTpe) if (!tarray.tpe.isNullable) { val array = finishTransformExpr(tarray) val index = finishTransformExpr(tindex) @@ -2879,7 +2893,7 @@ private[optimizer] abstract class OptimizerCore( /* Rewrite to `tarray[index] = tvalue` as an `Assign(ArraySelect, _)`. * See `ArrayApply` above for the handling of a nullable `tarray`. */ - val elemType = cursoryArrayElemType(arrayTpe) + val elemType = arrayElemType(arrayTpe) if (!tarray.tpe.isNullable) { val array = finishTransformExpr(tarray) val index = finishTransformExpr(tindex) @@ -4984,8 +4998,8 @@ private[optimizer] abstract class OptimizerCore( } case Class_isInstance | Class_isAssignableFrom => - /* We could do something clever here if we added a knowledge query to - * turn a TypeRef into a Type. Barring that, we cannot tell whether a + /* TODO: Improve this, now that we have a knowledge query to + * turn a TypeRef into a Type. Before that, we couldn't tell whether a * ClassRef must become an `AnyType` or a `ClassType`. */ default @@ -5295,6 +5309,23 @@ private[optimizer] abstract class OptimizerCore( } } + private def arrayElemType(tpe: ArrayType): Type = { + val ArrayTypeRef(base, dimensions) = tpe.arrayTypeRef + + if (dimensions == 1) { + base match { + case PrimRef(elemType) => elemType + case ClassRef(ObjectClass) => AnyType + + case ClassRef(className) => + if (isJSType(className)) AnyType + else ClassType(className, nullable = true) + } + } else { + ArrayType(ArrayTypeRef(base, dimensions - 1), nullable = true) + } + } + private def checkNotNull(texpr: PreTransform)(implicit pos: Position): PreTransform = { if (!texpr.tpe.isNullable) texpr diff --git a/project/Build.scala b/project/Build.scala index b4cab25a64..ba0669e9bc 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2055,7 +2055,7 @@ object Build { )) } else { Some(ExpectedSizes( - fastLink = 423000 to 424000, + fastLink = 422000 to 423000, fullLink = 280000 to 281000, fastLinkGz = 60000 to 61000, fullLinkGz = 43000 to 44000, From 40f7ff5771211a7fb9aa917f330852fb4f409204 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 13 Oct 2024 09:21:14 +0200 Subject: [PATCH 028/121] IRChecker: Allow unsound assignments to arrays I have inspected the backends to make sure they handle this. For both backends, non-primitive arrays are not typed (beyond Object for WASM). So unchecked assignments are trivial: https://github.com/scala-js/scala-js/blob/0bc8c065a1db144f6bfcfbe72f2ad2a246284b2c/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala#L638-L642 https://github.com/scala-js/scala-js/blob/0bc8c065a1db144f6bfcfbe72f2ad2a246284b2c/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala#L741-L749 https://github.com/scala-js/scala-js/blob/0bc8c065a1db144f6bfcfbe72f2ad2a246284b2c/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala#L387-L403 --- .../scalajs/linker/checker/IRChecker.scala | 32 +++++++++++++++- .../org/scalajs/linker/IRCheckerTest.scala | 37 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index 3ab776070b..92f27dc03b 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -253,7 +253,37 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { _:JSSuperSelect | _:JSGlobalRef => } typecheckExpr(lhs, env) - typecheckExpect(rhs, env, lhs.tpe) + + val expectedRhsTpe = lhs match { + case ArraySelect(array, _) => + /* Array assignments are unsound due to covariance of arrays + * To maintain the subtyping relationship in the IR, we have to + * allow assignments of any type to + * - Array[Object] (that's expected) + * - and its subtypes (that's not expected) + */ + + array.tpe match { + case ArrayType(ArrayTypeRef(PrimRef(tpe), 1), _) => + // for primitive arrays, only allow assignment of that primitive. + tpe + + case _ => + /* All other types are either + * - Subtypes of Array[Object] (including null / nothing) + * Recall: `Array[Array[A]] <: Array[Object]` even for primitive `A`. + * - Ill typed IR + * (in which case typechecking the lhs above will emit an error). + * + * Allow any rhs. + */ + AnyType + } + + case _ => lhs.tpe + } + + typecheckExpect(rhs, env, expectedRhsTpe) case Return(expr, label) => val returnType = env.returnTypes(label.name) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala index cc4847d684..50ba841937 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala @@ -269,6 +269,43 @@ class IRCheckerTest { testLinkNoIRError(classDefs, MainTestModuleInitializers) } + @Test + def arrayAssignCovariant(): AsyncResult = await { + val classDefs = Seq( + classDef("Foo", superClass = Some(ObjectClass)), + mainTestClassDef( + Assign( + ArraySelect( + ArrayValue(ArrayTypeRef.of(ClassRef("Foo")), Nil), + int(1) + )(ClassType("Foo", true)), + int(1) // not a Foo, but OK. + ) + ) + ) + + testLinkNoIRError(classDefs, MainTestModuleInitializers) + } + + @Test + def arrayNoAssignCovariantPrimitive(): AsyncResult = await { + val classDefs = Seq( + mainTestClassDef( + Assign( + ArraySelect( + ArrayValue(ArrayTypeRef.of(IntRef), Nil), + int(1) + )(IntType), + str("foo") + ) + ) + ) + + for (log <- testLinkIRErrors(classDefs, MainTestModuleInitializers)) yield { + log.assertContainsError("int expected but string found for tree of type") + } + } + } object IRCheckerTest { From fb3f7c0123315d17e44ec5fa5eaaea7ed563fd74 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Tue, 12 Nov 2024 12:55:24 +0100 Subject: [PATCH 029/121] IRChecker: Distinguish any from expressions Follow-up to #5065 (8998caf50902b09619bd1a4bcafabca820e0d7f0). Most notably: - Records are expressions - Distinguish which places require `any` vs just an expression. --- .../scalajs/linker/checker/IRChecker.scala | 105 ++++++++++-------- 1 file changed, 58 insertions(+), 47 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index 92f27dc03b..91e48c2732 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -50,7 +50,7 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { classDef.fields.foreach { case _: FieldDef => // no further checks - case JSFieldDef(_, name, _) => typecheckExpr(name, Env.empty) + case JSFieldDef(_, name, _) => typecheckAny(name, Env.empty) } classDef.methods.foreach(checkMethodDef(_, classDef)) @@ -69,7 +69,7 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { topLevelExport.tree match { case TopLevelMethodExportDef(_, methodDef) => implicit val ctx = ErrorContext(methodDef) - typecheckExpect(methodDef.body, Env.empty, AnyType) + typecheckAny(methodDef.body, Env.empty) case _:TopLevelJSClassExportDef | _:TopLevelModuleExportDef | _:TopLevelFieldExportDef => @@ -89,7 +89,7 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { else if (superClass.kind == ClassKind.NativeJSClass && superClass.jsNativeLoadSpec.isEmpty) reportError(i"Native super class ${superClass.name} must have a native load spec") } { tree => - typecheckExpect(tree, Env.empty, AnyType) + typecheckAny(tree, Env.empty) } } else { assert(classDef.jsSuperClass.isEmpty) // checked by ClassDefChecker @@ -136,7 +136,7 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { val bodyEnv = Env.forConstructorOf(clazz.name.name) body.beforeSuper.foreach(typecheck(_, bodyEnv)) - body.superCall.args.foreach(typecheckExprOrSpread(_, bodyEnv)) + body.superCall.args.foreach(typecheckAnyOrSpread(_, bodyEnv)) body.afterSuper.foreach(typecheck(_, bodyEnv)) val resultType = body.afterSuper.lastOption.fold[Type](VoidType)(_.tpe) @@ -151,9 +151,9 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { val static = flags.namespace.isStatic - typecheckExpr(pName, Env.empty) + typecheckAny(pName, Env.empty) - typecheckExpect(body, Env.empty, AnyType) + typecheckAny(body, Env.empty) } private def checkJSPropertyDef(propDef: JSPropertyDef, @@ -161,15 +161,26 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { val JSPropertyDef(flags, pName, getterBody, setterArgAndBody) = propDef implicit val ctx = ErrorContext(propDef) - typecheckExpr(pName, Env.empty) + typecheckAny(pName, Env.empty) - getterBody.foreach(typecheckExpr(_, Env.empty)) + getterBody.foreach(typecheckAny(_, Env.empty)) setterArgAndBody.foreach { case (_, body) => typecheck(body, Env.empty) } } + private def typecheckExpr(tree: Tree, env: Env)( + implicit ctx: ErrorContext): Unit = { + typecheck(tree, env) + + if (tree.tpe == VoidType) { + reportError( + i"expression expected but type ${tree.tpe} " + + i" found for tree of type ${tree.getClass().getName()}") + } + } + private def typecheckExpect(tree: Tree, env: Env, expectedType: Type)( implicit ctx: ErrorContext): Unit = { typecheck(tree, env) @@ -180,18 +191,18 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { } } - private def typecheckExpr(tree: Tree, env: Env)( + private def typecheckAny(tree: Tree, env: Env)( implicit ctx: ErrorContext): Unit = { typecheckExpect(tree, env, AnyType) } - private def typecheckExprOrSpread(tree: TreeOrJSSpread, env: Env)( + private def typecheckAnyOrSpread(tree: TreeOrJSSpread, env: Env)( implicit ctx: ErrorContext): Unit = { tree match { case JSSpread(items) => - typecheckExpr(items, env) + typecheckAny(items, env) case tree: Tree => - typecheckExpr(tree, env) + typecheckAny(tree, env) } } @@ -303,7 +314,7 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { typecheck(body, env) case ForIn(obj, keyVar, _, body) => - typecheckExpr(obj, env) + typecheckAny(obj, env) typecheck(body, env) case TryCatch(block, errVar, _, handler) => @@ -317,7 +328,7 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { typecheck(finalizer, env) case Throw(expr) => - typecheckExpr(expr, env) + typecheckAny(expr, env) case Match(selector, cases, default) => // Typecheck the selector as an int or a java.lang.String @@ -408,7 +419,7 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { case Apply(flags, receiver, MethodIdent(method), args) => if (flags.isPrivate) reportError("Illegal flag for Apply: Private") - typecheckExpr(receiver, env) + typecheckAny(receiver, env) val fullCheck = receiver.tpe match { case ClassType(className, _) => /* For class types, we only perform full checks if the class has @@ -548,24 +559,24 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { } case IsInstanceOf(expr, testType) => - typecheckExpr(expr, env) + typecheckAny(expr, env) checkIsAsInstanceTargetType(testType) case AsInstanceOf(expr, tpe) => - typecheckExpr(expr, env) + typecheckAny(expr, env) checkIsAsInstanceTargetType(tpe) case GetClass(expr) => - typecheckExpr(expr, env) + typecheckAny(expr, env) case Clone(expr) => typecheckExpect(expr, env, ClassType(CloneableClass, nullable = true)) case IdentityHashCode(expr) => - typecheckExpr(expr, env) + typecheckAny(expr, env) case WrapAsThrowable(expr) => - typecheckExpr(expr, env) + typecheckAny(expr, env) case UnwrapFromThrowable(expr) => typecheckExpect(expr, env, ClassType(ThrowableClass, nullable = true)) @@ -575,12 +586,12 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { // JavaScript expressions case JSNew(ctor, args) => - typecheckExpr(ctor, env) + typecheckAny(ctor, env) for (arg <- args) - typecheckExprOrSpread(arg, env) + typecheckAnyOrSpread(arg, env) case JSPrivateSelect(qualifier, field) => - typecheckExpr(qualifier, env) + typecheckAny(qualifier, env) val className = field.name.className val checkedClass = lookupClass(className) if (!checkedClass.kind.isJSClass && checkedClass.kind != ClassKind.AbstractJSType) { @@ -595,34 +606,34 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { } case JSSelect(qualifier, item) => - typecheckExpr(qualifier, env) - typecheckExpr(item, env) + typecheckAny(qualifier, env) + typecheckAny(item, env) case JSFunctionApply(fun, args) => - typecheckExpr(fun, env) + typecheckAny(fun, env) for (arg <- args) - typecheckExprOrSpread(arg, env) + typecheckAnyOrSpread(arg, env) case JSMethodApply(receiver, method, args) => - typecheckExpr(receiver, env) - typecheckExpr(method, env) + typecheckAny(receiver, env) + typecheckAny(method, env) for (arg <- args) - typecheckExprOrSpread(arg, env) + typecheckAnyOrSpread(arg, env) case JSSuperSelect(superClass, qualifier, item) => - typecheckExpr(superClass, env) - typecheckExpr(qualifier, env) - typecheckExpr(item, env) + typecheckAny(superClass, env) + typecheckAny(qualifier, env) + typecheckAny(item, env) case JSSuperMethodCall(superClass, receiver, method, args) => - typecheckExpr(superClass, env) - typecheckExpr(receiver, env) - typecheckExpr(method, env) + typecheckAny(superClass, env) + typecheckAny(receiver, env) + typecheckAny(method, env) for (arg <- args) - typecheckExprOrSpread(arg, env) + typecheckAnyOrSpread(arg, env) case JSImportCall(arg) => - typecheckExpr(arg, env) + typecheckAny(arg, env) case JSNewTarget() => @@ -655,24 +666,24 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { reportError(i"Cannot load JS module of native JS module class $className without native load spec") case JSDelete(qualifier, item) => - typecheckExpr(qualifier, env) - typecheckExpr(item, env) + typecheckAny(qualifier, env) + typecheckAny(item, env) case JSUnaryOp(op, lhs) => - typecheckExpr(lhs, env) + typecheckAny(lhs, env) case JSBinaryOp(op, lhs, rhs) => - typecheckExpr(lhs, env) - typecheckExpr(rhs, env) + typecheckAny(lhs, env) + typecheckAny(rhs, env) case JSArrayConstr(items) => for (item <- items) - typecheckExprOrSpread(item, env) + typecheckAnyOrSpread(item, env) case JSObjectConstr(fields) => for ((key, value) <- fields) { - typecheckExpr(key, env) - typecheckExpr(value, env) + typecheckAny(key, env) + typecheckAny(value, env) } case JSGlobalRef(_) => @@ -698,7 +709,7 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { } // Then check the closure params and body in its own env - typecheckExpect(body, Env.empty, AnyType) + typecheckAny(body, Env.empty) case CreateJSClass(className, captureValues) => val clazz = lookupClass(className) From d15567de7f82f19391be829d90eb03dfacf06632 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 2 Nov 2024 19:38:18 +0100 Subject: [PATCH 030/121] Run IRChecker after optimizer if RuntimeLongs aren't in use --- .../scalajs/linker/checker/IRChecker.scala | 28 ++++-- .../org/scalajs/linker/frontend/Refiner.scala | 25 +++++- .../org/scalajs/linker/IRCheckerTest.scala | 89 ++++++++++++++++--- 3 files changed, 125 insertions(+), 17 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index 91e48c2732..fac0022dc3 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -28,7 +28,8 @@ import org.scalajs.linker.standard.LinkedClass import org.scalajs.linker.checker.ErrorReporter._ /** Checker for the validity of the IR. */ -private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { +private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, + postOptimizer: Boolean) { import IRChecker._ import reporter.reportError @@ -238,8 +239,14 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { case Assign(lhs, rhs) => def checkNonStaticField(receiver: Tree, name: FieldName): Unit = { receiver match { - case This() if env.inConstructorOf == Some(name.className) => - // ok + case This() if (postOptimizer && env.inConstructorOf.isDefined) || + env.inConstructorOf == Some(name.className) => + /* ctors can write immutable fields of the class they are constructing. + * postOptimizer, due to ctor inlining, we may write immutable parent class fields as well. + * IR checking of the lhs makes sure this field is actually in the parent class chain + * (otherwise `This` would be ill-typed). + */ + case _ => if (lookupClass(name.className).lookupField(name).exists(!_.flags.isMutable)) reportError(i"Assignment to immutable field $name.") @@ -725,6 +732,17 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { typecheckExpect(value, env, ctpe) } + case Transient(transient) if postOptimizer => + transient.traverse(new Traversers.Traverser { + override def traverse(tree: Tree): Unit = typecheck(tree, env) + }) + + case _: RecordSelect if postOptimizer => + // TODO + + case _: RecordValue if postOptimizer => + // TODO + case _:RecordSelect | _:RecordValue | _:Transient | _:JSSuperConstructorCall => reportError("invalid tree") } @@ -893,9 +911,9 @@ object IRChecker { * * @return Count of IR checking errors (0 in case of success) */ - def check(unit: LinkingUnit, logger: Logger): Int = { + def check(unit: LinkingUnit, logger: Logger, postOptimizer: Boolean = false): Int = { val reporter = new LoggerErrorReporter(logger) - new IRChecker(unit, reporter).check() + new IRChecker(unit, reporter, postOptimizer).check() reporter.errorCount } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala index 51da80fd6d..51c4cbd590 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala @@ -20,6 +20,7 @@ import org.scalajs.ir.Trees.ClassDef import org.scalajs.logging._ +import org.scalajs.linker.checker.IRChecker import org.scalajs.linker.interface.ModuleInitializer import org.scalajs.linker.standard._ import org.scalajs.linker.standard.ModuleSet.ModuleID @@ -33,6 +34,16 @@ final class Refiner(config: CommonPhaseConfig, checkIR: Boolean) { private val analyzer = new Analyzer(config, initial = false, checkIR = checkIR, failOnError = true, irLoader) + /* TODO: Remove this and replace with `checkIR` once the optimizer generates + * well-typed IR with runtime longs. + */ + private val shouldRunIRChecker = { + val optimizerUsesRuntimeLong = + !config.coreSpec.esFeatures.allowBigIntsForLongs && + !config.coreSpec.targetIsWebAssembly + checkIR && !optimizerUsesRuntimeLong + } + def refine(classDefs: Seq[(ClassDef, Version)], moduleInitializers: List[ModuleInitializer], symbolRequirements: SymbolRequirement, logger: Logger)( @@ -47,7 +58,7 @@ final class Refiner(config: CommonPhaseConfig, checkIR: Boolean) { val result = for { analysis <- analysis } yield { - logger.time("Refiner: Assemble LinkedClasses") { + val result = logger.time("Refiner: Assemble LinkedClasses") { val assembled = for { (classDef, version) <- classDefs if analysis.classInfos.contains(classDef.className) @@ -65,6 +76,18 @@ final class Refiner(config: CommonPhaseConfig, checkIR: Boolean) { new LinkingUnit(config.coreSpec, linkedClassDefs.toList, linkedTopLevelExports.flatten.toList, moduleInitializers, globalInfo) } + + if (shouldRunIRChecker) { + logger.time("Refiner: Check IR") { + val errorCount = IRChecker.check(result, logger, postOptimizer = true) + if (errorCount != 0) { + throw new AssertionError( + s"There were $errorCount IR checking errors after optimization (this is a Scala.js bug)") + } + } + } + + result } result.andThen { case _ => diff --git a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala index 50ba841937..bc7d7e1da1 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala @@ -28,7 +28,9 @@ import org.scalajs.logging._ import org.scalajs.junit.async._ import org.scalajs.linker.interface._ +import org.scalajs.linker.interface.unstable.IRFileImpl import org.scalajs.linker.standard._ +import org.scalajs.linker.frontend.Refiner import org.scalajs.linker.testutils._ import org.scalajs.linker.testutils.TestIRBuilder._ @@ -306,22 +308,65 @@ class IRCheckerTest { } } + def immutableFieldAssignTestClassDefs(parent: Boolean): Seq[ClassDef] = { + val ctorBodyUnderTest = + Assign(Select(thisFor("Bar"), FieldName("Foo", "fooFld"))(IntType), int(1)) + + Seq( + classDef( + "Foo", + superClass = Some(ObjectClass), + fields = List(FieldDef(EMF, FieldName("Foo", "fooFld"), NON, IntType)) + ), + classDef( + "Bar", + superClass = Some(if (parent) "Foo" else ObjectClass), + methods = List( + MethodDef( + EMF.withNamespace(MemberNamespace.Constructor), + NoArgConstructorName, NON, Nil, VoidType, + Some(ctorBodyUnderTest))(EOH, UNV) + ) + ), + mainTestClassDef(New("Bar", NoArgConstructorName, Nil)) + ) + } + + @Test + def noImmutableAssignNonParent(): AsyncResult = await { + val classDefs = immutableFieldAssignTestClassDefs(parent = false) + + for { + log <- testLinkIRErrors(classDefs, MainTestModuleInitializers, postOptimizer = true) + } yield { + log.assertContainsError("Foo expected but Bar! found for tree of type org.scalajs.ir.Trees$This") + } + } + + @Test + def allowImmutableAssignParent(): AsyncResult = await { + val classDefs = immutableFieldAssignTestClassDefs(parent = true) + testLinkNoIRError(classDefs, MainTestModuleInitializers, postOptimizer = true) + } + } object IRCheckerTest { def testLinkNoIRError(classDefs: Seq[ClassDef], - moduleInitializers: List[ModuleInitializer])( + moduleInitializers: List[ModuleInitializer], + postOptimizer: Boolean = false)( implicit ec: ExecutionContext): Future[Unit] = { - link(classDefs, moduleInitializers, new ScalaConsoleLogger(Level.Error)) + link(classDefs, moduleInitializers, new ScalaConsoleLogger(Level.Error), postOptimizer) } def testLinkIRErrors(classDefs: Seq[ClassDef], - moduleInitializers: List[ModuleInitializer])( + moduleInitializers: List[ModuleInitializer], + postOptimizer: Boolean = false)( implicit ec: ExecutionContext): Future[LogLines] = { val logger = new CapturingLogger - link(classDefs, moduleInitializers, logger).transform { + link(classDefs, moduleInitializers, logger, postOptimizer).transform { case Success(_) => Failure(new AssertionError("IR checking did not fail")) case Failure(_) => Success(logger.allLogLines) } @@ -329,23 +374,45 @@ object IRCheckerTest { private def link(classDefs: Seq[ClassDef], moduleInitializers: List[ModuleInitializer], - logger: Logger)(implicit ec: ExecutionContext): Future[Unit] = { - val config = StandardConfig() + logger: Logger, postOptimizer: Boolean)( + implicit ec: ExecutionContext): Future[Unit] = { + val baseConfig = StandardConfig() .withCheckIR(true) .withOptimizer(false) - val linkerFrontend = StandardLinkerFrontend(config) + + val config = { + /* Disable RuntimeLongs to workaround the Refiner disabling IRChecks in this case. + * TODO: Remove once we run IRChecks post optimizer all the time. + */ + if (postOptimizer) baseConfig.withESFeatures(_.withAllowBigIntsForLongs(true)) + else baseConfig + } val noSymbolRequirements = SymbolRequirement .factory("IRCheckerTest") .none() TestIRRepo.minilib.flatMap { stdLibFiles => - val irFiles = ( + if (postOptimizer) { + val refiner = new Refiner(CommonPhaseConfig.fromStandardConfig(config), checkIR = true) + + Future.traverse(stdLibFiles)(f => IRFileImpl.fromIRFile(f).tree).flatMap { stdLibClassDefs => + val allClassDefs = ( + stdLibClassDefs ++ + classDefs + ) + + refiner.refine(allClassDefs.map(c => (c, UNV)), moduleInitializers, + noSymbolRequirements, logger) + } + } else { + val linkerFrontend = StandardLinkerFrontend(config) + val irFiles = ( stdLibFiles ++ classDefs.map(MemClassDefIRFile(_)) - ) - - linkerFrontend.link(irFiles, moduleInitializers, noSymbolRequirements, logger) + ) + linkerFrontend.link(irFiles, moduleInitializers, noSymbolRequirements, logger) + } }.map(_ => ()) } } From 1d6214e9be9502e85887a0880362784645026907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 12 Nov 2024 16:33:32 +0100 Subject: [PATCH 031/121] Add JDK 17 and 21 to the CI; remove 16. Now we test the four active LTS versions: 1.8, 11, 17 and 21. We upgrade our Scala 3 references to 3.3.4 which is the latest LTS. Scala 3.3.1+ is required for JDK 21. We also remove the `MaxPermSize` option from the CI script. It has been useless since JDK 1.8, and actually causes an error starting with JDK 17. --- Jenkinsfile | 11 +++++--- .../src/sbt-test/scala3/basic/build.sbt | 2 +- .../src/sbt-test/scala3/junit/build.sbt | 2 +- .../sbt-test/scala3/tasty-reader/build.sbt | 2 +- .../scalajs/testing/adapter/TestAdapter.scala | 26 ++++++++++++++++- .../scalajs/testsuite/utils/Platform.scala | 2 ++ .../scalajs/testsuite/utils/Platform.scala | 2 ++ .../concurrent/ThreadLocalRandomTest.scala | 8 +++++- .../testsuite/niocharset/CharsetTest.scala | 28 ++++++++++++++----- 9 files changed, 67 insertions(+), 16 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 43009dfd57..921ac4915f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -64,7 +64,7 @@ rm -rf $TEST_LOCAL_IVY_HOME mkdir $TEST_LOCAL_IVY_HOME ln -s "$LOC_IVY_HOME/cache" "$TEST_LOCAL_IVY_HOME/cache" -export SBT_OPTS="-J-Xmx5G -J-XX:MaxPermSize=512M -Dsbt.boot.directory=$LOC_SBT_BOOT -Dsbt.ivy.home=$TEST_LOCAL_IVY_HOME -Divy.home=$TEST_LOCAL_IVY_HOME -Dsbt.global.base=$LOC_SBT_BASE" +export SBT_OPTS="-J-Xmx5G -Dsbt.boot.directory=$LOC_SBT_BOOT -Dsbt.ivy.home=$TEST_LOCAL_IVY_HOME -Divy.home=$TEST_LOCAL_IVY_HOME -Dsbt.global.base=$LOC_SBT_BASE" export COURSIER_CACHE="$LOC_CS_CACHE" export NODE_PATH="$HOME/node_modules/" @@ -539,7 +539,7 @@ def Tasks = [ ] def mainJavaVersion = "1.8" -def otherJavaVersions = ["11", "16"] +def otherJavaVersions = ["11", "17", "21"] def allJavaVersions = otherJavaVersions.clone() allJavaVersions << mainJavaVersion @@ -577,7 +577,7 @@ def otherScalaVersions = [ "2.13.12" ] -def scala3Version = "3.2.1" +def scala3Version = "3.3.4" def allESVersions = [ "ES5_1", @@ -614,7 +614,10 @@ allESVersions.each { esVersion -> } allJavaVersions.each { javaVersion -> // the `scala` version is irrelevant here - quickMatrix.add([task: "sbt-plugin-and-scalastyle", scala: mainScalaVersion, java: javaVersion]) + // We exclude JDK 21 because our sbt scripted tests use old sbt versions (on purpose), which do not support JDK 21 + if (javaVersion != '21') { + quickMatrix.add([task: "sbt-plugin-and-scalastyle", scala: mainScalaVersion, java: javaVersion]) + } } quickMatrix.add([task: "scala3-compat", scala: scala3Version, java: mainJavaVersion]) diff --git a/sbt-plugin/src/sbt-test/scala3/basic/build.sbt b/sbt-plugin/src/sbt-test/scala3/basic/build.sbt index 47788c2ad9..6a461c06fc 100644 --- a/sbt-plugin/src/sbt-test/scala3/basic/build.sbt +++ b/sbt-plugin/src/sbt-test/scala3/basic/build.sbt @@ -1,6 +1,6 @@ enablePlugins(ScalaJSPlugin) -scalaVersion := "3.0.0" +scalaVersion := "3.3.4" // Test CrossVersion.for3Use2_13 for %%% dependencies libraryDependencies += diff --git a/sbt-plugin/src/sbt-test/scala3/junit/build.sbt b/sbt-plugin/src/sbt-test/scala3/junit/build.sbt index 809933c9ef..d1d33900ab 100644 --- a/sbt-plugin/src/sbt-test/scala3/junit/build.sbt +++ b/sbt-plugin/src/sbt-test/scala3/junit/build.sbt @@ -1,3 +1,3 @@ enablePlugins(ScalaJSPlugin, ScalaJSJUnitPlugin) -scalaVersion := "3.0.0" +scalaVersion := "3.3.4" diff --git a/sbt-plugin/src/sbt-test/scala3/tasty-reader/build.sbt b/sbt-plugin/src/sbt-test/scala3/tasty-reader/build.sbt index a0af60a58e..683c749c5a 100644 --- a/sbt-plugin/src/sbt-test/scala3/tasty-reader/build.sbt +++ b/sbt-plugin/src/sbt-test/scala3/tasty-reader/build.sbt @@ -3,7 +3,7 @@ lazy val root = project.in(file(".")) lazy val testlib = project.in(file("testlib")) .enablePlugins(ScalaJSPlugin) .settings( - scalaVersion := "3.1.3" + scalaVersion := "3.3.4" ) lazy val app = project.in(file("app")) diff --git a/test-adapter/src/main/scala/org/scalajs/testing/adapter/TestAdapter.scala b/test-adapter/src/main/scala/org/scalajs/testing/adapter/TestAdapter.scala index 0172b89964..7c3bd880a3 100644 --- a/test-adapter/src/main/scala/org/scalajs/testing/adapter/TestAdapter.scala +++ b/test-adapter/src/main/scala/org/scalajs/testing/adapter/TestAdapter.scala @@ -47,6 +47,26 @@ final class TestAdapter(jsEnv: JSEnv, input: Seq[Input], config: TestAdapter.Con private implicit val executionContext: ExecutionContext = ExecutionContext.fromExecutor(ExecutionContext.global, reportFailure) + /** Accessor to the deprecated method `jl.Thread.getId()`. + * + * Since JDK 19, Thread.getId() is deprecated in favor of Thread.threadId(). + * The only reason is that getId() was not marked `final`, and hence there + * was no guarantee that subclasses wouldn't override it with something that + * is not the thread's ID. + * + * We cannot directly use Thread.threadId() since it was only added in JDK 19. + * Since we probably don't need to care about the potential "threat", we do + * the override-with-deprecated dance to silence the warning. + * + * Reminder: we cannot use `@nowarn` since it was only introduced in + * Scala 2.12.13/2.13.2. + */ + private val threadIDAccessor: ThreadIDAccessor = new ThreadIDAccessor { + @deprecated("warning silencer", since = "forever") + def getCurrentThreadId(): Long = + Thread.currentThread().getId() + } + /** Creates an `sbt.testing.Framework` for each framework that can be found. * * The returned Frameworks bind to this TestAdapter and are only valid until @@ -113,7 +133,7 @@ final class TestAdapter(jsEnv: JSEnv, input: Seq[Input], config: TestAdapter.Con } private[adapter] def getRunnerForThread(): ManagedRunner = { - val threadId = Thread.currentThread().getId() + val threadId = threadIDAccessor.getCurrentThreadId() // Note that this is thread safe, since each thread can only operate on // the value associated to its thread id. @@ -167,4 +187,8 @@ object TestAdapter { val com: RPCCore, val mux: RunMuxRPC ) + + private abstract class ThreadIDAccessor { + def getCurrentThreadId(): Long + } } diff --git a/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala b/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala index 97ad78c948..7712e426a6 100644 --- a/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala +++ b/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala @@ -36,6 +36,8 @@ object Platform { final val executingInJVMOnLowerThanJDK17 = false + def executingInJVMOnLowerThanJDK(version: Int): Boolean = false + def executingInWebAssembly: Boolean = BuildInfo.isWebAssembly def executingInNodeJS: Boolean = { diff --git a/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala b/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala index ba855134c9..e66c2faa24 100644 --- a/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala +++ b/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala @@ -34,6 +34,8 @@ object Platform { def executingInJVMOnLowerThanJDK17: Boolean = jdkVersion < 17 + def executingInJVMOnLowerThanJDK(version: Int): Boolean = jdkVersion < version + private lazy val jdkVersion = { val v = System.getProperty("java.version") if (v.startsWith("1.")) Integer.parseInt(v.drop(2).takeWhile(_.isDigit)) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ThreadLocalRandomTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ThreadLocalRandomTest.scala index 084effeb1d..d8c193d171 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ThreadLocalRandomTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ThreadLocalRandomTest.scala @@ -508,7 +508,13 @@ class ThreadLocalRandomTest { @Test def nextDoubleDoubleDouble(): Unit = { implicit val tlr = ThreadLocalRandom.current() - checkDoubleBounds(Double.MinValue, Double.MaxValue) + if (!executingInJVMOnLowerThanJDK(19) || executingInJVMOnLowerThanJDK(17)) { + /* For some reason, JDK 17-18 throw an IllegalArgumentException for this one. + * Older and more recent versions of the JDK succeed. + */ + checkDoubleBounds(Double.MinValue, Double.MaxValue) + } + checkDoubleBounds(Double.MinValue, 0L) checkDoubleBounds(Double.MaxValue, 0L) checkDoubleBounds(0.14303466203185822, 0.7471945354839639) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niocharset/CharsetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niocharset/CharsetTest.scala index 78df50ee1e..c6453c6cf7 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niocharset/CharsetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niocharset/CharsetTest.scala @@ -22,12 +22,19 @@ import org.junit.Assert._ import org.scalajs.testsuite.javalib.util.TrivialImmutableCollection import org.scalajs.testsuite.utils.AssertThrows.assertThrows -import org.scalajs.testsuite.utils.Platform.executingInJVM +import org.scalajs.testsuite.utils.Platform._ class CharsetTest { def javaSet[A](elems: A*): java.util.Set[A] = new java.util.HashSet(TrivialImmutableCollection(elems: _*)) + /* "default" was removed as alias of US_ASCII in JDK 18 through JEP 400. + * See https://openjdk.org/jeps/400#The-legacy-default-charset + * TODO Maybe we should remove it as well in our implementation. + */ + lazy val isDefaultSupported: Boolean = + !executingInJVM || executingInJVMOnLowerThanJDK(18) + @Test def defaultCharset(): Unit = { assertSame(UTF_8, Charset.defaultCharset()) } @@ -40,7 +47,9 @@ class CharsetTest { assertSame(ISO_8859_1, Charset.forName("l1")) assertSame(US_ASCII, Charset.forName("US-ASCII")) - assertSame(US_ASCII, Charset.forName("Default")) + + if (isDefaultSupported) + assertSame(US_ASCII, Charset.forName("Default")) assertSame(UTF_8, Charset.forName("UTF-8")) assertSame(UTF_8, Charset.forName("utf-8")) @@ -70,13 +79,14 @@ class CharsetTest { @Test def isSupported(): Unit = { assertTrue(Charset.isSupported("ISO-8859-1")) assertTrue(Charset.isSupported("US-ASCII")) - assertTrue(Charset.isSupported("Default")) assertTrue(Charset.isSupported("utf-8")) assertTrue(Charset.isSupported("UnicodeBigUnmarked")) assertTrue(Charset.isSupported("Utf_16le")) assertTrue(Charset.isSupported("UTF-16")) assertTrue(Charset.isSupported("unicode")) + assertEquals(isDefaultSupported, Charset.isSupported("Default")) + assertFalse(Charset.isSupported("this-charset-does-not-exist")) } @@ -90,10 +100,14 @@ class CharsetTest { "UnicodeBigUnmarked")) assertEquals(Charset.forName("UTF-16LE").aliases(), javaSet("UnicodeLittleUnmarked", "UTF_16LE", "X-UTF-16LE")) - assertEquals(Charset.forName("US-ASCII").aliases(), - javaSet("ANSI_X3.4-1968", "cp367", "csASCII", "iso-ir-6", "ASCII", - "iso_646.irv:1983", "ANSI_X3.4-1986", "ascii7", "default", - "ISO_646.irv:1991", "ISO646-US", "IBM367", "646", "us")) + + val expectedUSAsciiAliases = javaSet("ANSI_X3.4-1968", "cp367", "csASCII", + "iso-ir-6", "ASCII", "iso_646.irv:1983", "ANSI_X3.4-1986", "ascii7", + "ISO_646.irv:1991", "ISO646-US", "IBM367", "646", "us") + if (isDefaultSupported) + expectedUSAsciiAliases.add("default") + assertEquals(Charset.forName("US-ASCII").aliases(), expectedUSAsciiAliases) + assertEquals(Charset.forName("ISO-8859-1").aliases(), javaSet("819", "ISO8859-1", "l1", "ISO_8859-1:1987", "ISO_8859-1", "8859_1", "iso-ir-100", "latin1", "cp819", "ISO8859_1", "IBM819", "ISO_8859_1", From 8f98b8fc9fd5f2e9aeb891d7d8cc773d4d9291ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 13 Nov 2024 17:58:47 +0100 Subject: [PATCH 032/121] Add trait definitions for ju.Sequenced{Collection,Map,Set}. We only add the trait definitions and the relevant inheritance relationships. This is enough to fix linking issues arising only from type inference when compiling on JDK 21+. We do not any of the methods of those new traits in this commit. They all rely on new `reversed()` methods, which are supposed to provide reverse *views* of the collections. Implementing all those views correctly turns out to be a bottomless well, which is left for future work. --- javalib/src/main/scala/java/util/Deque.scala | 2 +- .../main/scala/java/util/LinkedHashMap.scala | 2 +- .../main/scala/java/util/LinkedHashSet.scala | 2 +- javalib/src/main/scala/java/util/List.scala | 2 +- .../scala/java/util/SequencedCollection.scala | 15 ++++++ .../main/scala/java/util/SequencedMap.scala | 17 +++++++ .../main/scala/java/util/SequencedSet.scala | 15 ++++++ .../src/main/scala/java/util/SortedMap.scala | 2 +- .../src/main/scala/java/util/SortedSet.scala | 2 +- project/Build.scala | 1 + .../util/SequencedCollectionTest.scala | 46 ++++++++++++++++++ .../javalib/util/SequencedMapTest.scala | 47 +++++++++++++++++++ .../javalib/util/SequencedSetTest.scala | 47 +++++++++++++++++++ 13 files changed, 194 insertions(+), 6 deletions(-) create mode 100644 javalib/src/main/scala/java/util/SequencedCollection.scala create mode 100644 javalib/src/main/scala/java/util/SequencedMap.scala create mode 100644 javalib/src/main/scala/java/util/SequencedSet.scala create mode 100644 test-suite/shared/src/test/require-jdk21/org/scalajs/testsuite/javalib/util/SequencedCollectionTest.scala create mode 100644 test-suite/shared/src/test/require-jdk21/org/scalajs/testsuite/javalib/util/SequencedMapTest.scala create mode 100644 test-suite/shared/src/test/require-jdk21/org/scalajs/testsuite/javalib/util/SequencedSetTest.scala diff --git a/javalib/src/main/scala/java/util/Deque.scala b/javalib/src/main/scala/java/util/Deque.scala index 89b70bc615..d4a4e0918c 100644 --- a/javalib/src/main/scala/java/util/Deque.scala +++ b/javalib/src/main/scala/java/util/Deque.scala @@ -12,7 +12,7 @@ package java.util -trait Deque[E] extends Queue[E] { +trait Deque[E] extends Queue[E] with SequencedCollection[E] { def addFirst(e: E): Unit def addLast(e: E): Unit def offerFirst(e: E): Boolean diff --git a/javalib/src/main/scala/java/util/LinkedHashMap.scala b/javalib/src/main/scala/java/util/LinkedHashMap.scala index ba46ffc844..958aeff409 100644 --- a/javalib/src/main/scala/java/util/LinkedHashMap.scala +++ b/javalib/src/main/scala/java/util/LinkedHashMap.scala @@ -17,7 +17,7 @@ import java.util.function.BiConsumer class LinkedHashMap[K, V](initialCapacity: Int, loadFactor: Float, accessOrder: Boolean) - extends HashMap[K, V](initialCapacity, loadFactor) { + extends HashMap[K, V](initialCapacity, loadFactor) with SequencedMap[K, V] { self => import LinkedHashMap._ diff --git a/javalib/src/main/scala/java/util/LinkedHashSet.scala b/javalib/src/main/scala/java/util/LinkedHashSet.scala index 6133d013d9..f24d3f5bd4 100644 --- a/javalib/src/main/scala/java/util/LinkedHashSet.scala +++ b/javalib/src/main/scala/java/util/LinkedHashSet.scala @@ -15,7 +15,7 @@ package java.util import java.lang.Cloneable class LinkedHashSet[E] private[util] (inner: LinkedHashMap[E, Any]) - extends HashSet[E](inner) with Set[E] with Cloneable with Serializable { + extends HashSet[E](inner) with SequencedSet[E] with Cloneable with Serializable { def this(initialCapacity: Int, loadFactor: Float) = this(new LinkedHashMap[E, Any](initialCapacity, loadFactor)) diff --git a/javalib/src/main/scala/java/util/List.scala b/javalib/src/main/scala/java/util/List.scala index 66465dde50..982e373b01 100644 --- a/javalib/src/main/scala/java/util/List.scala +++ b/javalib/src/main/scala/java/util/List.scala @@ -14,7 +14,7 @@ package java.util import java.util.function.UnaryOperator -trait List[E] extends Collection[E] { +trait List[E] extends SequencedCollection[E] { def replaceAll(operator: UnaryOperator[E]): Unit = { val iter = listIterator() while (iter.hasNext()) diff --git a/javalib/src/main/scala/java/util/SequencedCollection.scala b/javalib/src/main/scala/java/util/SequencedCollection.scala new file mode 100644 index 0000000000..9f49537f33 --- /dev/null +++ b/javalib/src/main/scala/java/util/SequencedCollection.scala @@ -0,0 +1,15 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util + +trait SequencedCollection[E] extends Collection[E] diff --git a/javalib/src/main/scala/java/util/SequencedMap.scala b/javalib/src/main/scala/java/util/SequencedMap.scala new file mode 100644 index 0000000000..a1eb13cd23 --- /dev/null +++ b/javalib/src/main/scala/java/util/SequencedMap.scala @@ -0,0 +1,17 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util + +import java.util.Map.Entry + +trait SequencedMap[K, V] extends Map[K, V] diff --git a/javalib/src/main/scala/java/util/SequencedSet.scala b/javalib/src/main/scala/java/util/SequencedSet.scala new file mode 100644 index 0000000000..8bf692997a --- /dev/null +++ b/javalib/src/main/scala/java/util/SequencedSet.scala @@ -0,0 +1,15 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util + +trait SequencedSet[E] extends SequencedCollection[E] with Set[E] diff --git a/javalib/src/main/scala/java/util/SortedMap.scala b/javalib/src/main/scala/java/util/SortedMap.scala index e2ac54cfef..c5076c0019 100644 --- a/javalib/src/main/scala/java/util/SortedMap.scala +++ b/javalib/src/main/scala/java/util/SortedMap.scala @@ -12,7 +12,7 @@ package java.util -trait SortedMap[K, V] extends Map[K, V] { +trait SortedMap[K, V] extends SequencedMap[K, V] { def firstKey(): K def comparator(): Comparator[_ >: K] def lastKey(): K diff --git a/javalib/src/main/scala/java/util/SortedSet.scala b/javalib/src/main/scala/java/util/SortedSet.scala index b4f6fcc83d..95694ab938 100644 --- a/javalib/src/main/scala/java/util/SortedSet.scala +++ b/javalib/src/main/scala/java/util/SortedSet.scala @@ -12,7 +12,7 @@ package java.util -trait SortedSet[E] extends Set[E] { +trait SortedSet[E] extends SequencedSet[E] { def comparator(): Comparator[_ >: E] def subSet(fromElement: E, toElement: E): SortedSet[E] def headSet(toElement: E): SortedSet[E] diff --git a/project/Build.scala b/project/Build.scala index ba0669e9bc..487e8482e7 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2134,6 +2134,7 @@ object Build { collectionsEraDependentDirectory(scalaV, sharedTestDir) :: includeIf(sharedTestDir / "require-jdk11", javaV >= 11) ::: includeIf(sharedTestDir / "require-jdk15", javaV >= 15) ::: + includeIf(sharedTestDir / "require-jdk21", javaV >= 21) ::: includeIf(testDir / "require-scala2", isJSTest) }, diff --git a/test-suite/shared/src/test/require-jdk21/org/scalajs/testsuite/javalib/util/SequencedCollectionTest.scala b/test-suite/shared/src/test/require-jdk21/org/scalajs/testsuite/javalib/util/SequencedCollectionTest.scala new file mode 100644 index 0000000000..02686c4e35 --- /dev/null +++ b/test-suite/shared/src/test/require-jdk21/org/scalajs/testsuite/javalib/util/SequencedCollectionTest.scala @@ -0,0 +1,46 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.testsuite.javalib.util + +import org.junit.Test +import org.junit.Assert._ + +import java.{util => ju} + +class SequencedCollectionTest { + + @Test def knownSequencedCollections(): Unit = { + def test(expected: Boolean, testClass: Class[_]): Unit = { + assertEquals(expected, classOf[ju.SequencedCollection[_]].isAssignableFrom(testClass)) + } + + test(true, classOf[ju.List[String]]) + test(true, classOf[ju.LinkedList[String]]) + test(true, classOf[ju.ArrayList[String]]) + test(true, classOf[ju.LinkedHashSet[String]]) + test(true, classOf[ju.TreeSet[String]]) + test(true, classOf[ju.NavigableSet[String]]) + test(true, classOf[ju.Deque[String]]) + test(true, classOf[ju.ArrayDeque[String]]) + test(true, classOf[ju.SequencedSet[String]]) + + test(false, classOf[ju.SequencedMap[String, String]]) + test(false, classOf[ju.HashSet[String]]) + test(false, classOf[ju.HashMap[String, String]]) + test(false, classOf[ju.LinkedHashMap[String, String]]) + test(false, classOf[ju.TreeMap[String, String]]) + test(false, classOf[ju.NavigableMap[String, String]]) + test(false, classOf[ju.Queue[String]]) + } + +} diff --git a/test-suite/shared/src/test/require-jdk21/org/scalajs/testsuite/javalib/util/SequencedMapTest.scala b/test-suite/shared/src/test/require-jdk21/org/scalajs/testsuite/javalib/util/SequencedMapTest.scala new file mode 100644 index 0000000000..5acf97f99a --- /dev/null +++ b/test-suite/shared/src/test/require-jdk21/org/scalajs/testsuite/javalib/util/SequencedMapTest.scala @@ -0,0 +1,47 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.testsuite.javalib.util + +import org.junit.Test +import org.junit.Assert._ + +import java.{util => ju} + +class SequencedMapTest { + + @Test def knownSequencedMaps(): Unit = { + def test(expected: Boolean, testClass: Class[_]): Unit = { + assertEquals(expected, classOf[ju.SequencedMap[_, _]].isAssignableFrom(testClass)) + } + + test(true, classOf[ju.SortedMap[String, String]]) + test(true, classOf[ju.LinkedHashMap[String, String]]) + test(true, classOf[ju.TreeMap[String, String]]) + test(true, classOf[ju.NavigableMap[String, String]]) + + test(false, classOf[ju.HashMap[String, String]]) + test(false, classOf[ju.LinkedHashSet[String]]) + test(false, classOf[ju.LinkedHashSet[String]]) + test(false, classOf[ju.TreeSet[String]]) + test(false, classOf[ju.NavigableSet[String]]) + test(false, classOf[ju.List[String]]) + test(false, classOf[ju.LinkedList[String]]) + test(false, classOf[ju.ArrayList[String]]) + test(false, classOf[ju.Deque[String]]) + test(false, classOf[ju.ArrayDeque[String]]) + test(false, classOf[ju.HashSet[String]]) + test(false, classOf[ju.SequencedCollection[String]]) + test(false, classOf[ju.SequencedSet[String]]) + } + +} diff --git a/test-suite/shared/src/test/require-jdk21/org/scalajs/testsuite/javalib/util/SequencedSetTest.scala b/test-suite/shared/src/test/require-jdk21/org/scalajs/testsuite/javalib/util/SequencedSetTest.scala new file mode 100644 index 0000000000..1bcf4be735 --- /dev/null +++ b/test-suite/shared/src/test/require-jdk21/org/scalajs/testsuite/javalib/util/SequencedSetTest.scala @@ -0,0 +1,47 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.testsuite.javalib.util + +import org.junit.Test +import org.junit.Assert._ + +import java.{util => ju} + +class SequencedSetTest { + + @Test def knownSequencedSets(): Unit = { + def test(expected: Boolean, testClass: Class[_]): Unit = { + assertEquals(expected, classOf[ju.SequencedSet[_]].isAssignableFrom(testClass)) + } + + test(true, classOf[ju.LinkedHashSet[String]]) + test(true, classOf[ju.LinkedHashSet[String]]) + test(true, classOf[ju.TreeSet[String]]) + test(true, classOf[ju.NavigableSet[String]]) + test(true, classOf[ju.SortedSet[String]]) + + test(false, classOf[ju.List[String]]) + test(false, classOf[ju.LinkedList[String]]) + test(false, classOf[ju.ArrayList[String]]) + test(false, classOf[ju.Deque[String]]) + test(false, classOf[ju.ArrayDeque[String]]) + test(false, classOf[ju.HashSet[String]]) + test(false, classOf[ju.HashMap[String, String]]) + test(false, classOf[ju.LinkedHashMap[String, String]]) + test(false, classOf[ju.TreeMap[String, String]]) + test(false, classOf[ju.NavigableMap[String, String]]) + test(false, classOf[ju.SequencedCollection[String]]) + test(false, classOf[ju.SequencedMap[String, String]]) + } + +} From 7ae4a051e7111808b473d48fa9e5a27577cde483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 16 Nov 2024 10:45:17 +0100 Subject: [PATCH 033/121] Simplify the test API we use to identify specific JDKs. --- .../testsuite/javalib/util/FormatterTestEx.scala | 4 ++-- .../org/scalajs/testsuite/utils/Platform.scala | 14 ++------------ .../org/scalajs/testsuite/utils/Platform.scala | 14 ++------------ .../scalajs/testsuite/javalib/net/URITest.scala | 4 ++-- .../testsuite/javalib/net/URLDecoderTest.scala | 2 +- .../testsuite/javalib/util/FormatterTest.scala | 4 ++-- .../scalajs/testsuite/javalib/util/MapTest.scala | 2 +- .../util/concurrent/ThreadLocalRandomTest.scala | 2 +- .../javalib/util/regex/RegexEngineTest.scala | 12 ++++++------ .../testsuite/niobuffer/BaseBufferTest.scala | 2 +- 10 files changed, 20 insertions(+), 40 deletions(-) diff --git a/test-suite-ex/shared/src/test/scala/org/scalajs/testsuite/javalib/util/FormatterTestEx.scala b/test-suite-ex/shared/src/test/scala/org/scalajs/testsuite/javalib/util/FormatterTestEx.scala index 27dc5ecb60..1f0186c747 100644 --- a/test-suite-ex/shared/src/test/scala/org/scalajs/testsuite/javalib/util/FormatterTestEx.scala +++ b/test-suite-ex/shared/src/test/scala/org/scalajs/testsuite/javalib/util/FormatterTestEx.scala @@ -76,7 +76,7 @@ class FormatterTestEx { * locale uses U+202F NARROW NO-BREAK SPACE as grouping * separator, instead of U+00A0 NO-BREAK SPACE. */ - if (!executingInJVMOnLowerThanJDK13) { + if (!executingInJVMOnLowerThanJDK(13)) { // U+202F NARROW NO-BREAK SPACE assertF(French, "1\u202F234\u202F567", "%,d", 1234567) assertF(French, "1\u202F234\u202F567,89", "%,.2f", 1234567.89) @@ -119,7 +119,7 @@ class FormatterTestEx { @Test def testFormatTurkish(): Unit = { assumeFalse("Affected by https://bugs.openjdk.java.net/browse/JDK-8060094", - executingInJVMOnJDK8OrLower) + executingInJVMOnLowerThanJDK(9)) // U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE assertF(Turkish, "TİTLE", "%S", "title") diff --git a/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala b/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala index 7712e426a6..6251f85b88 100644 --- a/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala +++ b/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala @@ -24,20 +24,10 @@ object Platform { */ final val executingInJVM = false - final val executingInJVMOnJDK8OrLower = false - - final val executingInJVMOnLowerThanJDK10 = false - - final val executingInJVMOnLowerThanJDK13 = false - - final val executingInJVMOnLowerThanJDK15 = false - - final val executingInJVMOnLowerThanJDK16 = false - - final val executingInJVMOnLowerThanJDK17 = false - def executingInJVMOnLowerThanJDK(version: Int): Boolean = false + def executingInJVMWithJDKIn(range: Range): Boolean = false + def executingInWebAssembly: Boolean = BuildInfo.isWebAssembly def executingInNodeJS: Boolean = { diff --git a/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala b/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala index e66c2faa24..6b480fd9b6 100644 --- a/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala +++ b/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala @@ -22,20 +22,10 @@ object Platform { */ final val executingInJVM = true - def executingInJVMOnJDK8OrLower: Boolean = jdkVersion <= 8 - - def executingInJVMOnLowerThanJDK10: Boolean = jdkVersion < 10 - - def executingInJVMOnLowerThanJDK13: Boolean = jdkVersion < 13 - - def executingInJVMOnLowerThanJDK15: Boolean = jdkVersion < 15 - - def executingInJVMOnLowerThanJDK16: Boolean = jdkVersion < 16 - - def executingInJVMOnLowerThanJDK17: Boolean = jdkVersion < 17 - def executingInJVMOnLowerThanJDK(version: Int): Boolean = jdkVersion < version + def executingInJVMWithJDKIn(range: Range): Boolean = range.contains(jdkVersion) + private lazy val jdkVersion = { val v = System.getProperty("java.version") if (v.startsWith("1.")) Integer.parseInt(v.drop(2).takeWhile(_.isDigit)) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/net/URITest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/net/URITest.scala index 9d74d833db..464b79af10 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/net/URITest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/net/URITest.scala @@ -18,7 +18,7 @@ import org.junit.Assert._ import org.junit.Test import org.scalajs.testsuite.utils.AssertThrows.assertThrows -import org.scalajs.testsuite.utils.Platform.executingInJVMOnLowerThanJDK15 +import org.scalajs.testsuite.utils.Platform._ class URITest { @@ -170,7 +170,7 @@ class URITest { val rel3 = new URI("/foo/ccc") // https://bugs.openjdk.java.net/browse/JDK-5064980 - if (!executingInJVMOnLowerThanJDK15) + if (!executingInJVMOnLowerThanJDK(15)) assertTrue(x.compareTo(y) == 0) assertTrue(x.compareTo(z) < 0) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/net/URLDecoderTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/net/URLDecoderTest.scala index de8e9dcc66..20f588b02c 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/net/URLDecoderTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/net/URLDecoderTest.scala @@ -99,7 +99,7 @@ class URLDecoderTest { * the implementation. So in all likelihood, it will not be coming back. * It is still throwing as of JDK 17. */ - if (!executingInJVMOnLowerThanJDK10) { + if (!executingInJVMOnLowerThanJDK(10)) { unsupportedEncoding("abc", enc = "dummy") unsupportedEncoding("a+b+c", enc = "dummy") } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/FormatterTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/FormatterTest.scala index f22659c320..06f3fb1fe9 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/FormatterTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/FormatterTest.scala @@ -1694,7 +1694,7 @@ class FormatterTest { @Test def indexNotInRangeThrows(): Unit = { assumeFalse("https://bugs.openjdk.java.net/browse/JDK-8253875", - executingInJVMOnLowerThanJDK16) + executingInJVMOnLowerThanJDK(16)) /* The public JavaDoc says that these situations throw an * IllegalFormatException, without being more specific. However, the bug @@ -1716,7 +1716,7 @@ class FormatterTest { @Test def widthOrPrecisionTooLargeThrows(): Unit = { assumeFalse("https://bugs.openjdk.java.net/browse/JDK-8253875", - executingInJVMOnLowerThanJDK16) + executingInJVMOnLowerThanJDK(16)) expectFormatterThrows(classOf[IllegalFormatWidthException], "%d %9876543210d", 56, 78) expectFormatterThrows(classOf[IllegalFormatPrecisionException], "%d %.9876543210f", 56, 78.5) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala index 6a401ad58c..5bacd9f8ed 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala @@ -1391,7 +1391,7 @@ trait MapTest { * https://bugs.openjdk.org/browse/JDK-8259622 */ assumeFalse("affected by JDK-8259622", - executingInJVMOnLowerThanJDK17 && !executingInJVMOnLowerThanJDK15 && + executingInJVMWithJDKIn(15 to 16) && mp.isInstanceOf[ju.TreeMap[_, _]]) mp.put("nullable", null) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ThreadLocalRandomTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ThreadLocalRandomTest.scala index d8c193d171..7bd09eeb1b 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ThreadLocalRandomTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ThreadLocalRandomTest.scala @@ -508,7 +508,7 @@ class ThreadLocalRandomTest { @Test def nextDoubleDoubleDouble(): Unit = { implicit val tlr = ThreadLocalRandom.current() - if (!executingInJVMOnLowerThanJDK(19) || executingInJVMOnLowerThanJDK(17)) { + if (!executingInJVMWithJDKIn(17 to 18)) { /* For some reason, JDK 17-18 throw an IllegalArgumentException for this one. * Older and more recent versions of the JDK succeed. */ diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/regex/RegexEngineTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/regex/RegexEngineTest.scala index 99beb524fe..4cc2a720b4 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/regex/RegexEngineTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/regex/RegexEngineTest.scala @@ -1034,7 +1034,7 @@ class RegexEngineTest { assertNotMatches(lower, "É") // https://bugs.openjdk.java.net/browse/JDK-8214245 - if (!executingInJVMOnLowerThanJDK15) { + if (!executingInJVMOnLowerThanJDK(15)) { val lowerCI = compile("\\p{Lower}", CaseInsensitive) assertMatches(lowerCI, "a") assertMatches(lowerCI, "f") @@ -1223,7 +1223,7 @@ class RegexEngineTest { assertNotMatches(lower, "É") // https://bugs.openjdk.java.net/browse/JDK-8214245 - if (!executingInJVMOnLowerThanJDK15) { + if (!executingInJVMOnLowerThanJDK(15)) { val lowerCI = compile("\\p{Lower}", CaseInsensitive | UnicodeCharacterClass) assertMatches(lowerCI, "a") assertMatches(lowerCI, "f") @@ -1389,7 +1389,7 @@ class RegexEngineTest { checkConsistency("javaIdentifierIgnorable", "[\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}]") checkConsistency("javaIdeographic", "\\p{IsIdeographic}") checkConsistency("javaISOControl", "[\u0000-\u001F\u007F-\u009F]") - if (!executingInJVMOnJDK8OrLower) { + if (!executingInJVMOnLowerThanJDK(9)) { checkConsistency("javaJavaIdentifierPart", "[\\p{L}\\p{Sc}\\p{Pc}\\p{Nd}\\p{Nl}\\p{Mn}\\p{Mc}\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}]") checkConsistency("javaJavaIdentifierStart", "[\\p{L}\\p{Sc}\\p{Pc}\\p{Nl}]") @@ -1459,7 +1459,7 @@ class RegexEngineTest { /* SignWriting is special because of its canonical name, which is not Sign_Writing. * It's from Unicode 8.0.0, so it requires JDK 9+. */ - if (!executingInJVMOnJDK8OrLower) { + if (!executingInJVMOnLowerThanJDK(9)) { val signWriting = compile("\\p{script=signwrItIng}") assertMatches(signWriting, "\uD836\uDC36") // U+1D836 SIGNWRITING HAND-FIST MIDDLE THUMB CUPPED INDEX UP assertNotMatches(signWriting, "A") @@ -1846,7 +1846,7 @@ class RegexEngineTest { assertNotMatches(complexUnionsAndIntersections, "z") // https://bugs.openjdk.java.net/browse/JDK-8216391 - if (!executingInJVMOnJDK8OrLower) { + if (!executingInJVMOnLowerThanJDK(9)) { val not_ad_or_mp = compile("[^a-d[m-p]]") assertNotMatches(not_ad_or_mp, "a") assertNotMatches(not_ad_or_mp, "c") @@ -2572,7 +2572,7 @@ class RegexEngineTest { } // JDK 9+ features - if (!executingInJVMOnJDK8OrLower) { + if (!executingInJVMOnLowerThanJDK(9)) { assertSyntaxErrorInJS("\\N{DIGIT TWO}", "\\N is not supported", 1) assertSyntaxErrorInJS("foo\\b{g}.", "\\b{g} is not supported", 4) assertSyntaxErrorInJS("foo\\X", "\\X is not supported", 4) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/BaseBufferTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/BaseBufferTest.scala index fc6822f31a..606732f87e 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/BaseBufferTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/BaseBufferTest.scala @@ -313,7 +313,7 @@ abstract class BaseBufferTest { @Test def compact(): Unit = { assumeFalse("Affected by a bug in the JDK.", - executingInJVMOnJDK8OrLower && + executingInJVMOnLowerThanJDK(9) && factory.isInstanceOf[BufferFactory.ByteBufferViewFactory]) if (!createsReadOnly) { From 8c05d338c6b324f61d688d6bddd37738040a1fe2 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 16 Nov 2024 15:25:33 +0100 Subject: [PATCH 034/121] ClassDef check RecordSelect assignments --- .../linker/checker/ClassDefChecker.scala | 25 +++++++++++++++++-- .../linker/checker/ClassDefCheckerTest.scala | 8 ++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index baf49343e3..1c4bd11d74 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -12,7 +12,7 @@ package org.scalajs.linker.checker -import scala.annotation.switch +import scala.annotation.{switch, tailrec} import scala.collection.mutable @@ -689,8 +689,29 @@ private final class ClassDefChecker(classDef: ClassDef, case JSGlobalRef(JSGlobalRef.FileLevelThis) => reportError(i"Assignment to global this.") + case RecordSelect(record, SimpleFieldIdent(fieldName)) => + record.tpe match { + case RecordType(fields) => + if (fields.find(_.name == fieldName).exists(!_.mutable)) + reportError(i"assignment to immutable record field $fieldName") + + case _ => + // ok (NothingType) or IRChecker will complain. + } + + @tailrec + def check(lhs: Tree): Unit = lhs match { + case RecordSelect(inner, _) => check(inner) + case _: VarRef => // ok + + case lhs => + reportError(i"Assignment to RecordSelect of illegal tree: ${lhs.getClass.getName}") + } + + check(record) + case _:Select | _:JSPrivateSelect | _:SelectStatic | - _:ArraySelect | _:RecordSelect | _:JSSelect | _:JSSuperSelect | + _:ArraySelect | _:JSSelect | _:JSSuperSelect | _:JSGlobalRef => } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala index 1d338034d8..78e4e1dd2c 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala @@ -709,6 +709,14 @@ class ClassDefCheckerTest { testIsInstanceOfError(ArrayType(ArrayTypeRef(IntRef, 1), nullable = true)) testAsInstanceOfError(ArrayType(ArrayTypeRef(IntRef, 1), nullable = false)) } + + @Test + def assignRecordSelect(): Unit = { + assertError( + mainTestClassDef(Assign(RecordSelect(int(5), "i")(IntType), int(6))), + "Assignment to RecordSelect of illegal tree: org.scalajs.ir.Trees$IntLiteral", + allowTransients = true) + } } private object ClassDefCheckerTest { From ad69956d977c08c0bde215aac7e5493a5baeec5f Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 16 Nov 2024 15:27:34 +0100 Subject: [PATCH 035/121] IRCheck Records --- .../scalajs/linker/checker/IRChecker.scala | 35 +++++++++++++-- .../org/scalajs/linker/IRCheckerTest.scala | 44 +++++++++++++++++++ .../linker/testutils/TestIRBuilder.scala | 4 ++ 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index fac0022dc3..125462cef3 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -737,11 +737,38 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, override def traverse(tree: Tree): Unit = typecheck(tree, env) }) - case _: RecordSelect if postOptimizer => - // TODO + case RecordSelect(record, SimpleFieldIdent(fieldName)) if postOptimizer => + record.tpe match { + case NothingType => // ok + + case RecordType(fields) => + fields.find(_.name == fieldName) match { + case Some(field) => + if (tree.tpe != field.tpe) + reportError(i"Record select of field type ${field.tpe} typed as ${tree.tpe}") + + case None => + reportError(i"Record select of non-existent field: $fieldName") + } + + case tpe => + reportError(i"Record type expected but $tpe found") + } - case _: RecordValue if postOptimizer => - // TODO + typecheck(record, env) + + case RecordValue(RecordType(fields), elems) if postOptimizer => + if (fields.size == elems.size) { + for ((field, elem) <- fields.zip(elems)) + typecheckExpect(elem, env, field.tpe) + } else { + reportError( + "Mismatched size for record fields / elements: " + + i"${fields.size} fields vs ${elems.size} elements") + + for (elem <- elems) + typecheck(elem, env) + } case _:RecordSelect | _:RecordValue | _:Transient | _:JSSuperConstructorCall => reportError("invalid tree") diff --git a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala index bc7d7e1da1..a0333eccf1 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala @@ -349,6 +349,50 @@ class IRCheckerTest { testLinkNoIRError(classDefs, MainTestModuleInitializers, postOptimizer = true) } + @Test + def badRecordSelect(): AsyncResult = await { + val recordValue = RecordValue( + RecordType(List( + RecordType.Field("i", NON, IntType, false), + RecordType.Field("s", NON, StringType, false) + )), + List(int(1), str("foo"))) + + val classDefs = Seq( + mainTestClassDef(Block( + RecordSelect(recordValue, "i")(StringType), + RecordSelect(recordValue, "x")(StringType) + )) + ) + + for { + log <- testLinkIRErrors(classDefs, MainTestModuleInitializers, postOptimizer = true) + } yield { + log.assertContainsError("Record select of field type int typed as string") + log.assertContainsError("Record select of non-existent field: x") + } + } + + @Test + def badRecordValue(): AsyncResult = await { + val classDefs = Seq( + mainTestClassDef(Block( + RecordValue(RecordType(Nil), List(int(1))), + RecordValue( + RecordType(List(RecordType.Field("i", NON, IntType, false))), + List(str("foo")) + ) + )) + ) + + for { + log <- testLinkIRErrors(classDefs, MainTestModuleInitializers, postOptimizer = true) + } yield { + log.assertContainsError("Mismatched size for record fields / elements: 0 fields vs 1 elements") + log.assertContainsError("int expected but string found") + } + } + } object IRCheckerTest { diff --git a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala index 295d78fd29..528dfb2fcc 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala @@ -183,11 +183,15 @@ object TestIRBuilder { LocalIdent(LocalName(name)) implicit def string2LabelIdent(name: String): LabelIdent = LabelIdent(LabelName(name)) + implicit def string2SimpleFieldIdent(name: String): SimpleFieldIdent = + SimpleFieldIdent(SimpleFieldName(name)) implicit def string2ClassIdent(name: String): ClassIdent = ClassIdent(ClassName(name)) implicit def localName2LocalIdent(name: LocalName): LocalIdent = LocalIdent(name) + implicit def simpleFieldName2SimpleFieldIdent(name: SimpleFieldName): SimpleFieldIdent = + SimpleFieldIdent(name) implicit def fieldName2FieldIdent(name: FieldName): FieldIdent = FieldIdent(name) implicit def methodName2MethodIdent(name: MethodName): MethodIdent = From bb4f6da3628913639a82ad371c22731d9b2347f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 13 Nov 2024 13:52:01 +0100 Subject: [PATCH 036/121] Allow `Return` arg to be a `void` if the target `Labeled` is a `void`. Previously, even if the target label of a `Return` declared a `void` result type, the argument to the `Return` had to be an expression, with a non-`void` type. Because of that rule, we generated fake `Undefined()` arguments to the `Return`s. In this commit, we remove that restriction. This simplifies the IR specification and removes some ad hoc code paths to generate the fake `Undefined()`. We do have to add some handling in the JS function emitter. The Wasm function emitter requires no change. The changes in `doReturnToLabel()` are necessary to maintain the status quo in terms of the emitted JS. My understanding is that before, we would get a lot of `Block(stats, Return(Undefined))` which are now `Return(Block(stats))`. If the `stats` contain `If`s and other branches, the new `Return` can be pushed down a lot more. When it wasn't pushed down, we would have regular fall-through behavior without `js.Break`s. Now with the `Return`s pushed down, we get a lot of `js.Break`s that are not actually necessary. The new optimization in `doReturnToLabel()` removes them. --- .../org/scalajs/nscplugin/GenJSCode.scala | 32 +++---- .../main/scala/org/scalajs/ir/Printers.scala | 6 +- .../scala/org/scalajs/ir/Transformers.scala | 2 +- .../scala/org/scalajs/ir/PrintersTest.scala | 1 + .../backend/emitter/FunctionEmitter.scala | 93 ++++++++++++------- .../scalajs/linker/checker/IRChecker.scala | 6 +- .../frontend/optimizer/OptimizerCore.scala | 9 +- .../org/scalajs/linker/OptimizerTest.scala | 46 ++++----- 8 files changed, 109 insertions(+), 86 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 81cfa6df15..d1b75dab51 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -2565,10 +2565,9 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } case Return(expr) => - js.Return(toIRType(expr.tpe) match { - case jstpe.VoidType => js.Block(genStat(expr), js.Undefined()) - case _ => genExpr(expr) - }, getEnclosingReturnLabel()) + js.Return( + genStatOrExpr(expr, isStat = toIRType(expr.tpe) == jstpe.VoidType), + getEnclosingReturnLabel()) case t: Try => genTry(t, isStat) @@ -2882,7 +2881,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * are transformed into * {{{ * ...labelParams = ...args; - * return@labelName (void 0) + * return@labelName; * }}} * * This is always correct, so it can handle arbitrary labels and jumps @@ -2943,7 +2942,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.While(js.BooleanLiteral(true), { js.Labeled(labelIdent, jstpe.VoidType, { if (isStat) - js.Block(transformedRhs, js.Return(js.Undefined(), blockLabelIdent)) + js.Block(transformedRhs, js.Return(js.Skip(), blockLabelIdent)) else js.Return(transformedRhs, blockLabelIdent) }) @@ -3454,7 +3453,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val paramSyms = info.paramSyms assertArgCountMatches(paramSyms.size) - val jump = js.Return(js.Undefined(), labelIdent) + val jump = js.Return(js.Skip(), labelIdent) if (args.isEmpty) { // fast path, applicable notably to loops and case labels @@ -3914,7 +3913,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) def genJumpToElseClause(implicit pos: ir.Position): js.Tree = { if (optElseClauseLabel.isEmpty) optElseClauseLabel = Some(freshLabelIdent("default")) - js.Return(js.Undefined(), optElseClauseLabel.get) + js.Return(js.Skip(), optElseClauseLabel.get) } for (caze @ CaseDef(pat, guard, body) <- cases) { @@ -4036,9 +4035,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val matchResultLabel = freshLabelIdent("matchResult") val patchedClauses = for ((alts, body) <- clauses) yield { implicit val pos = body.pos - val newBody = - if (isStat) js.Block(body, js.Return(js.Undefined(), matchResultLabel)) - else js.Return(body, matchResultLabel) + val newBody = js.Return(body, matchResultLabel) (alts, newBody) } js.Labeled(matchResultLabel, resultType, js.Block(List( @@ -4140,12 +4137,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val translatedMatch = genTranslatedMatch(cases, matchEnd) val genMore = genBlockWithCaseLabelDefs(more, isStat) val label = getEnclosingReturnLabel() - if (translatedMatch.tpe == jstpe.VoidType) { - // Could not actually reproduce this, but better be safe than sorry - translatedMatch :: js.Return(js.Undefined(), label) :: genMore - } else { - js.Return(translatedMatch, label) :: genMore - } + js.Return(translatedMatch, label) :: genMore // Otherwise, there is no matchEnd, only consecutive cases case Nil => @@ -4396,21 +4388,21 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) translatedBody match { case js.Block(stats) => val (stats1, testAndStats2) = stats.span { - case js.If(_, js.Return(js.Undefined(), `label`), js.Skip()) => + case js.If(_, js.Return(_, `label`), js.Skip()) => false case _ => true } testAndStats2 match { - case js.If(cond, _, _) :: stats2 => + case js.If(cond, js.Return(returnedValue, _), _) :: stats2 => val notCond = cond match { case js.UnaryOp(js.UnaryOp.Boolean_!, notCond) => notCond case _ => js.UnaryOp(js.UnaryOp.Boolean_!, cond) } - js.Block(stats1 :+ js.If(notCond, js.Block(stats2), js.Skip())(jstpe.VoidType)) + js.Block(stats1 :+ js.If(notCond, js.Block(stats2), returnedValue)(jstpe.VoidType)) case _ :: _ => throw new AssertionError("unreachable code") diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala index e245cffeb4..b5bfccc64e 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -189,8 +189,10 @@ object Printers { case Return(expr, label) => print("return@") print(label) - print(" ") - print(expr) + if (!expr.isInstanceOf[Skip]) { + print(" ") + print(expr) + } case If(cond, BooleanLiteral(true), elsep) => print(cond) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala index 2fcc5bd1ee..10f490225b 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala @@ -58,7 +58,7 @@ object Transformers { Assign(transformExpr(lhs).asInstanceOf[AssignLhs], transformExpr(rhs)) case Return(expr, label) => - Return(transformExpr(expr), label) + Return(transformExpr(expr), label) // pessimistic; maybe `expr` is actually a statement case If(cond, thenp, elsep) => If(transformExpr(cond), transform(thenp, isStat), diff --git a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala index a2e7a2d51f..980b7c9bc3 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -154,6 +154,7 @@ class PrintersTest { @Test def printReturn(): Unit = { assertPrintEquals("return@lab 5", Return(i(5), "lab")) + assertPrintEquals("return@lab", Return(Skip(), "lab")) } @Test def printIf(): Unit = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index 546bbb1e3b..834ec7a554 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -1538,25 +1538,30 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { def doReturnToLabel(l: LabelIdent): js.Tree = { val newLhs = env.lhsForLabeledExpr(l) - val body = pushLhsInto(newLhs, rhs, Set.empty) if (newLhs.hasNothingType) { /* A touch of peephole dead code elimination. * This is actually necessary to avoid dangling breaks to eliminated * labels, as in issue #2307. */ - body + pushLhsInto(newLhs, rhs, tailPosLabels) } else if (tailPosLabels.contains(l.name)) { - body - } else if (env.isDefaultBreakTarget(l.name)) { - js.Block(body, js.Break(None)) - } else if (env.isDefaultContinueTarget(l.name)) { - js.Block(body, js.Continue(None)) + pushLhsInto(newLhs, rhs, tailPosLabels) } else { - usedLabels += l.name - val transformedLabel = Some(transformLabelIdent(l)) - val jump = - if (env.isLabelTurnedIntoContinue(l.name)) js.Continue(transformedLabel) - else js.Break(transformedLabel) + val body = pushLhsInto(newLhs, rhs, Set.empty) + + val jump = if (env.isDefaultBreakTarget(l.name)) { + js.Break(None) + } else if (env.isDefaultContinueTarget(l.name)) { + js.Continue(None) + } else { + usedLabels += l.name + val transformedLabel = Some(transformLabelIdent(l)) + if (env.isLabelTurnedIntoContinue(l.name)) + js.Continue(transformedLabel) + else + js.Break(transformedLabel) + } + js.Block(body, jump) } } @@ -1597,7 +1602,10 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case Lhs.Assign(lhs) => doAssign(lhs, rhs) case Lhs.ReturnFromFunction => - js.Return(transformExpr(rhs, env.expectedReturnType)) + if (env.expectedReturnType == VoidType) + js.Block(transformStat(rhs, tailPosLabels = Set.empty), js.Return(js.Undefined())) + else + js.Return(transformExpr(rhs, env.expectedReturnType)) case Lhs.Return(l) => doReturnToLabel(l) case Lhs.Throw => @@ -2016,29 +2024,44 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { redo(CreateJSClass(className, newCaptureValues))(env) } - case _ => - if (lhs == Lhs.Discard) { - /* Go "back" to transformStat() after having dived into - * expression statements. Remember that Lhs.Discard is a trick that - * we use to "add" all the code of pushLhsInto() to transformStat(). - */ - rhs match { - case _:Skip | _:VarDef | _:Assign | _:While | - _:Debugger | _:JSSuperConstructorCall | _:JSDelete | - _:StoreModule | Transient(_:SystemArrayCopy) => - transformStat(rhs, tailPosLabels) - case _ => - throw new IllegalArgumentException( - "Illegal tree in JSDesugar.pushLhsInto():\n" + - "lhs = " + lhs + "\n" + - "rhs = " + rhs + " of class " + rhs.getClass) - } - } else { - throw new IllegalArgumentException( - "Illegal tree in JSDesugar.pushLhsInto():\n" + - "lhs = " + lhs + "\n" + - "rhs = " + rhs + " of class " + rhs.getClass) + // Statement-only trees + + case _:Skip | _:VarDef | _:Assign | _:While | _:Debugger | + _:JSSuperConstructorCall | _:JSDelete | _:StoreModule | + Transient(_:SystemArrayCopy) => + /* Go "back" to transformStat() after having dived into + * expression statements. This can only happen for Lhs.Discard and + * for Lhs.Return's whose target is a statement. + */ + lhs match { + case Lhs.Discard => + transformStat(rhs, tailPosLabels) + case Lhs.ReturnFromFunction => + /* If we get here, it is because desugarToFunctionInternal() + * found a top-level Labeled and eliminated it. Therefore, unless + * we're mistaken, by construction we cannot be in tail position + * of the whole function (otherwise doReturnToLabel would have + * eliminated the lhs). That means there is no point trying to + * avoid the `js.Return(js.Undefined())`. + */ + js.Block( + transformStat(rhs, tailPosLabels = Set.empty), + js.Return(js.Undefined())) + case Lhs.Return(l) => + doReturnToLabel(l) + + case _:Lhs.VarDef | _:Lhs.Assign | Lhs.Throw => + throw new IllegalArgumentException( + "Illegal tree in FunctionEmitter.pushLhsInto():\n" + + "lhs = " + lhs + "\n" + + "rhs = " + rhs + " of class " + rhs.getClass) } + + case _ => + throw new IllegalArgumentException( + "Illegal tree in FunctionEmitter.pushLhsInto():\n" + + "lhs = " + lhs + "\n" + + "rhs = " + rhs + " of class " + rhs.getClass) }) } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index fac0022dc3..51575cd744 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -304,11 +304,7 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, typecheckExpect(rhs, env, expectedRhsTpe) case Return(expr, label) => - val returnType = env.returnTypes(label.name) - if (returnType == VoidType) - typecheckExpr(expr, env) - else - typecheckExpect(expr, env, returnType) + typecheckExpect(expr, env, env.returnTypes(label.name)) case If(cond, thenp, elsep) => val tpe = tree.tpe diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index fe7d8c03a8..0f519afdb2 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -383,7 +383,11 @@ private[optimizer] abstract class OptimizerCore( case Return(expr, label) => val info = scope.env.labelInfos(label.name) val newLabel = LabelIdent(info.newName) - if (!info.acceptRecords) { + if (info.isStat) { + val newExpr = transformStat(expr) + info.returnedTypes.value ::= (VoidType, RefinedType.NoRefinedType) + Return(newExpr, newLabel) + } else if (!info.acceptRecords) { val newExpr = transformExpr(expr) info.returnedTypes.value ::= (newExpr.tpe, RefinedType(newExpr.tpe)) Return(newExpr, newLabel) @@ -5170,7 +5174,7 @@ private[optimizer] abstract class OptimizerCore( } } - val info = new LabelInfo(newLabel, acceptRecords = usePreTransform, + val info = new LabelInfo(newLabel, isStat, acceptRecords = usePreTransform, returnedTypes = newSimpleState(Nil)) val bodyScope = scope.withEnv(scope.env.withLabelInfo(oldLabelName, info)) @@ -6011,6 +6015,7 @@ private[optimizer] object OptimizerCore { private final class LabelInfo( val newName: LabelName, + val isStat: Boolean, val acceptRecords: Boolean, /** (actualType, originalType), actualType can be a RecordType. */ val returnedTypes: SimpleState[List[(Type, RefinedType)]]) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala index 0a77c1414c..08838c0627 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala @@ -272,28 +272,32 @@ class OptimizerTest { val matchAlts1 = LabelIdent("matchAlts1") val matchAlts2 = LabelIdent("matchAlts2") - val classDefs = Seq( - mainTestClassDef(Block( - Labeled(matchResult1, VoidType, Block( - VarDef(x1, NON, AnyType, mutable = false, Null()), - Labeled(matchAlts1, VoidType, Block( - Labeled(matchAlts2, VoidType, Block( - If(IsInstanceOf(VarRef(x1)(AnyType), ClassType(BoxedIntegerClass, nullable = false)), { - Return(Undefined(), matchAlts2) - }, Skip())(VoidType), - If(IsInstanceOf(VarRef(x1)(AnyType), ClassType(BoxedStringClass, nullable = false)), { - Return(Undefined(), matchAlts2) - }, Skip())(VoidType), - Return(Undefined(), matchAlts1) - )), - Return(Undefined(), matchResult1) - )), - Throw(New("java.lang.Exception", NoArgConstructorName, Nil)) - )) - )) - ) + val results = for (voidReturnArgument <- List(Undefined(), Skip())) yield { + val classDefs = Seq( + mainTestClassDef(Block( + Labeled(matchResult1, VoidType, Block( + VarDef(x1, NON, AnyType, mutable = false, Null()), + Labeled(matchAlts1, VoidType, Block( + Labeled(matchAlts2, VoidType, Block( + If(IsInstanceOf(VarRef(x1)(AnyType), ClassType(BoxedIntegerClass, nullable = false)), { + Return(voidReturnArgument, matchAlts2) + }, Skip())(VoidType), + If(IsInstanceOf(VarRef(x1)(AnyType), ClassType(BoxedStringClass, nullable = false)), { + Return(voidReturnArgument, matchAlts2) + }, Skip())(VoidType), + Return(voidReturnArgument, matchAlts1) + )), + Return(voidReturnArgument, matchResult1) + )), + Throw(New("java.lang.Exception", NoArgConstructorName, Nil)) + )) + )) + ) + + testLink(classDefs, MainTestModuleInitializers) + } - testLink(classDefs, MainTestModuleInitializers) + Future.sequence(results) } @Test From 0e9dfa69312ef0edbfdb3927d0154284956ff5ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 18 Nov 2024 17:52:02 +0100 Subject: [PATCH 037/121] Recognize more methods as anonfun's that should have the `inline` hint. When a `$anonfun` method gets an expanded name---typically because it must be made non-private---, its name receives a new *prefix*. That prevented the previous `startsWith` test to recognize them as the methods behind anonymous functions. We now use a `containsName` test to recognize them, so that we can give them the `inline` hint. --- .../src/main/scala/org/scalajs/nscplugin/GenJSCode.scala | 2 +- .../src/test/scala/org/scalajs/linker/LibrarySizeTest.scala | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index d1b75dab51..9839b71954 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -2068,7 +2068,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } else { val shouldMarkInline = { sym.hasAnnotation(InlineAnnotationClass) || - sym.name.startsWith(nme.ANON_FUN_NAME) || + sym.name.containsName(nme.ANON_FUN_NAME) || adHocInlineMethods.contains(sym.fullName) } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala index a9fbf49876..137d8e7400 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala @@ -70,9 +70,9 @@ class LibrarySizeTest { ) testLinkedSizes( - expectedFastLinkSize = 147459, - expectedFullLinkSizeWithoutClosure = 85718, - expectedFullLinkSizeWithClosure = 21495, + expectedFastLinkSize = 147046, + expectedFullLinkSizeWithoutClosure = 85355, + expectedFullLinkSizeWithClosure = 21492, classDefs, moduleInitializers = MainTestModuleInitializers ) From 5e9956bfd93fa2567e6207a5d34cb6118fc11eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 18 Nov 2024 13:23:21 +0100 Subject: [PATCH 038/121] Use explicit 0-arg functions instead of by-name params in javalib. --- .../src/main/scala/java/io/PrintStream.scala | 20 ++++++++--------- .../src/main/scala/java/io/PrintWriter.scala | 20 ++++++++--------- .../src/main/scala/java/lang/ClassValue.scala | 2 +- javalib/src/main/scala/java/lang/Float.scala | 10 ++++----- .../src/main/scala/java/lang/StackTrace.scala | 8 +++---- javalib/src/main/scala/java/lang/System.scala | 4 ++-- javalib/src/main/scala/java/lang/Utils.scala | 22 +++++++++---------- .../src/main/scala/java/math/BigInteger.scala | 11 ++++------ .../main/scala/java/nio/charset/Charset.scala | 2 +- .../scala/java/nio/charset/CoderResult.scala | 4 ++-- .../main/scala/java/util/AbstractMap.scala | 2 +- .../src/main/scala/java/util/Formatter.scala | 14 ++++++------ .../src/main/scala/java/util/ScalaOps.scala | 6 ++--- javalib/src/main/scala/java/util/Timer.scala | 10 ++++----- .../src/main/scala/java/util/TimerTask.scala | 4 ++-- .../java/util/regex/IndicesBuilder.scala | 4 ++-- .../main/scala/java/util/regex/Matcher.scala | 8 +++---- .../main/scala/java/util/regex/Pattern.scala | 2 +- .../java/util/regex/PatternCompiler.scala | 6 ++--- 19 files changed, 78 insertions(+), 81 deletions(-) diff --git a/javalib/src/main/scala/java/io/PrintStream.scala b/javalib/src/main/scala/java/io/PrintStream.scala index d6141609c3..9b61270d43 100644 --- a/javalib/src/main/scala/java/io/PrintStream.scala +++ b/javalib/src/main/scala/java/io/PrintStream.scala @@ -79,9 +79,9 @@ class PrintStream private (_out: OutputStream, autoFlush: Boolean, private var errorFlag: Boolean = false override def flush(): Unit = - ensureOpenAndTrapIOExceptions(out.flush()) + ensureOpenAndTrapIOExceptions(() => out.flush()) - override def close(): Unit = trapIOExceptions { + override def close(): Unit = trapIOExceptions { () => if (!closing) { closing = true encoder.close() @@ -133,7 +133,7 @@ class PrintStream private (_out: OutputStream, autoFlush: Boolean, */ override def write(b: Int): Unit = { - ensureOpenAndTrapIOExceptions { + ensureOpenAndTrapIOExceptions { () => out.write(b) if (autoFlush && b == '\n') flush() @@ -141,7 +141,7 @@ class PrintStream private (_out: OutputStream, autoFlush: Boolean, } override def write(buf: Array[Byte], off: Int, len: Int): Unit = { - ensureOpenAndTrapIOExceptions { + ensureOpenAndTrapIOExceptions { () => out.write(buf, off, len) if (autoFlush) flush() @@ -157,17 +157,17 @@ class PrintStream private (_out: OutputStream, autoFlush: Boolean, def print(s: String): Unit = printString(if (s == null) "null" else s) def print(obj: AnyRef): Unit = printString(String.valueOf(obj)) - private def printString(s: String): Unit = ensureOpenAndTrapIOExceptions { + private def printString(s: String): Unit = ensureOpenAndTrapIOExceptions { () => encoder.write(s) encoder.flushBuffer() } - def print(s: Array[Char]): Unit = ensureOpenAndTrapIOExceptions { + def print(s: Array[Char]): Unit = ensureOpenAndTrapIOExceptions { () => encoder.write(s) encoder.flushBuffer() } - def println(): Unit = ensureOpenAndTrapIOExceptions { + def println(): Unit = ensureOpenAndTrapIOExceptions { () => encoder.write('\n') // In Scala.js the line separator is always LF encoder.flushBuffer() if (autoFlush) @@ -214,15 +214,15 @@ class PrintStream private (_out: OutputStream, autoFlush: Boolean, this } - @inline private[this] def trapIOExceptions(body: => Unit): Unit = { + @inline private[this] def trapIOExceptions(body: () => Unit): Unit = { try { - body + body() } catch { case _: IOException => setError() } } - @inline private[this] def ensureOpenAndTrapIOExceptions(body: => Unit): Unit = { + @inline private[this] def ensureOpenAndTrapIOExceptions(body: () => Unit): Unit = { if (closed) setError() else trapIOExceptions(body) } diff --git a/javalib/src/main/scala/java/io/PrintWriter.scala b/javalib/src/main/scala/java/io/PrintWriter.scala index 5e3facf333..e03ddb7d2c 100644 --- a/javalib/src/main/scala/java/io/PrintWriter.scala +++ b/javalib/src/main/scala/java/io/PrintWriter.scala @@ -41,9 +41,9 @@ class PrintWriter(protected[io] var out: Writer, private var errorFlag: Boolean = false def flush(): Unit = - ensureOpenAndTrapIOExceptions(out.flush()) + ensureOpenAndTrapIOExceptions(() => out.flush()) - def close(): Unit = trapIOExceptions { + def close(): Unit = trapIOExceptions { () => if (!closed) { flush() closed = true @@ -76,19 +76,19 @@ class PrintWriter(protected[io] var out: Writer, protected[io] def clearError(): Unit = errorFlag = false override def write(c: Int): Unit = - ensureOpenAndTrapIOExceptions(out.write(c)) + ensureOpenAndTrapIOExceptions(() => out.write(c)) override def write(buf: Array[Char], off: Int, len: Int): Unit = - ensureOpenAndTrapIOExceptions(out.write(buf, off, len)) + ensureOpenAndTrapIOExceptions(() => out.write(buf, off, len)) override def write(buf: Array[Char]): Unit = - ensureOpenAndTrapIOExceptions(out.write(buf)) + ensureOpenAndTrapIOExceptions(() => out.write(buf)) override def write(s: String, off: Int, len: Int): Unit = - ensureOpenAndTrapIOExceptions(out.write(s, off, len)) + ensureOpenAndTrapIOExceptions(() => out.write(s, off, len)) override def write(s: String): Unit = - ensureOpenAndTrapIOExceptions(out.write(s)) + ensureOpenAndTrapIOExceptions(() => out.write(s)) def print(b: Boolean): Unit = write(String.valueOf(b)) def print(c: Char): Unit = write(c) @@ -147,15 +147,15 @@ class PrintWriter(protected[io] var out: Writer, this } - @inline private[this] def trapIOExceptions(body: => Unit): Unit = { + @inline private[this] def trapIOExceptions(body: () => Unit): Unit = { try { - body + body() } catch { case _: IOException => setError() } } - @inline private[this] def ensureOpenAndTrapIOExceptions(body: => Unit): Unit = { + @inline private[this] def ensureOpenAndTrapIOExceptions(body: () => Unit): Unit = { if (closed) setError() else trapIOExceptions(body) } diff --git a/javalib/src/main/scala/java/lang/ClassValue.scala b/javalib/src/main/scala/java/lang/ClassValue.scala index 74f91999ca..0ab92d37cb 100644 --- a/javalib/src/main/scala/java/lang/ClassValue.scala +++ b/javalib/src/main/scala/java/lang/ClassValue.scala @@ -50,7 +50,7 @@ abstract class ClassValue[T] protected () { def get(`type`: Class[_]): T = { if (useJSMap) { - mapGetOrElseUpdate(jsMap, `type`)(computeValue(`type`)) + mapGetOrElseUpdate(jsMap, `type`)(() => computeValue(`type`)) } else { /* We first perform `get`, and if the result is null, we use * `containsKey` to disambiguate a present null from an absent key. diff --git a/javalib/src/main/scala/java/lang/Float.scala b/javalib/src/main/scala/java/lang/Float.scala index 6bb6bb4565..c9c6ea2e84 100644 --- a/javalib/src/main/scala/java/lang/Float.scala +++ b/javalib/src/main/scala/java/lang/Float.scala @@ -122,14 +122,14 @@ object Float { } else if (undefOrIsDefined(groups(4))) { // Decimal notation val fullNumberStr = undefOrForceGet(groups(4)) - val integralPartStr = undefOrGetOrElse(groups(5))("") - val fractionalPartStr = undefOrGetOrElse(groups(6))("") + undefOrGetOrElse(groups(7))("") - val exponentStr = undefOrGetOrElse(groups(8))("0") + val integralPartStr = undefOrGetOrElse(groups(5))(() => "") + val fractionalPartStr = undefOrGetOrElse(groups(6))(() => "") + undefOrGetOrElse(groups(7))(() => "") + val exponentStr = undefOrGetOrElse(groups(8))(() => "0") parseFloatDecimal(fullNumberStr, integralPartStr, fractionalPartStr, exponentStr) } else { // Hexadecimal notation - val integralPartStr = undefOrGetOrElse(groups(10))("") - val fractionalPartStr = undefOrGetOrElse(groups(11))("") + undefOrGetOrElse(groups(12))("") + val integralPartStr = undefOrGetOrElse(groups(10))(() => "") + val fractionalPartStr = undefOrGetOrElse(groups(11))(() => "") + undefOrGetOrElse(groups(12))(() => "") val binaryExpStr = undefOrForceGet(groups(13)) parseFloatHexadecimal(integralPartStr, fractionalPartStr, binaryExpStr) } diff --git a/javalib/src/main/scala/java/lang/StackTrace.scala b/javalib/src/main/scala/java/lang/StackTrace.scala index 76b3d067e7..31960903b9 100644 --- a/javalib/src/main/scala/java/lang/StackTrace.scala +++ b/javalib/src/main/scala/java/lang/StackTrace.scala @@ -126,7 +126,7 @@ private[lang] object StackTrace { trace.push(new StackTraceElement(classAndMethodName(0), classAndMethodName(1), undefOrForceGet(mtch(2)), parseInt(undefOrForceGet(mtch(3))), - undefOrFold(mtch(4))(-1)(parseInt(_)))) + undefOrFold(mtch(4))(() => -1)(parseInt(_)))) } else { // just in case // (explicitly use the constructor with column number so that STE has an inlineable init) @@ -413,7 +413,7 @@ private[lang] object StackTrace { while (i < len) { val mtch = lineRE.exec(lines(i)) if (mtch ne null) { - val fnName = undefOrGetOrElse(mtch(3))("{anonymous}") + val fnName = undefOrGetOrElse(mtch(3))(() => "{anonymous}") result.push( fnName + "()@" + undefOrForceGet(mtch(2)) + ":" + undefOrForceGet(mtch(1)) @@ -438,7 +438,7 @@ private[lang] object StackTrace { while (i < len) { val mtch = lineRE.exec(lines(i)) if (mtch ne null) { - val fnName = undefOrFold(mtch(1))("global code")(_ + "()") + val fnName = undefOrFold(mtch(1))(() => "global code")(_ + "()") result.push(fnName + "@" + undefOrForceGet(mtch(2)) + ":" + undefOrForceGet(mtch(3))) } i += 1 @@ -458,7 +458,7 @@ private[lang] object StackTrace { val mtch = lineRE.exec(lines(i)) if (mtch ne null) { val location = undefOrForceGet(mtch(4)) + ":" + undefOrForceGet(mtch(1)) + ":" + undefOrForceGet(mtch(2)) - val fnName0 = undefOrGetOrElse(mtch(2))("global code") + val fnName0 = undefOrGetOrElse(mtch(2))(() => "global code") val fnName = fnName0 .jsReplace("""""".re, "$1") .jsReplace("""""".re, "{anonymous}") diff --git a/javalib/src/main/scala/java/lang/System.scala b/javalib/src/main/scala/java/lang/System.scala index 8075a6ac70..4938d237ca 100644 --- a/javalib/src/main/scala/java/lang/System.scala +++ b/javalib/src/main/scala/java/lang/System.scala @@ -233,11 +233,11 @@ object System { } def getProperty(key: String): String = - if (dict ne null) dictGetOrElse(dict, key)(null) + if (dict ne null) dictGetOrElse(dict, key)(() => null) else properties.getProperty(key) def getProperty(key: String, default: String): String = - if (dict ne null) dictGetOrElse(dict, key)(default) + if (dict ne null) dictGetOrElse(dict, key)(() => default) else properties.getProperty(key, default) def clearProperty(key: String): String = diff --git a/javalib/src/main/scala/java/lang/Utils.scala b/javalib/src/main/scala/java/lang/Utils.scala index b57bc2a520..6e0a535831 100644 --- a/javalib/src/main/scala/java/lang/Utils.scala +++ b/javalib/src/main/scala/java/lang/Utils.scala @@ -34,9 +34,9 @@ private[java] object Utils { x.asInstanceOf[A] @inline - def undefOrGetOrElse[A](x: js.UndefOr[A])(default: => A): A = + def undefOrGetOrElse[A](x: js.UndefOr[A])(default: () => A): A = if (undefOrIsDefined(x)) undefOrForceGet(x) - else default + else default() @inline def undefOrGetOrNull[A >: Null](x: js.UndefOr[A]): A = @@ -50,9 +50,9 @@ private[java] object Utils { } @inline - def undefOrFold[A, B](x: js.UndefOr[A])(default: => B)(f: A => B): B = + def undefOrFold[A, B](x: js.UndefOr[A])(default: () => B)(f: A => B): B = if (undefOrIsDefined(x)) f(undefOrForceGet(x)) - else default + else default() private object Cache { val safeHasOwnProperty = @@ -84,11 +84,11 @@ private[java] object Utils { @inline def dictGetOrElse[A](dict: js.Dictionary[_ <: A], key: String)( - default: => A): A = { + default: () => A): A = { if (dictContains(dict, key)) dictRawApply(dict, key) else - default + default() } def dictGetOrElseAndRemove[A](dict: js.Dictionary[_ <: A], key: String, @@ -139,16 +139,16 @@ private[java] object Utils { map.asInstanceOf[MapRaw[K, V]].set(key, value) @inline - def mapGetOrElse[K, V](map: js.Map[K, V], key: K)(default: => V): V = { + def mapGetOrElse[K, V](map: js.Map[K, V], key: K)(default: () => V): V = { val value = map.asInstanceOf[MapRaw[K, V]].getOrUndefined(key) if (!isUndefined(value) || mapHas(map, key)) value.asInstanceOf[V] - else default + else default() } @inline - def mapGetOrElseUpdate[K, V](map: js.Map[K, V], key: K)(default: => V): V = { - mapGetOrElse(map, key) { - val value = default + def mapGetOrElseUpdate[K, V](map: js.Map[K, V], key: K)(default: () => V): V = { + mapGetOrElse(map, key) { () => + val value = default() mapSet(map, key, value) value } diff --git a/javalib/src/main/scala/java/math/BigInteger.scala b/javalib/src/main/scala/java/math/BigInteger.scala index dcae03d0f9..bab8c44301 100644 --- a/javalib/src/main/scala/java/math/BigInteger.scala +++ b/javalib/src/main/scala/java/math/BigInteger.scala @@ -124,12 +124,6 @@ object BigInteger { reference } - @inline - private def checkCriticalArgument(expression: Boolean, errorMessage: => String): Unit = { - if (!expression) - throw new IllegalArgumentException(errorMessage) - } - private[math] def checkRangeBasedOnIntArrayLength(byteLength: Int): Unit = { if (byteLength < 0 || byteLength >= ((Int.MaxValue + 1) >>> 5)) throw new ArithmeticException("BigInteger would overflow supported range") @@ -221,7 +215,10 @@ class BigInteger extends Number with Comparable[BigInteger] { def this(numBits: Int, rnd: Random) = { this() - checkCriticalArgument(numBits >= 0, "numBits must be non-negative") + + if (numBits < 0) + throw new IllegalArgumentException("numBits must be non-negative") + if (numBits == 0) { sign = 0 numberLength = 1 diff --git a/javalib/src/main/scala/java/nio/charset/Charset.scala b/javalib/src/main/scala/java/nio/charset/Charset.scala index 752600eb8c..981bb16c07 100644 --- a/javalib/src/main/scala/java/nio/charset/Charset.scala +++ b/javalib/src/main/scala/java/nio/charset/Charset.scala @@ -80,7 +80,7 @@ object Charset { UTF_8 def forName(charsetName: String): Charset = { - dictGetOrElse(CharsetMap, charsetName.toLowerCase()) { + dictGetOrElse(CharsetMap, charsetName.toLowerCase()) { () => throw new UnsupportedCharsetException(charsetName) } } diff --git a/javalib/src/main/scala/java/nio/charset/CoderResult.scala b/javalib/src/main/scala/java/nio/charset/CoderResult.scala index 4cbb5a9fd0..1a4213f192 100644 --- a/javalib/src/main/scala/java/nio/charset/CoderResult.scala +++ b/javalib/src/main/scala/java/nio/charset/CoderResult.scala @@ -78,7 +78,7 @@ object CoderResult { } private def malformedForLengthImpl(length: Int): CoderResult = { - undefOrFold(uniqueMalformed(length)) { + undefOrFold(uniqueMalformed(length)) { () => val result = new CoderResult(Malformed, length) uniqueMalformed(length) = result result @@ -96,7 +96,7 @@ object CoderResult { } private def unmappableForLengthImpl(length: Int): CoderResult = { - undefOrFold(uniqueUnmappable(length)) { + undefOrFold(uniqueUnmappable(length)) { () => val result = new CoderResult(Unmappable, length) uniqueUnmappable(length) = result result diff --git a/javalib/src/main/scala/java/util/AbstractMap.scala b/javalib/src/main/scala/java/util/AbstractMap.scala index df75f5ba67..00249d5911 100644 --- a/javalib/src/main/scala/java/util/AbstractMap.scala +++ b/javalib/src/main/scala/java/util/AbstractMap.scala @@ -94,7 +94,7 @@ abstract class AbstractMap[K, V] protected () extends java.util.Map[K, V] { entrySet().scalaOps.exists(entry => Objects.equals(key, entry.getKey())) def get(key: Any): V = { - entrySet().scalaOps.findFold(entry => Objects.equals(key, entry.getKey())) { + entrySet().scalaOps.findFold(entry => Objects.equals(key, entry.getKey())) { () => null.asInstanceOf[V] } { entry => entry.getValue() diff --git a/javalib/src/main/scala/java/util/Formatter.scala b/javalib/src/main/scala/java/util/Formatter.scala index 750bebf4c4..c8c3af05ef 100644 --- a/javalib/src/main/scala/java/util/Formatter.scala +++ b/javalib/src/main/scala/java/util/Formatter.scala @@ -51,9 +51,9 @@ final class Formatter private (private[this] var dest: Appendable, def this(a: Appendable, l: Locale) = this(a, new Formatter.LocaleLocaleInfo(l)) @inline - private def trapIOExceptions(body: => Unit): Unit = { + private def trapIOExceptions(body: () => Unit): Unit = { try { - body + body() } catch { case th: IOException => lastIOException = th @@ -83,7 +83,7 @@ final class Formatter private (private[this] var dest: Appendable, @noinline private def sendToDestSlowPath(ss: js.Array[String]): Unit = { - trapIOExceptions { + trapIOExceptions { () => forArrayElems(ss)(dest.append(_)) } } @@ -92,7 +92,7 @@ final class Formatter private (private[this] var dest: Appendable, if (!closed && (dest ne null)) { dest match { case cl: Closeable => - trapIOExceptions { + trapIOExceptions { () => cl.close() } case _ => @@ -106,7 +106,7 @@ final class Formatter private (private[this] var dest: Appendable, if (dest ne null) { dest match { case fl: Flushable => - trapIOExceptions { + trapIOExceptions { () => fl.flush() } case _ => @@ -335,7 +335,7 @@ final class Formatter private (private[this] var dest: Appendable, * Int range. */ private def parsePositiveInt(capture: js.UndefOr[String]): Int = { - undefOrFold(capture) { + undefOrFold(capture) { () => -1 } { s => val x = js.Dynamic.global.parseInt(s, 10).asInstanceOf[Double] @@ -993,7 +993,7 @@ object Formatter { } @inline - private def assert(condition: Boolean, msg: => String): Unit = { + private def assert(condition: Boolean, msg: String): Unit = { if (!condition) throw new AssertionError(msg) } diff --git a/javalib/src/main/scala/java/util/ScalaOps.scala b/javalib/src/main/scala/java/util/ScalaOps.scala index 32023f2ab7..6f5cb57cff 100644 --- a/javalib/src/main/scala/java/util/ScalaOps.scala +++ b/javalib/src/main/scala/java/util/ScalaOps.scala @@ -72,7 +72,7 @@ private[java] object ScalaOps { @inline def indexWhere(f: A => Boolean): Int = __self.iterator().scalaOps.indexWhere(f) - @inline def findFold[B](f: A => Boolean)(default: => B)(g: A => B): B = + @inline def findFold[B](f: A => Boolean)(default: () => B)(g: A => B): B = __self.iterator().scalaOps.findFold(f)(default)(g) @inline def foldLeft[B](z: B)(f: (B, A) => B): B = @@ -127,14 +127,14 @@ private[java] object ScalaOps { // scalastyle:on return } - @inline def findFold[B](f: A => Boolean)(default: => B)(g: A => B): B = { + @inline def findFold[B](f: A => Boolean)(default: () => B)(g: A => B): B = { // scalastyle:off return while (__self.hasNext()) { val x = __self.next() if (f(x)) return g(x) } - default + default() // scalastyle:on return } diff --git a/javalib/src/main/scala/java/util/Timer.scala b/javalib/src/main/scala/java/util/Timer.scala index b377b5a33f..e802db4a31 100644 --- a/javalib/src/main/scala/java/util/Timer.scala +++ b/javalib/src/main/scala/java/util/Timer.scala @@ -47,7 +47,7 @@ class Timer() { private def scheduleOnce(task: TimerTask, delay: Long): Unit = { acquire(task) - task.timeout(delay) { + task.timeout(delay) { () => task.scheduledOnceAndStarted = true task.doRun() } @@ -76,12 +76,12 @@ class Timer() { task.doRun() val endTime = System.nanoTime() val duration = (endTime - startTime) / 1000000 - task.timeout(period - duration) { + task.timeout(period - duration) { () => loop() } } - task.timeout(delay) { + task.timeout(delay) { () => loop() } } @@ -112,13 +112,13 @@ class Timer() { loop(nextScheduledTime) } else { // Re-run after a timeout. - task.timeout(nextScheduledTime - nowTime) { + task.timeout(nextScheduledTime - nowTime) { () => loop(nextScheduledTime) } } } - task.timeout(delay) { + task.timeout(delay) { () => loop(System.nanoTime() / 1000000L + period) } } diff --git a/javalib/src/main/scala/java/util/TimerTask.scala b/javalib/src/main/scala/java/util/TimerTask.scala index 4299157e89..063e9dd47d 100644 --- a/javalib/src/main/scala/java/util/TimerTask.scala +++ b/javalib/src/main/scala/java/util/TimerTask.scala @@ -41,9 +41,9 @@ abstract class TimerTask { def scheduledExecutionTime(): Long = lastScheduled - private[util] def timeout(delay: Long)(body: => Unit): Unit = { + private[util] def timeout(delay: Long)(body: () => Unit): Unit = { if (!canceled) { - handle = setTimeout(() => body, delay.toDouble) + handle = setTimeout(() => body(), delay.toDouble) } } diff --git a/javalib/src/main/scala/java/util/regex/IndicesBuilder.scala b/javalib/src/main/scala/java/util/regex/IndicesBuilder.scala index 85216af945..3d2b480a94 100644 --- a/javalib/src/main/scala/java/util/regex/IndicesBuilder.scala +++ b/javalib/src/main/scala/java/util/regex/IndicesBuilder.scala @@ -182,7 +182,7 @@ private[regex] object IndicesBuilder { final def propagateFromEnd(matchResult: js.RegExp.ExecResult, indices: IndicesArray, end: Int): Unit = { - val start = undefOrFold(matchResult(newGroup))(-1)(matched => end - matched.length) + val start = undefOrFold(matchResult(newGroup))(() => -1)(matched => end - matched.length) propagate(matchResult, indices, start, end) } @@ -194,7 +194,7 @@ private[regex] object IndicesBuilder { final def propagateFromStart(matchResult: js.RegExp.ExecResult, indices: IndicesArray, start: Int): Int = { - val end = undefOrFold(matchResult(newGroup))(-1)(matched => start + matched.length) + val end = undefOrFold(matchResult(newGroup))(() => -1)(matched => start + matched.length) propagate(matchResult, indices, start, end) end } diff --git a/javalib/src/main/scala/java/util/regex/Matcher.scala b/javalib/src/main/scala/java/util/regex/Matcher.scala index 07f94a8076..5acda2c9bb 100644 --- a/javalib/src/main/scala/java/util/regex/Matcher.scala +++ b/javalib/src/main/scala/java/util/regex/Matcher.scala @@ -190,7 +190,7 @@ final class Matcher private[regex] ( pattern().getIndices(ensureLastMatch, lastMatchIsForMatches) private def startInternal(compiledGroup: Int): Int = - undefOrFold(indices(compiledGroup))(-1)(_._1 + regionStart()) + undefOrFold(indices(compiledGroup))(() => -1)(_._1 + regionStart()) def start(group: Int): Int = startInternal(pattern().numberedGroup(group)) @@ -199,7 +199,7 @@ final class Matcher private[regex] ( startInternal(pattern().namedGroup(name)) private def endInternal(compiledGroup: Int): Int = - undefOrFold(indices(compiledGroup))(-1)(_._2 + regionStart()) + undefOrFold(indices(compiledGroup))(() => -1)(_._2 + regionStart()) def end(group: Int): Int = endInternal(pattern().numberedGroup(group)) @@ -278,10 +278,10 @@ object Matcher { */ def start(group: Int): Int = - undefOrFold(indices(pattern.numberedGroup(group)))(-1)(_._1 + regionStart) + undefOrFold(indices(pattern.numberedGroup(group)))(() => -1)(_._1 + regionStart) def end(group: Int): Int = - undefOrFold(indices(pattern.numberedGroup(group)))(-1)(_._2 + regionStart) + undefOrFold(indices(pattern.numberedGroup(group)))(() => -1)(_._2 + regionStart) def group(group: Int): String = undefOrGetOrNull(ensureLastMatch(pattern.numberedGroup(group))) diff --git a/javalib/src/main/scala/java/util/regex/Pattern.scala b/javalib/src/main/scala/java/util/regex/Pattern.scala index 05ee30db58..52dcc3e8f0 100644 --- a/javalib/src/main/scala/java/util/regex/Pattern.scala +++ b/javalib/src/main/scala/java/util/regex/Pattern.scala @@ -133,7 +133,7 @@ final class Pattern private[regex] ( } private[regex] def namedGroup(name: String): Int = { - groupNumberMap(dictGetOrElse(namedGroups, name) { + groupNumberMap(dictGetOrElse(namedGroups, name) { () => throw new IllegalArgumentException(s"No group with name <$name>") }) } diff --git a/javalib/src/main/scala/java/util/regex/PatternCompiler.scala b/javalib/src/main/scala/java/util/regex/PatternCompiler.scala index 93754f24b7..751f2e8f78 100644 --- a/javalib/src/main/scala/java/util/regex/PatternCompiler.scala +++ b/javalib/src/main/scala/java/util/regex/PatternCompiler.scala @@ -1385,7 +1385,7 @@ private final class PatternCompiler(private val pattern: String, private var fla parseError("\\k is not followed by '<' for named capturing group") pIndex += 1 val groupName = parseGroupName() - val groupNumber = dictGetOrElse(namedGroups, groupName) { + val groupNumber = dictGetOrElse(namedGroups, groupName) { () => parseError(s"named capturing group <$groupName> does not exit") } val compiledGroupNumber = groupNumberMap(groupNumber) @@ -1639,7 +1639,7 @@ private final class PatternCompiler(private val pattern: String, private var fla // For anything else, we need built-in support for \p requireES2018Features("Unicode character family") - mapGetOrElse(predefinedPCharacterClasses, property) { + mapGetOrElse(predefinedPCharacterClasses, property) { () => val scriptPrefixLen = if (property.startsWith("Is")) { 2 } else if (property.startsWith("sc=")) { @@ -1674,7 +1674,7 @@ private final class PatternCompiler(private val pattern: String, private var fla val lowercase = scriptName.toLowerCase() - mapGetOrElseUpdate(canonicalizedScriptNameCache, lowercase) { + mapGetOrElseUpdate(canonicalizedScriptNameCache, lowercase) { () => val canonical = lowercase.jsReplace(scriptCanonicalizeRegExp, ((s: String) => s.toUpperCase()): js.Function1[String, String]) From 52f32a58a8160a66013a99ab194fa16d90842b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 18 Nov 2024 18:15:48 +0100 Subject: [PATCH 039/121] Directly use ju.function.* instead of Scala functions in the javalib. This way, we can simplify the `JavalibIRCleaner`. It does not need to deal with the function rewrites anymore. Code readability is only affected at the definition sites of the higher-order methods. The use sites leverage SAM conversion. Some use sites need a bit more help for the type inference, because Java function types are invariant in all their type parameters. Since virtually all the higher-order functions are always inlined anyway, this commit does not make a difference to the generated code. The only exceptions were `ju.TimerTask.timeout` and `jl.System.NanoTime.getPrecisionTime`. For those, we explicitly declare that we want a `js.Function0`. This won't make performance worse on Wasm because the relevant code uses JS interop anyway. The changes in this commit will also make the transition to `TypedClosure`s easier. Indeed, it would be tricky for the `JavalibIRCleaner` to deal with Scala functions without the possibility to use JS `Closure`s and without the full liberty of sending bare `TypedClosure`s across methods. --- .../src/main/scala/java/io/PrintStream.scala | 6 +- .../src/main/scala/java/io/PrintWriter.scala | 6 +- .../src/main/scala/java/lang/Integer.scala | 3 +- javalib/src/main/scala/java/lang/System.scala | 15 ++--- .../src/main/scala/java/lang/Throwables.scala | 18 +++--- javalib/src/main/scala/java/lang/Utils.scala | 34 ++++++----- .../src/main/scala/java/lang/_String.scala | 5 +- .../src/main/scala/java/math/BigInteger.scala | 5 +- .../src/main/scala/java/nio/GenBuffer.scala | 3 +- .../scala/java/util/AbstractCollection.scala | 6 +- .../main/scala/java/util/Collections.scala | 13 ++-- javalib/src/main/scala/java/util/Date.scala | 5 +- .../src/main/scala/java/util/Formatter.scala | 4 +- .../src/main/scala/java/util/Properties.scala | 5 +- .../main/scala/java/util/RedBlackTree.scala | 4 +- .../src/main/scala/java/util/ScalaOps.scala | 60 ++++++++++--------- .../src/main/scala/java/util/TimerTask.scala | 4 +- project/JavalibIRCleaner.scala | 58 +----------------- 18 files changed, 108 insertions(+), 146 deletions(-) diff --git a/javalib/src/main/scala/java/io/PrintStream.scala b/javalib/src/main/scala/java/io/PrintStream.scala index 9b61270d43..7868abd03c 100644 --- a/javalib/src/main/scala/java/io/PrintStream.scala +++ b/javalib/src/main/scala/java/io/PrintStream.scala @@ -214,15 +214,15 @@ class PrintStream private (_out: OutputStream, autoFlush: Boolean, this } - @inline private[this] def trapIOExceptions(body: () => Unit): Unit = { + @inline private[this] def trapIOExceptions(body: Runnable): Unit = { try { - body() + body.run() } catch { case _: IOException => setError() } } - @inline private[this] def ensureOpenAndTrapIOExceptions(body: () => Unit): Unit = { + @inline private[this] def ensureOpenAndTrapIOExceptions(body: Runnable): Unit = { if (closed) setError() else trapIOExceptions(body) } diff --git a/javalib/src/main/scala/java/io/PrintWriter.scala b/javalib/src/main/scala/java/io/PrintWriter.scala index e03ddb7d2c..57833e56f5 100644 --- a/javalib/src/main/scala/java/io/PrintWriter.scala +++ b/javalib/src/main/scala/java/io/PrintWriter.scala @@ -147,15 +147,15 @@ class PrintWriter(protected[io] var out: Writer, this } - @inline private[this] def trapIOExceptions(body: () => Unit): Unit = { + @inline private[this] def trapIOExceptions(body: Runnable): Unit = { try { - body() + body.run() } catch { case _: IOException => setError() } } - @inline private[this] def ensureOpenAndTrapIOExceptions(body: () => Unit): Unit = { + @inline private[this] def ensureOpenAndTrapIOExceptions(body: Runnable): Unit = { if (closed) setError() else trapIOExceptions(body) } diff --git a/javalib/src/main/scala/java/lang/Integer.scala b/javalib/src/main/scala/java/lang/Integer.scala index 8ef617e7d0..a4c2694365 100644 --- a/javalib/src/main/scala/java/lang/Integer.scala +++ b/javalib/src/main/scala/java/lang/Integer.scala @@ -13,6 +13,7 @@ package java.lang import java.lang.constant.{Constable, ConstantDesc} +import java.util.function._ import scala.scalajs.js import scala.scalajs.LinkingInfo @@ -132,7 +133,7 @@ object Integer { decodeGeneric(nm, valueOf(_, _)) @inline private[lang] def decodeGeneric[A](nm: String, - parse: (String, Int) => A): A = { + parse: BiFunction[String, Int, A]): A = { val len = nm.length() var i = 0 diff --git a/javalib/src/main/scala/java/lang/System.scala b/javalib/src/main/scala/java/lang/System.scala index 4938d237ca..976ea7ff15 100644 --- a/javalib/src/main/scala/java/lang/System.scala +++ b/javalib/src/main/scala/java/lang/System.scala @@ -19,6 +19,7 @@ import scala.scalajs.js.Dynamic.global import scala.scalajs.LinkingInfo import java.{util => ju} +import java.util.function._ object System { /* System contains a bag of unrelated features. If we naively implement @@ -70,7 +71,7 @@ object System { (new js.Date).getTime().toLong private object NanoTime { - val getHighPrecisionTime: () => scala.Double = { + val getHighPrecisionTime: js.Function0[scala.Double] = { import js.DynamicImplicits.truthValue if (js.typeOf(global.performance) != "undefined") { @@ -102,27 +103,27 @@ object System { def mismatch(): Nothing = throw new ArrayStoreException("Incompatible array types") - def impl(srcLen: Int, destLen: Int, f: (Int, Int) => Any): Unit = { + def impl(srcLen: Int, destLen: Int, f: BiConsumer[Int, Int]): Unit = { /* Perform dummy swaps to trigger an ArrayIndexOutOfBoundsException or * UBE if the positions / lengths are bad. */ if (srcPos < 0 || destPos < 0) - f(destPos, srcPos) + f.accept(destPos, srcPos) if (length < 0) - f(length, length) + f.accept(length, length) if (srcPos > srcLen - length || destPos > destLen - length) - f(destPos + length, srcPos + length) + f.accept(destPos + length, srcPos + length) if ((src ne dest) || destPos < srcPos || srcPos + length < destPos) { var i = 0 while (i < length) { - f(i + destPos, i + srcPos) + f.accept(i + destPos, i + srcPos) i += 1 } } else { var i = length - 1 while (i >= 0) { - f(i + destPos, i + srcPos) + f.accept(i + destPos, i + srcPos) i -= 1 } } diff --git a/javalib/src/main/scala/java/lang/Throwables.scala b/javalib/src/main/scala/java/lang/Throwables.scala index 38b17384b9..af7701641f 100644 --- a/javalib/src/main/scala/java/lang/Throwables.scala +++ b/javalib/src/main/scala/java/lang/Throwables.scala @@ -12,6 +12,8 @@ package java.lang +import java.util.function._ + import scala.scalajs.js import scala.scalajs.js.annotation.JSExport @@ -80,21 +82,21 @@ class Throwable protected (s: String, private var e: Throwable, def printStackTrace(s: java.io.PrintWriter): Unit = printStackTraceImpl(s.println(_)) - private[this] def printStackTraceImpl(sprintln: String => Unit): Unit = { + private[this] def printStackTraceImpl(sprintln: Consumer[String]): Unit = { getStackTrace() // will init it if still null // Message - sprintln(toString) + sprintln.accept(toString) // Trace if (stackTrace.length != 0) { var i = 0 while (i < stackTrace.length) { - sprintln(s" at ${stackTrace(i)}") + sprintln.accept(s" at ${stackTrace(i)}") i += 1 } } else { - sprintln(" ") + sprintln.accept(" ") } // Causes @@ -107,7 +109,7 @@ class Throwable protected (s: String, private var e: Throwable, val thisLength = thisTrace.length val parentLength = parentTrace.length - sprintln(s"Caused by: $wCause") + sprintln.accept(s"Caused by: $wCause") if (thisLength != 0) { /* Count how many frames are shared between this stack trace and the @@ -129,14 +131,14 @@ class Throwable protected (s: String, private var e: Throwable, val lengthToPrint = thisLength - sameFrameCount var i = 0 while (i < lengthToPrint) { - sprintln(s" at ${thisTrace(i)}") + sprintln.accept(s" at ${thisTrace(i)}") i += 1 } if (sameFrameCount > 0) - sprintln(s" ... $sameFrameCount more") + sprintln.accept(s" ... $sameFrameCount more") } else { - sprintln(" ") + sprintln.accept(" ") } } } diff --git a/javalib/src/main/scala/java/lang/Utils.scala b/javalib/src/main/scala/java/lang/Utils.scala index 6e0a535831..94d323e026 100644 --- a/javalib/src/main/scala/java/lang/Utils.scala +++ b/javalib/src/main/scala/java/lang/Utils.scala @@ -14,6 +14,8 @@ package java.lang import scala.language.implicitConversions +import java.util.function._ + import scala.scalajs.js import scala.scalajs.js.annotation._ @@ -34,9 +36,9 @@ private[java] object Utils { x.asInstanceOf[A] @inline - def undefOrGetOrElse[A](x: js.UndefOr[A])(default: () => A): A = + def undefOrGetOrElse[A](x: js.UndefOr[A])(default: Supplier[A]): A = if (undefOrIsDefined(x)) undefOrForceGet(x) - else default() + else default.get() @inline def undefOrGetOrNull[A >: Null](x: js.UndefOr[A]): A = @@ -44,15 +46,15 @@ private[java] object Utils { else null @inline - def undefOrForeach[A](x: js.UndefOr[A])(f: A => Any): Unit = { + def undefOrForeach[A](x: js.UndefOr[A])(f: Consumer[A]): Unit = { if (undefOrIsDefined(x)) - f(undefOrForceGet(x)) + f.accept(undefOrForceGet(x)) } @inline - def undefOrFold[A, B](x: js.UndefOr[A])(default: () => B)(f: A => B): B = + def undefOrFold[A, B](x: js.UndefOr[A])(default: Supplier[B])(f: Function[A, B]): B = if (undefOrIsDefined(x)) f(undefOrForceGet(x)) - else default() + else default.get() private object Cache { val safeHasOwnProperty = @@ -84,11 +86,11 @@ private[java] object Utils { @inline def dictGetOrElse[A](dict: js.Dictionary[_ <: A], key: String)( - default: () => A): A = { + default: Supplier[A]): A = { if (dictContains(dict, key)) dictRawApply(dict, key) else - default() + default.get() } def dictGetOrElseAndRemove[A](dict: js.Dictionary[_ <: A], key: String, @@ -139,27 +141,27 @@ private[java] object Utils { map.asInstanceOf[MapRaw[K, V]].set(key, value) @inline - def mapGetOrElse[K, V](map: js.Map[K, V], key: K)(default: () => V): V = { + def mapGetOrElse[K, V](map: js.Map[K, V], key: K)(default: Supplier[V]): V = { val value = map.asInstanceOf[MapRaw[K, V]].getOrUndefined(key) if (!isUndefined(value) || mapHas(map, key)) value.asInstanceOf[V] - else default() + else default.get() } @inline - def mapGetOrElseUpdate[K, V](map: js.Map[K, V], key: K)(default: () => V): V = { + def mapGetOrElseUpdate[K, V](map: js.Map[K, V], key: K)(default: Supplier[V]): V = { mapGetOrElse(map, key) { () => - val value = default() + val value = default.get() mapSet(map, key, value) value } } @inline - def forArrayElems[A](array: js.Array[A])(f: A => Any): Unit = { + def forArrayElems[A](array: js.Array[A])(f: Consumer[A]): Unit = { val len = array.length var i = 0 while (i != len) { - f(array(i)) + f.accept(array(i)) i += 1 } } @@ -173,12 +175,12 @@ private[java] object Utils { array.splice(index, 1)(0) @inline - def arrayExists[A](array: js.Array[A])(f: A => scala.Boolean): scala.Boolean = { + def arrayExists[A](array: js.Array[A])(f: Predicate[A]): scala.Boolean = { // scalastyle:off return val len = array.length var i = 0 while (i != len) { - if (f(array(i))) + if (f.test(array(i))) return true i += 1 } diff --git a/javalib/src/main/scala/java/lang/_String.scala b/javalib/src/main/scala/java/lang/_String.scala index d89ada96dc..ea29540e37 100644 --- a/javalib/src/main/scala/java/lang/_String.scala +++ b/javalib/src/main/scala/java/lang/_String.scala @@ -26,6 +26,7 @@ import java.lang.constant.{Constable, ConstantDesc} import java.nio.ByteBuffer import java.nio.charset.Charset import java.util.Locale +import java.util.function._ import java.util.regex._ /* This is the implementation of java.lang.String, which is a hijacked class. @@ -643,7 +644,7 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { * the character at the given index is not special. */ @inline - private def replaceCharsAtIndex(replacementAtIndex: Int => String): String = { + private def replaceCharsAtIndex(replacementAtIndex: IntFunction[String]): String = { var prep = "" val len = this.length() var i = 0 @@ -776,7 +777,7 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { def indent(n: Int): String = { - def forEachLn(f: String => String): String = { + def forEachLn(f: Function[String, String]): String = { var out = "" var i = 0 val xs = splitLines() diff --git a/javalib/src/main/scala/java/math/BigInteger.scala b/javalib/src/main/scala/java/math/BigInteger.scala index bab8c44301..9864183573 100644 --- a/javalib/src/main/scala/java/math/BigInteger.scala +++ b/javalib/src/main/scala/java/math/BigInteger.scala @@ -45,6 +45,7 @@ import scala.annotation.tailrec import java.util.Random import java.util.ScalaOps._ +import java.util.function._ object BigInteger { @@ -736,9 +737,9 @@ class BigInteger extends Number with Comparable[BigInteger] { @inline @tailrec - def loopBytes(tempDigit: Int => Unit): Unit = { + def loopBytes(tempDigit: IntConsumer): Unit = { if (bytesLen > firstByteNumber) { - tempDigit(digitIndex) + tempDigit.accept(digitIndex) loopBytes(tempDigit) } } diff --git a/javalib/src/main/scala/java/nio/GenBuffer.scala b/javalib/src/main/scala/java/nio/GenBuffer.scala index 9aa57e92d8..2fc5f52d3f 100644 --- a/javalib/src/main/scala/java/nio/GenBuffer.scala +++ b/javalib/src/main/scala/java/nio/GenBuffer.scala @@ -12,6 +12,7 @@ package java.nio +import java.util.function._ import java.util.internal.GenericArrayOps._ private[nio] object GenBuffer { @@ -137,7 +138,7 @@ private[nio] final class GenBuffer[B <: Buffer] private (val self: B) @inline def generic_compareTo(that: BufferType)( - compare: (ElementType, ElementType) => Int): Int = { + compare: BiFunction[ElementType, ElementType, Int]): Int = { // scalastyle:off return if (self eq that) { 0 diff --git a/javalib/src/main/scala/java/util/AbstractCollection.scala b/javalib/src/main/scala/java/util/AbstractCollection.scala index 8385d981da..34920f1bdb 100644 --- a/javalib/src/main/scala/java/util/AbstractCollection.scala +++ b/javalib/src/main/scala/java/util/AbstractCollection.scala @@ -18,6 +18,8 @@ import ScalaOps._ import java.lang.{reflect => jlr} +import java.util.function.Predicate + abstract class AbstractCollection[E] protected () extends Collection[E] { def iterator(): Iterator[E] def size(): Int @@ -78,11 +80,11 @@ abstract class AbstractCollection[E] protected () extends Collection[E] { def clear(): Unit = removeWhere(_ => true) - private def removeWhere(p: Any => Boolean): Boolean = { + private def removeWhere(p: Predicate[Any]): Boolean = { val iter = iterator() var changed = false while (iter.hasNext()) { - if (p(iter.next())) { + if (p.test(iter.next())) { iter.remove() changed = true } diff --git a/javalib/src/main/scala/java/util/Collections.scala b/javalib/src/main/scala/java/util/Collections.scala index 687ba74b5e..8f9a630de5 100644 --- a/javalib/src/main/scala/java/util/Collections.scala +++ b/javalib/src/main/scala/java/util/Collections.scala @@ -14,6 +14,7 @@ package java.util import java.{lang => jl} import java.io.Serializable +import java.util.function._ import scala.language.implicitConversions @@ -79,16 +80,16 @@ object Collections { binarySearchImpl(list, (elem: T) => c.compare(elem, key)) @inline - private def binarySearchImpl[E](list: List[E], compareToKey: E => Int): Int = { + private def binarySearchImpl[E](list: List[_ <: E], compareToKey: ToIntFunction[E]): Int = { def notFound(insertionPoint: Int): Int = { -insertionPoint - 1 } @tailrec - def binarySearch(lo: Int, hi: Int, get: Int => E): Int = { + def binarySearch(lo: Int, hi: Int, get: IntFunction[E]): Int = { if (lo < hi) { val mid = lo + (hi - lo) / 2 - val cmp = compareToKey(get(mid)) + val cmp = compareToKey.applyAsInt(get(mid)) if (cmp == 0) mid else if (cmp > 0) binarySearch(lo, mid, get) else binarySearch(mid + 1, hi, get) @@ -102,7 +103,7 @@ object Collections { binarySearch(0, list.size(), list.get(_)) case _ => - def getFrom(iter: ListIterator[E])(index: Int): E = { + def getFrom(iter: ListIterator[_ <: E])(index: Int): E = { val shift = index - iter.nextIndex() if (shift > 0) (0 until shift).foreach(_ => iter.next()) @@ -263,14 +264,14 @@ object Collections { min(coll, Comparator.naturalOrder[T]) def min[T](coll: Collection[_ <: T], comp: Comparator[_ >: T]): T = - coll.scalaOps.reduceLeft((a, b) => if (comp.compare(a, b) <= 0) a else b) + coll.scalaOps.reduceLeft[T]((a, b) => if (comp.compare(a, b) <= 0) a else b) // Differs from original type definition, original: [T <: jl.Comparable[_ >: T]], returning T def max[T <: AnyRef with jl.Comparable[T]](coll: Collection[_ <: T]): AnyRef = max(coll, Comparator.naturalOrder[T]) def max[T](coll: Collection[_ <: T], comp: Comparator[_ >: T]): T = - coll.scalaOps.reduceLeft((a, b) => if (comp.compare(a, b) >= 0) a else b) + coll.scalaOps.reduceLeft[T]((a, b) => if (comp.compare(a, b) >= 0) a else b) def rotate(list: List[_], distance: Int): Unit = rotateImpl(list, distance) diff --git a/javalib/src/main/scala/java/util/Date.scala b/javalib/src/main/scala/java/util/Date.scala index 170d5829d2..68fe483627 100644 --- a/javalib/src/main/scala/java/util/Date.scala +++ b/javalib/src/main/scala/java/util/Date.scala @@ -14,6 +14,7 @@ package java.util import java.lang.Cloneable import java.time.Instant +import java.util.function._ import scalajs.js @@ -69,9 +70,9 @@ class Date(private var millis: Long) extends Object } @inline - private def mutDate(mutator: js.Date => Unit): Unit = { + private def mutDate(mutator: Consumer[js.Date]): Unit = { val date = asDate() - mutator(date) + mutator.accept(date) millis = safeGetTime(date) } diff --git a/javalib/src/main/scala/java/util/Formatter.scala b/javalib/src/main/scala/java/util/Formatter.scala index c8c3af05ef..909fab1929 100644 --- a/javalib/src/main/scala/java/util/Formatter.scala +++ b/javalib/src/main/scala/java/util/Formatter.scala @@ -51,9 +51,9 @@ final class Formatter private (private[this] var dest: Appendable, def this(a: Appendable, l: Locale) = this(a, new Formatter.LocaleLocaleInfo(l)) @inline - private def trapIOExceptions(body: () => Unit): Unit = { + private def trapIOExceptions(body: Runnable): Unit = { try { - body() + body.run() } catch { case th: IOException => lastIOException = th diff --git a/javalib/src/main/scala/java/util/Properties.scala b/javalib/src/main/scala/java/util/Properties.scala index d75d89e601..d70e639fa4 100644 --- a/javalib/src/main/scala/java/util/Properties.scala +++ b/javalib/src/main/scala/java/util/Properties.scala @@ -19,6 +19,7 @@ import java.{lang => jl} import java.{util => ju} import java.io._ import java.nio.charset.StandardCharsets +import java.util.function._ import scala.scalajs.js @@ -114,8 +115,8 @@ class Properties(protected val defaults: Properties) } @inline @tailrec - private final def foreachAncestor(f: Properties => Unit): Unit = { - f(this) + private final def foreachAncestor(f: Consumer[Properties]): Unit = { + f.accept(this) if (defaults ne null) defaults.foreachAncestor(f) } diff --git a/javalib/src/main/scala/java/util/RedBlackTree.scala b/javalib/src/main/scala/java/util/RedBlackTree.scala index 3e91b22e66..a1554e264d 100644 --- a/javalib/src/main/scala/java/util/RedBlackTree.scala +++ b/javalib/src/main/scala/java/util/RedBlackTree.scala @@ -14,6 +14,8 @@ package java.util import scala.annotation.tailrec +import java.util.function._ + import scala.scalajs.js /** The red-black tree implementation used by `TreeSet`s and `TreeMap`s. @@ -609,7 +611,7 @@ private[util] object RedBlackTree { /** Returns `null.asInstanceOf[C]` if `node eq null`, otherwise `f(node)`. */ @inline - private def nullableNodeFlatMap[A, B, C](node: Node[A, B])(f: Node[A, B] => C): C = + private def nullableNodeFlatMap[A, B, C](node: Node[A, B])(f: Function[Node[A, B], C]): C = if (node eq null) null.asInstanceOf[C] else f(node) diff --git a/javalib/src/main/scala/java/util/ScalaOps.scala b/javalib/src/main/scala/java/util/ScalaOps.scala index 6f5cb57cff..a00c7d379f 100644 --- a/javalib/src/main/scala/java/util/ScalaOps.scala +++ b/javalib/src/main/scala/java/util/ScalaOps.scala @@ -12,6 +12,8 @@ package java.util +import java.util.function._ + /** Make some Scala collection APIs available on Java collections. */ private[java] object ScalaOps { @@ -26,10 +28,10 @@ private[java] object ScalaOps { @inline final class SimpleRange(start: Int, end: Int) { @inline - def foreach[U](f: Int => U): Unit = { + def foreach[U](f: IntConsumer): Unit = { var i = start while (i < end) { - f(i) + f.accept(i) i += 1 } } @@ -38,10 +40,10 @@ private[java] object ScalaOps { @inline final class SimpleInclusiveRange(start: Int, end: Int) { @inline - def foreach[U](f: Int => U): Unit = { + def foreach[U](f: IntConsumer): Unit = { var i = start while (i <= end) { - f(i) + f.accept(i) i += 1 } } @@ -57,28 +59,28 @@ private[java] object ScalaOps { val __self: java.lang.Iterable[A]) extends AnyVal { - @inline def foreach[U](f: A => U): Unit = + @inline def foreach(f: Consumer[A]): Unit = __self.iterator().scalaOps.foreach(f) - @inline def count(f: A => Boolean): Int = + @inline def count(f: Predicate[A]): Int = __self.iterator().scalaOps.count(f) - @inline def exists(f: A => Boolean): Boolean = + @inline def exists(f: Predicate[A]): Boolean = __self.iterator().scalaOps.exists(f) - @inline def forall(f: A => Boolean): Boolean = + @inline def forall(f: Predicate[A]): Boolean = __self.iterator().scalaOps.forall(f) - @inline def indexWhere(f: A => Boolean): Int = + @inline def indexWhere(f: Predicate[A]): Int = __self.iterator().scalaOps.indexWhere(f) - @inline def findFold[B](f: A => Boolean)(default: () => B)(g: A => B): B = + @inline def findFold[B](f: Predicate[A])(default: Supplier[B])(g: Function[A, B]): B = __self.iterator().scalaOps.findFold(f)(default)(g) - @inline def foldLeft[B](z: B)(f: (B, A) => B): B = + @inline def foldLeft[B](z: B)(f: BiFunction[B, A, B]): B = __self.iterator().scalaOps.foldLeft(z)(f) - @inline def reduceLeft[B >: A](f: (B, A) => B): B = + @inline def reduceLeft[B >: A](f: BiFunction[B, A, B]): B = __self.iterator().scalaOps.reduceLeft(f) @inline def mkString(start: String, sep: String, end: String): String = @@ -94,32 +96,32 @@ private[java] object ScalaOps { class JavaIteratorOps[A] private[ScalaOps] (val __self: Iterator[A]) extends AnyVal { - @inline def foreach[U](f: A => U): Unit = { + @inline def foreach(f: Consumer[A]): Unit = { while (__self.hasNext()) - f(__self.next()) + f.accept(__self.next()) } - @inline def count(f: A => Boolean): Int = - foldLeft(0)((prev, x) => if (f(x)) prev + 1 else prev) + @inline def count(f: Predicate[A]): Int = + foldLeft(0)((prev, x) => if (f.test(x)) prev + 1 else prev) - @inline def exists(f: A => Boolean): Boolean = { + @inline def exists(f: Predicate[A]): Boolean = { // scalastyle:off return while (__self.hasNext()) { - if (f(__self.next())) + if (f.test(__self.next())) return true } false // scalastyle:on return } - @inline def forall(f: A => Boolean): Boolean = - !exists(x => !f(x)) + @inline def forall(f: Predicate[A]): Boolean = + !exists(x => !f.test(x)) - @inline def indexWhere(f: A => Boolean): Int = { + @inline def indexWhere(f: Predicate[A]): Int = { // scalastyle:off return var i = 0 while (__self.hasNext()) { - if (f(__self.next())) + if (f.test(__self.next())) return i i += 1 } @@ -127,25 +129,25 @@ private[java] object ScalaOps { // scalastyle:on return } - @inline def findFold[B](f: A => Boolean)(default: () => B)(g: A => B): B = { + @inline def findFold[B](f: Predicate[A])(default: Supplier[B])(g: Function[A, B]): B = { // scalastyle:off return while (__self.hasNext()) { val x = __self.next() - if (f(x)) + if (f.test(x)) return g(x) } - default() + default.get() // scalastyle:on return } - @inline def foldLeft[B](z: B)(f: (B, A) => B): B = { + @inline def foldLeft[B](z: B)(f: BiFunction[B, A, B]): B = { var result: B = z while (__self.hasNext()) result = f(result, __self.next()) result } - @inline def reduceLeft[B >: A](f: (B, A) => B): B = { + @inline def reduceLeft[B >: A](f: BiFunction[B, A, B]): B = { if (!__self.hasNext()) throw new NoSuchElementException("collection is empty") foldLeft[B](__self.next())(f) @@ -174,9 +176,9 @@ private[java] object ScalaOps { class JavaEnumerationOps[A] private[ScalaOps] (val __self: Enumeration[A]) extends AnyVal { - @inline def foreach[U](f: A => U): Unit = { + @inline def foreach(f: Consumer[A]): Unit = { while (__self.hasMoreElements()) - f(__self.nextElement()) + f.accept(__self.nextElement()) } } diff --git a/javalib/src/main/scala/java/util/TimerTask.scala b/javalib/src/main/scala/java/util/TimerTask.scala index 063e9dd47d..439add8f55 100644 --- a/javalib/src/main/scala/java/util/TimerTask.scala +++ b/javalib/src/main/scala/java/util/TimerTask.scala @@ -41,9 +41,9 @@ abstract class TimerTask { def scheduledExecutionTime(): Long = lastScheduled - private[util] def timeout(delay: Long)(body: () => Unit): Unit = { + private[util] def timeout(delay: Long)(body: js.Function0[Any]): Unit = { if (!canceled) { - handle = setTimeout(() => body(), delay.toDouble) + handle = setTimeout(body, delay.toDouble) } } diff --git a/project/JavalibIRCleaner.scala b/project/JavalibIRCleaner.scala index 035732d7c4..61ed4ea743 100644 --- a/project/JavalibIRCleaner.scala +++ b/project/JavalibIRCleaner.scala @@ -33,8 +33,6 @@ import sbt.{Logger, MessageOnlyException} * the JS load spec of the mentioned class ref. * - Replace calls to "intrinsic" methods of the Scala.js library by their * meaning at call site. - * - Erase Scala FunctionN's to JavaScript closures (hence, of type `any`), - * including `new AnonFunctionN` and `someFunctionN.apply` calls. * * Afterwards, we check that the IR does not contain any reference to classes * under the `scala.*` package. @@ -321,15 +319,6 @@ final class JavalibIRCleaner(baseDirectoryURI: URI) { implicit val pos = tree.pos tree match { - // new AnonFunctionN(closure) --> closure - case New(AnonFunctionNClass(n), _, List(closure)) => - closure - - // someFunctionN.apply(args) --> someFunctionN(args) - case Apply(ApplyFlags.empty, fun, MethodIdent(FunctionApplyMethodName(n)), args) - if isFunctionNType(n, fun.tpe) => - JSFunctionApply(fun, args) - // <= 2.12 : toJSVarArgs(jsArrayOps(jsArray).toSeq) -> jsArray case IntrinsicCall(ScalaJSRuntimeMod, `toJSVarArgsReadOnlyMethodName`, List(Apply( @@ -637,8 +626,6 @@ final class JavalibIRCleaner(baseDirectoryURI: URI) { } object JavalibIRCleaner { - private final val MaxFunctionArity = 4 - // Within js.JavaScriptException, which is part of the linker private lib, we can refer to itself private val JavaScriptExceptionClass = ClassName("scala.scalajs.js.JavaScriptException") @@ -668,12 +655,6 @@ object JavalibIRCleaner { private val UnionTypeEvidence = ClassName("scala.scalajs.js.$bar$Evidence") private val LinkingInfoClass = ClassName("scala.scalajs.LinkingInfo$") - private val FunctionNClasses: IndexedSeq[ClassName] = - (0 to MaxFunctionArity).map(n => ClassName(s"scala.Function$n")) - - private val AnonFunctionNClasses: IndexedSeq[ClassName] = - (0 to MaxFunctionArity).map(n => ClassName(s"scala.scalajs.runtime.AnonFunction$n")) - private val enableJSNumberOpsDoubleMethodName = MethodName("enableJSNumberOps", List(DoubleRef), ClassRef(JSNumberOps)) private val enableJSNumberOpsIntMethodName = @@ -716,44 +697,7 @@ object JavalibIRCleaner { private val isWebAssemblyMethodName = MethodName("isWebAssembly", Nil, BooleanRef) private val linkerVersionMethodName = MethodName("linkerVersion", Nil, ClassRef(BoxedStringClass)) - private val functionApplyMethodNames: IndexedSeq[MethodName] = { - (0 to MaxFunctionArity).map { n => - MethodName("apply", (1 to n).toList.map(_ => ClassRef(ObjectClass)), ClassRef(ObjectClass)) - } - } - - private object AnonFunctionNClass { - private val AnonFunctionNClassToN: Map[ClassName, Int] = - AnonFunctionNClasses.zipWithIndex.toMap - - def apply(n: Int): ClassName = AnonFunctionNClasses(n) - - def unapply(cls: ClassName): Option[Int] = AnonFunctionNClassToN.get(cls) - } - - private object FunctionApplyMethodName { - private val FunctionApplyMethodNameToN: Map[MethodName, Int] = - functionApplyMethodNames.zipWithIndex.toMap - - def apply(n: Int): MethodName = functionApplyMethodNames(n) - - def unapply(name: MethodName): Option[Int] = FunctionApplyMethodNameToN.get(name) - } - - private def isFunctionNType(n: Int, tpe: Type): Boolean = tpe match { - case ClassType(cls, _) => - cls == FunctionNClasses(n) || cls == AnonFunctionNClasses(n) - case _ => - false - } - private val ClassNameSubstitutions: Map[ClassName, ClassName] = { - val functionTypePairs = for { - funClass <- FunctionNClasses ++ AnonFunctionNClasses - } yield { - funClass -> ObjectClass - } - val refBaseNames = List("Boolean", "Char", "Byte", "Short", "Int", "Long", "Float", "Double", "Object") val refPairs = for { @@ -777,7 +721,7 @@ object JavalibIRCleaner { ClassName("scala.MatchError") -> ClassName("java.lang.AssertionError"), ) - val allPairs = functionTypePairs ++ refPairs ++ tuplePairs ++ otherPairs + val allPairs = refPairs ++ tuplePairs ++ otherPairs allPairs.toMap } } From 0d16b42e54d823dbd79a06fb6247730a01206831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 25 Nov 2024 23:40:28 +0100 Subject: [PATCH 040/121] Codegen: Inline the target of `Function` nodes in their `js.Closure`s. The body of `Function` nodes always has a simple shape that calls a helper method. We previously generated that call in the body of the `js.Closure`, and marked the target method `@inline` so that the optimizer would always inline it. Instead, we now directly "inline" it from the codegen, by generating the `js.MethodDef` right inside the `js.Closure` scope. As is, this does not change the generated code. However, it may speed up (cold) linker runs, since it will have less work to do. Notably, it performs two fewer knowledge queries to find and inline the target method. It also reduces the total amount of methods to manipulate in the incremental analysis. More importantly, this will be necessary later if we want to add support for `async/await` or `function*/yield`. Indeed, for those, we will need `await`/`yield` expressions to be lexically scoped in the body of their enclosing closure. That won't work if they are in the body of a separate helper method. --- .../org/scalajs/nscplugin/GenJSCode.scala | 263 +++++++++++------- .../nscplugin/test/JSNewTargetTest.scala | 20 +- 2 files changed, 165 insertions(+), 118 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 9839b71954..267b85d20f 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -159,6 +159,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val currentClassSym = new ScopedVar[Symbol] private val fieldsMutatedInCurrentClass = new ScopedVar[mutable.Set[Name]] private val generatedSAMWrapperCount = new ScopedVar[VarBox[Int]] + private val delambdafyTargetDefDefs = new ScopedVar[mutable.Map[Symbol, DefDef]] def currentThisTypeNullable: jstpe.Type = encodeClassType(currentClassSym) @@ -185,10 +186,12 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) private val mutableLocalVars = new ScopedVar[mutable.Set[Symbol]] private val mutatedLocalVars = new ScopedVar[mutable.Set[Symbol]] - private def withPerMethodBodyState[A](methodSym: Symbol)(body: => A): A = { + private def withPerMethodBodyState[A](methodSym: Symbol, + initThisLocalVarIdent: Option[js.LocalIdent] = None)(body: => A): A = { + withScopedVars( currentMethodSym := methodSym, - thisLocalVarIdent := None, + thisLocalVarIdent := initThisLocalVarIdent, enclosingLabelDefInfos := Map.empty, isModuleInitialized := new VarBox(false), undefinedDefaultParams := mutable.Set.empty, @@ -236,6 +239,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) currentClassSym := clsSym, fieldsMutatedInCurrentClass := mutable.Set.empty, generatedSAMWrapperCount := new VarBox(0), + delambdafyTargetDefDefs := mutable.Map.empty, currentMethodSym := null, thisLocalVarIdent := null, enclosingLabelDefInfos := null, @@ -481,7 +485,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) withScopedVars( currentClassSym := sym, fieldsMutatedInCurrentClass := mutable.Set.empty, - generatedSAMWrapperCount := new VarBox(0) + generatedSAMWrapperCount := new VarBox(0), + delambdafyTargetDefDefs := mutable.Map.empty ) { val tree = if (isJSType(sym)) { if (!sym.isTraitOrInterface && isNonNativeJSClass(sym) && @@ -590,6 +595,34 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } + private def collectDefDefs(impl: Template): List[DefDef] = { + val b = List.newBuilder[DefDef] + + for (stat <- impl.body) { + stat match { + case stat: DefDef => + if (stat.symbol.isDelambdafyTarget) + delambdafyTargetDefDefs += stat.symbol -> stat + else + b += stat + + case EmptyTree | _:ValDef => + () + + case _ => + abort(s"Unexpected tree in template: $stat at ${stat.pos}") + } + } + + b.result() + } + + private def consumeDelambdafyTarget(sym: Symbol): DefDef = { + delambdafyTargetDefDefs.remove(sym).getOrElse { + abort(s"Cannot resolve delambdafy target $sym at ${sym.pos}") + } + } + // Generate a class -------------------------------------------------------- /** Gen the IR ClassDef for a class definition (maybe a module class). @@ -640,26 +673,13 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val methodsBuilder = List.newBuilder[js.MethodDef] val jsNativeMembersBuilder = List.newBuilder[js.JSNativeMemberDef] - def gen(tree: Tree): Unit = { - tree match { - case EmptyTree => () - case Template(_, _, body) => body foreach gen - - case ValDef(mods, name, tpt, rhs) => - () // fields are added via genClassFields() - - case dd: DefDef => - if (dd.symbol.hasAnnotation(JSNativeAnnotation)) - jsNativeMembersBuilder += genJSNativeMemberDef(dd) - else - methodsBuilder ++= genMethod(dd) - - case _ => abort("Illegal tree in gen of genClass(): " + tree) - } + for (dd <- collectDefDefs(impl)) { + if (dd.symbol.hasAnnotation(JSNativeAnnotation)) + jsNativeMembersBuilder += genJSNativeMemberDef(dd) + else + methodsBuilder ++= genMethod(dd) } - gen(impl) - val fields = if (!isHijacked) genClassFields(cd) else Nil val jsNativeMembers = jsNativeMembersBuilder.result() @@ -797,44 +817,31 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val generatedMethods = new ListBuffer[js.MethodDef] val dispatchMethodNames = new ListBuffer[JSName] - def gen(tree: Tree): Unit = { - tree match { - case EmptyTree => () - case Template(_, _, body) => body foreach gen - - case ValDef(mods, name, tpt, rhs) => - () // fields are added via genClassFields() - - case dd: DefDef => - val sym = dd.symbol - val exposed = isExposed(sym) - - if (sym.isClassConstructor) { - constructorTrees += dd - } else if (exposed && sym.isAccessor && !sym.isLazy) { - /* Exposed accessors must not be emitted, since the field they - * access is enough. - */ - } else if (sym.hasAnnotation(JSOptionalAnnotation)) { - // Optional methods must not be emitted - } else { - generatedMethods ++= genMethod(dd) + for (dd <- collectDefDefs(cd.impl)) { + val sym = dd.symbol + val exposed = isExposed(sym) - // Collect the names of the dispatchers we have to create - if (exposed && !sym.isDeferred) { - /* We add symbols that we have to expose here. This way we also - * get inherited stuff that is implemented in this class. - */ - dispatchMethodNames += jsNameOf(sym) - } - } + if (sym.isClassConstructor) { + constructorTrees += dd + } else if (exposed && sym.isAccessor && !sym.isLazy) { + /* Exposed accessors must not be emitted, since the field they + * access is enough. + */ + } else if (sym.hasAnnotation(JSOptionalAnnotation)) { + // Optional methods must not be emitted + } else { + generatedMethods ++= genMethod(dd) - case _ => abort("Illegal tree in gen of genClass(): " + tree) + // Collect the names of the dispatchers we have to create + if (exposed && !sym.isDeferred) { + /* We add symbols that we have to expose here. This way we also + * get inherited stuff that is implemented in this class. + */ + dispatchMethodNames += jsNameOf(sym) + } } } - gen(cd.impl) - // Static members (exported from the companion object) val (staticFields, staticExports) = { /* Phase travel is necessary for non-top-level classes, because flatten @@ -1158,20 +1165,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val classIdent = encodeClassNameIdent(sym) - // fill in class info builder - def gen(tree: Tree): List[js.MethodDef] = { - tree match { - case EmptyTree => Nil - case Template(_, _, body) => body.flatMap(gen) - - case dd: DefDef => - genMethod(dd).toList - - case _ => - abort("Illegal tree in gen of genInterface(): " + tree) - } - } - val generatedMethods = gen(cd.impl) + val generatedMethods = collectDefDefs(cd.impl).flatMap(genMethod(_)) val interfaces = genClassInterfaces(sym, forJSClass = false) val allMemberDefs = @@ -2045,11 +2039,13 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * * Other (normal) methods are emitted with `genMethodDef()`. */ - def genMethodWithCurrentLocalNameScope(dd: DefDef): js.MethodDef = { + def genMethodWithCurrentLocalNameScope(dd: DefDef, + initThisLocalVarIdent: Option[js.LocalIdent] = None): js.MethodDef = { + implicit val pos = dd.pos val sym = dd.symbol - withPerMethodBodyState(sym) { + withPerMethodBodyState(sym, initThisLocalVarIdent) { val methodName = encodeMethodSym(sym) val originalName = originalNameOfMethod(sym) @@ -6409,13 +6405,16 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * * To translate them, we first construct a JS closure for the body: * {{{ - * lambda( - * _this, capture1, ..., captureM, arg1, ..., argN) { - * _this.someMethod(arg1, ..., argN, capture1, ..., captureM) + * arrow-lambda<_this = this, capture1: U1 = capture1, ..., captureM: UM = captureM>( + * arg1: any, ..., argN: any): any = { + * val arg1Unboxed: T1 = arg1.asInstanceOf[T1]; + * ... + * val argNUnboxed: TN = argN.asInstanceOf[TN]; + * // inlined body of `someMethod`, boxed * } * }}} * In the closure, input params are unboxed before use, and the result of - * `someMethod()` is boxed back. + * the body of `someMethod` is boxed back. * * Then, we wrap that closure in a class satisfying the expected type. * For Scala function types, we use the existing @@ -6440,53 +6439,101 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) targetTree @ Select(receiver, _), allArgs0)) = originalFunction val captureSyms = - global.delambdafy.FreeVarTraverser.freeVarsOf(originalFunction) + global.delambdafy.FreeVarTraverser.freeVarsOf(originalFunction).toList val target = targetTree.symbol - val params = paramTrees.map(_.symbol) - val allArgs = allArgs0 map genExpr + val isTargetStatic = compileAsStaticMethod(target) - val formalCaptures = captureSyms.toList.map(genParamDef(_, pos)) - val actualCaptures = formalCaptures.map(_.ref) - - val formalArgs = params.map(genParamDef(_)) - - val (allFormalCaptures, body, allActualCaptures) = if (!compileAsStaticMethod(target)) { - val thisActualCapture = genExpr(receiver) - val thisFormalCapture = js.ParamDef( - freshLocalIdent("this")(receiver.pos), thisOriginalName, - thisActualCapture.tpe, mutable = false)(receiver.pos) - val thisCaptureArg = thisFormalCapture.ref + // Gen actual captures in the local name scope of the enclosing method + val actualCaptures: List[js.Tree] = { + val base = captureSyms.map(genVarRef(_)) + if (isTargetStatic) + base + else + genExpr(receiver) :: base + } - val body = if (isJSType(receiver.tpe) && target.owner != ObjectClass) { - assert(isNonNativeJSClass(target.owner) && !isExposed(target), - s"A Function lambda is trying to call an exposed JS method ${target.fullName}") - genApplyJSClassMethod(thisCaptureArg, target, allArgs) + val closure: js.Closure = withNewLocalNameScope { + // Gen the formal capture params for the closure + val thisFormalCapture: Option[js.ParamDef] = if (isTargetStatic) { + None } else { - genApplyMethodMaybeStatically(thisCaptureArg, target, allArgs) + Some(js.ParamDef( + freshLocalIdent("this")(receiver.pos), thisOriginalName, + toIRType(receiver.tpe), mutable = false)(receiver.pos)) } + val formalCaptures: List[js.ParamDef] = + thisFormalCapture.toList ::: captureSyms.map(genParamDef(_, pos)) - (thisFormalCapture :: formalCaptures, - body, thisActualCapture :: actualCaptures) - } else { - val body = genApplyStatic(target, allArgs) + // Gen the inlined target method body + val genMethodDef = { + genMethodWithCurrentLocalNameScope(consumeDelambdafyTarget(target), + initThisLocalVarIdent = thisFormalCapture.map(_.name)) + } + val js.MethodDef(methodFlags, _, _, methodParams, _, methodBody) = genMethodDef - (formalCaptures, body, actualCaptures) - } + /* If the target method was not supposed to be static, but genMethodDef + * turns out to be static, it means it is a non-exposed method of a JS + * class. The `this` param was turned into a regular param, for which + * we need a `js.VarDef`. + */ + val (maybeThisParamAsVarDef, remainingMethodParams) = { + if (methodFlags.namespace.isStatic && !isTargetStatic) { + val thisParamDef :: remainingMethodParams = methodParams: @unchecked + val thisParamAsVarDef = js.VarDef(thisParamDef.name, thisParamDef.originalName, + thisParamDef.ptpe, thisParamDef.mutable, thisFormalCapture.get.ref) + (thisParamAsVarDef :: Nil, remainingMethodParams) + } else { + (Nil, methodParams) + } + } - val (patchedFormalArgs, paramsLocals) = - patchFunParamsWithBoxes(target, formalArgs, useParamsBeforeLambdaLift = true) + // After that, the args found in the `Function` node had better match the remaining method params + assert(remainingMethodParams.size == allArgs0.size, + s"Arity mismatch: $remainingMethodParams <-> $allArgs0 at $pos") - val patchedBody = - js.Block(paramsLocals :+ ensureResultBoxed(body, target)) + /* Declare each method param as a VarDef, initialized to the corresponding arg. + * In practice, all the args are `This` nodes or `VarRef` nodes, so the + * optimizer will alias those VarDefs away. + * We do this because we have different Symbols, hence different + * encoded LocalIdents. + */ + val methodParamsAsVarDefs = for ((methodParam, arg) <- remainingMethodParams.zip(allArgs0)) yield { + js.VarDef(methodParam.name, methodParam.originalName, methodParam.ptpe, + methodParam.mutable, genExpr(arg)) + } - val closure = js.Closure( + /* Adapt the params and result so that they are boxed from the outside. + * We need this because a `js.Closure` is always from `any`s to `any`. + * + * TODO In total we generate *3* locals for each original param: the + * patched ParamDef, the VarDef for the unboxed value, and a VarDef for + * the original parameter of the delambdafy target. In theory we only + * need 2: can we make it so? + */ + val formalArgs = paramTrees.map(p => genParamDef(p.symbol)) + val (patchedFormalArgs, paramsLocals) = + patchFunParamsWithBoxes(target, formalArgs, useParamsBeforeLambdaLift = true) + val patchedBodyWithBox = + ensureResultBoxed(methodBody.get, target) + + // Finally, assemble all the pieces + val fullClosureBody = js.Block( + paramsLocals ::: + maybeThisParamAsVarDef ::: + methodParamsAsVarDefs ::: + patchedBodyWithBox :: + Nil + ) + js.Closure( arrow = true, - allFormalCaptures, + formalCaptures, patchedFormalArgs, restParam = None, - patchedBody, - allActualCaptures) + fullClosureBody, + actualCaptures + ) + } // Wrap the closure in the appropriate box for the SAM type val funSym = originalFunction.tpe.typeSymbolDirect @@ -6494,7 +6541,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) /* This is a scala.FunctionN. We use the existing AnonFunctionN * wrapper. */ - genJSFunctionToScala(closure, params.size) + genJSFunctionToScala(closure, paramTrees.size) } else { /* This is an arbitrary SAM type (can only happen in 2.12). * We have to synthesize a class like LambdaMetaFactory would do on diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSNewTargetTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSNewTargetTest.scala index edfa1afd01..2e2c1664f2 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSNewTargetTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSNewTargetTest.scala @@ -122,16 +122,6 @@ class JSNewTargetTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:4: error: Illegal use of js.`new`.target. - |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var. - |It cannot be used inside a lambda or by-name parameter, nor in any other location. - | val x = () => js.`new`.target - | ^ - |newSource1.scala:5: error: Illegal use of js.`new`.target. - |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var. - |It cannot be used inside a lambda or by-name parameter, nor in any other location. - | val y = Option(null).getOrElse(js.`new`.target) - | ^ |newSource1.scala:6: error: Illegal use of js.`new`.target. |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var. |It cannot be used inside a lambda or by-name parameter, nor in any other location. @@ -142,6 +132,16 @@ class JSNewTargetTest extends DirectTest with TestHelpers { |It cannot be used inside a lambda or by-name parameter, nor in any other location. | val w: js.ThisFunction0[Any, Any] = (x: Any) => js.`new`.target | ^ + |newSource1.scala:4: error: Illegal use of js.`new`.target. + |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var. + |It cannot be used inside a lambda or by-name parameter, nor in any other location. + | val x = () => js.`new`.target + | ^ + |newSource1.scala:5: error: Illegal use of js.`new`.target. + |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var. + |It cannot be used inside a lambda or by-name parameter, nor in any other location. + | val y = Option(null).getOrElse(js.`new`.target) + | ^ """ } From a235da719cf0755ac17c0a67f6add4f94635d524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 5 Dec 2024 16:15:08 +0100 Subject: [PATCH 041/121] Update the CLA check after the rename of Lightbend to Akka. The URLs did contain a redirect, but `curl` does not follow redirects by default. We should always have the canonical URLs anyway, so I update them instead of making `curl` follow redirects. --- ci/check-cla.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/check-cla.sh b/ci/check-cla.sh index 05fc9ad6b0..fd3e487c95 100755 --- a/ci/check-cla.sh +++ b/ci/check-cla.sh @@ -4,13 +4,13 @@ set -eux AUTHOR="$1" echo "Pull request submitted by $AUTHOR"; URL_AUTHOR=$(jq -rn --arg x "$AUTHOR" '$x|@uri') -signed=$(curl -s "https://www.lightbend.com/contribute/cla/scala/check/$URL_AUTHOR" | jq -r ".signed"); +signed=$(curl -s "https://contribute.akka.io/contribute/cla/scala/check/$URL_AUTHOR" | jq -r ".signed"); if [ "$signed" = "true" ] ; then echo "CLA check for $AUTHOR successful"; else echo "CLA check for $AUTHOR failed"; echo "Please sign the Scala CLA to contribute to Scala.js."; - echo "Go to https://www.lightbend.com/contribute/cla/scala and then"; + echo "Go to https://contribute.akka.io/contribute/cla/scala and then"; echo "comment on the pull request to ask for a new check."; exit 1; fi; From 6ff7c8e7a349d07ddb5883c341e72bd7834cc97d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 14 Dec 2024 11:03:19 +0100 Subject: [PATCH 042/121] Use the new dedicated GitHub action to check the CLA. See https://github.com/scala/cla-checker --- .github/workflows/cla.yml | 6 ++++-- ci/check-cla.sh | 16 ---------------- 2 files changed, 4 insertions(+), 18 deletions(-) delete mode 100755 ci/check-cla.sh diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index e3a39bd5aa..718146327c 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -4,5 +4,7 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - run: ./ci/check-cla.sh "${{ github.event.pull_request.user.login }}" + - name: Verify CLA + uses: scala/cla-checker@v1 + with: + author: ${{ github.event.pull_request.user.login }} diff --git a/ci/check-cla.sh b/ci/check-cla.sh deleted file mode 100755 index fd3e487c95..0000000000 --- a/ci/check-cla.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -set -eux - -AUTHOR="$1" -echo "Pull request submitted by $AUTHOR"; -URL_AUTHOR=$(jq -rn --arg x "$AUTHOR" '$x|@uri') -signed=$(curl -s "https://contribute.akka.io/contribute/cla/scala/check/$URL_AUTHOR" | jq -r ".signed"); -if [ "$signed" = "true" ] ; then - echo "CLA check for $AUTHOR successful"; -else - echo "CLA check for $AUTHOR failed"; - echo "Please sign the Scala CLA to contribute to Scala.js."; - echo "Go to https://contribute.akka.io/contribute/cla/scala and then"; - echo "comment on the pull request to ask for a new check."; - exit 1; -fi; From 2f0eed8fe960ec0a5ee6ad06e2858796f94537af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 14 Dec 2024 12:56:09 +0100 Subject: [PATCH 043/121] Remove the stat/expr distinction from `Transformers.Transformer`. Recent changes have all but removed the distinction between statements and expressions in the IR. Notably: * bb4f6da3628913639a82ad371c22731d9b2347f5 Allow Return arg to be a void if the target Labeled is a void. * cdcff9973579ed77e87343f3edd9429661b4a786 Rename NoType to VoidType and print it as "void". Therefore, it made no sense anymore that `Transformer.transform` had an `isStat` argument. In fact, the first of the two commits mentionned above semi-broke the semantics of that argument anyway. Moreover, `isStat` was never *used* in our codebase (only forwarded everywhere for no reason). The only exception was a deserialization hack for IR <= 1.5. This one was already hacking around the fact the IR produced back then was not well-typed. See #4442 for context. We now remove that parameter everywhere. It makes the deserialization hack a bit more verbose, but everything else becomes simpler. --- .../org/scalajs/nscplugin/GenJSCode.scala | 31 ++-- .../scala/org/scalajs/ir/Serializers.scala | 97 ++++++---- .../scala/org/scalajs/ir/Transformers.scala | 166 ++++++++---------- .../src/main/scala/org/scalajs/ir/Trees.scala | 2 +- .../scala/org/scalajs/ir/PrintersTest.scala | 2 +- .../backend/emitter/FunctionEmitter.scala | 11 +- .../linker/backend/emitter/Transients.scala | 35 ++-- .../backend/wasmemitter/WasmTransients.scala | 33 ++-- project/BinaryIncompatibilities.scala | 9 + project/JavalibIRCleaner.scala | 12 +- 10 files changed, 205 insertions(+), 193 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 267b85d20f..41b035fde2 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -1107,19 +1107,19 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) // After the super call, substitute `selfRef` for `This()` val afterSuper = new ir.Transformers.Transformer { - override def transform(tree: js.Tree, isStat: Boolean): js.Tree = tree match { + override def transform(tree: js.Tree): js.Tree = tree match { case js.This() => selfRef(tree.pos) // Don't traverse closure boundaries case closure: js.Closure => - val newCaptureValues = closure.captureValues.map(transformExpr) + val newCaptureValues = transformTrees(closure.captureValues) closure.copy(captureValues = newCaptureValues)(closure.pos) case tree => - super.transform(tree, isStat) + super.transform(tree) } - }.transformStats(ctorBody.afterSuper) + }.transformTrees(ctorBody.afterSuper) beforeSuper ::: superCall ::: afterSuper } @@ -2170,20 +2170,18 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.ParamDef(name, originalName, ptpe, newMutable(name.name, mutable))(p.pos) } val transformer = new ir.Transformers.Transformer { - override def transform(tree: js.Tree, isStat: Boolean): js.Tree = tree match { + override def transform(tree: js.Tree): js.Tree = tree match { case js.VarDef(name, originalName, vtpe, mutable, rhs) => - assert(isStat, s"found a VarDef in expression position at ${tree.pos}") super.transform(js.VarDef(name, originalName, vtpe, - newMutable(name.name, mutable), rhs)(tree.pos), isStat) + newMutable(name.name, mutable), rhs)(tree.pos)) case js.Closure(arrow, captureParams, params, restParam, body, captureValues) => js.Closure(arrow, captureParams, params, restParam, body, - captureValues.map(transformExpr))(tree.pos) + transformTrees(captureValues))(tree.pos) case _ => - super.transform(tree, isStat) + super.transform(tree) } } - val newBody = body.map( - b => transformer.transform(b, isStat = resultType == jstpe.VoidType)) + val newBody = transformer.transformTreeOpt(body) js.MethodDef(flags, methodName, originalName, newParams, resultType, newBody)(methodDef.optimizerHints, Unversioned)(methodDef.pos) } @@ -2208,18 +2206,17 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.ParamDef(name, originalName, newType(name, ptpe), mutable)(p.pos) } val transformer = new ir.Transformers.Transformer { - override def transform(tree: js.Tree, isStat: Boolean): js.Tree = tree match { + override def transform(tree: js.Tree): js.Tree = tree match { case tree @ js.VarRef(name) => js.VarRef(name)(newType(name, tree.tpe))(tree.pos) case js.Closure(arrow, captureParams, params, restParam, body, captureValues) => js.Closure(arrow, captureParams, params, restParam, body, - captureValues.map(transformExpr))(tree.pos) + transformTrees(captureValues))(tree.pos) case _ => - super.transform(tree, isStat) + super.transform(tree) } } - val newBody = body.map( - b => transformer.transform(b, isStat = resultType == jstpe.VoidType)) + val newBody = transformer.transformTreeOpt(body) js.MethodDef(flags, methodName, originalName, newParams, resultType, newBody)(methodDef.optimizerHints, Unversioned)(methodDef.pos) } @@ -7282,7 +7279,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) def traverse(traverser: ir.Traversers.Traverser): Unit = () - def transform(transformer: ir.Transformers.Transformer, isStat: Boolean)( + def transform(transformer: ir.Transformers.Transformer)( implicit pos: ir.Position): js.Tree = { js.Transient(this) } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index 0a366a12b5..11bafee148 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -1645,15 +1645,15 @@ object Serializers { * We must replace those occurrences with a `Class_name` as well. */ val transformer = new Transformers.Transformer { - override def transform(tree: Tree, isStat: Boolean): Tree = tree match { + override def transform(tree: Tree): Tree = tree match { case JSSelect(_, StringLiteral("name")) => implicit val pos = tree.pos UnaryOp(UnaryOp.Class_name, thisJLClass) case _ => - super.transform(tree, isStat) + super.transform(tree) } } - transformer.transform(method.body.get, isStat = method.resultType == VoidType) + transformer.transform(method.body.get) } val newOptimizerHints = @@ -2065,40 +2065,69 @@ object Serializers { JSNativeMemberDef(flags, name, jsNativeLoadSpec) } - private def bodyHack5(body: Tree, isStat: Boolean): Tree = { - if (!hacks.use5) { - body - } else { - /* #4442 and #4601: Patch Labeled, If, Match and TryCatch nodes in - * statement position to have type VoidType. These 4 nodes are the - * control structures whose result type is explicitly specified (and - * not derived from their children like Block or TryFinally, or - * constant like While). - */ - new Transformers.Transformer { - override def transform(tree: Tree, isStat: Boolean): Tree = { - val newTree = super.transform(tree, isStat) - if (isStat && newTree.tpe != VoidType) { - newTree match { - case Labeled(label, _, body) => - Labeled(label, VoidType, body)(newTree.pos) - case If(cond, thenp, elsep) => - If(cond, thenp, elsep)(VoidType)(newTree.pos) - case Match(selector, cases, default) => - Match(selector, cases, default)(VoidType)(newTree.pos) - case TryCatch(block, errVar, errVarOriginalName, handler) => - TryCatch(block, errVar, errVarOriginalName, handler)(VoidType)(newTree.pos) - case _ => - newTree - } - } else { - newTree - } - } - }.transform(body, isStat) + /* #4442 and #4601: Patch Labeled, If, Match and TryCatch nodes in + * statement position to have type VoidType. These 4 nodes are the + * control structures whose result type is explicitly specified (and + * not derived from their children like Block or TryFinally, or + * constant like While). + */ + private object BodyHack5Transformer extends Transformers.Transformer { + def transformStat(tree: Tree): Tree = { + implicit val pos = tree.pos + + tree match { + // Nodes that we actually need to alter + case Labeled(label, _, body) => + Labeled(label, VoidType, transformStat(body)) + case If(cond, thenp, elsep) => + If(transform(cond), transformStat(thenp), transformStat(elsep))(VoidType) + case Match(selector, cases, default) => + Match(transform(selector), cases.map(c => (c._1, transformStat(c._2))), + transformStat(default))(VoidType) + case TryCatch(block, errVar, errVarOriginalName, handler) => + TryCatch(transformStat(block), errVar, errVarOriginalName, + transformStat(handler))(VoidType) + + // Nodes for which we need to forward the statement position + case Block(stats) => + Block(stats.map(transformStat(_))) + case TryFinally(block, finalizer) => + Block(transformStat(block), transformStat(finalizer)) + + // For everything else, delegate to transform + case _ => + transform(tree) + } } + + override def transform(tree: Tree): Tree = { + implicit val pos = tree.pos + + tree match { + // Nodes that force a statement position for some of their parts + case Block(stats) => + Block(stats.init.map(transformStat(_)), transform(stats.last)) + case While(cond, body) => + While(transform(cond), transformStat(body)) + case ForIn(obj, keyVar, keyVarOriginalName, body) => + ForIn(transform(obj), keyVar, keyVarOriginalName, transformStat(body)) + case TryFinally(block, finalizer) => + TryFinally(transform(block), transformStat(finalizer)) + + case _ => + super.transform(tree) + } + } + + def transform(tree: Tree, isStat: Boolean): Tree = + if (isStat) transformStat(tree) + else transform(tree) } + private def bodyHack5(body: Tree, isStat: Boolean): Tree = + if (!hacks.use5) body + else BodyHack5Transformer.transform(body, isStat) + private def bodyHack5Expr(body: Tree): Tree = bodyHack5(body, isStat = false) def readTopLevelExportDef(): TopLevelExportDef = { diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala index 10f490225b..5aa07f32ff 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala @@ -19,204 +19,197 @@ import Version.Unversioned object Transformers { abstract class Transformer { - final def transformStats(trees: List[Tree]): List[Tree] = - trees.map(transformStat(_)) - - final def transformStat(tree: Tree): Tree = - transform(tree, isStat = true) - - final def transformExpr(tree: Tree): Tree = - transform(tree, isStat = false) - - def transformExprOrJSSpread(tree: TreeOrJSSpread): TreeOrJSSpread = { + final def transformTreeOrJSSpread(tree: TreeOrJSSpread): TreeOrJSSpread = { implicit val pos = tree.pos tree match { - case JSSpread(items) => JSSpread(transformExpr(items)) - case tree: Tree => transformExpr(tree) + case JSSpread(items) => JSSpread(transform(items)) + case tree: Tree => transform(tree) } } - def transform(tree: Tree, isStat: Boolean): Tree = { + final def transformTrees(trees: List[Tree]): List[Tree] = + trees.map(transform(_)) + + final def transformTreeOpt(treeOpt: Option[Tree]): Option[Tree] = + treeOpt.map(transform(_)) + + def transform(tree: Tree): Tree = { implicit val pos = tree.pos tree match { // Definitions case VarDef(ident, originalName, vtpe, mutable, rhs) => - VarDef(ident, originalName, vtpe, mutable, transformExpr(rhs)) + VarDef(ident, originalName, vtpe, mutable, transform(rhs)) // Control flow constructs case Block(stats) => - Block(stats.init.map(transformStat) :+ transform(stats.last, isStat)) + Block(transformTrees(stats)) case Labeled(label, tpe, body) => - Labeled(label, tpe, transform(body, isStat)) + Labeled(label, tpe, transform(body)) case Assign(lhs, rhs) => - Assign(transformExpr(lhs).asInstanceOf[AssignLhs], transformExpr(rhs)) + Assign(transform(lhs).asInstanceOf[AssignLhs], transform(rhs)) case Return(expr, label) => - Return(transformExpr(expr), label) // pessimistic; maybe `expr` is actually a statement + Return(transform(expr), label) case If(cond, thenp, elsep) => - If(transformExpr(cond), transform(thenp, isStat), - transform(elsep, isStat))(tree.tpe) + If(transform(cond), transform(thenp), transform(elsep))(tree.tpe) case While(cond, body) => - While(transformExpr(cond), transformStat(body)) + While(transform(cond), transform(body)) case ForIn(obj, keyVar, keyVarOriginalName, body) => - ForIn(transformExpr(obj), keyVar, keyVarOriginalName, - transformStat(body)) + ForIn(transform(obj), keyVar, keyVarOriginalName, transform(body)) case TryCatch(block, errVar, errVarOriginalName, handler) => - TryCatch(transform(block, isStat), errVar, errVarOriginalName, - transform(handler, isStat))(tree.tpe) + TryCatch(transform(block), errVar, errVarOriginalName, + transform(handler))(tree.tpe) case TryFinally(block, finalizer) => - TryFinally(transform(block, isStat), transformStat(finalizer)) + TryFinally(transform(block), transform(finalizer)) case Throw(expr) => - Throw(transformExpr(expr)) + Throw(transform(expr)) case Match(selector, cases, default) => - Match(transformExpr(selector), - cases map (c => (c._1, transform(c._2, isStat))), - transform(default, isStat))(tree.tpe) + Match(transform(selector), cases.map(c => (c._1, transform(c._2))), + transform(default))(tree.tpe) // Scala expressions case New(className, ctor, args) => - New(className, ctor, args map transformExpr) + New(className, ctor, transformTrees(args)) case Select(qualifier, field) => - Select(transformExpr(qualifier), field)(tree.tpe) + Select(transform(qualifier), field)(tree.tpe) case Apply(flags, receiver, method, args) => - Apply(flags, transformExpr(receiver), method, - args map transformExpr)(tree.tpe) + Apply(flags, transform(receiver), method, + transformTrees(args))(tree.tpe) case ApplyStatically(flags, receiver, className, method, args) => - ApplyStatically(flags, transformExpr(receiver), className, method, - args map transformExpr)(tree.tpe) + ApplyStatically(flags, transform(receiver), className, method, + transformTrees(args))(tree.tpe) case ApplyStatic(flags, className, method, args) => - ApplyStatic(flags, className, method, args map transformExpr)(tree.tpe) + ApplyStatic(flags, className, method, transformTrees(args))(tree.tpe) case ApplyDynamicImport(flags, className, method, args) => - ApplyDynamicImport(flags, className, method, args.map(transformExpr)) + ApplyDynamicImport(flags, className, method, transformTrees(args)) case UnaryOp(op, lhs) => - UnaryOp(op, transformExpr(lhs)) + UnaryOp(op, transform(lhs)) case BinaryOp(op, lhs, rhs) => - BinaryOp(op, transformExpr(lhs), transformExpr(rhs)) + BinaryOp(op, transform(lhs), transform(rhs)) case NewArray(tpe, length) => - NewArray(tpe, transformExpr(length)) + NewArray(tpe, transform(length)) case ArrayValue(tpe, elems) => - ArrayValue(tpe, elems map transformExpr) + ArrayValue(tpe, transformTrees(elems)) case ArrayLength(array) => - ArrayLength(transformExpr(array)) + ArrayLength(transform(array)) case ArraySelect(array, index) => - ArraySelect(transformExpr(array), transformExpr(index))(tree.tpe) + ArraySelect(transform(array), transform(index))(tree.tpe) case RecordValue(tpe, elems) => - RecordValue(tpe, elems map transformExpr) + RecordValue(tpe, transformTrees(elems)) case RecordSelect(record, field) => - RecordSelect(transformExpr(record), field)(tree.tpe) + RecordSelect(transform(record), field)(tree.tpe) case IsInstanceOf(expr, testType) => - IsInstanceOf(transformExpr(expr), testType) + IsInstanceOf(transform(expr), testType) case AsInstanceOf(expr, tpe) => - AsInstanceOf(transformExpr(expr), tpe) + AsInstanceOf(transform(expr), tpe) case GetClass(expr) => - GetClass(transformExpr(expr)) + GetClass(transform(expr)) case Clone(expr) => - Clone(transformExpr(expr)) + Clone(transform(expr)) case IdentityHashCode(expr) => - IdentityHashCode(transformExpr(expr)) + IdentityHashCode(transform(expr)) case WrapAsThrowable(expr) => - WrapAsThrowable(transformExpr(expr)) + WrapAsThrowable(transform(expr)) case UnwrapFromThrowable(expr) => - UnwrapFromThrowable(transformExpr(expr)) + UnwrapFromThrowable(transform(expr)) // JavaScript expressions case JSNew(ctor, args) => - JSNew(transformExpr(ctor), args.map(transformExprOrJSSpread)) + JSNew(transform(ctor), args.map(transformTreeOrJSSpread)) case JSPrivateSelect(qualifier, field) => - JSPrivateSelect(transformExpr(qualifier), field) + JSPrivateSelect(transform(qualifier), field) case JSSelect(qualifier, item) => - JSSelect(transformExpr(qualifier), transformExpr(item)) + JSSelect(transform(qualifier), transform(item)) case JSFunctionApply(fun, args) => - JSFunctionApply(transformExpr(fun), args.map(transformExprOrJSSpread)) + JSFunctionApply(transform(fun), args.map(transformTreeOrJSSpread)) case JSMethodApply(receiver, method, args) => - JSMethodApply(transformExpr(receiver), transformExpr(method), - args.map(transformExprOrJSSpread)) + JSMethodApply(transform(receiver), transform(method), + args.map(transformTreeOrJSSpread)) case JSSuperSelect(superClass, qualifier, item) => - JSSuperSelect(superClass, transformExpr(qualifier), - transformExpr(item)) + JSSuperSelect(superClass, transform(qualifier), transform(item)) case JSSuperMethodCall(superClass, receiver, method, args) => - JSSuperMethodCall(superClass, transformExpr(receiver), - transformExpr(method), args.map(transformExprOrJSSpread)) + JSSuperMethodCall(superClass, transform(receiver), + transform(method), args.map(transformTreeOrJSSpread)) case JSSuperConstructorCall(args) => - JSSuperConstructorCall(args.map(transformExprOrJSSpread)) + JSSuperConstructorCall(args.map(transformTreeOrJSSpread)) case JSImportCall(arg) => - JSImportCall(transformExpr(arg)) + JSImportCall(transform(arg)) case JSDelete(qualifier, item) => - JSDelete(transformExpr(qualifier), transformExpr(item)) + JSDelete(transform(qualifier), transform(item)) case JSUnaryOp(op, lhs) => - JSUnaryOp(op, transformExpr(lhs)) + JSUnaryOp(op, transform(lhs)) case JSBinaryOp(op, lhs, rhs) => - JSBinaryOp(op, transformExpr(lhs), transformExpr(rhs)) + JSBinaryOp(op, transform(lhs), transform(rhs)) case JSArrayConstr(items) => - JSArrayConstr(items.map(transformExprOrJSSpread)) + JSArrayConstr(items.map(transformTreeOrJSSpread)) case JSObjectConstr(fields) => JSObjectConstr(fields.map { field => - (transformExpr(field._1), transformExpr(field._2)) + (transform(field._1), transform(field._2)) }) case JSTypeOfGlobalRef(globalRef) => - JSTypeOfGlobalRef(transformExpr(globalRef).asInstanceOf[JSGlobalRef]) + JSTypeOfGlobalRef(transform(globalRef).asInstanceOf[JSGlobalRef]) // Atomic expressions case Closure(arrow, captureParams, params, restParam, body, captureValues) => - Closure(arrow, captureParams, params, restParam, transformExpr(body), - captureValues.map(transformExpr)) + Closure(arrow, captureParams, params, restParam, transform(body), + transformTrees(captureValues)) case CreateJSClass(className, captureValues) => - CreateJSClass(className, captureValues.map(transformExpr)) + CreateJSClass(className, transformTrees(captureValues)) // Transients case Transient(value) => - value.transform(this, isStat) + value.transform(this) // Trees that need not be transformed @@ -233,7 +226,7 @@ object Transformers { def transformClassDef(tree: ClassDef): ClassDef = { import tree._ ClassDef(name, originalName, kind, jsClassCaptures, superClass, - interfaces, jsSuperClass.map(transformExpr), jsNativeLoadSpec, + interfaces, transformTreeOpt(jsSuperClass), jsNativeLoadSpec, fields.map(transformAnyFieldDef(_)), methods.map(transformMethodDef), jsConstructor.map(transformJSConstructorDef), jsMethodProps.map(transformJSMethodPropDef), jsNativeMembers, @@ -246,7 +239,7 @@ object Transformers { def transformMethodDef(methodDef: MethodDef): MethodDef = { val MethodDef(flags, name, originalName, args, resultType, body) = methodDef - val newBody = body.map(transform(_, isStat = resultType == VoidType)) + val newBody = transformTreeOpt(body) MethodDef(flags, name, originalName, args, resultType, newBody)( methodDef.optimizerHints, Unversioned)(methodDef.pos) } @@ -265,29 +258,26 @@ object Transformers { case JSPropertyDef(flags, name, getterBody, setterArgAndBody) => JSPropertyDef( flags, - transformExpr(name), - getterBody.map(transformStat), - setterArgAndBody map { case (arg, body) => - (arg, transformStat(body)) + transform(name), + transformTreeOpt(getterBody), + setterArgAndBody.map { case (arg, body) => + (arg, transform(body)) })(Unversioned)(jsMethodPropDef.pos) } } def transformJSMethodDef(jsMethodDef: JSMethodDef): JSMethodDef = { val JSMethodDef(flags, name, args, restParam, body) = jsMethodDef - JSMethodDef(flags, transformExpr(name), args, restParam, transformExpr(body))( + JSMethodDef(flags, transform(name), args, restParam, transform(body))( jsMethodDef.optimizerHints, Unversioned)(jsMethodDef.pos) } def transformJSConstructorBody(body: JSConstructorBody): JSConstructorBody = { implicit val pos = body.pos - val newBeforeSuper = body.beforeSuper.map(transformStat(_)) - val newSuperCall = transformStat(body.superCall).asInstanceOf[JSSuperConstructorCall] - val newAfterSuper = body.afterSuper match { - case stats :+ expr => stats.map(transformStat(_)) :+ transformExpr(expr) - case empty => empty // cannot use Nil here because the compiler does not know that it is exhaustive - } + val newBeforeSuper = transformTrees(body.beforeSuper) + val newSuperCall = transform(body.superCall).asInstanceOf[JSSuperConstructorCall] + val newAfterSuper = transformTrees(body.afterSuper) JSConstructorBody(newBeforeSuper, newSuperCall, newAfterSuper) } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index bf7eaff25b..4b1e58ce12 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -1158,7 +1158,7 @@ object Trees { * * Implementations should transform contained trees and potentially adjust the result. */ - def transform(transformer: Transformers.Transformer, isStat: Boolean)( + def transform(transformer: Transformers.Transformer)( implicit pos: Position): Tree /** Prints the IR representation of this transient node. diff --git a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala index 980b7c9bc3..052db4b9da 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -948,7 +948,7 @@ class PrintersTest { def traverse(traverser: Traversers.Traverser): Unit = ??? - def transform(transformer: Transformers.Transformer, isStat: Boolean)( + def transform(transformer: Transformers.Transformer)( implicit pos: Position): Tree = ??? def printIR(out: Printers.IRTreePrinter): Unit = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index 834ec7a554..f2847be772 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -3355,10 +3355,8 @@ private object FunctionEmitter { def traverse(traverser: Traverser): Unit = () - def transform(transformer: Transformer, isStat: Boolean)( - implicit pos: Position): Tree = { + def transform(transformer: Transformer)(implicit pos: Position): Tree = Transient(this) - } def printIR(out: org.scalajs.ir.Printers.IRTreePrinter): Unit = out.print(ident.name) @@ -3373,10 +3371,9 @@ private object FunctionEmitter { traverser.traverse(argArray) } - def transform(transformer: Transformer, isStat: Boolean)( - implicit pos: Position): Tree = { - Transient(JSNewVararg(transformer.transformExpr(ctor), - transformer.transformExpr(argArray))) + def transform(transformer: Transformer)(implicit pos: Position): Tree = { + Transient(JSNewVararg(transformer.transform(ctor), + transformer.transform(argArray))) } def printIR(out: IRTreePrinter): Unit = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Transients.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Transients.scala index dc772cfa1a..61f9ece247 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Transients.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Transients.scala @@ -40,9 +40,9 @@ object Transients { def traverse(traverser: Traverser): Unit = traverser.traverse(expr) - def transform(transformer: Transformer, isStat: Boolean)( + def transform(transformer: Transformer)( implicit pos: Position): Tree = { - Transient(Cast(transformer.transformExpr(expr), tpe)) + Transient(Cast(transformer.transform(expr), tpe)) } def printIR(out: IRTreePrinter): Unit = { @@ -72,12 +72,9 @@ object Transients { traverser.traverse(length) } - def transform(transformer: Transformer, isStat: Boolean)( - implicit pos: Position): Tree = { - import transformer.transformExpr - - Transient(SystemArrayCopy(transformExpr(src), transformExpr(srcPos), - transformExpr(dest), transformExpr(destPos), transformExpr(length))) + def transform(t: Transformer)(implicit pos: Position): Tree = { + Transient(SystemArrayCopy(t.transform(src), t.transform(srcPos), + t.transform(dest), t.transform(destPos), t.transform(length))) } def printIR(out: IRTreePrinter): Unit = { @@ -101,9 +98,9 @@ object Transients { def traverse(traverser: Traverser): Unit = traverser.traverse(runtimeClass) - def transform(transformer: Transformer, isStat: Boolean)( + def transform(transformer: Transformer)( implicit pos: Position): Tree = { - Transient(ZeroOf(transformer.transformExpr(runtimeClass))) + Transient(ZeroOf(transformer.transform(runtimeClass))) } def printIR(out: IRTreePrinter): Unit = { @@ -126,10 +123,10 @@ object Transients { traverser.traverse(nativeArray) } - def transform(transformer: Transformer, isStat: Boolean)( + def transform(transformer: Transformer)( implicit pos: Position): Tree = { - Transient(NativeArrayWrapper(transformer.transformExpr(elemClass), - transformer.transformExpr(nativeArray))(tpe)) + Transient(NativeArrayWrapper(transformer.transform(elemClass), + transformer.transform(nativeArray))(tpe)) } def printIR(out: IRTreePrinter): Unit = { @@ -150,9 +147,9 @@ object Transients { def traverse(traverser: Traverser): Unit = traverser.traverse(obj) - def transform(transformer: Transformer, isStat: Boolean)( + def transform(transformer: Transformer)( implicit pos: Position): Tree = { - Transient(ObjectClassName(transformer.transformExpr(obj))) + Transient(ObjectClassName(transformer.transform(obj))) } def printIR(out: IRTreePrinter): Unit = { @@ -172,9 +169,9 @@ object Transients { def traverse(traverser: Traverser): Unit = traverser.traverse(expr) - def transform(transformer: Transformer, isStat: Boolean)( + def transform(transformer: Transformer)( implicit pos: Position): Tree = { - Transient(ArrayToTypedArray(transformer.transformExpr(expr), primRef)) + Transient(ArrayToTypedArray(transformer.transform(expr), primRef)) } def printIR(out: IRTreePrinter): Unit = { @@ -198,9 +195,9 @@ object Transients { def traverse(traverser: Traverser): Unit = traverser.traverse(expr) - def transform(transformer: Transformer, isStat: Boolean)( + def transform(transformer: Transformer)( implicit pos: Position): Tree = { - Transient(TypedArrayToArray(transformer.transformExpr(expr), primRef)) + Transient(TypedArrayToArray(transformer.transform(expr), primRef)) } def printIR(out: IRTreePrinter): Unit = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/WasmTransients.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/WasmTransients.scala index b1471a3f47..bf41838a98 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/WasmTransients.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/WasmTransients.scala @@ -43,10 +43,8 @@ object WasmTransients { def traverse(traverser: Traverser): Unit = traverser.traverse(lhs) - def transform(transformer: Transformer, isStat: Boolean)( - implicit pos: Position): Tree = { - Transient(WasmUnaryOp(op, transformer.transformExpr(lhs))) - } + def transform(transformer: Transformer)(implicit pos: Position): Tree = + Transient(WasmUnaryOp(op, transformer.transform(lhs))) def wasmInstr: wa.SimpleInstr = (op: @switch) match { case I32Clz => wa.I32Clz @@ -141,10 +139,9 @@ object WasmTransients { traverser.traverse(rhs) } - def transform(transformer: Transformer, isStat: Boolean)( - implicit pos: Position): Tree = { - Transient(WasmBinaryOp(op, transformer.transformExpr(lhs), - transformer.transformExpr(rhs))) + def transform(transformer: Transformer)(implicit pos: Position): Tree = { + Transient(WasmBinaryOp(op, transformer.transform(lhs), + transformer.transform(rhs))) } def wasmInstr: wa.SimpleInstr = (op: @switch) match { @@ -234,10 +231,8 @@ object WasmTransients { traverser.traverse(codePoint) } - def transform(transformer: Transformer, isStat: Boolean)( - implicit pos: Position): Tree = { - Transient(WasmStringFromCodePoint(transformer.transformExpr(codePoint))) - } + def transform(transformer: Transformer)(implicit pos: Position): Tree = + Transient(WasmStringFromCodePoint(transformer.transform(codePoint))) def printIR(out: IRTreePrinter): Unit = { out.print("$stringFromCodePoint") @@ -269,10 +264,9 @@ object WasmTransients { traverser.traverse(index) } - def transform(transformer: Transformer, isStat: Boolean)( - implicit pos: Position): Tree = { - Transient(WasmCodePointAt(transformer.transformExpr(string), - transformer.transformExpr(index))) + def transform(transformer: Transformer)(implicit pos: Position): Tree = { + Transient(WasmCodePointAt(transformer.transform(string), + transformer.transform(index))) } def printIR(out: IRTreePrinter): Unit = { @@ -308,10 +302,9 @@ object WasmTransients { optEnd.foreach(traverser.traverse(_)) } - def transform(transformer: Transformer, isStat: Boolean)( - implicit pos: Position): Tree = { - Transient(WasmSubstring(transformer.transformExpr(string), - transformer.transformExpr(start), optEnd.map(transformer.transformExpr(_)))) + def transform(transformer: Transformer)(implicit pos: Position): Tree = { + Transient(WasmSubstring(transformer.transform(string), + transformer.transform(start), transformer.transformTreeOpt(optEnd))) } def printIR(out: IRTreePrinter): Unit = { diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 75c5dbb18a..133cf0906a 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -10,6 +10,15 @@ object BinaryIncompatibilities { ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$JSLinkingInfo$"), ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.ir.Trees#*.tpe"), ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Types$NoType$"), + + // !!! Breaking, OK in minor release + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Transformers#Transformer.transform"), + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Transformers#Transformer.transformExpr"), + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Transformers#Transformer.transformExprOrJSSpread"), + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Transformers#Transformer.transformStat"), + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Transformers#Transformer.transformStats"), + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Trees#Transient#Value.transform"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("org.scalajs.ir.Trees#Transient#Value.transform"), ) val Linker = Seq( diff --git a/project/JavalibIRCleaner.scala b/project/JavalibIRCleaner.scala index 61ed4ea743..dc0d38d249 100644 --- a/project/JavalibIRCleaner.scala +++ b/project/JavalibIRCleaner.scala @@ -302,12 +302,12 @@ final class JavalibIRCleaner(baseDirectoryURI: URI) { } } - override def transform(tree: Tree, isStat: Boolean): Tree = { + override def transform(tree: Tree): Tree = { implicit val pos = tree.pos - val tree1 = preTransform(tree, isStat) - val tree2 = if (tree1 eq tree) super.transform(tree, isStat) else transform(tree1, isStat) - val result = postTransform(tree2, isStat) + val tree1 = preTransform(tree) + val tree2 = if (tree1 eq tree) super.transform(tree) else transform(tree1) + val result = postTransform(tree2) if (transformType(result.tpe) != result.tpe) reportError(s"the result type of a ${result.getClass().getSimpleName()} was not transformed") @@ -315,7 +315,7 @@ final class JavalibIRCleaner(baseDirectoryURI: URI) { result } - private def preTransform(tree: Tree, isStat: Boolean): Tree = { + private def preTransform(tree: Tree): Tree = { implicit val pos = tree.pos tree match { @@ -421,7 +421,7 @@ final class JavalibIRCleaner(baseDirectoryURI: URI) { } } - private def postTransform(tree: Tree, isStat: Boolean): Tree = { + private def postTransform(tree: Tree): Tree = { implicit val pos = tree.pos tree match { From 6041bad6963a8017c26889d7191edc4182e98ca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 16 Dec 2024 16:47:12 +0100 Subject: [PATCH 044/121] Fix #5085: Zero.stripTrailingZeros() returns BigDecimal.ZERO. As directly specified by its JavaDoc. --- javalib/src/main/scala/java/math/BigDecimal.scala | 5 ++--- .../javalib/math/BigDecimalScaleOperationsTest.scala | 4 ++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/javalib/src/main/scala/java/math/BigDecimal.scala b/javalib/src/main/scala/java/math/BigDecimal.scala index 98baffe296..d045ffc57e 100644 --- a/javalib/src/main/scala/java/math/BigDecimal.scala +++ b/javalib/src/main/scala/java/math/BigDecimal.scala @@ -1226,9 +1226,8 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { def stripTrailingZeros(): BigDecimal = { if (isZero) { - // Preserve RI compatibility, so BigDecimal.equals (which checks - // value *and* scale) continues to work. - this + // As specified by the JavaDoc, we must return BigDecimal.ZERO, which has a scale of 0 + BigDecimal.ZERO } else { val lastPow = BigTenPows.length - 1 diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/BigDecimalScaleOperationsTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/BigDecimalScaleOperationsTest.scala index 5589f3bb60..0faca1f058 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/BigDecimalScaleOperationsTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/BigDecimalScaleOperationsTest.scala @@ -287,4 +287,8 @@ class BigDecimalScaleOperationsTest { val prec = aNumber.precision() assertEquals(prec, 68) } + + @Test def testStripTrailingZeros(): Unit = { + assertEquals(0, java.math.BigDecimal.valueOf(0, 9).stripTrailingZeros().scale()) + } } From 3d65f79a9bb7f9c2d864db5208bd41b30be5cda7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 21 Dec 2024 10:45:06 +0100 Subject: [PATCH 045/121] Improve test suite error messages when `HashersTest` fails. --- ir/shared/src/test/scala/org/scalajs/ir/HashersTest.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ir/shared/src/test/scala/org/scalajs/ir/HashersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/HashersTest.scala index 4115c19c81..c92f911f11 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/HashersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/HashersTest.scala @@ -38,11 +38,9 @@ class HashersTest { out.close() out.toByteArray() } + val actualString = actualBytes.map(b => "%02x".format(b & 0xff)).mkString - val expectedBytes = expected.grouped(2) - .map(Integer.parseInt(_, 16).toByte).toArray - - assertArrayEquals(expectedBytes, actualBytes) + assertEquals(expected, actualString) } private val bodyWithInterestingStuff = Block( From 5dc41278c02a03e2f6312f177236122f48c84efe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 21 Dec 2024 11:17:44 +0100 Subject: [PATCH 046/121] Directly store a LocalName inside VarRef, instead of a LocalIdent. The only point of a `LocalIdent` is to keep track of a position in addition to the local name. However, in a `VarRef`, the position should always be the same as the `VarRef` itself. Storing a separate instance of `LocalIdent` is therefore wasteful. --- .../org/scalajs/nscplugin/GenJSCode.scala | 64 +++++++++---------- .../org/scalajs/nscplugin/GenJSExports.scala | 10 +-- .../org/scalajs/nscplugin/JSEncoding.scala | 7 +- .../main/scala/org/scalajs/ir/Hashers.scala | 4 +- .../main/scala/org/scalajs/ir/Printers.scala | 4 +- .../scala/org/scalajs/ir/Serializers.scala | 9 ++- .../src/main/scala/org/scalajs/ir/Trees.scala | 6 +- .../scala/org/scalajs/ir/HashersTest.scala | 4 +- .../scala/org/scalajs/ir/TestIRBuilder.scala | 4 +- .../backend/emitter/FunctionEmitter.scala | 29 +++++---- .../backend/wasmemitter/ClassEmitter.scala | 2 +- .../backend/wasmemitter/FunctionEmitter.scala | 6 +- .../linker/checker/ClassDefChecker.scala | 4 +- .../frontend/optimizer/IncOptimizer.scala | 2 +- .../frontend/optimizer/OptimizerCore.scala | 41 ++++++------ .../org/scalajs/linker/OptimizerTest.scala | 4 +- project/BinaryIncompatibilities.scala | 1 + project/JavaLangObject.scala | 2 +- project/JavalibIRCleaner.scala | 2 +- 19 files changed, 108 insertions(+), 97 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 41b035fde2..23981dcaba 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -179,7 +179,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) // Per method body private val currentMethodSym = new ScopedVar[Symbol] - private val thisLocalVarIdent = new ScopedVar[Option[js.LocalIdent]] + private val thisLocalVarName = new ScopedVar[Option[LocalName]] private val enclosingLabelDefInfos = new ScopedVar[Map[Symbol, EnclosingLabelDefInfo]] private val isModuleInitialized = new ScopedVar[VarBox[Boolean]] private val undefinedDefaultParams = new ScopedVar[mutable.Set[Symbol]] @@ -187,11 +187,11 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) private val mutatedLocalVars = new ScopedVar[mutable.Set[Symbol]] private def withPerMethodBodyState[A](methodSym: Symbol, - initThisLocalVarIdent: Option[js.LocalIdent] = None)(body: => A): A = { + initThisLocalVarName: Option[LocalName] = None)(body: => A): A = { withScopedVars( currentMethodSym := methodSym, - thisLocalVarIdent := initThisLocalVarIdent, + thisLocalVarName := initThisLocalVarName, enclosingLabelDefInfos := Map.empty, isModuleInitialized := new VarBox(false), undefinedDefaultParams := mutable.Set.empty, @@ -241,7 +241,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) generatedSAMWrapperCount := new VarBox(0), delambdafyTargetDefDefs := mutable.Map.empty, currentMethodSym := null, - thisLocalVarIdent := null, + thisLocalVarName := null, enclosingLabelDefInfos := null, isModuleInitialized := null, undefinedDefaultParams := null, @@ -999,9 +999,9 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * FIXME This could clash with a local variable of the constructor or a JS * class capture. How do we avoid this? */ - val selfName = freshLocalIdent("this")(pos) + val selfIdent = freshLocalIdent("this")(pos) def selfRef(implicit pos: ir.Position) = - js.VarRef(selfName)(jstpe.AnyType) + js.VarRef(selfIdent.name)(jstpe.AnyType) def memberLambda(params: List[js.ParamDef], restParam: Option[js.ParamDef], body: js.Tree)(implicit pos: ir.Position) = { @@ -1101,7 +1101,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.JSNew(jsSuperClassRef, args) } - val selfVarDef = js.VarDef(selfName, thisOriginalName, jstpe.AnyType, mutable = false, newTree) + val selfVarDef = js.VarDef(selfIdent.copy(), // copy for the correct `pos` + thisOriginalName, jstpe.AnyType, mutable = false, newTree) selfVarDef :: memberDefinitions } @@ -2040,12 +2041,12 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * Other (normal) methods are emitted with `genMethodDef()`. */ def genMethodWithCurrentLocalNameScope(dd: DefDef, - initThisLocalVarIdent: Option[js.LocalIdent] = None): js.MethodDef = { + initThisLocalVarName: Option[LocalName] = None): js.MethodDef = { implicit val pos = dd.pos val sym = dd.symbol - withPerMethodBodyState(sym, initThisLocalVarIdent) { + withPerMethodBodyState(sym, initThisLocalVarName) { val methodName = encodeMethodSym(sym) val originalName = originalNameOfMethod(sym) @@ -2112,8 +2113,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) methodDef } else { val patches = ( - unmutatedMutableLocalVars.map(encodeLocalSym(_).name -> false) ::: - mutatedImmutableLocalVals.map(encodeLocalSym(_).name -> true) + unmutatedMutableLocalVars.map(encodeLocalSymName(_) -> false) ::: + mutatedImmutableLocalVals.map(encodeLocalSymName(_) -> true) ).toMap patchMutableFlagOfLocals(methodDef, patches) } @@ -2195,15 +2196,15 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) private def patchTypeOfParamDefs(methodDef: js.MethodDef, patches: Map[LocalName, jstpe.Type]): js.MethodDef = { - def newType(name: js.LocalIdent, oldType: jstpe.Type): jstpe.Type = - patches.getOrElse(name.name, oldType) + def newType(name: LocalName, oldType: jstpe.Type): jstpe.Type = + patches.getOrElse(name, oldType) val js.MethodDef(flags, methodName, originalName, params, resultType, body) = methodDef val newParams = for { p @ js.ParamDef(name, originalName, ptpe, mutable) <- params } yield { - js.ParamDef(name, originalName, newType(name, ptpe), mutable)(p.pos) + js.ParamDef(name, originalName, newType(name.name, ptpe), mutable)(p.pos) } val transformer = new ir.Transformers.Transformer { override def transform(tree: js.Tree): js.Tree = tree match { @@ -2292,7 +2293,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val innerBody = { withScopedVars( - thisLocalVarIdent := Some(thisLocalIdent) + thisLocalVarName := Some(thisLocalIdent.name) ) { js.Block(otherStats.map(genStat) :+ ( if (bodyIsStat) genStat(rhs) @@ -2371,7 +2372,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val thisLocalIdent = freshLocalIdent("this") withScopedVars( - thisLocalVarIdent := Some(thisLocalIdent) + thisLocalVarName := Some(thisLocalIdent.name) ) { val staticNamespace = if (namespace.isPrivate) js.MemberNamespace.PrivateStatic @@ -2782,7 +2783,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) case _ => mutatedLocalVars += sym js.Assign( - js.VarRef(encodeLocalSym(sym))(toIRType(sym.tpe)), + js.VarRef(encodeLocalSymName(sym))(toIRType(sym.tpe)), genRhs) } @@ -2832,15 +2833,14 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * is one. */ private def genThis()(implicit pos: Position): js.Tree = { - thisLocalVarIdent.fold[js.Tree] { + thisLocalVarName.fold[js.Tree] { if (tryingToGenMethodAsJSFunction) { throw new CancelGenMethodAsJSFunction( "Trying to generate `this` inside the body") } js.This()(currentThisType) - } { thisLocalIdent => - // .copy() to get the correct position - js.VarRef(thisLocalIdent.copy())(currentThisTypeNullable) + } { thisLocalName => + js.VarRef(thisLocalName)(currentThisTypeNullable) } } @@ -3093,7 +3093,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) implicit pos: Position): js.Tree = { val exceptIdent = freshLocalIdent("e") - val origExceptVar = js.VarRef(exceptIdent)(jstpe.AnyType) + val origExceptVar = js.VarRef(exceptIdent.name)(jstpe.AnyType) val mightCatchJavaScriptException = catches.exists { caseDef => caseDef.pat match { @@ -3486,7 +3486,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) List.newBuilder[(js.VarRef, jstpe.Type, js.LocalIdent, js.Tree)] for ((formalArgSym, arg) <- targetSyms.zip(values)) { - val formalArg = encodeLocalSym(formalArgSym) + val formalArgName = encodeLocalSymName(formalArgSym) val actualArg = genExpr(arg) /* #3267 The formal argument representing the special `this` of a @@ -3511,13 +3511,13 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) else actualArg actualArg match { - case js.VarRef(`formalArg`) => + case js.VarRef(`formalArgName`) => // This is trivial assignment, we don't need it case _ => mutatedLocalVars += formalArgSym - quadruplets += ((js.VarRef(formalArg)(tpe), tpe, - freshLocalIdent(formalArg.name.withPrefix("temp$")), + quadruplets += ((js.VarRef(formalArgName)(tpe), tpe, + freshLocalIdent(formalArgName.withPrefix("temp$")), fixedActualArg)) } } @@ -3538,7 +3538,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) yield js.VarDef(tempArg, NoOriginalName, argType, mutable = false, actualArg) val trueAssignments = for ((formalArg, argType, tempArg, _) <- quadruplets) - yield js.Assign(formalArg, js.VarRef(tempArg)(argType)) + yield js.Assign(formalArg, js.VarRef(tempArg.name)(argType)) tempAssignments ::: trueAssignments } } @@ -5002,7 +5002,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val callTrgIdent = freshLocalIdent() val callTrgVarDef = js.VarDef(callTrgIdent, NoOriginalName, receiverType, mutable = false, genExpr(receiver)) - val callTrg = js.VarRef(callTrgIdent)(receiverType) + val callTrg = js.VarRef(callTrgIdent.name)(receiverType) val arguments = args zip sym.tpe.params map { case (arg, param) => /* No need for enteringPosterasure, because value classes are not @@ -5453,7 +5453,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val fVarDef = js.VarDef(freshLocalIdent("f"), NoOriginalName, jstpe.AnyType, mutable = false, arg2) val keyVarIdent = freshLocalIdent("key") - val keyVarRef = js.VarRef(keyVarIdent)(jstpe.AnyType) + val keyVarRef = js.VarRef(keyVarIdent.name)(jstpe.AnyType) js.Block( objVarDef, fVarDef, @@ -5488,7 +5488,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val handlerVarDef = js.VarDef(freshLocalIdent("handler"), NoOriginalName, jstpe.AnyType, mutable = false, arg2) val exceptionVarIdent = freshLocalIdent("e") - val exceptionVarRef = js.VarRef(exceptionVarIdent)(jstpe.AnyType) + val exceptionVarRef = js.VarRef(exceptionVarIdent.name)(jstpe.AnyType) js.Block( bodyVarDef, handlerVarDef, @@ -6465,7 +6465,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) // Gen the inlined target method body val genMethodDef = { genMethodWithCurrentLocalNameScope(consumeDelambdafyTarget(target), - initThisLocalVarIdent = thisFormalCapture.map(_.name)) + initThisLocalVarName = thisFormalCapture.map(_.name.name)) } val js.MethodDef(methodFlags, _, _, methodParams, _, methodBody) = genMethodDef @@ -6777,7 +6777,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) // Utilities --------------------------------------------------------------- def genVarRef(sym: Symbol)(implicit pos: Position): js.VarRef = - js.VarRef(encodeLocalSym(sym))(toIRType(sym.tpe)) + js.VarRef(encodeLocalSymName(sym))(toIRType(sym.tpe)) def genParamDef(sym: Symbol): js.ParamDef = genParamDef(sym, toIRType(sym.tpe)) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala index bf668424ae..603a6286d3 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala @@ -684,7 +684,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { val superClass = { val superClassSym = currentClassSym.superClass if (isNestedJSClass(superClassSym)) { - js.VarRef(js.LocalIdent(JSSuperClassParamName))(jstpe.AnyType) + js.VarRef(JSSuperClassParamName)(jstpe.AnyType) } else { js.LoadJSConstructor(encodeClassName(superClassSym)) } @@ -1053,7 +1053,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { def genArgRef(index: Int)(implicit pos: Position): js.Tree = { if (index < minArgc) - js.VarRef(js.LocalIdent(fixedParamNames(index)))(jstpe.AnyType) + js.VarRef(fixedParamNames(index))(jstpe.AnyType) else js.JSSelect(genRestArgRef(), js.IntLiteral(index - minArgc)) } @@ -1073,16 +1073,16 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { def genRestArgRef()(implicit pos: Position): js.Tree = { assert(needsRestParam, s"trying to generate a reference to non-existent rest param at $pos") - js.VarRef(js.LocalIdent(restParamName))(jstpe.AnyType) + js.VarRef(restParamName)(jstpe.AnyType) } def genAllArgsRefsForForwarder()(implicit pos: Position): List[js.TreeOrJSSpread] = { val fixedArgRefs = fixedParamNames.toList.map { paramName => - js.VarRef(js.LocalIdent(paramName))(jstpe.AnyType) + js.VarRef(paramName)(jstpe.AnyType) } if (needsRestParam) { - val restArgRef = js.VarRef(js.LocalIdent(restParamName))(jstpe.AnyType) + val restArgRef = js.VarRef(restParamName)(jstpe.AnyType) fixedArgRefs :+ js.JSSpread(restArgRef) } else { fixedArgRefs diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala index 51ec7de709..c4d8188c44 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala @@ -256,7 +256,10 @@ trait JSEncoding[G <: Global with Singleton] extends SubComponent { } } - def encodeLocalSym(sym: Symbol)(implicit pos: Position): js.LocalIdent = { + def encodeLocalSym(sym: Symbol)(implicit pos: Position): js.LocalIdent = + js.LocalIdent(encodeLocalSymName(sym)) + + def encodeLocalSymName(sym: Symbol): LocalName = { /* The isValueParameter case is necessary to work around an internal bug * of scalac: for some @varargs methods, the owner of some parameters is * the enclosing class rather the method, so !sym.owner.isClass fails. @@ -266,7 +269,7 @@ trait JSEncoding[G <: Global with Singleton] extends SubComponent { require(sym.isValueParameter || (!sym.owner.isClass && sym.isTerm && !sym.isMethod && !sym.isModule), "encodeLocalSym called with non-local symbol: " + sym) - js.LocalIdent(localSymbolName(sym)) + localSymbolName(sym) } def encodeClassType(sym: Symbol): jstpe.Type = { diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala index 1c159fe9b2..10847a8ba3 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala @@ -524,9 +524,9 @@ object Hashers { mixTag(TagClassOf) mixTypeRef(typeRef) - case VarRef(ident) => + case VarRef(name) => mixTag(TagVarRef) - mixLocalIdent(ident) + mixName(name) mixType(tree.tpe) case This() => diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala index b5bfccc64e..af6ed1023c 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -869,8 +869,8 @@ object Printers { // Atomic expressions - case VarRef(ident) => - print(ident) + case VarRef(name) => + print(name) case This() => print("this") diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index 11bafee148..b8fd8790b2 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -551,9 +551,9 @@ object Serializers { writeTagAndPos(TagClassOf) writeTypeRef(typeRef) - case VarRef(ident) => + case VarRef(name) => writeTagAndPos(TagVarRef) - writeLocalIdent(ident) + writeName(name) writeType(tree.tpe) case This() => @@ -1388,7 +1388,10 @@ object Serializers { case TagClassOf => ClassOf(readTypeRef()) case TagVarRef => - VarRef(readLocalIdent())(readType()) + val name = + if (hacks.use17) readLocalIdent().name + else readLocalName() + VarRef(name)(readType()) case TagThis => val tpe = readType() diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index 4b1e58ce12..dfb4671d26 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -104,13 +104,13 @@ object Trees { implicit val pos: Position) extends Tree { val tpe = VoidType - def ref(implicit pos: Position): VarRef = VarRef(name)(vtpe) + def ref(implicit pos: Position): VarRef = VarRef(name.name)(vtpe) } sealed case class ParamDef(name: LocalIdent, originalName: OriginalName, ptpe: Type, mutable: Boolean)( implicit val pos: Position) extends IRNode { - def ref(implicit pos: Position): VarRef = VarRef(name)(ptpe) + def ref(implicit pos: Position): VarRef = VarRef(name.name)(ptpe) } // Control flow constructs @@ -1088,7 +1088,7 @@ object Trees { // Atomic expressions - sealed case class VarRef(ident: LocalIdent)(val tpe: Type)( + sealed case class VarRef(name: LocalName)(val tpe: Type)( implicit val pos: Position) extends AssignLhs sealed case class This()(val tpe: Type)(implicit val pos: Position) diff --git a/ir/shared/src/test/scala/org/scalajs/ir/HashersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/HashersTest.scala index c92f911f11..612174ffff 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/HashersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/HashersTest.scala @@ -91,7 +91,7 @@ class HashersTest { ) test( - "309805e5680ffa1804811ff5c9ebc77e91846957", + "82df9d6beb7df0ee9f501380323bdb2038cc50cb", MethodDef(MemberFlags.empty, mIIMethodName, NON, List(ParamDef("x", NON, IntType, mutable = false)), IntType, Some(bodyWithInterestingStuff))( @@ -106,7 +106,7 @@ class HashersTest { } test( - "c0f1ef1b22fd1cfdc9bba78bf3e0f433e9f82fc1", + "d0fa6c753502e3d1df34e53ca6f6afb5cbdcd9d4", JSMethodDef(MemberFlags.empty, s("m"), List(ParamDef("x", NON, AnyType, mutable = false)), None, bodyWithInterestingStuff)( diff --git a/ir/shared/src/test/scala/org/scalajs/ir/TestIRBuilder.scala b/ir/shared/src/test/scala/org/scalajs/ir/TestIRBuilder.scala index d89d8050a7..8c578c296e 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/TestIRBuilder.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/TestIRBuilder.scala @@ -37,6 +37,8 @@ object TestIRBuilder { val NoOptHints = OptimizerHints.empty // String -> Name conversions + implicit def string2localName(name: String): LocalName = + LocalName(name) implicit def string2simpleFieldName(name: String): SimpleFieldName = SimpleFieldName(name) implicit def string2className(name: String): ClassName = @@ -79,7 +81,7 @@ object TestIRBuilder { def d(value: Double): DoubleLiteral = DoubleLiteral(value) def s(value: String): StringLiteral = StringLiteral(value) - def ref(ident: LocalIdent, tpe: Type): VarRef = VarRef(ident)(tpe) + def ref(name: LocalName, tpe: Type): VarRef = VarRef(name)(tpe) def arrayType(base: NonArrayTypeRef, dimensions: Int): ArrayType = ArrayType(ArrayTypeRef(base, dimensions), nullable = true) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index f2847be772..6abeb4dd36 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -450,7 +450,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { implicit pos: Position): js.Ident = { val recIdent = (tree.record: @unchecked) match { - case VarRef(ident) => transformLocalVarIdent(ident) + case record: VarRef => transformLocalVarRefIdent(record) case Transient(JSVarRef(ident, _)) => ident case record: RecordSelect => makeRecordFieldIdentForVarRef(record) } @@ -1456,7 +1456,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { lhs.tpe match { case RecordType(fields) => val ident = (lhs: @unchecked) match { - case VarRef(ident) => transformLocalVarIdent(ident) + case lhs: VarRef => transformLocalVarRefIdent(lhs) case Transient(JSVarRef(ident, _)) => ident } val elems = extractRecordElems(rhs) @@ -1495,9 +1495,9 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case RecordValue(_, elems) => elems - case VarRef(ident) => - val jsIdent = transformLocalVarIdent(ident) - val mutable = env.isLocalMutable(ident) + case recordTree @ VarRef(name) => + val jsIdent = transformLocalVarRefIdent(recordTree) + val mutable = env.isLocalMutable(name) for (RecordType.Field(fName, fOrigName, fTpe, _) <- recordType.fields) yield Transient(JSVarRef(makeRecordFieldIdent(jsIdent, fName, fOrigName), mutable)(fTpe)) @@ -3014,10 +3014,10 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { // Atomic expressions - case VarRef(name) => + case tree @ VarRef(name) => env.varKind(name) match { case VarKind.Mutable | VarKind.Immutable => - js.VarRef(transformLocalVarIdent(name)) + js.VarRef(transformLocalVarRefIdent(tree)) case VarKind.ThisAlias => js.This() @@ -3026,7 +3026,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { fileLevelVar(VarField.thiz) case VarKind.ClassCapture => - fileLevelVar(VarField.cc, genName(name.name)) + fileLevelVar(VarField.cc, genName(name)) } case Transient(JSVarRef(name, _)) => @@ -3165,7 +3165,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { * For other usages (notably ApplyDynamicImport), we generate the * body, so it's easy to ensure no collision. */ - def permitImplicitNameCapture = forceName.forall(_ == name.name) + def permitImplicitNameCapture = forceName.forall(_ == name) env.varKind(name) match { case VarKind.Immutable if !env.inLoopForVarCapture && permitImplicitNameCapture => @@ -3255,6 +3255,9 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { private def transformLabelIdent(ident: LabelIdent): js.Ident = js.Ident(genName(ident.name))(ident.pos) + private def transformLocalVarRefIdent(varRef: VarRef): js.Ident = + js.Ident(transformLocalName(varRef.name))(varRef.pos) + private def transformLocalVarIdent(ident: LocalIdent): js.Ident = js.Ident(transformLocalName(ident.name))(ident.pos) @@ -3433,13 +3436,13 @@ private object FunctionEmitter { defaultContinueTargets: Set[LabelName], val inLoopForVarCapture: Boolean ) { - def varKind(ident: LocalIdent): VarKind = { + def varKind(name: LocalName): VarKind = { // If we do not know the var, it must be a JS class capture. - vars.getOrElse(ident.name, VarKind.ClassCapture) + vars.getOrElse(name, VarKind.ClassCapture) } - def isLocalMutable(ident: LocalIdent): Boolean = - VarKind.Mutable == varKind(ident) + def isLocalMutable(name: LocalName): Boolean = + VarKind.Mutable == varKind(name) def lhsForLabeledExpr(label: LabelIdent): Lhs = labeledExprLHSes(label.name) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala index 43409fa133..44d18110fb 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala @@ -1007,7 +1007,7 @@ class ClassEmitter(coreSpec: CoreSpec) { val helperBuilder = new CustomJSHelperBuilder.WithTreeEval() { protected def evalTreeAtCallSite(tree: Tree, expectedType: Type): Unit = tree match { - case VarRef(LocalIdent(localName)) if classCaptureParamsOfTypeAny.contains(localName) => + case VarRef(localName) if classCaptureParamsOfTypeAny.contains(localName) => /* Common shape for the `jsSuperClass` value * We can only deal with class captures of type `AnyType` in this way, * since otherwise we might need `adapt` to box the values. diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index 83a6854719..85cc991c39 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -464,7 +464,7 @@ private class FunctionEmitter private ( val RecordSelect(record, field) = tree val recordStorage = record match { - case VarRef(LocalIdent(name)) => + case VarRef(name) => lookupLocal(name) case record: RecordSelect => lookupRecordSelect(record) @@ -778,7 +778,7 @@ private class FunctionEmitter private ( markPosition(tree) fb += wa.Call(helperID) - case VarRef(LocalIdent(name)) => + case VarRef(name) => genTree(rhs, lhs.tpe) markPosition(tree) genWriteToStorage(lookupLocal(name)) @@ -2336,7 +2336,7 @@ private class FunctionEmitter private ( } private def genVarRef(tree: VarRef): Type = { - val VarRef(LocalIdent(name)) = tree + val VarRef(name) = tree markPosition(tree) if (tree.tpe == NothingType) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index 1c4bd11d74..919e2262c6 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -682,7 +682,7 @@ private final class ClassDefChecker(classDef: ClassDef, checkTree(rhs, env) lhs match { - case VarRef(LocalIdent(name)) => + case VarRef(name) => if (env.locals.get(name).exists(!_.mutable)) reportError(i"Assignment to immutable variable $name.") @@ -964,7 +964,7 @@ private final class ClassDefChecker(classDef: ClassDef, // Atomic expressions - case VarRef(LocalIdent(name)) => + case VarRef(name) => env.locals.get(name).fold[Unit] { reportError(i"Cannot find variable $name in scope") } { localDef => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index 38a02b54ad..5b2cf7ad5e 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -1009,7 +1009,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: case lit: Literal => Some(FieldBody.Literal(lit)) - case VarRef(LocalIdent(valName)) => + case VarRef(valName) => paramBodies.get(valName) case LoadModule(moduleClassName) => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 0f519afdb2..b280a7776a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -923,7 +923,7 @@ private[optimizer] abstract class OptimizerCore( case tree: Block => pretransformBlock(tree)(cont) - case VarRef(LocalIdent(name)) => + case VarRef(name) => val localDef = scope.env.localDefs.getOrElse(name, { throw new AssertionError( s"Cannot find local def '$name' at $pos\n" + @@ -1427,8 +1427,7 @@ private[optimizer] abstract class OptimizerCore( replacement match { case ReplaceWithRecordVarRef(name, recordType, used, cancelFun) => used.value = used.value.inc - PreTransRecordTree( - VarRef(LocalIdent(name))(recordType), tpe, cancelFun) + PreTransRecordTree(VarRef(name)(recordType), tpe, cancelFun) case InlineClassInstanceReplacement(structure, fieldLocalDefs, cancelFun) => val recordType = structure.recordType @@ -1828,8 +1827,8 @@ private[optimizer] abstract class OptimizerCore( override def traverse(tree: Tree): Unit = { super.traverse(tree) tree match { - case Assign(VarRef(ident), _) => mutatedLocalVars += ident.name - case _ => () + case Assign(VarRef(name), _) => mutatedLocalVars += name + case _ => () } } } @@ -1851,10 +1850,10 @@ private[optimizer] abstract class OptimizerCore( implicit val pos = body.pos body match { - case VarRef(ident) => - if (ident.name == valName) + case VarRef(name) => + if (name == valName) Success(valTree) - else if (valTreeInfo.mutatedLocalVars.contains(ident.name)) + else if (valTreeInfo.mutatedLocalVars.contains(name)) Failed else NotFoundPureSoFar @@ -2224,9 +2223,9 @@ private[optimizer] abstract class OptimizerCore( MethodIdent(methodName), normalizedParams.zip(referenceArgs).map { case ((name, ptpe), AsInstanceOf(_, castTpe)) => - AsInstanceOf(VarRef(LocalIdent(name))(ptpe), castTpe) + AsInstanceOf(VarRef(name)(ptpe), castTpe) case ((name, ptpe), _) => - VarRef(LocalIdent(name))(ptpe) + VarRef(name)(ptpe) } )(body.tpe) @@ -2477,7 +2476,7 @@ private[optimizer] abstract class OptimizerCore( cancelFun() used.value = used.value.inc - val module = VarRef(LocalIdent(moduleVarName))(AnyType) + val module = VarRef(moduleVarName)(AnyType) path.foldLeft[Tree](module) { (inner, pathElem) => JSSelect(inner, StringLiteral(pathElem)) } @@ -2691,7 +2690,7 @@ private[optimizer] abstract class OptimizerCore( optQualDeclaredType = Some(optReceiver.get._1), field, isLhsOfAssign = false)(cont) - case Assign(lhs @ Select(This(), field), VarRef(LocalIdent(rhsName))) + case Assign(lhs @ Select(This(), field), VarRef(rhsName)) if formals.size == 1 && formals.head.name.name == rhsName => assert(isStat, "Found Assign in expression position") assert(optReceiver.isDefined, @@ -3501,10 +3500,10 @@ private[optimizer] abstract class OptimizerCore( * the equals() method has been inlined as a reference * equality test. */ - case (BinaryOp(BinaryOp.===, VarRef(lhsIdent), Null()), - BinaryOp(BinaryOp.===, VarRef(rhsIdent), Null()), + case (BinaryOp(BinaryOp.===, VarRef(lhsName), Null()), + BinaryOp(BinaryOp.===, VarRef(rhsName), Null()), BinaryOp(BinaryOp.===, MaybeCast(l: VarRef), r: VarRef)) - if l.ident == lhsIdent && r.ident == rhsIdent => + if l.name == lhsName && r.name == rhsName => BinaryOp(BinaryOp.===, l, r)(elsep.pos) // Example: (x > y) || (x == y) -> (x >= y) @@ -3601,7 +3600,7 @@ private[optimizer] abstract class OptimizerCore( value) withBinding(rtLongBinding) { (scope1, cont1) => implicit val scope = scope1 - val tRef = VarRef(LocalIdent(tName))(rtLongClassType) + val tRef = VarRef(tName)(rtLongClassType) val newTree = New(LongImpl.RuntimeLongClass, MethodIdent(LongImpl.initFromParts), List(Apply(ApplyFlags.empty, tRef, MethodIdent(LongImpl.lo), Nil)(IntType), @@ -5497,7 +5496,7 @@ private[optimizer] abstract class OptimizerCore( buildInner(LocalDef(value.tpe, false, ReplaceWithConstant(literal)), cont) - case PreTransTree(VarRef(LocalIdent(refName)), _) + case PreTransTree(VarRef(refName), _) if !localIsMutable(refName) => buildInner(LocalDef(value.tpe, false, ReplaceWithVarRef(refName, newSimpleState(UsedAtLeastOnce))), cont) @@ -5862,7 +5861,7 @@ private[optimizer] object OptimizerCore { def newReplacement(implicit pos: Position): Tree = this.replacement match { case ReplaceWithVarRef(name, used) => used.value = used.value.inc - VarRef(LocalIdent(name))(tpe.base) + VarRef(name)(tpe.base) /* Allocate an instance of RuntimeLong on the fly. * See the comment in finishTransformExpr about why it is desirable and @@ -5871,7 +5870,7 @@ private[optimizer] object OptimizerCore { case ReplaceWithRecordVarRef(name, recordType, used, _) if tpe.base == ClassType(LongImpl.RuntimeLongClass, nullable = false) => used.value = used.value.inc - createNewLong(VarRef(LocalIdent(name))(recordType)) + createNewLong(VarRef(name)(recordType)) case ReplaceWithRecordVarRef(_, _, _, cancelFun) => cancelFun() @@ -6755,7 +6754,7 @@ private[optimizer] object OptimizerCore { case ApplyStatically(_, This(), className, method, args) => args.size == params.size && args.zip(params).forall { - case (VarRef(LocalIdent(aname)), ParamDef(LocalIdent(pname), _, _, _)) => + case (VarRef(aname), ParamDef(LocalIdent(pname), _, _, _)) => aname == pname case _ => false @@ -6766,7 +6765,7 @@ private[optimizer] object OptimizerCore { !method.name.isReflectiveProxy && (args.size == params.size) && args.zip(params).forall { - case (MaybeUnbox(VarRef(LocalIdent(aname)), _), + case (MaybeUnbox(VarRef(aname), _), ParamDef(LocalIdent(pname), _, _, _)) => aname == pname case _ => false } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala index 08838c0627..e80affe3bc 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala @@ -268,7 +268,7 @@ class OptimizerTest { */ val matchResult1 = LabelIdent("matchResult1") - val x1 = LocalIdent("x1") + val x1 = LocalName("x1") val matchAlts1 = LabelIdent("matchAlts1") val matchAlts2 = LabelIdent("matchAlts2") @@ -475,7 +475,7 @@ class OptimizerTest { traverseMainMethod(moduleSet) { case c: Closure => c.captureValues match { - case List(VarRef(LocalIdent(name))) => + case List(VarRef(name)) => assertEquals(s"unexpected capture name: $c", c.captureParams.head.name.name, name) case _ => diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 133cf0906a..7c373ae05f 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -8,6 +8,7 @@ object BinaryIncompatibilities { // !!! Breaking, OK in minor release ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$JSLinkingInfo"), ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$JSLinkingInfo$"), + ProblemFilters.exclude[Problem]("org.scalajs.ir.Trees#VarRef.*"), ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.ir.Trees#*.tpe"), ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Types$NoType$"), diff --git a/project/JavaLangObject.scala b/project/JavaLangObject.scala index 789d57df73..eb3eedabef 100644 --- a/project/JavaLangObject.scala +++ b/project/JavaLangObject.scala @@ -87,7 +87,7 @@ object JavaLangObject { Some { BinaryOp(BinaryOp.===, This()(ThisType), - VarRef(LocalIdent(LocalName("that")))(AnyType)) + VarRef(LocalName("that"))(AnyType)) })(OptimizerHints.empty.withInline(true), Unversioned), /* protected def clone(): Object = diff --git a/project/JavalibIRCleaner.scala b/project/JavalibIRCleaner.scala index dc0d38d249..6f014a4e93 100644 --- a/project/JavalibIRCleaner.scala +++ b/project/JavalibIRCleaner.scala @@ -215,7 +215,7 @@ final class JavalibIRCleaner(baseDirectoryURI: URI) { def argsCorrespond(args: List[Tree], paramDefs: List[ParamDef]): Boolean = { (args.size == paramDefs.size) && args.zip(paramDefs).forall { - case (VarRef(LocalIdent(argName)), ParamDef(LocalIdent(paramName), _, _, _)) => + case (VarRef(argName), ParamDef(LocalIdent(paramName), _, _, _)) => argName == paramName case _ => false From 2ba9ea710e86b1aa1b62c2e35656faabc3a55754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 21 Dec 2024 11:55:12 +0100 Subject: [PATCH 047/121] Remove `LabelIdent`. Directly store `LabelName` instead. The position of labels is always irrelevant, as they are synthetic. Therefore, storing them through `LabelIdent`s is wasteful. --- .../org/scalajs/nscplugin/GenJSCode.scala | 28 ++++++------- .../org/scalajs/nscplugin/JSEncoding.scala | 15 +++---- .../nscplugin/test/OptimizationTest.scala | 4 +- .../main/scala/org/scalajs/ir/Hashers.scala | 9 +---- .../main/scala/org/scalajs/ir/Printers.scala | 4 -- .../scala/org/scalajs/ir/Serializers.scala | 24 +++++------ .../src/main/scala/org/scalajs/ir/Trees.scala | 7 +--- .../scala/org/scalajs/ir/TestIRBuilder.scala | 4 +- .../backend/emitter/FunctionEmitter.scala | 40 +++++++++---------- .../backend/wasmemitter/FunctionEmitter.scala | 4 +- .../linker/checker/ClassDefChecker.scala | 10 ++--- .../scalajs/linker/checker/IRChecker.scala | 4 +- .../frontend/optimizer/OptimizerCore.scala | 12 +++--- .../org/scalajs/linker/OptimizerTest.scala | 6 +-- .../linker/testutils/TestIRBuilder.scala | 2 - project/BinaryIncompatibilities.scala | 10 +++++ 16 files changed, 86 insertions(+), 97 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 23981dcaba..674ffc3ba6 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -29,6 +29,7 @@ import org.scalajs.ir import org.scalajs.ir.{Trees => js, Types => jstpe, ClassKind, Hashers, OriginalName} import org.scalajs.ir.Names.{ LocalName, + LabelName, SimpleFieldName, FieldName, SimpleMethodName, @@ -2890,8 +2891,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val labelParamSyms = tree.params.map(_.symbol) val info = new EnclosingLabelDefInfoWithResultAsAssigns(labelParamSyms) - val labelIdent = encodeLabelSym(sym) - val labelName = labelIdent.name + val labelName = encodeLabelSym(sym) val transformedRhs = withScopedVars( enclosingLabelDefInfos := enclosingLabelDefInfos.get + (sym -> info) @@ -2906,7 +2906,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) */ object ReturnFromThisLabel { def unapply(tree: js.Return): Option[js.Tree] = { - if (tree.label.name == labelName) Some(exprToStat(tree.expr)) + if (tree.label == labelName) Some(exprToStat(tree.expr)) else None } } @@ -2915,7 +2915,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) if (transformedRhs.tpe == jstpe.NothingType) { // In this case, we do not need the outer block label js.While(js.BooleanLiteral(true), { - js.Labeled(labelIdent, jstpe.VoidType, { + js.Labeled(labelName, jstpe.VoidType, { transformedRhs match { // Eliminate a trailing return@lab case js.Block(stats :+ ReturnFromThisLabel(exprAsStat)) => @@ -2927,17 +2927,17 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) }) } else { // When all else has failed, we need the full machinery - val blockLabelIdent = freshLabelIdent("block") + val blockLabelName = freshLabelName("block") val bodyType = if (isStat) jstpe.VoidType else toIRType(tree.tpe) - js.Labeled(blockLabelIdent, bodyType, { + js.Labeled(blockLabelName, bodyType, { js.While(js.BooleanLiteral(true), { - js.Labeled(labelIdent, jstpe.VoidType, { + js.Labeled(labelName, jstpe.VoidType, { if (isStat) - js.Block(transformedRhs, js.Return(js.Skip(), blockLabelIdent)) + js.Block(transformedRhs, js.Return(js.Skip(), blockLabelName)) else - js.Return(transformedRhs, blockLabelIdent) + js.Return(transformedRhs, blockLabelName) }) }) }) @@ -3901,11 +3901,11 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) var clauses: List[(List[js.MatchableLiteral], js.Tree)] = Nil var optElseClause: Option[js.Tree] = None - var optElseClauseLabel: Option[js.LabelIdent] = None + var optElseClauseLabel: Option[LabelName] = None def genJumpToElseClause(implicit pos: ir.Position): js.Tree = { if (optElseClauseLabel.isEmpty) - optElseClauseLabel = Some(freshLabelIdent("default")) + optElseClauseLabel = Some(freshLabelName("default")) js.Return(js.Skip(), optElseClauseLabel.get) } @@ -4025,7 +4025,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) optElseClauseLabel.fold[js.Tree] { buildMatch(clauses.reverse, elseClause, resultType) } { elseClauseLabel => - val matchResultLabel = freshLabelIdent("matchResult") + val matchResultLabel = freshLabelName("matchResult") val patchedClauses = for ((alts, body) <- clauses) yield { implicit val pos = body.pos val newBody = js.Return(body, matchResultLabel) @@ -4366,7 +4366,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * all jumps to case labels are already caught upstream by `genCaseBody()` * inside `genTranslatedMatch()`. */ - private def genOptimizedCaseLabeled(label: js.LabelIdent, + private def genOptimizedCaseLabeled(label: LabelName, translatedBody: js.Tree, returnCount: Int)( implicit pos: Position): js.Tree = { @@ -4423,7 +4423,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * !!! There is quite of bit of code duplication with * OptimizerCore.tryOptimizePatternMatch. */ - def genOptimizedMatchEndLabeled(label: js.LabelIdent, tpe: jstpe.Type, + def genOptimizedMatchEndLabeled(label: LabelName, tpe: jstpe.Type, translatedCases: List[js.Tree], returnCount: Int)( implicit pos: Position): js.Tree = { def default: js.Tree = diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala index c4d8188c44..b1b4c888f5 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala @@ -91,7 +91,7 @@ trait JSEncoding[G <: Global with Singleton] extends SubComponent { case None => inner case Some(labelName) => - js.Labeled(js.LabelIdent(labelName), tpe, inner) + js.Labeled(labelName, tpe, inner) } } } @@ -151,29 +151,26 @@ trait JSEncoding[G <: Global with Singleton] extends SubComponent { private def freshLabelName(base: LabelName): LabelName = freshNameGeneric(base, usedLabelNames)(_.withSuffix(_)) - private def freshLabelName(base: String): LabelName = + def freshLabelName(base: String): LabelName = freshLabelName(LabelName(base)) - def freshLabelIdent(base: String)(implicit pos: ir.Position): js.LabelIdent = - js.LabelIdent(freshLabelName(base)) - private def labelSymbolName(sym: Symbol): LabelName = labelSymbolNames.getOrElseUpdate(sym, freshLabelName(sym.name.toString)) - def getEnclosingReturnLabel()(implicit pos: ir.Position): js.LabelIdent = { + def getEnclosingReturnLabel()(implicit pos: Position): LabelName = { val box = returnLabelName.get if (box == null) throw new IllegalStateException(s"No enclosing returnable scope at $pos") if (box.value.isEmpty) box.value = Some(freshLabelName("_return")) - js.LabelIdent(box.value.get) + box.value.get } // Encoding methods ---------------------------------------------------------- - def encodeLabelSym(sym: Symbol)(implicit pos: Position): js.LabelIdent = { + def encodeLabelSym(sym: Symbol): LabelName = { require(sym.isLabel, "encodeLabelSym called with non-label symbol: " + sym) - js.LabelIdent(labelSymbolName(sym)) + labelSymbolName(sym) } def encodeFieldSym(sym: Symbol)(implicit pos: Position): js.FieldIdent = { diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala index 47f843c696..24914ca07b 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala @@ -250,7 +250,7 @@ class OptimizationTest extends JSASTTest { } } """.hasNot("non-return labeled block") { - case js.Labeled(name, _, _) if !name.name.nameString.startsWith("_return") => + case js.Labeled(name, _, _) if !name.nameString.startsWith("_return") => } } @@ -323,7 +323,7 @@ class OptimizationTest extends JSASTTest { } } """.hasNot("non-return labeled block") { - case js.Labeled(name, _, _) if !name.name.nameString.startsWith("_return") => + case js.Labeled(name, _, _) if !name.nameString.startsWith("_return") => } } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala index 10847a8ba3..a653c15fce 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala @@ -185,7 +185,7 @@ object Hashers { case Labeled(label, tpe, body) => mixTag(TagLabeled) - mixLabelIdent(label) + mixName(label) mixType(tpe) mixTree(body) @@ -197,7 +197,7 @@ object Hashers { case Return(expr, label) => mixTag(TagReturn) mixTree(expr) - mixLabelIdent(label) + mixName(label) case If(cond, thenp, elsep) => mixTag(TagIf) @@ -646,11 +646,6 @@ object Hashers { mixName(ident.name) } - def mixLabelIdent(ident: LabelIdent): Unit = { - mixPos(ident.pos) - mixName(ident.name) - } - def mixSimpleFieldIdent(ident: SimpleFieldIdent): Unit = { mixPos(ident.pos) mixName(ident.name) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala index af6ed1023c..4c4401a406 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -122,7 +122,6 @@ object Printers { def printAnyNode(node: IRNode): Unit = { node match { case node: LocalIdent => print(node) - case node: LabelIdent => print(node) case node: SimpleFieldIdent => print(node) case node: FieldIdent => print(node) case node: MethodIdent => print(node) @@ -1138,9 +1137,6 @@ object Printers { def print(ident: LocalIdent): Unit = print(ident.name) - def print(ident: LabelIdent): Unit = - print(ident.name) - def print(ident: SimpleFieldIdent): Unit = print(ident.name) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index b8fd8790b2..da0dc4d01e 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -265,7 +265,7 @@ object Serializers { case Labeled(label, tpe, body) => writeTagAndPos(TagLabeled) - writeLabelIdent(label); writeType(tpe); writeTree(body) + writeName(label); writeType(tpe); writeTree(body) case Assign(lhs, rhs) => writeTagAndPos(TagAssign) @@ -273,7 +273,7 @@ object Serializers { case Return(expr, label) => writeTagAndPos(TagReturn) - writeTree(expr); writeLabelIdent(label) + writeTree(expr); writeName(label) case If(cond, thenp, elsep) => writeTagAndPos(TagIf) @@ -786,11 +786,6 @@ object Serializers { writeName(ident.name) } - def writeLabelIdent(ident: LabelIdent): Unit = { - writePosition(ident.pos) - writeName(ident.name) - } - def writeSimpleFieldIdent(ident: SimpleFieldIdent): Unit = { writePosition(ident.pos) writeName(ident.name) @@ -1135,7 +1130,7 @@ object Serializers { case TagVarDef => VarDef(readLocalIdent(), readOriginalName(), readType(), readBoolean(), readTree()) case TagSkip => Skip() case TagBlock => Block(readTrees()) - case TagLabeled => Labeled(readLabelIdent(), readType(), readTree()) + case TagLabeled => Labeled(readLabelName(), readType(), readTree()) case TagAssign => val lhs0 = readTree() @@ -1155,7 +1150,7 @@ object Serializers { Assign(lhs.asInstanceOf[AssignLhs], rhs) - case TagReturn => Return(readTree(), readLabelIdent()) + case TagReturn => Return(readTree(), readLabelName()) case TagIf => If(readTree(), readTree(), readTree())(readType()) case TagWhile => While(readTree(), readTree()) @@ -2166,11 +2161,6 @@ object Serializers { LocalIdent(readLocalName()) } - def readLabelIdent(): LabelIdent = { - implicit val pos = readPosition() - LabelIdent(readLabelName()) - } - def readFieldIdent(): FieldIdent = { // For historical reasons, the className comes *before* the position val className = readClassName() @@ -2399,6 +2389,12 @@ object Serializers { } private def readLabelName(): LabelName = { + /* Before 1.18, `LabelName`s were always wrapped in `LabelIdent`s, whose + * encoding was a `Position` followed by the actual `LabelName`. + */ + if (hacks.use17) + readPosition() // intentional discard + val i = readInt() val existing = labelNames(i) if (existing ne null) { diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index dfb4671d26..917df781ed 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -56,9 +56,6 @@ object Trees { sealed case class LocalIdent(name: LocalName)(implicit val pos: Position) extends IRNode - sealed case class LabelIdent(name: LabelName)(implicit val pos: Position) - extends IRNode - sealed case class SimpleFieldIdent(name: SimpleFieldName)(implicit val pos: Position) extends IRNode @@ -150,7 +147,7 @@ object Trees { def unapply(block: Block): Some[List[Tree]] = Some(block.stats) } - sealed case class Labeled(label: LabelIdent, tpe: Type, body: Tree)( + sealed case class Labeled(label: LabelName, tpe: Type, body: Tree)( implicit val pos: Position) extends Tree sealed trait AssignLhs extends Tree @@ -160,7 +157,7 @@ object Trees { val tpe = VoidType } - sealed case class Return(expr: Tree, label: LabelIdent)( + sealed case class Return(expr: Tree, label: LabelName)( implicit val pos: Position) extends Tree { val tpe = NothingType } diff --git a/ir/shared/src/test/scala/org/scalajs/ir/TestIRBuilder.scala b/ir/shared/src/test/scala/org/scalajs/ir/TestIRBuilder.scala index 8c578c296e..44aed0ef4f 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/TestIRBuilder.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/TestIRBuilder.scala @@ -39,6 +39,8 @@ object TestIRBuilder { // String -> Name conversions implicit def string2localName(name: String): LocalName = LocalName(name) + implicit def string2labelName(name: String): LabelName = + LabelName(name) implicit def string2simpleFieldName(name: String): SimpleFieldName = SimpleFieldName(name) implicit def string2className(name: String): ClassName = @@ -47,8 +49,6 @@ object TestIRBuilder { // String -> Ident conversions implicit def string2localIdent(name: String): LocalIdent = LocalIdent(LocalName(name)) - implicit def string2labelIdent(name: String): LabelIdent = - LabelIdent(LabelName(name)) implicit def string2simpleFieldIdent(name: String): SimpleFieldIdent = SimpleFieldIdent(SimpleFieldName(name)) implicit def string2classIdent(name: String): ClassIdent = diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index 6abeb4dd36..89516298bb 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -710,12 +710,12 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case Labeled(label, _, innerBody) => val innerBodyEnv = loopEnv .withLabeledExprLHS(label, Lhs.Discard) - .withTurnLabelIntoContinue(label.name) + .withTurnLabelIntoContinue(label) .withDefaultBreakTargets(tailPosLabels) - .withDefaultContinueTargets(Set(label.name)) + .withDefaultContinueTargets(Set(label)) val newBody = - pushLhsInto(Lhs.Discard, innerBody, Set(label.name))(innerBodyEnv) - val optLabel = if (usedLabels.contains(label.name)) + pushLhsInto(Lhs.Discard, innerBody, Set(label))(innerBodyEnv) + val optLabel = if (usedLabels.contains(label)) Some(transformLabelIdent(label)) else None @@ -1536,27 +1536,27 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { } } - def doReturnToLabel(l: LabelIdent): js.Tree = { - val newLhs = env.lhsForLabeledExpr(l) + def doReturnToLabel(label: LabelName): js.Tree = { + val newLhs = env.lhsForLabeledExpr(label) if (newLhs.hasNothingType) { /* A touch of peephole dead code elimination. * This is actually necessary to avoid dangling breaks to eliminated * labels, as in issue #2307. */ pushLhsInto(newLhs, rhs, tailPosLabels) - } else if (tailPosLabels.contains(l.name)) { + } else if (tailPosLabels.contains(label)) { pushLhsInto(newLhs, rhs, tailPosLabels) } else { val body = pushLhsInto(newLhs, rhs, Set.empty) - val jump = if (env.isDefaultBreakTarget(l.name)) { + val jump = if (env.isDefaultBreakTarget(label)) { js.Break(None) - } else if (env.isDefaultContinueTarget(l.name)) { + } else if (env.isDefaultContinueTarget(label)) { js.Continue(None) } else { - usedLabels += l.name - val transformedLabel = Some(transformLabelIdent(l)) - if (env.isLabelTurnedIntoContinue(l.name)) + usedLabels += label + val transformedLabel = Some(transformLabelIdent(label)) + if (env.isLabelTurnedIntoContinue(label)) js.Continue(transformedLabel) else js.Break(transformedLabel) @@ -1649,8 +1649,8 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { extractLet { newLhs => val bodyEnv = env.withLabeledExprLHS(label, newLhs) val newBody = - pushLhsInto(newLhs, body, tailPosLabels + label.name)(bodyEnv) - if (usedLabels.contains(label.name)) + pushLhsInto(newLhs, body, tailPosLabels + label)(bodyEnv) + if (usedLabels.contains(label)) js.Labeled(transformLabelIdent(label), newBody) else newBody @@ -3252,8 +3252,8 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { private def transformParamDef(paramDef: ParamDef): js.ParamDef = js.ParamDef(transformLocalVarIdent(paramDef.name, paramDef.originalName))(paramDef.pos) - private def transformLabelIdent(ident: LabelIdent): js.Ident = - js.Ident(genName(ident.name))(ident.pos) + private def transformLabelIdent(label: LabelName)(implicit pos: Position): js.Ident = + js.Ident(genName(label)) private def transformLocalVarRefIdent(varRef: VarRef): js.Ident = js.Ident(transformLocalName(varRef.name))(varRef.pos) @@ -3401,7 +3401,7 @@ private object FunctionEmitter { override def hasNothingType: Boolean = true } - final case class Return(label: LabelIdent) extends Lhs { + final case class Return(label: LabelName) extends Lhs { override def hasNothingType: Boolean = true } @@ -3444,7 +3444,7 @@ private object FunctionEmitter { def isLocalMutable(name: LocalName): Boolean = VarKind.Mutable == varKind(name) - def lhsForLabeledExpr(label: LabelIdent): Lhs = labeledExprLHSes(label.name) + def lhsForLabeledExpr(label: LabelName): Lhs = labeledExprLHSes(label) def isLabelTurnedIntoContinue(label: LabelName): Boolean = labelsTurnedIntoContinue.contains(label) @@ -3478,8 +3478,8 @@ private object FunctionEmitter { copy(vars = vars + (ident.name -> kind)) } - def withLabeledExprLHS(label: LabelIdent, lhs: Lhs): Env = - copy(labeledExprLHSes = labeledExprLHSes + (label.name -> lhs)) + def withLabeledExprLHS(label: LabelName, lhs: Lhs): Env = + copy(labeledExprLHSes = labeledExprLHSes + (label -> lhs)) def withTurnLabelIntoContinue(label: LabelName): Env = copy(labelsTurnedIntoContinue = labelsTurnedIntoContinue + label) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index 85cc991c39..22332110ac 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -3853,7 +3853,7 @@ private class FunctionEmitter private ( } def genLabeled(tree: Labeled, expectedType: Type): Type = { - val Labeled(LabelIdent(labelName), tpe, body) = tree + val Labeled(labelName, tpe, body) = tree val entry = new LabeledEntry(currentUnwindingStackDepth, labelName, expectedType) @@ -4082,7 +4082,7 @@ private class FunctionEmitter private ( } def genReturn(tree: Return): Type = { - val Return(expr, LabelIdent(labelName)) = tree + val Return(expr, labelName) = tree val targetEntry = enclosingLabeledBlocks(labelName) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index 919e2262c6..763255f2c1 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -667,7 +667,7 @@ private final class ClassDefChecker(classDef: ClassDef, case Labeled(label, _, body) => checkDeclareLabel(label) - checkTree(body, env.withLabel(label.name)) + checkTree(body, env.withLabel(label)) case Assign(lhs, rhs) => lhs match { @@ -716,7 +716,7 @@ private final class ClassDefChecker(classDef: ClassDef, } case Return(expr, label) => - if (!env.returnLabels.contains(label.name)) + if (!env.returnLabels.contains(label)) reportError(i"unknown label $label.") checkTree(expr, env) @@ -1057,10 +1057,10 @@ private final class ClassDefChecker(classDef: ClassDef, reportError(i"Duplicate local variable name ${ident.name}.") } - private def checkDeclareLabel(label: LabelIdent)( + private def checkDeclareLabel(label: LabelName)( implicit ctx: ErrorContext): Unit = { - if (!declaredLabelNamesPerMethod.add(label.name)) - reportError(i"Duplicate label named ${label.name}.") + if (!declaredLabelNamesPerMethod.add(label)) + reportError(i"Duplicate label named $label.") } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index b4b743ee63..dcd87e7c5a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -234,7 +234,7 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, trees.foreach(typecheck(_, env)) case Labeled(label, tpe, body) => - typecheckExpect(body, env.withLabeledReturnType(label.name, tpe), tpe) + typecheckExpect(body, env.withLabeledReturnType(label, tpe), tpe) case Assign(lhs, rhs) => def checkNonStaticField(receiver: Tree, name: FieldName): Unit = { @@ -304,7 +304,7 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, typecheckExpect(rhs, env, expectedRhsTpe) case Return(expr, label) => - typecheckExpect(expr, env, env.returnTypes(label.name)) + typecheckExpect(expr, env, env.returnTypes(label)) case If(cond, thenp, elsep) => val tpe = tree.tpe diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index b280a7776a..94a294a4cc 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -338,7 +338,7 @@ private[optimizer] abstract class OptimizerCore( case tree: Block => transformBlock(tree, isStat) - case Labeled(ident @ LabelIdent(label), tpe, body) => + case Labeled(label, tpe, body) => trampoline { pretransformLabeled(label, if (isStat) VoidType else tpe, body, isStat, usePreTransform = false)(finishTransform(isStat)) @@ -381,8 +381,8 @@ private[optimizer] abstract class OptimizerCore( } case Return(expr, label) => - val info = scope.env.labelInfos(label.name) - val newLabel = LabelIdent(info.newName) + val info = scope.env.labelInfos(label) + val newLabel = info.newName if (info.isStat) { val newExpr = transformStat(expr) info.returnedTypes.value ::= (VoidType, RefinedType.NoRefinedType) @@ -960,7 +960,7 @@ private[optimizer] abstract class OptimizerCore( transformExpr(default))(tree.tpe).toPreTransform) } - case Labeled(ident @ LabelIdent(label), tpe, body) => + case Labeled(label, tpe, body) => pretransformLabeled(label, tpe, body, isStat = false, usePreTransform = true)(cont) @@ -5169,7 +5169,7 @@ private[optimizer] abstract class OptimizerCore( tryOptimizePatternMatch(oldLabelName, newLabel, refinedType, returnCount, newBody) getOrElse { - Labeled(LabelIdent(newLabel), refinedType, newBody) + Labeled(newLabel, refinedType, newBody) } } @@ -5263,7 +5263,7 @@ private[optimizer] abstract class OptimizerCore( if (revAlts.size == returnCount - 1) { def tryDropReturn(body: Tree): Option[Tree] = body match { - case BlockOrAlone(prep, Return(result, LabelIdent(`newLabelName`))) => + case BlockOrAlone(prep, Return(result, `newLabelName`)) => val result1 = if (refinedType == VoidType) keepOnlySideEffects(result) else result diff --git a/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala index e80affe3bc..7b37ab5648 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala @@ -267,10 +267,10 @@ class OptimizerTest { * optimized code can be linked all the way to the Emitter. */ - val matchResult1 = LabelIdent("matchResult1") + val matchResult1 = LabelName("matchResult1") val x1 = LocalName("x1") - val matchAlts1 = LabelIdent("matchAlts1") - val matchAlts2 = LabelIdent("matchAlts2") + val matchAlts1 = LabelName("matchAlts1") + val matchAlts2 = LabelName("matchAlts2") val results = for (voidReturnArgument <- List(Undefined(), Skip())) yield { val classDefs = Seq( diff --git a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala index 528dfb2fcc..d48649c245 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala @@ -181,8 +181,6 @@ object TestIRBuilder { implicit def string2LocalIdent(name: String): LocalIdent = LocalIdent(LocalName(name)) - implicit def string2LabelIdent(name: String): LabelIdent = - LabelIdent(LabelName(name)) implicit def string2SimpleFieldIdent(name: String): SimpleFieldIdent = SimpleFieldIdent(SimpleFieldName(name)) implicit def string2ClassIdent(name: String): ClassIdent = diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 7c373ae05f..ec5063ac59 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -6,8 +6,13 @@ import com.typesafe.tools.mima.core.ProblemFilters._ object BinaryIncompatibilities { val IR = Seq( // !!! Breaking, OK in minor release + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Printers#IRTreePrinter.print"), ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$JSLinkingInfo"), ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$JSLinkingInfo$"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$LabelIdent"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$LabelIdent$"), + ProblemFilters.exclude[Problem]("org.scalajs.ir.Trees#Labeled.*"), + ProblemFilters.exclude[Problem]("org.scalajs.ir.Trees#Return.*"), ProblemFilters.exclude[Problem]("org.scalajs.ir.Trees#VarRef.*"), ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.ir.Trees#*.tpe"), ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Types$NoType$"), @@ -20,6 +25,11 @@ object BinaryIncompatibilities { ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Transformers#Transformer.transformStats"), ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Trees#Transient#Value.transform"), ProblemFilters.exclude[ReversedMissingMethodProblem]("org.scalajs.ir.Trees#Transient#Value.transform"), + + // private, not an issue + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Serializers#Deserializer.readLabelIdent"), + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Serializers#Serializer.writeLabelIdent"), + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Hashers#TreeHasher.mixLabelIdent"), ) val Linker = Seq( From c1646d14fb01e915766215a7ab5450c08e7153e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 15 Dec 2024 13:44:52 +0100 Subject: [PATCH 048/121] Merge several IR nodes into `UnaryOp`. `ArrayLength`, `GetClass`, `Clone`, `IdentityHashCode`, `WrapAsThrowable`, `UnwrapFromThrowable` and `Throw` are now ops of `UnaryOp`, rather than having their own IR nodes. They all follow the general contract of how a `UnaryOp` must behave: first evaluate the argument, then perform an operation that only depends on the value of the argument (and not, for example, on the scope). It makes the JS `FunctionEmitter` a bit messier. However, everything else gets simpler, as we get rid of dedicated paths for those 7 IR nodes. In order to better fit with the other `UnaryOp`s, notably the ones for `jl.Class` operations, we demand non-nullable arguments. An explicit `CheckNotNull` must be used for arguments that are nullable. --- In this commit, we do not update the compiler yet. It still emits the old IR nodes, so that we can test the deserialiation hacks. That also means the case classes still exist for now. --- .../main/scala/org/scalajs/ir/Printers.scala | 73 ++-- .../scala/org/scalajs/ir/Serializers.scala | 87 +++-- .../src/main/scala/org/scalajs/ir/Trees.scala | 54 ++- .../scala/org/scalajs/ir/PrintersTest.scala | 8 + .../scala/org/scalajs/linker/RunTest.scala | 4 +- .../org/scalajs/linker/analyzer/Infos.scala | 17 +- .../linker/backend/emitter/CoreJSLib.scala | 21 +- .../backend/emitter/FunctionEmitter.scala | 223 ++++++------ .../linker/backend/emitter/Transients.scala | 4 +- .../backend/wasmemitter/FunctionEmitter.scala | 329 +++++++----------- .../linker/checker/ClassDefChecker.scala | 34 +- .../scalajs/linker/checker/IRChecker.scala | 47 ++- .../frontend/optimizer/IncOptimizer.scala | 4 +- .../frontend/optimizer/OptimizerCore.scala | 184 ++++------ .../org/scalajs/linker/IRCheckerTest.scala | 42 ++- .../org/scalajs/linker/OptimizerTest.scala | 10 +- project/Build.scala | 2 +- 17 files changed, 533 insertions(+), 610 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala index 4c4401a406..3008148c71 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -347,40 +347,47 @@ object Printers { case UnaryOp(op, lhs) => import UnaryOp._ - if (op < String_length) { - print('(') - print((op: @switch) match { - case Boolean_! => - "!" - case IntToChar => - "(char)" - case IntToByte => - "(byte)" - case IntToShort => - "(short)" - case CharToInt | ByteToInt | ShortToInt | LongToInt | DoubleToInt => - "(int)" - case IntToLong | DoubleToLong => - "(long)" - case DoubleToFloat | LongToFloat => - "(float)" - case IntToDouble | LongToDouble | FloatToDouble => - "(double)" - }) - print(lhs) - print(')') - } else { + def p(prefix: String, suffix: String): Unit = { + print(prefix) print(lhs) - print((op: @switch) match { - case String_length => ".length" - case CheckNotNull => ".notNull" - case Class_name => ".name" - case Class_isPrimitive => ".isPrimitive" - case Class_isInterface => ".isInterface" - case Class_isArray => ".isArray" - case Class_componentType => ".componentType" - case Class_superClass => ".superClass" - }) + print(suffix) + } + + (op: @switch) match { + case Boolean_! => + p("(!", ")") + case IntToChar => + p("((char)", ")") + case IntToByte => + p("((byte)", ")") + case IntToShort => + p("((short)", ")") + case CharToInt | ByteToInt | ShortToInt | LongToInt | DoubleToInt => + p("((int)", ")") + case IntToLong | DoubleToLong => + p("((long)", ")") + case DoubleToFloat | LongToFloat => + p("((float)", ")") + case IntToDouble | LongToDouble | FloatToDouble => + p("((double)", ")") + + case String_length => p("", ".length") + case CheckNotNull => p("", ".notNull") + case Class_name => p("", ".name") + case Class_isPrimitive => p("", ".isPrimitive") + case Class_isInterface => p("", ".isInterface") + case Class_isArray => p("", ".isArray") + case Class_componentType => p("", ".componentType") + case Class_superClass => p("", ".superClass") + case Array_length => p("", ".length") + case GetClass => p("", ".getClass()") + + case Clone => p("(", ")") + case IdentityHashCode => p("(", ")") + case WrapAsThrowable => p("(", ")") + case UnwrapFromThrowable => p("(", ")") + + case Throw => p("throw ", "") } case BinaryOp(BinaryOp.Int_-, IntLiteral(0), rhs) => diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index da0dc4d01e..369cb5990c 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -1139,8 +1139,8 @@ object Serializers { * (throw qual.field[null]) = rhs --> qual.field[null] = rhs */ lhs0 match { - case Throw(sel: Select) if sel.tpe == NullType => sel - case _ => lhs0 + case UnaryOp(UnaryOp.Throw, sel: Select) if sel.tpe == NullType => sel + case _ => lhs0 } } else { lhs0 @@ -1170,13 +1170,6 @@ object Serializers { case TagTryFinally => TryFinally(readTree(), readTree()) - case TagThrow => - val expr = readTree() - val patchedExpr = - if (hacks.use8) throwArgumentHack8(expr) - else expr - Throw(patchedExpr) - case TagMatch => Match(readTree(), List.fill(readInt()) { (readTrees().map(_.asInstanceOf[MatchableLiteral]), readTree()) @@ -1207,7 +1200,7 @@ object Serializers { /* Note [Nothing FieldDef rewrite] * qual.field[nothing] --> throw qual.field[null] */ - Throw(Select(qualifier, field)(NullType)) + UnaryOp(UnaryOp.Throw, Select(qualifier, field)(NullType)) } else { Select(qualifier, field)(tpe) } @@ -1246,6 +1239,36 @@ object Serializers { case TagUnaryOp => UnaryOp(readByte(), readTree()) case TagBinaryOp => BinaryOp(readByte(), readTree(), readTree()) + case TagArrayLength | TagGetClass | TagClone | TagIdentityHashCode | + TagWrapAsThrowable | TagUnwrapFromThrowable | TagThrow => + if (false /*!hacks.use17*/) { // scalastyle:ignore + throw new IOException( + s"Illegal legacy node $tag found in class ${enclosingClassName.nameString}") + } + + val lhs = readTree() + def checkNotNullLhs: Tree = UnaryOp(UnaryOp.CheckNotNull, lhs) + + (tag: @switch) match { + case TagArrayLength => + UnaryOp(UnaryOp.Array_length, checkNotNullLhs) + case TagGetClass => + UnaryOp(UnaryOp.GetClass, checkNotNullLhs) + case TagClone => + UnaryOp(UnaryOp.Clone, checkNotNullLhs) + case TagIdentityHashCode => + UnaryOp(UnaryOp.IdentityHashCode, lhs) + case TagWrapAsThrowable => + UnaryOp(UnaryOp.WrapAsThrowable, lhs) + case TagUnwrapFromThrowable => + UnaryOp(UnaryOp.UnwrapFromThrowable, checkNotNullLhs) + case TagThrow => + val patchedLhs = + if (hacks.use8) throwArgumentHack8(lhs) + else lhs + UnaryOp(UnaryOp.Throw, patchedLhs) + } + case TagNewArray => val arrayTypeRef = readArrayTypeRef() val lengths = readTrees() @@ -1279,7 +1302,6 @@ object Serializers { } case TagArrayValue => ArrayValue(readArrayTypeRef(), readTrees()) - case TagArrayLength => ArrayLength(readTree()) case TagArraySelect => ArraySelect(readTree(), readTree())(readType()) case TagRecordValue => RecordValue(readType().asInstanceOf[RecordType], readTrees()) @@ -1299,14 +1321,6 @@ object Serializers { IsInstanceOf(expr, testType) case TagAsInstanceOf => AsInstanceOf(readTree(), readType()) - case TagGetClass => GetClass(readTree()) - case TagClone => Clone(readTree()) - case TagIdentityHashCode => IdentityHashCode(readTree()) - - case TagWrapAsThrowable => - WrapAsThrowable(readTree()) - case TagUnwrapFromThrowable => - UnwrapFromThrowable(readTree()) case TagJSNew => JSNew(readTree(), readTreeOrJSSpreads()) case TagJSPrivateSelect => JSPrivateSelect(readTree(), readFieldIdent()) @@ -1462,10 +1476,13 @@ object Serializers { * `runtime.package$.unwrapJavaScriptException(x)`. */ private def throwArgumentHack8(expr: Tree)(implicit pos: Position): Tree = { + def unwrapFromThrowable(t: Tree): Tree = + UnaryOp(UnaryOp.UnwrapFromThrowable, t) + expr.tpe match { case NullType => // Evaluate the expression then definitely run into an NPE UB - UnwrapFromThrowable(expr) + unwrapFromThrowable(expr) case ClassType(_, _) => expr match { @@ -1476,7 +1493,7 @@ object Serializers { /* Common case (explicit re-throw of the form `throw th`) where we don't need the IIFE. * if (expr === null) unwrapFromThrowable(null) else expr */ - If(BinaryOp(BinaryOp.===, expr, Null()), UnwrapFromThrowable(Null()), expr)(AnyType) + If(BinaryOp(BinaryOp.===, expr, Null()), unwrapFromThrowable(Null()), expr)(AnyType) case _ => /* General case where we need to avoid evaluating `expr` twice. * ((x) => if (x === null) unwrapFromThrowable(null) else x)(expr) @@ -1485,7 +1502,7 @@ object Serializers { val xParamDef = ParamDef(x, OriginalName.NoOriginalName, AnyType, mutable = false) val xRef = xParamDef.ref val closure = Closure(arrow = true, Nil, List(xParamDef), None, { - If(BinaryOp(BinaryOp.===, xRef, Null()), UnwrapFromThrowable(Null()), xRef)(AnyType) + If(BinaryOp(BinaryOp.===, xRef, Null()), unwrapFromThrowable(Null()), xRef)(AnyType) }, Nil) JSFunctionApply(closure, List(expr)) } @@ -1681,6 +1698,12 @@ object Serializers { VarDef(LocalIdent(LocalName(name)), NoOriginalName, vtpe, mutable, rhs) } + def arrayLength(t: Tree)(implicit pos: Position): Tree = + UnaryOp(UnaryOp.Array_length, t) + + def getClass(t: Tree)(implicit pos: Position): Tree = + UnaryOp(UnaryOp.GetClass, t) + val jlClassRef = ClassRef(ClassClass) val intArrayTypeRef = ArrayTypeRef(IntRef, 1) val objectRef = ClassRef(ObjectClass) @@ -1738,7 +1761,7 @@ object Serializers { length, result, innerOffset, - If(BinaryOp(BinaryOp.Int_<, innerOffset.ref, ArrayLength(dimensions.ref)), { + If(BinaryOp(BinaryOp.Int_<, innerOffset.ref, arrayLength(dimensions.ref)), { Block( result2, innerComponentType, @@ -1808,11 +1831,11 @@ object Serializers { Block( outermostComponentType, i, - While(BinaryOp(BinaryOp.Int_!=, i.ref, ArrayLength(lengthsParam.ref)), { + While(BinaryOp(BinaryOp.Int_!=, i.ref, arrayLength(lengthsParam.ref)), { Block( Assign( outermostComponentType.ref, - GetClass(Apply(EAF, This()(ClassType(ReflectArrayModClass, nullable = false)), + getClass(Apply(EAF, This()(ClassType(ReflectArrayModClass, nullable = false)), MethodIdent(newInstanceSingleName), List(outermostComponentType.ref, IntLiteral(0)))(AnyType)) ), @@ -1942,7 +1965,7 @@ object Serializers { */ assert(args.size == 1) - val patchedBody = Some(IdentityHashCode(args(0).ref)) + val patchedBody = Some(UnaryOp(UnaryOp.IdentityHashCode, args(0).ref)) val patchedOptimizerHints = OptimizerHints.empty.withInline(true) MethodDef(flags, name, originalName, args, resultType, patchedBody)( @@ -1967,11 +1990,13 @@ object Serializers { val patchedBody = Some { If(IsInstanceOf(thisValue, cloneableClassType.toNonNullable), - Clone(AsInstanceOf(thisValue, cloneableClassType)), - Throw(New( - HackNames.CloneNotSupportedExceptionClass, - MethodIdent(NoArgConstructorName), - Nil)))(cloneableClassType) + UnaryOp(UnaryOp.Clone, + UnaryOp(UnaryOp.CheckNotNull, AsInstanceOf(thisValue, cloneableClassType))), + UnaryOp(UnaryOp.Throw, + New( + HackNames.CloneNotSupportedExceptionClass, + MethodIdent(NoArgConstructorName), + Nil)))(cloneableClassType) } val patchedOptimizerHints = OptimizerHints.empty.withInline(true) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index 917df781ed..031970c895 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -280,12 +280,24 @@ object Trees { } /** Unary operation. + * + * All unary operations follow common evaluation steps: + * + * 1. Let `lhsValue` be the result of evaluating `lhs`. + * 2. Perform an operation that depends on `op` and `lhsValue`. * * The `Class_x` operations take a `jl.Class!` argument, i.e., a * non-nullable `jl.Class`. * + * Likewise, `Array_length`, `GetClass`, `Clone` and `UnwrapFromThrowable` + * take arguments of non-nullable types. + * * `CheckNotNull` throws NPEs subject to UB. * + * `Throw` always throws, obviously. + * + * `Clone` and `WrapAsThrowable` are side-effect-free but not pure. + * * Otherwise, unary operations preserve pureness. */ sealed case class UnaryOp(op: UnaryOp.Code, lhs: Tree)( @@ -337,9 +349,26 @@ object Trees { final val Class_componentType = 23 final val Class_superClass = 24 + // Misc ops introduced in 1.18, which used to have dedicated IR nodes + final val Array_length = 25 + final val GetClass = 26 + final val Clone = 27 + final val IdentityHashCode = 28 + final val WrapAsThrowable = 29 + final val UnwrapFromThrowable = 30 + final val Throw = 31 + def isClassOp(op: Code): Boolean = op >= Class_name && op <= Class_superClass + def isPureOp(op: Code): Boolean = (op: @switch) match { + case CheckNotNull | Clone | WrapAsThrowable | Throw => false + case _ => true + } + + def isSideEffectFreeOp(op: Code): Boolean = + op != CheckNotNull && op != Throw + def resultTypeOf(op: Code, argType: Type): Type = (op: @switch) match { case Boolean_! | Class_isPrimitive | Class_isInterface | Class_isArray => BooleanType @@ -349,7 +378,8 @@ object Trees { ByteType case IntToShort => ShortType - case CharToInt | ByteToInt | ShortToInt | LongToInt | DoubleToInt | String_length => + case CharToInt | ByteToInt | ShortToInt | LongToInt | DoubleToInt | + String_length | Array_length | IdentityHashCode => IntType case IntToLong | DoubleToLong => LongType @@ -357,12 +387,18 @@ object Trees { FloatType case IntToDouble | LongToDouble | FloatToDouble => DoubleType - case CheckNotNull => + case CheckNotNull | Clone => argType.toNonNullable case Class_name => StringType - case Class_componentType | Class_superClass => + case Class_componentType | Class_superClass | GetClass => ClassType(ClassClass, nullable = true) + case WrapAsThrowable => + ClassType(ThrowableClass, nullable = false) + case UnwrapFromThrowable => + AnyType + case Throw => + NothingType } } @@ -826,11 +862,7 @@ object Trees { val tpe = VoidType } - /** Unary operation (always preserves pureness). - * - * Operations which do not preserve pureness are not allowed in this tree. - * These are notably ++ and -- - */ + /** JavaScript unary operation. */ sealed case class JSUnaryOp(op: JSUnaryOp.Code, lhs: Tree)( implicit val pos: Position) extends Tree { val tpe = JSUnaryOp.resultTypeOf(op) @@ -851,11 +883,7 @@ object Trees { AnyType } - /** Binary operation (always preserves pureness). - * - * Operations which do not preserve pureness are not allowed in this tree. - * These are notably +=, -=, *=, /= and %= - */ + /** JavaScript binary operation. */ sealed case class JSBinaryOp(op: JSBinaryOp.Code, lhs: Tree, rhs: Tree)( implicit val pos: Position) extends Tree { val tpe = JSBinaryOp.resultTypeOf(op) diff --git a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala index 052db4b9da..06c2d0de56 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -403,6 +403,14 @@ class PrintersTest { assertPrintEquals("x.isArray", UnaryOp(Class_isArray, classVarRef)) assertPrintEquals("x.componentType", UnaryOp(Class_componentType, classVarRef)) assertPrintEquals("x.superClass", UnaryOp(Class_superClass, classVarRef)) + + assertPrintEquals("x.length", UnaryOp(Array_length, ref("x", arrayType(IntRef, 1)))) + assertPrintEquals("x.getClass()", UnaryOp(GetClass, ref("x", AnyType))) + assertPrintEquals("(x)", UnaryOp(Clone, ref("x", arrayType(ObjectClass, 1)))) + assertPrintEquals("(x)", UnaryOp(IdentityHashCode, ref("x", AnyType))) + assertPrintEquals("(e)", UnaryOp(WrapAsThrowable, ref("e", AnyType))) + assertPrintEquals("(e)", + UnaryOp(UnwrapFromThrowable, ref("e", ClassType(ThrowableClass, nullable = true)))) } @Test def printPseudoUnaryOp(): Unit = { diff --git a/linker/jvm/src/test/scala/org/scalajs/linker/RunTest.scala b/linker/jvm/src/test/scala/org/scalajs/linker/RunTest.scala index 0f7f6a628a..5c49da7dee 100644 --- a/linker/jvm/src/test/scala/org/scalajs/linker/RunTest.scala +++ b/linker/jvm/src/test/scala/org/scalajs/linker/RunTest.scala @@ -71,7 +71,7 @@ class RunTest { val classDefs = Seq( mainTestClassDef(Block( VarDef("e", NON, ClassType(ThrowableClass, nullable = true), mutable = false, - WrapAsThrowable(JSNew(JSGlobalRef("RangeError"), List(str("boom"))))), + UnaryOp(UnaryOp.WrapAsThrowable, JSNew(JSGlobalRef("RangeError"), List(str("boom"))))), genAssert(IsInstanceOf(e, ClassType("java.lang.Exception", nullable = false))), genAssertEquals(str("RangeError: boom"), Apply(EAF, e, getMessage, Nil)(ClassType(BoxedStringClass, nullable = true))) @@ -87,7 +87,7 @@ class RunTest { private def genAssert(test: Tree): Tree = { If(UnaryOp(UnaryOp.Boolean_!, test), - Throw(JSNew(JSGlobalRef("Error"), List(str("Assertion failed")))), + UnaryOp(UnaryOp.Throw, JSNew(JSGlobalRef("Error"), List(str("Assertion failed")))), Skip())( VoidType) } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala index 84a8d8a630..1458e107f4 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala @@ -684,6 +684,13 @@ object Infos { op match { case Class_superClass => builder.addUsedClassSuperClass() + case GetClass => + builder.addAccessedClassClass() + case WrapAsThrowable => + builder.addUsedInstanceTest(ThrowableClass) + builder.addInstantiatedClass(JavaScriptExceptionClass, AnyArgConstructorName) + case UnwrapFromThrowable => + builder.addUsedInstanceTest(JavaScriptExceptionClass) case _ => // do nothing } @@ -727,16 +734,6 @@ object Infos { builder.maybeAddAccessedClassData(cls) builder.addAccessedClassClass() - case GetClass(_) => - builder.addAccessedClassClass() - - case WrapAsThrowable(_) => - builder.addUsedInstanceTest(ThrowableClass) - builder.addInstantiatedClass(JavaScriptExceptionClass, AnyArgConstructorName) - - case UnwrapFromThrowable(_) => - builder.addUsedInstanceTest(JavaScriptExceptionClass) - case JSPrivateSelect(_, field) => builder.addStaticallyReferencedClass(field.name.className) // for the private name of the field builder.addFieldRead(field.name) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala index 77e47c1fb4..467e73d68b 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala @@ -712,23 +712,16 @@ private[emitter] object CoreJSLib { Return(constantClassResult(BoxedUnitClass)) } ), { - If(instance === Null(), { - if (nullPointers == CheckedBehavior.Unchecked) - Return(genApply(instance, getClassMethodName, Nil)) - else - genCallHelper(VarField.throwNullPointerException) + If(genIsInstanceOfHijackedClass(instance, BoxedLongClass), { + Return(constantClassResult(BoxedLongClass)) }, { - If(genIsInstanceOfHijackedClass(instance, BoxedLongClass), { - Return(constantClassResult(BoxedLongClass)) + If(genIsInstanceOfHijackedClass(instance, BoxedCharacterClass), { + Return(constantClassResult(BoxedCharacterClass)) }, { - If(genIsInstanceOfHijackedClass(instance, BoxedCharacterClass), { - Return(constantClassResult(BoxedCharacterClass)) + If(genIsScalaJSObject(instance), { + Return(scalaObjectResult(instance)) }, { - If(genIsScalaJSObject(instance), { - Return(scalaObjectResult(instance)) - }, { - Return(jsObjectResult) - }) + Return(jsObjectResult) }) }) }) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index 89516298bb..12af2ad9ab 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -1031,8 +1031,10 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { } result - case UnaryOp(op, lhs) if op != UnaryOp.CheckNotNull || noExtractYet => + case arg @ UnaryOp(op, lhs) + if canUnaryOpBeExpression(arg) && (UnaryOp.isPureOp(op) || noExtractYet) => UnaryOp(op, rec(lhs)) + case BinaryOp(op, lhs, rhs) => val newRhs = rec(rhs) BinaryOp(op, rec(lhs), newRhs) @@ -1083,8 +1085,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { ApplyStatic(flags, className, method, recs(args))(arg.tpe) case ApplyDynamicImport(flags, className, method, args) if noExtractYet => ApplyDynamicImport(flags, className, method, recs(args)) - case ArrayLength(array) if noExtractYet => - ArrayLength(rec(array)) case ArraySelect(array, index) if noExtractYet => val newIndex = rec(index) ArraySelect(rec(array), newIndex)(arg.tpe) @@ -1219,6 +1219,22 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { } } + private def canUnaryOpBeExpression(tree: UnaryOp): Boolean = { + import UnaryOp._ + + tree.op match { + case Throw => + false + case WrapAsThrowable | UnwrapFromThrowable => + tree.lhs match { + case VarRef(_) | Transient(JSVarRef(_, _)) => true + case _ => false + } + case _ => + true + } + } + /** Common implementation for the functions below. * A pure expression can be moved around or executed twice, because it * will always produce the same result and never have side-effects. @@ -1255,6 +1271,16 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case Transient(JSVarRef(_, mutable)) => allowUnpure || !mutable + case tree @ UnaryOp(op, lhs) if canUnaryOpBeExpression(tree) => + if (op == UnaryOp.CheckNotNull) + testNPE(lhs) + else if (UnaryOp.isPureOp(op)) + test(lhs) + else if (UnaryOp.isSideEffectFreeOp(op)) + allowUnpure && test(lhs) + else + allowSideEffects && test(lhs) + // Division and modulo, preserve pureness unless they can divide by 0 case BinaryOp(BinaryOp.Int_/ | BinaryOp.Int_%, lhs, rhs) if !allowSideEffects => rhs match { @@ -1281,16 +1307,8 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case Block(trees) => trees forall test case If(cond, thenp, elsep) => test(cond) && test(thenp) && test(elsep) case BinaryOp(_, lhs, rhs) => test(lhs) && test(rhs) - case UnaryOp(op, lhs) => if (op == UnaryOp.CheckNotNull) testNPE(lhs) else test(lhs) - case ArrayLength(array) => testNPE(array) case RecordSelect(record, _) => test(record) case IsInstanceOf(expr, _) => test(expr) - case IdentityHashCode(expr) => test(expr) - case GetClass(arg) => testNPE(arg) - - // Expressions preserving pureness (modulo NPE) but requiring that expr be a var - case WrapAsThrowable(expr @ (VarRef(_) | Transient(JSVarRef(_, _)))) => test(expr) - case UnwrapFromThrowable(expr @ (VarRef(_) | Transient(JSVarRef(_, _)))) => testNPE(expr) // Transients preserving pureness (modulo NPE) case Transient(Cast(expr, _)) => @@ -1298,7 +1316,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case Transient(ZeroOf(runtimeClass)) => test(runtimeClass) // ZeroOf *assumes* that `runtimeClass ne null` case Transient(ObjectClassName(obj)) => - testNPE(obj) + test(obj) // Expressions preserving side-effect freedom (modulo NPE) case Select(qualifier, _) => @@ -1307,8 +1325,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { allowUnpure case ArrayValue(tpe, elems) => allowUnpure && (elems forall test) - case Clone(arg) => - allowUnpure && testNPE(arg) case JSArrayConstr(items) => allowUnpure && (items.forall(testJSArg)) case tree @ JSObjectConstr(items) => @@ -1685,9 +1701,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { js.TryFinally(newBlock, newFinalizer) } - case Throw(expr) => - pushLhsInto(Lhs.Throw, expr, tailPosLabels) - /** Matches are desugared into switches * * A match is different from a switch in two respects, both linked @@ -1757,8 +1770,22 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { } case UnaryOp(op, lhs) => - unnest(lhs) { (newLhs, env) => - redo(UnaryOp(op, newLhs))(env) + op match { + case UnaryOp.Throw => + pushLhsInto(Lhs.Throw, lhs, tailPosLabels) + + case UnaryOp.WrapAsThrowable | UnaryOp.UnwrapFromThrowable => + unnest(lhs) { (newLhs, newEnv) => + implicit val env = newEnv + withTempJSVar(newLhs) { varRef => + redo(UnaryOp(op, varRef)) + } + } + + case _ => + unnest(lhs) { (newLhs, env) => + redo(UnaryOp(op, newLhs))(env) + } } case BinaryOp(op, lhs, rhs) => @@ -1776,11 +1803,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { redo(ArrayValue(tpe, newElems))(env) } - case ArrayLength(array) => - unnest(array) { (newArray, env) => - redo(ArrayLength(newArray))(env) - } - case ArraySelect(array, index) => unnest(checkNotNull(array), index) { (newArray, newIndex, env) => redo(ArraySelect(newArray, newIndex)(rhs.tpe))(env) @@ -1801,37 +1823,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { redo(AsInstanceOf(newExpr, tpe))(env) } - case GetClass(expr) => - unnest(expr) { (newExpr, env) => - redo(GetClass(newExpr))(env) - } - - case Clone(expr) => - unnest(expr) { (newExpr, env) => - redo(Clone(newExpr))(env) - } - - case IdentityHashCode(expr) => - unnest(expr) { (newExpr, env) => - redo(IdentityHashCode(newExpr))(env) - } - - case WrapAsThrowable(expr) => - unnest(expr) { (newExpr, newEnv) => - implicit val env = newEnv - withTempJSVar(newExpr) { varRef => - redo(WrapAsThrowable(varRef)) - } - } - - case UnwrapFromThrowable(expr) => - unnest(expr) { (newExpr, newEnv) => - implicit val env = newEnv - withTempJSVar(newExpr) { varRef => - redo(UnwrapFromThrowable(varRef)) - } - } - case Transient(Cast(expr, tpe)) => unnest(expr) { (newExpr, env) => redo(Transient(Cast(newExpr, tpe)))(env) @@ -2429,6 +2420,66 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { js.Apply(genGetDataOf(newLhs) DOT cpn.getComponentType, Nil) case Class_superClass => js.Apply(genGetDataOf(newLhs) DOT cpn.getSuperclass, Nil) + + case Array_length => + genIdentBracketSelect( + genSyntheticPropSelect(newLhs, SyntheticProperty.u), + "length") + + case GetClass => + genCallHelper(VarField.objectGetClass, newLhs) + + case Clone => + lhs.tpe match { + /* If the argument is known to be an array, directly call its + * `clone__O` method. + * This happens all the time when calling `clone()` on an array, + * since the optimizer will inline `java.lang.Object.clone()` in + * those cases, leaving a `Clone()` node an array. + */ + case _: ArrayType => + genApply(newLhs, cloneMethodName, Nil) + + /* Otherwise, if it might be an array, use the full dispatcher. + * In theory, only the `CloneableClass` case is required, since + * `Clone` only accepts values of type `Cloneable`. However, since + * the inliner does not always refine the type of receivers, we + * also account for other supertypes of array types. There is a + * similar issue for CharSequenceClass in `Apply` nodes. + * + * TODO Is the above comment still relevant now that the optimizer + * is type-preserving? + * + * In practice, this only happens in the (non-inlined) definition + * of `java.lang.Object.clone()` itself, since everywhere else it + * is inlined in contexts where the receiver has a more precise + * type. + */ + case ClassType(CloneableClass, _) | ClassType(SerializableClass, _) | + ClassType(ObjectClass, _) | AnyType | AnyNotNullType => + genCallHelper(VarField.objectOrArrayClone, newLhs) + + // Otherwise, it is known not to be an array. + case _ => + genCallHelper(VarField.objectClone, newLhs) + } + + case IdentityHashCode => + genCallHelper(VarField.systemIdentityHashCode, newLhs) + + case WrapAsThrowable => + val newLhsVar = newLhs.asInstanceOf[js.VarRef] + js.If( + genIsInstanceOfClass(newLhsVar, ThrowableClass), + newLhsVar, + genScalaClassNew(JavaScriptExceptionClass, AnyArgConstructorName, newLhsVar)) + + case UnwrapFromThrowable => + val newLhsVar = newLhs.asInstanceOf[js.VarRef] + js.If( + genIsInstanceOfClass(newLhsVar, JavaScriptExceptionClass), + genSelect(newLhsVar, FieldIdent(exceptionFieldName)), + newLhsVar) } case BinaryOp(op, lhs, rhs) => @@ -2739,12 +2790,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { extractWithGlobals( genArrayValue(typeRef, elems.map(transformExpr(_, preserveChar)))) - case ArrayLength(array) => - val newArray = transformExprNoChar(checkNotNull(array)) - genIdentBracketSelect( - genSyntheticPropSelect(newArray, SyntheticProperty.u), - "length") - case ArraySelect(array, index) => val newArray = transformExprNoChar(checkNotNull(array)) val newIndex = transformExprNoChar(index) @@ -2764,62 +2809,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case AsInstanceOf(expr, tpe) => extractWithGlobals(genAsInstanceOf(transformExprNoChar(expr), tpe)) - case GetClass(expr) => - genCallHelper(VarField.objectGetClass, transformExprNoChar(expr)) - - case Clone(expr) => - val newExpr = transformExprNoChar(checkNotNull(expr)) - expr.tpe match { - /* If the argument is known to be an array, directly call its - * `clone__O` method. - * This happens all the time when calling `clone()` on an array, - * since the optimizer will inline `java.lang.Object.clone()` in - * those cases, leaving a `Clone()` node an array. - */ - case _: ArrayType => - genApply(newExpr, cloneMethodName, Nil) - - /* Otherwise, if it might be an array, use the full dispatcher. - * In theory, only the `CloneableClass` case is required, since - * `Clone` only accepts values of type `Cloneable`. However, since - * the inliner does not always refine the type of receivers, we - * also account for other supertypes of array types. There is a - * similar issue for CharSequenceClass in `Apply` nodes. - * - * TODO Is the above comment still relevant now that the optimizer - * is type-preserving? - * - * In practice, this only happens in the (non-inlined) definition - * of `java.lang.Object.clone()` itself, since everywhere else it - * is inlined in contexts where the receiver has a more precise - * type. - */ - case ClassType(CloneableClass, _) | ClassType(SerializableClass, _) | - ClassType(ObjectClass, _) | AnyType | AnyNotNullType => - genCallHelper(VarField.objectOrArrayClone, newExpr) - - // Otherwise, it is known not to be an array. - case _ => - genCallHelper(VarField.objectClone, newExpr) - } - - case IdentityHashCode(expr) => - genCallHelper(VarField.systemIdentityHashCode, transformExprNoChar(expr)) - - case WrapAsThrowable(expr) => - val newExpr = transformExprNoChar(expr).asInstanceOf[js.VarRef] - js.If( - genIsInstanceOfClass(newExpr, ThrowableClass), - newExpr, - genScalaClassNew(JavaScriptExceptionClass, AnyArgConstructorName, newExpr)) - - case UnwrapFromThrowable(expr) => - val newExpr = transformExprNoChar(expr).asInstanceOf[js.VarRef] - js.If( - genIsInstanceOfClass(newExpr, JavaScriptExceptionClass), - genSelect(newExpr, FieldIdent(exceptionFieldName)), - genCheckNotNull(newExpr)) - case prop: LinkTimeProperty => transformExpr( config.coreSpec.linkTimeProperties.transformLinkTimeProperty(prop), diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Transients.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Transients.scala index 61f9ece247..00301771cc 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Transients.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Transients.scala @@ -137,9 +137,7 @@ object Transients { /** Intrinsic for `obj.getClass().getName()`. * - * This node accepts any value for `obj`, including `null`. Its - * implementation takes care of throwing `NullPointerException`s as - * required. + * The argument's type must conform to `AnyNotNullType`. */ final case class ObjectClassName(obj: Tree) extends Transient.Value { val tpe: Type = StringType diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index 22332110ac..78f0b2ec39 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -537,7 +537,6 @@ private class FunctionEmitter private ( case t: ApplyDynamicImport => genApplyDynamicImport(t) case t: IsInstanceOf => genIsInstanceOf(t) case t: AsInstanceOf => genAsInstanceOf(t) - case t: GetClass => genGetClass(t) case t: Block => genBlock(t, expectedType) case t: Labeled => unwinding.genLabeled(t, expectedType) case t: Return => unwinding.genReturn(t) @@ -551,14 +550,9 @@ private class FunctionEmitter private ( case t: ForIn => genForIn(t) case t: TryCatch => genTryCatch(t, expectedType) case t: TryFinally => unwinding.genTryFinally(t, expectedType) - case t: Throw => genThrow(t) case t: Match => genMatch(t, expectedType) case t: Debugger => VoidType // ignore case t: Skip => VoidType - case t: Clone => genClone(t) - case t: IdentityHashCode => genIdentityHashCode(t) - case t: WrapAsThrowable => genWrapAsThrowable(t) - case t: UnwrapFromThrowable => genUnwrapFromThrowable(t) case t: LinkTimeProperty => genLinkTimeProperty(t) // JavaScript expressions @@ -581,7 +575,6 @@ private class FunctionEmitter private ( case t: Closure => genClosure(t) // array - case t: ArrayLength => genArrayLength(t) case t: NewArray => genNewArray(t) case t: ArraySelect => genArraySelect(t) case t: ArrayValue => genArrayValue(t) @@ -602,6 +595,11 @@ private class FunctionEmitter private ( case _: JSSuperConstructorCall => throw new AssertionError(s"Invalid tree: $tree") + + case _:Throw | _:ArrayLength | _:GetClass | _:Clone | _:IdentityHashCode | + _:WrapAsThrowable | _:UnwrapFromThrowable => + throw new AssertionError( + s"illegal legacy node of class ${tree.getClass().getSimpleName()}") } genAdapt(generatedType, expectedType) @@ -1370,11 +1368,27 @@ private class FunctionEmitter private ( } private def genUnaryOp(tree: UnaryOp): Type = { + // scalastyle:off return + import UnaryOp._ val UnaryOp(op, lhs) = tree - genTreeAuto(lhs) + /* Touch of peephole optimization; useful so that the various operators can + * assume the NothingType case away (e.g., `Array_length`, `Clone`). + */ + if (lhs.tpe == NothingType) { + genTreeAuto(lhs) + return NothingType + } + + // scalastyle:on return + + genTree(lhs, op match { + case IdentityHashCode => AnyNotNullType + case WrapAsThrowable | Throw => AnyType + case _ => lhs.tpe + }) markPosition(tree) @@ -1451,6 +1465,107 @@ private class FunctionEmitter private ( fb += wa.Call(genFunctionID.getComponentType) case Class_superClass => fb += wa.Call(genFunctionID.getSuperClass) + + case Array_length => + val ArrayType(arrayTypeRef, _) = lhs.tpe: @unchecked + fb += wa.StructGet( + genTypeID.forArrayClass(arrayTypeRef), + genFieldID.objStruct.arrayUnderlying + ) + fb += wa.ArrayLen + + case GetClass => + val needHijackedClassDispatch = lhs.tpe match { + case ClassType(className, _) => + ctx.getClassInfo(className).isAncestorOfHijackedClass + case ArrayType(_, _) => + false + case _ => + true + } + + if (!needHijackedClassDispatch) { + fb += wa.StructGet(genTypeID.ObjectStruct, genFieldID.objStruct.vtable) + fb += wa.Call(genFunctionID.getClassOf) + } else { + genAdapt(lhs.tpe, AnyNotNullType) // no-op when the optimizer is enabled + fb += wa.Call(genFunctionID.anyGetClass) + } + + case Clone => + val lhsLocal = addSyntheticLocal(watpe.RefType(genTypeID.ObjectStruct)) + fb += wa.LocalTee(lhsLocal) + fb += wa.LocalGet(lhsLocal) + fb += wa.StructGet(genTypeID.ObjectStruct, genFieldID.objStruct.vtable) + fb += wa.StructGet(genTypeID.typeData, genFieldID.typeData.cloneFunction) + // cloneFunction: (ref jl.Object) -> (ref jl.Object) + fb += wa.CallRef(genTypeID.cloneFunctionType) + + // cast the (ref jl.Object) back down to the result type + transformSingleType(lhs.tpe) match { + case watpe.RefType(_, watpe.HeapType.Type(genTypeID.ObjectStruct)) => + // no need to cast to (ref null? jl.Object) + case wasmType: watpe.RefType => + fb += wa.RefCast(wasmType.toNonNullable) + case wasmType => + // Since no hijacked class extends jl.Cloneable, this case cannot happen + throw new AssertionError( + s"Unexpected type for Clone: ${lhs.tpe} (Wasm: $wasmType)") + } + + case IdentityHashCode => + // TODO Avoid dispatch when we know a more precise type than any + fb += wa.Call(genFunctionID.identityHashCode) + + case WrapAsThrowable => + val nonNullThrowableType = watpe.RefType(genTypeID.ThrowableStruct) + val jsExceptionType = watpe.RefType(genTypeID.JSExceptionStruct) + + val anyRefToNonNullThrowable = + Sig(List(watpe.RefType.anyref), List(nonNullThrowableType)) + fb.block(anyRefToNonNullThrowable) { doneLabel => + // if expr.isInstanceOf[Throwable], then br $done + fb += wa.BrOnCast(doneLabel, watpe.RefType.anyref, nonNullThrowableType) + + // otherwise, wrap in a new JavaScriptException + + val lhsLocal = addSyntheticLocal(watpe.RefType.anyref) + val instanceLocal = addSyntheticLocal(jsExceptionType) + + fb += wa.LocalSet(lhsLocal) + fb += wa.Call(genFunctionID.newDefault(SpecialNames.JSExceptionClass)) + fb += wa.LocalTee(instanceLocal) + fb += wa.LocalGet(lhsLocal) + fb += wa.Call( + genFunctionID.forMethod( + MemberNamespace.Constructor, + SpecialNames.JSExceptionClass, + SpecialNames.AnyArgConstructorName + ) + ) + fb += wa.LocalGet(instanceLocal) + } + + case UnwrapFromThrowable => + val nonNullThrowableToAnyRef = + Sig(List(watpe.RefType(genTypeID.ThrowableStruct)), List(watpe.RefType.anyref)) + fb.block(nonNullThrowableToAnyRef) { doneLabel => + // if !expr.isInstanceOf[js.JavaScriptException], then br $done + fb += wa.BrOnCastFail( + doneLabel, + watpe.RefType(genTypeID.ThrowableStruct), + watpe.RefType(genTypeID.JSExceptionStruct) + ) + // otherwise, unwrap the JavaScriptException by reading its field + fb += wa.StructGet( + genTypeID.JSExceptionStruct, + genFieldID.forClassInstanceField(SpecialNames.exceptionFieldName) + ) + } + + case Throw => + fb += wa.ExternConvertAny + fb += wa.Throw(genTagID.exception) } tree.tpe @@ -2287,41 +2402,6 @@ private class FunctionEmitter private ( } } - private def genGetClass(tree: GetClass): Type = { - /* Unlike in `genApply` or `genStringConcat`, here we make no effort to - * optimize known-primitive receivers. In practice, such cases would be - * useless. - */ - - val GetClass(expr) = tree - - val needHijackedClassDispatch = expr.tpe match { - case ClassType(className, _) => - ctx.getClassInfo(className).isAncestorOfHijackedClass - case ArrayType(_, _) | NothingType | NullType => - false - case _ => - true - } - - if (!needHijackedClassDispatch) { - val typeDataLocal = addSyntheticLocal(watpe.RefType(genTypeID.typeData)) - - genTreeAuto(expr) - markPosition(tree) - genCheckNonNullFor(expr) - fb += wa.StructGet(genTypeID.ObjectStruct, genFieldID.objStruct.vtable) - fb += wa.Call(genFunctionID.getClassOf) - } else { - genTreeToAny(expr) - markPosition(tree) - genAsNonNullOrNPEFor(expr) - fb += wa.Call(genFunctionID.anyGetClass) - } - - tree.tpe - } - private def genReadStorage(storage: VarStorage): Unit = { storage match { case VarStorage.Local(localID) => @@ -2491,17 +2571,6 @@ private class FunctionEmitter private ( expectedType } - private def genThrow(tree: Throw): Type = { - val Throw(expr) = tree - - genTree(expr, AnyType) - markPosition(tree) - fb += wa.ExternConvertAny - fb += wa.Throw(genTagID.exception) - - NothingType - } - private def genBlock(tree: Block, expectedType: Type): Type = { val Block(stats) = tree @@ -2595,81 +2664,6 @@ private class FunctionEmitter private ( ClassType(boxClassName, nullable = false) } - private def genIdentityHashCode(tree: IdentityHashCode): Type = { - val IdentityHashCode(expr) = tree - - // TODO Avoid dispatch when we know a more precise type than any - genTree(expr, AnyType) - - markPosition(tree) - fb += wa.Call(genFunctionID.identityHashCode) - - IntType - } - - private def genWrapAsThrowable(tree: WrapAsThrowable): Type = { - val WrapAsThrowable(expr) = tree - - val nonNullThrowableType = watpe.RefType(genTypeID.ThrowableStruct) - val jsExceptionType = watpe.RefType(genTypeID.JSExceptionStruct) - - fb.block(nonNullThrowableType) { doneLabel => - genTree(expr, AnyType) - - markPosition(tree) - - // if expr.isInstanceOf[Throwable], then br $done - fb += wa.BrOnCast(doneLabel, watpe.RefType.anyref, nonNullThrowableType) - - // otherwise, wrap in a new JavaScriptException - - val exprLocal = addSyntheticLocal(watpe.RefType.anyref) - val instanceLocal = addSyntheticLocal(jsExceptionType) - - fb += wa.LocalSet(exprLocal) - fb += wa.Call(genFunctionID.newDefault(SpecialNames.JSExceptionClass)) - fb += wa.LocalTee(instanceLocal) - fb += wa.LocalGet(exprLocal) - fb += wa.Call( - genFunctionID.forMethod( - MemberNamespace.Constructor, - SpecialNames.JSExceptionClass, - SpecialNames.AnyArgConstructorName - ) - ) - fb += wa.LocalGet(instanceLocal) - } - - tree.tpe - } - - private def genUnwrapFromThrowable(tree: UnwrapFromThrowable): Type = { - val UnwrapFromThrowable(expr) = tree - - fb.block(watpe.RefType.anyref) { doneLabel => - genTreeAuto(expr) - - markPosition(tree) - - genAsNonNullOrNPEFor(expr) - - // if !expr.isInstanceOf[js.JavaScriptException], then br $done - fb += wa.BrOnCastFail( - doneLabel, - watpe.RefType(genTypeID.ThrowableStruct), - watpe.RefType(genTypeID.JSExceptionStruct) - ) - - // otherwise, unwrap the JavaScriptException by reading its field - fb += wa.StructGet( - genTypeID.JSExceptionStruct, - genFieldID.forClassInstanceField(SpecialNames.exceptionFieldName) - ) - } - - AnyType - } - private def genLinkTimeProperty(tree: LinkTimeProperty): Type = { val lit = ctx.coreSpec.linkTimeProperties.transformLinkTimeProperty(tree) genLiteral(lit, lit.tpe) @@ -2943,37 +2937,6 @@ private class FunctionEmitter private ( AnyType } - private def genArrayLength(tree: ArrayLength): Type = { - val ArrayLength(array) = tree - - genTreeAuto(array) - - markPosition(tree) - - array.tpe match { - case ArrayType(arrayTypeRef, _) => - // Get the underlying array - genCheckNonNullFor(array) - fb += wa.StructGet( - genTypeID.forArrayClass(arrayTypeRef), - genFieldID.objStruct.arrayUnderlying - ) - // Get the length - fb += wa.ArrayLen - IntType - - case NothingType => - // unreachable - NothingType - case NullType => - genNPE() - NothingType - case _ => - throw new IllegalArgumentException( - s"ArraySelect.array must be an array type, but has type ${tree.array.tpe}") - } - } - private def genNewArray(tree: NewArray): Type = { val NewArray(arrayTypeRef, length) = tree @@ -3166,51 +3129,6 @@ private class FunctionEmitter private ( AnyNotNullType } - private def genClone(tree: Clone): Type = { - val Clone(expr) = tree - - expr.tpe match { - case NothingType => - genTree(expr, NothingType) - NothingType - - case NullType => - genTree(expr, NullType) - genNPE() - NothingType - - case exprType => - val exprLocal = addSyntheticLocal(watpe.RefType(genTypeID.ObjectStruct)) - - genTreeAuto(expr) - - markPosition(tree) - - genAsNonNullOrNPEFor(expr) - fb += wa.LocalTee(exprLocal) - - fb += wa.LocalGet(exprLocal) - fb += wa.StructGet(genTypeID.ObjectStruct, genFieldID.objStruct.vtable) - fb += wa.StructGet(genTypeID.typeData, genFieldID.typeData.cloneFunction) - // cloneFunction: (ref jl.Object) -> (ref jl.Object) - fb += wa.CallRef(genTypeID.cloneFunctionType) - - // cast the (ref jl.Object) back down to the result type - transformSingleType(exprType) match { - case watpe.RefType(_, watpe.HeapType.Type(genTypeID.ObjectStruct)) => - // no need to cast to (ref null? jl.Object) - case wasmType: watpe.RefType => - fb += wa.RefCast(wasmType.toNonNullable) - case wasmType => - // Since no hijacked class extends jl.Cloneable, this case cannot happen - throw new AssertionError( - s"Unexpected type for Clone: $exprType (Wasm: $wasmType)") - } - - exprType - } - } - private def genMatch(tree: Match, expectedType: Type): Type = { val Match(selector, cases, defaultBody) = tree @@ -3410,7 +3328,6 @@ private class FunctionEmitter private ( case Transients.ObjectClassName(obj) => genTreeToAny(obj) markPosition(tree) - genAsNonNullOrNPEFor(obj) fb += wa.Call(genFunctionID.anyGetClassName) StringType diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index 763255f2c1..c6dd5e7fd8 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -747,9 +747,6 @@ private final class ClassDefChecker(classDef: ClassDef, checkTree(block, env) checkTree(finalizer, env) - case Throw(expr) => - checkTree(expr, env) - case Match(selector, cases, default) => checkTree(selector, env) for ((alts, body) <- cases) { @@ -816,12 +813,7 @@ private final class ClassDefChecker(classDef: ClassDef, checkArrayTypeRef(typeRef) checkTrees(elems, env) - case ArrayLength(array) => - checkArrayReceiverType(array.tpe) - checkTree(array, env) - case ArraySelect(array, index) => - checkArrayReceiverType(array.tpe) checkTree(array, env) checkTree(index, env) @@ -857,21 +849,6 @@ private final class ClassDefChecker(classDef: ClassDef, // ok } - case GetClass(expr) => - checkTree(expr, env) - - case Clone(expr) => - checkTree(expr, env) - - case IdentityHashCode(expr) => - checkTree(expr, env) - - case WrapAsThrowable(expr) => - checkTree(expr, env) - - case UnwrapFromThrowable(expr) => - checkTree(expr, env) - case LinkTimeProperty(name) => // JavaScript expressions @@ -1019,6 +996,10 @@ private final class ClassDefChecker(classDef: ClassDef, transient.traverse(new Traversers.Traverser { override def traverse(tree: Tree): Unit = checkTree(tree, env) }) + + case _:Throw | _:ArrayLength | _:GetClass | _:Clone | _:IdentityHashCode | + _:WrapAsThrowable | _:UnwrapFromThrowable => + reportError(i"illegal legacy node of class ${tree.getClass().getSimpleName()}") } newEnv @@ -1029,13 +1010,6 @@ private final class ClassDefChecker(classDef: ClassDef, reportError("invalid transient tree") } - private def checkArrayReceiverType(tpe: Type)( - implicit ctx: ErrorContext): Unit = tpe match { - case tpe: ArrayType => checkArrayType(tpe) - case NullType | NothingType => // ok - case _ => reportError(i"Array type expected but $tpe found") - } - private def checkArrayType(tpe: ArrayType)( implicit ctx: ErrorContext): Unit = { checkArrayTypeRef(tpe.arrayTypeRef) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index dcd87e7c5a..fd17c06147 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -330,9 +330,6 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, typecheckExpect(block, env, tpe) typecheck(finalizer, env) - case Throw(expr) => - typecheckAny(expr, env) - case Match(selector, cases, default) => // Typecheck the selector as an int or a java.lang.String typecheck(selector, env) @@ -466,6 +463,16 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, i"with non-object result type: $resultType") } + case UnaryOp(UnaryOp.Array_length, lhs) => + // Array_length is a bit special because it allows any non-nullable array type + typecheck(lhs, env) + lhs.tpe match { + case NothingType | ArrayType(_, false) => + // ok + case other => + reportError(i"Array type expected but $other found") + } + case UnaryOp(op, lhs) => import UnaryOp._ val expectedArgType = (op: @switch) match { @@ -487,11 +494,17 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, DoubleType case String_length => StringType - case CheckNotNull => + case CheckNotNull | IdentityHashCode | WrapAsThrowable | Throw => AnyType case Class_name | Class_isPrimitive | Class_isInterface | Class_isArray | Class_componentType | Class_superClass => ClassType(ClassClass, nullable = false) + case GetClass => + AnyNotNullType + case Clone => + ClassType(CloneableClass, nullable = false) + case UnwrapFromThrowable => + ClassType(ThrowableClass, nullable = false) } typecheckExpect(lhs, env, expectedArgType) @@ -541,13 +554,6 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, for (elem <- elems) typecheckExpect(elem, env, elemType) - case ArrayLength(array) => - typecheckExpr(array, env) - if (array.tpe != NullType && - array.tpe != NothingType && - !array.tpe.isInstanceOf[ArrayType]) - reportError(i"Array type expected but ${array.tpe} found") - case ArraySelect(array, index) => typecheckExpect(index, env, IntType) typecheckExpr(array, env) @@ -569,21 +575,6 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, typecheckAny(expr, env) checkIsAsInstanceTargetType(tpe) - case GetClass(expr) => - typecheckAny(expr, env) - - case Clone(expr) => - typecheckExpect(expr, env, ClassType(CloneableClass, nullable = true)) - - case IdentityHashCode(expr) => - typecheckAny(expr, env) - - case WrapAsThrowable(expr) => - typecheckAny(expr, env) - - case UnwrapFromThrowable(expr) => - typecheckExpect(expr, env, ClassType(ThrowableClass, nullable = true)) - case LinkTimeProperty(name) => // JavaScript expressions @@ -768,6 +759,10 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, case _:RecordSelect | _:RecordValue | _:Transient | _:JSSuperConstructorCall => reportError("invalid tree") + + case _:Throw | _:ArrayLength | _:GetClass | _:Clone | _:IdentityHashCode | + _:WrapAsThrowable | _:UnwrapFromThrowable => + reportError(i"illegal legacy node of class ${tree.getClass().getSimpleName()}") } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index 5b2cf7ad5e..589220ef0f 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -882,8 +882,8 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: case UnaryOp(UnaryOp.CheckNotNull, expr) => config.coreSpec.semantics.nullPointers == CheckedBehavior.Unchecked && isTriviallySideEffectFree(expr) - case GetClass(expr) => // Before 1.17, we used GetClass as CheckNotNull - config.coreSpec.semantics.nullPointers == CheckedBehavior.Unchecked && + case UnaryOp(op, expr) => // Before 1.17, we used GetClass as CheckNotNull + UnaryOp.isSideEffectFreeOp(op) && isTriviallySideEffectFree(expr) case New(className, _, args) => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 94a294a4cc..61612d9783 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -456,9 +456,6 @@ private[optimizer] abstract class OptimizerCore( val newFinalizer = transformStat(finalizer) TryFinally(newBlock, newFinalizer) - case Throw(expr) => - Throw(transformExpr(expr)) - case Match(selector, cases, default) => val newSelector = transformExpr(selector) newSelector match { @@ -533,9 +530,6 @@ private[optimizer] abstract class OptimizerCore( case ArrayValue(tpe, elems) => ArrayValue(tpe, elems map transformExpr) - case ArrayLength(array) => - ArrayLength(transformExpr(array)) - case ArraySelect(array, index) => val newArray = transformExpr(array) @@ -588,44 +582,6 @@ private[optimizer] abstract class OptimizerCore( } } - case GetClass(expr) => - trampoline { - pretransformExpr(expr) { texpr => - def constant(typeRef: TypeRef): TailRec[Tree] = - TailCalls.done(Block(checkNotNullStatement(texpr), ClassOf(typeRef))) - - texpr.tpe match { - case RefinedType(ClassType(LongImpl.RuntimeLongClass, false), true) => - constant(ClassRef(BoxedLongClass)) - case RefinedType(ClassType(className, false), true) => - constant(ClassRef(className)) - case RefinedType(ArrayType(arrayTypeRef, false), true) => - constant(arrayTypeRef) - case RefinedType(AnyType | AnyNotNullType | ClassType(ObjectClass, _), _) => - // The result can be anything, including null - TailCalls.done(GetClass(finishTransformExpr(texpr))) - case _ => - /* If texpr.tpe is neither AnyType nor j.l.Object, it cannot be - * a JS object, so its getClass() cannot be null. Cast away - * nullability to help downstream optimizations. - */ - val newGetClass = GetClass(finishTransformExpr(texpr)) - TailCalls.done(makeCast(newGetClass, newGetClass.tpe.toNonNullable)) - } - } - } - - case Clone(expr) => - Clone(transformExpr(expr)) - - case IdentityHashCode(expr) => - IdentityHashCode(transformExpr(expr)) - - case _:WrapAsThrowable | _:UnwrapFromThrowable => - trampoline { - pretransformExpr(tree)(finishTransform(isStat)) - } - case prop: LinkTimeProperty => config.coreSpec.linkTimeProperties.transformLinkTimeProperty(prop) @@ -993,42 +949,6 @@ private[optimizer] abstract class OptimizerCore( case tree: BinaryOp => pretransformBinaryOp(tree)(cont) - case WrapAsThrowable(expr) => - pretransformExpr(expr) { texpr => - if (isSubtype(texpr.tpe.base, ThrowableClassType.toNonNullable)) { - cont(texpr) - } else { - if (texpr.tpe.isExact) { - pretransformNew(AllocationSite.Tree(tree), JavaScriptExceptionClass, - MethodIdent(AnyArgConstructorName), texpr :: Nil)(cont) - } else { - cont(PreTransTree(WrapAsThrowable(finishTransformExpr(texpr)))) - } - } - } - - case UnwrapFromThrowable(expr) => - pretransformExpr(expr) { texpr => - def default = - cont(PreTransTree(UnwrapFromThrowable(finishTransformExpr(texpr)))) - - val baseTpe = texpr.tpe.base - - if (baseTpe == NothingType) { - cont(texpr) - } else if (baseTpe == NullType) { - cont(checkNotNull(texpr)) - } else if (isSubtype(baseTpe, JavaScriptExceptionClassType)) { - pretransformSelectCommon(AnyType, texpr, optQualDeclaredType = None, - FieldIdent(exceptionFieldName), isLhsOfAssign = false)(cont) - } else { - if (texpr.tpe.isExact || !isSubtype(JavaScriptExceptionClassType.toNonNullable, baseTpe)) - cont(checkNotNull(texpr)) - else - default - } - } - case tree: JSSelect => pretransformJSSelect(tree, isLhsOfAssign = false)(cont) @@ -1565,7 +1485,7 @@ private[optimizer] abstract class OptimizerCore( finishTransformBindings(bindingsAndStats, finishTransformStat(result)) case PreTransUnaryOp(op, lhs) => - if (op == UnaryOp.CheckNotNull) + if (!UnaryOp.isSideEffectFreeOp(op)) finishTransformExpr(stat) else finishTransformStat(lhs) @@ -1689,15 +1609,13 @@ private[optimizer] abstract class OptimizerCore( keepOnlySideEffects(length) case ArrayValue(_, elems) => Block(elems.map(keepOnlySideEffects(_)))(stat.pos) - case ArrayLength(array) => - checkNotNullStatement(array)(stat.pos) case ArraySelect(array, index) if semantics.arrayIndexOutOfBounds == CheckedBehavior.Unchecked => Block(checkNotNullStatement(array)(stat.pos), keepOnlySideEffects(index))(stat.pos) case Select(qualifier, _) => checkNotNullStatement(qualifier)(stat.pos) case Closure(_, _, _, _, _, captureValues) => Block(captureValues.map(keepOnlySideEffects))(stat.pos) - case UnaryOp(op, arg) if op != UnaryOp.CheckNotNull => + case UnaryOp(op, arg) if UnaryOp.isSideEffectFreeOp(op) => keepOnlySideEffects(arg) case If(cond, thenp, elsep) => (keepOnlySideEffects(thenp), keepOnlySideEffects(elsep)) match { @@ -1716,14 +1634,6 @@ private[optimizer] abstract class OptimizerCore( Block(elems.map(keepOnlySideEffects))(stat.pos) case RecordSelect(record, _) => keepOnlySideEffects(record) - case GetClass(expr) => - checkNotNullStatement(expr)(stat.pos) - case Clone(expr) => - checkNotNullStatement(expr)(stat.pos) - case WrapAsThrowable(expr) => - keepOnlySideEffects(expr) - case UnwrapFromThrowable(expr) => - checkNotNullStatement(expr)(stat.pos) /* By definition, a failed cast is always UB, so it cannot have side effects. * However, if the target type is `nothing`, we keep the cast not to lose @@ -1873,9 +1783,6 @@ private[optimizer] abstract class OptimizerCore( case If(cond, thenp, elsep) => rec(cond).mapOrFailed(If(_, thenp, elsep)(body.tpe)) - case Throw(expr) => - rec(expr).mapOrFailed(Throw(_)) - case Match(selector, cases, default) => rec(selector).mapOrFailed(Match(_, cases, default)(body.tpe)) @@ -1914,7 +1821,7 @@ private[optimizer] abstract class OptimizerCore( recs(args).mapOrFailed(ApplyStatic(flags, className, method, _)(body.tpe)) case UnaryOp(op, arg) => - rec(arg).mapOrKeepGoingIf(UnaryOp(op, _))(keepGoingIf = op != UnaryOp.CheckNotNull) + rec(arg).mapOrKeepGoingIf(UnaryOp(op, _))(keepGoingIf = UnaryOp.isPureOp(op)) case BinaryOp(op, lhs, rhs) => import BinaryOp._ @@ -1941,9 +1848,6 @@ private[optimizer] abstract class OptimizerCore( case ArrayValue(typeRef, elems) => recs(elems).mapOrKeepGoing(ArrayValue(typeRef, _)) - case ArrayLength(array) => - rec(array).mapOrKeepGoingIf(ArrayLength(_))(keepGoingIf = isNotNull(array)) - case ArraySelect(array, index) => rec(array) match { case Success(newArray) => @@ -1973,12 +1877,6 @@ private[optimizer] abstract class OptimizerCore( case Transient(Cast(expr, tpe)) => rec(expr).mapOrKeepGoing(newExpr => makeCast(newExpr, tpe)) - case GetClass(expr) => - rec(expr).mapOrKeepGoingIf(GetClass(_))(keepGoingIf = isNotNull(expr)) - - case Clone(expr) => - rec(expr).mapOrFailed(Clone(_)) - case JSUnaryOp(op, arg) => rec(arg).mapOrFailed(JSUnaryOp(op, _)) @@ -2911,8 +2809,7 @@ private[optimizer] abstract class OptimizerCore( val tarray = targs.head tarray.tpe.base match { case _: ArrayType => - val array = finishTransformExpr(tarray) - contTree(Trees.ArrayLength(array)) + cont(foldUnaryOp(UnaryOp.Array_length, checkNotNull(tarray))) case _ => default } @@ -3094,7 +2991,8 @@ private[optimizer] abstract class OptimizerCore( If( Transient(WasmBinaryOp(WasmBinaryOp.I32GtU, cpLocalDef.newReplacement, IntLiteral(Character.MAX_CODE_POINT))), - Throw(New(IllegalArgumentExceptionClass, MethodIdent(NoArgConstructorName), Nil)), + UnaryOp(UnaryOp.Throw, + New(IllegalArgumentExceptionClass, MethodIdent(NoArgConstructorName), Nil)), Skip() )(VoidType), Transient(WasmStringFromCodePoint(cpLocalDef.newReplacement)) @@ -3187,10 +3085,16 @@ private[optimizer] abstract class OptimizerCore( case ClassGetName => optTReceiver.get match { case PreTransMaybeBlock(bindingsAndStats, - PreTransTree(MaybeCast(GetClass(expr)), _)) => + PreTransTree(MaybeCast(UnaryOp(UnaryOp.GetClass, expr)), _)) => contTree(finishTransformBindings( bindingsAndStats, Transient(ObjectClassName(expr)))) + // Same thing, but the argument stayed as a PreTransUnaryOp + case PreTransMaybeBlock(bindingsAndStats, + PreTransUnaryOp(UnaryOp.GetClass, texpr)) => + contTree(finishTransformBindings( + bindingsAndStats, Transient(ObjectClassName(finishTransformExpr(texpr))))) + case _ => default } @@ -3425,7 +3329,7 @@ private[optimizer] abstract class OptimizerCore( * coming from Scala.js < 1.15.1 (since 1.15.1, we intercept that shape * already in the compiler back-end). */ - case If(cond, th: Throw, Assign(Select(This(), _), value)) :: rest => + case If(cond, th @ UnaryOp(UnaryOp.Throw, _), Assign(Select(This(), _), value)) :: rest => // work around a bug of the compiler (these should be @-bindings) val stat = stats.head.asInstanceOf[If] val ass = stat.elsep.asInstanceOf[Assign] @@ -3567,7 +3471,42 @@ private[optimizer] abstract class OptimizerCore( val UnaryOp(op, arg) = tree pretransformExpr(arg) { tlhs => - expandLongOps(foldUnaryOp(op, tlhs))(cont) + def folded: PreTransform = + foldUnaryOp(op, tlhs) + + op match { + case UnaryOp.WrapAsThrowable => + if (isSubtype(tlhs.tpe.base, ThrowableClassType.toNonNullable)) { + cont(tlhs) + } else { + if (tlhs.tpe.isExact) { + pretransformNew(AllocationSite.Tree(tree), JavaScriptExceptionClass, + MethodIdent(AnyArgConstructorName), tlhs :: Nil)(cont) + } else { + cont(folded) + } + } + + case UnaryOp.UnwrapFromThrowable => + val baseTpe = tlhs.tpe.base + + if (baseTpe == NothingType) { + cont(tlhs) + } else if (baseTpe == NullType) { + cont(checkNotNull(tlhs)) + } else if (isSubtype(baseTpe, JavaScriptExceptionClassType)) { + pretransformSelectCommon(AnyType, tlhs, optQualDeclaredType = None, + FieldIdent(exceptionFieldName), isLhsOfAssign = false)(cont) + } else { + if (tlhs.tpe.isExact || !isSubtype(JavaScriptExceptionClassType.toNonNullable, baseTpe)) + cont(checkNotNull(tlhs)) + else + cont(folded) + } + + case _ => + expandLongOps(folded)(cont) + } } } @@ -3949,6 +3888,28 @@ private[optimizer] abstract class OptimizerCore( default } + case GetClass => + def constant(typeRef: TypeRef): PreTransform = + PreTransTree(Block(finishTransformStat(arg), ClassOf(typeRef))) + + arg.tpe match { + case RefinedType(ClassType(LongImpl.RuntimeLongClass, false), true) => + constant(ClassRef(BoxedLongClass)) + case RefinedType(ClassType(className, false), true) => + constant(ClassRef(className)) + case RefinedType(ArrayType(arrayTypeRef, false), true) => + constant(arrayTypeRef) + case RefinedType(AnyType | AnyNotNullType | ClassType(ObjectClass, _), _) => + // The result can be anything, including null + default + case _ => + /* If texpr.tpe is neither AnyType nor j.l.Object, it cannot be + * a JS object, so its getClass() cannot be null. Cast away + * nullability to help downstream optimizations. + */ + foldCast(default, ClassType(ClassClass, nullable = false)) + } + case _ => default } @@ -6919,7 +6880,6 @@ private[optimizer] object OptimizerCore { case ApplyStatically(_, receiver, _, _, Nil) => isTrivialArg(receiver) case ApplyStatic(_, _, _, Nil) => true - case ArrayLength(array) => isTrivialArg(array) case ArraySelect(array, index) => isTrivialArg(array) && isTrivialArg(index) case AsInstanceOf(inner, _) => isSimpleArg(inner) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala index a0333eccf1..0fe3988e97 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala @@ -31,6 +31,7 @@ import org.scalajs.linker.interface._ import org.scalajs.linker.interface.unstable.IRFileImpl import org.scalajs.linker.standard._ import org.scalajs.linker.frontend.Refiner +import org.scalajs.linker.backend.emitter.PrivateLibHolder import org.scalajs.linker.testutils._ import org.scalajs.linker.testutils.TestIRBuilder._ @@ -83,7 +84,7 @@ class IRCheckerTest { callMethOn(ApplyStatic(EAF, MainTestClassName, nullBarMethodName, Nil)(ClassType("Bar", nullable = true))), callMethOn(Null()), - callMethOn(Throw(Null())) + callMethOn(UnaryOp(UnaryOp.Throw, Null())) )) ) ) @@ -255,15 +256,45 @@ class IRCheckerTest { } } + @Test + def unaryOpNonNullableArguments(): AsyncResult = await { + import UnaryOp._ + + // List of ops that take non-nullable reference types as argument + val ops = List( + Class_name, + Class_isPrimitive, + Class_isInterface, + Class_isArray, + Class_componentType, + Class_superClass, + Array_length, + GetClass, + Clone, + UnwrapFromThrowable + ) + + val results = for (op <- ops) yield { + val classDefs = Seq( + mainTestClassDef(UnaryOp(op, Null())) + ) + + for (log <- testLinkIRErrors(classDefs, MainTestModuleInitializers)) yield { + log.assertContainsError("expected but null found") + } + } + + Future.sequence(results) + } + @Test def arrayOpsNullOrNothing(): AsyncResult = await { val classDefs = Seq( mainTestClassDef( Block( ArraySelect(Null(), int(1))(NothingType), - ArrayLength(Null()), - ArraySelect(Throw(Null()), int(1))(NothingType), - ArrayLength(Throw(Null())) + ArraySelect(UnaryOp(UnaryOp.Throw, Null()), int(1))(NothingType), + UnaryOp(UnaryOp.Array_length, UnaryOp(UnaryOp.Throw, Null())) ) ) ) @@ -453,7 +484,8 @@ object IRCheckerTest { val linkerFrontend = StandardLinkerFrontend(config) val irFiles = ( stdLibFiles ++ - classDefs.map(MemClassDefIRFile(_)) + classDefs.map(MemClassDefIRFile(_)) ++ + PrivateLibHolder.files ) linkerFrontend.link(irFiles, moduleInitializers, noSymbolRequirements, logger) } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala index 7b37ab5648..3954dfe278 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala @@ -76,7 +76,7 @@ class OptimizerTest { // @noinline def witness(): AnyRef = throw null MethodDef(EMF, witnessMethodName, NON, Nil, AnyType, Some { - Throw(Null()) + UnaryOp(UnaryOp.Throw, Null()) })(EOH.withNoinline(true), UNV), // @noinline def reachClone(): Object = clone() @@ -124,10 +124,10 @@ class OptimizerTest { } }.hasExactly(if (inlinedWhenOnObject) 1 else 0, "IsInstanceOf node") { case IsInstanceOf(_, _) => true - }.hasExactly(if (inlinedWhenOnObject) 1 else 0, "Throw node") { - case Throw(_) => true + }.hasExactly(if (inlinedWhenOnObject) 1 else 0, "throw operation") { + case UnaryOp(UnaryOp.Throw, _) => true }.hasExactly(if (inlinedWhenOnObject) 3 else 2, "built-in () operations") { - case Clone(_) => true + case UnaryOp(UnaryOp.Clone, _) => true }.hasExactly(if (inlinedWhenOnObject) 0 else 1, "call to clone() (not inlined)") { case Apply(_, _, MethodIdent(`cloneMethodName`), _) => true } @@ -289,7 +289,7 @@ class OptimizerTest { )), Return(voidReturnArgument, matchResult1) )), - Throw(New("java.lang.Exception", NoArgConstructorName, Nil)) + UnaryOp(UnaryOp.Throw, New("java.lang.Exception", NoArgConstructorName, Nil)) )) )) ) diff --git a/project/Build.scala b/project/Build.scala index 487e8482e7..2fc1510e58 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2066,7 +2066,7 @@ object Build { if (!useMinifySizes) { Some(ExpectedSizes( fastLink = 449000 to 450000, - fullLink = 95000 to 96000, + fullLink = 94000 to 95000, fastLinkGz = 58000 to 59000, fullLinkGz = 25000 to 26000, )) From e4bdf168d46637db211d87bf9312a79740f8ec04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 15 Dec 2024 15:19:05 +0100 Subject: [PATCH 049/121] Remove the IR nodes that were merged in `UnaryOp`. And update the compiler to directly generate the new `UnaryOp`s. We only enable the deserialization hack when reading IR <= 1.17. --- .../org/scalajs/nscplugin/GenJSCode.scala | 24 +++++++------ .../org/scalajs/nscplugin/GenJSExports.scala | 2 +- .../nscplugin/test/OptimizationTest.scala | 4 +-- .../main/scala/org/scalajs/ir/Hashers.scala | 28 --------------- .../main/scala/org/scalajs/ir/Printers.scala | 32 ----------------- .../scala/org/scalajs/ir/Serializers.scala | 30 +--------------- .../scala/org/scalajs/ir/Transformers.scala | 21 ----------- .../scala/org/scalajs/ir/Traversers.scala | 21 ----------- .../src/main/scala/org/scalajs/ir/Trees.scala | 35 ------------------- .../scala/org/scalajs/ir/PrintersTest.scala | 29 --------------- .../backend/wasmemitter/FunctionEmitter.scala | 5 --- .../linker/checker/ClassDefChecker.scala | 4 --- .../scalajs/linker/checker/IRChecker.scala | 4 --- project/BinaryIncompatibilities.scala | 14 ++++++++ project/JavaLangObject.scala | 9 ++--- 15 files changed, 37 insertions(+), 225 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 674ffc3ba6..a64d6a8656 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -2572,9 +2572,10 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) ex match { case js.New(cls, _, _) if cls != JavaScriptExceptionClassName => // Common case where ex is neither null nor a js.JavaScriptException - js.Throw(ex) + js.UnaryOp(js.UnaryOp.Throw, ex) case _ => - js.Throw(js.UnwrapFromThrowable(ex)) + js.UnaryOp(js.UnaryOp.Throw, + js.UnaryOp(js.UnaryOp.UnwrapFromThrowable, js.UnaryOp(js.UnaryOp.CheckNotNull, ex))) } /* !!! Copy-pasted from `CleanUp.scala` upstream and simplified with @@ -3108,13 +3109,14 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val (exceptValDef, exceptVar) = if (mightCatchJavaScriptException) { val valDef = js.VarDef(freshLocalIdent("e"), NoOriginalName, - encodeClassType(ThrowableClass), mutable = false, js.WrapAsThrowable(origExceptVar)) + encodeClassType(ThrowableClass), mutable = false, + js.UnaryOp(js.UnaryOp.WrapAsThrowable, origExceptVar)) (valDef, valDef.ref) } else { (js.Skip(), origExceptVar) } - val elseHandler: js.Tree = js.Throw(origExceptVar) + val elseHandler: js.Tree = js.UnaryOp(js.UnaryOp.Throw, origExceptVar) val handler = catches.foldRight(elseHandler) { (caseDef, elsep) => implicit val pos = caseDef.pos @@ -3323,7 +3325,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) private def genThrowClassCastException()(implicit pos: Position): js.Tree = { val ctor = ClassCastExceptionClass.info.member( nme.CONSTRUCTOR).suchThat(_.tpe.params.isEmpty) - js.Throw(genNew(ClassCastExceptionClass, ctor, Nil)) + js.UnaryOp(js.UnaryOp.Throw, genNew(ClassCastExceptionClass, ctor, Nil)) } /** Gen JS code for a super call, of the form Class.super[mix].fun(args). @@ -4891,7 +4893,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.Assign(genSelect(fun.tpe.paramTypes(1)), arguments(1)) } else { // length of the array - js.ArrayLength(arrayValue) + js.UnaryOp(js.UnaryOp.Array_length, + js.UnaryOp(js.UnaryOp.CheckNotNull, arrayValue)) } } @@ -5326,7 +5329,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) case IDENTITY_HASH_CODE => // runtime.identityHashCode(arg) val arg = genArgs1 - js.IdentityHashCode(arg) + js.UnaryOp(js.UnaryOp.IdentityHashCode, arg) case DEBUGGER => // js.special.debugger() @@ -5463,7 +5466,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) case JS_THROW => // js.special.throw(arg) - js.Throw(genArgs1) + js.UnaryOp(js.UnaryOp.Throw, genArgs1) case JS_TRY_CATCH => /* js.special.tryCatch(arg1, arg2) @@ -5502,11 +5505,12 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) case WRAP_AS_THROWABLE => // js.special.wrapAsThrowable(arg) - js.WrapAsThrowable(genArgs1) + js.UnaryOp(js.UnaryOp.WrapAsThrowable, genArgs1) case UNWRAP_FROM_THROWABLE => // js.special.unwrapFromThrowable(arg) - js.UnwrapFromThrowable(genArgs1) + js.UnaryOp(js.UnaryOp.UnwrapFromThrowable, + js.UnaryOp(js.UnaryOp.CheckNotNull, genArgs1)) case LINKTIME_PROPERTY => // LinkingInfo.linkTimePropertyXXX("...") diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala index 603a6286d3..4cdc4369c3 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala @@ -1022,7 +1022,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { private def genThrowTypeError(msg: String = "No matching overload")( implicit pos: Position): js.Tree = { - js.Throw(js.StringLiteral(msg)) + js.UnaryOp(js.UnaryOp.Throw, js.StringLiteral(msg)) } class FormalArgsRegistry(minArgc: Int, needsRestParam: Boolean) { diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala index 24914ca07b..b10bef4b95 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala @@ -519,7 +519,7 @@ class OptimizationTest extends JSASTTest { } } """.hasNot("WrapAsThrowable") { - case js.WrapAsThrowable(_) => + case js.UnaryOp(js.UnaryOp.WrapAsThrowable, _) => } // Confidence check @@ -536,7 +536,7 @@ class OptimizationTest extends JSASTTest { } } """.hasExactly(1, "WrapAsThrowable") { - case js.WrapAsThrowable(_) => + case js.UnaryOp(js.UnaryOp.WrapAsThrowable, _) => } } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala index a653c15fce..770d83a7c7 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala @@ -232,10 +232,6 @@ object Hashers { mixTree(finalizer) mixType(tree.tpe) - case Throw(expr) => - mixTag(TagThrow) - mixTree(expr) - case Match(selector, cases, default) => mixTag(TagMatch) mixTree(selector) @@ -331,10 +327,6 @@ object Hashers { mixArrayTypeRef(typeRef) mixTrees(elems) - case ArrayLength(array) => - mixTag(TagArrayLength) - mixTree(array) - case ArraySelect(array, index) => mixTag(TagArraySelect) mixTree(array) @@ -362,26 +354,6 @@ object Hashers { mixTree(expr) mixType(tpe) - case GetClass(expr) => - mixTag(TagGetClass) - mixTree(expr) - - case Clone(expr) => - mixTag(TagClone) - mixTree(expr) - - case IdentityHashCode(expr) => - mixTag(TagIdentityHashCode) - mixTree(expr) - - case WrapAsThrowable(expr) => - mixTag(TagWrapAsThrowable) - mixTree(expr) - - case UnwrapFromThrowable(expr) => - mixTag(TagUnwrapFromThrowable) - mixTree(expr) - case JSNew(ctor, args) => mixTag(TagJSNew) mixTree(ctor) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala index 3008148c71..d6a490b634 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -260,10 +260,6 @@ object Printers { print(" finally ") printBlock(finalizer) - case Throw(expr) => - print("throw ") - print(expr) - case Match(selector, cases, default) => print("match (") print(selector) @@ -532,10 +528,6 @@ object Printers { print(typeRef) printArgs(elems) - case ArrayLength(array) => - print(array) - print(".length") - case ArraySelect(array, index) => print(array) print('[') @@ -571,30 +563,6 @@ object Printers { print(tpe) print(']') - case GetClass(expr) => - print(expr) - print(".getClass()") - - case Clone(expr) => - print("(") - print(expr) - print(')') - - case IdentityHashCode(expr) => - print("(") - print(expr) - print(')') - - case WrapAsThrowable(expr) => - print("(") - print(expr) - print(")") - - case UnwrapFromThrowable(expr) => - print("(") - print(expr) - print(")") - // JavaScript expressions case JSNew(ctor, args) => diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index 369cb5990c..66c3249f9e 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -299,10 +299,6 @@ object Serializers { writeTagAndPos(TagTryFinally) writeTree(block); writeTree(finalizer) - case Throw(expr) => - writeTagAndPos(TagThrow) - writeTree(expr) - case Match(selector, cases, default) => writeTagAndPos(TagMatch) writeTree(selector) @@ -377,10 +373,6 @@ object Serializers { writeTagAndPos(TagArrayValue) writeArrayTypeRef(tpe); writeTrees(elems) - case ArrayLength(array) => - writeTagAndPos(TagArrayLength) - writeTree(array) - case ArraySelect(array, index) => writeTagAndPos(TagArraySelect) writeTree(array); writeTree(index) @@ -403,26 +395,6 @@ object Serializers { writeTagAndPos(TagAsInstanceOf) writeTree(expr); writeType(tpe) - case GetClass(expr) => - writeTagAndPos(TagGetClass) - writeTree(expr) - - case Clone(expr) => - writeTagAndPos(TagClone) - writeTree(expr) - - case IdentityHashCode(expr) => - writeTagAndPos(TagIdentityHashCode) - writeTree(expr) - - case WrapAsThrowable(expr) => - writeTagAndPos(TagWrapAsThrowable) - writeTree(expr) - - case UnwrapFromThrowable(expr) => - writeTagAndPos(TagUnwrapFromThrowable) - writeTree(expr) - case JSNew(ctor, args) => writeTagAndPos(TagJSNew) writeTree(ctor); writeTreeOrJSSpreads(args) @@ -1241,7 +1213,7 @@ object Serializers { case TagArrayLength | TagGetClass | TagClone | TagIdentityHashCode | TagWrapAsThrowable | TagUnwrapFromThrowable | TagThrow => - if (false /*!hacks.use17*/) { // scalastyle:ignore + if (!hacks.use17) { throw new IOException( s"Illegal legacy node $tag found in class ${enclosingClassName.nameString}") } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala index 5aa07f32ff..7be426e058 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala @@ -73,9 +73,6 @@ object Transformers { case TryFinally(block, finalizer) => TryFinally(transform(block), transform(finalizer)) - case Throw(expr) => - Throw(transform(expr)) - case Match(selector, cases, default) => Match(transform(selector), cases.map(c => (c._1, transform(c._2))), transform(default))(tree.tpe) @@ -114,9 +111,6 @@ object Transformers { case ArrayValue(tpe, elems) => ArrayValue(tpe, transformTrees(elems)) - case ArrayLength(array) => - ArrayLength(transform(array)) - case ArraySelect(array, index) => ArraySelect(transform(array), transform(index))(tree.tpe) @@ -132,21 +126,6 @@ object Transformers { case AsInstanceOf(expr, tpe) => AsInstanceOf(transform(expr), tpe) - case GetClass(expr) => - GetClass(transform(expr)) - - case Clone(expr) => - Clone(transform(expr)) - - case IdentityHashCode(expr) => - IdentityHashCode(transform(expr)) - - case WrapAsThrowable(expr) => - WrapAsThrowable(transform(expr)) - - case UnwrapFromThrowable(expr) => - UnwrapFromThrowable(transform(expr)) - // JavaScript expressions case JSNew(ctor, args) => diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala index ec39bb1086..d242bbfce7 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala @@ -64,9 +64,6 @@ object Traversers { traverse(block) traverse(finalizer) - case Throw(expr) => - traverse(expr) - case Match(selector, cases, default) => traverse(selector) cases foreach (c => (c._1 map traverse, traverse(c._2))) @@ -107,9 +104,6 @@ object Traversers { case ArrayValue(tpe, elems) => elems foreach traverse - case ArrayLength(array) => - traverse(array) - case ArraySelect(array, index) => traverse(array) traverse(index) @@ -126,21 +120,6 @@ object Traversers { case AsInstanceOf(expr, _) => traverse(expr) - case GetClass(expr) => - traverse(expr) - - case Clone(expr) => - traverse(expr) - - case IdentityHashCode(expr) => - traverse(expr) - - case WrapAsThrowable(expr) => - traverse(expr) - - case UnwrapFromThrowable(expr) => - traverse(expr) - // JavaScript expressions case JSNew(ctor, args) => diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index 031970c895..6cd4db23c7 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -188,10 +188,6 @@ object Trees { val tpe = block.tpe } - sealed case class Throw(expr: Tree)(implicit val pos: Position) extends Tree { - val tpe = NothingType - } - /** A break-free switch (without fallthrough behavior). * * Unlike a JavaScript switch, it can be used in expression position. @@ -562,11 +558,6 @@ object Trees { val tpe = ArrayType(typeRef, nullable = false) } - sealed case class ArrayLength(array: Tree)(implicit val pos: Position) - extends Tree { - val tpe = IntType - } - sealed case class ArraySelect(array: Tree, index: Tree)(val tpe: Type)( implicit val pos: Position) extends AssignLhs @@ -588,32 +579,6 @@ object Trees { implicit val pos: Position) extends Tree - sealed case class GetClass(expr: Tree)(implicit val pos: Position) - extends Tree { - val tpe = ClassType(ClassClass, nullable = true) - } - - sealed case class Clone(expr: Tree)(implicit val pos: Position) - extends Tree { - // this is OK because our type system does not have singleton types - val tpe: Type = expr.tpe.toNonNullable - } - - sealed case class IdentityHashCode(expr: Tree)(implicit val pos: Position) - extends Tree { - val tpe = IntType - } - - sealed case class WrapAsThrowable(expr: Tree)(implicit val pos: Position) - extends Tree { - val tpe = ClassType(ThrowableClass, nullable = false) - } - - sealed case class UnwrapFromThrowable(expr: Tree)(implicit val pos: Position) - extends Tree { - val tpe = AnyType - } - // JavaScript expressions sealed case class JSNew(ctor: Tree, args: List[TreeOrJSSpread])( diff --git a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala index 06c2d0de56..0b2cb5a9b6 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -267,10 +267,6 @@ class PrintersTest { TryFinally(TryCatch(i(5), "e", NON, i(6))(IntType), i(7))) } - @Test def printThrow(): Unit = { - assertPrintEquals("throw null", Throw(Null())) - } - @Test def printMatch(): Unit = { assertPrintEquals( """ @@ -582,10 +578,6 @@ class PrintersTest { ArrayValue(ArrayTypeRef(IntRef, 2), List(Null()))) } - @Test def printArrayLength(): Unit = { - assertPrintEquals("x.length", ArrayLength(ref("x", arrayType(IntRef, 1)))) - } - @Test def printArraySelect(): Unit = { assertPrintEquals("x[3]", ArraySelect(ref("x", arrayType(IntRef, 1)), i(3))(IntType)) @@ -614,27 +606,6 @@ class PrintersTest { AsInstanceOf(ref("x", AnyType), IntType)) } - @Test def printGetClass(): Unit = { - assertPrintEquals("x.getClass()", GetClass(ref("x", AnyType))) - } - - @Test def printClone(): Unit = { - assertPrintEquals("(x)", Clone(ref("x", arrayType(ObjectClass, 1)))) - } - - @Test def printIdentityHashCode(): Unit = { - assertPrintEquals("(x)", IdentityHashCode(ref("x", AnyType))) - } - - @Test def printWrapAsThrowable(): Unit = { - assertPrintEquals("(e)", WrapAsThrowable(ref("e", AnyType))) - } - - @Test def printUnwrapFromThrowable(): Unit = { - assertPrintEquals("(e)", - UnwrapFromThrowable(ref("e", ClassType(ThrowableClass, nullable = true)))) - } - @Test def printJSNew(): Unit = { assertPrintEquals("new C()", JSNew(ref("C", AnyType), Nil)) assertPrintEquals("new C(4, 5)", JSNew(ref("C", AnyType), List(i(4), i(5)))) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index 78f0b2ec39..bf0e54adc5 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -595,11 +595,6 @@ private class FunctionEmitter private ( case _: JSSuperConstructorCall => throw new AssertionError(s"Invalid tree: $tree") - - case _:Throw | _:ArrayLength | _:GetClass | _:Clone | _:IdentityHashCode | - _:WrapAsThrowable | _:UnwrapFromThrowable => - throw new AssertionError( - s"illegal legacy node of class ${tree.getClass().getSimpleName()}") } genAdapt(generatedType, expectedType) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index c6dd5e7fd8..1e75d4db31 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -996,10 +996,6 @@ private final class ClassDefChecker(classDef: ClassDef, transient.traverse(new Traversers.Traverser { override def traverse(tree: Tree): Unit = checkTree(tree, env) }) - - case _:Throw | _:ArrayLength | _:GetClass | _:Clone | _:IdentityHashCode | - _:WrapAsThrowable | _:UnwrapFromThrowable => - reportError(i"illegal legacy node of class ${tree.getClass().getSimpleName()}") } newEnv diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index fd17c06147..4c2c4f8464 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -759,10 +759,6 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, case _:RecordSelect | _:RecordValue | _:Transient | _:JSSuperConstructorCall => reportError("invalid tree") - - case _:Throw | _:ArrayLength | _:GetClass | _:Clone | _:IdentityHashCode | - _:WrapAsThrowable | _:UnwrapFromThrowable => - reportError(i"illegal legacy node of class ${tree.getClass().getSimpleName()}") } } diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index ec5063ac59..305e97d428 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -7,13 +7,27 @@ object BinaryIncompatibilities { val IR = Seq( // !!! Breaking, OK in minor release ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Printers#IRTreePrinter.print"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$ArrayLength"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$ArrayLength$"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$Clone"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$Clone$"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$GetClass"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$GetClass$"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$IdentityHashCode"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$IdentityHashCode$"), ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$JSLinkingInfo"), ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$JSLinkingInfo$"), ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$LabelIdent"), ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$LabelIdent$"), ProblemFilters.exclude[Problem]("org.scalajs.ir.Trees#Labeled.*"), ProblemFilters.exclude[Problem]("org.scalajs.ir.Trees#Return.*"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$Throw"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$Throw$"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$UnwrapFromThrowable"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$UnwrapFromThrowable$"), ProblemFilters.exclude[Problem]("org.scalajs.ir.Trees#VarRef.*"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$WrapAsThrowable"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$WrapAsThrowable$"), ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.ir.Trees#*.tpe"), ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Types$NoType$"), diff --git a/project/JavaLangObject.scala b/project/JavaLangObject.scala index eb3eedabef..1b32bac8ff 100644 --- a/project/JavaLangObject.scala +++ b/project/JavaLangObject.scala @@ -62,7 +62,7 @@ object JavaLangObject { Nil, ClassType(ClassClass, nullable = true), Some { - GetClass(This()(ThisType)) + UnaryOp(UnaryOp.GetClass, This()(ThisType)) })(OptimizerHints.empty.withInline(true), Unversioned), /* def hashCode(): Int = (this) */ @@ -73,7 +73,7 @@ object JavaLangObject { Nil, IntType, Some { - IdentityHashCode(This()(ThisType)) + UnaryOp(UnaryOp.IdentityHashCode, This()(ThisType)) })(OptimizerHints.empty.withInline(true), Unversioned), /* def equals(that: Object): Boolean = this eq that */ @@ -102,9 +102,10 @@ object JavaLangObject { AnyType, Some { If(IsInstanceOf(This()(ThisType), ClassType(CloneableClass, nullable = false)), { - Clone(AsInstanceOf(This()(ThisType), ClassType(CloneableClass, nullable = true))) + UnaryOp(UnaryOp.Clone, UnaryOp(UnaryOp.CheckNotNull, + AsInstanceOf(This()(ThisType), ClassType(CloneableClass, nullable = true)))) }, { - Throw(New(ClassName("java.lang.CloneNotSupportedException"), + UnaryOp(UnaryOp.Throw, New(ClassName("java.lang.CloneNotSupportedException"), MethodIdent(NoArgConstructorName), Nil)) })(AnyType) })(OptimizerHints.empty.withInline(true), Unversioned), From 24c79c26e938f6bc398a4637c690cb18cf8bf11a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 18 Dec 2024 11:13:21 +0100 Subject: [PATCH 050/121] Fix #4947: Retain the `structure` of `PreTransRecordTree`. Instead of only retaining the `RecordType`. The `structure` contains the complete information required to map `FieldName`s to the record's `SimpleFieldName`s. --- .../frontend/optimizer/IncOptimizer.scala | 7 +- .../frontend/optimizer/OptimizerCore.scala | 291 ++++++++++-------- .../testsuite/compiler/RegressionTest.scala | 29 ++ 3 files changed, 192 insertions(+), 135 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index 5b2cf7ad5e..c7c2f3d1ef 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -804,10 +804,13 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: None } else { val allFields = computeAllInstanceFieldDefs() - Some(new OptimizerCore.InlineableClassStructure(allFields)) + Some(new OptimizerCore.InlineableClassStructure(className, allFields)) } - tryNewInlineable != oldTryNewInlineable + (tryNewInlineable, oldTryNewInlineable) match { + case (Some(n), Some(o)) => !n.sameStructureAs(o) + case _ => tryNewInlineable != oldTryNewInlineable + } } /** UPDATE PASS ONLY, used by `computeInlineableFieldBodies` and `updateTryNewInlineable`. */ diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 94a294a4cc..bfaff0c8d8 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -383,24 +383,41 @@ private[optimizer] abstract class OptimizerCore( case Return(expr, label) => val info = scope.env.labelInfos(label) val newLabel = info.newName + + // Recall that `info.returnedTypes` does not want to contain NothingType if (info.isStat) { val newExpr = transformStat(expr) - info.returnedTypes.value ::= (VoidType, RefinedType.NoRefinedType) - Return(newExpr, newLabel) + if (newExpr.tpe == NothingType) { + newExpr + } else { + /* We need to track even the `VoidType`s. We need to know how many + * `Return`s we produce in order to decide whether we can remove + * the `Labeled`. + */ + info.returnedTreeTypes.value ::= RefinedType.NoRefinedType + Return(newExpr, newLabel) + } } else if (!info.acceptRecords) { val newExpr = transformExpr(expr) - info.returnedTypes.value ::= (newExpr.tpe, RefinedType(newExpr.tpe)) - Return(newExpr, newLabel) + if (newExpr.tpe == NothingType) { + newExpr + } else { + info.returnedTreeTypes.value ::= RefinedType(newExpr.tpe) + Return(newExpr, newLabel) + } } else trampoline { pretransformNoLocalDef(expr) { texpr => - texpr match { - case PreTransRecordTree(newExpr, origType, cancelFun) => - info.returnedTypes.value ::= (newExpr.tpe, origType) - TailCalls.done(Return(newExpr, newLabel)) + val resultTree: Tree = texpr match { + case _ if texpr.tpe.isNothingType => + finishTransformExpr(texpr) + case PreTransRecordTree(newExpr, structure, cancelFun) => + info.returnedStructures.value ::= structure + Return(newExpr, newLabel) case PreTransTree(newExpr, tpe) => - info.returnedTypes.value ::= (newExpr.tpe, tpe) - TailCalls.done(Return(newExpr, newLabel)) + info.returnedTreeTypes.value ::= tpe + Return(newExpr, newLabel) } + TailCalls.done(resultTree) } } @@ -1183,19 +1200,14 @@ private[optimizer] abstract class OptimizerCore( tthenp)) } else { (resolveLocalDef(tthenp), resolveLocalDef(telsep)) match { - case (PreTransRecordTree(thenTree, thenOrigType, thenCancelFun), - PreTransRecordTree(elseTree, elseOrigType, elseCancelFun)) => - val commonType = { - if (thenTree.tpe == elseTree.tpe && thenOrigType == elseOrigType) - thenTree.tpe - else - cancelFun() - } - val refinedOrigType = - constrainedLub(thenOrigType, elseOrigType, tree.tpe) + case (PreTransRecordTree(thenTree, thenStructure, thenCancelFun), + PreTransRecordTree(elseTree, elseStructure, elseCancelFun)) => + if (!thenStructure.sameClassAs(elseStructure)) + cancelFun() + assert(thenTree.tpe == elseTree.tpe) cont(PreTransRecordTree( - If(newCond, thenTree, elseTree)(commonType), - refinedOrigType, + If(newCond, thenTree, elseTree)(thenTree.tpe), + thenStructure, cancelFun)) case (tthenpNoLocalDef, telsepNoLocalDef) => @@ -1274,18 +1286,16 @@ private[optimizer] abstract class OptimizerCore( case _ => def default: TailRec[Tree] = { resolveLocalDef(preTransQual) match { - case PreTransRecordTree(newQual, origType, cancelFun) => - val recordType = newQual.tpe.asInstanceOf[RecordType] - /* FIXME How come this lookup requires only the `simpleName`? - * The `recordType` is created at `InlineableClassStructure.recordType`, - * where it uses an allocator. Something fishy is going on here. - * (And no, this is not dead code.) - */ - val recordField = recordType.findField(field.name.simpleName) + case PreTransRecordTree(newQual, structure, cancelFun) => + val recordField = structure.findField(field.name) val sel = RecordSelect(newQual, SimpleFieldIdent(recordField.name))(recordField.tpe) sel.tpe match { case _: RecordType => - cont(PreTransRecordTree(sel, RefinedType(expectedType), cancelFun)) + /* We're trying to select a reified subrecord. We lost its + * structure so cannot build an accurate `PreTransRecordTree`. + * This did not happen in our test suite at the time of writing. + */ + cancelFun() case _ => cont(PreTransTree(sel)) } @@ -1352,34 +1362,29 @@ private[optimizer] abstract class OptimizerCore( cont(PreTransTree(Assign(lhs.asInstanceOf[AssignLhs], rhs))) resolveLocalDef(tlhs) match { - case PreTransRecordTree(lhsTree, lhsOrigType, lhsCancelFun) => - val recordType = lhsTree.tpe.asInstanceOf[RecordType] - + case PreTransRecordTree(lhsTree, lhsStructure, lhsCancelFun) => def buildInner(trhs: PreTransform): TailRec[Tree] = { resolveLocalDef(trhs) match { - case PreTransRecordTree(rhsTree, rhsOrigType, rhsCancelFun) => - if (rhsTree.tpe != recordType || rhsOrigType != lhsOrigType) + case PreTransRecordTree(rhsTree, rhsStructure, rhsCancelFun) => + if (!lhsStructure.sameClassAs(rhsStructure)) lhsCancelFun() + assert(rhsTree.tpe == lhsTree.tpe) contAssign(lhsTree, rhsTree) case _ => lhsCancelFun() } } - (trhs.tpe.base, lhsOrigType) match { - case (LongType, RefinedType( - ClassType(LongImpl.RuntimeLongClass, false), true)) => - /* The lhs is a stack-allocated RuntimeLong, but the rhs is - * a primitive Long. We expand the primitive Long into a - * new stack-allocated RuntimeLong so that we do not need - * to cancel. - */ - expandLongValue(trhs) { expandedRhs => - buildInner(expandedRhs) - } - - case _ => - buildInner(trhs) + if (lhsStructure.className == LongImpl.RuntimeLongClass && trhs.tpe.base == LongType) { + /* The lhs is a stack-allocated RuntimeLong, but the rhs is a + * primitive Long. We expand the primitive Long into a new + * stack-allocated RuntimeLong so that we do not need to cancel. + */ + expandLongValue(trhs) { expandedRhs => + buildInner(expandedRhs) + } + } else { + buildInner(trhs) } case PreTransTree(lhsTree, _) => @@ -1413,9 +1418,9 @@ private[optimizer] abstract class OptimizerCore( preTrans match { case PreTransBlock(bindingsAndStats, result) => resolveLocalDef(result) match { - case PreTransRecordTree(tree, tpe, cancelFun) => + case PreTransRecordTree(tree, structure, cancelFun) => PreTransRecordTree(finishTransformBindings(bindingsAndStats, tree), - tpe, cancelFun) + structure, cancelFun) case PreTransTree(tree, tpe) => PreTransTree(finishTransformBindings(bindingsAndStats, tree), tpe) } @@ -1423,11 +1428,11 @@ private[optimizer] abstract class OptimizerCore( case _:PreTransUnaryOp | _:PreTransBinaryOp => PreTransTree(finishTransformExpr(preTrans), preTrans.tpe) - case PreTransLocalDef(localDef @ LocalDef(tpe, _, replacement)) => - replacement match { - case ReplaceWithRecordVarRef(name, recordType, used, cancelFun) => + case PreTransLocalDef(localDef) => + localDef.replacement match { + case ReplaceWithRecordVarRef(name, structure, used, cancelFun) => used.value = used.value.inc - PreTransRecordTree(VarRef(name)(recordType), tpe, cancelFun) + PreTransRecordTree(VarRef(name)(structure.recordType), structure, cancelFun) case InlineClassInstanceReplacement(structure, fieldLocalDefs, cancelFun) => val recordType = structure.recordType @@ -1436,7 +1441,7 @@ private[optimizer] abstract class OptimizerCore( PreTransRecordTree( RecordValue(recordType, structure.fieldNames.map( id => fieldLocalDefs(id).newReplacement)), - tpe, cancelFun) + structure, cancelFun) case _ => PreTransTree(localDef.newReplacement, localDef.tpe) @@ -1447,38 +1452,35 @@ private[optimizer] abstract class OptimizerCore( } } - /** Resolves any [[RecordType]] in a [[PreTransform]]. + /** Resolves the [[InlineableClassStructure]] of a [[PreTransform]], if any. * * If `preTrans` would resolve to a `PreTransRecordTree`, returns a `Some` - * of its (lowered) [[RecordType]] and its `cancelFun`. Otherwise, returns - * `None`. - * - * Note that the record type is not the same as `preTrans.tpe.base`, which - * is the *original* type of the tree (not lowered to a record type). + * of its [[InlineableClassStructure]] and its `cancelFun`. Otherwise, + * returns `None`. */ - private def resolveRecordType( - preTrans: PreTransform): Option[(RecordType, CancelFun)] = { + private def resolveRecordStructure( + preTrans: PreTransform): Option[(InlineableClassStructure, CancelFun)] = { preTrans match { case PreTransBlock(_, result) => - resolveRecordType(result) + resolveRecordStructure(result) case _:PreTransUnaryOp | _:PreTransBinaryOp => None case PreTransLocalDef(localDef @ LocalDef(tpe, _, replacement)) => replacement match { - case ReplaceWithRecordVarRef(name, recordType, used, cancelFun) => - Some((recordType, cancelFun)) + case ReplaceWithRecordVarRef(name, structure, used, cancelFun) => + Some((structure, cancelFun)) case InlineClassInstanceReplacement(structure, fieldLocalDefs, cancelFun) => - Some((structure.recordType, cancelFun)) + Some((structure, cancelFun)) case _ => None } - case PreTransRecordTree(tree, _, cancelFun) => - Some((tree.tpe.asInstanceOf[RecordType], cancelFun)) + case PreTransRecordTree(tree, structure, cancelFun) => + Some((structure, cancelFun)) case PreTransTree(_, _) => None @@ -1531,8 +1533,8 @@ private[optimizer] abstract class OptimizerCore( * * We do something similar in LocalDef.newReplacement. */ - case PreTransRecordTree(tree, tpe, _) - if tpe.base == ClassType(LongImpl.RuntimeLongClass, nullable = false) => + case PreTransRecordTree(tree, structure, _) + if structure.className == LongImpl.RuntimeLongClass => tree match { case RecordValue(_, List(lo, hi)) => createNewLong(lo, hi) @@ -1642,8 +1644,9 @@ private[optimizer] abstract class OptimizerCore( if (used.value.isUsed) { val ident = LocalIdent(name) resolveLocalDef(value) match { - case PreTransRecordTree(valueTree, valueTpe, cancelFun) => - val recordType = valueTree.tpe.asInstanceOf[RecordType] + case PreTransRecordTree(valueTree, valueStructure, cancelFun) => + val recordType = valueStructure.recordType + assert(valueTree.tpe == recordType) if (!isImmutableType(recordType)) cancelFun() Block(VarDef(ident, originalName, recordType, mutable, valueTree), innerBody) @@ -5162,56 +5165,63 @@ private[optimizer] abstract class OptimizerCore( implicit scope: Scope, pos: Position): TailRec[Tree] = tailcall { val newLabel = freshLabelName(oldLabelName) - def doMakeTree(newBody: Tree, returnedTypes: List[Type]): Tree = { - val refinedType = - returnedTypes.reduce(constrainedLub(_, _, resultType, isStat)) - val returnCount = returnedTypes.size - 1 - - tryOptimizePatternMatch(oldLabelName, newLabel, refinedType, + def doMakeTree(newBody: Tree, newResultType: Type, returnCount: Int): Tree = { + tryOptimizePatternMatch(oldLabelName, newLabel, newResultType, returnCount, newBody) getOrElse { - Labeled(newLabel, refinedType, newBody) + Labeled(newLabel, newResultType, newBody) } } val info = new LabelInfo(newLabel, isStat, acceptRecords = usePreTransform, - returnedTypes = newSimpleState(Nil)) + returnedTreeTypes = newSimpleState(Nil), returnedStructures = newSimpleState(Nil)) val bodyScope = scope.withEnv(scope.env.withLabelInfo(oldLabelName, info)) if (usePreTransform) { assert(!isStat, "Cannot use pretransform in statement position") tryOrRollback { cancelFun => pretransformExpr(body) { tbody0 => - val returnedTypes0 = info.returnedTypes.value - if (returnedTypes0.isEmpty) { + val returnedTypes = info.returnedTreeTypes.value + val returnedStructures = info.returnedStructures.value + + if (returnedTypes.isEmpty && returnedStructures.isEmpty) { // no return to that label, we can eliminate it cont(tbody0) } else { + // Extract the body, and take its result into account as well val tbody = resolveLocalDef(tbody0) - val (newBody, returnedTypes) = tbody match { - case PreTransRecordTree(bodyTree, origType, _) => - (bodyTree, (bodyTree.tpe, origType) :: returnedTypes0) + val (newBody, resultTypes, resultStructures) = tbody match { + case PreTransRecordTree(bodyTree, structure, _) => + (bodyTree, returnedTypes, structure :: returnedStructures) case PreTransTree(bodyTree, tpe) => - (bodyTree, (bodyTree.tpe, tpe) :: returnedTypes0) + if (tpe.isNothingType) + (bodyTree, returnedTypes, returnedStructures) + else + (bodyTree, tpe :: returnedTypes, returnedStructures) } - val (actualTypes, origTypes) = returnedTypes.unzip - val refinedOrigType = - origTypes.reduce(constrainedLub(_, _, resultType)) - actualTypes.collectFirst { - case actualType: RecordType => actualType - }.fold[TailRec[Tree]] { - // None of the returned types are records + + if (resultStructures.isEmpty) { + // implies returnedStructures.isEmpty, which implies !returnedTypes.isEmpty, which implies: + assert(!resultTypes.isEmpty) + + // No records; compute constrained lub of refined types + val refinedType = resultTypes.reduce(constrainedLub(_, _, resultType)) cont(PreTransTree( - doMakeTree(newBody, actualTypes), refinedOrigType)) - } { recordType => - if (actualTypes.exists(t => t != recordType && t != NothingType)) - cancelFun() + doMakeTree(newBody, refinedType.base, returnedTypes.size), + refinedType)) + } else { + // At least one record -- they must all agree on being records of the same class - val resultTree = doMakeTree(newBody, actualTypes) + if (resultTypes.nonEmpty) + cancelFun() - if (origTypes.exists(t => t != refinedOrigType && !t.isNothingType)) + val structure = resultStructures.head + if (resultStructures.tail.exists(!_.sameClassAs(structure))) cancelFun() - cont(PreTransRecordTree(resultTree, refinedOrigType, cancelFun)) + cont(PreTransRecordTree( + doMakeTree(newBody, structure.recordType, returnedStructures.size), + structure, + cancelFun)) } } } (bodyScope) @@ -5220,15 +5230,19 @@ private[optimizer] abstract class OptimizerCore( usePreTransform = false)(cont) } } else { + assert(info.returnedStructures.value.isEmpty) + val newBody = transform(body, isStat)(bodyScope) - val returnedTypes0 = info.returnedTypes.value.map(_._1) - if (returnedTypes0.isEmpty) { + val returnedTypes = info.returnedTreeTypes.value + if (returnedTypes.isEmpty) { // no return to that label, we can eliminate it cont(PreTransTree(newBody)) } else { - val returnedTypes = newBody.tpe :: returnedTypes0 - val tree = doMakeTree(newBody, returnedTypes) - cont(PreTransTree(tree)) + val refinedType = + returnedTypes.fold(RefinedType(newBody.tpe))(constrainedLub(_, _, resultType)) + cont(PreTransTree( + doMakeTree(newBody, refinedType.base, returnedTypes.size), + refinedType)) } } } @@ -5458,9 +5472,9 @@ private[optimizer] abstract class OptimizerCore( val used = newSimpleState[IsUsed](Unused) - val (replacement, refinedType) = resolveRecordType(value) match { - case Some((recordType, cancelFun)) => - (ReplaceWithRecordVarRef(newName, recordType, used, cancelFun), value.tpe) + val (replacement, refinedType) = resolveRecordStructure(value) match { + case Some((structure, cancelFun)) => + (ReplaceWithRecordVarRef(newName, structure, used, cancelFun), value.tpe) case None => (ReplaceWithVarRef(newName, used), tpe) @@ -5544,10 +5558,10 @@ private[optimizer] abstract class OptimizerCore( PreTransBlock(bindingsAndStats, result) case result: PreTransResult => PreTransBlock(bindingsAndStats, result) - case PreTransRecordTree(tree, tpe, cancelFun) => + case PreTransRecordTree(tree, structure, cancelFun) => PreTransRecordTree( finishTransformBindings(bindingsAndStats, tree), - tpe, cancelFun) + structure, cancelFun) case PreTransTree(tree, tpe) => PreTransTree( finishTransformBindings(bindingsAndStats, tree), @@ -5660,7 +5674,10 @@ private[optimizer] object OptimizerCore { private val ClassTagApplyMethodName = MethodName("apply", List(ClassRef(ClassClass)), ClassRef(ClassName("scala.reflect.ClassTag"))) - final class InlineableClassStructure(private val allFields: List[FieldDef]) { + final class InlineableClassStructure(val className: ClassName, private val allFields: List[FieldDef]) { + private[OptimizerCore] val refinedType: RefinedType = + RefinedType(ClassType(className, nullable = false), isExact = true) + private[OptimizerCore] val fieldNames: List[FieldName] = allFields.map(_.name.name) @@ -5677,28 +5694,30 @@ private[optimizer] object OptimizerCore { RecordType(recordFields) } - private val recordFieldNames: Map[FieldName, RecordType.Field] = { + private val recordFields: Map[FieldName, RecordType.Field] = { val elems = for ((fieldDef, recordField) <- allFields.zip(recordType.fields)) yield fieldDef.name.name -> recordField elems.toMap } + private[OptimizerCore] def findField(fieldName: FieldName): RecordType.Field = + recordFields(fieldName) + private[OptimizerCore] def fieldOriginalName(fieldName: FieldName): OriginalName = - recordFieldNames(fieldName).originalName + recordFields(fieldName).originalName - override def equals(that: Any): Boolean = that match { - case that: InlineableClassStructure => - this.allFields == that.allFields - case _ => - false + def sameStructureAs(that: InlineableClassStructure): Boolean = { + assert(this.sameClassAs(that)) + this.allFields == that.allFields } - override def hashCode(): Int = allFields.## + def sameClassAs(that: InlineableClassStructure): Boolean = + this.className == that.className override def toString(): String = { allFields .map(f => s"${f.name.name.nameString}: ${f.ftpe}") - .mkString("InlineableClassStructure(", ", ", ")") + .mkString(s"InlineableClassStructure(${className.nameString}, ", ", ", ")") } } @@ -5867,10 +5886,10 @@ private[optimizer] object OptimizerCore { * See the comment in finishTransformExpr about why it is desirable and * safe to do so. */ - case ReplaceWithRecordVarRef(name, recordType, used, _) + case ReplaceWithRecordVarRef(name, structure, used, _) if tpe.base == ClassType(LongImpl.RuntimeLongClass, nullable = false) => used.value = used.value.inc - createNewLong(VarRef(name)(recordType)) + createNewLong(VarRef(name)(structure.recordType)) case ReplaceWithRecordVarRef(_, _, _, cancelFun) => cancelFun() @@ -5966,7 +5985,7 @@ private[optimizer] object OptimizerCore { used: SimpleState[IsUsed]) extends LocalDefReplacement private final case class ReplaceWithRecordVarRef(name: LocalName, - recordType: RecordType, + structure: InlineableClassStructure, used: SimpleState[IsUsed], cancelFun: CancelFun) extends LocalDefReplacement @@ -6016,8 +6035,10 @@ private[optimizer] object OptimizerCore { val newName: LabelName, val isStat: Boolean, val acceptRecords: Boolean, - /** (actualType, originalType), actualType can be a RecordType. */ - val returnedTypes: SimpleState[List[(Type, RefinedType)]]) + /** Types of normal trees that are returned; cannot contain `RecordType` nor `NothingType`. */ + val returnedTreeTypes: SimpleState[List[RefinedType]], + /** Record structures that are returned. */ + val returnedStructures: SimpleState[List[InlineableClassStructure]]) private class OptEnv( val thisLocalDef: Option[LocalDef], @@ -6198,8 +6219,8 @@ private[optimizer] object OptimizerCore { "Prefer directly creating the relevant PreTransRecordTree", "forever") def apply(stat: Tree, result: PreTransRecordTree): PreTransRecordTree = { - PreTransRecordTree(Block(stat, result.tree)(result.pos), result.tpe, - result.cancelFun) + PreTransRecordTree(Block(stat, result.tree)(result.pos), + result.structure, result.cancelFun) } @deprecated( @@ -6218,8 +6239,8 @@ private[optimizer] object OptimizerCore { new PreTransBlock(Right(stat) :: innerBindingsAndStats, innerResult) case result: PreTransResult => new PreTransBlock(Right(stat) :: Nil, result) - case PreTransRecordTree(tree, tpe, cancelFun) => - PreTransRecordTree(Block(stat, tree)(tree.pos), tpe, cancelFun) + case PreTransRecordTree(tree, structure, cancelFun) => + PreTransRecordTree(Block(stat, tree)(tree.pos), structure, cancelFun) case PreTransTree(tree, tpe) => PreTransTree(Block(stat, tree)(tree.pos), tpe) } @@ -6286,9 +6307,13 @@ private[optimizer] object OptimizerCore { * whereas `tree.tpe` is always the lowered `RecordType`. */ private final case class PreTransRecordTree(tree: Tree, - tpe: RefinedType, cancelFun: CancelFun) extends PreTransGenTree { + structure: InlineableClassStructure, cancelFun: CancelFun) + extends PreTransGenTree { + def pos: Position = tree.pos + val tpe: RefinedType = structure.refinedType + assert(tree.tpe.isInstanceOf[RecordType], s"Cannot create a PreTransRecordTree with non-record type ${tree.tpe}") } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala index 72319844a9..5ad32e6f30 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala @@ -964,6 +964,22 @@ class RegressionTest { } } + @Test + def inlineClassWithMultipleFieldsOfSameSimpleName_Issue4947(): Unit = { + @noinline + def hide(x: Any): Any = x + + @noinline + def test: Boolean = false + + val b = + if (test) new Bug4947.B("f") + else new Bug4947.B("g") + + assertEquals(1, hide(b.foo)) + assertEquals("g", hide(b.bar)) + } + } object RegressionTest { @@ -1036,6 +1052,19 @@ object RegressionTest { def bar(x: String): String = x } + object Bug4947 { + class A { + private val x: Int = 1 + def foo: Int = x + } + + @inline + class B(init: String) extends A { + private val x: String = init + def bar: String = x + } + } + /* The objects and classes here intentionally have names that differ only in * case, and are intentionally defined in a specific order. This is required * to properly test the fix for #4148 (static forwarders can overwrite From eac8bf71214c77e031a272171e5ecf54b4d3c969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 17 Dec 2024 11:28:14 +0100 Subject: [PATCH 051/121] Merge the IR node `This` into `VarRef`, with a magic `LocalName`. Everywhere we handle `VarRef`s, we applied the same treatment to `This` nodes. In some places, like in the optimizer's `Binding`s, we also had an additional abstraction to represent either an actual `VarRef` or a `this` value. We now merge `This` as a particular case of `VarRef`. We use a magic `LocalName` that is otherwise invalid, namely `.this`. A variable of that name cannot be declared, either in a `ParamDef` or in a `VarDef`. It is only introduced for receivers, and is always immutable. Several areas of the code get simpler with the merge. The optimizer's `Binding`s is the most obvious example. The `FunctionEmitter`s also benefit from the changes. Other areas get nothing but a reduction of alternatives in pattern matches. `this` values still hold particular meaning in many situations. Therefore, we keep a source compatible constructor/extractor object for `This()`. --- .../org/scalajs/nscplugin/GenJSCode.scala | 8 +- .../main/scala/org/scalajs/ir/Hashers.scala | 13 +- .../src/main/scala/org/scalajs/ir/Names.scala | 30 +++- .../main/scala/org/scalajs/ir/Printers.scala | 9 +- .../scala/org/scalajs/ir/Serializers.scala | 24 +-- .../scala/org/scalajs/ir/Transformers.scala | 2 +- .../scala/org/scalajs/ir/Traversers.scala | 2 +- .../src/main/scala/org/scalajs/ir/Trees.scala | 10 +- .../scala/org/scalajs/ir/PrintersTest.scala | 3 - .../backend/emitter/FunctionEmitter.scala | 45 ++--- .../backend/wasmemitter/FunctionEmitter.scala | 26 +-- .../linker/checker/ClassDefChecker.scala | 44 +++-- .../linker/checker/ErrorReporter.scala | 2 + .../scalajs/linker/checker/IRChecker.scala | 2 - .../frontend/optimizer/IncOptimizer.scala | 2 +- .../frontend/optimizer/OptimizerCore.scala | 164 +++++++----------- .../org/scalajs/linker/IRCheckerTest.scala | 2 +- .../linker/checker/ClassDefCheckerTest.scala | 124 +++++++++++-- .../linker/testutils/TestIRBuilder.scala | 2 +- project/BinaryIncompatibilities.scala | 4 + 20 files changed, 298 insertions(+), 220 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index a64d6a8656..5d393dfa47 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -1107,7 +1107,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) selfVarDef :: memberDefinitions } - // After the super call, substitute `selfRef` for `This()` + // After the super call, substitute `selfRef` for `this` val afterSuper = new ir.Transformers.Transformer { override def transform(tree: js.Tree): js.Tree = tree match { case js.This() => @@ -2430,7 +2430,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) tree match { case js.Block(stats :+ expr) => js.Block(stats :+ exprToStat(expr)) - case _:js.Literal | _:js.This | _:js.VarRef => + case _:js.Literal | _:js.VarRef => js.Skip() case _ => tree @@ -4907,8 +4907,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val newReceiver = genExpr(receiver) val newArg = genStatOrExpr(arg, isStat) newReceiver match { - case js.This() => - // common case for which there is no side-effect nor NPE + case newReceiver: js.VarRef if !newReceiver.tpe.isNullable => + // common case (notably for `this`) for which there is no side-effect nor NPE newArg case _ => js.Block( diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala index 770d83a7c7..4f6c3eeb72 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala @@ -497,12 +497,13 @@ object Hashers { mixTypeRef(typeRef) case VarRef(name) => - mixTag(TagVarRef) - mixName(name) - mixType(tree.tpe) - - case This() => - mixTag(TagThis) + if (name.isThis) { + // "Optimized" representation, like in Serializers + mixTag(TagThis) + } else { + mixTag(TagVarRef) + mixName(name) + } mixType(tree.tpe) case Closure(arrow, captureParams, params, restParam, body, captureValues) => diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Names.scala b/ir/shared/src/main/scala/org/scalajs/ir/Names.scala index 8193227be0..d9bee3518b 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Names.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Names.scala @@ -64,6 +64,10 @@ object Names { * * Local names must be non-empty, and can contain any Unicode code point * except `/ . ; [`. + * + * As an exception, the local name `".this"` represents the `this` binding. + * It cannot be used to declare variables (in `VarDef`s or `ParamDef`s) but + * can be referred to with a `VarRef`. */ final class LocalName private (encoded: UTF8String) extends Name(encoded) with Comparable[LocalName] { @@ -79,22 +83,40 @@ object Names { protected def stringPrefix: String = "LocalName" - final def withPrefix(prefix: LocalName): LocalName = + final def isThis: Boolean = + this eq LocalName.This + + final def withPrefix(prefix: LocalName): LocalName = { + require(!isThis && !prefix.isThis, "cannot concatenate LocalName.This") new LocalName(prefix.encoded ++ this.encoded) + } final def withPrefix(prefix: String): LocalName = LocalName(UTF8String(prefix) ++ this.encoded) - final def withSuffix(suffix: LocalName): LocalName = + final def withSuffix(suffix: LocalName): LocalName = { + require(!isThis && !suffix.isThis, "cannot concatenate LocalName.This") new LocalName(this.encoded ++ suffix.encoded) + } final def withSuffix(suffix: String): LocalName = LocalName(this.encoded ++ UTF8String(suffix)) } object LocalName { - def apply(name: UTF8String): LocalName = - new LocalName(validateSimpleEncodedName(name)) + private final val ThisEncodedName: UTF8String = + UTF8String(".this") + + /** The unique `LocalName` with encoded name `.this`. */ + val This: LocalName = + new LocalName(ThisEncodedName) + + def apply(name: UTF8String): LocalName = { + if (UTF8String.equals(name, ThisEncodedName)) + This + else + new LocalName(validateSimpleEncodedName(name)) + } def apply(name: String): LocalName = LocalName(UTF8String(name)) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala index d6a490b634..5c606e81ee 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -570,7 +570,6 @@ object Printers { case JSPrivateSelect(qual, _) => containsOnlySelectsFromAtom(qual) case JSSelect(qual, _) => containsOnlySelectsFromAtom(qual) case VarRef(_) => true - case This() => true case _ => false // in particular, Apply } if (containsOnlySelectsFromAtom(ctor)) { @@ -844,10 +843,10 @@ object Printers { // Atomic expressions case VarRef(name) => - print(name) - - case This() => - print("this") + if (name.isThis) + print("this") + else + print(name) case Closure(arrow, captureParams, params, restParam, body, captureValues) => if (arrow) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index 66c3249f9e..74e7d2c1aa 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -524,12 +524,13 @@ object Serializers { writeTypeRef(typeRef) case VarRef(name) => - writeTagAndPos(TagVarRef) - writeName(name) - writeType(tree.tpe) - - case This() => - writeTagAndPos(TagThis) + if (name.isThis) { + // "Optimized" representation that is compatible with IR < 1.18 + writeTagAndPos(TagThis) + } else { + writeTagAndPos(TagVarRef) + writeName(name) + } writeType(tree.tpe) case Closure(arrow, captureParams, params, restParam, body, captureValues) => @@ -1155,10 +1156,13 @@ object Serializers { if (hacks.use13) { val cls = readClassName() val rhs = readTree() - if (cls != enclosingClassName || !rhs.isInstanceOf[This]) { - throw new IOException( - s"Illegal legacy StoreModule(${cls.nameString}, $rhs) " + - s"found in class ${enclosingClassName.nameString}") + rhs match { + case This() if cls == enclosingClassName => + // ok + case _ => + throw new IOException( + s"Illegal legacy StoreModule(${cls.nameString}, $rhs) " + + s"found in class ${enclosingClassName.nameString}") } } StoreModule() diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala index 7be426e058..82617dfd01 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala @@ -195,7 +195,7 @@ object Transformers { case _:Skip | _:Debugger | _:LoadModule | _:StoreModule | _:SelectStatic | _:SelectJSNativeMember | _:LoadJSConstructor | _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | - _:Literal | _:VarRef | _:This | _:JSGlobalRef | _:LinkTimeProperty => + _:Literal | _:VarRef | _:JSGlobalRef | _:LinkTimeProperty => tree } } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala index d242bbfce7..3edb2ac178 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala @@ -201,7 +201,7 @@ object Traversers { case _:Skip | _:Debugger | _:LoadModule | _:StoreModule | _:SelectStatic | _:SelectJSNativeMember | _:LoadJSConstructor | _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | - _:Literal | _:VarRef | _:This | _:JSGlobalRef | _:LinkTimeProperty => + _:Literal | _:VarRef | _:JSGlobalRef | _:LinkTimeProperty => } def traverseClassDef(tree: ClassDef): Unit = { diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index 6cd4db23c7..7fc574d991 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -1081,8 +1081,14 @@ object Trees { sealed case class VarRef(name: LocalName)(val tpe: Type)( implicit val pos: Position) extends AssignLhs - sealed case class This()(val tpe: Type)(implicit val pos: Position) - extends Tree + /** Convenience constructor and extractor for `VarRef`s representing `this` bindings. */ + object This { + def apply()(tpe: Type)(implicit pos: Position): VarRef = + VarRef(LocalName.This)(tpe) + + def unapply(tree: VarRef): Boolean = + tree.name.isThis + } /** Closure with explicit captures. * diff --git a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala index 0b2cb5a9b6..6f68760422 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -863,9 +863,6 @@ class PrintersTest { @Test def printVarRef(): Unit = { assertPrintEquals("x", VarRef("x")(IntType)) - } - - @Test def printThis(): Unit = { assertPrintEquals("this", This()(AnyType)) } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index 12af2ad9ab..df5ed07b70 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -1261,7 +1261,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { def test(tree: Tree): Boolean = tree match { // Atomic expressions case _: Literal => true - case _: This => true case _: JSNewTarget => true case _: LinkTimeProperty => true @@ -2468,18 +2467,18 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { genCallHelper(VarField.systemIdentityHashCode, newLhs) case WrapAsThrowable => - val newLhsVar = newLhs.asInstanceOf[js.VarRef] + assert(newLhs.isInstanceOf[js.VarRef] || newLhs.isInstanceOf[js.This], newLhs) js.If( - genIsInstanceOfClass(newLhsVar, ThrowableClass), - newLhsVar, - genScalaClassNew(JavaScriptExceptionClass, AnyArgConstructorName, newLhsVar)) + genIsInstanceOfClass(newLhs, ThrowableClass), + newLhs, + genScalaClassNew(JavaScriptExceptionClass, AnyArgConstructorName, newLhs)) case UnwrapFromThrowable => - val newLhsVar = newLhs.asInstanceOf[js.VarRef] + assert(newLhs.isInstanceOf[js.VarRef] || newLhs.isInstanceOf[js.This], newLhs) js.If( - genIsInstanceOfClass(newLhsVar, JavaScriptExceptionClass), - genSelect(newLhsVar, FieldIdent(exceptionFieldName)), - newLhsVar) + genIsInstanceOfClass(newLhs, JavaScriptExceptionClass), + genSelect(newLhs, FieldIdent(exceptionFieldName)), + newLhs) } case BinaryOp(op, lhs, rhs) => @@ -2519,7 +2518,10 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { * that the body of `Object.equals__O__Z` can be compiled as * `this === that` instead of `Object.is(this, that)`. */ - !tree.isInstanceOf[This] + tree match { + case This() => false + case _ => true + } case ClassType(BoxedByteClass | BoxedShortClass | BoxedIntegerClass | BoxedFloatClass | BoxedDoubleClass, _) => true @@ -3021,12 +3023,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case Transient(JSVarRef(name, _)) => js.VarRef(name) - case This() => - if (env.hasExplicitThis) - fileLevelVar(VarField.thiz) - else - js.This() - case tree: Closure => transformClosure(tree) @@ -3180,12 +3176,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { VarKind.Immutable } - case This() if env.hasExplicitThis => - VarKind.ExplicitThisAlias - - case This() if permitImplicitJSThisCapture => - VarKind.ThisAlias - case _ => explicitCapture() VarKind.Immutable @@ -3415,7 +3405,6 @@ private object FunctionEmitter { // Environment final class Env private ( - val hasExplicitThis: Boolean, val expectedReturnType: Type, val enclosingClassName: Option[ClassName], vars: Map[LocalName, VarKind], @@ -3448,7 +3437,7 @@ private object FunctionEmitter { copy(enclosingClassName = enclosingClassName) def withExplicitThis(): Env = - copy(hasExplicitThis = true) + copy(vars = vars + (LocalName.This -> VarKind.ExplicitThisAlias)) def withVars(newVars: Map[LocalName, VarKind]): Env = copy(vars = vars ++ newVars) @@ -3483,7 +3472,6 @@ private object FunctionEmitter { copy(inLoopForVarCapture = inLoopForVarCapture) private def copy( - hasExplicitThis: Boolean = this.hasExplicitThis, expectedReturnType: Type = this.expectedReturnType, enclosingClassName: Option[ClassName] = this.enclosingClassName, vars: Map[LocalName, VarKind] = this.vars, @@ -3492,15 +3480,18 @@ private object FunctionEmitter { defaultBreakTargets: Set[LabelName] = this.defaultBreakTargets, defaultContinueTargets: Set[LabelName] = this.defaultContinueTargets, inLoopForVarCapture: Boolean = this.inLoopForVarCapture): Env = { - new Env(hasExplicitThis, expectedReturnType, enclosingClassName, vars, + new Env(expectedReturnType, enclosingClassName, vars, labeledExprLHSes, labelsTurnedIntoContinue, defaultBreakTargets, defaultContinueTargets, inLoopForVarCapture) } } object Env { + private val InitVars: Map[LocalName, VarKind] = + Map(LocalName.This -> VarKind.ThisAlias) + def empty(expectedReturnType: Type): Env = { - new Env(false, expectedReturnType, None, Map.empty, Map.empty, Set.empty, + new Env(expectedReturnType, None, InitVars, Map.empty, Set.empty, Set.empty, Set.empty, false) } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index bf0e54adc5..7ef7a87ac3 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -209,9 +209,12 @@ object FunctionEmitter { Some(VarStorage.Local(newTargetParam)) } - val receiverStorage = receiverType.map { tpe => - val receiverParam = fb.addParam(receiverOriginalName, tpe) - VarStorage.Local(receiverParam) + val receiverEnv: Env = receiverType match { + case None => + Map.empty + case Some(tpe) => + val receiverParam = fb.addParam(receiverOriginalName, tpe) + Map(LocalName.This -> VarStorage.Local(receiverParam)) } val normalParamsEnv: Env = paramDefs.map { paramDef => @@ -222,7 +225,7 @@ object FunctionEmitter { paramDef.name.name -> VarStorage.Local(param) }.toMap - val fullEnv: Env = captureParamsEnv ++ preSuperEnvEnv ++ normalParamsEnv + val fullEnv: Env = captureParamsEnv ++ preSuperEnvEnv ++ receiverEnv ++ normalParamsEnv fb.setResultTypes(resultTypes) @@ -230,7 +233,6 @@ object FunctionEmitter { fb, enclosingClassName, newTargetStorage, - receiverStorage, fullEnv ) } @@ -275,7 +277,6 @@ private class FunctionEmitter private ( val fb: FunctionBuilder, enclosingClassName: Option[ClassName], _newTargetStorage: Option[FunctionEmitter.VarStorage.Local], - _receiverStorage: Option[FunctionEmitter.VarStorage.Local], paramsEnv: FunctionEmitter.Env )(implicit ctx: WasmContext) { import FunctionEmitter._ @@ -290,9 +291,6 @@ private class FunctionEmitter private ( private def newTargetStorage: VarStorage.Local = _newTargetStorage.getOrElse(throw new Error("Cannot access new.target in this context.")) - private def receiverStorage: VarStorage.Local = - _receiverStorage.getOrElse(throw new Error("Cannot access to the receiver in this context.")) - /** Opens a new scope in which NPEs can be thrown by jumping to the NPE label. * * When NPEs are unchecked this is a no-op (other than calling `body`). @@ -530,7 +528,6 @@ private class FunctionEmitter private ( case t: VarRef => genVarRef(t) case t: LoadModule => genLoadModule(t) case t: StoreModule => genStoreModule(t) - case t: This => genThis(t) case t: ApplyStatically => genApplyStatically(t) case t: Apply => genApply(t) case t: ApplyStatic => genApplyStatic(t) @@ -1347,9 +1344,8 @@ private class FunctionEmitter private ( throw new AssertionError(s"Cannot emit $tree at ${tree.pos} without enclosing class name") } - genTreeAuto(This()(ClassType(className, nullable = false))(tree.pos)) - markPosition(tree) + genReadStorage(lookupLocal(LocalName.This)) fb += wa.GlobalSet(genGlobalID.forModuleInstance(className)) VoidType } @@ -2421,12 +2417,6 @@ private class FunctionEmitter private ( tree.tpe } - private def genThis(tree: This): Type = { - markPosition(tree) - genReadStorage(receiverStorage) - tree.tpe - } - private def genVarDef(tree: VarDef): Type = { /* This is an isolated VarDef that is not in a Block. * Its scope is empty by construction, and therefore it need not be stored. diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index 1e75d4db31..103d19f963 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -150,8 +150,10 @@ private final class ClassDefChecker(classDef: ClassDef, } { implicit val ctx = ErrorContext(p) val name = ident.name + if (name.isThis) + reportError(i"Illegal JS class capture with name '$name'") if (!alreadyDeclared.add(name)) - reportError(i"Duplicate JS class capture '$name'") + reportError(i"Duplicate JS class capture '$name'") if (tpe == VoidType) reportError(i"The JS class capture $name cannot have type VoidType") if (mutable) @@ -300,9 +302,8 @@ private final class ClassDefChecker(classDef: ClassDef, // Body for (body <- optBody) { - val thisType = if (static) VoidType else instanceThisType val bodyEnv = Env.fromParams(params) - .withThisType(thisType) + .withMaybeThisType(!static, instanceThisType) if (isConstructor) checkConstructorBody(body, bodyEnv) @@ -357,9 +358,8 @@ private final class ClassDefChecker(classDef: ClassDef, checkExportedPropertyName(pName) checkJSParamDefs(params, restParam) - val thisType = if (static) VoidType else instanceThisType - val env = - Env.fromParams(classDef.jsClassCaptures.getOrElse(Nil) ++ params ++ restParam).withThisType(thisType) + val env = Env.fromParams(classDef.jsClassCaptures.getOrElse(Nil) ++ params ++ restParam) + .withMaybeThisType(!static, instanceThisType) checkTree(body, env) } @@ -383,11 +383,11 @@ private final class ClassDefChecker(classDef: ClassDef, checkExportedPropertyName(pName) val jsClassCaptures = classDef.jsClassCaptures.getOrElse(Nil) - val thisType = if (static) VoidType else instanceThisType getterBody.foreach { body => withPerMethodState { - val bodyEnv = Env.fromParams(jsClassCaptures).withThisType(thisType) + val bodyEnv = Env.fromParams(jsClassCaptures) + .withMaybeThisType(!static, instanceThisType) checkTree(body, bodyEnv) } } @@ -395,7 +395,8 @@ private final class ClassDefChecker(classDef: ClassDef, setterArgAndBody.foreach { case (setterArg, body) => withPerMethodState { checkJSParamDefs(setterArg :: Nil, None) - val bodyEnv = Env.fromParams(jsClassCaptures :+ setterArg).withThisType(thisType) + val bodyEnv = Env.fromParams(jsClassCaptures :+ setterArg) + .withMaybeThisType(!static, instanceThisType) checkTree(body, bodyEnv) } } @@ -948,13 +949,7 @@ private final class ClassDefChecker(classDef: ClassDef, if (tree.tpe != localDef.tpe) reportError(i"Variable $name of type ${localDef.tpe} typed as ${tree.tpe}") } - - case This() => - if (env.thisType == VoidType) - reportError(i"Cannot find `this` in scope") - else if (tree.tpe != env.thisType) - reportError(i"`this` of type ${env.thisType} typed as ${tree.tpe}") - if (env.isThisRestricted) + if (env.isThisRestricted && name.isThis) reportError(i"Restricted use of `this` before the super constructor call") case Closure(arrow, captureParams, params, restParam, body, captureValues) => @@ -984,7 +979,7 @@ private final class ClassDefChecker(classDef: ClassDef, val bodyEnv = Env .fromParams(captureParams ++ params ++ restParam) .withHasNewTarget(!arrow) - .withThisType(if (arrow) VoidType else AnyType) + .withMaybeThisType(!arrow, AnyType) checkTree(body, bodyEnv) } @@ -1023,6 +1018,8 @@ private final class ClassDefChecker(classDef: ClassDef, private def checkDeclareLocalVar(ident: LocalIdent)( implicit ctx: ErrorContext): Unit = { + if (ident.name.isThis) + reportError(i"Illegal definition of a variable with name ${ident.name}") if (!declaredLocalVarNamesPerMethod.add(ident.name)) reportError(i"Duplicate local variable name ${ident.name}.") } @@ -1072,8 +1069,6 @@ object ClassDefChecker { private class Env( /** Whether there is a valid `new.target` in scope. */ val hasNewTarget: Boolean, - /** The type of `this` in scope, or `VoidType` if there is no `this` in scope. */ - val thisType: Type, /** Local variables in scope (including through closures). */ val locals: Map[LocalName, LocalDef], /** Return types by label. */ @@ -1087,7 +1082,11 @@ object ClassDefChecker { copy(hasNewTarget = hasNewTarget) def withThisType(thisType: Type): Env = - copy(thisType = thisType) + withLocal(LocalDef(LocalName.This, thisType, mutable = false)) + + def withMaybeThisType(hasThis: Boolean, thisType: Type): Env = + if (hasThis) withThisType(thisType) + else this def withLocal(localDef: LocalDef): Env = copy(locals = locals + (localDef.name -> localDef)) @@ -1100,12 +1099,11 @@ object ClassDefChecker { private def copy( hasNewTarget: Boolean = hasNewTarget, - thisType: Type = thisType, locals: Map[LocalName, LocalDef] = locals, returnLabels: Set[LabelName] = returnLabels, isThisRestricted: Boolean = isThisRestricted ): Env = { - new Env(hasNewTarget, thisType, locals, returnLabels, isThisRestricted) + new Env(hasNewTarget, locals, returnLabels, isThisRestricted) } } @@ -1113,7 +1111,6 @@ object ClassDefChecker { val empty: Env = { new Env( hasNewTarget = false, - thisType = VoidType, locals = Map.empty, returnLabels = Set.empty, isThisRestricted = false @@ -1127,7 +1124,6 @@ object ClassDefChecker { new Env( hasNewTarget = false, - thisType = VoidType, paramLocalDefs.toMap, Set.empty, isThisRestricted = false diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ErrorReporter.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ErrorReporter.scala index 18c6527d13..ab45bd4316 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ErrorReporter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ErrorReporter.scala @@ -35,6 +35,8 @@ private[checker] object ErrorReporter { private def format(arg: Any): String = { arg match { + case arg: LocalName if arg.isThis => "`this`" + case arg: Name => arg.nameString case arg: FieldName => arg.nameString case arg: MethodName => arg.displayName diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index 4c2c4f8464..a749e4807e 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -692,8 +692,6 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, case _: VarRef => - case This() => - case Closure(arrow, captureParams, params, restParam, body, captureValues) => assert(captureParams.size == captureValues.size) // checked by ClassDefChecker diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index a3fb5319c3..6d4aa83022 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -876,7 +876,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: val getterDependenciesBuilder = Set.newBuilder[(ClassName, MethodName)] def isTriviallySideEffectFree(tree: Tree): Boolean = tree match { - case _:VarRef | _:Literal | _:This | _:Skip => + case _:VarRef | _:Literal | _:Skip => true case Closure(_, _, _, _, _, captureValues) => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index d55b70744d..5f275ea841 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -232,16 +232,6 @@ private[optimizer] abstract class OptimizerCore( (newName, newOriginalName) } - private def freshLocalName(base: Binding.Name, - mutable: Boolean): (LocalName, OriginalName) = { - base match { - case Binding.This => - freshLocalName(LocalThisNameForFresh, thisOriginalName, mutable) - case Binding.Local(name, originalName) => - freshLocalName(name, originalName, mutable) - } - } - private def freshLabelName(base: LabelName): LabelName = labelNameAllocator.freshName(base) @@ -659,7 +649,7 @@ private[optimizer] abstract class OptimizerCore( // Atomic expressions - case _:VarRef | _:This => + case _:VarRef => trampoline { pretransformExpr(tree)(finishTransform(isStat)) } @@ -779,7 +769,8 @@ private[optimizer] abstract class OptimizerCore( case PreTransLit(literal) => captureParamLocalDefs += paramName -> LocalDef(tcaptureValue.tpe, false, ReplaceWithConstant(literal)) - case PreTransLocalDef(LocalDef(_, /* mutable = */ false, ReplaceWithVarRef(captureName, _))) => + case PreTransLocalDef(LocalDef(_, /* mutable = */ false, ReplaceWithVarRef(captureName, _))) + if !captureName.isThis => captureParamLocalDefsForVarRefs.get(captureName).fold[Unit] { captureParamLocalDefsForVarRefs += captureName -> addCaptureParam(captureName) } { prevLocalDef => @@ -906,16 +897,6 @@ private[optimizer] abstract class OptimizerCore( }) cont(localDef.toPreTransform) - case This() => - val localDef = scope.env.thisLocalDef.getOrElse { - throw new AssertionError( - s"Found invalid 'this' at $pos\n" + - s"While optimizing $debugID\n" + - s"Env is ${scope.env}\n" + - s"Inlining ${scope.implsBeingInlined}") - } - cont(localDef.toPreTransform) - case tree: If => pretransformIf(tree)(cont) @@ -1591,7 +1572,7 @@ private[optimizer] abstract class OptimizerCore( /** Keeps only the side effects of a Tree (overapproximation). */ private def keepOnlySideEffects(stat: Tree): Tree = stat match { - case _:VarRef | _:This | _:Literal | _:SelectStatic => + case _:VarRef | _:Literal | _:SelectStatic => Skip()(stat.pos) case VarDef(_, _, _, _, rhs) => keepOnlySideEffects(rhs) @@ -1903,9 +1884,6 @@ private[optimizer] abstract class OptimizerCore( case _: Literal => NotFoundPureSoFar - case This() => - NotFoundPureSoFar - case Closure(arrow, captureParams, params, restParam, body, captureValues) => recs(captureValues).mapOrKeepGoing(Closure(arrow, captureParams, params, restParam, body, _)) @@ -2105,12 +2083,11 @@ private[optimizer] abstract class OptimizerCore( * the unboxes in the correct evaluation order. */ - /* Generate a new, fake body that we will inline. For - * type-preservation, the type of its `This()` node is the type of - * our receiver but non-nullable. For stability, the parameter - * names are normalized (taking them from `body` would make the - * result depend on which method came up first in the list of - * targets). + /* Generate a new, fake body that we will inline. For type + * preservation, the type of its `this` var ref is the type of our + * receiver but non-nullable. For stability, the parameter names + * are normalized (taking them from `body` would make the result + * depend on which method came up first in the list of targets). */ val thisType = treceiver.tpe.base.toNonNullable val normalizedParams: List[(LocalName, Type)] = { @@ -2132,10 +2109,10 @@ private[optimizer] abstract class OptimizerCore( // Construct bindings; need to check null for the receiver to preserve evaluation order val receiverBinding = - Binding(Binding.This, thisType, mutable = false, checkNotNull(treceiver)) + Binding.forReceiver(thisType, checkNotNull(treceiver)) val argsBindings = normalizedParams.zip(targs).map { case ((name, ptpe), targ) => - Binding(Binding.Local(name, NoOriginalName), ptpe, mutable = false, targ) + Binding(name, NoOriginalName, ptpe, mutable = false, targ) } withBindings(receiverBinding :: argsBindings) { (bodyScope, cont1) => @@ -2581,12 +2558,12 @@ private[optimizer] abstract class OptimizerCore( case This() if args.isEmpty => assert(optReceiver.isDefined, - "There was a This(), there should be a receiver") + "There was a `this`, there should be a receiver") cont(foldCast(checkNotNull(optReceiver.get._2), optReceiver.get._1)) case Select(This(), field) if formals.isEmpty => assert(optReceiver.isDefined, - "There was a This(), there should be a receiver") + "There was a `this`, there should be a receiver") pretransformSelectCommon(body.tpe, optReceiver.get._2, optQualDeclaredType = Some(optReceiver.get._1), field, isLhsOfAssign = false)(cont) @@ -2595,7 +2572,7 @@ private[optimizer] abstract class OptimizerCore( if formals.size == 1 && formals.head.name.name == rhsName => assert(isStat, "Found Assign in expression position") assert(optReceiver.isDefined, - "There was a This(), there should be a receiver") + "There was a `this`, there should be a receiver") val treceiver = optReceiver.get._2 val trhs = args.head @@ -2634,7 +2611,7 @@ private[optimizer] abstract class OptimizerCore( */ val (declaredType, value0) = receiver val value = foldCast(checkNotNull(value0), declaredType) - Binding(Binding.This, declaredType, false, value) + Binding.forReceiver(declaredType, value) } assert(formals.size == args.size, @@ -3193,8 +3170,7 @@ private[optimizer] abstract class OptimizerCore( val initialFieldBindings = for { RecordType.Field(name, originalName, tpe, mutable) <- structure.recordType.fields } yield { - Binding(Binding.Local(name.toLocalName, originalName), tpe, mutable, - PreTransTree(zeroOf(tpe))) + Binding(name.toLocalName, originalName, tpe, mutable, PreTransTree(zeroOf(tpe))) } withNewLocalDefs(initialFieldBindings) { (initialFieldLocalDefList, cont1) => @@ -3282,22 +3258,22 @@ private[optimizer] abstract class OptimizerCore( } stats match { - case This() :: rest => + case VarRef(_) :: rest => + // mostly for `this` inlineClassConstructorBodyList(allocationSite, structure, thisLocalDef, inputFieldsLocalDefs, className, rest, cancelFun)(buildInner)(cont) - case Assign(s @ Select(ths: This, field), value) :: rest + case Assign(s @ Select(This(), field), value) :: rest if !inputFieldsLocalDefs.contains(field.name) => // Field is being optimized away. Only keep side effects of the write. withStat(value, rest) - case Assign(s @ Select(ths: This, field), value) :: rest + case Assign(s @ Select(This(), field), value) :: rest if !inputFieldsLocalDefs(field.name).mutable => pretransformExpr(value) { tvalue => val originalName = structure.fieldOriginalName(field.name) - val binding = Binding( - Binding.Local(field.name.simpleName.toLocalName, originalName), - s.tpe, false, tvalue) + val binding = Binding(field.name.simpleName.toLocalName, + originalName, s.tpe, mutable = false, tvalue) withNewLocalDef(binding) { (localDef, cont1) => if (localDef.contains(thisLocalDef)) { /* Uh oh, there is a `val x = ...this...`. We can't keep it, @@ -3342,7 +3318,7 @@ private[optimizer] abstract class OptimizerCore( Assign(lhs, If(cond, th, value)(lhs.tpe)(stat.pos))(ass.pos) :: rest, cancelFun)(buildInner)(cont) - case ApplyStatically(flags, ths: This, superClass, superCtor, args) :: rest + case ApplyStatically(flags, This(), superClass, superCtor, args) :: rest if flags.isConstructor => pretransformExprs(args) { targs => inlineClassConstructorBody(allocationSite, structure, @@ -5338,8 +5314,10 @@ private[optimizer] abstract class OptimizerCore( (name -> localDef, newParamDef) } - private def newThisLocalDef(thisType: Type): LocalDef = - LocalDef(RefinedType(thisType), false, ReplaceWithThis()) + private def newThisLocalDef(thisType: Type): LocalDef = { + LocalDef(RefinedType(thisType), false, + ReplaceWithVarRef(LocalName.This, new SimpleState(this, UsedAtLeastOnce))) + } private def withBindings(bindings: List[Binding])( buildInner: (Scope, PreTransCont) => TailRec[Tree])( @@ -5403,7 +5381,7 @@ private[optimizer] abstract class OptimizerCore( buildInner: (LocalDef, PreTransCont) => TailRec[Tree])( cont: PreTransCont)( implicit scope: Scope): TailRec[Tree] = tailcall { - val Binding(bindingName, declaredType, mutable, value) = binding + val Binding(bindingName, originalName, declaredType, mutable, value) = binding implicit val pos = value.pos def withDedicatedVar(tpe: RefinedType): TailRec[Tree] = { @@ -5423,13 +5401,13 @@ private[optimizer] abstract class OptimizerCore( * RuntimeLong. */ expandLongValue(value) { expandedValue => - val expandedBinding = Binding(bindingName, rtLongClassType, - mutable, expandedValue) + val expandedBinding = Binding(bindingName, originalName, + rtLongClassType, mutable, expandedValue) withNewLocalDef(expandedBinding)(buildInner)(cont) } } else { // Otherwise, we effectively declare a new binding - val (newName, newOriginalName) = freshLocalName(bindingName, mutable) + val (newName, newOriginalName) = freshLocalName(bindingName, originalName, mutable) val used = newSimpleState[IsUsed](Unused) @@ -5609,7 +5587,7 @@ private[optimizer] abstract class OptimizerCore( private[optimizer] object OptimizerCore { - /** When creating a `freshName` based on a `Binding.This`, use this name as + /** When creating a `freshName` based on a `LocalName.This`, use this name as * base. */ private val LocalThisNameForFresh = LocalName("this") @@ -5855,15 +5833,11 @@ private[optimizer] object OptimizerCore { case ReplaceWithRecordVarRef(_, _, _, cancelFun) => cancelFun() - case ReplaceWithThis() => - This()(tpe.base) - case ReplaceWithOtherLocalDef(localDef) => /* A previous version would push down the `tpe` of this `LocalDef` to * use for the replacement. While that creates trees with narrower types, - * it also creates inconsistent trees: - * - This() not typed as the enclosing class. - * - VarRef not typed as the corresponding VarDef / ParamDef. + * it also creates inconsistent trees, with `VarRef`s that are not typed + * as the corresponding VarDef / ParamDef / receiver type. * * Type based optimizations happen (mainly) in the optimizer so * consistent downstream types are more important than narrower types; @@ -5917,20 +5891,19 @@ private[optimizer] object OptimizerCore { elemLocalDefs.exists(_.contains(that)) case _:ReplaceWithVarRef | _:ReplaceWithRecordVarRef | - _:ReplaceWithThis | _:ReplaceWithConstant => + _:ReplaceWithConstant => false }) } def tryWithRefinedType(refinedType: RefinedType): LocalDef = { - /* Only adjust if the replacement if ReplaceWithThis or - * ReplaceWithVarRef, because other types have nothing to gain - * (e.g., ReplaceWithConstant) or we want to keep them unwrapped - * because they are examined in optimizations (notably all the - * types with virtualized objects). + /* Only adjust if the replacement if ReplaceWithVarRef, because other + * types have nothing to gain (e.g., ReplaceWithConstant) or we want to + * keep them unwrapped because they are examined in optimizations + * (notably all the types with virtualized objects). */ replacement match { - case _:ReplaceWithThis | _:ReplaceWithVarRef => + case _:ReplaceWithVarRef => LocalDef(refinedType, mutable, ReplaceWithOtherLocalDef(this)) case replacement: ReplaceWithOtherLocalDef => LocalDef(refinedType, mutable, replacement) @@ -5950,8 +5923,6 @@ private[optimizer] object OptimizerCore { used: SimpleState[IsUsed], cancelFun: CancelFun) extends LocalDefReplacement - private final case class ReplaceWithThis() extends LocalDefReplacement - /** An alias to another `LocalDef`, used only to refine the type of that * `LocalDef` in a specific scope. * @@ -6002,41 +5973,37 @@ private[optimizer] object OptimizerCore { val returnedStructures: SimpleState[List[InlineableClassStructure]]) private class OptEnv( - val thisLocalDef: Option[LocalDef], val localDefs: Map[LocalName, LocalDef], val labelInfos: Map[LabelName, LabelInfo]) { def withThisLocalDef(rep: LocalDef): OptEnv = - withThisLocalDef(Some(rep)) + withLocalDef(LocalName.This, rep) + /** Optionally adds a binding for `this`. + * + * If `rep.isEmpty`, returns this environment unchanged. + */ def withThisLocalDef(rep: Option[LocalDef]): OptEnv = - new OptEnv(rep, localDefs, labelInfos) + if (rep.isEmpty) this + else withThisLocalDef(rep.get) def withLocalDef(oldName: LocalName, rep: LocalDef): OptEnv = - new OptEnv(thisLocalDef, localDefs + (oldName -> rep), labelInfos) - - def withLocalDef(oldName: Binding.Name, rep: LocalDef): OptEnv = { - oldName match { - case Binding.This => withThisLocalDef(rep) - case Binding.Local(name, _) => withLocalDef(name, rep) - } - } + new OptEnv(localDefs + (oldName -> rep), labelInfos) def withLocalDefs(reps: List[(LocalName, LocalDef)]): OptEnv = - new OptEnv(thisLocalDef, localDefs ++ reps, labelInfos) + new OptEnv(localDefs ++ reps, labelInfos) def withLabelInfo(oldName: LabelName, info: LabelInfo): OptEnv = - new OptEnv(thisLocalDef, localDefs, labelInfos + (oldName -> info)) + new OptEnv(localDefs, labelInfos + (oldName -> info)) override def toString(): String = { - "thisLocalDef:\n " + thisLocalDef.fold("")(_.toString()) + "\n" + "localDefs:" + localDefs.mkString("\n ", "\n ", "\n") + "labelInfos:" + labelInfos.mkString("\n ", "\n ", "") } } private object OptEnv { - val Empty: OptEnv = new OptEnv(None, Map.empty, Map.empty) + val Empty: OptEnv = new OptEnv(Map.empty, Map.empty) } private class Scope private ( @@ -6337,26 +6304,21 @@ private[optimizer] object OptimizerCore { } } - private final case class Binding(name: Binding.Name, declaredType: Type, - mutable: Boolean, value: PreTransform) + private final case class Binding(name: LocalName, originalName: OriginalName, + declaredType: Type, mutable: Boolean, value: PreTransform) private object Binding { - sealed abstract class Name - - case object This extends Name - - final case class Local(name: LocalName, originalName: OriginalName) - extends Name - def apply(localIdent: LocalIdent, originalName: OriginalName, declaredType: Type, mutable: Boolean, value: PreTransform): Binding = { - apply(Local(localIdent.name, originalName), declaredType, - mutable, value) + apply(localIdent.name, originalName, declaredType, mutable, value) } + def forReceiver(declaredType: Type, value: PreTransform): Binding = + apply(LocalName.This, NoOriginalName, declaredType, mutable = false, value) + def temp(baseName: LocalName, declaredType: Type, mutable: Boolean, value: PreTransform): Binding = { - apply(Local(baseName, NoOriginalName), declaredType, mutable, value) + apply(baseName, NoOriginalName, declaredType, mutable, value) } def temp(baseName: LocalName, value: PreTransform): Binding = @@ -6764,7 +6726,7 @@ private[optimizer] object OptimizerCore { val shouldInline = inlineable && { optimizerHints.inline || isForwarder || { body match { - case _:Skip | _:This | _:Literal => + case _:Skip | _:VarRef | _:Literal => true // Shape of accessors @@ -6852,7 +6814,7 @@ private[optimizer] object OptimizerCore { private val TraitInitSimpleMethodName = SimpleMethodName("$init$") private def isTrivialConstructorStat(stat: Tree): Boolean = stat match { - case This() => + case _: VarRef => true case ApplyStatically(_, This(), _, _, Nil) => true @@ -6915,7 +6877,7 @@ private[optimizer] object OptimizerCore { } private def isTrivialArg(arg: Tree): Boolean = arg match { - case _:VarRef | _:This | _:Literal | _:LoadModule => + case _:VarRef | _:Literal | _:LoadModule => true case _ => false @@ -7025,6 +6987,9 @@ private[optimizer] object OptimizerCore { EmitterReservedJSIdentifiers.map(i => LocalName(i) -> 1).toMap final class Local extends FreshNameAllocator[LocalName](InitialLocalMap) { + override def freshName(base: LocalName): LocalName = + super.freshName(if (base.isThis) LocalThisNameForFresh else base) + protected def nameWithSuffix(name: LocalName, suffix: String): LocalName = name.withSuffix(suffix) } @@ -7052,6 +7017,7 @@ private[optimizer] object OptimizerCore { def originalNameForFresh(base: Name, originalName: OriginalName, freshName: Name): OriginalName = { if (originalName.isDefined || (freshName eq base)) originalName + else if (base eq LocalName.This) thisOriginalName else OriginalName(base) } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala index 0fe3988e97..d283ae189e 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala @@ -370,7 +370,7 @@ class IRCheckerTest { for { log <- testLinkIRErrors(classDefs, MainTestModuleInitializers, postOptimizer = true) } yield { - log.assertContainsError("Foo expected but Bar! found for tree of type org.scalajs.ir.Trees$This") + log.assertContainsError("Foo expected but Bar! found for tree of type org.scalajs.ir.Trees$VarRef") } } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala index 78e4e1dd2c..2d22331cc0 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala @@ -383,6 +383,108 @@ class ClassDefCheckerTest { "Illegal constructor call") } + @Test + def illegalThisDeclarations(): Unit = { + val thisParamDef = paramDef(LocalName.This, AnyType) + + // Local var + assertError( + mainTestClassDef(Block( + VarDef(LocalName.This, NON, IntType, mutable = false, int(5)) + )), + "Illegal definition of a variable with name `this`" + ) + + // Method param + assertError( + classDef( + "Foo", superClass = Some(ObjectClass), + methods = List( + trivialCtor("Foo"), + MethodDef(EMF, m("foo", List(I), V), NON, List(thisParamDef), VoidType, Some(Skip()))(EOH, UNV) + ) + ), + "Illegal definition of a variable with name `this`" + ) + + // Capture param of a Closure + assertError( + mainTestClassDef(Block( + Closure(arrow = true, List(thisParamDef), Nil, None, int(5), List(int(6))) + )), + "Illegal definition of a variable with name `this`" + ) + + // Param of a closure + assertError( + mainTestClassDef(Block( + Closure(arrow = true, Nil, List(thisParamDef), None, int(5), Nil) + )), + "Illegal definition of a variable with name `this`" + ) + + // Rest param of a closure + assertError( + mainTestClassDef(Block( + Closure(arrow = true, Nil, Nil, Some(thisParamDef), int(5), Nil) + )), + "Illegal definition of a variable with name `this`" + ) + + // JS method param + assertError( + classDef( + "Foo", superClass = Some(ObjectClass), + kind = ClassKind.JSClass, + jsConstructor = Some(trivialJSCtor()), + jsMethodProps = List( + JSMethodDef(EMF, str("foo"), List(thisParamDef), None, Skip())(EOH, UNV) + ) + ), + "Illegal definition of a variable with name `this`" + ) + + // JS class capture + assertError( + classDef( + "Foo", superClass = Some(ObjectClass), + kind = ClassKind.JSClass, + jsClassCaptures = Some(List(thisParamDef)), + jsConstructor = Some(trivialJSCtor()) + ), + "Illegal JS class capture with name '`this`'" + ) + } + + @Test + def assignmentToImmutable(): Unit = { + assertError( + mainTestClassDef(Block( + VarDef("x", NON, IntType, mutable = false, int(5)), + Assign(VarRef("x")(IntType), int(6)) + )), + "Assignment to immutable variable x." + ) + + assertError( + classDef( + "Foo", superClass = Some(ObjectClass), + methods = List( + trivialCtor("Foo"), + MethodDef(EMF, m("foo", Nil, V), NON, Nil, VoidType, Some(Block( + Assign(thisFor("Foo"), thisFor("Foo")) + )))(EOH, UNV) + ) + ), + "Assignment to immutable variable `this`." + ) + + assertError( + mainTestClassDef(Assign(JSGlobalRef(JSGlobalRef.FileLevelThis), int(5))), + "Assignment to global this." + ) + } + @Test def thisType(): Unit = { def testThisTypeError(static: Boolean, expr: Tree, expectedMsg: String): Unit = { @@ -404,47 +506,47 @@ class ClassDefCheckerTest { testThisTypeError(static = true, This()(VoidType), - "Cannot find `this` in scope") + "Cannot find variable `this` in scope") testThisTypeError(static = true, This()(ClassType("Foo", nullable = false)), - "Cannot find `this` in scope") + "Cannot find variable `this` in scope") testThisTypeError(static = false, This()(VoidType), - "`this` of type Foo! typed as void") + "Variable `this` of type Foo! typed as void") testThisTypeError(static = false, This()(AnyType), - "`this` of type Foo! typed as any") + "Variable `this` of type Foo! typed as any") testThisTypeError(static = false, This()(AnyNotNullType), - "`this` of type Foo! typed as any!") + "Variable `this` of type Foo! typed as any!") testThisTypeError(static = false, This()(ClassType("Bar", nullable = false)), - "`this` of type Foo! typed as Bar!") + "Variable `this` of type Foo! typed as Bar!") testThisTypeError(static = false, This()(ClassType("Foo", nullable = true)), - "`this` of type Foo! typed as Foo") + "Variable `this` of type Foo! typed as Foo") testThisTypeError(static = false, Closure(arrow = true, Nil, Nil, None, This()(VoidType), Nil), - "Cannot find `this` in scope") + "Cannot find variable `this` in scope") testThisTypeError(static = false, Closure(arrow = true, Nil, Nil, None, This()(AnyType), Nil), - "Cannot find `this` in scope") + "Cannot find variable `this` in scope") testThisTypeError(static = false, Closure(arrow = false, Nil, Nil, None, This()(VoidType), Nil), - "`this` of type any typed as void") + "Variable `this` of type any typed as void") testThisTypeError(static = false, Closure(arrow = false, Nil, Nil, None, This()(ClassType("Foo", nullable = false)), Nil), - "`this` of type any typed as Foo!") + "Variable `this` of type any typed as Foo!") } @Test diff --git a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala index d48649c245..e6b045c286 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala @@ -167,7 +167,7 @@ object TestIRBuilder { else None } - def thisFor(cls: ClassName): This = + def thisFor(cls: ClassName): VarRef = This()(ClassType(cls, nullable = false)) implicit def string2LocalName(name: String): LocalName = diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 305e97d428..5df88fa444 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -21,6 +21,10 @@ object BinaryIncompatibilities { ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$LabelIdent$"), ProblemFilters.exclude[Problem]("org.scalajs.ir.Trees#Labeled.*"), ProblemFilters.exclude[Problem]("org.scalajs.ir.Trees#Return.*"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$This"), + ProblemFilters.exclude[MissingTypesProblem]("org.scalajs.ir.Trees$This$"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.ir.Trees#This.apply"), + ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.scalajs.ir.Trees#This.unapply"), ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$Throw"), ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$Throw$"), ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$UnwrapFromThrowable"), From cebcd0a36d29d97c65b32446ea8d5f43953c0b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 27 Dec 2024 18:12:59 +0100 Subject: [PATCH 052/121] Ignore the deprecation of mutable.AnyRefMap in Scala 2.13.16+. It was deprecated because mutable.HashMap is just as fast, starting from 2.13.0. However we still use it for performance in Scala 2.12.x, which is important because that's the version of the linker used by the sbt plugin. --- project/Build.scala | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 2fc1510e58..d7f7f5e234 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -482,6 +482,20 @@ object Build { }, ) + def addWconfSettingIf2_13(conf: String): Def.Setting[_] = { + scalacOptions ++= { + /* We exclude 2.13.0 and 2.13.1 because they did not support -Wconf yet. + * Fortunately, our use cases for -Wconf are only triggered with later + * versions. + */ + val v = scalaVersion.value + if (v.startsWith("2.13.") && v != "2.13.0" && v != "2.13.1") + List("-Wconf:" + conf) + else + Nil + } + } + val commonSettings = Seq( organization := "org.scala-js", version := scalaJSVersion, @@ -517,6 +531,14 @@ object Build { "-encoding", "utf8" ), + /* Ignore the deprecation of mutable.AnyRefMap in Scala 2.13.16+. + * It was deprecated because mutable.HashMap is just as fast, starting + * from 2.13.0. However we still use it for performance in Scala 2.12.x, + * which is important because that's the version of the linker used by + * the sbt plugin. + */ + addWconfSettingIf2_13("cat=deprecation&origin=scala\\.collection\\.mutable\\.AnyRefMap.*:s"), + scalastyleCheck := Def.task { val _ = (scalastyle in Compile).toTask("").value (scalastyle in Test).toTask("").value @@ -1816,17 +1838,7 @@ object Build { /* Silence a Scala 2.13.13+ warning that we cannot address without breaking our API. * See `js.WrappedDictionary.keys` and `js.WrappedMap.keys`. */ - scalacOptions ++= { - /* We only need the option in 2.13.13+, but listing all previous 2.13.x - * versions is cumberson. We only exclude 2.13.0 and 2.13.1 because - * they did not support -Wconf at all. - */ - val v = scalaVersion.value - if (v.startsWith("2.13.") && v != "2.13.0" && v != "2.13.1") - List("-Wconf:msg=overriding method keys in trait MapOps is deprecated:s") - else - Nil - }, + addWconfSettingIf2_13("msg=overriding method keys in trait MapOps is deprecated:s"), test in Test := { streams.value.log.warn("Skipping library/test. Run testSuite/test to test library.") From 23032648f150b572bb5d45c2cb7628957a15acdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 29 Dec 2024 13:00:33 +0100 Subject: [PATCH 053/121] Refactoring: Introduce LocalScope{Transfomer,Traverser}. In several places, we transform or traverse (part of) a method body in a way that is dependent on the local scope. In those situations, we must stop at `Closure` boundaries. We now introduce dedicated subclasses that handle this particular behavior. This factors out the correct handling of `Closure`s, and at the same time makes the intent clearer. One use of `Traverser` in `OptimizerCore` did not handle `Closure`s in a special way, but it should also be about the local scope. --- .../org/scalajs/nscplugin/GenJSCode.scala | 18 +++--------------- .../scala/org/scalajs/ir/Transformers.scala | 15 +++++++++++++++ .../main/scala/org/scalajs/ir/Traversers.scala | 14 ++++++++++++++ .../frontend/optimizer/OptimizerCore.scala | 2 +- 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 5d393dfa47..cccd431460 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -1108,16 +1108,10 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } // After the super call, substitute `selfRef` for `this` - val afterSuper = new ir.Transformers.Transformer { + val afterSuper = new ir.Transformers.LocalScopeTransformer { override def transform(tree: js.Tree): js.Tree = tree match { case js.This() => selfRef(tree.pos) - - // Don't traverse closure boundaries - case closure: js.Closure => - val newCaptureValues = transformTrees(closure.captureValues) - closure.copy(captureValues = newCaptureValues)(closure.pos) - case tree => super.transform(tree) } @@ -2171,14 +2165,11 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } yield { js.ParamDef(name, originalName, ptpe, newMutable(name.name, mutable))(p.pos) } - val transformer = new ir.Transformers.Transformer { + val transformer = new ir.Transformers.LocalScopeTransformer { override def transform(tree: js.Tree): js.Tree = tree match { case js.VarDef(name, originalName, vtpe, mutable, rhs) => super.transform(js.VarDef(name, originalName, vtpe, newMutable(name.name, mutable), rhs)(tree.pos)) - case js.Closure(arrow, captureParams, params, restParam, body, captureValues) => - js.Closure(arrow, captureParams, params, restParam, body, - transformTrees(captureValues))(tree.pos) case _ => super.transform(tree) } @@ -2207,13 +2198,10 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } yield { js.ParamDef(name, originalName, newType(name.name, ptpe), mutable)(p.pos) } - val transformer = new ir.Transformers.Transformer { + val transformer = new ir.Transformers.LocalScopeTransformer { override def transform(tree: js.Tree): js.Tree = tree match { case tree @ js.VarRef(name) => js.VarRef(name)(newType(name, tree.tpe))(tree.pos) - case js.Closure(arrow, captureParams, params, restParam, body, captureValues) => - js.Closure(arrow, captureParams, params, restParam, body, - transformTrees(captureValues))(tree.pos) case _ => super.transform(tree) } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala index 82617dfd01..bbc0c3350b 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala @@ -277,4 +277,19 @@ object Transformers { } } + /** Transformer that only transforms in the local scope. + * + * In practice, this means stopping at `Closure` boundaries: their + * `captureValues` are transformed, but not their other members. + */ + abstract class LocalScopeTransformer extends Transformer { + override def transform(tree: Tree): Tree = tree match { + case Closure(arrow, captureParams, params, restParam, body, captureValues) => + Closure(arrow, captureParams, params, restParam, body, + transformTrees(captureValues))(tree.pos) + case _ => + super.transform(tree) + } + } + } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala index 3edb2ac178..232014750e 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala @@ -245,4 +245,18 @@ object Traversers { } } + /** Traverser that only traverses the local scope. + * + * In practice, this means stopping at `Closure` boundaries: their + * `captureValues` are traversed, but not their other members. + */ + abstract class LocalScopeTraverser extends Traverser { + override def traverse(tree: Tree): Unit = tree match { + case Closure(_, _, _, _, _, captureValues) => + captureValues.foreach(traverse(_)) + case _ => + super.traverse(tree) + } + } + } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 5f275ea841..584e269323 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -1713,7 +1713,7 @@ private[optimizer] abstract class OptimizerCore( private def tryInsertAtFirstEvalContext(valName: LocalName, valTree: Tree, body: Tree): Option[Tree] = { import EvalContextInsertion._ - object valTreeInfo extends Traversers.Traverser { + object valTreeInfo extends Traversers.LocalScopeTraverser { val mutatedLocalVars = mutable.Set.empty[LocalName] traverse(valTree) From edc13a8f02c10e36eb151549aa790ad17a3c0804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 29 Dec 2024 14:42:55 +0100 Subject: [PATCH 054/121] Drop support for Scala 2.12.{2-5}. These were the 2.12.x versions for which we still had some custom code paths, workarounds or exceptions lying around. Scala 2.12.2 and 2.12.3 exhibited compiler crashes on recent changes in the linker, which triggered this decision. --- Jenkinsfile | 3 - .../scalajs/nscplugin/CompatComponent.scala | 5 - .../org/scalajs/nscplugin/GenJSCode.scala | 10 +- .../nscplugin/test/JSInteropTest.scala | 4 - ...icForwardersWarningsTopLevelOnlyTest.scala | 22 +-- .../src/main/scala/org/scalajs/ir/Trees.scala | 8 +- .../linker/backend/javascript/Trees.scala | 8 +- project/Build.scala | 16 -- project/JavalibIRCleaner.scala | 32 ---- .../resources/2.12.1/BlacklistedTests.txt | 117 --------------- .../resources/2.12.2/BlacklistedTests.txt | 124 ---------------- .../resources/2.12.3/BlacklistedTests.txt | 130 ---------------- .../resources/2.12.4/BlacklistedTests.txt | 132 ----------------- .../resources/2.12.5/BlacklistedTests.txt | 139 ------------------ .../testsuite/compiler/RegressionTest.scala | 13 -- 15 files changed, 17 insertions(+), 746 deletions(-) delete mode 100644 scala-test-suite/src/test/resources/2.12.1/BlacklistedTests.txt delete mode 100644 scala-test-suite/src/test/resources/2.12.2/BlacklistedTests.txt delete mode 100644 scala-test-suite/src/test/resources/2.12.3/BlacklistedTests.txt delete mode 100644 scala-test-suite/src/test/resources/2.12.4/BlacklistedTests.txt delete mode 100644 scala-test-suite/src/test/resources/2.12.5/BlacklistedTests.txt diff --git a/Jenkinsfile b/Jenkinsfile index 921ac4915f..aaa2897fbc 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -546,9 +546,6 @@ allJavaVersions << mainJavaVersion def mainScalaVersion = "2.12.19" def mainScalaVersions = ["2.12.19", "2.13.13"] def otherScalaVersions = [ - "2.12.2", - "2.12.3", - "2.12.5", "2.12.6", "2.12.7", "2.12.8", diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/CompatComponent.scala b/compiler/src/main/scala/org/scalajs/nscplugin/CompatComponent.scala index af59c2b4d0..2c8951a67f 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/CompatComponent.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/CompatComponent.scala @@ -71,11 +71,6 @@ trait CompatComponent { lazy val DottyEnumSingletonCompat = AttachmentsCompat.Inner.DottyEnumSingletonAlias - implicit final class SAMFunctionCompatOps(self: SAMFunction) { - // Introduced in 2.12.5 to synthesize bridges in LMF classes - def synthCls: Symbol = NoSymbol - } - /* global.genBCode.bTypes.initializeCoreBTypes() * Early 2.12.x versions require that this method be called from * GenJSCode.run(), but it disappeared later in the 2.12.x series. diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 5d393dfa47..fc31a97bb7 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -6605,16 +6605,10 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) /* scala/bug#10512: any methods which `samInfo.sam` overrides need * bridges made for them. - * On Scala < 2.12.5, `synthCls` is polyfilled to `NoSymbol` and hence - * `samBridges` will always be empty. This causes our compiler to be - * bug-compatible on these versions. */ - val synthCls = samInfo.synthCls - val samBridges = if (synthCls == NoSymbol) { - Nil - } else { + val samBridges = { import scala.reflect.internal.Flags.BRIDGE - synthCls.info.findMembers(excludedFlags = 0L, requiredFlags = BRIDGE).toList + samInfo.synthCls.info.findMembers(excludedFlags = 0L, requiredFlags = BRIDGE).toList } for (sam <- samInfo.sam :: samBridges) { diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala index 582aba2f40..907bf0c83e 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala @@ -4419,10 +4419,6 @@ class JSInteropTest extends DirectTest with TestHelpers { val postUnarySpace = { val hasNoSpace = { - version == "2.12.2" || - version == "2.12.3" || - version == "2.12.4" || - version == "2.12.5" || version == "2.12.6" || version == "2.12.7" || version == "2.12.8" || diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersWarningsTopLevelOnlyTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersWarningsTopLevelOnlyTest.scala index e0781dcf1b..1b2438655e 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersWarningsTopLevelOnlyTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersWarningsTopLevelOnlyTest.scala @@ -24,22 +24,6 @@ class StaticForwardersWarningsTopLevelOnlyTest extends DirectTest with TestHelpe @Test def warnWhenAvoidingStaticForwardersForTopLevelObject: Unit = { - val jvmBackendIssuesWarningOfItsOwn = { - scalaVersion != "2.12.2" && - scalaVersion != "2.12.3" && - scalaVersion != "2.12.4" - } - val jvmBackendMessage = if (!jvmBackendIssuesWarningOfItsOwn) { - "" - } else { - """ - |newSource1.scala:4: warning: Generated class a differs only in case from A. - | Such classes will overwrite one another on case-insensitive filesystems. - | object a { - | ^ - """ - } - """ class A @@ -50,7 +34,11 @@ class StaticForwardersWarningsTopLevelOnlyTest extends DirectTest with TestHelpe s""" |newSource1.scala:4: warning: Not generating the static forwarders of a because its name differs only in case from the name of another class or trait in this compilation unit. | object a { - | ^$jvmBackendMessage + | ^ + |newSource1.scala:4: warning: Generated class a differs only in case from A. + | Such classes will overwrite one another on case-insensitive filesystems. + | object a { + | ^ """ } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index 7fc574d991..e75ab17925 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -20,9 +20,11 @@ import Position.NoPosition import Types._ object Trees { - /* The case classes for IR Nodes are sealed instead of final because making - * them final triggers bugs with Scala 2.12.{1-4}, in combination - * with their `implicit val pos`. + /* The case classes for IR Nodes are sealed instead of final for historical + * reasons. Making them final used to trigger bugs with Scala 2.12.{1-4}, in + * combination with their `implicit val pos`. + * TODO Now that we dropped support for Scala 2.12.5 and below, we should + * revisit this. */ /** Base class for all nodes in the IR. diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Trees.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Trees.scala index 2ad5d6658f..d0117dede1 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Trees.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Trees.scala @@ -22,9 +22,11 @@ import org.scalajs.ir.OriginalName.NoOriginalName import org.scalajs.ir.Position.NoPosition object Trees { - /* The case classes for JS Trees are sealed instead of final because making - * them final triggers bugs with 2.12.{1-4}, in combination - * with their `implicit val pos`. + /* The case classes for JS Trees are sealed instead of final for historical + * reasons. Making them final used to trigger bugs with Scala 2.12.{1-4}, in + * combination with their `implicit val pos`. + * TODO Now that we dropped support for Scala 2.12.5 and below, we should + * revisit this. */ /** AST node of JavaScript. */ diff --git a/project/Build.scala b/project/Build.scala index d7f7f5e234..5c1c69e528 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -950,9 +950,6 @@ object Build { val thisBuildSettings = Def.settings( cross212ScalaVersions := Seq( - "2.12.2", - "2.12.3", - "2.12.5", "2.12.6", "2.12.7", "2.12.8", @@ -2149,19 +2146,6 @@ object Build { includeIf(sharedTestDir / "require-jdk21", javaV >= 21) ::: includeIf(testDir / "require-scala2", isJSTest) }, - - sources in Test := { - val allSources = (sources in Test).value - val scalaV = scalaVersion.value - - val hasBugWithOverriddenMethods = - Set("2.12.2", "2.12.3", "2.12.4").contains(scalaV) - - if (hasBugWithOverriddenMethods) - allSources.filter(_.getName != "SAMWithOverridingBridgesTest.scala") - else - allSources - } ) def testSuiteBootstrapSetting(testSuiteLinker: Project) = Def.settings( diff --git a/project/JavalibIRCleaner.scala b/project/JavalibIRCleaner.scala index 6f014a4e93..d2f4be2881 100644 --- a/project/JavalibIRCleaner.scala +++ b/project/JavalibIRCleaner.scala @@ -356,33 +356,6 @@ final class JavalibIRCleaner(baseDirectoryURI: URI) { JSUnaryOp(JSUnaryOp.!, JSUnaryOp(JSUnaryOp.!, arg)), BooleanType) - // s"..." interpolator in 2.12.2 up to 2.12.4 - case Apply( - ApplyFlags.empty, - New(StringContextClass, MethodIdent(`stringContextCtorMethodName`), - List(ScalaVarArgsReadOnlyLiteral(stringElems))), - MethodIdent(`sMethodName`), - List(ScalaVarArgsReadOnlyLiteral(valueElems))) => - if (stringElems.size != valueElems.size + 1) { - reportError("Found s\"...\" interpolator but the sizes do not match") - tree - } else { - val processedEscapesStringElems = stringElems.map { s => - (s: @unchecked) match { - case StringLiteral(value) => - StringLiteral(StringContext.processEscapes(value)) - } - } - val stringsIter = processedEscapesStringElems.iterator - val valuesIter = valueElems.iterator - var result: Tree = stringsIter.next() - while (valuesIter.hasNext) { - result = BinaryOp(BinaryOp.String_+, result, valuesIter.next()) - result = BinaryOp(BinaryOp.String_+, result, stringsIter.next()) - } - result - } - // LinkingInfo // Must stay in sync with the definitions in `scala.scalajs.LinkingInfo` case IntrinsicCall(LinkingInfoClass, `esVersionMethodName`, Nil) => @@ -649,7 +622,6 @@ object JavalibIRCleaner { private val ReadOnlySeq = ClassName("scala.collection.Seq") private val ScalaSerializable = ClassName("scala.Serializable") private val ScalaJSRuntimeMod = ClassName("scala.scalajs.runtime.package$") - private val StringContextClass = ClassName("scala.StringContext") private val UnionType = ClassName("scala.scalajs.js.$bar") private val UnionTypeMod = ClassName("scala.scalajs.js.$bar$") private val UnionTypeEvidence = ClassName("scala.scalajs.js.$bar$Evidence") @@ -671,10 +643,6 @@ object JavalibIRCleaner { MethodName("jsArrayOps", List(ClassRef(JSArray)), ClassRef(JSArrayOps)) private val number2dynamicMethodName = MethodName("number2dynamic", List(DoubleRef), ClassRef(JSDynamic)) - private val sMethodName = - MethodName("s", List(ClassRef(ReadOnlySeq)), ClassRef(BoxedStringClass)) - private val stringContextCtorMethodName = - MethodName.constructor(List(ClassRef(ReadOnlySeq))) private val toImmutableSeqExtensionMethodName = MethodName("toSeq$extension", List(ClassRef(JSArray)), ClassRef(ImmutableSeq)) private val toJSVarArgsImmutableMethodName = diff --git a/scala-test-suite/src/test/resources/2.12.1/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.1/BlacklistedTests.txt deleted file mode 100644 index 94059d76c2..0000000000 --- a/scala-test-suite/src/test/resources/2.12.1/BlacklistedTests.txt +++ /dev/null @@ -1,117 +0,0 @@ -## Do not compile -scala/lang/annotations/BytecodeTest.scala -scala/lang/annotations/RunTest.scala -scala/lang/traits/BytecodeTest.scala -scala/lang/traits/RunTest.scala -scala/lang/primitives/NaNTest.scala -scala/lang/primitives/BoxUnboxTest.scala -scala/reflect/ClassOfTest.scala -scala/reflect/QTest.scala -scala/reflect/io/ZipArchiveTest.scala -scala/reflect/internal/util/AbstractFileClassLoaderTest.scala -scala/reflect/internal/util/SourceFileTest.scala -scala/reflect/internal/util/StringOpsTest.scala -scala/reflect/internal/PrintersTest.scala -scala/reflect/internal/ScopeTest.scala -scala/reflect/internal/TreeGenTest.scala -scala/reflect/internal/TypesTest.scala -scala/reflect/internal/util/WeakHashSetTest.scala -scala/reflect/internal/MirrorsTest.scala -scala/reflect/internal/NamesTest.scala -scala/tools/nsc/backend/jvm/BTypesTest.scala -scala/tools/nsc/backend/jvm/BytecodeTest.scala -scala/tools/nsc/backend/jvm/DefaultMethodTest.scala -scala/tools/nsc/backend/jvm/DirectCompileTest.scala -scala/tools/nsc/backend/jvm/IndyLambdaTest.scala -scala/tools/nsc/backend/jvm/IndySammyTest.scala -scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala -scala/tools/nsc/backend/jvm/StringConcatTest.scala -scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala -scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala -scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala -scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala -scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala -scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala -scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala -scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala -scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala -scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerTest.scala -scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala -scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala -scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala -scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala -scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala -scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala -scala/tools/nsc/ScriptRunnerTest.scala -scala/tools/nsc/classpath/AggregateClassPathTest.scala -scala/tools/nsc/classpath/PathResolverBaseTest.scala -scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala -scala/tools/nsc/doc/html/HtmlDocletTest.scala -scala/tools/nsc/interpreter/CompletionTest.scala -scala/tools/nsc/interpreter/ScriptedTest.scala -scala/tools/nsc/interpreter/TabulatorTest.scala -scala/tools/nsc/settings/ScalaVersionTest.scala -scala/tools/nsc/settings/SettingsTest.scala -scala/tools/nsc/symtab/CannotHaveAttrsTest.scala -scala/tools/nsc/symtab/FlagsTest.scala -scala/tools/nsc/symtab/FreshNameExtractorTest.scala -scala/tools/nsc/symtab/StdNamesTest.scala -scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala -scala/tools/nsc/symtab/SymbolTableTest.scala -scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala -scala/tools/nsc/transform/patmat/SolvingTest.scala -scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala -scala/tools/nsc/util/StackTraceTest.scala -scala/tools/testing/BytecodeTesting.scala -scala/tools/testing/RunTesting.scala -scala/util/matching/RegexTest.scala - -## Do not link -scala/PartialFunctionSerializationTest.scala -scala/lang/stringinterpol/StringContextTest.scala -scala/collection/IteratorTest.scala -scala/collection/NewBuilderTest.scala -scala/collection/ParallelConsistencyTest.scala -scala/collection/SeqViewTest.scala -scala/collection/SetMapConsistencyTest.scala -scala/collection/convert/WrapperSerializationTest.scala -scala/collection/immutable/ListTest.scala -scala/collection/immutable/StreamTest.scala -scala/collection/immutable/StringLikeTest.scala -scala/collection/mutable/ArrayBufferTest.scala -scala/collection/mutable/MutableListTest.scala -scala/collection/mutable/OpenHashMapTest.scala -scala/collection/mutable/PriorityQueueTest.scala -scala/collection/parallel/immutable/ParRangeTest.scala -scala/concurrent/duration/SerializationTest.scala -scala/concurrent/impl/DefaultPromiseTest.scala -scala/io/SourceTest.scala -scala/runtime/ScalaRunTimeTest.scala -scala/sys/process/PipedProcessTest.scala -scala/sys/process/ProcessTest.scala -scala/tools/testing/AssertUtilTest.scala -scala/tools/testing/AssertThrowsTest.scala -scala/util/SpecVersionTest.scala -scala/util/SystemPropertiesTest.scala - -## Tests fail - -# Reflection -scala/reflect/ClassTagTest.scala - -# Require strict-floats -scala/math/BigDecimalTest.scala - -# Test fails only some times with -# 'set scalaJSLinkerConfig in scalaTestSuite ~= (_.withOptimizer(false))' -scala/collection/immutable/PagedSeqTest.scala - -# Bugs -scala/collection/convert/MapWrapperTest.scala - -# Tests passed but are too slow (timeouts) -scala/collection/immutable/ListSetTest.scala -scala/util/SortingTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.2/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.2/BlacklistedTests.txt deleted file mode 100644 index 7ab77797a7..0000000000 --- a/scala-test-suite/src/test/resources/2.12.2/BlacklistedTests.txt +++ /dev/null @@ -1,124 +0,0 @@ -## Do not compile -scala/lang/annotations/BytecodeTest.scala -scala/lang/annotations/RunTest.scala -scala/lang/traits/BytecodeTest.scala -scala/lang/traits/RunTest.scala -scala/lang/primitives/NaNTest.scala -scala/lang/primitives/BoxUnboxTest.scala -scala/reflect/ClassOfTest.scala -scala/reflect/QTest.scala -scala/reflect/io/ZipArchiveTest.scala -scala/reflect/internal/util/AbstractFileClassLoaderTest.scala -scala/reflect/internal/util/SourceFileTest.scala -scala/reflect/internal/util/StringOpsTest.scala -scala/reflect/internal/PrintersTest.scala -scala/reflect/internal/ScopeTest.scala -scala/reflect/internal/TreeGenTest.scala -scala/reflect/internal/TypesTest.scala -scala/reflect/internal/util/WeakHashSetTest.scala -scala/reflect/internal/MirrorsTest.scala -scala/reflect/internal/NamesTest.scala -scala/tools/nsc/backend/jvm/BTypesTest.scala -scala/tools/nsc/backend/jvm/BytecodeTest.scala -scala/tools/nsc/backend/jvm/DefaultMethodTest.scala -scala/tools/nsc/backend/jvm/DirectCompileTest.scala -scala/tools/nsc/backend/jvm/IndyLambdaTest.scala -scala/tools/nsc/backend/jvm/IndySammyTest.scala -scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala -scala/tools/nsc/backend/jvm/StringConcatTest.scala -scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala -scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala -scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala -scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala -scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala -scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala -scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala -scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala -scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala -scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerTest.scala -scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala -scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala -scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala -scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala -scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala -scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala -scala/tools/nsc/ScriptRunnerTest.scala -scala/tools/nsc/classpath/AggregateClassPathTest.scala -scala/tools/nsc/classpath/JrtClassPathTest.scala -scala/tools/nsc/classpath/PathResolverBaseTest.scala -scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala -scala/tools/nsc/doc/html/HtmlDocletTest.scala -scala/tools/nsc/interpreter/CompletionTest.scala -scala/tools/nsc/interpreter/ScriptedTest.scala -scala/tools/nsc/interpreter/TabulatorTest.scala -scala/tools/nsc/reporters/ConsoleReporterTest.scala -scala/tools/nsc/settings/ScalaVersionTest.scala -scala/tools/nsc/settings/SettingsTest.scala -scala/tools/nsc/symtab/CannotHaveAttrsTest.scala -scala/tools/nsc/symtab/FlagsTest.scala -scala/tools/nsc/symtab/FreshNameExtractorTest.scala -scala/tools/nsc/symtab/StdNamesTest.scala -scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala -scala/tools/nsc/symtab/SymbolTableTest.scala -scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala -scala/tools/nsc/transform/patmat/SolvingTest.scala -scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala -scala/tools/nsc/typechecker/Implicits.scala -scala/tools/nsc/util/StackTraceTest.scala -scala/tools/testing/BytecodeTesting.scala -scala/tools/testing/RunTesting.scala -scala/util/matching/RegexTest.scala - -## Do not link -scala/PartialFunctionSerializationTest.scala -scala/lang/stringinterpol/StringContextTest.scala -scala/collection/IteratorTest.scala -scala/collection/NewBuilderTest.scala -scala/collection/ParallelConsistencyTest.scala -scala/collection/SeqViewTest.scala -scala/collection/SetMapConsistencyTest.scala -scala/collection/concurrent/TrieMapTest.scala -scala/collection/convert/WrapperSerializationTest.scala -scala/collection/immutable/ListTest.scala -scala/collection/immutable/StreamTest.scala -scala/collection/immutable/StringLikeTest.scala -scala/collection/mutable/ArrayBufferTest.scala -scala/collection/mutable/MutableListTest.scala -scala/collection/mutable/OpenHashMapTest.scala -scala/collection/mutable/PriorityQueueTest.scala -scala/collection/parallel/immutable/ParRangeTest.scala -scala/concurrent/duration/SerializationTest.scala -scala/concurrent/impl/DefaultPromiseTest.scala -scala/io/SourceTest.scala -scala/runtime/ScalaRunTimeTest.scala -scala/sys/process/PipedProcessTest.scala -scala/sys/process/ProcessTest.scala -scala/tools/testing/AssertUtilTest.scala -scala/tools/testing/AssertThrowsTest.scala -scala/util/SpecVersionTest.scala -scala/util/SystemPropertiesTest.scala - -## Tests fail - -# Reflection -scala/reflect/ClassTagTest.scala - -# Require strict-floats -scala/math/BigDecimalTest.scala - -# Difference of getClass() on primitive values -scala/collection/immutable/RangeTest.scala - -# Test fails only some times with -# 'set scalaJSLinkerConfig in scalaTestSuite ~= (_.withOptimizer(false))' -scala/collection/immutable/PagedSeqTest.scala - -# Bugs -scala/collection/convert/MapWrapperTest.scala - -# Tests passed but are too slow (timeouts) -scala/collection/immutable/ListSetTest.scala -scala/util/SortingTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.3/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.3/BlacklistedTests.txt deleted file mode 100644 index 3f06cda42a..0000000000 --- a/scala-test-suite/src/test/resources/2.12.3/BlacklistedTests.txt +++ /dev/null @@ -1,130 +0,0 @@ -## Do not compile -scala/lang/annotations/BytecodeTest.scala -scala/lang/annotations/RunTest.scala -scala/lang/traits/BytecodeTest.scala -scala/lang/traits/RunTest.scala -scala/lang/primitives/NaNTest.scala -scala/lang/primitives/BoxUnboxTest.scala -scala/reflect/ClassOfTest.scala -scala/reflect/QTest.scala -scala/reflect/io/ZipArchiveTest.scala -scala/reflect/internal/util/AbstractFileClassLoaderTest.scala -scala/reflect/internal/util/SourceFileTest.scala -scala/reflect/internal/util/StringOpsTest.scala -scala/reflect/internal/PrintersTest.scala -scala/reflect/internal/ScopeTest.scala -scala/reflect/internal/TreeGenTest.scala -scala/reflect/internal/TypesTest.scala -scala/reflect/internal/util/WeakHashSetTest.scala -scala/reflect/internal/MirrorsTest.scala -scala/reflect/internal/NamesTest.scala -scala/tools/nsc/backend/jvm/BTypesTest.scala -scala/tools/nsc/backend/jvm/BytecodeTest.scala -scala/tools/nsc/backend/jvm/DefaultMethodTest.scala -scala/tools/nsc/backend/jvm/DirectCompileTest.scala -scala/tools/nsc/backend/jvm/GenericSignaturesTest.scala -scala/tools/nsc/backend/jvm/IndyLambdaTest.scala -scala/tools/nsc/backend/jvm/IndySammyTest.scala -scala/tools/nsc/backend/jvm/InnerClassAttributeTest.scala -scala/tools/nsc/backend/jvm/NestedClassesCollectorTest.scala -scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala -scala/tools/nsc/backend/jvm/StringConcatTest.scala -scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala -scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala -scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala -scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala -scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala -scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala -scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala -scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala -scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala -scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerTest.scala -scala/tools/nsc/backend/jvm/opt/InlineSourceMatcherTest.scala -scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala -scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala -scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala -scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala -scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala -scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala -scala/tools/nsc/ScriptRunnerTest.scala -scala/tools/nsc/classpath/AggregateClassPathTest.scala -scala/tools/nsc/classpath/JrtClassPathTest.scala -scala/tools/nsc/classpath/PathResolverBaseTest.scala -scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala -scala/tools/nsc/doc/html/HtmlDocletTest.scala -scala/tools/nsc/interpreter/CompletionTest.scala -scala/tools/nsc/interpreter/ScriptedTest.scala -scala/tools/nsc/interpreter/TabulatorTest.scala -scala/tools/nsc/reporters/ConsoleReporterTest.scala -scala/tools/nsc/settings/ScalaVersionTest.scala -scala/tools/nsc/settings/SettingsTest.scala -scala/tools/nsc/symtab/CannotHaveAttrsTest.scala -scala/tools/nsc/symtab/FlagsTest.scala -scala/tools/nsc/symtab/FreshNameExtractorTest.scala -scala/tools/nsc/symtab/StdNamesTest.scala -scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala -scala/tools/nsc/symtab/SymbolTableTest.scala -scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala -scala/tools/nsc/transform/patmat/SolvingTest.scala -scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala -scala/tools/nsc/typechecker/Implicits.scala -scala/tools/nsc/util/StackTraceTest.scala -scala/tools/testing/BytecodeTesting.scala -scala/tools/testing/RunTesting.scala -scala/util/matching/RegexTest.scala - -## Do not link -scala/MatchErrorSerializationTest.scala -scala/PartialFunctionSerializationTest.scala -scala/lang/stringinterpol/StringContextTest.scala -scala/collection/IteratorTest.scala -scala/collection/NewBuilderTest.scala -scala/collection/ParallelConsistencyTest.scala -scala/collection/SeqViewTest.scala -scala/collection/SetMapConsistencyTest.scala -scala/collection/concurrent/TrieMapTest.scala -scala/collection/convert/WrapperSerializationTest.scala -scala/collection/immutable/ListTest.scala -scala/collection/immutable/StreamTest.scala -scala/collection/immutable/StringLikeTest.scala -scala/collection/mutable/ArrayBufferTest.scala -scala/collection/mutable/MutableListTest.scala -scala/collection/mutable/OpenHashMapTest.scala -scala/collection/mutable/PriorityQueueTest.scala -scala/collection/parallel/immutable/ParRangeTest.scala -scala/concurrent/duration/SerializationTest.scala -scala/concurrent/impl/DefaultPromiseTest.scala -scala/io/SourceTest.scala -scala/runtime/ScalaRunTimeTest.scala -scala/sys/process/PipedProcessTest.scala -scala/sys/process/ProcessTest.scala -scala/tools/testing/AssertUtilTest.scala -scala/tools/testing/AssertThrowsTest.scala -scala/util/SpecVersionTest.scala -scala/util/SystemPropertiesTest.scala - -## Tests fail - -# Reflection -scala/reflect/ClassTagTest.scala - -# Require strict-floats -scala/math/BigDecimalTest.scala - -# Difference of getClass() on primitive values -scala/collection/immutable/RangeTest.scala - -# Test fails only some times with -# 'set scalaJSOptimizerOptions in scalaTestSuite ~= (_.withDisableOptimizer(true))' -# and' 'set scalaJSUseRhino in Global := false' -scala/collection/immutable/PagedSeqTest.scala - -# Bugs -scala/collection/convert/MapWrapperTest.scala - -# Tests passed but are too slow (timeouts) -scala/collection/immutable/ListSetTest.scala -scala/util/SortingTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.4/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.4/BlacklistedTests.txt deleted file mode 100644 index 0ecca4009f..0000000000 --- a/scala-test-suite/src/test/resources/2.12.4/BlacklistedTests.txt +++ /dev/null @@ -1,132 +0,0 @@ -## Do not compile -scala/lang/annotations/BytecodeTest.scala -scala/lang/annotations/RunTest.scala -scala/lang/traits/BytecodeTest.scala -scala/lang/traits/RunTest.scala -scala/lang/primitives/NaNTest.scala -scala/lang/primitives/BoxUnboxTest.scala -scala/reflect/ClassOfTest.scala -scala/reflect/QTest.scala -scala/reflect/io/ZipArchiveTest.scala -scala/reflect/internal/util/AbstractFileClassLoaderTest.scala -scala/reflect/internal/util/SourceFileTest.scala -scala/reflect/internal/util/StringOpsTest.scala -scala/reflect/internal/PrintersTest.scala -scala/reflect/internal/ScopeTest.scala -scala/reflect/internal/TreeGenTest.scala -scala/reflect/internal/TypesTest.scala -scala/reflect/internal/util/WeakHashSetTest.scala -scala/reflect/internal/MirrorsTest.scala -scala/reflect/internal/NamesTest.scala -scala/tools/nsc/backend/jvm/BTypesTest.scala -scala/tools/nsc/backend/jvm/BytecodeTest.scala -scala/tools/nsc/backend/jvm/DefaultMethodTest.scala -scala/tools/nsc/backend/jvm/DirectCompileTest.scala -scala/tools/nsc/backend/jvm/GenericSignaturesTest.scala -scala/tools/nsc/backend/jvm/IndyLambdaTest.scala -scala/tools/nsc/backend/jvm/IndySammyTest.scala -scala/tools/nsc/backend/jvm/InnerClassAttributeTest.scala -scala/tools/nsc/backend/jvm/NestedClassesCollectorTest.scala -scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala -scala/tools/nsc/backend/jvm/StringConcatTest.scala -scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala -scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala -scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala -scala/tools/nsc/backend/jvm/opt/BoxUnboxTest.scala -scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala -scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala -scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala -scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala -scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala -scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala -scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerTest.scala -scala/tools/nsc/backend/jvm/opt/InlineSourceMatcherTest.scala -scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala -scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala -scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala -scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala -scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala -scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala -scala/tools/nsc/ScriptRunnerTest.scala -scala/tools/nsc/classpath/AggregateClassPathTest.scala -scala/tools/nsc/classpath/JrtClassPathTest.scala -scala/tools/nsc/classpath/PathResolverBaseTest.scala -scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala -scala/tools/nsc/classpath/ZipAndJarFileLookupFactoryTest.scala -scala/tools/nsc/doc/html/HtmlDocletTest.scala -scala/tools/nsc/interpreter/CompletionTest.scala -scala/tools/nsc/interpreter/ScriptedTest.scala -scala/tools/nsc/interpreter/TabulatorTest.scala -scala/tools/nsc/reporters/ConsoleReporterTest.scala -scala/tools/nsc/settings/ScalaVersionTest.scala -scala/tools/nsc/settings/SettingsTest.scala -scala/tools/nsc/symtab/CannotHaveAttrsTest.scala -scala/tools/nsc/symtab/FlagsTest.scala -scala/tools/nsc/symtab/FreshNameExtractorTest.scala -scala/tools/nsc/symtab/StdNamesTest.scala -scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala -scala/tools/nsc/symtab/SymbolTableTest.scala -scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala -scala/tools/nsc/transform/patmat/SolvingTest.scala -scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala -scala/tools/nsc/typechecker/Implicits.scala -scala/tools/nsc/util/StackTraceTest.scala -scala/tools/testing/BytecodeTesting.scala -scala/tools/testing/RunTesting.scala -scala/util/matching/RegexTest.scala - -## Do not link -scala/MatchErrorSerializationTest.scala -scala/PartialFunctionSerializationTest.scala -scala/lang/stringinterpol/StringContextTest.scala -scala/collection/IteratorTest.scala -scala/collection/NewBuilderTest.scala -scala/collection/ParallelConsistencyTest.scala -scala/collection/SeqViewTest.scala -scala/collection/SetMapConsistencyTest.scala -scala/collection/concurrent/TrieMapTest.scala -scala/collection/convert/WrapperSerializationTest.scala -scala/collection/immutable/ListTest.scala -scala/collection/immutable/StreamTest.scala -scala/collection/immutable/StringLikeTest.scala -scala/collection/mutable/ArrayBufferTest.scala -scala/collection/mutable/MutableListTest.scala -scala/collection/mutable/OpenHashMapTest.scala -scala/collection/mutable/PriorityQueueTest.scala -scala/collection/parallel/immutable/ParRangeTest.scala -scala/concurrent/duration/SerializationTest.scala -scala/concurrent/impl/DefaultPromiseTest.scala -scala/io/SourceTest.scala -scala/runtime/ScalaRunTimeTest.scala -scala/sys/process/PipedProcessTest.scala -scala/sys/process/ProcessTest.scala -scala/tools/testing/AssertUtilTest.scala -scala/tools/testing/AssertThrowsTest.scala -scala/util/SpecVersionTest.scala -scala/util/SystemPropertiesTest.scala - -## Tests fail - -# Reflection -scala/reflect/ClassTagTest.scala - -# Require strict-floats -scala/math/BigDecimalTest.scala - -# Difference of getClass() on primitive values -scala/collection/immutable/RangeTest.scala - -# Test fails only some times with -# 'set scalaJSOptimizerOptions in scalaTestSuite ~= (_.withDisableOptimizer(true))' -# and' 'set scalaJSUseRhino in Global := false' -scala/collection/immutable/PagedSeqTest.scala - -# Bugs -scala/collection/convert/MapWrapperTest.scala - -# Tests passed but are too slow (timeouts) -scala/collection/immutable/ListSetTest.scala -scala/util/SortingTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.5/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.5/BlacklistedTests.txt deleted file mode 100644 index 6cef01dfa3..0000000000 --- a/scala-test-suite/src/test/resources/2.12.5/BlacklistedTests.txt +++ /dev/null @@ -1,139 +0,0 @@ -## Do not compile -scala/lang/annotations/BytecodeTest.scala -scala/lang/annotations/RunTest.scala -scala/lang/traits/BytecodeTest.scala -scala/lang/traits/RunTest.scala -scala/lang/primitives/NaNTest.scala -scala/lang/primitives/BoxUnboxTest.scala -scala/reflect/ClassOfTest.scala -scala/reflect/QTest.scala -scala/reflect/io/AbstractFileTest.scala -scala/reflect/io/ZipArchiveTest.scala -scala/reflect/internal/util/AbstractFileClassLoaderTest.scala -scala/reflect/internal/util/SourceFileTest.scala -scala/reflect/internal/util/StringOpsTest.scala -scala/reflect/internal/PrintersTest.scala -scala/reflect/internal/ScopeTest.scala -scala/reflect/internal/TreeGenTest.scala -scala/reflect/internal/TypesTest.scala -scala/reflect/internal/util/WeakHashSetTest.scala -scala/reflect/internal/MirrorsTest.scala -scala/reflect/internal/NamesTest.scala -scala/tools/nsc/backend/jvm/BTypesTest.scala -scala/tools/nsc/backend/jvm/BytecodeTest.scala -scala/tools/nsc/backend/jvm/DefaultMethodTest.scala -scala/tools/nsc/backend/jvm/DirectCompileTest.scala -scala/tools/nsc/backend/jvm/GenericSignaturesTest.scala -scala/tools/nsc/backend/jvm/IndyLambdaTest.scala -scala/tools/nsc/backend/jvm/IndySammyTest.scala -scala/tools/nsc/backend/jvm/InnerClassAttributeTest.scala -scala/tools/nsc/backend/jvm/NestedClassesCollectorTest.scala -scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala -scala/tools/nsc/backend/jvm/PerRunInitTest.scala -scala/tools/nsc/backend/jvm/StringConcatTest.scala -scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala -scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala -scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala -scala/tools/nsc/backend/jvm/opt/BoxUnboxTest.scala -scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala -scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala -scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala -scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala -scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala -scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala -scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerTest.scala -scala/tools/nsc/backend/jvm/opt/InlineSourceMatcherTest.scala -scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala -scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala -scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala -scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala -scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala -scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala -scala/tools/nsc/ScriptRunnerTest.scala -scala/tools/nsc/classpath/AggregateClassPathTest.scala -scala/tools/nsc/classpath/JrtClassPathTest.scala -scala/tools/nsc/classpath/MultiReleaseJarTest.scala -scala/tools/nsc/classpath/PathResolverBaseTest.scala -scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala -scala/tools/nsc/classpath/ZipAndJarFileLookupFactoryTest.scala -scala/tools/nsc/doc/html/HtmlDocletTest.scala -scala/tools/nsc/interpreter/CompletionTest.scala -scala/tools/nsc/interpreter/ScriptedTest.scala -scala/tools/nsc/interpreter/TabulatorTest.scala -scala/tools/nsc/reporters/ConsoleReporterTest.scala -scala/tools/nsc/settings/ScalaVersionTest.scala -scala/tools/nsc/settings/SettingsTest.scala -scala/tools/nsc/symtab/CannotHaveAttrsTest.scala -scala/tools/nsc/symtab/FlagsTest.scala -scala/tools/nsc/symtab/FreshNameExtractorTest.scala -scala/tools/nsc/symtab/StdNamesTest.scala -scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala -scala/tools/nsc/symtab/SymbolTableTest.scala -scala/tools/nsc/transform/MixinTest.scala -scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala -scala/tools/nsc/transform/patmat/SolvingTest.scala -scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala -scala/tools/nsc/typechecker/Implicits.scala -scala/tools/nsc/typechecker/TypedTreeTest.scala -scala/tools/nsc/util/StackTraceTest.scala -scala/tools/testing/BytecodeTesting.scala -scala/tools/testing/RunTesting.scala -scala/util/matching/RegexTest.scala - -## Do not link -scala/MatchErrorSerializationTest.scala -scala/PartialFunctionSerializationTest.scala -scala/lang/stringinterpol/StringContextTest.scala -scala/collection/IteratorTest.scala -scala/collection/NewBuilderTest.scala -scala/collection/ParallelConsistencyTest.scala -scala/collection/SeqViewTest.scala -scala/collection/SetMapConsistencyTest.scala -scala/collection/concurrent/TrieMapTest.scala -scala/collection/convert/WrapperSerializationTest.scala -scala/collection/immutable/ListTest.scala -scala/collection/immutable/StreamTest.scala -scala/collection/immutable/StringLikeTest.scala -scala/collection/mutable/ArrayBufferTest.scala -scala/collection/mutable/MutableListTest.scala -scala/collection/mutable/OpenHashMapTest.scala -scala/collection/mutable/PriorityQueueTest.scala -scala/collection/parallel/TaskTest.scala -scala/collection/parallel/immutable/ParRangeTest.scala -scala/concurrent/FutureTest.scala -scala/concurrent/duration/SerializationTest.scala -scala/concurrent/impl/DefaultPromiseTest.scala -scala/io/SourceTest.scala -scala/runtime/ScalaRunTimeTest.scala -scala/sys/process/PipedProcessTest.scala -scala/sys/process/ProcessTest.scala -scala/tools/testing/AssertUtilTest.scala -scala/tools/testing/AssertThrowsTest.scala -scala/util/SpecVersionTest.scala -scala/util/SystemPropertiesTest.scala - -## Tests fail - -# Reflection -scala/reflect/ClassTagTest.scala - -# Require strict-floats -scala/math/BigDecimalTest.scala - -# Difference of getClass() on primitive values -scala/collection/immutable/RangeTest.scala - -# Test fails only some times with -# 'set scalaJSOptimizerOptions in scalaTestSuite ~= (_.withDisableOptimizer(true))' -# and' 'set scalaJSUseRhino in Global := false' -scala/collection/immutable/PagedSeqTest.scala - -# Bugs -scala/collection/convert/MapWrapperTest.scala - -# Tests passed but are too slow (timeouts) -scala/collection/immutable/ListSetTest.scala -scala/util/SortingTest.scala diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala index 5ad32e6f30..6642451b5d 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala @@ -20,7 +20,6 @@ import org.junit.Assume._ import org.scalajs.testsuite.utils.AssertThrows.{assertThrows, _} -import org.scalajs.testsuite.utils.Platform import org.scalajs.testsuite.utils.Platform._ class RegressionTest { @@ -102,14 +101,6 @@ class RegressionTest { } @Test def classLiteralsForExistentialValueTypes_Issue218(): Unit = { - import Platform.scalaVersion - - assumeFalse("Affected by https://github.com/scala/bug/issues/10551", - Platform.executingInJVM && { - scalaVersion == "2.12.2" || scalaVersion == "2.12.3" || - scalaVersion == "2.12.4" - }) - assertEquals("org.scalajs.testsuite.compiler.RegressionTest$Bug218Foo", scala.reflect.classTag[Bug218Foo[_]].toString) } @@ -717,10 +708,6 @@ class RegressionTest { } @Test def superMixinCallIn212_Issue3013(): Unit = { - assumeTrue( - "Super mixin calls are broken in Scala/JVM 2.12.{0-2}", - !Platform.executingInJVM || Platform.scalaVersion != "2.12.2") - import Bug3013._ val b = new B From de3a015ba31e651dda6cf4d347ceb5c91cfa04fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 5 Jan 2025 11:51:22 +0100 Subject: [PATCH 055/121] Remove dead code in `OptimizerCore`. --- .../frontend/optimizer/OptimizerCore.scala | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 584e269323..4aa187b975 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -5725,8 +5725,6 @@ private[optimizer] object OptimizerCore { private final class TooManyRollbacksException extends scala.util.control.ControlThrowable - private val AnonFunctionClassPrefix = "sjsr_AnonFunction" - private type CancelFun = () => Nothing private type PreTransCont = PreTransform => TailRec[Tree] @@ -5736,12 +5734,6 @@ private[optimizer] object OptimizerCore { def isNullable: Boolean = base.isNullable def isNothingType: Boolean = base == NothingType - - def toNonNullable: RefinedType = { - if (!isNullable) this - else if (base == NullType) RefinedType.Nothing - else RefinedType(base.toNonNullable, isExact, allocationSite) - } } private object RefinedType { @@ -5768,7 +5760,6 @@ private[optimizer] object OptimizerCore { } val NoRefinedType = RefinedType(VoidType) - val Nothing = RefinedType(NothingType) } /** @@ -6018,12 +6009,6 @@ private[optimizer] object OptimizerCore { copy(implsBeingInlined = implsBeingInlined + impl) } - def inlining(impls: Set[Scope.InliningID]): Scope = { - val intersection = implsBeingInlined.intersect(impls) - assert(intersection.isEmpty, s"Circular inlining of $intersection") - copy(implsBeingInlined = implsBeingInlined ++ impls) - } - def withImportReplacement(importReplacement: ImportReplacement): Scope = { assert(this.importReplacement.isEmpty, "Alreadying replacing " + s"$this.importReplacement while trying to replace $importReplacement") @@ -6418,10 +6403,6 @@ private[optimizer] object OptimizerCore { if (y >= 0) x-y <= x else x-y > x - /** Tests whether `-x` is valid without falling out of range. */ - private def canNegateLong(x: Long): Boolean = - x != Long.MinValue - private final class Intrinsics(intrinsicsMap: Map[(ClassName, MethodName), Int]) { def apply(flags: ApplyFlags, target: AbstractMethodID): Int = { if (flags.isPrivate || flags.isConstructor) { From 67fac71cc04aea45e045aef11f90b7f68f6a6499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 5 Jan 2025 12:26:38 +0100 Subject: [PATCH 056/121] Inline `RefinedType.NoRefinedType` at its unique use site. The other code paths in the method have to allocate a `RefinedType` anyway, so there was no strong reason to avoid the allocation there. --- .../org/scalajs/linker/frontend/optimizer/OptimizerCore.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 4aa187b975..53b8a11ee5 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -384,7 +384,7 @@ private[optimizer] abstract class OptimizerCore( * `Return`s we produce in order to decide whether we can remove * the `Labeled`. */ - info.returnedTreeTypes.value ::= RefinedType.NoRefinedType + info.returnedTreeTypes.value ::= RefinedType(VoidType) Return(newExpr, newLabel) } } else if (!info.acceptRecords) { @@ -5758,8 +5758,6 @@ private[optimizer] object OptimizerCore { } RefinedType(tpe, isExact) } - - val NoRefinedType = RefinedType(VoidType) } /** From e86b32da589744369e4955cf0e1de03399e206dd Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 5 Jan 2025 14:28:04 +0100 Subject: [PATCH 057/121] Remove coreSpec from ModuleSet / LinkingUnit and verifyModuleSet The core spec is configuration, not a part of the linking unit. One could argue that `verifyModuleSet` adds a little bit of safety, but IMO, it does not warrant the cost / boilerplate of copying coreSpec around everywhere. --- .../backend/closure/ClosureLinkerBackend.scala | 2 -- .../linker/backend/BasicLinkerBackend.scala | 2 -- .../org/scalajs/linker/frontend/BaseLinker.scala | 2 +- .../org/scalajs/linker/frontend/LinkingUnit.scala | 1 - .../org/scalajs/linker/frontend/Refiner.scala | 2 +- .../frontend/modulesplitter/ModuleSplitter.scala | 4 ++-- .../scalajs/linker/standard/LinkerBackend.scala | 15 --------------- .../org/scalajs/linker/standard/ModuleSet.scala | 1 - project/BinaryIncompatibilities.scala | 4 ++++ 9 files changed, 8 insertions(+), 25 deletions(-) diff --git a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala index a013049de8..de9fdefa68 100644 --- a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala +++ b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala @@ -99,8 +99,6 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config) */ def emit(moduleSet: ModuleSet, output: OutputDirectory, logger: Logger)( implicit ec: ExecutionContext): Future[Report] = { - verifyModuleSet(moduleSet) - require(moduleSet.modules.size <= 1, "Cannot use multiple modules with the Closure Compiler") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala index 84b01582b9..622daf283f 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/BasicLinkerBackend.scala @@ -76,8 +76,6 @@ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) */ def emit(moduleSet: ModuleSet, output: OutputDirectory, logger: Logger)( implicit ec: ExecutionContext): Future[Report] = { - verifyModuleSet(moduleSet) - // Reset stats. totalModules = moduleSet.modules.size diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala index e9756e38e1..98b3fdf0de 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala @@ -95,7 +95,7 @@ final class BaseLinker(config: CommonPhaseConfig, checkIR: Boolean) { analysis.isClassSuperClassUsed ) - new LinkingUnit(config.coreSpec, linkedClassDefs.toList, + new LinkingUnit(linkedClassDefs.toList, linkedTopLevelExports.flatten.toList, moduleInitializers.toList, globalInfo) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkingUnit.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkingUnit.scala index 836147cba5..a512aa6ec2 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkingUnit.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkingUnit.scala @@ -17,7 +17,6 @@ import org.scalajs.linker.interface.ModuleInitializer import org.scalajs.linker.standard._ final class LinkingUnit private[frontend] ( - val coreSpec: CoreSpec, val classDefs: List[LinkedClass], val topLevelExports: List[LinkedTopLevelExport], val moduleInitializers: List[ModuleInitializer], diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala index 51c4cbd590..a263a7b388 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala @@ -73,7 +73,7 @@ final class Refiner(config: CommonPhaseConfig, checkIR: Boolean) { analysis.isClassSuperClassUsed ) - new LinkingUnit(config.coreSpec, linkedClassDefs.toList, + new LinkingUnit(linkedClassDefs.toList, linkedTopLevelExports.flatten.toList, moduleInitializers, globalInfo) } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/modulesplitter/ModuleSplitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/modulesplitter/ModuleSplitter.scala index 2518679ca1..93f9d12a8e 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/modulesplitter/ModuleSplitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/modulesplitter/ModuleSplitter.scala @@ -45,7 +45,7 @@ final class ModuleSplitter private (analyzer: ModuleAnalyzer) { * We have to do it here, otherwise we break the assumption, that all * non-abstract classes are reached through a static path. */ - new ModuleSet(unit.coreSpec, Nil, Nil, unit.globalInfo) + new ModuleSet(Nil, Nil, unit.globalInfo) } else { val analysis = logger.time("Module Splitter: Analyze Modules") { analyzer.analyze(dependencyInfo) @@ -124,7 +124,7 @@ final class ModuleSplitter private (analyzer: ModuleAnalyzer) { val modules = builders.values.map(_.result()).toList - new ModuleSet(unit.coreSpec, modules, abstractClasses.result(), unit.globalInfo) + new ModuleSet(modules, abstractClasses.result(), unit.globalInfo) } private def publicModuleDependencies( diff --git a/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkerBackend.scala b/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkerBackend.scala index b1d35ebba4..7b1a9f201b 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkerBackend.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkerBackend.scala @@ -54,19 +54,4 @@ abstract class LinkerBackend { def emit(moduleSet: ModuleSet, output: OutputDirectory, logger: Logger)( implicit ec: ExecutionContext): Future[Report] - /** Verify that a `ModuleSet` can be processed by this `LinkerBackend`. - * - * Currently, this only tests that the module set core specification - * matches [[coreSpec]]. - * - * In the future, this test could be extended to test [[symbolRequirements]] - * too. - * - * @throws java.lang.IllegalArgumentException if there is a mismatch - */ - protected def verifyModuleSet(moduleSet: ModuleSet): Unit = { - require(moduleSet.coreSpec == coreSpec, - "ModuleSet and LinkerBackend must agree on their core specification") - } - } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/standard/ModuleSet.scala b/linker/shared/src/main/scala/org/scalajs/linker/standard/ModuleSet.scala index c03557baa9..639032bdfe 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/standard/ModuleSet.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/standard/ModuleSet.scala @@ -34,7 +34,6 @@ import org.scalajs.linker.interface.ModuleInitializer * are no public modules. */ final class ModuleSet private[linker] ( - val coreSpec: CoreSpec, val modules: List[ModuleSet.Module], /** Abstract classes may not have any definitions, but are still required diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 5df88fa444..c4530e7467 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -51,6 +51,10 @@ object BinaryIncompatibilities { ) val Linker = Seq( + // !!! Breaking, OK in minor release + exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.ModuleSet.coreSpec"), + exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.ModuleSet.this"), + exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.LinkerBackend.verifyModuleSet"), ) val LinkerInterface = Seq( From 68b1c71867656081db1529dbb6523e4ea4e78343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 5 Jan 2025 11:35:06 +0100 Subject: [PATCH 058/121] Drop support for Scala 2.13.{0-2}. These were the 2.13.x versions for which we still had some custom code paths, workarounds or exceptions lying around. This commit is a followup to edc13a8f02c10e36eb151549aa790ad17a3c0804. --- CODINGSTYLE.md | 2 +- Jenkinsfile | 3 - .../org/scalajs/nscplugin/GenJSCode.scala | 2 +- .../nscplugin/test/DiverseErrorsTest.scala | 9 +- .../nscplugin/test/JSInteropTest.scala | 4 +- .../test/util/VersionDependentUtils.scala | 19 +- project/Build.scala | 17 +- .../resources/2.13.0/BlacklistedTests.txt | 180 ----------------- .../resources/2.13.1/BlacklistedTests.txt | 181 ----------------- .../resources/2.13.2/BlacklistedTests.txt | 190 ------------------ scalalib/overrides-2.13.0/scala/Symbol.scala | 113 ----------- scalalib/overrides-2.13.1/scala/Symbol.scala | 113 ----------- scalalib/overrides-2.13.2/scala/Symbol.scala | 113 ----------- .../testsuite/scalalib/SymbolTest.scala | 9 +- 14 files changed, 12 insertions(+), 943 deletions(-) delete mode 100644 scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt delete mode 100644 scala-test-suite/src/test/resources/2.13.1/BlacklistedTests.txt delete mode 100644 scala-test-suite/src/test/resources/2.13.2/BlacklistedTests.txt delete mode 100644 scalalib/overrides-2.13.0/scala/Symbol.scala delete mode 100644 scalalib/overrides-2.13.1/scala/Symbol.scala delete mode 100644 scalalib/overrides-2.13.2/scala/Symbol.scala diff --git a/CODINGSTYLE.md b/CODINGSTYLE.md index 47cb6a448a..eb79ec4986 100644 --- a/CODINGSTYLE.md +++ b/CODINGSTYLE.md @@ -227,7 +227,7 @@ value :: list ``` When calling a method declared with an empty pair of parentheses, always use `()`. -Not doing so causes (fatal) warnings when calling Scala-declared methods in Scala 2.13.3+. +Not doing so causes (fatal) warnings when calling Scala-declared methods in Scala 2.13.x. For consistency, we also apply this rule to all Java-defined methods, including `toString()`. ### Method definition diff --git a/Jenkinsfile b/Jenkinsfile index aaa2897fbc..63e464571d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -559,9 +559,6 @@ def otherScalaVersions = [ "2.12.16", "2.12.17", "2.12.18", - "2.13.0", - "2.13.1", - "2.13.2", "2.13.3", "2.13.4", "2.13.5", diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index acf2d096ad..7c4548e67f 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -3875,7 +3875,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * true `int`s can reach the back-end, as asserted by the String-switch * transformation in `cleanup`. Therefore, we do not adapt, preserving * the `string`s and `null`s that come out of the pattern matching in - * Scala 2.13.2+. + * Scala 2.13.x. */ val genSelector = genExpr(selector) diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/DiverseErrorsTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/DiverseErrorsTest.scala index da1360b581..9ed869cbcc 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/DiverseErrorsTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/DiverseErrorsTest.scala @@ -26,13 +26,8 @@ class DiverseErrorsTest extends DirectTest with TestHelpers { private def version = scala.util.Properties.versionNumberString - private val allowsSingletonClassOf = ( - !version.startsWith("2.12.") && - version != "2.13.0" && - version != "2.13.1" && - version != "2.13.2" && - version != "2.13.3" - ) + private val allowsSingletonClassOf = + !version.startsWith("2.12.") && version != "2.13.3" @Test def noIsInstanceOnJS(): Unit = { diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala index 907bf0c83e..8d79f251a3 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala @@ -4423,9 +4423,7 @@ class JSInteropTest extends DirectTest with TestHelpers { version == "2.12.7" || version == "2.12.8" || version == "2.12.9" || - version == "2.12.10" || - version == "2.13.0" || - version == "2.13.1" + version == "2.12.10" } if (hasNoSpace) "" else " " diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/util/VersionDependentUtils.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/VersionDependentUtils.scala index 5b54f73a1b..7e52a1edf8 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/util/VersionDependentUtils.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/VersionDependentUtils.scala @@ -15,23 +15,12 @@ package org.scalajs.nscplugin.test.util object VersionDependentUtils { val scalaVersion = scala.util.Properties.versionNumberString - /** Does the current Scala version support the `@nowarn` annotation? */ - val scalaSupportsNoWarn = { - !scalaVersion.startsWith("2.12.") && - scalaVersion != "2.13.0" && - scalaVersion != "2.13.1" - } + private val isScala212 = scalaVersion.startsWith("2.12.") - private val usesColonInMethodSig = { - /* Yes, this is the same test as in scalaSupportsNoWarn, but that's - * completely coincidental, so we have a copy. - */ - !scalaVersion.startsWith("2.12.") && - scalaVersion != "2.13.0" && - scalaVersion != "2.13.1" - } + /** Does the current Scala version support the `@nowarn` annotation? */ + val scalaSupportsNoWarn = !isScala212 def methodSig(params: String, resultType: String): String = - if (usesColonInMethodSig) params + ": " + resultType + if (!isScala212) params + ": " + resultType else params + resultType } diff --git a/project/Build.scala b/project/Build.scala index 5c1c69e528..9eb18bbd1e 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -484,12 +484,7 @@ object Build { def addWconfSettingIf2_13(conf: String): Def.Setting[_] = { scalacOptions ++= { - /* We exclude 2.13.0 and 2.13.1 because they did not support -Wconf yet. - * Fortunately, our use cases for -Wconf are only triggered with later - * versions. - */ - val v = scalaVersion.value - if (v.startsWith("2.13.") && v != "2.13.0" && v != "2.13.1") + if (scalaVersion.value.startsWith("2.13.")) List("-Wconf:" + conf) else Nil @@ -739,12 +734,7 @@ object Build { scalacOptions in (Compile, doc) := { val prev = (scalacOptions in (Compile, doc)).value - val scalaV = scalaVersion.value - def scaladocFullySupportsJDKgreaterThan8 = { - !scalaV.startsWith("2.12.") && - scalaV != "2.13.0" && scalaV != "2.13.1" && scalaV != "2.13.2" - } - if (javaVersion.value > 8 && !scaladocFullySupportsJDKgreaterThan8) + if (javaVersion.value > 8 && scalaVersion.value.startsWith("2.12.")) prev.filter(_ != "-Xfatal-warnings") else prev @@ -966,9 +956,6 @@ object Build { "2.12.19", ), cross213ScalaVersions := Seq( - "2.13.0", - "2.13.1", - "2.13.2", "2.13.3", "2.13.4", "2.13.5", diff --git a/scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt deleted file mode 100644 index 67798232ba..0000000000 --- a/scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt +++ /dev/null @@ -1,180 +0,0 @@ -## Do not compile -scala/ExtractorTest.scala -scala/OptionTest.scala -scala/SerializationStabilityTest.scala -scala/StringTest.scala -scala/collection/FactoriesTest.scala -scala/collection/LazyZipOpsTest.scala -scala/collection/immutable/IndexedSeqTest.scala -scala/collection/immutable/RangeTest.scala -scala/lang/annotations/BytecodeTest.scala -scala/lang/annotations/RunTest.scala -scala/lang/traits/BytecodeTest.scala -scala/lang/traits/RunTest.scala -scala/lang/primitives/NaNTest.scala -scala/reflect/ClassOfTest.scala -scala/reflect/FieldAccessTest.scala -scala/reflect/QTest.scala -scala/reflect/io/ZipArchiveTest.scala -scala/reflect/internal/Infer.scala -scala/reflect/internal/LongNamesTest.scala -scala/reflect/internal/MirrorsTest.scala -scala/reflect/internal/NamesTest.scala -scala/reflect/internal/PositionsTest.scala -scala/reflect/internal/PrintersTest.scala -scala/reflect/internal/ScopeTest.scala -scala/reflect/internal/TreeGenTest.scala -scala/reflect/internal/TypesTest.scala -scala/reflect/internal/util/AbstractFileClassLoaderTest.scala -scala/reflect/internal/util/FileUtilsTest.scala -scala/reflect/internal/util/SourceFileTest.scala -scala/reflect/internal/util/StringOpsTest.scala -scala/reflect/internal/util/WeakHashSetTest.scala -scala/reflect/io/AbstractFileTest.scala -scala/tools/cmd/CommandLineParserTest.scala -scala/tools/nsc/DeterminismTest.scala -scala/tools/nsc/GlobalCustomizeClassloaderTest.scala -scala/tools/nsc/ScriptRunnerTest.scala -scala/tools/nsc/backend/jvm/BTypesTest.scala -scala/tools/nsc/backend/jvm/BytecodeTest.scala -scala/tools/nsc/backend/jvm/DefaultMethodTest.scala -scala/tools/nsc/backend/jvm/DirectCompileTest.scala -scala/tools/nsc/backend/jvm/GenericSignaturesTest.scala -scala/tools/nsc/backend/jvm/IndyLambdaTest.scala -scala/tools/nsc/backend/jvm/IndySammyTest.scala -scala/tools/nsc/backend/jvm/InnerClassAttributeTest.scala -scala/tools/nsc/backend/jvm/NestedClassesCollectorTest.scala -scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala -scala/tools/nsc/backend/jvm/PerRunInitTest.scala -scala/tools/nsc/backend/jvm/StringConcatTest.scala -scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala -scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala -scala/tools/nsc/backend/jvm/analysis/TypeFlowAnalyzerTest.scala -scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala -scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala -scala/tools/nsc/backend/jvm/opt/BoxUnboxTest.scala -scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala -scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala -scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala -scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala -scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala -scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerTest.scala -scala/tools/nsc/backend/jvm/opt/InlineSourceMatcherTest.scala -scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala -scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala -scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala -scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala -scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala -scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala -scala/tools/nsc/classpath/AggregateClassPathTest.scala -scala/tools/nsc/classpath/JrtClassPathTest.scala -scala/tools/nsc/classpath/MultiReleaseJarTest.scala -scala/tools/nsc/classpath/PathResolverBaseTest.scala -scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala -scala/tools/nsc/classpath/ZipAndJarFileLookupFactoryTest.scala -scala/tools/nsc/doc/html/HtmlDocletTest.scala -scala/tools/nsc/doc/html/StringLiteralTest.scala -scala/tools/nsc/interpreter/CompletionTest.scala -scala/tools/nsc/interpreter/ScriptedTest.scala -scala/tools/nsc/interpreter/TabulatorTest.scala -scala/tools/nsc/parser/ParserTest.scala -scala/tools/nsc/reporters/ConsoleReporterTest.scala -scala/tools/nsc/reporters/PositionFilterTest.scala -scala/tools/nsc/settings/ScalaVersionTest.scala -scala/tools/nsc/settings/SettingsTest.scala -scala/tools/nsc/symtab/CannotHaveAttrsTest.scala -scala/tools/nsc/symtab/FlagsTest.scala -scala/tools/nsc/symtab/FreshNameExtractorTest.scala -scala/tools/nsc/symtab/StdNamesTest.scala -scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala -scala/tools/nsc/symtab/SymbolTableTest.scala -scala/tools/nsc/symtab/classfile/PicklerTest.scala -scala/tools/nsc/transform/ErasureTest.scala -scala/tools/nsc/transform/MixinTest.scala -scala/tools/nsc/transform/ReleaseFenceTest.scala -scala/tools/nsc/transform/SpecializationTest.scala -scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala -scala/tools/nsc/transform/patmat/SolvingTest.scala -scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala -scala/tools/nsc/typechecker/Implicits.scala -scala/tools/nsc/typechecker/Infer.scala -scala/tools/nsc/typechecker/NamerTest.scala -scala/tools/nsc/typechecker/OverridingPairsTest.scala -scala/tools/nsc/typechecker/ParamAliasTest.scala -scala/tools/nsc/typechecker/TypedTreeTest.scala -scala/tools/nsc/util/StackTraceTest.scala -scala/tools/testing/BytecodeTesting.scala -scala/tools/testing/RunTesting.scala -scala/tools/testing/VirtualCompilerTesting.scala -scala/util/ChainingOpsTest.scala -scala/util/matching/RegexTest.scala - -## Do not link -scala/CollectTest.scala -scala/MatchErrorSerializationTest.scala -scala/PartialFunctionSerializationTest.scala -scala/lang/stringinterpol/StringContextTest.scala -scala/collection/IterableTest.scala -scala/collection/IteratorTest.scala -scala/collection/NewBuilderTest.scala -scala/collection/SeqViewTest.scala -scala/collection/SetMapConsistencyTest.scala -scala/collection/SetMapRulesTest.scala -scala/collection/concurrent/TrieMapTest.scala -scala/collection/convert/WrapperSerializationTest.scala -scala/collection/immutable/ChampMapSmokeTest.scala -scala/collection/immutable/ChampSetSmokeTest.scala -scala/collection/immutable/LazyListGCTest.scala -scala/collection/immutable/LazyListLazinessTest.scala -scala/collection/immutable/ListTest.scala -scala/collection/immutable/SerializationTest.scala -scala/collection/immutable/StreamTest.scala -scala/collection/immutable/StringLikeTest.scala -scala/collection/immutable/VectorTest.scala -scala/collection/mutable/ArrayBufferTest.scala -scala/collection/mutable/OpenHashMapTest.scala -scala/collection/mutable/PriorityQueueTest.scala -scala/collection/mutable/SerializationTest.scala -scala/concurrent/FutureTest.scala -scala/concurrent/duration/SerializationTest.scala -scala/concurrent/impl/DefaultPromiseTest.scala -scala/io/SourceTest.scala -scala/jdk/AccumulatorTest.scala -scala/jdk/DurationConvertersTest.scala -scala/jdk/FunctionConvertersTest.scala -scala/jdk/OptionConvertersTest.scala -scala/jdk/StepperConversionTest.scala -scala/jdk/StepperTest.scala -scala/jdk/StreamConvertersTest.scala -scala/jdk/StreamConvertersTypingTest.scala -scala/math/OrderingTest.scala -scala/runtime/ScalaRunTimeTest.scala -scala/sys/process/ParserTest.scala -scala/sys/process/PipedProcessTest.scala -scala/sys/process/ProcessBuilderTest.scala -scala/sys/process/ProcessTest.scala -scala/tools/testing/AssertUtilTest.scala -scala/tools/testing/AssertThrowsTest.scala -scala/util/PropertiesTest.scala -scala/util/SpecVersionTest.scala -scala/util/SystemPropertiesTest.scala - -## Tests fail - -# Reflection -scala/reflect/ClassTagTest.scala - -# Require strict-floats -scala/math/BigDecimalTest.scala - -# Tests passed but are too slow (timeouts) -scala/collection/immutable/ListSetTest.scala -scala/util/SortingTest.scala - -# Relies on undefined behavior -scala/collection/StringOpsTest.scala -scala/collection/StringParsersTest.scala -scala/collection/convert/MapWrapperTest.scala diff --git a/scala-test-suite/src/test/resources/2.13.1/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.1/BlacklistedTests.txt deleted file mode 100644 index 70b0dce1a0..0000000000 --- a/scala-test-suite/src/test/resources/2.13.1/BlacklistedTests.txt +++ /dev/null @@ -1,181 +0,0 @@ -## Do not compile -scala/ExtractorTest.scala -scala/OptionTest.scala -scala/SerializationStabilityTest.scala -scala/StringTest.scala -scala/collection/FactoriesTest.scala -scala/collection/LazyZipOpsTest.scala -scala/collection/immutable/IndexedSeqTest.scala -scala/collection/immutable/RangeTest.scala -scala/lang/annotations/BytecodeTest.scala -scala/lang/annotations/RunTest.scala -scala/lang/traits/BytecodeTest.scala -scala/lang/traits/RunTest.scala -scala/lang/primitives/NaNTest.scala -scala/reflect/ClassOfTest.scala -scala/reflect/FieldAccessTest.scala -scala/reflect/QTest.scala -scala/reflect/io/ZipArchiveTest.scala -scala/reflect/internal/Infer.scala -scala/reflect/internal/LongNamesTest.scala -scala/reflect/internal/MirrorsTest.scala -scala/reflect/internal/NamesTest.scala -scala/reflect/internal/PositionsTest.scala -scala/reflect/internal/PrintersTest.scala -scala/reflect/internal/ScopeTest.scala -scala/reflect/internal/TreeGenTest.scala -scala/reflect/internal/TypesTest.scala -scala/reflect/internal/util/AbstractFileClassLoaderTest.scala -scala/reflect/internal/util/FileUtilsTest.scala -scala/reflect/internal/util/SourceFileTest.scala -scala/reflect/internal/util/StringOpsTest.scala -scala/reflect/internal/util/WeakHashSetTest.scala -scala/reflect/io/AbstractFileTest.scala -scala/reflect/runtime/ThreadSafetyTest.scala -scala/tools/cmd/CommandLineParserTest.scala -scala/tools/nsc/DeterminismTest.scala -scala/tools/nsc/FileUtils.scala -scala/tools/nsc/GlobalCustomizeClassloaderTest.scala -scala/tools/nsc/PhaseAssemblyTest.scala -scala/tools/nsc/PipelineMainTest.scala -scala/tools/nsc/ScriptRunnerTest.scala -scala/tools/nsc/backend/jvm/BTypesTest.scala -scala/tools/nsc/backend/jvm/BytecodeTest.scala -scala/tools/nsc/backend/jvm/DefaultMethodTest.scala -scala/tools/nsc/backend/jvm/DirectCompileTest.scala -scala/tools/nsc/backend/jvm/GenericSignaturesTest.scala -scala/tools/nsc/backend/jvm/IndyLambdaTest.scala -scala/tools/nsc/backend/jvm/IndySammyTest.scala -scala/tools/nsc/backend/jvm/InnerClassAttributeTest.scala -scala/tools/nsc/backend/jvm/NestedClassesCollectorTest.scala -scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala -scala/tools/nsc/backend/jvm/PerRunInitTest.scala -scala/tools/nsc/backend/jvm/StringConcatTest.scala -scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala -scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala -scala/tools/nsc/backend/jvm/analysis/TypeFlowAnalyzerTest.scala -scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala -scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala -scala/tools/nsc/backend/jvm/opt/BoxUnboxTest.scala -scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala -scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala -scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala -scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala -scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala -scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerTest.scala -scala/tools/nsc/backend/jvm/opt/InlineSourceMatcherTest.scala -scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala -scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala -scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala -scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala -scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala -scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala -scala/tools/nsc/classpath/AggregateClassPathTest.scala -scala/tools/nsc/classpath/JrtClassPathTest.scala -scala/tools/nsc/classpath/MultiReleaseJarTest.scala -scala/tools/nsc/classpath/PathResolverBaseTest.scala -scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala -scala/tools/nsc/classpath/ZipAndJarFileLookupFactoryTest.scala -scala/tools/nsc/doc/html/HtmlDocletTest.scala -scala/tools/nsc/doc/html/StringLiteralTest.scala -scala/tools/nsc/interpreter/CompletionTest.scala -scala/tools/nsc/interpreter/ScriptedTest.scala -scala/tools/nsc/interpreter/TabulatorTest.scala -scala/tools/nsc/parser/ParserTest.scala -scala/tools/nsc/reporters/ConsoleReporterTest.scala -scala/tools/nsc/reporters/PositionFilterTest.scala -scala/tools/nsc/settings/ScalaVersionTest.scala -scala/tools/nsc/settings/SettingsTest.scala -scala/tools/nsc/settings/TargetTest.scala -scala/tools/nsc/symtab/CannotHaveAttrsTest.scala -scala/tools/nsc/symtab/FlagsTest.scala -scala/tools/nsc/symtab/FreshNameExtractorTest.scala -scala/tools/nsc/symtab/StdNamesTest.scala -scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala -scala/tools/nsc/symtab/SymbolTableTest.scala -scala/tools/nsc/symtab/classfile/PicklerTest.scala -scala/tools/nsc/transform/ErasureTest.scala -scala/tools/nsc/transform/MixinTest.scala -scala/tools/nsc/transform/ReleaseFenceTest.scala -scala/tools/nsc/transform/SpecializationTest.scala -scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala -scala/tools/nsc/transform/patmat/SolvingTest.scala -scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala -scala/tools/nsc/typechecker/Implicits.scala -scala/tools/nsc/typechecker/Infer.scala -scala/tools/nsc/typechecker/NamerTest.scala -scala/tools/nsc/typechecker/OverridingPairsTest.scala -scala/tools/nsc/typechecker/ParamAliasTest.scala -scala/tools/nsc/typechecker/TypedTreeTest.scala -scala/tools/nsc/util/StackTraceTest.scala -scala/util/ChainingOpsTest.scala -scala/util/matching/RegexTest.scala - -## Do not link -scala/CollectTest.scala -scala/MatchErrorSerializationTest.scala -scala/PartialFunctionSerializationTest.scala -scala/lang/stringinterpol/StringContextTest.scala -scala/collection/IterableTest.scala -scala/collection/IteratorTest.scala -scala/collection/NewBuilderTest.scala -scala/collection/SeqViewTest.scala -scala/collection/SetMapConsistencyTest.scala -scala/collection/SetMapRulesTest.scala -scala/collection/concurrent/TrieMapTest.scala -scala/collection/convert/WrapperSerializationTest.scala -scala/collection/immutable/ChampMapSmokeTest.scala -scala/collection/immutable/ChampSetSmokeTest.scala -scala/collection/immutable/LazyListGCTest.scala -scala/collection/immutable/LazyListLazinessTest.scala -scala/collection/immutable/ListTest.scala -scala/collection/immutable/SerializationTest.scala -scala/collection/immutable/StreamTest.scala -scala/collection/immutable/StringLikeTest.scala -scala/collection/immutable/VectorTest.scala -scala/collection/mutable/ArrayBufferTest.scala -scala/collection/mutable/OpenHashMapTest.scala -scala/collection/mutable/PriorityQueueTest.scala -scala/collection/mutable/SerializationTest.scala -scala/concurrent/FutureTest.scala -scala/concurrent/duration/SerializationTest.scala -scala/concurrent/impl/DefaultPromiseTest.scala -scala/io/SourceTest.scala -scala/jdk/AccumulatorTest.scala -scala/jdk/DurationConvertersTest.scala -scala/jdk/FunctionConvertersTest.scala -scala/jdk/OptionConvertersTest.scala -scala/jdk/StepperConversionTest.scala -scala/jdk/StepperTest.scala -scala/jdk/StreamConvertersTest.scala -scala/jdk/StreamConvertersTypingTest.scala -scala/math/OrderingTest.scala -scala/runtime/ScalaRunTimeTest.scala -scala/sys/process/ParserTest.scala -scala/sys/process/PipedProcessTest.scala -scala/sys/process/ProcessBuilderTest.scala -scala/sys/process/ProcessTest.scala -scala/tools/testkit/AssertUtilTest.scala -scala/util/PropertiesTest.scala -scala/util/SpecVersionTest.scala -scala/util/SystemPropertiesTest.scala - -## Tests fail - -# Reflection -scala/reflect/ClassTagTest.scala - -# Require strict-floats -scala/math/BigDecimalTest.scala - -# Tests passed but are too slow (timeouts) -scala/collection/immutable/ListSetTest.scala -scala/util/SortingTest.scala - -# Relies on undefined behavior -scala/collection/StringOpsTest.scala -scala/collection/StringParsersTest.scala -scala/collection/convert/MapWrapperTest.scala diff --git a/scala-test-suite/src/test/resources/2.13.2/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.2/BlacklistedTests.txt deleted file mode 100644 index 7ad0c9c4c5..0000000000 --- a/scala-test-suite/src/test/resources/2.13.2/BlacklistedTests.txt +++ /dev/null @@ -1,190 +0,0 @@ -## Do not compile -scala/ExtractorTest.scala -scala/OptionTest.scala -scala/SerializationStabilityTest.scala -scala/StringTest.scala -scala/collection/FactoriesTest.scala -scala/collection/LazyZipOpsTest.scala -scala/collection/immutable/IndexedSeqTest.scala -scala/collection/immutable/MapHashcodeTest.scala -scala/collection/immutable/RangeTest.scala -scala/collection/mutable/BitSetTest.scala -scala/lang/annotations/BytecodeTest.scala -scala/lang/annotations/RunTest.scala -scala/lang/traits/BytecodeTest.scala -scala/lang/traits/RunTest.scala -scala/lang/primitives/NaNTest.scala -scala/reflect/ClassOfTest.scala -scala/reflect/FieldAccessTest.scala -scala/reflect/QTest.scala -scala/reflect/io/ZipArchiveTest.scala -scala/reflect/internal/Infer.scala -scala/reflect/internal/LongNamesTest.scala -scala/reflect/internal/MirrorsTest.scala -scala/reflect/internal/NamesTest.scala -scala/reflect/internal/PositionsTest.scala -scala/reflect/internal/PrintersTest.scala -scala/reflect/internal/ScopeTest.scala -scala/reflect/internal/TreeGenTest.scala -scala/reflect/internal/TypesTest.scala -scala/reflect/internal/util/AbstractFileClassLoaderTest.scala -scala/reflect/internal/util/FileUtilsTest.scala -scala/reflect/internal/util/SourceFileTest.scala -scala/reflect/internal/util/StringOpsTest.scala -scala/reflect/internal/util/WeakHashSetTest.scala -scala/reflect/io/AbstractFileTest.scala -scala/reflect/runtime/ThreadSafetyTest.scala -scala/tools/cmd/CommandLineParserTest.scala -scala/tools/nsc/Build.scala -scala/tools/nsc/DeterminismTest.scala -scala/tools/nsc/FileUtils.scala -scala/tools/nsc/GlobalCustomizeClassloaderTest.scala -scala/tools/nsc/PhaseAssemblyTest.scala -scala/tools/nsc/PickleWriteTest.scala -scala/tools/nsc/PipelineMainTest.scala -scala/tools/nsc/ScriptRunnerTest.scala -scala/tools/nsc/backend/jvm/BTypesTest.scala -scala/tools/nsc/backend/jvm/BytecodeTest.scala -scala/tools/nsc/backend/jvm/DefaultMethodTest.scala -scala/tools/nsc/backend/jvm/DirectCompileTest.scala -scala/tools/nsc/backend/jvm/GenericSignaturesTest.scala -scala/tools/nsc/backend/jvm/IndyLambdaTest.scala -scala/tools/nsc/backend/jvm/IndySammyTest.scala -scala/tools/nsc/backend/jvm/InnerClassAttributeTest.scala -scala/tools/nsc/backend/jvm/NestedClassesCollectorTest.scala -scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala -scala/tools/nsc/backend/jvm/PerRunInitTest.scala -scala/tools/nsc/backend/jvm/StringConcatTest.scala -scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala -scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala -scala/tools/nsc/backend/jvm/analysis/TypeFlowAnalyzerTest.scala -scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala -scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala -scala/tools/nsc/backend/jvm/opt/BoxUnboxTest.scala -scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala -scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala -scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala -scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala -scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala -scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala -scala/tools/nsc/backend/jvm/opt/InlinerTest.scala -scala/tools/nsc/backend/jvm/opt/InlineSourceMatcherTest.scala -scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala -scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala -scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala -scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala -scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala -scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala -scala/tools/nsc/classpath/AggregateClassPathTest.scala -scala/tools/nsc/classpath/JrtClassPathTest.scala -scala/tools/nsc/classpath/MultiReleaseJarTest.scala -scala/tools/nsc/classpath/PathResolverBaseTest.scala -scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala -scala/tools/nsc/classpath/ZipAndJarFileLookupFactoryTest.scala -scala/tools/nsc/doc/html/HtmlDocletTest.scala -scala/tools/nsc/doc/html/StringLiteralTest.scala -scala/tools/nsc/interpreter/CompletionTest.scala -scala/tools/nsc/interpreter/ScriptedTest.scala -scala/tools/nsc/interpreter/TabulatorTest.scala -scala/tools/nsc/parser/ParserTest.scala -scala/tools/nsc/reporters/ConsoleReporterTest.scala -scala/tools/nsc/reporters/PositionFilterTest.scala -scala/tools/nsc/reporters/WConfTest.scala -scala/tools/nsc/settings/ScalaVersionTest.scala -scala/tools/nsc/settings/SettingsTest.scala -scala/tools/nsc/settings/TargetTest.scala -scala/tools/nsc/symtab/CannotHaveAttrsTest.scala -scala/tools/nsc/symtab/FlagsTest.scala -scala/tools/nsc/symtab/FreshNameExtractorTest.scala -scala/tools/nsc/symtab/StdNamesTest.scala -scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala -scala/tools/nsc/symtab/SymbolTableTest.scala -scala/tools/nsc/symtab/classfile/PicklerTest.scala -scala/tools/nsc/transform/ErasureTest.scala -scala/tools/nsc/transform/MixinTest.scala -scala/tools/nsc/transform/ReleaseFenceTest.scala -scala/tools/nsc/transform/SpecializationTest.scala -scala/tools/nsc/transform/UncurryTest.scala -scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala -scala/tools/nsc/transform/patmat/SolvingTest.scala -scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala -scala/tools/nsc/typechecker/Implicits.scala -scala/tools/nsc/typechecker/Infer.scala -scala/tools/nsc/typechecker/NamerTest.scala -scala/tools/nsc/typechecker/OverridingPairsTest.scala -scala/tools/nsc/typechecker/ParamAliasTest.scala -scala/tools/nsc/typechecker/TypedTreeTest.scala -scala/tools/nsc/util/StackTraceTest.scala -scala/util/ChainingOpsTest.scala -scala/util/TryTest.scala -scala/util/matching/RegexTest.scala - -## Do not link -scala/CollectTest.scala -scala/MatchErrorSerializationTest.scala -scala/PartialFunctionSerializationTest.scala -scala/lang/stringinterpol/StringContextTest.scala -scala/collection/IterableTest.scala -scala/collection/IteratorTest.scala -scala/collection/NewBuilderTest.scala -scala/collection/SeqViewTest.scala -scala/collection/SetMapConsistencyTest.scala -scala/collection/SetMapRulesTest.scala -scala/collection/concurrent/TrieMapTest.scala -scala/collection/convert/WrapperSerializationTest.scala -scala/collection/immutable/ChampMapSmokeTest.scala -scala/collection/immutable/ChampSetSmokeTest.scala -scala/collection/immutable/LazyListGCTest.scala -scala/collection/immutable/LazyListLazinessTest.scala -scala/collection/immutable/ListTest.scala -scala/collection/immutable/SerializationTest.scala -scala/collection/immutable/StreamTest.scala -scala/collection/immutable/StringLikeTest.scala -scala/collection/immutable/VectorTest.scala -scala/collection/mutable/AnyRefMapTest.scala -scala/collection/mutable/ArrayBufferTest.scala -scala/collection/mutable/OpenHashMapTest.scala -scala/collection/mutable/PriorityQueueTest.scala -scala/collection/mutable/SerializationTest.scala -scala/concurrent/FutureTest.scala -scala/concurrent/duration/SerializationTest.scala -scala/concurrent/impl/DefaultPromiseTest.scala -scala/io/SourceTest.scala -scala/jdk/AccumulatorTest.scala -scala/jdk/DurationConvertersTest.scala -scala/jdk/FunctionConvertersTest.scala -scala/jdk/OptionConvertersTest.scala -scala/jdk/StepperConversionTest.scala -scala/jdk/StepperTest.scala -scala/jdk/StreamConvertersTest.scala -scala/jdk/StreamConvertersTypingTest.scala -scala/math/OrderingTest.scala -scala/runtime/ScalaRunTimeTest.scala -scala/sys/env.scala -scala/sys/process/ParserTest.scala -scala/sys/process/PipedProcessTest.scala -scala/sys/process/ProcessBuilderTest.scala -scala/sys/process/ProcessTest.scala -scala/tools/testkit/AssertUtilTest.scala -scala/util/PropertiesTest.scala -scala/util/SpecVersionTest.scala -scala/util/SystemPropertiesTest.scala - -## Tests fail - -# Reflection -scala/reflect/ClassTagTest.scala - -# Require strict-floats -scala/math/BigDecimalTest.scala - -# Tests passed but are too slow (timeouts) -scala/collection/immutable/ListSetTest.scala -scala/util/SortingTest.scala - -# Relies on undefined behavior -scala/collection/StringOpsTest.scala -scala/collection/StringParsersTest.scala -scala/collection/convert/MapWrapperTest.scala diff --git a/scalalib/overrides-2.13.0/scala/Symbol.scala b/scalalib/overrides-2.13.0/scala/Symbol.scala deleted file mode 100644 index 0000b5fe6f..0000000000 --- a/scalalib/overrides-2.13.0/scala/Symbol.scala +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Scala (https://www.scala-lang.org) - * - * Copyright EPFL and Lightbend, Inc. - * - * Licensed under Apache License 2.0 - * (http://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -package scala - -import scala.scalajs.js - -/** This class provides a simple way to get unique objects for equal strings. - * Since symbols are interned, they can be compared using reference equality. - * Instances of `Symbol` can be created easily with Scala's built-in quote - * mechanism. - * - * For instance, the Scala term `'mysym` will - * invoke the constructor of the `Symbol` class in the following way: - * `Symbol("mysym")`. - * - * @author Martin Odersky, Iulian Dragos - * @since 1.7 - */ -final class Symbol private (val name: String) extends Serializable { - /** Converts this symbol to a string. - */ - override def toString(): String = "'" + name - - @throws(classOf[java.io.ObjectStreamException]) - private def readResolve(): Any = Symbol.apply(name) - override def hashCode = name.hashCode() - override def equals(other: Any) = this eq other.asInstanceOf[AnyRef] -} - -// Modified to use Scala.js specific cache -object Symbol extends JSUniquenessCache[Symbol] { - override def apply(name: String): Symbol = super.apply(name) - protected def valueFromKey(name: String): Symbol = new Symbol(name) - protected def keyFromValue(sym: Symbol): Option[String] = Some(sym.name) -} - -private[scala] abstract class JSUniquenessCache[V] -{ - private val cache = js.Dictionary.empty[V] - - protected def valueFromKey(k: String): V - protected def keyFromValue(v: V): Option[String] - - def apply(name: String): V = - cache.getOrElseUpdate(name, valueFromKey(name)) - - def unapply(other: V): Option[String] = keyFromValue(other) -} - -/** This is private so it won't appear in the library API, but - * abstracted to offer some hope of reusability. */ -/* DELETED for Scala.js -private[scala] abstract class UniquenessCache[K >: js.String, V >: Null] -{ - - import java.lang.ref.WeakReference - import java.util.WeakHashMap - import java.util.concurrent.locks.ReentrantReadWriteLock - - private[this] val rwl = new ReentrantReadWriteLock() - private[this] val rlock = rwl.readLock - private[this] val wlock = rwl.writeLock - private[this] val map = new WeakHashMap[K, WeakReference[V]] - - protected def valueFromKey(k: K): V - protected def keyFromValue(v: V): Option[K] - - def apply(name: K): V = { - def cached(): V = { - rlock.lock - try { - val reference = map get name - if (reference == null) null - else reference.get // will be null if we were gc-ed - } - finally rlock.unlock - } - def updateCache(): V = { - wlock.lock - try { - val res = cached() - if (res != null) res - else { - // If we don't remove the old String key from the map, we can - // wind up with one String as the key and a different String as - // the name field in the Symbol, which can lead to surprising GC - // behavior and duplicate Symbols. See scala/bug#6706. - map remove name - val sym = valueFromKey(name) - map.put(name, new WeakReference(sym)) - sym - } - } - finally wlock.unlock - } - - val res = cached() - if (res == null) updateCache() - else res - } - def unapply(other: V): Option[K] = keyFromValue(other) -} -*/ diff --git a/scalalib/overrides-2.13.1/scala/Symbol.scala b/scalalib/overrides-2.13.1/scala/Symbol.scala deleted file mode 100644 index 0000b5fe6f..0000000000 --- a/scalalib/overrides-2.13.1/scala/Symbol.scala +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Scala (https://www.scala-lang.org) - * - * Copyright EPFL and Lightbend, Inc. - * - * Licensed under Apache License 2.0 - * (http://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -package scala - -import scala.scalajs.js - -/** This class provides a simple way to get unique objects for equal strings. - * Since symbols are interned, they can be compared using reference equality. - * Instances of `Symbol` can be created easily with Scala's built-in quote - * mechanism. - * - * For instance, the Scala term `'mysym` will - * invoke the constructor of the `Symbol` class in the following way: - * `Symbol("mysym")`. - * - * @author Martin Odersky, Iulian Dragos - * @since 1.7 - */ -final class Symbol private (val name: String) extends Serializable { - /** Converts this symbol to a string. - */ - override def toString(): String = "'" + name - - @throws(classOf[java.io.ObjectStreamException]) - private def readResolve(): Any = Symbol.apply(name) - override def hashCode = name.hashCode() - override def equals(other: Any) = this eq other.asInstanceOf[AnyRef] -} - -// Modified to use Scala.js specific cache -object Symbol extends JSUniquenessCache[Symbol] { - override def apply(name: String): Symbol = super.apply(name) - protected def valueFromKey(name: String): Symbol = new Symbol(name) - protected def keyFromValue(sym: Symbol): Option[String] = Some(sym.name) -} - -private[scala] abstract class JSUniquenessCache[V] -{ - private val cache = js.Dictionary.empty[V] - - protected def valueFromKey(k: String): V - protected def keyFromValue(v: V): Option[String] - - def apply(name: String): V = - cache.getOrElseUpdate(name, valueFromKey(name)) - - def unapply(other: V): Option[String] = keyFromValue(other) -} - -/** This is private so it won't appear in the library API, but - * abstracted to offer some hope of reusability. */ -/* DELETED for Scala.js -private[scala] abstract class UniquenessCache[K >: js.String, V >: Null] -{ - - import java.lang.ref.WeakReference - import java.util.WeakHashMap - import java.util.concurrent.locks.ReentrantReadWriteLock - - private[this] val rwl = new ReentrantReadWriteLock() - private[this] val rlock = rwl.readLock - private[this] val wlock = rwl.writeLock - private[this] val map = new WeakHashMap[K, WeakReference[V]] - - protected def valueFromKey(k: K): V - protected def keyFromValue(v: V): Option[K] - - def apply(name: K): V = { - def cached(): V = { - rlock.lock - try { - val reference = map get name - if (reference == null) null - else reference.get // will be null if we were gc-ed - } - finally rlock.unlock - } - def updateCache(): V = { - wlock.lock - try { - val res = cached() - if (res != null) res - else { - // If we don't remove the old String key from the map, we can - // wind up with one String as the key and a different String as - // the name field in the Symbol, which can lead to surprising GC - // behavior and duplicate Symbols. See scala/bug#6706. - map remove name - val sym = valueFromKey(name) - map.put(name, new WeakReference(sym)) - sym - } - } - finally wlock.unlock - } - - val res = cached() - if (res == null) updateCache() - else res - } - def unapply(other: V): Option[K] = keyFromValue(other) -} -*/ diff --git a/scalalib/overrides-2.13.2/scala/Symbol.scala b/scalalib/overrides-2.13.2/scala/Symbol.scala deleted file mode 100644 index 0000b5fe6f..0000000000 --- a/scalalib/overrides-2.13.2/scala/Symbol.scala +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Scala (https://www.scala-lang.org) - * - * Copyright EPFL and Lightbend, Inc. - * - * Licensed under Apache License 2.0 - * (http://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -package scala - -import scala.scalajs.js - -/** This class provides a simple way to get unique objects for equal strings. - * Since symbols are interned, they can be compared using reference equality. - * Instances of `Symbol` can be created easily with Scala's built-in quote - * mechanism. - * - * For instance, the Scala term `'mysym` will - * invoke the constructor of the `Symbol` class in the following way: - * `Symbol("mysym")`. - * - * @author Martin Odersky, Iulian Dragos - * @since 1.7 - */ -final class Symbol private (val name: String) extends Serializable { - /** Converts this symbol to a string. - */ - override def toString(): String = "'" + name - - @throws(classOf[java.io.ObjectStreamException]) - private def readResolve(): Any = Symbol.apply(name) - override def hashCode = name.hashCode() - override def equals(other: Any) = this eq other.asInstanceOf[AnyRef] -} - -// Modified to use Scala.js specific cache -object Symbol extends JSUniquenessCache[Symbol] { - override def apply(name: String): Symbol = super.apply(name) - protected def valueFromKey(name: String): Symbol = new Symbol(name) - protected def keyFromValue(sym: Symbol): Option[String] = Some(sym.name) -} - -private[scala] abstract class JSUniquenessCache[V] -{ - private val cache = js.Dictionary.empty[V] - - protected def valueFromKey(k: String): V - protected def keyFromValue(v: V): Option[String] - - def apply(name: String): V = - cache.getOrElseUpdate(name, valueFromKey(name)) - - def unapply(other: V): Option[String] = keyFromValue(other) -} - -/** This is private so it won't appear in the library API, but - * abstracted to offer some hope of reusability. */ -/* DELETED for Scala.js -private[scala] abstract class UniquenessCache[K >: js.String, V >: Null] -{ - - import java.lang.ref.WeakReference - import java.util.WeakHashMap - import java.util.concurrent.locks.ReentrantReadWriteLock - - private[this] val rwl = new ReentrantReadWriteLock() - private[this] val rlock = rwl.readLock - private[this] val wlock = rwl.writeLock - private[this] val map = new WeakHashMap[K, WeakReference[V]] - - protected def valueFromKey(k: K): V - protected def keyFromValue(v: V): Option[K] - - def apply(name: K): V = { - def cached(): V = { - rlock.lock - try { - val reference = map get name - if (reference == null) null - else reference.get // will be null if we were gc-ed - } - finally rlock.unlock - } - def updateCache(): V = { - wlock.lock - try { - val res = cached() - if (res != null) res - else { - // If we don't remove the old String key from the map, we can - // wind up with one String as the key and a different String as - // the name field in the Symbol, which can lead to surprising GC - // behavior and duplicate Symbols. See scala/bug#6706. - map remove name - val sym = valueFromKey(name) - map.put(name, new WeakReference(sym)) - sym - } - } - finally wlock.unlock - } - - val res = cached() - if (res == null) updateCache() - else res - } - def unapply(other: V): Option[K] = keyFromValue(other) -} -*/ diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/scalalib/SymbolTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/scalalib/SymbolTest.scala index 15ecdef4b0..bc6ad8f53a 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/scalalib/SymbolTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/scalalib/SymbolTest.scala @@ -56,14 +56,7 @@ class SymbolTest { @Test def testToString(): Unit = { val scalajs = Symbol("ScalaJS") - val toStringUsesQuoteSyntax = { - scalaVersion.startsWith("2.12.") || - scalaVersion == "2.13.0" || - scalaVersion == "2.13.1" || - scalaVersion == "2.13.2" - } - - if (toStringUsesQuoteSyntax) { + if (scalaVersion.startsWith("2.12.")) { assertEquals("'ScalaJS", scalajs.toString) assertEquals("'$", Symbol("$").toString) assertEquals("'$$", Symbol("$$").toString) From 40d68469bb85a0133beaf16ef7d836cbc03b0945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 5 Jan 2025 22:24:14 +0100 Subject: [PATCH 059/121] Version 1.18.0. --- ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index 37521f5065..5d96f087bb 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,8 +17,8 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.18.0-SNAPSHOT", - binaryEmitted = "1.18-SNAPSHOT" + current = "1.18.0", + binaryEmitted = "1.18" ) /** Helper class to allow for testing of logic. */ From 5eb1546d574301094f23deb24538687c182c888c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 6 Jan 2025 15:49:14 +0100 Subject: [PATCH 060/121] Towards 1.18.1. --- .../org/scalajs/ir/ScalaJSVersions.scala | 2 +- project/BinaryIncompatibilities.scala | 47 ------------------- project/Build.scala | 2 +- 3 files changed, 2 insertions(+), 49 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index 5d96f087bb..b30026fb8f 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.18.0", + current = "1.18.1-SNAPSHOT", binaryEmitted = "1.18" ) diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index c4530e7467..4713fe6bf8 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -5,56 +5,9 @@ import com.typesafe.tools.mima.core.ProblemFilters._ object BinaryIncompatibilities { val IR = Seq( - // !!! Breaking, OK in minor release - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Printers#IRTreePrinter.print"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$ArrayLength"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$ArrayLength$"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$Clone"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$Clone$"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$GetClass"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$GetClass$"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$IdentityHashCode"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$IdentityHashCode$"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$JSLinkingInfo"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$JSLinkingInfo$"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$LabelIdent"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$LabelIdent$"), - ProblemFilters.exclude[Problem]("org.scalajs.ir.Trees#Labeled.*"), - ProblemFilters.exclude[Problem]("org.scalajs.ir.Trees#Return.*"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$This"), - ProblemFilters.exclude[MissingTypesProblem]("org.scalajs.ir.Trees$This$"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.ir.Trees#This.apply"), - ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.scalajs.ir.Trees#This.unapply"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$Throw"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$Throw$"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$UnwrapFromThrowable"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$UnwrapFromThrowable$"), - ProblemFilters.exclude[Problem]("org.scalajs.ir.Trees#VarRef.*"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$WrapAsThrowable"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$WrapAsThrowable$"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.ir.Trees#*.tpe"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Types$NoType$"), - - // !!! Breaking, OK in minor release - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Transformers#Transformer.transform"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Transformers#Transformer.transformExpr"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Transformers#Transformer.transformExprOrJSSpread"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Transformers#Transformer.transformStat"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Transformers#Transformer.transformStats"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Trees#Transient#Value.transform"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("org.scalajs.ir.Trees#Transient#Value.transform"), - - // private, not an issue - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Serializers#Deserializer.readLabelIdent"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Serializers#Serializer.writeLabelIdent"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Hashers#TreeHasher.mixLabelIdent"), ) val Linker = Seq( - // !!! Breaking, OK in minor release - exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.ModuleSet.coreSpec"), - exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.ModuleSet.this"), - exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.LinkerBackend.verifyModuleSet"), ) val LinkerInterface = Seq( diff --git a/project/Build.scala b/project/Build.scala index 9eb18bbd1e..e453908f9f 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -394,7 +394,7 @@ object Build { val previousVersions = List("1.0.0", "1.0.1", "1.1.0", "1.1.1", "1.2.0", "1.3.0", "1.3.1", "1.4.0", "1.5.0", "1.5.1", "1.6.0", "1.7.0", "1.7.1", "1.8.0", "1.9.0", "1.10.0", "1.10.1", "1.11.0", "1.12.0", "1.13.0", - "1.13.1", "1.13.2", "1.14.0", "1.15.0", "1.16.0", "1.17.0") + "1.13.1", "1.13.2", "1.14.0", "1.15.0", "1.16.0", "1.17.0", "1.18.0") val previousVersion = previousVersions.last val previousBinaryCrossVersion = CrossVersion.binaryWith("sjs1_", "") From f0c74cb450df190a1b3a0ef41dc489cae35cd3fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 7 Jan 2025 11:26:46 +0100 Subject: [PATCH 061/121] Use `CheckNotNull` in the `Throw` deserialization hack. Back in Scala.js 1.11, we did not have `CheckNotNull` as a separate node. The deserialization hack was therefore pretty complicated, but all it wanted to do was check for null values. Unfortunately we broke that deserialization hack in Scala.js 1.18.0 because `UnwrapFromThrowable` now demands a non-nullable argument. So we have to insert an explicit `CheckNotNull`. It turns out we can dramatically simplify the whole hack with nothing but `CheckNotNull`. --- .../scala/org/scalajs/ir/Serializers.scala | 71 +++++-------------- .../scalajs/linker/BackwardsCompatTest.scala | 34 +++++++++ 2 files changed, 51 insertions(+), 54 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index 74e7d2c1aa..c1d082c9f5 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -1424,69 +1424,32 @@ object Serializers { * so that `Throw(e)` only ever throws the value of `e`, while the NPE UB * is specified by `UnwrapFromThrowable`. Among other things, this allows * the user-space code `js.special.throw(e)` to indiscriminately throw `e` - * even if it is `null`. + * even if it is `null`. Later, in Scala.js 1.18, we further separated the + * null check of `UnwrapFromThrowable` to be an explicit `CheckNotNull`. * - * With this hack, we patch `Throw(e)` where `e` is a nullable `Throwable` - * by inserting an appropriate `UnwrapFromThrowable`. + * With this hack, we patch `Throw(e)` by inserting an appropriate + * `CheckNotNull`. * - * Naively, we would just return `UnwrapFromThrowable(e)`. Unfortunately, - * we cannot prove that this is type-correct when the type of `e` is a - * `ClassType(cls)`, as we cannot test whether `cls` is a subclass of - * `java.lang.Throwable`. So we have to produce the following instead: - * - * {{{ - * if (expr === null) unwrapFromThrowable(null) else expr - * }}} - * - * except that evaluates `expr` twice. If it is a `VarRef`, which is a - * common case, that is fine. Otherwise, we have to wrap this pattern in - * an IIFE. - * - * We also have to avoid the transformation altogether when the `expr` is - * an `AnyType`. This happens when the previous Scala.js compiler already - * provides the unwrapped exception, which is either + * However, we must not do that when the previous Scala.js compiler + * already provides the *unwrapped* exception. This happened in two + * situations: * * - when automatically re-throwing an unhandled exception at the end of a * `try..catch`, or * - when throwing a maybe-JavaScriptException, with an explicit call to * `runtime.package$.unwrapJavaScriptException(x)`. + * + * Fortunately, in both situations, the type of the `expr` is always + * `AnyType`. We can accurately use that test to know whether we need to + * apply the patch. */ private def throwArgumentHack8(expr: Tree)(implicit pos: Position): Tree = { - def unwrapFromThrowable(t: Tree): Tree = - UnaryOp(UnaryOp.UnwrapFromThrowable, t) - - expr.tpe match { - case NullType => - // Evaluate the expression then definitely run into an NPE UB - unwrapFromThrowable(expr) - - case ClassType(_, _) => - expr match { - case New(_, _, _) => - // Common case (`throw new SomeException(...)`) that is known not to be `null` - expr - case VarRef(_) => - /* Common case (explicit re-throw of the form `throw th`) where we don't need the IIFE. - * if (expr === null) unwrapFromThrowable(null) else expr - */ - If(BinaryOp(BinaryOp.===, expr, Null()), unwrapFromThrowable(Null()), expr)(AnyType) - case _ => - /* General case where we need to avoid evaluating `expr` twice. - * ((x) => if (x === null) unwrapFromThrowable(null) else x)(expr) - */ - val x = LocalIdent(LocalName("x")) - val xParamDef = ParamDef(x, OriginalName.NoOriginalName, AnyType, mutable = false) - val xRef = xParamDef.ref - val closure = Closure(arrow = true, Nil, List(xParamDef), None, { - If(BinaryOp(BinaryOp.===, xRef, Null()), unwrapFromThrowable(Null()), xRef)(AnyType) - }, Nil) - JSFunctionApply(closure, List(expr)) - } - - case _ => - // Do not transform expressions of other types, in particular `AnyType` - expr - } + if (expr.tpe == AnyType) + expr + else if (!expr.tpe.isNullable) + expr // no CheckNotNull needed; common case because of `throw new ...` + else + UnaryOp(UnaryOp.CheckNotNull, expr) } def readTrees(): List[Tree] = diff --git a/linker/shared/src/test/scala/org/scalajs/linker/BackwardsCompatTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/BackwardsCompatTest.scala index 54a1a0f105..c1e33411d9 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/BackwardsCompatTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/BackwardsCompatTest.scala @@ -75,6 +75,40 @@ class BackwardsCompatTest { test(classDefs, MainTestModuleInitializers) } + @Test + def testThrowHackWithVariable_Issue5107(): AsyncResult = await { + val Base64Class = ClassName("java.util.Base64") + val DecoderClass = ClassName("java.util.Base64$Decoder") + val ByteBufferClass = ClassName("java.nio.ByteBuffer") + + val DecoderTypeRef = ClassRef(DecoderClass) + val ByteBufferTypeRef = ClassRef(ByteBufferClass) + val AB = ArrayTypeRef(ByteRef, 1) + + val DecoderType = ClassType(DecoderClass, nullable = true) + val ByteBufferType = ClassType(ByteBufferClass, nullable = true) + + /* java.util.Base64.getDecoder().decode(java.nio.ByteBuffer.wrap(Array(65, 81, 73, 61))) + * That is the only method I found in our javalib that contains a `throw e`, + * as opposed to a `throw new ...`. + */ + val classDefs = Seq( + mainTestClassDef(systemOutPrintln { + Apply( + EAF, + ApplyStatic(EAF, Base64Class, m("getDecoder", Nil, DecoderTypeRef), Nil)(DecoderType), + m("decode", List(ByteBufferTypeRef), ByteBufferTypeRef), + List( + ApplyStatic(EAF, ByteBufferClass, m("wrap", List(AB), ByteBufferTypeRef), + List(ArrayValue(AB, List[Byte](65, 81, 73, 61).map(ByteLiteral(_)))))(ByteBufferType) + ) + )(ByteBufferType) + }) + ) + + test(classDefs, MainTestModuleInitializers) + } + private def test(classDefs: Seq[ClassDef], moduleInitializers: Seq[ModuleInitializer]): Future[_] = { val classDefFiles = classDefs.map(MemClassDefIRFile(_)) From 0b94e0386d8d5dfb2086a39155ebd175739ed673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 7 Jan 2025 12:36:18 +0100 Subject: [PATCH 062/121] Fix missing `CheckNotNull`s in the deserialization hacks of `newInstance`. --- .../scala/org/scalajs/ir/Serializers.scala | 4 +-- .../scalajs/linker/BackwardsCompatTest.scala | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index c1d082c9f5..bf50ef07fc 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -1638,10 +1638,10 @@ object Serializers { } def arrayLength(t: Tree)(implicit pos: Position): Tree = - UnaryOp(UnaryOp.Array_length, t) + UnaryOp(UnaryOp.Array_length, UnaryOp(UnaryOp.CheckNotNull, t)) def getClass(t: Tree)(implicit pos: Position): Tree = - UnaryOp(UnaryOp.GetClass, t) + UnaryOp(UnaryOp.GetClass, UnaryOp(UnaryOp.CheckNotNull, t)) val jlClassRef = ClassRef(ClassClass) val intArrayTypeRef = ArrayTypeRef(IntRef, 1) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/BackwardsCompatTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/BackwardsCompatTest.scala index c1e33411d9..3439a80251 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/BackwardsCompatTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/BackwardsCompatTest.scala @@ -109,6 +109,33 @@ class BackwardsCompatTest { test(classDefs, MainTestModuleInitializers) } + @Test + def testArrayNewInstanceHacks_Issue5107(): AsyncResult = await { + val ReflectArrayClass = ClassName("java.lang.reflect.Array") + + val ClassClassRef = ClassRef(ClassClass) + + val AI = ArrayTypeRef(IntRef, 1) + + /* jlr.Array.newInstance(classOf[String], 5) + * jlr.Array.newInstance(classOf[String], Array(5, 4)) + */ + val classDefs = Seq( + mainTestClassDef(Block( + systemOutPrintln( + ApplyStatic(EAF, ReflectArrayClass, m("newInstance", List(ClassClassRef, I), O), + List(ClassOf(T), int(5)))(AnyType) + ), + systemOutPrintln( + ApplyStatic(EAF, ReflectArrayClass, m("newInstance", List(ClassClassRef, AI), O), + List(ClassOf(T), ArrayValue(AI, List(int(5), int(4)))))(AnyType) + ) + )) + ) + + test(classDefs, MainTestModuleInitializers) + } + private def test(classDefs: Seq[ClassDef], moduleInitializers: Seq[ModuleInitializer]): Future[_] = { val classDefFiles = classDefs.map(MemClassDefIRFile(_)) From 0fdaff514b09eca6ea559fb1ed62668c76fbfd5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 8 Jan 2025 09:13:53 +0100 Subject: [PATCH 063/121] Version 1.18.1. --- ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index b30026fb8f..dc545b120f 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.18.1-SNAPSHOT", + current = "1.18.1", binaryEmitted = "1.18" ) From 2cdb10b08a123e8f0097b89915d318e5d80aa84e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 8 Jan 2025 17:47:01 +0100 Subject: [PATCH 064/121] Towards 1.18.2. --- ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala | 2 +- project/Build.scala | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index dc545b120f..581ed885cd 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.18.1", + current = "1.18.2-SNAPSHOT", binaryEmitted = "1.18" ) diff --git a/project/Build.scala b/project/Build.scala index e453908f9f..8e02322bfe 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -394,7 +394,8 @@ object Build { val previousVersions = List("1.0.0", "1.0.1", "1.1.0", "1.1.1", "1.2.0", "1.3.0", "1.3.1", "1.4.0", "1.5.0", "1.5.1", "1.6.0", "1.7.0", "1.7.1", "1.8.0", "1.9.0", "1.10.0", "1.10.1", "1.11.0", "1.12.0", "1.13.0", - "1.13.1", "1.13.2", "1.14.0", "1.15.0", "1.16.0", "1.17.0", "1.18.0") + "1.13.1", "1.13.2", "1.14.0", "1.15.0", "1.16.0", "1.17.0", "1.18.0", + "1.18.1") val previousVersion = previousVersions.last val previousBinaryCrossVersion = CrossVersion.binaryWith("sjs1_", "") From 4f96d2c336609834f7dc057197cc5b5ae12e375b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 12 Jan 2025 13:03:02 +0100 Subject: [PATCH 065/121] Upgrade to Scala 2.12.20. --- Jenkinsfile | 5 +- .../{2.12.19 => 2.12.20}/BlacklistedTests.txt | 1 + .../{2.12.19 => 2.12.20}/neg/choices.check | 0 .../neg/partestInvalidFlag.check | 0 .../{2.12.19 => 2.12.20}/neg/t11952b.check | 0 .../neg/t6446-additional.check | 0 .../{2.12.19 => 2.12.20}/neg/t6446-list.check | 0 .../neg/t6446-missing.check | 0 .../neg/t6446-show-phases.check | 0 .../neg/t7494-no-options.check | 0 .../run/Course-2002-01.check | 0 .../run/Course-2002-02.check | 0 .../run/Course-2002-04.check | 0 .../run/Course-2002-08.check | 0 .../run/Course-2002-09.check | 0 .../run/Course-2002-10.check | 0 .../{2.12.19 => 2.12.20}/run/Meter.check | 0 .../run/MeterCaseClass.check | 0 .../run/anyval-box-types.check | 0 .../scalajs/{2.12.19 => 2.12.20}/run/bugs.sem | 0 .../run/caseClassHash.check | 0 .../{2.12.19 => 2.12.20}/run/classof.check | 0 .../{2.12.19 => 2.12.20}/run/deeps.check | 0 .../run/dynamic-anyval.check | 0 .../{2.12.19 => 2.12.20}/run/exceptions-2.sem | 0 .../run/exceptions-nest.check | 0 .../run/exceptions-nest.sem | 0 .../run/impconvtimes.check | 0 .../{2.12.19 => 2.12.20}/run/imports.check | 0 .../run/inlineHandlers.sem | 0 .../run/interpolation.check | 0 .../run/interpolationMultiline1.check | 0 .../run/macro-bundle-static.check | 0 .../run/macro-bundle-toplevel.check | 0 .../run/macro-bundle-whitebox-decl.check | 0 .../{2.12.19 => 2.12.20}/run/misc.check | 0 .../run/optimizer-array-load.sem | 0 .../{2.12.19 => 2.12.20}/run/pf-catch.sem | 0 .../{2.12.19 => 2.12.20}/run/promotion.check | 0 .../{2.12.19 => 2.12.20}/run/runtime.check | 0 .../{2.12.19 => 2.12.20}/run/spec-self.check | 0 .../{2.12.19 => 2.12.20}/run/structural.check | 0 .../{2.12.19 => 2.12.20}/run/t0421-new.check | 0 .../{2.12.19 => 2.12.20}/run/t0421-old.check | 0 .../{2.12.19 => 2.12.20}/run/t1503.sem | 0 .../{2.12.19 => 2.12.20}/run/t3702.check | 0 .../{2.12.19 => 2.12.20}/run/t4148.sem | 0 .../{2.12.19 => 2.12.20}/run/t4617.check | 0 .../{2.12.19 => 2.12.20}/run/t5356.check | 0 .../{2.12.19 => 2.12.20}/run/t5552.check | 0 .../{2.12.19 => 2.12.20}/run/t5568.check | 0 .../{2.12.19 => 2.12.20}/run/t5629b.check | 0 .../{2.12.19 => 2.12.20}/run/t5680.check | 0 .../{2.12.19 => 2.12.20}/run/t5866.check | 0 .../run/t6318_primitives.check | 0 .../{2.12.19 => 2.12.20}/run/t6662.check | 0 .../{2.12.19 => 2.12.20}/run/t6827.sem | 0 .../{2.12.19 => 2.12.20}/run/t7657.check | 0 .../{2.12.19 => 2.12.20}/run/t7763.sem | 0 .../{2.12.19 => 2.12.20}/run/t8570a.check | 0 .../{2.12.19 => 2.12.20}/run/t8601b.sem | 0 .../{2.12.19 => 2.12.20}/run/t8601c.sem | 0 .../{2.12.19 => 2.12.20}/run/t8601d.sem | 0 .../{2.12.19 => 2.12.20}/run/t8764.check | 0 .../{2.12.19 => 2.12.20}/run/t8925.sem | 0 .../{2.12.19 => 2.12.20}/run/t9387b.check | 0 .../{2.12.19 => 2.12.20}/run/t9656.check | 0 .../run/try-catch-unify.check | 0 .../run/virtpatmat_switch.check | 0 .../run/virtpatmat_typetag.check | 0 project/Build.scala | 1 + .../change-config-and-source/build.sbt | 2 +- .../incremental/change-config/build.sbt | 2 +- .../incremental/fix-compile-error/build.sbt | 2 +- .../linker/concurrent-linker-use/build.sbt | 2 +- .../sbt-test/linker/custom-linker/build.sbt | 4 +- .../no-root-dependency-resolution/build.sbt | 2 +- .../linker/non-existent-classpath/build.sbt | 2 +- .../sbt-test/settings/cross-version/build.sbt | 2 +- .../src/sbt-test/settings/env-vars/build.sbt | 2 +- .../settings/legacy-link-empty/build.sbt | 2 +- .../settings/legacy-link-tasks/build.sbt | 2 +- .../sbt-test/settings/module-init/build.sbt | 2 +- .../sbt-test/settings/source-map/build.sbt | 2 +- .../testing/multi-framework/build.sbt | 2 +- .../resources/2.12.20/BlacklistedTests.txt | 197 ++++++++++++++++++ 86 files changed, 217 insertions(+), 17 deletions(-) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/BlacklistedTests.txt (99%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/neg/choices.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/neg/partestInvalidFlag.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/neg/t11952b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/neg/t6446-additional.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/neg/t6446-list.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/neg/t6446-missing.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/neg/t6446-show-phases.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/neg/t7494-no-options.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/Course-2002-01.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/Course-2002-02.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/Course-2002-04.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/Course-2002-08.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/Course-2002-09.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/Course-2002-10.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/Meter.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/MeterCaseClass.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/anyval-box-types.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/bugs.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/caseClassHash.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/classof.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/deeps.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/dynamic-anyval.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/exceptions-2.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/exceptions-nest.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/exceptions-nest.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/impconvtimes.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/imports.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/inlineHandlers.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/interpolation.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/interpolationMultiline1.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/macro-bundle-static.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/macro-bundle-toplevel.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/macro-bundle-whitebox-decl.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/misc.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/optimizer-array-load.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/pf-catch.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/promotion.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/runtime.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/spec-self.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/structural.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t0421-new.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t0421-old.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t1503.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t3702.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t4148.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t4617.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t5356.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t5552.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t5568.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t5629b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t5680.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t5866.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t6318_primitives.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t6662.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t6827.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t7657.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t7763.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t8570a.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t8601b.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t8601c.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t8601d.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t8764.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t8925.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t9387b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/t9656.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/try-catch-unify.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/virtpatmat_switch.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.19 => 2.12.20}/run/virtpatmat_typetag.check (100%) create mode 100644 scala-test-suite/src/test/resources/2.12.20/BlacklistedTests.txt diff --git a/Jenkinsfile b/Jenkinsfile index 63e464571d..e9e1630b8b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -543,8 +543,8 @@ def otherJavaVersions = ["11", "17", "21"] def allJavaVersions = otherJavaVersions.clone() allJavaVersions << mainJavaVersion -def mainScalaVersion = "2.12.19" -def mainScalaVersions = ["2.12.19", "2.13.13"] +def mainScalaVersion = "2.12.20" +def mainScalaVersions = ["2.12.20", "2.13.13"] def otherScalaVersions = [ "2.12.6", "2.12.7", @@ -559,6 +559,7 @@ def otherScalaVersions = [ "2.12.16", "2.12.17", "2.12.18", + "2.12.19", "2.13.3", "2.13.4", "2.13.5", diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/BlacklistedTests.txt b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/BlacklistedTests.txt similarity index 99% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/BlacklistedTests.txt rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/BlacklistedTests.txt index 1bb8c9a08d..82db4eb5b7 100644 --- a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/BlacklistedTests.txt +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/BlacklistedTests.txt @@ -145,6 +145,7 @@ run/t10233.scala run/t10244.scala run/t10522.scala run/t11255 +run/t12774.scala run/transient-object.scala # Using System.getProperties diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/choices.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/neg/choices.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/choices.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/neg/choices.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/partestInvalidFlag.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/neg/partestInvalidFlag.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/partestInvalidFlag.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/neg/partestInvalidFlag.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t11952b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/neg/t11952b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t11952b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/neg/t11952b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t6446-additional.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/neg/t6446-additional.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t6446-additional.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/neg/t6446-additional.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t6446-list.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/neg/t6446-list.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t6446-list.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/neg/t6446-list.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t6446-missing.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/neg/t6446-missing.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t6446-missing.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/neg/t6446-missing.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t6446-show-phases.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/neg/t6446-show-phases.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t6446-show-phases.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/neg/t6446-show-phases.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t7494-no-options.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/neg/t7494-no-options.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/neg/t7494-no-options.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/neg/t7494-no-options.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-01.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/Course-2002-01.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-01.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/Course-2002-01.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-02.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/Course-2002-02.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-02.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/Course-2002-02.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-04.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/Course-2002-04.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-04.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/Course-2002-04.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-08.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/Course-2002-08.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-08.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/Course-2002-08.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-09.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/Course-2002-09.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-09.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/Course-2002-09.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-10.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/Course-2002-10.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Course-2002-10.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/Course-2002-10.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Meter.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/Meter.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/Meter.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/Meter.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/MeterCaseClass.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/MeterCaseClass.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/MeterCaseClass.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/MeterCaseClass.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/anyval-box-types.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/anyval-box-types.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/anyval-box-types.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/anyval-box-types.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/bugs.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/bugs.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/bugs.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/bugs.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/caseClassHash.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/caseClassHash.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/caseClassHash.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/caseClassHash.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/classof.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/classof.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/classof.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/classof.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/deeps.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/deeps.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/deeps.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/deeps.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/dynamic-anyval.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/dynamic-anyval.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/dynamic-anyval.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/dynamic-anyval.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/exceptions-2.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/exceptions-2.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/exceptions-2.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/exceptions-2.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/exceptions-nest.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/exceptions-nest.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/exceptions-nest.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/exceptions-nest.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/exceptions-nest.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/exceptions-nest.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/exceptions-nest.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/exceptions-nest.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/impconvtimes.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/impconvtimes.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/impconvtimes.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/impconvtimes.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/imports.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/imports.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/imports.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/imports.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/inlineHandlers.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/inlineHandlers.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/inlineHandlers.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/inlineHandlers.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/interpolation.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/interpolation.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/interpolation.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/interpolation.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/interpolationMultiline1.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/interpolationMultiline1.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/interpolationMultiline1.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/interpolationMultiline1.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/macro-bundle-static.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/macro-bundle-static.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/macro-bundle-static.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/macro-bundle-static.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/macro-bundle-toplevel.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/macro-bundle-toplevel.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/macro-bundle-toplevel.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/macro-bundle-toplevel.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/macro-bundle-whitebox-decl.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/macro-bundle-whitebox-decl.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/macro-bundle-whitebox-decl.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/macro-bundle-whitebox-decl.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/misc.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/misc.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/misc.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/misc.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/optimizer-array-load.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/optimizer-array-load.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/optimizer-array-load.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/optimizer-array-load.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/pf-catch.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/pf-catch.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/pf-catch.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/pf-catch.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/promotion.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/promotion.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/promotion.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/promotion.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/runtime.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/runtime.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/runtime.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/runtime.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/spec-self.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/spec-self.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/spec-self.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/spec-self.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/structural.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/structural.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/structural.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/structural.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t0421-new.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t0421-new.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t0421-new.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t0421-new.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t0421-old.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t0421-old.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t0421-old.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t0421-old.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t1503.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t1503.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t1503.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t1503.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t3702.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t3702.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t3702.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t3702.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t4148.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t4148.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t4148.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t4148.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t4617.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t4617.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t4617.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t4617.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5356.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t5356.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5356.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t5356.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5552.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t5552.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5552.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t5552.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5568.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t5568.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5568.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t5568.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5629b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t5629b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5629b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t5629b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5680.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t5680.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5680.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t5680.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5866.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t5866.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t5866.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t5866.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t6318_primitives.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t6318_primitives.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t6318_primitives.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t6318_primitives.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t6662.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t6662.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t6662.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t6662.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t6827.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t6827.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t6827.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t6827.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t7657.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t7657.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t7657.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t7657.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t7763.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t7763.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t7763.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t7763.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8570a.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t8570a.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8570a.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t8570a.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8601b.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t8601b.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8601b.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t8601b.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8601c.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t8601c.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8601c.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t8601c.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8601d.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t8601d.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8601d.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t8601d.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8764.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t8764.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8764.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t8764.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8925.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t8925.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t8925.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t8925.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t9387b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t9387b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t9387b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t9387b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t9656.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t9656.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/t9656.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/t9656.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/try-catch-unify.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/try-catch-unify.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/try-catch-unify.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/try-catch-unify.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/virtpatmat_switch.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/virtpatmat_switch.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/virtpatmat_switch.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/virtpatmat_switch.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/virtpatmat_typetag.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/virtpatmat_typetag.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.19/run/virtpatmat_typetag.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.20/run/virtpatmat_typetag.check diff --git a/project/Build.scala b/project/Build.scala index 8e02322bfe..561214147f 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -955,6 +955,7 @@ object Build { "2.12.17", "2.12.18", "2.12.19", + "2.12.20", ), cross213ScalaVersions := Seq( "2.13.3", diff --git a/sbt-plugin/src/sbt-test/incremental/change-config-and-source/build.sbt b/sbt-plugin/src/sbt-test/incremental/change-config-and-source/build.sbt index d0f231b6de..7f5bb2e802 100644 --- a/sbt-plugin/src/sbt-test/incremental/change-config-and-source/build.sbt +++ b/sbt-plugin/src/sbt-test/incremental/change-config-and-source/build.sbt @@ -1,4 +1,4 @@ -scalaVersion := "2.12.19" +scalaVersion := "2.12.20" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/incremental/change-config/build.sbt b/sbt-plugin/src/sbt-test/incremental/change-config/build.sbt index d0f231b6de..7f5bb2e802 100644 --- a/sbt-plugin/src/sbt-test/incremental/change-config/build.sbt +++ b/sbt-plugin/src/sbt-test/incremental/change-config/build.sbt @@ -1,4 +1,4 @@ -scalaVersion := "2.12.19" +scalaVersion := "2.12.20" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/incremental/fix-compile-error/build.sbt b/sbt-plugin/src/sbt-test/incremental/fix-compile-error/build.sbt index d0f231b6de..7f5bb2e802 100644 --- a/sbt-plugin/src/sbt-test/incremental/fix-compile-error/build.sbt +++ b/sbt-plugin/src/sbt-test/incremental/fix-compile-error/build.sbt @@ -1,4 +1,4 @@ -scalaVersion := "2.12.19" +scalaVersion := "2.12.20" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/linker/concurrent-linker-use/build.sbt b/sbt-plugin/src/sbt-test/linker/concurrent-linker-use/build.sbt index 0c84905ef6..dfa800157b 100644 --- a/sbt-plugin/src/sbt-test/linker/concurrent-linker-use/build.sbt +++ b/sbt-plugin/src/sbt-test/linker/concurrent-linker-use/build.sbt @@ -11,7 +11,7 @@ lazy val concurrentUseOfLinkerTest = taskKey[Any]("") name := "Scala.js sbt test" version := scalaJSVersion -scalaVersion := "2.12.19" +scalaVersion := "2.12.20" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/linker/custom-linker/build.sbt b/sbt-plugin/src/sbt-test/linker/custom-linker/build.sbt index 1d4b83961d..cff4d0dc5c 100644 --- a/sbt-plugin/src/sbt-test/linker/custom-linker/build.sbt +++ b/sbt-plugin/src/sbt-test/linker/custom-linker/build.sbt @@ -13,14 +13,14 @@ inThisBuild(Def.settings( version := scalaJSVersion, - scalaVersion := "2.12.19", + scalaVersion := "2.12.20", )) lazy val check = taskKey[Any]("") lazy val customLinker = project.in(file("custom-linker")) .settings( - scalaVersion := "2.12.19", // needs to match the minor version of Scala used by sbt + scalaVersion := "2.12.20", // needs to match the minor version of Scala used by sbt libraryDependencies += "org.scala-js" %% "scalajs-linker" % scalaJSVersion, ) diff --git a/sbt-plugin/src/sbt-test/linker/no-root-dependency-resolution/build.sbt b/sbt-plugin/src/sbt-test/linker/no-root-dependency-resolution/build.sbt index 2359461fa6..31a6169d0b 100644 --- a/sbt-plugin/src/sbt-test/linker/no-root-dependency-resolution/build.sbt +++ b/sbt-plugin/src/sbt-test/linker/no-root-dependency-resolution/build.sbt @@ -1,7 +1,7 @@ name := "Scala.js sbt test" version in ThisBuild := scalaJSVersion -scalaVersion in ThisBuild := "2.12.19" +scalaVersion in ThisBuild := "2.12.20" // Disable the IvyPlugin on the root project disablePlugins(sbt.plugins.IvyPlugin) diff --git a/sbt-plugin/src/sbt-test/linker/non-existent-classpath/build.sbt b/sbt-plugin/src/sbt-test/linker/non-existent-classpath/build.sbt index a3d81f1d03..3e470558f8 100644 --- a/sbt-plugin/src/sbt-test/linker/non-existent-classpath/build.sbt +++ b/sbt-plugin/src/sbt-test/linker/non-existent-classpath/build.sbt @@ -1,5 +1,5 @@ version := scalaJSVersion -scalaVersion := "2.12.19" +scalaVersion := "2.12.20" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/settings/cross-version/build.sbt b/sbt-plugin/src/sbt-test/settings/cross-version/build.sbt index bab1e57ecf..774305e752 100644 --- a/sbt-plugin/src/sbt-test/settings/cross-version/build.sbt +++ b/sbt-plugin/src/sbt-test/settings/cross-version/build.sbt @@ -3,7 +3,7 @@ import org.scalajs.sbtplugin.ScalaJSCrossVersion val check = taskKey[Unit]("Run checks of this test") version := scalaJSVersion -scalaVersion := "2.12.19" +scalaVersion := "2.12.20" lazy val js = project.enablePlugins(ScalaJSPlugin).settings( check := { diff --git a/sbt-plugin/src/sbt-test/settings/env-vars/build.sbt b/sbt-plugin/src/sbt-test/settings/env-vars/build.sbt index 83878dc189..c975c2ba68 100644 --- a/sbt-plugin/src/sbt-test/settings/env-vars/build.sbt +++ b/sbt-plugin/src/sbt-test/settings/env-vars/build.sbt @@ -1,5 +1,5 @@ inThisBuild(Def.settings( - scalaVersion := "2.12.19", + scalaVersion := "2.12.20", )) lazy val sharedSettings = Def.settings( diff --git a/sbt-plugin/src/sbt-test/settings/legacy-link-empty/build.sbt b/sbt-plugin/src/sbt-test/settings/legacy-link-empty/build.sbt index 4044def10a..dcd8e9e22b 100644 --- a/sbt-plugin/src/sbt-test/settings/legacy-link-empty/build.sbt +++ b/sbt-plugin/src/sbt-test/settings/legacy-link-empty/build.sbt @@ -1,4 +1,4 @@ version := scalaJSVersion -scalaVersion := "2.12.19" +scalaVersion := "2.12.20" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/settings/legacy-link-tasks/build.sbt b/sbt-plugin/src/sbt-test/settings/legacy-link-tasks/build.sbt index 001fe4b7ca..8058a8682e 100644 --- a/sbt-plugin/src/sbt-test/settings/legacy-link-tasks/build.sbt +++ b/sbt-plugin/src/sbt-test/settings/legacy-link-tasks/build.sbt @@ -1,7 +1,7 @@ val checkNoClosure = taskKey[Unit]("Check that fullOptJS wasn't run with closure") version := scalaJSVersion -scalaVersion := "2.12.19" +scalaVersion := "2.12.20" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/settings/module-init/build.sbt b/sbt-plugin/src/sbt-test/settings/module-init/build.sbt index b3cb9bef50..2835a2690f 100644 --- a/sbt-plugin/src/sbt-test/settings/module-init/build.sbt +++ b/sbt-plugin/src/sbt-test/settings/module-init/build.sbt @@ -3,7 +3,7 @@ import org.scalajs.linker.interface.ModuleInitializer val check = taskKey[Unit]("Run checks of this test") version := scalaJSVersion -scalaVersion := "2.12.19" +scalaVersion := "2.12.20" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/settings/source-map/build.sbt b/sbt-plugin/src/sbt-test/settings/source-map/build.sbt index fa3901cdcc..2fe6ae5b00 100644 --- a/sbt-plugin/src/sbt-test/settings/source-map/build.sbt +++ b/sbt-plugin/src/sbt-test/settings/source-map/build.sbt @@ -3,7 +3,7 @@ import org.scalajs.linker.interface.ModuleInitializer val check = taskKey[Unit]("Run checks of this test") version := scalaJSVersion -scalaVersion := "2.12.19" +scalaVersion := "2.12.20" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/testing/multi-framework/build.sbt b/sbt-plugin/src/sbt-test/testing/multi-framework/build.sbt index 5a8b4240c9..054279d188 100644 --- a/sbt-plugin/src/sbt-test/testing/multi-framework/build.sbt +++ b/sbt-plugin/src/sbt-test/testing/multi-framework/build.sbt @@ -1,5 +1,5 @@ inThisBuild(version := scalaJSVersion) -inThisBuild(scalaVersion := "2.12.19") +inThisBuild(scalaVersion := "2.12.20") lazy val root = project.in(file(".")). aggregate(multiTestJS, multiTestJVM) diff --git a/scala-test-suite/src/test/resources/2.12.20/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.20/BlacklistedTests.txt new file mode 100644 index 0000000000..6c78101e5b --- /dev/null +++ b/scala-test-suite/src/test/resources/2.12.20/BlacklistedTests.txt @@ -0,0 +1,197 @@ +## Do not compile +scala/lang/annotations/BytecodeTest.scala +scala/lang/annotations/RunTest.scala +scala/lang/traits/BytecodeTest.scala +scala/lang/traits/RunTest.scala +scala/lang/primitives/NaNTest.scala +scala/lang/primitives/BoxUnboxTest.scala +scala/collection/SeqTest.scala +scala/collection/Sizes.scala +scala/collection/immutable/HashMapTest.scala +scala/collection/immutable/HashSetTest.scala +scala/collection/immutable/ListMapTest.scala +scala/collection/immutable/MapHashcodeTest.scala +scala/collection/immutable/SetTest.scala +scala/collection/immutable/SeqTest.scala +scala/collection/immutable/SmallMapTest.scala +scala/collection/immutable/SortedMapTest.scala +scala/collection/immutable/SortedSetTest.scala +scala/collection/immutable/TreeMapTest.scala +scala/collection/immutable/TreeSetTest.scala +scala/reflect/ClassOfTest.scala +scala/reflect/QTest.scala +scala/reflect/io/AbstractFileTest.scala +scala/reflect/io/ZipArchiveTest.scala +scala/reflect/internal/util/AbstractFileClassLoaderTest.scala +scala/reflect/internal/util/FileUtilsTest.scala +scala/reflect/internal/util/SourceFileTest.scala +scala/reflect/internal/util/StringOpsTest.scala +scala/reflect/internal/util/WeakHashSetTest.scala +scala/reflect/internal/LongNamesTest.scala +scala/reflect/internal/MirrorsTest.scala +scala/reflect/internal/NamesTest.scala +scala/reflect/internal/PositionsTest.scala +scala/reflect/internal/PrintersTest.scala +scala/reflect/internal/ScopeTest.scala +scala/reflect/internal/TreeGenTest.scala +scala/reflect/internal/TypesTest.scala +scala/reflect/macros/AttachmentsTest.scala +scala/reflect/runtime/ReflectionUtilsShowTest.scala +scala/reflect/runtime/ThreadSafetyTest.scala +scala/runtime/BooleanBoxingTest.scala +scala/runtime/ByteBoxingTest.scala +scala/runtime/CharBoxingTest.scala +scala/runtime/DoubleBoxingTest.scala +scala/runtime/IntBoxingTest.scala +scala/runtime/FloatBoxingTest.scala +scala/runtime/LongBoxingTest.scala +scala/runtime/ShortBoxingTest.scala +scala/tools/cmd/CommandLineParserTest.scala +scala/tools/nsc/Build.scala +scala/tools/nsc/DeterminismTest.scala +scala/tools/nsc/DeterminismTester.scala +scala/tools/nsc/FileUtils.scala +scala/tools/nsc/GlobalCustomizeClassloaderTest.scala +scala/tools/nsc/PickleWriteTest.scala +scala/tools/nsc/PipelineMainTest.scala +scala/tools/nsc/async/AnnotationDrivenAsync.scala +scala/tools/nsc/async/CustomFuture.scala +scala/tools/nsc/backend/jvm/BTypesTest.scala +scala/tools/nsc/backend/jvm/BytecodeTest.scala +scala/tools/nsc/backend/jvm/ClassfileParserTest.scala +scala/tools/nsc/backend/jvm/DefaultMethodTest.scala +scala/tools/nsc/backend/jvm/DirectCompileTest.scala +scala/tools/nsc/backend/jvm/GenericSignaturesTest.scala +scala/tools/nsc/backend/jvm/IndyLambdaDirectTest.scala +scala/tools/nsc/backend/jvm/IndyLambdaTest.scala +scala/tools/nsc/backend/jvm/IndySammyTest.scala +scala/tools/nsc/backend/jvm/InnerClassAttributeTest.scala +scala/tools/nsc/backend/jvm/LineNumberTest.scala +scala/tools/nsc/backend/jvm/NestedClassesCollectorTest.scala +scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala +scala/tools/nsc/backend/jvm/PerRunInitTest.scala +scala/tools/nsc/backend/jvm/StringConcatTest.scala +scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala +scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala +scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala +scala/tools/nsc/backend/jvm/opt/BoxUnboxAndInlineTest.scala +scala/tools/nsc/backend/jvm/opt/BoxUnboxTest.scala +scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala +scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala +scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala +scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala +scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala +scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +scala/tools/nsc/backend/jvm/opt/InlineSourceMatcherTest.scala +scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala +scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala +scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala +scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala +scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala +scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala +scala/tools/nsc/ScriptRunnerTest.scala +scala/tools/nsc/classpath/AggregateClassPathTest.scala +scala/tools/nsc/classpath/JrtClassPathTest.scala +scala/tools/nsc/classpath/MultiReleaseJarTest.scala +scala/tools/nsc/classpath/PathResolverBaseTest.scala +scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala +scala/tools/nsc/classpath/ZipAndJarFileLookupFactoryTest.scala +scala/tools/nsc/doc/html/HtmlDocletTest.scala +scala/tools/nsc/interpreter/CompletionTest.scala +scala/tools/nsc/interpreter/ScriptedTest.scala +scala/tools/nsc/interpreter/TabulatorTest.scala +scala/tools/nsc/parser/ParserTest.scala +scala/tools/nsc/reporters/ConsoleReporterTest.scala +scala/tools/nsc/reporters/WConfTest.scala +scala/tools/nsc/settings/ScalaVersionTest.scala +scala/tools/nsc/settings/SettingsTest.scala +scala/tools/nsc/settings/TargetTest.scala +scala/tools/nsc/symtab/CannotHaveAttrsTest.scala +scala/tools/nsc/symtab/FlagsTest.scala +scala/tools/nsc/symtab/FreshNameExtractorTest.scala +scala/tools/nsc/symtab/StdNamesTest.scala +scala/tools/nsc/symtab/SymbolLoadersAssociatedFileTest.scala +scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala +scala/tools/nsc/symtab/SymbolTableTest.scala +scala/tools/nsc/symtab/classfile/PicklerTest.scala +scala/tools/nsc/transform/MixinTest.scala +scala/tools/nsc/transform/SpecializationTest.scala +scala/tools/nsc/transform/ThicketTransformerTest.scala +scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala +scala/tools/nsc/transform/patmat/SolvingTest.scala +scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala +scala/tools/nsc/typechecker/Implicits.scala +scala/tools/nsc/typechecker/NamerTest.scala +scala/tools/nsc/typechecker/ParamAliasTest.scala +scala/tools/nsc/typechecker/TreeAttachmentTest.scala +scala/tools/nsc/typechecker/TypedTreeTest.scala +scala/tools/nsc/util/StackTraceTest.scala +scala/tools/testing/AllocationTest.scala +scala/tools/testing/BytecodeTesting.scala +scala/tools/testing/JOL.scala +scala/tools/testing/RunTesting.scala +scala/tools/testing/VirtualCompilerTesting.scala +scala/util/matching/RegexTest.scala + +## Do not link +scala/MatchErrorSerializationTest.scala +scala/PartialFunctionSerializationTest.scala +scala/lang/stringinterpol/StringContextTest.scala +scala/collection/IteratorTest.scala +scala/collection/NewBuilderTest.scala +scala/collection/ParallelConsistencyTest.scala +scala/collection/SetMapRulesTest.scala +scala/collection/SeqViewTest.scala +scala/collection/SetMapConsistencyTest.scala +scala/collection/concurrent/TrieMapTest.scala +scala/collection/convert/WrapperSerializationTest.scala +scala/collection/immutable/ListTest.scala +scala/collection/immutable/RedBlackTreeSerialFormat.scala +scala/collection/immutable/StreamTest.scala +scala/collection/immutable/StringLikeTest.scala +scala/collection/immutable/VectorTest.scala +scala/collection/mutable/AnyRefMapTest.scala +scala/collection/mutable/ArrayBufferTest.scala +scala/collection/mutable/MutableListTest.scala +scala/collection/mutable/OpenHashMapTest.scala +scala/collection/mutable/PriorityQueueTest.scala +scala/collection/parallel/TaskTest.scala +scala/collection/parallel/immutable/ParRangeTest.scala +scala/concurrent/FutureTest.scala +scala/concurrent/duration/SerializationTest.scala +scala/concurrent/impl/DefaultPromiseTest.scala +scala/io/SourceTest.scala +scala/runtime/ScalaRunTimeTest.scala +scala/sys/process/PipedProcessTest.scala +scala/sys/process/ProcessTest.scala +scala/tools/testing/AssertUtilTest.scala +scala/tools/testing/AssertThrowsTest.scala +scala/util/SpecVersionTest.scala +scala/util/SystemPropertiesTest.scala + +## Tests fail + +# Reflection +scala/reflect/ClassTagTest.scala + +# Require strict-floats +scala/math/BigDecimalTest.scala + +# Difference of getClass() on primitive values +scala/collection/immutable/RangeTest.scala + +# Test fails only some times with +# 'set scalaJSOptimizerOptions in scalaTestSuite ~= (_.withDisableOptimizer(true))' +# and' 'set scalaJSUseRhino in Global := false' +scala/collection/immutable/PagedSeqTest.scala + +# Bugs +scala/collection/convert/MapWrapperTest.scala + +# Tests passed but are too slow (timeouts) +scala/collection/immutable/ListSetTest.scala +scala/util/SortingTest.scala From ff72b7f24a1aa2fd4cc1b8aa36af73b0606e5e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 12 Jan 2025 13:55:06 +0100 Subject: [PATCH 066/121] Upgrade to Scala 2.13.16, and add 2.13.14, 2.13.15 to the CI. --- Jenkinsfile | 7 +- .../{2.13.13 => 2.13.16}/BlacklistedTests.txt | 16 +- .../{2.13.13 => 2.13.16}/neg/choices.check | 0 .../neg/partestInvalidFlag.check | 0 .../{2.13.13 => 2.13.16}/neg/t11952b.check | 0 .../{2.13.13 => 2.13.16}/neg/t12494.check | 0 .../neg/t6446-additional.check | 0 .../{2.13.13 => 2.13.16}/neg/t6446-list.check | 0 .../neg/t6446-missing.check | 0 .../neg/t6446-show-phases.check | 0 .../neg/t7494-no-options.check | 0 .../scalajs/2.13.16/neg/t8755-regress-a.check | 33 +++ .../partest/scalajs/2.13.16/neg/t8755.check | 34 +++ .../partest/scalajs/2.13.16/neg/t8755b.check | 32 +++ .../partest/scalajs/2.13.16/neg/t8755c.check | 33 +++ .../run/Course-2002-01.check | 0 .../run/Course-2002-02.check | 0 .../run/Course-2002-04.check | 0 .../run/Course-2002-08.check | 0 .../run/Course-2002-09.check | 0 .../run/Course-2002-10.check | 0 .../{2.13.13 => 2.13.16}/run/Meter.check | 0 .../run/MeterCaseClass.check | 0 .../run/anyval-box-types.check | 0 .../scalajs/{2.13.13 => 2.13.16}/run/bugs.sem | 0 .../run/caseClassHash.check | 0 .../{2.13.13 => 2.13.16}/run/classof.check | 0 .../{2.13.13 => 2.13.16}/run/deeps.check | 0 .../run/dynamic-anyval.check | 0 .../{2.13.13 => 2.13.16}/run/exceptions-2.sem | 0 .../run/exceptions-nest.check | 0 .../run/exceptions-nest.sem | 0 .../run/impconvtimes.check | 0 .../{2.13.13 => 2.13.16}/run/imports.check | 0 .../run/inlineHandlers.sem | 0 .../run/interpolation.check | 0 .../run/interpolationMultiline1.check | 0 .../run/macro-bundle-static.check | 0 .../run/macro-bundle-toplevel.check | 0 .../run/macro-bundle-whitebox-decl.check | 0 ...expand-varargs-implicit-over-varargs.check | 0 .../{2.13.13 => 2.13.16}/run/misc.check | 0 .../run/optimizer-array-load.sem | 0 .../{2.13.13 => 2.13.16}/run/pf-catch.sem | 0 .../{2.13.13 => 2.13.16}/run/promotion.check | 0 .../{2.13.13 => 2.13.16}/run/runtime.check | 0 .../run/sammy_vararg_cbn.check | 0 .../{2.13.13 => 2.13.16}/run/spec-self.check | 0 .../run/string-switch.check | 0 .../{2.13.13 => 2.13.16}/run/structural.check | 0 .../{2.13.13 => 2.13.16}/run/t0421-new.check | 0 .../{2.13.13 => 2.13.16}/run/t0421-old.check | 0 .../{2.13.13 => 2.13.16}/run/t12221.check | 0 .../{2.13.13 => 2.13.16}/run/t1503.sem | 0 .../{2.13.13 => 2.13.16}/run/t3702.check | 0 .../{2.13.13 => 2.13.16}/run/t4148.sem | 0 .../{2.13.13 => 2.13.16}/run/t4617.check | 0 .../{2.13.13 => 2.13.16}/run/t5356.check | 0 .../{2.13.13 => 2.13.16}/run/t5552.check | 0 .../{2.13.13 => 2.13.16}/run/t5568.check | 0 .../{2.13.13 => 2.13.16}/run/t5629b.check | 0 .../{2.13.13 => 2.13.16}/run/t5680.check | 0 .../{2.13.13 => 2.13.16}/run/t5866.check | 0 .../{2.13.13 => 2.13.16}/run/t5966.check | 0 .../{2.13.13 => 2.13.16}/run/t6265.check | 0 .../run/t6318_primitives.check | 0 .../{2.13.13 => 2.13.16}/run/t6662.check | 0 .../{2.13.13 => 2.13.16}/run/t6827.sem | 0 .../{2.13.13 => 2.13.16}/run/t7657.check | 0 .../{2.13.13 => 2.13.16}/run/t7763.sem | 0 .../{2.13.13 => 2.13.16}/run/t8570a.check | 0 .../{2.13.13 => 2.13.16}/run/t8601b.sem | 0 .../{2.13.13 => 2.13.16}/run/t8601c.sem | 0 .../{2.13.13 => 2.13.16}/run/t8601d.sem | 0 .../{2.13.13 => 2.13.16}/run/t8764.check | 0 .../{2.13.13 => 2.13.16}/run/t8925.sem | 0 .../{2.13.13 => 2.13.16}/run/t9387b.check | 0 .../run/try-catch-unify.check | 0 .../run/virtpatmat_switch.check | 0 .../run/virtpatmat_typetag.check | 0 project/Build.scala | 5 +- .../src/sbt-test/cross-version/2.13/build.sbt | 2 +- .../sbt-test/scala3/tasty-reader/build.sbt | 2 +- .../resources/2.13.14/BlacklistedTests.txt | 247 +++++++++++++++++ .../resources/2.13.15/BlacklistedTests.txt | 249 +++++++++++++++++ .../resources/2.13.16/BlacklistedTests.txt | 250 ++++++++++++++++++ 86 files changed, 903 insertions(+), 7 deletions(-) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/BlacklistedTests.txt (99%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/neg/choices.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/neg/partestInvalidFlag.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/neg/t11952b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/neg/t12494.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/neg/t6446-additional.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/neg/t6446-list.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/neg/t6446-missing.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/neg/t6446-show-phases.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/neg/t7494-no-options.check (100%) create mode 100644 partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t8755-regress-a.check create mode 100644 partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t8755.check create mode 100644 partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t8755b.check create mode 100644 partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t8755c.check rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/Course-2002-01.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/Course-2002-02.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/Course-2002-04.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/Course-2002-08.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/Course-2002-09.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/Course-2002-10.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/Meter.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/MeterCaseClass.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/anyval-box-types.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/bugs.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/caseClassHash.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/classof.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/deeps.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/dynamic-anyval.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/exceptions-2.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/exceptions-nest.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/exceptions-nest.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/impconvtimes.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/imports.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/inlineHandlers.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/interpolation.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/interpolationMultiline1.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/macro-bundle-static.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/macro-bundle-toplevel.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/macro-bundle-whitebox-decl.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/macro-expand-varargs-implicit-over-varargs.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/misc.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/optimizer-array-load.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/pf-catch.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/promotion.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/runtime.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/sammy_vararg_cbn.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/spec-self.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/string-switch.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/structural.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t0421-new.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t0421-old.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t12221.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t1503.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t3702.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t4148.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t4617.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t5356.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t5552.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t5568.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t5629b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t5680.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t5866.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t5966.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t6265.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t6318_primitives.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t6662.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t6827.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t7657.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t7763.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t8570a.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t8601b.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t8601c.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t8601d.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t8764.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t8925.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/t9387b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/try-catch-unify.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/virtpatmat_switch.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.13 => 2.13.16}/run/virtpatmat_typetag.check (100%) create mode 100644 scala-test-suite/src/test/resources/2.13.14/BlacklistedTests.txt create mode 100644 scala-test-suite/src/test/resources/2.13.15/BlacklistedTests.txt create mode 100644 scala-test-suite/src/test/resources/2.13.16/BlacklistedTests.txt diff --git a/Jenkinsfile b/Jenkinsfile index e9e1630b8b..aa8a89111d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -544,7 +544,7 @@ def allJavaVersions = otherJavaVersions.clone() allJavaVersions << mainJavaVersion def mainScalaVersion = "2.12.20" -def mainScalaVersions = ["2.12.20", "2.13.13"] +def mainScalaVersions = ["2.12.20", "2.13.16"] def otherScalaVersions = [ "2.12.6", "2.12.7", @@ -569,7 +569,10 @@ def otherScalaVersions = [ "2.13.9", "2.13.10", "2.13.11", - "2.13.12" + "2.13.12", + "2.12.13", + "2.12.14", + "2.12.15" ] def scala3Version = "3.3.4" diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/BlacklistedTests.txt b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/BlacklistedTests.txt similarity index 99% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/BlacklistedTests.txt rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/BlacklistedTests.txt index 49e69dacc8..a468ddcaa1 100644 --- a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/BlacklistedTests.txt +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/BlacklistedTests.txt @@ -94,6 +94,7 @@ run/predef-cycle.scala run/synchronized.scala run/sd409.scala run/t12572.scala +run/deadlock.scala # Uses java.security run/t2318.scala @@ -783,7 +784,6 @@ run/t7319.scala run/t7482a.scala run/t7634.scala run/t7747-repl.scala -run/t7805-repl-i.scala run/tpeCache-tyconCache.scala run/repl-empty-package run/repl-javap-def.scala @@ -843,6 +843,9 @@ run/repl-release.scala run/eta-dependent.scala run/t10655.scala run/repl-suspended-warnings.scala +run/annot-infix-tostr.scala +run/substSymRefinementOwner.scala +run/t13050.scala # Using Scala Script (partest.ScriptTest) @@ -935,7 +938,6 @@ run/t9097.scala run/macroPlugins-enterStats.scala run/sbt-icode-interface.scala run/t8502b.scala -run/repl-paste-parse.scala run/t5463.scala run/t8433.scala run/sd275.scala @@ -975,6 +977,8 @@ run/debug-type-error.scala run/t12757.scala run/t12757b.scala run/t12757c.scala +run/huge-string.scala +run/t12720.scala # Using partest.StoreReporterDirectTest run/package-object-stale-decl.scala @@ -984,6 +988,9 @@ run/package-object-with-inner-class-in-ancestor-simpler.scala run/package-object-with-inner-class-in-ancestor-simpler-still.scala run/t7324.scala run/t10171 +run/t12289.scala +run/t12289b.scala +run/t12289c.scala # partest.StubErrorMessageTest run/StubErrorBInheritsFromA.scala @@ -1013,6 +1020,7 @@ run/t7974 run/t8601-closure-elim.scala run/t4788 run/t4788-separate-compilation +run/staticQualifier # partest.SessionTest run/t8843-repl-xlat.scala @@ -1022,6 +1030,8 @@ run/t8918-unary-ids.scala run/t1931.scala run/t8935-class.scala run/t8935-object.scala +run/t7722.scala +run/t7879.scala # partest.JavapTest run/t8608-no-format.scala @@ -1132,6 +1142,8 @@ run/t12380 run/t12523 run/t12290 run/t9714 +run/t13007 +run/t13307b # Using scala-script run/t7791-script-linenums.scala diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/choices.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/choices.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/choices.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/choices.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/partestInvalidFlag.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/partestInvalidFlag.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/partestInvalidFlag.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/partestInvalidFlag.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t11952b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t11952b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t11952b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t11952b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t12494.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t12494.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t12494.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t12494.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t6446-additional.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t6446-additional.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t6446-additional.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t6446-additional.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t6446-list.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t6446-list.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t6446-list.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t6446-list.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t6446-missing.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t6446-missing.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t6446-missing.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t6446-missing.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t6446-show-phases.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t6446-show-phases.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t6446-show-phases.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t6446-show-phases.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t7494-no-options.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t7494-no-options.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/neg/t7494-no-options.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t7494-no-options.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t8755-regress-a.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t8755-regress-a.check new file mode 100644 index 0000000000..4adf98bdd3 --- /dev/null +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t8755-regress-a.check @@ -0,0 +1,33 @@ + phase name id description + ---------- -- ----------- + parser 1 parse source into ASTs, perform simple desugaring + jspretyper 2 capture pre-typer only tree info (for Scala.js) + namer 3 resolve names, attach symbols to named trees +packageobjects 4 load package objects + typer 5 the meat and potatoes: type the trees + C8 0 C8 makes C7 reachable + jsinterop 7 prepare ASTs for JavaScript interop +superaccessors 8 add super accessors in traits and nested classes + C7 0 C7 has only a before constraint + extmethods 10 add extension methods for inline classes + pickler 11 serialize symbol tables + refchecks 12 reference/override checking, translate nested objects + patmat 13 translate match expressions +xplicitinnerjs 14 make references to inner JS classes explicit + uncurry 15 uncurry, translate function values to anonymous classes + fields 16 synthesize accessors and fields, add bitmaps for lazy vals + tailcalls 17 replace tail calls by jumps + specialize 18 @specialized-driven class and method specialization +xplicitlocaljs 19 make references to local JS classes explicit + explicitouter 20 this refs to outer pointers + erasure 21 erase types, add interfaces for traits + posterasure 22 clean up erased inline classes + lambdalift 23 move nested functions to top level + constructors 24 move field definitions into constructors + flatten 25 eliminate inner classes + mixin 26 mixin composition + jscode 27 generate JavaScript code from ASTs + cleanup 28 platform-specific cleanups, generate reflective calls + delambdafy 29 remove lambdas + jvm 30 generate JVM bytecode + terminal 31 the last phase during a compilation run diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t8755.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t8755.check new file mode 100644 index 0000000000..ed7312e737 --- /dev/null +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t8755.check @@ -0,0 +1,34 @@ +warning: No phase `refchicks` for ploogin.runsAfter - did you mean refchecks? +warning: No phase `java` for ploogin.runsBefore - did you mean jvm? +warning: Dropping phase ploogin, it is not reachable from parser + phase name id description + ---------- -- ----------- + parser 1 parse source into ASTs, perform simple desugaring + jspretyper 2 capture pre-typer only tree info (for Scala.js) + namer 3 resolve names, attach symbols to named trees +packageobjects 4 load package objects + typer 5 the meat and potatoes: type the trees + jsinterop 6 prepare ASTs for JavaScript interop +superaccessors 7 add super accessors in traits and nested classes + extmethods 8 add extension methods for inline classes + pickler 9 serialize symbol tables + refchecks 10 reference/override checking, translate nested objects + patmat 11 translate match expressions +xplicitinnerjs 12 make references to inner JS classes explicit + uncurry 13 uncurry, translate function values to anonymous classes + fields 14 synthesize accessors and fields, add bitmaps for lazy vals + tailcalls 15 replace tail calls by jumps + specialize 16 @specialized-driven class and method specialization +xplicitlocaljs 17 make references to local JS classes explicit + explicitouter 18 this refs to outer pointers + erasure 19 erase types, add interfaces for traits + posterasure 20 clean up erased inline classes + lambdalift 21 move nested functions to top level + constructors 22 move field definitions into constructors + flatten 23 eliminate inner classes + mixin 24 mixin composition + jscode 25 generate JavaScript code from ASTs + cleanup 26 platform-specific cleanups, generate reflective calls + delambdafy 27 remove lambdas + jvm 28 generate JVM bytecode + terminal 29 the last phase during a compilation run diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t8755b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t8755b.check new file mode 100644 index 0000000000..61518a437a --- /dev/null +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t8755b.check @@ -0,0 +1,32 @@ +warning: Dropping phase ploogin, it is not reachable from parser + phase name id description + ---------- -- ----------- + parser 1 parse source into ASTs, perform simple desugaring + jspretyper 2 capture pre-typer only tree info (for Scala.js) + namer 3 resolve names, attach symbols to named trees +packageobjects 4 load package objects + typer 5 the meat and potatoes: type the trees + jsinterop 6 prepare ASTs for JavaScript interop +superaccessors 7 add super accessors in traits and nested classes + extmethods 8 add extension methods for inline classes + pickler 9 serialize symbol tables + refchecks 10 reference/override checking, translate nested objects + patmat 11 translate match expressions +xplicitinnerjs 12 make references to inner JS classes explicit + uncurry 13 uncurry, translate function values to anonymous classes + fields 14 synthesize accessors and fields, add bitmaps for lazy vals + tailcalls 15 replace tail calls by jumps + specialize 16 @specialized-driven class and method specialization +xplicitlocaljs 17 make references to local JS classes explicit + explicitouter 18 this refs to outer pointers + erasure 19 erase types, add interfaces for traits + posterasure 20 clean up erased inline classes + lambdalift 21 move nested functions to top level + constructors 22 move field definitions into constructors + flatten 23 eliminate inner classes + mixin 24 mixin composition + jscode 25 generate JavaScript code from ASTs + cleanup 26 platform-specific cleanups, generate reflective calls + delambdafy 27 remove lambdas + jvm 28 generate JVM bytecode + terminal 29 the last phase during a compilation run diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t8755c.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t8755c.check new file mode 100644 index 0000000000..aedf56a035 --- /dev/null +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/neg/t8755c.check @@ -0,0 +1,33 @@ + phase name id description + ---------- -- ----------- + parser 1 parse source into ASTs, perform simple desugaring + jspretyper 2 capture pre-typer only tree info (for Scala.js) + namer 3 resolve names, attach symbols to named trees +packageobjects 4 load package objects + typer 5 the meat and potatoes: type the trees + C1 0 C1 tests phase assembly + jsinterop 7 prepare ASTs for JavaScript interop +superaccessors 8 add super accessors in traits and nested classes + extmethods 9 add extension methods for inline classes + pickler 10 serialize symbol tables + refchecks 11 reference/override checking, translate nested objects + patmat 12 translate match expressions +xplicitinnerjs 13 make references to inner JS classes explicit + C6 0 C6 tests phase assembly after a phase missing in Scaladoc + uncurry 15 uncurry, translate function values to anonymous classes + fields 16 synthesize accessors and fields, add bitmaps for lazy vals + tailcalls 17 replace tail calls by jumps + specialize 18 @specialized-driven class and method specialization +xplicitlocaljs 19 make references to local JS classes explicit + explicitouter 20 this refs to outer pointers + erasure 21 erase types, add interfaces for traits + posterasure 22 clean up erased inline classes + lambdalift 23 move nested functions to top level + constructors 24 move field definitions into constructors + flatten 25 eliminate inner classes + mixin 26 mixin composition + jscode 27 generate JavaScript code from ASTs + cleanup 28 platform-specific cleanups, generate reflective calls + delambdafy 29 remove lambdas + jvm 30 generate JVM bytecode + terminal 31 the last phase during a compilation run diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-01.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/Course-2002-01.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-01.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/Course-2002-01.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-02.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/Course-2002-02.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-02.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/Course-2002-02.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-04.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/Course-2002-04.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-04.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/Course-2002-04.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-08.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/Course-2002-08.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-08.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/Course-2002-08.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-09.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/Course-2002-09.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-09.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/Course-2002-09.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-10.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/Course-2002-10.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Course-2002-10.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/Course-2002-10.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Meter.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/Meter.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/Meter.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/Meter.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/MeterCaseClass.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/MeterCaseClass.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/MeterCaseClass.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/MeterCaseClass.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/anyval-box-types.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/anyval-box-types.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/anyval-box-types.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/anyval-box-types.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/bugs.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/bugs.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/bugs.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/bugs.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/caseClassHash.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/caseClassHash.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/caseClassHash.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/caseClassHash.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/classof.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/classof.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/classof.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/classof.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/deeps.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/deeps.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/deeps.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/deeps.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/dynamic-anyval.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/dynamic-anyval.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/dynamic-anyval.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/dynamic-anyval.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/exceptions-2.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/exceptions-2.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/exceptions-2.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/exceptions-2.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/exceptions-nest.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/exceptions-nest.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/exceptions-nest.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/exceptions-nest.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/exceptions-nest.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/exceptions-nest.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/exceptions-nest.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/exceptions-nest.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/impconvtimes.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/impconvtimes.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/impconvtimes.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/impconvtimes.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/imports.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/imports.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/imports.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/imports.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/inlineHandlers.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/inlineHandlers.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/inlineHandlers.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/inlineHandlers.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/interpolation.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/interpolation.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/interpolation.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/interpolation.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/interpolationMultiline1.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/interpolationMultiline1.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/interpolationMultiline1.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/interpolationMultiline1.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/macro-bundle-static.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/macro-bundle-static.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/macro-bundle-static.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/macro-bundle-static.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/macro-bundle-toplevel.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/macro-bundle-toplevel.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/macro-bundle-toplevel.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/macro-bundle-toplevel.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/macro-bundle-whitebox-decl.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/macro-bundle-whitebox-decl.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/macro-bundle-whitebox-decl.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/macro-bundle-whitebox-decl.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/macro-expand-varargs-implicit-over-varargs.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/macro-expand-varargs-implicit-over-varargs.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/macro-expand-varargs-implicit-over-varargs.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/macro-expand-varargs-implicit-over-varargs.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/misc.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/misc.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/misc.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/misc.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/optimizer-array-load.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/optimizer-array-load.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/optimizer-array-load.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/optimizer-array-load.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/pf-catch.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/pf-catch.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/pf-catch.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/pf-catch.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/promotion.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/promotion.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/promotion.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/promotion.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/runtime.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/runtime.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/runtime.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/runtime.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/sammy_vararg_cbn.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/sammy_vararg_cbn.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/sammy_vararg_cbn.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/sammy_vararg_cbn.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/spec-self.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/spec-self.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/spec-self.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/spec-self.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/string-switch.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/string-switch.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/string-switch.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/string-switch.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/structural.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/structural.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/structural.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/structural.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t0421-new.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t0421-new.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t0421-new.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t0421-new.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t0421-old.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t0421-old.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t0421-old.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t0421-old.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t12221.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t12221.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t12221.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t12221.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t1503.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t1503.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t1503.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t1503.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t3702.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t3702.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t3702.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t3702.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t4148.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t4148.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t4148.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t4148.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t4617.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t4617.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t4617.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t4617.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5356.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5356.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5356.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5356.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5552.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5552.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5552.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5552.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5568.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5568.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5568.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5568.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5629b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5629b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5629b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5629b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5680.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5680.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5680.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5680.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5866.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5866.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5866.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5866.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5966.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5966.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t5966.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t5966.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t6265.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t6265.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t6265.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t6265.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t6318_primitives.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t6318_primitives.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t6318_primitives.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t6318_primitives.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t6662.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t6662.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t6662.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t6662.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t6827.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t6827.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t6827.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t6827.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t7657.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t7657.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t7657.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t7657.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t7763.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t7763.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t7763.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t7763.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8570a.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t8570a.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8570a.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t8570a.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8601b.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t8601b.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8601b.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t8601b.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8601c.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t8601c.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8601c.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t8601c.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8601d.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t8601d.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8601d.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t8601d.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8764.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t8764.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8764.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t8764.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8925.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t8925.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t8925.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t8925.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t9387b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t9387b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/t9387b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/t9387b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/try-catch-unify.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/try-catch-unify.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/try-catch-unify.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/try-catch-unify.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/virtpatmat_switch.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/virtpatmat_switch.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/virtpatmat_switch.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/virtpatmat_switch.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/virtpatmat_typetag.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/virtpatmat_typetag.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.13/run/virtpatmat_typetag.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.16/run/virtpatmat_typetag.check diff --git a/project/Build.scala b/project/Build.scala index 561214147f..8dd80196c4 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -969,6 +969,9 @@ object Build { "2.13.11", "2.13.12", "2.13.13", + "2.13.14", + "2.13.15", + "2.13.16", ), default212ScalaVersion := cross212ScalaVersions.value.last, @@ -2064,7 +2067,7 @@ object Build { if (!useMinifySizes) { Some(ExpectedSizes( fastLink = 449000 to 450000, - fullLink = 94000 to 95000, + fullLink = 95000 to 96000, fastLinkGz = 58000 to 59000, fullLinkGz = 25000 to 26000, )) diff --git a/sbt-plugin/src/sbt-test/cross-version/2.13/build.sbt b/sbt-plugin/src/sbt-test/cross-version/2.13/build.sbt index 9eaf7236fc..82c5d1a728 100644 --- a/sbt-plugin/src/sbt-test/cross-version/2.13/build.sbt +++ b/sbt-plugin/src/sbt-test/cross-version/2.13/build.sbt @@ -2,6 +2,6 @@ enablePlugins(ScalaJSPlugin) enablePlugins(ScalaJSJUnitPlugin) version := scalaJSVersion -scalaVersion := "2.13.13" +scalaVersion := "2.13.16" scalaJSUseMainModuleInitializer := true diff --git a/sbt-plugin/src/sbt-test/scala3/tasty-reader/build.sbt b/sbt-plugin/src/sbt-test/scala3/tasty-reader/build.sbt index 683c749c5a..479044430c 100644 --- a/sbt-plugin/src/sbt-test/scala3/tasty-reader/build.sbt +++ b/sbt-plugin/src/sbt-test/scala3/tasty-reader/build.sbt @@ -10,7 +10,7 @@ lazy val app = project.in(file("app")) .enablePlugins(ScalaJSPlugin) .dependsOn(testlib) .settings( - scalaVersion := "2.13.13", + scalaVersion := "2.13.16", scalacOptions += "-Ytasty-reader", scalaJSUseMainModuleInitializer := true ) diff --git a/scala-test-suite/src/test/resources/2.13.14/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.14/BlacklistedTests.txt new file mode 100644 index 0000000000..c813883a16 --- /dev/null +++ b/scala-test-suite/src/test/resources/2.13.14/BlacklistedTests.txt @@ -0,0 +1,247 @@ +## Do not compile +scala/ExtractorTest.scala +scala/OptionTest.scala +scala/SerializationStabilityTest.scala +scala/StringTest.scala +scala/collection/FactoriesTest.scala +scala/collection/LazyZipOpsTest.scala +scala/collection/SeqTest.scala +scala/collection/immutable/HashMapTest.scala +scala/collection/immutable/HashSetTest.scala +scala/collection/immutable/IndexedSeqTest.scala +scala/collection/immutable/IntMapTest.scala +scala/collection/immutable/LazyListTest.scala +scala/collection/immutable/ListMapTest.scala +scala/collection/immutable/LongMapTest.scala +scala/collection/immutable/MapHashcodeTest.scala +scala/collection/immutable/SeqTest.scala +scala/collection/immutable/SmallMapTest.scala +scala/collection/immutable/SortedMapTest.scala +scala/collection/immutable/SortedSetTest.scala +scala/collection/immutable/TreeMapTest.scala +scala/collection/immutable/TreeSetTest.scala +scala/lang/annotations/BytecodeTest.scala +scala/lang/annotations/RunTest.scala +scala/lang/traits/BytecodeTest.scala +scala/lang/traits/RunTest.scala +scala/lang/primitives/NaNTest.scala +scala/reflect/ClassOfTest.scala +scala/reflect/FieldAccessTest.scala +scala/reflect/QTest.scala +scala/reflect/macros/AttachmentsTest.scala +scala/reflect/io/ZipArchiveTest.scala +scala/reflect/internal/InferTest.scala +scala/reflect/internal/LongNamesTest.scala +scala/reflect/internal/MirrorsTest.scala +scala/reflect/internal/NamesTest.scala +scala/reflect/internal/PositionsTest.scala +scala/reflect/internal/PrintersTest.scala +scala/reflect/internal/ScopeTest.scala +scala/reflect/internal/SubstMapTest.scala +scala/reflect/internal/TreeGenTest.scala +scala/reflect/internal/TypesTest.scala +scala/reflect/internal/util/AbstractFileClassLoaderTest.scala +scala/reflect/internal/util/FileUtilsTest.scala +scala/reflect/internal/util/SourceFileTest.scala +scala/reflect/internal/util/StringOpsTest.scala +scala/reflect/internal/util/WeakHashSetTest.scala +scala/reflect/io/AbstractFileTest.scala +scala/reflect/runtime/ReflectionUtilsShowTest.scala +scala/reflect/runtime/ThreadSafetyTest.scala +scala/runtime/BooleanBoxingTest.scala +scala/runtime/ByteBoxingTest.scala +scala/runtime/CharBoxingTest.scala +scala/runtime/DoubleBoxingTest.scala +scala/runtime/IntBoxingTest.scala +scala/runtime/FloatBoxingTest.scala +scala/runtime/LongBoxingTest.scala +scala/runtime/ShortBoxingTest.scala +scala/tools/nsc/Build.scala +scala/tools/nsc/DeterminismTest.scala +scala/tools/nsc/DeterminismTester.scala +scala/tools/nsc/FileUtils.scala +scala/tools/nsc/GlobalCustomizeClassloaderTest.scala +scala/tools/nsc/MainRunnerTest.scala +scala/tools/nsc/PhaseAssemblyTest.scala +scala/tools/nsc/PickleWriteTest.scala +scala/tools/nsc/PipelineMainTest.scala +scala/tools/nsc/ScriptRunnerTest.scala +scala/tools/nsc/async/AnnotationDrivenAsyncTest.scala +scala/tools/nsc/async/CustomFuture.scala +scala/tools/nsc/backend/jvm/BTypesTest.scala +scala/tools/nsc/backend/jvm/BytecodeTest.scala +scala/tools/nsc/backend/jvm/ClassfileParserTest.scala +scala/tools/nsc/backend/jvm/DefaultMethodTest.scala +scala/tools/nsc/backend/jvm/DirectCompileTest.scala +scala/tools/nsc/backend/jvm/GenericSignaturesTest.scala +scala/tools/nsc/backend/jvm/IndyLambdaTest.scala +scala/tools/nsc/backend/jvm/IndySammyTest.scala +scala/tools/nsc/backend/jvm/InnerClassAttributeTest.scala +scala/tools/nsc/backend/jvm/LineNumberTest.scala +scala/tools/nsc/backend/jvm/NestedClassesCollectorTest.scala +scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala +scala/tools/nsc/backend/jvm/PerRunInitTest.scala +scala/tools/nsc/backend/jvm/StringConcatTest.scala +scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala +scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala +scala/tools/nsc/backend/jvm/analysis/TypeFlowAnalyzerTest.scala +scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala +scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala +scala/tools/nsc/backend/jvm/opt/BoxUnboxAndInlineTest.scala +scala/tools/nsc/backend/jvm/opt/BoxUnboxTest.scala +scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala +scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala +scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala +scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala +scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +scala/tools/nsc/backend/jvm/opt/InlineSourceMatcherTest.scala +scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala +scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala +scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala +scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala +scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala +scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala +scala/tools/nsc/classpath/AggregateClassPathTest.scala +scala/tools/nsc/classpath/JrtClassPathTest.scala +scala/tools/nsc/classpath/MultiReleaseJarTest.scala +scala/tools/nsc/classpath/PathResolverBaseTest.scala +scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala +scala/tools/nsc/classpath/ZipAndJarFileLookupFactoryTest.scala +scala/tools/nsc/doc/html/HtmlDocletTest.scala +scala/tools/nsc/doc/html/ModelFactoryTest.scala +scala/tools/nsc/doc/html/StringLiteralTest.scala +scala/tools/nsc/interpreter/CompletionTest.scala +scala/tools/nsc/interpreter/ScriptedTest.scala +scala/tools/nsc/interpreter/TabulatorTest.scala +scala/tools/nsc/parser/ParserTest.scala +scala/tools/nsc/reporters/ConsoleReporterTest.scala +scala/tools/nsc/reporters/PositionFilterTest.scala +scala/tools/nsc/reporters/WConfTest.scala +scala/tools/nsc/reporters/AbstractCodeActionTest.scala +scala/tools/nsc/reporters/CodeActionXsource3Test.scala +scala/tools/nsc/reporters/CodeActionTest.scala +scala/tools/nsc/settings/ScalaVersionTest.scala +scala/tools/nsc/settings/SettingsTest.scala +scala/tools/nsc/settings/TargetTest.scala +scala/tools/nsc/symtab/CannotHaveAttrsTest.scala +scala/tools/nsc/symtab/FlagsTest.scala +scala/tools/nsc/symtab/FreshNameExtractorTest.scala +scala/tools/nsc/symtab/StdNamesTest.scala +scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala +scala/tools/nsc/symtab/SymbolTableTest.scala +scala/tools/nsc/symtab/classfile/PicklerTest.scala +scala/tools/nsc/transform/ErasureTest.scala +scala/tools/nsc/transform/MixinTest.scala +scala/tools/nsc/transform/ReleaseFenceTest.scala +scala/tools/nsc/transform/SpecializationTest.scala +scala/tools/nsc/transform/ThicketTransformerTest.scala +scala/tools/nsc/transform/UncurryTest.scala +scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala +scala/tools/nsc/transform/patmat/SolvingTest.scala +scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala +scala/tools/nsc/typechecker/ConstantFolderTest.scala +scala/tools/nsc/typechecker/ImplicitsTest.scala +scala/tools/nsc/typechecker/InferencerTest.scala +scala/tools/nsc/typechecker/NamerTest.scala +scala/tools/nsc/typechecker/OverridingPairsTest.scala +scala/tools/nsc/typechecker/ParamAliasTest.scala +scala/tools/nsc/typechecker/TreeAttachmentTest.scala +scala/tools/nsc/typechecker/TypedTreeTest.scala +scala/tools/nsc/QuickfixTest.scala +scala/tools/nsc/util/StackTraceTest.scala +scala/tools/testkit/ReflectUtilTest.scala +scala/tools/xsbt/BridgeTesting.scala +scala/tools/xsbt/BasicBridgeTest.scala +scala/tools/xsbt/ClassNameTest.scala +scala/tools/xsbt/CodeActionTest.scala +scala/tools/xsbt/DependencyTest.scala +scala/tools/xsbt/ExtractAPITest.scala +scala/tools/xsbt/ExtractUsedNamesTest.scala +scala/tools/xsbt/InteractiveConsoleInterfaceTest.scala +scala/tools/xsbt/SameAPI.scala +scala/tools/xsbt/TestCallback.scala +scala/util/ChainingOpsTest.scala + +## Do not link +scala/CollectTest.scala +scala/MatchErrorSerializationTest.scala +scala/PartialFunctionSerializationTest.scala +scala/lang/stringinterpol/StringContextTest.scala +scala/collection/IterableTest.scala +scala/collection/IteratorTest.scala +scala/collection/NewBuilderTest.scala +scala/collection/SeqViewTest.scala +scala/collection/SetMapConsistencyTest.scala +scala/collection/SetMapRulesTest.scala +scala/collection/Sizes.scala +scala/collection/ViewTest.scala +scala/collection/concurrent/TrieMapTest.scala +scala/collection/convert/EqualsTest.scala +scala/collection/convert/JConcurrentMapWrapperTest.scala +scala/collection/convert/WrapperSerializationTest.scala +scala/collection/immutable/ChampMapSmokeTest.scala +scala/collection/immutable/ChampSetSmokeTest.scala +scala/collection/immutable/LazyListGCTest.scala +scala/collection/immutable/LazyListLazinessTest.scala +scala/collection/immutable/ListTest.scala +scala/collection/immutable/SerializationTest.scala +scala/collection/immutable/StreamTest.scala +scala/collection/immutable/StringLikeTest.scala +scala/collection/immutable/VectorTest.scala +scala/collection/mutable/AnyRefMapTest.scala +scala/collection/mutable/ArrayBufferTest.scala +scala/collection/mutable/ListBufferTest.scala +scala/collection/mutable/OpenHashMapTest.scala +scala/collection/mutable/PriorityQueueTest.scala +scala/collection/mutable/SerializationTest.scala +scala/concurrent/FutureTest.scala +scala/concurrent/duration/SerializationTest.scala +scala/concurrent/impl/DefaultPromiseTest.scala +scala/io/SourceTest.scala +scala/jdk/AccumulatorTest.scala +scala/jdk/DurationConvertersTest.scala +scala/jdk/FunctionConvertersTest.scala +scala/jdk/OptionConvertersTest.scala +scala/jdk/StepperConversionTest.scala +scala/jdk/StepperTest.scala +scala/jdk/StreamConvertersTest.scala +scala/jdk/StreamConvertersTypingTest.scala +scala/math/OrderingTest.scala +scala/runtime/ScalaRunTimeTest.scala +scala/sys/env.scala +scala/sys/process/ParserTest.scala +scala/sys/process/PipedProcessTest.scala +scala/sys/process/ProcessBuilderTest.scala +scala/sys/process/ProcessTest.scala +scala/tools/testkit/AssertUtilTest.scala +scala/util/PropertiesTest.scala +scala/util/SpecVersionTest.scala +scala/util/SystemPropertiesTest.scala + +## Tests fail + +# Reflection +scala/reflect/ClassTagTest.scala + +# Require strict-floats +scala/math/BigDecimalTest.scala + +# Tests passed but are too slow (timeouts) +scala/collection/immutable/ListSetTest.scala +scala/util/SortingTest.scala + +# Whitebox testing of ArrayBuilder.ofUnit +scala/collection/mutable/ArrayBuilderTest.scala + +# Relies on undefined behavior +scala/collection/MapTest.scala +scala/collection/StringOpsTest.scala +scala/collection/StringParsersTest.scala +scala/collection/convert/CollectionConvertersTest.scala +scala/collection/convert/MapWrapperTest.scala +scala/collection/immutable/NumericRangeTest.scala +scala/math/BigIntTest.scala diff --git a/scala-test-suite/src/test/resources/2.13.15/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.15/BlacklistedTests.txt new file mode 100644 index 0000000000..00808d0865 --- /dev/null +++ b/scala-test-suite/src/test/resources/2.13.15/BlacklistedTests.txt @@ -0,0 +1,249 @@ +## Do not compile +scala/ExtractorTest.scala +scala/OptionTest.scala +scala/SerializationStabilityTest.scala +scala/StringTest.scala +scala/collection/FactoriesTest.scala +scala/collection/LazyZipOpsTest.scala +scala/collection/SeqTest.scala +scala/collection/immutable/HashMapTest.scala +scala/collection/immutable/HashSetTest.scala +scala/collection/immutable/IndexedSeqTest.scala +scala/collection/immutable/IntMapTest.scala +scala/collection/immutable/LazyListTest.scala +scala/collection/immutable/ListMapTest.scala +scala/collection/immutable/LongMapTest.scala +scala/collection/immutable/MapHashcodeTest.scala +scala/collection/immutable/SeqTest.scala +scala/collection/immutable/SmallMapTest.scala +scala/collection/immutable/SortedMapTest.scala +scala/collection/immutable/SortedSetTest.scala +scala/collection/immutable/TreeMapTest.scala +scala/collection/immutable/TreeSetTest.scala +scala/collection/mutable/LongMapTest.scala +scala/lang/annotations/BytecodeTest.scala +scala/lang/annotations/RunTest.scala +scala/lang/traits/BytecodeTest.scala +scala/lang/traits/RunTest.scala +scala/lang/primitives/NaNTest.scala +scala/reflect/ClassOfTest.scala +scala/reflect/FieldAccessTest.scala +scala/reflect/QTest.scala +scala/reflect/macros/AttachmentsTest.scala +scala/reflect/io/ZipArchiveTest.scala +scala/reflect/internal/InferTest.scala +scala/reflect/internal/LongNamesTest.scala +scala/reflect/internal/MirrorsTest.scala +scala/reflect/internal/NamesTest.scala +scala/reflect/internal/PositionsTest.scala +scala/reflect/internal/PrintersTest.scala +scala/reflect/internal/ScopeTest.scala +scala/reflect/internal/SubstMapTest.scala +scala/reflect/internal/TreeGenTest.scala +scala/reflect/internal/TypesTest.scala +scala/reflect/internal/util/AbstractFileClassLoaderTest.scala +scala/reflect/internal/util/FileUtilsTest.scala +scala/reflect/internal/util/SourceFileTest.scala +scala/reflect/internal/util/StringOpsTest.scala +scala/reflect/internal/util/WeakHashSetTest.scala +scala/reflect/io/AbstractFileTest.scala +scala/reflect/runtime/ReflectionUtilsShowTest.scala +scala/reflect/runtime/ThreadSafetyTest.scala +scala/runtime/BooleanBoxingTest.scala +scala/runtime/ByteBoxingTest.scala +scala/runtime/CharBoxingTest.scala +scala/runtime/DoubleBoxingTest.scala +scala/runtime/IntBoxingTest.scala +scala/runtime/FloatBoxingTest.scala +scala/runtime/LongBoxingTest.scala +scala/runtime/ShortBoxingTest.scala +scala/tools/nsc/Build.scala +scala/tools/nsc/DeterminismTest.scala +scala/tools/nsc/DeterminismTester.scala +scala/tools/nsc/FileUtils.scala +scala/tools/nsc/GlobalCustomizeClassloaderTest.scala +scala/tools/nsc/MainRunnerTest.scala +scala/tools/nsc/PhaseAssemblyTest.scala +scala/tools/nsc/PickleWriteTest.scala +scala/tools/nsc/PipelineMainTest.scala +scala/tools/nsc/ScriptRunnerTest.scala +scala/tools/nsc/async/AnnotationDrivenAsyncTest.scala +scala/tools/nsc/async/CustomFuture.scala +scala/tools/nsc/backend/jvm/BTypesTest.scala +scala/tools/nsc/backend/jvm/BytecodeTest.scala +scala/tools/nsc/backend/jvm/ClassfileParserTest.scala +scala/tools/nsc/backend/jvm/DefaultMethodTest.scala +scala/tools/nsc/backend/jvm/DirectCompileTest.scala +scala/tools/nsc/backend/jvm/GenericSignaturesTest.scala +scala/tools/nsc/backend/jvm/IndyLambdaTest.scala +scala/tools/nsc/backend/jvm/IndySammyTest.scala +scala/tools/nsc/backend/jvm/InnerClassAttributeTest.scala +scala/tools/nsc/backend/jvm/LineNumberTest.scala +scala/tools/nsc/backend/jvm/NestedClassesCollectorTest.scala +scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala +scala/tools/nsc/backend/jvm/PerRunInitTest.scala +scala/tools/nsc/backend/jvm/StringConcatTest.scala +scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala +scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala +scala/tools/nsc/backend/jvm/analysis/TypeFlowAnalyzerTest.scala +scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala +scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala +scala/tools/nsc/backend/jvm/opt/BoxUnboxAndInlineTest.scala +scala/tools/nsc/backend/jvm/opt/BoxUnboxTest.scala +scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala +scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala +scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala +scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala +scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +scala/tools/nsc/backend/jvm/opt/InlineSourceMatcherTest.scala +scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala +scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala +scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala +scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala +scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala +scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala +scala/tools/nsc/classpath/AggregateClassPathTest.scala +scala/tools/nsc/classpath/JrtClassPathTest.scala +scala/tools/nsc/classpath/MultiReleaseJarTest.scala +scala/tools/nsc/classpath/PathResolverBaseTest.scala +scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala +scala/tools/nsc/classpath/ZipAndJarFileLookupFactoryTest.scala +scala/tools/nsc/doc/html/HtmlDocletTest.scala +scala/tools/nsc/doc/html/ModelFactoryTest.scala +scala/tools/nsc/doc/html/StringLiteralTest.scala +scala/tools/nsc/interpreter/CompletionTest.scala +scala/tools/nsc/interpreter/ScriptedTest.scala +scala/tools/nsc/interpreter/TabulatorTest.scala +scala/tools/nsc/parser/ParserTest.scala +scala/tools/nsc/reporters/ConsoleReporterTest.scala +scala/tools/nsc/reporters/PositionFilterTest.scala +scala/tools/nsc/reporters/WConfTest.scala +scala/tools/nsc/reporters/AbstractCodeActionTest.scala +scala/tools/nsc/reporters/CodeActionXsource3Test.scala +scala/tools/nsc/reporters/CodeActionTest.scala +scala/tools/nsc/settings/ScalaVersionTest.scala +scala/tools/nsc/settings/SettingsTest.scala +scala/tools/nsc/settings/TargetTest.scala +scala/tools/nsc/symtab/CannotHaveAttrsTest.scala +scala/tools/nsc/symtab/FlagsTest.scala +scala/tools/nsc/symtab/FreshNameExtractorTest.scala +scala/tools/nsc/symtab/StdNamesTest.scala +scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala +scala/tools/nsc/symtab/SymbolTableTest.scala +scala/tools/nsc/symtab/classfile/PicklerTest.scala +scala/tools/nsc/transform/ErasureTest.scala +scala/tools/nsc/transform/MixinTest.scala +scala/tools/nsc/transform/ReleaseFenceTest.scala +scala/tools/nsc/transform/SpecializationTest.scala +scala/tools/nsc/transform/ThicketTransformerTest.scala +scala/tools/nsc/transform/UncurryTest.scala +scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala +scala/tools/nsc/transform/patmat/SolvingTest.scala +scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala +scala/tools/nsc/typechecker/ConstantFolderTest.scala +scala/tools/nsc/typechecker/ImplicitsTest.scala +scala/tools/nsc/typechecker/InferencerTest.scala +scala/tools/nsc/typechecker/NamerTest.scala +scala/tools/nsc/typechecker/OverridingPairsTest.scala +scala/tools/nsc/typechecker/ParamAliasTest.scala +scala/tools/nsc/typechecker/TreeAttachmentTest.scala +scala/tools/nsc/typechecker/TypedTreeTest.scala +scala/tools/nsc/QuickfixTest.scala +scala/tools/nsc/util/StackTraceTest.scala +scala/tools/testkit/ReflectUtilTest.scala +scala/tools/xsbt/BridgeTesting.scala +scala/tools/xsbt/BasicBridgeTest.scala +scala/tools/xsbt/ClassNameTest.scala +scala/tools/xsbt/CodeActionTest.scala +scala/tools/xsbt/DependencyTest.scala +scala/tools/xsbt/ExtractAPITest.scala +scala/tools/xsbt/ExtractUsedNamesTest.scala +scala/tools/xsbt/InteractiveConsoleInterfaceTest.scala +scala/tools/xsbt/SameAPI.scala +scala/tools/xsbt/TestCallback.scala +scala/util/ChainingOpsTest.scala + +## Do not link +scala/CollectTest.scala +scala/MatchErrorSerializationTest.scala +scala/PartialFunctionSerializationTest.scala +scala/lang/stringinterpol/StringContextTest.scala +scala/collection/IterableTest.scala +scala/collection/IteratorTest.scala +scala/collection/NewBuilderTest.scala +scala/collection/SeqViewTest.scala +scala/collection/SetMapConsistencyTest.scala +scala/collection/SetMapRulesTest.scala +scala/collection/Sizes.scala +scala/collection/ViewTest.scala +scala/collection/concurrent/TrieMapTest.scala +scala/collection/convert/EqualsTest.scala +scala/collection/convert/JConcurrentMapWrapperTest.scala +scala/collection/convert/WrapperSerializationTest.scala +scala/collection/immutable/ChampMapSmokeTest.scala +scala/collection/immutable/ChampSetSmokeTest.scala +scala/collection/immutable/LazyListGCTest.scala +scala/collection/immutable/LazyListLazinessTest.scala +scala/collection/immutable/ListTest.scala +scala/collection/immutable/SerializationTest.scala +scala/collection/immutable/StreamTest.scala +scala/collection/immutable/StringLikeTest.scala +scala/collection/immutable/VectorTest.scala +scala/collection/mutable/AnyRefMapTest.scala +scala/collection/mutable/ArrayBufferTest.scala +scala/collection/mutable/ListBufferTest.scala +scala/collection/mutable/OpenHashMapTest.scala +scala/collection/mutable/PriorityQueueTest.scala +scala/collection/mutable/SerializationTest.scala +scala/concurrent/FutureTest.scala +scala/concurrent/duration/SerializationTest.scala +scala/concurrent/impl/DefaultPromiseTest.scala +scala/collection/generic/DecoratorsTest.scala +scala/io/SourceTest.scala +scala/jdk/AccumulatorTest.scala +scala/jdk/DurationConvertersTest.scala +scala/jdk/FunctionConvertersTest.scala +scala/jdk/OptionConvertersTest.scala +scala/jdk/StepperConversionTest.scala +scala/jdk/StepperTest.scala +scala/jdk/StreamConvertersTest.scala +scala/jdk/StreamConvertersTypingTest.scala +scala/math/OrderingTest.scala +scala/runtime/ScalaRunTimeTest.scala +scala/sys/env.scala +scala/sys/process/ParserTest.scala +scala/sys/process/PipedProcessTest.scala +scala/sys/process/ProcessBuilderTest.scala +scala/sys/process/ProcessTest.scala +scala/tools/testkit/AssertUtilTest.scala +scala/util/PropertiesTest.scala +scala/util/SpecVersionTest.scala +scala/util/SystemPropertiesTest.scala + +## Tests fail + +# Reflection +scala/reflect/ClassTagTest.scala + +# Require strict-floats +scala/math/BigDecimalTest.scala + +# Tests passed but are too slow (timeouts) +scala/collection/immutable/ListSetTest.scala +scala/util/SortingTest.scala + +# Whitebox testing of ArrayBuilder.ofUnit +scala/collection/mutable/ArrayBuilderTest.scala + +# Relies on undefined behavior +scala/collection/MapTest.scala +scala/collection/StringOpsTest.scala +scala/collection/StringParsersTest.scala +scala/collection/convert/CollectionConvertersTest.scala +scala/collection/convert/MapWrapperTest.scala +scala/collection/immutable/NumericRangeTest.scala +scala/math/BigIntTest.scala diff --git a/scala-test-suite/src/test/resources/2.13.16/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.16/BlacklistedTests.txt new file mode 100644 index 0000000000..0bbff2e24d --- /dev/null +++ b/scala-test-suite/src/test/resources/2.13.16/BlacklistedTests.txt @@ -0,0 +1,250 @@ +## Do not compile +scala/ExtractorTest.scala +scala/OptionTest.scala +scala/SerializationStabilityTest.scala +scala/StringTest.scala +scala/collection/FactoriesTest.scala +scala/collection/LazyZipOpsTest.scala +scala/collection/SeqTest.scala +scala/collection/immutable/HashMapTest.scala +scala/collection/immutable/HashSetTest.scala +scala/collection/immutable/IndexedSeqTest.scala +scala/collection/immutable/IntMapTest.scala +scala/collection/immutable/LazyListTest.scala +scala/collection/immutable/ListMapTest.scala +scala/collection/immutable/LongMapTest.scala +scala/collection/immutable/MapHashcodeTest.scala +scala/collection/immutable/SeqTest.scala +scala/collection/immutable/SmallMapTest.scala +scala/collection/immutable/SortedMapTest.scala +scala/collection/immutable/SortedSetTest.scala +scala/collection/immutable/TreeMapTest.scala +scala/collection/immutable/TreeSetTest.scala +scala/collection/mutable/LongMapTest.scala +scala/lang/annotations/BytecodeTest.scala +scala/lang/annotations/RunTest.scala +scala/lang/traits/BytecodeTest.scala +scala/lang/traits/RunTest.scala +scala/lang/primitives/NaNTest.scala +scala/reflect/ClassOfTest.scala +scala/reflect/FieldAccessTest.scala +scala/reflect/QTest.scala +scala/reflect/macros/AttachmentsTest.scala +scala/reflect/io/ZipArchiveTest.scala +scala/reflect/internal/InferTest.scala +scala/reflect/internal/LongNamesTest.scala +scala/reflect/internal/MirrorsTest.scala +scala/reflect/internal/NamesTest.scala +scala/reflect/internal/PositionsTest.scala +scala/reflect/internal/PrintersTest.scala +scala/reflect/internal/ScopeTest.scala +scala/reflect/internal/SubstMapTest.scala +scala/reflect/internal/TreeGenTest.scala +scala/reflect/internal/TypesTest.scala +scala/reflect/internal/util/AbstractFileClassLoaderTest.scala +scala/reflect/internal/util/FileUtilsTest.scala +scala/reflect/internal/util/SourceFileTest.scala +scala/reflect/internal/util/StringOpsTest.scala +scala/reflect/internal/util/WeakHashSetTest.scala +scala/reflect/io/AbstractFileTest.scala +scala/reflect/runtime/ReflectionUtilsShowTest.scala +scala/reflect/runtime/ThreadSafetyTest.scala +scala/runtime/BooleanBoxingTest.scala +scala/runtime/ByteBoxingTest.scala +scala/runtime/CharBoxingTest.scala +scala/runtime/DoubleBoxingTest.scala +scala/runtime/IntBoxingTest.scala +scala/runtime/FloatBoxingTest.scala +scala/runtime/LongBoxingTest.scala +scala/runtime/ShortBoxingTest.scala +scala/tools/nsc/Build.scala +scala/tools/nsc/DeterminismTest.scala +scala/tools/nsc/DeterminismTester.scala +scala/tools/nsc/FileUtils.scala +scala/tools/nsc/GlobalCustomizeClassloaderTest.scala +scala/tools/nsc/MainRunnerTest.scala +scala/tools/nsc/PhaseAssemblyTest.scala +scala/tools/nsc/PickleWriteTest.scala +scala/tools/nsc/PipelineMainTest.scala +scala/tools/nsc/ScriptRunnerTest.scala +scala/tools/nsc/async/AnnotationDrivenAsyncTest.scala +scala/tools/nsc/async/CustomFuture.scala +scala/tools/nsc/backend/jvm/BTypesTest.scala +scala/tools/nsc/backend/jvm/BytecodeTest.scala +scala/tools/nsc/backend/jvm/ClassfileParserTest.scala +scala/tools/nsc/backend/jvm/DefaultMethodTest.scala +scala/tools/nsc/backend/jvm/DirectCompileTest.scala +scala/tools/nsc/backend/jvm/GenericSignaturesTest.scala +scala/tools/nsc/backend/jvm/IndyLambdaTest.scala +scala/tools/nsc/backend/jvm/IndySammyTest.scala +scala/tools/nsc/backend/jvm/InnerClassAttributeTest.scala +scala/tools/nsc/backend/jvm/LineNumberTest.scala +scala/tools/nsc/backend/jvm/NestedClassesCollectorTest.scala +scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala +scala/tools/nsc/backend/jvm/PerRunInitTest.scala +scala/tools/nsc/backend/jvm/StringConcatTest.scala +scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala +scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala +scala/tools/nsc/backend/jvm/analysis/TypeFlowAnalyzerTest.scala +scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala +scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala +scala/tools/nsc/backend/jvm/opt/BoxUnboxAndInlineTest.scala +scala/tools/nsc/backend/jvm/opt/BoxUnboxTest.scala +scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala +scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala +scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala +scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala +scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +scala/tools/nsc/backend/jvm/opt/InlineSourceMatcherTest.scala +scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala +scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala +scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala +scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala +scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala +scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala +scala/tools/nsc/classpath/AggregateClassPathTest.scala +scala/tools/nsc/classpath/JrtClassPathTest.scala +scala/tools/nsc/classpath/MultiReleaseJarTest.scala +scala/tools/nsc/classpath/PathResolverBaseTest.scala +scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala +scala/tools/nsc/classpath/ZipAndJarFileLookupFactoryTest.scala +scala/tools/nsc/doc/html/HtmlDocletTest.scala +scala/tools/nsc/doc/html/ModelFactoryTest.scala +scala/tools/nsc/doc/html/StringLiteralTest.scala +scala/tools/nsc/interpreter/CompletionTest.scala +scala/tools/nsc/interpreter/ScriptedTest.scala +scala/tools/nsc/interpreter/TabulatorTest.scala +scala/tools/nsc/parser/ParserTest.scala +scala/tools/nsc/reporters/ConsoleReporterTest.scala +scala/tools/nsc/reporters/PositionFilterTest.scala +scala/tools/nsc/reporters/WConfTest.scala +scala/tools/nsc/reporters/AbstractCodeActionTest.scala +scala/tools/nsc/reporters/CodeActionXsource3Test.scala +scala/tools/nsc/reporters/CodeActionTest.scala +scala/tools/nsc/settings/ScalaVersionTest.scala +scala/tools/nsc/settings/SettingsTest.scala +scala/tools/nsc/settings/TargetTest.scala +scala/tools/nsc/symtab/CannotHaveAttrsTest.scala +scala/tools/nsc/symtab/FlagsTest.scala +scala/tools/nsc/symtab/FreshNameExtractorTest.scala +scala/tools/nsc/symtab/StdNamesTest.scala +scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala +scala/tools/nsc/symtab/SymbolTableTest.scala +scala/tools/nsc/symtab/classfile/PicklerTest.scala +scala/tools/nsc/transform/ErasureTest.scala +scala/tools/nsc/transform/MixinTest.scala +scala/tools/nsc/transform/ReleaseFenceTest.scala +scala/tools/nsc/transform/SpecializationTest.scala +scala/tools/nsc/transform/ThicketTransformerTest.scala +scala/tools/nsc/transform/UncurryTest.scala +scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala +scala/tools/nsc/transform/patmat/SolvingTest.scala +scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala +scala/tools/nsc/typechecker/ConstantFolderTest.scala +scala/tools/nsc/typechecker/ImplicitsTest.scala +scala/tools/nsc/typechecker/InferencerTest.scala +scala/tools/nsc/typechecker/NamerTest.scala +scala/tools/nsc/typechecker/OverridingPairsTest.scala +scala/tools/nsc/typechecker/ParamAliasTest.scala +scala/tools/nsc/typechecker/TreeAttachmentTest.scala +scala/tools/nsc/typechecker/TypedTreeTest.scala +scala/tools/nsc/typechecker/XMLTest.scala +scala/tools/nsc/QuickfixTest.scala +scala/tools/nsc/util/StackTraceTest.scala +scala/tools/testkit/ReflectUtilTest.scala +scala/tools/xsbt/BridgeTesting.scala +scala/tools/xsbt/BasicBridgeTest.scala +scala/tools/xsbt/ClassNameTest.scala +scala/tools/xsbt/CodeActionTest.scala +scala/tools/xsbt/DependencyTest.scala +scala/tools/xsbt/ExtractAPITest.scala +scala/tools/xsbt/ExtractUsedNamesTest.scala +scala/tools/xsbt/InteractiveConsoleInterfaceTest.scala +scala/tools/xsbt/SameAPI.scala +scala/tools/xsbt/TestCallback.scala +scala/util/ChainingOpsTest.scala + +## Do not link +scala/CollectTest.scala +scala/MatchErrorSerializationTest.scala +scala/PartialFunctionSerializationTest.scala +scala/lang/stringinterpol/StringContextTest.scala +scala/collection/IterableTest.scala +scala/collection/IteratorTest.scala +scala/collection/NewBuilderTest.scala +scala/collection/SeqViewTest.scala +scala/collection/SetMapConsistencyTest.scala +scala/collection/SetMapRulesTest.scala +scala/collection/Sizes.scala +scala/collection/ViewTest.scala +scala/collection/concurrent/TrieMapTest.scala +scala/collection/convert/EqualsTest.scala +scala/collection/convert/JConcurrentMapWrapperTest.scala +scala/collection/convert/WrapperSerializationTest.scala +scala/collection/immutable/ChampMapSmokeTest.scala +scala/collection/immutable/ChampSetSmokeTest.scala +scala/collection/immutable/LazyListGCTest.scala +scala/collection/immutable/LazyListLazinessTest.scala +scala/collection/immutable/ListTest.scala +scala/collection/immutable/SerializationTest.scala +scala/collection/immutable/StreamTest.scala +scala/collection/immutable/StringLikeTest.scala +scala/collection/immutable/VectorTest.scala +scala/collection/mutable/AnyRefMapTest.scala +scala/collection/mutable/ArrayBufferTest.scala +scala/collection/mutable/ListBufferTest.scala +scala/collection/mutable/OpenHashMapTest.scala +scala/collection/mutable/PriorityQueueTest.scala +scala/collection/mutable/SerializationTest.scala +scala/concurrent/FutureTest.scala +scala/concurrent/duration/SerializationTest.scala +scala/concurrent/impl/DefaultPromiseTest.scala +scala/collection/generic/DecoratorsTest.scala +scala/io/SourceTest.scala +scala/jdk/AccumulatorTest.scala +scala/jdk/DurationConvertersTest.scala +scala/jdk/FunctionConvertersTest.scala +scala/jdk/OptionConvertersTest.scala +scala/jdk/StepperConversionTest.scala +scala/jdk/StepperTest.scala +scala/jdk/StreamConvertersTest.scala +scala/jdk/StreamConvertersTypingTest.scala +scala/math/OrderingTest.scala +scala/runtime/ScalaRunTimeTest.scala +scala/sys/env.scala +scala/sys/process/ParserTest.scala +scala/sys/process/PipedProcessTest.scala +scala/sys/process/ProcessBuilderTest.scala +scala/sys/process/ProcessTest.scala +scala/tools/testkit/AssertUtilTest.scala +scala/util/PropertiesTest.scala +scala/util/SpecVersionTest.scala +scala/util/SystemPropertiesTest.scala + +## Tests fail + +# Reflection +scala/reflect/ClassTagTest.scala + +# Require strict-floats +scala/math/BigDecimalTest.scala + +# Tests passed but are too slow (timeouts) +scala/collection/immutable/ListSetTest.scala +scala/util/SortingTest.scala + +# Whitebox testing of ArrayBuilder.ofUnit +scala/collection/mutable/ArrayBuilderTest.scala + +# Relies on undefined behavior +scala/collection/MapTest.scala +scala/collection/StringOpsTest.scala +scala/collection/StringParsersTest.scala +scala/collection/convert/CollectionConvertersTest.scala +scala/collection/convert/MapWrapperTest.scala +scala/collection/immutable/NumericRangeTest.scala +scala/math/BigIntTest.scala From 78ce1c81052fb15e64efb5af798d7bb43b119ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 15 Jan 2025 17:46:39 +0100 Subject: [PATCH 067/121] Fix #5115: Apply the StoreModule hack to JSConstructorDef from 1.8. Before 1.11, the body of `JSConstructorDef`s was a straight list of trees. We have a hack that extracts the three parts of a structured `JSConstructorBody`: `beforeSuper`, `superCall` and `afterSuper`. In 1.18, we introduced a separate hack to ensure that `afterSuper` starts with a `StoreModule` instruction. Unfortunately, we did not apply the latter hack on the result of the former. This means our deserializer incorrectly reads JS module classes from before 1.11. This commit makes sure to also apply the hack in that situation. Unfortunately, this does not come with automated tests. We do not have any non-native JS module class in our standard library, which is currently our only source of old IR for backward compatibility tests. --- .../scala/org/scalajs/ir/Serializers.scala | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index bf50ef07fc..707dfc61d7 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -1543,7 +1543,7 @@ object Serializers { val (jsConstructor, jsMethodProps) = { if (hacks.use8 && kind.isJSClass) { assert(jsConstructorBuilder.result().isEmpty, "found JSConstructorDef in pre 1.8 IR") - jsConstructorHack(jsMethodPropsBuilder.result()) + jsConstructorHack(kind, jsMethodPropsBuilder.result()) } else { (jsConstructorBuilder.result(), jsMethodPropsBuilder.result()) } @@ -1803,7 +1803,7 @@ object Serializers { newInstanceRecMethod :: newMethods } - private def jsConstructorHack( + private def jsConstructorHack(ownerKind: ClassKind, jsMethodProps: List[JSMethodPropDef]): (Option[JSConstructorDef], List[JSMethodPropDef]) = { val jsConstructorBuilder = new OptionBuilder[JSConstructorDef] val jsMethodPropsBuilder = List.newBuilder[JSMethodPropDef] @@ -1817,8 +1817,9 @@ object Serializers { } bodyStats.span(!_.isInstanceOf[JSSuperConstructorCall]) match { - case (beforeSuper, (superCall: JSSuperConstructorCall) :: afterSuper) => + case (beforeSuper, (superCall: JSSuperConstructorCall) :: afterSuper0) => val newFlags = flags.withNamespace(MemberNamespace.Constructor) + val afterSuper = maybeHackJSConstructorDefAfterSuper(ownerKind, afterSuper0, superCall.pos) val newBody = JSConstructorBody(beforeSuper, superCall, afterSuper)(body.pos) val ctorDef = JSConstructorDef(newFlags, args, restParam, newBody)( methodDef.optimizerHints, Unversioned)(methodDef.pos) @@ -1966,19 +1967,22 @@ object Serializers { val beforeSuper = readTrees() val superCall = readTree().asInstanceOf[JSSuperConstructorCall] val afterSuper0 = readTrees() + val afterSuper = maybeHackJSConstructorDefAfterSuper(ownerKind, afterSuper0, superCall.pos) + val body = JSConstructorBody(beforeSuper, superCall, afterSuper)(bodyPos) + JSConstructorDef(flags, params, restParam, body)( + OptimizerHints.fromBits(readInt()), optHash) + } - val afterSuper = if (hacks.use17 && ownerKind == ClassKind.JSModuleClass) { + private def maybeHackJSConstructorDefAfterSuper(ownerKind: ClassKind, + afterSuper0: List[Tree], superCallPos: Position): List[Tree] = { + if (hacks.use17 && ownerKind == ClassKind.JSModuleClass) { afterSuper0 match { case StoreModule() :: _ => afterSuper0 - case _ => StoreModule()(superCall.pos) :: afterSuper0 + case _ => StoreModule()(superCallPos) :: afterSuper0 } } else { afterSuper0 } - - val body = JSConstructorBody(beforeSuper, superCall, afterSuper)(bodyPos) - JSConstructorDef(flags, params, restParam, body)( - OptimizerHints.fromBits(readInt()), optHash) } private def readJSMethodDef()(implicit pos: Position): JSMethodDef = { From 7b5528e8aa425b56a0f1b329b146007dd7cdfe4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 13 Jan 2025 10:30:25 +0100 Subject: [PATCH 068/121] Ensure the switch of Bug1955 is always a switch by adding more cases. Under 4 cases, some versions of the compiler do not emit a switch match. That defeats the purpose of that test. --- .../testsuite/compiler/RegressionTest.scala | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala index 6642451b5d..301045cf74 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala @@ -418,10 +418,13 @@ class RegressionTest { @Test def switchMatchWithGuardAndResultTypeOfBoxedUnit_Issue1955(): Unit = { val bug = new Bug1955 bug.bug(2, true) - assertEquals(0, bug.result) + assertEquals(22, bug.result) + bug.bug(2, false) + assertEquals(-1, bug.result) bug.bug(1, true) assertEquals(579, bug.result) - assertThrows(classOf[MatchError], bug.bug(2, false)) + bug.bug(6, true) + assertEquals(-1, bug.result) } @Test def switchMatchWithGuardInStatementPosButWithNonUnitBranches_Issue4105(): Unit = { @@ -987,9 +990,12 @@ object RegressionTest { } def bug(x: Int, e: Boolean): Unit = { - x match { - case 1 => doSomething(123, 456, ()) - case 2 if e => + (x: @switch) match { + case 1 => doSomething(123, 456, ()) + case 2 if e => result = 22 + case 3 => result = 33 + case 4 => result = 44 + case _ => result = -1 } if (false) () From 498e56b0d0fb0484dbbf4eafe74f87a94ab03181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 13 Jan 2025 13:29:28 +0100 Subject: [PATCH 069/121] Fix #5112: Recognize asInstanceOf around jump to default label. That shape gets produced by Scala 2.13.16+ in some cases. --- .../org/scalajs/nscplugin/GenJSCode.scala | 2 +- .../testsuite/compiler/RegressionTest.scala | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 7c4548e67f..d707905ba7 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -3903,7 +3903,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) assert(guard == EmptyTree, s"found a case guard at ${caze.pos}") def genBody(body: Tree): js.Tree = body match { - case app @ Apply(_, Nil) if app.symbol == defaultLabelSym => + case MaybeAsInstanceOf(app @ Apply(_, Nil)) if app.symbol == defaultLabelSym => genJumpToElseClause case Block(List(app @ Apply(_, Nil)), _) if app.symbol == defaultLabelSym => genJumpToElseClause diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala index 301045cf74..f3d762645b 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala @@ -427,6 +427,20 @@ class RegressionTest { assertEquals(-1, bug.result) } + @Test def switchMatchWithGuardAndResultTypeOfBoxedUnitWithTailrec_Issue5112(): Unit = { + val bug = new Bug5112 + bug.bug('d', true) + assertEquals(44, bug.result) + bug.bug('a', true) + assertEquals(11, bug.result) + bug.bug('E', true) + assertEquals(22, bug.result) + bug.bug('e', false) + assertEquals(44, bug.result) + bug.bug('G', false) + assertEquals(33, bug.result) + } + @Test def switchMatchWithGuardInStatementPosButWithNonUnitBranches_Issue4105(): Unit = { def encodeString(string: String, isKey: Boolean): String = { val buffer = new java.lang.StringBuilder() @@ -1002,6 +1016,25 @@ object RegressionTest { } } + class Bug5112 { + var result: Int = 0 + + // The tail-recursive transformation is required to trigger the bug + @tailrec + final def bug(ch: Char, guard: Boolean): Unit = { + if (ch >= 'A' && ch <= 'Z') { + bug((ch + 'a' - 'A').toChar, guard) + } else { + (ch: @switch) match { + case 'a' => result = 11 + case 'c' | 'e' if guard => result = 22 + case 'g' => result = 33 + case _ => result = 44 + } + } + } + } + object Bug3013 { trait A1 { private val s = "A1" From ee3e9a026cc6a0d5919b1f2a9036021b01d5eaa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 13 Jan 2025 14:06:35 +0100 Subject: [PATCH 070/121] Codegen: Make the handling of default clause jumps more generic. Previously, we had to recognize specific shapes of case bodies in order to extract jumps to default labels. This was fragile, as changes in nsc's codegen could invalidate our assumptions. We now take a more general approach. We register the default label in the `enclosingLabelDefInfos`. Jumps to that label can therefore be handled wherever they appear inside the case bodies. We still require a specific shape to extract the *definition* of the default label in the wildcard case. This can't be helped, as far as I can tell. Ultimately, the JS code generated for the test suites is unchanged, both in 2.12 and 2.13. --- .../org/scalajs/nscplugin/GenJSCode.scala | 138 +++++++----------- 1 file changed, 51 insertions(+), 87 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index d707905ba7..0ad5ed423e 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -3883,93 +3883,55 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) if (isStat) jstpe.VoidType else toIRType(tree.tpe) - val defaultLabelSym = cases.collectFirst { + val optDefaultLabelSymAndInfo = cases.collectFirst { case CaseDef(Ident(nme.WILDCARD), EmptyTree, body @ LabelDef(_, Nil, rhs)) if hasSynthCaseSymbol(body) => - body.symbol - }.getOrElse(NoSymbol) + body.symbol -> new EnclosingLabelDefInfoWithResultAsAssigns(Nil) + } var clauses: List[(List[js.MatchableLiteral], js.Tree)] = Nil var optElseClause: Option[js.Tree] = None - var optElseClauseLabel: Option[LabelName] = None - - def genJumpToElseClause(implicit pos: ir.Position): js.Tree = { - if (optElseClauseLabel.isEmpty) - optElseClauseLabel = Some(freshLabelName("default")) - js.Return(js.Skip(), optElseClauseLabel.get) - } - - for (caze @ CaseDef(pat, guard, body) <- cases) { - assert(guard == EmptyTree, s"found a case guard at ${caze.pos}") - - def genBody(body: Tree): js.Tree = body match { - case MaybeAsInstanceOf(app @ Apply(_, Nil)) if app.symbol == defaultLabelSym => - genJumpToElseClause - case Block(List(app @ Apply(_, Nil)), _) if app.symbol == defaultLabelSym => - genJumpToElseClause - - case If(cond, thenp, elsep) => - js.If(genExpr(cond), genBody(thenp), genBody(elsep))( - resultType)(body.pos) - - /* For #1955. If we receive a tree with the shape - * if (cond) { - * thenp - * } else { - * elsep - * } - * scala.runtime.BoxedUnit.UNIT - * we rewrite it as - * if (cond) { - * thenp - * scala.runtime.BoxedUnit.UNIT - * } else { - * elsep - * scala.runtime.BoxedUnit.UNIT - * } - * so that it fits the shape of if/elses we can deal with. - */ - case Block(List(If(cond, thenp, elsep)), s: Select) - if s.symbol == definitions.BoxedUnit_UNIT => - val newThenp = Block(thenp, s).setType(s.tpe).setPos(thenp.pos) - val newElsep = Block(elsep, s).setType(s.tpe).setPos(elsep.pos) - js.If(genExpr(cond), genBody(newThenp), genBody(newElsep))( - resultType)(body.pos) - case _ => + withScopedVars( + enclosingLabelDefInfos := enclosingLabelDefInfos.get ++ optDefaultLabelSymAndInfo.toList + ) { + for (caze @ CaseDef(pat, guard, body) <- cases) { + assert(guard == EmptyTree, s"found a case guard at ${caze.pos}") + + def genBody(body: Tree): js.Tree = genStatOrExpr(body, isStat) - } - def invalidCase(tree: Tree): Nothing = - abort(s"Invalid case in alternative in switch-like pattern match: $tree at: ${tree.pos}") + def invalidCase(tree: Tree): Nothing = + abort(s"Invalid case in alternative in switch-like pattern match: $tree at: ${tree.pos}") - def genMatchableLiteral(tree: Literal): js.MatchableLiteral = { - genExpr(tree) match { - case matchableLiteral: js.MatchableLiteral => matchableLiteral - case otherExpr => invalidCase(tree) + def genMatchableLiteral(tree: Literal): js.MatchableLiteral = { + genExpr(tree) match { + case matchableLiteral: js.MatchableLiteral => matchableLiteral + case otherExpr => invalidCase(tree) + } } - } - pat match { - case lit: Literal => - clauses = (List(genMatchableLiteral(lit)), genBody(body)) :: clauses - case Ident(nme.WILDCARD) => - optElseClause = Some(body match { - case LabelDef(_, Nil, rhs) if hasSynthCaseSymbol(body) => - genBody(rhs) - case _ => - genBody(body) - }) - case Alternative(alts) => - val genAlts = { - alts map { - case lit: Literal => genMatchableLiteral(lit) - case _ => invalidCase(tree) + pat match { + case lit: Literal => + clauses = (List(genMatchableLiteral(lit)), genBody(body)) :: clauses + case Ident(nme.WILDCARD) => + optElseClause = Some(body match { + case LabelDef(_, Nil, rhs) if hasSynthCaseSymbol(body) => + genBody(rhs) + case _ => + genBody(body) + }) + case Alternative(alts) => + val genAlts = { + alts map { + case lit: Literal => genMatchableLiteral(lit) + case _ => invalidCase(tree) + } } - } - clauses = (genAlts, genBody(body)) :: clauses - case _ => - invalidCase(tree) + clauses = (genAlts, genBody(body)) :: clauses + case _ => + invalidCase(tree) + } } } @@ -4012,21 +3974,23 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } - optElseClauseLabel.fold[js.Tree] { - buildMatch(clauses.reverse, elseClause, resultType) - } { elseClauseLabel => - val matchResultLabel = freshLabelName("matchResult") - val patchedClauses = for ((alts, body) <- clauses) yield { - implicit val pos = body.pos - val newBody = js.Return(body, matchResultLabel) - (alts, newBody) - } - js.Labeled(matchResultLabel, resultType, js.Block(List( - js.Labeled(elseClauseLabel, jstpe.VoidType, { + optDefaultLabelSymAndInfo match { + case Some((defaultLabelSym, defaultLabelInfo)) if defaultLabelInfo.generatedReturns > 0 => + val matchResultLabel = freshLabelName("matchResult") + val patchedClauses = for ((alts, body) <- clauses) yield { + implicit val pos = body.pos + val newBody = js.Return(body, matchResultLabel) + (alts, newBody) + } + js.Labeled(matchResultLabel, resultType, js.Block(List( + js.Labeled(encodeLabelSym(defaultLabelSym), jstpe.VoidType, { buildMatch(patchedClauses.reverse, js.Skip(), jstpe.VoidType) }), elseClause - ))) + ))) + + case _ => + buildMatch(clauses.reverse, elseClause, resultType) } } From dac9aa7ff54c6c793eb915329aaa83539f088af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 14 Jan 2025 17:49:16 +0100 Subject: [PATCH 071/121] Optimize the shapes of `js.Dynamic.literal()` in Scala 2.13. Somehow it had always escaped us that we did not correctly perform that optimization in Scala 2.13. --- .../linker/frontend/optimizer/OptimizerCore.scala | 7 +++++-- project/Build.scala | 14 +++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 53b8a11ee5..dae5f87e43 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -3086,7 +3086,7 @@ private[optimizer] abstract class OptimizerCore( tprops match { case PreTransMaybeBlock(bindingsAndStats, PreTransLocalDef(LocalDef( - RefinedType(ClassType(JSWrappedArrayClass, _), _), + RefinedType(ClassType(JSWrappedArrayClass | WrappedVarArgsClass, _), _), false, InlineClassInstanceReplacement(_, wrappedArrayFields, _)))) => assert(wrappedArrayFields.size == 1) @@ -5597,6 +5597,7 @@ private[optimizer] object OptimizerCore { private val ClassTagModuleClass = ClassName("scala.reflect.ClassTag$") private val JavaScriptExceptionClass = ClassName("scala.scalajs.js.JavaScriptException") private val JSWrappedArrayClass = ClassName("scala.scalajs.js.WrappedArray") + private val WrappedVarArgsClass = ClassName("scala.scalajs.runtime.WrappedVarArgs") private val NilClass = ClassName("scala.collection.immutable.Nil$") private val Tuple2Class = ClassName("scala.Tuple2") @@ -6495,6 +6496,7 @@ private[optimizer] object OptimizerCore { private val ClassClassRef = ClassRef(ClassClass) private val StringClassRef = ClassRef(BoxedStringClass) private val SeqClassRef = ClassRef(ClassName("scala.collection.Seq")) + private val ImmutableSeqClassRef = ClassRef(ClassName("scala.collection.immutable.Seq")) private val JSObjectClassRef = ClassRef(ClassName("scala.scalajs.js.Object")) private val JSArrayClassRef = ClassRef(ClassName("scala.scalajs.js.Array")) @@ -6520,7 +6522,8 @@ private[optimizer] object OptimizerCore { m("getName", Nil, StringClassRef) -> ClassGetName ), ClassName("scala.scalajs.js.special.package$") -> List( - m("objectLiteral", List(SeqClassRef), JSObjectClassRef) -> ObjectLiteral + m("objectLiteral", List(SeqClassRef), JSObjectClassRef) -> ObjectLiteral, // 2.12 + m("objectLiteral", List(ImmutableSeqClassRef), JSObjectClassRef) -> ObjectLiteral // 2.13 ) ) diff --git a/project/Build.scala b/project/Build.scala index 8dd80196c4..3e2f1c72f4 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2066,17 +2066,17 @@ object Build { case `default213Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 449000 to 450000, - fullLink = 95000 to 96000, - fastLinkGz = 58000 to 59000, + fastLink = 439000 to 440000, + fullLink = 92000 to 93000, + fastLinkGz = 57000 to 58000, fullLinkGz = 25000 to 26000, )) } else { Some(ExpectedSizes( - fastLink = 304000 to 305000, - fullLink = 261000 to 262000, - fastLinkGz = 48000 to 49000, - fullLinkGz = 43000 to 44000, + fastLink = 298000 to 299000, + fullLink = 256000 to 257000, + fastLinkGz = 47000 to 48000, + fullLinkGz = 42000 to 43000, )) } From c7cbc7ada4e88cf8dc3181c6963c4c30ef8be461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 21 Jan 2025 21:08:00 +0100 Subject: [PATCH 072/121] Version 1.18.2. --- ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index 581ed885cd..ca9c29094f 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.18.2-SNAPSHOT", + current = "1.18.2", binaryEmitted = "1.18" ) From 797953158c3f7ec6130832c71086c2722b2bbc70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 22 Jan 2025 11:16:06 +0100 Subject: [PATCH 073/121] Towards 1.18.3. --- ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala | 2 +- project/Build.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index ca9c29094f..0f03e6a638 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.18.2", + current = "1.18.3-SNAPSHOT", binaryEmitted = "1.18" ) diff --git a/project/Build.scala b/project/Build.scala index 3e2f1c72f4..8749ef5c74 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -395,7 +395,7 @@ object Build { "1.3.0", "1.3.1", "1.4.0", "1.5.0", "1.5.1", "1.6.0", "1.7.0", "1.7.1", "1.8.0", "1.9.0", "1.10.0", "1.10.1", "1.11.0", "1.12.0", "1.13.0", "1.13.1", "1.13.2", "1.14.0", "1.15.0", "1.16.0", "1.17.0", "1.18.0", - "1.18.1") + "1.18.1", "1.18.2") val previousVersion = previousVersions.last val previousBinaryCrossVersion = CrossVersion.binaryWith("sjs1_", "") From 795d3e062de91e341b072e5b25e61e4faa1e07aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 31 Jan 2025 11:06:31 +0100 Subject: [PATCH 074/121] Bump the Scala 3 version used for source compat of `ir/` to 3.6.3. `private[this]` is deprecated in favor of `private`. This should not have a real impact on post-JIT performance. `var x = _` is deprecated in favor `= scala.compiletime.unitialized`. The latter does not exist in Scala 2, so we use `= null`. We only used that during the construction of a `Deserializer` instance. It could theoretically affect performance, but not in any measurable way compared to everything else that happens in a `Deserializer`. --- Jenkinsfile | 2 +- .../main/scala/org/scalajs/ir/Hashers.scala | 4 +- .../scala/org/scalajs/ir/Serializers.scala | 54 +++++++++---------- project/Build.scala | 2 +- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index aa8a89111d..8f6ecc3c8e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -575,7 +575,7 @@ def otherScalaVersions = [ "2.12.15" ] -def scala3Version = "3.3.4" +def scala3Version = "3.6.3" def allESVersions = [ "ES5_1", diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala index 4f6c3eeb72..d37b6ffdfc 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala @@ -136,9 +136,9 @@ object Hashers { } private final class TreeHasher { - private[this] val digestBuilder = new SHA1.DigestBuilder + private val digestBuilder = new SHA1.DigestBuilder - private[this] val digestStream = { + private val digestStream = { new DataOutputStream(new OutputStream { def write(b: Int): Unit = digestBuilder.update(b.toByte) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index 707dfc61d7..4866e36457 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -121,24 +121,24 @@ object Serializers { } private final class Serializer { - private[this] val bufferUnderlying = new JumpBackByteArrayOutputStream - private[this] val buffer = new DataOutputStream(bufferUnderlying) + private val bufferUnderlying = new JumpBackByteArrayOutputStream + private val buffer = new DataOutputStream(bufferUnderlying) - private[this] val files = mutable.ListBuffer.empty[URI] - private[this] val fileIndexMap = mutable.Map.empty[URI, Int] + private val files = mutable.ListBuffer.empty[URI] + private val fileIndexMap = mutable.Map.empty[URI, Int] private def fileToIndex(file: URI): Int = fileIndexMap.getOrElseUpdate(file, (files += file).size - 1) - private[this] val encodedNames = mutable.ListBuffer.empty[UTF8String] - private[this] val encodedNameIndexMap = mutable.Map.empty[EncodedNameKey, Int] + private val encodedNames = mutable.ListBuffer.empty[UTF8String] + private val encodedNameIndexMap = mutable.Map.empty[EncodedNameKey, Int] private def encodedNameToIndex(encoded: UTF8String): Int = { val byteString = new EncodedNameKey(encoded) encodedNameIndexMap.getOrElseUpdate(byteString, (encodedNames += encoded).size - 1) } - private[this] val methodNames = mutable.ListBuffer.empty[MethodName] - private[this] val methodNameIndexMap = mutable.Map.empty[MethodName, Int] + private val methodNames = mutable.ListBuffer.empty[MethodName] + private val methodNameIndexMap = mutable.Map.empty[MethodName, Int] private def methodNameToIndex(methodName: MethodName): Int = { methodNameIndexMap.getOrElseUpdate(methodName, { // need to reserve the internal simple names @@ -159,12 +159,12 @@ object Serializers { }) } - private[this] val strings = mutable.ListBuffer.empty[String] - private[this] val stringIndexMap = mutable.Map.empty[String, Int] + private val strings = mutable.ListBuffer.empty[String] + private val stringIndexMap = mutable.Map.empty[String, Int] private def stringToIndex(str: String): Int = stringIndexMap.getOrElseUpdate(str, (strings += str).size - 1) - private[this] var lastPosition: Position = Position.NoPosition + private var lastPosition: Position = Position.NoPosition def serialize(stream: OutputStream, classDef: ClassDef): Unit = { // Write tree to buffer and record files, names and strings @@ -988,16 +988,16 @@ object Serializers { private final class Deserializer(buf: ByteBuffer) { require(buf.order() == ByteOrder.BIG_ENDIAN) - private[this] var hacks: Hacks = _ - private[this] var files: Array[URI] = _ - private[this] var encodedNames: Array[UTF8String] = _ - private[this] var localNames: Array[LocalName] = _ - private[this] var labelNames: Array[LabelName] = _ - private[this] var simpleFieldNames: Array[SimpleFieldName] = _ - private[this] var simpleMethodNames: Array[SimpleMethodName] = _ - private[this] var classNames: Array[ClassName] = _ - private[this] var methodNames: Array[MethodName] = _ - private[this] var strings: Array[String] = _ + private var hacks: Hacks = null + private var files: Array[URI] = null + private var encodedNames: Array[UTF8String] = null + private var localNames: Array[LocalName] = null + private var labelNames: Array[LabelName] = null + private var simpleFieldNames: Array[SimpleFieldName] = null + private var simpleMethodNames: Array[SimpleMethodName] = null + private var classNames: Array[ClassName] = null + private var methodNames: Array[MethodName] = null + private var strings: Array[String] = null /** Uniqueness cache for FieldName's. * @@ -1008,13 +1008,13 @@ object Serializers { * to make them all `eq`, consuming less memory and speeding up equality * tests. */ - private[this] val uniqueFieldNames = mutable.AnyRefMap.empty[FieldName, FieldName] + private val uniqueFieldNames = mutable.AnyRefMap.empty[FieldName, FieldName] - private[this] var lastPosition: Position = Position.NoPosition + private var lastPosition: Position = Position.NoPosition - private[this] var enclosingClassName: ClassName = _ - private[this] var thisTypeForHack: Option[Type] = None - private[this] var patchDynamicImportThunkSuperCtorCall: Boolean = false + private var enclosingClassName: ClassName = null + private var thisTypeForHack: Option[Type] = None + private var patchDynamicImportThunkSuperCtorCall: Boolean = false def deserializeEntryPointsInfo(): EntryPointsInfo = { hacks = new Hacks(sourceVersion = readHeader()) @@ -2535,7 +2535,7 @@ object Serializers { } private class OptionBuilder[T] { - private[this] var value: Option[T] = None + private var value: Option[T] = None def +=(x: T): Unit = { require(value.isEmpty) diff --git a/project/Build.scala b/project/Build.scala index 8749ef5c74..446aeebfab 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1061,7 +1061,7 @@ object Build { */ scalacOptions ++= { if (scalaVersion.value.startsWith("3.")) - List("-Ysafe-init") + List("-Wsafe-init") else Nil }, From 2a77c9cea28dfa4086802f230035366e55c20fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 22 Jan 2025 12:11:22 +0100 Subject: [PATCH 075/121] Refactoring: Generic notion of CheckingPhase for the IR checkers. We previously had two boolean options for deciding what IR is allowed or not between phases. Of the four possible combinations of values, one was actually invalid. With the introduction of the desugarer, there would have been *three* such boolean options. Only 4 out of their 8 combinations would have been valid. We now introduce a generic concept of `CheckingPhase`. Some IR features are allowed as output of each `CheckingPhase`. Since the `ClassDefChecker` and `IRChecker` must agree on what features are valid when, we factor that logic into `FeatureSet`. It represents a set of logical features, and can compute which set is valid for each phase. --- .../scalajs/linker/analyzer/Analyzer.scala | 14 ++-- .../scalajs/linker/analyzer/InfoLoader.scala | 36 +++------ .../linker/checker/CheckingPhase.scala | 28 +++++++ .../linker/checker/ClassDefChecker.scala | 39 +++++----- .../scalajs/linker/checker/FeatureSet.scala | 76 +++++++++++++++++++ .../scalajs/linker/checker/IRChecker.scala | 19 +++-- .../scalajs/linker/frontend/BaseLinker.scala | 8 +- .../org/scalajs/linker/frontend/Refiner.scala | 10 ++- .../org/scalajs/linker/BaseLinkerTest.scala | 5 +- .../linker/checker/ClassDefCheckerTest.scala | 8 +- .../linker/testutils/LinkingUtils.scala | 4 +- 11 files changed, 173 insertions(+), 74 deletions(-) create mode 100644 linker/shared/src/main/scala/org/scalajs/linker/checker/CheckingPhase.scala create mode 100644 linker/shared/src/main/scala/org/scalajs/linker/checker/FeatureSet.scala diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala index dc4dab1816..9dc14c1574 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala @@ -29,6 +29,7 @@ import org.scalajs.ir.Trees.{MemberNamespace, JSNativeLoadSpec} import org.scalajs.ir.Types.ClassRef import org.scalajs.linker._ +import org.scalajs.linker.checker.CheckingPhase import org.scalajs.linker.frontend.IRLoader import org.scalajs.linker.interface._ import org.scalajs.linker.interface.unstable.ModuleInitializerImpl @@ -43,15 +44,10 @@ import Analysis._ import Infos.{NamespacedMethodName, ReachabilityInfo, ReachabilityInfoInClass} final class Analyzer(config: CommonPhaseConfig, initial: Boolean, - checkIR: Boolean, failOnError: Boolean, irLoader: IRLoader) { - - private val infoLoader: InfoLoader = { - new InfoLoader(irLoader, - if (!checkIR) InfoLoader.NoIRCheck - else if (initial) InfoLoader.InitialIRCheck - else InfoLoader.InternalIRCheck - ) - } + checkIRFor: Option[CheckingPhase], failOnError: Boolean, irLoader: IRLoader) { + + private val infoLoader: InfoLoader = + new InfoLoader(irLoader, checkIRFor) def computeReachability(moduleInitializers: Seq[ModuleInitializer], symbolRequirements: SymbolRequirement, logger: Logger)(implicit ec: ExecutionContext): Future[Analysis] = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/InfoLoader.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/InfoLoader.scala index 7eeb5d197b..83003e6be5 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/InfoLoader.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/InfoLoader.scala @@ -22,14 +22,14 @@ import org.scalajs.ir.Trees._ import org.scalajs.logging._ -import org.scalajs.linker.checker.ClassDefChecker +import org.scalajs.linker.checker._ import org.scalajs.linker.frontend.IRLoader import org.scalajs.linker.interface.LinkingException import org.scalajs.linker.CollectionsCompat.MutableMapCompatOps import Platform.emptyThreadSafeMap -private[analyzer] final class InfoLoader(irLoader: IRLoader, irCheckMode: InfoLoader.IRCheckMode) { +private[analyzer] final class InfoLoader(irLoader: IRLoader, checkIRFor: Option[CheckingPhase]) { private var logger: Logger = _ private val cache = emptyThreadSafeMap[ClassName, InfoLoader.ClassInfoCache] @@ -44,7 +44,7 @@ private[analyzer] final class InfoLoader(irLoader: IRLoader, irCheckMode: InfoLo implicit ec: ExecutionContext): Option[Future[Infos.ClassInfo]] = { if (irLoader.classExists(className)) { val infoCache = cache.getOrElseUpdate(className, - new InfoLoader.ClassInfoCache(className, irLoader, irCheckMode)) + new InfoLoader.ClassInfoCache(className, irLoader, checkIRFor)) Some(infoCache.loadInfo(logger)) } else { None @@ -58,15 +58,9 @@ private[analyzer] final class InfoLoader(irLoader: IRLoader, irCheckMode: InfoLo } private[analyzer] object InfoLoader { - sealed trait IRCheckMode - - case object NoIRCheck extends IRCheckMode - case object InitialIRCheck extends IRCheckMode - case object InternalIRCheck extends IRCheckMode - private type MethodInfos = Array[Map[MethodName, Infos.MethodInfo]] - private class ClassInfoCache(className: ClassName, irLoader: IRLoader, irCheckMode: InfoLoader.IRCheckMode) { + private class ClassInfoCache(className: ClassName, irLoader: IRLoader, checkIRFor: Option[CheckingPhase]) { private var cacheUsed: Boolean = false private var version: Version = Version.Unversioned private var info: Future[Infos.ClassInfo] = _ @@ -86,26 +80,18 @@ private[analyzer] object InfoLoader { if (!version.sameVersion(newVersion)) { version = newVersion info = irLoader.loadClassDef(className).map { tree => - irCheckMode match { - case InfoLoader.NoIRCheck => - // no check - - case InfoLoader.InitialIRCheck => - val errorCount = ClassDefChecker.check(tree, - postBaseLinker = false, postOptimizer = false, logger) - if (errorCount != 0) { + for (previousPhase <- checkIRFor) { + val errorCount = ClassDefChecker.check(tree, previousPhase, logger) + if (errorCount != 0) { + if (previousPhase == CheckingPhase.Compiler) { throw new LinkingException( s"There were $errorCount ClassDef checking errors.") - } - - case InfoLoader.InternalIRCheck => - val errorCount = ClassDefChecker.check(tree, - postBaseLinker = true, postOptimizer = true, logger) - if (errorCount != 0) { + } else { throw new LinkingException( - s"There were $errorCount ClassDef checking errors after optimizing. " + + s"There were $errorCount ClassDef checking errors after transformations. " + "Please report this as a bug.") } + } } generateInfos(tree) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/CheckingPhase.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/CheckingPhase.scala new file mode 100644 index 0000000000..ff198744de --- /dev/null +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/CheckingPhase.scala @@ -0,0 +1,28 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.linker.checker + +/** A phase *after which* we are checking IR. + * + * When checking IR (with `ClassDefChecker` or `IRChecker`), different nodes + * and transients are allowed between different phases. The `CheckingPhase` + * records the *previous* phase to run before the check. We are therefore + * checking that the IR is a valid *output* of the target phase. + */ +sealed abstract class CheckingPhase + +object CheckingPhase { + case object Compiler extends CheckingPhase + case object BaseLinker extends CheckingPhase + case object Optimizer extends CheckingPhase +} diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index 103d19f963..b3dd10acb8 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -28,11 +28,13 @@ import org.scalajs.linker.standard.LinkedClass /** Checker for the validity of the IR. */ private final class ClassDefChecker(classDef: ClassDef, - postBaseLinker: Boolean, postOptimizer: Boolean, reporter: ErrorReporter) { + previousPhase: CheckingPhase, reporter: ErrorReporter) { import ClassDefChecker._ import reporter.reportError + private val featureSet = FeatureSet.allowedAfter(previousPhase) + private[this] val isJLObject = classDef.name.name == ObjectClass private[this] val instanceThisType: Type = { @@ -107,7 +109,7 @@ private final class ClassDefChecker(classDef: ClassDef, * module classes and JS classes that are never instantiated. The classes * may still exist because their class data are accessed. */ - if (!postBaseLinker) { + if (!featureSet.supports(FeatureSet.OptionalConstructors)) { /* Check that we have exactly 1 constructor in a module class. This goes * together with `checkMethodDef`, which checks that a constructor in a * module class must be 0-arg. @@ -489,7 +491,7 @@ private final class ClassDefChecker(classDef: ClassDef, private def checkMethodNameNamespace(name: MethodName, namespace: MemberNamespace)( implicit ctx: ErrorContext): Unit = { if (name.isReflectiveProxy) { - if (postBaseLinker) { + if (featureSet.supports(FeatureSet.ReflectiveProxies)) { if (namespace != MemberNamespace.Public) reportError("reflective profixes are only allowed in the public namespace") } else { @@ -557,7 +559,7 @@ private final class ClassDefChecker(classDef: ClassDef, } if (rest.isEmpty) { - if (!postOptimizer) + if (!featureSet.supports(FeatureSet.RelaxedCtorBodies)) reportError(i"Constructor must contain a delegate constructor call") val bodyStatsStoreModulesHandled = @@ -576,7 +578,7 @@ private final class ClassDefChecker(classDef: ClassDef, checkTree(receiver, unrestrictedEnv) // check that the This itself is valid - if (!postOptimizer) { + if (!featureSet.supports(FeatureSet.RelaxedCtorBodies)) { if (!(cls == classDef.className || classDef.superClass.exists(_.name == cls))) { implicit val ctx = ErrorContext(delegateCtorCall) reportError( @@ -597,7 +599,7 @@ private final class ClassDefChecker(classDef: ClassDef, implicit ctx: ErrorContext): List[Tree] = { if (classDef.kind.hasModuleAccessor) { - if (postOptimizer) { + if (featureSet.supports(FeatureSet.RelaxedCtorBodies)) { /* If the super constructor call was inlined, the StoreModule can be anywhere. * Moreover, the optimizer can remove StoreModules altogether in many cases. */ @@ -673,7 +675,7 @@ private final class ClassDefChecker(classDef: ClassDef, case Assign(lhs, rhs) => lhs match { case Select(This(), field) if env.isThisRestricted => - if (postOptimizer || field.name.className == classDef.className) + if (featureSet.supports(FeatureSet.RelaxedCtorBodies) || field.name.className == classDef.className) checkTree(lhs, env.withIsThisRestricted(false)) else checkTree(lhs, env) @@ -819,11 +821,13 @@ private final class ClassDefChecker(classDef: ClassDef, checkTree(index, env) case RecordSelect(record, _) => - checkAllowTransients() + if (!featureSet.supports(FeatureSet.Records)) + reportError("invalid use of records") checkTree(record, env) case RecordValue(_, elems) => - checkAllowTransients() + if (!featureSet.supports(FeatureSet.Records)) + reportError("invalid use of records") checkTrees(elems, env) case IsInstanceOf(expr, testType) => @@ -987,7 +991,9 @@ private final class ClassDefChecker(classDef: ClassDef, checkTrees(captureValues, env) case Transient(transient) => - checkAllowTransients() + if (!featureSet.supports(FeatureSet.OptimizedTransients)) + reportError(i"invalid transient tree of class ${transient.getClass().getName()}") + transient.traverse(new Traversers.Traverser { override def traverse(tree: Tree): Unit = checkTree(tree, env) }) @@ -996,11 +1002,6 @@ private final class ClassDefChecker(classDef: ClassDef, newEnv } - private def checkAllowTransients()(implicit ctx: ErrorContext): Unit = { - if (!postOptimizer) - reportError("invalid transient tree") - } - private def checkArrayType(tpe: ArrayType)( implicit ctx: ErrorContext): Unit = { checkArrayTypeRef(tpe.arrayTypeRef) @@ -1036,13 +1037,13 @@ object ClassDefChecker { * * @return Count of IR checking errors (0 in case of success) */ - def check(classDef: ClassDef, postBaseLinker: Boolean, postOptimizer: Boolean, logger: Logger): Int = { + def check(classDef: ClassDef, previousPhase: CheckingPhase, logger: Logger): Int = { val reporter = new LoggerErrorReporter(logger) - new ClassDefChecker(classDef, postBaseLinker, postOptimizer, reporter).checkClassDef() + new ClassDefChecker(classDef, previousPhase, reporter).checkClassDef() reporter.errorCount } - def check(linkedClass: LinkedClass, postOptimizer: Boolean, logger: Logger): Int = { + def check(linkedClass: LinkedClass, previousPhase: CheckingPhase, logger: Logger): Int = { // Rebuild a ClassDef out of the LinkedClass import linkedClass._ implicit val pos = linkedClass.pos @@ -1063,7 +1064,7 @@ object ClassDefChecker { topLevelExportDefs = Nil )(optimizerHints) - check(classDef, postBaseLinker = true, postOptimizer, logger) + check(classDef, previousPhase, logger) } private class Env( diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/FeatureSet.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/FeatureSet.scala new file mode 100644 index 0000000000..73f89b0d8b --- /dev/null +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/FeatureSet.scala @@ -0,0 +1,76 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.linker.checker + +import org.scalajs.linker.checker.CheckingPhase._ + +/** A set of conditional IR features that the checkers can accept. + * + * At any given phase, the `ClassDefChecker` and the `IRChecker` must agree on + * the set of IR features that are valid. A `FeatureSet` factors out the + * knowledge of what feature is acceptable when. + */ +private[checker] final class FeatureSet private (private val flags: Int) extends AnyVal { + /** Does this feature set support (all of) the given feature set. */ + def supports(features: FeatureSet): Boolean = + (features.flags & flags) == features.flags + + /** The union of this feature set and `that` feature set. */ + def |(that: FeatureSet): FeatureSet = + new FeatureSet(this.flags | that.flags) +} + +private[checker] object FeatureSet { + /** Empty feature set. */ + val Empty = new FeatureSet(0) + + // Individual features + + /** Optional constructors in module classes and JS classes. */ + val OptionalConstructors = new FeatureSet(1 << 0) + + /** Explicit reflective proxy definitions. */ + val ReflectiveProxies = new FeatureSet(1 << 1) + + /** Transients that are the result of optimizations. */ + val OptimizedTransients = new FeatureSet(1 << 2) + + /** Records and record types. */ + val Records = new FeatureSet(1 << 3) + + /** Relaxed constructor discipline. + * + * - Optional super/delegate constructor call. + * - Delegate constructor calls can target any super class. + * - `this.x = ...` assignments before the delegate call can assign super class fields. + * - `StoreModule` can be anywhere, or not be there at all. + */ + val RelaxedCtorBodies = new FeatureSet(1 << 4) + + // Common sets + + /** Features introduced by the base linker. */ + private val Linked = + OptionalConstructors | ReflectiveProxies + + /** IR that is only the result of optimizations. */ + private val Optimized = + OptimizedTransients | Records | RelaxedCtorBodies + + /** The set of features allowed as output of the given phase. */ + def allowedAfter(phase: CheckingPhase): FeatureSet = phase match { + case Compiler => Empty + case BaseLinker => Linked + case Optimizer => Linked | Optimized + } +} diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index a749e4807e..de827f396a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -29,11 +29,13 @@ import org.scalajs.linker.checker.ErrorReporter._ /** Checker for the validity of the IR. */ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, - postOptimizer: Boolean) { + previousPhase: CheckingPhase) { import IRChecker._ import reporter.reportError + private val featureSet = FeatureSet.allowedAfter(previousPhase) + private val classes: mutable.Map[ClassName, CheckedClass] = { val tups = for (classDef <- unit.classDefs) yield { implicit val ctx = ErrorContext(classDef) @@ -239,7 +241,7 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, case Assign(lhs, rhs) => def checkNonStaticField(receiver: Tree, name: FieldName): Unit = { receiver match { - case This() if (postOptimizer && env.inConstructorOf.isDefined) || + case This() if (featureSet.supports(FeatureSet.RelaxedCtorBodies) && env.inConstructorOf.isDefined) || env.inConstructorOf == Some(name.className) => /* ctors can write immutable fields of the class they are constructing. * postOptimizer, due to ctor inlining, we may write immutable parent class fields as well. @@ -717,12 +719,14 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, typecheckExpect(value, env, ctpe) } - case Transient(transient) if postOptimizer => + case Transient(transient) if featureSet.supports(FeatureSet.OptimizedTransients) => + // No precise rules, but at least check that its children type-check on their own transient.traverse(new Traversers.Traverser { override def traverse(tree: Tree): Unit = typecheck(tree, env) }) - case RecordSelect(record, SimpleFieldIdent(fieldName)) if postOptimizer => + case RecordSelect(record, SimpleFieldIdent(fieldName)) + if featureSet.supports(FeatureSet.Records) => record.tpe match { case NothingType => // ok @@ -742,7 +746,8 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, typecheck(record, env) - case RecordValue(RecordType(fields), elems) if postOptimizer => + case RecordValue(RecordType(fields), elems) + if featureSet.supports(FeatureSet.Records) => if (fields.size == elems.size) { for ((field, elem) <- fields.zip(elems)) typecheckExpect(elem, env, field.tpe) @@ -923,9 +928,9 @@ object IRChecker { * * @return Count of IR checking errors (0 in case of success) */ - def check(unit: LinkingUnit, logger: Logger, postOptimizer: Boolean = false): Int = { + def check(unit: LinkingUnit, logger: Logger, previousPhase: CheckingPhase): Int = { val reporter = new LoggerErrorReporter(logger) - new IRChecker(unit, reporter, postOptimizer).check() + new IRChecker(unit, reporter, previousPhase).check() reporter.errorCount } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala index 98b3fdf0de..ddf5b71f14 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala @@ -36,8 +36,10 @@ final class BaseLinker(config: CommonPhaseConfig, checkIR: Boolean) { import BaseLinker._ private val irLoader = new FileIRLoader - private val analyzer = - new Analyzer(config, initial = true, checkIR = checkIR, failOnError = true, irLoader) + private val analyzer = { + val checkIRFor = if (checkIR) Some(CheckingPhase.Compiler) else None + new Analyzer(config, initial = true, checkIRFor, failOnError = true, irLoader) + } private val methodSynthesizer = new MethodSynthesizer(irLoader) def link(irInput: Seq[IRFile], @@ -56,7 +58,7 @@ final class BaseLinker(config: CommonPhaseConfig, checkIR: Boolean) { } yield { if (checkIR) { logger.time("Linker: Check IR") { - val errorCount = IRChecker.check(linkResult, logger) + val errorCount = IRChecker.check(linkResult, logger, CheckingPhase.BaseLinker) if (errorCount != 0) { throw new LinkingException( s"There were $errorCount IR checking errors.") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala index a263a7b388..0f074adf55 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala @@ -20,7 +20,7 @@ import org.scalajs.ir.Trees.ClassDef import org.scalajs.logging._ -import org.scalajs.linker.checker.IRChecker +import org.scalajs.linker.checker._ import org.scalajs.linker.interface.ModuleInitializer import org.scalajs.linker.standard._ import org.scalajs.linker.standard.ModuleSet.ModuleID @@ -31,8 +31,10 @@ final class Refiner(config: CommonPhaseConfig, checkIR: Boolean) { import Refiner._ private val irLoader = new ClassDefIRLoader - private val analyzer = - new Analyzer(config, initial = false, checkIR = checkIR, failOnError = true, irLoader) + private val analyzer = { + val checkIRFor = if (checkIR) Some(CheckingPhase.Optimizer) else None + new Analyzer(config, initial = false, checkIRFor, failOnError = true, irLoader) + } /* TODO: Remove this and replace with `checkIR` once the optimizer generates * well-typed IR with runtime longs. @@ -79,7 +81,7 @@ final class Refiner(config: CommonPhaseConfig, checkIR: Boolean) { if (shouldRunIRChecker) { logger.time("Refiner: Check IR") { - val errorCount = IRChecker.check(result, logger, postOptimizer = true) + val errorCount = IRChecker.check(result, logger, CheckingPhase.Optimizer) if (errorCount != 0) { throw new AssertionError( s"There were $errorCount IR checking errors after optimization (this is a Scala.js bug)") diff --git a/linker/shared/src/test/scala/org/scalajs/linker/BaseLinkerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/BaseLinkerTest.scala index 1997d0f789..22033b7d3c 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/BaseLinkerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/BaseLinkerTest.scala @@ -24,7 +24,7 @@ import org.scalajs.junit.async._ import org.scalajs.logging._ -import org.scalajs.linker.checker.ClassDefChecker +import org.scalajs.linker.checker._ import org.scalajs.linker.interface.StandardConfig import org.scalajs.linker.standard._ @@ -86,7 +86,8 @@ class BaseLinkerTest { for (moduleSet <- linkToModuleSet(classDefs, MainTestModuleInitializers, config = config)) yield { val clazz = findClass(moduleSet, BoxedIntegerClass).get - val errorCount = ClassDefChecker.check(clazz, postOptimizer = false, + val previousPhase = CheckingPhase.BaseLinker + val errorCount = ClassDefChecker.check(clazz, previousPhase, new ScalaConsoleLogger(Level.Error)) assertEquals(0, errorCount) } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala index 2d22331cc0..8bbf5ff00a 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala @@ -257,7 +257,7 @@ class ClassDefCheckerTest { ) ), "reflective profixes are only allowed in the public namespace", - allowReflectiveProxies = true + previousPhase = CheckingPhase.BaseLinker ) } @@ -817,13 +817,13 @@ class ClassDefCheckerTest { assertError( mainTestClassDef(Assign(RecordSelect(int(5), "i")(IntType), int(6))), "Assignment to RecordSelect of illegal tree: org.scalajs.ir.Trees$IntLiteral", - allowTransients = true) + previousPhase = CheckingPhase.Optimizer) } } private object ClassDefCheckerTest { private def assertError(clazz: ClassDef, expectMsg: String, - allowReflectiveProxies: Boolean = false, allowTransients: Boolean = false) = { + previousPhase: CheckingPhase = CheckingPhase.Compiler): Unit = { var seen = false val reporter = new ErrorReporter { def reportError(msg: String)(implicit ctx: ErrorReporter.ErrorContext) = { @@ -833,7 +833,7 @@ private object ClassDefCheckerTest { } } - new ClassDefChecker(clazz, allowReflectiveProxies, allowTransients, reporter).checkClassDef() + new ClassDefChecker(clazz, previousPhase, reporter).checkClassDef() assertTrue("no errors reported", seen) } } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/testutils/LinkingUtils.scala b/linker/shared/src/test/scala/org/scalajs/linker/testutils/LinkingUtils.scala index f3854a7a76..68a59755ba 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/testutils/LinkingUtils.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/testutils/LinkingUtils.scala @@ -20,6 +20,7 @@ import org.scalajs.logging._ import org.scalajs.linker._ import org.scalajs.linker.analyzer._ +import org.scalajs.linker.checker.CheckingPhase import org.scalajs.linker.frontend.FileIRLoader import org.scalajs.linker.interface._ import org.scalajs.linker.standard._ @@ -101,8 +102,9 @@ object LinkingUtils { val injectedIRFiles = StandardLinkerBackend(config).injectedIRFiles val irLoader = new FileIRLoader + val checkIRFor = Some(CheckingPhase.Compiler) val analyzer = new Analyzer(CommonPhaseConfig.fromStandardConfig(config), - initial = true, checkIR = true, failOnError = false, irLoader) + initial = true, checkIRFor, failOnError = false, irLoader) val logger = new ScalaConsoleLogger(Level.Error) for { From ca39c30dd73285545293d285835df3054b778479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 31 Jan 2025 17:13:52 +0100 Subject: [PATCH 076/121] Remove ancient codegen opt for expanded Scala anon function classes. Since the very early days of Scala.js, we have had a code size optimization for lambdas expanded as anonymous classes. We tried very hard to recover the captures from fields, and the body of the `apply` method, in order to reconstruct a `js.Closure`. That made sense as long as we supported Scala 2.11 (and before that, 2.10). However, since Scala 2.12.x, scalac almost never expands lambdas of `scala.FunctionN`. Instead, they reach the backend as `Function` node. The only situations where scalac still expands lambdas as anonymous classes is if it used in an argument to a super constructor (or delegate `this()` constructor). The code paths for that code size optimization are therefore almost dead code, and do not pull their weight anymore. It was, AFAICT, the only place where we still *tried* to emit something a certain way, with a fallback. This commit removes the optimization and all the related infrastructure. --- .../org/scalajs/nscplugin/GenJSCode.scala | 187 ++++-------------- .../scalajs/nscplugin/test/JSSAMTest.scala | 4 +- 2 files changed, 39 insertions(+), 152 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 0ad5ed423e..60cea646b0 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -204,8 +204,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } // For anonymous methods - // These have a default, since we always read them. - private val tryingToGenMethodAsJSFunction = new ScopedVar[Boolean](false) + // It has a default, since we always read it. private val paramAccessorLocals = new ScopedVar(Map.empty[Symbol, js.ParamDef]) /* Contextual JS class value for some operations of nested JS classes that @@ -223,11 +222,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } - private class CancelGenMethodAsJSFunction(message: String) - extends scala.util.control.ControlThrowable { - override def getMessage(): String = message - } - // Rewriting of anonymous function classes --------------------------------- /** Start nested generation of a class. @@ -248,7 +242,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) undefinedDefaultParams := null, mutableLocalVars := null, mutatedLocalVars := null, - tryingToGenMethodAsJSFunction := false, paramAccessorLocals := Map.empty )(withNewLocalNameScope(body)) } @@ -387,21 +380,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) private val generatedStaticForwarderClasses = ListBuffer.empty[(Symbol, js.ClassDef)] private def consumeLazilyGeneratedAnonClass(sym: Symbol): ClassDef = { - /* If we are trying to generate an method as JSFunction, we cannot - * actually consume the symbol, since we might fail trying and retry. - * We will then see the same tree again and not find the symbol anymore. - * - * If we are sure this is the only generation, we remove the symbol to - * make sure we don't generate the same class twice. - */ - val optDef = { - if (tryingToGenMethodAsJSFunction) - lazilyGeneratedAnonClasses.get(sym) - else - lazilyGeneratedAnonClasses.remove(sym) - } - - optDef.getOrElse { + lazilyGeneratedAnonClasses.remove(sym).getOrElse { abort("Couldn't find tree for lazily generated anonymous class " + s"${sym.fullName} at ${sym.pos}") } @@ -450,25 +429,22 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } val allClassDefs = collectClassDefs(cunit.body) - /* There are three types of anonymous classes we want to generate - * only once we need them so we can inline them at construction site: + /* There are two types of anonymous classes we want to generate only + * once we need them, so we can inline them at construction site: * - * - anonymous class that are JS types, which includes: - * - lambdas for js.FunctionN and js.ThisFunctionN (SAMs). (We may - * not generate actual Scala classes for these). - * - anonymous (non-lambda) JS classes. These classes may not have - * their own prototype. Therefore, their constructor *must* be - * inlined. - * - lambdas for scala.FunctionN. This is only an optimization and may - * fail. In the case of failure, we fall back to generating a - * fully-fledged Scala class. + * - Lambdas for `js.Function` SAMs, including `js.FunctionN`, + * `js.ThisFunctionN` and custom JS function SAMs. We must generate + * JS functions for these, instead of actual classes. + * - Anonymous (non-lambda) JS classes. These classes may not have + * their own prototype. Therefore, their constructor *must* be + * inlined. * * Since for all these, we don't know how they inter-depend, we just * store them in a map at this point. */ val (lazyAnons, fullClassDefs) = allClassDefs.partition { cd => val sym = cd.symbol - isAnonymousJSClass(sym) || isJSFunctionDef(sym) || sym.isAnonymousFunction + isAnonymousJSClass(sym) || isJSFunctionDef(sym) } lazilyGeneratedAnonClasses ++= lazyAnons.map(cd => cd.symbol -> cd) @@ -2824,9 +2800,10 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) */ private def genThis()(implicit pos: Position): js.Tree = { thisLocalVarName.fold[js.Tree] { - if (tryingToGenMethodAsJSFunction) { - throw new CancelGenMethodAsJSFunction( - "Trying to generate `this` inside the body") + if (isJSFunctionDef(currentClassSym)) { + abort( + "Unexpected `this` reference inside the body of a JS function class: " + + currentClassSym.fullName) } js.This()(currentThisType) } { thisLocalName => @@ -3359,10 +3336,10 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } /** Gen JS code for a constructor call (new). + * * Further refined into: - * * new String(...) * * new of a hijacked boxed class - * * new of an anonymous function class that was recorded as JS function + * * new of a JS function class * * new of a JS class * * new Array * * regular new @@ -3382,13 +3359,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } else if (isJSFunctionDef(clsSym)) { val classDef = consumeLazilyGeneratedAnonClass(clsSym) genJSFunction(classDef, args.map(genExpr)) - } else if (clsSym.isAnonymousFunction) { - val classDef = consumeLazilyGeneratedAnonClass(clsSym) - tryGenAnonFunctionClass(classDef, args.map(genExpr)).getOrElse { - // Cannot optimize anonymous function class. Generate full class. - generatedClasses += nestedGenerateClass(clsSym)(genClass(classDef)) -> clsSym.pos - genNew(clsSym, ctor, genActualArgs(ctor, args)) - } } else if (isJSType(clsSym)) { genPrimitiveJSNew(tree) } else { @@ -6057,69 +6027,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) // Synthesizers for JS functions ------------------------------------------- - /** Try and generate JS code for an anonymous function class. - * - * Returns Some() if the class could be rewritten that way, None - * otherwise. - * - * We make the following assumptions on the form of such classes: - * - It is an anonymous function - * - Includes being anonymous, final, and having exactly one constructor - * - It is not a PartialFunction - * - It has no field other than param accessors - * - It has exactly one constructor - * - It has exactly one non-bridge method apply if it is not specialized, - * or a method apply$...$sp and a forwarder apply if it is specialized. - * - As a precaution: it is synthetic - * - * From a class looking like this: - * - * final class (outer, capture1, ..., captureM) extends AbstractionFunctionN[...] { - * def apply(param1, ..., paramN) = { - * - * } - * } - * new (o, c1, ..., cM) - * - * we generate a function: - * - * arrow-lambda(param1, ..., paramN) { - * - * } - * - * so that, at instantiation point, we can write: - * - * new AnonFunctionN(function) - * - * the latter tree is returned in case of success. - * - * Trickier things apply when the function is specialized. - */ - private def tryGenAnonFunctionClass(cd: ClassDef, - capturedArgs: List[js.Tree]): Option[js.Tree] = { - // scalastyle:off return - implicit val pos = cd.pos - val sym = cd.symbol - assert(sym.isAnonymousFunction, - s"tryGenAndRecordAnonFunctionClass called with non-anonymous function $cd") - - if (!sym.superClass.fullName.startsWith("scala.runtime.AbstractFunction")) { - /* This is an anonymous class for a non-LMF capable SAM in 2.12. - * We must not rewrite it, as it would then not inherit from the - * appropriate parent class and/or interface. - */ - None - } else { - nestedGenerateClass(sym) { - val (functionBase, arity) = - tryGenAnonFunctionClassGeneric(cd, capturedArgs)(_ => return None) - - Some(genJSFunctionToScala(functionBase, arity)) - } - } - // scalastyle:on return - } - /** Gen a conversion from a JavaScript function into a Scala function. */ private def genJSFunctionToScala(jsFunction: js.Tree, arity: Int)( implicit pos: Position): js.Tree = { @@ -6136,11 +6043,9 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * functions are not classes, we deconstruct the ClassDef, then * reconstruct it to be a genuine Closure. * - * Compared to `tryGenAnonFunctionClass()`, this function must - * always succeed, because we really cannot afford keeping them as - * anonymous classes. The good news is that it can do so, because the - * body of SAM lambdas is hoisted in the enclosing class. Hence, the - * apply() method is just a forwarder to calling that hoisted method. + * We can always do so, because the body of SAM lambdas is hoisted in the + * enclosing class. Hence, the apply() method is just a forwarder to + * calling that hoisted method. * * From a class looking like this: * @@ -6163,26 +6068,18 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) s"genAndRecordJSFunctionClass called with non-JS function $cd") nestedGenerateClass(sym) { - val (function, _) = tryGenAnonFunctionClassGeneric(cd, captures)(msg => - abort(s"Could not generate function for JS function: $msg")) - - function + genJSFunctionInner(cd, captures) } } - /** Code common to tryGenAndRecordAnonFunctionClass and - * genAndRecordJSFunctionClass. - */ - private def tryGenAnonFunctionClassGeneric(cd: ClassDef, - initialCapturedArgs: List[js.Tree])( - fail: (=> String) => Nothing): (js.Tree, Int) = { + /** The code of `genJSFunction` that is inside the `nestedGenerateClass` wrapper. */ + private def genJSFunctionInner(cd: ClassDef, + initialCapturedArgs: List[js.Tree]): js.Closure = { implicit val pos = cd.pos val sym = cd.symbol - // First checks - - if (sym.isSubClass(PartialFunctionClass)) - fail(s"Cannot rewrite PartialFunction $cd") + def fail(reason: String): Nothing = + abort(s"Could not generate function for JS function: $reason") // First step: find the apply method def, and collect param accessors @@ -6210,10 +6107,12 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) if (!ddsym.isPrimaryConstructor) fail(s"Non-primary constructor $ddsym in anon function $cd") } else { - val name = dd.name.toString - if (name == "apply" || (ddsym.isSpecialized && name.startsWith("apply$"))) { - if ((applyDef eq null) || ddsym.isSpecialized) + if (dd.name == nme.apply) { + if (!ddsym.isBridge) { + if (applyDef ne null) + fail(s"Found duplicate apply method $ddsym in $cd") applyDef = dd + } } else if (ddsym.hasAnnotation(JSOptionalAnnotation)) { // Ignore (this is useful for default parameters in custom JS function types) } else { @@ -6253,24 +6152,15 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) // Third step: emit the body of the apply method def val applyMethod = withScopedVars( - paramAccessorLocals := (paramAccessors zip ctorParamDefs).toMap, - tryingToGenMethodAsJSFunction := true + paramAccessorLocals := (paramAccessors zip ctorParamDefs).toMap ) { - try { - genMethodWithCurrentLocalNameScope(applyDef) - } catch { - case e: CancelGenMethodAsJSFunction => - fail(e.getMessage) - } + genMethodWithCurrentLocalNameScope(applyDef) } // Fourth step: patch the body to unbox parameters and box result - val hasRepeatedParam = { - sym.superClass == JSFunctionClass && // Scala functions are known not to have repeated params - enteringUncurry { - applyDef.symbol.paramss.flatten.lastOption.exists(isRepeated(_)) - } + val hasRepeatedParam = enteringUncurry { + applyDef.symbol.paramss.flatten.lastOption.exists(isRepeated(_)) } val js.MethodDef(_, _, _, params, _, body) = applyMethod @@ -6303,8 +6193,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val ok = patchedParams.nonEmpty if (!ok) { reporter.error(pos, - "The SAM or apply method for a js.ThisFunction must have a " + - "leading non-varargs parameter") + "The apply method for a js.ThisFunction must have a leading non-varargs parameter") } ok } @@ -6335,9 +6224,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } - val arity = params.size - - (closure, arity) + closure } } diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSSAMTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSSAMTest.scala index 3513fff2e3..4eedcb3447 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSSAMTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSSAMTest.scala @@ -126,10 +126,10 @@ class JSSAMTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:14: error: The SAM or apply method for a js.ThisFunction must have a leading non-varargs parameter + |newSource1.scala:14: error: The apply method for a js.ThisFunction must have a leading non-varargs parameter | val badThisFunction1: BadThisFunction1 = () => 42 | ^ - |newSource1.scala:15: error: The SAM or apply method for a js.ThisFunction must have a leading non-varargs parameter + |newSource1.scala:15: error: The apply method for a js.ThisFunction must have a leading non-varargs parameter | val badThisFunction2: BadThisFunction2 = args => args.size | ^ """ From 0181706748268e4e33cfc3b3c5e3438702732e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 4 Feb 2025 09:51:36 +0100 Subject: [PATCH 077/121] Wasm: Remove dead code jsLinkingInfo object. This was forgotten in 7bf410dd922662dabfc85f1d76d40431c1fe0910. --- .../linker/backend/wasmemitter/Emitter.scala | 20 ------------------- .../backend/wasmemitter/LoaderContent.scala | 3 +-- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala index fcbe987f50..51d1c99fd0 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala @@ -259,25 +259,6 @@ final class Emitter(config: Emitter.Config) { implicit val noPos = Position.NoPosition - // Linking info - val linkingInfo = { - // must be in sync with scala.scalajs.runtime.LinkingInfo - import config.coreSpec._ - import js.{BooleanLiteral => bool, IntLiteral => int, StringLiteral => str} - - def objectFreeze(tree: js.Tree): js.Tree = - js.Apply(js.DotSelect(js.VarRef(js.Ident("Object")), js.Ident("freeze")), tree :: Nil) - - objectFreeze(js.ObjectConstr(List( - str("esVersion") -> int(esFeatures.esVersion.edition), - str("assumingES6") -> bool(esFeatures.useECMAScript2015Semantics), // different name for historical reasons - str("isWebAssembly") -> bool(true), - str("productionMode") -> bool(semantics.productionMode), - str("linkerVersion") -> str(ScalaJSVersions.current), - str("fileLevelThis") -> js.This() - ))) - } - // Sort for stability val importedModules = module.externalDependencies.toList.sorted @@ -326,7 +307,6 @@ final class Emitter(config: Emitter.Config) { js.VarRef(loadFunIdent), List( js.StringLiteral(config.internalWasmFileURIPattern(module.id)), - linkingInfo, exportSettersDict, customJSHelpersDict ) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala index a096a66c5d..271894e7f5 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala @@ -180,10 +180,9 @@ const stringBuiltinPolyfills = { equals: (a, b) => a === b, }; -export async function load(wasmFileURL, linkingInfo, exportSetters, customJSHelpers) { +export async function load(wasmFileURL, exportSetters, customJSHelpers) { const myScalaJSHelpers = { ...scalaJSHelpers, - jsLinkingInfo: linkingInfo, idHashCodeMap: new WeakMap() }; const importsObj = { From bdd73fb9ea90c9cf0d15773d64bb42fb3a18312d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 31 Jan 2025 14:16:57 +0100 Subject: [PATCH 078/121] Make the guards for deserialization hacks clearer. Previously, deserialization hacks were guarded by the last IR version to which they applied. This was confusing when IR versions skipped one or more minor number, for example from 1.8 to 1.11. The version that introduces the hack was 1.11, but the guards were `hacks.use8`. Comments and error messages were typically disagreeing with that convention. For example, in that situation, they would refer to "prior to 1.11" or "1.11 introduced". That was another mismatch between the programmatic guards and the text in the comments. Overall this was very confusing. Now, we refer to the *first* IR version to which a hack must *not* be applied. This corresponds to the comments, and in general to the IR version that introduced the hack. --- In addition, we take the opportunity to use a unique `useBelow(V)` instead of individual `useV` methods. The fixed list of `useV` methods with their corresponding string versions had become too long. --- .../scala/org/scalajs/ir/Serializers.scala | 156 ++++++++---------- .../org/scalajs/ir/SerializersTest.scala | 61 +++++++ project/BinaryIncompatibilities.scala | 3 + 3 files changed, 135 insertions(+), 85 deletions(-) create mode 100644 ir/shared/src/test/scala/org/scalajs/ir/SerializersTest.scala diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index 4866e36457..dbd8a03f83 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -41,6 +41,14 @@ object Serializers { */ final val IRMagicNumber = 0xCAFE4A53 + /** A regex for a compatible stable binary IR version from which we may need + * to migrate things with hacks. + */ + private val CompatibleStableIRVersionRegex = { + val prefix = java.util.regex.Pattern.quote(ScalaJSVersions.binaryCross + ".") + new scala.util.matching.Regex(prefix + "(\\d+)") + } + // For deserialization hack private final val DynamicImportThunkClass = ClassName("scala.scalajs.runtime.DynamicImportThunk") @@ -1107,7 +1115,7 @@ object Serializers { case TagAssign => val lhs0 = readTree() - val lhs = if (hacks.use4 && lhs0.tpe == NothingType) { + val lhs = if (hacks.useBelow(5) && lhs0.tpe == NothingType) { /* Note [Nothing FieldDef rewrite] * (throw qual.field[null]) = rhs --> qual.field[null] = rhs */ @@ -1128,7 +1136,7 @@ object Serializers { case TagWhile => While(readTree(), readTree()) case TagDoWhile => - if (!hacks.use12) + if (!hacks.useBelow(13)) throw new IOException(s"Found invalid pre-1.13 DoWhile loop at $pos") // Rewrite `do { body } while (cond)` to `while ({ body; cond }) {}` val body = readTree() @@ -1153,7 +1161,7 @@ object Serializers { case TagLoadModule => LoadModule(readClassName()) case TagStoreModule => - if (hacks.use13) { + if (hacks.useBelow(16)) { val cls = readClassName() val rhs = readTree() rhs match { @@ -1172,7 +1180,7 @@ object Serializers { val field = readFieldIdent() val tpe = readType() - if (hacks.use4 && tpe == NothingType) { + if (hacks.useBelow(5) && tpe == NothingType) { /* Note [Nothing FieldDef rewrite] * qual.field[nothing] --> throw qual.field[null] */ @@ -1217,7 +1225,7 @@ object Serializers { case TagArrayLength | TagGetClass | TagClone | TagIdentityHashCode | TagWrapAsThrowable | TagUnwrapFromThrowable | TagThrow => - if (!hacks.use17) { + if (!hacks.useBelow(18)) { throw new IOException( s"Illegal legacy node $tag found in class ${enclosingClassName.nameString}") } @@ -1240,7 +1248,7 @@ object Serializers { UnaryOp(UnaryOp.UnwrapFromThrowable, checkNotNullLhs) case TagThrow => val patchedLhs = - if (hacks.use8) throwArgumentHack8(lhs) + if (hacks.useBelow(11)) throwArgumentHackBelow11(lhs) else lhs UnaryOp(UnaryOp.Throw, patchedLhs) } @@ -1253,7 +1261,7 @@ object Serializers { NewArray(arrayTypeRef, length) case _ => - if (hacks.use16) { + if (hacks.useBelow(17)) { // Rewrite as a call to j.l.r.Array.newInstance val ArrayTypeRef(base, origDims) = arrayTypeRef val newDims = origDims - lengths.size @@ -1284,7 +1292,7 @@ object Serializers { case TagIsInstanceOf => val expr = readTree() val testType0 = readType() - val testType = if (hacks.use16) { + val testType = if (hacks.useBelow(17)) { testType0 match { case ClassType(className, true) => ClassType(className, nullable = false) case ArrayType(arrayTypeRef, true) => ArrayType(arrayTypeRef, nullable = false) @@ -1302,7 +1310,7 @@ object Serializers { case TagJSPrivateSelect => JSPrivateSelect(readTree(), readFieldIdent()) case TagJSSelect => - if (hacks.use17 && buf.get(buf.position()) == TagJSLinkingInfo) { + if (hacks.useBelow(18) && buf.get(buf.position()) == TagJSLinkingInfo) { val jsLinkingInfo = readTree() readTree() match { case StringLiteral("productionMode") => @@ -1345,7 +1353,7 @@ object Serializers { case TagJSTypeOfGlobalRef => JSTypeOfGlobalRef(readTree().asInstanceOf[JSGlobalRef]) case TagJSLinkingInfo => - if (hacks.use17) { + if (hacks.useBelow(18)) { JSObjectConstr(List( (StringLiteral("productionMode"), LinkTimeProperty(ProductionMode)(BooleanType)), (StringLiteral("esVersion"), LinkTimeProperty(ESVersion)(IntType)), @@ -1374,7 +1382,7 @@ object Serializers { case TagVarRef => val name = - if (hacks.use17) readLocalIdent().name + if (hacks.useBelow(18)) readLocalIdent().name else readLocalName() VarRef(name)(readType()) @@ -1409,7 +1417,7 @@ object Serializers { } } - /** Patches the argument of a `Throw` for IR version until 1.8. + /** Patches the argument of a `Throw` for IR version below 1.11. * * Prior to Scala.js 1.11, `Throw(e)` was emitted by the compiler with * the somewhat implied assumption that it would "throw an NPE" (but @@ -1443,7 +1451,7 @@ object Serializers { * `AnyType`. We can accurately use that test to know whether we need to * apply the patch. */ - private def throwArgumentHack8(expr: Tree)(implicit pos: Position): Tree = { + private def throwArgumentHackBelow11(expr: Tree)(implicit pos: Position): Tree = { if (expr.tpe == AnyType) expr else if (!expr.tpe.isNullable) @@ -1465,11 +1473,11 @@ object Serializers { val originalName = readOriginalName() val kind = ClassKind.fromByte(readByte()) - if (hacks.use16) { + if (hacks.useBelow(17)) { thisTypeForHack = kind match { case ClassKind.Class | ClassKind.ModuleClass | ClassKind.Interface => Some(ClassType(cls, nullable = false)) - case ClassKind.HijackedClass if hacks.use8 => + case ClassKind.HijackedClass if hacks.useBelow(11) => // Use getOrElse as safety guard for otherwise invalid inputs Some(BoxedClassToPrimType.getOrElse(cls, ClassType(cls, nullable = false))) case _ => @@ -1484,7 +1492,7 @@ object Serializers { val superClass = readOptClassIdent() val parents = readClassIdents() - if (hacks.use17 && kind.isClass) { + if (hacks.useBelow(18) && kind.isClass) { /* In 1.18, we started enforcing the constructor chaining discipline. * Unfortunately, we used to generate a wrong super constructor call in * synthetic classes extending `DynamicImportThunk`, so we patch them. @@ -1493,7 +1501,7 @@ object Serializers { superClass.exists(_.name == DynamicImportThunkClass) } - /* jsSuperClass is not hacked like in readMemberDef.bodyHack5. The + /* jsSuperClass is not hacked like in readMemberDef.bodyHackBelow6. The * compilers before 1.6 always use a simple VarRef() as jsSuperClass, * when there is one, so no hack is required. */ @@ -1528,22 +1536,22 @@ object Serializers { val methods = { val methods0 = methodsBuilder.result() - if (hacks.use4 && kind.isJSClass) { + if (hacks.useBelow(5) && kind.isJSClass) { // #4409: Filter out abstract methods in non-native JS classes for version < 1.5 methods0.filter(_.body.isDefined) - } else if (hacks.use16 && cls == ClassClass) { - jlClassMethodsHack16(methods0) - } else if (hacks.use16 && cls == HackNames.ReflectArrayModClass) { - jlReflectArrayMethodsHack16(methods0) + } else if (hacks.useBelow(17) && cls == ClassClass) { + jlClassMethodsHackBelow17(methods0) + } else if (hacks.useBelow(17) && cls == HackNames.ReflectArrayModClass) { + jlReflectArrayMethodsHackBelow17(methods0) } else { methods0 } } val (jsConstructor, jsMethodProps) = { - if (hacks.use8 && kind.isJSClass) { - assert(jsConstructorBuilder.result().isEmpty, "found JSConstructorDef in pre 1.8 IR") - jsConstructorHack(kind, jsMethodPropsBuilder.result()) + if (hacks.useBelow(11) && kind.isJSClass) { + assert(jsConstructorBuilder.result().isEmpty, "found JSConstructorDef in pre 1.11 IR") + jsConstructorHackBelow11(kind, jsMethodPropsBuilder.result()) } else { (jsConstructorBuilder.result(), jsMethodPropsBuilder.result()) } @@ -1557,7 +1565,7 @@ object Serializers { optimizerHints) } - private def jlClassMethodsHack16(methods: List[MethodDef]): List[MethodDef] = { + private def jlClassMethodsHackBelow17(methods: List[MethodDef]): List[MethodDef] = { for (method <- methods) yield { implicit val pos = method.pos @@ -1621,7 +1629,7 @@ object Serializers { } } - private def jlReflectArrayMethodsHack16(methods: List[MethodDef]): List[MethodDef] = { + private def jlReflectArrayMethodsHackBelow17(methods: List[MethodDef]): List[MethodDef] = { /* Basically this method hard-codes new implementations for the two * overloads of newInstance. * It is horrible, but better than pollute everything else in the linker. @@ -1803,7 +1811,7 @@ object Serializers { newInstanceRecMethod :: newMethods } - private def jsConstructorHack(ownerKind: ClassKind, + private def jsConstructorHackBelow11(ownerKind: ClassKind, jsMethodProps: List[JSMethodPropDef]): (Option[JSConstructorDef], List[JSMethodPropDef]) = { val jsConstructorBuilder = new OptionBuilder[JSConstructorDef] val jsMethodPropsBuilder = List.newBuilder[JSMethodPropDef] @@ -1847,7 +1855,7 @@ object Serializers { val originalName = readOriginalName() val ftpe0 = readType() - val ftpe = if (hacks.use4 && ftpe0 == NothingType) { + val ftpe = if (hacks.useBelow(5) && ftpe0 == NothingType) { /* Note [Nothing FieldDef rewrite] * val field: nothing --> val field: null */ @@ -1881,7 +1889,7 @@ object Serializers { * rewrite it as a static initializers instead (``). */ val name0 = readMethodIdent() - if (hacks.use1 && + if (hacks.useBelow(2) && name0.name == ClassInitializerName && !ownerKind.isJSType) { MethodIdent(StaticInitializerName)(name0.pos) @@ -1896,11 +1904,11 @@ object Serializers { val body = readOptTree() val optimizerHints = OptimizerHints.fromBits(readInt()) - if (hacks.use0 && + if (hacks.useBelow(1) && flags.namespace == MemberNamespace.Public && owner == HackNames.SystemModule && name.name == HackNames.identityHashCodeName) { - /* #3976: 1.0 javalib relied on wrong linker dispatch. + /* #3976: Before 1.1, the javalib relied on wrong linker dispatch. * We simply replace it with a correct implementation. */ assert(args.size == 1) @@ -1910,7 +1918,7 @@ object Serializers { MethodDef(flags, name, originalName, args, resultType, patchedBody)( patchedOptimizerHints, optHash) - } else if (hacks.use4 && + } else if (hacks.useBelow(5) && flags.namespace == MemberNamespace.Public && owner == ObjectClass && name.name == HackNames.cloneName) { @@ -1943,7 +1951,7 @@ object Serializers { MethodDef(flags, name, originalName, args, resultType, patchedBody)( patchedOptimizerHints, optHash) } else { - val patchedBody = body.map(bodyHack5(_, isStat = resultType == VoidType)) + val patchedBody = body.map(bodyHackBelow6(_, isStat = resultType == VoidType)) MethodDef(flags, name, originalName, args, resultType, patchedBody)( optimizerHints, optHash) } @@ -1958,7 +1966,7 @@ object Serializers { assert(len >= 0) /* JSConstructorDef was introduced in 1.11. Therefore, by - * construction, they never need the body hack of 1.5. + * construction, they never need the body hack below 1.6. */ val flags = MemberFlags.fromBits(readInt()) @@ -1975,7 +1983,7 @@ object Serializers { private def maybeHackJSConstructorDefAfterSuper(ownerKind: ClassKind, afterSuper0: List[Tree], superCallPos: Position): List[Tree] = { - if (hacks.use17 && ownerKind == ClassKind.JSModuleClass) { + if (hacks.useBelow(18) && ownerKind == ClassKind.JSModuleClass) { afterSuper0 match { case StoreModule() :: _ => afterSuper0 case _ => StoreModule()(superCallPos) :: afterSuper0 @@ -1992,16 +2000,16 @@ object Serializers { assert(len >= 0) val flags = MemberFlags.fromBits(readInt()) - val name = bodyHack5Expr(readTree()) + val name = bodyHackBelow6Expr(readTree()) val (params, restParam) = readParamDefsWithRest() - val body = bodyHack5Expr(readTree()) + val body = bodyHackBelow6Expr(readTree()) JSMethodDef(flags, name, params, restParam, body)( OptimizerHints.fromBits(readInt()), optHash) } private def readJSPropertyDef()(implicit pos: Position): JSPropertyDef = { val optHash: Version = { - if (hacks.use12) { + if (hacks.useBelow(13)) { Unversioned } else { val optHash = readOptHash() @@ -2013,11 +2021,11 @@ object Serializers { } val flags = MemberFlags.fromBits(readInt()) - val name = bodyHack5Expr(readTree()) - val getterBody = readOptTree().map(bodyHack5Expr(_)) + val name = bodyHackBelow6Expr(readTree()) + val getterBody = readOptTree().map(bodyHackBelow6Expr(_)) val setterArgAndBody = { if (readBoolean()) - Some((readParamDef(), bodyHack5Expr(readTree()))) + Some((readParamDef(), bodyHackBelow6Expr(readTree()))) else None } @@ -2037,7 +2045,7 @@ object Serializers { * not derived from their children like Block or TryFinally, or * constant like While). */ - private object BodyHack5Transformer extends Transformers.Transformer { + private object BodyHackBelow6Transformer extends Transformers.Transformer { def transformStat(tree: Tree): Tree = { implicit val pos = tree.pos @@ -2090,11 +2098,11 @@ object Serializers { else transform(tree) } - private def bodyHack5(body: Tree, isStat: Boolean): Tree = - if (!hacks.use5) body - else BodyHack5Transformer.transform(body, isStat) + private def bodyHackBelow6(body: Tree, isStat: Boolean): Tree = + if (!hacks.useBelow(6)) body + else BodyHackBelow6Transformer.transform(body, isStat) - private def bodyHack5Expr(body: Tree): Tree = bodyHack5(body, isStat = false) + private def bodyHackBelow6Expr(body: Tree): Tree = bodyHackBelow6(body, isStat = false) def readTopLevelExportDef(): TopLevelExportDef = { implicit val pos = readPosition() @@ -2108,7 +2116,7 @@ object Serializers { } def readModuleID(): String = - if (hacks.use2) DefaultModuleID + if (hacks.useBelow(3)) DefaultModuleID else readString() (tag: @switch) match { @@ -2173,7 +2181,7 @@ object Serializers { val ptpe = readType() val mutable = readBoolean() - if (hacks.use4) { + if (hacks.useBelow(5)) { val rest = readBoolean() assert(!rest, "Illegal rest parameter") } @@ -2185,7 +2193,7 @@ object Serializers { List.fill(readInt())(readParamDef()) def readParamDefsWithRest(): (List[ParamDef], Option[ParamDef]) = { - if (hacks.use4) { + if (hacks.useBelow(5)) { val (params, isRest) = List.fill(readInt()) { implicit val pos = readPosition() (ParamDef(readLocalIdent(), readOriginalName(), readType(), readBoolean()), readBoolean()) @@ -2360,7 +2368,7 @@ object Serializers { /* Before 1.18, `LabelName`s were always wrapped in `LabelIdent`s, whose * encoding was a `Position` followed by the actual `LabelName`. */ - if (hacks.use17) + if (hacks.useBelow(18)) readPosition() // intentional discard val i = readInt() @@ -2476,41 +2484,19 @@ object Serializers { } } - /** Hacks for backwards compatible deserializing. */ - private final class Hacks(sourceVersion: String) { - val use0: Boolean = sourceVersion == "1.0" - - val use1: Boolean = use0 || sourceVersion == "1.1" - - val use2: Boolean = use1 || sourceVersion == "1.2" - - private val use3: Boolean = use2 || sourceVersion == "1.3" - - val use4: Boolean = use3 || sourceVersion == "1.4" - - val use5: Boolean = use4 || sourceVersion == "1.5" - - private val use6: Boolean = use5 || sourceVersion == "1.6" - - private val use7: Boolean = use6 || sourceVersion == "1.7" - - val use8: Boolean = use7 || sourceVersion == "1.8" - - assert(sourceVersion != "1.9", "source version 1.9 does not exist") - assert(sourceVersion != "1.10", "source version 1.10 does not exist") - - private val use11: Boolean = use8 || sourceVersion == "1.11" - - val use12: Boolean = use11 || sourceVersion == "1.12" - - val use13: Boolean = use12 || sourceVersion == "1.13" - - assert(sourceVersion != "1.14", "source version 1.14 does not exist") - assert(sourceVersion != "1.15", "source version 1.15 does not exist") - - val use16: Boolean = use13 || sourceVersion == "1.16" + /** Hacks for backwards compatible deserializing. + * + * `private[ir]` for testing purposes only. + */ + private[ir] final class Hacks(sourceVersion: String) { + private val fromVersion = sourceVersion match { + case CompatibleStableIRVersionRegex(minorDigits) => minorDigits.toInt + case _ => Int.MaxValue // never use any hack + } - val use17: Boolean = use16 || sourceVersion == "1.17" + /** Should we use the hacks to migrate from an IR version below `targetVersion`? */ + def useBelow(targetVersion: Int): Boolean = + fromVersion < targetVersion } /** Names needed for hacks. */ diff --git a/ir/shared/src/test/scala/org/scalajs/ir/SerializersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/SerializersTest.scala new file mode 100644 index 0000000000..a8c18507d9 --- /dev/null +++ b/ir/shared/src/test/scala/org/scalajs/ir/SerializersTest.scala @@ -0,0 +1,61 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.ir + +import org.junit.Test +import org.junit.Assert._ + +class SerializersTest { + @Test def testHacksUseBelow(): Unit = { + import Serializers.Hacks + + val hacks1_0 = new Hacks("1.0") + assertFalse(hacks1_0.useBelow(0)) + assertTrue(hacks1_0.useBelow(1)) + assertTrue(hacks1_0.useBelow(5)) + assertTrue(hacks1_0.useBelow(15)) + assertTrue(hacks1_0.useBelow(1000)) + + val hacks1_7 = new Hacks("1.7") + assertFalse(hacks1_7.useBelow(0)) + assertFalse(hacks1_7.useBelow(1)) + assertFalse(hacks1_7.useBelow(5)) + assertFalse(hacks1_7.useBelow(7)) + assertTrue(hacks1_7.useBelow(8)) + assertTrue(hacks1_7.useBelow(15)) + assertTrue(hacks1_7.useBelow(1000)) + + val hacks1_50 = new Hacks("1.50") + assertFalse(hacks1_50.useBelow(0)) + assertFalse(hacks1_50.useBelow(1)) + assertFalse(hacks1_50.useBelow(5)) + assertFalse(hacks1_50.useBelow(15)) + assertTrue(hacks1_50.useBelow(1000)) + + // Non-stable versions never get any hacks + val hacks1_9_snapshot = new Hacks("1.9-SNAPSHOT") + assertFalse(hacks1_9_snapshot.useBelow(0)) + assertFalse(hacks1_9_snapshot.useBelow(1)) + assertFalse(hacks1_9_snapshot.useBelow(5)) + assertFalse(hacks1_9_snapshot.useBelow(15)) + assertFalse(hacks1_9_snapshot.useBelow(1000)) + + // Incompatible versions never get any hacks + val hacks2_5 = new Hacks("2.5") + assertFalse(hacks2_5.useBelow(0)) + assertFalse(hacks2_5.useBelow(1)) + assertFalse(hacks2_5.useBelow(5)) + assertFalse(hacks2_5.useBelow(15)) + assertFalse(hacks2_5.useBelow(1000)) + } +} diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 4713fe6bf8..af6f47f735 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -5,6 +5,9 @@ import com.typesafe.tools.mima.core.ProblemFilters._ object BinaryIncompatibilities { val IR = Seq( + // private, not an issue + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Serializers$Deserializer$BodyHack5Transformer$"), + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Serializers#Hacks.use*"), ) val Linker = Seq( From 38636cf028b0506835c5c0dea5b1ab31cefa7d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 25 Jan 2025 14:06:32 +0100 Subject: [PATCH 079/121] Bump the version to 1.19.0-SNAPSHOT for the upcoming changes. --- ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index 0f03e6a638..3f7c4d501e 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.18.3-SNAPSHOT", + current = "1.19.0-SNAPSHOT", binaryEmitted = "1.18" ) From 1987d8726b40405147bad5b4f3ee5bf06452afd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 29 Dec 2024 20:49:17 +0100 Subject: [PATCH 080/121] Add a desugaring pass between the base linker and the optimizer. Previously, the emitters and the optimizer all had to perform the same desugaring for `LinkTimeProperty` nodes. Instead, we now perform the desugaring in a dedicated phase, after the base linker. The reachability analysis records whether each method needs desugaring or not. We mark those that do so that the desugaring pass knows what to process. Methods that do not require desugaring are not processed, and so incur no additional cost. No caching is performed in `Desugarer`. It processes so few methods that caching makes it (slightly) *slower*. The machinery is heavy. It definitely outweighs the benefits in terms of duplication for `LinkTimeProperty` alone. However, the same machinery will be used to desugar `NewLambda` nodes. This commit serves as a stepping stone in that direction. --- .../scala/org/scalajs/ir/Transformers.scala | 22 ++- .../scalajs/linker/analyzer/Analysis.scala | 4 + .../scalajs/linker/analyzer/Analyzer.scala | 11 +- .../org/scalajs/linker/analyzer/Infos.scala | 2 + .../backend/emitter/FunctionEmitter.scala | 10 +- .../backend/wasmemitter/DerivedClasses.scala | 1 + .../backend/wasmemitter/FunctionEmitter.scala | 9 +- .../linker/checker/CheckingPhase.scala | 1 + .../linker/checker/ClassDefChecker.scala | 2 + .../scalajs/linker/checker/FeatureSet.scala | 28 ++- .../scalajs/linker/checker/IRChecker.scala | 5 +- .../scalajs/linker/frontend/BaseLinker.scala | 32 ++-- .../scalajs/linker/frontend/Desugarer.scala | 159 ++++++++++++++++++ .../linker/frontend/LinkerFrontendImpl.scala | 13 +- .../frontend/optimizer/OptimizerCore.scala | 3 - .../scalajs/linker/standard/LinkedClass.scala | 44 ++++- .../standard/LinkedTopLevelExport.scala | 3 +- .../org/scalajs/linker/IRCheckerTest.scala | 50 +++++- project/BinaryIncompatibilities.scala | 3 + 19 files changed, 342 insertions(+), 60 deletions(-) create mode 100644 linker/shared/src/main/scala/org/scalajs/linker/frontend/Desugarer.scala diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala index bbc0c3350b..a2edeaf797 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala @@ -234,14 +234,8 @@ object Transformers { case jsMethodDef: JSMethodDef => transformJSMethodDef(jsMethodDef) - case JSPropertyDef(flags, name, getterBody, setterArgAndBody) => - JSPropertyDef( - flags, - transform(name), - transformTreeOpt(getterBody), - setterArgAndBody.map { case (arg, body) => - (arg, transform(body)) - })(Unversioned)(jsMethodPropDef.pos) + case jsPropertyDef: JSPropertyDef => + transformJSPropertyDef(jsPropertyDef) } } @@ -251,6 +245,18 @@ object Transformers { jsMethodDef.optimizerHints, Unversioned)(jsMethodDef.pos) } + def transformJSPropertyDef(jsPropertyDef: JSPropertyDef): JSPropertyDef = { + val JSPropertyDef(flags, name, getterBody, setterArgAndBody) = jsPropertyDef + JSPropertyDef( + flags, + transform(name), + transformTreeOpt(getterBody), + setterArgAndBody.map { case (arg, body) => + (arg, transform(body)) + } + )(Unversioned)(jsPropertyDef.pos) + } + def transformJSConstructorBody(body: JSConstructorBody): JSConstructorBody = { implicit val pos = body.pos diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala index 0c1b0118e5..781fc30c48 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala @@ -84,6 +84,8 @@ object Analysis { def methodInfos( namespace: MemberNamespace): scala.collection.Map[MethodName, MethodInfo] + def anyJSMemberNeedsDesugaring: Boolean + def displayName: String = className.nameString } @@ -103,6 +105,7 @@ object Analysis { def instantiatedSubclasses: scala.collection.Seq[ClassInfo] def nonExistent: Boolean def syntheticKind: MethodSyntheticKind + def needsDesugaring: Boolean def displayName: String = methodName.displayName @@ -161,6 +164,7 @@ object Analysis { def owningClass: ClassName def staticDependencies: scala.collection.Set[ClassName] def externalDependencies: scala.collection.Set[String] + def needsDesugaring: Boolean } sealed trait Error { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala index 9dc14c1574..e1faf2fa1a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala @@ -686,6 +686,9 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean, val publicMethodInfos: mutable.Map[MethodName, MethodInfo] = methodInfos(MemberNamespace.Public) + def anyJSMemberNeedsDesugaring: Boolean = + data.jsMethodProps.exists(info => (info.globalFlags & ReachabilityInfo.FlagNeedsDesugaring) != 0) + def lookupAbstractMethod(methodName: MethodName): MethodInfo = { val candidatesIterator = for { ancestor <- ancestors.iterator @@ -1285,6 +1288,9 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean, def isDefaultBridge: Boolean = syntheticKind.isInstanceOf[MethodSyntheticKind.DefaultBridge] + def needsDesugaring: Boolean = + (data.globalFlags & ReachabilityInfo.FlagNeedsDesugaring) != 0 + /** Throws MatchError if `!isDefaultBridge`. */ def defaultBridgeTarget: ClassName = (syntheticKind: @unchecked) match { case MethodSyntheticKind.DefaultBridge(target) => target @@ -1367,6 +1373,9 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean, def staticDependencies: scala.collection.Set[ClassName] = _staticDependencies.keySet def externalDependencies: scala.collection.Set[String] = _externalDependencies.keySet + def needsDesugaring: Boolean = + (data.reachability.globalFlags & ReachabilityInfo.FlagNeedsDesugaring) != 0 + def reach(): Unit = followReachabilityInfo(data.reachability, this)(FromExports) } @@ -1441,7 +1450,7 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean, } } - val globalFlags = data.globalFlags + val globalFlags = data.globalFlags & ~ReachabilityInfo.FlagNeedsDesugaring if (globalFlags != 0) { if ((globalFlags & ReachabilityInfo.FlagAccessedClassClass) != 0) { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala index 1458e107f4..c713d5799e 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala @@ -115,6 +115,7 @@ object Infos { final val FlagAccessedImportMeta = 1 << 2 final val FlagUsedExponentOperator = 1 << 3 final val FlagUsedClassSuperClass = 1 << 4 + final val FlagNeedsDesugaring = 1 << 5 } /** Things from a given class that are reached by one method. */ @@ -395,6 +396,7 @@ object Infos { setFlag(ReachabilityInfo.FlagUsedClassSuperClass) def addReferencedLinkTimeProperty(linkTimeProperty: LinkTimeProperty): this.type = { + setFlag(ReachabilityInfo.FlagNeedsDesugaring) linkTimeProperties.append((linkTimeProperty.name, linkTimeProperty.tpe)) this } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index df5ed07b70..2252d71cd4 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -1260,9 +1260,8 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { def test(tree: Tree): Boolean = tree match { // Atomic expressions - case _: Literal => true - case _: JSNewTarget => true - case _: LinkTimeProperty => true + case _: Literal => true + case _: JSNewTarget => true // Vars (side-effect free, pure if immutable) case VarRef(name) => @@ -2811,11 +2810,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case AsInstanceOf(expr, tpe) => extractWithGlobals(genAsInstanceOf(transformExprNoChar(expr), tpe)) - case prop: LinkTimeProperty => - transformExpr( - config.coreSpec.linkTimeProperties.transformLinkTimeProperty(prop), - preserveChar) - // Transients case Transient(Cast(expr, tpe)) => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/DerivedClasses.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/DerivedClasses.scala index 1935e130fc..c65065dab5 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/DerivedClasses.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/DerivedClasses.scala @@ -146,6 +146,7 @@ object DerivedClasses { staticDependencies = Set.empty, externalDependencies = Set.empty, dynamicDependencies = Set.empty, + desugaringRequirements = LinkedClass.DesugaringRequirements.Empty, clazz.version ) } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index 7ef7a87ac3..68e1ab881d 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -550,7 +550,6 @@ private class FunctionEmitter private ( case t: Match => genMatch(t, expectedType) case t: Debugger => VoidType // ignore case t: Skip => VoidType - case t: LinkTimeProperty => genLinkTimeProperty(t) // JavaScript expressions case t: JSNew => genJSNew(t) @@ -590,7 +589,7 @@ private class FunctionEmitter private ( // Transients (only generated by the optimizer) case t: Transient => genTransient(t) - case _: JSSuperConstructorCall => + case _:JSSuperConstructorCall | _:LinkTimeProperty => throw new AssertionError(s"Invalid tree: $tree") } @@ -2649,12 +2648,6 @@ private class FunctionEmitter private ( ClassType(boxClassName, nullable = false) } - private def genLinkTimeProperty(tree: LinkTimeProperty): Type = { - val lit = ctx.coreSpec.linkTimeProperties.transformLinkTimeProperty(tree) - genLiteral(lit, lit.tpe) - lit.tpe - } - private def genJSNew(tree: JSNew): Type = { val JSNew(ctor, args) = tree diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/CheckingPhase.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/CheckingPhase.scala index ff198744de..ac62d4d25e 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/CheckingPhase.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/CheckingPhase.scala @@ -24,5 +24,6 @@ sealed abstract class CheckingPhase object CheckingPhase { case object Compiler extends CheckingPhase case object BaseLinker extends CheckingPhase + case object Desugarer extends CheckingPhase case object Optimizer extends CheckingPhase } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index b3dd10acb8..671a320632 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -855,6 +855,8 @@ private final class ClassDefChecker(classDef: ClassDef, } case LinkTimeProperty(name) => + if (!featureSet.supports(FeatureSet.LinkTimeProperty)) + reportError(i"Illegal link-time property '$name' after desugaring") // JavaScript expressions diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/FeatureSet.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/FeatureSet.scala index 73f89b0d8b..59f1d89c54 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/FeatureSet.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/FeatureSet.scala @@ -36,17 +36,20 @@ private[checker] object FeatureSet { // Individual features + /** The `LinkTimeProperty` IR node. */ + val LinkTimeProperty = new FeatureSet(1 << 0) + /** Optional constructors in module classes and JS classes. */ - val OptionalConstructors = new FeatureSet(1 << 0) + val OptionalConstructors = new FeatureSet(1 << 1) /** Explicit reflective proxy definitions. */ - val ReflectiveProxies = new FeatureSet(1 << 1) + val ReflectiveProxies = new FeatureSet(1 << 2) /** Transients that are the result of optimizations. */ - val OptimizedTransients = new FeatureSet(1 << 2) + val OptimizedTransients = new FeatureSet(1 << 3) /** Records and record types. */ - val Records = new FeatureSet(1 << 3) + val Records = new FeatureSet(1 << 4) /** Relaxed constructor discipline. * @@ -55,7 +58,7 @@ private[checker] object FeatureSet { * - `this.x = ...` assignments before the delegate call can assign super class fields. * - `StoreModule` can be anywhere, or not be there at all. */ - val RelaxedCtorBodies = new FeatureSet(1 << 4) + val RelaxedCtorBodies = new FeatureSet(1 << 5) // Common sets @@ -63,14 +66,23 @@ private[checker] object FeatureSet { private val Linked = OptionalConstructors | ReflectiveProxies + /** Features that must be desugared away. */ + private val NeedsDesugaring = + LinkTimeProperty + + /** IR that is only the result of desugaring (currently empty). */ + private val Desugared = + Empty + /** IR that is only the result of optimizations. */ private val Optimized = OptimizedTransients | Records | RelaxedCtorBodies /** The set of features allowed as output of the given phase. */ def allowedAfter(phase: CheckingPhase): FeatureSet = phase match { - case Compiler => Empty - case BaseLinker => Linked - case Optimizer => Linked | Optimized + case Compiler => NeedsDesugaring + case BaseLinker => Linked | NeedsDesugaring + case Desugarer => Linked | Desugared + case Optimizer => Linked | Desugared | Optimized } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index de827f396a..a6c786fb01 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -577,7 +577,7 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, typecheckAny(expr, env) checkIsAsInstanceTargetType(tpe) - case LinkTimeProperty(name) => + case LinkTimeProperty(name) if featureSet.supports(FeatureSet.LinkTimeProperty) => // JavaScript expressions @@ -760,7 +760,8 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, typecheck(elem, env) } - case _:RecordSelect | _:RecordValue | _:Transient | _:JSSuperConstructorCall => + case _:RecordSelect | _:RecordValue | _:Transient | + _:JSSuperConstructorCall | _:LinkTimeProperty => reportError("invalid tree") } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala index ddf5b71f14..07eb08b294 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala @@ -129,17 +129,20 @@ private[frontend] object BaseLinker { classInfo.isAnySubclassInstantiated } - val methods = classDef.methods.filter { m => - val methodInfo = - classInfo.methodInfos(m.flags.namespace)(m.methodName) - - val reachable = methodInfo.isReachable - assert(m.body.isDefined || !reachable, - s"The abstract method ${classDef.name.name}.${m.methodName} " + - "is reachable.") - - reachable - } + // Will stay empty for most classes + var desugaringRequirements = LinkedClass.DesugaringRequirements.Empty + + val methods: List[MethodDef] = classDef.methods.iterator + .map(m => m -> classInfo.methodInfos(m.flags.namespace)(m.methodName)) + .filter(_._2.isReachable) + .map { case (m, info) => + assert(m.body.isDefined, + s"The abstract method ${classDef.name.name}.${m.methodName} is reachable.") + if (info.needsDesugaring) + desugaringRequirements = desugaringRequirements.addMethod(m.flags.namespace, m.methodName) + m + } + .toList val jsConstructor = if (classInfo.isAnySubclassInstantiated) classDef.jsConstructor @@ -149,6 +152,9 @@ private[frontend] object BaseLinker { if (classInfo.isAnySubclassInstantiated) classDef.jsMethodProps else Nil + if (classInfo.anyJSMemberNeedsDesugaring) + desugaringRequirements = desugaringRequirements.addAnyExportedMember() + val jsNativeMembers = classDef.jsNativeMembers .filter(m => classInfo.jsNativeMembersUsed.contains(m.name.name)) @@ -181,6 +187,7 @@ private[frontend] object BaseLinker { staticDependencies = classInfo.staticDependencies.toSet, externalDependencies = classInfo.externalDependencies.toSet, dynamicDependencies = classInfo.dynamicDependencies.toSet, + desugaringRequirements, version) val linkedTopLevelExports = for { @@ -189,7 +196,8 @@ private[frontend] object BaseLinker { val infos = analysis.topLevelExportInfos( (ModuleID(topLevelExport.moduleID), topLevelExport.topLevelExportName)) new LinkedTopLevelExport(classDef.className, topLevelExport, - infos.staticDependencies.toSet, infos.externalDependencies.toSet) + infos.staticDependencies.toSet, infos.externalDependencies.toSet, + needsDesugaring = infos.needsDesugaring) } (linkedClass, linkedTopLevelExports) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/Desugarer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Desugarer.scala new file mode 100644 index 0000000000..65323bfd69 --- /dev/null +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Desugarer.scala @@ -0,0 +1,159 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.linker.frontend + +import org.scalajs.logging._ + +import org.scalajs.linker.standard._ +import org.scalajs.linker.checker._ + +import org.scalajs.ir.Names._ +import org.scalajs.ir.Transformers._ +import org.scalajs.ir.Trees._ +import org.scalajs.ir.{Position, Version} + +/** Desugars a linking unit. */ +final class Desugarer(config: CommonPhaseConfig, checkIR: Boolean) { + import Desugarer._ + + private val desugarTransformer = new DesugarTransformer(config.coreSpec) + + def desugar(unit: LinkingUnit, logger: Logger): LinkingUnit = { + val result = logger.time("Desugarer: Desugar") { + val desugaredClasses = unit.classDefs.map(desugarClass(_)) + val desugaredTopLevelExports = unit.topLevelExports.map(desugarTopLevelExport(_)) + + new LinkingUnit(desugaredClasses, desugaredTopLevelExports, + unit.moduleInitializers, unit.globalInfo) + } + + if (checkIR) { + logger.time("Desugarer: Check IR") { + val errorCount = IRChecker.check(result, logger, CheckingPhase.Desugarer) + if (errorCount != 0) { + throw new AssertionError( + s"There were $errorCount IR checking errors after desugaring (this is a Scala.js bug)") + } + } + } + + result + } + + private def desugarClass(linkedClass: LinkedClass): LinkedClass = { + import linkedClass._ + + if (desugaringRequirements.isEmpty) { + linkedClass + } else { + val newMethods = methods.map { method => + if (!desugaringRequirements.containsMethod(method.flags.namespace, method.methodName)) + method + else + desugarTransformer.transformMethodDef(method) + } + + val newJSConstructorDef = + if (!desugaringRequirements.exportedMembers) jsConstructorDef + else jsConstructorDef.map(desugarTransformer.transformJSConstructorDef(_)) + + val newExportedMembers = + if (!desugaringRequirements.exportedMembers) exportedMembers + else exportedMembers.map(desugarTransformer.transformJSMethodPropDef(_)) + + new LinkedClass( + name, + kind, + jsClassCaptures, + superClass, + interfaces, + jsSuperClass, + jsNativeLoadSpec, + fields, + methods = newMethods, + jsConstructorDef = newJSConstructorDef, + exportedMembers = newExportedMembers, + jsNativeMembers, + optimizerHints, + pos, + ancestors, + hasInstances, + hasDirectInstances, + hasInstanceTests, + hasRuntimeTypeInfo, + fieldsRead, + staticFieldsRead, + staticDependencies, + externalDependencies, + dynamicDependencies, + LinkedClass.DesugaringRequirements.Empty, + version + ) + } + } + + private def desugarTopLevelExport(tle: LinkedTopLevelExport): LinkedTopLevelExport = { + import tle._ + if (!tle.needsDesugaring) { + tle + } else { + val newTree = desugarTransformer.transformTopLevelExportDef(tree) + new LinkedTopLevelExport(owningClass, newTree, staticDependencies, + externalDependencies, needsDesugaring = false) + } + } +} + +private[linker] object Desugarer { + + private final class DesugarTransformer(coreSpec: CoreSpec) + extends ClassTransformer { + + override def transform(tree: Tree): Tree = { + tree match { + case prop: LinkTimeProperty => + coreSpec.linkTimeProperties.transformLinkTimeProperty(prop) + + case _ => + super.transform(tree) + } + } + + /* Transfer Version from old members to transformed members. + * We can do this because the transformation only depends on the + * `coreSpec`, which is immutable. + */ + + override def transformMethodDef(methodDef: MethodDef): MethodDef = { + val newMethodDef = super.transformMethodDef(methodDef) + newMethodDef.copy()(newMethodDef.optimizerHints, methodDef.version)(newMethodDef.pos) + } + + override def transformJSConstructorDef(jsConstructor: JSConstructorDef): JSConstructorDef = { + val newJSConstructor = super.transformJSConstructorDef(jsConstructor) + newJSConstructor.copy()(newJSConstructor.optimizerHints, jsConstructor.version)( + newJSConstructor.pos) + } + + override def transformJSMethodDef(jsMethodDef: JSMethodDef): JSMethodDef = { + val newJSMethodDef = super.transformJSMethodDef(jsMethodDef) + newJSMethodDef.copy()(newJSMethodDef.optimizerHints, jsMethodDef.version)( + newJSMethodDef.pos) + } + + override def transformJSPropertyDef(jsPropertyDef: JSPropertyDef): JSPropertyDef = { + val newJSPropertyDef = super.transformJSPropertyDef(jsPropertyDef) + newJSPropertyDef.copy()(jsPropertyDef.version)(newJSPropertyDef.pos) + } + } +} diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkerFrontendImpl.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkerFrontendImpl.scala index 11d10064a9..d75fd57eab 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkerFrontendImpl.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkerFrontendImpl.scala @@ -40,6 +40,9 @@ final class LinkerFrontendImpl private (config: LinkerFrontendImpl.Config) private[this] val linker: BaseLinker = new BaseLinker(config.commonConfig, config.checkIR) + private[this] val desugarer: Desugarer = + new Desugarer(config.commonConfig, config.checkIR) + private[this] val optOptimizer: Option[IncOptimizer] = LinkerFrontendImplPlatform.createOptimizer(config) @@ -69,8 +72,14 @@ final class LinkerFrontendImpl private (config: LinkerFrontendImpl.Config) preOptimizerRequirements) } - val optimizedResult = optOptimizer.fold(linkResult) { optimizer => - linkResult.flatMap(optimize(_, symbolRequirements, optimizer, logger)) + val desugaredResult = linkResult.map { unit => + logger.time("Desugarer") { + desugarer.desugar(unit, logger) + } + } + + val optimizedResult = optOptimizer.fold(desugaredResult) { optimizer => + desugaredResult.flatMap(optimize(_, symbolRequirements, optimizer, logger)) } optimizedResult.map { unit => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index dae5f87e43..9c026ac032 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -589,9 +589,6 @@ private[optimizer] abstract class OptimizerCore( } } - case prop: LinkTimeProperty => - config.coreSpec.linkTimeProperties.transformLinkTimeProperty(prop) - // JavaScript expressions case JSNew(ctor, args) => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedClass.scala b/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedClass.scala index afa257b289..010214777a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedClass.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedClass.scala @@ -14,7 +14,7 @@ package org.scalajs.linker.standard import org.scalajs.ir.Trees._ import org.scalajs.ir.{ClassKind, Position, Version} -import org.scalajs.ir.Names.{ClassName, FieldName} +import org.scalajs.ir.Names.{ClassName, FieldName, MethodName} /** A ClassDef after linking. * @@ -65,6 +65,9 @@ final class LinkedClass( val externalDependencies: Set[String], val dynamicDependencies: Set[ClassName], + // Desugaring requirements + val desugaringRequirements: LinkedClass.DesugaringRequirements, + val version: Version) { require(ancestors.headOption.contains(name.name), @@ -89,3 +92,42 @@ final class LinkedClass( def fullName: String = className.nameString } + +object LinkedClass { + /** Desugaring requirements of a `LinkedClass`. + * + * These requirements are a set of members that need desugaring. + */ + final class DesugaringRequirements private ( + methods: Vector[Set[MethodName]], // indexed by MemberNamespace ordinal + val exportedMembers: Boolean + ) { + private def this() = { + this( + methods = Vector.fill(MemberNamespace.Count)(Set.empty), + exportedMembers = false + ) + } + + /** Are these requirements empty, i.e., does the corresponding require no desugaring at all? */ + def isEmpty: Boolean = + this eq DesugaringRequirements.Empty // by construction, only that specific instance is empty + + /** Do the requirements contain the given method, i.e., does that method need desugaring? */ + def containsMethod(namespace: MemberNamespace, methodName: MethodName): Boolean = + methods(namespace.ordinal).contains(methodName) + + def addMethod(namespace: MemberNamespace, methodName: MethodName): DesugaringRequirements = { + val newMethods = + methods.updated(namespace.ordinal, methods(namespace.ordinal) + methodName) + new DesugaringRequirements(newMethods, exportedMembers) + } + + def addAnyExportedMember(): DesugaringRequirements = + new DesugaringRequirements(methods, exportedMembers = true) + } + + object DesugaringRequirements { + val Empty: DesugaringRequirements = new DesugaringRequirements() + } +} diff --git a/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedTopLevelExport.scala b/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedTopLevelExport.scala index 1702ecd02e..339280f150 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedTopLevelExport.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedTopLevelExport.scala @@ -19,7 +19,8 @@ final class LinkedTopLevelExport( val owningClass: ClassName, val tree: TopLevelExportDef, val staticDependencies: Set[ClassName], - val externalDependencies: Set[String] + val externalDependencies: Set[String], + val needsDesugaring: Boolean ) { def exportName: String = tree.topLevelExportName } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala index d283ae189e..1d6dd4759f 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala @@ -18,8 +18,9 @@ import scala.util.{Failure, Success} import org.junit.Test import org.junit.Assert._ -import org.scalajs.ir.ClassKind +import org.scalajs.ir.{ClassKind, EntryPointsInfo} import org.scalajs.ir.Names._ +import org.scalajs.ir.Transformers._ import org.scalajs.ir.Trees._ import org.scalajs.ir.Types._ @@ -427,6 +428,41 @@ class IRCheckerTest { } object IRCheckerTest { + /** Version of the minilib where we have replaced every node requiring + * desugaring by a placeholder. + * + * We need this to directly feed to the IR checker post-optimizer, since + * nodes requiring desugaring are rejected at that point. + */ + private lazy val minilibRequiringNoDesugaring: Future[Seq[IRFile]] = { + import scala.concurrent.ExecutionContext.Implicits.global + + TestIRRepo.minilib.map { stdLibFiles => + for (irFile <- stdLibFiles) yield { + val irFileImpl = IRFileImpl.fromIRFile(irFile) + + val patchedTreeFuture = irFileImpl.tree.map { tree => + new ClassTransformer { + override def transform(tree: Tree): Tree = tree match { + case tree: LinkTimeProperty => zeroOf(tree.tpe) + case _ => super.transform(tree) + } + }.transformClassDef(tree) + } + + new IRFileImpl(irFileImpl.path, irFileImpl.version) { + /** Entry points information for this file. */ + def entryPointsInfo(implicit ec: ExecutionContext): Future[EntryPointsInfo] = + irFileImpl.entryPointsInfo(ec) + + /** IR Tree of this file. */ + def tree(implicit ec: ExecutionContext): Future[ClassDef] = + patchedTreeFuture + } + } + } + } + def testLinkNoIRError(classDefs: Seq[ClassDef], moduleInitializers: List[ModuleInitializer], postOptimizer: Boolean = false)( @@ -467,8 +503,8 @@ object IRCheckerTest { .factory("IRCheckerTest") .none() - TestIRRepo.minilib.flatMap { stdLibFiles => - if (postOptimizer) { + if (postOptimizer) { + minilibRequiringNoDesugaring.flatMap { stdLibFiles => val refiner = new Refiner(CommonPhaseConfig.fromStandardConfig(config), checkIR = true) Future.traverse(stdLibFiles)(f => IRFileImpl.fromIRFile(f).tree).flatMap { stdLibClassDefs => @@ -480,7 +516,9 @@ object IRCheckerTest { refiner.refine(allClassDefs.map(c => (c, UNV)), moduleInitializers, noSymbolRequirements, logger) } - } else { + }.map(_ => ()) + } else { + TestIRRepo.minilib.flatMap { stdLibFiles => val linkerFrontend = StandardLinkerFrontend(config) val irFiles = ( stdLibFiles ++ @@ -488,7 +526,7 @@ object IRCheckerTest { PrivateLibHolder.files ) linkerFrontend.link(irFiles, moduleInitializers, noSymbolRequirements, logger) - } - }.map(_ => ()) + }.map(_ => ()) + } } } diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index af6f47f735..8ac39f199d 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -11,6 +11,9 @@ object BinaryIncompatibilities { ) val Linker = Seq( + // !!! Breaking, OK in minor release + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.LinkedClass.this"), + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.LinkedTopLevelExport.this"), ) val LinkerInterface = Seq( From b38201c0cf6f99fc146d0ee90de989b23d6e9c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 16 Jan 2025 23:35:37 +0100 Subject: [PATCH 081/121] Drop support for non-strict floats. Non-strict floats were deprecated 3 years ago in Scala.js 1.9.0. I don't recall seeing any comment about them ever since. --- Jenkinsfile | 36 -- javalib/src/main/scala/java/lang/Float.scala | 9 +- .../scala/java/lang/FloatingPointBits.scala | 46 +-- .../scalajs/linker/interface/Semantics.scala | 25 +- .../linker/interface/StandardConfig.scala | 1 - .../backend/WebAssemblyLinkerBackend.scala | 4 - .../linker/backend/emitter/CoreJSLib.scala | 312 +++++++++--------- .../linker/backend/emitter/SJSGen.scala | 16 +- project/BinaryIncompatibilities.scala | 2 + project/Build.scala | 1 - .../scalajs/testsuite/utils/BuildInfo.scala | 1 - .../scalajs/testsuite/utils/Platform.scala | 34 -- .../testsuite/compiler/FloatJSTest.scala | 2 - .../scalajs/testsuite/utils/Requires.scala | 5 - .../scalajs/testsuite/utils/Platform.scala | 2 - .../testsuite/compiler/DoubleTest.scala | 4 - .../testsuite/compiler/FloatTest.scala | 28 +- .../scalajs/testsuite/compiler/LongTest.scala | 2 - .../compiler/RuntimeTypeTestsTest.scala | 2 +- .../testsuite/javalib/lang/ClassTest.scala | 2 +- .../testsuite/javalib/lang/FloatTest.scala | 15 +- .../testsuite/javalib/lang/ObjectTest.scala | 2 +- .../javalib/math/BigDecimalConvertTest.scala | 14 +- .../javalib/math/BigIntegerConvertTest.scala | 12 - 24 files changed, 196 insertions(+), 381 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 8f6ecc3c8e..5cbf59ce7f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -232,17 +232,6 @@ def Tasks = [ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ $testSuite$v/test && - sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ - $testSuite$v/test && - sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ - 'set scalaJSStage in Global := FullOptStage' \ - $testSuite$v/test && - sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ - $testSuite$v/test && sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAllowBigIntsForLongs(true)))' \ $testSuite$v/test && @@ -312,20 +301,6 @@ def Tasks = [ 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ - 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ - 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ - 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ - 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ ++$scala $testSuite$v/test ''', @@ -355,17 +330,6 @@ def Tasks = [ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ - 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ - ++$scala $testSuite$v/test && sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion).withAllowBigIntsForLongs(true)))' \ ++$scala $testSuite$v/test && sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion).withAllowBigIntsForLongs(true)).withOptimizer(false))' \ diff --git a/javalib/src/main/scala/java/lang/Float.scala b/javalib/src/main/scala/java/lang/Float.scala index c9c6ea2e84..8fa4ce3070 100644 --- a/javalib/src/main/scala/java/lang/Float.scala +++ b/javalib/src/main/scala/java/lang/Float.scala @@ -150,14 +150,7 @@ object Float { val zDouble = z.toDouble if (zDouble == z0) { - /* This branch is always taken when strictFloats are disabled, and there - * is no Math.fround support. In that case, Floats are basically - * equivalent to Doubles, and we make no specific guarantee about the - * result, so we can quickly return `z`. - * More importantly, the computations in the `else` branch assume that - * Float operations are exact, so we must return early. - * - * This branch is also always taken when z0 is 0.0 or Infinity, which the + /* This branch is always taken when z0 is 0.0 or Infinity, which the * `else` branch assumes does not happen. */ z diff --git a/javalib/src/main/scala/java/lang/FloatingPointBits.scala b/javalib/src/main/scala/java/lang/FloatingPointBits.scala index fb9b89ff93..96e1c8f64c 100644 --- a/javalib/src/main/scala/java/lang/FloatingPointBits.scala +++ b/javalib/src/main/scala/java/lang/FloatingPointBits.scala @@ -149,7 +149,7 @@ private[lang] object FloatingPointBits { float32Array(0) = value int32Array(0) } else { - floatToIntBitsPolyfill(value.toDouble) + floatToIntBitsPolyfill(value) } } @@ -181,8 +181,7 @@ private[lang] object FloatingPointBits { * Note that if typed arrays are not supported, it is almost certain that * fround is not supported natively, so Float operations are extremely slow. * - * We therefore do all computations in Doubles here, which is also more - * predictable, since the results do not depend on strict floats semantics. + * We therefore do all computations in Doubles here. */ private def intBitsToFloatPolyfill(bits: Int): scala.Double = { @@ -194,21 +193,23 @@ private[lang] object FloatingPointBits { decodeIEEE754(ebits, fbits, floatPowsOf2, scala.Float.MinPositiveValue, sign, e, f) } - private def floatToIntBitsPolyfill(value: scala.Double): Int = { + private def floatToIntBitsPolyfill(floatValue: scala.Float): Int = { // Some constants val ebits = 8 val fbits = 23 + // Force computations to be on Doubles + val value = floatValue.toDouble + // Determine sign bit and compute the absolute value av val sign = if (value < 0.0 || (value == 0.0 && 1.0 / value < 0.0)) -1 else 1 val s = sign & scala.Int.MinValue val av = sign * value // Compute e and f - val avr = forceFround(av) val powsOf2 = this.floatPowsOf2 // local cache - val e = encodeIEEE754Exponent(ebits, powsOf2, avr) - val f = encodeIEEE754MantissaBits(ebits, fbits, powsOf2, scala.Float.MinPositiveValue.toDouble, avr, e) + val e = encodeIEEE754Exponent(ebits, powsOf2, av) + val f = encodeIEEE754MantissaBits(ebits, fbits, powsOf2, scala.Float.MinPositiveValue.toDouble, av, e) // Encode s | (e << fbits) | rawToInt(f) @@ -277,37 +278,6 @@ private[lang] object FloatingPointBits { } } - /** Force rounding of `av` to fit in 32 bits (this is a manual `fround`). - * - * `av` must not be negative, i.e., `av < 0.0` must be false (it can be - * `NaN` or `Infinity`). - * - * When we use strict-float semantics, this is redundant, because the input - * came from a `Float` and is therefore guaranteed to be rounded already. - * However, here we don't know whether we use strict floats semantics or - * not, so we must always do it. This is not a big deal because, if this - * code is called, then any operation on `Float`s is calling the same code - * from the `CoreJSLib`, so doing one more such operation for - * `floatToIntBits` is negligible. - * - * TODO Remove this when we get rid of non-strict float semantics altogether. - */ - @inline - private def forceFround(av: scala.Double): scala.Double = { - // See the `fround` polyfill in CoreJSLib - val overflowThreshold = 3.4028235677973366e38 - val normalThreshold = 1.1754943508222875e-38 - if (av >= overflowThreshold) { - scala.Double.PositiveInfinity - } else if (av >= normalThreshold) { - val p = av * 536870913.0 // pow(2, 29) + 1 - p + (av - p) - } else { - val roundingFactor = scala.Double.MinPositiveValue / scala.Float.MinPositiveValue.toDouble - (av * roundingFactor) / roundingFactor - } - } - private def encodeIEEE754Exponent(ebits: Int, powsOf2: js.Array[scala.Double], av: scala.Double): Int = { diff --git a/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/Semantics.scala b/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/Semantics.scala index 5795b1c5db..d8608016c1 100644 --- a/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/Semantics.scala +++ b/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/Semantics.scala @@ -12,6 +12,8 @@ package org.scalajs.linker.interface +import scala.annotation.compileTimeOnly + import CheckedBehavior._ import Fingerprint.FingerprintBuilder @@ -23,12 +25,16 @@ final class Semantics private ( val nullPointers: CheckedBehavior, val stringIndexOutOfBounds: CheckedBehavior, val moduleInit: CheckedBehavior, - val strictFloats: Boolean, val productionMode: Boolean, val runtimeClassNameMapper: Semantics.RuntimeClassNameMapper) { import Semantics._ + @deprecated( + "non-strict floats are not supported anymore; strictFloats is always true", + since = "1.19.0") + val strictFloats: Boolean = true + def withAsInstanceOfs(behavior: CheckedBehavior): Semantics = copy(asInstanceOfs = behavior) @@ -50,13 +56,11 @@ final class Semantics private ( def withModuleInit(moduleInit: CheckedBehavior): Semantics = copy(moduleInit = moduleInit) - @deprecated( - "Scala.js now uses strict floats by default. " + - "Non-strict float semantics are deprecated and will eventually be " + - "removed.", - "1.9.0") + @compileTimeOnly( + "Non-strict floats are not supported anymore. " + + "The default is `true` and cannot be turned to `false`.") def withStrictFloats(strictFloats: Boolean): Semantics = - copy(strictFloats = strictFloats) + this def withProductionMode(productionMode: Boolean): Semantics = copy(productionMode = productionMode) @@ -86,7 +90,6 @@ final class Semantics private ( this.nullPointers == that.nullPointers && this.stringIndexOutOfBounds == that.stringIndexOutOfBounds && this.moduleInit == that.moduleInit && - this.strictFloats == that.strictFloats && this.productionMode == that.productionMode && this.runtimeClassNameMapper == that.runtimeClassNameMapper case _ => @@ -103,7 +106,6 @@ final class Semantics private ( acc = mix(acc, nullPointers.##) acc = mix(acc, stringIndexOutOfBounds.##) acc = mix(acc, moduleInit.##) - acc = mix(acc, strictFloats.##) acc = mix(acc, productionMode.##) acc = mixLast(acc, runtimeClassNameMapper.##) finalizeHash(acc, 10) @@ -118,7 +120,6 @@ final class Semantics private ( | nullPointers = $nullPointers, | stringIndexOutOfBounds = $stringIndexOutOfBounds, | moduleInit = $moduleInit, - | strictFloats = $strictFloats, | productionMode = $productionMode |)""".stripMargin } @@ -131,7 +132,6 @@ final class Semantics private ( nullPointers: CheckedBehavior = this.nullPointers, stringIndexOutOfBounds: CheckedBehavior = this.stringIndexOutOfBounds, moduleInit: CheckedBehavior = this.moduleInit, - strictFloats: Boolean = this.strictFloats, productionMode: Boolean = this.productionMode, runtimeClassNameMapper: RuntimeClassNameMapper = this.runtimeClassNameMapper): Semantics = { @@ -143,7 +143,6 @@ final class Semantics private ( nullPointers = nullPointers, stringIndexOutOfBounds = stringIndexOutOfBounds, moduleInit = moduleInit, - strictFloats = strictFloats, productionMode = productionMode, runtimeClassNameMapper = runtimeClassNameMapper) } @@ -262,7 +261,6 @@ object Semantics { .addField("nullPointers", semantics.nullPointers) .addField("stringIndexOutOfBounds", semantics.stringIndexOutOfBounds) .addField("moduleInit", semantics.moduleInit) - .addField("strictFloats", semantics.strictFloats) .addField("productionMode", semantics.productionMode) .addField("runtimeClassNameMapper", semantics.runtimeClassNameMapper) .build() @@ -277,7 +275,6 @@ object Semantics { nullPointers = Fatal, stringIndexOutOfBounds = Fatal, moduleInit = Unchecked, - strictFloats = true, productionMode = false, runtimeClassNameMapper = RuntimeClassNameMapper.keepAll()) } diff --git a/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/StandardConfig.scala b/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/StandardConfig.scala index 508728fce5..2f3a2afe7b 100644 --- a/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/StandardConfig.scala +++ b/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/StandardConfig.scala @@ -192,7 +192,6 @@ final class StandardConfig private ( * * - `moduleKind == ModuleKind.ESModule` * - `esFeatures.useECMAScript2015Semantics == true` (true by default) - * - `semantics.strictFloats == true` (true by default; non-strict floats are deprecated) * * We may lift these restrictions in the future, although we do not expect * to do so. diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/WebAssemblyLinkerBackend.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/WebAssemblyLinkerBackend.scala index ba12a26b59..a167a7bf9f 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/WebAssemblyLinkerBackend.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/WebAssemblyLinkerBackend.scala @@ -40,10 +40,6 @@ final class WebAssemblyLinkerBackend(config: LinkerBackendImpl.Config) coreSpec.esFeatures.useECMAScript2015Semantics, s"The WebAssembly backend only supports the ECMAScript 2015 semantics." ) - require( - coreSpec.semantics.strictFloats, - "The WebAssembly backend only supports strict float semantics." - ) require(coreSpec.targetIsWebAssembly, s"A WebAssembly backend cannot be used with CoreSpec targeting JavaScript") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala index 467e73d68b..8b30a408f5 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala @@ -191,154 +191,150 @@ private[emitter] object CoreJSLib { case FroundBuiltin => val v = varRef("v") - if (!strictFloats) { - genArrowFunction(paramList(v), Return(+v)) - } else { - val Float32ArrayRef = globalRef("Float32Array") - - /* (function(array) { - * return function(v) { - * array[0] = v; - * return array[0]; - * } - * })(new Float32Array(1)) - * - * Allocating the Float32Array once and for all, and capturing it - * in an IIFE, is *much* faster than recreating it in every call of - * the polyfill (about an order of magnitude). - */ - val array = varRef("array") - val typedArrayPolyfillInner = genArrowFunction(paramList(v), { - Block( - BracketSelect(array, 0) := v, - Return(BracketSelect(array, 0)) - ) - }) - val typedArrayPolyfill = Apply( - genArrowFunction(paramList(array), Return(typedArrayPolyfillInner)), - New(Float32ArrayRef, 1 :: Nil) :: Nil) - - // scalastyle:off line.size.limit - /* Originally inspired by the Typed Array polyfills written by - * Joshua Bell: - * https://github.com/inexorabletash/polyfill/blob/a682f42c1092280bb01907c245979fb07219513d/typedarray.js#L150-L255 - * Then simplified quite a lot because - * 1) we do not need to produce the actual bit string that serves - * as storage of the floats, and - * 2) we are only interested in the float32 case. - * - * Eventually, the last bits of the above were replaced by an - * application of Veltkamp's splitting (see below). The inspiration - * for that use case came from core-js' implementation at - * https://github.com/zloirock/core-js/blob/a3f591658e063a6e2c2594ec3c80eff16340a98d/packages/core-js/internals/math-fround.js - * The code does not mention Veltkamp's splitting, but the PR - * discussion that led to it does, although with a question mark, - * and without any explanation of how/why it works: - * https://github.com/paulmillr/es6-shim/pull/140#issuecomment-91787165 - * We tracked down the descriptions and proofs relative to - * Veltkamp's splitting and re-derived an implementation from there. - * - * The direct tests for this polyfill are the tests for `toFloat` - * in org.scalajs.testsuite.compiler.DoubleTest. - */ - // scalastyle:on line.size.limit - val sign = varRef("sign") - val av = varRef("av") - val p = varRef("p") - - val Inf = double(Double.PositiveInfinity) - val overflowThreshold = double(3.4028235677973366e38) - val normalThreshold = double(1.1754943508222875e-38) - - val noTypedArrayPolyfill = genArrowFunction(paramList(v), Block( - v := +v, // turns `null` into +0, making sure not to deoptimize what follows - const(sign, If(v < 0, -1, 1)), // 1 for NaN, +0 and -0 - const(av, sign * v), // abs(v), or -0 if v is -0 - If(av >= overflowThreshold, { // also handles the case av === Infinity - Return(sign * Inf) - }, If(av >= normalThreshold, Block( - /* Here, we know that both the input and output are expressed - * in a Double normal form, so standard floating point - * algorithms from papers can be used. - * - * We use Veltkamp's splitting, as described and studied in - * Sylvie Boldo. - * Pitfalls of a Full Floating-Point Proof: Example on the - * Formal Proof of the Veltkamp/Dekker Algorithms - * https://dx.doi.org/10.1007/11814771_6 - * Section 3, with β = 2, t = 53, s = 53 - 24 = 29, x = av. - * 53 is the number of effective mantissa bits in a Double; - * 24 in a Float. - * - * ◦ is the round-to-nearest operation with a tie-breaking - * rule (in our case, break-to-even). - * - * Let C = βˢ + 1 = 536870913 - * p = ◦(x × C) - * q = ◦(x − p) - * x₁ = ◦(p + q) - * - * Boldo proves that x₁ is the (t-s)-bit float closest to x, - * using the same tie-breaking rule as ◦. Since (t-s) = 24, - * this is the closest float32 (with 24 mantissa bits), and - * therefore the correct result of `fround`. - * - * Boldo also proves that if the computation of x × C does not - * cause overflow, then none of the following operations will - * cause overflow. We know that x (av) is less than the - * overflowThreshold, and overflowThreshold × C does not - * overflow, so that computation can never cause an overflow. - * - * If the reader does not have access to Boldo's paper, they - * may refer instead to - * Claude-Pierre Jeannerod, Jean-Michel Muller, Paul Zimmermann. - * On various ways to split a floating-point number. - * ARITH 2018 - 25th IEEE Symposium on Computer Arithmetic, - * Jun 2018, Amherst (MA), United States. - * pp.53-60, 10.1109/ARITH.2018.8464793. hal-01774587v2 - * available at - * https://hal.inria.fr/hal-01774587v2/document - * Section III, although that paper defers some theorems and - * proofs to Boldo's. - */ - const(p, av * 536870913), - Return(sign * (p + (av - p))) - ), { - /* Here, the result is represented as a subnormal form in a - * float32 representation. - * - * We round `av` to the nearest multiple of the smallest - * positive Float value (i.e., `Float.MinPositiveValue`), - * breaking ties to an even multiple. - * - * We do this by leveraging the inherent loss of precision near - * the minimum positive *double* value: conceptually, we divide - * the value by - * Float.MinPositiveValue / Double.MinPositiveValue - * which will drop the excess precision, applying exactly the - * rounding strategy that we want. Then we multiply the value - * back by the same constant. - * - * However, `Float.MinPositiveValue / Double.MinPositiveValue` - * is not representable as a finite Double. Therefore, we - * instead use the *inverse* constant - * Double.MinPositiveValue / Float.MinPositiveValue - * and we first multiply by that constant, then divide by it. - * - * --- - * - * As an additional "hack", the input values NaN, +0 and -0 - * also fall in this code path. For them, this computation - * happens to be an identity, and is therefore correct as well. - */ - val roundingFactor = double(Double.MinPositiveValue / Float.MinPositiveValue.toDouble) - Return(sign * ((av * roundingFactor) / roundingFactor)) - })) - )) + val Float32ArrayRef = globalRef("Float32Array") - If(typeof(Float32ArrayRef) !== str("undefined"), - typedArrayPolyfill, noTypedArrayPolyfill) - } + /* (function(array) { + * return function(v) { + * array[0] = v; + * return array[0]; + * } + * })(new Float32Array(1)) + * + * Allocating the Float32Array once and for all, and capturing it + * in an IIFE, is *much* faster than recreating it in every call of + * the polyfill (about an order of magnitude). + */ + val array = varRef("array") + val typedArrayPolyfillInner = genArrowFunction(paramList(v), { + Block( + BracketSelect(array, 0) := v, + Return(BracketSelect(array, 0)) + ) + }) + val typedArrayPolyfill = Apply( + genArrowFunction(paramList(array), Return(typedArrayPolyfillInner)), + New(Float32ArrayRef, 1 :: Nil) :: Nil) + + // scalastyle:off line.size.limit + /* Originally inspired by the Typed Array polyfills written by + * Joshua Bell: + * https://github.com/inexorabletash/polyfill/blob/a682f42c1092280bb01907c245979fb07219513d/typedarray.js#L150-L255 + * Then simplified quite a lot because + * 1) we do not need to produce the actual bit string that serves + * as storage of the floats, and + * 2) we are only interested in the float32 case. + * + * Eventually, the last bits of the above were replaced by an + * application of Veltkamp's splitting (see below). The inspiration + * for that use case came from core-js' implementation at + * https://github.com/zloirock/core-js/blob/a3f591658e063a6e2c2594ec3c80eff16340a98d/packages/core-js/internals/math-fround.js + * The code does not mention Veltkamp's splitting, but the PR + * discussion that led to it does, although with a question mark, + * and without any explanation of how/why it works: + * https://github.com/paulmillr/es6-shim/pull/140#issuecomment-91787165 + * We tracked down the descriptions and proofs relative to + * Veltkamp's splitting and re-derived an implementation from there. + * + * The direct tests for this polyfill are the tests for `toFloat` + * in org.scalajs.testsuite.compiler.DoubleTest. + */ + // scalastyle:on line.size.limit + val sign = varRef("sign") + val av = varRef("av") + val p = varRef("p") + + val Inf = double(Double.PositiveInfinity) + val overflowThreshold = double(3.4028235677973366e38) + val normalThreshold = double(1.1754943508222875e-38) + + val noTypedArrayPolyfill = genArrowFunction(paramList(v), Block( + v := +v, // turns `null` into +0, making sure not to deoptimize what follows + const(sign, If(v < 0, -1, 1)), // 1 for NaN, +0 and -0 + const(av, sign * v), // abs(v), or -0 if v is -0 + If(av >= overflowThreshold, { // also handles the case av === Infinity + Return(sign * Inf) + }, If(av >= normalThreshold, Block( + /* Here, we know that both the input and output are expressed + * in a Double normal form, so standard floating point + * algorithms from papers can be used. + * + * We use Veltkamp's splitting, as described and studied in + * Sylvie Boldo. + * Pitfalls of a Full Floating-Point Proof: Example on the + * Formal Proof of the Veltkamp/Dekker Algorithms + * https://dx.doi.org/10.1007/11814771_6 + * Section 3, with β = 2, t = 53, s = 53 - 24 = 29, x = av. + * 53 is the number of effective mantissa bits in a Double; + * 24 in a Float. + * + * ◦ is the round-to-nearest operation with a tie-breaking + * rule (in our case, break-to-even). + * + * Let C = βˢ + 1 = 536870913 + * p = ◦(x × C) + * q = ◦(x − p) + * x₁ = ◦(p + q) + * + * Boldo proves that x₁ is the (t-s)-bit float closest to x, + * using the same tie-breaking rule as ◦. Since (t-s) = 24, + * this is the closest float32 (with 24 mantissa bits), and + * therefore the correct result of `fround`. + * + * Boldo also proves that if the computation of x × C does not + * cause overflow, then none of the following operations will + * cause overflow. We know that x (av) is less than the + * overflowThreshold, and overflowThreshold × C does not + * overflow, so that computation can never cause an overflow. + * + * If the reader does not have access to Boldo's paper, they + * may refer instead to + * Claude-Pierre Jeannerod, Jean-Michel Muller, Paul Zimmermann. + * On various ways to split a floating-point number. + * ARITH 2018 - 25th IEEE Symposium on Computer Arithmetic, + * Jun 2018, Amherst (MA), United States. + * pp.53-60, 10.1109/ARITH.2018.8464793. hal-01774587v2 + * available at + * https://hal.inria.fr/hal-01774587v2/document + * Section III, although that paper defers some theorems and + * proofs to Boldo's. + */ + const(p, av * 536870913), + Return(sign * (p + (av - p))) + ), { + /* Here, the result is represented as a subnormal form in a + * float32 representation. + * + * We round `av` to the nearest multiple of the smallest + * positive Float value (i.e., `Float.MinPositiveValue`), + * breaking ties to an even multiple. + * + * We do this by leveraging the inherent loss of precision near + * the minimum positive *double* value: conceptually, we divide + * the value by + * Float.MinPositiveValue / Double.MinPositiveValue + * which will drop the excess precision, applying exactly the + * rounding strategy that we want. Then we multiply the value + * back by the same constant. + * + * However, `Float.MinPositiveValue / Double.MinPositiveValue` + * is not representable as a finite Double. Therefore, we + * instead use the *inverse* constant + * Double.MinPositiveValue / Float.MinPositiveValue + * and we first multiply by that constant, then divide by it. + * + * --- + * + * As an additional "hack", the input values NaN, +0 and -0 + * also fall in this code path. For them, this computation + * happens to be an identity, and is therefore correct as well. + */ + val roundingFactor = double(Double.MinPositiveValue / Float.MinPositiveValue.toDouble) + Return(sign * ((av * roundingFactor) / roundingFactor)) + })) + )) + + If(typeof(Float32ArrayRef) !== str("undefined"), + typedArrayPolyfill, noTypedArrayPolyfill) case PrivateSymbolBuiltin => /* function privateJSFieldSymbol(description) { @@ -693,15 +689,11 @@ private[emitter] object CoreJSLib { }) }) }, { - if (strictFloats) { - If(genCallHelper(VarField.isFloat, instance), { - Return(constantClassResult(BoxedFloatClass)) - }, { - Return(constantClassResult(BoxedDoubleClass)) - }) - } else { + If(genCallHelper(VarField.isFloat, instance), { Return(constantClassResult(BoxedFloatClass)) - } + }, { + Return(constantClassResult(BoxedDoubleClass)) + }) }) ) }, @@ -1313,12 +1305,10 @@ private[emitter] object CoreJSLib { (Apply(genIdentBracketSelect(BigIntRef, "asIntN"), int(64) :: v :: Nil) === v)) } ) ::: - condDefs(strictFloats)( - defineFunction1(VarField.isFloat) { v => - Return((typeof(v) === str("number")) && - ((v !== v) || (genCallPolyfillableBuiltin(FroundBuiltin, v) === v))) - } - ) + defineFunction1(VarField.isFloat) { v => + Return((typeof(v) === str("number")) && + ((v !== v) || (genCallPolyfillableBuiltin(FroundBuiltin, v) === v))) + } } private def defineBoxFunctions(): List[Tree] = ( diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala index 38f73dfd8a..2ff301a7bf 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala @@ -437,7 +437,7 @@ private[emitter] final class SJSGen( case ShortType => genCallHelper(VarField.isShort, expr) case IntType => genCallHelper(VarField.isInt, expr) case LongType => genIsLong(expr) - case FloatType => genIsFloat(expr) + case FloatType => genCallHelper(VarField.isFloat, expr) case DoubleType => typeof(expr) === "number" case StringType => typeof(expr) === "string" @@ -478,7 +478,7 @@ private[emitter] final class SJSGen( case BoxedShortClass => genCallHelper(VarField.isShort, expr) case BoxedIntegerClass => genCallHelper(VarField.isInt, expr) case BoxedLongClass => genIsLong(expr) - case BoxedFloatClass => genIsFloat(expr) + case BoxedFloatClass => genCallHelper(VarField.isFloat, expr) case BoxedDoubleClass => typeof(expr) === "number" case BoxedStringClass => typeof(expr) === "string" } @@ -493,15 +493,6 @@ private[emitter] final class SJSGen( else expr instanceof globalVar(VarField.c, LongImpl.RuntimeLongClass) } - private def genIsFloat(expr: Tree)( - implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, - pos: Position): Tree = { - import TreeDSL._ - - if (semantics.strictFloats) genCallHelper(VarField.isFloat, expr) - else typeof(expr) === "number" - } - def genAsInstanceOf(expr: Tree, tpe: Type)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, tracking: GlobalRefTracking, pos: Position): WithGlobals[Tree] = { @@ -524,8 +515,7 @@ private[emitter] final class SJSGen( case StringType => wg(expr || StringLiteral("")) case FloatType => - if (semantics.strictFloats) genCallPolyfillableBuiltin(FroundBuiltin, expr) - else wg(UnaryOp(irt.JSUnaryOp.+, expr)) + genCallPolyfillableBuiltin(FroundBuiltin, expr) case VoidType | NullType | NothingType | AnyNotNullType | ClassType(_, false) | ArrayType(_, false) | _:RecordType => diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 8ac39f199d..032ec1440e 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -17,6 +17,8 @@ object BinaryIncompatibilities { ) val LinkerInterface = Seq( + // private, not an issue + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.linker.interface.Semantics.this"), ) val SbtPlugin = Seq( diff --git a/project/Build.scala b/project/Build.scala index 446aeebfab..d08f6224c9 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2326,7 +2326,6 @@ object Build { "compliantNullPointers" -> (sems.nullPointers == CheckedBehavior.Compliant), "compliantStringIndexOutOfBounds" -> (sems.stringIndexOutOfBounds == CheckedBehavior.Compliant), "compliantModuleInit" -> (sems.moduleInit == CheckedBehavior.Compliant), - "strictFloats" -> sems.strictFloats, "productionMode" -> sems.productionMode, "esVersion" -> linkerConfig.esFeatures.esVersion.edition, "useECMAScript2015Semantics" -> linkerConfig.esFeatures.useECMAScript2015Semantics, diff --git a/test-suite/js/src/main/scala-ide-stubs/org/scalajs/testsuite/utils/BuildInfo.scala b/test-suite/js/src/main/scala-ide-stubs/org/scalajs/testsuite/utils/BuildInfo.scala index d577790522..8356a0da7c 100644 --- a/test-suite/js/src/main/scala-ide-stubs/org/scalajs/testsuite/utils/BuildInfo.scala +++ b/test-suite/js/src/main/scala-ide-stubs/org/scalajs/testsuite/utils/BuildInfo.scala @@ -31,7 +31,6 @@ private[utils] object BuildInfo { final val compliantNullPointers = false final val compliantStringIndexOutOfBounds = false final val compliantModuleInit = false - final val strictFloats = false final val productionMode = false final val esVersion = 0 final val useECMAScript2015Semantics = false diff --git a/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala b/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala index 6251f85b88..65ab362539 100644 --- a/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala +++ b/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala @@ -88,40 +88,6 @@ object Platform { def hasDirectBuffers: Boolean = typedArrays - /** Do we use strict-floats semantics? - * - * If yes, `number` values that cannot be exactly represented as `Float` - * values respond `false` to an `isInstanceOf[Float]` test. If not, they - * respond `true`. - * - * In addition, if we use strict-float semantics, all arithmetic operations - * on `Float` are accurate wrt. 32-bit floating point semantics. In other - * words, `hasStrictFloats` implies `hasAccurateFloats`. - */ - def hasStrictFloats: Boolean = BuildInfo.strictFloats - - /** Are `Float` arithmetics accurate? - * - * If yes, the result of arithmetic operations on `Float`s, as well as - * `number.toFloat` operations, will be accurate wrt. IEEE-754 32-bit - * floating point operations. In other words, they behave exactly as - * specified on the JVM. - * - * This is true if either or both of the following are true: - * - * - We use strict-float semantics (see `hasStrictFloats`), or - * - The JavaScript runtime supports `Math.fround`. - * - * If neither is true, then the result of `Float` arithmetics can behave as - * if they were `Double` arithmetics instead. - * - * When the runtime does not support `Math.fround` but we strict-float - * semantics, Scala.js uses a semantically correct polyfill for it, which - * guarantees accurate float arithmetics. - */ - def hasAccurateFloats: Boolean = - hasStrictFloats || js.typeOf(js.Dynamic.global.Math.fround) != "undefined" - def regexSupportsUnicodeCase: Boolean = assumedESVersion >= ESVersion.ES2015 diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/FloatJSTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/FloatJSTest.scala index 612ea03d74..054f2bac59 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/FloatJSTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/FloatJSTest.scala @@ -17,8 +17,6 @@ import org.junit.Assert._ import org.scalajs.testsuite.utils.Requires -object FloatJSTest extends Requires.StrictFloats - class FloatJSTest { @noinline def froundNotInlined(x: Double): Float = diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/utils/Requires.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/utils/Requires.scala index c72fed474d..90e539e020 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/utils/Requires.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/utils/Requires.scala @@ -23,9 +23,4 @@ object Requires { assumeTrue("Assumed typed arrays are supported", typedArrays) } - trait StrictFloats { - @BeforeClass def needsTypedArrays(): Unit = - assumeTrue("Assumed strict floats", hasStrictFloats) - } - } diff --git a/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala b/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala index 6b480fd9b6..38e5007356 100644 --- a/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala +++ b/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala @@ -46,8 +46,6 @@ object Platform { def hasCompliantStringIndexOutOfBounds: Boolean = true def hasCompliantModule: Boolean = true def hasDirectBuffers: Boolean = true - def hasStrictFloats: Boolean = true - def hasAccurateFloats: Boolean = true def regexSupportsUnicodeCase: Boolean = true def regexSupportsUnicodeCharacterClasses: Boolean = true diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/DoubleTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/DoubleTest.scala index 6f158aa23e..f7b7d2005d 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/DoubleTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/DoubleTest.scala @@ -16,8 +16,6 @@ import org.junit.Test import org.junit.Assert._ import org.junit.Assume._ -import org.scalajs.testsuite.utils.Platform.hasAccurateFloats - class DoubleTest { final def assertExactEquals(expected: Double, actual: Double): Unit = assertTrue(s"expected: $expected; actual: $actual", expected.equals(actual)) @@ -62,8 +60,6 @@ class DoubleTest { def toFloat(): Unit = { // This is the closest we get to directly testing our `Math.fround` polyfill - assumeTrue("requires accurate floats", hasAccurateFloats) - @noinline def test(expected: Float, value: Double): Unit = assertExactEquals(s"for value $value", expected, value.toFloat) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/FloatTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/FloatTest.scala index d4dbdee940..60a4e40060 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/FloatTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/FloatTest.scala @@ -15,8 +15,6 @@ package org.scalajs.testsuite.compiler import org.junit.Test import org.junit.Assert._ -import org.scalajs.testsuite.utils.Platform.hasStrictFloats - class FloatTest { final def assertExactEquals(expected: Float, actual: Float): Unit = assertTrue(s"expected: $expected; actual: $actual", expected.equals(actual)) @@ -144,17 +142,15 @@ class FloatTest { // Non-special values // { val l = List(2.1f, 5.5f, -151.189f); for (n <- l; d <- l) println(s" test(${n % d}f, ${n}f, ${d}f)") } - if (hasStrictFloats) { - test(0.0f, 2.1f, 2.1f) - test(2.1f, 2.1f, 5.5f) - test(2.1f, 2.1f, -151.189f) - test(1.3000002f, 5.5f, 2.1f) - test(0.0f, 5.5f, 5.5f) - test(5.5f, 5.5f, -151.189f) - test(-2.0890021f, -151.189f, 2.1f) - test(-2.6889954f, -151.189f, 5.5f) - test(-0.0f, -151.189f, -151.189f) - } + test(0.0f, 2.1f, 2.1f) + test(2.1f, 2.1f, 5.5f) + test(2.1f, 2.1f, -151.189f) + test(1.3000002f, 5.5f, 2.1f) + test(0.0f, 5.5f, 5.5f) + test(5.5f, 5.5f, -151.189f) + test(-2.0890021f, -151.189f, 2.1f) + test(-2.6889954f, -151.189f, 5.5f) + test(-0.0f, -151.189f, -151.189f) } @Test @@ -244,10 +240,8 @@ class FloatTest { @inline def negate(x: Float): Float = -x - if (hasStrictFloats) { - assertExactEquals(0.8f, (hide(0.1f) + 0.3f) + 0.4f) - assertExactEquals(0.8000001f, 0.1f + (0.3f + hide(0.4f))) - } + assertExactEquals(0.8f, (hide(0.1f) + 0.3f) + 0.4f) + assertExactEquals(0.8000001f, 0.1f + (0.3f + hide(0.4f))) assertExactEquals(0.0f, 0.0f + hide(-0.0f)) assertExactEquals(0.0f, 0.0f - hide(0.0f)) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/LongTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/LongTest.scala index dd9ed7a5a6..108dc817de 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/LongTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/LongTest.scala @@ -609,8 +609,6 @@ class LongTest { } @Test def toFloat(): Unit = { - assumeTrue("Assumed accurate floats", hasAccurateFloats) - @inline def test(expected: Float, x: Long, epsilon: Float = 0.0f): Unit = { assertEquals(expected, x.toFloat, epsilon) assertEquals(expected, hideFromOptimizer(x).toFloat, epsilon) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RuntimeTypeTestsTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RuntimeTypeTestsTest.scala index 5916c9a301..1b73998241 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RuntimeTypeTestsTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RuntimeTypeTestsTest.scala @@ -198,7 +198,7 @@ class RuntimeTypeTestsTest { testFloat(false, 'e') testDouble(false, 'f') - testFloat(!hasStrictFloats, 1.2) + testFloat(false, 1.2) // Special cases for negative 0, NaN and Infinity diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ClassTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ClassTest.scala index a447e5c48b..4994137cf8 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ClassTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ClassTest.scala @@ -108,7 +108,7 @@ class ClassTest { test("java.lang.Float", -0.0f) test("java.lang.Float", 1.5f) test("java.lang.Float", Float.NaN) - test(if (hasStrictFloats) "java.lang.Double" else "java.lang.Float", 1.4) + test("java.lang.Double", 1.4) test("java.lang.String", "hello") test("java.lang.Object", new Object) test("scala.Some", Some(5)) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/FloatTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/FloatTest.scala index e16c2703d2..37bcc4838a 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/FloatTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/FloatTest.scala @@ -21,7 +21,7 @@ import java.lang.{Float => JFloat} import scala.util.Try import org.scalajs.testsuite.utils.AssertThrows.assertThrows -import org.scalajs.testsuite.utils.Platform.{executingInJVM, hasAccurateFloats} +import org.scalajs.testsuite.utils.Platform.executingInJVM class FloatTest { @@ -109,16 +109,9 @@ class FloatTest { @Test def parseStringMethods(): Unit = { def test(expected: Float, s: String): Unit = { - if (hasAccurateFloats) { - assertEquals(s, expected: Any, JFloat.parseFloat(s)) - assertEquals(s, expected: Any, JFloat.valueOf(s).floatValue()) - assertEquals(s, expected: Any, new JFloat(s).floatValue()) - } else { - val epsilon = Math.ulp(expected) - assertEquals(s, expected, JFloat.parseFloat(s), epsilon) - assertEquals(s, expected, JFloat.valueOf(s).floatValue(), epsilon) - assertEquals(s, expected, new JFloat(s).floatValue(), epsilon) - } + assertEquals(s, expected: Any, JFloat.parseFloat(s)) + assertEquals(s, expected: Any, JFloat.valueOf(s).floatValue()) + assertEquals(s, expected: Any, new JFloat(s).floatValue()) } // Specials diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ObjectTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ObjectTest.scala index 2868849763..cd86d36945 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ObjectTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ObjectTest.scala @@ -43,7 +43,7 @@ class ObjectTest { test(classOf[java.lang.Float], -0.0f) test(classOf[java.lang.Float], 1.5f) test(classOf[java.lang.Float], Float.NaN) - test(if (hasStrictFloats) classOf[java.lang.Double] else classOf[java.lang.Float], 1.4) + test(classOf[java.lang.Double], 1.4) test(classOf[java.lang.String], "hello") test(classOf[java.lang.Object], new Object) test(classOf[Some[_]], Some(5)) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/BigDecimalConvertTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/BigDecimalConvertTest.scala index 57533ab042..89c611735c 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/BigDecimalConvertTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/BigDecimalConvertTest.scala @@ -73,8 +73,6 @@ class BigDecimalConvertTest { } @Test def testFloatValueNegInfinity(): Unit = { - assumeTrue("requires accurate floats", hasAccurateFloats) - val a = "-123809648392384755735.63567887678287E+200" val aNumber = new BigDecimal(a) val result = Float.NegativeInfinity @@ -89,8 +87,6 @@ class BigDecimalConvertTest { } @Test def testFloatValuePosInfinity(): Unit = { - assumeTrue("requires accurate floats", hasAccurateFloats) - val a = "123809648373567356745735.6356789787678287E+200" val aNumber = new BigDecimal(a) val result = Float.PositiveInfinity @@ -99,14 +95,8 @@ class BigDecimalConvertTest { /** Test cases for `Float.parseFloat`, with an indirection through `BigDecimal`. */ @Test def testFloatValueLikeParseFloat_Issue4726(): Unit = { - def test(expected: Float, s: String): Unit = { - if (hasAccurateFloats) { - assertEquals(s, expected: Any, new BigDecimal(s).floatValue()) - } else { - val epsilon = Math.ulp(expected) - assertEquals(s, expected, new BigDecimal(s).floatValue(), epsilon) - } - } + def test(expected: Float, s: String): Unit = + assertEquals(s, expected: Any, new BigDecimal(s).floatValue()) // Zeros (BigDecimal has no negative 0, so they all parse to +0.0f) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/BigIntegerConvertTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/BigIntegerConvertTest.scala index c383d455f4..cc2e5cd3e6 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/BigIntegerConvertTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/BigIntegerConvertTest.scala @@ -255,8 +255,6 @@ class BigIntegerConvertTest { } @Test def testFloatValueNegativeInfinity2(): Unit = { - assumeTrue("requires accurate floats", hasAccurateFloats) - val a = Array[Byte](0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1) val aSign = -1 val aNumber = new BigInteger(aSign, a).floatValue() @@ -264,8 +262,6 @@ class BigIntegerConvertTest { } @Test def testFloatValueNegMantissaIsZero(): Unit = { - assumeTrue("requires accurate floats", hasAccurateFloats) - val a = Array[Byte](1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) val aSign = -1 val aNumber = new BigInteger(aSign, a).floatValue() @@ -300,8 +296,6 @@ class BigIntegerConvertTest { } @Test def testFloatValuePastNegMaxValue(): Unit = { - assumeTrue("requires accurate floats", hasAccurateFloats) - val a = Array[Byte](0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1) val aSign = -1 val aNumber = new BigInteger(aSign, a).floatValue() @@ -309,8 +303,6 @@ class BigIntegerConvertTest { } @Test def testFloatValuePastPosMaxValue(): Unit = { - assumeTrue("requires accurate floats", hasAccurateFloats) - val a = Array[Byte](0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1) val aSign = 1 val aNumber = new BigInteger(aSign, a).floatValue() @@ -333,8 +325,6 @@ class BigIntegerConvertTest { } @Test def testFloatValuePositiveInfinity1(): Unit = { - assumeTrue("requires accurate floats", hasAccurateFloats) - val a = Array[Byte](0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1) val aSign = 1 val aNumber: Float = new BigInteger(aSign, a).floatValue() @@ -648,8 +638,6 @@ class BigIntegerConvertTest { } @Test def testFloatValueBug2482(): Unit = { - assumeTrue("Assumed strict floats", hasStrictFloats) - val a = "2147483649" val result = 2.14748365E9f val aNumber = new BigInteger(a).floatValue() From dbce9db8527d81b9324f1daae1d347f945438a5e Mon Sep 17 00:00:00 2001 From: Leonid Dubinsky Date: Thu, 20 Feb 2025 15:01:06 -0500 Subject: [PATCH 082/121] JUnit: populate sbt.testing.Event.throwable on test failure If a Throwable associated with the test event is available, bubble it up through the SBT's test interface using the slot dedicated to just such information: `sbt.testing.Event.throwable`. In addition to it being a shame to throw away information about the test failure that is literally in our hands, this simplifies integration with runners that assume that test failure is always accompanied by a `Throwable` (e.g., Gradle). --- .../scala/org/scalajs/junit/Reporter.scala | 12 ++++--- .../junit/AssertEquals2TestAssertions_.txt | 2 +- .../junit/AssertEquals2TestAssertions_a.txt | 2 +- .../junit/AssertEquals2TestAssertions_n.txt | 2 +- .../junit/AssertEquals2TestAssertions_na.txt | 2 +- .../junit/AssertEquals2TestAssertions_nv.txt | 2 +- .../junit/AssertEquals2TestAssertions_nva.txt | 2 +- .../junit/AssertEquals2TestAssertions_nvc.txt | 2 +- .../AssertEquals2TestAssertions_nvca.txt | 2 +- .../junit/AssertEquals2TestAssertions_v.txt | 2 +- .../junit/AssertEquals2TestAssertions_va.txt | 2 +- .../junit/AssertEquals2TestAssertions_vc.txt | 2 +- .../junit/AssertEquals2TestAssertions_vs.txt | 2 +- .../junit/AssertEquals2TestAssertions_vsn.txt | 2 +- .../AssertEqualsDoubleTestAssertions_.txt | 12 +++---- .../AssertEqualsDoubleTestAssertions_a.txt | 12 +++---- .../AssertEqualsDoubleTestAssertions_n.txt | 12 +++---- .../AssertEqualsDoubleTestAssertions_na.txt | 12 +++---- .../AssertEqualsDoubleTestAssertions_nv.txt | 12 +++---- .../AssertEqualsDoubleTestAssertions_nva.txt | 12 +++---- .../AssertEqualsDoubleTestAssertions_nvc.txt | 12 +++---- .../AssertEqualsDoubleTestAssertions_nvca.txt | 12 +++---- .../AssertEqualsDoubleTestAssertions_v.txt | 12 +++---- .../AssertEqualsDoubleTestAssertions_va.txt | 12 +++---- .../AssertEqualsDoubleTestAssertions_vc.txt | 12 +++---- .../AssertEqualsDoubleTestAssertions_vs.txt | 12 +++---- .../AssertEqualsDoubleTestAssertions_vsn.txt | 12 +++---- .../junit/AssertEqualsTestAssertions_.txt | 2 +- .../junit/AssertEqualsTestAssertions_a.txt | 2 +- .../junit/AssertEqualsTestAssertions_n.txt | 2 +- .../junit/AssertEqualsTestAssertions_na.txt | 2 +- .../junit/AssertEqualsTestAssertions_nv.txt | 2 +- .../junit/AssertEqualsTestAssertions_nva.txt | 2 +- .../junit/AssertEqualsTestAssertions_nvc.txt | 2 +- .../junit/AssertEqualsTestAssertions_nvca.txt | 2 +- .../junit/AssertEqualsTestAssertions_v.txt | 2 +- .../junit/AssertEqualsTestAssertions_va.txt | 2 +- .../junit/AssertEqualsTestAssertions_vc.txt | 2 +- .../junit/AssertEqualsTestAssertions_vs.txt | 2 +- .../junit/AssertEqualsTestAssertions_vsn.txt | 2 +- .../junit/AssertFalse2TestAssertions_.txt | 2 +- .../junit/AssertFalse2TestAssertions_a.txt | 2 +- .../junit/AssertFalse2TestAssertions_n.txt | 2 +- .../junit/AssertFalse2TestAssertions_na.txt | 2 +- .../junit/AssertFalse2TestAssertions_nv.txt | 2 +- .../junit/AssertFalse2TestAssertions_nva.txt | 2 +- .../junit/AssertFalse2TestAssertions_nvc.txt | 2 +- .../junit/AssertFalse2TestAssertions_nvca.txt | 2 +- .../junit/AssertFalse2TestAssertions_v.txt | 2 +- .../junit/AssertFalse2TestAssertions_va.txt | 2 +- .../junit/AssertFalse2TestAssertions_vc.txt | 2 +- .../junit/AssertFalse2TestAssertions_vs.txt | 2 +- .../junit/AssertFalse2TestAssertions_vsn.txt | 2 +- .../junit/AssertFalseTestAssertions_.txt | 2 +- .../junit/AssertFalseTestAssertions_a.txt | 2 +- .../junit/AssertFalseTestAssertions_n.txt | 2 +- .../junit/AssertFalseTestAssertions_na.txt | 2 +- .../junit/AssertFalseTestAssertions_nv.txt | 2 +- .../junit/AssertFalseTestAssertions_nva.txt | 2 +- .../junit/AssertFalseTestAssertions_nvc.txt | 2 +- .../junit/AssertFalseTestAssertions_nvca.txt | 2 +- .../junit/AssertFalseTestAssertions_v.txt | 2 +- .../junit/AssertFalseTestAssertions_va.txt | 2 +- .../junit/AssertFalseTestAssertions_vc.txt | 2 +- .../junit/AssertFalseTestAssertions_vs.txt | 2 +- .../junit/AssertFalseTestAssertions_vsn.txt | 2 +- .../AssertStringEqualsTestAssertions_.txt | 2 +- .../AssertStringEqualsTestAssertions_a.txt | 2 +- .../AssertStringEqualsTestAssertions_n.txt | 2 +- .../AssertStringEqualsTestAssertions_na.txt | 2 +- .../AssertStringEqualsTestAssertions_nv.txt | 2 +- .../AssertStringEqualsTestAssertions_nva.txt | 2 +- .../AssertStringEqualsTestAssertions_nvc.txt | 2 +- .../AssertStringEqualsTestAssertions_nvca.txt | 2 +- .../AssertStringEqualsTestAssertions_v.txt | 2 +- .../AssertStringEqualsTestAssertions_va.txt | 2 +- .../AssertStringEqualsTestAssertions_vc.txt | 2 +- .../AssertStringEqualsTestAssertions_vs.txt | 2 +- .../AssertStringEqualsTestAssertions_vsn.txt | 2 +- .../junit/AssertTrueTestAssertions_.txt | 4 +-- .../junit/AssertTrueTestAssertions_a.txt | 4 +-- .../junit/AssertTrueTestAssertions_n.txt | 4 +-- .../junit/AssertTrueTestAssertions_na.txt | 4 +-- .../junit/AssertTrueTestAssertions_nv.txt | 4 +-- .../junit/AssertTrueTestAssertions_nva.txt | 4 +-- .../junit/AssertTrueTestAssertions_nvc.txt | 4 +-- .../junit/AssertTrueTestAssertions_nvca.txt | 4 +-- .../junit/AssertTrueTestAssertions_v.txt | 4 +-- .../junit/AssertTrueTestAssertions_va.txt | 4 +-- .../junit/AssertTrueTestAssertions_vc.txt | 4 +-- .../junit/AssertTrueTestAssertions_vs.txt | 4 +-- .../junit/AssertTrueTestAssertions_vsn.txt | 4 +-- .../junit/AssumeAfterAssumeAssertions_.txt | 2 +- .../junit/AssumeAfterAssumeAssertions_a.txt | 2 +- .../junit/AssumeAfterAssumeAssertions_n.txt | 2 +- .../junit/AssumeAfterAssumeAssertions_na.txt | 2 +- .../junit/AssumeAfterAssumeAssertions_nv.txt | 2 +- .../junit/AssumeAfterAssumeAssertions_nva.txt | 2 +- .../junit/AssumeAfterAssumeAssertions_nvc.txt | 2 +- .../AssumeAfterAssumeAssertions_nvca.txt | 2 +- .../junit/AssumeAfterAssumeAssertions_v.txt | 2 +- .../junit/AssumeAfterAssumeAssertions_va.txt | 2 +- .../junit/AssumeAfterAssumeAssertions_vc.txt | 2 +- .../junit/AssumeAfterAssumeAssertions_vs.txt | 2 +- .../junit/AssumeAfterAssumeAssertions_vsn.txt | 2 +- .../junit/AssumeAfterClassTestAssertions_.txt | 4 +-- .../AssumeAfterClassTestAssertions_a.txt | 4 +-- .../AssumeAfterClassTestAssertions_n.txt | 4 +-- .../AssumeAfterClassTestAssertions_na.txt | 4 +-- .../AssumeAfterClassTestAssertions_nv.txt | 4 +-- .../AssumeAfterClassTestAssertions_nva.txt | 4 +-- .../AssumeAfterClassTestAssertions_nvc.txt | 4 +-- .../AssumeAfterClassTestAssertions_nvca.txt | 4 +-- .../AssumeAfterClassTestAssertions_v.txt | 4 +-- .../AssumeAfterClassTestAssertions_va.txt | 4 +-- .../AssumeAfterClassTestAssertions_vc.txt | 4 +-- .../AssumeAfterClassTestAssertions_vs.txt | 4 +-- .../AssumeAfterClassTestAssertions_vsn.txt | 4 +-- .../junit/AssumeAfterExceptionAssertions_.txt | 2 +- .../AssumeAfterExceptionAssertions_a.txt | 2 +- .../AssumeAfterExceptionAssertions_n.txt | 2 +- .../AssumeAfterExceptionAssertions_na.txt | 2 +- .../AssumeAfterExceptionAssertions_nv.txt | 2 +- .../AssumeAfterExceptionAssertions_nva.txt | 2 +- .../AssumeAfterExceptionAssertions_nvc.txt | 2 +- .../AssumeAfterExceptionAssertions_nvca.txt | 2 +- .../AssumeAfterExceptionAssertions_v.txt | 2 +- .../AssumeAfterExceptionAssertions_va.txt | 2 +- .../AssumeAfterExceptionAssertions_vc.txt | 2 +- .../AssumeAfterExceptionAssertions_vs.txt | 2 +- .../AssumeAfterExceptionAssertions_vsn.txt | 2 +- .../junit/AssumeInAfterAssertions_.txt | 2 +- .../junit/AssumeInAfterAssertions_a.txt | 2 +- .../junit/AssumeInAfterAssertions_n.txt | 2 +- .../junit/AssumeInAfterAssertions_na.txt | 2 +- .../junit/AssumeInAfterAssertions_nv.txt | 2 +- .../junit/AssumeInAfterAssertions_nva.txt | 2 +- .../junit/AssumeInAfterAssertions_nvc.txt | 2 +- .../junit/AssumeInAfterAssertions_nvca.txt | 2 +- .../junit/AssumeInAfterAssertions_v.txt | 2 +- .../junit/AssumeInAfterAssertions_va.txt | 2 +- .../junit/AssumeInAfterAssertions_vc.txt | 2 +- .../junit/AssumeInAfterAssertions_vs.txt | 2 +- .../junit/AssumeInAfterAssertions_vsn.txt | 2 +- .../scalajs/junit/AssumeTestAssertions_.txt | 2 +- .../scalajs/junit/AssumeTestAssertions_a.txt | 2 +- .../scalajs/junit/AssumeTestAssertions_n.txt | 2 +- .../scalajs/junit/AssumeTestAssertions_na.txt | 2 +- .../scalajs/junit/AssumeTestAssertions_nv.txt | 2 +- .../junit/AssumeTestAssertions_nva.txt | 2 +- .../junit/AssumeTestAssertions_nvc.txt | 2 +- .../junit/AssumeTestAssertions_nvca.txt | 2 +- .../scalajs/junit/AssumeTestAssertions_v.txt | 2 +- .../scalajs/junit/AssumeTestAssertions_va.txt | 2 +- .../scalajs/junit/AssumeTestAssertions_vc.txt | 2 +- .../scalajs/junit/AssumeTestAssertions_vs.txt | 2 +- .../junit/AssumeTestAssertions_vsn.txt | 2 +- .../scalajs/junit/AsyncTestAssertions_.txt | 6 ++-- .../scalajs/junit/AsyncTestAssertions_a.txt | 6 ++-- .../scalajs/junit/AsyncTestAssertions_n.txt | 6 ++-- .../scalajs/junit/AsyncTestAssertions_na.txt | 6 ++-- .../scalajs/junit/AsyncTestAssertions_nv.txt | 6 ++-- .../scalajs/junit/AsyncTestAssertions_nva.txt | 6 ++-- .../scalajs/junit/AsyncTestAssertions_nvc.txt | 6 ++-- .../junit/AsyncTestAssertions_nvca.txt | 6 ++-- .../scalajs/junit/AsyncTestAssertions_v.txt | 6 ++-- .../scalajs/junit/AsyncTestAssertions_va.txt | 6 ++-- .../scalajs/junit/AsyncTestAssertions_vc.txt | 6 ++-- .../scalajs/junit/AsyncTestAssertions_vs.txt | 6 ++-- .../scalajs/junit/AsyncTestAssertions_vsn.txt | 6 ++-- .../junit/BeforeAndAfterTestAssertions_.txt | 2 +- .../junit/BeforeAndAfterTestAssertions_a.txt | 2 +- .../junit/BeforeAndAfterTestAssertions_n.txt | 2 +- .../junit/BeforeAndAfterTestAssertions_na.txt | 2 +- .../junit/BeforeAndAfterTestAssertions_nv.txt | 2 +- .../BeforeAndAfterTestAssertions_nva.txt | 2 +- .../BeforeAndAfterTestAssertions_nvc.txt | 2 +- .../BeforeAndAfterTestAssertions_nvca.txt | 2 +- .../junit/BeforeAndAfterTestAssertions_v.txt | 2 +- .../junit/BeforeAndAfterTestAssertions_va.txt | 2 +- .../junit/BeforeAndAfterTestAssertions_vc.txt | 2 +- .../junit/BeforeAndAfterTestAssertions_vs.txt | 2 +- .../BeforeAndAfterTestAssertions_vsn.txt | 2 +- .../junit/BeforeAssumeFailTestAssertions_.txt | 2 +- .../BeforeAssumeFailTestAssertions_a.txt | 2 +- .../BeforeAssumeFailTestAssertions_n.txt | 2 +- .../BeforeAssumeFailTestAssertions_na.txt | 2 +- .../BeforeAssumeFailTestAssertions_nv.txt | 2 +- .../BeforeAssumeFailTestAssertions_nva.txt | 2 +- .../BeforeAssumeFailTestAssertions_nvc.txt | 2 +- .../BeforeAssumeFailTestAssertions_nvca.txt | 2 +- .../BeforeAssumeFailTestAssertions_v.txt | 2 +- .../BeforeAssumeFailTestAssertions_va.txt | 2 +- .../BeforeAssumeFailTestAssertions_vc.txt | 2 +- .../BeforeAssumeFailTestAssertions_vs.txt | 2 +- .../BeforeAssumeFailTestAssertions_vsn.txt | 2 +- .../junit/ExceptionAfterAssumeAssertions_.txt | 2 +- .../ExceptionAfterAssumeAssertions_a.txt | 2 +- .../ExceptionAfterAssumeAssertions_n.txt | 2 +- .../ExceptionAfterAssumeAssertions_na.txt | 2 +- .../ExceptionAfterAssumeAssertions_nv.txt | 2 +- .../ExceptionAfterAssumeAssertions_nva.txt | 2 +- .../ExceptionAfterAssumeAssertions_nvc.txt | 2 +- .../ExceptionAfterAssumeAssertions_nvca.txt | 2 +- .../ExceptionAfterAssumeAssertions_v.txt | 2 +- .../ExceptionAfterAssumeAssertions_va.txt | 2 +- .../ExceptionAfterAssumeAssertions_vc.txt | 2 +- .../ExceptionAfterAssumeAssertions_vs.txt | 2 +- .../ExceptionAfterAssumeAssertions_vsn.txt | 2 +- .../junit/ExceptionAfterClassAssertions_.txt | 6 ++-- .../junit/ExceptionAfterClassAssertions_a.txt | 6 ++-- .../junit/ExceptionAfterClassAssertions_n.txt | 6 ++-- .../ExceptionAfterClassAssertions_na.txt | 6 ++-- .../ExceptionAfterClassAssertions_nv.txt | 6 ++-- .../ExceptionAfterClassAssertions_nva.txt | 6 ++-- .../ExceptionAfterClassAssertions_nvc.txt | 6 ++-- .../ExceptionAfterClassAssertions_nvca.txt | 6 ++-- .../junit/ExceptionAfterClassAssertions_v.txt | 6 ++-- .../ExceptionAfterClassAssertions_va.txt | 6 ++-- .../ExceptionAfterClassAssertions_vc.txt | 6 ++-- .../ExceptionAfterClassAssertions_vs.txt | 6 ++-- .../ExceptionAfterClassAssertions_vsn.txt | 6 ++-- ...xceptionBeforeAndAfterClassAssertions_.txt | 2 +- ...ceptionBeforeAndAfterClassAssertions_a.txt | 2 +- ...ceptionBeforeAndAfterClassAssertions_n.txt | 2 +- ...eptionBeforeAndAfterClassAssertions_na.txt | 2 +- ...eptionBeforeAndAfterClassAssertions_nv.txt | 2 +- ...ptionBeforeAndAfterClassAssertions_nva.txt | 2 +- ...ptionBeforeAndAfterClassAssertions_nvc.txt | 2 +- ...tionBeforeAndAfterClassAssertions_nvca.txt | 2 +- ...ceptionBeforeAndAfterClassAssertions_v.txt | 2 +- ...eptionBeforeAndAfterClassAssertions_va.txt | 2 +- ...eptionBeforeAndAfterClassAssertions_vc.txt | 2 +- ...eptionBeforeAndAfterClassAssertions_vs.txt | 2 +- ...ptionBeforeAndAfterClassAssertions_vsn.txt | 2 +- .../junit/ExceptionBeforeClassAssertions_.txt | 2 +- .../ExceptionBeforeClassAssertions_a.txt | 2 +- .../ExceptionBeforeClassAssertions_n.txt | 2 +- .../ExceptionBeforeClassAssertions_na.txt | 2 +- .../ExceptionBeforeClassAssertions_nv.txt | 2 +- .../ExceptionBeforeClassAssertions_nva.txt | 2 +- .../ExceptionBeforeClassAssertions_nvc.txt | 2 +- .../ExceptionBeforeClassAssertions_nvca.txt | 2 +- .../ExceptionBeforeClassAssertions_v.txt | 2 +- .../ExceptionBeforeClassAssertions_va.txt | 2 +- .../ExceptionBeforeClassAssertions_vc.txt | 2 +- .../ExceptionBeforeClassAssertions_vs.txt | 2 +- .../ExceptionBeforeClassAssertions_vsn.txt | 2 +- .../junit/ExceptionEverywhereAssertions_.txt | 2 +- .../junit/ExceptionEverywhereAssertions_a.txt | 2 +- .../junit/ExceptionEverywhereAssertions_n.txt | 2 +- .../ExceptionEverywhereAssertions_na.txt | 2 +- .../ExceptionEverywhereAssertions_nv.txt | 2 +- .../ExceptionEverywhereAssertions_nva.txt | 2 +- .../ExceptionEverywhereAssertions_nvc.txt | 2 +- .../ExceptionEverywhereAssertions_nvca.txt | 2 +- .../junit/ExceptionEverywhereAssertions_v.txt | 2 +- .../ExceptionEverywhereAssertions_va.txt | 2 +- .../ExceptionEverywhereAssertions_vc.txt | 2 +- .../ExceptionEverywhereAssertions_vs.txt | 2 +- .../ExceptionEverywhereAssertions_vsn.txt | 2 +- .../junit/ExceptionInAfterTestAssertions_.txt | 2 +- .../ExceptionInAfterTestAssertions_a.txt | 2 +- .../ExceptionInAfterTestAssertions_n.txt | 2 +- .../ExceptionInAfterTestAssertions_na.txt | 2 +- .../ExceptionInAfterTestAssertions_nv.txt | 2 +- .../ExceptionInAfterTestAssertions_nva.txt | 2 +- .../ExceptionInAfterTestAssertions_nvc.txt | 2 +- .../ExceptionInAfterTestAssertions_nvca.txt | 2 +- .../ExceptionInAfterTestAssertions_v.txt | 2 +- .../ExceptionInAfterTestAssertions_va.txt | 2 +- .../ExceptionInAfterTestAssertions_vc.txt | 2 +- .../ExceptionInAfterTestAssertions_vs.txt | 2 +- .../ExceptionInAfterTestAssertions_vsn.txt | 2 +- .../ExceptionInBeforeTestAssertions_.txt | 2 +- .../ExceptionInBeforeTestAssertions_a.txt | 2 +- .../ExceptionInBeforeTestAssertions_n.txt | 2 +- .../ExceptionInBeforeTestAssertions_na.txt | 2 +- .../ExceptionInBeforeTestAssertions_nv.txt | 2 +- .../ExceptionInBeforeTestAssertions_nva.txt | 2 +- .../ExceptionInBeforeTestAssertions_nvc.txt | 2 +- .../ExceptionInBeforeTestAssertions_nvca.txt | 2 +- .../ExceptionInBeforeTestAssertions_v.txt | 2 +- .../ExceptionInBeforeTestAssertions_va.txt | 2 +- .../ExceptionInBeforeTestAssertions_vc.txt | 2 +- .../ExceptionInBeforeTestAssertions_vs.txt | 2 +- .../ExceptionInBeforeTestAssertions_vsn.txt | 2 +- .../ExceptionInConstructorTestAssertions_.txt | 2 +- ...ExceptionInConstructorTestAssertions_a.txt | 2 +- ...ExceptionInConstructorTestAssertions_n.txt | 2 +- ...xceptionInConstructorTestAssertions_na.txt | 2 +- ...xceptionInConstructorTestAssertions_nv.txt | 2 +- ...ceptionInConstructorTestAssertions_nva.txt | 2 +- ...ceptionInConstructorTestAssertions_nvc.txt | 2 +- ...eptionInConstructorTestAssertions_nvca.txt | 2 +- ...ExceptionInConstructorTestAssertions_v.txt | 2 +- ...xceptionInConstructorTestAssertions_va.txt | 2 +- ...xceptionInConstructorTestAssertions_vc.txt | 2 +- ...xceptionInConstructorTestAssertions_vs.txt | 2 +- ...ceptionInConstructorTestAssertions_vsn.txt | 2 +- .../junit/ExceptionTestAssertions_.txt | 2 +- .../junit/ExceptionTestAssertions_a.txt | 2 +- .../junit/ExceptionTestAssertions_n.txt | 2 +- .../junit/ExceptionTestAssertions_na.txt | 2 +- .../junit/ExceptionTestAssertions_nv.txt | 2 +- .../junit/ExceptionTestAssertions_nva.txt | 2 +- .../junit/ExceptionTestAssertions_nvc.txt | 2 +- .../junit/ExceptionTestAssertions_nvca.txt | 2 +- .../junit/ExceptionTestAssertions_v.txt | 2 +- .../junit/ExceptionTestAssertions_va.txt | 2 +- .../junit/ExceptionTestAssertions_vc.txt | 2 +- .../junit/ExceptionTestAssertions_vs.txt | 2 +- .../junit/ExceptionTestAssertions_vsn.txt | 2 +- .../scalajs/junit/ExpectTestAssertions_.txt | 10 +++--- .../scalajs/junit/ExpectTestAssertions_a.txt | 10 +++--- .../scalajs/junit/ExpectTestAssertions_n.txt | 10 +++--- .../scalajs/junit/ExpectTestAssertions_na.txt | 10 +++--- .../scalajs/junit/ExpectTestAssertions_nv.txt | 10 +++--- .../junit/ExpectTestAssertions_nva.txt | 10 +++--- .../junit/ExpectTestAssertions_nvc.txt | 10 +++--- .../junit/ExpectTestAssertions_nvca.txt | 10 +++--- .../scalajs/junit/ExpectTestAssertions_v.txt | 10 +++--- .../scalajs/junit/ExpectTestAssertions_va.txt | 10 +++--- .../scalajs/junit/ExpectTestAssertions_vc.txt | 10 +++--- .../scalajs/junit/ExpectTestAssertions_vs.txt | 10 +++--- .../junit/ExpectTestAssertions_vsn.txt | 10 +++--- .../scalajs/junit/IgnoreTestAssertions_.txt | 2 +- .../scalajs/junit/IgnoreTestAssertions_a.txt | 2 +- .../scalajs/junit/IgnoreTestAssertions_n.txt | 2 +- .../scalajs/junit/IgnoreTestAssertions_na.txt | 2 +- .../scalajs/junit/IgnoreTestAssertions_nv.txt | 2 +- .../junit/IgnoreTestAssertions_nva.txt | 2 +- .../junit/IgnoreTestAssertions_nvc.txt | 2 +- .../junit/IgnoreTestAssertions_nvca.txt | 2 +- .../scalajs/junit/IgnoreTestAssertions_v.txt | 2 +- .../scalajs/junit/IgnoreTestAssertions_va.txt | 2 +- .../scalajs/junit/IgnoreTestAssertions_vc.txt | 2 +- .../scalajs/junit/IgnoreTestAssertions_vs.txt | 2 +- .../junit/IgnoreTestAssertions_vsn.txt | 2 +- .../junit/MethodNameDecodeTestAssertions_.txt | 2 +- .../MethodNameDecodeTestAssertions_a.txt | 2 +- .../MethodNameDecodeTestAssertions_n.txt | 2 +- .../MethodNameDecodeTestAssertions_na.txt | 2 +- .../MethodNameDecodeTestAssertions_nv.txt | 2 +- .../MethodNameDecodeTestAssertions_nva.txt | 2 +- .../MethodNameDecodeTestAssertions_nvc.txt | 2 +- .../MethodNameDecodeTestAssertions_nvca.txt | 2 +- .../MethodNameDecodeTestAssertions_v.txt | 2 +- .../MethodNameDecodeTestAssertions_va.txt | 2 +- .../MethodNameDecodeTestAssertions_vc.txt | 2 +- .../MethodNameDecodeTestAssertions_vs.txt | 2 +- .../MethodNameDecodeTestAssertions_vsn.txt | 2 +- .../scalajs/junit/Multi1TestAssertions_.txt | 4 +-- .../scalajs/junit/Multi1TestAssertions_a.txt | 4 +-- .../scalajs/junit/Multi1TestAssertions_n.txt | 4 +-- .../scalajs/junit/Multi1TestAssertions_na.txt | 4 +-- .../scalajs/junit/Multi1TestAssertions_nv.txt | 4 +-- .../junit/Multi1TestAssertions_nva.txt | 4 +-- .../junit/Multi1TestAssertions_nvc.txt | 4 +-- .../junit/Multi1TestAssertions_nvca.txt | 4 +-- .../scalajs/junit/Multi1TestAssertions_v.txt | 4 +-- .../scalajs/junit/Multi1TestAssertions_va.txt | 4 +-- .../scalajs/junit/Multi1TestAssertions_vc.txt | 4 +-- .../scalajs/junit/Multi1TestAssertions_vs.txt | 4 +-- .../junit/Multi1TestAssertions_vsn.txt | 4 +-- .../scalajs/junit/Multi2TestAssertions_.txt | 10 +++--- .../scalajs/junit/Multi2TestAssertions_a.txt | 10 +++--- .../scalajs/junit/Multi2TestAssertions_n.txt | 10 +++--- .../scalajs/junit/Multi2TestAssertions_na.txt | 10 +++--- .../scalajs/junit/Multi2TestAssertions_nv.txt | 10 +++--- .../junit/Multi2TestAssertions_nva.txt | 10 +++--- .../junit/Multi2TestAssertions_nvc.txt | 10 +++--- .../junit/Multi2TestAssertions_nvca.txt | 10 +++--- .../scalajs/junit/Multi2TestAssertions_v.txt | 10 +++--- .../scalajs/junit/Multi2TestAssertions_va.txt | 10 +++--- .../scalajs/junit/Multi2TestAssertions_vc.txt | 10 +++--- .../scalajs/junit/Multi2TestAssertions_vs.txt | 10 +++--- .../junit/Multi2TestAssertions_vsn.txt | 10 +++--- .../junit/MultiAssumeFail1TestAssertions_.txt | 10 +++--- .../MultiAssumeFail1TestAssertions_a.txt | 10 +++--- .../MultiAssumeFail1TestAssertions_n.txt | 10 +++--- .../MultiAssumeFail1TestAssertions_na.txt | 10 +++--- .../MultiAssumeFail1TestAssertions_nv.txt | 10 +++--- .../MultiAssumeFail1TestAssertions_nva.txt | 10 +++--- .../MultiAssumeFail1TestAssertions_nvc.txt | 10 +++--- .../MultiAssumeFail1TestAssertions_nvca.txt | 10 +++--- .../MultiAssumeFail1TestAssertions_v.txt | 10 +++--- .../MultiAssumeFail1TestAssertions_va.txt | 10 +++--- .../MultiAssumeFail1TestAssertions_vc.txt | 10 +++--- .../MultiAssumeFail1TestAssertions_vs.txt | 10 +++--- .../MultiAssumeFail1TestAssertions_vsn.txt | 10 +++--- .../junit/MultiAssumeFail2TestAssertions_.txt | 10 +++--- .../MultiAssumeFail2TestAssertions_a.txt | 10 +++--- .../MultiAssumeFail2TestAssertions_n.txt | 10 +++--- .../MultiAssumeFail2TestAssertions_na.txt | 10 +++--- .../MultiAssumeFail2TestAssertions_nv.txt | 10 +++--- .../MultiAssumeFail2TestAssertions_nva.txt | 10 +++--- .../MultiAssumeFail2TestAssertions_nvc.txt | 10 +++--- .../MultiAssumeFail2TestAssertions_nvca.txt | 10 +++--- .../MultiAssumeFail2TestAssertions_v.txt | 10 +++--- .../MultiAssumeFail2TestAssertions_va.txt | 10 +++--- .../MultiAssumeFail2TestAssertions_vc.txt | 10 +++--- .../MultiAssumeFail2TestAssertions_vs.txt | 10 +++--- .../MultiAssumeFail2TestAssertions_vsn.txt | 10 +++--- .../MultiBeforeAssumeFailTestAssertions_.txt | 2 +- .../MultiBeforeAssumeFailTestAssertions_a.txt | 2 +- .../MultiBeforeAssumeFailTestAssertions_n.txt | 2 +- ...MultiBeforeAssumeFailTestAssertions_na.txt | 2 +- ...MultiBeforeAssumeFailTestAssertions_nv.txt | 2 +- ...ultiBeforeAssumeFailTestAssertions_nva.txt | 2 +- ...ultiBeforeAssumeFailTestAssertions_nvc.txt | 2 +- ...ltiBeforeAssumeFailTestAssertions_nvca.txt | 2 +- .../MultiBeforeAssumeFailTestAssertions_v.txt | 2 +- ...MultiBeforeAssumeFailTestAssertions_va.txt | 2 +- ...MultiBeforeAssumeFailTestAssertions_vc.txt | 2 +- ...MultiBeforeAssumeFailTestAssertions_vs.txt | 2 +- ...ultiBeforeAssumeFailTestAssertions_vsn.txt | 2 +- .../junit/MultiIgnore1TestAssertions_.txt | 10 +++--- .../junit/MultiIgnore1TestAssertions_a.txt | 10 +++--- .../junit/MultiIgnore1TestAssertions_n.txt | 10 +++--- .../junit/MultiIgnore1TestAssertions_na.txt | 10 +++--- .../junit/MultiIgnore1TestAssertions_nv.txt | 10 +++--- .../junit/MultiIgnore1TestAssertions_nva.txt | 10 +++--- .../junit/MultiIgnore1TestAssertions_nvc.txt | 10 +++--- .../junit/MultiIgnore1TestAssertions_nvca.txt | 10 +++--- .../junit/MultiIgnore1TestAssertions_v.txt | 10 +++--- .../junit/MultiIgnore1TestAssertions_va.txt | 10 +++--- .../junit/MultiIgnore1TestAssertions_vc.txt | 10 +++--- .../junit/MultiIgnore1TestAssertions_vs.txt | 10 +++--- .../junit/MultiIgnore1TestAssertions_vsn.txt | 10 +++--- .../junit/MultiIgnore2TestAssertions_.txt | 10 +++--- .../junit/MultiIgnore2TestAssertions_a.txt | 10 +++--- .../junit/MultiIgnore2TestAssertions_n.txt | 10 +++--- .../junit/MultiIgnore2TestAssertions_na.txt | 10 +++--- .../junit/MultiIgnore2TestAssertions_nv.txt | 10 +++--- .../junit/MultiIgnore2TestAssertions_nva.txt | 10 +++--- .../junit/MultiIgnore2TestAssertions_nvc.txt | 10 +++--- .../junit/MultiIgnore2TestAssertions_nvca.txt | 10 +++--- .../junit/MultiIgnore2TestAssertions_v.txt | 10 +++--- .../junit/MultiIgnore2TestAssertions_va.txt | 10 +++--- .../junit/MultiIgnore2TestAssertions_vc.txt | 10 +++--- .../junit/MultiIgnore2TestAssertions_vs.txt | 10 +++--- .../junit/MultiIgnore2TestAssertions_vsn.txt | 10 +++--- .../junit/MultiIgnoreAllTestAssertions_.txt | 10 +++--- .../junit/MultiIgnoreAllTestAssertions_a.txt | 10 +++--- .../junit/MultiIgnoreAllTestAssertions_n.txt | 10 +++--- .../junit/MultiIgnoreAllTestAssertions_na.txt | 10 +++--- .../junit/MultiIgnoreAllTestAssertions_nv.txt | 10 +++--- .../MultiIgnoreAllTestAssertions_nva.txt | 10 +++--- .../MultiIgnoreAllTestAssertions_nvc.txt | 10 +++--- .../MultiIgnoreAllTestAssertions_nvca.txt | 10 +++--- .../junit/MultiIgnoreAllTestAssertions_v.txt | 10 +++--- .../junit/MultiIgnoreAllTestAssertions_va.txt | 10 +++--- .../junit/MultiIgnoreAllTestAssertions_vc.txt | 10 +++--- .../junit/MultiIgnoreAllTestAssertions_vs.txt | 10 +++--- .../MultiIgnoreAllTestAssertions_vsn.txt | 10 +++--- .../org/scalajs/junit/utils/JUnitTest.scala | 31 ++++++++++++++----- 457 files changed, 1006 insertions(+), 987 deletions(-) diff --git a/junit-runtime/src/main/scala/org/scalajs/junit/Reporter.scala b/junit-runtime/src/main/scala/org/scalajs/junit/Reporter.scala index 4673a4cf9e..4cdbfbeb9f 100644 --- a/junit-runtime/src/main/scala/org/scalajs/junit/Reporter.scala +++ b/junit-runtime/src/main/scala/org/scalajs/junit/Reporter.scala @@ -59,7 +59,7 @@ private[junit] final class Reporter(eventHandler: EventHandler, if (errors.nonEmpty) { emit(errors.head) - emitEvent(method, Status.Failure) + emitEvent(method, Status.Failure, new OptionalThrowable(errors.head)) errors.tail.foreach(emit) } } @@ -67,7 +67,7 @@ private[junit] final class Reporter(eventHandler: EventHandler, def reportAssumptionViolation(method: Option[String], timeInSeconds: Double, e: Throwable): Unit = { logTestException(_.warn, "Test assumption in test ", method, e, timeInSeconds) - emitEvent(method, Status.Skipped) + emitEvent(method, Status.Skipped, new OptionalThrowable(e)) } private def logTestInfo(level: Reporter.Level, method: Option[String], msg: String): Unit = @@ -114,11 +114,15 @@ private[junit] final class Reporter(eventHandler: EventHandler, prefix + Ansi.c(name, color) } - private def emitEvent(method: Option[String], status: Status): Unit = { + private def emitEvent( + method: Option[String], + status: Status, + throwable: OptionalThrowable = new OptionalThrowable + ): Unit = { val testName = method.fold(taskDef.fullyQualifiedName())(method => taskDef.fullyQualifiedName() + "." + settings.decodeName(method)) val selector = new TestSelector(testName) - eventHandler.handle(new JUnitEvent(taskDef, status, selector)) + eventHandler.handle(new JUnitEvent(taskDef, status, selector, throwable)) } def log(level: Reporter.Level, s: String): Unit = { diff --git a/junit-test/outputs/org/scalajs/junit/AssertEquals2TestAssertions_.txt b/junit-test/outputs/org/scalajs/junit/AssertEquals2TestAssertions_.txt index 2a161db9ae..2072559893 100644 --- a/junit-test/outputs/org/scalajs/junit/AssertEquals2TestAssertions_.txt +++ b/junit-test/outputs/org/scalajs/junit/AssertEquals2TestAssertions_.txt @@ -1,7 +1,7 @@ ldTest run started ldTest org.scalajs.junit.AssertEquals2Test.test started leTest org.scalajs.junit.AssertEquals2Test.test failed: This is the message expected: but was:, took