diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index ef5d20f120..8dfacc7a37 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -27,7 +27,15 @@ import scala.reflect.internal.Flags import org.scalajs.ir import org.scalajs.ir.{Trees => js, Types => jstpe, ClassKind, Hashers, OriginalName} -import org.scalajs.ir.Names.{LocalName, SimpleFieldName, FieldName, SimpleMethodName, MethodName, ClassName} +import org.scalajs.ir.Names.{ + LocalName, + SimpleFieldName, + FieldName, + SimpleMethodName, + MethodName, + ClassName, + BoxedStringClass +} import org.scalajs.ir.OriginalName.NoOriginalName import org.scalajs.ir.Trees.OptimizerHints import org.scalajs.ir.Version.Unversioned @@ -5202,10 +5210,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) genStatOrExpr(args(1), isStat) } - case LINKING_INFO => - // runtime.linkingInfo - js.JSLinkingInfo() - case IDENTITY_HASH_CODE => // runtime.identityHashCode(arg) val arg = genArgs1 @@ -5390,6 +5394,22 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) case UNWRAP_FROM_THROWABLE => // js.special.unwrapFromThrowable(arg) js.UnwrapFromThrowable(genArgs1) + + case LINKTIME_PROPERTY => + // LinkingInfo.linkTimePropertyXXX("...") + val arg = genArgs1 + val tpe: jstpe.Type = toIRType(tree.tpe) match { + case jstpe.ClassType(BoxedStringClass, _) => jstpe.StringType + case irType => irType + } + arg match { + case js.StringLiteral(name) => + js.LinkTimeProperty(name)(tpe) + case _ => + reporter.error(args.head.pos, + "The argument of linkTimePropertyXXX must be a String literal: \"...\"") + js.LinkTimeProperty("erroneous")(tpe) + } } } @@ -5501,8 +5521,16 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) def genSelectGet(propName: js.Tree): js.Tree = genSuperReference(propName) - def genSelectSet(propName: js.Tree, value: js.Tree): js.Tree = - js.Assign(genSuperReference(propName), value) + def genSelectSet(propName: js.Tree, value: js.Tree): js.Tree = { + val lhs = genSuperReference(propName) + lhs match { + case js.JSGlobalRef(js.JSGlobalRef.FileLevelThis) => + reporter.error(pos, + "Illegal assignment to global this.") + case _ => + } + js.Assign(lhs, value) + } def genCall(methodName: js.Tree, args: List[js.TreeOrJSSpread]): js.Tree = { diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala index 8214985b6a..5a46388543 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala @@ -127,6 +127,11 @@ trait JSDefinitions { lazy val Runtime_identityHashCode = getMemberMethod(RuntimePackageModule, newTermName("identityHashCode")) lazy val Runtime_dynamicImport = getMemberMethod(RuntimePackageModule, newTermName("dynamicImport")) + lazy val LinkingInfoModule = getRequiredModule("scala.scalajs.LinkingInfo") + lazy val LinkingInfo_linkTimePropertyBoolean = getMemberMethod(LinkingInfoModule, newTermName("linkTimePropertyBoolean")) + lazy val LinkingInfo_linkTimePropertyInt = getMemberMethod(LinkingInfoModule, newTermName("linkTimePropertyInt")) + lazy val LinkingInfo_linkTimePropertyString = getMemberMethod(LinkingInfoModule, newTermName("linkTimePropertyString")) + lazy val DynamicImportThunkClass = getRequiredClass("scala.scalajs.runtime.DynamicImportThunk") lazy val DynamicImportThunkClass_apply = getMemberMethod(DynamicImportThunkClass, nme.apply) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala index df5ff293db..c93709363b 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala @@ -55,8 +55,7 @@ abstract class JSPrimitives { final val CREATE_INNER_JS_CLASS = CONSTRUCTOROF + 1 // runtime.createInnerJSClass final val CREATE_LOCAL_JS_CLASS = CREATE_INNER_JS_CLASS + 1 // runtime.createLocalJSClass final val WITH_CONTEXTUAL_JS_CLASS_VALUE = CREATE_LOCAL_JS_CLASS + 1 // runtime.withContextualJSClassValue - final val LINKING_INFO = WITH_CONTEXTUAL_JS_CLASS_VALUE + 1 // runtime.linkingInfo - final val IDENTITY_HASH_CODE = LINKING_INFO + 1 // runtime.identityHashCode + final val IDENTITY_HASH_CODE = WITH_CONTEXTUAL_JS_CLASS_VALUE + 1 // runtime.identityHashCode final val DYNAMIC_IMPORT = IDENTITY_HASH_CODE + 1 // runtime.dynamicImport final val STRICT_EQ = DYNAMIC_IMPORT + 1 // js.special.strictEquals @@ -69,8 +68,9 @@ abstract class JSPrimitives { final val WRAP_AS_THROWABLE = JS_TRY_CATCH + 1 // js.special.wrapAsThrowable final val UNWRAP_FROM_THROWABLE = WRAP_AS_THROWABLE + 1 // js.special.unwrapFromThrowable final val DEBUGGER = UNWRAP_FROM_THROWABLE + 1 // js.special.debugger + final val LINKTIME_PROPERTY = DEBUGGER + 1 // LinkingInfo.linkTimePropertyXXX - final val LastJSPrimitiveCode = DEBUGGER + final val LastJSPrimitiveCode = LINKTIME_PROPERTY /** Initialize the map of primitive methods (for GenJSCode) */ def init(): Unit = initWithPrimitives(addPrimitive) @@ -109,7 +109,6 @@ abstract class JSPrimitives { addPrimitive(Runtime_createLocalJSClass, CREATE_LOCAL_JS_CLASS) addPrimitive(Runtime_withContextualJSClassValue, WITH_CONTEXTUAL_JS_CLASS_VALUE) - addPrimitive(Runtime_linkingInfo, LINKING_INFO) addPrimitive(Runtime_identityHashCode, IDENTITY_HASH_CODE) addPrimitive(Runtime_dynamicImport, DYNAMIC_IMPORT) @@ -123,6 +122,10 @@ abstract class JSPrimitives { addPrimitive(Special_wrapAsThrowable, WRAP_AS_THROWABLE) addPrimitive(Special_unwrapFromThrowable, UNWRAP_FROM_THROWABLE) addPrimitive(Special_debugger, DEBUGGER) + + addPrimitive(LinkingInfo_linkTimePropertyBoolean, LINKTIME_PROPERTY) + addPrimitive(LinkingInfo_linkTimePropertyInt, LINKTIME_PROPERTY) + addPrimitive(LinkingInfo_linkTimePropertyString, LINKTIME_PROPERTY) } def isJavaScriptPrimitive(code: Int): Boolean = diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSGlobalScopeTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSGlobalScopeTest.scala index 6d5628abf2..19d3f0f8d3 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSGlobalScopeTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSGlobalScopeTest.scala @@ -342,7 +342,7 @@ class JSGlobalScopeTest extends DirectTest with TestHelpers { "extends", "false", "finally", "for", "function", "if", "implements", "import", "in", "instanceof", "interface", "let", "new", "null", "package", "private", "protected", "public", "return", "static", - "super", "switch", "this", "throw", "true", "try", "typeof", "var", + "super", "switch", "throw", "true", "try", "typeof", "var", "void", "while", "with", "yield") for (reservedIdentifier <- reservedIdentifiers) { @@ -497,4 +497,34 @@ class JSGlobalScopeTest extends DirectTest with TestHelpers { } } + @Test + def rejectAssignmentToGlobalThis(): Unit = { + """ + import scala.scalajs.js + import scala.scalajs.js.annotation._ + + object Main { + def main(): Unit = { + js.Dynamic.global.`this` = 0 + GlobalScope.globalThis = 0 + } + } + + @js.native + @JSGlobalScope + object GlobalScope extends js.Any { + @JSName("this") + var globalThis: Any = js.native + } + """ hasErrors + s""" + |newSource1.scala:44: error: Illegal assignment to global this. + | js.Dynamic.global.`this` = 0 + | ^ + |newSource1.scala:45: error: Illegal assignment to global this. + | GlobalScope.globalThis = 0 + | ^ + """ + } + } diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala index 7e140ebd38..47f843c696 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala @@ -170,14 +170,14 @@ class OptimizationTest extends JSASTTest { // Verify the optimized emitted code for 'new js.Object' and 'new js.Array' """ import scala.scalajs.js - class A { val o = new js.Object val a = new js.Array } """. - hasNot("any reference to the global scope") { - case js.JSLinkingInfo() => + hasNot("any reference to the global scope nor loading JS constructor") { + case js.JSGlobalRef(_) => + case js.LoadJSConstructor(_) => } } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala index d37bf739e7..e866e32fb4 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala @@ -478,9 +478,6 @@ object Hashers { mixTag(TagJSTypeOfGlobalRef) mixTree(globalRef) - case JSLinkingInfo() => - mixTag(TagJSLinkingInfo) - case Undefined() => mixTag(TagUndefined) @@ -550,6 +547,11 @@ object Hashers { mixName(className) mixTrees(captureValues) + case LinkTimeProperty(name) => + mixTag(TagLinkTimeProperty) + mixString(name) + mixType(tree.tpe) + case Transient(value) => throw new InvalidIRException(tree, "Cannot hash a transient IR node (its value is of class " + diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala index 34f5743e69..5895bf9773 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -778,9 +778,6 @@ object Printers { print(globalRef) print(")") - case JSLinkingInfo() => - print("") - // Literals case Undefined() => @@ -901,6 +898,11 @@ object Printers { print(className) printRow(captureValues, "](", ", ", ")") + case LinkTimeProperty(name) => + print("(") + print(name) + print(")") + // Transient case Transient(value) => diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index cd695d6894..37521f5065 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,8 +17,8 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.17.1-SNAPSHOT", - binaryEmitted = "1.17" + current = "1.18.0-SNAPSHOT", + binaryEmitted = "1.18-SNAPSHOT" ) /** Helper class to allow for testing of logic. */ diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index ae2eec6b1f..0b3d7bd6a8 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -25,6 +25,7 @@ import Names._ import OriginalName.NoOriginalName import Position._ import Trees._ +import LinkTimeProperty.{ProductionMode, ESVersion, UseECMAScript2015Semantics, IsWebAssembly, LinkerVersion} import Types._ import Tags._ import Version.Unversioned @@ -500,9 +501,6 @@ object Serializers { writeTagAndPos(TagJSTypeOfGlobalRef) writeTree(globalRef) - case JSLinkingInfo() => - writeTagAndPos(TagJSLinkingInfo) - case Undefined() => writeTagAndPos(TagUndefined) @@ -572,6 +570,11 @@ object Serializers { writeName(className) writeTrees(captureValues) + case LinkTimeProperty(name) => + writeTagAndPos(TagLinkTimeProperty) + writeString(name) + writeType(tree.tpe) + case Transient(value) => throw new InvalidIRException(tree, "Cannot serialize a transient IR node (its value is of class " + @@ -1290,9 +1293,32 @@ object Serializers { case TagUnwrapFromThrowable => UnwrapFromThrowable(readTree()) - case TagJSNew => JSNew(readTree(), readTreeOrJSSpreads()) - case TagJSPrivateSelect => JSPrivateSelect(readTree(), readFieldIdent()) - case TagJSSelect => JSSelect(readTree(), readTree()) + case TagJSNew => JSNew(readTree(), readTreeOrJSSpreads()) + case TagJSPrivateSelect => JSPrivateSelect(readTree(), readFieldIdent()) + + case TagJSSelect => + if (hacks.use17 && buf.get(buf.position()) == TagJSLinkingInfo) { + val jsLinkingInfo = readTree() + readTree() match { + case StringLiteral("productionMode") => + LinkTimeProperty(ProductionMode)(BooleanType) + case StringLiteral("esVersion") => + LinkTimeProperty(ESVersion)(IntType) + case StringLiteral("assumingES6") => + LinkTimeProperty(UseECMAScript2015Semantics)(BooleanType) + case StringLiteral("isWebAssembly") => + LinkTimeProperty(IsWebAssembly)(BooleanType) + case StringLiteral("linkerVersion") => + LinkTimeProperty(LinkerVersion)(StringType) + case StringLiteral("fileLevelThis") => + JSGlobalRef(JSGlobalRef.FileLevelThis) + case otherItem => + JSSelect(jsLinkingInfo, otherItem) + } + } else { + JSSelect(readTree(), readTree()) + } + case TagJSFunctionApply => JSFunctionApply(readTree(), readTreeOrJSSpreads()) case TagJSMethodApply => JSMethodApply(readTree(), readTree(), readTreeOrJSSpreads()) case TagJSSuperSelect => JSSuperSelect(readTree(), readTree(), readTree()) @@ -1312,7 +1338,21 @@ object Serializers { JSObjectConstr(List.fill(readInt())((readTree(), readTree()))) case TagJSGlobalRef => JSGlobalRef(readString()) case TagJSTypeOfGlobalRef => JSTypeOfGlobalRef(readTree().asInstanceOf[JSGlobalRef]) - case TagJSLinkingInfo => JSLinkingInfo() + + case TagJSLinkingInfo => + if (hacks.use17) { + JSObjectConstr(List( + (StringLiteral("productionMode"), LinkTimeProperty(ProductionMode)(BooleanType)), + (StringLiteral("esVersion"), LinkTimeProperty(ESVersion)(IntType)), + (StringLiteral("assumingES6"), LinkTimeProperty(UseECMAScript2015Semantics)(BooleanType)), + (StringLiteral("isWebAssembly"), LinkTimeProperty(IsWebAssembly)(BooleanType)), + (StringLiteral("linkerVersion"), LinkTimeProperty(LinkerVersion)(StringType)), + (StringLiteral("fileLevelThis"), JSGlobalRef(JSGlobalRef.FileLevelThis)) + )) + } else { + throw new IOException( + s"Found invalid pre-1.18 JSLinkingInfo def at ${pos}") + } case TagUndefined => Undefined() case TagNull => Null() @@ -1355,6 +1395,9 @@ object Serializers { case TagCreateJSClass => CreateJSClass(readClassName(), readTrees()) + + case TagLinkTimeProperty => + LinkTimeProperty(readString())(readType()) } } @@ -2429,6 +2472,8 @@ object Serializers { assert(sourceVersion != "1.15", "source version 1.15 does not exist") val use16: Boolean = use13 || sourceVersion == "1.16" + + val use17: Boolean = use16 || sourceVersion == "1.17" } /** Names needed for hacks. */ diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala b/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala index 3c3162245b..577a3ceca6 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala @@ -127,6 +127,9 @@ private[ir] object Tags { final val TagWrapAsThrowable = TagJSNewTarget + 1 final val TagUnwrapFromThrowable = TagWrapAsThrowable + 1 + // New in 1.18 + final val TagLinkTimeProperty = TagUnwrapFromThrowable + 1 + // Tags for member defs final val TagFieldDef = 1 diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala index 09b82b1757..0b9719da0f 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala @@ -222,8 +222,8 @@ object Transformers { case _:Skip | _:Debugger | _:LoadModule | _:StoreModule | _:SelectStatic | _:SelectJSNativeMember | _:LoadJSConstructor | - _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | _:JSLinkingInfo | - _:Literal | _:VarRef | _:This | _:JSGlobalRef => + _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | + _:Literal | _:VarRef | _:This | _:JSGlobalRef | _:LinkTimeProperty => tree } } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala index 2f68fac81f..ec39bb1086 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala @@ -221,8 +221,8 @@ object Traversers { case _:Skip | _:Debugger | _:LoadModule | _:StoreModule | _:SelectStatic | _:SelectJSNativeMember | _:LoadJSConstructor | - _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | _:JSLinkingInfo | - _:Literal | _:VarRef | _:This | _:JSGlobalRef => + _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | + _:Literal | _:VarRef | _:This | _:JSGlobalRef | _:LinkTimeProperty => } def traverseClassDef(tree: ClassDef): Unit = { diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index 6cc39f24a8..96145a12f5 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -966,8 +966,15 @@ object Trees { * [[isJSIdentifierName]]) *and* it is not reserved (see * [[ReservedJSIdentifierNames]]). */ - def isValidJSGlobalRefName(name: String): Boolean = - isJSIdentifierName(name) && !ReservedJSIdentifierNames.contains(name) + def isValidJSGlobalRefName(name: String): Boolean = { + (isJSIdentifierName(name) && !ReservedJSIdentifierNames.contains(name)) || + name == FileLevelThis + } + + /** The JavaScript value that is an alias to `this` + * at the top-level of the generated file. + */ + final val FileLevelThis = "this" } sealed case class JSTypeOfGlobalRef(globalRef: JSGlobalRef)( @@ -975,10 +982,6 @@ object Trees { val tpe = AnyType } - sealed case class JSLinkingInfo()(implicit val pos: Position) extends Tree { - val tpe = AnyType - } - // Literals /** Marker for literals. Literals are always pure. @@ -1072,6 +1075,18 @@ object Trees { val tpe = ClassType(ClassClass, nullable = false) } + sealed case class LinkTimeProperty(name: String)(val tpe: Type)( + implicit val pos: Position) + extends Tree + + object LinkTimeProperty { + final val ProductionMode = "core/productionMode" + final val ESVersion = "core/esVersion" + final val UseECMAScript2015Semantics = "core/useECMAScript2015Semantics" + final val IsWebAssembly = "core/isWebAssembly" + final val LinkerVersion = "core/linkerVersion" + } + // Atomic expressions sealed case class VarRef(ident: LocalIdent)(val tpe: Type)( diff --git a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala index 57d80c3373..1df89dc5d8 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -807,10 +807,6 @@ class PrintersTest { assertPrintEquals("(typeof global:Foo)", JSTypeOfGlobalRef(JSGlobalRef("Foo"))) } - @Test def printJSLinkingInfo(): Unit = { - assertPrintEquals("", JSLinkingInfo()) - } - @Test def printUndefined(): Unit = { assertPrintEquals("(void 0)", Undefined()) } @@ -937,6 +933,14 @@ class PrintersTest { CreateJSClass("Foo", List(ref("x", IntType), ref("y", AnyType)))) } + @Test def printLinkTimeProperty(): Unit = { + assertPrintEquals( + """ + |(foo) + """, + LinkTimeProperty("foo")(StringType)) + } + @Test def printTransient(): Unit = { class MyTransient(expr: Tree) extends Transient.Value { val tpe: Type = AnyType diff --git a/javalib/src/main/scala/java/lang/Character.scala b/javalib/src/main/scala/java/lang/Character.scala index 50c38793ac..a085f427d7 100644 --- a/javalib/src/main/scala/java/lang/Character.scala +++ b/javalib/src/main/scala/java/lang/Character.scala @@ -15,7 +15,7 @@ package java.lang import scala.annotation.{tailrec, switch} import scala.scalajs.js -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import scala.scalajs.LinkingInfo.ESVersion import java.lang.constant.Constable @@ -128,7 +128,7 @@ object Character { if (!isValidCodePoint(codePoint)) throw new IllegalArgumentException() - if (linkingInfo.esVersion >= ESVersion.ES2015) { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { js.Dynamic.global.String.fromCodePoint(codePoint).asInstanceOf[String] } else { if (codePoint < MIN_SUPPLEMENTARY_CODE_POINT) { diff --git a/javalib/src/main/scala/java/lang/ClassValue.scala b/javalib/src/main/scala/java/lang/ClassValue.scala index 94f46965e4..74f91999ca 100644 --- a/javalib/src/main/scala/java/lang/ClassValue.scala +++ b/javalib/src/main/scala/java/lang/ClassValue.scala @@ -16,14 +16,14 @@ import java.util.HashMap import scala.scalajs.js import scala.scalajs.js.annotation._ -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import scala.scalajs.LinkingInfo.ESVersion import Utils._ abstract class ClassValue[T] protected () { private val jsMap: js.Map[Class[_], T] = { - if (linkingInfo.esVersion >= ESVersion.ES2015 || js.typeOf(js.Dynamic.global.Map) != "undefined") + if (LinkingInfo.esVersion >= ESVersion.ES2015 || js.typeOf(js.Dynamic.global.Map) != "undefined") new js.Map() else null @@ -35,7 +35,7 @@ abstract class ClassValue[T] protected () { * emitting ES 2015 code, which allows to dead-code-eliminate the branches * using `HashMap`s, and therefore `HashMap` itself. */ - linkingInfo.esVersion >= ESVersion.ES2015 || jsMap != null + LinkingInfo.esVersion >= ESVersion.ES2015 || jsMap != null } /* We use a HashMap instead of an IdentityHashMap because the latter is diff --git a/javalib/src/main/scala/java/lang/Double.scala b/javalib/src/main/scala/java/lang/Double.scala index e2768f8aca..aa6e3bc8d9 100644 --- a/javalib/src/main/scala/java/lang/Double.scala +++ b/javalib/src/main/scala/java/lang/Double.scala @@ -15,7 +15,7 @@ package java.lang import java.lang.constant.{Constable, ConstantDesc} import scala.scalajs.js -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import Utils._ @@ -365,7 +365,7 @@ object Double { !isNaN(d) && !isInfinite(d) @inline def hashCode(value: scala.Double): Int = { - if (linkingInfo.isWebAssembly) + if (LinkingInfo.isWebAssembly) hashCodeForWasm(value) else FloatingPointBits.numberHashCode(value) diff --git a/javalib/src/main/scala/java/lang/FloatingPointBits.scala b/javalib/src/main/scala/java/lang/FloatingPointBits.scala index 3b3f972346..fb9b89ff93 100644 --- a/javalib/src/main/scala/java/lang/FloatingPointBits.scala +++ b/javalib/src/main/scala/java/lang/FloatingPointBits.scala @@ -20,11 +20,11 @@ import scala.scalajs.LinkingInfo.ESVersion /** Manipulating the bits of floating point numbers. */ private[lang] object FloatingPointBits { - import scala.scalajs.runtime.linkingInfo + import scala.scalajs.LinkingInfo private[this] val _areTypedArraysSupported = { // Here we use the `esVersion` test to dce the 4 subsequent tests - linkingInfo.esVersion >= ESVersion.ES2015 || { + LinkingInfo.esVersion >= ESVersion.ES2015 || { js.typeOf(global.ArrayBuffer) != "undefined" && js.typeOf(global.Int32Array) != "undefined" && js.typeOf(global.Float32Array) != "undefined" && @@ -42,7 +42,7 @@ private[lang] object FloatingPointBits { * * If we emit ES5, replace `areTypedArraysSupported` by * `_areTypedArraysSupported` so we do not calculate it multiple times. */ - linkingInfo.esVersion >= ESVersion.ES2015 || _areTypedArraysSupported + LinkingInfo.esVersion >= ESVersion.ES2015 || _areTypedArraysSupported } private val arrayBuffer = diff --git a/javalib/src/main/scala/java/lang/Integer.scala b/javalib/src/main/scala/java/lang/Integer.scala index 78267337da..8ef617e7d0 100644 --- a/javalib/src/main/scala/java/lang/Integer.scala +++ b/javalib/src/main/scala/java/lang/Integer.scala @@ -15,7 +15,7 @@ package java.lang import java.lang.constant.{Constable, ConstantDesc} import scala.scalajs.js -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import scala.scalajs.LinkingInfo.ESVersion /* This is a hijacked class. Its instances are primitive numbers. @@ -279,7 +279,7 @@ object Integer { // Intrinsic, fallback on actual code for non-literal in JS @inline def numberOfLeadingZeros(i: scala.Int): scala.Int = { - if (linkingInfo.esVersion >= ESVersion.ES2015) js.Math.clz32(i) + if (LinkingInfo.esVersion >= ESVersion.ES2015) js.Math.clz32(i) else clz32Dynamic(i) } diff --git a/javalib/src/main/scala/java/lang/Math.scala b/javalib/src/main/scala/java/lang/Math.scala index 42262255f3..cb965bb56b 100644 --- a/javalib/src/main/scala/java/lang/Math.scala +++ b/javalib/src/main/scala/java/lang/Math.scala @@ -16,7 +16,7 @@ package lang import scala.scalajs.js import js.Dynamic.{ global => g } -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import scala.scalajs.LinkingInfo.ESVersion object Math { @@ -24,7 +24,7 @@ object Math { final val PI = 3.141592653589793 @inline private def assumingES6: scala.Boolean = - linkingInfo.esVersion >= ESVersion.ES2015 + LinkingInfo.esVersion >= ESVersion.ES2015 @inline def abs(a: scala.Int): scala.Int = if (a < 0) -a else a @inline def abs(a: scala.Long): scala.Long = if (a < 0) -a else a diff --git a/javalib/src/main/scala/java/lang/System.scala b/javalib/src/main/scala/java/lang/System.scala index c7424a218a..8075a6ac70 100644 --- a/javalib/src/main/scala/java/lang/System.scala +++ b/javalib/src/main/scala/java/lang/System.scala @@ -16,7 +16,7 @@ import java.io._ import scala.scalajs.js import scala.scalajs.js.Dynamic.global -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import java.{util => ju} @@ -200,7 +200,7 @@ object System { dictSet(result, "java.vm.specification.vendor", "Oracle Corporation") dictSet(result, "java.vm.specification.name", "Java Virtual Machine Specification") dictSet(result, "java.vm.name", "Scala.js") - dictSet(result, "java.vm.version", linkingInfo.linkerVersion) + dictSet(result, "java.vm.version", LinkingInfo.linkerVersion) dictSet(result, "java.specification.version", "1.8") dictSet(result, "java.specification.vendor", "Oracle Corporation") dictSet(result, "java.specification.name", "Java Platform API Specification") diff --git a/javalib/src/main/scala/java/lang/_String.scala b/javalib/src/main/scala/java/lang/_String.scala index d5b3e546fa..d89ada96dc 100644 --- a/javalib/src/main/scala/java/lang/_String.scala +++ b/javalib/src/main/scala/java/lang/_String.scala @@ -19,7 +19,7 @@ import java.util.Comparator import scala.scalajs.js import scala.scalajs.js.annotation._ import scala.scalajs.js.JSStringOps.enableJSStringOps -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import scala.scalajs.LinkingInfo.ESVersion import java.lang.constant.{Constable, ConstantDesc} @@ -56,7 +56,7 @@ final class _String private () // scalastyle:ignore // Wasm intrinsic def codePointAt(index: Int): Int = { - if (linkingInfo.esVersion >= ESVersion.ES2015) { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { charAt(index) // bounds check this.asInstanceOf[js.Dynamic].codePointAt(index).asInstanceOf[Int] } else { @@ -164,7 +164,7 @@ final class _String private () // scalastyle:ignore @inline def endsWith(suffix: String): scala.Boolean = { - if (linkingInfo.esVersion >= ESVersion.ES2015) { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { suffix.getClass() // null check thisString.asInstanceOf[js.Dynamic].endsWith(suffix).asInstanceOf[scala.Boolean] } else { @@ -270,7 +270,7 @@ final class _String private () // scalastyle:ignore def repeat(count: Int): String = { if (count < 0) { throw new IllegalArgumentException - } else if (linkingInfo.esVersion >= ESVersion.ES2015) { + } else if (LinkingInfo.esVersion >= ESVersion.ES2015) { /* This will throw a `js.RangeError` if `count` is too large, instead of * an `OutOfMemoryError`. That's fine because the behavior of `repeat` is * not specified for `count` too large. @@ -316,7 +316,7 @@ final class _String private () // scalastyle:ignore @inline def startsWith(prefix: String): scala.Boolean = { - if (linkingInfo.esVersion >= ESVersion.ES2015) { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { prefix.getClass() // null check thisString.asInstanceOf[js.Dynamic].startsWith(prefix).asInstanceOf[scala.Boolean] } else { @@ -326,7 +326,7 @@ final class _String private () // scalastyle:ignore @inline def startsWith(prefix: String, toffset: Int): scala.Boolean = { - if (linkingInfo.esVersion >= ESVersion.ES2015) { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { prefix.getClass() // null check (toffset <= length() && toffset >= 0 && thisString.asInstanceOf[js.Dynamic].startsWith(prefix, toffset).asInstanceOf[scala.Boolean]) diff --git a/javalib/src/main/scala/java/util/Random.scala b/javalib/src/main/scala/java/util/Random.scala index 8cd654cd1f..f0452ffb0d 100644 --- a/javalib/src/main/scala/java/util/Random.scala +++ b/javalib/src/main/scala/java/util/Random.scala @@ -15,7 +15,7 @@ package java.util import scala.annotation.tailrec import scala.scalajs.js -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo class Random(seed_in: Long) extends AnyRef with java.io.Serializable { /* This class has two different implementations of seeding and computing @@ -39,7 +39,7 @@ class Random(seed_in: Long) extends AnyRef with java.io.Serializable { def setSeed(seed_in: Long): Unit = { val seed = ((seed_in ^ 0x5DEECE66DL) & ((1L << 48) - 1)) // as documented - if (linkingInfo.isWebAssembly) { + if (LinkingInfo.isWebAssembly) { this.seed = seed } else { seedHi = (seed >>> 24).toInt @@ -50,7 +50,7 @@ class Random(seed_in: Long) extends AnyRef with java.io.Serializable { @noinline protected def next(bits: Int): Int = - if (linkingInfo.isWebAssembly) nextWasm(bits) + if (LinkingInfo.isWebAssembly) nextWasm(bits) else nextJS(bits) @inline diff --git a/javalib/src/main/scala/java/util/regex/PatternCompiler.scala b/javalib/src/main/scala/java/util/regex/PatternCompiler.scala index b2f001407f..93754f24b7 100644 --- a/javalib/src/main/scala/java/util/regex/PatternCompiler.scala +++ b/javalib/src/main/scala/java/util/regex/PatternCompiler.scala @@ -30,7 +30,7 @@ import java.util.ScalaOps._ import scala.scalajs.js import scala.scalajs.js.JSStringOps.enableJSStringOps -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import scala.scalajs.LinkingInfo.ESVersion /** Compiler from Java regular expressions to JavaScript regular expressions. @@ -83,15 +83,15 @@ private[regex] object PatternCompiler { /** Cache for `Support.supportsUnicode`. */ private val _supportsUnicode = - (linkingInfo.esVersion >= ESVersion.ES2015) || featureTest("u") + (LinkingInfo.esVersion >= ESVersion.ES2015) || featureTest("u") /** Cache for `Support.supportsSticky`. */ private val _supportsSticky = - (linkingInfo.esVersion >= ESVersion.ES2015) || featureTest("y") + (LinkingInfo.esVersion >= ESVersion.ES2015) || featureTest("y") /** Cache for `Support.supportsDotAll`. */ private val _supportsDotAll = - (linkingInfo.esVersion >= ESVersion.ES2018) || featureTest("us") + (LinkingInfo.esVersion >= ESVersion.ES2018) || featureTest("us") /** Cache for `Support.supportsIndices`. */ private val _supportsIndices = @@ -107,17 +107,17 @@ private[regex] object PatternCompiler { /** Tests whether the underlying JS RegExp supports the 'u' flag. */ @inline def supportsUnicode: Boolean = - (linkingInfo.esVersion >= ESVersion.ES2015) || _supportsUnicode + (LinkingInfo.esVersion >= ESVersion.ES2015) || _supportsUnicode /** Tests whether the underlying JS RegExp supports the 'y' flag. */ @inline def supportsSticky: Boolean = - (linkingInfo.esVersion >= ESVersion.ES2015) || _supportsSticky + (LinkingInfo.esVersion >= ESVersion.ES2015) || _supportsSticky /** Tests whether the underlying JS RegExp supports the 's' flag. */ @inline def supportsDotAll: Boolean = - (linkingInfo.esVersion >= ESVersion.ES2018) || _supportsDotAll + (LinkingInfo.esVersion >= ESVersion.ES2018) || _supportsDotAll /** Tests whether the underlying JS RegExp supports the 'd' flag. */ @inline @@ -131,7 +131,7 @@ private[regex] object PatternCompiler { */ @inline def enableUnicodeCaseInsensitive: Boolean = - linkingInfo.esVersion >= ESVersion.ES2015 + LinkingInfo.esVersion >= ESVersion.ES2015 /** Tests whether features requiring \p{} and/or look-behind assertions are enabled. * @@ -140,7 +140,7 @@ private[regex] object PatternCompiler { */ @inline def enableUnicodeCharacterClassesAndLookBehinds: Boolean = - linkingInfo.esVersion >= ESVersion.ES2018 + LinkingInfo.esVersion >= ESVersion.ES2018 } import Support._ @@ -215,7 +215,7 @@ private[regex] object PatternCompiler { import InlinedHelpers._ private def codePointToString(codePoint: Int): String = { - if (linkingInfo.esVersion >= ESVersion.ES2015) { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { js.Dynamic.global.String.fromCodePoint(codePoint).asInstanceOf[String] } else { if (isBmpCodePoint(codePoint)) { diff --git a/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala b/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala index 945753d91b..e0bd4e1223 100644 --- a/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala +++ b/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala @@ -13,7 +13,6 @@ package java.util.regex import scala.scalajs.js -import scala.scalajs.runtime.linkingInfo import scala.scalajs.LinkingInfo class PatternSyntaxException(desc: String, regex: String, index: Int) diff --git a/library/src/main/scala/scala/scalajs/LinkingInfo.scala b/library/src/main/scala/scala/scalajs/LinkingInfo.scala index bf1bfa9c00..ea9d6c1a2f 100644 --- a/library/src/main/scala/scala/scalajs/LinkingInfo.scala +++ b/library/src/main/scala/scala/scalajs/LinkingInfo.scala @@ -14,8 +14,6 @@ package scala.scalajs object LinkingInfo { - import scala.scalajs.runtime.linkingInfo - /** Returns true if we are linking for production, false otherwise. * * `productionMode` is always equal to `!developmentMode`. @@ -46,7 +44,7 @@ object LinkingInfo { */ @inline def productionMode: Boolean = - linkingInfo.productionMode + linkTimePropertyBoolean("core/productionMode") /** Returns true if we are linking for development, false otherwise. * @@ -124,7 +122,7 @@ object LinkingInfo { */ @inline def esVersion: Int = - linkingInfo.esVersion + linkTimePropertyInt("core/esVersion") /** Returns true if we are assuming that the target platform supports * ECMAScript 6, false otherwise. @@ -222,7 +220,7 @@ object LinkingInfo { */ @inline def useECMAScript2015Semantics: Boolean = - linkingInfo.assumingES6 // name mismatch for historical reasons + linkTimePropertyBoolean("core/useECMAScript2015Semantics") /** Whether we are linking to WebAssembly. * @@ -256,7 +254,12 @@ object LinkingInfo { */ @inline def isWebAssembly: Boolean = - linkingInfo.isWebAssembly + linkTimePropertyBoolean("core/isWebAssembly") + + /** Version of the linker. */ + @inline + def linkerVersion: String = + linkTimePropertyString("core/linkerVersion") /** Constants for the value of `esVersion`. */ object ESVersion { @@ -326,4 +329,13 @@ object LinkingInfo { */ final val ES2021 = 12 } + + private[scalajs] def linkTimePropertyInt(name: String): Int = + throw new java.lang.Error("stub") + + private[scalajs] def linkTimePropertyBoolean(name: String): Boolean = + throw new java.lang.Error("stub") + + private[scalajs] def linkTimePropertyString(name: String): String = + throw new java.lang.Error("stub") } diff --git a/library/src/main/scala/scala/scalajs/js/special/package.scala b/library/src/main/scala/scala/scalajs/js/special/package.scala index 2ad1da9ece..64f7edd6fb 100644 --- a/library/src/main/scala/scala/scalajs/js/special/package.scala +++ b/library/src/main/scala/scala/scalajs/js/special/package.scala @@ -214,7 +214,7 @@ package object special { */ @inline def fileLevelThis: scala.Any = - scala.scalajs.runtime.linkingInfo.fileLevelThis + js.Dynamic.global.`this` /** Exact equivalent of the `debugger` keyword of JavaScript. * diff --git a/library/src/main/scala/scala/scalajs/runtime/LinkingInfo.scala b/library/src/main/scala/scala/scalajs/runtime/LinkingInfo.scala index 3dd8395202..0645b93f0f 100644 --- a/library/src/main/scala/scala/scalajs/runtime/LinkingInfo.scala +++ b/library/src/main/scala/scala/scalajs/runtime/LinkingInfo.scala @@ -15,7 +15,11 @@ package scala.scalajs.runtime import scala.scalajs.js /** Information about link-time configuration of Scala.js. */ -sealed trait LinkingInfo extends js.Object { +@deprecated( + "Use scala.scalajs.LinkingInfo instead. " + + "For fileLevelThis, use scala.scalajs.js.special.fileLevelThis.", + since = "1.18.0") +trait LinkingInfo extends js.Object { /** Version (edition) of ECMAScript that is assumed to be supported by the * runtime. * diff --git a/library/src/main/scala/scala/scalajs/runtime/package.scala b/library/src/main/scala/scala/scalajs/runtime/package.scala index 151769c2b9..d3ba4f766f 100644 --- a/library/src/main/scala/scala/scalajs/runtime/package.scala +++ b/library/src/main/scala/scala/scalajs/runtime/package.scala @@ -97,7 +97,18 @@ package object runtime { * * See [[LinkingInfo]] for details. */ - def linkingInfo: LinkingInfo = throw new Error("stub") + @deprecated( + "Use scala.scalajs.LinkingInfo instead. " + + "For fileLevelThis, use scala.scalajs.js.special.fileLevelThis.", + since = "1.18.0") + def linkingInfo: LinkingInfo = new LinkingInfo { + override val esVersion: Int = scalajs.LinkingInfo.esVersion + override val assumingES6: Boolean = scalajs.LinkingInfo.assumingES6 + override val isWebAssembly: Boolean = scalajs.LinkingInfo.isWebAssembly + override val productionMode: Boolean = scalajs.LinkingInfo.productionMode + override val linkerVersion: String = scalajs.LinkingInfo.linkerVersion + override val fileLevelThis: Any = js.special.fileLevelThis + } /** Identity hash code of an object. */ def identityHashCode(x: Object): Int = throw new Error("stub") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala index 3c1e6f251b..0c1b0118e5 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala @@ -208,6 +208,12 @@ object Analysis { final case class ExponentOperatorWithoutES2016Support(from: From) extends Error + final case class InvalidLinkTimeProperty( + linkTimePropertyName: String, + linkTimePropertyType: Type, + from: From + ) extends Error + sealed trait From final case class FromMethod(methodInfo: MethodInfo) extends From final case class FromDispatch(classInfo: ClassInfo, methodName: MethodName) extends From @@ -264,6 +270,8 @@ object Analysis { "Uses import.meta with a module kind other than ESModule" case ExponentOperatorWithoutES2016Support(_) => "Uses the ** operator with an ECMAScript version older than ES 2016" + case InvalidLinkTimeProperty(name, tpe, _) => + s"Uses invalid link-time property ${name} of type ${tpe}" } logger.log(level, headMsg) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala index 79f1ef432f..daf81ec531 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala @@ -1478,6 +1478,14 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean, _classSuperClassUsed.set(true) } } + + if (data.referencedLinkTimeProperties.nonEmpty) { + for ((name, tpe) <- data.referencedLinkTimeProperties) { + if (!config.coreSpec.linkTimeProperties.validate(name, tpe)) { + _errors ::= InvalidLinkTimeProperty(name, tpe, from) + } + } + } } @tailrec diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala index 6b622ae82a..3ff5ee059d 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala @@ -78,13 +78,14 @@ object Infos { val isAbstract: Boolean, version: Version, byClass: Array[ReachabilityInfoInClass], - globalFlags: ReachabilityInfo.Flags - ) extends ReachabilityInfo(version, byClass, globalFlags) + globalFlags: ReachabilityInfo.Flags, + referencedLinkTimeProperties: Array[(String, Type)] + ) extends ReachabilityInfo(version, byClass, globalFlags, referencedLinkTimeProperties) object MethodInfo { def apply(isAbstract: Boolean, reachabilityInfo: ReachabilityInfo): MethodInfo = { import reachabilityInfo._ - new MethodInfo(isAbstract, version, byClass, globalFlags) + new MethodInfo(isAbstract, version, byClass, globalFlags, referencedLinkTimeProperties) } } @@ -102,7 +103,8 @@ object Infos { */ val version: Version, val byClass: Array[ReachabilityInfoInClass], - val globalFlags: ReachabilityInfo.Flags + val globalFlags: ReachabilityInfo.Flags, + val referencedLinkTimeProperties: Array[(String, Type)] ) object ReachabilityInfo { @@ -196,8 +198,10 @@ object Infos { } final class ReachabilityInfoBuilder(version: Version) { + import ReachabilityInfoBuilder._ private val byClass = mutable.Map.empty[ClassName, ReachabilityInfoInClassBuilder] private var flags: ReachabilityInfo.Flags = 0 + private val linkTimeProperties = mutable.ListBuffer.empty[(String, Type)] private def forClass(cls: ClassName): ReachabilityInfoInClassBuilder = byClass.getOrElseUpdate(cls, new ReachabilityInfoInClassBuilder(cls)) @@ -390,8 +394,22 @@ object Infos { def addUsedClassSuperClass(): this.type = setFlag(ReachabilityInfo.FlagUsedClassSuperClass) - def result(): ReachabilityInfo = - new ReachabilityInfo(version, byClass.valuesIterator.map(_.result()).toArray, flags) + def addReferencedLinkTimeProperty(linkTimeProperty: LinkTimeProperty): this.type = { + linkTimeProperties.append((linkTimeProperty.name, linkTimeProperty.tpe)) + this + } + + def result(): ReachabilityInfo = { + val referencedLinkTimeProperties = + if (linkTimeProperties.isEmpty) emptyLinkTimePropertyArray + else linkTimeProperties.toArray + new ReachabilityInfo(version, byClass.valuesIterator.map(_.result()).toArray, flags, + referencedLinkTimeProperties) + } + } + + object ReachabilityInfoBuilder { + private val emptyLinkTimePropertyArray = new Array[(String, Type)](0) } final class ReachabilityInfoInClassBuilder(val className: ClassName) { @@ -744,6 +762,9 @@ object Infos { case VarDef(_, _, vtpe, _, _) => builder.maybeAddReferencedClass(vtpe) + case linkTimeProperty: LinkTimeProperty => + builder.addReferencedLinkTimeProperty(linkTimeProperty) + case _ => } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala index 0243d9f7bc..1b1751df60 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala @@ -40,6 +40,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { import sjsGen._ import jsGen._ import config._ + import coreSpec._ import nameGen._ import varGen._ diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala index 35e480f233..ffbea0e2f7 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala @@ -63,6 +63,7 @@ private[emitter] object CoreJSLib { import sjsGen._ import jsGen._ import config._ + import coreSpec._ import nameGen._ import varGen._ import esFeatures._ @@ -127,7 +128,7 @@ private[emitter] object CoreJSLib { } private def buildPreObjectDefinitions(): List[Tree] = { - defineLinkingInfo() ::: + defineFileLevelThis() ::: defineJSBuiltinsSnapshotsAndPolyfills() ::: declareCachedL0() ::: defineCharClass() ::: @@ -154,22 +155,8 @@ private[emitter] object CoreJSLib { assignCachedL0() } - private def defineLinkingInfo(): List[Tree] = { - // must be in sync with scala.scalajs.runtime.LinkingInfo - - def objectFreeze(tree: Tree): Tree = - Apply(genIdentBracketSelect(ObjectRef, "freeze"), tree :: Nil) - - val linkingInfo = objectFreeze(ObjectConstr(List( - str("esVersion") -> int(esVersion.edition), - str("assumingES6") -> bool(useECMAScript2015Semantics), // different name for historical reasons - str("isWebAssembly") -> bool(false), - str("productionMode") -> bool(productionMode), - str("linkerVersion") -> str(ScalaJSVersions.current), - str("fileLevelThis") -> This() - ))) - - extractWithGlobals(globalVarDef(VarField.linkingInfo, CoreVar, linkingInfo)) + private def defineFileLevelThis(): List[Tree] = { + extractWithGlobals(globalVarDef(VarField.fileLevelThis, CoreVar, This())) } private def defineJSBuiltinsSnapshotsAndPolyfills(): List[Tree] = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala index 07e4dee5f8..c6576f4f5d 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala @@ -37,6 +37,7 @@ final class Emitter(config: Emitter.Config, prePrinter: Emitter.PrePrinter) { import Emitter._ import config._ + import coreSpec._ require(!config.minify || prePrinter == PrePrinter.Off, "When using the 'minify' option, the prePrinter must be Off.") @@ -1088,23 +1089,16 @@ object Emitter { /** Configuration for the Emitter. */ final class Config private ( - val semantics: Semantics, - val moduleKind: ModuleKind, - val esFeatures: ESFeatures, + val coreSpec: CoreSpec, val jsHeader: String, val internalModulePattern: ModuleID => String, val optimizeBracketSelects: Boolean, val trackAllGlobalRefs: Boolean, val minify: Boolean ) { - private def this( - semantics: Semantics, - moduleKind: ModuleKind, - esFeatures: ESFeatures) = { + private def this(coreSpec: CoreSpec) = { this( - semantics, - moduleKind, - esFeatures, + coreSpec, jsHeader = "", internalModulePattern = "./" + _.id, optimizeBracketSelects = true, @@ -1117,14 +1111,8 @@ object Emitter { if (trackAllGlobalRefs) GlobalRefTracking.All else GlobalRefTracking.Dangerous - def withSemantics(f: Semantics => Semantics): Config = - copy(semantics = f(semantics)) - - def withModuleKind(moduleKind: ModuleKind): Config = - copy(moduleKind = moduleKind) - - def withESFeatures(f: ESFeatures => ESFeatures): Config = - copy(esFeatures = f(esFeatures)) + def withCoreSpec(coreSpec: CoreSpec): Config = + copy(coreSpec = coreSpec) def withJSHeader(jsHeader: String): Config = { require(StandardConfig.isValidJSHeader(jsHeader), jsHeader) @@ -1144,16 +1132,14 @@ object Emitter { copy(minify = minify) private def copy( - semantics: Semantics = semantics, - moduleKind: ModuleKind = moduleKind, - esFeatures: ESFeatures = esFeatures, + coreSpec: CoreSpec = coreSpec, jsHeader: String = jsHeader, internalModulePattern: ModuleID => String = internalModulePattern, optimizeBracketSelects: Boolean = optimizeBracketSelects, trackAllGlobalRefs: Boolean = trackAllGlobalRefs, minify: Boolean = minify ): Config = { - new Config(semantics, moduleKind, esFeatures, jsHeader, + new Config(coreSpec, jsHeader, internalModulePattern, optimizeBracketSelects, trackAllGlobalRefs, minify) } @@ -1161,7 +1147,7 @@ object Emitter { object Config { def apply(coreSpec: CoreSpec): Config = - new Config(coreSpec.semantics, coreSpec.moduleKind, coreSpec.esFeatures) + new Config(coreSpec) } sealed trait PrePrinter { @@ -1257,7 +1243,7 @@ object Emitter { ancestors: List[ClassName], moduleContext: ModuleContext) private def symbolRequirements(config: Config): SymbolRequirement = { - import config.semantics._ + import config.coreSpec.semantics._ import CheckedBehavior._ val factory = SymbolRequirement.factory("emitter") @@ -1313,7 +1299,7 @@ object Emitter { callMethod(BoxedDoubleClass, hashCodeMethodName), callMethod(BoxedStringClass, hashCodeMethodName), - cond(!config.esFeatures.allowBigIntsForLongs) { + cond(!config.coreSpec.esFeatures.allowBigIntsForLongs) { multiple( instanceTests(LongImpl.RuntimeLongClass), instantiateClass(LongImpl.RuntimeLongClass, LongImpl.AllConstructors.toList), diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index b8b802695f..dcd45d0533 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -251,6 +251,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { import sjsGen._ import jsGen._ import config._ + import coreSpec._ import nameGen._ import varGen._ @@ -1243,10 +1244,10 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { def test(tree: Tree): Boolean = tree match { // Atomic expressions - case _: Literal => true - case _: This => true - case _: JSNewTarget => true - case _: JSLinkingInfo => true + case _: Literal => true + case _: This => true + case _: JSNewTarget => true + case _: LinkTimeProperty => true // Vars (side-effect free, pure if immutable) case VarRef(name) => @@ -2796,6 +2797,11 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { genSelect(newExpr, FieldIdent(exceptionFieldName)), genCheckNotNull(newExpr)) + case prop: LinkTimeProperty => + transformExpr( + config.coreSpec.linkTimeProperties.transformLinkTimeProperty(prop), + preserveChar) + // Transients case Transient(Cast(expr, tpe)) => @@ -2947,14 +2953,14 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { }) case JSGlobalRef(name) => - js.VarRef(transformGlobalVarIdent(name)) + if (name == JSGlobalRef.FileLevelThis) + globalVar(VarField.fileLevelThis, CoreVar) + else + js.VarRef(transformGlobalVarIdent(name)) case JSTypeOfGlobalRef(globalRef) => js.UnaryOp(JSUnaryOp.typeof, transformExprNoChar(globalRef)) - case JSLinkingInfo() => - globalVar(VarField.linkingInfo, CoreVar) - // Literals case Undefined() => js.Undefined() diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala index 3676a947c8..ea9be90e76 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala @@ -25,6 +25,7 @@ import org.scalajs.linker.interface.ESVersion private[emitter] final class JSGen(val config: Emitter.Config) { import config._ + import coreSpec._ /** Should we use ECMAScript classes for JavaScript classes and Throwable * classes? diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala index 5a02604391..c7a94abfb9 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala @@ -152,7 +152,7 @@ private[emitter] final class KnowledgeGuardian(config: Emitter.Config) { private def computeStaticFieldMirrors( moduleSet: ModuleSet): Map[ClassName, Map[FieldName, List[String]]] = { - if (config.moduleKind != ModuleKind.NoModule) { + if (config.coreSpec.moduleKind != ModuleKind.NoModule) { Map.empty } else { var result = Map.empty[ClassName, Map[FieldName, List[String]]] diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala index 44f8708265..d16df3126a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala @@ -38,6 +38,7 @@ private[emitter] final class SJSGen( import jsGen._ import config._ + import coreSpec._ import nameGen._ import varGen._ diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala index a43a127f61..44193542b9 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala @@ -137,8 +137,8 @@ private[emitter] object VarField { // Core fields: Generated by the CoreJSLib - /** The linking info object. */ - final val linkingInfo = mk("$linkingInfo") + /** The alias to file level `this` in the generated JS file. */ + final val fileLevelThis = mk("$fileLevelThis") /** The TypeData class. */ final val TypeData = mk("$TypeData") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala index d867b347ed..cdcf20560f 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala @@ -97,7 +97,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, val ident = globalVarIdent(field, scope, origName) val varDef = genLet(ident, mutable = true, value) - if (config.moduleKind == ModuleKind.ESModule && !moduleContext.public) { + if (config.coreSpec.moduleKind == ModuleKind.ESModule && !moduleContext.public) { val setterIdent = globalVarIdent(setterField, scope) val x = Ident("x") val setter = FunctionDef(setterIdent, List(ParamDef(x)), None, { @@ -117,7 +117,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, def needToUseGloballyMutableVarSetter[T](scope: T)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, scopeType: Scope[T]): Boolean = { - config.moduleKind == ModuleKind.ESModule && + config.coreSpec.moduleKind == ModuleKind.ESModule && globalKnowledge.getModule(scopeType.reprClass(scope)) != moduleContext.moduleID } @@ -125,7 +125,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, origName: OriginalName = NoOriginalName)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { - assert(config.moduleKind == ModuleKind.ESModule) + assert(config.coreSpec.moduleKind == ModuleKind.ESModule) val ident = globalVarIdent(field, scope, origName) foldSameModule[T, Tree](scope) { @@ -163,7 +163,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, } { moduleID => val moduleName = config.internalModulePattern(moduleID) - val moduleTree = config.moduleKind match { + val moduleTree = config.coreSpec.moduleKind match { case ModuleKind.NoModule => /* If we get here, it means that what we are trying to import is in a * different module than the module we're currently generating @@ -279,7 +279,7 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, if (moduleContext.public) { WithGlobals(tree :: Nil) } else { - val exportStat = config.moduleKind match { + val exportStat = config.coreSpec.moduleKind match { case ModuleKind.NoModule => throw new AssertionError("non-public module in NoModule mode") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala index d74aa2d810..6a00aaab2c 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala @@ -311,7 +311,6 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { ) } - addGlobalHelperImport(genGlobalID.jsLinkingInfo, RefType.any) addGlobalHelperImport(genGlobalID.undef, RefType.any) addGlobalHelperImport(genGlobalID.bFalse, RefType.any) addGlobalHelperImport(genGlobalID.bTrue, RefType.any) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index 051fd64887..88d53a47e3 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -575,6 +575,7 @@ private class FunctionEmitter private ( case t: IdentityHashCode => genIdentityHashCode(t) case t: WrapAsThrowable => genWrapAsThrowable(t) case t: UnwrapFromThrowable => genUnwrapFromThrowable(t) + case t: LinkTimeProperty => genLinkTimeProperty(t) // JavaScript expressions case t: JSNew => genJSNew(t) @@ -593,7 +594,6 @@ private class FunctionEmitter private ( case t: JSObjectConstr => genJSObjectConstr(t) case t: JSGlobalRef => genJSGlobalRef(t) case t: JSTypeOfGlobalRef => genJSTypeOfGlobalRef(t) - case t: JSLinkingInfo => genJSLinkingInfo(t) case t: Closure => genClosure(t) // array @@ -2702,6 +2702,12 @@ private class FunctionEmitter private ( AnyType } + private def genLinkTimeProperty(tree: LinkTimeProperty): Type = { + val lit = ctx.coreSpec.linkTimeProperties.transformLinkTimeProperty(tree) + genLiteral(lit, lit.tpe) + lit.tpe + } + private def genJSNew(tree: JSNew): Type = { val JSNew(ctor, args) = tree @@ -2938,14 +2944,19 @@ private class FunctionEmitter private ( val JSGlobalRef(name) = tree implicit val pos = tree.pos + markPosition(pos) - val builder = new CustomJSHelperBuilder() - val helperID = builder.build(AnyType) { - js.Return(builder.genGlobalRef(name)) - } + if (name == JSGlobalRef.FileLevelThis) { + // In ES modules global this is undefined, and Wasm backend only supports `ESModule` + fb += wa.GlobalGet(genGlobalID.undef) + } else { + val builder = new CustomJSHelperBuilder() + val helperID = builder.build(AnyType) { + js.Return(builder.genGlobalRef(name)) + } - markPosition(pos) - fb += wa.Call(helperID) + fb += wa.Call(helperID) + } AnyType } @@ -2964,12 +2975,6 @@ private class FunctionEmitter private ( AnyType } - private def genJSLinkingInfo(tree: JSLinkingInfo): Type = { - markPosition(tree) - fb += wa.GlobalGet(genGlobalID.jsLinkingInfo) - AnyType - } - private def genArrayLength(tree: ArrayLength): Type = { val ArrayLength(array) = tree diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala index f01d9f49e9..6a01f55cb6 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala @@ -53,7 +53,6 @@ object VarGen { */ sealed abstract class JSHelperGlobalID extends GlobalID - case object jsLinkingInfo extends JSHelperGlobalID case object undef extends JSHelperGlobalID case object bFalse extends JSHelperGlobalID case object bTrue extends JSHelperGlobalID diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index b569825470..6c4c6d5426 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -562,6 +562,9 @@ private final class ClassDefChecker(classDef: ClassDef, if (env.locals.get(name).exists(!_.mutable)) reportError(i"Assignment to immutable variable $name.") + case JSGlobalRef(JSGlobalRef.FileLevelThis) => + reportError(i"Assignment to global this.") + case _:Select | _:JSPrivateSelect | _:SelectStatic | _:ArraySelect | _:RecordSelect | _:JSSelect | _:JSSuperSelect | _:JSGlobalRef => @@ -727,6 +730,8 @@ private final class ClassDefChecker(classDef: ClassDef, case UnwrapFromThrowable(expr) => checkTree(expr, env) + case LinkTimeProperty(name) => + // JavaScript expressions case JSNew(ctor, args) => @@ -801,8 +806,6 @@ private final class ClassDefChecker(classDef: ClassDef, case JSTypeOfGlobalRef(_) => - case JSLinkingInfo() => - // Literals case ClassOf(typeRef) => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index 5bf5e259c4..4d388be2ea 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -536,6 +536,8 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { case UnwrapFromThrowable(expr) => typecheckExpect(expr, env, ClassType(ThrowableClass, nullable = true)) + case LinkTimeProperty(name) => + // JavaScript expressions case JSNew(ctor, args) => @@ -643,8 +645,6 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { case JSTypeOfGlobalRef(_) => - case JSLinkingInfo() => - // Literals case _: Literal => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 2e49b2d40f..3f80a981fa 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -610,6 +610,9 @@ private[optimizer] abstract class OptimizerCore( pretransformExpr(tree)(finishTransform(isStat)) } + case prop: LinkTimeProperty => + config.coreSpec.linkTimeProperties.transformLinkTimeProperty(prop) + // JavaScript expressions case JSNew(ctor, args) => @@ -695,7 +698,7 @@ private[optimizer] abstract class OptimizerCore( // Trees that need not be transformed case _:Skip | _:Debugger | _:StoreModule | - _:SelectStatic | _:JSNewTarget | _:JSImportMeta | _:JSLinkingInfo | + _:SelectStatic | _:JSNewTarget | _:JSImportMeta | _:JSGlobalRef | _:JSTypeOfGlobalRef | _:Literal => tree @@ -2393,7 +2396,7 @@ private[optimizer] abstract class OptimizerCore( val titem = optimizeJSBracketSelectItem(titem0) def default: TailRec[Tree] = { - cont(PreTransTree(foldJSSelect(finishTransformExpr(tqual), + cont(PreTransTree(JSSelect(finishTransformExpr(tqual), finishTransformExpr(titem)))) } @@ -5096,33 +5099,6 @@ private[optimizer] abstract class OptimizerCore( } } - private def foldJSSelect(qualifier: Tree, item: Tree)( - implicit pos: Position): Tree = { - // !!! Must be in sync with scala.scalajs.runtime.LinkingInfo - - import config.coreSpec.esFeatures - - (qualifier, item) match { - case (JSLinkingInfo(), StringLiteral("productionMode")) => - BooleanLiteral(semantics.productionMode) - - case (JSLinkingInfo(), StringLiteral("esVersion")) => - IntLiteral(esFeatures.esVersion.edition) - - case (JSLinkingInfo(), StringLiteral("assumingES6")) => - BooleanLiteral(esFeatures.useECMAScript2015Semantics) - - case (JSLinkingInfo(), StringLiteral("isWebAssembly")) => - BooleanLiteral(isWasm) - - case (JSLinkingInfo(), StringLiteral("version")) => - StringLiteral(ScalaJSVersions.current) - - case _ => - JSSelect(qualifier, item) - } - } - private def transformMethodDefBody(optTarget: Option[MethodID], thisType: Type, params: List[ParamDef], jsClassCaptures: List[ParamDef], resultType: Type, body: Tree, isNoArgCtor: Boolean): (List[ParamDef], Tree) = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/standard/CoreSpec.scala b/linker/shared/src/main/scala/org/scalajs/linker/standard/CoreSpec.scala index 3c4c979adc..e5e285268f 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/standard/CoreSpec.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/standard/CoreSpec.scala @@ -96,6 +96,9 @@ final class CoreSpec private ( targetIsWebAssembly ) } + + private[linker] lazy val linkTimeProperties = new LinkTimeProperties( + semantics, esFeatures, targetIsWebAssembly) } private[linker] object CoreSpec { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkTimeProperties.scala b/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkTimeProperties.scala new file mode 100644 index 0000000000..875196c736 --- /dev/null +++ b/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkTimeProperties.scala @@ -0,0 +1,68 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.linker.standard + +import org.scalajs.ir.{Types => jstpe, Trees => js} +import org.scalajs.ir.Trees.LinkTimeProperty._ +import org.scalajs.ir.ScalaJSVersions +import org.scalajs.ir.Position.NoPosition +import org.scalajs.linker.interface.{Semantics, ESFeatures} + +private[linker] final class LinkTimeProperties ( + semantics: Semantics, + esFeatures: ESFeatures, + targetIsWebAssembly: Boolean +) { + import LinkTimeProperties._ + + private val linkTimeProperties: Map[String, LinkTimeValue] = Map( + ESVersion -> + LinkTimeInt(esFeatures.esVersion.edition), + UseECMAScript2015Semantics -> + LinkTimeBoolean(esFeatures.useECMAScript2015Semantics), + IsWebAssembly -> + LinkTimeBoolean(targetIsWebAssembly), + ProductionMode -> + LinkTimeBoolean(semantics.productionMode), + LinkerVersion -> + LinkTimeString(ScalaJSVersions.current) + ) + + def validate(name: String, tpe: jstpe.Type): Boolean = { + linkTimeProperties.get(name).exists { + case _: LinkTimeBoolean => tpe == jstpe.BooleanType + case _: LinkTimeInt => tpe == jstpe.IntType + case _: LinkTimeString => tpe == jstpe.StringType + } + } + + def transformLinkTimeProperty(prop: js.LinkTimeProperty): js.Literal = { + val value = linkTimeProperties.getOrElse(prop.name, + throw new IllegalArgumentException(s"link time property not found: '${prop.name}' of type ${prop.tpe}")) + value match { + case LinkTimeBoolean(value) => + js.BooleanLiteral(value)(prop.pos) + case LinkTimeInt(value) => + js.IntLiteral(value)(prop.pos) + case LinkTimeString(value) => + js.StringLiteral(value)(prop.pos) + } + } +} + +private[linker] object LinkTimeProperties { + sealed abstract class LinkTimeValue + final case class LinkTimeInt(value: Int) extends LinkTimeValue + final case class LinkTimeBoolean(value: Boolean) extends LinkTimeValue + final case class LinkTimeString(value: String) extends LinkTimeValue +} diff --git a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala index f797ad25a1..1ae0a8d801 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala @@ -806,6 +806,37 @@ class AnalyzerTest { assertFalse(I2barMethodInfo.isAbstractReachable) } } + + @Test + def invalidLinkTimeProperty(): AsyncResult = await { + def test(invalidLinkTimeProperty: LinkTimeProperty): Future[Unit] = { + val classDefs = Seq( + classDef("A", + kind = ClassKind.ModuleClass, superClass = Some(ObjectClass), + methods = List( + trivialCtor("A"), + mainMethodDef(invalidLinkTimeProperty) + ) + ) + ) + + val moduleInitializer = ModuleInitializer.mainMethodWithArgs("A", "main") + + val analysis = computeAnalysis(classDefs, + moduleInitializers = List(moduleInitializer)) + + assertContainsError(s"InvalidLinkTimeProperty(${invalidLinkTimeProperty.name})", analysis) { + case InvalidLinkTimeProperty(name, tpe, _) => + name == invalidLinkTimeProperty.name && tpe == invalidLinkTimeProperty.tpe + } + } + + val results = List( + test(LinkTimeProperty("not-found")(IntType)), + test(LinkTimeProperty(LinkTimeProperty.ESVersion)(BooleanType)) // ESVersion should be IntType + ) + Future.sequence(results) + } } object AnalyzerTest { diff --git a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala index 9ab95541de..a9fbf49876 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala @@ -70,9 +70,9 @@ class LibrarySizeTest { ) testLinkedSizes( - expectedFastLinkSize = 147629, + expectedFastLinkSize = 147459, expectedFullLinkSizeWithoutClosure = 85718, - expectedFullLinkSizeWithClosure = 21625, + expectedFullLinkSizeWithClosure = 21495, classDefs, moduleInitializers = MainTestModuleInitializers ) diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 4713fe6bf8..959802dff4 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -5,6 +5,9 @@ import com.typesafe.tools.mima.core.ProblemFilters._ object BinaryIncompatibilities { val IR = Seq( + // !!! Breaking, OK in minor release + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$JSLinkingInfo"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$JSLinkingInfo$") ) val Linker = Seq( diff --git a/project/Build.scala b/project/Build.scala index 9ba80d3b93..b4cab25a64 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2048,7 +2048,7 @@ object Build { case `default212Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 623000 to 624000, + fastLink = 622000 to 623000, fullLink = 96000 to 97000, fastLinkGz = 75000 to 79000, fullLinkGz = 25000 to 26000, @@ -2056,7 +2056,7 @@ object Build { } else { Some(ExpectedSizes( fastLink = 423000 to 424000, - fullLink = 281000 to 282000, + fullLink = 280000 to 281000, fastLinkGz = 60000 to 61000, fullLinkGz = 43000 to 44000, )) @@ -2065,7 +2065,7 @@ object Build { case `default213Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 450000 to 451000, + fastLink = 449000 to 450000, fullLink = 95000 to 96000, fastLinkGz = 58000 to 59000, fullLinkGz = 25000 to 26000, diff --git a/project/JavalibIRCleaner.scala b/project/JavalibIRCleaner.scala index 4eab2d5a6f..035732d7c4 100644 --- a/project/JavalibIRCleaner.scala +++ b/project/JavalibIRCleaner.scala @@ -394,6 +394,17 @@ final class JavalibIRCleaner(baseDirectoryURI: URI) { result } + // LinkingInfo + // Must stay in sync with the definitions in `scala.scalajs.LinkingInfo` + case IntrinsicCall(LinkingInfoClass, `esVersionMethodName`, Nil) => + LinkTimeProperty(LinkTimeProperty.ESVersion)(IntType) + + case IntrinsicCall(LinkingInfoClass, `isWebAssemblyMethodName`, Nil) => + LinkTimeProperty(LinkTimeProperty.IsWebAssembly)(BooleanType) + + case IntrinsicCall(LinkingInfoClass, `linkerVersionMethodName`, Nil) => + LinkTimeProperty(LinkTimeProperty.LinkerVersion)(StringType) + case _ => tree } @@ -655,6 +666,7 @@ object JavalibIRCleaner { private val UnionType = ClassName("scala.scalajs.js.$bar") private val UnionTypeMod = ClassName("scala.scalajs.js.$bar$") private val UnionTypeEvidence = ClassName("scala.scalajs.js.$bar$Evidence") + private val LinkingInfoClass = ClassName("scala.scalajs.LinkingInfo$") private val FunctionNClasses: IndexedSeq[ClassName] = (0 to MaxFunctionArity).map(n => ClassName(s"scala.Function$n")) @@ -699,6 +711,11 @@ object JavalibIRCleaner { private val writeReplaceMethodName = MethodName("writeReplace", Nil, ClassRef(ObjectClass)) + // LinkingInfo + private val esVersionMethodName = MethodName("esVersion", Nil, IntRef) + private val isWebAssemblyMethodName = MethodName("isWebAssembly", Nil, BooleanRef) + private val linkerVersionMethodName = MethodName("linkerVersion", Nil, ClassRef(BoxedStringClass)) + private val functionApplyMethodNames: IndexedSeq[MethodName] = { (0 to MaxFunctionArity).map { n => MethodName("apply", (1 to n).toList.map(_ => ClassRef(ObjectClass)), ClassRef(ObjectClass)) diff --git a/scalalib/overrides-2.12/scala/collection/mutable/ArrayBuilder.scala b/scalalib/overrides-2.12/scala/collection/mutable/ArrayBuilder.scala index 0e1c43c45e..fc3e0ee4a6 100644 --- a/scalalib/overrides-2.12/scala/collection/mutable/ArrayBuilder.scala +++ b/scalalib/overrides-2.12/scala/collection/mutable/ArrayBuilder.scala @@ -14,7 +14,7 @@ import scala.reflect.ClassTag import scala.runtime.BoxedUnit import scala.scalajs.js -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo /** A builder class for arrays. * @@ -37,7 +37,7 @@ object ArrayBuilder { */ @inline def make[T: ClassTag](): ArrayBuilder[T] = - if (linkingInfo.isWebAssembly) makeForWasm() + if (LinkingInfo.isWebAssembly) makeForWasm() else makeForJS() /** Implementation of `make` for JS. */ diff --git a/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala b/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala index a49ae231c2..531a6438a2 100644 --- a/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala +++ b/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala @@ -17,7 +17,7 @@ import scala.reflect.ClassTag import scala.runtime.BoxedUnit import scala.scalajs.js -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo /** A builder class for arrays. * @@ -90,7 +90,7 @@ object ArrayBuilder { */ @inline def make[T: ClassTag]: ArrayBuilder[T] = - if (linkingInfo.isWebAssembly) makeForWasm + if (LinkingInfo.isWebAssembly) makeForWasm else makeForJS /** Implementation of `make` for JS. */ diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/InteroperabilityTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/InteroperabilityTest.scala index cc8f62fdcc..d69dd148f0 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/InteroperabilityTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/InteroperabilityTest.scala @@ -741,6 +741,22 @@ class InteroperabilityTest { assertEquals(6, InteroperabilityTestLetConstGlobals_method(5)) } + @Test def accessGlobalThis(): Unit = { + import InteroperabilityTestScalaObjectContainer._ + assumeTrue(isNoModule) + assertSame(js.Math, GlobalScope.globalThis.asInstanceOf[js.Dynamic].Math) + assertSame(js.Math, GlobalScope.`this`.asInstanceOf[js.Dynamic].Math) + assertSame(js.Math, js.Dynamic.global.`this`.Math) + } + + @Test def accessGlobalThisESModule(): Unit = { + import InteroperabilityTestScalaObjectContainer._ + assumeTrue(isESModule) + assertSame(js.undefined, GlobalScope.globalThis) + assertSame(js.undefined, GlobalScope.`this`) + assertSame(js.undefined, js.Dynamic.global.`this`) + } + } object InteroperabilityTest { @@ -1154,4 +1170,12 @@ object InteroperabilityTestScalaObjectContainer { var InteroperabilityTestLetConstGlobals_variable: String = js.native def InteroperabilityTestLetConstGlobals_method(x: Int): Int = js.native } + + @js.native + @JSGlobalScope + object GlobalScope extends js.Any { + def `this`: Any = js.native + @JSName("this") + def globalThis: Any = js.native + } } diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/javalib/lang/SystemJSTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/javalib/lang/SystemJSTest.scala index 12dc332f64..b57f6f25ce 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/javalib/lang/SystemJSTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/javalib/lang/SystemJSTest.scala @@ -15,7 +15,7 @@ package org.scalajs.testsuite.javalib.lang import org.scalajs.testsuite.utils.Platform import scala.scalajs.js -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import org.junit.Test import org.junit.Assert._ @@ -179,6 +179,6 @@ class SystemJSTest { assertEquals("/", get("file.separator")) assertEquals(":", get("path.separator")) assertEquals("\n", get("line.separator")) - assertEquals(linkingInfo.linkerVersion, get("java.vm.version")) + assertEquals(LinkingInfo.linkerVersion, get("java.vm.version")) } } diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/MiscInteropTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/MiscInteropTest.scala index bb422ac84a..6e5c97a7a4 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/MiscInteropTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/MiscInteropTest.scala @@ -194,6 +194,22 @@ class MiscInteropTest { assertEquals((), js.Dynamic.global.undefined) } + @Test def typeOfGlobalThis(): Unit = { + import MiscInteropTest._ + assumeTrue(isNoModule) + assertSame("object", js.typeOf(GlobalScope.globalThis)) + assertSame("object", js.typeOf(GlobalScope.`this`)) + assertSame("object", js.typeOf(js.Dynamic.global.`this`)) + } + + @Test def accessGlobalThisESModule(): Unit = { + import MiscInteropTest._ + assumeTrue(isESModule) + assertSame("undefined", js.typeOf(GlobalScope.globalThis)) + assertSame("undefined", js.typeOf(GlobalScope.`this`)) + assertSame("undefined", js.typeOf(js.Dynamic.global.`this`)) + } + // Emitted classes @Test def meaningfulNameProperty(): Unit = { @@ -234,4 +250,11 @@ object MiscInteropTest { class SomeJSClass extends js.Object + @js.native + @JSGlobalScope + object GlobalScope extends js.Any { + def `this`: Any = js.native + @JSName("this") + def globalThis: Any = js.native + } } diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkingInfoTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkingInfoTest.scala index 7dbcd44d22..50fc1fe8af 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkingInfoTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkingInfoTest.scala @@ -51,4 +51,13 @@ class LinkingInfoTest { assertEquals(11, ESVersion.ES2020) assertEquals(12, ESVersion.ES2021) } + + @Test def isolatedJSLinkingInfo(): Unit = { + val linkingInfo = scala.scalajs.runtime.linkingInfo + assertEquals(Platform.isInProductionMode, linkingInfo.productionMode) + assertEquals(Platform.assumedESVersion, linkingInfo.esVersion) + assertEquals(Platform.assumedESVersion >= ESVersion.ES2015, linkingInfo.assumingES6) + assertEquals(Platform.executingInWebAssembly, linkingInfo.isWebAssembly) + assertEquals(Platform.assumedESVersion, linkingInfo.esVersion) + } }