dotty.tools.backend.sjs.JSCodeGen.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala3-compiler_3 Show documentation
Show all versions of scala3-compiler_3 Show documentation
scala3-compiler-bootstrapped
package dotty.tools.backend.sjs
import scala.language.unsafeNulls
import scala.annotation.switch
import scala.collection.mutable
import dotty.tools.FatalError
import dotty.tools.dotc.CompilationUnit
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.core.*
import Contexts.*
import Decorators.*
import Flags.*
import Names.*
import NameKinds.DefaultGetterName
import Types.*
import Symbols.*
import Phases.*
import StdNames.*
import TypeErasure.ErasedValueType
import dotty.tools.dotc.transform.{Erasure, ValueClasses}
import dotty.tools.dotc.util.SourcePosition
import dotty.tools.dotc.report
import dotty.tools.sjs.ir
import dotty.tools.sjs.ir.{ClassKind, Position, Names => jsNames, Trees => js, Types => jstpe}
import dotty.tools.sjs.ir.Names.{ClassName, MethodName, SimpleMethodName}
import dotty.tools.sjs.ir.OriginalName
import dotty.tools.sjs.ir.OriginalName.NoOriginalName
import dotty.tools.sjs.ir.Trees.OptimizerHints
import dotty.tools.sjs.ir.Version.Unversioned
import dotty.tools.dotc.transform.sjs.JSSymUtils.*
import JSEncoding.*
import ScopedVar.withScopedVars
import scala.reflect.NameTransformer
/** Main codegen for Scala.js IR.
*
* [[GenSJSIR]] creates one instance of `JSCodeGen` per compilation unit.
* The `run()` method processes the whole compilation unit and generates
* `.sjsir` files for it.
*
* There are 4 main levels of translation:
*
* - `genCompilationUnit()` iterates through all the type definitions in the
* compilation unit. Each generated `js.ClassDef` is serialized to an
* `.sjsir` file.
* - `genScalaClass()` and other similar methods generate the skeleton of
* classes.
* - `genMethod()` and similar methods generate the declarations of methods.
* - `genStatOrExpr()` and everything else generate the bodies of methods.
*/
class JSCodeGen()(using genCtx: Context) {
import JSCodeGen.*
import tpd.*
val sjsPlatform = dotty.tools.dotc.config.SJSPlatform.sjsPlatform
val jsdefn = JSDefinitions.jsdefn
private val primitives = new JSPrimitives(genCtx)
val positionConversions = new JSPositions()(using genCtx)
import positionConversions.*
private val jsExportsGen = new JSExportsGen(this)
// Some state --------------------------------------------------------------
private val lazilyGeneratedAnonClasses = new MutableSymbolMap[TypeDef]
private val generatedClasses = mutable.ListBuffer.empty[js.ClassDef]
private val generatedStaticForwarderClasses = mutable.ListBuffer.empty[(Symbol, js.ClassDef)]
val currentClassSym = new ScopedVar[Symbol]
private val currentMethodSym = new ScopedVar[Symbol]
private val localNames = new ScopedVar[LocalNameGenerator]
private val thisLocalVarIdent = new ScopedVar[Option[js.LocalIdent]]
private val isModuleInitialized = new ScopedVar[ScopedVar.VarBox[Boolean]]
private val undefinedDefaultParams = new ScopedVar[mutable.Set[Symbol]]
/* Contextual JS class value for some operations of nested JS classes that need one. */
private val contextualJSClassValue = new ScopedVar[Option[js.Tree]](None)
/** Resets all of the scoped state in the context of `body`. */
private def resetAllScopedVars[T](body: => T): T = {
withScopedVars(
currentClassSym := null,
currentMethodSym := null,
localNames := null,
thisLocalVarIdent := null,
isModuleInitialized := null,
undefinedDefaultParams := null
) {
body
}
}
private def withPerMethodBodyState[A](methodSym: Symbol)(body: => A): A = {
withScopedVars(
currentMethodSym := methodSym,
thisLocalVarIdent := None,
isModuleInitialized := new ScopedVar.VarBox(false),
undefinedDefaultParams := mutable.Set.empty,
) {
body
}
}
private def acquireContextualJSClassValue[A](f: Option[js.Tree] => A): A = {
val jsClassValue = contextualJSClassValue.get
withScopedVars(
contextualJSClassValue := None
) {
f(jsClassValue)
}
}
def withNewLocalNameScope[A](body: => A): A = {
withScopedVars(localNames := new LocalNameGenerator) {
body
}
}
/** Implicitly materializes the current local name generator. */
implicit def implicitLocalNames: LocalNameGenerator = localNames.get
def currentThisType: jstpe.Type = {
encodeClassType(currentClassSym) match {
case tpe @ jstpe.ClassType(cls) =>
jstpe.BoxedClassToPrimType.getOrElse(cls, tpe)
case tpe =>
tpe
}
}
/** Returns a new fresh local identifier. */
private def freshLocalIdent()(implicit pos: Position): js.LocalIdent =
localNames.get.freshLocalIdent()
/** Returns a new fresh local identifier. */
def freshLocalIdent(base: String)(implicit pos: Position): js.LocalIdent =
localNames.get.freshLocalIdent(base)
/** Returns a new fresh local identifier. */
private def freshLocalIdent(base: TermName)(implicit pos: Position): js.LocalIdent =
localNames.get.freshLocalIdent(base)
private def consumeLazilyGeneratedAnonClass(sym: Symbol): TypeDef = {
val typeDef = lazilyGeneratedAnonClasses.remove(sym)
if (typeDef == null) {
throw new FatalError(
i"Could not find tree for lazily generated anonymous class ${sym.fullName} at ${sym.sourcePos}")
} else {
typeDef
}
}
// Compilation unit --------------------------------------------------------
def run(): Unit = {
try {
genCompilationUnit(ctx.compilationUnit)
} finally {
generatedClasses.clear()
generatedStaticForwarderClasses.clear()
}
}
/** Generates the Scala.js IR for a compilation unit
* This method iterates over all the class and interface definitions
* found in the compilation unit and emits their IR (.sjsir).
*
* Some classes are never actually emitted:
* - Classes representing primitive types
* - The scala.Array class
*
* TODO Some classes representing anonymous functions are not actually emitted.
* Instead, a temporary representation of their `apply` method is built
* and recorded, so that it can be inlined as a JavaScript anonymous
* function in the method that instantiates it.
*
* Other ClassDefs are emitted according to their nature:
* * Non-native JS class -> `genNonNativeJSClass()`
* * Other JS type (<: js.Any) -> `genRawJSClassData()`
* * Interface -> `genInterface()`
* * Normal class -> `genClass()`
*/
private def genCompilationUnit(cunit: CompilationUnit): Unit = {
def collectTypeDefs(tree: Tree): List[TypeDef] = {
tree match {
case EmptyTree => Nil
case PackageDef(_, stats) => stats.flatMap(collectTypeDefs)
case cd: TypeDef => cd :: Nil
case _: ValDef => Nil // module instance
}
}
val allTypeDefs = collectTypeDefs(cunit.tpdTree)
/* #13221 Set JavaStatic on all the Module fields of static module classes.
* This is necessary for `desugarIdent` not to crash in some obscure
* scenarios.
*
* !!! Part of this logic is duplicated in BCodeSkelBuilder.genPlainClass
*
* However, here we only do this for Module fields, not all fields.
*/
for (typeDef <- allTypeDefs) {
if (typeDef.symbol.is(ModuleClass)) {
typeDef.symbol.info.decls.foreach { f =>
if (f.isField && f.is(Module))
f.setFlag(JavaStatic)
}
}
}
val (anonJSClassTypeDefs, otherTypeDefs) =
allTypeDefs.partition(td => td.symbol.isAnonymousClass && td.symbol.isJSType)
// Record the TypeDefs of anonymous JS classes to be lazily generated
for (td <- anonJSClassTypeDefs)
lazilyGeneratedAnonClasses(td.symbol) = td
/* Finally, we emit true code for the remaining class defs. */
for (td <- otherTypeDefs) {
val sym = td.symbol
implicit val pos: Position = sym.span
/* Do not actually emit code for primitive types nor scala.Array. */
val isPrimitive =
sym.isPrimitiveValueClass || sym == defn.ArrayClass
if (!isPrimitive) {
withScopedVars(
currentClassSym := sym
) {
val tree = if (sym.isJSType) {
if (!sym.is(Trait) && sym.isNonNativeJSClass)
genNonNativeJSClass(td)
else
genRawJSClassData(td)
} else if (sym.is(Trait)) {
genInterface(td)
} else {
genScalaClass(td)
}
generatedClasses += tree
}
}
}
for (tree <- generatedClasses)
genIRFile(cunit, tree)
if (generatedStaticForwarderClasses.nonEmpty) {
/* #4148 Add generated static forwarder classes, except those that
* would collide with regular classes on case insensitive file systems.
*/
/* I could not find any reference anywhere about what locale is used
* by case insensitive file systems to compare case-insensitively.
* In doubt, force the English locale, which is probably going to do
* the right thing in virtually all cases (especially if users stick
* to ASCII class names), and it has the merit of being deterministic,
* as opposed to using the OS' default locale.
* The JVM backend performs a similar test to emit a warning for
* conflicting top-level classes. However, it uses `toLowerCase()`
* without argument, which is not deterministic.
*/
def caseInsensitiveNameOf(classDef: js.ClassDef): String =
classDef.name.name.nameString.toLowerCase(java.util.Locale.ENGLISH)
val generatedCaseInsensitiveNames =
generatedClasses.map(caseInsensitiveNameOf).toSet
for ((site, classDef) <- generatedStaticForwarderClasses) {
if (!generatedCaseInsensitiveNames.contains(caseInsensitiveNameOf(classDef))) {
genIRFile(cunit, classDef)
} else {
report.warning(
s"Not generating the static forwarders of ${classDef.name.name.nameString} " +
"because its name differs only in case from the name of another class or trait in this compilation unit.",
site.srcPos)
}
}
}
}
private def genIRFile(cunit: CompilationUnit, tree: ir.Trees.ClassDef): Unit = {
val outfile = getFileFor(cunit, tree.name.name, ".sjsir")
val output = outfile.bufferedOutput
try {
ir.Serializers.serialize(output, tree)
} finally {
output.close()
}
}
private def getFileFor(cunit: CompilationUnit, className: ClassName,
suffix: String): dotty.tools.io.AbstractFile = {
val outputDirectory = ctx.settings.outputDir.value
val pathParts = className.nameString.split('.')
val dir = pathParts.init.foldLeft(outputDirectory)(_.subdirectoryNamed(_))
val filename = pathParts.last
dir.fileNamed(filename + suffix)
}
// Generate a class --------------------------------------------------------
/** Gen the IR ClassDef for a Scala class definition (maybe a module class).
*/
private def genScalaClass(td: TypeDef): js.ClassDef = {
val sym = td.symbol.asClass
implicit val pos: SourcePosition = sym.sourcePos
assert(!sym.is(Trait),
"genScalaClass() must be called only for normal classes: "+sym)
assert(sym.superClass != NoSymbol, sym)
if (hasDefaultCtorArgsAndJSModule(sym)) {
report.error(
"Implementation restriction: " +
"constructors of Scala classes cannot have default parameters if their companion module is JS native.",
td)
}
val classIdent = encodeClassNameIdent(sym)
val originalName = originalNameOfClass(sym)
val isHijacked = false //isHijackedBoxedClass(sym)
// Optimizer hints
val isDynamicImportThunk = sym.isSubClass(jsdefn.DynamicImportThunkClass)
def isStdLibClassWithAdHocInlineAnnot(sym: Symbol): Boolean = {
val fullName = sym.fullName.toString
(fullName.startsWith("scala.Tuple") && !fullName.endsWith("$")) ||
(fullName.startsWith("scala.collection.mutable.ArrayOps$of"))
}
val shouldMarkInline = (
isDynamicImportThunk ||
sym.hasAnnotation(jsdefn.InlineAnnot) ||
(sym.isAnonymousFunction && !sym.isSubClass(defn.PartialFunctionClass)) ||
isStdLibClassWithAdHocInlineAnnot(sym))
val optimizerHints = {
OptimizerHints.empty
.withInline(shouldMarkInline)
.withNoinline(sym.hasAnnotation(jsdefn.NoinlineAnnot))
}
// Generate members (constructor + methods)
val methodsBuilder = List.newBuilder[js.MethodDef]
val jsNativeMembersBuilder = List.newBuilder[js.JSNativeMemberDef]
val tpl = td.rhs.asInstanceOf[Template]
for (tree <- tpl.constr :: tpl.body) {
tree match {
case EmptyTree => ()
case vd: ValDef =>
// fields are added via genClassFields(), but we need to generate the JS native members
val sym = vd.symbol
if (!sym.is(Module) && sym.hasAnnotation(jsdefn.JSNativeAnnot))
jsNativeMembersBuilder += genJSNativeMemberDef(vd)
case dd: DefDef =>
val sym = dd.symbol
if sym.hasAnnotation(jsdefn.JSNativeAnnot) then
if !sym.is(Accessor) then
jsNativeMembersBuilder += genJSNativeMemberDef(dd)
else
methodsBuilder ++= genMethod(dd)
case _ =>
throw new FatalError("Illegal tree in body of genScalaClass(): " + tree)
}
}
val (fields, staticGetterDefs) = if (!isHijacked) genClassFields(td) else (Nil, Nil)
val jsNativeMembers = jsNativeMembersBuilder.result()
val generatedMethods = methodsBuilder.result() ::: staticGetterDefs
// Generate member exports
val memberExports = jsExportsGen.genMemberExports(sym)
// Generate top-level export definitions
val topLevelExportDefs = jsExportsGen.genTopLevelExports(sym)
// Static initializer
val optStaticInitializer = {
// Initialization of reflection data, if required
val reflectInit = {
val enableReflectiveInstantiation = {
sym.baseClasses.exists { ancestor =>
ancestor.hasAnnotation(jsdefn.EnableReflectiveInstantiationAnnot)
}
}
if (enableReflectiveInstantiation)
genRegisterReflectiveInstantiation(sym).toList
else
Nil
}
// Initialization of the module because of field exports
val needsStaticModuleInit =
topLevelExportDefs.exists(_.isInstanceOf[js.TopLevelFieldExportDef])
val staticModuleInit =
if (!needsStaticModuleInit) Nil
else List(genLoadModule(sym))
val staticInitializerStats = reflectInit ::: staticModuleInit
if (staticInitializerStats.nonEmpty)
List(genStaticConstructorWithStats(ir.Names.StaticInitializerName, js.Block(staticInitializerStats)))
else
Nil
}
val optDynamicImportForwarder =
if (isDynamicImportThunk) List(genDynamicImportForwarder(sym))
else Nil
val allMethodsExceptStaticForwarders: List[js.MethodDef] =
generatedMethods ::: optStaticInitializer ::: optDynamicImportForwarder
// Add static forwarders
val allMethods = if (!isCandidateForForwarders(sym)) {
allMethodsExceptStaticForwarders
} else {
if (isStaticModule(sym)) {
/* If the module class has no linked class, we must create one to
* hold the static forwarders. Otherwise, this is going to be handled
* when generating the companion class.
*/
if (!sym.linkedClass.exists) {
val forwarders = genStaticForwardersFromModuleClass(Nil, sym)
if (forwarders.nonEmpty) {
val forwardersClassDef = js.ClassDef(
js.ClassIdent(ClassName(classIdent.name.nameString.stripSuffix("$"))),
originalName,
ClassKind.Class,
None,
Some(js.ClassIdent(ir.Names.ObjectClass)),
Nil,
None,
None,
fields = Nil,
methods = forwarders,
jsConstructor = None,
jsMethodProps = Nil,
jsNativeMembers = Nil,
topLevelExportDefs = Nil
)(js.OptimizerHints.empty)
generatedStaticForwarderClasses += sym -> forwardersClassDef
}
}
allMethodsExceptStaticForwarders
} else {
val forwarders = genStaticForwardersForClassOrInterface(
allMethodsExceptStaticForwarders, sym)
allMethodsExceptStaticForwarders ::: forwarders
}
}
// The complete class definition
val kind =
if (isStaticModule(sym)) ClassKind.ModuleClass
else if (isHijacked) ClassKind.HijackedClass
else ClassKind.Class
val classDefinition = js.ClassDef(
classIdent,
originalName,
kind,
None,
Some(encodeClassNameIdent(sym.superClass)),
genClassInterfaces(sym, forJSClass = false),
None,
None,
fields,
allMethods,
jsConstructor = None,
memberExports,
jsNativeMembers,
topLevelExportDefs)(
optimizerHints)
ir.Hashers.hashClassDef(classDefinition)
}
/** Gen the IR ClassDef for a Scala.js-defined JS class. */
private def genNonNativeJSClass(td: TypeDef): js.ClassDef = {
val sym = td.symbol.asClass
implicit val pos: SourcePosition = sym.sourcePos
assert(sym.isNonNativeJSClass,
i"genNonNativeJSClass() must be called only for non-native JS classes: $sym")
assert(sym.superClass != NoSymbol, sym)
if (hasDefaultCtorArgsAndJSModule(sym)) {
report.error(
"Implementation restriction: " +
"constructors of non-native JS classes cannot have default parameters if their companion module is JS native.",
td)
}
val classIdent = encodeClassNameIdent(sym)
val originalName = originalNameOfClass(sym)
// Generate members (constructor + methods)
val constructorTrees = new mutable.ListBuffer[DefDef]
val generatedMethods = new mutable.ListBuffer[js.MethodDef]
val dispatchMethodNames = new mutable.ListBuffer[JSName]
val tpl = td.rhs.asInstanceOf[Template]
for (tree <- tpl.constr :: tpl.body) {
tree match {
case EmptyTree => ()
case _: ValDef =>
() // fields are added via genClassFields()
case dd: DefDef =>
val sym = dd.symbol
val exposed = sym.isJSExposed
if (sym.isClassConstructor) {
constructorTrees += dd
} else if (exposed && sym.is(Accessor, butNot = Lazy)) {
// Exposed accessors must not be emitted, since the field they access is enough.
} else if (sym.hasAnnotation(jsdefn.JSOptionalAnnot)) {
// Optional methods must not be emitted
} else {
generatedMethods ++= genMethod(dd)
// Collect the names of the dispatchers we have to create
if (exposed && !sym.is(Deferred)) {
/* We add symbols that we have to expose here. This way we also
* get inherited stuff that is implemented in this class.
*/
dispatchMethodNames += sym.jsName
}
}
case _ =>
throw new FatalError("Illegal tree in gen of genNonNativeJSClass(): " + tree)
}
}
// Static members (exported from the companion object)
val (staticFields, staticExports) = {
val module = sym.companionModule
if (!module.exists) {
(Nil, Nil)
} else {
val companionModuleClass = module.moduleClass
val (staticFields, staticExports) = withScopedVars(currentClassSym := companionModuleClass) {
jsExportsGen.genStaticExports(companionModuleClass)
}
if (staticFields.nonEmpty) {
generatedMethods +=
genStaticConstructorWithStats(ir.Names.ClassInitializerName, genLoadModule(companionModuleClass))
}
(staticFields, staticExports)
}
}
val topLevelExports = jsExportsGen.genTopLevelExports(sym)
val (generatedConstructor, jsClassCaptures) = withNewLocalNameScope {
val isNested = sym.isNestedJSClass
if (isNested)
localNames.reserveLocalName(JSSuperClassParamName)
val (captures, ctor) = genJSClassCapturesAndConstructor(constructorTrees.toList)
val jsClassCaptures = if (isNested) {
val superParam = js.ParamDef(js.LocalIdent(JSSuperClassParamName),
NoOriginalName, jstpe.AnyType, mutable = false)
Some(superParam :: captures)
} else {
assert(captures.isEmpty, s"found non nested JS class with captures $captures at $pos")
None
}
(ctor, jsClassCaptures)
}
// Generate fields
val (fields, staticGetterDefs) = genClassFields(td)
val methods = generatedMethods.toList ::: staticGetterDefs
val jsMethodProps =
jsExportsGen.genJSClassDispatchers(sym, dispatchMethodNames.result().distinct) ::: staticExports
// The complete class definition
val kind =
if (isStaticModule(sym)) ClassKind.JSModuleClass
else ClassKind.JSClass
val classDefinition = js.ClassDef(
classIdent,
originalNameOfClass(sym),
kind,
jsClassCaptures,
Some(encodeClassNameIdent(sym.superClass)),
genClassInterfaces(sym, forJSClass = true),
jsSuperClass = jsClassCaptures.map(_.head.ref),
None,
fields ::: staticFields,
methods,
Some(generatedConstructor),
jsMethodProps,
jsNativeMembers = Nil,
topLevelExports)(
OptimizerHints.empty)
ir.Hashers.hashClassDef(classDefinition)
}
/** Gen the IR ClassDef for a raw JS class or trait.
*/
private def genRawJSClassData(td: TypeDef): js.ClassDef = {
val sym = td.symbol.asClass
implicit val pos: Position = sym.span
val classIdent = encodeClassNameIdent(sym)
val kind = {
if (sym.is(Trait)) ClassKind.AbstractJSType
else if (sym.is(ModuleClass)) ClassKind.NativeJSModuleClass
else ClassKind.NativeJSClass
}
val superClass =
if (sym.is(Trait)) None
else Some(encodeClassNameIdent(sym.superClass))
val jsNativeLoadSpec = computeJSNativeLoadSpecOfClass(sym)
js.ClassDef(
classIdent,
originalNameOfClass(sym),
kind,
None,
superClass,
genClassInterfaces(sym, forJSClass = true),
None,
jsNativeLoadSpec,
Nil,
Nil,
None,
Nil,
Nil,
Nil)(
OptimizerHints.empty)
}
/** Gen the IR ClassDef for an interface definition.
*/
private def genInterface(td: TypeDef): js.ClassDef = {
val sym = td.symbol.asClass
implicit val pos: SourcePosition = sym.sourcePos
val classIdent = encodeClassNameIdent(sym)
val generatedMethods = new mutable.ListBuffer[js.MethodDef]
val tpl = td.rhs.asInstanceOf[Template]
for (tree <- tpl.constr :: tpl.body) {
tree match {
case EmptyTree => ()
case dd: DefDef => generatedMethods ++= genMethod(dd)
case _ =>
throw new FatalError(
i"""Illegal tree in gen of genInterface(): $tree
|class = $td
|in ${ctx.compilationUnit}""")
}
}
val superInterfaces = genClassInterfaces(sym, forJSClass = false)
val genMethodsList = generatedMethods.toList
val allMemberDefs =
if (!isCandidateForForwarders(sym)) genMethodsList
else genMethodsList ::: genStaticForwardersForClassOrInterface(genMethodsList, sym)
val classDef = js.ClassDef(
classIdent,
originalNameOfClass(sym),
ClassKind.Interface,
None,
None,
superInterfaces,
None,
None,
Nil,
allMemberDefs,
None,
Nil,
Nil,
Nil)(
OptimizerHints.empty)
ir.Hashers.hashClassDef(classDef)
}
private def genClassInterfaces(sym: ClassSymbol, forJSClass: Boolean)(
implicit pos: Position): List[js.ClassIdent] = {
for {
intf <- sym.directlyInheritedTraits
if !(forJSClass && intf == defn.DynamicClass)
} yield {
encodeClassNameIdent(intf)
}
}
// Static forwarders -------------------------------------------------------
/* This mimics the logic in BCodeHelpers.addForwarders and the code that
* calls it, except that we never have collisions with existing methods in
* the companion class. This is because in the IR, only methods with the
* same `MethodName` (including signature) and that are also
* `PublicStatic` would collide. There should never be an actual collision
* because the only `PublicStatic` methods that are otherwise generated are
* the bodies of SAMs, which have mangled names. If that assumption is
* broken, an error message is emitted asking the user to report a bug.
*
* It is important that we always emit forwarders, because some Java APIs
* actually have a public static method and a public instance method with
* the same name. For example the class `Integer` has a
* `def hashCode(): Int` and a `static def hashCode(Int): Int`. The JVM
* back-end considers them as colliding because they have the same name,
* but we must not.
*
* By default, we only emit forwarders for top-level objects, like the JVM
* back-end. However, if requested via a compiler option, we enable them
* for all static objects. This is important so we can implement static
* methods of nested static classes of JDK APIs (see scala-js/#3950).
*/
/** Is the given Scala class, interface or module class a candidate for
* static forwarders?
*
* - the flag `-XnoForwarders` is not set to true, and
* - the symbol is static, and
* - either of both of the following is true:
* - the flag `-scalajsGenStaticForwardersForNonTopLevelObjects` is set to true, or
* - the symbol was originally at the package level
*
* Other than the Scala.js-specific flag, and the fact that we also consider
* interfaces, this performs the same tests as the JVM back-end.
*/
def isCandidateForForwarders(sym: Symbol): Boolean = {
!ctx.settings.XnoForwarders.value && sym.isStatic && {
ctx.settings.scalajsGenStaticForwardersForNonTopLevelObjects.value || {
atPhase(flattenPhase) {
toDenot(sym).owner.is(PackageClass)
}
}
}
}
/** Gen the static forwarders to the members of a class or interface for
* methods of its companion object.
*
* This is only done if there exists a companion object and it is not a JS
* type.
*
* Precondition: `isCandidateForForwarders(sym)` is true
*/
def genStaticForwardersForClassOrInterface(
existingMethods: List[js.MethodDef], sym: Symbol)(
implicit pos: SourcePosition): List[js.MethodDef] = {
val module = sym.companionModule
if (!module.exists) {
Nil
} else {
val moduleClass = module.moduleClass
if (!moduleClass.isJSType)
genStaticForwardersFromModuleClass(existingMethods, moduleClass)
else
Nil
}
}
/** Gen the static forwarders for the methods of a module class.
*
* Precondition: `isCandidateForForwarders(moduleClass)` is true
*/
def genStaticForwardersFromModuleClass(existingMethods: List[js.MethodDef],
moduleClass: Symbol)(
implicit pos: SourcePosition): List[js.MethodDef] = {
assert(moduleClass.is(ModuleClass), moduleClass)
val existingPublicStaticMethodNames = existingMethods.collect {
case js.MethodDef(flags, name, _, _, _, _)
if flags.namespace == js.MemberNamespace.PublicStatic =>
name.name
}.toSet
val staticNames = moduleClass.companionClass.info.allMembers
.collect { case d if d.name.isTermName && d.symbol.isScalaStatic => d.name }.toSet
val members = {
moduleClass.info.membersBasedOnFlags(required = Flags.Method,
excluded = Flags.ExcludedForwarder).map(_.symbol)
}
def isExcluded(m: Symbol): Boolean = {
def hasAccessBoundary = m.accessBoundary(defn.RootClass) ne defn.RootClass
def isOfJLObject: Boolean = m.owner eq defn.ObjectClass
def isDefaultParamOfJSNativeDef: Boolean = {
m.name.is(DefaultGetterName) && {
val info = new DefaultParamInfo(m)
!info.isForConstructor && info.attachedMethod.hasAnnotation(jsdefn.JSNativeAnnot)
}
}
m.is(Deferred)
|| m.isConstructor
|| hasAccessBoundary
|| isOfJLObject
|| m.hasAnnotation(jsdefn.JSNativeAnnot) || isDefaultParamOfJSNativeDef // #4557
|| staticNames(m.name)
}
val forwarders = for {
m <- members
if !isExcluded(m)
} yield {
withNewLocalNameScope {
val flags = js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic)
val methodIdent = encodeMethodSym(m)
val originalName = originalNameOfMethod(m)
val jsParams = for {
(paramName, paramInfo) <- m.info.paramNamess.flatten.zip(m.info.paramInfoss.flatten)
} yield {
js.ParamDef(freshLocalIdent(paramName), NoOriginalName,
toIRType(paramInfo), mutable = false)
}
val resultType = toIRType(m.info.resultType)
if (existingPublicStaticMethodNames.contains(methodIdent.name)) {
report.error(
"Unexpected situation: found existing public static method " +
s"${methodIdent.name.nameString} in the companion class of " +
s"${moduleClass.fullName}; cannot generate a static forwarder " +
"the method of the same name in the object." +
"Please report this as a bug in the Scala.js support in dotty.",
pos)
}
js.MethodDef(flags, methodIdent, originalName, jsParams, resultType, Some {
genApplyMethod(genLoadModule(moduleClass), m, jsParams.map(_.ref))
})(OptimizerHints.empty, Unversioned)
}
}
forwarders.toList
}
// Generate the fields of a class ------------------------------------------
/** Gen definitions for the fields of a class. */
private def genClassFields(td: TypeDef): (List[js.AnyFieldDef], List[js.MethodDef]) = {
val classSym = td.symbol.asClass
assert(currentClassSym.get == classSym,
"genClassFields called with a ClassDef other than the current one")
val isJSClass = classSym.isNonNativeJSClass
val fieldDefs = List.newBuilder[js.AnyFieldDef]
val staticGetterDefs = List.newBuilder[js.MethodDef]
// Term members that are neither methods nor modules are fields
classSym.info.decls.filter { f =>
!f.isOneOf(MethodOrModule) && f.isTerm
&& !f.hasAnnotation(jsdefn.JSNativeAnnot)
&& !f.hasAnnotation(jsdefn.JSOptionalAnnot)
&& !f.hasAnnotation(jsdefn.JSExportStaticAnnot)
}.foreach { f =>
implicit val pos = f.span
val isTopLevelExport = f.hasAnnotation(jsdefn.JSExportTopLevelAnnot)
val isJavaStatic = f.is(JavaStatic)
assert(!(isTopLevelExport && isJavaStatic),
em"found ${f.fullName} which is both a top-level export and a Java static")
val isStaticField = isTopLevelExport || isJavaStatic
val namespace = if isStaticField then js.MemberNamespace.PublicStatic else js.MemberNamespace.Public
val mutable = isStaticField || f.is(Mutable)
val flags = js.MemberFlags.empty.withMutable(mutable).withNamespace(namespace)
val irTpe0 =
if (isJSClass) genExposedFieldIRType(f)
else if (isTopLevelExport) jstpe.AnyType
else toIRType(f.info)
// scala-js/#4370 Fields cannot have type NothingType
val irTpe =
if (irTpe0 == jstpe.NothingType) encodeClassType(defn.NothingClass)
else irTpe0
if (isJSClass && f.isJSExposed)
fieldDefs += js.JSFieldDef(flags, genExpr(f.jsName)(f.sourcePos), irTpe)
else
val fieldIdent = encodeFieldSym(f)
val originalName = originalNameOfField(f)
fieldDefs += js.FieldDef(flags, fieldIdent, originalName, irTpe)
if isJavaStatic then
// Here we are generating a public static getter for the static field,
// this is its API for other units. This is necessary for singleton
// enum values, which are backed by static fields.
val className = encodeClassName(classSym)
val body = js.Block(
js.LoadModule(className),
js.SelectStatic(fieldIdent)(irTpe))
staticGetterDefs += js.MethodDef(
js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic),
encodeStaticMemberSym(f), originalName, Nil, irTpe,
Some(body))(
OptimizerHints.empty, Unversioned)
}
(fieldDefs.result(), staticGetterDefs.result())
}
def genExposedFieldIRType(f: Symbol): jstpe.Type = {
val tpeEnteringPosterasure = atPhase(elimErasedValueTypePhase)(f.info)
tpeEnteringPosterasure match {
case tpe: ErasedValueType =>
/* Here, we must store the field as the boxed representation of
* the value class. The default value of that field, as
* initialized at the time the instance is created, will
* therefore be null. This will not match the behavior we would
* get in a Scala class. To match the behavior, we would need to
* initialized to an instance of the boxed representation, with
* an underlying value set to the zero of its type. However we
* cannot implement that, so we live with the discrepancy.
*
* In dotc this is usually not an issue, because it unboxes `null` to
* the zero of the underlying type, unlike scalac which throws an NPE.
*/
jstpe.ClassType(encodeClassName(tpe.tycon.typeSymbol))
case _ =>
// Other types are not boxed, so we can initialized them to their true zero.
toIRType(f.info)
}
}
// Static initializers -----------------------------------------------------
private def genStaticConstructorWithStats(name: MethodName, stats: js.Tree)(
implicit pos: Position): js.MethodDef = {
js.MethodDef(
js.MemberFlags.empty.withNamespace(js.MemberNamespace.StaticConstructor),
js.MethodIdent(name),
NoOriginalName,
Nil,
jstpe.NoType,
Some(stats))(
OptimizerHints.empty, Unversioned)
}
private def genRegisterReflectiveInstantiation(sym: Symbol)(
implicit pos: SourcePosition): Option[js.Tree] = {
if (isStaticModule(sym))
genRegisterReflectiveInstantiationForModuleClass(sym)
else if (sym.is(ModuleClass))
None // scala-js#3228
else if (sym.is(Lifted) && !sym.originalOwner.isClass)
None // scala-js#3227
else
genRegisterReflectiveInstantiationForNormalClass(sym)
}
private def genRegisterReflectiveInstantiationForModuleClass(sym: Symbol)(
implicit pos: SourcePosition): Option[js.Tree] = {
val fqcnArg = js.StringLiteral(sym.fullName.toString)
val runtimeClassArg = js.ClassOf(toTypeRef(sym.info))
val loadModuleFunArg =
js.Closure(arrow = true, Nil, Nil, None, genLoadModule(sym), Nil)
val stat = genApplyMethod(
genLoadModule(jsdefn.ReflectModule),
jsdefn.Reflect_registerLoadableModuleClass,
List(fqcnArg, runtimeClassArg, loadModuleFunArg))
Some(stat)
}
private def genRegisterReflectiveInstantiationForNormalClass(sym: Symbol)(
implicit pos: SourcePosition): Option[js.Tree] = {
val ctors =
if (sym.is(Abstract)) Nil
else sym.info.member(nme.CONSTRUCTOR).alternatives.map(_.symbol).filter(m => !m.isOneOf(Private | Protected))
if (ctors.isEmpty) {
None
} else {
val constructorsInfos = for {
ctor <- ctors
} yield {
withNewLocalNameScope {
val (parameterTypes, formalParams, actualParams) = (for {
(paramName, paramInfo) <- ctor.info.paramNamess.flatten.zip(ctor.info.paramInfoss.flatten)
} yield {
val paramType = js.ClassOf(toTypeRef(paramInfo))
val paramDef = js.ParamDef(freshLocalIdent(paramName),
NoOriginalName, jstpe.AnyType, mutable = false)
val actualParam = unbox(paramDef.ref, paramInfo)
(paramType, paramDef, actualParam)
}).unzip3
val paramTypesArray = js.JSArrayConstr(parameterTypes)
val newInstanceFun = js.Closure(arrow = true, Nil, formalParams, None, {
js.New(encodeClassName(sym), encodeMethodSym(ctor), actualParams)
}, Nil)
js.JSArrayConstr(List(paramTypesArray, newInstanceFun))
}
}
val fqcnArg = js.StringLiteral(sym.fullName.toString)
val runtimeClassArg = js.ClassOf(toTypeRef(sym.info))
val ctorsInfosArg = js.JSArrayConstr(constructorsInfos)
val stat = genApplyMethod(
genLoadModule(jsdefn.ReflectModule),
jsdefn.Reflect_registerInstantiatableClass,
List(fqcnArg, runtimeClassArg, ctorsInfosArg))
Some(stat)
}
}
// Constructor of a non-native JS class ------------------------------------
def genJSClassCapturesAndConstructor(constructorTrees: List[DefDef])(
implicit pos: SourcePosition): (List[js.ParamDef], js.JSConstructorDef) = {
/* We need to merge all Scala constructors into a single one because the
* IR, like JavaScript, only allows a single one.
*
* We do this by applying:
* 1. Applying runtime type based dispatch, just like exports.
* 2. Splitting secondary ctors into parts before and after the `this` call.
* 3. Topo-sorting all constructor statements and including/excluding
* them based on the overload that was chosen.
*/
val (primaryTree :: Nil, secondaryTrees) =
constructorTrees.partition(_.symbol.isPrimaryConstructor): @unchecked
val primaryCtor = genPrimaryJSClassCtor(primaryTree)
val secondaryCtors = secondaryTrees.map(genSecondaryJSClassCtor(_))
// VarDefs for the parameters of all constructors.
val paramVarDefs = for {
vparam <- constructorTrees.flatMap(_.paramss.flatten)
} yield {
val sym = vparam.symbol
val tpe = toIRType(sym.info)
js.VarDef(encodeLocalSym(sym), originalNameOfLocal(sym), tpe, mutable = true, jstpe.zeroOf(tpe))(vparam.span)
}
/* organize constructors in a called-by tree
* (the implicit root is the primary constructor)
*/
val ctorTree = {
val ctorToChildren = secondaryCtors
.groupBy(_.targetCtor)
.withDefaultValue(Nil)
/* when constructing the call-by tree, we use pre-order traversal to
* assign overload numbers.
* this puts all descendants of a ctor in a range of overloads numbers.
*
* this property is useful, later, when we need to make statements
* conditional based on the chosen overload.
*/
var nextOverloadNum = 0
def subTree[T <: JSCtor](ctor: T): ConstructorTree[T] = {
val overloadNum = nextOverloadNum
nextOverloadNum += 1
val subtrees = ctorToChildren(ctor.sym).map(subTree(_))
new ConstructorTree(overloadNum, ctor, subtrees)
}
subTree(primaryCtor)
}
/* prepare overload dispatch for all constructors.
* as a side-product, we retrieve the capture parameters.
*/
val (exports, jsClassCaptures) = {
val exports = List.newBuilder[jsExportsGen.Exported]
val jsClassCaptures = List.newBuilder[js.ParamDef]
def add(tree: ConstructorTree[? <: JSCtor]): Unit = {
val (e, c) = genJSClassCtorDispatch(tree.ctor.sym,
tree.ctor.paramsAndInfo, tree.overloadNum)
exports += e
jsClassCaptures ++= c
tree.subCtors.foreach(add(_))
}
add(ctorTree)
(exports.result(), jsClassCaptures.result())
}
// The name 'constructor' is used for error reporting here
val (formalArgs, restParam, overloadDispatchBody) =
jsExportsGen.genOverloadDispatch(JSName.Literal("constructor"), exports, jstpe.IntType)
val overloadVar = js.VarDef(freshLocalIdent("overload"), NoOriginalName,
jstpe.IntType, mutable = false, overloadDispatchBody)
val constructorBody = wrapJSCtorBody(
paramVarDefs :+ overloadVar,
genJSClassCtorBody(overloadVar.ref, ctorTree),
js.Undefined() :: Nil
)
val constructorDef = js.JSConstructorDef(
js.MemberFlags.empty.withNamespace(js.MemberNamespace.Constructor),
formalArgs, restParam, constructorBody)(OptimizerHints.empty, Unversioned)
(jsClassCaptures, constructorDef)
}
private def genPrimaryJSClassCtor(dd: DefDef): PrimaryJSCtor = {
val sym = dd.symbol
assert(sym.isPrimaryConstructor, s"called with non-primary ctor: $sym")
var preSuperStats = List.newBuilder[js.Tree]
var jsSuperCall: Option[js.JSSuperConstructorCall] = None
val postSuperStats = List.newBuilder[js.Tree]
/* Move param accessor initializers after the super constructor call since
* JS cannot access `this` before the super constructor call.
*
* dotc inserts statements before the super constructor call for param
* accessor initializers (including val's and var's declared in the
* params). Those statements are assignments whose rhs'es are always simple
* Idents (the constructor params).
*
* There can also be local `val`s before the super constructor call for
* default arguments to the super constructor. These must remain before.
*
* Our strategy is therefore to move only the field assignments after the
* super constructor call. They are therefore executed later than for a
* Scala class (as specified for non-native JS classes semantics).
* However, side effects and evaluation order of all the other
* computations remains unchanged.
*/
withPerMethodBodyState(sym) {
def isThisField(tree: Tree): Boolean = tree match {
case Select(ths: This, _) => ths.symbol == currentClassSym.get
case tree: Ident => desugarIdent(tree).exists(isThisField(_))
case _ => false
}
def rec(tree: Tree): Unit = {
tree match {
case Block(stats, expr) =>
stats.foreach(rec(_))
rec(expr)
case tree @ Apply(fun @ Select(Super(This(_), _), _), args)
if fun.symbol.isClassConstructor =>
assert(jsSuperCall.isEmpty, s"Found 2 JS Super calls at ${dd.sourcePos}")
implicit val pos: Position = tree.span
jsSuperCall = Some(js.JSSuperConstructorCall(genActualJSArgs(fun.symbol, args)))
case tree if jsSuperCall.isDefined =>
// Once we're past the super constructor call, everything goes after.
postSuperStats += genStat(tree)
case Assign(lhs, Ident(_)) if isThisField(lhs) =>
/* If that shape appears before the jsSuperCall, it is a param
* accessor initializer. We move it.
*/
postSuperStats += genStat(tree)
case stat =>
// Other statements are left before.
preSuperStats += genStat(stat)
}
}
rec(dd.rhs)
}
assert(jsSuperCall.isDefined,
s"Did not find Super call in primary JS construtor at ${dd.sourcePos}")
new PrimaryJSCtor(sym, genParamsAndInfo(sym, dd.paramss),
js.JSConstructorBody(preSuperStats.result(), jsSuperCall.get, postSuperStats.result())(dd.span))
}
private def genSecondaryJSClassCtor(dd: DefDef): SplitSecondaryJSCtor = {
val sym = dd.symbol
assert(!sym.isPrimaryConstructor, s"called with primary ctor $sym")
def flattenBlocks(t: Tree): List[Tree] = t match {
case Block(stats, expr) => (stats :+ expr).flatMap(flattenBlocks)
case _ => t :: Nil
}
val stats = flattenBlocks(dd.rhs)
val beforeThisCall = List.newBuilder[js.Tree]
var thisCall: Option[(Symbol, List[js.Tree])] = None
val afterThisCall = List.newBuilder[js.Tree]
withPerMethodBodyState(sym) {
stats.foreach {
case tree @ Apply(fun @ Select(This(_), _), args)
if fun.symbol.isClassConstructor =>
assert(thisCall.isEmpty,
s"duplicate this() call in secondary JS constructor at ${dd.sourcePos}")
implicit val pos: Position = tree.span
val sym = fun.symbol
thisCall = Some((sym, genActualArgs(sym, args)))
case stat =>
val jsStat = genStat(stat)
if (thisCall.isEmpty)
beforeThisCall += jsStat
else
afterThisCall += jsStat
}
}
assert(thisCall.isDefined,
i"could not find the this() call in secondary JS constructor at ${dd.sourcePos}:\n${stats.map(_.show).mkString("\n")}")
val Some((targetCtor, ctorArgs)) = thisCall: @unchecked
new SplitSecondaryJSCtor(sym, genParamsAndInfo(sym, dd.paramss),
beforeThisCall.result(), targetCtor, ctorArgs, afterThisCall.result())
}
private def genParamsAndInfo(ctorSym: Symbol,
vparamss: List[ParamClause]): List[(Symbol, JSParamInfo)] = {
implicit val pos: SourcePosition = ctorSym.sourcePos
val paramSyms = if (vparamss.isEmpty) Nil else vparamss.head.map(_.symbol)
paramSyms.zip(ctorSym.jsParamInfos)
}
private def genJSClassCtorDispatch(ctorSym: Symbol,
allParamsAndInfos: List[(Symbol, JSParamInfo)],
overloadNum: Int): (jsExportsGen.Exported, List[js.ParamDef]) = {
implicit val pos: SourcePosition = ctorSym.sourcePos
/* `allParams` are the parameters as seen from inside the constructor body,
* i.e., the ones generated by the trees in the constructor body.
*/
val (captureParamsAndInfos, normalParamsAndInfos) =
allParamsAndInfos.partition(_._2.capture)
/* For class captures, we need to generate different names than the ones
* used by the constructor body. This is necessary so that we can forward
* captures properly between constructor delegation calls.
*/
val (jsClassCaptures, captureAssigns) = (for {
(param, info) <- captureParamsAndInfos
} yield {
val ident = freshLocalIdent(param.name.toTermName)
val jsClassCapture =
js.ParamDef(ident, originalNameOfLocal(param), toIRType(info.info), mutable = false)
val captureAssign =
js.Assign(genVarRef(param), jsClassCapture.ref)
(jsClassCapture, captureAssign)
}).unzip
val normalInfos = normalParamsAndInfos.map(_._2).toIndexedSeq
val jsExport = new jsExportsGen.Exported(ctorSym, normalInfos) {
def genBody(formalArgsRegistry: jsExportsGen.FormalArgsRegistry): js.Tree = {
val paramAssigns = for {
((param, info), i) <- normalParamsAndInfos.zipWithIndex
} yield {
val rhs = jsExportsGen.genScalaArg(this, i, formalArgsRegistry, info, static = true,
captures = captureParamsAndInfos.map(pi => genVarRef(pi._1)))(
prevArgsCount => normalParamsAndInfos.take(prevArgsCount).map(pi => genVarRef(pi._1)))
js.Assign(genVarRef(param), rhs)
}
js.Block(captureAssigns ::: paramAssigns, js.IntLiteral(overloadNum))
}
}
(jsExport, jsClassCaptures)
}
/** Generates a JS constructor body based on a constructor tree. */
private def genJSClassCtorBody(overloadVar: js.VarRef,
ctorTree: ConstructorTree[PrimaryJSCtor])(implicit pos: Position): js.JSConstructorBody = {
/* generates a statement that conditionally executes body iff the chosen
* overload is any of the descendants of `tree` (including itself).
*
* here we use the property from building the trees, that a set of
* descendants always has a range of overload numbers.
*/
def ifOverload(tree: ConstructorTree[?], body: js.Tree): js.Tree = body match {
case js.Skip() => js.Skip()
case body =>
val x = overloadVar
val cond = {
import tree.{lo, hi}
if (lo == hi) {
js.BinaryOp(js.BinaryOp.Int_==, js.IntLiteral(lo), x)
} else {
val lhs = js.BinaryOp(js.BinaryOp.Int_<=, js.IntLiteral(lo), x)
val rhs = js.BinaryOp(js.BinaryOp.Int_<=, x, js.IntLiteral(hi))
js.If(lhs, rhs, js.BooleanLiteral(false))(jstpe.BooleanType)
}
}
js.If(cond, body, js.Skip())(jstpe.NoType)
}
/* preStats / postStats use pre/post order traversal respectively to
* generate a topo-sorted sequence of statements.
*/
def preStats(tree: ConstructorTree[SplitSecondaryJSCtor],
nextParamsAndInfo: List[(Symbol, JSParamInfo)]): js.Tree = {
val inner = tree.subCtors.map(preStats(_, tree.ctor.paramsAndInfo))
assert(tree.ctor.ctorArgs.size == nextParamsAndInfo.size, "param count mismatch")
val paramsInfosAndArgs = nextParamsAndInfo.zip(tree.ctor.ctorArgs)
val (captureParamsInfosAndArgs, normalParamsInfosAndArgs) =
paramsInfosAndArgs.partition(_._1._2.capture)
val captureAssigns = for {
((param, _), arg) <- captureParamsInfosAndArgs
} yield {
js.Assign(genVarRef(param), arg)
}
val normalAssigns = for {
(((param, info), arg), i) <- normalParamsInfosAndArgs.zipWithIndex
} yield {
val newArg = arg match {
case js.Transient(UndefinedParam) =>
/* Go full circle: We have ignored the default param getter for
* this, we'll create it again.
*
* This seems not optimal: We could simply not ignore the calls to
* default param getters in the first place.
*
* However, this proves to be difficult: Because of translations in
* earlier phases, calls to default param getters may be assigned
* to temporary variables first (see the undefinedDefaultParams
* ScopedVar). If this happens, it becomes increasingly difficult
* to distinguish a default param getter call for a constructor
* call of *this* instance (in which case we would want to keep
* the default param getter call) from one for a *different*
* instance (in which case we would want to discard the default
* param getter call)
*
* Because of this, it ends up being easier to just re-create the
* default param getter call if necessary.
*/
implicit val pos: SourcePosition = tree.ctor.sym.sourcePos
jsExportsGen.genCallDefaultGetter(tree.ctor.sym, i, static = false,
captures = captureParamsInfosAndArgs.map(p => genVarRef(p._1._1)))(
prevArgsCount => normalParamsInfosAndArgs.take(prevArgsCount).map(p => genVarRef(p._1._1)))
case arg => arg
}
js.Assign(genVarRef(param), newArg)
}
ifOverload(tree, js.Block(
inner ++ tree.ctor.beforeCall ++ captureAssigns ++ normalAssigns))
}
def postStats(tree: ConstructorTree[SplitSecondaryJSCtor]): js.Tree = {
val inner = tree.subCtors.map(postStats(_))
ifOverload(tree, js.Block(tree.ctor.afterCall ++ inner))
}
val primaryCtor = ctorTree.ctor
val secondaryCtorTrees = ctorTree.subCtors
wrapJSCtorBody(
secondaryCtorTrees.map(preStats(_, primaryCtor.paramsAndInfo)),
primaryCtor.body,
secondaryCtorTrees.map(postStats(_))
)
}
private def wrapJSCtorBody(before: List[js.Tree], body: js.JSConstructorBody,
after: List[js.Tree]): js.JSConstructorBody = {
js.JSConstructorBody(before ::: body.beforeSuper, body.superCall,
body.afterSuper ::: after)(body.pos)
}
private sealed trait JSCtor {
val sym: Symbol
val paramsAndInfo: List[(Symbol, JSParamInfo)]
}
private class PrimaryJSCtor(val sym: Symbol,
val paramsAndInfo: List[(Symbol, JSParamInfo)],
val body: js.JSConstructorBody) extends JSCtor
private class SplitSecondaryJSCtor(val sym: Symbol,
val paramsAndInfo: List[(Symbol, JSParamInfo)],
val beforeCall: List[js.Tree],
val targetCtor: Symbol, val ctorArgs: List[js.Tree],
val afterCall: List[js.Tree]) extends JSCtor
private class ConstructorTree[Ctor <: JSCtor](
val overloadNum: Int, val ctor: Ctor,
val subCtors: List[ConstructorTree[SplitSecondaryJSCtor]]) {
val lo: Int = overloadNum
val hi: Int = subCtors.lastOption.fold(lo)(_.hi)
assert(lo <= hi, "bad overload range")
}
// Generate a method -------------------------------------------------------
/** Generates the JSNativeMemberDef. */
def genJSNativeMemberDef(tree: ValOrDefDef): js.JSNativeMemberDef = {
implicit val pos = tree.span
val sym = tree.symbol
val flags = js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic)
val methodName = encodeJSNativeMemberSym(sym)
val jsNativeLoadSpec = computeJSNativeLoadSpecOfValDef(sym)
js.JSNativeMemberDef(flags, methodName, jsNativeLoadSpec)
}
private def genMethod(dd: DefDef): Option[js.MethodDef] = {
withScopedVars(
localNames := new LocalNameGenerator
) {
genMethodWithCurrentLocalNameScope(dd)
}
}
/** Gen JS code for a method definition in a class or in an impl class.
* On the JS side, method names are mangled to encode the full signature
* of the Scala method, as described in `JSEncoding`, to support
* overloading.
*
* Some methods are not emitted at all:
* - Primitives, since they are never actually called
* - Constructors of hijacked classes
*
* Constructors are emitted by generating their body as a statement.
*
* Other (normal) methods are emitted with `genMethodBody()`.
*/
private def genMethodWithCurrentLocalNameScope(dd: DefDef): Option[js.MethodDef] = {
implicit val pos = dd.span
val sym = dd.symbol
val vparamss = dd.termParamss
val rhs = dd.rhs
/* Is this method a default accessor that should be ignored?
*
* This is the case iff one of the following applies:
* - It is a constructor default accessor and the linked class is a
* native JS class.
* - It is a default accessor for a native JS def, but with the caveat
* that its rhs must be `js.native` because of #4553.
*
* Both of those conditions can only happen if the default accessor is in
* a module class, so we use that as a fast way out. (But omitting that
* condition would not change the result.)
*
* This is different than `isJSDefaultParam` in `genApply`: we do not
* ignore default accessors of *non-native* JS types. Neither for
* constructor default accessor nor regular default accessors. We also
* do not need to worry about non-constructor members of native JS types,
* since for those, the entire member list is ignored in `genJSClassData`.
*/
def isIgnorableDefaultParam: Boolean = {
sym.name.is(DefaultGetterName) && sym.owner.is(ModuleClass) && {
val info = new DefaultParamInfo(sym)
if (info.isForConstructor) {
/* This is a default accessor for a constructor parameter. Check
* whether the attached constructor is a native JS constructor,
* which is the case iff the linked class is a native JS type.
*/
info.constructorOwner.hasAnnotation(jsdefn.JSNativeAnnot)
} else {
/* #4553 We need to ignore default accessors for JS native defs.
* However, because Scala.js <= 1.7.0 actually emitted code calling
* those accessors, we must keep default accessors that would
* compile. The only accessors we can actually get rid of are those
* that are `= js.native`.
*/
!sym.owner.isJSType &&
info.attachedMethod.hasAnnotation(jsdefn.JSNativeAnnot) && {
dd.rhs match {
case MaybeAsInstanceOf(Apply(fun, _)) =>
fun.symbol == jsdefn.JSPackage_native
case _ =>
false
}
}
}
}
}
withPerMethodBodyState(sym) {
assert(vparamss.isEmpty || vparamss.tail.isEmpty,
"Malformed parameter list: " + vparamss)
val params = if (vparamss.isEmpty) Nil else vparamss.head.map(_.symbol)
val methodName = encodeMethodSym(sym)
val originalName = originalNameOfMethod(sym)
def jsParams = params.map(genParamDef(_))
if (primitives.isPrimitive(sym)) {
None
} else if (sym.is(Deferred) && currentClassSym.isNonNativeJSClass) {
// scala-js/#4409: Do not emit abstract methods in non-native JS classes
None
} else if (sym.is(Deferred)) {
Some(js.MethodDef(js.MemberFlags.empty, methodName, originalName,
jsParams, toIRType(patchedResultType(sym)), None)(
OptimizerHints.empty, Unversioned))
} else if (isIgnorableDefaultParam) {
// #11592
None
} else if (sym.is(Bridge) && sym.name.is(DefaultGetterName) && currentClassSym.isNonNativeJSClass) {
/* #12572 Bridges for default accessors in non-native JS classes must not be emitted,
* because they call another default accessor, making their entire body an
* that cannot be eliminated.
* Such methods are never called anyway, because they are filtered out in
* JSExportsGen.defaultGetterDenot().
*/
None
} else /*if (sym.isClassConstructor && isHijackedBoxedClass(sym.owner)) {
None
} else*/ {
/*def isTraitImplForwarder = dd.rhs match {
case app: Apply => foreignIsImplClass(app.symbol.owner)
case _ => false
}*/
val shouldMarkInline = {
sym.hasAnnotation(jsdefn.InlineAnnot) ||
sym.isAnonymousFunction
}
val shouldMarkNoinline = {
sym.hasAnnotation(jsdefn.NoinlineAnnot) /*&&
!isTraitImplForwarder*/
}
val optimizerHints = {
OptimizerHints.empty
.withInline(shouldMarkInline)
.withNoinline(shouldMarkNoinline)
}
val methodDef = {
if (sym.isClassConstructor) {
val namespace = js.MemberNamespace.Constructor
js.MethodDef(js.MemberFlags.empty.withNamespace(namespace),
methodName, originalName, jsParams, jstpe.NoType, Some(genStat(rhs)))(
optimizerHints, Unversioned)
} else {
val namespace = if (isMethodStaticInIR(sym)) {
if (sym.isPrivate) js.MemberNamespace.PrivateStatic
else js.MemberNamespace.PublicStatic
} else {
if (sym.isPrivate) js.MemberNamespace.Private
else js.MemberNamespace.Public
}
val resultIRType = toIRType(patchedResultType(sym))
genMethodDef(namespace, methodName, originalName,
params, resultIRType, rhs, optimizerHints)
}
}
Some(methodDef)
}
}
}
/** Generates the MethodDef of a (non-constructor) method
*
* Most normal methods are emitted straightforwardly. If the result
* type is Unit, then the body is emitted as a statement. Otherwise, it is
* emitted as an expression.
*
* Instance methods in non-native JS classes are compiled as static methods
* taking an explicit parameter for their `this` value. Static methods in
* non-native JS classes are compiled as is, like methods in Scala classes.
*/
private def genMethodDef(namespace: js.MemberNamespace, methodName: js.MethodIdent,
originalName: OriginalName, paramsSyms: List[Symbol], resultIRType: jstpe.Type,
tree: Tree, optimizerHints: OptimizerHints): js.MethodDef = {
implicit val pos = tree.span
val jsParams = paramsSyms.map(genParamDef(_))
def genBody() = localNames.makeLabeledIfRequiresEnclosingReturn(resultIRType) {
if (resultIRType == jstpe.NoType) genStat(tree)
else genExpr(tree)
}
if (namespace.isStatic || !currentClassSym.isNonNativeJSClass) {
val flags = js.MemberFlags.empty.withNamespace(namespace)
js.MethodDef(flags, methodName, originalName, jsParams, resultIRType, Some(genBody()))(
optimizerHints, Unversioned)
} else {
val thisLocalIdent = freshLocalIdent("this")
withScopedVars(
thisLocalVarIdent := Some(thisLocalIdent)
) {
val staticNamespace =
if (namespace.isPrivate) js.MemberNamespace.PrivateStatic
else js.MemberNamespace.PublicStatic
val flags =
js.MemberFlags.empty.withNamespace(staticNamespace)
val thisParamDef = js.ParamDef(thisLocalIdent, thisOriginalName,
jstpe.AnyType, mutable = false)
js.MethodDef(flags, methodName, originalName,
thisParamDef :: jsParams, resultIRType, Some(genBody()))(
optimizerHints, Unversioned)
}
}
}
// ParamDefs ---------------------------------------------------------------
def genParamDef(sym: Symbol): js.ParamDef =
genParamDef(sym, toIRType(sym.info))
private def genParamDef(sym: Symbol, ptpe: jstpe.Type): js.ParamDef =
genParamDef(sym, ptpe, sym.span)
private def genParamDef(sym: Symbol, pos: Position): js.ParamDef =
genParamDef(sym, toIRType(sym.info), pos)
private def genParamDef(sym: Symbol, ptpe: jstpe.Type, pos: Position): js.ParamDef = {
js.ParamDef(encodeLocalSym(sym)(implicitly, pos, implicitly),
originalNameOfLocal(sym), ptpe, mutable = false)(pos)
}
// Generate statements and expressions -------------------------------------
/** Gen JS code for a tree in statement position (in the IR).
*/
private def genStat(tree: Tree): js.Tree = {
exprToStat(genStatOrExpr(tree, isStat = true))
}
/** Turn a JavaScript expression of type Unit into a statement */
private def exprToStat(tree: js.Tree): js.Tree = {
/* Any JavaScript expression is also a statement, but at least we get rid
* of some pure expressions that come from our own codegen.
*/
implicit val pos = tree.pos
tree match {
case js.Block(stats :+ expr) =>
js.Block(stats :+ exprToStat(expr))
case _:js.Literal | _:js.This | _:js.VarRef =>
js.Skip()
case _ =>
tree
}
}
/** Gen JS code for a tree in expression position (in the IR).
*/
private def genExpr(tree: Tree): js.Tree = {
val result = genStatOrExpr(tree, isStat = false)
assert(result.tpe != jstpe.NoType,
s"genExpr($tree) returned a tree with type NoType at pos ${tree.span}")
result
}
def genExpr(name: JSName)(implicit pos: SourcePosition): js.Tree = name match {
case JSName.Literal(name) => js.StringLiteral(name)
case JSName.Computed(sym) => genComputedJSName(sym)
}
private def genComputedJSName(sym: Symbol)(implicit pos: SourcePosition): js.Tree = {
/* By construction (i.e. restriction in PrepJSInterop), we know that sym
* must be a static method.
* Therefore, at this point, we can invoke it by loading its owner and
* calling it.
*/
def moduleOrGlobalScope = genLoadModuleOrGlobalScope(sym.owner)
def module = genLoadModule(sym.owner)
if (sym.owner.isJSType) {
if (!sym.owner.isNonNativeJSClass || sym.isJSExposed)
genApplyJSMethodGeneric(sym, moduleOrGlobalScope, args = Nil, isStat = false)
else
genApplyJSClassMethod(module, sym, arguments = Nil)
} else {
genApplyMethod(module, sym, arguments = Nil)
}
}
/** Gen JS code for a tree in expression position (in the IR) or the
* global scope.
*/
def genExprOrGlobalScope(tree: Tree): MaybeGlobalScope = {
implicit def pos: SourcePosition = tree.sourcePos
tree match {
case _: This =>
val sym = tree.symbol
if (sym != currentClassSym.get && sym.is(Module))
genLoadModuleOrGlobalScope(sym)
else
MaybeGlobalScope.NotGlobalScope(genExpr(tree))
case _:Ident | _:Select =>
val sym = tree.symbol
if (sym.is(Module)) {
assert(!sym.is(PackageClass), "Cannot use package as value: " + tree)
genLoadModuleOrGlobalScope(sym)
} else {
MaybeGlobalScope.NotGlobalScope(genExpr(tree))
}
case Apply(fun, _) =>
if (fun.symbol == jsdefn.JSDynamic_global)
MaybeGlobalScope.GlobalScope(pos)
else
MaybeGlobalScope.NotGlobalScope(genExpr(tree))
case _ =>
MaybeGlobalScope.NotGlobalScope(genExpr(tree))
}
}
/** Gen JS code for a tree in statement or expression position (in the IR).
*
* This is the main transformation method. Each node of the Scala AST
* is transformed into an equivalent portion of the JS AST.
*/
private def genStatOrExpr(tree: Tree, isStat: Boolean): js.Tree = {
implicit val pos: SourcePosition = tree.sourcePos
report.debuglog(" " + tree)
report.debuglog("")
tree match {
/** Local val or var declaration */
case tree @ ValDef(name, _, _) =>
val sym = tree.symbol
val rhs = tree.rhs
val rhsTree = genExpr(rhs)
rhsTree match {
case js.Transient(UndefinedParam) =>
/* This is an intermediate assignment for default params on a
* js.Any. Add the symbol to the corresponding set to inform
* the Ident resolver how to replace it and don't emit the symbol.
*/
undefinedDefaultParams += sym
js.Skip()
case _ =>
js.VarDef(encodeLocalSym(sym), originalNameOfLocal(sym),
toIRType(sym.info), sym.is(Mutable), rhsTree)
}
case If(cond, thenp, elsep) =>
val tpe =
if (isStat) jstpe.NoType
else toIRType(tree.tpe)
js.If(genExpr(cond), genStatOrExpr(thenp, isStat),
genStatOrExpr(elsep, isStat))(tpe)
case Labeled(bind, expr) =>
js.Labeled(encodeLabelSym(bind.symbol), toIRType(tree.tpe), genStatOrExpr(expr, isStat))
case Return(expr, from) =>
val fromSym = from.symbol
val label =
if (fromSym.is(Label)) encodeLabelSym(fromSym)
else localNames.get.getEnclosingReturnLabel()
js.Return(toIRType(expr.tpe) match {
case jstpe.NoType => js.Block(genStat(expr), js.Undefined())
case _ => genExpr(expr)
}, label)
case WhileDo(cond, body) =>
val genCond =
if (cond == EmptyTree) js.BooleanLiteral(true)
else genExpr(cond)
js.While(genCond, genStat(body))
case t: Try =>
genTry(t, isStat)
case app: Apply =>
genApply(app, isStat)
case app: TypeApply =>
genTypeApply(app)
/*case app: ApplyDynamic =>
genApplyDynamic(app)*/
case tree: This =>
val currentClass = currentClassSym.get
val symIsModuleClass = tree.symbol.is(ModuleClass)
assert(tree.symbol == currentClass || symIsModuleClass,
s"Trying to access the this of another class: tree.symbol = ${tree.symbol}, class symbol = $currentClass")
if (symIsModuleClass && tree.symbol != currentClass)
genLoadModule(tree.symbol)
else
genThis()
case Select(qualifier, _) =>
val sym = tree.symbol
if (sym.is(Module)) {
assert(!sym.is(Package), "Cannot use package as value: " + tree)
genLoadModule(sym)
} else if (sym.is(JavaStatic)) {
genLoadStaticField(sym)
} else if (sym.hasAnnotation(jsdefn.JSNativeAnnot)) {
genJSNativeMemberSelect(tree)
} else {
val (field, boxed) = genAssignableField(sym, qualifier)
if (boxed) unbox(field, atPhase(elimErasedValueTypePhase)(sym.info))
else field
}
case tree: Ident =>
desugarIdent(tree).fold[js.Tree] {
val sym = tree.symbol
assert(!sym.is(Package), "Cannot use package as value: " + tree)
if (sym.is(Module)) {
genLoadModule(sym)
} else if (undefinedDefaultParams.contains(sym)) {
/* This is a default parameter whose assignment was moved to
* a local variable. Put an undefined param instead.
*/
js.Transient(UndefinedParam)
} else {
genVarRef(sym)
}
} { select =>
genStatOrExpr(select, isStat)
}
case Literal(value) =>
import Constants.*
value.tag match {
case UnitTag =>
js.Skip()
case BooleanTag =>
js.BooleanLiteral(value.booleanValue)
case ByteTag =>
js.ByteLiteral(value.byteValue)
case ShortTag =>
js.ShortLiteral(value.shortValue)
case CharTag =>
js.CharLiteral(value.charValue)
case IntTag =>
js.IntLiteral(value.intValue)
case LongTag =>
js.LongLiteral(value.longValue)
case FloatTag =>
js.FloatLiteral(value.floatValue)
case DoubleTag =>
js.DoubleLiteral(value.doubleValue)
case StringTag =>
js.StringLiteral(value.stringValue)
case NullTag =>
js.Null()
case ClazzTag =>
genClassConstant(value.typeValue)
}
case Block(stats, expr) =>
// #15419 Collapse { ; BoxedUnit } to
val genStatsAndExpr0 = stats.map(genStat(_)) :+ genStatOrExpr(expr, isStat)
val genStatsAndExpr = genStatsAndExpr0 match {
case (undefParam @ js.Transient(UndefinedParam)) :: js.Undefined() :: Nil =>
undefParam :: Nil
case _ =>
genStatsAndExpr0
}
js.Block(genStatsAndExpr)
case Typed(expr, _) =>
expr match {
case _: Super => genThis()
case _ => genExpr(expr)
}
case Assign(lhs0, rhs) =>
val sym = lhs0.symbol
if (sym.is(JavaStaticTerm) && sym.source != ctx.compilationUnit.source)
throw new FatalError(s"Assignment to static member ${sym.fullName} not supported")
def genRhs = genExpr(rhs)
val lhs = lhs0 match {
case lhs: Ident => desugarIdent(lhs).getOrElse(lhs)
case lhs => lhs
}
lhs match {
case lhs: Select =>
val qualifier = lhs.qualifier
def ctorAssignment = (
currentMethodSym.get.name == nme.CONSTRUCTOR &&
currentMethodSym.get.owner == qualifier.symbol &&
qualifier.isInstanceOf[This]
)
// TODO This fails for OFFSET$x fields. Re-enable when we can.
/*if (!sym.is(Mutable) && !ctorAssignment)
throw new FatalError(s"Assigning to immutable field ${sym.fullName} at $pos")*/
if (sym.hasAnnotation(jsdefn.JSNativeAnnot)) {
/* This is an assignment to a @js.native field. Since we reject
* `@js.native var`s as compile errors, this can only happen in
* the constructor of the enclosing object.
* We simply ignore the assignment, since the field will not be
* emitted at all.
*/
js.Skip()
} else {
val (field, boxed) = genAssignableField(sym, qualifier)
if (boxed) {
val genBoxedRhs = box(genRhs, atPhase(elimErasedValueTypePhase)(sym.info))
js.Assign(field, genBoxedRhs)
} else {
js.Assign(field, genRhs)
}
}
case _ =>
js.Assign(genVarRef(sym), genRhs)
}
/** Array constructor */
case javaSeqLiteral: JavaSeqLiteral =>
genJavaSeqLiteral(javaSeqLiteral)
/** A Match reaching the backend is supposed to be optimized as a switch */
case mtch: Match =>
genMatch(mtch, isStat)
case tree: Closure =>
genClosure(tree)
case EmptyTree =>
js.Skip()
case _ =>
throw new FatalError("Unexpected tree in genExpr: " +
tree + "/" + tree.getClass + " at: " + (tree.span: Position))
}
} // end of genStatOrExpr()
private def qualifierOf(fun: Tree): Tree = fun match {
case fun: Ident =>
fun.tpe match {
case TermRef(prefix: TermRef, _) => tpd.ref(prefix)
case TermRef(prefix: ThisType, _) => tpd.This(prefix.cls)
}
case Select(qualifier, _) =>
qualifier
case TypeApply(fun, _) =>
qualifierOf(fun)
}
/** Gen JS this of the current class.
* Normally encoded straightforwardly as a JS this.
* But must be replaced by the `thisLocalVarIdent` local variable if there
* is one.
*/
private def genThis()(implicit pos: Position): js.Tree = {
/*if (tryingToGenMethodAsJSFunction) {
throw new CancelGenMethodAsJSFunction(
"Trying to generate `this` inside the body")
}*/
thisLocalVarIdent.fold[js.Tree] {
js.This()(currentThisType)
} { thisLocalIdent =>
js.VarRef(thisLocalIdent)(currentThisType)
}
}
/** Gen IR code for a `try..catch` or `try..finally` block.
*
* `try..finally` blocks are compiled straightforwardly to `try..finally`
* blocks of the IR.
*
* `try..catch` blocks are a bit more subtle, as the IR does not have
* type-based selection of exceptions to catch. We thus encode explicitly
* the type tests, like in:
*
* ```
* try { ... }
* catch (e) {
* if (e.isInstanceOf[IOException]) { ... }
* else if (e.isInstanceOf[Exception]) { ... }
* else {
* throw e; // default, re-throw
* }
* }
* ```
*
* In addition, there are provisions to handle catching JavaScript
* exceptions (which do not extend `Throwable`) as wrapped in a
* `js.JavaScriptException`.
*/
private def genTry(tree: Try, isStat: Boolean): js.Tree = {
implicit val pos: SourcePosition = tree.sourcePos
val Try(block, catches, finalizer) = tree
val blockAST = genStatOrExpr(block, isStat)
val resultType =
if (isStat) jstpe.NoType
else toIRType(tree.tpe)
val handled =
if (catches.isEmpty) blockAST
else genTryCatch(blockAST, catches, resultType, isStat)
genStat(finalizer) match {
case js.Skip() => handled
case ast => js.TryFinally(handled, ast)
}
}
private def genTryCatch(body: js.Tree, catches: List[CaseDef],
resultType: jstpe.Type,
isStat: Boolean)(implicit pos: SourcePosition): js.Tree = {
val exceptIdent = freshLocalIdent("e")
val origExceptVar = js.VarRef(exceptIdent)(jstpe.AnyType)
val mightCatchJavaScriptException = catches.exists { caseDef =>
caseDef.pat match {
case Typed(Ident(nme.WILDCARD), tpt) =>
isMaybeJavaScriptException(tpt.tpe)
case Ident(nme.WILDCARD) =>
true
case pat @ Bind(_, _) =>
isMaybeJavaScriptException(pat.symbol.info)
}
}
val (exceptValDef, exceptVar) = if (mightCatchJavaScriptException) {
val valDef = js.VarDef(freshLocalIdent("e"), NoOriginalName,
encodeClassType(defn.ThrowableClass), mutable = false, js.WrapAsThrowable(origExceptVar))
(valDef, valDef.ref)
} else {
(js.Skip(), origExceptVar)
}
val elseHandler: js.Tree = js.Throw(origExceptVar)
val handler = catches.foldRight(elseHandler) { (caseDef, elsep) =>
implicit val pos: SourcePosition = caseDef.sourcePos
val CaseDef(pat, _, body) = caseDef
// Extract exception type and variable
val (tpe, boundVar) = (pat match {
case Typed(Ident(nme.WILDCARD), tpt) =>
(tpt.tpe, None)
case Ident(nme.WILDCARD) =>
(defn.ThrowableType, None)
case Bind(_, _) =>
val ident = encodeLocalSym(pat.symbol)
val origName = originalNameOfLocal(pat.symbol)
(pat.symbol.info, Some(ident, origName))
})
// Generate the body that must be executed if the exception matches
val bodyWithBoundVar = (boundVar match {
case None =>
genStatOrExpr(body, isStat)
case Some((boundVarIdent, boundVarOriginalName)) =>
val castException = genAsInstanceOf(exceptVar, tpe)
js.Block(
js.VarDef(boundVarIdent, boundVarOriginalName, toIRType(tpe),
mutable = false, castException),
genStatOrExpr(body, isStat))
})
// Generate the test
if (tpe =:= defn.ThrowableType) {
bodyWithBoundVar
} else {
val cond = genIsInstanceOf(exceptVar, tpe)
js.If(cond, bodyWithBoundVar, elsep)(resultType)
}
}
js.TryCatch(body, exceptIdent, NoOriginalName,
js.Block(exceptValDef, handler))(resultType)
}
/** Gen JS code for an Apply node (method call)
*
* There's a whole bunch of varieties of Apply nodes: regular method
* calls, super calls, constructor calls, isInstanceOf/asInstanceOf,
* primitives, JS calls, etc. They are further dispatched in here.
*/
private def genApply(tree: Apply, isStat: Boolean): js.Tree = {
implicit val pos = tree.span
val args = tree.args
val sym = tree.fun.symbol
/* Is the method a JS default accessor, which should become an
* `UndefinedParam` rather than being compiled normally.
*
* This is true iff one of the following conditions apply:
* - It is a constructor default param for the constructor of a JS class.
* - It is a default param of an instance method of a native JS type.
* - It is a default param of an instance method of a non-native JS type
* and the attached method is exposed.
* - It is a default param for a native JS def.
*
* This is different than `isIgnorableDefaultParam` in
* `genMethodWithCurrentLocalNameScope`: we include here the default
* accessors of *non-native* JS types (unless the corresponding methods are
* not exposed). We also need to handle non-constructor members of native
* JS types.
*/
def isJSDefaultParam: Boolean = {
sym.name.is(DefaultGetterName) && {
val info = new DefaultParamInfo(sym)
if (info.isForConstructor) {
/* This is a default accessor for a constructor parameter. Check
* whether the attached constructor is a JS constructor, which is
* the case iff the linked class is a JS type.
*/
info.constructorOwner.isJSType
} else {
if (sym.owner.isJSType) {
/* The default accessor is in a JS type. It is a JS default
* param iff the enclosing class is native or the attached method
* is exposed.
*/
!sym.owner.isNonNativeJSClass || info.attachedMethod.isJSExposed
} else {
/* The default accessor is in a Scala type. It is a JS default
* param iff the attached method is a native JS def. This can
* only happen if the owner is a module class, which we test
* first as a fast way out.
*/
sym.owner.is(ModuleClass) && info.attachedMethod.hasAnnotation(jsdefn.JSNativeAnnot)
}
}
}
}
tree.fun match {
case _ if isJSDefaultParam =>
js.Transient(UndefinedParam)
case Select(Super(_, _), _) =>
genSuperCall(tree, isStat)
case Select(New(_), nme.CONSTRUCTOR) =>
genApplyNew(tree)
case _ =>
if (primitives.isPrimitive(tree)) {
genPrimitiveOp(tree, isStat)
} else if (Erasure.Boxing.isBox(sym)) {
// Box a primitive value (cannot be Unit)
val arg = args.head
makePrimitiveBox(genExpr(arg), arg.tpe)
} else if (Erasure.Boxing.isUnbox(sym)) {
// Unbox a primitive value (cannot be Unit)
val arg = args.head
makePrimitiveUnbox(genExpr(arg), tree.tpe)
} else {
genNormalApply(tree, isStat)
}
}
}
/** Gen JS code for a super call, of the form Class.super[mix].fun(args).
*
* This does not include calls defined in mixin traits, as these are
* already desugared by the 'mixin' phase. Only calls to super classes
* remain.
*
* Since a class has exactly one direct superclass, and calling a method
* two classes above the current one is invalid in Scala, the `mix` item is
* irrelevant.
*/
private def genSuperCall(tree: Apply, isStat: Boolean): js.Tree = {
implicit val pos = tree.span
val Apply(fun @ Select(sup @ Super(qual, _), _), args) = tree: @unchecked
val sym = fun.symbol
if (sym == defn.Any_getClass) {
// The only primitive that is also callable as super call
js.GetClass(genThis())
} else if (currentClassSym.isNonNativeJSClass) {
genJSSuperCall(tree, isStat)
} else {
/* #3013 `qual` can be `this.$outer()` in some cases since Scala 2.12,
* so we call `genExpr(qual)`, not just `genThis()`.
*/
val superCall = genApplyMethodStatically(
genExpr(qual), sym, genActualArgs(sym, args))
// Initialize the module instance just after the super constructor call.
if (isStaticModule(currentClassSym) && !isModuleInitialized.get.value &&
currentMethodSym.get.isClassConstructor) {
isModuleInitialized.get.value = true
js.Block(superCall, js.StoreModule())
} else {
superCall
}
}
}
/** Gen JS code for a constructor call (new).
* Further refined into:
* * new String(...)
* * new of a hijacked boxed class
* * new of an anonymous function class that was recorded as JS function
* * new of a raw JS class
* * new Array
* * regular new
*/
private def genApplyNew(tree: Apply): js.Tree = {
implicit val pos: SourcePosition = tree.sourcePos
val Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) = tree: @unchecked
val ctor = fun.symbol
val tpe = tpt.tpe
assert(ctor.isClassConstructor,
"'new' call to non-constructor: " + ctor.name)
val clsSym = tpe.typeSymbol
if (isHijackedClass(clsSym)) {
genNewHijackedClass(clsSym, ctor, args.map(genExpr))
} else /*if (translatedAnonFunctions contains tpe.typeSymbol) {
val functionMaker = translatedAnonFunctions(tpe.typeSymbol)
functionMaker(args map genExpr)
} else*/ if (clsSym.isJSType) {
genNewJSClass(tree)
} else {
toTypeRef(tpe) match {
case jstpe.ClassRef(className) =>
js.New(className, encodeMethodSym(ctor), genActualArgs(ctor, args))
case other =>
throw new FatalError(s"Non ClassRef cannot be instantiated: $other")
}
}
}
/** Gen JS code for a call to a constructor of a hijacked class.
* Reroute them to the `new` method with the same signature in the
* companion object.
*/
private def genNewHijackedClass(clazz: Symbol, ctor: Symbol,
args: List[js.Tree])(implicit pos: SourcePosition): js.Tree = {
val className = encodeClassName(clazz)
val initName = encodeMethodSym(ctor).name
val newName = MethodName(newSimpleMethodName, initName.paramTypeRefs,
jstpe.ClassRef(className))
val newMethodIdent = js.MethodIdent(newName)
js.ApplyStatic(js.ApplyFlags.empty, className, newMethodIdent, args)(
jstpe.ClassType(className))
}
/** Gen JS code for a new of a JS class (subclass of `js.Any`). */
private def genNewJSClass(tree: Apply): js.Tree = {
acquireContextualJSClassValue { jsClassValue =>
implicit val pos: Position = tree.span
val Apply(fun @ Select(New(tpt), _), args) = tree: @unchecked
val cls = tpt.tpe.typeSymbol
val ctor = fun.symbol
val nestedJSClass = cls.isNestedJSClass
assert(jsClassValue.isDefined == nestedJSClass,
s"$cls at $pos: jsClassValue.isDefined = ${jsClassValue.isDefined} " +
s"but isInnerNonNativeJSClass = $nestedJSClass")
def genArgs: List[js.TreeOrJSSpread] = genActualJSArgs(ctor, args)
def genArgsAsClassCaptures: List[js.Tree] = args.map(genExpr)
jsClassValue.fold {
// Static JS class (by construction, it cannot be a module class, as their News do not reach the back-end)
if (cls == jsdefn.JSObjectClass && args.isEmpty)
js.JSObjectConstr(Nil)
else if (cls == jsdefn.JSArrayClass && args.isEmpty)
js.JSArrayConstr(Nil)
else
js.JSNew(genLoadJSConstructor(cls), genArgs)
} { jsClassVal =>
// Nested JS class
if (cls.isAnonymousClass)
genNewAnonJSClass(cls, jsClassVal, genArgsAsClassCaptures)(fun.span)
else if (atPhase(erasurePhase)(cls.is(ModuleClass))) // LambdaLift removes the ModuleClass flag of lifted classes
js.JSNew(js.CreateJSClass(encodeClassName(cls), jsClassVal :: genArgsAsClassCaptures), Nil)
else
js.JSNew(jsClassVal, genArgs)
}
}
}
/** Generate an instance of an anonymous (non-lambda) JS class inline
*
* @param sym Class to generate the instance of
* @param jsSuperClassValue JS class value of the super class
* @param args Arguments to the Scala constructor, which map to JS class captures
* @param pos Position of the original New tree
*/
private def genNewAnonJSClass(sym: Symbol, jsSuperClassValue: js.Tree, args: List[js.Tree])(
implicit pos: Position): js.Tree = {
assert(sym.isAnonymousClass,
s"Generating AnonJSClassNew of non anonymous JS class ${sym.fullName}")
// Find the TypeDef for this anonymous class and generate it
val typeDef = consumeLazilyGeneratedAnonClass(sym)
val originalClassDef = resetAllScopedVars {
withScopedVars(
currentClassSym := sym
) {
genNonNativeJSClass(typeDef)
}
}
// Partition class members.
val privateFieldDefs = mutable.ListBuffer.empty[js.FieldDef]
val jsFieldDefs = mutable.ListBuffer.empty[js.JSFieldDef]
originalClassDef.fields.foreach {
case fdef: js.FieldDef =>
privateFieldDefs += fdef
case fdef: js.JSFieldDef =>
jsFieldDefs += fdef
}
assert(originalClassDef.jsNativeMembers.isEmpty,
"Found JS native members in anonymous JS class at " + pos)
assert(originalClassDef.topLevelExportDefs.isEmpty,
"Found top-level exports in anonymous JS class at " + pos)
// Make new class def with static members
val newClassDef = {
implicit val pos = originalClassDef.pos
val parent = js.ClassIdent(jsNames.ObjectClass)
js.ClassDef(originalClassDef.name, originalClassDef.originalName,
ClassKind.AbstractJSType, None, Some(parent), interfaces = Nil,
jsSuperClass = None, jsNativeLoadSpec = None, fields = Nil,
methods = originalClassDef.methods, jsConstructor = None,
jsMethodProps = Nil, jsNativeMembers = Nil, topLevelExportDefs = Nil)(
originalClassDef.optimizerHints)
}
generatedClasses += newClassDef
// Construct inline class definition
val jsClassCaptures = originalClassDef.jsClassCaptures.getOrElse {
throw new AssertionError(s"no class captures for anonymous JS class at $pos")
}
val js.JSConstructorDef(_, ctorParams, ctorRestParam, ctorBody) = originalClassDef.jsConstructor.getOrElse {
throw new AssertionError("No ctor found")
}
assert(ctorParams.isEmpty && ctorRestParam.isEmpty,
s"non-empty constructor params for anonymous JS class at $pos")
/* The first class capture is always a reference to the super class.
* This is enforced by genJSClassCapturesAndConstructor.
*/
def jsSuperClassRef(implicit pos: ir.Position): js.VarRef =
jsClassCaptures.head.ref
/* The `this` reference.
* FIXME This could clash with a local variable of the constructor or a JS
* class capture. It seems Scala 2 has the same vulnerability. How do we
* avoid this?
*/
val selfName = freshLocalIdent("this")(pos)
def selfRef(implicit pos: ir.Position) =
js.VarRef(selfName)(jstpe.AnyType)
def memberLambda(params: List[js.ParamDef], restParam: Option[js.ParamDef], body: js.Tree)(implicit pos: ir.Position): js.Closure =
js.Closure(arrow = false, captureParams = Nil, params, restParam, body, captureValues = Nil)
val fieldDefinitions = jsFieldDefs.toList.map { fdef =>
implicit val pos = fdef.pos
js.Assign(js.JSSelect(selfRef, fdef.name), jstpe.zeroOf(fdef.ftpe))
}
val memberDefinitions0 = originalClassDef.jsMethodProps.toList.map {
case mdef: js.JSMethodDef =>
implicit val pos = mdef.pos
val impl = memberLambda(mdef.args, mdef.restParam, mdef.body)
js.Assign(js.JSSelect(selfRef, mdef.name), impl)
case pdef: js.JSPropertyDef =>
implicit val pos = pdef.pos
val optGetter = pdef.getterBody.map { body =>
js.StringLiteral("get") -> memberLambda(params = Nil, restParam = None, body)
}
val optSetter = pdef.setterArgAndBody.map { case (arg, body) =>
js.StringLiteral("set") -> memberLambda(params = arg :: Nil, restParam = None, body)
}
val descriptor = js.JSObjectConstr(
optGetter.toList :::
optSetter.toList :::
List(js.StringLiteral("configurable") -> js.BooleanLiteral(true))
)
js.JSMethodApply(js.JSGlobalRef("Object"),
js.StringLiteral("defineProperty"),
List(selfRef, pdef.name, descriptor))
}
val memberDefinitions1 = fieldDefinitions ::: memberDefinitions0
val memberDefinitions = if (privateFieldDefs.isEmpty) {
memberDefinitions1
} else {
/* Private fields, declared in FieldDefs, are stored in a separate
* object, itself stored as a non-enumerable field of the `selfRef`.
* The name of that field is retrieved at
* `scala.scalajs.runtime.privateFieldsSymbol()`, and is a Symbol if
* supported, or a randomly generated string that has the same enthropy
* as a UUID (i.e., 128 random bits).
*
* This encoding solves two issues:
*
* - Hide private fields in anonymous JS classes from `JSON.stringify`
* and other cursory inspections in JS (#2748).
* - Get around the fact that abstract JS types cannot declare
* FieldDefs (#3777).
*/
val fieldsObjValue = {
js.JSObjectConstr(privateFieldDefs.toList.map { fdef =>
implicit val pos = fdef.pos
js.StringLiteral(fdef.name.name.nameString) -> jstpe.zeroOf(fdef.ftpe)
})
}
val definePrivateFieldsObj = {
/* Object.defineProperty(selfRef, privateFieldsSymbol, {
* value: fieldsObjValue
* });
*
* `writable`, `configurable` and `enumerable` are false by default.
*/
js.JSMethodApply(
js.JSGlobalRef("Object"),
js.StringLiteral("defineProperty"),
List(
selfRef,
genPrivateFieldsSymbol()(using sym.sourcePos),
js.JSObjectConstr(List(
js.StringLiteral("value") -> fieldsObjValue
))
)
)
}
definePrivateFieldsObj :: memberDefinitions1
}
// Transform the constructor body.
val inlinedCtorStats: List[js.Tree] = {
val beforeSuper = ctorBody.beforeSuper
val superCall = {
implicit val pos = ctorBody.superCall.pos
val js.JSSuperConstructorCall(args) = ctorBody.superCall
val newTree = {
val ident = originalClassDef.superClass.getOrElse(throw new FatalError("No superclass"))
if (args.isEmpty && ident.name == JSObjectClassName)
js.JSObjectConstr(Nil)
else
js.JSNew(jsSuperClassRef, args)
}
val selfVarDef = js.VarDef(selfName, thisOriginalName, jstpe.AnyType, mutable = false, newTree)
selfVarDef :: memberDefinitions
}
// After the super call, substitute `selfRef` for `This()`
val afterSuper = new ir.Transformers.Transformer {
override def transform(tree: js.Tree, isStat: Boolean): js.Tree = tree match {
case js.This() =>
selfRef(tree.pos)
// Don't traverse closure boundaries
case closure: js.Closure =>
val newCaptureValues = closure.captureValues.map(transformExpr)
closure.copy(captureValues = newCaptureValues)(closure.pos)
case tree =>
super.transform(tree, isStat)
}
}.transformStats(ctorBody.afterSuper)
beforeSuper ::: superCall ::: afterSuper
}
val closure = js.Closure(arrow = true, jsClassCaptures, Nil, None,
js.Block(inlinedCtorStats, selfRef), jsSuperClassValue :: args)
js.JSFunctionApply(closure, Nil)
}
/** Gen JS code for a primitive method call. */
private def genPrimitiveOp(tree: Apply, isStat: Boolean): js.Tree = {
import dotty.tools.backend.ScalaPrimitivesOps.*
implicit val pos = tree.span
val Apply(fun, args) = tree
val receiver = qualifierOf(fun)
val code = primitives.getPrimitive(tree, receiver.tpe)
if (isArithmeticOp(code) || isLogicalOp(code) || isComparisonOp(code))
genSimpleOp(tree, receiver :: args, code)
else if (code == CONCAT)
genStringConcat(tree, receiver, args)
else if (code == HASH)
genScalaHash(tree, receiver)
else if (isArrayOp(code))
genArrayOp(tree, code)
else if (code == SYNCHRONIZED)
genSynchronized(tree, isStat)
else if (isCoercion(code))
genCoercion(tree, receiver, code)
else if (code == JSPrimitives.THROW)
genThrow(tree, args)
else if (JSPrimitives.isJSPrimitive(code))
genJSPrimitive(tree, args, code, isStat)
else
throw new FatalError(s"Unknown primitive: ${tree.symbol.fullName} at: $pos")
}
/** Gen JS code for a simple operation (arithmetic, logical, or comparison) */
private def genSimpleOp(tree: Apply, args: List[Tree], code: Int): js.Tree = {
args match {
case List(arg) => genSimpleUnaryOp(tree, arg, code)
case List(lhs, rhs) => genSimpleBinaryOp(tree, lhs, rhs, code)
case _ => throw new FatalError("Incorrect arity for primitive")
}
}
/** Gen JS code for a simple unary operation. */
private def genSimpleUnaryOp(tree: Apply, arg: Tree, code: Int): js.Tree = {
import dotty.tools.backend.ScalaPrimitivesOps.*
implicit val pos = tree.span
val resultIRType = toIRType(tree.tpe)
val genArg = adaptPrimitive(genExpr(arg), resultIRType)
(code: @switch) match {
case POS =>
genArg
case NEG =>
(resultIRType: @unchecked) match {
case jstpe.IntType =>
js.BinaryOp(js.BinaryOp.Int_-, js.IntLiteral(0), genArg)
case jstpe.LongType =>
js.BinaryOp(js.BinaryOp.Long_-, js.LongLiteral(0), genArg)
case jstpe.FloatType =>
js.BinaryOp(js.BinaryOp.Float_*, js.FloatLiteral(-1.0f), genArg)
case jstpe.DoubleType =>
js.BinaryOp(js.BinaryOp.Double_*, js.DoubleLiteral(-1.0), genArg)
}
case NOT =>
(resultIRType: @unchecked) match {
case jstpe.IntType =>
js.BinaryOp(js.BinaryOp.Int_^, js.IntLiteral(-1), genArg)
case jstpe.LongType =>
js.BinaryOp(js.BinaryOp.Long_^, js.LongLiteral(-1), genArg)
}
case ZNOT =>
js.UnaryOp(js.UnaryOp.Boolean_!, genArg)
case _ =>
throw new FatalError("Unknown unary operation code: " + code)
}
}
/** Gen JS code for a simple binary operation. */
private def genSimpleBinaryOp(tree: Apply, lhs: Tree, rhs: Tree, code: Int): js.Tree = {
import dotty.tools.backend.ScalaPrimitivesOps.*
implicit val pos: SourcePosition = tree.sourcePos
val lhsIRType = toIRType(lhs.tpe)
val rhsIRType = toIRType(rhs.tpe)
val isShift = isShiftOp(code)
val opType = {
if (isShift) {
if (lhsIRType == jstpe.LongType) jstpe.LongType
else jstpe.IntType
} else {
(lhsIRType, rhsIRType) match {
case (jstpe.DoubleType, _) | (_, jstpe.DoubleType) => jstpe.DoubleType
case (jstpe.FloatType, _) | (_, jstpe.FloatType) => jstpe.FloatType
case (jstpe.LongType, _) | (_, jstpe.LongType) => jstpe.LongType
case (jstpe.IntType | jstpe.ByteType | jstpe.ShortType | jstpe.CharType, _) => jstpe.IntType
case (_, jstpe.IntType | jstpe.ByteType | jstpe.ShortType | jstpe.CharType) => jstpe.IntType
case (jstpe.BooleanType, _) | (_, jstpe.BooleanType) => jstpe.BooleanType
case _ => jstpe.AnyType
}
}
}
val lsrc =
if (opType == jstpe.AnyType) genExpr(lhs)
else adaptPrimitive(genExpr(lhs), opType)
val rsrc =
if (opType == jstpe.AnyType) genExpr(rhs)
else adaptPrimitive(genExpr(rhs), if (isShift) jstpe.IntType else opType)
if (opType == jstpe.AnyType && isUniversalEqualityOp(code)) {
genUniversalEqualityOp(lhs.tpe, rhs.tpe, lsrc, rsrc, code)
} else if (code == ZOR) {
js.If(lsrc, js.BooleanLiteral(true), rsrc)(jstpe.BooleanType)
} else if (code == ZAND) {
js.If(lsrc, rsrc, js.BooleanLiteral(false))(jstpe.BooleanType)
} else {
import js.BinaryOp.*
(opType: @unchecked) match {
case jstpe.IntType =>
val op = (code: @switch) match {
case ADD => Int_+
case SUB => Int_-
case MUL => Int_*
case DIV => Int_/
case MOD => Int_%
case OR => Int_|
case AND => Int_&
case XOR => Int_^
case LSL => Int_<<
case LSR => Int_>>>
case ASR => Int_>>
case EQ => Int_==
case NE => Int_!=
case LT => Int_<
case LE => Int_<=
case GT => Int_>
case GE => Int_>=
}
js.BinaryOp(op, lsrc, rsrc)
case jstpe.FloatType =>
def withFloats(op: Int): js.Tree =
js.BinaryOp(op, lsrc, rsrc)
def toDouble(value: js.Tree): js.Tree =
js.UnaryOp(js.UnaryOp.FloatToDouble, value)
def withDoubles(op: Int): js.Tree =
js.BinaryOp(op, toDouble(lsrc), toDouble(rsrc))
(code: @switch) match {
case ADD => withFloats(Float_+)
case SUB => withFloats(Float_-)
case MUL => withFloats(Float_*)
case DIV => withFloats(Float_/)
case MOD => withFloats(Float_%)
case EQ => withDoubles(Double_==)
case NE => withDoubles(Double_!=)
case LT => withDoubles(Double_<)
case LE => withDoubles(Double_<=)
case GT => withDoubles(Double_>)
case GE => withDoubles(Double_>=)
}
case jstpe.DoubleType =>
val op = (code: @switch) match {
case ADD => Double_+
case SUB => Double_-
case MUL => Double_*
case DIV => Double_/
case MOD => Double_%
case EQ => Double_==
case NE => Double_!=
case LT => Double_<
case LE => Double_<=
case GT => Double_>
case GE => Double_>=
}
js.BinaryOp(op, lsrc, rsrc)
case jstpe.LongType =>
val op = (code: @switch) match {
case ADD => Long_+
case SUB => Long_-
case MUL => Long_*
case DIV => Long_/
case MOD => Long_%
case OR => Long_|
case XOR => Long_^
case AND => Long_&
case LSL => Long_<<
case LSR => Long_>>>
case ASR => Long_>>
case EQ => Long_==
case NE => Long_!=
case LT => Long_<
case LE => Long_<=
case GT => Long_>
case GE => Long_>=
}
js.BinaryOp(op, lsrc, rsrc)
case jstpe.BooleanType =>
val op = (code: @switch) match {
case EQ => Boolean_==
case NE => Boolean_!=
case OR => Boolean_|
case AND => Boolean_&
case XOR => Boolean_!=
}
js.BinaryOp(op, lsrc, rsrc)
case jstpe.AnyType =>
val op = code match {
case ID => ===
case NI => !==
}
js.BinaryOp(op, lsrc, rsrc)
}
}
}
private def adaptPrimitive(value: js.Tree, to: jstpe.Type)(
implicit pos: Position): js.Tree = {
genConversion(value.tpe, to, value)
}
/* This method corresponds to the method of the same name in
* BCodeBodyBuilder of the JVM back-end. It ends up calling the method
* BCodeIdiomatic.emitT2T, whose logic we replicate here.
*/
private def genConversion(from: jstpe.Type, to: jstpe.Type, value: js.Tree)(
implicit pos: Position): js.Tree = {
import js.UnaryOp.*
if (from == to || from == jstpe.NothingType) {
value
} else if (from == jstpe.BooleanType || to == jstpe.BooleanType) {
throw new AssertionError(s"Invalid genConversion from $from to $to")
} else {
def intValue = (from: @unchecked) match {
case jstpe.IntType => value
case jstpe.CharType => js.UnaryOp(CharToInt, value)
case jstpe.ByteType => js.UnaryOp(ByteToInt, value)
case jstpe.ShortType => js.UnaryOp(ShortToInt, value)
case jstpe.LongType => js.UnaryOp(LongToInt, value)
case jstpe.FloatType => js.UnaryOp(DoubleToInt, js.UnaryOp(FloatToDouble, value))
case jstpe.DoubleType => js.UnaryOp(DoubleToInt, value)
}
def doubleValue = from match {
case jstpe.DoubleType => value
case jstpe.FloatType => js.UnaryOp(FloatToDouble, value)
case jstpe.LongType => js.UnaryOp(LongToDouble, value)
case _ => js.UnaryOp(IntToDouble, intValue)
}
(to: @unchecked) match {
case jstpe.CharType =>
js.UnaryOp(IntToChar, intValue)
case jstpe.ByteType =>
js.UnaryOp(IntToByte, intValue)
case jstpe.ShortType =>
js.UnaryOp(IntToShort, intValue)
case jstpe.IntType =>
intValue
case jstpe.LongType =>
from match {
case jstpe.FloatType | jstpe.DoubleType =>
js.UnaryOp(DoubleToLong, doubleValue)
case _ =>
js.UnaryOp(IntToLong, intValue)
}
case jstpe.FloatType =>
if (from == jstpe.LongType)
js.UnaryOp(js.UnaryOp.LongToFloat, value)
else
js.UnaryOp(js.UnaryOp.DoubleToFloat, doubleValue)
case jstpe.DoubleType =>
doubleValue
}
}
}
/** Gen JS code for a universal equality test. */
private def genUniversalEqualityOp(ltpe: Type, rtpe: Type, lhs: js.Tree, rhs: js.Tree, code: Int)(
implicit pos: SourcePosition): js.Tree = {
import dotty.tools.backend.ScalaPrimitivesOps.*
val bypassEqEq = {
// Do not call equals if we have a literal null at either side.
lhs.isInstanceOf[js.Null] ||
rhs.isInstanceOf[js.Null]
}
if (bypassEqEq) {
js.BinaryOp(
if (code == EQ) js.BinaryOp.=== else js.BinaryOp.!==,
lhs, rhs)
} else {
val body = genEqEqPrimitive(ltpe, rtpe, lhs, rhs)
if (code == EQ) body
else js.UnaryOp(js.UnaryOp.Boolean_!, body)
}
}
private lazy val externalEqualsNumNum: Symbol =
defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumNum)
private lazy val externalEqualsNumChar: Symbol =
defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumChar)
private lazy val externalEqualsNumObject: Symbol =
defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumObject)
private lazy val externalEquals: Symbol =
defn.BoxesRunTimeModule.info.decl(nme.equals_).suchThat(toDenot(_).info.firstParamTypes.size == 2).symbol
/** Gen JS code for a call to Any.== */
private def genEqEqPrimitive(ltpe: Type, rtpe: Type, lsrc: js.Tree, rsrc: js.Tree)(
implicit pos: SourcePosition): js.Tree = {
report.debuglog(s"$ltpe == $rtpe")
val lsym = ltpe.typeSymbol.asClass
val rsym = rtpe.typeSymbol.asClass
/* True if the equality comparison is between values that require the
* use of the rich equality comparator
* (scala.runtime.BoxesRunTime.equals).
* This is the case when either side of the comparison might have a
* run-time type subtype of java.lang.Number or java.lang.Character,
* **which includes when either is a JS type**.
* When it is statically known that both sides are equal and subtypes of
* Number or Character, not using the rich equality is possible (their
* own equals method will do ok), except for java.lang.Float and
* java.lang.Double: their `equals` have different behavior around `NaN`
* and `-0.0`, see Javadoc (scala-dev#329, scala-js#2799).
*/
val mustUseAnyComparator: Boolean = {
lsym.isJSType || rsym.isJSType || {
val p = ctx.platform
p.isMaybeBoxed(lsym) && p.isMaybeBoxed(rsym) && {
val areSameFinals = lsym.is(Final) && rsym.is(Final) && (ltpe =:= rtpe)
!areSameFinals || lsym == defn.BoxedFloatClass || lsym == defn.BoxedDoubleClass
}
}
}
if (mustUseAnyComparator) {
val equalsMethod: Symbol = {
val ptfm = ctx.platform
if (lsym.derivesFrom(defn.BoxedNumberClass)) {
if (rsym.derivesFrom(defn.BoxedNumberClass)) externalEqualsNumNum
else if (rsym.derivesFrom(defn.BoxedCharClass)) externalEqualsNumChar
else externalEqualsNumObject
} else externalEquals
}
genApplyStatic(equalsMethod, List(lsrc, rsrc))
} else {
// if (lsrc eq null) rsrc eq null else lsrc.equals(rsrc)
if (lsym == defn.StringClass) {
// String.equals(that) === (this eq that)
js.BinaryOp(js.BinaryOp.===, lsrc, rsrc)
} else {
/* This requires to evaluate both operands in local values first.
* The optimizer will eliminate them if possible.
*/
val ltemp = js.VarDef(freshLocalIdent(), NoOriginalName, lsrc.tpe, mutable = false, lsrc)
val rtemp = js.VarDef(freshLocalIdent(), NoOriginalName, rsrc.tpe, mutable = false, rsrc)
js.Block(
ltemp,
rtemp,
js.If(js.BinaryOp(js.BinaryOp.===, ltemp.ref, js.Null()),
js.BinaryOp(js.BinaryOp.===, rtemp.ref, js.Null()),
genApplyMethod(ltemp.ref, defn.Any_equals, List(rtemp.ref)))(
jstpe.BooleanType))
}
}
}
/** Gen JS code for string concatenation.
*/
private def genStringConcat(tree: Apply, receiver: Tree,
args: List[Tree]): js.Tree = {
implicit val pos = tree.span
js.BinaryOp(js.BinaryOp.String_+, genExpr(receiver), genExpr(args.head))
}
/** Gen JS code for a call to Any.## */
private def genScalaHash(tree: Apply, receiver: Tree): js.Tree = {
implicit val pos: SourcePosition = tree.sourcePos
genModuleApplyMethod(defn.ScalaRuntimeModule.requiredMethod(nme.hash_),
List(genExpr(receiver)))
}
/** Gen JS code for an array operation (get, set or length) */
private def genArrayOp(tree: Tree, code: Int): js.Tree = {
import dotty.tools.backend.ScalaPrimitivesOps.*
implicit val pos = tree.span
val Apply(fun, args) = tree: @unchecked
val arrayObj = qualifierOf(fun)
val genArray = genExpr(arrayObj)
val genArgs = args.map(genExpr)
def elementType: Type = arrayObj.tpe.widenDealias match {
case defn.ArrayOf(el) => el
case JavaArrayType(el) => el
case tpe =>
val msg = em"expected Array $tpe"
report.error(msg)
ErrorType(msg)
}
def genSelect(): js.AssignLhs =
js.ArraySelect(genArray, genArgs(0))(toIRType(elementType))
if (isArrayGet(code)) {
// get an item of the array
assert(args.length == 1,
s"Array get requires 1 argument, found ${args.length} in $tree")
genSelect()
} else if (isArraySet(code)) {
// set an item of the array
assert(args.length == 2,
s"Array set requires 2 arguments, found ${args.length} in $tree")
js.Assign(genSelect(), genArgs(1))
} else {
// length of the array
js.ArrayLength(genArray)
}
}
/** Gen JS code for a call to AnyRef.synchronized */
private def genSynchronized(tree: Apply, isStat: Boolean): js.Tree = {
/* JavaScript is single-threaded, so we can drop the
* synchronization altogether.
*/
val Apply(fun, List(arg)) = tree
val receiver = qualifierOf(fun)
val genReceiver = genExpr(receiver)
val genArg = genStatOrExpr(arg, isStat)
genReceiver match {
case js.This() =>
// common case for which there is no side-effect nor NPE
genArg
case _ =>
implicit val pos = tree.span
js.Block(
js.If(js.BinaryOp(js.BinaryOp.===, genReceiver, js.Null()),
js.Throw(js.New(NullPointerExceptionClass, js.MethodIdent(jsNames.NoArgConstructorName), Nil)),
js.Skip())(jstpe.NoType),
genArg)
}
}
/** Gen JS code for a coercion */
private def genCoercion(tree: Apply, receiver: Tree, code: Int): js.Tree = {
implicit val pos = tree.span
val source = genExpr(receiver)
val resultType = toIRType(tree.tpe)
adaptPrimitive(source, resultType)
}
/** Gen a call to the special `throw` method. */
private def genThrow(tree: Apply, args: List[Tree]): js.Tree = {
implicit val pos: SourcePosition = tree.sourcePos
val exception = args.head
val genException = genExpr(exception)
genException match {
case js.New(cls, _, _) if cls != JavaScriptExceptionClassName =>
// Common case where ex is neither null nor a js.JavaScriptException
js.Throw(genException)
case _ =>
js.Throw(js.UnwrapFromThrowable(genException))
}
}
/** Gen a "normal" apply (to a true method).
*
* But even these are further refined into:
* * Methods of java.lang.String, which are redirected to the
* RuntimeString trait implementation.
* * Calls to methods of raw JS types (Scala.js -> JS interop)
* * Calls to methods in impl classes of Scala2 traits.
* * Regular method call
*/
private def genNormalApply(tree: Apply, isStat: Boolean): js.Tree = {
implicit val pos = tree.span
val fun = tree.fun match {
case fun: Ident => desugarIdent(fun).get
case fun: Select => fun
}
val receiver = fun.qualifier
val args = tree.args
val sym = fun.symbol
def isStringMethodFromObject: Boolean = sym.name match {
case nme.toString_ | nme.equals_ | nme.hashCode_ => true
case _ => false
}
if (isMethodStaticInIR(sym)) {
genApplyStatic(sym, genActualArgs(sym, args))
} else if (sym.owner.isJSType) {
if (!sym.owner.isNonNativeJSClass || sym.isJSExposed)
genApplyJSMethodGeneric(sym, genExprOrGlobalScope(receiver), genActualJSArgs(sym, args), isStat)(tree.sourcePos)
else
genApplyJSClassMethod(genExpr(receiver), sym, genActualArgs(sym, args))
} else if (sym.hasAnnotation(jsdefn.JSNativeAnnot)) {
genJSNativeMemberCall(tree)
} else {
genApplyMethodMaybeStatically(genExpr(receiver), sym, genActualArgs(sym, args))
}
}
/** Gen JS code for a call to a JS method (of a subclass of `js.Any`).
*
* Basically it boils down to calling the method as a `JSBracketSelect`,
* without name mangling. But other aspects come into play:
*
* - Operator methods are translated to JS operators (not method calls)
* - `apply` is translated as a function call, i.e., `o()` instead of `o.apply()`
* - Scala varargs are turned into JS varargs (see `genPrimitiveJSArgs()`)
* - Getters and parameterless methods are translated as `JSBracketSelect`
* - Setters are translated to `Assign` to `JSBracketSelect`
*/
private def genApplyJSMethodGeneric(sym: Symbol,
receiver: MaybeGlobalScope, args: List[js.TreeOrJSSpread], isStat: Boolean,
jsSuperClassValue: Option[js.Tree] = None)(
implicit pos: SourcePosition): js.Tree = {
def argsNoSpread: List[js.Tree] = {
assert(!args.exists(_.isInstanceOf[js.JSSpread]), s"Unexpected spread at $pos")
args.asInstanceOf[List[js.Tree]]
}
val argc = args.size // meaningful only for methods that don't have varargs
def requireNotSuper(): Unit = {
if (jsSuperClassValue.isDefined)
report.error("Illegal super call in Scala.js-defined JS class", pos)
}
def requireNotSpread(arg: js.TreeOrJSSpread): js.Tree =
arg.asInstanceOf[js.Tree]
def genSuperReference(propName: js.Tree): js.AssignLhs = {
jsSuperClassValue.fold[js.AssignLhs] {
genJSSelectOrGlobalRef(receiver, propName)
} { superClassValue =>
js.JSSuperSelect(superClassValue, ruleOutGlobalScope(receiver), propName)
}
}
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 genCall(methodName: js.Tree, args: List[js.TreeOrJSSpread]): js.Tree = {
jsSuperClassValue.fold[js.Tree] {
genJSMethodApplyOrGlobalRefApply(receiver, methodName, args)
} { superClassValue =>
js.JSSuperMethodCall(superClassValue, ruleOutGlobalScope(receiver), methodName, args)
}
}
val boxedResult = sym.jsCallingConvention match {
case JSCallingConvention.UnaryOp(code) =>
requireNotSuper()
assert(argc == 0, s"bad argument count ($argc) for unary op at $pos")
js.JSUnaryOp(code, ruleOutGlobalScope(receiver))
case JSCallingConvention.BinaryOp(code) =>
requireNotSuper()
assert(argc == 1, s"bad argument count ($argc) for binary op at $pos")
js.JSBinaryOp(code, ruleOutGlobalScope(receiver), requireNotSpread(args.head))
case JSCallingConvention.Call =>
requireNotSuper()
if (sym.owner.isSubClass(jsdefn.JSThisFunctionClass))
js.JSMethodApply(ruleOutGlobalScope(receiver), js.StringLiteral("call"), args)
else
js.JSFunctionApply(ruleOutGlobalScope(receiver), args)
case JSCallingConvention.Property(jsName) =>
argsNoSpread match {
case Nil =>
genSelectGet(genExpr(jsName))
case value :: Nil =>
genSelectSet(genExpr(jsName), value)
case _ =>
throw new AssertionError(s"property methods should have 0 or 1 non-varargs arguments at $pos")
}
case JSCallingConvention.BracketAccess =>
argsNoSpread match {
case keyArg :: Nil =>
genSelectGet(keyArg)
case keyArg :: valueArg :: Nil =>
genSelectSet(keyArg, valueArg)
case _ =>
throw new AssertionError(s"@JSBracketAccess methods should have 1 or 2 non-varargs arguments at $pos")
}
case JSCallingConvention.BracketCall =>
val (methodName, actualArgs) = extractFirstArg(args)
genCall(methodName, actualArgs)
case JSCallingConvention.Method(jsName) =>
genCall(genExpr(jsName), args)
}
if (isStat) {
boxedResult
} else {
val tpe = atPhase(elimErasedValueTypePhase) {
sym.info.finalResultType
}
if (tpe.isRef(defn.BoxedUnitClass) && sym.isGetter) {
/* Work around to reclaim Scala 2 erasure behavior, assumed by the test
* NonNativeJSTypeTest.defaultValuesForFields.
* Scala 2 erases getters of `Unit`-typed fields as returning `Unit`
* (not `BoxedUnit`). Therefore, when called in expression position,
* the call site introduces an explicit `BoxedUnit.UNIT`. Even if the
* field has not been initialized at all (with `= _`), this results in
* an actual `()` value.
* In Scala 3, the same pattern returns `null`, as a `BoxedUnit`, so we
* introduce here an explicit `()` value.
* TODO We should remove this branch if the upstream test is updated
* not to assume such a strict interpretation of erasure.
*/
js.Block(boxedResult, js.Undefined())
} else {
unbox(boxedResult, tpe)
}
}
}
/** Extract the first argument in a list of actual arguments.
*
* This is nothing else than decomposing into head and tail, except that
* we assert that the first element is not a JSSpread.
*/
private def extractFirstArg(args: List[js.TreeOrJSSpread]): (js.Tree, List[js.TreeOrJSSpread]) = {
assert(args.nonEmpty,
"Trying to extract the first argument of an empty argument list")
val firstArg = args.head
assert(!firstArg.isInstanceOf[js.JSSpread],
"Trying to extract the first argument of an argument list starting " +
"with a Spread argument: " + firstArg)
(firstArg.asInstanceOf[js.Tree], args.tail)
}
/** Gen JS code for a call to a native JS def or val. */
private def genJSNativeMemberSelect(tree: Tree): js.Tree =
genJSNativeMemberSelectOrCall(tree, Nil)
/** Gen JS code for a call to a native JS def or val. */
private def genJSNativeMemberCall(tree: Apply): js.Tree =
genJSNativeMemberSelectOrCall(tree, tree.args)
/** Gen JS code for a call to a native JS def or val. */
private def genJSNativeMemberSelectOrCall(tree: Tree, args: List[Tree]): js.Tree = {
val sym = tree.symbol
implicit val pos = tree.span
val jsNativeMemberValue =
js.SelectJSNativeMember(encodeClassName(sym.owner), encodeJSNativeMemberSym(sym))
val boxedResult =
if (sym.isJSGetter) jsNativeMemberValue
else js.JSFunctionApply(jsNativeMemberValue, genActualJSArgs(sym, args))
unbox(boxedResult, atPhase(elimErasedValueTypePhase) {
sym.info.resultType
})
}
private def genJSSuperCall(tree: Apply, isStat: Boolean): js.Tree = {
acquireContextualJSClassValue { explicitJSSuperClassValue =>
implicit val pos = tree.span
val Apply(fun @ Select(sup @ Super(qual, _), _), args) = tree: @unchecked
val sym = fun.symbol
val genReceiver = genExpr(qual)
def genScalaArgs = genActualArgs(sym, args)
def genJSArgs = genActualJSArgs(sym, args)
if (sym.owner == defn.ObjectClass) {
// Normal call anyway
assert(!sym.isClassConstructor,
s"Trying to call the super constructor of Object in a non-native JS class at $pos")
genApplyMethod(genReceiver, sym, genScalaArgs)
} else if (sym.isClassConstructor) {
throw new AssertionError(
s"calling a JS super constructor should have happened in genPrimaryJSClassCtor at $pos")
} else if (sym.owner.isNonNativeJSClass && !sym.isJSExposed) {
// Reroute to the static method
genApplyJSClassMethod(genReceiver, sym, genScalaArgs)
} else {
val jsSuperClassValue = explicitJSSuperClassValue.orElse {
Some(genLoadJSConstructor(currentClassSym.get.asClass.superClass))
}
genApplyJSMethodGeneric(sym, MaybeGlobalScope.NotGlobalScope(genReceiver),
genJSArgs, isStat, jsSuperClassValue)(tree.sourcePos)
}
}
}
/** Gen JS code for a call to a polymorphic method.
*
* The only methods that reach the back-end as polymorphic are
* `isInstanceOf` and `asInstanceOf`.
*
* (Well, in fact `DottyRunTime.newRefArray` too, but it is handled as a
* primitive instead.)
*/
private def genTypeApply(tree: TypeApply): js.Tree = {
implicit val pos: SourcePosition = tree.sourcePos
val TypeApply(fun, targs) = tree
val sym = fun.symbol
val receiver = qualifierOf(fun)
val to = targs.head.tpe
assert(!isPrimitiveValueType(receiver.tpe),
s"Found receiver of type test with primitive type ${receiver.tpe} at $pos")
assert(!isPrimitiveValueType(to),
s"Found target type of type test with primitive type ${receiver.tpe} at $pos")
val genReceiver = genExpr(receiver)
if (sym == defn.Any_asInstanceOf) {
genAsInstanceOf(genReceiver, to)
} else if (sym == defn.Any_isInstanceOf) {
genIsInstanceOf(genReceiver, to)
} else {
throw new FatalError(
s"Unexpected type application $fun with symbol ${sym.fullName}")
}
}
/** Gen JS code for a Java Seq literal. */
private def genJavaSeqLiteral(tree: JavaSeqLiteral): js.Tree = {
implicit val pos = tree.span
val genElems = tree.elems.map(genExpr)
val arrayTypeRef = toTypeRef(tree.tpe).asInstanceOf[jstpe.ArrayTypeRef]
js.ArrayValue(arrayTypeRef, genElems)
}
/** Gen JS code for a switch-`Match`, which is translated into an IR `js.Match`. */
def genMatch(tree: Tree, isStat: Boolean): js.Tree = {
implicit val pos = tree.span
val Match(selector, cases) = tree: @unchecked
def abortMatch(msg: String): Nothing =
throw new FatalError(s"$msg in switch-like pattern match at ${tree.span}: $tree")
val genSelector = genExpr(selector)
// Sanity check: we can handle Ints and Strings (including `null`s), but nothing else
genSelector.tpe match {
case jstpe.IntType | jstpe.ClassType(jsNames.BoxedStringClass) | jstpe.NullType | jstpe.NothingType =>
// ok
case _ =>
abortMatch(s"Invalid selector type ${genSelector.tpe}")
}
val resultType = toIRType(tree.tpe) match {
case jstpe.NothingType => jstpe.NothingType // must take priority over NoType below
case _ if isStat => jstpe.NoType
case resType => resType
}
var clauses: List[(List[js.MatchableLiteral], js.Tree)] = Nil
var optDefaultClause: Option[js.Tree] = None
for (caze @ CaseDef(pat, guard, body) <- cases) {
if (guard != EmptyTree)
abortMatch("Found a case guard")
val genBody = genStatOrExpr(body, isStat)
def invalidCase(): Nothing =
abortMatch("Invalid case")
def genMatchableLiteral(tree: Literal): js.MatchableLiteral = {
genExpr(tree) match {
case matchableLiteral: js.MatchableLiteral => matchableLiteral
case otherExpr => invalidCase()
}
}
pat match {
case lit: Literal =>
clauses = (List(genMatchableLiteral(lit)), genBody) :: clauses
case Ident(nme.WILDCARD) =>
optDefaultClause = Some(genBody)
case Alternative(alts) =>
val genAlts = alts.map {
case lit: Literal => genMatchableLiteral(lit)
case _ => invalidCase()
}
clauses = (genAlts, genBody) :: clauses
case _ =>
invalidCase()
}
}
clauses = clauses.reverse
val defaultClause = optDefaultClause.getOrElse {
throw new AssertionError("No elseClause in pattern match")
}
/* Builds a `js.Match`, but simplifies it to a `js.If` if there is only
* one case with one alternative, and to a `js.Block` if there is no case
* at all. This happens in practice in the standard library. Having no
* case is a typical product of `match`es that are full of
* `case n if ... =>`, which are used instead of `if` chains for
* convenience and/or readability.
*/
def isInt(tree: js.Tree): Boolean = tree.tpe == jstpe.IntType
clauses match {
case Nil =>
// Completely remove the Match. Preserve the side-effects of `genSelector`.
js.Block(exprToStat(genSelector), defaultClause)
case (uniqueAlt :: Nil, caseRhs) :: Nil =>
/* Simplify the `match` as an `if`, so that the optimizer has less
* work to do, and we emit less code at the end of the day.
* Use `Int_==` instead of `===` if possible, since it is a common case.
*/
val op =
if (isInt(genSelector) && isInt(uniqueAlt)) js.BinaryOp.Int_==
else js.BinaryOp.===
js.If(js.BinaryOp(op, genSelector, uniqueAlt), caseRhs, defaultClause)(resultType)
case _ =>
// We have more than one case: use a js.Match
js.Match(genSelector, clauses, defaultClause)(resultType)
}
}
/** Gen JS code for a closure.
*
* Input: a `Closure` tree of the form
* {{{
* Closure(env, call, functionalInterface)
* }}}
* representing the pseudo-syntax
* {{{
* { (p1, ..., pm) => call(env1, ..., envn, p1, ..., pm) }: functionInterface
* }}}
* where `envi` are identifiers in the local scope. The qualifier of `call`
* is also implicitly captured.
*
* Output: a `js.Closure` tree of the form
* {{{
* js.Closure(formalCaptures, formalParams, body, actualCaptures)
* }}}
* representing the pseudo-syntax
* {{{
* lambda(
* formalParam1, ..., formalParamM) = body
* }}}
* where the `actualCaptures` and `body` are, in general, arbitrary
* expressions. But in this case, `actualCaptures` will be identifiers from
* `env`, and the `body` will be of the form
* {{{
* call(formalCapture1.ref, ..., formalCaptureN.ref,
* formalParam1.ref, ...formalParamM.ref)
* }}}
*
* When the `js.Closure` node is evaluated, i.e., when the closure value is
* created, the expressions of the `actualCaptures` are evaluated, and the
* results of those evaluations is "stored" in the environment of the
* closure as the corresponding `formalCapture`.
*
* When we later *call* the closure, the `formalCaptures` already have their
* values from the environment, and they are available in the `body`. The
* `formalParams` of the created closure receive their values from the
* actual arguments at the call-site of the closure, and they are also
* available in the `body`.
*/
private def genClosure(tree: Closure): js.Tree = {
implicit val pos = tree.span
val Closure(env, call, functionalInterface) = tree
val envSize = env.size
val (fun, args) = call match {
// case Apply(fun, args) => (fun, args) // Conjectured not to happen
case t @ Select(_, _) => (t, Nil)
case t @ Ident(_) => (t, Nil)
}
val sym = fun.symbol
val isStaticCall = isMethodStaticInIR(sym)
val qualifier = qualifierOf(fun)
val allCaptureValues =
if (isStaticCall) env
else qualifier :: env
val formalAndActualCaptures = allCaptureValues.map { value =>
implicit val pos = value.span
val (formalIdent, originalName) = value match {
case Ident(name) => (freshLocalIdent(name.toTermName), OriginalName(name.toString))
case This(_) => (freshLocalIdent("this"), thisOriginalName)
case _ => (freshLocalIdent(), NoOriginalName)
}
val formalCapture = js.ParamDef(formalIdent, originalName,
toIRType(value.tpe), mutable = false)
val actualCapture = genExpr(value)
(formalCapture, actualCapture)
}
val (formalCaptures, actualCaptures) = formalAndActualCaptures.unzip
val funInterfaceSym = functionalInterface.tpe.typeSymbol
val hasRepeatedParam = {
funInterfaceSym.exists && {
val Seq(samMethodDenot) = funInterfaceSym.info.possibleSamMethods
val samMethod = samMethodDenot.symbol
atPhase(elimRepeatedPhase)(samMethod.info.paramInfoss.flatten.exists(_.isRepeatedParam))
}
}
val formalParamNames = sym.info.paramNamess.flatten.drop(envSize)
val formalParamTypes = sym.info.paramInfoss.flatten.drop(envSize)
val formalParamRepeateds =
if (hasRepeatedParam) (0 until (formalParamTypes.size - 1)).map(_ => false) :+ true
else (0 until formalParamTypes.size).map(_ => false)
val formalAndActualParams = formalParamNames.lazyZip(formalParamTypes).lazyZip(formalParamRepeateds).map {
(name, tpe, repeated) =>
val formalParam = js.ParamDef(freshLocalIdent(name),
OriginalName(name.toString), jstpe.AnyType, mutable = false)
val actualParam =
if (repeated) genJSArrayToVarArgs(formalParam.ref)(tree.sourcePos)
else unbox(formalParam.ref, tpe)
(formalParam, actualParam)
}
val (formalAndRestParams, actualParams) = formalAndActualParams.unzip
val (formalParams, restParam) =
if (hasRepeatedParam) (formalAndRestParams.init, Some(formalAndRestParams.last))
else (formalAndRestParams, None)
val genBody = {
val call = if (isStaticCall) {
genApplyStatic(sym, formalCaptures.map(_.ref) ::: actualParams)
} else {
val thisCaptureRef :: argCaptureRefs = formalCaptures.map(_.ref): @unchecked
if (!sym.owner.isNonNativeJSClass || sym.isJSExposed)
genApplyMethodMaybeStatically(thisCaptureRef, sym, argCaptureRefs ::: actualParams)
else
genApplyJSClassMethod(thisCaptureRef, sym, argCaptureRefs ::: actualParams)
}
box(call, sym.info.finalResultType)
}
val isThisFunction = funInterfaceSym.isSubClass(jsdefn.JSThisFunctionClass) && {
val ok = formalParams.nonEmpty
if (!ok)
report.error("The SAM or apply method for a js.ThisFunction must have a leading non-varargs parameter", tree)
ok
}
if (isThisFunction) {
val thisParam :: otherParams = formalParams: @unchecked
js.Closure(
arrow = false,
formalCaptures,
otherParams,
restParam,
js.Block(
js.VarDef(thisParam.name, thisParam.originalName,
thisParam.ptpe, mutable = false,
js.This()(thisParam.ptpe)(thisParam.pos))(thisParam.pos),
genBody),
actualCaptures)
} else {
val closure = js.Closure(arrow = true, formalCaptures, formalParams, restParam, genBody, actualCaptures)
if (!funInterfaceSym.exists || defn.isFunctionClass(funInterfaceSym)) {
val formalCount = formalParams.size
val cls = ClassName("scala.scalajs.runtime.AnonFunction" + formalCount)
val ctorName = MethodName.constructor(
jstpe.ClassRef(ClassName("scala.scalajs.js.Function" + formalCount)) :: Nil)
js.New(cls, js.MethodIdent(ctorName), List(closure))
} else if (funInterfaceSym.name == tpnme.FunctionXXL && funInterfaceSym.owner == defn.ScalaRuntimePackageClass) {
val cls = ClassName("scala.scalajs.runtime.AnonFunctionXXL")
val ctorName = MethodName.constructor(
jstpe.ClassRef(ClassName("scala.scalajs.js.Function1")) :: Nil)
js.New(cls, js.MethodIdent(ctorName), List(closure))
} else {
assert(funInterfaceSym.isJSType,
s"Invalid functional interface $funInterfaceSym reached the back-end")
closure
}
}
}
/** Generates a static method instantiating and calling this
* DynamicImportThunk's `apply`:
*
* {{{
* static def dynamicImport$;;Ljava.lang.Object(): any = {
* new .;:V().apply;Ljava.lang.Object()
* }
* }}}
*/
private def genDynamicImportForwarder(clsSym: Symbol)(using Position): js.MethodDef = {
withNewLocalNameScope {
val ctor = clsSym.primaryConstructor
val paramSyms = ctor.paramSymss.flatten
val paramDefs = paramSyms.map(genParamDef(_))
val body = {
val inst = js.New(encodeClassName(clsSym), encodeMethodSym(ctor), paramDefs.map(_.ref))
genApplyMethod(inst, jsdefn.DynamicImportThunkClass_apply, Nil)
}
js.MethodDef(
js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic),
encodeDynamicImportForwarderIdent(paramSyms),
NoOriginalName,
paramDefs,
jstpe.AnyType,
Some(body))(OptimizerHints.empty, Unversioned)
}
}
/** Boxes a value of the given type before `elimErasedValueType`.
*
* This should be used when sending values to a JavaScript context, which
* is erased/boxed at the IR level, although it is not erased at the
* dotty/JVM level.
*
* @param expr Tree to be boxed if needed.
* @param tpeEnteringElimErasedValueType The type of `expr` as it was
* entering the `elimErasedValueType` phase.
*/
def box(expr: js.Tree, tpeEnteringElimErasedValueType: Type)(implicit pos: Position): js.Tree = {
tpeEnteringElimErasedValueType match {
case tpe if isPrimitiveValueType(tpe) =>
makePrimitiveBox(expr, tpe)
case tpe: ErasedValueType =>
val boxedClass = tpe.tycon.typeSymbol
val ctor = boxedClass.primaryConstructor
js.New(encodeClassName(boxedClass), encodeMethodSym(ctor), List(expr))
case _ =>
expr
}
}
/** Unboxes a value typed as Any to the given type before `elimErasedValueType`.
*
* This should be used when receiving values from a JavaScript context,
* which is erased/boxed at the IR level, although it is not erased at the
* dotty/JVM level.
*
* @param expr Tree to be extracted.
* @param tpeEnteringElimErasedValueType The type of `expr` as it was
* entering the `elimErasedValueType` phase.
*/
def unbox(expr: js.Tree, tpeEnteringElimErasedValueType: Type)(implicit pos: Position): js.Tree = {
tpeEnteringElimErasedValueType match {
case tpe if isPrimitiveValueType(tpe) =>
makePrimitiveUnbox(expr, tpe)
case tpe: ErasedValueType =>
val boxedClass = tpe.tycon.typeSymbol.asClass
val unboxMethod = ValueClasses.valueClassUnbox(boxedClass)
val content = genApplyMethod(
js.AsInstanceOf(expr, encodeClassType(boxedClass)), unboxMethod, Nil)
if (unboxMethod.info.resultType <:< tpe.erasedUnderlying)
content
else
unbox(content, tpe.erasedUnderlying)
case tpe =>
genAsInstanceOf(expr, tpe)
}
}
/** Gen JS code for an asInstanceOf cast (for reference types only) */
private def genAsInstanceOf(value: js.Tree, to: Type)(implicit pos: Position): js.Tree =
genAsInstanceOf(value, toIRType(to))
/** Gen JS code for an asInstanceOf cast (for reference types only) */
private def genAsInstanceOf(value: js.Tree, to: jstpe.Type)(implicit pos: Position): js.Tree = {
to match {
case jstpe.AnyType =>
value
case jstpe.NullType =>
js.If(
js.BinaryOp(js.BinaryOp.===, value, js.Null()),
js.Null(),
genThrowClassCastException())(
jstpe.NullType)
case jstpe.NothingType =>
js.Block(value, genThrowClassCastException())
case _ =>
js.AsInstanceOf(value, to)
}
}
private def genThrowClassCastException()(implicit pos: Position): js.Tree = {
js.Throw(js.New(jsNames.ClassCastExceptionClass,
js.MethodIdent(jsNames.NoArgConstructorName), Nil))
}
/** Gen JS code for an isInstanceOf test (for reference types only) */
def genIsInstanceOf(value: js.Tree, to: Type)(
implicit pos: SourcePosition): js.Tree = {
val sym = to.typeSymbol
if (sym == defn.ObjectClass) {
js.BinaryOp(js.BinaryOp.!==, value, js.Null())
} else if (sym.isJSType) {
if (sym.is(Trait)) {
report.error(
em"isInstanceOf[${sym.fullName}] not supported because it is a JS trait",
pos)
js.BooleanLiteral(true)
} else {
js.AsInstanceOf(js.JSBinaryOp(
js.JSBinaryOp.instanceof, value, genLoadJSConstructor(sym)),
jstpe.BooleanType)
}
} else {
// The Scala type system prevents x.isInstanceOf[Null] and ...[Nothing]
assert(sym != defn.NullClass && sym != defn.NothingClass,
s"Found a .isInstanceOf[$sym] at $pos")
js.IsInstanceOf(value, toIRType(to))
}
}
/** Gen a statically linked call to an instance method. */
def genApplyMethodMaybeStatically(receiver: js.Tree, method: Symbol,
arguments: List[js.Tree])(implicit pos: Position): js.Tree = {
if (method.isPrivate || method.isClassConstructor)
genApplyMethodStatically(receiver, method, arguments)
else
genApplyMethod(receiver, method, arguments)
}
/** Gen a dynamically linked call to a Scala method. */
def genApplyMethod(receiver: js.Tree, method: Symbol, arguments: List[js.Tree])(
implicit pos: Position): js.Tree = {
assert(!method.isPrivate,
s"Cannot generate a dynamic call to private method $method at $pos")
js.Apply(js.ApplyFlags.empty, receiver, encodeMethodSym(method), arguments)(
toIRType(patchedResultType(method)))
}
/** Gen a statically linked call to an instance method. */
def genApplyMethodStatically(receiver: js.Tree, method: Symbol, arguments: List[js.Tree])(
implicit pos: Position): js.Tree = {
val flags = js.ApplyFlags.empty
.withPrivate(method.isPrivate && !method.isClassConstructor)
.withConstructor(method.isClassConstructor)
js.ApplyStatically(flags, receiver, encodeClassName(method.owner),
encodeMethodSym(method), arguments)(
toIRType(patchedResultType(method)))
}
/** Gen a call to a static method. */
private def genApplyStatic(method: Symbol, arguments: List[js.Tree])(
implicit pos: Position): js.Tree = {
js.ApplyStatic(js.ApplyFlags.empty.withPrivate(method.isPrivate),
encodeClassName(method.owner), encodeMethodSym(method), arguments)(
toIRType(patchedResultType(method)))
}
/** Gen a call to a non-exposed method of a non-native JS class. */
def genApplyJSClassMethod(receiver: js.Tree, method: Symbol, arguments: List[js.Tree])(
implicit pos: Position): js.Tree = {
genApplyStatic(method, receiver :: arguments)
}
/** Gen a call to a method of a Scala top-level module. */
private def genModuleApplyMethod(methodSym: Symbol, arguments: List[js.Tree])(
implicit pos: SourcePosition): js.Tree = {
genApplyMethod(genLoadModule(methodSym.owner), methodSym, arguments)
}
/** Gen a boxing operation (tpe is the primitive type) */
private def makePrimitiveBox(expr: js.Tree, tpe: Type)(
implicit pos: Position): js.Tree = {
toIRType(tpe) match {
case jstpe.NoType => // for JS interop cases
js.Block(expr, js.Undefined())
case jstpe.BooleanType | jstpe.CharType | jstpe.ByteType |
jstpe.ShortType | jstpe.IntType | jstpe.LongType | jstpe.FloatType |
jstpe.DoubleType =>
expr // box is identity for all those primitive types
case typeRef =>
throw new FatalError(
s"makePrimitiveBox requires a primitive type, found $typeRef for $tpe at $pos")
}
}
/** Gen an unboxing operation (tpe is the primitive type) */
private def makePrimitiveUnbox(expr: js.Tree, tpe: Type)(
implicit pos: Position): js.Tree = {
toIRType(tpe) match {
case jstpe.NoType => expr // for JS interop cases
case irTpe => js.AsInstanceOf(expr, irTpe)
}
}
/** Gen JS code for a Scala.js-specific primitive method */
private def genJSPrimitive(tree: Apply, args: List[Tree], code: Int,
isStat: Boolean): js.Tree = {
import JSPrimitives.*
implicit val pos = tree.span
def genArgs1: js.Tree = {
assert(args.size == 1,
s"Expected exactly 1 argument for JS primitive $code but got " +
s"${args.size} at $pos")
genExpr(args.head)
}
def genArgs2: (js.Tree, js.Tree) = {
assert(args.size == 2,
s"Expected exactly 2 arguments for JS primitive $code but got " +
s"${args.size} at $pos")
(genExpr(args.head), genExpr(args.tail.head))
}
def genArgsVarLength: List[js.TreeOrJSSpread] =
genActualJSArgs(tree.symbol, args)
def resolveReifiedJSClassSym(arg: Tree): Symbol = {
def fail(): Symbol = {
report.error(
tree.symbol.name.toString + " must be called with a constant " +
"classOf[T] representing a class extending js.Any " +
"(not a trait nor an object)",
tree.sourcePos)
NoSymbol
}
arg match {
case Literal(value) if value.tag == Constants.ClazzTag =>
val classSym = value.typeValue.typeSymbol
if (classSym.isJSType && !classSym.is(Trait) && !classSym.is(ModuleClass))
classSym
else
fail()
case _ =>
fail()
}
}
(code: @switch) match {
case DYNNEW =>
// js.Dynamic.newInstance(clazz)(actualArgs: _*)
val (jsClass, actualArgs) = extractFirstArg(genArgsVarLength)
js.JSNew(jsClass, actualArgs)
case ARR_CREATE =>
// js.Array(elements: _*)
js.JSArrayConstr(genArgsVarLength)
case CONSTRUCTOROF =>
// runtime.constructorOf(clazz)
val classSym = resolveReifiedJSClassSym(args.head)
if (classSym == NoSymbol)
js.Undefined() // compile error emitted by resolveReifiedJSClassSym
else
genLoadJSConstructor(classSym)
case CREATE_INNER_JS_CLASS | CREATE_LOCAL_JS_CLASS =>
// runtime.createInnerJSClass(clazz, superClass)
// runtime.createLocalJSClass(clazz, superClass, fakeNewInstances)
val classSym = resolveReifiedJSClassSym(args(0))
val superClassValue = genExpr(args(1))
if (classSym == NoSymbol) {
js.Undefined() // compile error emitted by resolveReifiedJSClassSym
} else {
val captureValues = {
if (code == CREATE_INNER_JS_CLASS) {
/* Private inner classes that do not actually access their outer
* pointer do not receive an outer argument. We therefore count
* the number of constructors that have non-empty param list to
* know how many times we need to pass `this`.
*/
val requiredThisParams =
classSym.info.decls.lookupAll(nme.CONSTRUCTOR).count(_.info.paramInfoss.head.nonEmpty)
val outer = genThis()
List.fill(requiredThisParams)(outer)
} else {
val fakeNewInstances = args(2).asInstanceOf[JavaSeqLiteral].elems
fakeNewInstances.flatMap(genCaptureValuesFromFakeNewInstance(_))
}
}
js.CreateJSClass(encodeClassName(classSym), superClassValue :: captureValues)
}
case WITH_CONTEXTUAL_JS_CLASS_VALUE =>
// withContextualJSClassValue(jsclass, inner)
val jsClassValue = genExpr(args(0))
withScopedVars(
contextualJSClassValue := Some(jsClassValue)
) {
genStatOrExpr(args(1), isStat)
}
case LINKING_INFO =>
// runtime.linkingInfo
js.JSLinkingInfo()
case DEBUGGER =>
// js.special.debugger()
js.Debugger()
case UNITVAL =>
// BoxedUnit.UNIT, which is the boxed version of ()
js.Undefined()
case JS_NEW_TARGET =>
// js.new.target
val valid = currentMethodSym.get.isClassConstructor && currentClassSym.isNonNativeJSClass
if (!valid) {
report.error(
"Illegal use of js.`new`.target.\n" +
"It can only be used in the constructor of a JS class, " +
"as a statement or in the rhs of a val or var.\n" +
"It cannot be used inside a lambda or by-name parameter, nor in any other location.",
tree.sourcePos)
}
js.JSNewTarget()
case JS_IMPORT =>
// js.import(arg)
val arg = genArgs1
js.JSImportCall(arg)
case JS_IMPORT_META =>
// js.import.meta
js.JSImportMeta()
case DYNAMIC_IMPORT =>
// runtime.dynamicImport
assert(args.size == 1,
s"Expected exactly 1 argument for JS primitive $code but got " +
s"${args.size} at $pos")
args.head match {
case Block(stats, expr @ Typed(Apply(fun @ Select(New(tpt), _), args), _)) =>
/* stats is always empty if no other compiler plugin is present.
* However, code instrumentation (notably scoverage) might add
* statements here. If this is the case, the thunk anonymous class
* has already been created when the other plugin runs (i.e. the
* plugin ran after jsinterop).
*
* Therefore, it is OK to leave the statements on our side of the
* dynamic loading boundary.
*/
val clsSym = tpt.symbol
val ctor = fun.symbol
assert(clsSym.isSubClass(jsdefn.DynamicImportThunkClass),
s"expected subclass of DynamicImportThunk, got: $clsSym at: ${expr.sourcePos}")
assert(ctor.isPrimaryConstructor,
s"expected primary constructor, got: $ctor at: ${expr.sourcePos}")
js.Block(
stats.map(genStat(_)),
js.ApplyDynamicImport(
js.ApplyFlags.empty,
encodeClassName(clsSym),
encodeDynamicImportForwarderIdent(ctor.paramSymss.flatten),
genActualArgs(ctor, args))
)
case tree =>
throw new FatalError(
s"Unexpected argument tree in dynamicImport: $tree/${tree.getClass} at: $pos")
}
case JS_NATIVE =>
// js.native
report.error(
"js.native may only be used as stub implementation in facade types",
tree.sourcePos)
js.Undefined()
case TYPEOF =>
// js.typeOf(arg)
val arg = genArgs1
val typeofExpr = arg match {
case arg: js.JSGlobalRef => js.JSTypeOfGlobalRef(arg)
case _ => js.JSUnaryOp(js.JSUnaryOp.typeof, arg)
}
js.AsInstanceOf(typeofExpr, jstpe.ClassType(jsNames.BoxedStringClass))
case STRICT_EQ =>
// js.special.strictEquals(arg1, arg2)
val (arg1, arg2) = genArgs2
js.JSBinaryOp(js.JSBinaryOp.===, arg1, arg2)
case IN =>
// js.special.in(arg1, arg2)
val (arg1, arg2) = genArgs2
js.AsInstanceOf(js.JSBinaryOp(js.JSBinaryOp.in, arg1, arg2),
jstpe.BooleanType)
case INSTANCEOF =>
// js.special.instanceof(arg1, arg2)
val (arg1, arg2) = genArgs2
js.AsInstanceOf(js.JSBinaryOp(js.JSBinaryOp.instanceof, arg1, arg2),
jstpe.BooleanType)
case DELETE =>
// js.special.delete(arg1, arg2)
val (arg1, arg2) = genArgs2
js.JSDelete(arg1, arg2)
case FORIN =>
/* js.special.forin(arg1, arg2)
*
* We must generate:
*
* val obj = arg1
* val f = arg2
* for (val key in obj) {
* f(key)
* }
*
* with temporary vals, because `arg2` must be evaluated only
* once, and after `arg1`.
*/
val (arg1, arg2) = genArgs2
val objVarDef = js.VarDef(freshLocalIdent("obj"), NoOriginalName,
jstpe.AnyType, mutable = false, arg1)
val fVarDef = js.VarDef(freshLocalIdent("f"), NoOriginalName,
jstpe.AnyType, mutable = false, arg2)
val keyVarIdent = freshLocalIdent("key")
val keyVarRef = js.VarRef(keyVarIdent)(jstpe.AnyType)
js.Block(
objVarDef,
fVarDef,
js.ForIn(objVarDef.ref, keyVarIdent, NoOriginalName, {
js.JSFunctionApply(fVarDef.ref, List(keyVarRef))
}))
case JS_THROW =>
// js.special.throw(arg)
js.Throw(genArgs1)
case JS_TRY_CATCH =>
/* js.special.tryCatch(arg1, arg2)
*
* We must generate:
*
* val body = arg1
* val handler = arg2
* try {
* body()
* } catch (e) {
* handler(e)
* }
*
* with temporary vals, because `arg2` must be evaluated before
* `body` executes. Moreover, exceptions thrown while evaluating
* the function values `arg1` and `arg2` must not be caught.
*/
val (arg1, arg2) = genArgs2
val bodyVarDef = js.VarDef(freshLocalIdent("body"), NoOriginalName,
jstpe.AnyType, mutable = false, arg1)
val handlerVarDef = js.VarDef(freshLocalIdent("handler"), NoOriginalName,
jstpe.AnyType, mutable = false, arg2)
val exceptionVarIdent = freshLocalIdent("e")
val exceptionVarRef = js.VarRef(exceptionVarIdent)(jstpe.AnyType)
js.Block(
bodyVarDef,
handlerVarDef,
js.TryCatch(
js.JSFunctionApply(bodyVarDef.ref, Nil),
exceptionVarIdent,
NoOriginalName,
js.JSFunctionApply(handlerVarDef.ref, List(exceptionVarRef))
)(jstpe.AnyType)
)
case WRAP_AS_THROWABLE =>
// js.special.wrapAsThrowable(arg)
js.WrapAsThrowable(genArgs1)
case UNWRAP_FROM_THROWABLE =>
// js.special.unwrapFromThrowable(arg)
js.UnwrapFromThrowable(genArgs1)
case UNION_FROM | UNION_FROM_TYPE_CONSTRUCTOR =>
/* js.|.from and js.|.fromTypeConstructor
* We should not have to deal with those. They have a perfectly valid
* user-space implementation. However, the Dotty type checker inserts
* way too many of those, even when they are completely unnecessary.
* That still wouldn't be an issue ... if only it did not insert them
* around the default getters to their parameters! But even there it
* does it (although the types are, by construction, *equivalent*!),
* and that kills our `UndefinedParam` treatment. So we have to handle
* those two methods as primitives to completely eliminate them.
*
* Hopefully this will become unnecessary when/if we manage to
* reinterpret js.| as a true Dotty union type.
*/
genArgs2._1
case REFLECT_SELECTABLE_SELECTDYN =>
// scala.reflect.Selectable.selectDynamic
genReflectiveCall(tree, isSelectDynamic = true)
case REFLECT_SELECTABLE_APPLYDYN =>
// scala.reflect.Selectable.applyDynamic
genReflectiveCall(tree, isSelectDynamic = false)
}
}
/** Gen the SJSIR for a reflective call.
*
* Reflective calls are calls to a structural type field or method that
* involve a reflective Selectable. They look like the following in source
* code:
* {{{
* import scala.reflect.Selectable.reflectiveSelectable
*
* type Structural = {
* val foo: Int
* def bar(x: Int, y: String): String
* }
*
* val structural: Structural = new {
* val foo: Int = 5
* def bar(x: Int, y: String): String = x.toString + y
* }
*
* structural.foo
* structural.bar(6, "hello")
* }}}
*
* After expansion by the Scala 3 rules for structural member selections and
* calls, they look like
*
* {{{
* reflectiveSelectable(structural).selectDynamic("foo")
* reflectiveSelectable(structural).applyDynamic("bar",
* classOf[Int], classOf[String]
* )(
* 6, "hello"
* )
* }}}
*
* When the original `structural` value is already of a subtype of
* `scala.reflect.Selectable`, there is no conversion involved. There could
* also be any other arbitrary conversion, such as the deprecated bridge for
* Scala 2's `import scala.language.reflectiveCalls`. In general, the shape
* is therefore the following, for some `selectable: reflect.Selectable`:
*
* {{{
* selectable.selectDynamic("foo")
* selectable.applyDynamic("bar",
* classOf[Int], classOf[String]
* )(
* 6, "hello"
* )
* }}}
*
* and eventually reaches the back-end as
*
* {{{
* selectable.selectDynamic("foo") // same as above
* selectable.applyDynamic("bar",
* wrapRefArray([ classOf[Int], classOf[String] : jl.Class ]
* )(
* genericWrapArray([ Int.box(6), "hello" : Object ])
* )
* }}}
*
* In SJSIR, they must be encoded as follows:
*
* {{{
* selectable.selectedValue;O().foo;R()
* selectable.selectedValue;O().bar;I;Ljava.lang.String;R(
* Int.box(6).asInstanceOf[int],
* "hello".asInstanceOf[java.lang.String]
* )
* }}}
*
* where `selectedValue;O()` is declared in `scala.reflect.Selectable` and
* holds the actual instance on which to perform the reflective operations.
* For the typical use case from the first snippet, it returns `structural`.
*
* This means that we must deconstruct the elaborated calls to recover:
*
* - the method name as a compile-time string `foo` or `bar`
* - the `tp: Type`s that have been wrapped in `classOf[tp]`, as a
* compile-time List[Type], from which we'll derive `jstpe.Type`s for the
* `asInstanceOf`s and `jstpe.TypeRef`s for the `MethodName.reflectiveProxy`
* - the actual arguments as a compile-time `List[Tree]`
*
* Virtually all of the code in `genReflectiveCall` deals with recovering
* those elements. Constructing the IR Tree is the easy part after that.
*/
private def genReflectiveCall(tree: Apply, isSelectDynamic: Boolean): js.Tree = {
implicit val pos = tree.span
val Apply(fun @ Select(receiver, _), args) = tree: @unchecked
val selectedValueTree = js.Apply(js.ApplyFlags.empty, genExpr(receiver),
js.MethodIdent(selectedValueMethodName), Nil)(jstpe.AnyType)
// Extract the method name as a String
val methodNameStr = args.head match {
case Literal(Constants.Constant(name: String)) =>
name
case _ =>
report.error(
"The method name given to Selectable.selectDynamic or Selectable.applyDynamic " +
"must be a literal string. " +
"Other uses are not supported in Scala.js.",
args.head.sourcePos)
"erroneous"
}
val (formalParamTypeRefs, actualArgs) = if (isSelectDynamic) {
(Nil, Nil)
} else {
// Extract the param type refs and actual args from the 2nd and 3rd argument to applyDynamic
args.tail match {
case WrapArray(classOfsArray: JavaSeqLiteral) :: WrapArray(actualArgsAnyArray: JavaSeqLiteral) :: Nil =>
// Extract jstpe.Type's and jstpe.TypeRef's from the classOf[_] trees
val formalParamTypesAndTypeRefs = classOfsArray.elems.map {
// classOf[tp] -> tp
case Literal(const) if const.tag == Constants.ClazzTag =>
toIRTypeAndTypeRef(const.typeValue)
// Anything else is invalid
case otherTree =>
report.error(
"The java.lang.Class[_] arguments passed to Selectable.applyDynamic must be " +
"literal classOf[T] expressions (typically compiler-generated). " +
"Other uses are not supported in Scala.js.",
otherTree.sourcePos)
(jstpe.AnyType, jstpe.ClassRef(jsNames.ObjectClass))
}
// Gen the actual args, downcasting them to the formal param types
val actualArgs = actualArgsAnyArray.elems.zip(formalParamTypesAndTypeRefs).map {
(actualArgAny, formalParamTypeAndTypeRef) =>
val genActualArgAny = genExpr(actualArgAny)
genAsInstanceOf(genActualArgAny, formalParamTypeAndTypeRef._1)(genActualArgAny.pos)
}
(formalParamTypesAndTypeRefs.map(pair => toParamOrResultTypeRef(pair._2)), actualArgs)
case _ =>
report.error(
"Passing the varargs of Selectable.applyDynamic with `: _*` " +
"is not supported in Scala.js.",
tree.sourcePos)
(Nil, Nil)
}
}
val methodName = MethodName.reflectiveProxy(NameTransformer.encode(methodNameStr), formalParamTypeRefs)
js.Apply(js.ApplyFlags.empty, selectedValueTree, js.MethodIdent(methodName), actualArgs)(jstpe.AnyType)
}
/** Gen actual actual arguments to Scala method call.
* Returns a list of the transformed arguments.
*
* This tries to optimize repeated arguments (varargs) by turning them
* into js.WrappedArray instead of Scala wrapped arrays.
*/
private def genActualArgs(sym: Symbol, args: List[Tree])(
implicit pos: Position): List[js.Tree] = {
args.map(genExpr)
/*val wereRepeated = exitingPhase(currentRun.typerPhase) {
sym.tpe.params.map(p => isScalaRepeatedParamType(p.tpe))
}
if (wereRepeated.size > args.size) {
// Should not happen, but let's not crash
args.map(genExpr)
} else {
/* Arguments that are in excess compared to the type signature after
* erasure are lambda-lifted arguments. They cannot be repeated, hence
* the extension to `false`.
*/
for ((arg, wasRepeated) <- args.zipAll(wereRepeated, EmptyTree, false)) yield {
if (wasRepeated) {
tryGenRepeatedParamAsJSArray(arg, handleNil = false).fold {
genExpr(arg)
} { genArgs =>
genNew(WrappedArrayClass, WrappedArray_ctor,
List(js.JSArrayConstr(genArgs)))
}
} else {
genExpr(arg)
}
}
}*/
}
/** Gen actual actual arguments to a JS method call.
* Returns a list of the transformed arguments.
*
* - TODO Repeated arguments (varargs) are expanded
* - Default arguments are omitted or replaced by undefined
* - All arguments are boxed
*
* Repeated arguments that cannot be expanded at compile time (i.e., if a
* Seq is passed to a varargs parameter with the syntax `seq: _*`) will be
* wrapped in a [[js.JSSpread]] node to be expanded at runtime.
*/
private def genActualJSArgs(sym: Symbol, args: List[Tree])(
implicit pos: Position): List[js.TreeOrJSSpread] = {
var reversedArgs: List[js.TreeOrJSSpread] = Nil
for ((arg, info) <- args.zip(sym.jsParamInfos)) {
if (info.repeated) {
reversedArgs = genJSRepeatedParam(arg) reverse_::: reversedArgs
} else if (info.capture) {
// Ignore captures
assert(sym.isClassConstructor,
i"Found a capture param in method ${sym.fullName}, which is not a class constructor, at $pos")
} else {
val unboxedArg = genExpr(arg)
val boxedArg = unboxedArg match {
case js.Transient(UndefinedParam) =>
unboxedArg
case _ =>
box(unboxedArg, info.info)
}
reversedArgs ::= boxedArg
}
}
/* Remove all consecutive UndefinedParam's at the end of the argument
* list. No check is performed whether they may be there, since they will
* only be placed where default arguments can be anyway.
*/
reversedArgs = reversedArgs.dropWhile(_.isInstanceOf[js.Transient])
/* Find remaining UndefinedParam and replace by js.Undefined. This can
* happen with named arguments or with multiple argument lists.
*/
reversedArgs = reversedArgs map {
case js.Transient(UndefinedParam) => js.Undefined()
case arg => arg
}
reversedArgs.reverse
}
/** Gen JS code for a repeated param of a JS method.
*
* In this case `arg` has type `Seq[T]` for some `T`, but the result should
* be an expanded list of the elements in the sequence. So this method
* takes care of the conversion.
*
* It is specialized for the shapes of tree generated by the desugaring
* of repeated params in Scala, so that these are actually expanded at
* compile-time.
*
* Otherwise, it returns a `JSSpread` with the `Seq` converted to a
* `js.Array`.
*/
private def genJSRepeatedParam(arg: Tree): List[js.TreeOrJSSpread] = {
tryGenRepeatedParamAsJSArray(arg, handleNil = true).getOrElse {
/* Fall back to calling runtime.genTraversableOnce2jsArray
* to perform the conversion to js.Array, then wrap in a Spread
* operator.
*/
implicit val pos: SourcePosition = arg.sourcePos
val jsArrayArg = genModuleApplyMethod(
jsdefn.Runtime_toJSVarArgs,
List(genExpr(arg)))
List(js.JSSpread(jsArrayArg))
}
}
/** Try and expand an actual argument to a repeated param `(xs: T*)`.
*
* This method recognizes the shapes of tree generated by the desugaring
* of repeated params in Scala, and expands them.
* If `arg` does not have the shape of a generated repeated param, this
* method returns `None`.
*/
private def tryGenRepeatedParamAsJSArray(arg: Tree,
handleNil: Boolean): Option[List[js.Tree]] = {
implicit val pos = arg.span
// Given a method `def foo(args: T*)`
arg match {
// foo(arg1, arg2, ..., argN) where N > 0
case MaybeAsInstanceOf(WrapArray(MaybeAsInstanceOf(array: JavaSeqLiteral))) =>
/* Value classes in arrays are already boxed, so no need to use
* the type before erasure.
* TODO Is this true in dotty?
*/
Some(array.elems.map(e => box(genExpr(e), e.tpe)))
// foo()
case Ident(_) if handleNil && arg.symbol == defn.NilModule =>
Some(Nil)
// foo(argSeq: _*) - cannot be optimized
case _ =>
None
}
}
private object MaybeAsInstanceOf {
def unapply(tree: Tree): Some[Tree] = tree match {
case TypeApply(asInstanceOf_? @ Select(base, _), _)
if asInstanceOf_?.symbol == defn.Any_asInstanceOf =>
Some(base)
case _ =>
Some(tree)
}
}
private object WrapArray {
lazy val isWrapArray: Set[Symbol] = {
val names0 = defn.ScalaValueClasses().map(sym => nme.wrapXArray(sym.name))
val names1 = names0 ++ Set(nme.wrapRefArray, nme.genericWrapArray)
val symsInPredef = names1.map(defn.ScalaPredefModule.requiredMethod(_))
val symsInScalaRunTime = names1.map(defn.ScalaRuntimeModule.requiredMethod(_))
(symsInPredef ++ symsInScalaRunTime).toSet
}
def unapply(tree: Apply): Option[Tree] = tree match {
case Apply(wrapArray_?, List(wrapped)) if isWrapArray(wrapArray_?.symbol) =>
Some(wrapped)
case _ =>
None
}
}
/** Wraps a `js.Array` to use as varargs. */
def genJSArrayToVarArgs(arrayRef: js.Tree)(implicit pos: SourcePosition): js.Tree =
genModuleApplyMethod(jsdefn.Runtime_toScalaVarArgs, List(arrayRef))
/** Gen the actual capture values for a JS constructor based on its fake `new` invocation. */
private def genCaptureValuesFromFakeNewInstance(tree: Tree): List[js.Tree] = {
implicit val pos: Position = tree.span
val Apply(fun @ Select(New(_), _), args) = tree: @unchecked
val sym = fun.symbol
/* We use the same strategy as genActualJSArgs to detect which parameters were
* introduced by explicitouter or lambdalift (but reversed, of course).
*/
val existedBeforeUncurry = atPhase(elimRepeatedPhase) {
sym.info.paramNamess.flatten.toSet
}
for {
(arg, paramName) <- args.zip(sym.info.paramNamess.flatten)
if !existedBeforeUncurry(paramName)
} yield {
genExpr(arg)
}
}
private def genVarRef(sym: Symbol)(implicit pos: Position): js.VarRef =
js.VarRef(encodeLocalSym(sym))(toIRType(sym.info))
private def genAssignableField(sym: Symbol, qualifier: Tree)(implicit pos: SourcePosition): (js.AssignLhs, Boolean) = {
def qual = genExpr(qualifier)
if (sym.owner.isNonNativeJSClass) {
val f = if (sym.isJSExposed) {
js.JSSelect(qual, genExpr(sym.jsName))
} else if (sym.owner.isAnonymousClass) {
js.JSSelect(
js.JSSelect(qual, genPrivateFieldsSymbol()),
encodeFieldSymAsStringLiteral(sym))
} else {
js.JSPrivateSelect(qual, encodeFieldSym(sym))
}
(f, true)
} else if (sym.hasAnnotation(jsdefn.JSExportTopLevelAnnot)) {
val f = js.SelectStatic(encodeFieldSym(sym))(jstpe.AnyType)
(f, true)
} else if (sym.hasAnnotation(jsdefn.JSExportStaticAnnot)) {
val jsName = sym.getAnnotation(jsdefn.JSExportStaticAnnot).get.argumentConstantString(0).getOrElse {
sym.defaultJSName
}
val companionClass = sym.owner.linkedClass
val f = js.JSSelect(genLoadJSConstructor(companionClass), js.StringLiteral(jsName))
(f, true)
} else {
val className = encodeClassName(sym.owner)
val fieldIdent = encodeFieldSym(sym)
/* #4370 Fields cannot have type NothingType, so we box them as
* scala.runtime.Nothing$ instead. They will be initialized with
* `null`, and any attempt to access them will throw a
* `ClassCastException` (generated in the unboxing code).
*/
val (irType, boxed) = toIRType(sym.info) match
case jstpe.NothingType =>
(encodeClassType(defn.NothingClass), true)
case ftpe =>
(ftpe, false)
val f =
if sym.is(JavaStatic) then
js.SelectStatic(fieldIdent)(irType)
else
js.Select(qual, fieldIdent)(irType)
(f, boxed)
}
}
/** Gen JS code for loading a Java static field.
*/
private def genLoadStaticField(sym: Symbol)(implicit pos: SourcePosition): js.Tree = {
/* Actually, there is no static member in Scala.js. If we come here, that
* is because we found the symbol in a Java-emitted .class in the
* classpath. But the corresponding implementation in Scala.js will
* actually be a val in the companion module.
*/
if (sym == defn.BoxedUnit_UNIT) {
js.Undefined()
} else if (sym == defn.BoxedUnit_TYPE) {
js.ClassOf(jstpe.VoidRef)
} else {
val className = encodeClassName(sym.owner)
val method = encodeStaticMemberSym(sym)
js.ApplyStatic(js.ApplyFlags.empty, className, method, Nil)(toIRType(sym.info))
}
}
/** Generates a call to `runtime.privateFieldsSymbol()` */
private def genPrivateFieldsSymbol()(implicit pos: SourcePosition): js.Tree =
genModuleApplyMethod(jsdefn.Runtime_privateFieldsSymbol, Nil)
/** Generate loading of a module value.
*
* Can be given either the module symbol or its module class symbol.
*
* If the module we load refers to the global scope (i.e., it is
* annotated with `@JSGlobalScope`), report a compile error specifying
* that a global scope object should only be used as the qualifier of a
* `.`-selection.
*/
def genLoadModule(sym: Symbol)(implicit pos: SourcePosition): js.Tree =
ruleOutGlobalScope(genLoadModuleOrGlobalScope(sym))
/** Generate loading of a module value or the global scope.
*
* Can be given either the module symbol of its module class symbol.
*
* Unlike `genLoadModule`, this method does not fail if the module we load
* refers to the global scope.
*/
def genLoadModuleOrGlobalScope(sym0: Symbol)(
implicit pos: SourcePosition): MaybeGlobalScope = {
require(sym0.is(Module),
"genLoadModule called with non-module symbol: " + sym0)
val sym = if (sym0.isTerm) sym0.moduleClass else sym0
// Does that module refer to the global scope?
if (sym.hasAnnotation(jsdefn.JSGlobalScopeAnnot)) {
MaybeGlobalScope.GlobalScope(pos)
} else {
val cls = encodeClassName(sym)
val tree =
if (sym.isJSType) js.LoadJSModule(cls)
else js.LoadModule(cls)
MaybeGlobalScope.NotGlobalScope(tree)
}
}
/** Gen JS code representing the constructor of a JS class. */
private def genLoadJSConstructor(sym: Symbol)(
implicit pos: Position): js.Tree = {
assert(!isStaticModule(sym) && !sym.is(Trait),
s"genLoadJSConstructor called with non-class $sym")
js.LoadJSConstructor(encodeClassName(sym))
}
private inline val GenericGlobalObjectInformationMsg = {
"\n " +
"See https://www.scala-js.org/doc/interoperability/global-scope.html " +
"for further information."
}
/** Rule out the `GlobalScope` case of a `MaybeGlobalScope` and extract the
* value tree.
*
* If `tree` represents the global scope, report a compile error.
*/
private def ruleOutGlobalScope(tree: MaybeGlobalScope): js.Tree = {
tree match {
case MaybeGlobalScope.NotGlobalScope(t) =>
t
case MaybeGlobalScope.GlobalScope(pos) =>
reportErrorLoadGlobalScope()(pos)
}
}
/** Report a compile error specifying that the global scope cannot be
* loaded as a value.
*/
private def reportErrorLoadGlobalScope()(implicit pos: SourcePosition): js.Tree = {
report.error(
"Loading the global scope as a value (anywhere but as the " +
"left-hand-side of a `.`-selection) is not allowed." +
GenericGlobalObjectInformationMsg,
pos)
js.Undefined()
}
/** Gen a JS bracket select or a `JSGlobalRef`.
*
* If the receiver is a normal value, i.e., not the global scope, then
* emit a `JSSelect`.
*
* Otherwise, if the `item` is a constant string that is a valid
* JavaScript identifier, emit a `JSGlobalRef`.
*
* Otherwise, report a compile error.
*/
private def genJSSelectOrGlobalRef(qual: MaybeGlobalScope, item: js.Tree)(
implicit pos: SourcePosition): js.AssignLhs = {
qual match {
case MaybeGlobalScope.NotGlobalScope(qualTree) =>
js.JSSelect(qualTree, item)
case MaybeGlobalScope.GlobalScope(_) =>
item match {
case js.StringLiteral(value) =>
if (js.JSGlobalRef.isValidJSGlobalRefName(value)) {
js.JSGlobalRef(value)
} else if (js.JSGlobalRef.ReservedJSIdentifierNames.contains(value)) {
report.error(
"Invalid selection in the global scope of the reserved " +
s"identifier name `$value`." +
GenericGlobalObjectInformationMsg,
pos)
js.JSGlobalRef("erroneous")
} else {
report.error(
"Selecting a field of the global scope whose name is " +
"not a valid JavaScript identifier is not allowed." +
GenericGlobalObjectInformationMsg,
pos)
js.JSGlobalRef("erroneous")
}
case _ =>
report.error(
"Selecting a field of the global scope with a dynamic " +
"name is not allowed." +
GenericGlobalObjectInformationMsg,
pos)
js.JSGlobalRef("erroneous")
}
}
}
/** Gen a JS bracket method apply or an apply of a `GlobalRef`.
*
* If the receiver is a normal value, i.e., not the global scope, then
* emit a `JSMethodApply`.
*
* Otherwise, if the `method` is a constant string that is a valid
* JavaScript identifier, emit a `JSFunctionApply(JSGlobalRef(...), ...)`.
*
* Otherwise, report a compile error.
*/
private def genJSMethodApplyOrGlobalRefApply(
receiver: MaybeGlobalScope, method: js.Tree, args: List[js.TreeOrJSSpread])(
implicit pos: SourcePosition): js.Tree = {
receiver match {
case MaybeGlobalScope.NotGlobalScope(receiverTree) =>
js.JSMethodApply(receiverTree, method, args)
case MaybeGlobalScope.GlobalScope(_) =>
method match {
case js.StringLiteral(value) =>
if (js.JSGlobalRef.isValidJSGlobalRefName(value)) {
js.JSFunctionApply(js.JSGlobalRef(value), args)
} else if (js.JSGlobalRef.ReservedJSIdentifierNames.contains(value)) {
report.error(
"Invalid call in the global scope of the reserved " +
s"identifier name `$value`." +
GenericGlobalObjectInformationMsg,
pos)
js.Undefined()
} else {
report.error(
"Calling a method of the global scope whose name is not " +
"a valid JavaScript identifier is not allowed." +
GenericGlobalObjectInformationMsg,
pos)
js.Undefined()
}
case _ =>
report.error(
"Calling a method of the global scope with a dynamic " +
"name is not allowed." +
GenericGlobalObjectInformationMsg,
pos)
js.Undefined()
}
}
}
private def computeJSNativeLoadSpecOfValDef(sym: Symbol): js.JSNativeLoadSpec = {
atPhaseBeforeTransforms {
computeJSNativeLoadSpecOfInPhase(sym)
}
}
private def computeJSNativeLoadSpecOfClass(sym: Symbol): Option[js.JSNativeLoadSpec] = {
if (sym.is(Trait) || sym.hasAnnotation(jsdefn.JSGlobalScopeAnnot)) {
None
} else {
atPhaseBeforeTransforms {
if (sym.owner.isStaticOwner)
Some(computeJSNativeLoadSpecOfInPhase(sym))
else
None
}
}
}
private def computeJSNativeLoadSpecOfInPhase(sym: Symbol)(using Context): js.JSNativeLoadSpec = {
import js.JSNativeLoadSpec.*
val symOwner = sym.owner
// Marks a code path as unexpected because it should have been reported as an error in `PrepJSInterop`.
def unexpected(msg: String): Nothing =
throw new FatalError(i"$msg for ${sym.fullName} at ${sym.srcPos}")
if (symOwner.hasAnnotation(jsdefn.JSNativeAnnot)) {
val jsName = sym.jsName match {
case JSName.Literal(jsName) => jsName
case JSName.Computed(_) => unexpected("could not read the simple JS name as a string literal")
}
if (symOwner.hasAnnotation(jsdefn.JSGlobalScopeAnnot)) {
Global(jsName, Nil)
} else {
val ownerLoadSpec = computeJSNativeLoadSpecOfInPhase(symOwner)
ownerLoadSpec match {
case Global(globalRef, path) =>
Global(globalRef, path :+ jsName)
case Import(module, path) =>
Import(module, path :+ jsName)
case ImportWithGlobalFallback(Import(module, modulePath), Global(globalRef, globalPath)) =>
ImportWithGlobalFallback(
Import(module, modulePath :+ jsName),
Global(globalRef, globalPath :+ jsName))
}
}
} else {
def parsePath(pathName: String): List[String] =
pathName.split('.').toList
def parseGlobalPath(pathName: String): Global = {
val globalRef :: path = parsePath(pathName): @unchecked
Global(globalRef, path)
}
val annot = sym.annotations.find { annot =>
annot.symbol == jsdefn.JSGlobalAnnot || annot.symbol == jsdefn.JSImportAnnot
}.getOrElse {
unexpected("could not find the JS native load spec annotation")
}
if (annot.symbol == jsdefn.JSGlobalAnnot) {
val pathName = annot.argumentConstantString(0).getOrElse {
sym.defaultJSName
}
parseGlobalPath(pathName)
} else { // annot.symbol == jsdefn.JSImportAnnot
val module = annot.argumentConstantString(0).getOrElse {
unexpected("could not read the module argument as a string literal")
}
val path = annot.argumentConstantString(1).fold {
if (annot.arguments.sizeIs < 2)
parsePath(sym.defaultJSName)
else
Nil
} { pathName =>
parsePath(pathName)
}
val importSpec = Import(module, path)
annot.argumentConstantString(2).fold[js.JSNativeLoadSpec] {
importSpec
} { globalPathName =>
ImportWithGlobalFallback(importSpec, parseGlobalPath(globalPathName))
}
}
}
}
private def isMethodStaticInIR(sym: Symbol): Boolean =
sym.is(JavaStatic) || sym.isScalaStatic
/** Generate a Class[_] value (e.g. coming from classOf[T]) */
private def genClassConstant(tpe: Type)(implicit pos: Position): js.Tree =
js.ClassOf(toTypeRef(tpe))
private def isStaticModule(sym: Symbol): Boolean =
sym.is(Module) && sym.isStatic
private def isPrimitiveValueType(tpe: Type): Boolean = {
tpe.widenDealias match {
case JavaArrayType(_) => false
case _: ErasedValueType => false
case t => t.typeSymbol.asClass.isPrimitiveValueClass
}
}
protected lazy val isHijackedClass: Set[Symbol] = {
/* This list is a duplicate of ir.Definitions.HijackedClasses, but
* with global.Symbol's instead of IR encoded names as Strings.
* We also add java.lang.Void, which BoxedUnit "erases" to.
*/
Set[Symbol](
defn.BoxedUnitClass, defn.BoxedBooleanClass, defn.BoxedCharClass, defn.BoxedByteClass,
defn.BoxedShortClass, defn.BoxedIntClass, defn.BoxedLongClass, defn.BoxedFloatClass,
defn.BoxedDoubleClass, defn.StringClass, jsdefn.JavaLangVoidClass
)
}
private def isMaybeJavaScriptException(tpe: Type): Boolean =
jsdefn.JavaScriptExceptionClass.isSubClass(tpe.typeSymbol)
private def hasDefaultCtorArgsAndJSModule(classSym: Symbol): Boolean = {
def hasNativeCompanion =
classSym.companionModule.moduleClass.hasAnnotation(jsdefn.JSNativeAnnot)
def hasDefaultParameters =
classSym.info.decls.exists(sym => sym.isClassConstructor && sym.hasDefaultParams)
hasNativeCompanion && hasDefaultParameters
}
// Copied from DottyBackendInterface
private val desugared = new java.util.IdentityHashMap[Type, tpd.Select]
def desugarIdent(i: Ident): Option[tpd.Select] = {
var found = desugared.get(i.tpe)
if (found == null) {
tpd.desugarIdent(i) match {
case sel: tpd.Select =>
desugared.put(i.tpe, sel)
found = sel
case _ =>
}
}
if (found == null) None else Some(found)
}
}
object JSCodeGen {
private val NullPointerExceptionClass = ClassName("java.lang.NullPointerException")
private val JSObjectClassName = ClassName("scala.scalajs.js.Object")
private val JavaScriptExceptionClassName = ClassName("scala.scalajs.js.JavaScriptException")
private val ObjectClassRef = jstpe.ClassRef(ir.Names.ObjectClass)
private val newSimpleMethodName = SimpleMethodName("new")
private val selectedValueMethodName = MethodName("selectedValue", Nil, ObjectClassRef)
private val ObjectArgConstructorName = MethodName.constructor(List(ObjectClassRef))
private val thisOriginalName = OriginalName("this")
sealed abstract class MaybeGlobalScope
object MaybeGlobalScope {
final case class NotGlobalScope(tree: js.Tree) extends MaybeGlobalScope
final case class GlobalScope(pos: SourcePosition) extends MaybeGlobalScope
}
/** Marker object for undefined parameters in JavaScript semantic calls.
*
* To be used inside a `js.Transient` node.
*/
case object UndefinedParam extends js.Transient.Value {
val tpe: jstpe.Type = jstpe.UndefType
def traverse(traverser: ir.Traversers.Traverser): Unit = ()
def transform(transformer: ir.Transformers.Transformer, isStat: Boolean)(
implicit pos: ir.Position): js.Tree = {
js.Transient(this)
}
def printIR(out: ir.Printers.IRTreePrinter): Unit =
out.print("")
}
/** Info about a default param accessor.
*
* The method must have a default getter name for this class to make sense.
*/
private class DefaultParamInfo(sym: Symbol)(using Context) {
private val methodName = sym.name.exclude(DefaultGetterName)
def isForConstructor: Boolean = methodName == nme.CONSTRUCTOR
/** When `isForConstructor` is true, returns the owner of the attached
* constructor.
*/
def constructorOwner: Symbol = sym.owner.linkedClass
/** When `isForConstructor` is false, returns the method attached to the
* specified default accessor.
*/
def attachedMethod: Symbol = {
// If there are overloads, we need to find the one that has default params.
val overloads = sym.owner.info.decl(methodName)
if (!overloads.isOverloaded)
overloads.symbol
else
overloads.suchThat(_.is(HasDefaultParams, butNot = Bridge)).symbol
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy