diff --git a/build.sbt b/build.sbt index e3374e3358..e9bdde6b6b 100644 --- a/build.sbt +++ b/build.sbt @@ -14,8 +14,9 @@ val sbtPlugin = Build.plugin val javalibintf = Build.javalibintf val javalibInternal = Build.javalibInternal val javalib = Build.javalib -val scalalib = Build.scalalib +val scalalibInternal = Build.scalalibInternal val libraryAux = Build.libraryAux +val scalalib = Build.scalalib val library = Build.library val testInterface = Build.testInterface val testBridge = Build.testBridge 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 245ca3eee9..b6476e0c09 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.14.0", + current = "1.15.0", binaryEmitted = "1.13" ) diff --git a/javalib/src/main/scala/java/io/FilterReader.scala b/javalib/src/main/scala/java/io/FilterReader.scala new file mode 100644 index 0000000000..810c875dde --- /dev/null +++ b/javalib/src/main/scala/java/io/FilterReader.scala @@ -0,0 +1,35 @@ +/* + * 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.io + +abstract class FilterReader protected (protected val in: Reader) extends Reader { + + in.getClass() // null check + + override def close(): Unit = in.close() + + override def mark(readLimit: Int): Unit = in.mark(readLimit) + + override def markSupported(): Boolean = in.markSupported() + + override def read(): Int = in.read() + + override def read(buffer: Array[Char], offset: Int, count: Int): Int = + in.read(buffer, offset, count) + + override def ready(): Boolean = in.ready() + + override def reset(): Unit = in.reset() + + override def skip(count: Long): Long = in.skip(count) +} 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 0c87bd53bf..0701d2fd84 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 @@ -45,23 +45,15 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { def buildClass(className: ClassName, kind: ClassKind, jsClassCaptures: Option[List[ParamDef]], hasClassInitializer: Boolean, - superClass: Option[ClassIdent], jsSuperClass: Option[Tree], useESClass: Boolean, ctor: js.Tree, + superClass: Option[ClassIdent], jsSuperClass: Option[Tree], useESClass: Boolean, ctorDefs: List[js.Tree], memberDefs: List[js.MethodDef], exportedDefs: List[js.Tree])( implicit moduleContext: ModuleContext, - globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[js.Tree] = { + globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[List[js.Tree]] = { - def allES6Defs = { - js.Block(ctor +: (memberDefs ++ exportedDefs)) match { - case js.Block(allDefs) => allDefs - case js.Skip() => Nil - case oneDef => List(oneDef) - } - } + def allES6Defs = ctorDefs ::: memberDefs ::: exportedDefs - def allES5Defs(classVar: js.Tree) = { - WithGlobals(js.Block( - ctor, assignES5ClassMembers(classVar, memberDefs), js.Block(exportedDefs: _*))) - } + def allES5Defs(classVar: js.Tree) = + WithGlobals(ctorDefs ::: assignES5ClassMembers(classVar, memberDefs) ::: exportedDefs) if (!kind.isJSClass) { assert(jsSuperClass.isEmpty, className) @@ -70,14 +62,14 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val parentVarWithGlobals = for (parentIdent <- superClass) yield { implicit val pos = parentIdent.pos if (shouldExtendJSError(className, superClass)) untrackedGlobalRef("Error") - else WithGlobals(globalVar("c", parentIdent.name)) + else WithGlobals(globalVar(VarField.c, parentIdent.name)) } WithGlobals.option(parentVarWithGlobals).flatMap { parentVar => - globalClassDef("c", className, parentVar, allES6Defs) + globalClassDef(VarField.c, className, parentVar, allES6Defs) } } else { - allES5Defs(globalVar("c", className)) + allES5Defs(globalVar(VarField.c, className)) } } else { // Wrap the entire class def in an accessor function @@ -85,17 +77,17 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val genStoreJSSuperClass = jsSuperClass.map { jsSuperClass => for (rhs <- desugarExpr(jsSuperClass, resultType = AnyType)) yield { - js.VarDef(fileLevelVar("superClass").ident, Some(rhs)) + js.VarDef(fileLevelVar(VarField.superClass).ident, Some(rhs)) } } - val classValueIdent = fileLevelVarIdent("b", genName(className)) + val classValueIdent = fileLevelVarIdent(VarField.b, genName(className)) val classValueVar = js.VarRef(classValueIdent) val createClassValueVar = genEmptyMutableLet(classValueIdent) val entireClassDefWithGlobals = if (useESClass) { genJSSuperCtor(superClass, jsSuperClass).map { jsSuperClass => - classValueVar := js.ClassDef(Some(classValueIdent), Some(jsSuperClass), allES6Defs) + List(classValueVar := js.ClassDef(Some(classValueIdent), Some(jsSuperClass), allES6Defs)) } } else { allES5Defs(classValueVar) @@ -106,7 +98,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { entireClassDef <- entireClassDefWithGlobals createStaticFields <- genCreateStaticFieldsOfJSClass(className) } yield { - optStoreJSSuperClass.toList ::: entireClassDef :: createStaticFields + optStoreJSSuperClass.toList ::: entireClassDef ::: createStaticFields } jsClassCaptures.fold { @@ -123,14 +115,14 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { }), js.Return(classValueVar) ) - createAccessor <- globalFunctionDef("a", className, Nil, None, body) + createAccessor <- globalFunctionDef(VarField.a, className, Nil, None, body) } yield { - js.Block(createClassValueVar, createAccessor) + createClassValueVar :: createAccessor } } { jsClassCaptures => val captureParamDefs = for (param <- jsClassCaptures) yield { implicit val pos = param.pos - val ident = fileLevelVarIdent("cc", genName(param.name.name), + val ident = fileLevelVarIdent(VarField.cc, genName(param.name.name), param.originalName.orElse(param.name.name)) js.ParamDef(ident) } @@ -146,7 +138,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { Nil ) - globalFunctionDef("a", className, captureParamDefs, None, body) + globalFunctionDef(VarField.a, className, captureParamDefs, None, body) } } } @@ -173,7 +165,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { def genScalaClassConstructor(className: ClassName, superClass: Option[ClassIdent], useESClass: Boolean, initToInline: Option[MethodDef])( implicit moduleContext: ModuleContext, - globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[js.Tree] = { + globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[List[js.Tree]] = { assert(superClass.isDefined || className == ObjectClass, s"Class $className is missing a parent class") @@ -192,45 +184,45 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } if (args.isEmpty && isTrivialCtorBody) - js.Skip() + Nil else - js.MethodDef(static = false, js.Ident("constructor"), args, restParam, body) + js.MethodDef(static = false, js.Ident("constructor"), args, restParam, body) :: Nil } } else { import TreeDSL._ - val ctorVar = globalVar("c", className) + val ctorVar = globalVar(VarField.c, className) val chainProtoWithGlobals = superClass match { case None => - WithGlobals(js.Skip()) + WithGlobals.nil case Some(_) if shouldExtendJSError(className, superClass) => untrackedGlobalRef("Error").map(chainPrototypeWithLocalCtor(className, ctorVar, _)) case Some(parentIdent) => - WithGlobals(ctorVar.prototype := js.New(globalVar("h", parentIdent.name), Nil)) + WithGlobals(List(ctorVar.prototype := js.New(globalVar(VarField.h, parentIdent.name), Nil))) } for { ctorFun <- jsConstructorFunWithGlobals realCtorDef <- - globalFunctionDef("c", className, ctorFun.args, ctorFun.restParam, ctorFun.body) + globalFunctionDef(VarField.c, className, ctorFun.args, ctorFun.restParam, ctorFun.body) inheritableCtorDef <- - globalFunctionDef("h", className, Nil, None, js.Skip()) + globalFunctionDef(VarField.h, className, Nil, None, js.Skip()) chainProto <- chainProtoWithGlobals } yield { - js.Block( - // Real constructor - js.DocComment("@constructor"), - realCtorDef, - chainProto, - genIdentBracketSelect(ctorVar.prototype, "constructor") := ctorVar, - - // Inheritable constructor - js.DocComment("@constructor"), - inheritableCtorDef, - globalVar("h", className).prototype := ctorVar.prototype + ( + // Real constructor + js.DocComment("@constructor") :: + realCtorDef ::: + chainProto ::: + (genIdentBracketSelect(ctorVar.prototype, "constructor") := ctorVar) :: + + // Inheritable constructor + js.DocComment("@constructor") :: + inheritableCtorDef ::: + (globalVar(VarField.h, className).prototype := ctorVar.prototype) :: Nil ) } } @@ -240,7 +232,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { def genJSConstructor(className: ClassName, superClass: Option[ClassIdent], jsSuperClass: Option[Tree], useESClass: Boolean, jsConstructorDef: JSConstructorDef)( implicit moduleContext: ModuleContext, - globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[js.Tree] = { + globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[List[js.Tree]] = { val JSConstructorDef(_, params, restParam, body) = jsConstructorDef val ctorFunWithGlobals = desugarToFunction(className, params, restParam, body) @@ -248,7 +240,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { if (useESClass) { for (fun <- ctorFunWithGlobals) yield { js.MethodDef(static = false, js.Ident("constructor"), - fun.args, fun.restParam, fun.body) + fun.args, fun.restParam, fun.body) :: Nil } } else { for { @@ -257,14 +249,12 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } yield { import TreeDSL._ - val ctorVar = fileLevelVar("b", genName(className)) + val ctorVar = fileLevelVar(VarField.b, genName(className)) - js.Block( - js.DocComment("@constructor"), - ctorVar := ctorFun, - chainPrototypeWithLocalCtor(className, ctorVar, superCtor), - genIdentBracketSelect(ctorVar.prototype, "constructor") := ctorVar - ) + js.DocComment("@constructor") :: + (ctorVar := ctorFun) :: + chainPrototypeWithLocalCtor(className, ctorVar, superCtor) ::: + (genIdentBracketSelect(ctorVar.prototype, "constructor") := ctorVar) :: Nil } } } @@ -273,7 +263,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[js.Tree] = { if (jsSuperClass.isDefined) { - WithGlobals(fileLevelVar("superClass")) + WithGlobals(fileLevelVar(VarField.superClass)) } else { genJSClassConstructor(superClass.get.name, keepOnlyDangerousVarNames = true) } @@ -348,12 +338,12 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } private def chainPrototypeWithLocalCtor(className: ClassName, ctorVar: js.Tree, - superCtor: js.Tree)(implicit pos: Position): js.Tree = { + superCtor: js.Tree)(implicit pos: Position): List[js.Tree] = { import TreeDSL._ - val dummyCtor = fileLevelVar("hh", genName(className)) + val dummyCtor = fileLevelVar(VarField.hh, genName(className)) - js.Block( + List( js.DocComment("@constructor"), genConst(dummyCtor.ident, js.Function(false, Nil, None, js.Skip())), dummyCtor.prototype := superCtor.prototype, @@ -391,12 +381,12 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val value = genZeroOf(ftpe) if (flags.isMutable) - globallyMutableVarDef("t", "u", varScope, value, origName.orElse(name)) + globallyMutableVarDef(VarField.t, VarField.u, varScope, value, origName.orElse(name)) else - globalVarDef("t", varScope, value, origName.orElse(name)) + globalVarDef(VarField.t, varScope, value, origName.orElse(name)) } - WithGlobals.list(defs) + WithGlobals.flatten(defs) } /** Generates the creation of the private JS field defs for a JavaScript @@ -420,11 +410,11 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } symbolValueWithGlobals.flatMap { symbolValue => - globalVarDef("r", (className, name), symbolValue, origName.orElse(name)) + globalVarDef(VarField.r, (className, name), symbolValue, origName.orElse(name)) } } - WithGlobals.list(defs) + WithGlobals.flatten(defs) } /** Generates the creation of the static fields for a JavaScript class. */ @@ -436,7 +426,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { if field.flags.namespace.isStatic } yield { implicit val pos = field.pos - val classVarRef = fileLevelVar("b", genName(className)) + val classVarRef = fileLevelVar(VarField.b, genName(className)) val zero = genBoxedZeroOf(field.ftpe) field match { case FieldDef(_, name, originalName, _) => @@ -462,7 +452,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { def genStaticInitialization(className: ClassName)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): List[js.Tree] = { - val field = globalVar("sct", (className, StaticInitializerName), + val field = globalVar(VarField.sct, (className, StaticInitializerName), StaticInitializerOriginalName) js.Apply(field, Nil) :: Nil } @@ -472,7 +462,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): List[js.Tree] = { if (hasClassInitializer) { - val field = globalVar("sct", (className, ClassInitializerName), + val field = globalVar(VarField.sct, (className, ClassInitializerName), ClassInitializerOriginalName) js.Apply(field, Nil) :: Nil } else { @@ -497,7 +487,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { def genStaticLikeMethod(className: ClassName, method: MethodDef)( implicit moduleContext: ModuleContext, - globalKnowledge: GlobalKnowledge): WithGlobals[js.Tree] = { + globalKnowledge: GlobalKnowledge): WithGlobals[List[js.Tree]] = { val methodBody = method.body.getOrElse( throw new AssertionError("Cannot generate an abstract method")) @@ -528,12 +518,12 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } val field = namespace match { - case MemberNamespace.Public => "f" - case MemberNamespace.Private => "p" - case MemberNamespace.PublicStatic => "s" - case MemberNamespace.PrivateStatic => "ps" - case MemberNamespace.Constructor => "ct" - case MemberNamespace.StaticConstructor => "sct" + case MemberNamespace.Public => VarField.f + case MemberNamespace.Private => VarField.p + case MemberNamespace.PublicStatic => VarField.s + case MemberNamespace.PrivateStatic => VarField.ps + case MemberNamespace.Constructor => VarField.ct + case MemberNamespace.StaticConstructor => VarField.sct } val methodName = method.name.name @@ -570,11 +560,11 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { private def genJSProperty(className: ClassName, kind: ClassKind, useESClass: Boolean, property: JSPropertyDef)( implicit moduleContext: ModuleContext, - globalKnowledge: GlobalKnowledge): WithGlobals[js.Tree] = { + globalKnowledge: GlobalKnowledge): WithGlobals[List[js.Tree]] = { if (useESClass) genJSPropertyES6(className, property) else - genJSPropertyES5(className, kind, property) + genJSPropertyES5(className, kind, property).map(_ :: Nil) } private def genJSPropertyES5(className: ClassName, kind: ClassKind, property: JSPropertyDef)( @@ -612,31 +602,27 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { private def genJSPropertyES6(className: ClassName, property: JSPropertyDef)( implicit moduleContext: ModuleContext, - globalKnowledge: GlobalKnowledge): WithGlobals[js.Tree] = { + globalKnowledge: GlobalKnowledge): WithGlobals[List[js.Tree]] = { implicit val pos = property.pos val static = property.flags.namespace.isStatic genMemberNameTree(property.name).flatMap { propName => - val getterWithGlobals = property.getterBody.fold { - WithGlobals[js.Tree](js.Skip()) - } { body => + val getterWithGlobals = property.getterBody.map { body => for (fun <- desugarToFunction(className, Nil, body, resultType = AnyType)) yield js.GetterDef(static, propName, fun.body) } - val setterWithGlobals = property.setterArgAndBody.fold { - WithGlobals[js.Tree](js.Skip()) - } { case (arg, body) => + val setterWithGlobals = property.setterArgAndBody.map { case (arg, body) => for (fun <- desugarToFunction(className, arg :: Nil, body, resultType = NoType)) yield js.SetterDef(static, propName, fun.args.head, fun.body) } for { - getter <- getterWithGlobals - setter <- setterWithGlobals + getter <- WithGlobals.option(getterWithGlobals) + setter <- WithGlobals.option(setterWithGlobals) } yield { - js.Block(getter, setter) + getter.toList ::: setter.toList } } } @@ -647,8 +633,8 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { import TreeDSL._ val classVarRef = - if (kind.isJSClass) fileLevelVar("b", genName(className)) - else globalVar("c", className) + if (kind.isJSClass) fileLevelVar(VarField.b, genName(className)) + else globalVar(VarField.c, className) if (namespace.isStatic) classVarRef else classVarRef.prototype @@ -754,23 +740,23 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } val createIsStatWithGlobals = if (needIsFunction) { - globalFunctionDef("is", className, List(objParam), None, js.Return(isExpression)) + globalFunctionDef(VarField.is, className, List(objParam), None, js.Return(isExpression)) } else { - WithGlobals(js.Skip()) + WithGlobals.nil } val createAsStatWithGlobals = if (semantics.asInstanceOfs == Unchecked) { - WithGlobals(js.Skip()) + WithGlobals.nil } else { - globalFunctionDef("as", className, List(objParam), None, js.Return { + globalFunctionDef(VarField.as, className, List(objParam), None, js.Return { val isCond = - if (needIsFunction) js.Apply(globalVar("is", className), List(obj)) + if (needIsFunction) js.Apply(globalVar(VarField.is, className), List(obj)) else isExpression js.If(isCond || (obj === js.Null()), { obj }, { - genCallHelper("throwClassCastException", + genCallHelper(VarField.throwClassCastException, obj, js.StringLiteral(displayName)) }) }) @@ -780,10 +766,10 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { createIsStat <- createIsStatWithGlobals createAsStat <- createAsStatWithGlobals } yield { - List(createIsStat, createAsStat) + createIsStat ::: createAsStat } } else { - WithGlobals(Nil) + WithGlobals.nil } } @@ -805,7 +791,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val depth = depthParam.ref val createIsArrayOfStatWithGlobals = { - globalFunctionDef("isArrayOf", className, List(objParam, depthParam), None, { + globalFunctionDef(VarField.isArrayOf, className, List(objParam, depthParam), None, { js.Return(!(!({ genIsScalaJSObject(obj) && ((obj DOT "$classData" DOT "arrayDepth") === depth) && @@ -816,15 +802,15 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } val createAsArrayOfStatWithGlobals = if (semantics.asInstanceOfs == Unchecked) { - WithGlobals(js.Skip()) + WithGlobals.nil } else { - globalFunctionDef("asArrayOf", className, List(objParam, depthParam), None, { + globalFunctionDef(VarField.asArrayOf, className, List(objParam, depthParam), None, { js.Return { - js.If(js.Apply(globalVar("isArrayOf", className), List(obj, depth)) || + js.If(js.Apply(globalVar(VarField.isArrayOf, className), List(obj, depth)) || (obj === js.Null()), { obj }, { - genCallHelper("throwArrayCastException", + genCallHelper(VarField.throwArrayCastException, obj, js.StringLiteral("L"+displayName+";"), depth) }) } @@ -835,7 +821,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { createIsArrayOfStat <- createIsArrayOfStatWithGlobals createAsArrayOfStat <- createAsArrayOfStatWithGlobals } yield { - List(createIsArrayOfStat, createAsArrayOfStat) + createIsArrayOfStat ::: createAsArrayOfStat } } @@ -855,7 +841,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { superClass: Option[ClassIdent], ancestors: List[ClassName], jsNativeLoadSpec: Option[JSNativeLoadSpec])( implicit moduleContext: ModuleContext, - globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[js.Tree] = { + globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[List[js.Tree]] = { import TreeDSL._ val isObjectClass = @@ -872,7 +858,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { if (isObjectClass) js.Null() else js.Undefined() } { parent => - globalVar("d", parent.name) + globalVar(VarField.d, parent.name) } } else { js.Undefined() @@ -886,7 +872,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { /* Ancestors of hijacked classes, including java.lang.Object, have a * normal $is_pack_Class test but with a non-standard behavior. */ - WithGlobals(globalVar("is", className)) + WithGlobals(globalVar(VarField.is, className)) } else if (HijackedClasses.contains(className)) { /* Hijacked classes have a special isInstanceOf test. */ val xParam = js.ParamDef(js.Ident("x")) @@ -902,7 +888,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { * cannot be performed and must throw. */ if (kind != ClassKind.JSClass && kind != ClassKind.NativeJSClass) { - WithGlobals(globalVar("noIsInstance", CoreVar)) + WithGlobals(globalVar(VarField.noIsInstance, CoreVar)) } else if (kind == ClassKind.JSClass && !globalKnowledge.hasInstances(className)) { /* We need to constant-fold the instance test, to avoid emitting * `x instanceof $a_TheClass()`, because `$a_TheClass` won't be @@ -942,10 +928,10 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { val prunedParams = allParams.reverse.dropWhile(_.isInstanceOf[js.Undefined]).reverse - val typeData = js.Apply(js.New(globalVar("TypeData", CoreVar), Nil) DOT "initClass", + val typeData = js.Apply(js.New(globalVar(VarField.TypeData, CoreVar), Nil) DOT "initClass", prunedParams) - globalVarDef("d", className, typeData) + globalVarDef(VarField.d, className, typeData) } } @@ -954,12 +940,12 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { globalKnowledge: GlobalKnowledge, pos: Position): js.Tree = { import TreeDSL._ - globalVar("c", className).prototype DOT "$classData" := globalVar("d", className) + globalVar(VarField.c, className).prototype DOT "$classData" := globalVar(VarField.d, className) } def genModuleAccessor(className: ClassName, kind: ClassKind)( implicit moduleContext: ModuleContext, - globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[js.Tree] = { + globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[List[js.Tree]] = { import TreeDSL._ val tpe = ClassType(className) @@ -967,7 +953,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { require(kind.hasModuleAccessor, s"genModuleAccessor called with non-module class: $className") - val moduleInstance = fileLevelVarIdent("n", genName(className)) + val moduleInstance = fileLevelVarIdent(VarField.n, genName(className)) val createModuleInstanceField = genEmptyMutableLet(moduleInstance) @@ -981,7 +967,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { genNonNativeJSClassConstructor(className), Nil) } else { - js.New(globalVar("c", className), Nil) + js.New(globalVar(VarField.c, className), Nil) } } } @@ -1004,23 +990,23 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { ) }, js.If(moduleInstanceVar === js.Null(), { val decodedName = className.nameString.stripSuffix("$") - genCallHelper("throwModuleInitError", js.StringLiteral(decodedName)) + genCallHelper(VarField.throwModuleInitError, js.StringLiteral(decodedName)) }, js.Skip())) } val body = js.Block(initBlock, js.Return(moduleInstanceVar)) - globalFunctionDef("m", className, Nil, None, body) + globalFunctionDef(VarField.m, className, Nil, None, body) } - createAccessor.map(js.Block(createModuleInstanceField, _)) + createAccessor.map(createModuleInstanceField :: _) } def genExportedMember(className: ClassName, kind: ClassKind, useESClass: Boolean, member: JSMethodPropDef)( implicit moduleContext: ModuleContext, - globalKnowledge: GlobalKnowledge): WithGlobals[js.Tree] = { + globalKnowledge: GlobalKnowledge): WithGlobals[List[js.Tree]] = { member match { - case m: JSMethodDef => genJSMethod(className, kind, useESClass, m) + case m: JSMethodDef => genJSMethod(className, kind, useESClass, m).map(_ :: Nil) case p: JSPropertyDef => genJSProperty(className, kind, useESClass, p) } } @@ -1076,7 +1062,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { genAssignToNoModuleExportVar(exportName, exportedValue) case ModuleKind.ESModule => - val field = fileLevelVar("e", exportName) + val field = fileLevelVar(VarField.e, exportName) val let = js.Let(field.ident, mutable = true, Some(exportedValue)) val exportStat = js.Export((field.ident -> js.ExportName(exportName)) :: Nil) WithGlobals(js.Block(let, exportStat)) @@ -1117,10 +1103,10 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { /* Initial value of the export. Updates are taken care of explicitly * when we assign to the static field. */ - genAssignToNoModuleExportVar(exportName, globalVar("t", varScope)) + genAssignToNoModuleExportVar(exportName, globalVar(VarField.t, varScope)) case ModuleKind.ESModule => - WithGlobals(globalVarExport("t", varScope, js.ExportName(exportName))) + WithGlobals(globalVarExport(VarField.t, varScope, js.ExportName(exportName))) case ModuleKind.CommonJSModule => globalRef("exports").flatMap { exportsVarRef => @@ -1129,7 +1115,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { js.StringLiteral(exportName), List( "get" -> js.Function(arrow = false, Nil, None, { - js.Return(globalVar("t", varScope)) + js.Return(globalVar(VarField.t, varScope)) }), "configurable" -> js.BooleanLiteral(true) ) @@ -1148,14 +1134,14 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { ModuleInitializerImpl.fromInitializer(initializer) match { case VoidMainMethod(className, mainMethodName) => - WithGlobals(js.Apply(globalVar("s", (className, mainMethodName)), Nil)) + WithGlobals(js.Apply(globalVar(VarField.s, (className, mainMethodName)), Nil)) case MainMethodWithArgs(className, mainMethodName, args) => val stringArrayTypeRef = ArrayTypeRef(ClassRef(BoxedStringClass), 1) val argsArrayWithGlobals = genArrayValue(stringArrayTypeRef, args.map(js.StringLiteral(_))) for (argsArray <- argsArrayWithGlobals) yield { - js.Apply(globalVar("s", (className, mainMethodName)), argsArray :: Nil) + js.Apply(globalVar(VarField.s, (className, mainMethodName)), argsArray :: Nil) } } } 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 7abc4b2396..8a04956ad8 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 @@ -53,9 +53,9 @@ private[emitter] object CoreJSLib { * These must have class definitions (but not static fields) available. */ final class Lib private[CoreJSLib] ( - val preObjectDefinitions: Tree, - val postObjectDefinitions: Tree, - val initialization: Tree) + val preObjectDefinitions: List[Tree], + val postObjectDefinitions: List[Tree], + val initialization: List[Tree]) private class CoreJSLibBuilder(sjsGen: SJSGen)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge) { @@ -121,36 +121,36 @@ private[emitter] object CoreJSLib { WithGlobals(lib, trackedGlobalRefs) } - private def buildPreObjectDefinitions(): Tree = Block( - defineLinkingInfo(), - defineJSBuiltinsSnapshotsAndPolyfills(), - declareCachedL0(), - definePropertyName(), - defineCharClass(), - defineRuntimeFunctions(), - defineObjectGetClassFunctions(), - defineDispatchFunctions(), - defineArithmeticOps(), - defineES2015LikeHelpers(), - defineModuleHelpers(), - defineIntrinsics(), - defineIsPrimitiveFunctions(), + private def buildPreObjectDefinitions(): List[Tree] = { + defineLinkingInfo() ::: + defineJSBuiltinsSnapshotsAndPolyfills() ::: + declareCachedL0() ::: + definePropertyName() ::: + defineCharClass() ::: + defineRuntimeFunctions() ::: + defineObjectGetClassFunctions() ::: + defineDispatchFunctions() ::: + defineArithmeticOps() ::: + defineES2015LikeHelpers() ::: + defineModuleHelpers() ::: + defineIntrinsics() ::: + defineIsPrimitiveFunctions() ::: defineBoxFunctions() - ) + } - private def buildPostObjectDefinitions(): Tree = Block( - defineSpecializedArrayClasses(), - defineTypeDataClass(), - defineSpecializedIsArrayOfFunctions(), - defineSpecializedAsArrayOfFunctions(), + private def buildPostObjectDefinitions(): List[Tree] = { + defineSpecializedArrayClasses() ::: + defineTypeDataClass() ::: + defineSpecializedIsArrayOfFunctions() ::: + defineSpecializedAsArrayOfFunctions() ::: defineSpecializedTypeDatas() - ) + } - private def buildInitializations(): Tree = Block( + private def buildInitializations(): List[Tree] = { assignCachedL0() - ) + } - private def defineLinkingInfo(): Tree = { + private def defineLinkingInfo(): List[Tree] = { // must be in sync with scala.scalajs.runtime.LinkingInfo def objectFreeze(tree: Tree): Tree = @@ -164,10 +164,10 @@ private[emitter] object CoreJSLib { str("fileLevelThis") -> This() ))) - extractWithGlobals(globalVarDef("linkingInfo", CoreVar, linkingInfo)) + extractWithGlobals(globalVarDef(VarField.linkingInfo, CoreVar, linkingInfo)) } - private def defineJSBuiltinsSnapshotsAndPolyfills(): Tree = { + private def defineJSBuiltinsSnapshotsAndPolyfills(): List[Tree] = { def genPolyfillFor(builtin: PolyfillableBuiltin): Tree = builtin match { case ObjectIsBuiltin => val x = varRef("x") @@ -491,10 +491,7 @@ private[emitter] object CoreJSLib { Apply(funGenerator, Nil) } - val polyfillDefs = for { - builtin <- PolyfillableBuiltin.All - if esVersion < builtin.availableInESVersion - } yield { + PolyfillableBuiltin.All.withFilter(esVersion < _.availableInESVersion).flatMap { builtin => val polyfill = genPolyfillFor(builtin) val rhs = builtin match { case builtin: GlobalVarBuiltin => @@ -506,26 +503,25 @@ private[emitter] object CoreJSLib { // NamespaceGlobalVar.builtinName || polyfill genIdentBracketSelect(globalRef(builtin.namespaceGlobalVar), builtin.builtinName) || polyfill } - extractWithGlobals(globalVarDef(builtin.builtinName, CoreVar, rhs)) + extractWithGlobals(globalVarDef(builtin.polyfillField, CoreVar, rhs)) } - Block(polyfillDefs) } - private def declareCachedL0(): Tree = { - condTree(!allowBigIntsForLongs)( - extractWithGlobals(globalVarDecl("L0", CoreVar)) + private def declareCachedL0(): List[Tree] = { + condDefs(!allowBigIntsForLongs)( + extractWithGlobals(globalVarDecl(VarField.L0, CoreVar)) ) } - private def assignCachedL0(): Tree = { - condTree(!allowBigIntsForLongs)(Block( - globalVar("L0", CoreVar) := genScalaClassNew( + private def assignCachedL0(): List[Tree] = { + condDefs(!allowBigIntsForLongs)(List( + globalVar(VarField.L0, CoreVar) := genScalaClassNew( LongImpl.RuntimeLongClass, LongImpl.initFromParts, 0, 0), - genClassDataOf(LongRef) DOT "zero" := globalVar("L0", CoreVar) + genClassDataOf(LongRef) DOT "zero" := globalVar(VarField.L0, CoreVar) )) } - private def definePropertyName(): Tree = { + private def definePropertyName(): List[Tree] = { /* Encodes a property name for runtime manipulation. * * Usage: @@ -536,13 +532,13 @@ private[emitter] object CoreJSLib { * Closure) but we must still get hold of a string of that name for * runtime reflection. */ - defineFunction1("propertyName") { obj => + defineFunction1(VarField.propertyName) { obj => val prop = varRef("prop") ForIn(genEmptyImmutableLet(prop.ident), obj, Return(prop)) } } - private def defineCharClass(): Tree = { + private def defineCharClass(): List[Tree] = { val ctor = { val c = varRef("c") MethodDef(static = false, Ident("constructor"), paramList(c), None, { @@ -558,22 +554,20 @@ private[emitter] object CoreJSLib { } if (useClassesForRegularClasses) { - extractWithGlobals(globalClassDef("Char", CoreVar, None, ctor :: toStr :: Nil)) + extractWithGlobals(globalClassDef(VarField.Char, CoreVar, None, ctor :: toStr :: Nil)) } else { - Block( - defineFunction("Char", ctor.args, ctor.body), - assignES5ClassMembers(globalVar("Char", CoreVar), List(toStr)) - ) + defineFunction(VarField.Char, ctor.args, ctor.body) ::: + assignES5ClassMembers(globalVar(VarField.Char, CoreVar), List(toStr)) } } - private def defineRuntimeFunctions(): Tree = Block( - condTree(asInstanceOfs != CheckedBehavior.Unchecked || arrayStores != CheckedBehavior.Unchecked)( + private def defineRuntimeFunctions(): List[Tree] = ( + condDefs(asInstanceOfs != CheckedBehavior.Unchecked || arrayStores != CheckedBehavior.Unchecked)( /* Returns a safe string description of a value. * This helper is never called for `value === null`. As implemented, * it would return `"object"` if it were. */ - defineFunction1("valueDescription") { value => + defineFunction1(VarField.valueDescription) { value => Return { If(typeof(value) === str("number"), { If((value === 0) && (int(1) / value < 0), { @@ -604,89 +598,89 @@ private[emitter] object CoreJSLib { }) } } - ), + ) ::: - condTree(asInstanceOfs != CheckedBehavior.Unchecked)(Block( - defineFunction2("throwClassCastException") { (instance, classFullName) => + condDefs(asInstanceOfs != CheckedBehavior.Unchecked)( + defineFunction2(VarField.throwClassCastException) { (instance, classFullName) => Throw(maybeWrapInUBE(asInstanceOfs, { genScalaClassNew(ClassCastExceptionClass, StringArgConstructorName, - genCallHelper("valueDescription", instance) + str(" cannot be cast to ") + classFullName) + genCallHelper(VarField.valueDescription, instance) + str(" cannot be cast to ") + classFullName) })) - }, + } ::: - defineFunction3("throwArrayCastException") { (instance, classArrayEncodedName, depth) => + defineFunction3(VarField.throwArrayCastException) { (instance, classArrayEncodedName, depth) => Block( While(depth.prefix_--, { classArrayEncodedName := (str("[") + classArrayEncodedName) }), - genCallHelper("throwClassCastException", instance, classArrayEncodedName) + genCallHelper(VarField.throwClassCastException, instance, classArrayEncodedName) ) } - )), + ) ::: - condTree(arrayIndexOutOfBounds != CheckedBehavior.Unchecked)( - defineFunction1("throwArrayIndexOutOfBoundsException") { i => + condDefs(arrayIndexOutOfBounds != CheckedBehavior.Unchecked)( + defineFunction1(VarField.throwArrayIndexOutOfBoundsException) { i => Throw(maybeWrapInUBE(arrayIndexOutOfBounds, { genScalaClassNew(ArrayIndexOutOfBoundsExceptionClass, StringArgConstructorName, If(i === Null(), Null(), str("") + i)) })) } - ), + ) ::: - condTree(arrayStores != CheckedBehavior.Unchecked)( - defineFunction1("throwArrayStoreException") { v => + condDefs(arrayStores != CheckedBehavior.Unchecked)( + defineFunction1(VarField.throwArrayStoreException) { v => Throw(maybeWrapInUBE(arrayStores, { genScalaClassNew(ArrayStoreExceptionClass, StringArgConstructorName, - If(v === Null(), Null(), genCallHelper("valueDescription", v))) + If(v === Null(), Null(), genCallHelper(VarField.valueDescription, v))) })) } - ), + ) ::: - condTree(negativeArraySizes != CheckedBehavior.Unchecked)( - defineFunction0("throwNegativeArraySizeException") { + condDefs(negativeArraySizes != CheckedBehavior.Unchecked)( + defineFunction0(VarField.throwNegativeArraySizeException) { Throw(maybeWrapInUBE(negativeArraySizes, { genScalaClassNew(NegativeArraySizeExceptionClass, NoArgConstructorName) })) } - ), + ) ::: - condTree(moduleInit == CheckedBehavior.Fatal)( - defineFunction1("throwModuleInitError") { name => + condDefs(moduleInit == CheckedBehavior.Fatal)( + defineFunction1(VarField.throwModuleInitError) { name => Throw(genScalaClassNew(UndefinedBehaviorErrorClass, StringArgConstructorName, str("Initializer of ") + name + str(" called before completion of its super constructor"))) } - ), + ) ::: - condTree(nullPointers != CheckedBehavior.Unchecked)(Block( - defineFunction0("throwNullPointerException") { + condDefs(nullPointers != CheckedBehavior.Unchecked)( + defineFunction0(VarField.throwNullPointerException) { Throw(maybeWrapInUBE(nullPointers, { genScalaClassNew(NullPointerExceptionClass, NoArgConstructorName) })) - }, + } ::: // "checkNotNull", but with a very short name - defineFunction1("n") { x => + defineFunction1(VarField.n) { x => Block( - If(x === Null(), genCallHelper("throwNullPointerException")), + If(x === Null(), genCallHelper(VarField.throwNullPointerException)), Return(x) ) } - )), + ) ::: - defineFunction1("noIsInstance") { instance => + defineFunction1(VarField.noIsInstance) { instance => Throw(New(TypeErrorRef, str("Cannot call isInstance() on a Class representing a JS trait/object") :: Nil)) - }, + } ::: - defineFunction2("newArrayObject") { (arrayClassData, lengths) => - Return(genCallHelper("newArrayObjectInternal", arrayClassData, lengths, int(0))) - }, + defineFunction2(VarField.newArrayObject) { (arrayClassData, lengths) => + Return(genCallHelper(VarField.newArrayObjectInternal, arrayClassData, lengths, int(0))) + } ::: - defineFunction3("newArrayObjectInternal") { (arrayClassData, lengths, lengthIndex) => + defineFunction3(VarField.newArrayObjectInternal) { (arrayClassData, lengths, lengthIndex) => val result = varRef("result") val subArrayClassData = varRef("subArrayClassData") val subLengthIndex = varRef("subLengthIndex") @@ -702,36 +696,36 @@ private[emitter] object CoreJSLib { const(underlying, result.u), For(let(i, 0), i < underlying.length, i.++, { BracketSelect(underlying, i) := - genCallHelper("newArrayObjectInternal", subArrayClassData, lengths, subLengthIndex) + genCallHelper(VarField.newArrayObjectInternal, subArrayClassData, lengths, subLengthIndex) }) )), Return(result) ) - }, + } ::: - defineFunction1("objectClone") { instance => + defineFunction1(VarField.objectClone) { instance => // return Object.create(Object.getPrototypeOf(instance), $getOwnPropertyDescriptors(instance)); val callGetOwnPropertyDescriptors = genCallPolyfillableBuiltin( GetOwnPropertyDescriptorsBuiltin, instance) Return(Apply(genIdentBracketSelect(ObjectRef, "create"), List( Apply(genIdentBracketSelect(ObjectRef, "getPrototypeOf"), instance :: Nil), callGetOwnPropertyDescriptors))) - }, + } ::: - defineFunction1("objectOrArrayClone") { instance => + defineFunction1(VarField.objectOrArrayClone) { instance => // return instance.$classData.isArrayClass ? instance.clone__O() : $objectClone(instance); Return(If(genIdentBracketSelect(instance DOT classData, "isArrayClass"), Apply(instance DOT genName(cloneMethodName), Nil), - genCallHelper("objectClone", instance))) + genCallHelper(VarField.objectClone, instance))) } ) - private def defineObjectGetClassFunctions(): Tree = { + private def defineObjectGetClassFunctions(): List[Tree] = { // objectGetClass and objectClassName - def defineObjectGetClassBasedFun(name: String, + def defineObjectGetClassBasedFun(name: VarField, constantClassResult: ClassName => Tree, - scalaObjectResult: VarRef => Tree, jsObjectResult: Tree): Tree = { + scalaObjectResult: VarRef => Tree, jsObjectResult: Tree): List[Tree] = { defineFunction1(name) { instance => Switch(typeof(instance), List( str("string") -> { @@ -739,7 +733,7 @@ private[emitter] object CoreJSLib { }, str("number") -> { Block( - If(genCallHelper("isInt", instance), { + If(genCallHelper(VarField.isInt, instance), { If((instance << 24 >> 24) === instance, { Return(constantClassResult(BoxedByteClass)) }, { @@ -751,7 +745,7 @@ private[emitter] object CoreJSLib { }) }, { if (strictFloats) { - If(genCallHelper("isFloat", instance), { + If(genCallHelper(VarField.isFloat, instance), { Return(constantClassResult(BoxedFloatClass)) }, { Return(constantClassResult(BoxedDoubleClass)) @@ -773,7 +767,7 @@ private[emitter] object CoreJSLib { if (nullPointers == CheckedBehavior.Unchecked) Return(Apply(instance DOT genName(getClassMethodName), Nil)) else - genCallHelper("throwNullPointerException") + genCallHelper(VarField.throwNullPointerException) }, { If(genIsInstanceOfHijackedClass(instance, BoxedLongClass), { Return(constantClassResult(BoxedLongClass)) @@ -794,46 +788,43 @@ private[emitter] object CoreJSLib { } - Block( - /* We use isClassClassInstantiated as an over-approximation of whether - * the program contains any `GetClass` node. If `j.l.Class` is not - * instantiated, then we know that there is no `GetClass` node, and it is - * safe to omit the definition of `objectGetClass`. However, it is - * possible that we generate `objectGetClass` even if it is not - * necessary, in the case that `j.l.Class` is otherwise instantiated - * (i.e., through a `ClassOf` node). - */ - condTree(globalKnowledge.isClassClassInstantiated)( - defineObjectGetClassBasedFun("objectGetClass", - className => genClassOf(className), - instance => Apply(instance DOT classData DOT "getClassOf", Nil), - Null() - ) - ), - - defineObjectGetClassBasedFun("objectClassName", - { className => - StringLiteral(RuntimeClassNameMapperImpl.map( - semantics.runtimeClassNameMapper, className.nameString)) - }, - instance => genIdentBracketSelect(instance DOT classData, "name"), - { - if (nullPointers == CheckedBehavior.Unchecked) - Apply(Null() DOT genName(getNameMethodName), Nil) - else - genCallHelper("throwNullPointerException") - } + /* We use isClassClassInstantiated as an over-approximation of whether + * the program contains any `GetClass` node. If `j.l.Class` is not + * instantiated, then we know that there is no `GetClass` node, and it is + * safe to omit the definition of `objectGetClass`. However, it is + * possible that we generate `objectGetClass` even if it is not + * necessary, in the case that `j.l.Class` is otherwise instantiated + * (i.e., through a `ClassOf` node). + */ + condDefs(globalKnowledge.isClassClassInstantiated)( + defineObjectGetClassBasedFun(VarField.objectGetClass, + className => genClassOf(className), + instance => Apply(instance DOT classData DOT "getClassOf", Nil), + Null() ) + ) ::: + defineObjectGetClassBasedFun(VarField.objectClassName, + { className => + StringLiteral(RuntimeClassNameMapperImpl.map( + semantics.runtimeClassNameMapper, className.nameString)) + }, + instance => genIdentBracketSelect(instance DOT classData, "name"), + { + if (nullPointers == CheckedBehavior.Unchecked) + Apply(Null() DOT genName(getNameMethodName), Nil) + else + genCallHelper(VarField.throwNullPointerException) + } ) } - private def defineDispatchFunctions(): Tree = { + private def defineDispatchFunctions(): List[Tree] = { val instance = varRef("instance") def defineDispatcher(methodName: MethodName, args: List[VarRef], - body: Tree): Tree = { - defineFunction("dp_" + genName(methodName), - paramList((instance :: args): _*), body) + body: Tree): List[Tree] = { + val params = paramList((instance :: args): _*) + extractWithGlobals(globalFunctionDef(VarField.dp, methodName, params, None, body)) } /* A standard dispatcher performs a type test on the instance and then @@ -844,7 +835,7 @@ private[emitter] object CoreJSLib { * - The implementation in java.lang.Object (if this is a JS object). */ def defineStandardDispatcher(methodName: MethodName, - implementingClasses: Set[ClassName]): Tree = { + implementingClasses: Set[ClassName]): List[Tree] = { val args = methodName.paramTypeRefs.indices.map(i => varRef("x" + i)).toList @@ -863,9 +854,9 @@ private[emitter] object CoreJSLib { def genHijackedMethodApply(className: ClassName): Tree = { val instanceAsPrimitive = - if (className == BoxedCharacterClass) genCallHelper("uC", instance) + if (className == BoxedCharacterClass) genCallHelper(VarField.uC, instance) else instance - Apply(globalVar("f", (className, methodName)), instanceAsPrimitive :: args) + Apply(globalVar(VarField.f, (className, methodName)), instanceAsPrimitive :: args) } def genBodyNoSwitch(hijackedClasses: List[ClassName]): Tree = { @@ -881,7 +872,7 @@ private[emitter] object CoreJSLib { if (implementedInObject) { val staticObjectCall: Tree = { - val fun = globalVar("c", ObjectClass).prototype DOT genName(methodName) + val fun = globalVar(VarField.c, ObjectClass).prototype DOT genName(methodName) Return(Apply(fun DOT "call", instance :: args)) } @@ -915,9 +906,7 @@ private[emitter] object CoreJSLib { val methodsInRepresentativeClasses = globalKnowledge.methodsInRepresentativeClasses() - val dispatchers = for { - (methodName, implementingClasses) <- methodsInRepresentativeClasses - } yield { + methodsInRepresentativeClasses.flatMap { case (methodName, implementingClasses) => if (methodName == toStringMethodName) { // toString()java.lang.String is special as per IR spec. defineDispatcher(toStringMethodName, Nil, { @@ -929,11 +918,9 @@ private[emitter] object CoreJSLib { defineStandardDispatcher(methodName, implementingClasses) } } - - Block(dispatchers) } - private def defineArithmeticOps(): Tree = { + private def defineArithmeticOps(): List[Tree] = { val throwDivByZero = { Throw(genScalaClassNew(ArithmeticExceptionClass, StringArgConstructorName, str("/ by zero"))) @@ -942,92 +929,86 @@ private[emitter] object CoreJSLib { def wrapBigInt64(tree: Tree): Tree = Apply(genIdentBracketSelect(BigIntRef, "asIntN"), 64 :: tree :: Nil) - Block( - defineFunction2("intDiv") { (x, y) => - If(y === 0, throwDivByZero, { - Return((x / y) | 0) - }) - }, - - defineFunction2("intMod") { (x, y) => - If(y === 0, throwDivByZero, { - Return((x % y) | 0) - }) - }, - - defineFunction1("doubleToInt") { x => - Return(If(x > 2147483647, 2147483647, If(x < -2147483648, -2147483648, x | 0))) - }, - - condTree(semantics.stringIndexOutOfBounds != CheckedBehavior.Unchecked)( - defineFunction2("charAt") { (s, i) => - val r = varRef("r") - - val throwStringIndexOutOfBoundsException = { - Throw(maybeWrapInUBE(semantics.stringIndexOutOfBounds, - genScalaClassNew(StringIndexOutOfBoundsExceptionClass, IntArgConstructorName, i))) - } - - Block( - const(r, Apply(genIdentBracketSelect(s, "charCodeAt"), List(i))), - If(r !== r, throwStringIndexOutOfBoundsException, Return(r)) - ) + defineFunction2(VarField.intDiv) { (x, y) => + If(y === 0, throwDivByZero, { + Return((x / y) | 0) + }) + } ::: + defineFunction2(VarField.intMod) { (x, y) => + If(y === 0, throwDivByZero, { + Return((x % y) | 0) + }) + } ::: + defineFunction1(VarField.doubleToInt) { x => + Return(If(x > 2147483647, 2147483647, If(x < -2147483648, -2147483648, x | 0))) + } ::: + condDefs(semantics.stringIndexOutOfBounds != CheckedBehavior.Unchecked)( + defineFunction2(VarField.charAt) { (s, i) => + val r = varRef("r") + + val throwStringIndexOutOfBoundsException = { + Throw(maybeWrapInUBE(semantics.stringIndexOutOfBounds, + genScalaClassNew(StringIndexOutOfBoundsExceptionClass, IntArgConstructorName, i))) } - ), - condTree(allowBigIntsForLongs)(Block( - defineFunction2("longDiv") { (x, y) => - If(y === bigInt(0), throwDivByZero, { - Return(wrapBigInt64(x / y)) - }) - }, - defineFunction2("longMod") { (x, y) => - If(y === bigInt(0), throwDivByZero, { - Return(wrapBigInt64(x % y)) - }) - }, + Block( + const(r, Apply(genIdentBracketSelect(s, "charCodeAt"), List(i))), + If(r !== r, throwStringIndexOutOfBoundsException, Return(r)) + ) + } + ) ::: + condDefs(allowBigIntsForLongs)( + defineFunction2(VarField.longDiv) { (x, y) => + If(y === bigInt(0), throwDivByZero, { + Return(wrapBigInt64(x / y)) + }) + } ::: + defineFunction2(VarField.longMod) { (x, y) => + If(y === bigInt(0), throwDivByZero, { + Return(wrapBigInt64(x % y)) + }) + } ::: - defineFunction1("doubleToLong")(x => Return { - If(x < double(-9223372036854775808.0), { // -2^63 - bigInt(-9223372036854775808L) + defineFunction1(VarField.doubleToLong)(x => Return { + If(x < double(-9223372036854775808.0), { // -2^63 + bigInt(-9223372036854775808L) + }, { + If (x >= double(9223372036854775808.0), { // 2^63 + bigInt(9223372036854775807L) }, { - If (x >= double(9223372036854775808.0), { // 2^63 - bigInt(9223372036854775807L) + If (x !== x, { // NaN + bigInt(0L) }, { - If (x !== x, { // NaN - bigInt(0L) - }, { - Apply(BigIntRef, - Apply(genIdentBracketSelect(MathRef, "trunc"), x :: Nil) :: Nil) - }) + Apply(BigIntRef, + Apply(genIdentBracketSelect(MathRef, "trunc"), x :: Nil) :: Nil) }) }) - }), + }) + }) ::: - defineFunction1("longToFloat") { x => - val abs = varRef("abs") - val y = varRef("y") - val absR = varRef("absR") + defineFunction1(VarField.longToFloat) { x => + val abs = varRef("abs") + val y = varRef("y") + val absR = varRef("absR") - // See RuntimeLong.toFloat for the strategy - Block( - const(abs, If(x < bigInt(0L), -x, x)), - const(y, If(abs <= bigInt(1L << 53) || (abs & bigInt(0xffffL)) === bigInt(0L), { - abs - }, { - (abs & bigInt(~0xffffL)) | bigInt(0x8000L) - })), - const(absR, Apply(NumberRef, y :: Nil)), - Return(genCallPolyfillableBuiltin(FroundBuiltin, If(x < bigInt(0L), -absR, absR))) - ) - } - )) + // See RuntimeLong.toFloat for the strategy + Block( + const(abs, If(x < bigInt(0L), -x, x)), + const(y, If(abs <= bigInt(1L << 53) || (abs & bigInt(0xffffL)) === bigInt(0L), { + abs + }, { + (abs & bigInt(~0xffffL)) | bigInt(0x8000L) + })), + const(absR, Apply(NumberRef, y :: Nil)), + Return(genCallPolyfillableBuiltin(FroundBuiltin, If(x < bigInt(0L), -absR, absR))) + ) + } ) } - private def defineES2015LikeHelpers(): Tree = Block( - condTree(esVersion < ESVersion.ES2015)( - defineFunction2("newJSObjectWithVarargs") { (ctor, args) => + private def defineES2015LikeHelpers(): List[Tree] = ( + condDefs(esVersion < ESVersion.ES2015)( + defineFunction2(VarField.newJSObjectWithVarargs) { (ctor, args) => val instance = varRef("instance") val result = varRef("result") @@ -1041,9 +1022,9 @@ private[emitter] object CoreJSLib { Return(If(result === Null(), instance, result))) ) } - ), + ) ::: - defineFunction2("resolveSuperRef") { (superClass, propName) => + defineFunction2(VarField.resolveSuperRef) { (superClass, propName) => val getPrototypeOf = varRef("getPrototypeOf") val getOwnPropertyDescriptor = varRef("getOwnPropertyDescriptor") val superProto = varRef("superProto") @@ -1059,14 +1040,14 @@ private[emitter] object CoreJSLib { superProto := Apply(getPrototypeOf, superProto :: Nil) )) ) - }, + } ::: - defineFunction3("superGet") { (superClass, self, propName) => + defineFunction3(VarField.superGet) { (superClass, self, propName) => val desc = varRef("desc") val getter = varRef("getter") Block( - const(desc, genCallHelper("resolveSuperRef", superClass, propName)), + const(desc, genCallHelper(VarField.resolveSuperRef, superClass, propName)), If(desc !== Undefined(), Block( const(getter, genIdentBracketSelect(desc, "get")), Return(If(getter !== Undefined(), @@ -1074,14 +1055,14 @@ private[emitter] object CoreJSLib { genIdentBracketSelect(getter, "value"))) )) ) - }, + } ::: - defineFunction4("superSet") { (superClass, self, propName, value) => + defineFunction4(VarField.superSet) { (superClass, self, propName, value) => val desc = varRef("desc") val setter = varRef("setter") Block( - const(desc, genCallHelper("resolveSuperRef", superClass, propName)), + const(desc, genCallHelper(VarField.resolveSuperRef, superClass, propName)), If(desc !== Undefined(), Block( const(setter, genIdentBracketSelect(desc, "set")), If(setter !== Undefined(), Block( @@ -1095,9 +1076,9 @@ private[emitter] object CoreJSLib { } ) - private def defineModuleHelpers(): Tree = { - condTree(moduleKind == ModuleKind.CommonJSModule)( - defineFunction1("moduleDefault") { m => + private def defineModuleHelpers(): List[Tree] = { + condDefs(moduleKind == ModuleKind.CommonJSModule)( + defineFunction1(VarField.moduleDefault) { m => Return(If( m && (typeof(m) === str("object")) && (str("default") in m), BracketSelect(m, str("default")), @@ -1106,22 +1087,22 @@ private[emitter] object CoreJSLib { ) } - private def defineIntrinsics(): Tree = Block( - condTree(arrayIndexOutOfBounds != CheckedBehavior.Unchecked)( - defineFunction5("arraycopyCheckBounds") { (srcLen, srcPos, destLen, destPos, length) => + private def defineIntrinsics(): List[Tree] = ( + condDefs(arrayIndexOutOfBounds != CheckedBehavior.Unchecked)( + defineFunction5(VarField.arraycopyCheckBounds) { (srcLen, srcPos, destLen, destPos, length) => If((srcPos < 0) || (destPos < 0) || (length < 0) || (srcPos > ((srcLen - length) | 0)) || (destPos > ((destLen - length) | 0)), { - genCallHelper("throwArrayIndexOutOfBoundsException", Null()) + genCallHelper(VarField.throwArrayIndexOutOfBoundsException, Null()) }) } - ), + ) ::: - defineFunction5("arraycopyGeneric") { (srcArray, srcPos, destArray, destPos, length) => + defineFunction5(VarField.arraycopyGeneric) { (srcArray, srcPos, destArray, destPos, length) => val i = varRef("i") Block( if (arrayIndexOutOfBounds != CheckedBehavior.Unchecked) { - genCallHelper("arraycopyCheckBounds", srcArray.length, + genCallHelper(VarField.arraycopyCheckBounds, srcArray.length, srcPos, destArray.length, destPos, length) } else { Skip() @@ -1136,26 +1117,26 @@ private[emitter] object CoreJSLib { }) }) ) - }, + } ::: - condTree(esVersion < ESVersion.ES2015)( - defineFunction5("systemArraycopy") { (src, srcPos, dest, destPos, length) => - genCallHelper("arraycopyGeneric", src.u, srcPos, dest.u, destPos, length) + condDefs(esVersion < ESVersion.ES2015)( + defineFunction5(VarField.systemArraycopy) { (src, srcPos, dest, destPos, length) => + genCallHelper(VarField.arraycopyGeneric, src.u, srcPos, dest.u, destPos, length) } - ), - condTree(esVersion >= ESVersion.ES2015 && nullPointers != CheckedBehavior.Unchecked)( - defineFunction5("systemArraycopy") { (src, srcPos, dest, destPos, length) => + ) ::: + condDefs(esVersion >= ESVersion.ES2015 && nullPointers != CheckedBehavior.Unchecked)( + defineFunction5(VarField.systemArraycopy) { (src, srcPos, dest, destPos, length) => Apply(src DOT "copyTo", List(srcPos, dest, destPos, length)) } - ), + ) ::: - condTree(arrayStores != CheckedBehavior.Unchecked)(Block( - defineFunction5("systemArraycopyRefs") { (src, srcPos, dest, destPos, length) => + condDefs(arrayStores != CheckedBehavior.Unchecked)( + defineFunction5(VarField.systemArraycopyRefs) { (src, srcPos, dest, destPos, length) => If(Apply(genIdentBracketSelect(dest DOT classData, "isAssignableFrom"), List(src DOT classData)), { /* Fast-path, no need for array store checks. This always applies * for arrays of the same type, and a fortiori, when `src eq dest`. */ - genCallHelper("arraycopyGeneric", src.u, srcPos, dest.u, destPos, length) + genCallHelper(VarField.arraycopyGeneric, src.u, srcPos, dest.u, destPos, length) }, { /* Slow copy with "set" calls for every element. By construction, * we have `src ne dest` in this case. @@ -1165,7 +1146,7 @@ private[emitter] object CoreJSLib { Block( const(srcArray, src.u), condTree(arrayIndexOutOfBounds != CheckedBehavior.Unchecked) { - genCallHelper("arraycopyCheckBounds", srcArray.length, + genCallHelper(VarField.arraycopyCheckBounds, srcArray.length, srcPos, dest.u.length, destPos, length) }, For(let(i, 0), i < length, i := ((i + 1) | 0), { @@ -1173,10 +1154,10 @@ private[emitter] object CoreJSLib { }) ) }) - }, + } ::: - defineFunction5("systemArraycopyFull") { (src, srcPos, dest, destPos, length) => - val ObjectArray = globalVar("ac", ObjectClass) + defineFunction5(VarField.systemArraycopyFull) { (src, srcPos, dest, destPos, length) => + val ObjectArray = globalVar(VarField.ac, ObjectClass) val srcData = varRef("srcData") Block( @@ -1187,28 +1168,28 @@ private[emitter] object CoreJSLib { // Fast path: the values are array of the same type genUncheckedArraycopy(List(src, srcPos, dest, destPos, length)) }, { - genCallHelper("throwArrayStoreException", Null()) + genCallHelper(VarField.throwArrayStoreException, Null()) }) }, { /* src and dest are of different types; the only situation that * can still be valid is if they are two reference array types. */ If((src instanceof ObjectArray) && (dest instanceof ObjectArray), { - genCallHelper("systemArraycopyRefs", src, srcPos, dest, destPos, length) + genCallHelper(VarField.systemArraycopyRefs, src, srcPos, dest, destPos, length) }, { - genCallHelper("throwArrayStoreException", Null()) + genCallHelper(VarField.throwArrayStoreException, Null()) }) }) ) } - )), + ) ::: // systemIdentityHashCode locally { val WeakMapRef = globalRef("WeakMap") - val lastIDHash = fileLevelVar("lastIDHash") - val idHashCodeMap = fileLevelVar("idHashCodeMap") + val lastIDHash = fileLevelVar(VarField.lastIDHash) + val idHashCodeMap = fileLevelVar(VarField.idHashCodeMap) val obj = varRef("obj") val biHash = varRef("biHash") @@ -1217,7 +1198,7 @@ private[emitter] object CoreJSLib { def functionSkeleton(defaultImpl: Tree): Function = { def genHijackedMethodApply(className: ClassName, arg: Tree): Tree = - Apply(globalVar("f", (className, hashCodeMethodName)), arg :: Nil) + Apply(globalVar(VarField.f, (className, hashCodeMethodName)), arg :: Nil) def genReturnHijackedMethodApply(className: ClassName): Tree = Return(genHijackedMethodApply(className, obj)) @@ -1334,91 +1315,90 @@ private[emitter] object CoreJSLib { } } - Block( + List( let(lastIDHash, 0), const(idHashCodeMap, if (esVersion >= ESVersion.ES2015) New(WeakMapRef, Nil) - else If(typeof(WeakMapRef) !== str("undefined"), New(WeakMapRef, Nil), Null())), + else If(typeof(WeakMapRef) !== str("undefined"), New(WeakMapRef, Nil), Null())) + ) ::: ( if (esVersion >= ESVersion.ES2015) { val f = weakMapBasedFunction - defineFunction("systemIdentityHashCode", f.args, f.body) + defineFunction(VarField.systemIdentityHashCode, f.args, f.body) } else { - extractWithGlobals(globalVarDef("systemIdentityHashCode", CoreVar, + extractWithGlobals(globalVarDef(VarField.systemIdentityHashCode, CoreVar, If(idHashCodeMap !== Null(), weakMapBasedFunction, fieldBasedFunction))) } ) } ) - private def defineIsPrimitiveFunctions(): Tree = { - def defineIsIntLike(name: String, specificTest: VarRef => Tree): Tree = { + private def defineIsPrimitiveFunctions(): List[Tree] = { + def defineIsIntLike(name: VarField, specificTest: VarRef => Tree): List[Tree] = { defineFunction1(name) { v => Return((typeof(v) === str("number")) && specificTest(v) && ((int(1) / v) !== (int(1) / double(-0.0)))) } } - Block( - defineIsIntLike("isByte", v => (v << 24 >> 24) === v), - defineIsIntLike("isShort", v => (v << 16 >> 16) === v), - defineIsIntLike("isInt", v => (v | 0) === v), - condTree(allowBigIntsForLongs)( - defineFunction1("isLong") { v => - Return((typeof(v) === str("bigint")) && - (Apply(genIdentBracketSelect(BigIntRef, "asIntN"), int(64) :: v :: Nil) === v)) - } - ), - condTree(strictFloats)( - defineFunction1("isFloat") { v => - Return((typeof(v) === str("number")) && - ((v !== v) || (genCallPolyfillableBuiltin(FroundBuiltin, v) === v))) - } - ) + defineIsIntLike(VarField.isByte, v => (v << 24 >> 24) === v) ::: + defineIsIntLike(VarField.isShort, v => (v << 16 >> 16) === v) ::: + defineIsIntLike(VarField.isInt, v => (v | 0) === v) ::: + condDefs(allowBigIntsForLongs)( + defineFunction1(VarField.isLong) { v => + Return((typeof(v) === str("bigint")) && + (Apply(genIdentBracketSelect(BigIntRef, "asIntN"), int(64) :: v :: Nil) === v)) + } + ) ::: + condDefs(strictFloats)( + defineFunction1(VarField.isFloat) { v => + Return((typeof(v) === str("number")) && + ((v !== v) || (genCallPolyfillableBuiltin(FroundBuiltin, v) === v))) + } ) } - private def defineBoxFunctions(): Tree = Block( + private def defineBoxFunctions(): List[Tree] = ( // Boxes for Chars - defineFunction1("bC") { c => - Return(New(globalVar("Char", CoreVar), c :: Nil)) - }, - extractWithGlobals(globalVarDef("bC0", CoreVar, genCallHelper("bC", 0))), - + defineFunction1(VarField.bC) { c => + Return(New(globalVar(VarField.Char, CoreVar), c :: Nil)) + } ::: + extractWithGlobals(globalVarDef(VarField.bC0, CoreVar, genCallHelper(VarField.bC, 0))) + ) ::: ( if (asInstanceOfs != CheckedBehavior.Unchecked) { // Unboxes for everything - def defineUnbox(name: String, boxedClassName: ClassName, resultExpr: VarRef => Tree): Tree = { + def defineUnbox(name: VarField, boxedClassName: ClassName, resultExpr: VarRef => Tree): List[Tree] = { val fullName = boxedClassName.nameString defineFunction1(name)(v => Return { If(genIsInstanceOfHijackedClass(v, boxedClassName) || (v === Null()), resultExpr(v), - genCallHelper("throwClassCastException", v, str(fullName))) + genCallHelper(VarField.throwClassCastException, v, str(fullName))) }) } - Block( - defineUnbox("uV", BoxedUnitClass, _ => Undefined()), - defineUnbox("uZ", BoxedBooleanClass, v => !(!v)), - defineUnbox("uC", BoxedCharacterClass, v => If(v === Null(), 0, v DOT "c")), - defineUnbox("uB", BoxedByteClass, _ | 0), - defineUnbox("uS", BoxedShortClass, _ | 0), - defineUnbox("uI", BoxedIntegerClass, _ | 0), - defineUnbox("uJ", BoxedLongClass, v => If(v === Null(), genLongZero(), v)), + ( + defineUnbox(VarField.uV, BoxedUnitClass, _ => Undefined()) ::: + defineUnbox(VarField.uZ, BoxedBooleanClass, v => !(!v)) ::: + defineUnbox(VarField.uC, BoxedCharacterClass, v => If(v === Null(), 0, v DOT "c")) ::: + defineUnbox(VarField.uB, BoxedByteClass, _ | 0) ::: + defineUnbox(VarField.uS, BoxedShortClass, _ | 0) ::: + defineUnbox(VarField.uI, BoxedIntegerClass, _ | 0) ::: + defineUnbox(VarField.uJ, BoxedLongClass, v => If(v === Null(), genLongZero(), v)) ::: /* Since the type test ensures that v is either null or a float, we can * use + instead of fround. */ - defineUnbox("uF", BoxedFloatClass, v => +v), + defineUnbox(VarField.uF, BoxedFloatClass, v => +v) ::: - defineUnbox("uD", BoxedDoubleClass, v => +v), - defineUnbox("uT", BoxedStringClass, v => If(v === Null(), StringLiteral(""), v)) + defineUnbox(VarField.uD, BoxedDoubleClass, v => +v) ::: + defineUnbox(VarField.uT, BoxedStringClass, v => If(v === Null(), StringLiteral(""), v)) ) } else { // Unboxes for Chars and Longs - Block( - defineFunction1("uC") { v => + ( + defineFunction1(VarField.uC) { v => Return(If(v === Null(), 0, v DOT "c")) - }, - defineFunction1("uJ") { v => + } ::: + defineFunction1(VarField.uJ) { v => Return(If(v === Null(), genLongZero(), v)) } ) @@ -1430,9 +1410,9 @@ private[emitter] object CoreJSLib { * Other array classes are created dynamically from their TypeData's * `initArray` initializer, and extend the array class for `Object`. */ - private def defineSpecializedArrayClasses(): Tree = Block( - for (componentTypeRef <- specializedArrayTypeRefs) yield { - val ArrayClass = globalVar("ac", componentTypeRef) + private def defineSpecializedArrayClasses(): List[Tree] = { + specializedArrayTypeRefs.flatMap { componentTypeRef => + val ArrayClass = globalVar(VarField.ac, componentTypeRef) val isArrayOfObject = componentTypeRef == ClassRef(ObjectClass) val isTypedArray = usesUnderlyingTypedArray(componentTypeRef) @@ -1453,7 +1433,7 @@ private[emitter] object CoreJSLib { val boundsCheck = { If((i < 0) || (i >= This().u.length), - genCallHelper("throwArrayIndexOutOfBoundsException", i)) + genCallHelper(VarField.throwArrayIndexOutOfBoundsException, i)) } List( @@ -1496,7 +1476,7 @@ private[emitter] object CoreJSLib { if (isTypedArray) { Block( if (semantics.arrayIndexOutOfBounds != CheckedBehavior.Unchecked) { - genCallHelper("arraycopyCheckBounds", This().u.length, + genCallHelper(VarField.arraycopyCheckBounds, This().u.length, srcPos, dest.u.length, destPos, length) } else { Skip() @@ -1507,7 +1487,7 @@ private[emitter] object CoreJSLib { Nil) ) } else { - genCallHelper("arraycopyGeneric", This().u, srcPos, + genCallHelper(VarField.arraycopyGeneric, This().u, srcPos, dest.u, destPos, length) } }) @@ -1524,30 +1504,28 @@ private[emitter] object CoreJSLib { val members = getAndSet ::: copyTo ::: clone :: Nil if (useClassesForRegularClasses) { - extractWithGlobals(globalClassDef("ac", componentTypeRef, - Some(globalVar("c", ObjectClass)), ctor :: members)) + extractWithGlobals(globalClassDef(VarField.ac, componentTypeRef, + Some(globalVar(VarField.c, ObjectClass)), ctor :: members)) } else { - val clsDef = Block( - extractWithGlobals(globalFunctionDef("ac", componentTypeRef, - ctor.args, ctor.restParam, ctor.body)), - (ArrayClass.prototype := New(globalVar("h", ObjectClass), Nil)), - (ArrayClass.prototype DOT "constructor" := ArrayClass), + val clsDef = { + extractWithGlobals(globalFunctionDef(VarField.ac, componentTypeRef, + ctor.args, ctor.restParam, ctor.body)) ::: + (ArrayClass.prototype := New(globalVar(VarField.h, ObjectClass), Nil)) :: + (ArrayClass.prototype DOT "constructor" := ArrayClass) :: assignES5ClassMembers(ArrayClass, members) - ) + } componentTypeRef match { case _: ClassRef => - Block( - clsDef, - extractWithGlobals(globalFunctionDef("ah", ObjectClass, Nil, None, Skip())), - (globalVar("ah", ObjectClass).prototype := ArrayClass.prototype) - ) + clsDef ::: + extractWithGlobals(globalFunctionDef(VarField.ah, ObjectClass, Nil, None, Skip())) ::: + (globalVar(VarField.ah, ObjectClass).prototype := ArrayClass.prototype) :: Nil case _: PrimRef => clsDef } } } - ) + } private def genArrayClassConstructorBody(arg: VarRef, componentTypeRef: NonArrayTypeRef): Tree = { @@ -1555,7 +1533,7 @@ private[emitter] object CoreJSLib { If(typeof(arg) === str("number"), { val arraySizeCheck = condTree(negativeArraySizes != CheckedBehavior.Unchecked) { - If(arg < 0, genCallHelper("throwNegativeArraySizeException")) + If(arg < 0, genCallHelper(VarField.throwNegativeArraySizeException)) } getArrayUnderlyingTypedArrayClassRef(componentTypeRef) match { @@ -1579,7 +1557,7 @@ private[emitter] object CoreJSLib { }) } - private def defineTypeDataClass(): Tree = { + private def defineTypeDataClass(): List[Tree] = { def privateFieldSet(fieldName: String, value: Tree): Tree = This() DOT fieldName := value @@ -1646,7 +1624,7 @@ private[emitter] object CoreJSLib { genArrowFunction(paramList(obj), Return(bool(false)))), If(arrayClass !== Undefined(), { // it is undefined for void privateFieldSet("_arrayOf", - Apply(New(globalVar("TypeData", CoreVar), Nil) DOT "initSpecializedArray", + Apply(New(globalVar(VarField.TypeData, CoreVar), Nil) DOT "initSpecializedArray", List(This(), arrayClass, typedArrayClass))) }), Return(This()) @@ -1670,7 +1648,7 @@ private[emitter] object CoreJSLib { paramList(internalNameObj, isInterface, fullName, ancestors, isJSType, parentData, isInstance), None, { Block( - const(internalName, genCallHelper("propertyName", internalNameObj)), + const(internalName, genCallHelper(VarField.propertyName, internalNameObj)), if (globalKnowledge.isParentDataAccessed) privateFieldSet("parentData", parentData) else @@ -1787,13 +1765,13 @@ private[emitter] object CoreJSLib { val boundsCheck = condTree(arrayIndexOutOfBounds != CheckedBehavior.Unchecked) { If((i < 0) || (i >= This().u.length), - genCallHelper("throwArrayIndexOutOfBoundsException", i)) + genCallHelper(VarField.throwArrayIndexOutOfBoundsException, i)) } val storeCheck = { If((v !== Null()) && !(componentData DOT "isJSType") && !Apply(genIdentBracketSelect(componentData, "isInstance"), v :: Nil), - genCallHelper("throwArrayStoreException", v)) + genCallHelper(VarField.throwArrayStoreException, v)) } List( @@ -1816,7 +1794,7 @@ private[emitter] object CoreJSLib { val length = varRef("length") val methodDef = MethodDef(static = false, Ident("copyTo"), paramList(srcPos, dest, destPos, length), None, { - genCallHelper("arraycopyGeneric", This().u, srcPos, + genCallHelper(VarField.arraycopyGeneric, This().u, srcPos, dest.u, destPos, length) }) methodDef :: Nil @@ -1832,14 +1810,14 @@ private[emitter] object CoreJSLib { val members = set ::: copyTo ::: clone :: Nil if (useClassesForRegularClasses) { - ClassDef(Some(ArrayClass.ident), Some(globalVar("ac", ObjectClass)), + ClassDef(Some(ArrayClass.ident), Some(globalVar(VarField.ac, ObjectClass)), ctor :: members) } else { Block( - FunctionDef(ArrayClass.ident, ctor.args, ctor.restParam, ctor.body), - ArrayClass.prototype := New(globalVar("ah", ObjectClass), Nil), - ArrayClass.prototype DOT "constructor" := ArrayClass, - assignES5ClassMembers(ArrayClass, members) + FunctionDef(ArrayClass.ident, ctor.args, ctor.restParam, ctor.body) :: + (ArrayClass.prototype := New(globalVar(VarField.ah, ObjectClass), Nil)) :: + (ArrayClass.prototype DOT "constructor" := ArrayClass) :: + assignES5ClassMembers(ArrayClass, members) ) } } @@ -1887,7 +1865,7 @@ private[emitter] object CoreJSLib { Block( If(!(This() DOT "_arrayOf"), This() DOT "_arrayOf" := - Apply(New(globalVar("TypeData", CoreVar), Nil) DOT "initArray", This() :: Nil), + Apply(New(globalVar(VarField.TypeData, CoreVar), Nil) DOT "initArray", This() :: Nil), Skip()), Return(This() DOT "_arrayOf") ) @@ -1930,7 +1908,7 @@ private[emitter] object CoreJSLib { if (asInstanceOfs != CheckedBehavior.Unchecked) { If((obj !== Null()) && !(This() DOT "isJSType") && !Apply(genIdentBracketSelect(This(), "isInstance"), obj :: Nil), - genCallHelper("throwClassCastException", obj, genIdentBracketSelect(This(), "name")), + genCallHelper(VarField.throwClassCastException, obj, genIdentBracketSelect(This(), "name")), Skip()) } else { Skip() @@ -1965,7 +1943,7 @@ private[emitter] object CoreJSLib { For(let(i, 0), i < lengths.length, i.++, { arrayClassData := Apply(arrayClassData DOT "getArrayOf", Nil) }), - Return(genCallHelper("newArrayObject", arrayClassData, lengths)) + Return(genCallHelper(VarField.newArrayObject, arrayClassData, lengths)) ) }) } @@ -1996,23 +1974,21 @@ private[emitter] object CoreJSLib { ) if (useClassesForRegularClasses) { - extractWithGlobals(globalClassDef("TypeData", CoreVar, None, ctor :: members)) + extractWithGlobals(globalClassDef(VarField.TypeData, CoreVar, None, ctor :: members)) } else { - Block( - defineFunction("TypeData", ctor.args, ctor.body), - assignES5ClassMembers(globalVar("TypeData", CoreVar), members) - ) + defineFunction(VarField.TypeData, ctor.args, ctor.body) ::: + assignES5ClassMembers(globalVar(VarField.TypeData, CoreVar), members) } } - private def defineSpecializedIsArrayOfFunctions(): Tree = { + private def defineSpecializedIsArrayOfFunctions(): List[Tree] = { // isArrayOf_O val obj = varRef("obj") val depth = varRef("depth") val data = varRef("data") val arrayDepth = varRef("arrayDepth") - val forObj = extractWithGlobals(globalFunctionDef("isArrayOf", ObjectClass, paramList(obj, depth), None, { + val forObj = extractWithGlobals(globalFunctionDef(VarField.isArrayOf, ObjectClass, paramList(obj, depth), None, { Block( const(data, obj && (obj DOT "$classData")), If(!data, { @@ -2030,22 +2006,22 @@ private[emitter] object CoreJSLib { ) })) - val forPrims = for (primRef <- orderedPrimRefsWithoutVoid) yield { + val forPrims = orderedPrimRefsWithoutVoid.flatMap { primRef => val obj = varRef("obj") val depth = varRef("depth") - extractWithGlobals(globalFunctionDef("isArrayOf", primRef, paramList(obj, depth), None, { + extractWithGlobals(globalFunctionDef(VarField.isArrayOf, primRef, paramList(obj, depth), None, { Return(!(!(obj && (obj DOT classData) && ((obj DOT classData DOT "arrayDepth") === depth) && ((obj DOT classData DOT "arrayBase") === genClassDataOf(primRef))))) })) } - Block(forObj :: forPrims) + forObj ::: forPrims } - private def defineSpecializedAsArrayOfFunctions(): Tree = { - condTree(asInstanceOfs != CheckedBehavior.Unchecked)(Block( - for (typeRef <- specializedArrayTypeRefs) yield { + private def defineSpecializedAsArrayOfFunctions(): List[Tree] = { + condDefs(asInstanceOfs != CheckedBehavior.Unchecked)( + specializedArrayTypeRefs.flatMap { typeRef => val encodedName = typeRef match { case typeRef: PrimRef => typeRef.charCode.toString() case _ => "L" + ObjectClass.nameString + ";" @@ -2053,18 +2029,18 @@ private[emitter] object CoreJSLib { val obj = varRef("obj") val depth = varRef("depth") - extractWithGlobals(globalFunctionDef("asArrayOf", typeRef, paramList(obj, depth), None, { - If(Apply(globalVar("isArrayOf", typeRef), obj :: depth :: Nil) || (obj === Null()), { + extractWithGlobals(globalFunctionDef(VarField.asArrayOf, typeRef, paramList(obj, depth), None, { + If(Apply(globalVar(VarField.isArrayOf, typeRef), obj :: depth :: Nil) || (obj === Null()), { Return(obj) }, { - genCallHelper("throwArrayCastException", obj, str(encodedName), depth) + genCallHelper(VarField.throwArrayCastException, obj, str(encodedName), depth) }) })) } - )) + ) } - private def defineSpecializedTypeDatas(): Tree = { + private def defineSpecializedTypeDatas(): List[Tree] = { /* d_O must be first to correctly populate the parentData of array * classes. Unlike all other type datas, we assign the first of d_O * directly in the generated code, rather than through an `initXyz` @@ -2079,7 +2055,7 @@ private[emitter] object CoreJSLib { val that = varRef("that") val obj = varRef("obj") - val typeDataVar = globalVar("d", ObjectClass) + val typeDataVar = globalVar(VarField.d, ObjectClass) def privateFieldSet(fieldName: String, value: Tree): Tree = typeDataVar DOT fieldName := value @@ -2087,9 +2063,9 @@ private[emitter] object CoreJSLib { def publicFieldSet(fieldName: String, value: Tree): Tree = genIdentBracketSelect(typeDataVar, fieldName) := value - Block( - extractWithGlobals( - globalVarDef("d", ObjectClass, New(globalVar("TypeData", CoreVar), Nil))), + extractWithGlobals( + globalVarDef(VarField.d, ObjectClass, New(globalVar(VarField.TypeData, CoreVar), Nil))) ::: + List( privateFieldSet("ancestors", ObjectConstr(List((Ident(genName(ObjectClass)) -> 1)))), privateFieldSet("arrayEncodedName", str("L" + fullName + ";")), privateFieldSet("isAssignableFromFun", { @@ -2101,9 +2077,9 @@ private[emitter] object CoreJSLib { publicFieldSet("isInstance", genArrowFunction(paramList(obj), Return(obj !== Null()))), privateFieldSet("_arrayOf", { - Apply(New(globalVar("TypeData", CoreVar), Nil) DOT "initSpecializedArray", List( + Apply(New(globalVar(VarField.TypeData, CoreVar), Nil) DOT "initSpecializedArray", List( typeDataVar, - globalVar("ac", ObjectClass), + globalVar(VarField.ac, ObjectClass), Undefined(), // typedArray genArrowFunction(paramList(that), { val thatDepth = varRef("thatDepth") @@ -2118,11 +2094,11 @@ private[emitter] object CoreJSLib { }) )) }), - globalVar("c", ObjectClass).prototype DOT "$classData" := typeDataVar + globalVar(VarField.c, ObjectClass).prototype DOT "$classData" := typeDataVar ) } - val prims = for (primRef <- orderedPrimRefs) yield { + val prims = orderedPrimRefs.flatMap { primRef => /* Zero value, for use by the intrinsified code of * `scala.collection.mutable.ArrayBuilder.genericArrayBuilderResult`. * This code is Scala-specific, and "unboxes" `null` as the zero of @@ -2143,8 +2119,8 @@ private[emitter] object CoreJSLib { Undefined() } - extractWithGlobals(globalVarDef("d", primRef, { - Apply(New(globalVar("TypeData", CoreVar), Nil) DOT "initPrim", + extractWithGlobals(globalVarDef(VarField.d, primRef, { + Apply(New(globalVar(VarField.TypeData, CoreVar), Nil) DOT "initPrim", List(zero, str(primRef.charCode.toString()), str(primRef.displayName), if (primRef == VoidRef) Undefined() @@ -2153,38 +2129,38 @@ private[emitter] object CoreJSLib { })) } - Block(obj :: prims) + obj ::: prims } - private def defineFunction(name: String, args: List[ParamDef], body: Tree): Tree = + private def defineFunction(name: VarField, args: List[ParamDef], body: Tree): List[Tree] = extractWithGlobals(globalFunctionDef(name, CoreVar, args, None, body)) private val argRefs = List.tabulate(5)(i => varRef("arg" + i)) - private def defineFunction0(name: String)(body: Tree): Tree = + private def defineFunction0(name: VarField)(body: Tree): List[Tree] = defineFunction(name, Nil, body) - private def defineFunction1(name: String)(body: VarRef => Tree): Tree = { + private def defineFunction1(name: VarField)(body: VarRef => Tree): List[Tree] = { val a :: _ = argRefs defineFunction(name, paramList(a), body(a)) } - private def defineFunction2(name: String)(body: (VarRef, VarRef) => Tree): Tree = { + private def defineFunction2(name: VarField)(body: (VarRef, VarRef) => Tree): List[Tree] = { val a :: b :: _ = argRefs defineFunction(name, paramList(a, b), body(a, b)) } - private def defineFunction3(name: String)(body: (VarRef, VarRef, VarRef) => Tree): Tree = { + private def defineFunction3(name: VarField)(body: (VarRef, VarRef, VarRef) => Tree): List[Tree] = { val a :: b :: c :: _ = argRefs defineFunction(name, paramList(a, b, c), body(a, b, c)) } - private def defineFunction4(name: String)(body: (VarRef, VarRef, VarRef, VarRef) => Tree): Tree = { + private def defineFunction4(name: VarField)(body: (VarRef, VarRef, VarRef, VarRef) => Tree): List[Tree] = { val a :: b :: c :: d :: _ = argRefs defineFunction(name, paramList(a, b, c, d), body(a, b, c, d)) } - private def defineFunction5(name: String)(body: (VarRef, VarRef, VarRef, VarRef, VarRef) => Tree): Tree = { + private def defineFunction5(name: VarField)(body: (VarRef, VarRef, VarRef, VarRef, VarRef) => Tree): List[Tree] = { val a :: b :: c :: d :: e :: _ = argRefs defineFunction(name, paramList(a, b, c, d, e), body(a, b, c, d, e)) } @@ -2216,6 +2192,10 @@ private[emitter] object CoreJSLib { if (cond) tree else Skip() + private def condDefs(cond: Boolean)(trees: => List[Tree]): List[Tree] = + if (cond) trees + else Nil + private def varRef(name: String): VarRef = VarRef(Ident(name)) private def const(ref: VarRef, rhs: Tree): LocalDef = diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala index 0c3babc589..d5b23e4525 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala @@ -241,11 +241,11 @@ final class Emitter(config: Emitter.Config) { * requires consistency between the Analyzer and the Emitter. As such, * it is crucial that we verify it. */ - val defTreesIterator: Iterator[js.Tree] = ( + val defTrees: List[js.Tree] = ( /* The definitions of the CoreJSLib that come before the definition * of `j.l.Object`. They depend on nothing else. */ - coreJSLib.map(_.preObjectDefinitions).iterator ++ + coreJSLib.iterator.flatMap(_.preObjectDefinitions) ++ /* The definition of `j.l.Object` class. Unlike other classes, this * does not include its instance tests nor metadata. @@ -257,7 +257,7 @@ final class Emitter(config: Emitter.Config) { * definitions of the array classes, as well as type data for * primitive types and for `j.l.Object`. */ - coreJSLib.map(_.postObjectDefinitions).iterator ++ + coreJSLib.iterator.flatMap(_.postObjectDefinitions) ++ /* All class definitions, except `j.l.Object`, which depend on * nothing but their superclasses. @@ -267,7 +267,7 @@ final class Emitter(config: Emitter.Config) { /* The initialization of the CoreJSLib, which depends on the * definition of classes (n.b. the RuntimeLong class). */ - coreJSLib.map(_.initialization).iterator ++ + coreJSLib.iterator.flatMap(_.initialization) ++ /* All static field definitions, which depend on nothing, except * those of type Long which need $L0. @@ -288,17 +288,7 @@ final class Emitter(config: Emitter.Config) { /* Module initializers, which by spec run at the end. */ moduleInitializers.iterator - ) - - /* Flatten all the top-level js.Block's, because we temporarily use - * them to gather several top-level trees under a single `js.Tree`. - * TODO We should improve this in the future. - */ - val defTrees: List[js.Tree] = defTreesIterator.flatMap { - case js.Block(stats) => stats - case js.Skip() => Nil - case stat => stat :: Nil - }.toList + ).toList // Make sure that there is at least one non-import definition. assert(!defTrees.isEmpty, { @@ -391,9 +381,6 @@ final class Emitter(config: Emitter.Config) { val main = List.newBuilder[js.Tree] - def addToMain(treeWithGlobals: WithGlobals[js.Tree]): Unit = - main += extractWithGlobals(treeWithGlobals) - val (linkedInlineableInit, linkedMethods) = classEmitter.extractInlineableInit(linkedClass)(classCache) @@ -420,7 +407,7 @@ final class Emitter(config: Emitter.Config) { val methodCache = classCache.getStaticLikeMethodCache(namespace, methodDef.methodName) - addToMain(methodCache.getOrElseUpdate(methodDef.version, + main ++= extractWithGlobals(methodCache.getOrElseUpdate(methodDef.version, classEmitter.genStaticLikeMethod(className, methodDef)(moduleContext, methodCache))) } } @@ -582,7 +569,7 @@ final class Emitter(config: Emitter.Config) { useESClass, // invalidated by class version (depends on kind, config and ancestry only) ctor, // invalidated directly memberMethods, // invalidated directly - exportedMembers // invalidated directly + exportedMembers.flatten // invalidated directly )(moduleContext, fullClassCache, linkedClass.pos) // pos invalidated by class version } yield { clazz @@ -590,7 +577,7 @@ final class Emitter(config: Emitter.Config) { }) } - addToMain(fullClass) + main ++= extractWithGlobals(fullClass) } if (className != ObjectClass) { @@ -608,12 +595,12 @@ final class Emitter(config: Emitter.Config) { */ if (classEmitter.needInstanceTests(linkedClass)(classCache)) { - addToMain(classTreeCache.instanceTests.getOrElseUpdate( + main += extractWithGlobals(classTreeCache.instanceTests.getOrElseUpdate( classEmitter.genInstanceTests(className, kind)(moduleContext, classCache, linkedClass.pos))) } if (linkedClass.hasRuntimeTypeInfo) { - addToMain(classTreeCache.typeData.getOrElseUpdate( + main ++= extractWithGlobals(classTreeCache.typeData.getOrElseUpdate( classEmitter.genTypeData( className, // invalidated by overall class cache (part of ancestors) kind, // invalidated by class version @@ -630,7 +617,7 @@ final class Emitter(config: Emitter.Config) { } if (linkedClass.kind.hasModuleAccessor && linkedClass.hasInstances) { - addToMain(classTreeCache.moduleAccessor.getOrElseUpdate( + main ++= extractWithGlobals(classTreeCache.moduleAccessor.getOrElseUpdate( classEmitter.genModuleAccessor(className, kind)(moduleContext, classCache, linkedClass.pos))) } @@ -772,15 +759,15 @@ final class Emitter(config: Emitter.Config) { private[this] var _cacheUsed = false private[this] val _methodCaches = - Array.fill(MemberNamespace.Count)(mutable.Map.empty[MethodName, MethodCache[js.Tree]]) + Array.fill(MemberNamespace.Count)(mutable.Map.empty[MethodName, MethodCache[List[js.Tree]]]) private[this] val _memberMethodCache = mutable.Map.empty[MethodName, MethodCache[js.MethodDef]] - private[this] var _constructorCache: Option[MethodCache[js.Tree]] = None + private[this] var _constructorCache: Option[MethodCache[List[js.Tree]]] = None private[this] val _exportedMembersCache = - mutable.Map.empty[Int, MethodCache[js.Tree]] + mutable.Map.empty[Int, MethodCache[List[js.Tree]]] private[this] var _fullClassCache: Option[FullClassCache] = None @@ -820,20 +807,20 @@ final class Emitter(config: Emitter.Config) { } def getStaticLikeMethodCache(namespace: MemberNamespace, - methodName: MethodName): MethodCache[js.Tree] = { + methodName: MethodName): MethodCache[List[js.Tree]] = { _methodCaches(namespace.ordinal) .getOrElseUpdate(methodName, new MethodCache) } - def getConstructorCache(): MethodCache[js.Tree] = { + def getConstructorCache(): MethodCache[List[js.Tree]] = { _constructorCache.getOrElse { - val cache = new MethodCache[js.Tree] + val cache = new MethodCache[List[js.Tree]] _constructorCache = Some(cache) cache } } - def getExportedMemberCache(idx: Int): MethodCache[js.Tree] = + def getExportedMemberCache(idx: Int): MethodCache[List[js.Tree]] = _exportedMembersCache.getOrElseUpdate(idx, new MethodCache) def getFullClassCache(): FullClassCache = { @@ -863,7 +850,7 @@ final class Emitter(config: Emitter.Config) { } } - private final class MethodCache[T <: js.Tree] extends knowledgeGuardian.KnowledgeAccessor { + private final class MethodCache[T] extends knowledgeGuardian.KnowledgeAccessor { private[this] var _tree: WithGlobals[T] = null private[this] var _lastVersion: Version = Version.Unversioned private[this] var _cacheUsed = false @@ -899,11 +886,11 @@ final class Emitter(config: Emitter.Config) { } private class FullClassCache extends knowledgeGuardian.KnowledgeAccessor { - private[this] var _tree: WithGlobals[js.Tree] = null + private[this] var _tree: WithGlobals[List[js.Tree]] = null private[this] var _lastVersion: Version = Version.Unversioned - private[this] var _lastCtor: WithGlobals[js.Tree] = null + private[this] var _lastCtor: WithGlobals[List[js.Tree]] = null private[this] var _lastMemberMethods: List[WithGlobals[js.MethodDef]] = null - private[this] var _lastExportedMembers: List[WithGlobals[js.Tree]] = null + private[this] var _lastExportedMembers: List[WithGlobals[List[js.Tree]]] = null private[this] var _cacheUsed = false override def invalidate(): Unit = { @@ -917,9 +904,9 @@ final class Emitter(config: Emitter.Config) { def startRun(): Unit = _cacheUsed = false - def getOrElseUpdate(version: Version, ctor: WithGlobals[js.Tree], - memberMethods: List[WithGlobals[js.MethodDef]], exportedMembers: List[WithGlobals[js.Tree]], - compute: => WithGlobals[js.Tree]): WithGlobals[js.Tree] = { + def getOrElseUpdate(version: Version, ctor: WithGlobals[List[js.Tree]], + memberMethods: List[WithGlobals[js.MethodDef]], exportedMembers: List[WithGlobals[List[js.Tree]]], + compute: => WithGlobals[List[js.Tree]]): WithGlobals[List[js.Tree]] = { @tailrec def allSame[A <: AnyRef](xs: List[A], ys: List[A]): Boolean = { @@ -1049,9 +1036,9 @@ object Emitter { private final class DesugaredClassCache { val privateJSFields = new OneTimeCache[WithGlobals[List[js.Tree]]] val instanceTests = new OneTimeCache[WithGlobals[js.Tree]] - val typeData = new OneTimeCache[WithGlobals[js.Tree]] + val typeData = new OneTimeCache[WithGlobals[List[js.Tree]]] val setTypeData = new OneTimeCache[js.Tree] - val moduleAccessor = new OneTimeCache[WithGlobals[js.Tree]] + val moduleAccessor = new OneTimeCache[WithGlobals[List[js.Tree]]] val staticInitialization = new OneTimeCache[List[js.Tree]] val staticFields = new OneTimeCache[WithGlobals[List[js.Tree]]] } 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 d0cd14d62c..c299d8fdd7 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 @@ -400,7 +400,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { private def newSyntheticVar()(implicit pos: Position): js.Ident = { syntheticVarCounter += 1 - fileLevelVarIdent("$x" + syntheticVarCounter) + fileLevelVarIdent(VarField.x, syntheticVarCounter.toString()) } @inline @@ -482,7 +482,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { implicit pos: Position): WithGlobals[js.Function] = { performOptimisticThenPessimisticRuns { - val thisIdent = fileLevelVarIdent("thiz", thisOriginalName) + val thisIdent = fileLevelVarIdent(VarField.thiz, thisOriginalName) val env = env0.withExplicitThis() val js.Function(jsArrow, jsParams, restParam, jsBody) = desugarToFunctionInternal(arrow = false, params, None, body, isStat, env) @@ -667,7 +667,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { unnest(superClass, qualifier, item, rhs) { (newSuperClass, newQualifier, newItem, newRhs, env0) => implicit val env = env0 - genCallHelper("superSet", transformExprNoChar(newSuperClass), + genCallHelper(VarField.superSet, transformExprNoChar(newSuperClass), transformExprNoChar(newQualifier), transformExprNoChar(item), transformExprNoChar(rhs)) } @@ -678,7 +678,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { if (needToUseGloballyMutableVarSetter(scope)) { unnest(rhs) { (rhs, env0) => implicit val env = env0 - js.Apply(globalVar("u", scope), transformExpr(rhs, lhs.tpe) :: Nil) + js.Apply(globalVar(VarField.u, scope), transformExpr(rhs, lhs.tpe) :: Nil) } } else { // Assign normally. @@ -692,7 +692,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case StoreModule(className, value) => unnest(value) { (newValue, env0) => implicit val env = env0 - js.Assign(globalVar("n", className), transformExprNoChar(newValue)) + js.Assign(globalVar(VarField.n, className), transformExprNoChar(newValue)) } case While(cond, body) => @@ -773,7 +773,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { } else { val superCtor = { if (globalKnowledge.hasStoredSuperClass(enclosingClassName)) { - fileLevelVar("superClass") + fileLevelVar(VarField.superClass) } else { val superClass = globalKnowledge.getSuperClassOfJSClass(enclosingClassName) @@ -879,9 +879,9 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case (PrimArray(srcPrimRef), PrimArray(destPrimRef)) if srcPrimRef == destPrimRef => genUncheckedArraycopy(jsArgs) case (RefArray(), RefArray()) => - genCallHelper("systemArraycopyRefs", jsArgs: _*) + genCallHelper(VarField.systemArraycopyRefs, jsArgs: _*) case _ => - genCallHelper("systemArraycopyFull", jsArgs: _*) + genCallHelper(VarField.systemArraycopyFull, jsArgs: _*) } } } @@ -2209,7 +2209,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { genSelect(transformExprNoChar(checkNotNull(qualifier)), className, field) case SelectStatic(className, item) => - globalVar("t", (className, item.name)) + globalVar(VarField.t, (className, item.name)) case SelectJSNativeMember(className, member) => val jsNativeLoadSpec = @@ -2247,10 +2247,10 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { js.Apply(newReceiver(false) DOT transformMethodIdent(method), newArgs) def genDispatchApply(): js.Tree = - genCallHelper("dp_" + genName(methodName), newReceiver(false) :: newArgs: _*) + js.Apply(globalVar(VarField.dp, methodName), newReceiver(false) :: newArgs) def genHijackedMethodApply(className: ClassName): js.Tree = - genApplyStaticLike("f", className, method, newReceiver(className == BoxedCharacterClass) :: newArgs) + genApplyStaticLike(VarField.f, className, method, newReceiver(className == BoxedCharacterClass) :: newArgs) if (isMaybeHijackedClass(receiver.tpe) && !methodName.isReflectiveProxy) { @@ -2300,20 +2300,20 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { val transformedArgs = newReceiver :: newArgs if (flags.isConstructor) { - genApplyStaticLike("ct", className, method, transformedArgs) + genApplyStaticLike(VarField.ct, className, method, transformedArgs) } else if (flags.isPrivate) { - genApplyStaticLike("p", className, method, transformedArgs) + genApplyStaticLike(VarField.p, className, method, transformedArgs) } else if (globalKnowledge.isInterface(className)) { - genApplyStaticLike("f", className, method, transformedArgs) + genApplyStaticLike(VarField.f, className, method, transformedArgs) } else { val fun = - globalVar("c", className).prototype DOT transformMethodIdent(method) + globalVar(VarField.c, className).prototype DOT transformMethodIdent(method) js.Apply(fun DOT "call", transformedArgs) } case ApplyStatic(flags, className, method, args) => genApplyStaticLike( - if (flags.isPrivate) "ps" else "s", + if (flags.isPrivate) VarField.ps else VarField.s, className, method, transformTypedArgs(method.name, args)) @@ -2354,7 +2354,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { else genLongMethodApply(newLhs, LongImpl.toInt) case DoubleToInt => - genCallHelper("doubleToInt", newLhs) + genCallHelper(VarField.doubleToInt, newLhs) case DoubleToFloat => genFround(newLhs) @@ -2366,14 +2366,14 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { genLongMethodApply(newLhs, LongImpl.toDouble) case DoubleToLong => if (useBigIntForLongs) - genCallHelper("doubleToLong", newLhs) + genCallHelper(VarField.doubleToLong, newLhs) else genLongModuleApply(LongImpl.fromDouble, newLhs) // Long -> Float (neither widening nor narrowing) case LongToFloat => if (useBigIntForLongs) - genCallHelper("longToFloat", newLhs) + genCallHelper(VarField.longToFloat, newLhs) else genLongMethodApply(newLhs, LongImpl.toFloat) @@ -2458,14 +2458,14 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case IntLiteral(r) if r != 0 => or0(js.BinaryOp(JSBinaryOp./, newLhs, newRhs)) case _ => - genCallHelper("intDiv", newLhs, newRhs) + genCallHelper(VarField.intDiv, newLhs, newRhs) } case Int_% => rhs match { case IntLiteral(r) if r != 0 => or0(js.BinaryOp(JSBinaryOp.%, newLhs, newRhs)) case _ => - genCallHelper("intMod", newLhs, newRhs) + genCallHelper(VarField.intMod, newLhs, newRhs) } case Int_| => js.BinaryOp(JSBinaryOp.|, newLhs, newRhs) @@ -2513,7 +2513,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case LongLiteral(r) if r != 0L => wrapBigInt64(js.BinaryOp(JSBinaryOp./, newLhs, newRhs)) case _ => - genCallHelper("longDiv", newLhs, newRhs) + genCallHelper(VarField.longDiv, newLhs, newRhs) } } else { genLongMethodApply(newLhs, LongImpl./, newRhs) @@ -2524,7 +2524,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case LongLiteral(r) if r != 0L => wrapBigInt64(js.BinaryOp(JSBinaryOp.%, newLhs, newRhs)) case _ => - genCallHelper("longMod", newLhs, newRhs) + genCallHelper(VarField.longMod, newLhs, newRhs) } } else { genLongMethodApply(newLhs, LongImpl.%, newRhs) @@ -2631,7 +2631,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case String_charAt => semantics.stringIndexOutOfBounds match { case CheckedBehavior.Compliant | CheckedBehavior.Fatal => - genCallHelper("charAt", newLhs, newRhs) + genCallHelper(VarField.charAt, newLhs, newRhs) case CheckedBehavior.Unchecked => js.Apply(genIdentBracketSelect(newLhs, "charCodeAt"), List(newRhs)) } @@ -2672,7 +2672,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { extractWithGlobals(genAsInstanceOf(transformExprNoChar(expr), tpe)) case GetClass(expr) => - genCallHelper("objectGetClass", transformExprNoChar(expr)) + genCallHelper(VarField.objectGetClass, transformExprNoChar(expr)) case Clone(expr) => val newExpr = transformExprNoChar(checkNotNull(expr)) @@ -2700,15 +2700,15 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { */ case ClassType(CloneableClass) | ClassType(SerializableClass) | ClassType(ObjectClass) | AnyType => - genCallHelper("objectOrArrayClone", newExpr) + genCallHelper(VarField.objectOrArrayClone, newExpr) // Otherwise, it is known not to be an array. case _ => - genCallHelper("objectClone", newExpr) + genCallHelper(VarField.objectClone, newExpr) } case IdentityHashCode(expr) => - genCallHelper("systemIdentityHashCode", transformExprNoChar(expr)) + genCallHelper(VarField.systemIdentityHashCode, transformExprNoChar(expr)) case WrapAsThrowable(expr) => val newExpr = transformExprNoChar(expr).asInstanceOf[js.VarRef] @@ -2727,7 +2727,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { // Transients case Transient(CheckNotNull(obj)) => - genCallHelper("n", transformExpr(obj, preserveChar = true)) + genCallHelper(VarField.n, transformExpr(obj, preserveChar = true)) case Transient(AssumeNotNull(obj)) => transformExpr(obj, preserveChar = true) @@ -2753,7 +2753,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { } case Transient(ObjectClassName(obj)) => - genCallHelper("objectClassName", transformExprNoChar(obj)) + genCallHelper(VarField.objectClassName, transformExprNoChar(obj)) case Transient(ArrayToTypedArray(expr, primRef)) => val value = transformExprNoChar(checkNotNull(expr)) @@ -2800,7 +2800,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case Transient(JSNewVararg(constr, argsArray)) => assert(!es2015, s"generated a JSNewVargs with ES 2015+ at ${tree.pos}") - genCallHelper("newJSObjectWithVarargs", + genCallHelper(VarField.newJSObjectWithVarargs, transformExprNoChar(constr), transformExprNoChar(argsArray)) case JSPrivateSelect(qualifier, className, field) => @@ -2840,7 +2840,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { transformExprNoChar(method)), args.map(transformJSArg)) case JSSuperSelect(superClass, qualifier, item) => - genCallHelper("superGet", transformExprNoChar(superClass), + genCallHelper(VarField.superGet, transformExprNoChar(superClass), transformExprNoChar(qualifier), transformExprNoChar(item)) case JSImportCall(arg) => @@ -2902,7 +2902,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { js.UnaryOp(JSUnaryOp.typeof, transformExprNoChar(globalRef)) case JSLinkingInfo() => - globalVar("linkingInfo", CoreVar) + globalVar(VarField.linkingInfo, CoreVar) // Literals @@ -2943,10 +2943,10 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { js.This() case VarKind.ExplicitThisAlias => - fileLevelVar("thiz") + fileLevelVar(VarField.thiz) case VarKind.ClassCapture => - fileLevelVar("cc", genName(name.name)) + fileLevelVar(VarField.cc, genName(name.name)) } case Transient(JSVarRef(name, _)) => @@ -2954,7 +2954,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case This() => if (env.hasExplicitThis) - fileLevelVar("thiz") + fileLevelVar(VarField.thiz) else js.This() @@ -2971,7 +2971,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { for ((value, expectedType) <- captureValues.zip(expectedTypes)) yield transformExpr(value, expectedType) } - js.Apply(globalVar("a", className), transformedArgs) + js.Apply(globalVar(VarField.a, className), transformedArgs) // Invalid trees @@ -2984,7 +2984,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { if (preserveChar || tree.tpe != CharType) baseResult else - genCallHelper("bC", baseResult) + genCallHelper(VarField.bC, baseResult) } private def transformApplyDynamicImport(tree: ApplyDynamicImport)( @@ -3012,7 +3012,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { } val innerCall = extractWithGlobals { - withDynamicGlobalVar("s", (className, method.name)) { v => + withDynamicGlobalVar(VarField.s, (className, method.name)) { v => js.Apply(v, newArgs) } } @@ -3232,7 +3232,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { keepOnlyDangerousVarNames = false) } - private def genApplyStaticLike(field: String, className: ClassName, + private def genApplyStaticLike(field: VarField, className: ClassName, method: MethodIdent, args: List[js.Tree])( implicit pos: Position): js.Tree = { js.Apply(globalVar(field, (className, method.name)), args) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala index 6e3c9f22d9..c5e3aba380 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala @@ -157,17 +157,15 @@ private[emitter] final class JSGen(val config: Emitter.Config) { } def assignES5ClassMembers(classRef: Tree, members: List[MethodDef])( - implicit pos: Position): Tree = { + implicit pos: Position): List[Tree] = { import TreeDSL._ - val stats = for { + for { MethodDef(static, name, args, restParam, body) <- members } yield { val target = if (static) classRef else classRef.prototype genPropSelect(target, name) := Function(arrow = false, args, restParam, body) } - - Block(stats) } def genIIFE(captures: List[(ParamDef, Tree)], body: 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 index 743c9b437d..908d264a9f 100644 --- 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 @@ -15,7 +15,7 @@ package org.scalajs.linker.backend.emitter import org.scalajs.linker.interface.ESVersion private[emitter] sealed abstract class PolyfillableBuiltin( - val builtinName: String, val availableInESVersion: ESVersion) + val polyfillField: VarField, val availableInESVersion: ESVersion) private[emitter] object PolyfillableBuiltin { lazy val All: List[PolyfillableBuiltin] = List( @@ -27,18 +27,18 @@ private[emitter] object PolyfillableBuiltin { ) sealed abstract class GlobalVarBuiltin(val globalVar: String, - builtinName: String, availableInESVersion: ESVersion) - extends PolyfillableBuiltin(builtinName, availableInESVersion) + polyfillField: VarField, availableInESVersion: ESVersion) + extends PolyfillableBuiltin(polyfillField, availableInESVersion) sealed abstract class NamespacedBuiltin(val namespaceGlobalVar: String, - builtinName: String, availableInESVersion: ESVersion) - extends PolyfillableBuiltin(builtinName, availableInESVersion) + val builtinName: String, polyfillField: VarField, availableInESVersion: ESVersion) + extends PolyfillableBuiltin(polyfillField, 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 ObjectIsBuiltin extends NamespacedBuiltin("Object", "is", VarField.is, ESVersion.ES2015) + case object ImulBuiltin extends NamespacedBuiltin("Math", "imul", VarField.imul, ESVersion.ES2015) + case object FroundBuiltin extends NamespacedBuiltin("Math", "fround", VarField.fround, ESVersion.ES2015) case object PrivateSymbolBuiltin - extends GlobalVarBuiltin("Symbol", "privateJSFieldSymbol", ESVersion.ES2015) - case object GetOwnPropertyDescriptorsBuiltin - extends NamespacedBuiltin("Object", "getOwnPropertyDescriptors", ESVersion.ES2017) + extends GlobalVarBuiltin("Symbol", VarField.privateJSFieldSymbol, ESVersion.ES2015) + case object GetOwnPropertyDescriptorsBuiltin extends NamespacedBuiltin("Object", + "getOwnPropertyDescriptors", VarField.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 e36ee63582..0449e0ed92 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 @@ -87,7 +87,7 @@ private[emitter] final class SJSGen( if (useBigIntForLongs) BigIntLiteral(0L) else - globalVar("L0", CoreVar) + globalVar(VarField.L0, CoreVar) } def genBoxedZeroOf(tpe: Type)( @@ -100,7 +100,7 @@ private[emitter] final class SJSGen( def genBoxedCharZero()( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { - globalVar("bC0", CoreVar) + globalVar(VarField.bC0, CoreVar) } def genLongModuleApply(methodName: MethodName, args: Tree*)( @@ -153,7 +153,7 @@ private[emitter] final class SJSGen( if (esFeatures.esVersion >= ESVersion.ES2015 && semantics.nullPointers == CheckedBehavior.Unchecked) Apply(args.head DOT "copyTo", args.tail) else - genCallHelper("systemArraycopy", args: _*) + genCallHelper(VarField.systemArraycopy, args: _*) } def genSelect(receiver: Tree, className: ClassName, field: irt.FieldIdent)( @@ -178,7 +178,7 @@ private[emitter] final class SJSGen( pos: Position): Tree = { val fieldName = { implicit val pos = field.pos - globalVar("r", (className, field.name)) + globalVar(VarField.r, (className, field.name)) } BracketSelect(receiver, fieldName) @@ -199,7 +199,7 @@ private[emitter] final class SJSGen( !globalKnowledge.isInterface(className)) { genIsInstanceOfClass(expr, className) } else { - Apply(globalVar("is", className), List(expr)) + Apply(globalVar(VarField.is, className), List(expr)) } case ArrayType(arrayTypeRef) => @@ -207,15 +207,15 @@ private[emitter] final class SJSGen( case ArrayTypeRef(_:PrimRef | ClassRef(ObjectClass), 1) => expr instanceof genArrayConstrOf(arrayTypeRef) case ArrayTypeRef(base, depth) => - Apply(typeRefVar("isArrayOf", base), List(expr, IntLiteral(depth))) + Apply(typeRefVar(VarField.isArrayOf, base), List(expr, IntLiteral(depth))) } case UndefType => expr === Undefined() case BooleanType => typeof(expr) === "boolean" - case CharType => expr instanceof globalVar("Char", CoreVar) - case ByteType => genCallHelper("isByte", expr) - case ShortType => genCallHelper("isShort", expr) - case IntType => genCallHelper("isInt", expr) + case CharType => expr instanceof globalVar(VarField.Char, CoreVar) + case ByteType => genCallHelper(VarField.isByte, expr) + case ShortType => genCallHelper(VarField.isShort, expr) + case IntType => genCallHelper(VarField.isInt, expr) case LongType => genIsLong(expr) case FloatType => genIsFloat(expr) case DoubleType => typeof(expr) === "number" @@ -239,7 +239,7 @@ private[emitter] final class SJSGen( */ BooleanLiteral(false) } else { - expr instanceof globalVar("c", className) + expr instanceof globalVar(VarField.c, className) } } @@ -251,10 +251,10 @@ private[emitter] final class SJSGen( className match { case BoxedUnitClass => expr === Undefined() case BoxedBooleanClass => typeof(expr) === "boolean" - case BoxedCharacterClass => expr instanceof globalVar("Char", CoreVar) - case BoxedByteClass => genCallHelper("isByte", expr) - case BoxedShortClass => genCallHelper("isShort", expr) - case BoxedIntegerClass => genCallHelper("isInt", expr) + case BoxedCharacterClass => expr instanceof globalVar(VarField.Char, CoreVar) + case BoxedByteClass => genCallHelper(VarField.isByte, expr) + case BoxedShortClass => genCallHelper(VarField.isShort, expr) + case BoxedIntegerClass => genCallHelper(VarField.isInt, expr) case BoxedLongClass => genIsLong(expr) case BoxedFloatClass => genIsFloat(expr) case BoxedDoubleClass => typeof(expr) === "number" @@ -267,8 +267,8 @@ private[emitter] final class SJSGen( pos: Position): Tree = { import TreeDSL._ - if (useBigIntForLongs) genCallHelper("isLong", expr) - else expr instanceof globalVar("c", LongImpl.RuntimeLongClass) + if (useBigIntForLongs) genCallHelper(VarField.isLong, expr) + else expr instanceof globalVar(VarField.c, LongImpl.RuntimeLongClass) } private def genIsFloat(expr: Tree)( @@ -276,7 +276,7 @@ private[emitter] final class SJSGen( pos: Position): Tree = { import TreeDSL._ - if (semantics.strictFloats) genCallHelper("isFloat", expr) + if (semantics.strictFloats) genCallHelper(VarField.isFloat, expr) else typeof(expr) === "number" } @@ -295,9 +295,9 @@ private[emitter] final class SJSGen( case UndefType => wg(Block(expr, Undefined())) case BooleanType => wg(!(!expr)) - case CharType => wg(genCallHelper("uC", expr)) + case CharType => wg(genCallHelper(VarField.uC, expr)) case ByteType | ShortType| IntType => wg(expr | 0) - case LongType => wg(genCallHelper("uJ", expr)) + case LongType => wg(genCallHelper(VarField.uJ, expr)) case DoubleType => wg(UnaryOp(irt.JSUnaryOp.+, expr)) case StringType => wg(expr || StringLiteral("")) @@ -313,21 +313,21 @@ private[emitter] final class SJSGen( case ClassType(ObjectClass) => expr case ClassType(className) => - Apply(globalVar("as", className), List(expr)) + Apply(globalVar(VarField.as, className), List(expr)) case ArrayType(ArrayTypeRef(base, depth)) => - Apply(typeRefVar("asArrayOf", base), List(expr, IntLiteral(depth))) - - case UndefType => genCallHelper("uV", expr) - case BooleanType => genCallHelper("uZ", expr) - case CharType => genCallHelper("uC", expr) - case ByteType => genCallHelper("uB", expr) - case ShortType => genCallHelper("uS", expr) - case IntType => genCallHelper("uI", expr) - case LongType => genCallHelper("uJ", expr) - case FloatType => genCallHelper("uF", expr) - case DoubleType => genCallHelper("uD", expr) - case StringType => genCallHelper("uT", expr) + Apply(typeRefVar(VarField.asArrayOf, base), List(expr, IntLiteral(depth))) + + case UndefType => genCallHelper(VarField.uV, expr) + case BooleanType => genCallHelper(VarField.uZ, expr) + case CharType => genCallHelper(VarField.uC, expr) + case ByteType => genCallHelper(VarField.uB, expr) + case ShortType => genCallHelper(VarField.uS, expr) + case IntType => genCallHelper(VarField.uI, expr) + case LongType => genCallHelper(VarField.uJ, expr) + case FloatType => genCallHelper(VarField.uF, expr) + case DoubleType => genCallHelper(VarField.uD, expr) + case StringType => genCallHelper(VarField.uT, expr) case AnyType => expr case NoType | NullType | NothingType | _:RecordType => @@ -386,7 +386,7 @@ private[emitter] final class SJSGen( BoxedFloatClass ) ::: nonSmallNumberHijackedClassesOrderedForTypeTests - def genCallHelper(helperName: String, args: Tree*)( + def genCallHelper(helperName: VarField, args: Tree*)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { Apply(globalVar(helperName, CoreVar), args.toList) @@ -405,7 +405,7 @@ private[emitter] final class SJSGen( Apply(genIdentBracketSelect(namespace, builtin.builtinName), args.toList) } } else { - WithGlobals(genCallHelper(builtin.builtinName, args: _*)) + WithGlobals(genCallHelper(builtin.polyfillField, args: _*)) } } @@ -413,18 +413,18 @@ private[emitter] final class SJSGen( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { import TreeDSL._ - Apply(globalVar("m", moduleClass), Nil) + Apply(globalVar(VarField.m, moduleClass), Nil) } def genScalaClassNew(className: ClassName, ctor: MethodName, args: Tree*)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { - val encodedClassVar = globalVar("c", className) + val encodedClassVar = globalVar(VarField.c, className) val argsList = args.toList if (globalKnowledge.hasInlineableInit(className)) { New(encodedClassVar, argsList) } else { - Apply(globalVar("ct", (className, ctor)), New(encodedClassVar, Nil) :: argsList) + Apply(globalVar(VarField.ct, (className, ctor)), New(encodedClassVar, Nil) :: argsList) } } @@ -456,7 +456,7 @@ private[emitter] final class SJSGen( def genNonNativeJSClassConstructor(className: ClassName)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { - Apply(globalVar("a", className), Nil) + Apply(globalVar(VarField.a, className), Nil) } def genLoadJSFromSpec(spec: irt.JSNativeLoadSpec, @@ -487,7 +487,7 @@ private[emitter] final class SJSGen( val moduleValue = VarRef(externalModuleFieldIdent(module)) path match { case "default" :: rest if moduleKind == ModuleKind.CommonJSModule => - val defaultField = genCallHelper("moduleDefault", moduleValue) + val defaultField = genCallHelper(VarField.moduleDefault, moduleValue) WithGlobals(pathSelection(defaultField, rest)) case _ => WithGlobals(pathSelection(moduleValue, path)) @@ -513,7 +513,7 @@ private[emitter] final class SJSGen( case length :: Nil => New(genArrayConstrOf(arrayTypeRef), length :: Nil) case _ => - genCallHelper("newArrayObject", genClassDataOf(arrayTypeRef), + genCallHelper(VarField.newArrayObject, genClassDataOf(arrayTypeRef), ArrayConstr(lengths)) } } @@ -551,9 +551,9 @@ private[emitter] final class SJSGen( arrayTypeRef match { case ArrayTypeRef(primRef: PrimRef, 1) => - globalVar("ac", primRef) + globalVar(VarField.ac, primRef) case ArrayTypeRef(ClassRef(ObjectClass), 1) => - globalVar("ac", ObjectClass) + globalVar(VarField.ac, ObjectClass) case _ => genClassDataOf(arrayTypeRef) DOT "constr" } @@ -576,7 +576,7 @@ private[emitter] final class SJSGen( pos: Position): Tree = { typeRef match { case typeRef: NonArrayTypeRef => - typeRefVar("d", typeRef) + typeRefVar(VarField.d, typeRef) case ArrayTypeRef(base, dims) => val baseData = genClassDataOf(base) @@ -598,6 +598,6 @@ private[emitter] final class SJSGen( if (semantics.nullPointers == CheckedBehavior.Unchecked) obj else - genCallHelper("n", obj) + genCallHelper(VarField.n, obj) } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala new file mode 100644 index 0000000000..2be691d96e --- /dev/null +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala @@ -0,0 +1,277 @@ +/* + * 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 + +/** Namespace for generated fields. + * + * Mainly to avoid duplicate strings in memory. + * + * Also gives us additional compile-time safety against typos. + */ +private[emitter] final class VarField private (val str: String) extends AnyVal + +private[emitter] object VarField { + private def mk(str: String): VarField = { + require(str(0) == '$') + new VarField(str) + } + + // Scala class related fields. + + /** Scala classes (constructor functions). */ + final val c = mk("$c") + + /** Inheritable constructor functions for Scala classes. */ + final val h = mk("$h") + + /** Scala class constructors (). */ + final val ct = mk("$ct") + + /** Scala class initializers (). */ + final val sct = mk("$sct") + + /** Private (instance) methods. */ + final val p = mk("$p") + + /** Public static methods. */ + final val s = mk("$s") + + /** Private static methods. */ + final val ps = mk("$ps") + + /** Interface default and hijacked public methods. */ + final val f = mk("$f") + + /** Static fields. */ + final val t = mk("$t") + + /** Scala module accessor. */ + final val m = mk("$m") + + /** Var / let to store Scala module instance. + * + * Also used for null check in CoreJSLib. + */ + final val n = mk("$n") + + // JS class related fields. + + /** JS Class acessor / factories. */ + final val a = mk("$a") + + /** Var / let to store (top-level) JS Class. */ + final val b = mk("$b") + + /** Names for private JS fields. */ + final val r = mk("$r") + + // Reflection + + /** Class data. */ + final val d = mk("$d") + + /** isInstanceOf functions. + * + * Also used as Object.is polyfill. + */ + final val is = mk("$is") + + /** asInstanceOf functions. */ + final val as = mk("$as") + + /** isInstanceOf for array functions. */ + final val isArrayOf = mk("$isArrayOf") + + /** asInstanceOf for array functions. */ + final val asArrayOf = mk("$asArrayOf") + + // Modules + + /** External ES module imports. */ + final val i = mk("$i") + + /** Internal ES module imports. */ + final val j = mk("$j") + + /** ES module const export names. */ + final val e = mk("$e") + + /** Setters for globally mutable vars (for ES Modules). */ + final val u = mk("$u") + + // Local fields: Used to generate non-clashing *local* identifiers. + + /** Synthetic vars for the FunctionEmitter. */ + final val x = mk("$x") + + /** Dummy inheritable constructors for JS classes. */ + final val hh = mk("$hh") + + /** Local field for class captures. */ + final val cc = mk("$cc") + + /** Local field for super class. */ + final val superClass = mk("$superClass") + + /** Local field for this replacement. */ + final val thiz = mk("$thiz") + + /** Local field for dynamic imports. */ + final val module = mk("$module") + + // Core fields: Generated by the CoreJSLib + + /** The linking info object. */ + final val linkingInfo = mk("$linkingInfo") + + /** The TypeData class. */ + final val TypeData = mk("$TypeData") + + /** Long zero. */ + final val L0 = mk("$L0") + + /** Dispatchers. */ + final val dp = mk("$dp") + + // Char + + /** The Char class. */ + final val Char = mk("$Char") + + /** Boxed Char zero. */ + final val bC0 = mk("$bC0") + + /** Box char. */ + final val bC = mk("$bC") + + final val charAt = mk("$charAt") + + // Object helpers + + final val objectClone = mk("$objectClone") + + final val objectOrArrayClone = mk("$objectOrArrayClone") + + final val objectGetClass = mk("$objectGetClass") + + final val objectClassName = mk("$objectClassName") + + final val throwNullPointerException = mk("$throwNullPointerException") + + final val throwModuleInitError = mk("$throwModuleInitError") + + final val valueDescription = mk("$valueDescription") + + final val propertyName = mk("$propertyName") + + // ID hash subsystem + + final val systemIdentityHashCode = mk("$systemIdentityHashCode") + + final val lastIDHash = mk("$lastIDHash") + + final val idHashCodeMap = mk("$idHashCodeMap") + + // Cast helpers + + final val isByte = mk("$isByte") + + final val isShort = mk("$isShort") + + final val isInt = mk("$isInt") + + final val isLong = mk("$isLong") + + final val isFloat = mk("$isFloat") + + final val throwClassCastException = mk("$throwClassCastException") + + final val noIsInstance = mk("$noIsInstance") + + // Unboxes + final val uV = mk("$uV") + final val uZ = mk("$uZ") + final val uC = mk("$uC") + final val uB = mk("$uB") + final val uS = mk("$uS") + final val uI = mk("$uI") + final val uJ = mk("$uJ") + final val uF = mk("$uF") + final val uD = mk("$uD") + final val uT = mk("$uT") + + // Arrays + + /** Array constructors. */ + final val ac = mk("$ac") + + /** Inheritable array constructors. */ + final val ah = mk("$ah") + + final val arraycopyGeneric = mk("$arraycopyGeneric") + + final val arraycopyCheckBounds = mk("$arraycopyCheckBounds") + + final val systemArraycopy = mk("$systemArraycopy") + + final val systemArraycopyRefs = mk("$systemArraycopyRefs") + + final val systemArraycopyFull = mk("$systemArraycopyFull") + + final val newArrayObject = mk("$newArrayObject") + + final val newArrayObjectInternal = mk("$newArrayObjectInternal") + + final val throwArrayCastException = mk("$throwArrayCastException") + + final val throwArrayIndexOutOfBoundsException = mk("$throwArrayIndexOutOFBoundsException") + + final val throwArrayStoreException = mk("$throwArrayStoreException") + + final val throwNegativeArraySizeException = mk("$throwNegativeArraySizeException") + + // JS helpers + + final val newJSObjectWithVarargs = mk("$newJSObjectWithVarargs") + + final val superGet = mk("$superGet") + + final val superSet = mk("$superSet") + + final val resolveSuperRef = mk("$resolveSuperRef") + + final val moduleDefault = mk("$moduleDefault") + + // Arithmetic Call Helpers + + final val intDiv = mk("$intDiv") + + final val intMod = mk("$intMod") + + final val longToFloat = mk("$longToFloat") + + final val longDiv = mk("$longDiv") + + final val longMod = mk("$longMod") + + final val doubleToLong = mk("$doubleToLong") + + final val doubleToInt = mk("$doubleToInt") + + // Polyfills + + final val imul = mk("$imul") + final val fround = mk("$fround") + final val privateJSFieldSymbol = mk("$privateJSFieldSymbol") + final val getOwnPropertyDescriptors = mk("$getOwnPropertyDescriptors") +} diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala index 0ca1cd3deb..b58ac77235 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala @@ -39,7 +39,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, import jsGen._ import nameGen._ - def globalVar[T: Scope](field: String, scope: T, + def globalVar[T: Scope](field: VarField, scope: T, origName: OriginalName = NoOriginalName)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { @@ -51,33 +51,33 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, } } - def globalClassDef[T: Scope](field: String, scope: T, + def globalClassDef[T: Scope](field: VarField, scope: T, parentClass: Option[Tree], members: List[Tree], origName: OriginalName = NoOriginalName)( - implicit moduleContext: ModuleContext, pos: Position): WithGlobals[Tree] = { + implicit moduleContext: ModuleContext, pos: Position): WithGlobals[List[Tree]] = { val ident = globalVarIdent(field, scope, origName) maybeExport(ident, ClassDef(Some(ident), parentClass, members), mutable = false) } - def globalFunctionDef[T: Scope](field: String, scope: T, + def globalFunctionDef[T: Scope](field: VarField, scope: T, args: List[ParamDef], restParam: Option[ParamDef], body: Tree, origName: OriginalName = NoOriginalName)( - implicit moduleContext: ModuleContext, pos: Position): WithGlobals[Tree] = { + implicit moduleContext: ModuleContext, pos: Position): WithGlobals[List[Tree]] = { val ident = globalVarIdent(field, scope, origName) maybeExport(ident, FunctionDef(ident, args, restParam, body), mutable = false) } - def globalVarDef[T: Scope](field: String, scope: T, value: Tree, + def globalVarDef[T: Scope](field: VarField, scope: T, value: Tree, origName: OriginalName = NoOriginalName)( - implicit moduleContext: ModuleContext, pos: Position): WithGlobals[Tree] = { + implicit moduleContext: ModuleContext, pos: Position): WithGlobals[List[Tree]] = { val ident = globalVarIdent(field, scope, origName) maybeExport(ident, genConst(ident, value), mutable = false) } /** Attention: A globalVarDecl may only be modified from the module it was declared in. */ - def globalVarDecl[T: Scope](field: String, scope: T, + def globalVarDecl[T: Scope](field: VarField, scope: T, origName: OriginalName = NoOriginalName)( - implicit moduleContext: ModuleContext, pos: Position): WithGlobals[Tree] = { + implicit moduleContext: ModuleContext, pos: Position): WithGlobals[List[Tree]] = { val ident = globalVarIdent(field, scope, origName) maybeExport(ident, genEmptyMutableLet(ident), mutable = true) } @@ -86,9 +86,9 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, * module. As such, an additional field needs to be provided for an * additional setter. This is used when generating ES modules. */ - def globallyMutableVarDef[T: Scope](field: String, setterField: String, + def globallyMutableVarDef[T: Scope](field: VarField, setterField: VarField, scope: T, value: Tree, origName: OriginalName = NoOriginalName)( - implicit moduleContext: ModuleContext, pos: Position): WithGlobals[Tree] = { + implicit moduleContext: ModuleContext, pos: Position): WithGlobals[List[Tree]] = { val ident = globalVarIdent(field, scope, origName) val varDef = genLet(ident, mutable = true, value) @@ -102,7 +102,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, val exports = Export(genExportIdent(ident) :: genExportIdent(setterIdent) :: Nil) - WithGlobals(Block(varDef, setter, exports)) + WithGlobals(List(varDef, setter, exports)) } else { maybeExport(ident, varDef, mutable = true) } @@ -116,7 +116,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, globalKnowledge.getModule(scopeType.reprClass(scope)) != moduleContext.moduleID } - def globalVarExport[T: Scope](field: String, scope: T, exportName: ExportName, + def globalVarExport[T: Scope](field: VarField, scope: T, exportName: ExportName, origName: OriginalName = NoOriginalName)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { @@ -133,12 +133,12 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, } /** Apply the provided body to a dynamically loaded global var */ - def withDynamicGlobalVar[T: Scope](field: String, scope: T)(body: Tree => Tree)( + def withDynamicGlobalVar[T: Scope](field: VarField, scope: T)(body: Tree => Tree)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): WithGlobals[Tree] = { val ident = globalVarIdent(field, scope) - val module = fileLevelVarIdent("$module") + val module = fileLevelVarIdent(VarField.module) def unitPromise = { globalRef("Promise").map { promise => @@ -186,7 +186,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, } } - private def globalVarIdent[T](field: String, scope: T, + private def globalVarIdent[T](field: VarField, scope: T, origName: OriginalName = NoOriginalName)( implicit pos: Position, scopeType: Scope[T]): Ident = { genericIdent(field, scopeType.subField(scope), origName) @@ -205,7 +205,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, * * Returns the relevant coreJSLibVar for primitive types, globalVar otherwise. */ - def typeRefVar(field: String, typeRef: NonArrayTypeRef)( + def typeRefVar(field: VarField, typeRef: NonArrayTypeRef)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { /* Explicitly bringing `PrimRefScope` and `ClassScope` as local implicit @@ -229,49 +229,49 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, } } - def fileLevelVar(field: String, subField: String, + def fileLevelVar(field: VarField, subField: String, origName: OriginalName = NoOriginalName)( implicit pos: Position): VarRef = { VarRef(fileLevelVarIdent(field, subField, origName)) } - def fileLevelVar(field: String)(implicit pos: Position): VarRef = + def fileLevelVar(field: VarField)(implicit pos: Position): VarRef = VarRef(fileLevelVarIdent(field)) - def fileLevelVarIdent(field: String, subField: String, + def fileLevelVarIdent(field: VarField, subField: String, origName: OriginalName = NoOriginalName)( implicit pos: Position): Ident = { genericIdent(field, subField, origName) } - def fileLevelVarIdent(field: String)(implicit pos: Position): Ident = + def fileLevelVarIdent(field: VarField)(implicit pos: Position): Ident = fileLevelVarIdent(field, NoOriginalName) - def fileLevelVarIdent(field: String, origName: OriginalName)( + def fileLevelVarIdent(field: VarField, origName: OriginalName)( implicit pos: Position): Ident = { genericIdent(field, "", origName) } def externalModuleFieldIdent(moduleName: String)(implicit pos: Position): Ident = - fileLevelVarIdent("i", genModuleName(moduleName), OriginalName(moduleName)) + fileLevelVarIdent(VarField.i, genModuleName(moduleName), OriginalName(moduleName)) def internalModuleFieldIdent(module: ModuleID)(implicit pos: Position): Ident = - fileLevelVarIdent("j", genModuleName(module.id), OriginalName(module.id)) + fileLevelVarIdent(VarField.j, genModuleName(module.id), OriginalName(module.id)) - private def genericIdent(field: String, subField: String, + private def genericIdent(field: VarField, subField: String, origName: OriginalName = NoOriginalName)( implicit pos: Position): Ident = { val name = - if (subField == "") "$" + field - else "$" + field + "_" + subField + if (subField == "") field.str + else field.str + "_" + subField Ident(avoidClashWithGlobalRef(name), origName) } private def maybeExport(ident: Ident, tree: Tree, mutable: Boolean)( - implicit moduleContext: ModuleContext, pos: Position): WithGlobals[Tree] = { + implicit moduleContext: ModuleContext, pos: Position): WithGlobals[List[Tree]] = { if (moduleContext.public) { - WithGlobals(tree) + WithGlobals(tree :: Nil) } else { val exportStat = config.moduleKind match { case ModuleKind.NoModule => @@ -299,7 +299,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, } } - exportStat.map(Block(tree, _)) + exportStat.map(tree :: _ :: Nil) } } @@ -358,6 +358,11 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, def reprClass(x: (ClassName, MethodName)): ClassName = x._1 } + implicit object DispatcherScope extends Scope[MethodName] { + def subField(x: MethodName): String = genName(x) + def reprClass(x: MethodName): ClassName = ObjectClass + } + implicit object CoreJSLibScope extends Scope[CoreVar.type] { def subField(x: CoreVar.type): String = "" def reprClass(x: CoreVar.type): ClassName = ObjectClass diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/WithGlobals.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/WithGlobals.scala index 65b10a9ed1..6ff6b02544 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/WithGlobals.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/WithGlobals.scala @@ -98,10 +98,20 @@ private[emitter] object WithGlobals { * efficient. */ val values = xs.map(_.value) - val globalVarNames = xs.foldLeft(Set.empty[String]) { (prev, x) => + val globalVarNames = collectNames(xs) + WithGlobals(values, globalVarNames) + } + + def flatten[A](xs: List[WithGlobals[List[A]]]): WithGlobals[List[A]] = { + val values = xs.flatMap(_.value) + val globalVarNames = collectNames(xs) + WithGlobals(values, globalVarNames) + } + + private def collectNames(xs: List[WithGlobals[_]]): Set[String] = { + xs.foldLeft(Set.empty[String]) { (prev, x) => unionPreserveEmpty(prev, x.globalVarNames) } - WithGlobals(values, globalVarNames) } def option[A](xs: Option[WithGlobals[A]]): WithGlobals[Option[A]] = diff --git a/project/Build.scala b/project/Build.scala index 0e69559253..8deb00803d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -197,7 +197,7 @@ object MyScalaJSPlugin extends AutoPlugin { */ libraryDependencies ~= { libDeps => val blacklist = - Set("scalajs-compiler", "scalajs-library", "scalajs-test-bridge") + Set("scalajs-compiler", "scalajs-library", "scalajs-scalalib", "scalajs-test-bridge") libDeps.filterNot(dep => blacklist.contains(dep.name)) }, @@ -338,7 +338,7 @@ object Build { val previousVersions = List("1.0.0", "1.0.1", "1.1.0", "1.1.1", "1.2.0", "1.3.0", "1.3.1", "1.4.0", "1.5.0", "1.5.1", "1.6.0", "1.7.0", "1.7.1", "1.8.0", "1.9.0", "1.10.0", "1.10.1", "1.11.0", "1.12.0", "1.13.0", - "1.13.1", "1.13.2") + "1.13.1", "1.13.2", "1.14.0") val previousVersion = previousVersions.last val previousBinaryCrossVersion = CrossVersion.binaryWith("sjs1_", "") @@ -638,7 +638,7 @@ object Build { * - `"semver-spec"` for artifacts whose public API can only break in major releases (e.g., `library`) * * At the moment, we only set the version scheme for artifacts in the - * "library ecosystem", i.e., scalajs-javalib scalajs-library, + * "library ecosystem", i.e., scalajs-javalib, scalajs-scalalib, scalajs-library, * scalajs-test-interface, scalajs-junit-runtime and scalajs-test-bridge. * Artifacts of the "tools ecosystem" do not have a version scheme set, as * the jury is still out on what is the best way to specify them. @@ -749,7 +749,7 @@ object Build { } } - /** Depends on library and, by artificial transitivity, on the javalib. */ + /** Depends on library and, by artificial transitivity, on the javalib and scalalib. */ def dependsOnLibrary2_12: Project = { val library = LocalProject("library2_12") @@ -757,9 +757,9 @@ object Build { val project1 = project .dependsOn(library) - /* Because the javalib's exportsJar is false, but its actual products are - * only in its jar, we must manually add the jar on the internal - * classpath. + /* Because the javalib's and scalalib's exportsJar is false, but their + * actual products are only in their jar, we must manually add the jars + * on the internal classpath. * Once published, only jars are ever used, so this is fine. */ if (isGeneratingForIDE) { @@ -772,6 +772,12 @@ object Build { Test / internalDependencyClasspath += (javalib / Compile / packageBin).value, ) + .settings( + Compile / internalDependencyClasspath += + (scalalib.v2_12 / Compile / packageBin).value, + Test / internalDependencyClasspath += + (scalalib.v2_12 / Compile / packageBin).value, + ) } } @@ -818,21 +824,21 @@ object Build { } } - /** Depends on library and, by transitivity, on the javalib. */ + /** Depends on library and, by artificial transitivity, on the javalib and scalalib. */ def dependsOnLibrary: MultiScalaProject = { // Add a real dependency on the library val project1 = project .dependsOn(library) - /* Because the javalib's exportsJar is false, but its actual products are - * only in its jar, we must manually add the jar on the internal - * classpath. + /* Because the javalib's and scalalib's exportsJar is false, but their + * actual products are only in their jar, we must manually add the jars + * on the internal classpath. * Once published, only jars are ever used, so this is fine. */ if (isGeneratingForIDE) { project1 } else { - // Actually add classpath dependencies on the javalib jar + // Actually add classpath dependencies on the javalib and scalalib jars project1 .settings( Compile / internalDependencyClasspath += @@ -840,6 +846,14 @@ object Build { Test / internalDependencyClasspath += (javalib / Compile / packageBin).value, ) + .zippedSettings(scalalib) { scalalib => + Def.settings( + Compile / internalDependencyClasspath += + (scalalib / Compile / packageBin).value, + Test / internalDependencyClasspath += + (scalalib / Compile / packageBin).value, + ) + } } } @@ -919,7 +933,7 @@ object Build { linkerInterface, linkerInterfaceJS, linker, linkerJS, testAdapter, javalibintf, - javalibInternal, javalib, scalalib, libraryAux, library, + javalibInternal, javalib, scalalibInternal, libraryAux, scalalib, library, testInterface, jUnitRuntime, testBridge, jUnitPlugin, jUnitAsyncJS, jUnitAsyncJVM, jUnitTestOutputsJS, jUnitTestOutputsJVM, helloworld, reversi, testingExample, testSuite, testSuiteJVM, @@ -1358,12 +1372,14 @@ object Build { // JS libs publishLocal in javalib, + publishLocal in scalalib.v2_12, publishLocal in library.v2_12, publishLocal in testInterface.v2_12, publishLocal in testBridge.v2_12, publishLocal in jUnitRuntime.v2_12, publishLocal in irProjectJS.v2_12, + publishLocal in scalalib.v2_13, publishLocal in library.v2_13, publishLocal in testInterface.v2_13, publishLocal in testBridge.v2_13, @@ -1478,7 +1494,7 @@ object Build { * copied from `javalibInternal`. * * This the "public" version of the javalib, as depended on by the `library` - * and published on Maven. + * and `scalalib`, and published on Maven. */ lazy val javalib: Project = Project( id = "javalib", base = file("javalib-public") @@ -1506,8 +1522,13 @@ object Build { }, ) - lazy val scalalib: MultiScalaProject = MultiScalaProject( - id = "scalalib", base = file("scalalib") + /** The project that actually compiles the `scalalib`, but which is not + * exposed. + * + * Instead, its products are copied in `scalalib`. + */ + lazy val scalalibInternal: MultiScalaProject = MultiScalaProject( + id = "scalalibInternal", base = file("scalalib") ).enablePlugins( MyScalaJSPlugin ).settings( @@ -1523,7 +1544,7 @@ object Build { s"https://raw.githubusercontent.com/scala/scala/v${scalaVersion.value}/src/library/") option ++ prev }, - name := "Scala library for Scala.js", + name := "scalajs-scalalib-internal", publishArtifact in Compile := false, NoIDEExport.noIDEExportSettings, delambdafySetting, @@ -1669,12 +1690,54 @@ object Build { recompileAllOrNothingSettings, ).withScalaJSCompiler.dependsOnLibraryNoJar + /** An empty project, without source nor dependencies (other than the javalib), + * whose products are copied from `scalalibInternal` and `libraryAux`. + * + * This the "public" version of the scalalib, as depended on by the `library` + * and published on Maven. + */ + lazy val scalalib: MultiScalaProject = MultiScalaProject( + id = "scalalib", base = file("scalalib-public") + ).dependsOn( + javalib, + ).settings( + commonSettings, + name := "scalajs-scalalib", + publishSettings(Some(VersionScheme.BreakOnMajor)), + + /* The scalalib has a special version number that encodes both the Scala + * version and the Scala.js version. This allows us to back-publish for + * newer versions of Scala and older versions of Scala.js. The Scala + * version comes first so that Ivy resolution will choose 2.13.20+1.15.0 + * over 2.13.18+1.16.0. The former might not be as optimized as the + * latter, but at least it will contain all the binary API that might be + * required. + */ + version := scalaVersion.value + "+" + scalaJSVersion, + + exportJars := false, // very important, otherwise there's a cycle with the `library` + ).zippedSettings(Seq("scalalibInternal", "libraryAux"))(localProjects => + inConfig(Compile)(Seq( + // Use the .sjsir files from scalalibInternal and libraryAux (but not the .class files) + Compile / packageBin / mappings := { + val scalalibInternalMappings = (localProjects(0) / packageBin / mappings).value + val libraryAuxMappings = (localProjects(1) / packageBin / mappings).value + val allMappings = scalalibInternalMappings ++ libraryAuxMappings + allMappings.filter(_._2.endsWith(".sjsir")) + }, + )) + ) + lazy val library: MultiScalaProject = MultiScalaProject( id = "library", base = file("library") ).enablePlugins( MyScalaJSPlugin ).dependsOn( + // Project dependencies javalibintf % Provided, javalib, + ).dependsOn( + // MultiScalaProject dependencies + scalalib, ).settings( commonSettings, publishSettings(Some(VersionScheme.BreakOnMajor)), @@ -1727,25 +1790,6 @@ object Build { */ dependencyClasspath in doc ++= exportedProducts.value, )) - ).zippedSettings(Seq("scalalib", "libraryAux"))(localProjects => - inConfig(Compile)(Seq( - /* Add the .sjsir files from other lib projects - * (but not .class files) - */ - mappings in packageBin := { - val libraryMappings = (mappings in packageBin).value - - val filter = ("*.sjsir": NameFilter) - - val otherProducts = ( - (products in localProjects(0)).value ++ - (products in localProjects(1)).value) - val otherMappings = - otherProducts.flatMap(base => Path.selectSubpaths(base, filter)) - - libraryMappings ++ otherMappings - }, - )) ).withScalaJSCompiler // The Scala.js version of sbt-testing-interface 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 c45eabf03c..6f1d6f66a4 100644 --- a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala +++ b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala @@ -795,6 +795,8 @@ private[sbtplugin] object ScalaJSPluginInternal { scalaOrg %% "scala3-library_sjs1" % scalaV, /* scala3-library_sjs1 depends on some version of scalajs-library_2.13, * but we bump it to be at least scalaJSVersion. + * (It will also depend on some version of scalajs-scalalib_2.13, + * but we do not have to worry about that here.) */ "org.scala-js" % "scalajs-library_2.13" % scalaJSVersion, "org.scala-js" % "scalajs-test-bridge_2.13" % scalaJSVersion % "test" @@ -803,6 +805,12 @@ private[sbtplugin] object ScalaJSPluginInternal { prev ++ Seq( compilerPlugin("org.scala-js" % "scalajs-compiler" % scalaJSVersion cross CrossVersion.full), "org.scala-js" %% "scalajs-library" % scalaJSVersion, + /* scalajs-library depends on some version of scalajs-scalalib, + * but we want to make sure to bump it to be at least the one + * of our own `scalaVersion` (which would have back-published in + * the meantime). + */ + "org.scala-js" %% "scalajs-scalalib" % s"$scalaV+$scalaJSVersion", "org.scala-js" %% "scalajs-test-bridge" % scalaJSVersion % "test" ) } diff --git a/scripts/publish.sh b/scripts/publish.sh index 6a6ac44d00..1158326a4e 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -10,7 +10,7 @@ fi SUFFIXES="2_12 2_13" JAVA_LIBS="javalibintf javalib" -COMPILER="compiler jUnitPlugin" +FULL_SCALA_LIBS="compiler jUnitPlugin scalalib" JS_LIBS="library irJS linkerInterfaceJS linkerJS testInterface testBridge jUnitRuntime" JVM_LIBS="ir linkerInterface linker testAdapter" SCALA_LIBS="$JS_LIBS $JVM_LIBS" @@ -22,10 +22,10 @@ for p in $JAVA_LIBS; do done $CMD $ARGS -# Publish compiler +# Publish artifacts built with the full Scala version for s in $SUFFIXES; do ARGS="" - for p in $COMPILER; do + for p in $FULL_SCALA_LIBS; do ARGS="$ARGS +$p$s/publishSigned" done $CMD $ARGS diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/BufferedReaderTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/BufferedReaderTest.scala new file mode 100644 index 0000000000..c922ef1691 --- /dev/null +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/BufferedReaderTest.scala @@ -0,0 +1,166 @@ +/* + * 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.io + +import java.io._ + +import org.junit.Test +import org.junit.Assert._ + +import org.scalajs.testsuite.utils.AssertThrows.assertThrows + +class BufferedReaderTest { + + val str = "line1\nline2\r\n\nline4\rline5" + def newReader: BufferedReader = new BufferedReader(new StringReader(str), 3) + + @Test def close(): Unit = { + class UnderlyingReader extends StringReader(str) { + var closeCount: Int = 0 + + override def close(): Unit = { + closeCount += 1 + /* Do not call super.close(), to ensure IOExceptions come from + * BufferedReader, and not the underlying reader. + */ + } + } + + val underlying = new UnderlyingReader + val r = new BufferedReader(underlying) + r.read() + assertEquals(0, underlying.closeCount) + r.close() + assertEquals(1, underlying.closeCount) + + // close() actually prevents further use of the reader + assertThrows(classOf[IOException], r.mark(1)) + assertThrows(classOf[IOException], r.read()) + assertThrows(classOf[IOException], r.read(new Array[Char](1), 0, 1)) + assertThrows(classOf[IOException], r.read(new Array[Char](1))) + assertThrows(classOf[IOException], r.readLine()) + assertThrows(classOf[IOException], r.ready()) + assertThrows(classOf[IOException], r.reset()) + assertThrows(classOf[IOException], r.skip(1L)) + assertThrows(classOf[IllegalArgumentException], r.skip(-1L)) + + // close() is idempotent + r.close() + assertEquals(1, underlying.closeCount) + } + + @Test def read(): Unit = { + val r = newReader + + for (c <- str) { + assertEquals(c, r.read().toChar) + } + assertEquals(-1, r.read()) + } + + @Test def readArrayChar(): Unit = { + var read = 0 + val r = newReader + val buf = new Array[Char](15) + + // twice to force filling internal buffer + for (_ <- 0 to 1) { + val len = r.read(buf) + assertTrue(len > 0) + + for (i <- 0 until len) + assertEquals(str.charAt(i + read), buf(i)) + + read += len + } + } + + @Test def readArrayCharIntInt(): Unit = { + var read = 0 + val r = newReader + val buf = new Array[Char](15) + + // twice to force filling internal buffer + for (_ <- 0 to 1) { + val len = r.read(buf, 1, 10) + assertTrue(len > 0) + assertTrue(len < 11) + + for (i <- 0 until len) + assertEquals(str.charAt(i + read), buf(i + 1)) + + read += len + } + } + + @Test def markAndReset(): Unit = { + val r = newReader + assertEquals('l': Int, r.read()) + + // force moving and resizing buffer + r.mark(10) + + for (i <- 0 until 10) { + assertEquals(str.charAt(i + 1): Int, r.read()) + } + + r.reset() + + for (i <- 1 until str.length) { + assertEquals(str.charAt(i): Int, r.read()) + } + } + + @Test def readLine(): Unit = { + val r = newReader + + assertEquals("line1", r.readLine()) + assertEquals("line2", r.readLine()) + assertEquals("", r.readLine()) + assertEquals("line4", r.readLine()) + assertEquals("line5", r.readLine()) + assertEquals(null, r.readLine()) + } + + @Test def readLineEmptyStream(): Unit = { + val r = new BufferedReader(new StringReader("")) + + assertEquals(null, r.readLine()) + } + + @Test def readLineEmptyLinesOnly(): Unit = { + val r = new BufferedReader(new StringReader("\n\r\n\r\r\n"), 1) + + for (_ <- 1 to 4) + assertEquals("", r.readLine()) + + assertEquals(null, r.readLine()) + } + + @Test def skipReturns0AfterReachingEnd(): Unit = { + val r = newReader + assertEquals(25, r.skip(100)) + assertEquals(-1, r.read()) + + assertEquals(0, r.skip(100)) + assertEquals(-1, r.read()) + } + + @Test def markSupported(): Unit = { + assertTrue(newReader.markSupported) + } + + @Test def markThrowsWithNegativeLookahead(): Unit = { + assertThrows(classOf[IllegalArgumentException], newReader.mark(-10)) + } +} diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/FilterReaderTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/FilterReaderTest.scala new file mode 100644 index 0000000000..851d8a7ff0 --- /dev/null +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/FilterReaderTest.scala @@ -0,0 +1,55 @@ +/* + * 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.io + +import java.io._ + +import org.junit.Test +import org.junit.Assert._ + +import org.scalajs.testsuite.utils.AssertThrows.{ + assertThrows, + assertThrowsNPEIfCompliant +} + +class FilterReaderTest { + // use StringReader as delegate + val str = "asdf" + def newFilterReader: FilterReader = new FilterReader(new StringReader(str)) {} + + @Test def nullCtorArgThrows(): Unit = { + assertThrowsNPEIfCompliant(new FilterReader(null) {}) + } + + // test delegation + @Test def close(): Unit = { + val fr = newFilterReader + + fr.close() + fr.close() // multiple is fine + assertThrows(classOf[IOException], fr.read()) + } + + @Test def markSupported(): Unit = { + assertTrue(newFilterReader.markSupported) + } + + @Test def read(): Unit = { + val r = newFilterReader + + for (c <- str) { + assertEquals(c, r.read().toChar) + } + assertEquals(-1, r.read()) + } +} diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/InputStreamReaderTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/InputStreamReaderTest.scala new file mode 100644 index 0000000000..fa4ab4d4d2 --- /dev/null +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/InputStreamReaderTest.scala @@ -0,0 +1,86 @@ +/* + * 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.io + +import scala.annotation.tailrec + +import java.io._ + +import org.junit.Test +import org.junit.Assert._ + +import org.scalajs.testsuite.utils.AssertThrows.assertThrows + +class InputStreamReaderTest { + + @Test def readUTF8(): Unit = { + + val buf = Array[Byte](72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, + 46, -29, -127, -109, -29, -126, -109, -29, -127, -85, -29, -127, -95, + -29, -127, -81, -26, -105, -91, -26, -100, -84, -24, -86, -98, -29, + -126, -110, -24, -86, -83, -29, -126, -127, -29, -127, -66, -29, -127, + -103, -29, -127, -117, -29, -128, -126) + + val r = new InputStreamReader(new ByteArrayInputStream(buf)) + + def expectRead(str: String): Unit = { + val buf = new Array[Char](str.length) + @tailrec + def readAll(readSoFar: Int): Int = { + if (readSoFar == buf.length) readSoFar + else { + val newlyRead = r.read(buf, readSoFar, buf.length - readSoFar) + if (newlyRead == -1) readSoFar + else readAll(readSoFar + newlyRead) + } + } + assertEquals(str.length, readAll(0)) + assertEquals(str, new String(buf)) + } + + expectRead("Hello World.") + expectRead("こんにちは") + expectRead("日本語を読めますか。") + assertEquals(-1, r.read()) + } + + @Test def readEOFThrows(): Unit = { + val data = "Lorem ipsum".getBytes() + val streamReader = new InputStreamReader(new ByteArrayInputStream(data)) + val bytes = new Array[Char](11) + + assertEquals(11, streamReader.read(bytes)) + // Do it twice to check for a regression where this used to throw + assertEquals(-1, streamReader.read(bytes)) + assertEquals(-1, streamReader.read(bytes)) + assertThrows(classOf[IndexOutOfBoundsException], + streamReader.read(bytes, 10, 3)) + assertEquals(0, streamReader.read(new Array[Char](0))) + } + + @Test def skipReturns0AfterReachingEnd(): Unit = { + val data = "Lorem ipsum".getBytes() + val r = new InputStreamReader(new ByteArrayInputStream(data)) + assertTrue(r.skip(100) > 0) + assertEquals(-1, r.read()) + + assertEquals(0, r.skip(100)) + assertEquals(-1, r.read()) + } + + @Test def markThrowsNotSupported(): Unit = { + val data = "Lorem ipsum".getBytes() + val r = new InputStreamReader(new ByteArrayInputStream(data)) + assertThrows(classOf[IOException], r.mark(0)) + } +} diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReaderTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReaderTest.scala new file mode 100644 index 0000000000..0039020c2d --- /dev/null +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReaderTest.scala @@ -0,0 +1,34 @@ +/* + * 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.io + +import java.io._ + +import org.junit.Test +import org.junit.Assert._ + +/** Tests for our implementation of java.io._ reader classes */ +class ReaderTest { + object MyReader extends java.io.Reader { + def read(dbuf: Array[Char], off: Int, len: Int): Int = { + java.util.Arrays.fill(dbuf, off, off + len, 'A') + len + } + def close(): Unit = () + } + + @Test def skipIntIfPossible(): Unit = { + assertEquals(42, MyReader.skip(42)) + assertEquals(10000, MyReader.skip(10000)) // more than the 8192 batch size + } +} diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReadersTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReadersTest.scala deleted file mode 100644 index 65eb2660a9..0000000000 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReadersTest.scala +++ /dev/null @@ -1,366 +0,0 @@ -/* - * 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.io - -import scala.annotation.tailrec - -import java.io._ - -import org.junit.Test -import org.junit.Assert._ - -import org.scalajs.testsuite.utils.AssertThrows.assertThrows - -/** Tests for our implementation of java.io._ reader classes */ -class ReaderTest { - object MyReader extends java.io.Reader { - def read(dbuf: Array[Char], off: Int, len: Int): Int = { - java.util.Arrays.fill(dbuf, off, off + len, 'A') - len - } - def close(): Unit = () - } - - @Test def skipIntIfPossible(): Unit = { - assertEquals(42, MyReader.skip(42)) - assertEquals(10000, MyReader.skip(10000)) // more than the 8192 batch size - } -} - -class StringReaderTest { - val str = "asdf" - def newReader: StringReader = new StringReader(str) - - @Test def read()(): Unit = { - val r = newReader - - for (c <- str) { - assertEquals(c, r.read().toChar) - } - - assertEquals(-1, r.read()) - } - - @Test def readArrayCharIntInt(): Unit = { - val r = newReader - val buf = new Array[Char](10) - - assertEquals(4, r.read(buf, 2, 8)) - assertArrayEquals(buf.map(_.toInt), Array[Int](0,0,'a','s','d','f',0,0,0,0)) - assertEquals(-1, r.read(buf, 2, 8)) // #1560 - } - - @Test def readCharBuffer(): Unit = { - val r = newReader - val buf0 = java.nio.CharBuffer.allocate(25) - buf0.position(3) - val buf = buf0.slice() - buf.position(4) - buf.limit(14) - - assertEquals(4, r.read(buf)) - assertEquals(8, buf.position()) - buf.flip() - assertArrayEquals(buf.toString().map(_.toInt).toArray, - Array[Int](0, 0, 0, 0, 'a', 's', 'd', 'f')) - } - - @Test def ready(): Unit = { - val r = newReader - - for (c <- str) { - assertTrue(r.ready()) - assertEquals(c, r.read().toChar) - } - - assertTrue(r.ready()) - assertEquals(-1, r.read()) - - r.close() - assertThrows(classOf[IOException], r.ready()) - } - - @Test def markReset(): Unit = { - val r = newReader - r.mark(str.length) - - for (c <- str) { - assertEquals(c, r.read().toChar) - } - assertEquals(-1, r.read()) - - r.reset() - - for (c <- str) { - assertEquals(c, r.read().toChar) - } - assertEquals(-1, r.read()) - } - - @Test def skip(): Unit = { - val r = newReader - - assertEquals('a': Int, r.read()) - assertEquals(2, r.skip(2L).toInt) - - assertEquals('f': Int, r.read()) - assertEquals(-1, r.read()) - } - - @Test def close(): Unit = { - val r = newReader - - r.close() - assertThrows(classOf[IOException], r.read()) - } - - @Test def mark(): Unit = { - assertTrue(newReader.markSupported) - } - - @Test def markThrowsWithNegativeLookahead(): Unit = { - assertThrows(classOf[IllegalArgumentException], newReader.mark(-10)) - } - - @Test def skipAcceptsNegativeLookaheadAsLookback(): Unit = { - // StringReader.skip accepts negative lookahead - val r = newReader - assertEquals("already head", 0, r.skip(-1)) - assertEquals('a', r.read()) - - assertEquals(1, r.skip(1)) - assertEquals('d', r.read()) - - assertEquals(-2, r.skip(-2)) - assertEquals('s', r.read()) - } - - @Test def skipReturns0AfterReachingEnd(): Unit = { - val r = newReader - assertEquals(4, r.skip(100)) - assertEquals(-1, r.read()) - - assertEquals(0, r.skip(-100)) - assertEquals(-1, r.read()) - } - -} - -class BufferedReaderTest { - - val str = "line1\nline2\r\n\nline4\rline5" - def newReader: BufferedReader = new BufferedReader(new StringReader(str), 3) - - @Test def close(): Unit = { - class UnderlyingReader extends StringReader(str) { - var closeCount: Int = 0 - - override def close(): Unit = { - closeCount += 1 - /* Do not call super.close(), to ensure IOExceptions come from - * BufferedReader, and not the underlying reader. - */ - } - } - - val underlying = new UnderlyingReader - val r = new BufferedReader(underlying) - r.read() - assertEquals(0, underlying.closeCount) - r.close() - assertEquals(1, underlying.closeCount) - - // close() actually prevents further use of the reader - assertThrows(classOf[IOException], r.mark(1)) - assertThrows(classOf[IOException], r.read()) - assertThrows(classOf[IOException], r.read(new Array[Char](1), 0, 1)) - assertThrows(classOf[IOException], r.read(new Array[Char](1))) - assertThrows(classOf[IOException], r.readLine()) - assertThrows(classOf[IOException], r.ready()) - assertThrows(classOf[IOException], r.reset()) - assertThrows(classOf[IOException], r.skip(1L)) - assertThrows(classOf[IllegalArgumentException], r.skip(-1L)) - - // close() is idempotent - r.close() - assertEquals(1, underlying.closeCount) - } - - @Test def read(): Unit = { - val r = newReader - - for (c <- str) { - assertEquals(c, r.read().toChar) - } - assertEquals(-1, r.read()) - } - - @Test def readArrayChar(): Unit = { - var read = 0 - val r = newReader - val buf = new Array[Char](15) - - // twice to force filling internal buffer - for (_ <- 0 to 1) { - val len = r.read(buf) - assertTrue(len > 0) - - for (i <- 0 until len) - assertEquals(str.charAt(i+read), buf(i)) - - read += len - } - } - - @Test def readArrayCharIntInt(): Unit = { - var read = 0 - val r = newReader - val buf = new Array[Char](15) - - // twice to force filling internal buffer - for (_ <- 0 to 1) { - val len = r.read(buf, 1, 10) - assertTrue(len > 0) - assertTrue(len < 11) - - for (i <- 0 until len) - assertEquals(str.charAt(i+read), buf(i+1)) - - read += len - } - } - - @Test def markAndReset(): Unit = { - val r = newReader - assertEquals('l': Int, r.read()) - - // force moving and resizing buffer - r.mark(10) - - for (i <- 0 until 10) { - assertEquals(str.charAt(i+1): Int, r.read()) - } - - r.reset() - - for (i <- 1 until str.length) { - assertEquals(str.charAt(i): Int, r.read()) - } - } - - @Test def readLine(): Unit = { - val r = newReader - - assertEquals("line1", r.readLine()) - assertEquals("line2", r.readLine()) - assertEquals("", r.readLine()) - assertEquals("line4", r.readLine()) - assertEquals("line5", r.readLine()) - assertEquals(null, r.readLine()) - } - - @Test def readLineEmptyStream(): Unit = { - val r = new BufferedReader(new StringReader("")) - - assertEquals(null, r.readLine()) - } - - @Test def readLineEmptyLinesOnly(): Unit = { - val r = new BufferedReader(new StringReader("\n\r\n\r\r\n"), 1) - - for (_ <- 1 to 4) - assertEquals("", r.readLine()) - - assertEquals(null, r.readLine()) - } - - @Test def skipReturns0AfterReachingEnd(): Unit = { - val r = newReader - assertEquals(25, r.skip(100)) - assertEquals(-1, r.read()) - - assertEquals(0, r.skip(100)) - assertEquals(-1, r.read()) - } - - @Test def markSupported(): Unit = { - assertTrue(newReader.markSupported) - } - - @Test def markThrowsWithNegativeLookahead(): Unit = { - assertThrows(classOf[IllegalArgumentException], newReader.mark(-10)) - } -} - -class InputStreamReaderTest { - - @Test def readUTF8(): Unit = { - - val buf = Array[Byte](72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, - 46, -29, -127, -109, -29, -126, -109, -29, -127, -85, -29, -127, -95, - -29, -127, -81, -26, -105, -91, -26, -100, -84, -24, -86, -98, -29, - -126, -110, -24, -86, -83, -29, -126, -127, -29, -127, -66, -29, -127, - -103, -29, -127, -117, -29, -128, -126) - - val r = new InputStreamReader(new ByteArrayInputStream(buf)) - - def expectRead(str: String): Unit = { - val buf = new Array[Char](str.length) - @tailrec - def readAll(readSoFar: Int): Int = { - if (readSoFar == buf.length) readSoFar - else { - val newlyRead = r.read(buf, readSoFar, buf.length - readSoFar) - if (newlyRead == -1) readSoFar - else readAll(readSoFar + newlyRead) - } - } - assertEquals(str.length, readAll(0)) - assertEquals(str, new String(buf)) - } - - expectRead("Hello World.") - expectRead("こんにちは") - expectRead("日本語を読めますか。") - assertEquals(-1, r.read()) - } - - @Test def readEOFThrows(): Unit = { - val data = "Lorem ipsum".getBytes() - val streamReader = new InputStreamReader(new ByteArrayInputStream(data)) - val bytes = new Array[Char](11) - - assertEquals(11, streamReader.read(bytes)) - // Do it twice to check for a regression where this used to throw - assertEquals(-1, streamReader.read(bytes)) - assertEquals(-1, streamReader.read(bytes)) - assertThrows(classOf[IndexOutOfBoundsException], streamReader.read(bytes, 10, 3)) - assertEquals(0, streamReader.read(new Array[Char](0))) - } - - @Test def skipReturns0AfterReachingEnd(): Unit = { - val data = "Lorem ipsum".getBytes() - val r = new InputStreamReader(new ByteArrayInputStream(data)) - assertTrue(r.skip(100) > 0) - assertEquals(-1, r.read()) - - assertEquals(0, r.skip(100)) - assertEquals(-1, r.read()) - } - - @Test def markThrowsNotSupported(): Unit = { - val data = "Lorem ipsum".getBytes() - val r = new InputStreamReader(new ByteArrayInputStream(data)) - assertThrows(classOf[IOException], r.mark(0)) - } -} diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/StringReaderTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/StringReaderTest.scala new file mode 100644 index 0000000000..0c23d55c7b --- /dev/null +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/StringReaderTest.scala @@ -0,0 +1,139 @@ +/* + * 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.io + +import java.io._ + +import org.junit.Test +import org.junit.Assert._ + +import org.scalajs.testsuite.utils.AssertThrows.assertThrows + +class StringReaderTest { + val str = "asdf" + def newReader: StringReader = new StringReader(str) + + @Test def read()(): Unit = { + val r = newReader + + for (c <- str) { + assertEquals(c, r.read().toChar) + } + + assertEquals(-1, r.read()) + } + + @Test def readArrayCharIntInt(): Unit = { + val r = newReader + val buf = new Array[Char](10) + + assertEquals(4, r.read(buf, 2, 8)) + assertArrayEquals(buf.map(_.toInt), + Array[Int](0, 0, 'a', 's', 'd', 'f', 0, 0, 0, 0)) + assertEquals(-1, r.read(buf, 2, 8)) // #1560 + } + + @Test def readCharBuffer(): Unit = { + val r = newReader + val buf0 = java.nio.CharBuffer.allocate(25) + buf0.position(3) + val buf = buf0.slice() + buf.position(4) + buf.limit(14) + + assertEquals(4, r.read(buf)) + assertEquals(8, buf.position()) + buf.flip() + assertArrayEquals(buf.toString().map(_.toInt).toArray, + Array[Int](0, 0, 0, 0, 'a', 's', 'd', 'f')) + } + + @Test def ready(): Unit = { + val r = newReader + + for (c <- str) { + assertTrue(r.ready()) + assertEquals(c, r.read().toChar) + } + + assertTrue(r.ready()) + assertEquals(-1, r.read()) + + r.close() + assertThrows(classOf[IOException], r.ready()) + } + + @Test def markReset(): Unit = { + val r = newReader + r.mark(str.length) + + for (c <- str) { + assertEquals(c, r.read().toChar) + } + assertEquals(-1, r.read()) + + r.reset() + + for (c <- str) { + assertEquals(c, r.read().toChar) + } + assertEquals(-1, r.read()) + } + + @Test def skip(): Unit = { + val r = newReader + + assertEquals('a': Int, r.read()) + assertEquals(2, r.skip(2L).toInt) + + assertEquals('f': Int, r.read()) + assertEquals(-1, r.read()) + } + + @Test def close(): Unit = { + val r = newReader + + r.close() + assertThrows(classOf[IOException], r.read()) + } + + @Test def mark(): Unit = { + assertTrue(newReader.markSupported) + } + + @Test def markThrowsWithNegativeLookahead(): Unit = { + assertThrows(classOf[IllegalArgumentException], newReader.mark(-10)) + } + + @Test def skipAcceptsNegativeLookaheadAsLookback(): Unit = { + // StringReader.skip accepts negative lookahead + val r = newReader + assertEquals("already head", 0, r.skip(-1)) + assertEquals('a', r.read()) + + assertEquals(1, r.skip(1)) + assertEquals('d', r.read()) + + assertEquals(-2, r.skip(-2)) + assertEquals('s', r.read()) + } + + @Test def skipReturns0AfterReachingEnd(): Unit = { + val r = newReader + assertEquals(4, r.skip(100)) + assertEquals(-1, r.read()) + + assertEquals(0, r.skip(-100)) + assertEquals(-1, r.read()) + } +}