diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8bd3725294..6d1f0aebea 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,7 +41,7 @@ In order for a Pull Request to be considered, it has to meet these requirements: - Not violate [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself). - The [Boy Scout Rule](https://medium.com/@biratkirat/step-8-the-boy-scout-rule-robert-c-martin-uncle-bob-9ac839778385) should be applied. 2. Be accompanied by appropriate tests. -3. Be issued from a branch *other than master* (PRs coming from master will not be accepted, as we've had trouble in the past with such PRs) +3. Be issued from a branch *other than main or master* (PRs coming from `main` or `master` will not be accepted, as we've had trouble in the past with such PRs) If not *all* of these requirements are met then the code should **not** be merged into the distribution, and need not even be reviewed. diff --git a/DEVELOPING.md b/DEVELOPING.md index 5a7c858b12..bf409ce03e 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -12,7 +12,7 @@ requires [Node.js](https://nodejs.org/en/) to be installed. For complete support, Node.js >= 13.2.0 is required. The first time, or in the rare events where `package.json` changes -([history](https://github.com/scala-js/scala-js/commits/master/package.json)), +([history](https://github.com/scala-js/scala-js/commits/main/package.json)), you need to run $ npm install diff --git a/Jenkinsfile b/Jenkinsfile index 4d9b5f2d57..4d1ff74dd5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -151,6 +151,10 @@ def Tasks = [ sbtretry ++$scala testSuiteEx$v/test && sbtretry 'set scalaJSStage in Global := FullOptStage' \ ++$scala testSuiteEx$v/test && + sbtretry ++$scala \ + 'set scalaJSLinkerConfig in testSuiteEx.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \ + 'set scalaJSLinkerConfig in testSuiteEx.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ + testSuiteEx$v/test && sbtretry ++$scala testSuite$v/test:doc library$v/test compiler$v/test && sbtretry ++$scala \ 'set scalaJSLinkerConfig in reversi.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \ @@ -166,7 +170,10 @@ def Tasks = [ testInterface$v/compile:doc testBridge$v/compile:doc && sbtretry ++$scala headerCheck && sbtretry ++$scala partest$v/fetchScalaSource && - sbtretry ++$scala library$v/mimaReportBinaryIssues testInterface$v/mimaReportBinaryIssues + sbtretry ++$scala \ + library$v/mimaReportBinaryIssues \ + testInterface$v/mimaReportBinaryIssues \ + jUnitRuntime$v/mimaReportBinaryIssues ''', "test-suite-default-esversion": ''' @@ -195,6 +202,16 @@ def Tasks = [ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ ++$scala $testSuite$v/test \ $testSuite$v/clean && + sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ + ++$scala $testSuite$v/test && + sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ + 'set scalaJSStage in Global := FullOptStage' \ + ++$scala $testSuite$v/test \ + $testSuite$v/clean && + sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ + ++$scala $testSuite$v/test \ + $testSuite$v/clean && sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAllowBigIntsForLongs(true)))' \ ++$scala $testSuite$v/test \ $testSuite$v/clean && @@ -268,6 +285,22 @@ def Tasks = [ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ ++$scala $testSuite$v/test \ + $testSuite$v/clean && + 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 \ + $testSuite$v/clean && + 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 \ $testSuite$v/clean ''', @@ -301,6 +334,19 @@ def Tasks = [ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ ++$scala $testSuite$v/test \ $testSuite$v/clean && + 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 \ + $testSuite$v/clean && + 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 \ + $testSuite$v/clean && sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion).withAllowBigIntsForLongs(true)))' \ ++$scala $testSuite$v/test \ $testSuite$v/clean && @@ -411,7 +457,7 @@ def allJavaVersions = otherJavaVersions.clone() allJavaVersions << mainJavaVersion def mainScalaVersion = "2.12.15" -def mainScalaVersions = ["2.11.12", "2.12.15", "2.13.6"] +def mainScalaVersions = ["2.11.12", "2.12.15", "2.13.8"] def otherScalaVersions = [ "2.11.12", "2.12.1", @@ -433,7 +479,9 @@ def otherScalaVersions = [ "2.13.2", "2.13.3", "2.13.4", - "2.13.5" + "2.13.5", + "2.13.6", + "2.13.7" ] def allESVersions = [ @@ -479,7 +527,7 @@ allJavaVersions.each { javaVersion -> quickMatrix.add([task: "tools-sbtplugin", scala: "2.12.15", java: javaVersion]) quickMatrix.add([task: "tools", scala: "2.11.12", java: javaVersion]) } - quickMatrix.add([task: "tools", scala: "2.13.6", java: javaVersion]) + quickMatrix.add([task: "tools", scala: "2.13.8", java: javaVersion]) } // The 'full' matrix diff --git a/RELEASING.md b/RELEASING.md index b6d4c789f9..60965a93fa 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -7,7 +7,7 @@ 1. Create a "Version x.y.z." commit ([example][2]) and push it to a branch on your fork. 1. Ping people on the commit for review. - 1. Once you have LGTM, push to master (do *not* create a merge commit). + 1. Once you have LGTM, push to `main` (do *not* create a merge commit). 1. Testing (post results as comments to commit): - Full build - [Manual testing][3] @@ -29,10 +29,13 @@ - Announce on Twitter using the @scala_js account - Announce on [Gitter](https://gitter.im/scala-js/scala-js) - Cross-post as an Announcement in Scala Users ([example][7]) + - Send a PR to Scala Steward to "unleash" the release by updating + [these lines][8] with the next possible version numbers [1]: https://github.com/scala-js/scala-js/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20no%3Amilestone%20-label%3Ainvalid%20-label%3Aduplicate%20-label%3Aas-designed%20-label%3Aquestion%20-label%3Awontfix%20-label%3A%22can%27t%20reproduce%22%20-label%3A%22separate%20repo%22 [2]: https://github.com/scala-js/scala-js/commit/c3520bb9dae46757a975cccd428a77b8d6e6a75e -[3]: https://github.com/scala-js/scala-js/blob/master/TESTING.md +[3]: https://github.com/scala-js/scala-js/blob/main/TESTING.md [5]: https://github.com/scala-js/scala-js/commit/c6c82e80f56bd2008ff8273088bbbbbbbc30f777 [6]: https://github.com/scala-js/scala-js-website/commit/057f743c3fb8abe6077fb4debeeec45cd5c53d5d [7]: https://users.scala-lang.org/t/announcing-scala-js-1-4-0/7013 +[8]: https://github.com/scala-steward-org/scala-steward/blob/30f3217ce11bbb0208d70070e7d5f49a3b1a25f0/modules/core/src/main/resources/default.scala-steward.conf#L19-L73 diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 9298cee831..d8c0a67850 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -1210,7 +1210,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } def isDefaultParamOfJSNativeDef: Boolean = { - m.hasFlag(Flags.DEFAULTPARAM) && { + DefaultParamInfo.isApplicable(m) && { val info = new DefaultParamInfo(m) !info.isForConstructor && info.attachedMethod.hasAnnotation(JSNativeAnnotation) } @@ -1821,7 +1821,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * since for those, the entire member list is ignored in `genJSClassData`. */ def isIgnorableDefaultParam: Boolean = { - sym.hasFlag(Flags.DEFAULTPARAM) && sym.owner.isModuleClass && { + DefaultParamInfo.isApplicable(sym) && sym.owner.isModuleClass && { val info = new DefaultParamInfo(sym) if (info.isForConstructor) { /* This is a default accessor for a constructor parameter. Check @@ -2984,7 +2984,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * non-constructor members of native JS types. */ def isJSDefaultParam: Boolean = { - sym.hasFlag(Flags.DEFAULTPARAM) && { + DefaultParamInfo.isApplicable(sym) && { val info = new DefaultParamInfo(sym) if (info.isForConstructor) { /* This is a default accessor for a constructor parameter. Check @@ -3823,6 +3823,21 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } + /** Flatten nested Blocks that can be flattened without compromising the + * identification of pattern matches. + */ + private def flatStats(stats: List[Tree]): Iterator[Tree] = { + /* #4581 Never decompose a Block with LabelDef's, as they need to + * be processed by genBlockWithCaseLabelDefs. + */ + stats.iterator.flatMap { + case Block(stats, expr) if !stats.exists(isCaseLabelDef(_)) => + stats.iterator ++ Iterator.single(expr) + case tree => + Iterator.single(tree) + } + } + /** Predicate satisfied by LabelDefs produced by the pattern matcher, * except matchEnd's. */ @@ -5140,6 +5155,18 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } genAsInstanceOf(typeofExpr, StringClass.tpe) + case JS_NEW_TARGET => + // js.new.target + val valid = currentMethodSym.isClassConstructor && isNonNativeJSClass(currentClassSym) + if (!valid) { + reporter.error(pos, + "Illegal use of js.`new`.target.\n" + + "It can only be used in the constructor of a JS class, " + + "as a statement or in the rhs of a val or var.\n" + + "It cannot be used inside a lambda or by-name parameter, nor in any other location.") + } + js.JSNewTarget() + case JS_IMPORT => // js.import(arg) val arg = genArgs1 @@ -6798,9 +6825,25 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) else result.alternatives.head } + private object DefaultParamInfo { + /** Is the symbol applicable to `DefaultParamInfo`? + * + * This is true iff it is a default accessor and it is not an value class + * `$extension` method. The latter condition is for #4583. + * + * Excluding all `$extension` methods is fine because `DefaultParamInfo` + * is used for JS default accessors, i.e., default accessors of + * `@js.native def`s or of `def`s in JS types. Those can never appear in + * an `AnyVal` class (as a class, it cannot contain `@js.native def`s, and + * as `AnyVal` it cannot also extend `js.Any`). + */ + def isApplicable(sym: Symbol): Boolean = + sym.hasFlag(Flags.DEFAULTPARAM) && !sym.name.endsWith("$extension") + } + /** Info about a default param accessor. * - * The method must have the flag `DEFAULTPARAM` for this class to make + * `DefaultParamInfo.isApplicable(sym)` must be true for this class to make * sense. */ private class DefaultParamInfo(sym: Symbol) { @@ -6902,13 +6945,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } - private def flatStats(stats: List[Tree]): Iterator[Tree] = { - stats.iterator.flatMap { - case Block(stats, expr) => stats.iterator ++ Iterator.single(expr) - case tree => Iterator.single(tree) - } - } - sealed abstract class MaybeGlobalScope object MaybeGlobalScope { diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala index c3bfdb2edf..c0423f9485 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala @@ -92,6 +92,10 @@ trait JSDefinitions { lazy val JSConstructorTagModule = getRequiredModule("scala.scalajs.js.ConstructorTag") lazy val JSConstructorTag_materialize = getMemberMethod(JSConstructorTagModule, newTermName("materialize")) + lazy val JSNewModule = getRequiredModule("scala.scalajs.js.new") + lazy val JSNewModuleClass = JSNewModule.moduleClass + lazy val JSNew_target = getMemberMethod(JSNewModuleClass, newTermName("target")) + lazy val JSImportModule = getRequiredModule("scala.scalajs.js.import") lazy val JSImportModuleClass = JSImportModule.moduleClass lazy val JSImport_apply = getMemberMethod(JSImportModuleClass, nme.apply) @@ -135,6 +139,11 @@ trait JSDefinitions { lazy val EnableReflectiveInstantiationAnnotation = getRequiredClass("scala.scalajs.reflect.annotation.EnableReflectiveInstantiation") + lazy val ExecutionContextModule = getRequiredModule("scala.concurrent.ExecutionContext") + lazy val ExecutionContext_global = getMemberMethod(ExecutionContextModule, newTermName("global")) + + lazy val ExecutionContextImplicitsModule = getRequiredModule("scala.concurrent.ExecutionContext.Implicits") + lazy val ExecutionContextImplicits_global = getMemberMethod(ExecutionContextImplicitsModule, newTermName("global")) } // scalastyle:on line.size.limit diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala index d759479dfc..ade5ab2c2c 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala @@ -46,7 +46,9 @@ abstract class JSPrimitives { final val UNITVAL = JS_NATIVE + 1 // () value, which is undefined - final val JS_IMPORT = UNITVAL + 1 // js.import.apply(specifier) + final val JS_NEW_TARGET = UNITVAL + 1 // js.new.target + + final val JS_IMPORT = JS_NEW_TARGET + 1 // js.import.apply(specifier) final val JS_IMPORT_META = JS_IMPORT + 1 // js.import.meta final val CONSTRUCTOROF = JS_IMPORT_META + 1 // runtime.constructorOf(clazz) @@ -93,6 +95,8 @@ abstract class JSPrimitives { addPrimitive(BoxedUnit_UNIT, UNITVAL) + addPrimitive(JSNew_target, JS_NEW_TARGET) + addPrimitive(JSImport_apply, JS_IMPORT) addPrimitive(JSImport_meta, JS_IMPORT_META) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala index 1889e43185..dc2f712df4 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala @@ -377,6 +377,34 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) |program is unlikely to function properly.""".stripMargin) super.transform(tree) + case tree if tree.symbol == ExecutionContext_global || + tree.symbol == ExecutionContextImplicits_global => + if (scalaJSOpts.warnGlobalExecutionContext) { + global.runReporting.warning(tree.pos, + """The global execution context in Scala.js is based on JS Promises (microtasks). + |Using it may prevent macrotasks (I/O, timers, UI rendering) from running reliably. + | + |Unfortunately, there is no way with ECMAScript only to implement a performant + |macrotask execution context (and hence Scala.js core does not contain one). + | + |We recommend you use: https://github.com/scala-js/scala-js-macrotask-executor + |Please refer to the README.md of that project for more details regarding + |microtask vs. macrotask execution contexts. + | + |If you do not care about macrotask fairness, you can silence this warning by: + |- Adding @nowarn("cat=other") (Scala >= 2.13.x only) + |- Setting the -P:scalajs:nowarnGlobalExecutionContext compiler option + |- Using scala.scalajs.concurrent.JSExecutionContext.queue + | (the implementation of ExecutionContext.global in Scala.js) directly. + | + |If you do not care about performance, you can use + |scala.scalajs.concurrent.QueueExecutionContext.timeouts(). + |It is based on setTimeout which makes it fair but slow (due to clamping). + """.stripMargin, + WarningCategory.Other, tree.symbol) + } + super.transform(tree) + // Validate js.constructorOf[T] case TypeApply(ctorOfTree, List(tpeArg)) if ctorOfTree.symbol == JSPackage_constructorOf => @@ -884,15 +912,13 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) if (shouldCheckLiterals) checkJSGlobalLiteral(annot) val pathName = annot.stringArg(0).getOrElse { - val needsExplicitJSName = { - (enclosingOwner is OwnerKind.ScalaMod) && - !sym.owner.isPackageObjectClass - } - - if (needsExplicitJSName) { + val symTermName = sym.name.dropModule.toTermName.dropLocal + if (symTermName == nme.apply) { reporter.error(annot.pos, - "Native JS members inside non-native objects " + - "must have an explicit name in @JSGlobal") + "Native JS definitions named 'apply' must have an explicit name in @JSGlobal") + } else if (symTermName.endsWith(nme.SETTER_SUFFIX)) { + reporter.error(annot.pos, + "Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal") } jsInterop.defaultJSNameOf(sym) } @@ -904,7 +930,23 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) val module = annot.stringArg(0).getOrElse { "" // an error is reported by checkJSImportLiteral in this case } - val path = annot.stringArg(1).fold[List[String]](Nil)(parsePath) + val path = annot.stringArg(1).fold { + if (annot.args.size < 2) { + val symTermName = sym.name.dropModule.toTermName.dropLocal + if (symTermName == nme.apply) { + reporter.error(annot.pos, + "Native JS definitions named 'apply' must have an explicit name in @JSImport") + } else if (symTermName.endsWith(nme.SETTER_SUFFIX)) { + reporter.error(annot.pos, + "Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport") + } + parsePath(jsInterop.defaultJSNameOf(sym)) + } else { + Nil + } + } { pathName => + parsePath(pathName) + } val importSpec = Import(module, path) val loadSpec = annot.stringArg(2).fold[JSNativeLoadSpec] { importSpec @@ -1425,8 +1467,9 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) * Reports an error on the annotation if it is not the case. */ private def checkJSImportLiteral(annot: AnnotationInfo): Unit = { - assert(annot.args.size == 2 || annot.args.size == 3, - s"@JSImport annotation $annot does not have exactly 2 or 3 arguments") + val argCount = annot.args.size + assert(argCount >= 1 && argCount <= 3, + s"@JSImport annotation $annot does not have between 1 and 3 arguments") val firstArgIsValid = annot.stringArg(0).isDefined if (!firstArgIsValid) { @@ -1435,6 +1478,7 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) } val secondArgIsValid = { + argCount < 2 || annot.stringArg(1).isDefined || annot.args(1).symbol == JSImportNamespaceObject } @@ -1444,7 +1488,7 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) "JSImport.Namespace object.") } - val thirdArgIsValid = annot.args.size < 3 || annot.stringArg(2).isDefined + val thirdArgIsValid = argCount < 3 || annot.stringArg(2).isDefined if (!thirdArgIsValid) { reporter.error(annot.args(2).pos, "The third argument to @JSImport, when present, must be a " + diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSOptions.scala b/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSOptions.scala index 0edf02fe11..50cc0bf1c8 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSOptions.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSOptions.scala @@ -38,6 +38,11 @@ trait ScalaJSOptions { * should they be mapped to)? */ def sourceURIMaps: List[URIMap] + /** Whether to warn if the global execution context is used. + * + * See the warning itself or #4129 for context. + */ + def warnGlobalExecutionContext: Boolean } object ScalaJSOptions { diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSPlugin.scala b/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSPlugin.scala index 82d9de61a0..5e7ca6faba 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSPlugin.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSPlugin.scala @@ -62,6 +62,7 @@ class ScalaJSPlugin(val global: Global) extends NscPlugin { else relSourceMap.toList.map(URIMap(_, absSourceMap)) } + var warnGlobalExecutionContext: Boolean = true var _sourceURIMaps: List[URIMap] = Nil var relSourceMap: Option[URI] = None var absSourceMap: Option[URI] = None @@ -121,6 +122,8 @@ class ScalaJSPlugin(val global: Global) extends NscPlugin { fixClassOf = true } else if (option == "genStaticForwardersForNonTopLevelObjects") { genStaticForwardersForNonTopLevelObjects = true + } else if (option == "nowarnGlobalExecutionContext") { + warnGlobalExecutionContext = false } else if (option.startsWith("mapSourceURI:")) { val uris = option.stripPrefix("mapSourceURI:").split("->") diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/GlobalExecutionContextNoWarnTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/GlobalExecutionContextNoWarnTest.scala new file mode 100644 index 0000000000..94decfd65a --- /dev/null +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/GlobalExecutionContextNoWarnTest.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.nscplugin.test + +import org.scalajs.nscplugin.test.util._ +import org.scalajs.nscplugin.test.util.VersionDependentUtils.scalaSupportsNoWarn + +import org.junit.Assume._ +import org.junit.Test + +class GlobalExecutionContextNoWarnTest extends DirectTest with TestHelpers { + + override def extraArgs: List[String] = + super.extraArgs ::: List("-P:scalajs:nowarnGlobalExecutionContext") + + @Test + def noWarnOnUsage: Unit = { + """ + import scala.concurrent.ExecutionContext.global + + object Enclosing { + global + } + """.hasNoWarns() + } + + @Test + def noWarnOnImplicitUsage: Unit = { + """ + import scala.concurrent.ExecutionContext.Implicits.global + + object Enclosing { + scala.concurrent.Future { } + } + """.hasNoWarns() + } +} diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/GlobalExecutionContextWarnTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/GlobalExecutionContextWarnTest.scala new file mode 100644 index 0000000000..daef9b1add --- /dev/null +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/GlobalExecutionContextWarnTest.scala @@ -0,0 +1,122 @@ +/* + * 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.nscplugin.test + +import org.scalajs.nscplugin.test.util._ +import org.scalajs.nscplugin.test.util.VersionDependentUtils.scalaSupportsNoWarn + +import org.junit.Assume._ +import org.junit.Test + +class GlobalExecutionContextWarnTest extends DirectTest with TestHelpers { + + @Test + def warnOnUsage: Unit = { + """ + import scala.concurrent.ExecutionContext.global + + object Enclosing { + global + } + """ hasWarns + """ + |newSource1.scala:5: warning: The global execution context in Scala.js is based on JS Promises (microtasks). + |Using it may prevent macrotasks (I/O, timers, UI rendering) from running reliably. + | + |Unfortunately, there is no way with ECMAScript only to implement a performant + |macrotask execution context (and hence Scala.js core does not contain one). + | + |We recommend you use: https://github.com/scala-js/scala-js-macrotask-executor + |Please refer to the README.md of that project for more details regarding + |microtask vs. macrotask execution contexts. + | + |If you do not care about macrotask fairness, you can silence this warning by: + |- Adding @nowarn("cat=other") (Scala >= 2.13.x only) + |- Setting the -P:scalajs:nowarnGlobalExecutionContext compiler option + |- Using scala.scalajs.concurrent.JSExecutionContext.queue + | (the implementation of ExecutionContext.global in Scala.js) directly. + | + |If you do not care about performance, you can use + |scala.scalajs.concurrent.QueueExecutionContext.timeouts(). + |It is based on setTimeout which makes it fair but slow (due to clamping). + | + | global + | ^ + """ + } + + @Test + def warnOnImplicitUsage: Unit = { + """ + import scala.concurrent.ExecutionContext.Implicits.global + + object Enclosing { + scala.concurrent.Future { } + } + """ hasWarns + """ + |newSource1.scala:5: warning: The global execution context in Scala.js is based on JS Promises (microtasks). + |Using it may prevent macrotasks (I/O, timers, UI rendering) from running reliably. + | + |Unfortunately, there is no way with ECMAScript only to implement a performant + |macrotask execution context (and hence Scala.js core does not contain one). + | + |We recommend you use: https://github.com/scala-js/scala-js-macrotask-executor + |Please refer to the README.md of that project for more details regarding + |microtask vs. macrotask execution contexts. + | + |If you do not care about macrotask fairness, you can silence this warning by: + |- Adding @nowarn("cat=other") (Scala >= 2.13.x only) + |- Setting the -P:scalajs:nowarnGlobalExecutionContext compiler option + |- Using scala.scalajs.concurrent.JSExecutionContext.queue + | (the implementation of ExecutionContext.global in Scala.js) directly. + | + |If you do not care about performance, you can use + |scala.scalajs.concurrent.QueueExecutionContext.timeouts(). + |It is based on setTimeout which makes it fair but slow (due to clamping). + | + | scala.concurrent.Future { } + | ^ + """ + } + + @Test + def noWarnIfSelectivelyDisabled: Unit = { + assumeTrue(scalaSupportsNoWarn) + + """ + import scala.annotation.nowarn + import scala.concurrent.ExecutionContext.global + + object Enclosing { + global: @nowarn("cat=other") + } + """.hasNoWarns() + } + + @Test + def noWarnQueue: Unit = { + /* Test that JSExecutionContext.queue does not warn for good measure. + * We explicitly say it doesn't so we want to notice if it does. + */ + + """ + import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue + + object Enclosing { + scala.concurrent.Future { } + } + """.hasNoWarns() + } + +} 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 b08990e368..c9dac28830 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala @@ -1162,6 +1162,15 @@ class JSInteropTest extends DirectTest with TestHelpers { def goo_=(x: Int*): Unit = js.native @js.native @JSGlobal("hoo") def hoo_=(x: Int = 1): Unit = js.native + + @js.native @JSImport("module.js", "foo") + def foo2_=(x: Int): Int = js.native + @js.native @JSImport("module.js", "bar") + def bar2_=(x: Int, y: Int): Unit = js.native + @js.native @JSImport("module.js", "goo") + def goo2_=(x: Int*): Unit = js.native + @js.native @JSImport("module.js", "hoo") + def hoo2_=(x: Int = 1): Unit = js.native } """ hasErrors """ @@ -1177,6 +1186,40 @@ class JSInteropTest extends DirectTest with TestHelpers { |newSource1.scala:13: error: @js.native is not allowed on vars, lazy vals and setter defs | def hoo_=(x: Int = 1): Unit = js.native | ^ + |newSource1.scala:16: error: @js.native is not allowed on vars, lazy vals and setter defs + | def foo2_=(x: Int): Int = js.native + | ^ + |newSource1.scala:18: error: @js.native is not allowed on vars, lazy vals and setter defs + | def bar2_=(x: Int, y: Int): Unit = js.native + | ^ + |newSource1.scala:20: error: @js.native is not allowed on vars, lazy vals and setter defs + | def goo2_=(x: Int*): Unit = js.native + | ^ + |newSource1.scala:22: error: @js.native is not allowed on vars, lazy vals and setter defs + | def hoo2_=(x: Int = 1): Unit = js.native + | ^ + """ + + // containsErrors because some versions of the compiler use `_=` and some use `_=' (notice the quotes) + """ + object Container { + @js.native @JSGlobal("foo") + val foo_= : Int = js.native + } + """ containsErrors + """ + |newSource1.scala:7: error: Names of vals or vars may not end in `_= + """ + + // containsErrors because some versions of the compiler use `_=` and some use `_=' (notice the quotes) + """ + object Container { + @js.native @JSImport("module.js") + val foo_= : Int = js.native + } + """ containsErrors + """ + |newSource1.scala:7: error: Names of vals or vars may not end in `_= """ } @@ -1848,6 +1891,11 @@ class JSInteropTest extends DirectTest with TestHelpers { @js.native abstract class B extends js.Object + + object Container { + @js.native + class C extends js.Object + } """ hasErrors """ |newSource1.scala:6: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport. @@ -1856,6 +1904,9 @@ class JSInteropTest extends DirectTest with TestHelpers { |newSource1.scala:9: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport. | abstract class B extends js.Object | ^ + |newSource1.scala:13: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport. + | class C extends js.Object + | ^ """ } @@ -1863,190 +1914,643 @@ class JSInteropTest extends DirectTest with TestHelpers { """ @js.native object A extends js.Object + + object Container { + @js.native + object B extends js.Object + } """ hasErrors """ |newSource1.scala:6: error: Native JS objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope. | object A extends js.Object | ^ + |newSource1.scala:10: error: Native JS objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope. + | object B extends js.Object + | ^ """ } - @Test def noNativeClassObjectWithoutExplicitNameInsideScalaObject: Unit = { + @Test def noNativeDefinitionNamedApplyWithoutExplicitName: Unit = { + + """ + @js.native + @JSGlobal + class apply extends js.Object + + @js.native + @JSGlobal + object apply extends js.Object + """ hasErrors + """ + |newSource1.scala:6: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal + | @JSGlobal + | ^ + |newSource1.scala:10: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal + | @JSGlobal + | ^ + """ + + """ + @js.native + @JSImport("foo.js") + class apply extends js.Object + + @js.native + @JSImport("foo.js") + object apply extends js.Object + """ hasErrors + """ + |newSource1.scala:6: error: Native JS definitions named 'apply' must have an explicit name in @JSImport + | @JSImport("foo.js") + | ^ + |newSource1.scala:10: error: Native JS definitions named 'apply' must have an explicit name in @JSImport + | @JSImport("foo.js") + | ^ + """ """ object A { @js.native - class B extends js.Object + @JSGlobal + class apply extends js.Object + + @js.native + @JSGlobal + object apply extends js.Object } """ hasErrors """ - |newSource1.scala:7: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport. - | class B extends js.Object - | ^ + |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal + | @JSGlobal + | ^ + |newSource1.scala:11: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal + | @JSGlobal + | ^ """ """ object A { @js.native - object B extends js.Object + @JSImport("foo.js") + class apply extends js.Object + + @js.native + @JSImport("foo.js") + object apply extends js.Object } """ hasErrors """ - |newSource1.scala:7: error: Native JS objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope. - | object B extends js.Object - | ^ + |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSImport + | @JSImport("foo.js") + | ^ + |newSource1.scala:11: error: Native JS definitions named 'apply' must have an explicit name in @JSImport + | @JSImport("foo.js") + | ^ + """ + + """ + package object A { + @js.native + @JSGlobal + class apply extends js.Object + + @js.native + @JSGlobal + object apply extends js.Object + } + """ hasErrors + """ + |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal + | @JSGlobal + | ^ + |newSource1.scala:11: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal + | @JSGlobal + | ^ + """ + + """ + package object A { + @js.native + @JSImport("foo.js") + class apply extends js.Object + + @js.native + @JSImport("foo.js") + object apply extends js.Object + } + """ hasErrors + """ + |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSImport + | @JSImport("foo.js") + | ^ + |newSource1.scala:11: error: Native JS definitions named 'apply' must have an explicit name in @JSImport + | @JSImport("foo.js") + | ^ """ """ object A { @js.native @JSGlobal - class B extends js.Object + val apply: Int = js.native } """ hasErrors """ - |newSource1.scala:7: error: Native JS members inside non-native objects must have an explicit name in @JSGlobal + |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal | @JSGlobal | ^ """ + """ + object A { + @js.native + @JSImport("foo.js") + val apply: Int = js.native + } + """ hasErrors + """ + |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSImport + | @JSImport("foo.js") + | ^ + """ + """ object A { @js.native @JSGlobal - object B extends js.Object + def apply: Int = js.native } """ hasErrors """ - |newSource1.scala:7: error: Native JS members inside non-native objects must have an explicit name in @JSGlobal + |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal | @JSGlobal | ^ """ - // From issue #2401 """ - package object A { + object A { @js.native - object B extends js.Object + @JSImport("foo.js") + def apply: Int = js.native + } + """ hasErrors + """ + |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSImport + | @JSImport("foo.js") + | ^ + """ + """ + object A { @js.native @JSGlobal - object C extends js.Object + def apply(x: Int): Int = js.native } """ hasErrors """ - |newSource1.scala:7: error: Native JS objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope. - | object B extends js.Object - | ^ + |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal + | @JSGlobal + | ^ """ """ - package object A { + object A { @js.native - class B extends js.Object + @JSImport("foo.js") + def apply(x: Int): Int = js.native + } + """ hasErrors + """ + |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSImport + | @JSImport("foo.js") + | ^ + """ + + """ + @JSGlobal("apply") + @js.native + class apply extends js.Object + + @JSGlobal("apply") + @js.native + object apply extends js.Object + + object A { + @JSGlobal("apply") + @js.native + class apply extends js.Object + + @JSGlobal("apply") + @js.native + object apply extends js.Object + } + + object B { + @JSGlobal("apply") + @js.native + val apply: Int = js.native + } + + object C { + @JSGlobal("apply") + @js.native + def apply: Int = js.native + } + + object D { + @JSGlobal("apply") + @js.native + def apply(x: Int): Int = js.native + } + """.hasNoWarns() + + """ + @JSImport("foo.js", "apply") + @js.native + class apply extends js.Object + + @JSImport("foo.js", "apply") + @js.native + object apply extends js.Object + + object A { + @JSImport("foo.js", "apply") + @js.native + class apply extends js.Object + + @JSImport("foo.js", "apply") + @js.native + object apply extends js.Object + } + + object B { + @JSImport("foo.js", "apply") + @js.native + val apply: Int = js.native + } + + object C { + @JSImport("foo.js", "apply") + @js.native + def apply: Int = js.native + } + + object D { + @JSImport("foo.js", "apply") + @js.native + def apply(x: Int): Int = js.native + } + """.hasNoWarns() + + """ + @JSImport("foo.js", "apply", globalFallback = "apply") + @js.native + class apply extends js.Object + + @JSImport("foo.js", "apply", globalFallback = "apply") + @js.native + object apply extends js.Object + + object A { + @JSImport("foo.js", "apply", globalFallback = "apply") + @js.native + class apply extends js.Object + + @JSImport("foo.js", "apply", globalFallback = "apply") + @js.native + object apply extends js.Object + } + """.hasNoWarns() + } + + @Test def noNativeDefinitionWithSetterNameWithoutExplicitName: Unit = { + + """ + @js.native + @JSGlobal + class foo_= extends js.Object + + @js.native + @JSGlobal + object foo_= extends js.Object + """ hasErrors + """ + |newSource1.scala:6: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal + | @JSGlobal + | ^ + |newSource1.scala:10: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal + | @JSGlobal + | ^ + """ + + """ + @js.native + @JSImport("foo.js") + class foo_= extends js.Object + + @js.native + @JSImport("foo.js") + object foo_= extends js.Object + """ hasErrors + """ + |newSource1.scala:6: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport + | @JSImport("foo.js") + | ^ + |newSource1.scala:10: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport + | @JSImport("foo.js") + | ^ + """ + + """ + object A { @js.native @JSGlobal - class C extends js.Object + class foo_= extends js.Object + + @js.native + @JSGlobal + object foo_= extends js.Object } """ hasErrors """ - |newSource1.scala:7: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport. - | class B extends js.Object - | ^ + |newSource1.scala:7: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal + | @JSGlobal + | ^ + |newSource1.scala:11: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal + | @JSGlobal + | ^ """ """ object A { - @JSName("InnerB") @js.native - class B extends js.Object + @JSImport("foo.js") + class foo_= extends js.Object + + @js.native + @JSImport("foo.js") + object foo_= extends js.Object + } + """ hasErrors + """ + |newSource1.scala:7: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport + | @JSImport("foo.js") + | ^ + |newSource1.scala:11: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport + | @JSImport("foo.js") + | ^ + """ - @JSName("InnerC") + """ + package object A { @js.native - abstract class C extends js.Object + @JSGlobal + class foo_= extends js.Object - @JSName("InnerD") @js.native - object D extends js.Object + @JSGlobal + object foo_= extends js.Object } """ hasErrors """ - |newSource1.scala:6: error: @JSName can only be used on members of JS types. - | @JSName("InnerB") + |newSource1.scala:7: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal + | @JSGlobal | ^ - |newSource1.scala:8: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport. - | class B extends js.Object - | ^ - |newSource1.scala:10: error: @JSName can only be used on members of JS types. - | @JSName("InnerC") + |newSource1.scala:11: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal + | @JSGlobal | ^ - |newSource1.scala:12: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport. - | abstract class C extends js.Object - | ^ - |newSource1.scala:14: error: @JSName can only be used on members of JS types. - | @JSName("InnerD") + """ + + """ + package object A { + @js.native + @JSImport("foo.js") + class foo_= extends js.Object + + @js.native + @JSImport("foo.js") + object foo_= extends js.Object + } + """ hasErrors + """ + |newSource1.scala:7: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport + | @JSImport("foo.js") + | ^ + |newSource1.scala:11: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport + | @JSImport("foo.js") | ^ - |newSource1.scala:16: error: Native JS objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope. - | object D extends js.Object - | ^ """ + // containsErrors because some versions of the compiler use `_=` and some use `_=' (notice the quotes) """ object A { - @JSGlobal("InnerB") @js.native - class B extends js.Object + @JSGlobal + val foo_= : Int = js.native + } + """ containsErrors + """ + |newSource1.scala:8: error: Names of vals or vars may not end in `_= + """ - @JSGlobal("InnerC") + // containsErrors because some versions of the compiler use `_=` and some use `_=' (notice the quotes) + """ + object A { @js.native - object C extends js.Object + @JSImport("foo.js") + val foo_= : Int = js.native } - """.hasNoWarns() + """ containsErrors + """ + |newSource1.scala:8: error: Names of vals or vars may not end in `_= + """ + // containsErrors because some versions of the compiler use `_=` and some use `_=' (notice the quotes) """ object A { - @JSImport("InnerB", JSImport.Namespace) @js.native - class B extends js.Object + @JSGlobal + var foo_= : Int = js.native + } + """ containsErrors + """ + |newSource1.scala:8: error: Names of vals or vars may not end in `_= + """ - @JSImport("InnerC", JSImport.Namespace) + """ + object A { @js.native - object C extends js.Object + @JSGlobal + def foo_= : Int = js.native } - """.hasNoWarns() + """ hasErrors + """ + |newSource1.scala:7: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal + | @JSGlobal + | ^ + |newSource1.scala:8: error: @js.native is not allowed on vars, lazy vals and setter defs + | def foo_= : Int = js.native + | ^ + """ """ object A { - @JSImport("InnerB", JSImport.Namespace, globalFallback = "Foo") @js.native - class B extends js.Object + @JSGlobal("foo") + def foo_= : Int = js.native + } + """ hasErrors + """ + |newSource1.scala:8: error: @js.native is not allowed on vars, lazy vals and setter defs + | def foo_= : Int = js.native + | ^ + """ - @JSImport("InnerC", JSImport.Namespace, globalFallback = "Foo") + """ + object A { @js.native - object C extends js.Object + @JSImport("foo.js") + def foo_= : Int = js.native } - """.hasNoWarns() + """ hasErrors + """ + |newSource1.scala:7: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport + | @JSImport("foo.js") + | ^ + |newSource1.scala:8: error: @js.native is not allowed on vars, lazy vals and setter defs + | def foo_= : Int = js.native + | ^ + """ """ object A { @js.native - trait B extends js.Object + @JSImport("foo.js", "foo") + def foo_= : Int = js.native + } + """ hasErrors + """ + |newSource1.scala:8: error: @js.native is not allowed on vars, lazy vals and setter defs + | def foo_= : Int = js.native + | ^ + """ + + """ + object A { + @js.native + @JSGlobal + def foo_=(x: Int): Int = js.native + } + """ hasErrors + """ + |newSource1.scala:7: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal + | @JSGlobal + | ^ + |newSource1.scala:8: error: @js.native is not allowed on vars, lazy vals and setter defs + | def foo_=(x: Int): Int = js.native + | ^ + """ + + """ + object A { + @js.native + @JSGlobal("foo") + def foo_=(x: Int): Int = js.native + } + """ hasErrors + """ + |newSource1.scala:8: error: @js.native is not allowed on vars, lazy vals and setter defs + | def foo_=(x: Int): Int = js.native + | ^ + """ + + """ + object A { + @js.native + @JSImport("foo.js") + def foo_=(x: Int): Int = js.native + } + """ hasErrors + """ + |newSource1.scala:7: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport + | @JSImport("foo.js") + | ^ + |newSource1.scala:8: error: @js.native is not allowed on vars, lazy vals and setter defs + | def foo_=(x: Int): Int = js.native + | ^ + """ + + """ + object A { + @js.native + @JSImport("foo.js", "foo") + def foo_=(x: Int): Int = js.native + } + """ hasErrors + """ + |newSource1.scala:8: error: @js.native is not allowed on vars, lazy vals and setter defs + | def foo_=(x: Int): Int = js.native + | ^ + """ + + """ + @JSGlobal("foo") + @js.native + class foo_= extends js.Object + + @JSGlobal("foo") + @js.native + object foo_= extends js.Object + + object A { + @JSGlobal("foo") + @js.native + class foo_= extends js.Object + + @JSGlobal("foo") + @js.native + object foo_= extends js.Object } """.hasNoWarns() """ + @JSImport("foo.js", "foo_=") @js.native - @JSGlobal - object A extends js.Object { + class foo_= extends js.Object + + @JSImport("foo.js", "foo_=") + @js.native + object foo_= extends js.Object + + object A { + @JSImport("foo.js", "foo_=") @js.native - class B extends js.Object + class foo_= extends js.Object + + @JSImport("foo.js", "foo_=") + @js.native + object foo_= extends js.Object + } + """.hasNoWarns() + """ + @JSImport("foo.js", "foo_=", globalFallback = "foo") + @js.native + class foo_= extends js.Object + + @JSImport("foo.js", "foo_=", globalFallback = "foo") + @js.native + object foo_= extends js.Object + + object A { + @JSImport("foo.js", "foo_=", globalFallback = "foo") @js.native - trait C extends js.Object + class foo_= extends js.Object + @JSImport("foo.js", "foo_=", globalFallback = "foo") @js.native - object D extends js.Object + object foo_= extends js.Object } """.hasNoWarns() diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSNewTargetTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSNewTargetTest.scala new file mode 100644 index 0000000000..edfa1afd01 --- /dev/null +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSNewTargetTest.scala @@ -0,0 +1,149 @@ +/* + * 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.nscplugin.test + +import org.scalajs.nscplugin.test.util._ +import org.junit.Test + +// scalastyle:off line.size.limit + +class JSNewTargetTest extends DirectTest with TestHelpers { + + override def preamble: String = + """import scala.scalajs.js + """ + + @Test + def illegalInScalaClass(): Unit = { + + """ + class A { + js.`new`.target + + def this(x: Int) = { + this() + js.`new`.target + } + } + """ 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. + | js.`new`.target + | ^ + |newSource1.scala:8: 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. + | js.`new`.target + | ^ + """ + + """ + class A { + def foo(x: Int): Unit = + js.`new`.target + } + """ hasErrors + """ + |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. + | js.`new`.target + | ^ + """ + + """ + class A extends js.Object { + class B { + js.`new`.target + } + } + """ hasErrors + """ + |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. + | js.`new`.target + | ^ + """ + + } + + @Test + def illegalInDefOrLazyVal(): Unit = { + + """ + class A extends js.Object { + lazy val x = js.`new`.target + def y: js.Dynamic = js.`new`.target + def z(x: Int): Any = js.`new`.target + } + """ 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. + | lazy 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. + | def y: js.Dynamic = 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. + | def z(x: Int): Any = js.`new`.target + | ^ + """ + + } + + @Test + def illegalInLambdaOrByName(): Unit = { + + """ + class A extends js.Object { + val x = () => js.`new`.target + val y = Option(null).getOrElse(js.`new`.target) + val z: js.Function1[Int, Any] = (x: Int) => js.`new`.target + val w: js.ThisFunction0[Any, Any] = (x: Any) => js.`new`.target + } + """ 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. + | val z: js.Function1[Int, Any] = (x: Int) => js.`new`.target + | ^ + |newSource1.scala:7: 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 w: js.ThisFunction0[Any, Any] = (x: Any) => js.`new`.target + | ^ + """ + + } + +} 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 3eaccd630a..06f75eb392 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala @@ -393,6 +393,9 @@ object Hashers { mixTag(TagJSImportCall) mixTree(arg) + case JSNewTarget() => + mixTag(TagJSNewTarget) + case JSImportMeta() => mixTag(TagJSImportMeta) 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 d6b95944f3..737d3189b4 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -638,6 +638,9 @@ object Printers { print(arg) print(')') + case JSNewTarget() => + print("new.target") + case JSImportMeta() => print("import.meta") 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 91043c41e5..e06bbe21c3 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.7.1", - binaryEmitted = "1.7" + current = "1.9.0", + binaryEmitted = "1.8" ) /** Helper class to allow for testing of logic. */ 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 61945a41ff..a8a1e13bb9 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -448,6 +448,9 @@ object Serializers { writeTagAndPos(TagJSImportCall) writeTree(arg) + case JSNewTarget() => + writeTagAndPos(TagJSNewTarget) + case JSImportMeta() => writeTagAndPos(TagJSImportMeta) @@ -1149,6 +1152,7 @@ object Serializers { JSSuperMethodCall(readTree(), readTree(), readTree(), readTreeOrJSSpreads()) case TagJSSuperConstructorCall => JSSuperConstructorCall(readTreeOrJSSpreads()) case TagJSImportCall => JSImportCall(readTree()) + case TagJSNewTarget => JSNewTarget() case TagJSImportMeta => JSImportMeta() case TagLoadJSConstructor => LoadJSConstructor(readClassName()) case TagLoadJSModule => LoadJSModule(readClassName()) @@ -1227,12 +1231,19 @@ object Serializers { if (!hacks.use15) { body } else { - // #4442 Patch If and TryCatch nodes in statement position to have type NoType + /* #4442 and #4601: Patch Labeled, If, Match and TryCatch nodes in + * statement position to have type NoType. 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 != NoType) { newTree match { + case Labeled(label, _, body) => + Labeled(label, NoType, body)(newTree.pos) case If(cond, thenp, elsep) => If(cond, thenp, elsep)(NoType)(newTree.pos) case Match(selector, cases, default) => 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 380ce29dc2..2ea3eac68f 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala @@ -118,6 +118,10 @@ private[ir] object Tags { final val TagJSImportMeta = TagClone + 1 + // New in 1.8 + + final val TagJSNewTarget = TagJSImportMeta + 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 a3e0b42fb5..bd45d5d60f 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala @@ -217,8 +217,8 @@ object Transformers { // Trees that need not be transformed case _:Skip | _:Debugger | _:LoadModule | _:SelectStatic | _:SelectJSNativeMember | - _:LoadJSConstructor | _:LoadJSModule | _:JSImportMeta | _:JSLinkingInfo | - _:Literal | _:VarRef | _:This | _:JSGlobalRef => + _:LoadJSConstructor | _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | + _:JSLinkingInfo | _:Literal | _:VarRef | _:This | _:JSGlobalRef => 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 997157340e..d0bac2ffc1 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala @@ -221,8 +221,8 @@ object Traversers { // Trees that need not be traversed case _:Skip | _:Debugger | _:LoadModule | _:SelectStatic | _:SelectJSNativeMember | - _:LoadJSConstructor | _:LoadJSModule | _:JSImportMeta | _:JSLinkingInfo | - _:Literal | _:VarRef | _:This | _:JSGlobalRef => + _:LoadJSConstructor | _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | + _:JSLinkingInfo | _:Literal | _:VarRef | _:This | _:JSGlobalRef => } 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 4e76386187..85a77eead6 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -672,6 +672,19 @@ object Trees { val tpe = AnyType // it is a JavaScript Promise } + /** JavaScript meta-property `new.target`. + * + * This form is its own node, rather than using something like + * {{{ + * JSSelect(JSNew(), StringLiteral("target")) + * }}} + * because `new` is not a first-class term in JavaScript. `new.target` + * is a dedicated syntactic form that cannot be dissociated. + */ + sealed case class JSNewTarget()(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + /** JavaScript meta-property `import.meta`. * * This form is its own node, rather than using something like @@ -1190,7 +1203,7 @@ object Trees { case TopLevelJSClassExportDef(_, name) => name case TopLevelMethodExportDef(_, JSMethodDef(_, propName, _, _, _)) => - val StringLiteral(name) = propName + val StringLiteral(name) = propName: @unchecked // unchecked is needed for Scala 3.2+ name case TopLevelFieldExportDef(_, name, _) => name 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 7ff3033ff5..6135e16e87 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -667,6 +667,10 @@ class PrintersTest { assertPrintEquals("""import("foo.js")""", JSImportCall(StringLiteral("foo.js"))) } + @Test def printJSNewTarget(): Unit = { + assertPrintEquals("new.target", JSNewTarget()) + } + @Test def printJSImportMeta(): Unit = { assertPrintEquals("import.meta", JSImportMeta()) } diff --git a/javalanglib/src/main/scala/java/lang/Byte.scala b/javalanglib/src/main/scala/java/lang/Byte.scala index 022fc195d3..2c563b9e73 100644 --- a/javalanglib/src/main/scala/java/lang/Byte.scala +++ b/javalanglib/src/main/scala/java/lang/Byte.scala @@ -96,4 +96,10 @@ object Byte { @inline def compare(x: scala.Byte, y: scala.Byte): scala.Int = x - y + + @inline def toUnsignedInt(x: scala.Byte): scala.Int = + x.toInt & 0xff + + @inline def toUnsignedLong(x: scala.Byte): scala.Long = + toUnsignedInt(x).toLong } diff --git a/javalanglib/src/main/scala/java/lang/Short.scala b/javalanglib/src/main/scala/java/lang/Short.scala index c68a4ec05c..f44729b500 100644 --- a/javalanglib/src/main/scala/java/lang/Short.scala +++ b/javalanglib/src/main/scala/java/lang/Short.scala @@ -98,4 +98,10 @@ object Short { def reverseBytes(i: scala.Short): scala.Short = (((i >>> 8) & 0xff) + ((i & 0xff) << 8)).toShort + + @inline def toUnsignedInt(x: scala.Short): scala.Int = + x.toInt & 0xffff + + @inline def toUnsignedLong(x: scala.Short): scala.Long = + toUnsignedInt(x).toLong } diff --git a/javalanglib/src/main/scala/java/lang/_String.scala b/javalanglib/src/main/scala/java/lang/_String.scala index 174be4eeef..2d1ebcee0b 100644 --- a/javalanglib/src/main/scala/java/lang/_String.scala +++ b/javalanglib/src/main/scala/java/lang/_String.scala @@ -734,6 +734,208 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { } } + def stripLeading(): String = { + val len = length() + var idx = 0 + while (idx < len && Character.isWhitespace(charAt(idx))) + idx += 1 + substring(idx) + } + + def stripTrailing(): String = { + val len = length() + var idx = len - 1 + while (idx >= 0 && Character.isWhitespace(charAt(idx))) + idx -= 1 + substring(0, idx + 1) + } + + def strip(): String = { + val len = length() + var leading = 0 + while (leading < len && Character.isWhitespace(charAt(leading))) + leading += 1 + if (leading == len) { + "" + } else { + var trailing = len + while (Character.isWhitespace(charAt(trailing - 1))) + trailing -= 1 + if (leading == 0 && trailing == len) thisString + else substring(leading, trailing) + } + } + + def isBlank(): scala.Boolean = { + val len = length() + var start = 0 + while (start != len && Character.isWhitespace(charAt(start))) + start += 1 + start == len + } + + private def splitLines(): js.Array[String] = { + val xs = js.Array[String]() + val len = length() + var idx = 0 + var last = 0 + while (idx < len) { + val c = charAt(idx) + if (c == '\n' || c == '\r') { + xs.push(substring(last, idx)) + if (c == '\r' && idx + 1 < len && charAt(idx + 1) == '\n') + idx += 1 + last = idx + 1 + } + idx += 1 + } + // make sure we add the last segment, but not the last new line + if (last != len) + xs.push(substring(last)) + xs + } + + def indent(n: Int): String = { + + def forEachLn(f: js.Function1[String, String]): String = { + var out = "" + var i = 0 + val xs = splitLines() + while (i < xs.length) { + out += f(xs(i)) + "\n" + i += 1 + } + out + } + + if (n < 0) { + forEachLn { l => + // n is negative here + var idx = 0 + val lim = if (l.length() <= -n) l.length() else -n + while (idx < lim && Character.isWhitespace(l.charAt(idx))) + idx += 1 + l.substring(idx) + } + } else { + val padding = " ".asInstanceOf[_String].repeat(n) + forEachLn(padding + _) + } + } + + def stripIndent(): String = { + if (isEmpty()) { + "" + } else { + import Character.{isWhitespace => isWS} + // splitLines discards the last NL if it's empty so we identify it here first + val trailingNL = charAt(length() - 1) match { + // this also covers the \r\n case via the last \n + case '\r' | '\n' => true + case _ => false + } + + val xs = splitLines() + var i = 0 + var minLeading = Int.MaxValue + + while (i < xs.length) { + val l = xs(i) + // count the last line even if blank + if (i == xs.length - 1 || !l.asInstanceOf[_String].isBlank()) { + var idx = 0 + while (idx < l.length() && isWS(l.charAt(idx))) + idx += 1 + if (idx < minLeading) + minLeading = idx + } + i += 1 + } + // if trailingNL, then the last line is zero width + if (trailingNL || minLeading == Int.MaxValue) + minLeading = 0 + + var out = "" + var j = 0 + while (j < xs.length) { + val line = xs(j) + if (!line.asInstanceOf[_String].isBlank()) { + // we strip the computed leading WS and also any *trailing* WS + out += line.substring(minLeading).asInstanceOf[_String].stripTrailing() + } + // different from indent, we don't add an LF at the end unless there's already one + if (j != xs.length - 1) + out += "\n" + j += 1 + } + if (trailingNL) + out += "\n" + out + } + } + + def translateEscapes(): String = { + def isOctalDigit(c: Char): scala.Boolean = c >= '0' && c <= '7' + def isValidIndex(n: Int): scala.Boolean = n < length() + var i = 0 + var result = "" + while (i < length()) { + if (charAt(i) == '\\') { + if (isValidIndex(i + 1)) { + charAt(i + 1) match { + // , so CR(\r), LF(\n), or CRLF(\r\n) + case '\r' if isValidIndex(i + 2) && charAt(i + 2) == '\n' => + i += 1 // skip \r and \n and discard, so 2+1 chars + case '\r' | '\n' => // skip and discard + + // normal one char escapes + case 'b' => result += "\b" + case 't' => result += "\t" + case 'n' => result += "\n" + case 'f' => result += "\f" + case 'r' => result += "\r" + case 's' => result += " " + case '"' => result += "\"" + case '\'' => result += "\'" + case '\\' => result += "\\" + + // we're parsing octal now, as per JLS-3, we got three cases: + // 1) [0-3][0-7][0-7] + case a @ ('0' | '1' | '2' | '3') + if isValidIndex(i + 3) && isOctalDigit(charAt(i + 2)) && isOctalDigit(charAt(i + 3)) => + val codePoint = + ((a - '0') * 64) + ((charAt(i + 2) - '0') * 8) + (charAt(i + 3) - '0') + result += codePoint.toChar + i += 2 // skip two other numbers, so 2+2 chars + // 2) [0-7][0-7] + case a if isOctalDigit(a) && isValidIndex(i + 2) && isOctalDigit(charAt(i + 2)) => + val codePoint = ((a - '0') * 8) + (charAt(i + 2) - '0') + result += codePoint.toChar + i += 1 // skip one other number, so 2+1 chars + // 3) [0-7] + case a if isOctalDigit(a) => + val codePoint = a - '0' + result += codePoint.toChar + // bad escape otherwise, this catches everything else including the Unicode ones + case bad => + throw new IllegalArgumentException("Illegal escape: `\\" + bad + "`") + } + // skip ahead 2 chars (\ and the escape char) at minimum, cases above can add more if needed + i += 2 + } else { + throw new IllegalArgumentException("Illegal escape: `\\(end-of-string)`") + } + } else { + result += charAt(i) + i += 1 + } + } + result + } + + def transform[R](f: java.util.function.Function[String, R]): R = + f.apply(thisString) + @inline override def toString(): String = thisString diff --git a/javalib/src/main/scala/java/util/BitSet.scala b/javalib/src/main/scala/java/util/BitSet.scala new file mode 100644 index 0000000000..a0df60afa7 --- /dev/null +++ b/javalib/src/main/scala/java/util/BitSet.scala @@ -0,0 +1,690 @@ +/* + * 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.io.Serializable +import java.lang.Integer.bitCount +import java.lang.Integer.toUnsignedLong +import java.nio.{ByteBuffer, LongBuffer} +import java.util +import java.util.ScalaOps.IntScalaOps + +private object BitSet { + private final val AddressBitsPerWord = 5 // Int Based 2^5 = 32 + private final val ElementSize = 1 << AddressBitsPerWord + private final val RightBits = ElementSize - 1 + + def valueOf(longs: Array[Long]): util.BitSet = { + val bs = new util.BitSet + + for (i <- 0 until longs.length * 64) { + val idx = i / 64 + if ((longs(idx) & (1L << (i % 64))) != 0) + bs.set(i) + } + + bs + } + + def valueOf(lb: LongBuffer): BitSet = { + val arr = new Array[Long](lb.remaining()) + lb.get(arr) + lb.position(lb.position() - arr.length) // Restores the buffer position + valueOf(arr) + } + + def valueOf(bytes: Array[Byte]): BitSet = { + val bs = new BitSet + + for (i <- 0 until bytes.length * 8) { + val idx = i / 8 + if ((bytes(idx) & (1 << (i % 8))) != 0) + bs.set(i) + } + + bs + } + + def valueOf(bb: ByteBuffer): BitSet = { + val arr = new Array[Byte](bb.remaining()) + bb.get(arr) + bb.position(bb.position() - arr.length) // Restores the buffer position + valueOf(arr) + } +} + +class BitSet private (private var bits: Array[Int]) extends Serializable with Cloneable { + import BitSet.{AddressBitsPerWord, ElementSize, RightBits} + + def this(nbits: Int) = { + this( + bits = { + if (nbits < 0) + throw new NegativeArraySizeException + + val length = (nbits + BitSet.RightBits) >> BitSet.AddressBitsPerWord + + new Array[Int](length) + } + ) + } + + def this() = { + this(64) + } + + def toByteArray(): Array[Byte] = { + if (isEmpty()) { + new Array[Byte](0) + } else { + val l = (length() + 7) / 8 + val array = new Array[Byte](l) + + for (i <- 0 until length()) { + if (get(i)) + array(i / 8) = (array(i / 8) | (1 << (i % 8))).toByte + } + + array + } + } + + def toLongArray(): Array[Long] = { + if (isEmpty()) { + new Array[Long](0) + } else { + val l = (length() + 63) / 64 + val array = new Array[Long](l) + + for (i <- 0 until length()) { + if (get(i)) + array(i / 64) |= 1L << (i % 64) + } + + array + } + } + + def flip(bitIndex: Int): Unit = { + checkBitIndex(bitIndex) + + val len = (bitIndex >> AddressBitsPerWord) + 1 + ensureLength(len) + + bits(len - 1) ^= 1 << (bitIndex & RightBits) + } + + def flip(fromIndex: Int, toIndex: Int): Unit = { + checkToAndFromIndex(fromIndex, toIndex) + + if (fromIndex != toIndex) { + val len2 = ((toIndex - 1) >> AddressBitsPerWord) + 1 + ensureLength(len2) + val idx1 = fromIndex >> AddressBitsPerWord + val idx2 = (toIndex - 1) >> AddressBitsPerWord + val mask1 = (~0) << (fromIndex & RightBits) + val mask2 = (~0) >>> (ElementSize - (toIndex & RightBits)) + + if (idx1 == idx2) { + bits(idx1) ^= (mask1 & mask2) + } else { + bits(idx1) ^= mask1 + bits(idx2) ^= mask2 + for (i <- idx1 + 1 until idx2) + bits(i) ^= (~0) + } + } + } + + def set(bitIndex: Int): Unit = { + checkBitIndex(bitIndex) + + val len = (bitIndex >> AddressBitsPerWord) + 1 + ensureLength(len) + + bits(len - 1) |= 1 << (bitIndex & RightBits) + } + + def set(bitIndex: Int, value: Boolean): Unit = + if (value) set(bitIndex) + else clear(bitIndex) + + // fromIndex is inclusive, toIndex is exclusive + def set(fromIndex: Int, toIndex: Int): Unit = { + checkToAndFromIndex(fromIndex, toIndex) + + if (fromIndex != toIndex) { + val len2 = ((toIndex - 1) >> AddressBitsPerWord) + 1 + ensureLength(len2) + + val idx1 = fromIndex >> AddressBitsPerWord + val idx2 = (toIndex - 1) >> AddressBitsPerWord + val mask1 = (~0) << (fromIndex & RightBits) + val mask2 = (~0) >>> (ElementSize - (toIndex & RightBits)) + + if (idx1 == idx2) { + bits(idx1) |= (mask1 & mask2) + } else { + bits(idx1) |= mask1 + bits(idx2) |= mask2 + + for (i <- idx1 + 1 until idx2) + bits(i) |= (~0) + } + } + } + + def set(fromIndex: Int, toIndex: Int, value: Boolean): Unit = + if (value) set(fromIndex, toIndex) + else clear(fromIndex, toIndex) + + def clear(bitIndex: Int): Unit = { + checkBitIndex(bitIndex) + + val arrayPos = bitIndex >> AddressBitsPerWord + + if (arrayPos < bits.length) { + bits(arrayPos) &= ~(1 << (bitIndex & RightBits)) + } + } + + def clear(fromIndex: Int, toIndex: Int): Unit = { + checkToAndFromIndex(fromIndex, toIndex) + + val last = bits.length << AddressBitsPerWord + if (fromIndex >= last || fromIndex == toIndex) + return // scalastyle:ignore + + val toIndexOrLast = + if (toIndex > last) last + else toIndex + + val idx1 = fromIndex >> AddressBitsPerWord + val idx2 = (toIndexOrLast - 1) >> AddressBitsPerWord + val mask1 = (~0) << (fromIndex & RightBits) + val mask2 = (~0) >>> (ElementSize - (toIndexOrLast & RightBits)) + + if (idx1 == idx2) { + bits(idx1) &= ~(mask1 & mask2) + } else { + bits(idx1) &= ~mask1 + bits(idx2) &= ~mask2 + + for (i <- idx1 + 1 until idx2) + bits(i) = 0 + } + } + + def clear(): Unit = { + for (i <- 0 until bits.length) + bits(i) = 0 + } + + def get(bitIndex: Int): Boolean = { + checkBitIndex(bitIndex) + + val arrayPos = bitIndex >> AddressBitsPerWord + + if (arrayPos < bits.length) + (bits(arrayPos) & (1 << (bitIndex & RightBits))) != 0 + else + false + } + + def get(fromIndex: Int, toIndex: Int): BitSet = { + // scalastyle:off return + checkToAndFromIndex(fromIndex, toIndex) + + val last = bits.length << AddressBitsPerWord + if (fromIndex >= last || fromIndex == toIndex) + return new BitSet(0) + + val toIndexOrLast = + if (toIndex > last) last + else toIndex + + val idx1 = fromIndex >> AddressBitsPerWord + val idx2 = (toIndexOrLast - 1) >> AddressBitsPerWord + val mask1 = (~0) << (fromIndex & RightBits) + val mask2 = (~0) >>> (ElementSize - (toIndexOrLast & RightBits)) + + if (idx1 == idx2) { + val result = (bits(idx1) & (mask1 & mask2)) >>> (fromIndex % ElementSize) + if (result == 0) + return new BitSet(0) + + new BitSet(Array[Int](result)) + } else { + val newbits = new Array[Int](idx2 - idx1 + 1) + // first fill in the first and last indexes in the new bitset + newbits(0) = bits(idx1) & mask1 + newbits(newbits.length - 1) = bits(idx2) & mask2 + // fill in the in between elements of the new bitset + for (i <- 1 until idx2 - idx1) + newbits(i) = bits(idx1 + i) + + val numBitsToShift = fromIndex & RightBits + + if (numBitsToShift != 0) { + for (i <- 0 until newbits.length) { + // shift the current element to the right + newbits(i) = newbits(i) >>> numBitsToShift + // apply the last x bits of newbits[i+1] to the current + // element + if (i != newbits.length - 1) + newbits(i) |= newbits(i + 1) << (ElementSize - numBitsToShift) + } + } + + new BitSet(newbits) + } + // scalastyle:on return + } + + def nextSetBit(fromIndex: Int): Int = { + // scalastyle:off return + checkFromIndex(fromIndex) + + if (fromIndex >= (bits.length << AddressBitsPerWord)) + return -1 + + var idx = fromIndex >> AddressBitsPerWord + + // first check in the same bit set element + if (bits(idx) != 0) { + var j = fromIndex & RightBits + while (j < ElementSize) { + if ((bits(idx) & (1 << j)) != 0) + return (idx << AddressBitsPerWord) + j + j += 1 + } + } + + idx += 1 + + while (idx < bits.length && bits(idx) == 0) + idx += 1 + + if (idx == bits.length) + return -1 + + // we know for sure there is a bit set to true in this element + // since the bitset value is not 0 + var j = 0 + while (j < ElementSize) { + if ((bits(idx) & (1 << j)) != 0) + return (idx << AddressBitsPerWord) + j + j += 1 + } + + -1 + // scalastyle:on return + } + + def nextClearBit(fromIndex: Int): Int = { + // scalastyle:off return + checkFromIndex(fromIndex) + + val length = bits.length + val bssize = length << AddressBitsPerWord + + if (fromIndex >= bssize) + return fromIndex + + var idx = fromIndex >> AddressBitsPerWord + + if (bits(idx) != (~0)) { + var j = fromIndex % ElementSize + while (j < ElementSize) { + if ((bits(idx) & (1 << j)) == 0) + return idx * ElementSize + j + j += 1 + } + } + + idx += 1 + + while (idx < length && bits(idx) == (~0)) + idx += 1 + + if (idx == length) + return bssize + + var j = 0 + while (j < ElementSize) { + if ((bits(idx) & (1 << j)) == 0) + return (idx << AddressBitsPerWord) + j + j += 1 + } + + bssize + // scalastyle:on return + } + + def previousSetBit(fromIndex: Int): Int = { + // scalastyle:off return + if (fromIndex == -1) + return -1 + + checkFromIndex(fromIndex) + + val bssize = bits.length << AddressBitsPerWord + var idx = Math.min(bits.length - 1, fromIndex >> AddressBitsPerWord) + + if (bits(idx) != 0) { + if (idx == bssize) + return idx + + var j: Int = fromIndex % ElementSize + while (j >= 0) { + if ((bits(idx) & (1 << j)) != 0) + return idx * ElementSize + j + + j -= 1 + } + } + + idx -= 1 + + while (idx >= 0 && bits(idx) == 0) + idx -= 1 + + if (idx == -1) + return -1 + + var j: Int = ElementSize - 1 + while (j >= 0) { + if ((bits(idx) & (1 << j)) != 0) + return (idx << AddressBitsPerWord) + j + + j -= 1 + } + + bssize + // scalastyle:on return + } + + def previousClearBit(fromIndex: Int): Int = { + // scalastyle:off return + if (fromIndex == -1) + return -1 + + checkFromIndex(fromIndex) + + val length = bits.length + val bssize = length << AddressBitsPerWord + + if (fromIndex >= bssize) + return fromIndex + + var idx = Math.min(bits.length - 1, fromIndex >> AddressBitsPerWord) + + if (bits(idx) != (~0)) { + var j: Int = fromIndex % ElementSize + while (j >= 0) { + if ((bits(idx) & (1 << j)) == 0) + return idx * ElementSize + j + + j -= 1 + } + } + + idx -= 1 + + while (idx >= 0 && bits(idx) == (~0)) + idx -= 1 + + if (idx == -1) + return -1 + + var j: Int = ElementSize - 1 + while (j >= 0) { + if ((bits(idx) & (1 << j)) == 0) + return (idx << AddressBitsPerWord) + j + + j -= 1 + } + + bssize + // scalastyle:on return + } + + def length(): Int = { + val len = getActualArrayLength() + if (len == 0) + 0 + else + (len << AddressBitsPerWord) - Integer.numberOfLeadingZeros(bits(len - 1)) + } + + def isEmpty(): Boolean = getActualArrayLength() == 0 + + def intersects(set: BitSet): Boolean = { + // scalastyle:off return + val bsBits = set.bits + val length1 = bits.length + val length2 = set.bits.length + + if (length1 <= length2) { + var i: Int = 0 + while (i < length1) { + if ((bits(i) & bsBits(i)) != 0) + return true + + i += 1 + } + } else { + var i: Int = 0 + while (i < length2) { + if ((bits(i) & bsBits(i)) != 0) + return true + + i += 1 + } + } + + false + // scalastyle:on return + } + + def cardinality(): Int = { + var count = 0 + + val length = getActualArrayLength() + + for (idx <- 0 until length) { + count += bitCount(bits(idx)) + } + + count + } + + def and(set: BitSet): Unit = { + val bsBits = set.bits + val length1 = bits.length + val length2 = set.bits.length + + if (length1 <= length2) { + for (i <- 0 until length1) + bits(i) &= bsBits(i) + } else { + for (i <- 0 until length2) + bits(i) &= bsBits(i) + + for (i <- length2 until length1) + bits(i) = 0 + } + } + + def or(set: BitSet): Unit = { + val bsActualLen = set.getActualArrayLength() + + if (bsActualLen > bits.length) { + val tempBits = Arrays.copyOf(set.bits, bsActualLen) + + for (i <- 0 until bits.length) + tempBits(i) |= bits(i) + + bits = tempBits + } else { + val bsBits = set.bits + + for (i <- 0 until bsActualLen) + bits(i) |= bsBits(i) + } + } + + def xor(set: BitSet): Unit = { + val bsActualLen = set.getActualArrayLength() + + if (bsActualLen > bits.length) { + val tempBits = Arrays.copyOf(set.bits, bsActualLen) + + for (i <- 0 until bits.length) + tempBits(i) ^= bits(i) + + bits = tempBits + } else { + val bsBits = set.bits + + for (i <- 0 until bsActualLen) + bits(i) ^= bsBits(i) + } + } + + def andNot(set: BitSet): Unit = { + if (bits.length != 0) { + val bsBits = set.bits + + val minLength = Math.min(bits.length, set.bits.length) + + for (i <- 0 until minLength) + bits(i) &= ~bsBits(i) + } + } + + override def hashCode(): Int = { + var x: Long = 1234L + var i: Int = 0 + + while (i < bits.length) { + x ^= toUnsignedLong(bits(i)) * toUnsignedLong(i + 1) + i += 1 + } + + ((x >> 32) ^ x).toInt + } + + def size(): Int = bits.length << AddressBitsPerWord + + /** + * If one of the BitSets is larger than the other, check to see if + * any of its extra bits are set. If so return false. + */ + private def equalsImpl(other: BitSet): Boolean = { + // scalastyle:off return + val length1 = bits.length + val length2 = other.bits.length + + val smallerBS: BitSet = if (length1 <= length2) this else other + val smallerLength: Int = if (length1 <= length2) length1 else length2 + + val largerBS: BitSet = if (length1 > length2) this else other + val largerLength: Int = if (length1 > length2) length1 else length2 + + var i: Int = 0 + while (i < smallerLength) { + if (smallerBS.bits(i) != largerBS.bits(i)) + return false + + i += 1 + } + + // Check remainder bits, if they are zero these are equal + while (i < largerLength) { + if (largerBS.bits(i) != 0) + return false + + i += 1 + } + // scalastyle:on return + + true + } + + override def equals(obj: Any): Boolean = { + obj match { + case bs: BitSet => equalsImpl(bs) + case _ => false + } + } + + override def clone(): AnyRef = + new BitSet(bits.clone()) + + override def toString(): String = { + var result: String = "{" + var comma: Boolean = false + + for { + i <- 0 until getActualArrayLength() + j <- 0 until ElementSize + } { + if ((bits(i) & (1 << j)) != 0) { + if (comma) + result += ", " + else + comma = true + result += (i << AddressBitsPerWord) + j + } + } + + result += "}" + result + } + + final private def ensureLength(len: Int): Unit = { + if (len > bits.length) + bits = Arrays.copyOf(bits, Math.max(len, bits.length * 2)) + } + + final private def getActualArrayLength(): Int = { + var idx = bits.length - 1 + while (idx >= 0 && bits(idx) == 0) + idx -= 1 + + idx + 1 + } + + private def checkToAndFromIndex(fromIndex: Int, toIndex: Int): Unit = { + if (fromIndex < 0) + throw new IndexOutOfBoundsException(s"fromIndex < 0: $fromIndex") + + if (toIndex < 0) + throw new IndexOutOfBoundsException(s"toIndex < 0: $toIndex") + + if (toIndex < fromIndex) + throw new IndexOutOfBoundsException(s"fromIndex: $fromIndex > toIndex: $toIndex") + } + + private def checkFromIndex(fromIndex: Int): Unit = { + if (fromIndex < 0) + throw new IndexOutOfBoundsException(s"fromIndex < 0: $fromIndex") + } + + private def checkBitIndex(bitIndex: Int): Unit = { + if (bitIndex < 0) + throw new IndexOutOfBoundsException(s"bitIndex < 0: $bitIndex") + } +} + diff --git a/junit-async/js/src/main/scala/org/scalajs/junit/async/package.scala b/junit-async/js/src/main/scala/org/scalajs/junit/async/package.scala index 58eb712f36..5e6d312967 100644 --- a/junit-async/js/src/main/scala/org/scalajs/junit/async/package.scala +++ b/junit-async/js/src/main/scala/org/scalajs/junit/async/package.scala @@ -13,7 +13,13 @@ package org.scalajs.junit import scala.concurrent.Future -import scala.concurrent.ExecutionContext.Implicits.global + +/* Use the queue execution context (based on JS promises) explicitly: + * We do not have anything better at our disposal and it is accceptable in + * terms of fairness: All we use it for is to map over a completed Future once. + */ +import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue + import scala.util.{Try, Success} package object async { diff --git a/junit-runtime/src/main/scala/com/novocode/junit/JUnitFramework.scala b/junit-runtime/src/main/scala/com/novocode/junit/JUnitFramework.scala index b0e350f0e6..002f31251b 100644 --- a/junit-runtime/src/main/scala/com/novocode/junit/JUnitFramework.scala +++ b/junit-runtime/src/main/scala/com/novocode/junit/JUnitFramework.scala @@ -30,6 +30,7 @@ final class JUnitFramework extends Framework { f.runner(args, remoteArgs, testClassLoader) } + // Aka `workerRunner`; see the Scaladoc of `sbt.testing.Framework` about the name. def slaveRunner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader, send: String => Unit): Runner = { f.slaveRunner(args, remoteArgs, testClassLoader, send) diff --git a/junit-runtime/src/main/scala/org/scalajs/junit/JUnitFramework.scala b/junit-runtime/src/main/scala/org/scalajs/junit/JUnitFramework.scala index 674bf5874f..e960402f61 100644 --- a/junit-runtime/src/main/scala/org/scalajs/junit/JUnitFramework.scala +++ b/junit-runtime/src/main/scala/org/scalajs/junit/JUnitFramework.scala @@ -33,6 +33,7 @@ final class JUnitFramework extends Framework { new JUnitRunner(args, remoteArgs, parseRunSettings(args)) } + // Aka `workerRunner`; see the Scaladoc of `sbt.testing.Framework` about the name. def slaveRunner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader, send: String => Unit): Runner = { new JUnitRunner(args, remoteArgs, parseRunSettings(args)) diff --git a/junit-runtime/src/main/scala/org/scalajs/junit/JUnitTask.scala b/junit-runtime/src/main/scala/org/scalajs/junit/JUnitTask.scala index 6d15d71858..7c2dab2087 100644 --- a/junit-runtime/src/main/scala/org/scalajs/junit/JUnitTask.scala +++ b/junit-runtime/src/main/scala/org/scalajs/junit/JUnitTask.scala @@ -13,7 +13,14 @@ package org.scalajs.junit import scala.concurrent.Future -import scala.concurrent.ExecutionContext.Implicits.global + +/* Use the queue execution context (based on JS promises) explicitly: + * We do not have anything better at our disposal and it is accceptable in + * terms of fairness: We only use it for test dispatching and orchestation. + * The real async work is done in Bootstrapper#invokeTest which does not take + * an (implicit) ExecutionContext parameter. + */ +import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue import scala.util.{Try, Success, Failure} diff --git a/library/src/main/scala/scala/scalajs/js/BigInt.scala b/library/src/main/scala/scala/scalajs/js/BigInt.scala index 693f09f58e..10b6bdcd22 100644 --- a/library/src/main/scala/scala/scalajs/js/BigInt.scala +++ b/library/src/main/scala/scala/scalajs/js/BigInt.scala @@ -31,6 +31,7 @@ final class BigInt private[this] () extends js.Object { def +(other: BigInt): BigInt = js.native def *(other: BigInt): BigInt = js.native + def /(other: BigInt): BigInt = js.native def -(other: BigInt): BigInt = js.native def %(other: BigInt): BigInt = js.native @@ -70,6 +71,14 @@ final class BigInt private[this] () extends js.Object { * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/toString */ override def toString(): String = js.native + + /** Returns a string representation of this BigInt. + * + * The trailing "n" is not part of the string. + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/toString + */ + def toString(radix: Int): String = js.native } diff --git a/library/src/main/scala/scala/scalajs/js/annotation/JSImport.scala b/library/src/main/scala/scala/scalajs/js/annotation/JSImport.scala index 2ebb0150ff..3ee526cdd6 100644 --- a/library/src/main/scala/scala/scalajs/js/annotation/JSImport.scala +++ b/library/src/main/scala/scala/scalajs/js/annotation/JSImport.scala @@ -14,15 +14,10 @@ package scala.scalajs.js.annotation import scala.annotation.meta._ -/** Marks the annotated class or object as imported from another JS module. +/** Marks the annotated declaration as imported from another JS module. * * Intuitively, this corresponds to ECMAScript import directives. See the * documentation of the various constructors. - * - * `@JSImport` is not compatible with the `jsDependencies` mechanism offered - * by the Scala.js sbt plugin. You are responsible for resolving and/or - * bundling the JavaScript modules that you are importing using other - * mechanisms. */ @field @getter @setter class JSImport private () extends scala.annotation.StaticAnnotation { @@ -32,7 +27,21 @@ class JSImport private () extends scala.annotation.StaticAnnotation { * Intuitively, this corresponds to the following ECMAScript import * directive: * {{{ - * import { as AnnotatedClassOrObject } from + * import { AnnotatedDeclaration } from + * }}} + * + * The import name is inferred from the annotated declaration's name. + * To import the default export of a module, use `JSImport.Default` as + * the second parameter `name`. + */ + def this(module: String) = this() + + /** Named import of a member of the module. + * + * Intuitively, this corresponds to the following ECMAScript import + * directive: + * {{{ + * import { as AnnotatedDeclaration } from * }}} * * To import the default export of a module, use `JSImport.Default` as @@ -46,7 +55,7 @@ class JSImport private () extends scala.annotation.StaticAnnotation { * * Intuitively, this corresponds to * {{{ - * import * as AnnotatedObject from + * import * as AnnotatedDeclaration from * }}} */ def this(module: String, name: JSImport.Namespace.type) = this() @@ -88,7 +97,7 @@ object JSImport { * * Intuitively, it corresponds to `*` in an ECMAScript import: * {{{ - * import * as AnnotatedObject from + * import * as AnnotatedDeclaration from * }}} */ object Namespace diff --git a/library/src/main/scala/scala/scalajs/js/new.scala b/library/src/main/scala/scala/scalajs/js/new.scala new file mode 100644 index 0000000000..611fd9c3ce --- /dev/null +++ b/library/src/main/scala/scala/scalajs/js/new.scala @@ -0,0 +1,26 @@ +/* + * 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 scala.scalajs.js + +import scala.scalajs.js + +/** ECMAScript 2015 + * Meta-property `new.target`. + */ +object `new` { // scalastyle:ignore + /** ECMAScript 2015 + * Meta-property `new.target`. + */ + def target: js.Dynamic = + throw new java.lang.Error("stub") +} 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 1c88828b46..6fbaa9b13b 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 @@ -34,6 +34,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") def withStrictFloats(strictFloats: Boolean): Semantics = copy(strictFloats = strictFloats) @@ -224,7 +229,7 @@ object Semantics { asInstanceOfs = Fatal, arrayIndexOutOfBounds = Fatal, moduleInit = Unchecked, - strictFloats = false, + 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 6ddce3e986..d867c1d1bf 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 @@ -115,9 +115,11 @@ final class StandardConfig private ( * * The header must satisfy the following constraints: * - * - It must contain only valid JS whitespace and/or JS comments (single- or multi-line). + * - It must contain only valid JS whitespace and/or JS comments (single- or + * multi-line comment, or, at the very beginning, a hashbang comment). * - It must not use new line characters that are not UNIX new lines (`"\n"`). * - If non-empty, it must end with a new line. + * - It must not contain unpaired surrogate characters (i.e., it must be a valid UTF-16 string). * * Those requirements can be checked with [[StandardConfig.isValidJSHeader]]. * @@ -273,7 +275,8 @@ object StandardConfig { * * A header is valid if and only if it satisfies the following constraints: * - * - It must contain only valid JS whitespace and/or JS comments (single- or multi-line). + * - It must contain only valid JS whitespace and/or JS comments (single- or + * multi-line comment, or, at the very beginning, a hashbang comment). * - It must not use new line characters that are not UNIX new lines (`"\n"`). * - If non-empty, it must end with a new line. * - It must not contain unpaired surrogate characters (i.e., it must be a valid UTF-16 string). @@ -342,6 +345,17 @@ object StandardConfig { return false } + /* Accept a hashbang comment, but only at the very beginning + * This is a Stage 3 proposal: + * https://github.com/tc39/proposal-hashbang + * Documentation on MDN: + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#hashbang_comments + */ + case '#' if i == 1 && len >= 2 && jsHeader.charAt(1) == '!' => + i += 1 + while (i != len && jsHeader.charAt(i) != '\n') + i += 1 + /* Accept JavaScript Whitespace that are not Unicode new lines * https://262.ecma-international.org/12.0/#sec-white-space * TAB | SP | NBSP | ZWNBSP | diff --git a/linker-interface/shared/src/test/scala/org/scalajs/linker/interface/StandardConfigTest.scala b/linker-interface/shared/src/test/scala/org/scalajs/linker/interface/StandardConfigTest.scala index 623868715a..d77867180e 100644 --- a/linker-interface/shared/src/test/scala/org/scalajs/linker/interface/StandardConfigTest.scala +++ b/linker-interface/shared/src/test/scala/org/scalajs/linker/interface/StandardConfigTest.scala @@ -104,8 +104,32 @@ class StandardConfigTest { testInvalid("/* \uD834 */\n") testInvalid("/* \uDD1E */\n") + // Valid hashbang comments + testValid("#!/usr/bin/env node\n") + testValid("#!\n") + testValid("#! foo\n") + testValid("#! foo\tbar\n") + testValid("#! one\n// two\n") + testValid("#! α\n") // U+03B1 α Greek Small Letter Alpha + testValid("#! \uD834\uDD1E\n") // U+1D11E 𝄞 Musical Symbol G Clef + + // Invalid hashbang comments + testInvalid("#!") + testInvalid("#! foo") + testInvalid("#! foo\nbar\n") + testInvalid("#! \uD834\n") + testInvalid("#! \uDD1E\n") + testInvalid(" \t #! foo\n") + testInvalid(" #!foo\n") + testInvalid("\n#!foo\n") + testInvalid("// bar\n#!foo\n") + testInvalid("#foo\n") + testInvalid("#") + testInvalid("#\n") + testInvalid("##foo\n") + // Valid combination - testValid(" // foo\n\t/* foo bar\nbaz hello\n*/\n/**///foo\n") + testValid("#!foo\n // foo\n\t/* foo bar\nbaz hello\n*/\n/**///foo\n") // Invalid combination testInvalid(" ! // foo\n\t/* foo bar\nbaz hello\n*/\n/**///foo\n") @@ -114,5 +138,6 @@ class StandardConfigTest { testInvalid(" // foo\n\t/* foo bar\nbaz hello\n*/\n/**/!//foo\n") testInvalid(" // foo\n\t/* foo bar\nbaz hello\n*/\n/**//!/foo\n") testInvalid(" // foo\n\t/* foo bar\nbaz hello\n*/\n/**///foo\n!") + testInvalid(" // foo\n\t/* foo bar\nbaz hello\n*/\n#!foo\n/**///foo\n") } } diff --git a/linker/js/src/main/scala/org/scalajs/linker/NodeFS.scala b/linker/js/src/main/scala/org/scalajs/linker/NodeFS.scala index ca06816afc..28ee9cda14 100644 --- a/linker/js/src/main/scala/org/scalajs/linker/NodeFS.scala +++ b/linker/js/src/main/scala/org/scalajs/linker/NodeFS.scala @@ -52,53 +52,53 @@ private[linker] object NodeFS { def isDirectory(): Boolean } - @JSImport("fs", "open") + @JSImport("fs") @js.native def open(path: String, flags: String, callback: CB[Int]): Unit = js.native - @JSImport("fs", "close") + @JSImport("fs") @js.native def close(fd: Int, callback: CB[Unit]): Unit = js.native - @JSImport("fs", "read") + @JSImport("fs") @js.native def read(fd: Int, buffer: TypedArray[_, _], offset: Int, length: Int, position: Int, callback: CB[Int]): Unit = js.native - @JSImport("fs", "writeFile") + @JSImport("fs") @js.native def writeFile(path: String, data: TypedArray[_, _], callback: CB[Unit]): Unit = js.native - @JSImport("fs", "readdir") + @JSImport("fs") @js.native def readdir(path: String, opts: ReadDirOpt.type, cb: CB[js.Array[Dirent]]): Unit = js.native - @JSImport("fs", "readdir") + @JSImport("fs") @js.native def readdir(path: String, cb: CB[js.Array[String]]): Unit = js.native - @JSImport("fs", "readFile") + @JSImport("fs") @js.native def readFile(path: String, cb: CB[Uint8Array]): Unit = js.native - @JSImport("fs", "stat") + @JSImport("fs") @js.native def stat(path: String, cb: CB[Stats]): Unit = js.native - @JSImport("fs", "unlink") + @JSImport("fs") @js.native def unlink(path: String, cb: CB[Unit]): Unit = js.native - @JSImport("path", "join") + @JSImport("path") @js.native def join(paths: String*): String = js.native - @JSImport("path", "basename") + @JSImport("path") @js.native def basename(path: String): String = js.native - @JSImport("path", "dirname") + @JSImport("path") @js.native def dirname(path: String): String = js.native } diff --git a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala index 39b8b74cb0..276eb39ed6 100644 --- a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala +++ b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala @@ -344,6 +344,8 @@ private class ClosureAstTransformer(featureSet: FeatureSet, case ImportCall(arg) => new Node(Token.DYNAMIC_IMPORT, transformExpr(arg)) + case NewTarget() => + new Node(Token.NEW_TARGET) case Delete(prop) => new Node(Token.DELPROP, transformExpr(prop)) case UnaryOp(op, lhs) => diff --git a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/SyntheticAst.scala b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/SyntheticAst.scala new file mode 100644 index 0000000000..404ffc63fa --- /dev/null +++ b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/SyntheticAst.scala @@ -0,0 +1,41 @@ +/* + * 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.backend.closure + +import com.google.javascript.jscomp.{AbstractCompiler, SourceAst, SourceFile} +import com.google.javascript.rhino.{IR, InputId, Node} +import com.google.javascript.rhino.StaticSourceFile.SourceKind + +/** An AST generated totally by the compiler. + * + * This is a port of what we need from + * `com.google.javascript.jscomp.SyntheticAst`, before it was removed from the + * upstream repo in + * https://github.com/google/closure-compiler/commit/7a53987dd77dcc69511a42f58606f9d77709a50e + */ +private[closure] final class SyntheticAst(private var root: Node) extends SourceAst { + private val inputId = new InputId(root.getSourceFileName()) + private val sourceFile = SourceFile.fromCode(root.getSourceFileName(), "", SourceKind.STRONG) + + def getAstRoot(compiler: AbstractCompiler): Node = root + + def clearAst(): Unit = { + root = IR.script() + root.setInputId(inputId) + root.setStaticSourceFile(sourceFile) + } + + def getInputId(): InputId = inputId + + def getSourceFile(): SourceFile = sourceFile +} diff --git a/linker/jvm/src/test/scala/org/scalajs/linker/RunTest.scala b/linker/jvm/src/test/scala/org/scalajs/linker/RunTest.scala new file mode 100644 index 0000000000..ffac7fd344 --- /dev/null +++ b/linker/jvm/src/test/scala/org/scalajs/linker/RunTest.scala @@ -0,0 +1,80 @@ +/* + * 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 + +import scala.concurrent._ +import scala.concurrent.duration._ + +import java.nio.file.Path + +import org.junit.{Rule, Test} +import org.junit.Assert._ +import org.junit.rules.TemporaryFolder + +import org.scalajs.ir.Trees._ + +import org.scalajs.junit.async._ + +import org.scalajs.jsenv.nodejs.NodeJSEnv +import org.scalajs.jsenv.test.kit.TestKit + +import org.scalajs.linker.interface._ +import org.scalajs.linker.testutils.LinkingUtils._ +import org.scalajs.linker.testutils.TestIRBuilder._ + +class RunTest { + import scala.concurrent.ExecutionContext.Implicits.global + + @(Rule @scala.annotation.meta.getter) + val tempFolder = new TemporaryFolder() + + @Test + def jlClassIsCore_Issue4616(): AsyncResult = await { + // Check that if jl.Class is instantiated, it must be depended upon by jl.Object. + + val classDefs = Seq( + mainTestClassDef({ + consoleLog(ClassOf(T)) + }) + ) + + val linkerConfig = StandardConfig() + .withModuleKind(ModuleKind.ESModule) + .withModuleSplitStyle(ModuleSplitStyle.SmallestModules) + .withSourceMap(false) + .withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs")) + + testLinkAndRun(classDefs, MainTestModuleInitializers, linkerConfig, + TestKit.InputKind.ESModule) + } + + private def testLinkAndRun(classDefs: Seq[ClassDef], + moduleInitializers: List[ModuleInitializer], + linkerConfig: StandardConfig, inputKind: TestKit.InputKind): Future[Unit] = { + val output = tempFolder.newFolder().toPath + + val kit = new TestKit(new NodeJSEnv, timeout = 2.seconds, inputKind) + + for { + report <- testLink(classDefs, moduleInitializers, linkerConfig, PathOutputDirectory(output)) + } yield { + assertEquals(report.publicModules.size, 1) + + val path = output.resolve(report.publicModules.head.jsFileName) + + kit.withRun(Seq(kit.pathToInput(path))) { + _.succeeds() + } + } + } +} 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 3cf7db3c8e..871d108b30 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 @@ -200,6 +200,8 @@ object Analysis { final case class DynamicImportWithoutModuleSupport(from: From) extends Error + final case class NewTargetWithoutES2015Support(from: From) extends Error + final case class ImportMetaWithoutESModule(from: From) extends Error sealed trait From @@ -259,6 +261,8 @@ object Analysis { moduleIDs.map(_.id).mkString("[", ", ", "]") case DynamicImportWithoutModuleSupport(_) => "Uses dynamic import but module support is disabled" + case NewTargetWithoutES2015Support(_) => + "Uses new.target with an ECMAScript version older than ES 2015" case ImportMetaWithoutESModule(_) => "Uses import.meta with a module kind other than ESModule" } 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 f071efcb81..75fc15184a 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,7 +29,7 @@ import org.scalajs.ir.Trees.{MemberNamespace, JSNativeLoadSpec} import org.scalajs.ir.Types.ClassRef import org.scalajs.linker._ -import org.scalajs.linker.interface.{ModuleKind, ModuleInitializer} +import org.scalajs.linker.interface.{ESVersion, ModuleKind, ModuleInitializer} import org.scalajs.linker.interface.unstable.ModuleInitializerImpl import org.scalajs.linker.standard._ import org.scalajs.linker.standard.ModuleSet.ModuleID @@ -1270,6 +1270,17 @@ private final class Analyzer(config: CommonPhaseConfig, lookupClass(className)(_.accessData()) } + if (data.accessedClassClass) { + /* java.lang.Class is only ever instantiated in the CoreJSLib. + * Therefore, make java.lang.Object depend on it instead of the caller itself. + */ + objectClassInfo.staticDependencies += ClassClass + lookupClass(ClassClass) { clazz => + clazz.instantiated() + clazz.callMethodStatically(MemberNamespace.Constructor, ObjectArgConstructorName) + } + } + for (className <- data.referencedClasses) { /* No need to add to staticDependencies: The classes will not be * referenced in the final JS code. @@ -1354,6 +1365,10 @@ private final class Analyzer(config: CommonPhaseConfig, } } + if (data.accessedNewTarget && config.coreSpec.esFeatures.esVersion < ESVersion.ES2015) { + _errors += NewTargetWithoutES2015Support(from) + } + if (data.accessedImportMeta && config.coreSpec.moduleKind != ModuleKind.ESModule) { _errors += ImportMetaWithoutESModule(from) } 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 6e5011e8b1..027258f65f 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 @@ -90,13 +90,16 @@ object Infos { val usedInstanceTests: List[ClassName], val accessedClassData: List[ClassName], val referencedClasses: List[ClassName], + val accessedClassClass: Boolean, + val accessedNewTarget: Boolean, val accessedImportMeta: Boolean ) object ReachabilityInfo { val Empty: ReachabilityInfo = { new ReachabilityInfo(Map.empty, Map.empty, Map.empty, Map.empty, - Map.empty, Map.empty, Map.empty, Nil, Nil, Nil, Nil, Nil, false) + Map.empty, Map.empty, Map.empty, Nil, Nil, Nil, Nil, Nil, false, + false, false) } } @@ -159,6 +162,8 @@ object Infos { private val usedInstanceTests = mutable.Set.empty[ClassName] private val accessedClassData = mutable.Set.empty[ClassName] private val referencedClasses = mutable.Set.empty[ClassName] + private var accessedClassClass = false + private var accessedNewTarget = false private var accessedImportMeta = false def addPrivateJSFieldUsed(cls: ClassName, field: FieldName): this.type = { @@ -308,6 +313,16 @@ object Infos { this } + def addAccessedClassClass(): this.type = { + accessedClassClass = true + this + } + + def addAccessNewTarget(): this.type = { + accessedNewTarget = true + this + } + def addAccessImportMeta(): this.type = { accessedImportMeta = true this @@ -330,6 +345,8 @@ object Infos { usedInstanceTests = usedInstanceTests.toList, accessedClassData = accessedClassData.toList, referencedClasses = referencedClasses.toList, + accessedClassClass = accessedClassClass, + accessedNewTarget = accessedNewTarget, accessedImportMeta = accessedImportMeta ) } @@ -561,17 +578,17 @@ object Infos { case ClassOf(cls) => builder.maybeAddAccessedClassData(cls) - - // `ClassOf` instantiates a java.lang.Class. - builder.addInstantiatedClass(ClassClass, ObjectArgConstructorName) + builder.addAccessedClassClass() case GetClass(_) => - // `GetClass` instantiates a java.lang.Class. - builder.addInstantiatedClass(ClassClass, ObjectArgConstructorName) + builder.addAccessedClassClass() case JSPrivateSelect(qualifier, className, field) => builder.addPrivateJSFieldUsed(className, field.name) + case JSNewTarget() => + builder.addAccessNewTarget() + case JSImportMeta() => builder.addAccessImportMeta() 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 647ed4ada6..fc6d6a006f 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 @@ -442,20 +442,17 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } yield { implicit val pos = field.pos - val symbolValue = { + val symbolValueWithGlobals = { def description = origName.getOrElse(name).toString() val args = if (semantics.productionMode) Nil else js.StringLiteral(description) :: Nil - - if (esFeatures.esVersion >= ESVersion.ES2015) - js.Apply(js.VarRef(js.Ident("Symbol")), args) - else - genCallHelper("privateJSFieldSymbol", args: _*) + genCallPolyfillableBuiltin(PolyfillableBuiltin.PrivateSymbolBuiltin, args: _*) } - globalVarDef("r", (tree.className, name), symbolValue, - origName.orElse(name)) + symbolValueWithGlobals.flatMap { symbolValue => + globalVarDef("r", (tree.className, name), symbolValue, origName.orElse(name)) + } } WithGlobals.list(defs) 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 9ec681e9dc..f588ec11f3 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 @@ -28,6 +28,7 @@ import org.scalajs.linker.interface.unstable.RuntimeClassNameMapperImpl import org.scalajs.linker.backend.javascript.Trees._ import EmitterNames._ +import PolyfillableBuiltin._ private[emitter] object CoreJSLib { @@ -167,8 +168,8 @@ private[emitter] object CoreJSLib { } private def defineJSBuiltinsSnapshotsAndPolyfills(): Tree = { - def genPolyfillFor(builtinName: String): Tree = builtinName match { - case "is" => + def genPolyfillFor(builtin: PolyfillableBuiltin): Tree = builtin match { + case ObjectIsBuiltin => val x = varRef("x") val y = varRef("y") genArrowFunction(paramList(x, y), Return { @@ -181,7 +182,7 @@ private[emitter] object CoreJSLib { }) }) - case "imul" => + case ImulBuiltin => val a = varRef("a") val b = varRef("b") val ah = varRef("ah") @@ -196,21 +197,34 @@ private[emitter] object CoreJSLib { Return((al * bl) + (((ah * bl + al * bh) << 16) >>> 0) | 0) )) - case "fround" => + 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 typedArrayPolyfill = genArrowFunction(paramList(v), { + val typedArrayPolyfillInner = genArrowFunction(paramList(v), { Block( - const(array, New(Float32ArrayRef, 1 :: Nil)), 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 @@ -221,86 +235,120 @@ private[emitter] object CoreJSLib { * 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 isNegative = varRef("isNegative") + val sign = varRef("sign") val av = varRef("av") - val absResult = varRef("absResult") - val e = varRef("e") - val twoPowE = varRef("twoPowE") - val significand = varRef("significand") - - def callMathFun(fun: String, args: Tree*): Tree = - Apply(genIdentBracketSelect(MathRef, fun), args.toList) - - /* Rounds `value` to the nearest multiple of `unit`, breaking ties - * to an even multiple. The `unit` must be a (negative) power of 2. - * - * We do this by leveraging the inherent loss of precision near the - * minimum positive double value: conceptually, we divide the value - * by - * unit / 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, `unit / Double.MinPositiveValue` is not representable - * as a finite Double for all the values of `unit` that we are - * interested in (namely, `1 / 2^23`). Therefore, we instead use - * the *inverse* constant - * Double.MinPositiveValue / unit - * and we first multiply by that constant, then divide by it. - */ - def roundToNearestBreakTiesToEven(value: Tree, unit: Double): Tree = { - val roundingFactor = double(Double.MinPositiveValue / unit) - (value * roundingFactor) / roundingFactor - } + val p = varRef("p") val Inf = double(Double.PositiveInfinity) val overflowThreshold = double(3.4028235677973366e38) val normalThreshold = double(1.1754943508222875e-38) - val ln2 = double(0.6931471805599453) val noTypedArrayPolyfill = genArrowFunction(paramList(v), Block( - If((v !== v) || (v === 0), { - Return(v) - }), - const(isNegative, v < 0), - const(av, If(isNegative, -v, v)), - genEmptyMutableLet(absResult.ident), + 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 - absResult := Inf + Return(sign * Inf) }, If(av >= normalThreshold, Block( - const(e, callMathFun("floor", callMathFun("log", av) / ln2)), - let(twoPowE, callMathFun("pow", 2, e)), - let(significand, av / twoPowE), - /* Because of loss of precision in its computation, e might be 1 up or down, - * which causes twoPowE and significant to be a factor 2 up or down. - * We now adjust that so that significant is really in the range [1.0, 2.0). + /* 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. */ - If(significand < 1, Block( - twoPowE := twoPowE / 2, - significand := significand * 2 - ), If(significand >= 2, Block( - twoPowE := twoPowE * 2, - significand := significand / 2 - ))), - // Round the significant to 23 bits of fractional part, and multiply back by twoPowE - absResult := roundToNearestBreakTiesToEven(significand, 1.0 / (1 << 23).toDouble) * twoPowE + const(p, av * 536870913), + Return(sign * (p + (av - p))) ), { - // Round the value to a multiple of the smallest Float ULP, which is Float.MinPositiveValue - absResult := roundToNearestBreakTiesToEven(av, Float.MinPositiveValue.toDouble) - })), - Return(If(isNegative, -absResult, absResult)) + /* 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 "clz32" => + case Clz32Builtin => val i = varRef("i") val r = varRef("r") genArrowFunction(paramList(i), Block( @@ -314,7 +362,7 @@ private[emitter] object CoreJSLib { Return(r + (i >> 31)) )) - case "privateJSFieldSymbol" => + case PrivateSymbolBuiltin => /* function privateJSFieldSymbol(description) { * function rand32() { * const s = ((Math.random() * 4294967296.0) | 0).toString(16); @@ -359,7 +407,7 @@ private[emitter] object CoreJSLib { } )) - case "getOwnPropertyDescriptors" => + case GetOwnPropertyDescriptorsBuiltin => /* getOwnPropertyDescriptors = (() => { * // Fetch or polyfill Reflect.ownKeys * var ownKeysFun; @@ -457,31 +505,24 @@ private[emitter] object CoreJSLib { Apply(funGenerator, Nil) } - val mathBuiltins = Block( - List("imul", "fround", "clz32").map { builtinName => - val rhs0 = genIdentBracketSelect(MathRef, builtinName) - val rhs = - if (esVersion >= ESVersion.ES2015) rhs0 - else rhs0 || genPolyfillFor(builtinName) - extractWithGlobals(globalVarDef(builtinName, CoreVar, rhs)) + val polyfillDefs = for { + builtin <- PolyfillableBuiltin.All + if esVersion < builtin.availableInESVersion + } yield { + val polyfill = genPolyfillFor(builtin) + val rhs = builtin match { + case builtin: GlobalVarBuiltin => + // (typeof GlobalVar !== "undefined") ? GlobalVar : polyfill + val globalVarRef = globalRef(builtin.globalVar) + If(UnaryOp(JSUnaryOp.typeof, globalVarRef) !== str("undefined"), + globalVarRef, polyfill) + case builtin: NamespacedBuiltin => + // NamespaceGlobalVar.builtinName || polyfill + genIdentBracketSelect(globalRef(builtin.namespaceGlobalVar), builtin.builtinName) || polyfill } - ) - - val es5Compat = condTree(esVersion < ESVersion.ES2015)(Block( - extractWithGlobals(globalVarDef("is", CoreVar, - genIdentBracketSelect(ObjectRef, "is") || genPolyfillFor("is"))), - extractWithGlobals(globalVarDef("privateJSFieldSymbol", CoreVar, - If(UnaryOp(JSUnaryOp.typeof, SymbolRef) !== str("undefined"), - SymbolRef, genPolyfillFor("privateJSFieldSymbol")))) - )) - - val es2017Compat = condTree(esVersion < ESVersion.ES2017)(Block( - extractWithGlobals(globalVarDef("getOwnPropertyDescriptors", CoreVar, - genIdentBracketSelect(ObjectRef, "getOwnPropertyDescriptors") || - genPolyfillFor("getOwnPropertyDescriptors"))) - )) - - Block(mathBuiltins, es5Compat, es2017Compat) + extractWithGlobals(globalVarDef(builtin.builtinName, CoreVar, rhs)) + } + Block(polyfillDefs) } private def declareCachedL0(): Tree = { @@ -611,12 +652,8 @@ private[emitter] object CoreJSLib { defineFunction1("objectClone") { instance => // return Object.create(Object.getPrototypeOf(instance), $getOwnPropertyDescriptors(instance)); - val callGetOwnPropertyDescriptors = { - if (esVersion >= ESVersion.ES2017) - Apply(genIdentBracketSelect(ObjectRef, "getOwnPropertyDescriptors"), instance :: Nil) - else - genCallHelper("getOwnPropertyDescriptors", instance) - } + val callGetOwnPropertyDescriptors = genCallPolyfillableBuiltin( + GetOwnPropertyDescriptorsBuiltin, instance) Return(Apply(genIdentBracketSelect(ObjectRef, "create"), List( Apply(genIdentBracketSelect(ObjectRef, "getPrototypeOf"), instance :: Nil), callGetOwnPropertyDescriptors))) @@ -894,7 +931,7 @@ private[emitter] object CoreJSLib { (abs & bigInt(~0xffffL)) | bigInt(0x8000L) })), const(absR, Apply(NumberRef, y :: Nil)), - Return(genCallHelper("fround", If(x < bigInt(0L), -absR, absR))) + Return(genCallPolyfillableBuiltin(FroundBuiltin, If(x < bigInt(0L), -absR, absR))) ) } )) @@ -1188,7 +1225,7 @@ private[emitter] object CoreJSLib { condTree(strictFloats)( defineFunction1("isFloat") { v => Return((typeof(v) === str("number")) && - ((v !== v) || (genCallHelper("fround", v) === v))) + ((v !== v) || (genCallPolyfillableBuiltin(FroundBuiltin, v) === v))) } ) ) @@ -1953,6 +1990,11 @@ private[emitter] object CoreJSLib { private def genArrowFunction(args: List[ParamDef], body: Tree): Function = jsGen.genArrowFunction(args, None, body) + private def genCallPolyfillableBuiltin(builtin: PolyfillableBuiltin, + args: Tree*): Tree = { + extractWithGlobals(sjsGen.genCallPolyfillableBuiltin(builtin, args: _*)) + } + private def maybeWrapInUBE(behavior: CheckedBehavior, exception: Tree): Tree = { if (behavior == CheckedBehavior.Fatal) { genScalaClassNew(UndefinedBehaviorErrorClass, 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 b8289c3edc..0dfdc4ec64 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 @@ -33,6 +33,7 @@ import org.scalajs.linker.backend.javascript.{Trees => js} import java.io.StringWriter import EmitterNames._ +import PolyfillableBuiltin._ import Transients._ /** Desugaring of the IR to JavaScript functions. @@ -1222,6 +1223,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { // Atomic expressions case _: Literal => true case _: This => true + case _: JSNewTarget => true case _: JSLinkingInfo => true // Vars (side-effect free, pure if immutable) @@ -1255,8 +1257,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case If(cond, thenp, elsep) => test(cond) && test(thenp) && test(elsep) case BinaryOp(_, lhs, rhs) => test(lhs) && test(rhs) case UnaryOp(_, lhs) => test(lhs) - case JSBinaryOp(_, lhs, rhs) => test(lhs) && test(rhs) - case JSUnaryOp(_, lhs) => test(lhs) case ArrayLength(array) => test(array) case RecordSelect(record, _) => test(record) case IsInstanceOf(expr, _) => test(expr) @@ -1345,6 +1345,10 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { allowSideEffects case LoadJSModule(_) => allowSideEffects + case JSBinaryOp(_, lhs, rhs) => + allowSideEffects && test(lhs) && test(rhs) + case JSUnaryOp(_, lhs) => + allowSideEffects && test(lhs) case JSGlobalRef(_) => allowSideEffects case JSTypeOfGlobalRef(_) => @@ -2358,10 +2362,8 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { js.BinaryOp(if (op == ===) JSBinaryOp.=== else JSBinaryOp.!==, newLhs, newRhs) } else { - val objectIs = - if (!es2015) globalVar("is", CoreVar) - else genIdentBracketSelect(genGlobalVarRef("Object"), "is") - val objectIsCall = js.Apply(objectIs, newLhs :: newRhs :: Nil) + val objectIsCall = + genCallPolyfillableBuiltin(ObjectIsBuiltin, newLhs, newRhs) if (op == ===) objectIsCall else js.UnaryOp(JSUnaryOp.!, objectIsCall) } @@ -2385,7 +2387,8 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case IntLiteral(0) => or0(js.UnaryOp(JSUnaryOp.-, newRhs)) case _ => or0(js.BinaryOp(JSBinaryOp.-, newLhs, newRhs)) } - case Int_* => genCallHelper("imul", newLhs, newRhs) + case Int_* => + genCallPolyfillableBuiltin(ImulBuiltin, newLhs, newRhs) case Int_/ => rhs match { case IntLiteral(r) if r != 0 => @@ -2594,7 +2597,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { genIsInstanceOf(transformExprNoChar(expr), testType) case AsInstanceOf(expr, tpe) => - genAsInstanceOf(transformExprNoChar(expr), tpe) + extractWithGlobals(genAsInstanceOf(transformExprNoChar(expr), tpe)) case GetClass(expr) => genCallHelper("objectGetClass", transformExprNoChar(expr)) @@ -2659,7 +2662,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { } case Transient(NumberOfLeadingZeroes(num)) => - genCallHelper("clz32", transformExprNoChar(num)) + genCallPolyfillableBuiltin(Clz32Builtin, transformExprNoChar(num)) case Transient(ObjectClassName(obj)) => genCallHelper("objectClassName", transformExprNoChar(obj)) @@ -2755,6 +2758,9 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case JSImportCall(arg) => js.ImportCall(transformExprNoChar(arg)) + case JSNewTarget() => + js.NewTarget() + case JSImportMeta() => js.ImportMeta() @@ -2985,10 +2991,15 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { js.Apply(globalVar(field, (className, method.name)), args) } - private def genFround(arg: js.Tree)(implicit pos: Position): js.Tree = { - genCallHelper("fround", arg) + private def genCallPolyfillableBuiltin( + builtin: PolyfillableBuiltin, args: js.Tree*)( + implicit pos: Position): js.Tree = { + extractWithGlobals(sjsGen.genCallPolyfillableBuiltin(builtin, args: _*)) } + private def genFround(arg: js.Tree)(implicit pos: Position): js.Tree = + genCallPolyfillableBuiltin(FroundBuiltin, arg) + private def wrapBigInt32(tree: js.Tree)(implicit pos: Position): js.Tree = wrapBigIntN(32, tree) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/PolyfillableBuiltin.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/PolyfillableBuiltin.scala new file mode 100644 index 0000000000..0aa02d9a27 --- /dev/null +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/PolyfillableBuiltin.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.linker.backend.emitter + +import org.scalajs.linker.interface.ESVersion + +private[emitter] sealed abstract class PolyfillableBuiltin( + val builtinName: String, val availableInESVersion: ESVersion) + +private[emitter] object PolyfillableBuiltin { + lazy val All: List[PolyfillableBuiltin] = List( + ObjectIsBuiltin, + ImulBuiltin, + FroundBuiltin, + Clz32Builtin, + PrivateSymbolBuiltin, + GetOwnPropertyDescriptorsBuiltin + ) + + sealed abstract class GlobalVarBuiltin(val globalVar: String, + builtinName: String, availableInESVersion: ESVersion) + extends PolyfillableBuiltin(builtinName, availableInESVersion) + + sealed abstract class NamespacedBuiltin(val namespaceGlobalVar: String, + builtinName: String, availableInESVersion: ESVersion) + extends PolyfillableBuiltin(builtinName, availableInESVersion) + + case object ObjectIsBuiltin extends NamespacedBuiltin("Object", "is", ESVersion.ES2015) + case object ImulBuiltin extends NamespacedBuiltin("Math", "imul", ESVersion.ES2015) + case object FroundBuiltin extends NamespacedBuiltin("Math", "fround", ESVersion.ES2015) + case object Clz32Builtin extends NamespacedBuiltin("Math", "clz32", ESVersion.ES2015) + case object PrivateSymbolBuiltin + extends GlobalVarBuiltin("Symbol", "privateJSFieldSymbol", ESVersion.ES2015) + case object GetOwnPropertyDescriptorsBuiltin + extends NamespacedBuiltin("Object", "getOwnPropertyDescriptors", ESVersion.ES2017) +} 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 fc1bf00df3..124e1d2c08 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 @@ -21,6 +21,7 @@ import org.scalajs.linker.backend.javascript.Trees._ import org.scalajs.linker.interface._ import EmitterNames._ +import PolyfillableBuiltin._ /** Scala.js specific tree generators that are used across the board. * @@ -267,31 +268,34 @@ private[emitter] final class SJSGen( def genAsInstanceOf(expr: Tree, tpe: Type)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, - pos: Position): Tree = { + pos: Position): WithGlobals[Tree] = { import TreeDSL._ + // Local short-hand of WithGlobals(...) + def wg(tree: Tree): WithGlobals[Tree] = WithGlobals(tree) + if (semantics.asInstanceOfs == CheckedBehavior.Unchecked) { tpe match { case _:ClassType | _:ArrayType | AnyType => - expr + wg(expr) - case UndefType => Block(expr, Undefined()) - case BooleanType => !(!expr) - case CharType => genCallHelper("uC", expr) - case ByteType | ShortType| IntType => expr | 0 - case LongType => genCallHelper("uJ", expr) - case DoubleType => UnaryOp(irt.JSUnaryOp.+, expr) - case StringType => expr || StringLiteral("") + case UndefType => wg(Block(expr, Undefined())) + case BooleanType => wg(!(!expr)) + case CharType => wg(genCallHelper("uC", expr)) + case ByteType | ShortType| IntType => wg(expr | 0) + case LongType => wg(genCallHelper("uJ", expr)) + case DoubleType => wg(UnaryOp(irt.JSUnaryOp.+, expr)) + case StringType => wg(expr || StringLiteral("")) case FloatType => - if (semantics.strictFloats) genCallHelper("fround", expr) - else UnaryOp(irt.JSUnaryOp.+, expr) + if (semantics.strictFloats) genCallPolyfillableBuiltin(FroundBuiltin, expr) + else wg(UnaryOp(irt.JSUnaryOp.+, expr)) case NoType | NullType | NothingType | _:RecordType => throw new AssertionError(s"Unexpected type $tpe in genAsInstanceOf") } } else { - tpe match { + val resultTree = tpe match { case ClassType(ObjectClass) => expr case ClassType(className) => @@ -315,6 +319,8 @@ private[emitter] final class SJSGen( case NoType | NullType | NothingType | _:RecordType => throw new AssertionError(s"Unexpected type $tpe in genAsInstanceOf") } + + wg(resultTree) } } @@ -372,6 +378,23 @@ private[emitter] final class SJSGen( Apply(globalVar(helperName, CoreVar), args.toList) } + def genCallPolyfillableBuiltin(builtin: PolyfillableBuiltin, args: Tree*)( + implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, + pos: Position): WithGlobals[Tree] = { + if (esFeatures.esVersion >= builtin.availableInESVersion) { + builtin match { + case builtin: GlobalVarBuiltin => + for (global <- globalRef(builtin.globalVar)) yield + Apply(global, args.toList) + case builtin: NamespacedBuiltin => + for (namespace <- globalRef(builtin.namespaceGlobalVar)) yield + Apply(genIdentBracketSelect(namespace, builtin.builtinName), args.toList) + } + } else { + WithGlobals(genCallHelper(builtin.builtinName, args: _*)) + } + } + def genLoadModule(moduleClass: ClassName)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala index d3afba92e4..867c3e57c7 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/javascript/Printers.scala @@ -377,6 +377,9 @@ object Printers { print(arg) print(')') + case NewTarget() => + print("new.target") + case ImportMeta() => print("import.meta") 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 fb3fd7aeae..ee7a6df2a6 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 @@ -249,6 +249,9 @@ object Trees { sealed case class ImportCall(arg: Tree)(implicit val pos: Position) extends Tree + /** Meta-property `new.target`. */ + sealed case class NewTarget()(implicit val pos: Position) extends Tree + /** Meta-property `import.meta`. */ sealed case class ImportMeta()(implicit val pos: Position) extends Tree 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 51c08513b0..22781fef74 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 @@ -171,7 +171,7 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { else if (superClass.kind == ClassKind.NativeJSClass && superClass.jsNativeLoadSpec.isEmpty) reportError(i"Native super class ${superClass.name} must have a native load spec") } { tree => - val env = Env.fromSignature(NoType, classDef.jsClassCaptures, Nil) + val env = Env.fromSignature(hasNewTarget = false, NoType, classDef.jsClassCaptures, Nil) typecheckExpect(tree, env, AnyType) } } else { @@ -389,7 +389,7 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { val inConstructorOf = if (isConstructor) Some(classDef.name.name) else None - Env.fromSignature(thisType, None, params, inConstructorOf) + Env.fromSignature(hasNewTarget = false, thisType, None, params, inConstructorOf) } body.fold { @@ -441,7 +441,8 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { else ClassType(clazz.name) } - val bodyEnv = Env.fromSignature(thisType, clazz.jsClassCaptures, params ++ restParam) + val bodyEnv = Env.fromSignature(hasNewTarget = false, thisType, + clazz.jsClassCaptures, params ++ restParam) typecheckExpect(body, bodyEnv, AnyType) } } @@ -469,8 +470,9 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { (JSSuperConstructorCall(Nil)(methodDef.pos), Nil) } - val initialEnv = Env.fromSignature(NoType, clazz.jsClassCaptures, - params ++ restParam, inConstructorOf = Some(clazz.name)) + val initialEnv = Env.fromSignature(hasNewTarget = true, NoType, + clazz.jsClassCaptures, params ++ restParam, + inConstructorOf = Some(clazz.name)) val preparedEnv = typecheckBlockTrees(prepStats, initialEnv) @@ -506,7 +508,8 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { else ClassType(clazz.name) getterBody.foreach { getterBody => - val getterBodyEnv = Env.fromSignature(thisType, clazz.jsClassCaptures, Nil) + val getterBodyEnv = Env.fromSignature(hasNewTarget = false, thisType, + clazz.jsClassCaptures, Nil) typecheckExpect(getterBody, getterBodyEnv, AnyType) } @@ -516,8 +519,8 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { reportError("Setter argument of exported property def has type "+ i"${setterArg.ptpe}, but must be Any") - val setterBodyEnv = Env.fromSignature(thisType, clazz.jsClassCaptures, - List(setterArg)) + val setterBodyEnv = Env.fromSignature(hasNewTarget = false, thisType, + clazz.jsClassCaptures, List(setterArg)) typecheck(setterBody, setterBodyEnv) } } @@ -579,7 +582,7 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { checkJSParamDefs(params, restParam) - val bodyEnv = Env.fromSignature(NoType, None, params ++ restParam) + val bodyEnv = Env.fromSignature(hasNewTarget = false, NoType, None, params ++ restParam) typecheckExpect(body, bodyEnv, AnyType) } @@ -1032,6 +1035,10 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { case JSImportCall(arg) => typecheckExpr(arg, env) + case JSNewTarget() => + if (!env.hasNewTarget) + reportError(i"Cannot refer to `new.target` outside of a JS class constructor or non-arrow function") + case JSImportMeta() => case LoadJSConstructor(className) => @@ -1140,8 +1147,9 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { checkJSParamDefs(params, restParam) + val hasNewTarget = !arrow val thisType = if (arrow) NoType else AnyType - val bodyEnv = Env.fromSignature(thisType, None, captureParams ++ params ++ restParam) + val bodyEnv = Env.fromSignature(hasNewTarget, thisType, None, captureParams ++ params ++ restParam) typecheckExpect(body, bodyEnv, AnyType) } @@ -1314,6 +1322,8 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { } private class Env( + /** Whether there is a valid `new.target` in scope. */ + val hasNewTarget: Boolean, /** Type of `this`. Can be NoType. */ val thisTpe: Type, /** Local variables in scope (including through closures). */ @@ -1326,28 +1336,28 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { import Env._ def withThis(thisTpe: Type): Env = - new Env(thisTpe, this.locals, this.returnTypes, this.inConstructorOf) + new Env(hasNewTarget, thisTpe, this.locals, this.returnTypes, this.inConstructorOf) def withLocal(localDef: LocalDef): Env = { - new Env(thisTpe, locals + (localDef.name -> localDef), returnTypes, - this.inConstructorOf) + new Env(hasNewTarget, thisTpe, locals + (localDef.name -> localDef), + returnTypes, this.inConstructorOf) } def withLabeledReturnType(label: LabelName, returnType: Type): Env = - new Env(this.thisTpe, this.locals, + new Env(hasNewTarget, this.thisTpe, this.locals, returnTypes + (label -> returnType), this.inConstructorOf) } private object Env { - val empty: Env = new Env(NoType, Map.empty, Map.empty, None) + val empty: Env = new Env(hasNewTarget = false, NoType, Map.empty, Map.empty, None) - def fromSignature(thisType: Type, jsClassCaptures: Option[List[ParamDef]], + def fromSignature(hasNewTarget: Boolean, thisType: Type, jsClassCaptures: Option[List[ParamDef]], params: List[ParamDef], inConstructorOf: Option[ClassName] = None): Env = { val allParams = jsClassCaptures.getOrElse(Nil) ::: params val paramLocalDefs = for (p @ ParamDef(ident, _, tpe, mutable) <- allParams) yield ident.name -> LocalDef(ident.name, tpe, mutable) - new Env(thisType, paramLocalDefs.toMap, Map.empty, inConstructorOf) + new Env(hasNewTarget, thisType, paramLocalDefs.toMap, Map.empty, inConstructorOf) } } 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 7ab7b18c7d..9cc7db4f1e 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 @@ -62,16 +62,13 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: private var objectClass: Class = _ private val classes = collOps.emptyMap[ClassName, Class] - - private val staticLikes = - collOps.emptyParMap[ClassName, Array[StaticLikeNamespace]] - - private val interfaces = collOps.emptyMap[ClassName, InterfaceType] + private val interfaces = collOps.emptyParMap[ClassName, InterfaceType] private var methodsToProcess = collOps.emptyAddable[MethodImpl] + @inline private def getInterface(className: ClassName): InterfaceType = - interfaces.getOrElseUpdate(className, new InterfaceType(className)) + collOps.forceGet(interfaces, className) /** Update the incremental analyzer with a new run. */ def update(unit: LinkingUnit, logger: Logger): LinkingUnit = { @@ -90,14 +87,14 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: val newLinkedClasses = for (linkedClass <- unit.classDefs) yield { val className = linkedClass.className - val staticLikeContainers = collOps.forceGet(staticLikes, className) + val interface = getInterface(className) val publicContainer = classes.get(className).getOrElse { /* For interfaces, we need to look at default methods. * For other kinds of classes, the public namespace is necessarily * empty. */ - val container = staticLikeContainers(MemberNamespace.Public.ordinal) + val container = interface.staticLike(MemberNamespace.Public) assert( linkedClass.kind == ClassKind.Interface || container.methods.isEmpty, linkedClass.className -> linkedClass.kind) @@ -108,7 +105,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: val namespace = m.value.flags.namespace val container = if (namespace == MemberNamespace.Public) publicContainer - else staticLikeContainers(namespace.ordinal) + else interface.staticLike(namespace) container.methods(m.value.methodName).optimizedMethodDef } @@ -123,13 +120,10 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: * UPDATE PASS ONLY. (This IS the update pass). */ private def updateAndTagEverything(linkedClasses: List[LinkedClass]): Unit = { - val neededStaticLikes = collOps.emptyParMap[ClassName, LinkedClass] + val neededInterfaces = collOps.emptyParMap[ClassName, LinkedClass] val neededClasses = collOps.emptyParMap[ClassName, LinkedClass] for (linkedClass <- linkedClasses) { - // Update the list of ancestors for all linked classes - getInterface(linkedClass.className).ancestors = linkedClass.ancestors - - collOps.put(neededStaticLikes, linkedClass.className, linkedClass) + collOps.put(neededInterfaces, linkedClass.className, linkedClass) if (linkedClass.hasInstances && (linkedClass.kind.isClass || linkedClass.kind == ClassKind.HijackedClass)) { @@ -137,47 +131,32 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: } } - /* Remove deleted static-like stuff, and update existing ones. + /* Remove deleted interfaces, and update existing ones. * We don't even have to notify callers in case of additions or removals * because callers have got to be invalidated by themselves. * Only changed methods need to trigger notifications. * * Non-batch mode only. */ - assert(!batchMode || collOps.isEmpty(staticLikes)) + assert(!batchMode || collOps.isEmpty(interfaces)) if (!batchMode) { - collOps.retain(staticLikes) { (className, staticLikeNamespaces) => - collOps.remove(neededStaticLikes, className).fold { - /* Deleted static-like context. Mark all its methods as deleted, and - * remove it from known static-like contexts. - */ - for (staticLikeNamespace <- staticLikeNamespaces) - staticLikeNamespace.methods.values.foreach(_.delete()) + collOps.retain(interfaces) { (className, interface) => + collOps.remove(neededInterfaces, className).fold { + interface.delete() false } { linkedClass => - /* Existing static-like context. Update it. */ - for (staticLikeNamespace <- staticLikeNamespaces) { - val (_, changed, _) = staticLikeNamespace.updateWith(linkedClass) - for (method <- changed) { - staticLikeNamespace.myInterface.tagStaticCallersOf( - staticLikeNamespace.namespace, method) - } - } + interface.updateWith(linkedClass) true } } } - /* Add new static-like stuff. + /* Add new interfaces. * Easy, we don't have to notify anyone. */ - collOps.valuesForeach(neededStaticLikes) { linkedClass => - val namespaces = Array.tabulate(MemberNamespace.Count) { ord => - new StaticLikeNamespace(linkedClass.className, - MemberNamespace.fromOrdinal(ord)) - } - collOps.put(staticLikes, linkedClass.className, namespaces) - namespaces.foreach(_.updateWith(linkedClass)) + collOps.valuesForeach(neededInterfaces) { linkedClass => + val interface = new InterfaceType(linkedClass) + collOps.put(interfaces, interface.className, interface) } if (!batchMode) { @@ -213,11 +192,10 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: val newChildrenByParent = collOps.emptyAccMap[ClassName, LinkedClass] collOps.valuesForeach(neededClasses) { linkedClass => - linkedClass.superClass.fold { + linkedClass.superClass.fold[Unit] { assert(batchMode, "Trying to add java.lang.Object in incremental mode") - objectClass = new Class(None, linkedClass.className) + objectClass = new Class(None, linkedClass) classes += linkedClass.className -> objectClass - objectClass.setupAfterCreation(linkedClass) } { superClassName => collOps.acc(newChildrenByParent, superClassName.name, linkedClass) } @@ -254,17 +232,19 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: /** Base class for [[IncOptimizer.Class]] and * [[IncOptimizer.StaticLikeNamespace]]. */ - private abstract class MethodContainer(val className: ClassName, + private abstract class MethodContainer(linkedClass: LinkedClass, val namespace: MemberNamespace) { + val className: ClassName = linkedClass.className + def thisType: Type = if (namespace.isStatic) NoType else ClassType(className) - val myInterface = getInterface(className) - val methods = mutable.Map.empty[MethodName, MethodImpl] + updateWith(linkedClass) + def optimizedDefs: List[Versioned[MethodDef]] = { (for { method <- methods.values @@ -313,13 +293,6 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: } } - this match { - case cls: Class => - cls.isModuleClass = linkedClass.kind == ClassKind.ModuleClass - cls.fields = linkedClass.fields - case _ => - } - for (linkedMethodDef <- linkedMethodDefs) { val methodName = linkedMethodDef.value.methodName @@ -351,9 +324,10 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: * maintains a list of its direct subclasses, so that the instances of * [[Class]] form a tree of the class hierarchy. */ - private final class Class(val superClass: Option[Class], - _className: ClassName) - extends MethodContainer(_className, MemberNamespace.Public) { + private final class Class(val superClass: Option[Class], linkedClass: LinkedClass) + extends MethodContainer(linkedClass, MemberNamespace.Public) with Unregisterable { + + val myInterface = getInterface(className) if (className == ObjectClass) { assert(superClass.isEmpty) @@ -370,16 +344,18 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: val reverseParentChain: List[Class] = parentChain.reverse - var interfaces: Set[InterfaceType] = Set.empty + var interfaces: Set[InterfaceType] = linkedClass.ancestors.map(getInterface).toSet var subclasses: collOps.ParIterable[Class] = collOps.emptyParIterable - var isInstantiated: Boolean = false + var isInstantiated: Boolean = linkedClass.hasInstances - var isModuleClass: Boolean = false - var hasElidableModuleAccessor: Boolean = false + private var hasElidableModuleAccessor: Boolean = computeHasElidableModuleAccessor(linkedClass) + private val hasElidableModuleAccessorAskers = collOps.emptyMap[MethodImpl, Unit] - var fields: List[AnyFieldDef] = Nil + var fields: List[AnyFieldDef] = linkedClass.fields var tryNewInlineable: Option[OptimizerCore.InlineableClassStructure] = None + setupAfterCreation(linkedClass) + override def toString(): String = className.nameString @@ -447,6 +423,8 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: val (addedMethods, changedMethods, deletedMethods) = updateWith(linkedClass) + fields = linkedClass.fields + val oldInterfaces = interfaces val newInterfaces = linkedClass.ancestors.map(getInterface).toSet interfaces = newInterfaces @@ -496,7 +474,12 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: myInterface.tagStaticCallersOf(namespace, methodName) // Module class specifics - updateHasElidableModuleAccessor() + val newHasElidableModuleAccessor = computeHasElidableModuleAccessor(linkedClass) + if (hasElidableModuleAccessor != newHasElidableModuleAccessor) { + hasElidableModuleAccessor = newHasElidableModuleAccessor + hasElidableModuleAccessorAskers.keysIterator.foreach(_.tag()) + hasElidableModuleAccessorAskers.clear() + } // Inlineable class if (updateTryNewInlineable(linkedClass)) { @@ -517,28 +500,34 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: val subclassAcc = collOps.prepAdd(subclasses) collOps.foreach(getNewChildren(className)) { linkedClass => - val cls = new Class(Some(this), linkedClass.className) + val cls = new Class(Some(this), linkedClass) collOps.add(subclassAcc, cls) classes += linkedClass.className -> cls - cls.setupAfterCreation(linkedClass) cls.walkForAdditions(getNewChildren) } subclasses = collOps.finishAdd(subclassAcc) } + def askHasElidableModuleAccessor(asker: MethodImpl): Boolean = { + hasElidableModuleAccessorAskers.put(asker, ()) + asker.registerTo(this) + hasElidableModuleAccessor + } + /** UPDATE PASS ONLY. */ - def updateHasElidableModuleAccessor(): Unit = { + private def computeHasElidableModuleAccessor(linkedClass: LinkedClass): Boolean = { def lookupModuleConstructor: Option[MethodImpl] = { - collOps - .forceGet(staticLikes, className)(MemberNamespace.Constructor.ordinal) + myInterface + .staticLike(MemberNamespace.Constructor) .methods .get(NoArgConstructorName) } - hasElidableModuleAccessor = - isAdHocElidableModuleAccessor(className) || - (isModuleClass && lookupModuleConstructor.exists(isElidableModuleConstructor)) + val isModuleClass = linkedClass.kind == ClassKind.ModuleClass + + isAdHocElidableModuleAccessor(className) || + (isModuleClass && lookupModuleConstructor.exists(isElidableModuleConstructor)) } /** UPDATE PASS ONLY. */ @@ -568,13 +557,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: } /** UPDATE PASS ONLY. */ - def setupAfterCreation(linkedClass: LinkedClass): Unit = { - - updateWith(linkedClass) - interfaces = linkedClass.ancestors.map(getInterface).toSet - - isInstantiated = linkedClass.hasInstances - + private[this] def setupAfterCreation(linkedClass: LinkedClass): Unit = { if (batchMode) { if (isInstantiated) { /* Only add the class to all its ancestor interfaces */ @@ -605,7 +588,6 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: myInterface.tagStaticCallersOf(namespace, methodName) } - updateHasElidableModuleAccessor() updateTryNewInlineable(linkedClass) } @@ -623,7 +605,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: case ApplyStatic(flags, className, methodName, List(This())) if !flags.isPrivate => val container = - collOps.forceGet(staticLikes, className)(MemberNamespace.PublicStatic.ordinal) + getInterface(className).staticLike(MemberNamespace.PublicStatic) container.methods(methodName.name).originalDef.body.exists { case Skip() => true case _ => false @@ -634,7 +616,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: if !flags.isPrivate && !classes.contains(className) => // Since className is not in classes, it must be a default method call. val container = - collOps.forceGet(staticLikes, className)(MemberNamespace.Public.ordinal) + getInterface(className).staticLike(MemberNamespace.Public) container.methods.get(methodName.name) exists { methodDef => methodDef.originalDef.body exists { case Skip() => true @@ -645,10 +627,9 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: // Delegation to another constructor (super or in the same class) case ApplyStatically(flags, This(), className, methodName, args) if flags.isConstructor => - val namespace = MemberNamespace.Constructor.ordinal args.forall(isTriviallySideEffectFree) && { - collOps - .forceGet(staticLikes, className)(namespace) + getInterface(className) + .staticLike(MemberNamespace.Constructor) .methods .get(methodName.name) .exists(isElidableModuleConstructor) @@ -687,12 +668,16 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: } } } + + def unregisterDependee(dependee: MethodImpl): Unit = { + hasElidableModuleAccessorAskers.remove(dependee) + } } /** Namespace for static members of a class. */ - private final class StaticLikeNamespace(className: ClassName, + private final class StaticLikeNamespace(linkedClass: LinkedClass, namespace: MemberNamespace) - extends MethodContainer(className, namespace) { + extends MethodContainer(linkedClass, namespace) { /** BOTH PASSES. */ final def lookupMethod(methodName: MethodName): Option[MethodImpl] = @@ -706,13 +691,14 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: } /** Type of a class or interface. - * Types are created on demand when a method is called on a given - * [[org.scalajs.ir.Types.ClassType ClassType]]. + * + * There exists exactly one instance of this per LinkedClass. * * Fully concurrency safe unless otherwise noted. */ - private final class InterfaceType( - val className: ClassName) extends Unregisterable { + private final class InterfaceType(linkedClass: LinkedClass) extends Unregisterable { + + val className: ClassName = linkedClass.className private type MethodCallers = collOps.Map[MethodName, collOps.Map[MethodImpl, Unit]] @@ -723,64 +709,90 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: private val staticCallers = mutable.ArrayBuffer.fill[MethodCallers](MemberNamespace.Count)(collOps.emptyMap) - private var _ancestors: List[ClassName] = className :: Nil + private var _ancestors: List[ClassName] = linkedClass.ancestors private val _instantiatedSubclasses = collOps.emptyMap[Class, Unit] + private val staticLikes: Array[StaticLikeNamespace] = { + Array.tabulate(MemberNamespace.Count) { ord => + new StaticLikeNamespace(linkedClass, MemberNamespace.fromOrdinal(ord)) + } + } + override def toString(): String = s"intf ${className.nameString}" - /** PROCESS PASS ONLY. Concurrency safe except with - * [[addInstantiatedSubclass]] and [[removeInstantiatedSubclass]] - */ - def instantiatedSubclasses: Iterable[Class] = _instantiatedSubclasses.keys + /** PROCESS PASS ONLY. */ + def askDynamicCallTargets(methodName: MethodName, + asker: MethodImpl): List[MethodImpl] = { + dynamicCallers + .getOrElseUpdate(methodName, collOps.emptyMap) + .put(asker, ()) + asker.registerTo(this) + _instantiatedSubclasses.keys.flatMap(_.lookupMethod(methodName)).toList + } - /** UPDATE PASS ONLY. Concurrency safe except with - * [[instantiatedSubclasses]] - */ + /** PROCESS PASS ONLY. */ + def askStaticCallTarget(namespace: MemberNamespace, methodName: MethodName, + asker: MethodImpl): MethodImpl = { + staticCallers(namespace.ordinal) + .getOrElseUpdate(methodName, collOps.emptyMap) + .put(asker, ()) + asker.registerTo(this) + + def inStaticsLike = staticLike(namespace) + + val container = + if (namespace != MemberNamespace.Public) inStaticsLike + else classes.getOrElse(className, inStaticsLike) + + // Method must exist, otherwise it's a bug / invalid IR. + container.lookupMethod(methodName).getOrElse { + throw new AssertionError(s"could not find method $className.$methodName") + } + } + + /** UPDATE PASS ONLY. */ def addInstantiatedSubclass(x: Class): Unit = _instantiatedSubclasses.put(x, ()) - /** UPDATE PASS ONLY. Concurrency safe except with - * [[instantiatedSubclasses]] - */ + /** UPDATE PASS ONLY. */ def removeInstantiatedSubclass(x: Class): Unit = _instantiatedSubclasses -= x - /** PROCESS PASS ONLY. Concurrency safe except with [[ancestors_=]] */ - def ancestors: List[ClassName] = + /** PROCESS PASS ONLY. */ + def askAncestors(asker: MethodImpl): List[ClassName] = { + ancestorsAskers.put(asker, ()) + asker.registerTo(this) _ancestors + } + + @inline + def staticLike(namespace: MemberNamespace): StaticLikeNamespace = + staticLikes(namespace.ordinal) /** UPDATE PASS ONLY. Not concurrency safe. */ - def ancestors_=(v: List[ClassName]): Unit = { - if (v != _ancestors) { - _ancestors = v + def updateWith(linkedClass: LinkedClass): Unit = { + // Update ancestors + if (linkedClass.ancestors != _ancestors) { + _ancestors = linkedClass.ancestors ancestorsAskers.keysIterator.foreach(_.tag()) ancestorsAskers.clear() } - } - - /** PROCESS PASS ONLY. Concurrency safe except with [[ancestors_=]]. */ - def registerAskAncestors(asker: MethodImpl): Unit = - ancestorsAskers.put(asker, ()) - /** Register a dynamic-caller of an instance method. - * PROCESS PASS ONLY. - */ - def registerDynamicCaller(methodName: MethodName, caller: MethodImpl): Unit = { - dynamicCallers - .getOrElseUpdate(methodName, collOps.emptyMap) - .put(caller, ()) + // Update static likes + for (staticLike <- staticLikes) { + val (_, changed, _) = staticLike.updateWith(linkedClass) + for (method <- changed) { + this.tagStaticCallersOf(staticLike.namespace, method) + } + } } - /** Register a static-caller of an instance method. - * PROCESS PASS ONLY. - */ - def registerStaticCaller(namespace: MemberNamespace, methodName: MethodName, - caller: MethodImpl): Unit = { - staticCallers(namespace.ordinal) - .getOrElseUpdate(methodName, collOps.emptyMap) - .put(caller, ()) + /** UPDATE PASS ONLY. */ + def delete(): Unit = { + // Mark all static like methods as deleted. + staticLikes.foreach(_.methods.values.foreach(_.delete())) } /** Tag the dynamic-callers of an instance method. @@ -845,8 +857,11 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: s"$owner.${methodName.nameString}" /** PROCESS PASS ONLY. */ - def registerBodyAsker(asker: MethodImpl): Unit = + def askBody(asker: MethodImpl): MethodDef = { bodyAskers.put(asker, ()) + asker.registerTo(this) + originalDef + } /** UPDATE PASS ONLY. */ def tagBodyAskers(): Unit = { @@ -858,35 +873,8 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: def unregisterDependee(dependee: MethodImpl): Unit = bodyAskers.remove(dependee) - /** PROCESS PASS ONLY. */ - private def registerAskAncestors(intf: InterfaceType): Unit = { - intf.registerAskAncestors(this) - registeredTo.put(intf, ()) - } - - /** Register that this method is a dynamic-caller of an instance method. - * PROCESS PASS ONLY. - */ - private def registerDynamicCall(intf: InterfaceType, - methodName: MethodName): Unit = { - intf.registerDynamicCaller(methodName, this) - registeredTo.put(intf, ()) - } - - /** Register that this method is a static-caller of an instance method. - * PROCESS PASS ONLY. - */ - private def registerStaticCall(intf: InterfaceType, - namespace: MemberNamespace, methodName: MethodName): Unit = { - intf.registerStaticCaller(namespace, methodName, this) - registeredTo.put(intf, ()) - } - - /** PROCESS PASS ONLY. */ - def registerAskBody(target: MethodImpl): Unit = { - target.registerBodyAsker(this) - registeredTo.put(target, ()) - } + def registerTo(unregisterable: Unregisterable): Unit = + registeredTo.put(unregisterable, ()) /** UPDATE PASS ONLY. */ private def unregisterFromEverywhere(): Unit = { @@ -976,43 +964,26 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: val myself: MethodImpl.this.type = MethodImpl.this - protected def getMethodBody(method: MethodID): MethodDef = { - MethodImpl.this.registerAskBody(method) - method.originalDef - } + protected def getMethodBody(method: MethodID): MethodDef = + method.askBody(myself) /** Look up the targets of a dynamic call to an instance method. */ protected def dynamicCall(intfName: ClassName, methodName: MethodName): List[MethodID] = { - val intf = getInterface(intfName) - MethodImpl.this.registerDynamicCall(intf, methodName) - intf.instantiatedSubclasses.flatMap(_.lookupMethod(methodName)).toList + getInterface(intfName).askDynamicCallTargets(methodName, myself) } /** Look up the target of a static call to an instance method. */ protected def staticCall(className: ClassName, namespace: MemberNamespace, - methodName: MethodName): Option[MethodID] = { - - def inStaticsLike = - collOps.forceGet(staticLikes, className)(namespace.ordinal) - - val container = - if (namespace != MemberNamespace.Public) inStaticsLike - else classes.getOrElse(className, inStaticsLike) - - MethodImpl.this.registerStaticCall(container.myInterface, namespace, - methodName) - container.lookupMethod(methodName) + methodName: MethodName): MethodID = { + getInterface(className).askStaticCallTarget(namespace, methodName, myself) } - protected def getAncestorsOf(intfName: ClassName): List[ClassName] = { - val intf = getInterface(intfName) - registerAskAncestors(intf) - intf.ancestors - } + protected def getAncestorsOf(intfName: ClassName): List[ClassName] = + getInterface(intfName).askAncestors(myself) protected def hasElidableModuleAccessor(moduleClassName: ClassName): Boolean = - classes(moduleClassName).hasElidableModuleAccessor + classes(moduleClassName).askHasElidableModuleAccessor(myself) protected def tryNewInlineableClass( className: ClassName): Option[OptimizerCore.InlineableClassStructure] = { 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 0f15a6331b..4cd8f79d9a 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 @@ -60,7 +60,7 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { /** Returns the target of a static call. */ protected def staticCall(className: ClassName, namespace: MemberNamespace, - methodName: MethodName): Option[MethodID] + methodName: MethodName): MethodID /** Returns the list of ancestors of a class or interface. */ protected def getAncestorsOf(className: ClassName): List[ClassName] @@ -656,8 +656,9 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { // Trees that need not be transformed case _:Skip | _:Debugger | _:LoadModule | _:SelectStatic | - _:SelectJSNativeMember | _:JSImportMeta | _:LoadJSConstructor | _:LoadJSModule | - _:JSLinkingInfo | _:JSGlobalRef | _:JSTypeOfGlobalRef | _:Literal => + _:SelectJSNativeMember | _:JSNewTarget | _:JSImportMeta | + _:LoadJSConstructor | _:LoadJSModule | _:JSLinkingInfo | + _:JSGlobalRef | _:JSTypeOfGlobalRef | _:Literal => tree case _ => @@ -901,8 +902,8 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { if (!arrow || restParam.isDefined) { /* TentativeClosureReplacement assumes there are no rest * parameters, because that would not be inlineable anyway. - * Likewise, it assumes that there is no binding for `this`, which - * is only true for arrow functions. + * Likewise, it assumes that there is no binding for `this` nor for + * `new.target`, which is only true for arrow functions. * So we never try to inline non-arrow Closures, nor Closures with * a rest parameter. There are few use cases for either anyway. */ @@ -1419,8 +1420,36 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { case (Skip(), Skip()) => keepOnlySideEffects(cond) case (newThenp, newElsep) => If(cond, newThenp, newElsep)(NoType)(stat.pos) } - case BinaryOp(_, lhs, rhs) => - Block(keepOnlySideEffects(lhs), keepOnlySideEffects(rhs))(stat.pos) + + case BinaryOp(op, lhs, rhs) => + // Here we need to preserve the side-effects of integer division/modulo + import BinaryOp._ + + implicit val pos = stat.pos + val newLhs = keepOnlySideEffects(lhs) + + def finishNoSideEffects: Tree = + Block(newLhs, keepOnlySideEffects(rhs)) + + op match { + case Int_/ | Int_% => + rhs match { + case IntLiteral(r) if r != 0 => + finishNoSideEffects + case _ => + Block(newLhs, BinaryOp(op, IntLiteral(0), rhs)) + } + case Long_/ | Long_% => + rhs match { + case LongLiteral(r) if r != 0L => + finishNoSideEffects + case _ => + Block(newLhs, BinaryOp(op, LongLiteral(0L), rhs)) + } + case _ => + finishNoSideEffects + } + case RecordValue(_, elems) => Block(elems.map(keepOnlySideEffects))(stat.pos) case RecordSelect(record, _) => @@ -1482,7 +1511,7 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { treceiver.tpe.isExact || treceiver.tpe.base.isInstanceOf[ArrayType] val impls = - if (useStaticResolution) staticCall(className, namespace, methodName).toList + if (useStaticResolution) List(staticCall(className, namespace, methodName)) else dynamicCall(className, methodName) val allocationSites = (treceiver :: targs).map(_.tpe.allocationSite) @@ -1609,35 +1638,29 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { // Never inline reflective proxies treeNotInlined } else { - val optTarget = staticCall(className, MemberNamespace.forNonStaticCall(flags), + val target = staticCall(className, MemberNamespace.forNonStaticCall(flags), methodName) - if (optTarget.isEmpty) { - // just in case - treeNotInlined - } else { - val target = optTarget.get - pretransformExprs(receiver, args) { (treceiver, targs) => - val intrinsicCode = intrinsics(flags, target) - if (intrinsicCode >= 0) { - callIntrinsic(intrinsicCode, flags, Some(treceiver), methodName, - targs, isStat, usePreTransform)(cont) - } else { - val shouldInline = target.inlineable && ( - target.shouldInline || - shouldInlineBecauseOfArgs(target, treceiver :: targs)) - val allocationSites = - (treceiver :: targs).map(_.tpe.allocationSite) - val beingInlined = - scope.implsBeingInlined((allocationSites, target)) + pretransformExprs(receiver, args) { (treceiver, targs) => + val intrinsicCode = intrinsics(flags, target) + if (intrinsicCode >= 0) { + callIntrinsic(intrinsicCode, flags, Some(treceiver), methodName, + targs, isStat, usePreTransform)(cont) + } else { + val shouldInline = target.inlineable && ( + target.shouldInline || + shouldInlineBecauseOfArgs(target, treceiver :: targs)) + val allocationSites = + (treceiver :: targs).map(_.tpe.allocationSite) + val beingInlined = + scope.implsBeingInlined((allocationSites, target)) - if (shouldInline && !beingInlined) { - val receiverType = ClassType(target.enclosingClassName) - inline(allocationSites, Some((receiverType, treceiver)), targs, - target, isStat, usePreTransform)(cont) - } else { - treeNotInlined0(finishTransformExpr(treceiver), - targs.map(finishTransformExpr)) - } + if (shouldInline && !beingInlined) { + val receiverType = ClassType(target.enclosingClassName) + inline(allocationSites, Some((receiverType, treceiver)), targs, + target, isStat, usePreTransform)(cont) + } else { + treeNotInlined0(finishTransformExpr(treceiver), + targs.map(finishTransformExpr)) } } } @@ -1658,31 +1681,25 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { def treeNotInlined = treeNotInlined0(args.map(transformExpr)) - val optTarget = staticCall(className, MemberNamespace.forStaticCall(flags), + val target = staticCall(className, MemberNamespace.forStaticCall(flags), methodName) - if (optTarget.isEmpty) { - // just in case - treeNotInlined - } else { - val target = optTarget.get - pretransformExprs(args) { targs => - val intrinsicCode = intrinsics(flags, target) - if (intrinsicCode >= 0) { - callIntrinsic(intrinsicCode, flags, None, methodName, targs, + pretransformExprs(args) { targs => + val intrinsicCode = intrinsics(flags, target) + if (intrinsicCode >= 0) { + callIntrinsic(intrinsicCode, flags, None, methodName, targs, + isStat, usePreTransform)(cont) + } else { + val shouldInline = target.inlineable && ( + target.shouldInline || shouldInlineBecauseOfArgs(target, targs)) + val allocationSites = targs.map(_.tpe.allocationSite) + val beingInlined = + scope.implsBeingInlined((allocationSites, target)) + + if (shouldInline && !beingInlined) { + inline(allocationSites, None, targs, target, isStat, usePreTransform)(cont) } else { - val shouldInline = target.inlineable && ( - target.shouldInline || shouldInlineBecauseOfArgs(target, targs)) - val allocationSites = targs.map(_.tpe.allocationSite) - val beingInlined = - scope.implsBeingInlined((allocationSites, target)) - - if (shouldInline && !beingInlined) { - inline(allocationSites, None, targs, target, - isStat, usePreTransform)(cont) - } else { - treeNotInlined0(targs.map(finishTransformExpr)) - } + treeNotInlined0(targs.map(finishTransformExpr)) } } } @@ -2328,8 +2345,7 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { cont: PreTransCont)( implicit scope: Scope): TailRec[Tree] = tailcall { - val target = staticCall(ctorClass, MemberNamespace.Constructor, - ctor.name).getOrElse(cancelFun()) + val target = staticCall(ctorClass, MemberNamespace.Constructor, ctor.name) val targetID = (allocationSite :: args.map(_.tpe.allocationSite), target) if (scope.implsBeingInlined.contains(targetID)) cancelFun() 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 a37511159b..52e8974e68 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala @@ -560,6 +560,33 @@ class AnalyzerTest { } } + @Test + def newTargetWithoutES2015(): AsyncResult = await { + val classDefs = Seq( + mainTestClassDef(LoadJSConstructor("A")), + classDef("A", + kind = ClassKind.JSClass, + superClass = Some(JSObjectLikeClass), + memberDefs = List( + JSMethodDef(EMF, str("constructor"), Nil, None, { + JSNewTarget() + })(EOH, None) + ) + ), + JSObjectLikeClassDef + ) + + val moduleInitializer = MainTestModuleInitializers + + val analysis = computeAnalysis(classDefs, + moduleInitializers = MainTestModuleInitializers, + config = StandardConfig().withESFeatures(_.withESVersion(ESVersion.ES5_1))) + + assertContainsError("NewTargetWithoutES2015Support", analysis) { + case NewTargetWithoutES2015Support(_) => true + } + } + @Test def importMetaWithoutESModule(): AsyncResult = await { val classDefs = Seq( diff --git a/linker/shared/src/test/scala/org/scalajs/linker/EmitterTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/EmitterTest.scala index ffec18b4d1..3be7c59a4a 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/EmitterTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/EmitterTest.scala @@ -43,6 +43,7 @@ class EmitterTest { val t = "\t" val gClef = "\uD834\uDD1E" val header = s""" + |#!/usr/bin/env node |// foo | $t | /* bar 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 50f05e3c37..09efc21eea 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/IncrementalTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/IncrementalTest.scala @@ -297,6 +297,43 @@ class IncrementalTest { _ => MainTestModuleInitializers) } + @Test + def testInvalidateElidableModuleAccessors_Issue4593(): AsyncResult = await { + val FooModule = ClassName("Foo") + + def fooCtor(pre: Boolean) = { + val superCtor = { + ApplyStatically(EAF.withConstructor(true), + This()(ClassType(FooModule)), + ObjectClass, MethodIdent(NoArgConstructorName), + Nil)(NoType) + } + + val body = + if (pre) superCtor + else Block(superCtor, consoleLog(str("bar"))) + + MethodDef(MemberFlags.empty.withNamespace(MemberNamespace.Constructor), + MethodIdent(NoArgConstructorName), NON, Nil, NoType, + Some(body))(EOH, None) + } + + def classDefs(pre: Boolean): Seq[ClassDef] = Seq( + mainTestClassDef(Block( + consoleLog(str("foo")), + LoadModule(FooModule) + )), + classDef( + FooModule, + kind = ClassKind.ModuleClass, + superClass = Some(ObjectClass), + memberDefs = List(fooCtor(pre)) + ) + ) + + testIncrementalBidirectional(classDefs(_), _ => MainTestModuleInitializers) + } + } object IncrementalTest { 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 a6763c8549..521056381e 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 = 187341, - expectedFullLinkSizeWithoutClosure = 174498, - expectedFullLinkSizeWithClosure = 31513, + expectedFastLinkSize = 187512, + expectedFullLinkSizeWithoutClosure = 174703, + expectedFullLinkSizeWithClosure = 31649, classDefs, moduleInitializers = MainTestModuleInitializers ) @@ -116,15 +116,15 @@ object LibrarySizeTest { val fastSize = fastOutput.content("main.js").get.length val fullSize = fullOutput.content("main.js").get.length - val expectedFullLinkSize = - if (fullLinkConfig.closureCompiler) expectedFullLinkSizeWithClosure - else expectedFullLinkSizeWithoutClosure + val (expectedFullLinkSize, fullLinkTolerance) = + if (fullLinkConfig.closureCompiler) (expectedFullLinkSizeWithClosure, 100) + else (expectedFullLinkSizeWithoutClosure, 500) def roughlyEquals(expected: Int, actual: Int, tolerance: Int): Boolean = actual >= expected - tolerance && actual <= expected + tolerance if (!roughlyEquals(expectedFastLinkSize, fastSize, 500) || - !roughlyEquals(expectedFullLinkSize, fullSize, 100)) { + !roughlyEquals(expectedFullLinkSize, fullSize, fullLinkTolerance)) { fail( s"\nFastLink expected $expectedFastLinkSize but got $fastSize" + s"\nFullLink expected $expectedFullLinkSize but got $fullSize") diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.12/neg/choices.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.12/neg/choices.check new file mode 100644 index 0000000000..ca484583a3 --- /dev/null +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.12/neg/choices.check @@ -0,0 +1,6 @@ +error: Usage: -Yresolve-term-conflict: + where choices are package, object, error (default: error) +error: bad option: '-Yresolve-term-conflict' +error: bad options: -P:scalajs:nowarnGlobalExecutionContext -Yresolve-term-conflict +error: flags file may only contain compiler options, found: -Yresolve-term-conflict +four errors found diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.12/neg/partestInvalidFlag.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.12/neg/partestInvalidFlag.check new file mode 100644 index 0000000000..b0a60aaae2 --- /dev/null +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.12/neg/partestInvalidFlag.check @@ -0,0 +1,4 @@ +error: bad option: '-badCompilerFlag' +error: bad options: -P:scalajs:nowarnGlobalExecutionContext -badCompilerFlag notAFlag -Yopt:badChoice +error: flags file may only contain compiler options, found: -badCompilerFlag notAFlag -Yopt:badChoice +three errors found diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.12/run/issue192.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.12/run/issue192.sem deleted file mode 100644 index 10abbf7f3b..0000000000 --- a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.12/run/issue192.sem +++ /dev/null @@ -1 +0,0 @@ -strictFloats diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.15/neg/choices.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.15/neg/choices.check new file mode 100644 index 0000000000..061cff358e --- /dev/null +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.15/neg/choices.check @@ -0,0 +1,5 @@ +error: Usage: -Yresolve-term-conflict: where choices are package, object, error (default: error). +error: bad option: '-Yresolve-term-conflict' +error: bad options: -P:scalajs:nowarnGlobalExecutionContext -Yresolve-term-conflict +error: flags file may only contain compiler options, found: -Yresolve-term-conflict +four errors found diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.15/neg/partestInvalidFlag.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.15/neg/partestInvalidFlag.check new file mode 100644 index 0000000000..4f7f50be4e --- /dev/null +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.15/neg/partestInvalidFlag.check @@ -0,0 +1,4 @@ +error: bad option: '-badCompilerFlag' +error: bad options: -P:scalajs:nowarnGlobalExecutionContext -badCompilerFlag notAFlag -opt:badChoice +error: flags file may only contain compiler options, found: -badCompilerFlag notAFlag -opt:badChoice +three errors found diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.15/run/issue192.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.15/run/issue192.sem deleted file mode 100644 index 10abbf7f3b..0000000000 --- a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.15/run/issue192.sem +++ /dev/null @@ -1 +0,0 @@ -strictFloats diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/issue192.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/issue192.sem deleted file mode 100644 index 10abbf7f3b..0000000000 --- a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/issue192.sem +++ /dev/null @@ -1 +0,0 @@ -strictFloats diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/BlacklistedTests.txt b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/BlacklistedTests.txt similarity index 97% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/BlacklistedTests.txt rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/BlacklistedTests.txt index abbfd18add..c43da05e6b 100644 --- a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/BlacklistedTests.txt +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/BlacklistedTests.txt @@ -149,7 +149,6 @@ run/future-flatmap-exec-count.scala # Using detailed stack trace / getStackTrace() -run/productElementName-oob.scala run/t6308.scala run/t10277.scala run/t10277b.scala @@ -825,6 +824,8 @@ run/t11991.scala run/t12276.scala run/interpolation-repl.scala run/t12292.scala +run/StringConcat.scala +run/t10016.scala # Using Scala Script (partest.ScriptTest) @@ -947,8 +948,16 @@ run/t11815.scala run/splain.scala run/splain-tree.scala run/splain-truncrefined.scala +run/t12405.scala +run/t1406.scala +run/t1406b.scala # Using partest.StoreReporterDirectTest +run/package-object-stale-decl.scala +run/package-object-toolbox.scala +run/package-object-with-inner-class-in-ancestor.scala +run/package-object-with-inner-class-in-ancestor-simpler.scala +run/package-object-with-inner-class-in-ancestor-simpler-still.scala run/t10171 # partest.StubErrorMessageTest @@ -963,7 +972,9 @@ run/StubErrorTypeclass.scala run/StubErrorTypeDef.scala # partest.CompilerTest +run/infix-rangepos.scala run/t12062.scala +run/t12490.scala # partest.ASMConverters run/t9403 @@ -1005,6 +1016,9 @@ run/t0528.scala run/t1192.scala run/t3158.scala +# scala.tools.testkit.AssertUtil +run/productElementName.scala + # Using .java source files run/t4317 @@ -1104,6 +1118,7 @@ run/t1195-old.scala run/t3758-old.scala run/t4110-old.scala run/t6246.scala +run/t12481.scala # Using ScalaRunTime.stringOf run/value-class-extractor-seq.scala @@ -1112,6 +1127,9 @@ run/t3493.scala # Custom invoke dynamic node run/indy-via-macro run/indy-via-macro-with-dynamic-args +run/indy-via-macro-class-constant-bsa +run/indy-via-macro-method-type-bsa +run/indy-via-macro-reflector # Crashes our optimizer because of artificially insane amount of inlining run/t10594.scala diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/choices.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/choices.check new file mode 100644 index 0000000000..1526a9b6de --- /dev/null +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/choices.check @@ -0,0 +1,5 @@ +error: Usage: -Yresolve-term-conflict: where choices are package, object, error (default: error). +error: bad option: '-Yresolve-term-conflict' +error: bad options: -P:scalajs:nowarnGlobalExecutionContext -Yresolve-term-conflict +error: flags file may only contain compiler options, found: -Yresolve-term-conflict +4 errors diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/partestInvalidFlag.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/partestInvalidFlag.check new file mode 100644 index 0000000000..715d080af8 --- /dev/null +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/partestInvalidFlag.check @@ -0,0 +1,4 @@ +error: bad option: '-badCompilerFlag' +error: bad options: -P:scalajs:nowarnGlobalExecutionContext -badCompilerFlag notAFlag -opt:badChoice +error: flags file may only contain compiler options, found: -badCompilerFlag notAFlag -opt:badChoice +3 errors diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t11952b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/t11952b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t11952b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/t11952b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t6446-additional.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/t6446-additional.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t6446-additional.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/t6446-additional.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t6446-list.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/t6446-list.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t6446-list.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/t6446-list.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t6446-missing.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/t6446-missing.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t6446-missing.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/t6446-missing.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t6446-show-phases.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/t6446-show-phases.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t6446-show-phases.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/t6446-show-phases.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t7494-no-options.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/t7494-no-options.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t7494-no-options.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/neg/t7494-no-options.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-01.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/Course-2002-01.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-01.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/Course-2002-01.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-02.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/Course-2002-02.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-02.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/Course-2002-02.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-04.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/Course-2002-04.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-04.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/Course-2002-04.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-08.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/Course-2002-08.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-08.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/Course-2002-08.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-09.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/Course-2002-09.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-09.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/Course-2002-09.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-10.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/Course-2002-10.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-10.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/Course-2002-10.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Meter.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/Meter.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Meter.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/Meter.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/MeterCaseClass.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/MeterCaseClass.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/MeterCaseClass.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/MeterCaseClass.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/anyval-box-types.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/anyval-box-types.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/anyval-box-types.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/anyval-box-types.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/bugs.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/bugs.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/bugs.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/bugs.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/caseClassHash.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/caseClassHash.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/caseClassHash.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/caseClassHash.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/classof.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/classof.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/classof.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/classof.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/deeps.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/deeps.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/deeps.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/deeps.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/dynamic-anyval.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/dynamic-anyval.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/dynamic-anyval.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/dynamic-anyval.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/impconvtimes.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/impconvtimes.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/impconvtimes.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/impconvtimes.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/imports.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/imports.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/imports.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/imports.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/interpolation.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/interpolation.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/interpolation.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/interpolation.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/interpolationMultiline1.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/interpolationMultiline1.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/interpolationMultiline1.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/interpolationMultiline1.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/macro-bundle-static.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/macro-bundle-static.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/macro-bundle-static.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/macro-bundle-static.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/macro-bundle-toplevel.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/macro-bundle-toplevel.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/macro-bundle-toplevel.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/macro-bundle-toplevel.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/macro-bundle-whitebox-decl.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/macro-bundle-whitebox-decl.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/macro-bundle-whitebox-decl.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/macro-bundle-whitebox-decl.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/macro-expand-varargs-implicit-over-varargs.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/macro-expand-varargs-implicit-over-varargs.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/macro-expand-varargs-implicit-over-varargs.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/macro-expand-varargs-implicit-over-varargs.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/misc.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/misc.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/misc.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/misc.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/promotion.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/promotion.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/promotion.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/promotion.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/runtime.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/runtime.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/runtime.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/runtime.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/sammy_vararg_cbn.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/sammy_vararg_cbn.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/sammy_vararg_cbn.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/sammy_vararg_cbn.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/spec-self.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/spec-self.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/spec-self.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/spec-self.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/string-switch.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/string-switch.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/string-switch.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/string-switch.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/structural.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/structural.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/structural.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/structural.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t0421-new.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t0421-new.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t0421-new.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t0421-new.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t0421-old.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t0421-old.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t0421-old.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t0421-old.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t12221.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t12221.check new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t12221.check @@ -0,0 +1 @@ +1 diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t1503.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t1503.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t1503.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t1503.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t3702.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t3702.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t3702.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t3702.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t4148.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t4148.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t4148.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t4148.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t4617.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t4617.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t4617.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t4617.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5356.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t5356.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5356.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t5356.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5552.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t5552.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5552.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t5552.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5568.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t5568.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5568.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t5568.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5629b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t5629b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5629b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t5629b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5680.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t5680.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5680.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t5680.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5866.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t5866.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5866.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t5866.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5966.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t5966.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5966.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t5966.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t6265.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t6265.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t6265.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t6265.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t6318_primitives.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t6318_primitives.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t6318_primitives.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t6318_primitives.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t6662.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t6662.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t6662.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t6662.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t7657.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t7657.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t7657.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t7657.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t7763.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t7763.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t7763.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t7763.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t8570a.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t8570a.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t8570a.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t8570a.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t8764.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t8764.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t8764.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t8764.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t9387b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t9387b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t9387b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/t9387b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/try-catch-unify.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/try-catch-unify.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/try-catch-unify.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/try-catch-unify.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/virtpatmat_switch.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/virtpatmat_switch.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/virtpatmat_switch.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/virtpatmat_switch.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/virtpatmat_typetag.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/virtpatmat_typetag.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/virtpatmat_typetag.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.8/run/virtpatmat_typetag.check diff --git a/partest/src/main/scala-new-partest/scala/tools/partest/scalajs/ScalaJSRunner.scala b/partest/src/main/scala-new-partest/scala/tools/partest/scalajs/ScalaJSRunner.scala index 717775ffe6..344c24dcc8 100644 --- a/partest/src/main/scala-new-partest/scala/tools/partest/scalajs/ScalaJSRunner.scala +++ b/partest/src/main/scala-new-partest/scala/tools/partest/scalajs/ScalaJSRunner.scala @@ -12,6 +12,8 @@ package scala.tools.partest.scalajs +import java.io.File + import scala.tools.partest.nest import scala.tools.partest.nest.{AbstractRunner, DirectCompiler, TestInfo} @@ -23,6 +25,12 @@ class ScalaJSRunner(testInfo: ScalaJSTestInfo, suiteRunner: AbstractRunner, new DirectCompiler(this) with ScalaJSDirectCompiler } + override def flagsForCompilation(sources: List[File]): List[String] = { + // Never warn, so we do not need to update tons of checkfiles. + "-P:scalajs:nowarnGlobalExecutionContext" :: + super.flagsForCompilation(sources) + } + override def extraJavaOptions = { super.extraJavaOptions ++ Seq( s"-Dscalajs.partest.optMode=${options.optMode.id}", diff --git a/partest/src/main/scala-old-partest/scala/tools/partest/scalajs/ScalaJSRunner.scala b/partest/src/main/scala-old-partest/scala/tools/partest/scalajs/ScalaJSRunner.scala index 9946e7e787..865fdaaef9 100644 --- a/partest/src/main/scala-old-partest/scala/tools/partest/scalajs/ScalaJSRunner.scala +++ b/partest/src/main/scala-old-partest/scala/tools/partest/scalajs/ScalaJSRunner.scala @@ -47,6 +47,13 @@ class ScalaJSRunner(testFile: File, suiteRunner: SuiteRunner, } override def newCompiler = new DirectCompiler(this) with ScalaJSDirectCompiler + + override def flagsForCompilation(sources: List[File]): List[String] = { + // Never warn, so we do not need to update tons of checkfiles. + "-P:scalajs:nowarnGlobalExecutionContext" :: + super.flagsForCompilation(sources) + } + override def extraJavaOptions = { super.extraJavaOptions ++ Seq( s"-Dscalajs.partest.optMode=${options.optMode.id}", diff --git a/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala b/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala index 11938f8d3a..7aea515d4d 100644 --- a/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala +++ b/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala @@ -63,7 +63,6 @@ class MainGenericRunner { case "asInstanceOfs" => prev.withAsInstanceOfs(Compliant) case "arrayIndexOutOfBounds" => prev.withArrayIndexOutOfBounds(Compliant) case "moduleInit" => prev.withModuleInit(Compliant) - case "strictFloats" => prev.withStrictFloats(true) } } } diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 50ed423e45..4713fe6bf8 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -16,10 +16,7 @@ object BinaryIncompatibilities { val SbtPlugin = Seq( ) - val TestCommon = Seq( - ) - - val TestAdapter = TestCommon ++ Seq( + val TestAdapter = Seq( ) val Library = Seq( @@ -27,4 +24,7 @@ object BinaryIncompatibilities { val TestInterface = Seq( ) + + val JUnitRuntime = Seq( + ) } diff --git a/project/Build.scala b/project/Build.scala index 227b9c1228..b18602a021 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -44,7 +44,6 @@ object ExposedValues extends AutoPlugin { .withAsInstanceOfs(CheckedBehavior.Compliant) .withArrayIndexOutOfBounds(CheckedBehavior.Compliant) .withModuleInit(CheckedBehavior.Compliant) - .withStrictFloats(true) } } @@ -242,7 +241,7 @@ object Build { val packageMinilib = taskKey[File]("Produces the minilib jar.") 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.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") val previousVersion = previousVersions.last val previousBinaryCrossVersion = CrossVersion.binaryWith("sjs1_", "") @@ -846,6 +845,8 @@ object Build { ).settings( commonLinkerInterfaceSettings, + Test / scalacOptions ++= scalaJSCompilerOption("nowarnGlobalExecutionContext"), + /* Add the sources of scalajs-logging to managed sources. This is outside * of `target/` so that `clean` does not remove them, making IDE happier. */ @@ -976,8 +977,10 @@ object Build { commonLinkerSettings _ ).settings( libraryDependencies ++= Seq( - "com.google.javascript" % "closure-compiler" % "v20210601", - "com.google.jimfs" % "jimfs" % "1.1" % "test" + "com.google.javascript" % "closure-compiler" % "v20220202", + "com.google.jimfs" % "jimfs" % "1.1" % "test", + "org.scala-js" %% "scalajs-env-nodejs" % "1.3.0" % "test", + "org.scala-js" %% "scalajs-js-envs-test-kit" % "1.3.0" % "test" ) ++ ( parallelCollectionsDependencies(scalaVersion.value) ), @@ -1007,6 +1010,8 @@ object Build { ).zippedSettings("library")( commonLinkerSettings _ ).settings( + Test / scalacOptions ++= scalaJSCompilerOption("nowarnGlobalExecutionContext"), + if (isGeneratingForIDE) { unmanagedSourceDirectories in Compile += baseDirectory.value.getParentFile.getParentFile / "js/src/main/scala-ide-stubs" @@ -1069,7 +1074,7 @@ object Build { name := "Scala.js sbt test adapter", libraryDependencies ++= Seq( "org.scala-sbt" % "test-interface" % "1.0", - "org.scala-js" %% "scalajs-js-envs" % "1.1.1", + "org.scala-js" %% "scalajs-js-envs" % "1.3.0", "com.google.jimfs" % "jimfs" % "1.1" % "test", ), libraryDependencies ++= JUnitDeps, @@ -1098,8 +1103,8 @@ object Build { mimaBinaryIssueFilters ++= BinaryIncompatibilities.SbtPlugin, addSbtPlugin("org.portable-scala" % "sbt-platform-deps" % "1.0.1"), - libraryDependencies += "org.scala-js" %% "scalajs-js-envs" % "1.2.0", - libraryDependencies += "org.scala-js" %% "scalajs-env-nodejs" % "1.2.0", + libraryDependencies += "org.scala-js" %% "scalajs-js-envs" % "1.3.0", + libraryDependencies += "org.scala-js" %% "scalajs-env-nodejs" % "1.3.0", scriptedLaunchOpts += "-Dplugin.version=" + version.value, @@ -1576,6 +1581,7 @@ object Build { fatalWarningsSettings, name := "Scala.js test bridge", delambdafySetting, + Test / scalacOptions ++= scalaJSCompilerOption("nowarnGlobalExecutionContext"), /* By design, the test-bridge has a completely private API (it is * only loaded through a privately-known top-level export), so it * does not have `previousArtifactSetting` nor @@ -1599,6 +1605,8 @@ object Build { crossVersion := CrossVersion.binary, // no _sjs suffix fatalWarningsSettings, name := "Scala.js JUnit test runtime", + previousArtifactSetting, + mimaBinaryIssueFilters ++= BinaryIncompatibilities.JUnitRuntime, headerSources in Compile ~= { srcs => srcs.filter { src => @@ -1663,6 +1671,7 @@ object Build { MyScalaJSPlugin ).withScalaJSCompiler.settings( commonSettings, + fatalWarningsSettings, name := "Scala.js internal JUnit async JS support", publishArtifact in Compile := false ).dependsOn(library) @@ -1671,6 +1680,7 @@ object Build { id = "jUnitAsyncJVM", base = file("junit-async/jvm") ).settings( commonSettings, + fatalWarningsSettings, name := "Scala.js internal JUnit async JVM support", publishArtifact in Compile := false ) @@ -1716,7 +1726,7 @@ object Build { scalaVersion.value match { case Default2_11ScalaVersion => Some(ExpectedSizes( - fastLink = 520000 to 521000, + fastLink = 521000 to 522000, fullLink = 108000 to 109000, fastLinkGz = 66000 to 67000, fullLinkGz = 28000 to 29000, @@ -1732,10 +1742,10 @@ object Build { case Default2_13ScalaVersion => Some(ExpectedSizes( - fastLink = 778000 to 779000, - fullLink = 169000 to 170000, - fastLinkGz = 98000 to 99000, - fullLinkGz = 43000 to 44000, + fastLink = 734000 to 735000, + fullLink = 158000 to 159000, + fastLinkGz = 92000 to 93000, + fullLinkGz = 40000 to 41000, )) case _ => @@ -1946,10 +1956,13 @@ object Build { case FullOptStage => (scalaJSLinkerConfig in (Compile, fullLinkJS)).value } + val esVersion = linkerConfig.esFeatures.esVersion val moduleKind = linkerConfig.moduleKind val hasModules = moduleKind != ModuleKind.NoModule collectionsEraDependentDirectory(scalaV, testDir) :: + includeIf(testDir / "require-new-target", + esVersion >= ESVersion.ES2015) ::: includeIf(testDir / "require-modules", hasModules) ::: includeIf(testDir / "require-multi-modules", @@ -1971,6 +1984,7 @@ object Build { }, Test / scalacOptions ++= scalaJSCompilerOption("genStaticForwardersForNonTopLevelObjects"), + Test / scalacOptions ++= scalaJSCompilerOption("nowarnGlobalExecutionContext"), scalaJSLinkerConfig ~= { _.withSemantics(TestSuiteLinkerOptions.semantics _) }, scalaJSModuleInitializers in Test ++= TestSuiteLinkerOptions.moduleInitializers, @@ -2323,7 +2337,7 @@ object Build { resolvers += Resolver.typesafeIvyRepo("releases"), - libraryDependencies += "org.scala-js" %% "scalajs-env-nodejs" % "1.1.1", + libraryDependencies += "org.scala-js" %% "scalajs-env-nodejs" % "1.3.0", artifactPath in fetchScalaSource := baseDirectory.value.getParentFile / "fetchedSources" / scalaVersion.value, diff --git a/project/MultiScalaProject.scala b/project/MultiScalaProject.scala index ba3168b927..97b746ecaa 100644 --- a/project/MultiScalaProject.scala +++ b/project/MultiScalaProject.scala @@ -80,7 +80,7 @@ object MultiScalaProject { private final val versions = Map[String, Seq[String]]( "2.11" -> Seq("2.11.12"), "2.12" -> Seq("2.12.1", "2.12.2", "2.12.3", "2.12.4", "2.12.5", "2.12.6", "2.12.7", "2.12.8", "2.12.9", "2.12.10", "2.12.11", "2.12.12", "2.12.13", "2.12.14", "2.12.15"), - "2.13" -> Seq("2.13.0", "2.13.1", "2.13.2", "2.13.3", "2.13.4", "2.13.5", "2.13.6"), + "2.13" -> Seq("2.13.0", "2.13.1", "2.13.2", "2.13.3", "2.13.4", "2.13.5", "2.13.6", "2.13.7", "2.13.8"), ) val Default2_11ScalaVersion = versions("2.11").last diff --git a/project/build.properties b/project/build.properties index 9edb75b77c..e64c208ff5 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.4 +sbt.version=1.5.8 diff --git a/project/build.sbt b/project/build.sbt index b82d89e110..b6e9ec1096 100644 --- a/project/build.sbt +++ b/project/build.sbt @@ -10,8 +10,8 @@ libraryDependencies += "com.google.jimfs" % "jimfs" % "1.1" libraryDependencies += "org.eclipse.jgit" % "org.eclipse.jgit.pgm" % "3.2.0.201312181205-r" -libraryDependencies += "org.scala-js" %% "scalajs-js-envs" % "1.2.0" -libraryDependencies += "org.scala-js" %% "scalajs-env-nodejs" % "1.2.0" +libraryDependencies += "org.scala-js" %% "scalajs-js-envs" % "1.3.0" +libraryDependencies += "org.scala-js" %% "scalajs-env-nodejs" % "1.3.0" Compile / unmanagedSourceDirectories ++= { val root = baseDirectory.value.getParentFile diff --git a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/PipeOutputThread.scala b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/PipeOutputThread.scala new file mode 100644 index 0000000000..198cc38df8 --- /dev/null +++ b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/PipeOutputThread.scala @@ -0,0 +1,45 @@ +/* + * 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.sbtplugin + +import scala.annotation.tailrec + +import java.io._ + +// !!! Duplicate code with adapter/PipeOutputThread.scala. +private[sbtplugin] object PipeOutputThread { + def start(from: InputStream, to: OutputStream): Thread = { + val thread = new PipeOutputThread(from, to) + thread.start() + thread + } +} + +private final class PipeOutputThread(from: InputStream, to: OutputStream) extends Thread { + override def run(): Unit = { + try { + val buffer = new Array[Byte](8192) + @tailrec def loop(): Unit = { + val byteCount = from.read(buffer) + if (byteCount > 0) { + to.write(buffer, 0, byteCount) + to.flush() // if the sender flushed (which we can't tell), we need to propagate the flush + loop() + } + } + loop() + } finally { + from.close() + } + } +} diff --git a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala index edb0392050..35678a14e8 100644 --- a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala +++ b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala @@ -21,6 +21,7 @@ import scala.concurrent.duration._ import scala.util.{Failure, Success} import scala.util.control.NonFatal +import java.io.{InputStream, OutputStream} import java.util.concurrent.atomic.AtomicReference import sbt._ @@ -365,6 +366,26 @@ private[sbtplugin] object ScalaJSPluginInternal { val classpath = Attributed.data(fullClasspath.value) val log = streams.value.log val tlog = sbtLogger2ToolsLogger(log) + val config = configuration.value.name + + /* #4610 Warn if `-Xplugin:scalajs-compiler.jar` (Scala 2) or + * `-scalajs` (Scala 3) is missing from the `scalacOptions`. + * This feature is not automatically tested. + */ + def warnMissingScalacOption(thingMissing: String): Unit = { + log.warn( + s"$thingMissing was missing from `$config / scalacOptions`, but it is required to produce Scala.js IR.") + log.warn("Linking, running and/or testing will probably go wrong.") + log.warn("The most likely cause is that you used `scalacOptions := ...` instead of using `++=`.") + } + val scalacOpts = scalacOptions.value + if (scalaVersion.value.startsWith("2.")) { + if (!scalacOpts.exists(opt => opt.startsWith("-Xplugin:") && opt.contains("scalajs-compiler"))) + warnMissingScalacOption("The `scalajs-compiler.jar` compiler plugin") + } else { + if (!scalacOpts.contains("-scalajs")) + warnMissingScalacOption("The `-scalajs` flag") + } val (irFiles, paths) = enhanceIRVersionNotSupportedException { tlog.time("Update IR cache") { @@ -550,12 +571,55 @@ private[sbtplugin] object ScalaJSPluginInternal { log.debug(s"with JSEnv ${env.name}") val input = jsEnvInput.value - val config = RunConfig().withLogger(sbtLogger2ToolsLogger(log)) - val run = env.start(input, config) + /* The list of threads that are piping output to System.out and + * System.err. This is not an AtomicReference or any other thread-safe + * structure because: + * - `onOutputStream` is guaranteed to be called exactly once, and + * - `pipeOutputThreads` is only read once the run is completed + * (although the JSEnv interface does not explicitly specify that the + * call to `onOutputStream must happen before that, anything else is + * just plain unreasonable). + * We only mark it as `@volatile` to ensure that there is an + * appropriate memory barrier between writing to it and reading it back. + */ + @volatile var pipeOutputThreads: List[Thread] = Nil + + /* #4560 Explicitly redirect out/err to System.out/System.err, instead + * of relying on `inheritOut` and `inheritErr`, so that streams + * installed with `System.setOut` and `System.setErr` are always taken + * into account. sbt installs such alternative outputs when it runs in + * server mode. + */ + val config = RunConfig() + .withLogger(sbtLogger2ToolsLogger(log)) + .withInheritOut(false) + .withInheritErr(false) + .withOnOutputStream { (out, err) => + pipeOutputThreads = ( + out.map(PipeOutputThread.start(_, System.out)).toList ::: + err.map(PipeOutputThread.start(_, System.err)).toList + ) + } - enhanceNotInstalledException(resolvedScoped.value, log) { - Await.result(run.future, Duration.Inf) + try { + val run = env.start(input, config) + + enhanceNotInstalledException(resolvedScoped.value, log) { + Await.result(run.future, Duration.Inf) + } + } finally { + /* Wait for the pipe output threads to be done, to make sure that we + * do not finish the `run` task before *all* output has been + * transferred to System.out and System.err. + * We do that in a `finally` block so that the stdout and stderr + * streams are propagated even if the run finishes with a failure. + * `join()` itself does not throw except if the current thread is + * interrupted, which is not supposed to happen (if it does happen, + * the interrupted exception will shadow any error from the run). + */ + for (pipeOutputThread <- pipeOutputThreads) + pipeOutputThread.join() } }, 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 41e3af019d..adef806e9a 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.6" +scalaVersion := "2.13.8" scalaJSUseMainModuleInitializer := true diff --git a/sbt-plugin/src/sbt-test/linker/custom-linker/main/Main.scala b/sbt-plugin/src/sbt-test/linker/custom-linker/main/Main.scala index 04c328b439..dc71eefb6e 100644 --- a/sbt-plugin/src/sbt-test/linker/custom-linker/main/Main.scala +++ b/sbt-plugin/src/sbt-test/linker/custom-linker/main/Main.scala @@ -4,7 +4,7 @@ import scala.scalajs.js import scala.scalajs.js.annotation._ @js.native -@JSImport("foo.js", "JustImport") +@JSImport("foo.js") object JustImport extends js.Object @js.native @@ -16,7 +16,7 @@ object AnotherInFoo extends js.Object object ImportWithGlobalFallback extends js.Object @js.native -@JSImport("unused.js", "Unreachable") +@JSImport("unused.js") object Unreachable extends js.Object @js.native diff --git a/sbt-plugin/src/sbt-test/scala3/basic/project/build.properties b/sbt-plugin/src/sbt-test/scala3/basic/project/build.properties index 9edb75b77c..e64c208ff5 100644 --- a/sbt-plugin/src/sbt-test/scala3/basic/project/build.properties +++ b/sbt-plugin/src/sbt-test/scala3/basic/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.4 +sbt.version=1.5.8 diff --git a/sbt-plugin/src/sbt-test/scala3/junit/project/build.properties b/sbt-plugin/src/sbt-test/scala3/junit/project/build.properties index 9edb75b77c..e64c208ff5 100644 --- a/sbt-plugin/src/sbt-test/scala3/junit/project/build.properties +++ b/sbt-plugin/src/sbt-test/scala3/junit/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.4 +sbt.version=1.5.8 diff --git a/sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/BaseRunner.scala b/sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/BaseRunner.scala index 805f390d51..023670feef 100644 --- a/sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/BaseRunner.scala +++ b/sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/BaseRunner.scala @@ -17,8 +17,8 @@ abstract class BaseRunner( private[framework] def taskDone(): Unit /** Tasks need to wait for this future to complete if they get called with a - * continuation. This is used to ensure that the master/slave message channel - * eventually delivers messages. + * continuation. This is used to ensure that the controller/worker message + * channel eventually delivers messages. */ private[framework] val taskBlock: Future[Unit] diff --git a/sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/MasterRunner.scala b/sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/ControllerRunner.scala similarity index 73% rename from sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/MasterRunner.scala rename to sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/ControllerRunner.scala index e815ee013f..b8a90d4fe1 100644 --- a/sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/MasterRunner.scala +++ b/sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/ControllerRunner.scala @@ -6,7 +6,7 @@ import scala.concurrent._ import java.util.concurrent.atomic.AtomicInteger -final class MasterRunner( +final class ControllerRunner( args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader @@ -18,10 +18,10 @@ final class MasterRunner( /** Number of tasks completed in the whole system */ private[this] val doneCount = new AtomicInteger(0) - /** Number of running slaves in the whole system */ - private[this] val slaveCount = new AtomicInteger(0) + /** Number of running workers in the whole system */ + private[this] val workerCount = new AtomicInteger(0) - /** If a task gets called in the master, there is no point waiting for + /** If a task gets called in the controller, there is no point waiting for * messages. */ private[framework] override val taskBlock = Future.successful(()) @@ -32,13 +32,13 @@ final class MasterRunner( } def done(): String = { - val slaves = slaveCount.get + val workers = workerCount.get val registered = registeredCount.get val done = doneCount.get - if (slaves > 0) { + if (workers > 0) { throw new IllegalStateException( - s"There are still $slaves slaves running") + s"There are still $workers workers running") } if (registered != done) @@ -52,18 +52,18 @@ final class MasterRunner( def receiveMessage(msg: String): Option[String] = msg(0) match { case 's' => - slaveCount.incrementAndGet() + workerCount.incrementAndGet() // Send Hello message back Some("Hello") case 't' => - // Slave notifies us of registration of tasks + // A worker notifies us of the registration of tasks registeredCount.addAndGet(msg.tail.toInt) None case 'd' => - // Slave notifies us of completion of tasks + // A worker notifies us of the completion of tasks val count = msg.tail.toInt doneCount.addAndGet(count) - slaveCount.decrementAndGet() + workerCount.decrementAndGet() None } diff --git a/sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/DummyFramework.scala b/sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/DummyFramework.scala index c9d0f9abb9..7d9a2bd765 100644 --- a/sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/DummyFramework.scala +++ b/sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/DummyFramework.scala @@ -15,11 +15,12 @@ final class DummyFramework extends Framework { def fingerprints: Array[Fingerprint] = Array(DummyFingerprint) def runner(args: Array[String], remoteArgs: Array[String], - testClassLoader: ClassLoader): MasterRunner = - new MasterRunner(args, remoteArgs, testClassLoader) + testClassLoader: ClassLoader): ControllerRunner = + new ControllerRunner(args, remoteArgs, testClassLoader) + // Aka `workerRunner`; see the Scaladoc of `sbt.testing.Framework` about the name. def slaveRunner(args: Array[String], remoteArgs: Array[String], - testClassLoader: ClassLoader, send: String => Unit): SlaveRunner = - new SlaveRunner(args, remoteArgs, testClassLoader, send) + testClassLoader: ClassLoader, send: String => Unit): WorkerRunner = + new WorkerRunner(args, remoteArgs, testClassLoader, send) } diff --git a/sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/SlaveRunner.scala b/sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/WorkerRunner.scala similarity index 82% rename from sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/SlaveRunner.scala rename to sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/WorkerRunner.scala index 9035573d42..7fcafc6ddb 100644 --- a/sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/SlaveRunner.scala +++ b/sbt-plugin/src/sbt-test/testing/multi-framework/testFramework/shared/src/main/scala/sbttest/framework/WorkerRunner.scala @@ -4,7 +4,7 @@ import sbt.testing._ import scala.concurrent._ -final class SlaveRunner( +final class WorkerRunner( args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader, @@ -14,16 +14,16 @@ final class SlaveRunner( /** Number of tasks completed on this node */ private[this] var doneCount = 0 - /** Whether we have seen a Hello message from the master yet */ + /** Whether we have seen a Hello message from the controller yet */ private[this] val seenHello = Promise[Unit]() private[framework] override val taskBlock = seenHello.future - // Notify master of our existence + // Notify the controller of our existence send("s") def tasks(taskDefs: Array[TaskDef]): Array[Task] = { - // Notify master of new tasks + // Notify the controller of new tasks send("t" + taskDefs.length) taskDefs.map(newTask) } diff --git a/scala-test-suite/src/test/resources/2.13.7/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.7/BlacklistedTests.txt new file mode 100644 index 0000000000..b05984ddab --- /dev/null +++ b/scala-test-suite/src/test/resources/2.13.7/BlacklistedTests.txt @@ -0,0 +1,223 @@ +## 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/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/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/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/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/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/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/TypedTreeTest.scala +scala/tools/nsc/util/StackTraceTest.scala +scala/tools/testkit/ReflectUtilTest.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/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 + +# 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 diff --git a/scala-test-suite/src/test/resources/2.13.8/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.8/BlacklistedTests.txt new file mode 100644 index 0000000000..b05984ddab --- /dev/null +++ b/scala-test-suite/src/test/resources/2.13.8/BlacklistedTests.txt @@ -0,0 +1,223 @@ +## 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/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/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/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/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/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/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/TypedTreeTest.scala +scala/tools/nsc/util/StackTraceTest.scala +scala/tools/testkit/ReflectUtilTest.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/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 + +# 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 diff --git a/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala b/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala index 912cfa84fa..8f5e94b5e2 100644 --- a/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala +++ b/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala @@ -103,6 +103,10 @@ object ArrayBuilder { protected[this] def elems: Array[T] = throw new Error("unreachable") private var jsElems: js.Array[Any] = js.Array() + override def length: Int = jsElems.length + + override def knownSize: Int = jsElems.length + def addOne(elem: T): this.type = { val unboxedElem = if (isCharArrayBuilder) elem.asInstanceOf[Char].toInt diff --git a/test-adapter/src/main/scala/org/scalajs/testing/adapter/JSEnvRPC.scala b/test-adapter/src/main/scala/org/scalajs/testing/adapter/JSEnvRPC.scala index cc2ce09fa4..dab4394a8a 100644 --- a/test-adapter/src/main/scala/org/scalajs/testing/adapter/JSEnvRPC.scala +++ b/test-adapter/src/main/scala/org/scalajs/testing/adapter/JSEnvRPC.scala @@ -15,14 +15,35 @@ package org.scalajs.testing.adapter import scala.concurrent.ExecutionContext import org.scalajs.jsenv._ +import org.scalajs.logging.Logger import org.scalajs.testing.common._ /** RPC Core for use with a [[JSEnv]]. */ private[adapter] final class JSEnvRPC( - jsenv: JSEnv, input: Seq[Input], config: RunConfig)( + jsenv: JSEnv, input: Seq[Input], logger: Logger)( implicit ec: ExecutionContext) extends RPCCore { - private val run = jsenv.startWithCom(input, config, handleMessage) + private val run: JSComRun = { + /* #4560 Explicitly redirect out/err to System.out/System.err, instead of + * relying on `inheritOut` and `inheritErr`, so that streams installed with + * `System.setOut` and `System.setErr` are always taken into account. + * sbt installs such alternative outputs when it runs in server mode. + * + * We never wait for these threads to finish. In theory, tasks that use the + * test adapter may complete before all output has been transferred to + * `System.out` and `System.err`. + */ + val runConfig = RunConfig() + .withLogger(logger) + .withInheritOut(false) + .withInheritErr(false) + .withOnOutputStream { (out, err) => + out.foreach(o => PipeOutputThread.start(o, System.out)) + err.foreach(e => PipeOutputThread.start(e, System.err)) + } + + jsenv.startWithCom(input, runConfig, handleMessage) + } /* Once the com closes, ensure all still pending calls are failing. * This can be necessary, if the JSEnv terminates unexpectedly. diff --git a/test-adapter/src/main/scala/org/scalajs/testing/adapter/PipeOutputThread.scala b/test-adapter/src/main/scala/org/scalajs/testing/adapter/PipeOutputThread.scala new file mode 100644 index 0000000000..b35215a853 --- /dev/null +++ b/test-adapter/src/main/scala/org/scalajs/testing/adapter/PipeOutputThread.scala @@ -0,0 +1,45 @@ +/* + * 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.testing.adapter + +import scala.annotation.tailrec + +import java.io._ + +// !!! Duplicate code with sbtplugin/PipeOutputThread.scala. +private[adapter] object PipeOutputThread { + def start(from: InputStream, to: OutputStream): Thread = { + val thread = new PipeOutputThread(from, to) + thread.start() + thread + } +} + +private final class PipeOutputThread(from: InputStream, to: OutputStream) extends Thread { + override def run(): Unit = { + try { + val buffer = new Array[Byte](8192) + @tailrec def loop(): Unit = { + val byteCount = from.read(buffer) + if (byteCount > 0) { + to.write(buffer, 0, byteCount) + to.flush() // if the sender flushed (which we can't tell), we need to propagate the flush + loop() + } + } + loop() + } finally { + from.close() + } + } +} diff --git a/test-adapter/src/main/scala/org/scalajs/testing/adapter/RunnerAdapter.scala b/test-adapter/src/main/scala/org/scalajs/testing/adapter/RunnerAdapter.scala index a98c4fbf17..4a83da3222 100644 --- a/test-adapter/src/main/scala/org/scalajs/testing/adapter/RunnerAdapter.scala +++ b/test-adapter/src/main/scala/org/scalajs/testing/adapter/RunnerAdapter.scala @@ -26,16 +26,16 @@ import sbt.testing._ import TestAdapter.ManagedRunner private final class RunnerAdapter private (runnerArgs: RunnerArgs, - master: ManagedRunner, testAdapter: TestAdapter) extends Runner { + controller: ManagedRunner, testAdapter: TestAdapter) extends Runner { private val runID = runnerArgs.runID private val rpcGetter = () => getRunnerRPC() - private val slaves = TrieMap.empty[Long, ManagedRunner] + private val workers = TrieMap.empty[Long, ManagedRunner] - // Route master messages to slaves. - master.mux.attach(JVMEndpoints.msgMaster, runID) { msg => - slaves(msg.slaveId).mux.send(JSEndpoints.msgSlave, runID)(msg.msg) + // Route controller messages to workers. + controller.mux.attach(JVMEndpoints.msgController, runID) { msg => + workers(msg.workerId).mux.send(JSEndpoints.msgWorker, runID)(msg.msg) } def args(): Array[String] = runnerArgs.args.toArray @@ -51,16 +51,16 @@ private final class RunnerAdapter private (runnerArgs: RunnerArgs, } def done(): String = synchronized { - val slaves = this.slaves.values.toList // .toList to make it strict. + val workers = this.workers.values.toList // .toList to make it strict. try { - slaves.map(_.mux.call(JSEndpoints.done, runID)(())).foreach(_.await()) - master.mux.call(JSEndpoints.done, runID)(()).await() + workers.map(_.mux.call(JSEndpoints.done, runID)(())).foreach(_.await()) + controller.mux.call(JSEndpoints.done, runID)(()).await() } finally { - slaves.foreach(_.mux.detach(JVMEndpoints.msgSlave, runID)) - master.mux.detach(JVMEndpoints.msgMaster, runID) + workers.foreach(_.mux.detach(JVMEndpoints.msgWorker, runID)) + controller.mux.detach(JVMEndpoints.msgController, runID) - this.slaves.clear() + this.workers.clear() testAdapter.runDone(runID) } } @@ -68,18 +68,18 @@ private final class RunnerAdapter private (runnerArgs: RunnerArgs, private def getRunnerRPC(): RunMuxRPC = { val mRunner = testAdapter.getRunnerForThread() - if (mRunner != master && !slaves.contains(mRunner.id)) { - // Put the slave in the map so messages can be routed. - slaves.put(mRunner.id, mRunner) + if (mRunner != controller && !workers.contains(mRunner.id)) { + // Put the worker in the map so messages can be routed. + workers.put(mRunner.id, mRunner) // Attach message endpoint. - mRunner.mux.attach(JVMEndpoints.msgSlave, runID) { msg => - master.mux.send(JSEndpoints.msgMaster, runID)( + mRunner.mux.attach(JVMEndpoints.msgWorker, runID) { msg => + controller.mux.send(JSEndpoints.msgController, runID)( new FrameworkMessage(mRunner.id, msg)) } - // Start slave. - mRunner.com.call(JSEndpoints.createSlaveRunner)(runnerArgs).await() + // Start worker. + mRunner.com.call(JSEndpoints.createWorkerRunner)(runnerArgs).await() } mRunner.mux @@ -95,7 +95,7 @@ private[adapter] object RunnerAdapter { val runnerArgs = new RunnerArgs(runID, frameworkImplName, args.toList, remoteArgs.toList) val mRunner = testAdapter.getRunnerForThread() - mRunner.com.call(JSEndpoints.createMasterRunner)(runnerArgs).await() + mRunner.com.call(JSEndpoints.createControllerRunner)(runnerArgs).await() new RunnerAdapter(runnerArgs, mRunner, testAdapter) } catch { 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 83ca00dc49..41f9bee3a6 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 @@ -125,8 +125,7 @@ final class TestAdapter(jsEnv: JSEnv, input: Seq[Input], config: TestAdapter.Con // Otherwise we might leak runners. require(!closed, "We are closed. Cannot create new runner.") - val runConfig = RunConfig().withLogger(config.logger) - val com = new JSEnvRPC(jsEnv, input, runConfig) + val com = new JSEnvRPC(jsEnv, input, config.logger) val mux = new RunMuxRPC(com) new ManagedRunner(threadId, com, mux) diff --git a/test-bridge/src/main/scala/org/scalajs/testing/bridge/HTMLRunner.scala b/test-bridge/src/main/scala/org/scalajs/testing/bridge/HTMLRunner.scala index a997ba44a9..d2aab7f7dd 100644 --- a/test-bridge/src/main/scala/org/scalajs/testing/bridge/HTMLRunner.scala +++ b/test-bridge/src/main/scala/org/scalajs/testing/bridge/HTMLRunner.scala @@ -13,6 +13,13 @@ package org.scalajs.testing.bridge import scala.scalajs.js + +/* Use the queue (Promise) execution context by default to avoid slowdown by clamping + * + * To avoid blocking the UI thread, we use QueueExecutionContext.timeout in + * specific spots in the code. See #4129 for context. + */ +import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue import scala.scalajs.concurrent.QueueExecutionContext import scala.scalajs.js.annotation._ import js.URIUtils.{decodeURIComponent, encodeURIComponent} @@ -20,7 +27,6 @@ import js.URIUtils.{decodeURIComponent, encodeURIComponent} import scala.collection.mutable import scala.concurrent.{Future, Promise} -import scala.concurrent.ExecutionContext.Implicits.global import scala.util.Try @@ -442,13 +448,13 @@ protected[bridge] object HTMLRunner { // Mini dom facade. private object dom { - @JSGlobal("window") + @JSGlobal @js.native object window extends js.Object { def addEventListener(tpe: String, handler: js.Function0[Unit]): Unit = js.native } - @JSGlobal("document") + @JSGlobal @js.native object document extends js.Object { def body: Element = js.native diff --git a/test-bridge/src/main/scala/org/scalajs/testing/bridge/JSRPC.scala b/test-bridge/src/main/scala/org/scalajs/testing/bridge/JSRPC.scala index f73473fd70..3e3a53bbb1 100644 --- a/test-bridge/src/main/scala/org/scalajs/testing/bridge/JSRPC.scala +++ b/test-bridge/src/main/scala/org/scalajs/testing/bridge/JSRPC.scala @@ -15,8 +15,15 @@ package org.scalajs.testing.bridge import scala.scalajs.js import scala.scalajs.js.annotation._ +/* Use the queue execution context (based on JS promises) explicitly: + * We do not have anything better at our disposal and it is accceptable in + * terms of fairness: JSRPC only handles in-between test communcation, so any + * future chain will "yield" to I/O (waiting for a message) or an RPC handler in + * a finite number of steps. + */ +import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue + import scala.concurrent.duration._ -import scala.concurrent.ExecutionContext.Implicits.global import org.scalajs.testing.common.RPCCore diff --git a/test-bridge/src/main/scala/org/scalajs/testing/bridge/TestAdapterBridge.scala b/test-bridge/src/main/scala/org/scalajs/testing/bridge/TestAdapterBridge.scala index 8375573427..d683684629 100644 --- a/test-bridge/src/main/scala/org/scalajs/testing/bridge/TestAdapterBridge.scala +++ b/test-bridge/src/main/scala/org/scalajs/testing/bridge/TestAdapterBridge.scala @@ -29,8 +29,8 @@ private[bridge] object TestAdapterBridge { import JSEndpoints._ JSRPC.attach(detectFrameworks)(detectFrameworksFun) - JSRPC.attach(createMasterRunner)(createRunnerFun(isMaster = true)) - JSRPC.attach(createSlaveRunner)(createRunnerFun(isMaster = false)) + JSRPC.attach(createControllerRunner)(createRunnerFun(isController = true)) + JSRPC.attach(createWorkerRunner)(createRunnerFun(isController = false)) } private def detectFrameworksFun = { names: List[List[String]] => @@ -42,41 +42,41 @@ private[bridge] object TestAdapterBridge { } } - private def createRunnerFun(isMaster: Boolean) = { args: RunnerArgs => + private def createRunnerFun(isController: Boolean) = { args: RunnerArgs => val framework = FrameworkLoader.loadFramework(args.frameworkImpl) val loader = new ScalaJSClassLoader() val runID = args.runID val runner = { - if (isMaster) { + if (isController) { framework.runner(args.args.toArray, args.remoteArgs.toArray, loader) } else { framework.slaveRunner(args.args.toArray, args.remoteArgs.toArray, loader, - mux.send(JVMEndpoints.msgSlave, runID)) + mux.send(JVMEndpoints.msgWorker, runID)) } } mux.attach(JSEndpoints.tasks, runID)(tasksFun(runner)) mux.attachAsync(JSEndpoints.execute, runID)(executeFun(runID, runner)) - mux.attach(JSEndpoints.done, runID)(doneFun(runID, runner, isMaster)) + mux.attach(JSEndpoints.done, runID)(doneFun(runID, runner, isController)) - if (isMaster) { - mux.attach(JSEndpoints.msgMaster, runID)(msgMasterFun(runID, runner)) + if (isController) { + mux.attach(JSEndpoints.msgController, runID)(msgControllerFun(runID, runner)) } else { - mux.attach(JSEndpoints.msgSlave, runID)(runner.receiveMessage _) + mux.attach(JSEndpoints.msgWorker, runID)(runner.receiveMessage _) } } - private def detachRunnerCommands(runID: RunMux.RunID, isMaster: Boolean) = { + private def detachRunnerCommands(runID: RunMux.RunID, isController: Boolean) = { mux.detach(JSEndpoints.tasks, runID) mux.detach(JSEndpoints.execute, runID) mux.detach(JSEndpoints.done, runID) - if (isMaster) - mux.detach(JSEndpoints.msgMaster, runID) + if (isController) + mux.detach(JSEndpoints.msgController, runID) else - mux.detach(JSEndpoints.msgSlave, runID) + mux.detach(JSEndpoints.msgWorker, runID) } private def tasksFun(runner: Runner) = { taskDefs: List[TaskDef] => @@ -109,15 +109,15 @@ private[bridge] object TestAdapterBridge { promise.future } - private def doneFun(runID: RunMux.RunID, runner: Runner, isMaster: Boolean) = { _: Unit => + private def doneFun(runID: RunMux.RunID, runner: Runner, isController: Boolean) = { _: Unit => try runner.done() - finally detachRunnerCommands(runID, isMaster) + finally detachRunnerCommands(runID, isController) } - private def msgMasterFun(runID: RunMux.RunID, runner: Runner) = { msg: FrameworkMessage => + private def msgControllerFun(runID: RunMux.RunID, runner: Runner) = { msg: FrameworkMessage => for (reply <- runner.receiveMessage(msg.msg)) { - val fm = new FrameworkMessage(msg.slaveId, reply) - mux.send(JVMEndpoints.msgMaster, runID)(fm) + val fm = new FrameworkMessage(msg.workerId, reply) + mux.send(JVMEndpoints.msgController, runID)(fm) } } diff --git a/test-common/src/main/scala/org/scalajs/testing/common/FrameworkMessage.scala b/test-common/src/main/scala/org/scalajs/testing/common/FrameworkMessage.scala index 9b0465a0ae..bea92adb5f 100644 --- a/test-common/src/main/scala/org/scalajs/testing/common/FrameworkMessage.scala +++ b/test-common/src/main/scala/org/scalajs/testing/common/FrameworkMessage.scala @@ -12,12 +12,12 @@ package org.scalajs.testing.common -private[testing] final class FrameworkMessage(val slaveId: Long, val msg: String) +private[testing] final class FrameworkMessage(val workerId: Long, val msg: String) private[testing] object FrameworkMessage { implicit object FrameworkMessageSerializer extends Serializer[FrameworkMessage] { def serialize(x: FrameworkMessage, out: Serializer.SerializeState): Unit = { - out.write(x.slaveId) + out.write(x.workerId) out.write(x.msg) } diff --git a/test-common/src/main/scala/org/scalajs/testing/common/JSEndpoints.scala b/test-common/src/main/scala/org/scalajs/testing/common/JSEndpoints.scala index 9efacbfd14..32f0255bad 100644 --- a/test-common/src/main/scala/org/scalajs/testing/common/JSEndpoints.scala +++ b/test-common/src/main/scala/org/scalajs/testing/common/JSEndpoints.scala @@ -18,16 +18,16 @@ private[testing] object JSEndpoints { val detectFrameworks: RPCEndpoint.EP[List[List[String]], List[Option[FrameworkInfo]]] = RPCEndpoint[List[List[String]], List[Option[FrameworkInfo]]](2) - val createMasterRunner: RPCEndpoint.EP[RunnerArgs, Unit] = + val createControllerRunner: RPCEndpoint.EP[RunnerArgs, Unit] = RPCEndpoint[RunnerArgs, Unit](3) - val createSlaveRunner: RPCEndpoint.EP[RunnerArgs, Unit] = + val createWorkerRunner: RPCEndpoint.EP[RunnerArgs, Unit] = RPCEndpoint[RunnerArgs, Unit](4) - val msgSlave: MsgEndpoint.EP[RunMux[String]] = + val msgWorker: MsgEndpoint.EP[RunMux[String]] = MsgEndpoint[RunMux[String]](5) - val msgMaster: MsgEndpoint.EP[RunMux[FrameworkMessage]] = + val msgController: MsgEndpoint.EP[RunMux[FrameworkMessage]] = MsgEndpoint[RunMux[FrameworkMessage]](6) val tasks: RPCEndpoint.EP[RunMux[List[TaskDef]], List[TaskInfo]] = diff --git a/test-common/src/main/scala/org/scalajs/testing/common/JVMEndpoints.scala b/test-common/src/main/scala/org/scalajs/testing/common/JVMEndpoints.scala index 950b1e3d24..a6d31dba4b 100644 --- a/test-common/src/main/scala/org/scalajs/testing/common/JVMEndpoints.scala +++ b/test-common/src/main/scala/org/scalajs/testing/common/JVMEndpoints.scala @@ -15,9 +15,9 @@ package org.scalajs.testing.common import sbt.testing.Event private[testing] object JVMEndpoints { - val msgSlave: MsgEndpoint.EP[RunMux[String]] = MsgEndpoint[RunMux[String]](2) + val msgWorker: MsgEndpoint.EP[RunMux[String]] = MsgEndpoint[RunMux[String]](2) - val msgMaster: MsgEndpoint.EP[RunMux[FrameworkMessage]] = + val msgController: MsgEndpoint.EP[RunMux[FrameworkMessage]] = MsgEndpoint[RunMux[FrameworkMessage]](3) val event: MsgEndpoint.EP[RunMux[Event]] = MsgEndpoint[RunMux[Event]](4) diff --git a/test-common/src/main/scala/org/scalajs/testing/common/RPCCore.scala b/test-common/src/main/scala/org/scalajs/testing/common/RPCCore.scala index 9286cd521a..10f5b40871 100644 --- a/test-common/src/main/scala/org/scalajs/testing/common/RPCCore.scala +++ b/test-common/src/main/scala/org/scalajs/testing/common/RPCCore.scala @@ -94,10 +94,10 @@ private[testing] abstract class RPCCore()(implicit ec: ExecutionContext) { * future, we can improve this. */ val detail = opCode match { - case JSEndpoints.msgSlave.opCode => + case JSEndpoints.msgWorker.opCode => "; " + - "The test adapter could not send a message to a slave, " + - "which probably happens because the slave terminated early, " + + "The test adapter could not send a message to a worker, " + + "which probably happens because the worker terminated early, " + "without waiting for the reply to a call to send(). " + "This is probably a bug in the testing framework you are " + "using. See also #3201." diff --git a/test-interface/src/main/scala/sbt/testing/Framework.scala b/test-interface/src/main/scala/sbt/testing/Framework.scala index ddbae51047..f8525d3e8d 100644 --- a/test-interface/src/main/scala/sbt/testing/Framework.scala +++ b/test-interface/src/main/scala/sbt/testing/Framework.scala @@ -46,9 +46,15 @@ trait Framework { def runner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader): Runner - /** Scala.js specific: Creates a slave runner for a given run. + /** Scala.js specific: Creates a worker runner for a given run. * - * The slave may send a message to the master runner by calling `send`. + * The worker may send a message to the controller runner by calling `send`. + * + * @note + * This method is called `slaveRunner` rather than `workerRunner` for + * historical reasons. To preserve binary compatibility, it cannot be + * renamed. Moreover, since it must be implemented by user code, we cannot + * add another method with the right name and deprecate this one either. */ def slaveRunner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader, send: String => Unit): Runner diff --git a/test-interface/src/main/scala/sbt/testing/Runner.scala b/test-interface/src/main/scala/sbt/testing/Runner.scala index acc182dcac..df77fbe2f3 100644 --- a/test-interface/src/main/scala/sbt/testing/Runner.scala +++ b/test-interface/src/main/scala/sbt/testing/Runner.scala @@ -25,10 +25,11 @@ package sbt.testing * IllegalStateException. * * In Scala.js, the client may request multiple instances of - * Runner, where one of these instances is considered the master. - * The slaves receive a communication channel to the master. Once the master's - * done method is invoked, nothing may be invoked on the slaves - * or the master. Slaves can be de-commissioned before the master terminates. + * Runner, where one of these instances is considered the + * controller. The workers receive a communication channel to the controller. + * Once the controller's done method is invoked, nothing may be + * invoked on the workers nor on the controller. Workers can be + * de-commissioned before the controller terminates. */ trait Runner { @@ -102,9 +103,10 @@ trait Runner { * the Framework should throw an IllegalStateException (since it cannot * block). * - * Further, if this is the master, the client must not call this method - * before, all done methods of all slaves have returned (otherwise, - * IllegalStateException). If this is a slave, the returned string is ignored. + * Further, if this is the controller, the client must not call this method + * before all `done` methods of all workers have returned (otherwise, an + * `IllegalStateException` is thrown). If this is a worker, the returned + * string is ignored. * * @return a possibly multi-line summary string, or the empty string if no * summary is provided @@ -126,16 +128,17 @@ trait Runner { */ def args: Array[String] - /** Scala.js specific: Invoked on the master Runner, if a slave - * sends a message (through the channel provided by the client). + /** Scala.js specific: Invoked on the controller Runner when a + * worker sends a message (through the channel provided by the client). * - * The master may send a message back to the sending slave by returning the - * message in a Some. + * The controller may send a message back to the sending worker by returning + * the message in a `Some`. * - * Invoked on a slave Runner, if the master responds to a - * message (sent by the slave via the supplied closure in - * slaveRunner). The return value of the call is ignored in - * this case. + * Invoked on a worker Runner when the controller responds to a + * message (sent by the worker via the supplied closure in + * slaveRunner, aka `workerRunner`; see the Scaladoc of + * `sbt.testing.Framework` about the name.). The return value of the call is + * ignored in this case. */ def receiveMessage(msg: String): Option[String] diff --git a/test-suite-linker/src/main/scala/org/scalajs/bootstrap/TestSuiteLinker.scala b/test-suite-linker/src/main/scala/org/scalajs/bootstrap/TestSuiteLinker.scala index a307b34bee..4ad71e8de0 100644 --- a/test-suite-linker/src/main/scala/org/scalajs/bootstrap/TestSuiteLinker.scala +++ b/test-suite-linker/src/main/scala/org/scalajs/bootstrap/TestSuiteLinker.scala @@ -1,13 +1,15 @@ package org.scalajs.linker.test import scala.concurrent._ -import scala.concurrent.ExecutionContext.Implicits.global import scala.scalajs.js import scala.scalajs.js.annotation._ import scala.scalajs.js.typedarray._ import scala.scalajs.js.JSConverters._ +// Use the queue execution context explicitly to avoid warnings. +import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue + import org.scalajs.logging._ import org.scalajs.linker._ 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 49718e69e0..daed18e54e 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 @@ -74,6 +74,8 @@ object Platform { def hasCompliantModuleInit: Boolean = BuildInfo.compliantModuleInit + def hasDirectBuffers: Boolean = typedArrays + /** Do we use strict-floats semantics? * * If yes, `number` values that cannot be exactly represented as `Float` diff --git a/test-suite/js/src/test/require-modules/org/scalajs/testsuite/jsinterop/ModulesTest.scala b/test-suite/js/src/test/require-modules/org/scalajs/testsuite/jsinterop/ModulesTest.scala index 048ec2653e..78c4a20b46 100644 --- a/test-suite/js/src/test/require-modules/org/scalajs/testsuite/jsinterop/ModulesTest.scala +++ b/test-suite/js/src/test/require-modules/org/scalajs/testsuite/jsinterop/ModulesTest.scala @@ -63,11 +63,18 @@ class ModulesTest { @Test def testImportFunctionInModule(): Unit = { assertEquals(5, NativeMembers.ssum(2)) assertEquals(13, NativeMembers.ssum(2, 3)) + + assertEquals(5, NativeMembers.ssumRenamed(2)) + assertEquals(13, NativeMembers.ssumRenamed(2, 3)) + + assertEquals(15, NativeMembers.apply(5)) } @Test def testImportFieldInModule(): Unit = { assertEquals("string", js.typeOf(NativeMembers.strConstant)) assertEquals("string", js.typeOf(NativeMembers.strConstantAsDef)) + + assertEquals("string", js.typeOf(NativeMembers.strConstantRenamed)) } @Test def testImportFunctionInModulePackageObject(): Unit = { @@ -83,11 +90,18 @@ class ModulesTest { @Test def testImportObjectInModule(): Unit = { assertTrue((MyBox: Any).isInstanceOf[js.Object]) assertTrue(MyBox.make(5).isInstanceOf[MyBox[_]]) + + assertTrue((MyBoxRenamed: Any).isInstanceOf[js.Object]) + assertTrue(MyBoxRenamed.make(5).isInstanceOf[MyBoxRenamed[_]]) } @Test def testImportClassInModule(): Unit = { - val b = new MyBox(1L) + val a = new MyBox(1L) + assertEquals(1L, a.get()) + a.set(5L) + assertEquals(5L, a.get()) + val b = new MyBoxRenamed(1L) assertEquals(1L, b.get()) b.set(5L) assertEquals(5L, b.get()) @@ -117,11 +131,11 @@ package object modulestestpackageobject { * See #4554. */ @js.native - @JSImport(ModulesTest.modulePath, "ssum") + @JSImport(ModulesTest.modulePath) def ssum(x: Int, y: Int = 50): Int = js.native @js.native - @JSImport(ModulesTest.modulePath, "strConstant") + @JSImport(ModulesTest.modulePath) val strConstant: String = js.native @js.native @@ -163,31 +177,56 @@ object ModulesTest { * See #4554. */ @js.native - @JSImport(modulePath, "ssum") + @JSImport(modulePath) def ssum(x: Int, y: Int = 50): Int = js.native @js.native - @JSImport(modulePath, "strConstant") + @JSImport(modulePath, "ssum") + def ssumRenamed(x: Int, y: Int = 50): Int = js.native + + @js.native + @JSImport(modulePath) val strConstant: String = js.native + @js.native + @JSImport(modulePath, "strConstant") + val strConstantRenamed: String = js.native + @js.native @JSImport(modulePath, "strConstant") def strConstantAsDef: String = js.native + + @js.native + @JSImport(modulePath, "apply") + def apply(x: Int): Int = js.native } @js.native - @JSImport(modulePath, "MyBox") + @JSImport(modulePath) class MyBox[T](x: T) extends js.Object { def get(): T = js.native def set(x: T): Unit = js.native } @js.native - @JSImport(modulePath, "MyBox") + @JSImport(modulePath) object MyBox extends js.Object { def make[T](x: T): MyBox[T] = js.native } + @js.native + @JSImport(modulePath, "MyBox") + class MyBoxRenamed[T](x: T) extends js.Object { + def get(): T = js.native + def set(x: T): Unit = js.native + } + + @js.native + @JSImport(modulePath, "MyBox") + object MyBoxRenamed extends js.Object { + def make[T](x: T): MyBoxRenamed[T] = js.native + } + // #4001 - Test that unused super-classes are not imported. @js.native @JSImport("non-existent.js", "Foo") diff --git a/test-suite/js/src/test/require-new-target/org/scalajs/testsuite/jsinterop/NewTargetTest.scala b/test-suite/js/src/test/require-new-target/org/scalajs/testsuite/jsinterop/NewTargetTest.scala new file mode 100644 index 0000000000..32365fbdf3 --- /dev/null +++ b/test-suite/js/src/test/require-new-target/org/scalajs/testsuite/jsinterop/NewTargetTest.scala @@ -0,0 +1,94 @@ +/* + * 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.jsinterop + +import scala.scalajs.js +import scala.scalajs.js.annotation._ + +import org.junit.Assert._ +import org.junit.Test + +class NewTargetTest { + @Test def direct(): Unit = { + class Direct extends js.Object { + val inVal = js.`new`.target + var inVar = js.`new`.target + var inStat: js.Dynamic = _ + inStat = js.`new`.target + } + + val direct = new Direct() + assertSame(js.constructorOf[Direct], direct.inVal) + assertSame(js.constructorOf[Direct], direct.inVar) + assertSame(js.constructorOf[Direct], direct.inStat) + } + + @Test def parent(): Unit = { + class Parent extends js.Object { + val inVal = js.`new`.target + var inVar = js.`new`.target + var inStat: js.Dynamic = _ + inStat = js.`new`.target + } + + class Child extends Parent + + val child = new Child() + assertSame(js.constructorOf[Child], child.inVal) + assertSame(js.constructorOf[Child], child.inVar) + assertSame(js.constructorOf[Child], child.inStat) + } + + @Test def usableBeforeSuperConstructor(): Unit = { + class BeforeSuperCtorBase(val newTarget: js.Dynamic) extends js.Object + + class BeforeSuperCtorParent extends BeforeSuperCtorBase(js.`new`.target) + + class BeforeSuperCtorChild extends BeforeSuperCtorParent + + val parent = new BeforeSuperCtorParent() + assertSame(js.constructorOf[BeforeSuperCtorParent], parent.newTarget) + + val child = new BeforeSuperCtorChild() + assertSame(js.constructorOf[BeforeSuperCtorChild], child.newTarget) + } + + @Test def usableInSecondaryConstructor(): Unit = { + class SecondaryCtorParent(val one: js.Dynamic) extends js.Object { + var two: js.Dynamic = _ + + def this() = { + this(js.`new`.target) + two = js.`new`.target + } + } + + class SecondaryCtorChild extends SecondaryCtorParent + + val parent = new SecondaryCtorParent() + assertSame(js.constructorOf[SecondaryCtorParent], parent.one) + assertSame(js.constructorOf[SecondaryCtorParent], parent.two) + + val child = new SecondaryCtorChild() + assertSame(js.constructorOf[SecondaryCtorChild], child.one) + assertSame(js.constructorOf[SecondaryCtorChild], child.two) + } + + @Test def usableInObject(): Unit = { + object ObjectWithNewTarget extends js.Object { + val newTarget = js.`new`.target + } + + assertSame(ObjectWithNewTarget.asInstanceOf[js.Dynamic].constructor, ObjectWithNewTarget.newTarget) + } +} diff --git a/test-suite/js/src/test/resources-commonjs/modules-test.js b/test-suite/js/src/test/resources-commonjs/modules-test.js index b643824b46..65e31af316 100644 --- a/test-suite/js/src/test/resources-commonjs/modules-test.js +++ b/test-suite/js/src/test/resources-commonjs/modules-test.js @@ -6,6 +6,10 @@ exports.ssum = function(x, y) { return x * x + y * y; }; +exports.apply = function(x) { + return 3 * x; +}; + exports.default = function() { return 5; }; diff --git a/test-suite/js/src/test/resources-esmodule/modules-test.js b/test-suite/js/src/test/resources-esmodule/modules-test.js index 354208802a..ebad0dc6d3 100644 --- a/test-suite/js/src/test/resources-esmodule/modules-test.js +++ b/test-suite/js/src/test/resources-esmodule/modules-test.js @@ -6,6 +6,10 @@ export function ssum(x, y) { return x * x + y * y; } +export function apply(x) { + return 3 * x; +} + export default function() { return 5; } 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 5b21748203..e2edb8588a 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 @@ -423,11 +423,30 @@ class InteroperabilityTest { Global.interoperabilityTestGlobalValDefSetVariable(123) assertEquals(123, Global.interoperabilityTestGlobalValDefGetVariable()) assertEquals(126, Global.interoperabilityTestGlobalValDefFunction(3)) + Global.interoperabilityTestGlobalValDefSetVariable(7357) assertEquals(18, Global.interoperabilityTestGlobalValDefFunctionWithDefaultParam(10, 8)) assertEquals(15, Global.interoperabilityTestGlobalValDefFunctionWithDefaultParam(10)) assertEquals(25, Global.interoperabilityTestGlobalValDefFunctionWithDefaultParam()) assertEquals(23, Global.interoperabilityTestGlobalValDefFunctionWithDefaultParam(y = 3)) + + // Renamed + + assertEquals(654321, Global.interoperabilityTestGlobalValDefConstantRenamed) + + assertEquals(7357, Global.interoperabilityTestGlobalValDefVariableRenamed) + assertEquals(7357, Global.interoperabilityTestGlobalValDefGetVariableRenamed()) + assertEquals(7360, Global.interoperabilityTestGlobalValDefFunctionRenamed(3)) + + Global.interoperabilityTestGlobalValDefSetVariableRenamed(123) + assertEquals(123, Global.interoperabilityTestGlobalValDefGetVariableRenamed()) + assertEquals(126, Global.interoperabilityTestGlobalValDefFunctionRenamed(3)) + Global.interoperabilityTestGlobalValDefSetVariableRenamed(7357) + + assertEquals(18, Global.interoperabilityTestGlobalValDefFunctionWithDefaultParamRenamed(10, 8)) + assertEquals(15, Global.interoperabilityTestGlobalValDefFunctionWithDefaultParamRenamed(10)) + assertEquals(25, Global.interoperabilityTestGlobalValDefFunctionWithDefaultParamRenamed()) + assertEquals(23, Global.interoperabilityTestGlobalValDefFunctionWithDefaultParamRenamed(y = 3)) } @Test def accessTopLevelJSVarsAndFunctionsViaPackageObjectWithNativeValsAndDefs(): Unit = { @@ -451,20 +470,39 @@ class InteroperabilityTest { // Use alias for convenience: see end of file for definition import org.scalajs.testsuite.compiler.{interoperabilitytestglobalvalsanddefspackageobject => pack} - assertEquals(654321, pack.interoperabilityTestGlobalValDefConstant) + assertEquals(654321, pack.interoperabilityTestGlobalValDefConstantInPackageObject) + + assertEquals(7357, pack.interoperabilityTestGlobalValDefVariableInPackageObject) + assertEquals(7357, pack.interoperabilityTestGlobalValDefGetVariableInPackageObject()) + assertEquals(7360, pack.interoperabilityTestGlobalValDefFunctionInPackageObject(3)) + + pack.interoperabilityTestGlobalValDefSetVariableInPackageObject(123) + assertEquals(123, pack.interoperabilityTestGlobalValDefGetVariableInPackageObject()) + assertEquals(126, pack.interoperabilityTestGlobalValDefFunctionInPackageObject(3)) + pack.interoperabilityTestGlobalValDefSetVariableInPackageObject(7357) + + assertEquals(18, pack.interoperabilityTestGlobalValDefFunctionWithDefaultParamInPackageObject(10, 8)) + assertEquals(15, pack.interoperabilityTestGlobalValDefFunctionWithDefaultParamInPackageObject(10)) + assertEquals(25, pack.interoperabilityTestGlobalValDefFunctionWithDefaultParamInPackageObject()) + assertEquals(23, pack.interoperabilityTestGlobalValDefFunctionWithDefaultParamInPackageObject(y = 3)) + + // Renamed + + assertEquals(654321, pack.interoperabilityTestGlobalValDefConstantInPackageObjectRenamed) - assertEquals(7357, pack.interoperabilityTestGlobalValDefVariable) - assertEquals(7357, pack.interoperabilityTestGlobalValDefGetVariable()) - assertEquals(7360, pack.interoperabilityTestGlobalValDefFunction(3)) + assertEquals(7357, pack.interoperabilityTestGlobalValDefVariableInPackageObjectRenamed) + assertEquals(7357, pack.interoperabilityTestGlobalValDefGetVariableInPackageObjectRenamed()) + assertEquals(7360, pack.interoperabilityTestGlobalValDefFunctionInPackageObjectRenamed(3)) - pack.interoperabilityTestGlobalValDefSetVariable(123) - assertEquals(123, pack.interoperabilityTestGlobalValDefGetVariable()) - assertEquals(126, pack.interoperabilityTestGlobalValDefFunction(3)) + pack.interoperabilityTestGlobalValDefSetVariableInPackageObjectRenamed(123) + assertEquals(123, pack.interoperabilityTestGlobalValDefGetVariableInPackageObjectRenamed()) + assertEquals(126, pack.interoperabilityTestGlobalValDefFunctionInPackageObjectRenamed(3)) + pack.interoperabilityTestGlobalValDefSetVariableInPackageObjectRenamed(7357) - assertEquals(18, pack.interoperabilityTestGlobalValDefFunctionWithDefaultParam(10, 8)) - assertEquals(15, pack.interoperabilityTestGlobalValDefFunctionWithDefaultParam(10)) - assertEquals(25, pack.interoperabilityTestGlobalValDefFunctionWithDefaultParam()) - assertEquals(23, pack.interoperabilityTestGlobalValDefFunctionWithDefaultParam(y = 3)) + assertEquals(18, pack.interoperabilityTestGlobalValDefFunctionWithDefaultParamInPackageObjectRenamed(10, 8)) + assertEquals(15, pack.interoperabilityTestGlobalValDefFunctionWithDefaultParamInPackageObjectRenamed(10)) + assertEquals(25, pack.interoperabilityTestGlobalValDefFunctionWithDefaultParamInPackageObjectRenamed()) + assertEquals(23, pack.interoperabilityTestGlobalValDefFunctionWithDefaultParamInPackageObjectRenamed(y = 3)) } @@ -534,6 +572,8 @@ class InteroperabilityTest { } """); + import InteroperabilityTestScalaObjectContainer._ + assertJSArrayEquals(js.Array(6, 8), new InteroperabilityTestCtor().values) assertJSArrayEquals(js.Array(6, 7), new InteroperabilityTestCtor(y = 7).values) assertJSArrayEquals(js.Array(3, 8), new InteroperabilityTestCtor(3).values) @@ -548,6 +588,8 @@ class InteroperabilityTest { } """) + import InteroperabilityTestScalaObjectContainer._ + val obj = new InteroparabilityCtorInlineValue(10, -1) assertEquals(10, obj.x) @@ -663,6 +705,8 @@ class InteroperabilityTest { }; """) + import InteroperabilityTestScalaObjectContainer._ + assertEquals("object", js.typeOf(InteroperabilityTestConstObject)) assertEquals(42, InteroperabilityTestConstObject.x) } @@ -678,6 +722,8 @@ class InteroperabilityTest { }; """) + import InteroperabilityTestScalaObjectContainer._ + assertEquals("function", js.typeOf(js.constructorOf[InteroperabilityTestConstClass])) val obj = new InteroperabilityTestConstClass(5) @@ -695,6 +741,7 @@ class InteroperabilityTest { }); """) + import InteroperabilityTestScalaObjectContainer._ import InteroperabilityTestLetConstGlobals._ assertEquals("number", js.typeOf(InteroperabilityTestLetConstGlobals_value)) @@ -977,55 +1024,99 @@ object InteroperabilityTestGlobalScope extends js.Object { object InteroperabilityTestGlobalValsAndDefs { @js.native - @JSGlobal("interoperabilityTestGlobalValDefConstant") + @JSGlobal val interoperabilityTestGlobalValDefConstant: Int = js.native @js.native - @JSGlobal("interoperabilityTestGlobalValDefVariable") + @JSGlobal("interoperabilityTestGlobalValDefConstant") + val interoperabilityTestGlobalValDefConstantRenamed: Int = js.native + + @js.native + @JSGlobal def interoperabilityTestGlobalValDefVariable: Int = js.native @js.native - @JSGlobal("interoperabilityTestGlobalValDefGetVariable") + @JSGlobal("interoperabilityTestGlobalValDefVariable") + def interoperabilityTestGlobalValDefVariableRenamed: Int = js.native + + @js.native + @JSGlobal def interoperabilityTestGlobalValDefGetVariable(): Int = js.native @js.native - @JSGlobal("interoperabilityTestGlobalValDefSetVariable") + @JSGlobal("interoperabilityTestGlobalValDefGetVariable") + def interoperabilityTestGlobalValDefGetVariableRenamed(): Int = js.native + + @js.native + @JSGlobal def interoperabilityTestGlobalValDefSetVariable(x: Int): Unit = js.native @js.native - @JSGlobal("interoperabilityTestGlobalValDefFunction") + @JSGlobal("interoperabilityTestGlobalValDefSetVariable") + def interoperabilityTestGlobalValDefSetVariableRenamed(x: Int): Unit = js.native + + @js.native + @JSGlobal def interoperabilityTestGlobalValDefFunction(x: Int): Int = js.native + @js.native + @JSGlobal("interoperabilityTestGlobalValDefFunction") + def interoperabilityTestGlobalValDefFunctionRenamed(x: Int): Int = js.native + /* In this facade, 50 is not the actual default value for `y`. * We intentionally use a different value to check that it is ignored. * See #4554. * The default value `= js.native` of `x` is a test for #4553. */ @js.native - @JSGlobal("interoperabilityTestGlobalValDefFunctionWithDefaultParam") + @JSGlobal def interoperabilityTestGlobalValDefFunctionWithDefaultParam(x: Int = js.native, y: Int = 50): Int = js.native + + @js.native + @JSGlobal("interoperabilityTestGlobalValDefFunctionWithDefaultParam") + def interoperabilityTestGlobalValDefFunctionWithDefaultParamRenamed(x: Int = js.native, y: Int = 50): Int = js.native } package object interoperabilitytestglobalvalsanddefspackageobject { + @js.native + @JSGlobal + val interoperabilityTestGlobalValDefConstantInPackageObject: Int = js.native + @js.native @JSGlobal("interoperabilityTestGlobalValDefConstantInPackageObject") - val interoperabilityTestGlobalValDefConstant: Int = js.native + val interoperabilityTestGlobalValDefConstantInPackageObjectRenamed: Int = js.native + + @js.native + @JSGlobal + def interoperabilityTestGlobalValDefVariableInPackageObject: Int = js.native @js.native @JSGlobal("interoperabilityTestGlobalValDefVariableInPackageObject") - def interoperabilityTestGlobalValDefVariable: Int = js.native + def interoperabilityTestGlobalValDefVariableInPackageObjectRenamed: Int = js.native + + @js.native + @JSGlobal + def interoperabilityTestGlobalValDefGetVariableInPackageObject(): Int = js.native @js.native @JSGlobal("interoperabilityTestGlobalValDefGetVariableInPackageObject") - def interoperabilityTestGlobalValDefGetVariable(): Int = js.native + def interoperabilityTestGlobalValDefGetVariableInPackageObjectRenamed(): Int = js.native + + @js.native + @JSGlobal + def interoperabilityTestGlobalValDefSetVariableInPackageObject(x: Int): Unit = js.native @js.native @JSGlobal("interoperabilityTestGlobalValDefSetVariableInPackageObject") - def interoperabilityTestGlobalValDefSetVariable(x: Int): Unit = js.native + def interoperabilityTestGlobalValDefSetVariableInPackageObjectRenamed(x: Int): Unit = js.native + + @js.native + @JSGlobal + def interoperabilityTestGlobalValDefFunctionInPackageObject(x: Int): Int = js.native @js.native @JSGlobal("interoperabilityTestGlobalValDefFunctionInPackageObject") - def interoperabilityTestGlobalValDefFunction(x: Int): Int = js.native + def interoperabilityTestGlobalValDefFunctionInPackageObjectRenamed(x: Int): Int = js.native /* In this facade, 50 is not the actual default value for `y`. * We intentionally use a different value to check that it is ignored. @@ -1034,37 +1125,45 @@ package object interoperabilitytestglobalvalsanddefspackageobject { */ @js.native @JSGlobal("interoperabilityTestGlobalValDefFunctionWithDefaultParamInPackageObject") - def interoperabilityTestGlobalValDefFunctionWithDefaultParam(x: Int = js.native, y: Int = 50): Int = js.native + def interoperabilityTestGlobalValDefFunctionWithDefaultParamInPackageObject( + x: Int = js.native, y: Int = 50): Int = js.native + + @js.native + @JSGlobal("interoperabilityTestGlobalValDefFunctionWithDefaultParamInPackageObject") + def interoperabilityTestGlobalValDefFunctionWithDefaultParamInPackageObjectRenamed( + x: Int = js.native, y: Int = 50): Int = js.native } class SomeValueClass(val i: Int) extends AnyVal { override def toString(): String = s"SomeValueClass($i)" } -@js.native -@JSGlobal -class InteroperabilityTestCtor(x: Int = 5, y: Int = ???) extends js.Object { - def values: js.Array[Int] = js.native -} +object InteroperabilityTestScalaObjectContainer { + @js.native + @JSGlobal + class InteroperabilityTestCtor(x: Int = 5, y: Int = ???) extends js.Object { + def values: js.Array[Int] = js.native + } -@js.native -@JSGlobal -class InteroparabilityCtorInlineValue(val x: Int, var y: Int) extends js.Object + @js.native + @JSGlobal + class InteroparabilityCtorInlineValue(val x: Int, var y: Int) extends js.Object -@js.native -@JSGlobal -object InteroperabilityTestConstObject extends js.Object { - val x: Int = js.native -} + @js.native + @JSGlobal + object InteroperabilityTestConstObject extends js.Object { + val x: Int = js.native + } -@js.native -@JSGlobal -class InteroperabilityTestConstClass(val x: Int) extends js.Object + @js.native + @JSGlobal + class InteroperabilityTestConstClass(val x: Int) extends js.Object -@js.native -@JSGlobalScope -object InteroperabilityTestLetConstGlobals extends js.Any { - val InteroperabilityTestLetConstGlobals_value: Int = js.native - var InteroperabilityTestLetConstGlobals_variable: String = js.native - def InteroperabilityTestLetConstGlobals_method(x: Int): Int = js.native + @js.native + @JSGlobalScope + object InteroperabilityTestLetConstGlobals extends js.Any { + val InteroperabilityTestLetConstGlobals_value: Int = js.native + var InteroperabilityTestLetConstGlobals_variable: String = js.native + def InteroperabilityTestLetConstGlobals_method(x: Int): Int = js.native + } } diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/OptimizerTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/OptimizerTest.scala index 67668f65a0..9f2347610d 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/OptimizerTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/OptimizerTest.scala @@ -19,6 +19,7 @@ import org.junit.Test import org.junit.Assert._ import org.junit.Assume._ +import org.scalajs.testsuite.utils.AssertThrows.assertThrows import org.scalajs.testsuite.utils.Platform._ class OptimizerTest { @@ -432,6 +433,38 @@ class OptimizerTest { assertEquals("Scala.js", "Scala.j" + 's') } + // Division by zero + + @Test def divideByZero_Issue4604(): Unit = { + // Ints + + @noinline def intDivByZeroInExpressionPosition(): Int = { + 0 / 0 + } + + @noinline def intDivByZeroInStatementPosition(): Unit = { + 0 / 0 + fail("should be unreachable") + } + + assertThrows(classOf[ArithmeticException], intDivByZeroInExpressionPosition()) + assertThrows(classOf[ArithmeticException], intDivByZeroInStatementPosition()) + + // Longs + + @noinline def longDivByZeroInExpressionPosition(): Long = { + 0L / 0L + } + + @noinline def longDivByZeroInStatementPosition(): Unit = { + 0L / 0L + fail("should be unreachable") + } + + assertThrows(classOf[ArithmeticException], longDivByZeroInExpressionPosition()) + assertThrows(classOf[ArithmeticException], longDivByZeroInStatementPosition()) + } + // Virtualization of JSArrayConstr @Test def virtualizedJSArrayConstr(): Unit = { diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/RegressionJSTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/RegressionJSTest.scala index 42e1c29da8..86f8810b4f 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/RegressionJSTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/RegressionJSTest.scala @@ -17,6 +17,10 @@ import scala.scalajs.js.annotation._ import org.junit.Test import org.junit.Assert._ +import org.junit.Assume._ + +import org.scalajs.testsuite.utils.AssertThrows.assertThrows +import org.scalajs.testsuite.utils.Platform._ class RegressionJSTest { import RegressionJSTest._ @@ -96,6 +100,43 @@ class RegressionJSTest { assertEquals(6, obj2.bar) } + @Test def preserveSideEffectsOfJSOpsWithBigInts_Issue4621(): Unit = { + assumeTrue("requires BigInts support", jsBigInts) + + @noinline def bi(x: Int): js.BigInt = js.BigInt(x) + + // These must be stored as `val`s first in order to trigger the original problem + val bi5: Any = bi(5) + val bi0: Any = bi(0) + val bi1: Any = bi(1) + + assertThrows(classOf[js.JavaScriptException], { + bi5.asInstanceOf[js.Dynamic] / bi0.asInstanceOf[js.Dynamic] + fail("unreachable") // required for the above line to be in statement position + }) + assertThrows(classOf[js.JavaScriptException], { + +bi5.asInstanceOf[js.Dynamic] + fail("unreachable") + }) + } + + @Test def preserveSideEffectsOfJSOpsWithCustomValueOf_Issue4621(): Unit = { + // This must be a `val` in order to trigger the original problem + val obj: Any = new js.Object { + override def valueOf(): Double = + throw new UnsupportedOperationException() + } + + assertThrows(classOf[UnsupportedOperationException], { + obj.asInstanceOf[js.Dynamic] + 5.asInstanceOf[js.Dynamic] + fail("unreachable") + }) + assertThrows(classOf[UnsupportedOperationException], { + -obj.asInstanceOf[js.Dynamic] + fail("unreachable") + }) + } + } object RegressionJSTest { diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/NonNativeJSTypeTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/NonNativeJSTypeTest.scala index f409ae2cc4..034ba8886b 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/NonNativeJSTypeTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/NonNativeJSTypeTest.scala @@ -1026,6 +1026,17 @@ class NonNativeJSTypeTest { assertEquals(b.y, "xy") } + @Test def constructorsWithPatternMatch_Issue4581(): Unit = { + val a = new PrimaryConstructorWithPatternMatch_Issue4581(5 :: Nil) + assertEquals(5, a.head) + + val b = new SecondaryConstructorWithPatternMatch_Issue4581() + assertEquals(0, b.head) + + val c = new SecondaryConstructorWithPatternMatch_Issue4581(6 :: Nil) + assertEquals(6, c.head) + } + @Test def polytypeNullaryMethod_Issue2445(): Unit = { class PolyTypeNullaryMethod extends js.Object { def emptyArray[T]: js.Array[T] = js.Array() @@ -2015,6 +2026,28 @@ object NonNativeJSTypeTest { def this(x: Int) = this(x.toString())() } + class PrimaryConstructorWithPatternMatch_Issue4581(xs: List[Int]) extends js.Object { + var head: Int = 0 + + xs match { + case x :: xr => head = x + case _ => fail(xs.toString()) + } + } + + class SecondaryConstructorWithPatternMatch_Issue4581 extends js.Object { + var head: Int = 0 + + def this(xs: List[Int]) = { + this() + + xs match { + case x :: xr => head = x + case _ => fail(xs.toString()) + } + } + } + class SimpleConstructorAutoFields(val x: Int, var y: Int) extends js.Object { def sum(): Int = x + y } diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/BigIntTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/library/BigIntTest.scala index d465211079..949692b6e4 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/BigIntTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/library/BigIntTest.scala @@ -24,12 +24,18 @@ class BigIntTest { @Test def apply(): Unit = { val fromString = js.BigInt("9007199254740992") assertEquals(fromString.toString(), "9007199254740992") + assertEquals(fromString.toString(16), "20000000000000") + assertEquals(fromString.toString(3), "1121202011211211122211100012101112") val fromInt = js.BigInt(2147483647) assertEquals(fromInt.toString(), "2147483647") + assertEquals(fromInt.toString(16), "7fffffff") + assertEquals(fromInt.toString(3), "12112122212110202101") val fromDouble = js.BigInt(4294967295d) assertEquals(fromDouble.toString(), "4294967295") + assertEquals(fromDouble.toString(16), "ffffffff") + assertEquals(fromDouble.toString(3), "102002022201221111210") } @Test def asIntN(): Unit = { @@ -74,6 +80,9 @@ class BigIntTest { val multi = previousMaxSafe * js.BigInt(2) assertEquals(multi, js.BigInt("18014398509481982")) + val div = previousMaxSafe / js.BigInt(2) + assertEquals(div, js.BigInt("4503599627370495")) + val subtr = multi - js.BigInt(10) assertEquals(subtr, js.BigInt("18014398509481972")) 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 a5014c5b34..7e5537dc1d 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 @@ -41,6 +41,7 @@ object Platform { def hasCompliantAsInstanceOfs: Boolean = true def hasCompliantArrayIndexOutOfBounds: Boolean = true def hasCompliantModule: Boolean = true + def hasDirectBuffers: Boolean = true def hasStrictFloats: Boolean = true def hasAccurateFloats: Boolean = true diff --git a/test-suite/shared/src/test/require-jdk11/org/scalajs/testsuite/javalib/lang/StringTestOnJDK11.scala b/test-suite/shared/src/test/require-jdk11/org/scalajs/testsuite/javalib/lang/StringTestOnJDK11.scala index cddd080146..ea6e081c76 100644 --- a/test-suite/shared/src/test/require-jdk11/org/scalajs/testsuite/javalib/lang/StringTestOnJDK11.scala +++ b/test-suite/shared/src/test/require-jdk11/org/scalajs/testsuite/javalib/lang/StringTestOnJDK11.scala @@ -33,4 +33,121 @@ class StringTestOnJDK11 { assertTrue(str.repeat(100) == List.fill(100)(str).mkString("")) assertTrue(str.repeat(1000) == List.fill(1000)(str).mkString("")) } + + @Test def strip(): Unit = { + assertEquals("", "".strip()) + assertEquals("", " ".strip()) + assertEquals("", " ".strip()) + assertEquals("", " ".strip()) + assertEquals("", (" " * 1000).strip()) + assertEquals("\u0394", "\u0394".strip()) + assertEquals("a", "a ".strip()) + assertEquals("a", " a".strip()) + assertEquals("a", " a ".strip()) + assertEquals("a", " a ".strip()) + assertEquals("a", " a ".strip()) + assertEquals("a", " a ".strip()) + assertEquals("a b", " a b ".strip()) + assertEquals("a b", " a b ".strip()) + assertEquals("a_", "a_".strip()) + assertEquals("a_", " a_".strip()) + assertEquals("a_", " a_ ".strip()) + assertEquals("a_", " a_ ".strip()) + + assertEquals("A", "\u2028 A \u2028".strip()) + assertEquals("A", "\u2029 A \u2029".strip()) + assertEquals("A", "\u2004 A \u2004".strip()) + assertEquals("A", "\u200A A \u200A".strip()) + assertEquals("A", "\u3000 A \u3000".strip()) + assertEquals("A", "\u200A \u3000 A \u2028 \u2029 \u2004 ".strip()) + } + + @Test def stripLeading(): Unit = { + assertEquals("", "".stripLeading()) + assertEquals("", " ".stripLeading()) + assertEquals("", " ".stripLeading()) + assertEquals("", " ".stripLeading()) + assertEquals("", (" " * 1000).stripLeading()) + assertEquals("\u0394", "\u0394".stripLeading()) + assertEquals("a ", "a ".stripLeading()) + assertEquals("a", " a".stripLeading()) + assertEquals("a ", " a ".stripLeading()) + assertEquals("a ", " a ".stripLeading()) + assertEquals("a ", " a ".stripLeading()) + assertEquals("a ", " a ".stripLeading()) + assertEquals("a b ", " a b ".stripLeading()) + assertEquals("a b ", " a b ".stripLeading()) + assertEquals("a_", "a_".stripLeading()) + assertEquals("a_", " a_".stripLeading()) + assertEquals("a_ ", " a_ ".stripLeading()) + assertEquals("a_ ", " a_ ".stripLeading()) + assertEquals("A", " \t\n\r\f\u001C\u001D\u001E\u001FA".stripLeading()) + + assertEquals("A ", "\u2028 A ".stripLeading()) + assertEquals("A ", "\u2029 A ".stripLeading()) + assertEquals("A ", "\u2004 A ".stripLeading()) + assertEquals("A ", "\u200A A ".stripLeading()) + assertEquals("A ", "\u3000 A ".stripLeading()) + assertEquals("A ", "\u2028 \u2029 \u2004 \u200A \u3000 A ".stripLeading()) + } + + @Test def stripTrailing(): Unit = { + assertEquals("", "".stripTrailing()) + assertEquals("", " ".stripTrailing()) + assertEquals("", " ".stripTrailing()) + assertEquals("", " ".stripTrailing()) + assertEquals("", (" " * 1000).stripTrailing()) + assertEquals("\u0394", "\u0394".stripTrailing()) + assertEquals("a", "a ".stripTrailing()) + assertEquals(" a", " a".stripTrailing()) + assertEquals(" a", " a ".stripTrailing()) + assertEquals(" a", " a ".stripTrailing()) + assertEquals(" a", " a ".stripTrailing()) + assertEquals(" a", " a ".stripTrailing()) + assertEquals(" a b", " a b ".stripTrailing()) + assertEquals(" a b", " a b ".stripTrailing()) + assertEquals("a_", "a_".stripTrailing()) + assertEquals(" a_", " a_".stripTrailing()) + assertEquals(" a_", " a_ ".stripTrailing()) + assertEquals(" a_", " a_ ".stripTrailing()) + assertEquals("A", "A \t\n\r\f\u001C\u001D\u001E\u001F".stripTrailing()) + + assertEquals(" A", " A \u2028".stripTrailing()) + assertEquals(" A", " A \u2029".stripTrailing()) + assertEquals(" A", " A \u2004".stripTrailing()) + assertEquals(" A", " A \u200A".stripTrailing()) + assertEquals(" A", " A \u3000".stripTrailing()) + assertEquals(" A", " A \u2028 \u2029 \u2004 \u200A \u3000".stripTrailing()) + } + + @Test def isBlank(): Unit = { + assertFalse("a".isBlank()) + assertFalse(" a".isBlank()) + assertFalse("\u00A0".isBlank()) + assertFalse("\u2007".isBlank()) + assertFalse("\u202F".isBlank()) + + // from unicode: "Separator: Space, Line, Paragraph" + assertTrue("\u2028".isBlank()) + assertTrue("\u2029".isBlank()) + assertTrue("\u2004".isBlank()) + assertTrue("\u200A".isBlank()) + assertTrue("\u3000".isBlank()) + assertTrue("\u2028 \u2029 \u2004 \u200A \u3000".isBlank()) + + assertTrue("\t".isBlank()) + assertTrue("\n".isBlank()) + assertTrue("\u000B".isBlank()) + assertTrue("\f".isBlank()) + assertTrue("\r".isBlank()) + assertTrue("\u001C".isBlank()) + assertTrue("\u001D".isBlank()) + assertTrue("\u001E".isBlank()) + assertTrue("\u001F".isBlank()) + assertTrue("".isBlank()) + assertTrue(" ".isBlank()) + assertTrue(" ".isBlank()) + assertTrue(" \t\n\r\f\u001C\u001D\u001E\u001F".isBlank()) + assertTrue((" " * 1000).isBlank()) + } } diff --git a/test-suite/shared/src/test/require-jdk15/org/scalajs/testsuite/javalib/lang/StringTestOnJDK15.scala b/test-suite/shared/src/test/require-jdk15/org/scalajs/testsuite/javalib/lang/StringTestOnJDK15.scala new file mode 100644 index 0000000000..25ed53f386 --- /dev/null +++ b/test-suite/shared/src/test/require-jdk15/org/scalajs/testsuite/javalib/lang/StringTestOnJDK15.scala @@ -0,0 +1,230 @@ +/* + * 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.lang + +import org.junit.Test +import org.junit.Assert._ + +import org.scalajs.testsuite.utils.AssertThrows.assertThrows + +class StringTestOnJDK15 { + + // indent and transform are available since JDK 12 but we're not testing them separately + + @Test def indent(): Unit = { + assertEquals("", "".indent(1)) + assertEquals("", "".indent(0)) + assertEquals("", "".indent(-1)) + assertEquals(" \n", "\n".indent(1)) + assertEquals("\n", "\n".indent(0)) + assertEquals("\n", "\n".indent(-1)) + + // indent adds the extra new line due to JDK normalization requirements + assertEquals(" abc\n", "abc".indent(2)) + assertEquals(" abc\n", "abc".indent(1)) + assertEquals("abc\n", "abc".indent(0)) + assertEquals("abc\n", "abc".indent(-1)) + assertEquals("abc\n", "abc".indent(-2)) + assertEquals(" a\n b\n", "a\n b\n".indent(5)) + assertEquals("a\n b\n", "a\n b\n".indent(0)) + assertEquals("a\nb\n", "a\n b\n".indent(-5)) + assertEquals(" \n", " ".indent(0)) + assertEquals(" \n", " ".indent(6)) + assertEquals("\n", " ".indent(-6)) + assertEquals(" \n", " ".indent(-2)) + assertEquals(" \n", " ".indent(-6)) + + assertEquals(" a\n \n c\n", "a\n\nc".indent(2)) + assertEquals(" abc\n def\n", "abc\ndef".indent(2)) + assertEquals(" abc\n def\n \n \n \n a\n", "abc\ndef\n\n\n\na".indent(2)) + + assertEquals(" \n \n", "\n \n".indent(1)) + assertEquals(" \n \n \n", " \n \n ".indent(1)) + assertEquals(" \n \n \n \n", "\n\n\n\n".indent(1)) + assertEquals(" 0\n A\n B\n C\n D\n", "0\r\nA\r\nB\r\nC\r\nD".indent(1)) + assertEquals(" 0\n A\n B\n C\n D\n", "0\rA\rB\rC\rD".indent(1)) + + assertEquals(" \n \n \n", "\r\r\n\n".indent(2)) + assertEquals(" \n \n \n \n", "\r\r\r\r".indent(2)) + assertEquals(" \n \n", "\r\n\r\n".indent(2)) + assertEquals("\n\n\n", "\r\n\n\n".indent(-1)) + assertEquals("\n\n\n", "\r\n\n\n".indent(0)) + + // non-U+0020 WS + assertEquals(" \u2028 \u2029 \u2004 \u200a \u3000 \n", "\u2028 \u2029 \u2004 \u200A \u3000 ".indent(2)) + assertEquals("\u2029 \u2004 \u200A \u3000 \n", "\u2028 \u2029 \u2004 \u200A \u3000 ".indent(-2)) + assertEquals("\u2028 \u2029 \u2004 \u200A \u3000 \n", "\u2028 \u2029 \u2004 \u200A \u3000 ".indent(0)) + + } + + @Test def transform(): Unit = { + assertEquals("", "".transform(x => x)) + assertEquals("abcabc", "abc".transform(_ * 2)) + assertEquals("bar", "foo".transform(_ => "bar")) + } + + @Test def stripIndent(): Unit = { + + // single line indents + assertEquals("", "".stripIndent()) + assertEquals("", " ".stripIndent()) + assertEquals("-", "-".stripIndent()) + assertEquals("-", " -".stripIndent()) + assertEquals("-", " -".stripIndent()) + assertEquals("-", " - ".stripIndent()) + assertEquals("", " ".stripIndent()) + + // new line normalization + assertEquals("\n", "\n".stripIndent()) + assertEquals("\n", " \n".stripIndent()) + assertEquals("\n", " \n ".stripIndent()) + assertEquals("\n\n", "\n\n".stripIndent()) + assertEquals("\n\n\n", "\n\n\n".stripIndent()) + assertEquals("\n\n", "\n \n".stripIndent()) + assertEquals(" A\n B\n\n", " A\n B\r \n".stripIndent()) + assertEquals(" A\n B\n", " A\n B\r\n".stripIndent()) + assertEquals(" A\n B\n", " A\n B\n".stripIndent()) + assertEquals("A\nB", " A\n B".stripIndent()) + assertEquals("\n\n", "\n \n ".stripIndent()) + assertEquals("\n A\n B\n", " \n A\n B \n".stripIndent()) + assertEquals("\nA\nB", " \nA \nB".stripIndent()) + assertEquals("A\nA\nB", "A \nA \nB".stripIndent()) + assertEquals("A\nA\nA\nA", " A\n A\n A\n A".stripIndent()) + assertEquals("A\nA\nA\nA", " A\n A\n A\n A ".stripIndent()) + assertEquals("__\nABC\n Ac\nA", " __ \n ABC \n Ac\n A ".stripIndent()) + + // variable indents + assertEquals("A\n B\n C\n D\n E\n", "A\n B\n C\n D\n E\n ".stripIndent()) + assertEquals(" A\n B\n C\n D\n E\n", " A\n B\n C\n D\n E\n".stripIndent()) + assertEquals(" A\nB\n\n", " A\nB\n \n".stripIndent()) + assertEquals(" A\n B\n C\n", " A\n B\n C\n".stripIndent()) + + // variable indents (no trailing new line) + assertEquals("A\n B\n C\n D\n E", "A\n B\n C\n D\n E".stripIndent()) + assertEquals(" A\n B\nC\n D\n E", " A\n B\n C\n D\n E".stripIndent()) + assertEquals(" A\nB", " A\nB".stripIndent()) + assertEquals("A\n B\nC", " A\n B\n C".stripIndent()) + + // alternative WS and tabs + assertEquals( + "A\n\u2028B\n\u2028C\n\u2028\u2028D\n\u2028\u2028\u2028E", + "A\n\u2028B\n\u2028C\u2028\n\u2028\u2028D\u2028\n\u2028\u2028\u2028E \u2028".stripIndent()) + assertEquals( + "\u2028 A\n B\nC\n\n E", + "\u2029 \u2028 A\n B\n\u3000 C \u2028\n \t\n \u2004 E".stripIndent()) + assertEquals(" A\nB", " A\t\nB".stripIndent()) + assertEquals("\tA\n B\nC", "\t\tA\t\n B\n\tC".stripIndent()) + assertEquals("A\n B\nC", "\tA\n\t B\t\n\tC".stripIndent()) + + // leading/trailing WS + assertEquals("A\nB\n", " A\n B\n ".stripIndent()) + assertEquals("A\nB\n", " A\n B\n ".stripIndent()) + assertEquals(" A\n B\n", " A\n B\n".stripIndent()) + assertEquals(" A\n B\n", " A\n B\n ".stripIndent()) + assertEquals("A\nB\n", " A\n B\n ".stripIndent()) + assertEquals("A\nB\n", " A\n B\n ".stripIndent()) + assertEquals(" A\n B\n", " A\n B\n ".stripIndent()) + + assertEquals("\n", " \n".stripIndent()) + assertEquals("\n", " \n".stripIndent()) + assertEquals("\n", " \n ".stripIndent()) + assertEquals("\n", " \n ".stripIndent()) + assertEquals("\n", "\n ".stripIndent()) + assertEquals("\n", " \n".stripIndent()) + assertEquals("\n", " \n ".stripIndent()) + assertEquals("\n", " \n ".stripIndent()) + assertEquals("\n", " \n ".stripIndent()) + assertEquals("\n", " \n".stripIndent()) + assertEquals("\n", " \n ".stripIndent()) + assertEquals("\n", " \n ".stripIndent()) + assertEquals("\n", " \n ".stripIndent()) + } + + @Test def translateEscapes(): Unit = { + + // bad escapes + assertThrows(classOf[IllegalArgumentException], "\\u2022".translateEscapes()) + assertThrows(classOf[IllegalArgumentException], """\z""".translateEscapes()) + assertThrows(classOf[IllegalArgumentException], """\_""".translateEscapes()) + assertThrows(classOf[IllegalArgumentException], """\999""".translateEscapes()) + assertThrows(classOf[IllegalArgumentException], """\""".translateEscapes()) + assertThrows(classOf[IllegalArgumentException], """\ """.translateEscapes()) + assertThrows(classOf[IllegalArgumentException], """ \""".translateEscapes()) + assertThrows(classOf[IllegalArgumentException], """\_\""".translateEscapes()) + assertThrows(classOf[IllegalArgumentException], """\n\""".translateEscapes()) + assertThrows(classOf[IllegalArgumentException], """foo\""".translateEscapes()) + + def oct(s: String): Char = Integer.parseInt(s, 8).toChar + + // octals + assertEquals(s"${oct("333")}", """\333""".translateEscapes()) + assertEquals(s"${oct("12")}", """\12""".translateEscapes()) + assertEquals(s"${oct("77")}", """\77""".translateEscapes()) + assertEquals(s"${oct("42")}", """\42""".translateEscapes()) + assertEquals(s"${oct("0")}", """\0""".translateEscapes()) + assertEquals(s"${oct("00")}", """\00""".translateEscapes()) + assertEquals(s"${oct("000")}", """\000""".translateEscapes()) + assertEquals(s" ${oct("333")}_${oct("333")} ", """ \333_\333 """.translateEscapes()) + assertEquals(s" ${oct("12")}_${oct("12")} ", """ \12_\12 """.translateEscapes()) + assertEquals(s" ${oct("77")}_${oct("77")} ", """ \77_\77 """.translateEscapes()) + assertEquals(s" ${oct("42")}_${oct("42")} ", """ \42_\42 """.translateEscapes()) + assertEquals(s" ${oct("0")}_${oct("0")} ", """ \0_\0 """.translateEscapes()) + assertEquals(s" ${oct("00")}_${oct("00")} ", """ \00_\00 """.translateEscapes()) + assertEquals(s" ${oct("000")}_${oct("000")} ", """ \000_\000 """.translateEscapes()) + assertEquals(s"\t${oct("12")}${oct("34")}${oct("56")}${oct("7")} 89", """\t\12\34\56\7 89""".translateEscapes()) + assertEquals(s" ${oct("111")}1 ", """ \1111 """.translateEscapes()) + assertEquals(s" ${oct("54")}11 ", """ \5411 """.translateEscapes()) + assertEquals(s" ${oct("1")}92 ", """ \192 """.translateEscapes()) + assertEquals(s" ${oct("12")}81 ", """ \1281 """.translateEscapes()) + + // don't discard CR/LF if not preceded by \ + assertEquals("\r", "\r".translateEscapes()) + assertEquals("\n", "\n".translateEscapes()) + assertEquals("\r\n", "\r\n".translateEscapes()) + assertEquals(" \r \n ", " \r \n ".translateEscapes()) + assertEquals(" \r\n ", " \r\n ".translateEscapes()) + + // do discard otherwise + assertEquals("", "\\\n".translateEscapes()) + assertEquals("", "\\\r".translateEscapes()) + assertEquals("", "\\\r\n".translateEscapes()) + assertEquals("", "\\\n\\\n".translateEscapes()) + assertEquals("", "\\\r\\\n".translateEscapes()) + assertEquals(" ", "\\\n \\\n".translateEscapes()) + assertEquals(" ", " \\\n\\\n ".translateEscapes()) + assertEquals(" ", " \\\n".translateEscapes()) + + // expected should look syntactically equivalent to actual but in normal quotes + assertEquals("", """""".translateEscapes()) + assertEquals(" ", """ """.translateEscapes()) + assertEquals("\u2022", """•""".translateEscapes()) + assertEquals("\t\n", """\t\n""".translateEscapes()) + assertEquals("\r\n", """\r\n""".translateEscapes()) + assertEquals("\n\n", """\n\n""".translateEscapes()) + assertEquals("\n\n\n\n0\n\n\n\n0\n\n\n\naaaa\n\n\\", """\n\n\n\n0\n\n\n\n0\n\n\n\naaaa\n\n\\""".translateEscapes()) + assertEquals("a\nb\nc\nd\ne\nf\t", """a\nb\nc\nd\ne\nf\t""".translateEscapes()) + assertEquals("\na", """\na""".translateEscapes()) + assertEquals("\na\n", """\na\n""".translateEscapes()) + assertEquals("a\n", """a\n""".translateEscapes()) + assertEquals("a\nb", """a\nb""".translateEscapes()) + assertEquals("a\nb\n", """a\nb\n""".translateEscapes()) + assertEquals("abcd", """abcd""".translateEscapes()) + assertEquals("\"\' \r\f\n\t\b\\\"\' \r\f\n\t\b\"", """\"\'\s\r\f\n\t\b\\\"\'\s\r\f\n\t\b\"""".translateEscapes()) + assertEquals("\\\\", """\\\\""".translateEscapes()) + assertEquals("\\abcd", """\\abcd""".translateEscapes()) + assertEquals("abcd\\", """abcd\\""".translateEscapes()) + assertEquals("\\abcd\\", """\\abcd\\""".translateEscapes()) + assertEquals("\\\\\\", """\\\\\\""".translateEscapes()) + } + +} 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 74758a139b..2f189e9b65 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 @@ -925,6 +925,16 @@ class RegressionTest { assertEquals(true, a(1)) } + @Test + def anyValMethodWithDefaultParamsOverloadedInCompanion_Issue4583(): Unit = { + assertEquals(5, Bug4583.bar(5)) + assertEquals("hello", Bug4583.bar("hello")) + + val foo = new Bug4583(6) + assertEquals(6, foo.bar()) + assertEquals(8, foo.bar(2)) + } + } object RegressionTest { @@ -988,6 +998,15 @@ object RegressionTest { def foo(x: Int): Int = x + 1 } + class Bug4583(val x: Int) extends AnyVal { + def bar(y: Int = 0): Int = x + y + } + + object Bug4583 { + def bar(x: Int): Int = x + def bar(x: String): 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 diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ByteTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ByteTest.scala index f20010a516..f5b91b3d9b 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ByteTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ByteTest.scala @@ -43,6 +43,22 @@ class ByteTest { assertEquals(0, compare(3.toByte, 3.toByte)) } + @Test def toUnsignedInt(): Unit = { + assertEquals(0, JByte.toUnsignedInt(0.toByte)) + assertEquals(42, JByte.toUnsignedInt(42.toByte)) + assertEquals(214, JByte.toUnsignedInt(-42.toByte)) + assertEquals(128, JByte.toUnsignedInt(Byte.MinValue)) + assertEquals(127, JByte.toUnsignedInt(Byte.MaxValue)) + } + + @Test def toUnsignedLong(): Unit = { + assertEquals(0L, JByte.toUnsignedLong(0.toByte)) + assertEquals(42L, JByte.toUnsignedLong(42.toByte)) + assertEquals(214L, JByte.toUnsignedLong(-42.toByte)) + assertEquals(128L, JByte.toUnsignedLong(Byte.MinValue)) + assertEquals(127L, JByte.toUnsignedLong(Byte.MaxValue)) + } + @Test def parseString(): Unit = { def test(s: String, v: Byte): Unit = { assertEquals(v, JByte.parseByte(s)) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ShortTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ShortTest.scala index b4931f956e..61fbd4078f 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ShortTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ShortTest.scala @@ -43,6 +43,22 @@ class ShortTest { assertEquals(0, compare(3.toShort, 3.toShort)) } + @Test def toUnsignedInt(): Unit = { + assertEquals(0, JShort.toUnsignedInt(0.toShort)) + assertEquals(42, JShort.toUnsignedInt(42.toShort)) + assertEquals(65494, JShort.toUnsignedInt(-42.toShort)) + assertEquals(32768, JShort.toUnsignedInt(Short.MinValue)) + assertEquals(32767, JShort.toUnsignedInt(Short.MaxValue)) + } + + @Test def toUnsignedLong(): Unit = { + assertEquals(0L, JShort.toUnsignedLong(0.toShort)) + assertEquals(42L, JShort.toUnsignedLong(42.toShort)) + assertEquals(65494L, JShort.toUnsignedLong(-42.toShort)) + assertEquals(32768L, JShort.toUnsignedLong(Short.MinValue)) + assertEquals(32767L, JShort.toUnsignedLong(Short.MaxValue)) + } + @Test def parseString(): Unit = { def test(s: String, v: Short): Unit = { assertEquals(v, JShort.parseShort(s)) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/BitSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/BitSetTest.scala new file mode 100644 index 0000000000..c49a68d766 --- /dev/null +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/BitSetTest.scala @@ -0,0 +1,1538 @@ +/* + * 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 java.nio.{ByteBuffer, LongBuffer} +import java.util.BitSet +import org.junit.Assert.{assertThrows => junitAssertThrows, _} +import org.junit.Assume._ +import org.junit.Test +import org.scalajs.testsuite.utils.AssertThrows._ +import org.scalajs.testsuite.utils.Platform._ + +class BitSetTest { + @Test def test_Constructor_empty(): Unit = { + val bs = new BitSet + + assertEquals("Create BitSet of incorrect size", 64, bs.size()) + assertEquals("New BitSet had invalid string representation", "{}", bs.toString()) + } + + @Test def test_Constructor_Int(): Unit = { + var bs = new BitSet(128) + + assertEquals("Create BitSet of incorrect size", 128, bs.size()) + assertEquals("New BitSet had invalid string representation: " + bs.toString, "{}", bs.toString()) + + // All BitSets are created with elements of multiples of 64 on jvm, 32 in JS + bs = new BitSet(89) + + if (executingInJVM) { + assertEquals("Failed to round BitSet element size", 128, bs.size()) + } else { + assertEquals("Failed to round BitSet element size", 96, bs.size()) + } + + // "Failed to throw exception when creating a new BitSet with negative element value" + assertThrows(classOf[NegativeArraySizeException], new BitSet(-9)) + } + + @Test def test_clone(): Unit = { + val eightbs: BitSet = makeEightBS() + val bs: BitSet = eightbs.clone.asInstanceOf[BitSet] + assertEquals("clone failed to return equal BitSet", bs, eightbs) + } + + @Test def test_equals(): Unit = { + val eightbs: BitSet = makeEightBS() + var bs: BitSet = makeEightBS() + assertEquals("Same BitSet returned false", eightbs, eightbs) + assertEquals("Identical BitSet returned false", bs, eightbs) + bs.clear(6) + assertFalse("Different BitSets returned true", eightbs == bs) + + bs = makeEightBS() + bs.set(128) + assertFalse("Different sized BitSet with higher bit set returned true", eightbs == bs) + bs.clear(128) + assertTrue("Different sized BitSet with higher bits not set returned false", eightbs == bs) + } + + @Test def test_hashCode(): Unit = { + val bs: BitSet = makeEightBS() + bs.clear(2) + bs.clear(6) + assertEquals("BitSet returns wrong hash value", 1129, bs.hashCode) + bs.set(10) + bs.clear(3) + assertEquals("BitSet returns wrong hash value", 97, bs.hashCode) + } + + @Test def test_clear(): Unit = { + val eightbs = makeEightBS() + eightbs.clear() + + for (i <- 0 until 8) + assertFalse("Clear didn't clear bit " + i, eightbs.get(i)) + + assertEquals("Test1: Wrong length", 0, eightbs.length()) + val bs = new BitSet(3400) + bs.set(0, bs.size - 1) // ensure all bits are 1's + + bs.set(bs.size - 1) + bs.clear() + assertEquals("Test2: Wrong length", 0, bs.length()) + assertTrue("Test2: isEmpty() returned incorrect value", bs.isEmpty()) + assertEquals("Test2: cardinality() returned incorrect value", 0, bs.cardinality()) + } + + @Test def test_clear_bitIndex(): Unit = { + val eightbs = makeEightBS() + eightbs.clear(7) + assertFalse("Failed to clear bit", eightbs.get(7)) + + // Check to see all other bits are still set + for (i <- 0 until 7) + assertTrue("Clear cleared incorrect bits", eightbs.get(i)) + + eightbs.clear(165) + assertFalse("Failed to clear bit", eightbs.get(165)) + // Try out of range + assertThrows(classOf[IndexOutOfBoundsException], eightbs.clear(-1)) + + val bs = new BitSet(0) + assertEquals("Test1: Wrong length,", 0, bs.length()) + assertEquals("Test1: Wrong size,", 0, bs.size()) + bs.clear(0) + assertEquals("Test2: Wrong length,", 0, bs.length()) + assertEquals("Test2: Wrong size,", 0, bs.size()) + bs.clear(60) + assertEquals("Test3: Wrong length,", 0, bs.length()) + assertEquals("Test3: Wrong size,", 0, bs.size()) + bs.clear(120) + assertEquals("Test4: Wrong size,", 0, bs.size()) + assertEquals("Test4: Wrong length,", 0, bs.length()) + bs.set(25) + if (executingInJVM) { + assertEquals("Test5: Wrong size,", 64, bs.size()) + } else { + assertEquals("Test5: Wrong size,", 32, bs.size()) + } + + assertEquals("Test5: Wrong length,", 26, bs.length()) + bs.clear(80) + + if (executingInJVM) { + assertEquals("Test6: Wrong size,", 64, bs.size()) + } else { + assertEquals("Test6: Wrong size,", 32, bs.size()) + } + + assertEquals("Test6: Wrong length,", 26, bs.length()) + bs.clear(25) + + if (executingInJVM) { + assertEquals("Test7: Wrong size,", 64, bs.size()) + } else { + assertEquals("Test7: Wrong size,", 32, bs.size()) + } + + assertEquals("Test7: Wrong length,", 0, bs.length()) + } + + @Test def test_clear_fromIndex_toIndex(): Unit = { + val bitset = new BitSet + for (i <- 0 until 20) + bitset.set(i) + + bitset.clear(10, 10) + // Test for method void java.BitSet.clear(int, int) + // pos1 and pos2 are in the same bitset element + var bs = new BitSet(16) + var initialSize = bs.size + bs.set(0, initialSize) + bs.clear(5) + bs.clear(15) + bs.clear(7, 11) + + for (i <- 0 until 7) { + if (i == 5) + assertFalse("Shouldn't have flipped bit " + i, bs.get(i)) + else + assertTrue("Shouldn't have cleared bit " + i, bs.get(i)) + } + + for (i <- 7 until 11) + assertFalse("Failed to clear bit " + i, bs.get(i)) + + for (i <- 11 until initialSize) { + if (i == 15) + assertFalse("Shouldn't have flipped bit " + i, bs.get(i)) + else + assertTrue("Shouldn't have cleared bit " + i, bs.get(i)) + } + + for (i <- initialSize until bs.size()) + assertFalse("Shouldn't have flipped bit " + i, bs.get(i)) + + // pos1 and pos2 is in the same bitset element, boundry testing + bs = new BitSet(16) + initialSize = bs.size + bs.set(0, initialSize) + bs.clear(7, 64) + + if (executingInJVM) { + assertEquals("Failed to grow BitSet", 64, bs.size()) + } else { + assertEquals("Failed to grow BitSet", 32, bs.size()) + } + + for (i <- 0 until 7) + assertTrue("Shouldn't have cleared bit " + i, bs.get(i)) + + for (i <- 7 until 64) + assertFalse("Failed to clear bit " + i, bs.get(i)) + + for (i <- 64 until bs.size()) + assertTrue("Shouldn't have flipped bit " + i, !bs.get(i)) + + // more boundary testing + bs = new BitSet(32) + initialSize = bs.size + bs.set(0, initialSize) + bs.clear(0, 64) + + for (i <- 0 until 64) + assertFalse("Failed to clear bit " + i, bs.get(i)) + + for (i <- 64 until bs.size()) + assertFalse("Shouldn't have flipped bit " + i, bs.get(i)) + + bs = new BitSet(32) + initialSize = bs.size + bs.set(0, initialSize) + bs.clear(0, 65) + + for (i <- 0 until 65) + assertFalse("Failed to clear bit " + i, bs.get(i)) + + for (i <- 65 until bs.size()) + assertFalse("Shouldn't have flipped bit " + i, bs.get(i)) + + // pos1 and pos2 are in two sequential bitset elements + bs = new BitSet(128) + initialSize = bs.size + bs.set(0, initialSize) + bs.clear(7) + bs.clear(110) + bs.clear(9, 74) + + for (i <- 0 until 9) { + if (i == 7) + assertFalse("Shouldn't have flipped bit " + i, bs.get(i)) + else + assertTrue("Shouldn't have cleared bit " + i, bs.get(i)) + } + + for (i <- 9 until 74) + assertFalse("Failed to clear bit " + i, bs.get(i)) + + for (i <- 74 until initialSize) { + if (i == 110) + assertFalse("Shouldn't have flipped bit " + i, bs.get(i)) + else + assertTrue("Shouldn't have cleared bit " + i, bs.get(i)) + } + + for (i <- initialSize until bs.size()) + assertFalse("Shouldn't have flipped bit " + i, bs.get(i)) + + // pos1 and pos2 are in two non-sequential bitset elements + bs = new BitSet(256) + bs.set(0, 256) + bs.clear(7) + bs.clear(255) + bs.clear(9, 219) + + for (i <- 0 until 9) { + if (i == 7) + assertFalse("Shouldn't have flipped bit " + i, bs.get(i)) + else + assertTrue("Shouldn't have cleared bit " + i, bs.get(i)) + } + + for (i <- 9 until 219) + assertFalse("failed to clear bit " + i, bs.get(i)) + + for (i <- 219 until 255) + assertTrue("Shouldn't have cleared bit " + i, bs.get(i)) + + for (i <- 255 until bs.size()) + assertFalse("Shouldn't have flipped bit " + i, bs.get(i)) + + // test illegal args + bs = new BitSet(10) + // "Test1: Attempt to flip with negative index failed to generate exception" + assertThrows(classOf[IndexOutOfBoundsException], bs.clear(-1, 3)) + // "Test2: Attempt to flip with negative index failed to generate exception" + assertThrows(classOf[IndexOutOfBoundsException], bs.clear(2, -1)) + + bs.set(2, 4) + bs.clear(2, 2) + assertTrue("Bit got cleared incorrectly ", bs.get(2)) + + // "Test4: Attempt to flip with illegal args failed to generate exception" + assertThrows(classOf[IndexOutOfBoundsException], bs.clear(4, 2)) + + bs = new BitSet(0) + assertEquals("Test1: Wrong length,", 0, bs.length()) + assertEquals("Test1: Wrong size,", 0, bs.size()) + + bs.clear(0, 2) + assertEquals("Test2: Wrong length,", 0, bs.length()) + assertEquals("Test2: Wrong size,", 0, bs.size()) + + bs.clear(60, 64) + assertEquals("Test3: Wrong length,", 0, bs.length()) + assertEquals("Test3: Wrong size,", 0, bs.size()) + + bs.clear(64, 120) + assertEquals("Test4: Wrong length,", 0, bs.length()) + assertEquals("Test4: Wrong size,", 0, bs.size()) + + bs.set(25) + assertEquals("Test5: Wrong length,", 26, bs.length()) + + if (executingInJVM) { + assertEquals("Test5: Wrong size,", 64, bs.size()) + } else { + assertEquals("Test5: Wrong size,", 32, bs.size()) + } + + bs.clear(60, 64) + assertEquals("Test6: Wrong length,", 26, bs.length()) + + if (executingInJVM) { + assertEquals("Test6: Wrong size,", 64, bs.size()) + } else { + assertEquals("Test6: Wrong size,", 32, bs.size()) + } + + bs.clear(64, 120) + + if (executingInJVM) { + assertEquals("Test7: Wrong size,", 64, bs.size()) + } else { + assertEquals("Test7: Wrong size,", 32, bs.size()) + } + + assertEquals("Test7: Wrong length,", 26, bs.length()) + + bs.clear(80) + + if (executingInJVM) { + assertEquals("Test8: Wrong size,", 64, bs.size()) + } else { + assertEquals("Test8: Wrong size,", 32, bs.size()) + } + + assertEquals("Test8: Wrong length,", 26, bs.length()) + + bs.clear(25) + if (executingInJVM) { + assertEquals("Test9: Wrong size,", 64, bs.size()) + } else { + assertEquals("Test9: Wrong size,", 32, bs.size()) + } + + assertEquals("Test9: Wrong length,", 0, bs.length()) + } + + @Test def test_get_bitIndex(): Unit = { + val eightbs = makeEightBS() + var bs = new BitSet + bs.set(8) + assertFalse("Get returned true for index out of range", eightbs.get(99)) + assertTrue("Get returned false for set value", eightbs.get(3)) + assertFalse("Get returned true for a non set value", bs.get(0)) + + // "Attempt to get at negative index failed to generate exception" + assertThrows(classOf[IndexOutOfBoundsException], bs.get(-1)) + + bs = new BitSet(1) + assertFalse("Access greater than size", bs.get(64)) + + bs = new BitSet + bs.set(63) + assertTrue("Test highest bit", bs.get(63)) + + bs = new BitSet(0) + assertEquals("Test1: Wrong length,", 0, bs.length()) + assertEquals("Test1: Wrong size,", 0, bs.size()) + + bs.get(2) + assertEquals("Test2: Wrong length,", 0, bs.length()) + assertEquals("Test2: Wrong size,", 0, bs.size()) + + bs.get(70) + assertEquals("Test3: Wrong length,", 0, bs.length()) + assertEquals("Test3: Wrong size,", 0, bs.size()) + } + + @Test def test_get_fromIndex_toIndex(): Unit = { + val bitset = new BitSet(30) + bitset.get(3, 3) + + var bs: BitSet = new BitSet(512) + var resultbs: BitSet = null + var correctbs: BitSet = null + + bs.set(3, 9) + bs.set(10, 20) + bs.set(60, 75) + bs.set(121) + bs.set(130, 140) + + // pos1 and pos2 are in the same bitset element, at index0 + resultbs = bs.get(3, 6) + correctbs = new BitSet(3) + correctbs.set(0, 3) + assertEquals("Test1: Returned incorrect BitSet", correctbs, resultbs) + // pos1 and pos2 are in the same bitset element, at index 1 + resultbs = bs.get(100, 125) + correctbs = new BitSet(25) + correctbs.set(21) + assertEquals("Test2: Returned incorrect BitSet", correctbs, resultbs) + // pos1 in bitset element at index 0, and pos2 in bitset element at + // index 1 + resultbs = bs.get(15, 125) + correctbs = new BitSet(25) + correctbs.set(0, 5) + correctbs.set(45, 60) + correctbs.set(121 - 15) + assertEquals("Test3: Returned incorrect BitSet", correctbs, resultbs) + // pos1 in bitset element at index 1, and pos2 in bitset element at + // index 2 + resultbs = bs.get(70, 145) + correctbs = new BitSet(75) + correctbs.set(0, 5) + correctbs.set(51) + correctbs.set(60, 70) + assertEquals("Test4: Returned incorrect BitSet", correctbs, resultbs) + resultbs = bs.get(5, 145) + correctbs = new BitSet(140) + correctbs.set(0, 4) + correctbs.set(5, 15) + correctbs.set(55, 70) + correctbs.set(116) + correctbs.set(125, 135) + assertEquals("Test5: Returned incorrect BitSet", correctbs, resultbs) + // index 3 + resultbs = bs.get(5, 250) + correctbs = new BitSet(200) + correctbs.set(0, 4) + correctbs.set(5, 15) + correctbs.set(55, 70) + correctbs.set(116) + correctbs.set(125, 135) + assertEquals("Test6: Returned incorrect BitSet", correctbs, resultbs) + assertEquals("equality principle 1 ", bs.get(0, bs.size()), bs) + // more tests + var bs2 = new BitSet(129) + bs2.set(0, 20) + bs2.set(62, 65) + bs2.set(121, 123) + resultbs = bs2.get(1, 124) + correctbs = new BitSet(129) + correctbs.set(0, 19) + correctbs.set(61, 64) + correctbs.set(120, 122) + assertEquals("Test7: Returned incorrect BitSet", correctbs, resultbs) + // equality principle with some boundary conditions + bs2 = new BitSet(128) + bs2.set(2, 20) + bs2.set(62) + bs2.set(121, 123) + bs2.set(127) + resultbs = bs2.get(0, bs2.size()) + assertEquals("equality principle 2 ", resultbs, bs2) + bs2 = new BitSet(128) + bs2.set(2, 20) + bs2.set(62) + bs2.set(121, 123) + bs2.set(127) + bs2.flip(0, 128) + resultbs = bs2.get(0, bs.size()) + assertEquals("equality principle 3 ", resultbs, bs2) + bs = new BitSet(0) + assertEquals("Test1: Wrong length,", 0, bs.length()) + assertEquals("Test1: Wrong size,", 0, bs.size()) + bs.get(0, 2) + assertEquals("Test2: Wrong length,", 0, bs.length()) + assertEquals("Test2: Wrong size,", 0, bs.size()) + bs.get(60, 64) + assertEquals("Test3: Wrong length,", 0, bs.length()) + assertEquals("Test3: Wrong size,", 0, bs.size()) + bs.get(64, 120) + assertEquals("Test4: Wrong length,", 0, bs.length()) + assertEquals("Test4: Wrong size,", 0, bs.size()) + bs.set(25) + assertEquals("Test5: Wrong length,", 26, bs.length()) + + if (executingInJVM) { + assertEquals("Test5: Wrong size,", 64, bs.size()) + } else { + assertEquals("Test5: Wrong size,", 32, bs.size()) + } + + bs.get(60, 64) + assertEquals("Test6: Wrong length,", 26, bs.length()) + + if (executingInJVM) { + assertEquals("Test6: Wrong size,", 64, bs.size()) + } else { + assertEquals("Test6: Wrong size,", 32, bs.size()) + } + + bs.get(64, 120) + + if (executingInJVM) { + assertEquals("Test7: Wrong size,", 64, bs.size()) + } else { + assertEquals("Test7: Wrong size,", 32, bs.size()) + } + + assertEquals("Test7: Wrong length,", 26, bs.length()) + bs.get(80) + + if (executingInJVM) { + assertEquals("Test8: Wrong size,", 64, bs.size()) + } else { + assertEquals("Test8: Wrong size,", 32, bs.size()) + } + + assertEquals("Test8: Wrong length,", 26, bs.length()) + bs.get(25) + + if (executingInJVM) { + assertEquals("Test9: Wrong size,", 64, bs.size()) + } else { + assertEquals("Test9: Wrong size,", 32, bs.size()) + } + + assertEquals("Test9: Wrong length,", 26, bs.length()) + } + + @Test def test_flip_bitIndex(): Unit = { + val eightbs = makeEightBS() + var bs = new BitSet + bs.clear(8) + bs.clear(9) + bs.set(10) + bs.flip(9) + assertFalse("Failed to flip bit", bs.get(8)) + assertTrue("Failed to flip bit", bs.get(9)) + assertTrue("Failed to flip bit", bs.get(10)) + bs.set(8) + bs.set(9) + bs.clear(10) + bs.flip(9) + assertTrue("Failed to flip bit", bs.get(8)) + assertFalse("Failed to flip bit", bs.get(9)) + assertFalse("Failed to flip bit", bs.get(10)) + + // "Attempt to flip at negative index failed to generate exception" + assertThrows(classOf[IndexOutOfBoundsException], bs.flip(-1)) + + // Try setting a bit on a 64 boundary + bs.flip(128) + + if (executingInJVM) { + assertEquals("Failed to grow BitSet", 192, bs.size()) + } else { + assertEquals("Failed to grow BitSet", 160, bs.size()) + } + + assertTrue("Failed to flip bit", bs.get(128)) + + bs = new BitSet(64) + var i = bs.size - 1 + while (i >= 0) { + bs.flip(i) + assertTrue("Test1: Incorrectly flipped bit" + i, bs.get(i)) + assertEquals("Incorrect length", i + 1, bs.length()) + var j: Int = bs.size + while ({j -= 1; j} > i) + assertTrue("Test2: Incorrectly flipped bit" + j, !bs.get(j)) + + j = i + while ({j -= 1; j} >= 0) + assertTrue("Test3: Incorrectly flipped bit" + j, !bs.get(j)) + + bs.flip(i) + i -= 1 + } + + val bs0 = new BitSet(0) + assertEquals("Test1: Wrong size", 0, bs0.size()) + assertEquals("Test1: Wrong length", 0, bs0.length()) + + bs0.flip(0) + + if (executingInJVM) { + assertEquals("Test2: Wrong size", 64, bs0.size()) + } else { + assertEquals("Test2: Wrong size", 32, bs0.size()) + } + + assertEquals("Test2: Wrong length", 1, bs0.length()) + + bs0.flip(63) + assertEquals("Test3: Wrong size", 64, bs0.size()) + assertEquals("Test3: Wrong length", 64, bs0.length()) + + eightbs.flip(7) + assertTrue("Failed to flip bit 7", !eightbs.get(7)) + + for (i <- 0 until 7) + assertTrue("Flip flipped incorrect bits", eightbs.get(i)) + + eightbs.flip(127) + assertTrue("Failed to flip bit 127", eightbs.get(127)) + + eightbs.flip(127) + assertTrue("Failed to flip bit 127", !eightbs.get(127)) + } + + @Test def test_flip_fromIndex_toIndex(): Unit = { + val bitset = new BitSet + for (i <- 0 until 20) + bitset.set(i) + + bitset.flip(10, 10) + // Test for method void java.BitSet.flip(int, int) + var bs = new BitSet(16) + bs.set(7) + bs.set(10) + bs.flip(7, 11) + + for (i <- 0 until 7) + assertTrue("Shouldn't have flipped bit " + i, !bs.get(i)) + + assertFalse("Failed to flip bit 7", bs.get(7)) + assertTrue("Failed to flip bit 8", bs.get(8)) + assertTrue("Failed to flip bit 9", bs.get(9)) + assertFalse("Failed to flip bit 10", bs.get(10)) + + for (i <- 11 until bs.size()) + assertTrue("Shouldn't have flipped bit " + i, !bs.get(i)) + + bs = new BitSet(16) + bs.set(7) + bs.set(10) + bs.flip(7, 64) + assertEquals("Failed to grow BitSet", 64, bs.size()) + + for (i <- 0 until 7) + assertTrue("Shouldn't have flipped bit " + i, !bs.get(i)) + + assertFalse("Failed to flip bit 7", bs.get(7)) + assertTrue("Failed to flip bit 8", bs.get(8)) + assertTrue("Failed to flip bit 9", bs.get(9)) + assertFalse("Failed to flip bit 10", bs.get(10)) + + for (i <- 11 until 64) + assertTrue("failed to flip bit " + i, bs.get(i)) + + assertFalse("Shouldn't have flipped bit 64", bs.get(64)) + + bs = new BitSet(32) + bs.flip(0, 64) + + for (i <- 0 until 64) + assertTrue("Failed to flip bit " + i, bs.get(i)) + + assertFalse("Shouldn't have flipped bit 64", bs.get(64)) + + bs = new BitSet(32) + bs.flip(0, 65) + + for (i <- 0 until 65) + assertTrue("Failed to flip bit " + i, bs.get(i)) + + assertFalse("Shouldn't have flipped bit 65", bs.get(65)) + + bs = new BitSet(128) + bs.set(7) + bs.set(10) + bs.set(72) + bs.set(110) + bs.flip(9, 74) + + for (i <- 0 until 7) + assertFalse("Shouldn't have flipped bit " + i, bs.get(i)) + + assertTrue("Shouldn't have flipped bit 7", bs.get(7)) + assertFalse("Shouldn't have flipped bit 8", bs.get(8)) + assertTrue("Failed to flip bit 9", bs.get(9)) + assertFalse("Failed to flip bit 10", bs.get(10)) + + for (i <- 11 until 72) + assertTrue("failed to flip bit " + i, bs.get(i)) + + assertFalse("Failed to flip bit 72", bs.get(72)) + assertTrue("Failed to flip bit 73", bs.get(73)) + + for (i <- 74 until 110) + assertFalse("Shouldn't have flipped bit " + i, bs.get(i)) + + assertTrue("Shouldn't have flipped bit 110", bs.get(110)) + + for (i <- 111 until bs.size()) + assertFalse("Shouldn't have flipped bit " + i, bs.get(i)) + + bs = new BitSet(256) + bs.set(7) + bs.set(10) + bs.set(72) + bs.set(110) + bs.set(181) + bs.set(220) + bs.flip(9, 219) + + for (i <- 0 until 7) + assertFalse("Shouldn't have flipped bit " + i, bs.get(i)) + + assertTrue("Shouldn't have flipped bit 7", bs.get(7)) + assertFalse("Shouldn't have flipped bit 8", bs.get(8)) + assertTrue("Failed to flip bit 9", bs.get(9)) + assertFalse("Failed to flip bit 10", bs.get(10)) + + for (i <- 11 until 72) + assertTrue("failed to flip bit " + i, bs.get(i)) + + assertFalse("Failed to flip bit 72", bs.get(72)) + + for (i <- 73 until 110) + assertTrue("failed to flip bit " + i, bs.get(i)) + + assertFalse("Failed to flip bit 110", bs.get(110)) + + for (i <- 111 until 181) + assertTrue("failed to flip bit " + i, bs.get(i)) + + assertFalse("Failed to flip bit 181", bs.get(181)) + + for (i <- 182 until 219) + assertTrue("failed to flip bit " + i, bs.get(i)) + + assertFalse("Shouldn't have flipped bit 219", bs.get(219)) + assertTrue("Shouldn't have flipped bit 220", bs.get(220)) + + for (i <- 221 until bs.size()) + assertTrue("Shouldn't have flipped bit " + i, !bs.get(i)) + + bs = new BitSet(10) + // "Test1: Attempt to flip with negative index failed to generate exception" + assertThrows(classOf[IndexOutOfBoundsException], bs.flip(-1, 3)) + + // "Test2: Attempt to flip with negative index failed to generate exception" + assertThrows(classOf[IndexOutOfBoundsException], bs.flip(2, -1)) + + // "Test4: Attempt to flip with illegal args failed to generate exception" + assertThrows(classOf[IndexOutOfBoundsException], bs.flip(4, 2)) + } + + @Test def test_set_bitIndex(): Unit = { + var bs = new BitSet + bs.set(8) + assertTrue("Failed to set bit", bs.get(8)) + + // "Attempt to set at negative index failed to generate exception" + assertThrows(classOf[IndexOutOfBoundsException], bs.set(-1)) + + bs.set(128) + if (executingInJVM) { + assertEquals("Failed to grow BitSet", 192, bs.size()) + } else { + assertEquals("Failed to grow BitSet", 160, bs.size()) + } + + assertTrue("Failed to set bit", bs.get(128)) + + bs = new BitSet(64) + var i = bs.size + while ({i -= 1; i} >= 0) { + bs.set(i) + assertTrue("Incorrectly set", bs.get(i)) + assertEquals("Incorrect length", i + 1, bs.length()) + + var j = bs.size + while ({j -= 1; j} > i) + assertFalse("Incorrectly set bit " + j, bs.get(j)) + + var k = i + while ({k -= 1; k} >= 0) + assertFalse("Incorrectly set bit " + k, bs.get(k)) + + bs.clear(i) + } + + bs = new BitSet(0) + assertEquals("Test1: Wrong length", 0, bs.length()) + + bs.set(0) + assertEquals("Test2: Wrong length", 1, bs.length()) + } + + @Test def set_bitIndex_bool(): Unit = { + val eightbs = makeEightBS() + + eightbs.set(5, false) + assertFalse("Should have set bit 5 to true", eightbs.get(5)) + + eightbs.set(5, true) + assertTrue("Should have set bit 5 to false", eightbs.get(5)) + } + + @Test def set_fromIndex_toIndex(): Unit = { + val bitset = new BitSet(30) + bitset.set(29, 29) + + var bs = new BitSet(16) + bs.set(5) + bs.set(15) + bs.set(7, 11) + + for (i <- 0 until 7) { + if (i == 5) + assertTrue("Shouldn't have flipped bit " + i, bs.get(i)) + else + assertFalse("Shouldn't have set bit " + i, bs.get(i)) + } + + for (i <- 7 until 11) + assertTrue("Failed to set bit " + i, bs.get(i)) + + for (i <- 11 until bs.size()) { + if (i == 15) + assertTrue("Shouldn't have flipped bit " + i, bs.get(i)) + else + assertFalse("Shouldn't have set bit " + i, bs.get(i)) + } + + bs = new BitSet(16) + bs.set(7, 64) + assertEquals("Failed to grow BitSet", 64, bs.size()) + + for (i <- 0 until 7) + assertFalse("Shouldn't have set bit " + i, bs.get(i)) + + for (i <- 7 until 64) + assertTrue("Failed to set bit " + i, bs.get(i)) + + assertFalse("Shouldn't have set bit 64", bs.get(64)) + + bs = new BitSet(32) + bs.set(0, 64) + + for (i <- 0 until 64) + assertTrue("Failed to set bit " + i, bs.get(i)) + + assertFalse("Shouldn't have set bit 64", bs.get(64)) + + bs = new BitSet(32) + bs.set(0, 65) + + for (i <- 0 until 65) + assertTrue("Failed to set bit " + i, bs.get(i)) + + assertFalse("Shouldn't have set bit 65", bs.get(65)) + + bs = new BitSet(128) + bs.set(7) + bs.set(110) + bs.set(9, 74) + + for (i <- 0 until 9) { + if (i == 7) + assertTrue("Shouldn't have flipped bit " + i, bs.get(i)) + else + assertFalse("Shouldn't have set bit " + i, bs.get(i)) + } + + for (i <- 9 until 74) + assertTrue("Failed to set bit " + i, bs.get(i)) + + for (i <- 74 until bs.size()) { + if (i == 110) + assertTrue("Shouldn't have flipped bit " + i, bs.get(i)) + else + assertFalse("Shouldn't have set bit " + i, bs.get(i)) + } + + bs = new BitSet(256) + bs.set(7) + bs.set(255) + bs.set(9, 219) + + for (i <- 0 until 9) { + if (i == 7) + assertTrue("Shouldn't have set flipped " + i, bs.get(i)) + else + assertFalse("Shouldn't have set bit " + i, bs.get(i)) + } + + for (i <- 9 until 219) + assertTrue("failed to set bit " + i, bs.get(i)) + + for (i <- 219 until 255) + assertFalse("Shouldn't have set bit " + i, bs.get(i)) + + assertTrue("Shouldn't have flipped bit 255", bs.get(255)) + + bs = new BitSet(10) + // "Test1: Attempt to flip with negative index failed to generate exception" + assertThrows(classOf[IndexOutOfBoundsException], bs.set(-1, 3)) + + // "Test2: Attempt to flip with negative index failed to generate exception" + assertThrows(classOf[IndexOutOfBoundsException], bs.set(2, -1)) + + bs.set(2, 2) + assertFalse("Bit got set incorrectly ", bs.get(2)) + + // "Test4: Attempt to flip with illegal args failed to generate exception" + assertThrows(classOf[IndexOutOfBoundsException], bs.set(4, 2)) + } + + @Test def set_fromIndex_toIndex_bool(): Unit = { + val eightbs = makeEightBS() + + eightbs.set(3, 6, false) + assertTrue("Should have set bits 3, 4, and 5 to false", + !eightbs.get(3) && !eightbs.get(4) && !eightbs.get(5)) + eightbs.set(3, 6, true) + assertTrue("Should have set bits 3, 4, and 5 to true", + eightbs.get(3) && eightbs.get(4) && eightbs.get(5)) + } + + @Test def intersects(): Unit = { + val bs = new BitSet(500) + bs.set(5) + bs.set(63) + bs.set(64) + bs.set(71, 110) + bs.set(127, 130) + bs.set(192) + bs.set(450) + val bs2 = new BitSet(8) + assertFalse(bs.intersects(bs2)) + assertFalse(bs2.intersects(bs)) + + bs2.set(4) + assertFalse(bs.intersects(bs2)) + assertFalse(bs2.intersects(bs)) + + bs2.clear() + bs2.set(5) + assertTrue(bs.intersects(bs2)) + assertTrue(bs2.intersects(bs)) + + bs2.clear() + bs2.set(63) + assertTrue(bs.intersects(bs2)) + assertTrue(bs2.intersects(bs)) + + bs2.clear() + bs2.set(80) + assertTrue(bs.intersects(bs2)) + assertTrue(bs2.intersects(bs)) + + bs2.clear() + bs2.set(127) + assertTrue(bs.intersects(bs2)) + assertTrue(bs2.intersects(bs)) + + bs2.clear() + bs2.set(192) + assertTrue(bs.intersects(bs2)) + assertTrue(bs2.intersects(bs)) + + bs2.clear() + bs2.set(450) + assertTrue(bs.intersects(bs2)) + assertTrue(bs2.intersects(bs)) + + bs2.clear() + bs2.set(500) + assertFalse(bs.intersects(bs2)) + assertFalse(bs2.intersects(bs)) + } + + @Test def and(): Unit = { + val eightbs = makeEightBS() + val bs = new BitSet(128) + + // Initialize the bottom half of the BitSet + for (i <- 64 until 128) + bs.set(i) + + eightbs.and(bs) + assertFalse(eightbs == bs) + eightbs.set(3) + bs.set(3) + eightbs.and(bs) + assertTrue(bs.get(3)) + bs.and(eightbs) + + for (i <- 64 until 128) + assertFalse(bs.get(i)) + } + + def andNot(): Unit = { + var bs = makeEightBS() + bs.clear(5) + val bs2 = new BitSet + bs2.set(2) + bs2.set(3) + bs.andNot(bs2) + assertEquals("{0, 1, 4, 6, 7}", bs.toString()) + bs = new BitSet(0) + bs.andNot(bs2) + assertEquals(0, bs.size()) + } + + @Test def or(): Unit = { + val eightbs = makeEightBS() + var bs = new BitSet(128) + bs.or(eightbs) + + for (i <- 0 until 8) + assertTrue("OR failed to set bits", bs.get(i)) + + bs = new BitSet(0) + bs.or(eightbs) + + for (i <- 0 until 8) + assertTrue(bs.get(i)) + + eightbs.clear(5) + bs = new BitSet(128) + bs.or(eightbs) + assertFalse(bs.get(5)) + } + + @Test def xor(): Unit = { + val eightbs = makeEightBS() + var bs = makeEightBS() + bs.xor(eightbs) + + for (i <- 0 until 8) + assertFalse(bs.get(i)) + + bs.xor(eightbs) + + for (i <- 0 until 8) + assertTrue(bs.get(i)) + + bs = new BitSet(0) + bs.xor(eightbs) + + for (i <- 0 until 8) + assertTrue(bs.get(i)) + + bs = new BitSet + bs.set(63) + assertEquals("{63}", bs.toString()) + } + + @Test def size(): Unit = { + val eightbs = makeEightBS() + assertEquals(64, eightbs.size()) + eightbs.set(129) + assertTrue(eightbs.size >= 129) + } + + @Test def toStringTest(): Unit = { + val bs = new BitSet + assertEquals("{}", bs.toString()) + + bs.set(0) + assertEquals("{0}", bs.toString()) + + bs.clear(0) + assertEquals("{}", bs.toString()) + + bs.set(0) + bs.set(100) + bs.set(200) + bs.set(400) + assertEquals("{0, 100, 200, 400}", bs.toString()) + + bs.clear(200) + assertEquals("{0, 100, 400}", bs.toString()) + + bs.set(500) + assertEquals("{0, 100, 400, 500}", bs.toString()) + } + + @Test def length(): Unit = { + val bs = new BitSet + assertEquals(0, bs.length()) + bs.set(5) + assertEquals(6, bs.length()) + bs.set(10) + assertEquals(11, bs.length()) + bs.set(432) + assertEquals(433, bs.length()) + bs.set(300) + assertEquals(433, bs.length()) + } + + @Test def previousClearBit(): Unit = { + val bs = new BitSet(500) + bs.set(5) + bs.set(32) + bs.set(63) + bs.set(64) + bs.set(71, 110) + bs.set(127, 130) + bs.set(193) + bs.set(450) + + assertEquals(-1, bs.previousClearBit(-1)) + assertThrows(classOf[IndexOutOfBoundsException], bs.previousClearBit(-2)) + + assertEquals(0, bs.previousClearBit(0)) + assertEquals(4, bs.previousClearBit(4)) + assertEquals(4, bs.previousClearBit(5)) + assertEquals(6, bs.previousClearBit(6)) + + assertEquals(31, bs.previousClearBit(31)) + assertEquals(31, bs.previousClearBit(32)) + assertEquals(33, bs.previousClearBit(33)) + + assertEquals(62, bs.previousClearBit(62)) + assertEquals(62, bs.previousClearBit(63)) + assertEquals(62, bs.previousClearBit(64)) + assertEquals(65, bs.previousClearBit(65)) + + assertEquals(70, bs.previousClearBit(70)) + assertEquals(70, bs.previousClearBit(71)) + assertEquals(70, bs.previousClearBit(80)) + assertEquals(70, bs.previousClearBit(109)) + assertEquals(110, bs.previousClearBit(110)) + assertEquals(111, bs.previousClearBit(111)) + + assertEquals(126, bs.previousClearBit(126)) + assertEquals(126, bs.previousClearBit(127)) + assertEquals(126, bs.previousClearBit(128)) + assertEquals(126, bs.previousClearBit(129)) + assertEquals(130, bs.previousClearBit(130)) + + assertEquals(192, bs.previousClearBit(192)) + assertEquals(192, bs.previousClearBit(193)) + assertEquals(194, bs.previousClearBit(194)) + + assertEquals(255, bs.previousClearBit(255)) + assertEquals(256, bs.previousClearBit(256)) + + assertEquals(449, bs.previousClearBit(449)) + assertEquals(449, bs.previousClearBit(450)) + assertEquals(451, bs.previousClearBit(451)) + + assertEquals(500, bs.previousClearBit(500)) + assertEquals(800, bs.previousClearBit(800)) + } + + @Test def previousSetBit(): Unit = { + val bs = new BitSet(500) + bs.set(5) + bs.set(32) + bs.set(63) + bs.set(64) + bs.set(71, 110) + bs.set(127, 130) + bs.set(193) + bs.set(450) + + assertEquals(-1, bs.previousSetBit(-1)) + assertThrows(classOf[IndexOutOfBoundsException], bs.previousSetBit(-2)) + + assertEquals(-1, bs.previousSetBit(0)) + assertEquals(-1, bs.previousSetBit(4)) + assertEquals(5, bs.previousSetBit(5)) + assertEquals(5, bs.previousSetBit(6)) + + assertEquals(5, bs.previousSetBit(31)) + assertEquals(32, bs.previousSetBit(32)) + assertEquals(32, bs.previousSetBit(33)) + + assertEquals(32, bs.previousSetBit(62)) + assertEquals(63, bs.previousSetBit(63)) + assertEquals(64, bs.previousSetBit(64)) + assertEquals(64, bs.previousSetBit(65)) + + assertEquals(64, bs.previousSetBit(70)) + assertEquals(71, bs.previousSetBit(71)) + assertEquals(72, bs.previousSetBit(72)) + assertEquals(80, bs.previousSetBit(80)) + assertEquals(109, bs.previousSetBit(109)) + assertEquals(109, bs.previousSetBit(110)) + assertEquals(109, bs.previousSetBit(111)) + + assertEquals(109, bs.previousSetBit(126)) + assertEquals(127, bs.previousSetBit(127)) + assertEquals(128, bs.previousSetBit(128)) + assertEquals(129, bs.previousSetBit(129)) + assertEquals(129, bs.previousSetBit(130)) + assertEquals(129, bs.previousSetBit(131)) + + assertEquals(129, bs.previousSetBit(191)) + assertEquals(129, bs.previousSetBit(192)) + assertEquals(193, bs.previousSetBit(193)) + assertEquals(193, bs.previousSetBit(194)) + + assertEquals(193, bs.previousSetBit(255)) + assertEquals(193, bs.previousSetBit(256)) + + assertEquals(450, bs.previousSetBit(450)) + assertEquals(450, bs.previousSetBit(500)) + assertEquals(450, bs.previousSetBit(800)) + } + + @Test def nextSetBit(): Unit = { + val bs = new BitSet(500) + bs.set(5) + bs.set(32) + bs.set(63) + bs.set(64) + bs.set(71, 110) + bs.set(127, 130) + bs.set(193) + bs.set(450) + + // "Expected IndexOutOfBoundsException for negative index" + assertThrows(classOf[IndexOutOfBoundsException], bs.nextSetBit(-1)) + + assertEquals(5, bs.nextSetBit(0)) + assertEquals(5, bs.nextSetBit(5)) + assertEquals(32, bs.nextSetBit(6)) + assertEquals(32, bs.nextSetBit(32)) + assertEquals(63, bs.nextSetBit(33)) + + // boundary tests + assertEquals(63, bs.nextSetBit(63)) + assertEquals(64, bs.nextSetBit(64)) + + // at bitset element 1 + assertEquals(71, bs.nextSetBit(65)) + assertEquals(71, bs.nextSetBit(71)) + assertEquals(72, bs.nextSetBit(72)) + assertEquals(127, bs.nextSetBit(110)) + assertEquals(127, bs.nextSetBit(127)) + assertEquals(128, bs.nextSetBit(128)) + + // at bitset element 2 + assertEquals(193, bs.nextSetBit(130)) + assertEquals(193, bs.nextSetBit(191)) + assertEquals(193, bs.nextSetBit(192)) + assertEquals(193, bs.nextSetBit(193)) + assertEquals(450, bs.nextSetBit(194)) + assertEquals(450, bs.nextSetBit(255)) + assertEquals(450, bs.nextSetBit(256)) + assertEquals(450, bs.nextSetBit(450)) + assertEquals(-1, bs.nextSetBit(451)) + assertEquals(-1, bs.nextSetBit(511)) + assertEquals(-1, bs.nextSetBit(512)) + assertEquals(-1, bs.nextSetBit(800)) + } + + @Test def test_nextClearBit_bitIndex(): Unit = { + val bs = new BitSet(500) + bs.set(0, bs.size - 1) // ensure all the bits from 0 to bs.size() + + // -1 + bs.set(bs.size - 1) // are set to true + + bs.clear(5) + bs.clear(32) + bs.clear(63) + bs.clear(64) + bs.clear(71, 110) + bs.clear(127, 130) + bs.clear(193) + bs.clear(450) + + // "Expected IndexOutOfBoundsException for negative index" + assertThrows(classOf[IndexOutOfBoundsException], bs.nextClearBit(-1)) + + assertEquals(5, bs.nextClearBit(0)) + assertEquals(5, bs.nextClearBit(5)) + assertEquals(32, bs.nextClearBit(6)) + assertEquals(32, bs.nextClearBit(32)) + assertEquals(63, bs.nextClearBit(33)) + assertEquals(63, bs.nextClearBit(63)) + assertEquals(64, bs.nextClearBit(64)) + assertEquals(71, bs.nextClearBit(65)) + assertEquals(71, bs.nextClearBit(71)) + assertEquals(72, bs.nextClearBit(72)) + assertEquals(127, bs.nextClearBit(110)) + assertEquals(127, bs.nextClearBit(127)) + assertEquals(128, bs.nextClearBit(128)) + assertEquals(193, bs.nextClearBit(130)) + assertEquals(193, bs.nextClearBit(191)) + assertEquals(193, bs.nextClearBit(192)) + assertEquals(193, bs.nextClearBit(193)) + assertEquals(450, bs.nextClearBit(194)) + assertEquals(450, bs.nextClearBit(255)) + assertEquals(450, bs.nextClearBit(256)) + assertEquals(450, bs.nextClearBit(450)) + + // bitset has 1 still the end of bs.size() -1, but calling nextClearBit with any index value + // after the last true bit should return bs.size(), + assertEquals(512, bs.nextClearBit(451)) + assertEquals(512, bs.nextClearBit(511)) + assertEquals(512, bs.nextClearBit(512)) + + // if the index is larger than bs.size(), nextClearBit should return index; + assertEquals(513, bs.nextClearBit(513)) + assertEquals(800, bs.nextClearBit(800)) + } + + @Test def isEmpty(): Unit = { + val bs = new BitSet(500) + assertTrue(bs.isEmpty()) + + bs.set(3) + assertFalse(bs.isEmpty()) + + bs.clear() + bs.set(12) + assertFalse(bs.isEmpty()) + + bs.clear() + bs.set(128) + assertFalse(bs.isEmpty()) + + bs.clear() + bs.set(459) + assertFalse(bs.isEmpty()) + + bs.clear() + bs.set(511) + assertFalse(bs.isEmpty()) + } + + @Test def cardinality(): Unit = { + val bs = new BitSet(500) + bs.set(5) + bs.set(32) + bs.set(63) + bs.set(64) + bs.set(71, 110) + bs.set(127, 130) + bs.set(193) + bs.set(450) + assertEquals(48, bs.cardinality()) + + bs.flip(0, 500) + assertEquals(452, bs.cardinality()) + + bs.clear() + assertEquals(0, bs.cardinality()) + + bs.set(0, 500) + assertEquals(500, bs.cardinality()) + } + + @Test def toByteArray(): Unit = { + val bs = new BitSet(500) + assertArrayEquals(Array[Byte](), bs.toByteArray()) + bs.set(5) + assertArrayEquals(Array[Byte](32), bs.toByteArray()) + bs.set(32) + assertArrayEquals(Array[Byte](32, 0, 0, 0, 1), bs.toByteArray()) + bs.set(63) + assertArrayEquals(Array[Byte](32, 0, 0, 0, 1, 0, 0, -128), bs.toByteArray()) + bs.set(64) + assertArrayEquals(Array[Byte](32, 0, 0, 0, 1, 0, 0, -128, 1), bs.toByteArray()) + bs.set(71, 110) + assertArrayEquals(Array[Byte](32, 0, 0, 0, 1, 0, 0, -128, -127, -1, -1, -1, -1, 63), bs.toByteArray()) + bs.set(127, 130) + assertArrayEquals( + Array[Byte](32, 0, 0, 0, 1, 0, 0, -128, -127, -1, -1, -1, -1, 63, 0, -128, 3), bs.toByteArray()) + bs.set(193) + assertArrayEquals(Array[Byte](32, 0, 0, 0, 1, 0, 0, -128, -127, -1, -1, -1, -1, 63, 0, + -128, 3, 0, 0, 0, 0, 0, 0, 0, 2), bs.toByteArray()) + bs.set(450) + assertArrayEquals(Array[Byte](32, 0, 0, 0, 1, 0, 0, -128, -127, -1, -1, -1, -1, 63, 0, + -128, 3, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4), bs.toByteArray()) + } + + @Test def toLongArray(): Unit = { + val bs = new BitSet(500) + assertArrayEquals(Array[Long](), bs.toLongArray()) + bs.set(5) + assertArrayEquals(Array[Long](32L), bs.toLongArray()) + bs.set(32) + assertArrayEquals(Array[Long](4294967328L), bs.toLongArray()) + bs.set(63) + assertArrayEquals(Array[Long](-9223372032559808480L), bs.toLongArray()) + bs.set(64) + assertArrayEquals(Array[Long](-9223372032559808480L, 1L), bs.toLongArray()) + bs.set(71, 110) + assertArrayEquals(Array[Long](-9223372032559808480L, 70368744177537L), bs.toLongArray()) + bs.set(127, 130) + assertArrayEquals(Array[Long](-9223372032559808480L, -9223301668110598271L, 3L), bs.toLongArray()) + bs.set(193) + assertArrayEquals(Array[Long](-9223372032559808480L, -9223301668110598271L, 3L, 2L), bs.toLongArray()) + bs.set(450) + assertArrayEquals(Array[Long](-9223372032559808480L, -9223301668110598271L, 3L, 2L, + 0L, 0L, 0L, 4L), bs.toLongArray()) + } + + @Test def valueOf_ByteArray(): Unit = { + val bs = new BitSet(500) + assertEquals(bs, BitSet.valueOf(Array[Byte]())) + bs.set(5) + assertEquals(bs, BitSet.valueOf(Array[Byte](32))) + bs.set(32) + assertEquals(bs, BitSet.valueOf(Array[Byte](32, 0, 0, 0, 1))) + bs.set(63) + assertEquals(bs, BitSet.valueOf(Array[Byte](32, 0, 0, 0, 1, 0, 0, -128))) + bs.set(64) + assertEquals(bs, BitSet.valueOf(Array[Byte](32, 0, 0, 0, 1, 0, 0, -128, 1))) + bs.set(71, 110) + assertEquals(bs, BitSet.valueOf(Array[Byte](32, 0, 0, 0, 1, 0, 0, -128, -127, -1, -1, -1, -1, 63))) + bs.set(127, 130) + assertEquals(bs, + BitSet.valueOf(Array[Byte](32, 0, 0, 0, 1, 0, 0, -128, -127, -1, -1, -1, -1, 63, 0, -128, 3))) + bs.set(193) + assertEquals(bs, BitSet.valueOf(Array[Byte](32, 0, 0, 0, 1, 0, 0, -128, -127, -1, -1, -1, -1, 63, 0, + -128, 3, 0, 0, 0, 0, 0, 0, 0, 2))) + bs.set(450) + assertEquals(bs, BitSet.valueOf(Array[Byte](32, 0, 0, 0, 1, 0, 0, -128, -127, -1, -1, -1, -1, 63, 0, + -128, 3, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4))) + } + + @Test def valueOf_LongArray(): Unit = { + val bs = new BitSet(500) + assertEquals(bs, BitSet.valueOf(Array[Long]())) + bs.set(5) + assertEquals(bs, BitSet.valueOf(Array[Long](32L))) + bs.set(32) + assertEquals(bs, BitSet.valueOf(Array[Long](4294967328L))) + bs.set(63) + assertEquals(bs, BitSet.valueOf(Array[Long](-9223372032559808480L))) + bs.set(64) + assertEquals(bs, BitSet.valueOf(Array[Long](-9223372032559808480L, 1L))) + bs.set(71, 110) + assertEquals(bs, BitSet.valueOf(Array[Long](-9223372032559808480L, 70368744177537L))) + bs.set(127, 130) + assertEquals(bs, BitSet.valueOf(Array[Long](-9223372032559808480L, -9223301668110598271L, 3L))) + bs.set(193) + assertEquals(bs, BitSet.valueOf(Array[Long](-9223372032559808480L, -9223301668110598271L, 3L, 2L))) + bs.set(450) + assertEquals(bs, BitSet.valueOf(Array[Long](-9223372032559808480L, -9223301668110598271L, 3L, 2L, + 0L, 0L, 0L, 4L))) + } + + @Test def valueOf_ByteBuffer(): Unit = { + // Array-wrapped ByteBuffer + val emptyBS = new BitSet + val emptyBytes = emptyBS.toByteArray() + + assertEquals(emptyBS, BitSet.valueOf(ByteBuffer.wrap(emptyBytes))) + + val eightBS = makeEightBS() + val eightBytes = eightBS.toByteArray() + assertEquals(eightBS, BitSet.valueOf(ByteBuffer.wrap(eightBytes))) + + val bbWithPosition = ByteBuffer.wrap(192.toByte +: eightBytes) + assertEquals(192.toByte, bbWithPosition.get()) // extra byte + assertEquals(1, bbWithPosition.position()) + assertEquals(eightBS, BitSet.valueOf(bbWithPosition)) + assertEquals(1, bbWithPosition.position()) + + // ByteBuffer.allocate()ed + assertEquals(emptyBS, BitSet.valueOf(ByteBuffer.allocate(0))) + + val allocateByteBuffer = ByteBuffer.allocate(eightBytes.length + 1) + allocateByteBuffer.put(192.toByte) // extra byte + allocateByteBuffer.put(eightBytes) + allocateByteBuffer.rewind() + assertEquals(192.toByte, allocateByteBuffer.get()) // extra byte + assertEquals(1, allocateByteBuffer.position()) + assertEquals(eightBS, BitSet.valueOf(allocateByteBuffer)) + assertEquals(1, allocateByteBuffer.position()) + } + + @Test def valueOf_ByteBuffer_typedArrays(): Unit = { + assumeTrue("requires support for direct Buffers", hasDirectBuffers) + + val eightBS = makeEightBS() + val eightBytes = eightBS.toByteArray() + + // ByteBuffer.allocateDirect()ed + assertEquals(new BitSet, BitSet.valueOf(ByteBuffer.allocateDirect(0))) + + val directByteBuffer = ByteBuffer.allocateDirect(eightBytes.length + 1) + directByteBuffer.put(192.toByte) // extra byte + directByteBuffer.put(eightBytes) + directByteBuffer.rewind() + assertEquals(192.toByte, directByteBuffer.get()) // extra byte + assertEquals(1, directByteBuffer.position()) + assertEquals(eightBS, BitSet.valueOf(directByteBuffer)) + assertEquals(1, directByteBuffer.position()) + } + + @Test def valueOf_LongBuffer(): Unit = { + // Array-wrapped LongBuffer + val emptyBS = new BitSet + val emptyBSArray = emptyBS.toLongArray() + assertEquals(emptyBS, BitSet.valueOf(LongBuffer.wrap(emptyBSArray))) + + val eightBS = makeEightBS() + val eightBSArray = eightBS.toLongArray() + assertEquals(eightBS, BitSet.valueOf(LongBuffer.wrap(eightBSArray))) + + val lbWithPosition = LongBuffer.wrap(192L +: eightBSArray) + assertEquals(192L, lbWithPosition.get()) // extra byte + assertEquals(1, lbWithPosition.position()) + assertEquals(eightBS, BitSet.valueOf(lbWithPosition)) + assertEquals(1, lbWithPosition.position()) + + // LongBuffer.allocate()ed + assertEquals(emptyBS, BitSet.valueOf(LongBuffer.allocate(0))) + + val allocateLongBuffer = LongBuffer.allocate(eightBSArray.length + 1) + allocateLongBuffer.put(192L) // extra byte + allocateLongBuffer.put(eightBSArray) + allocateLongBuffer.rewind() + assertEquals(192L, allocateLongBuffer.get()) // extra byte + assertEquals(1, allocateLongBuffer.position()) + assertEquals(eightBS, BitSet.valueOf(allocateLongBuffer)) + assertEquals(1, allocateLongBuffer.position()) + } + + private def makeEightBS(): BitSet = { + val eightbs = new BitSet + for (i <- 0 until 8) { + eightbs.set(i) + } + eightbs + } +} diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/scalalib/ArrayBuilderTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/scalalib/ArrayBuilderTest.scala index 547f95e586..c667c1543e 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/scalalib/ArrayBuilderTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/scalalib/ArrayBuilderTest.scala @@ -324,11 +324,46 @@ class ArrayBuilderTest { b.addAll(arr, 3, 2) assertArrayEquals(Array[Int](4, 5), b.result()) } + + @Test def lengthAndKnownSize_Issue4627(): Unit = { + assumeFalse("Needs at least Scala 2.13", + scalaVersion.startsWith("2.11.") || + scalaVersion.startsWith("2.12.")) + + val b = ArrayBuilder.make[Int] + assertEquals(0, b.length) + assertEquals(0, b.knownSize) + + b += 5 + b += 7 + assertEquals(2, b.length) + assertEquals(2, b.knownSize) + + b += -1 + b += 98 + b += 4 + assertEquals(5, b.length) + assertEquals(5, b.knownSize) + + b.clear() + assertEquals(0, b.length) + assertEquals(0, b.knownSize) + + b += 4 + assertEquals(1, b.length) + assertEquals(1, b.knownSize) + } } object ArrayBuilderTest { implicit class ArrayBuilderCompat[A](val __sef: ArrayBuilder[A]) extends AnyVal { def addAll(xs: Array[_ <: A], offset: Int, length: Int): Unit = throw new AssertionError("unreachable code") + + def length: Int = + throw new AssertionError("unreachable code") + + def knownSize: Int = + throw new AssertionError("unreachable code") } }