org.scalajs.nscplugin.GenJSExports.scala Maven / Gradle / Ivy
The newest version!
/*
* Scala.js (https://www.scala-js.org/)
*
* Copyright EPFL.
*
* Licensed under Apache License 2.0
* (https://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package org.scalajs.nscplugin
import scala.collection.{immutable, mutable}
import scala.tools.nsc._
import scala.math.PartialOrdering
import scala.reflect.{ClassTag, classTag}
import scala.reflect.internal.Flags
import org.scalajs.ir
import org.scalajs.ir.{Trees => js, Types => jstpe}
import org.scalajs.ir.Names.LocalName
import org.scalajs.ir.OriginalName.NoOriginalName
import org.scalajs.ir.Trees.OptimizerHints
import org.scalajs.ir.Version.Unversioned
import org.scalajs.nscplugin.util.ScopedVar
import ScopedVar.withScopedVars
/** Generation of exports for JavaScript
*
* @author Sébastien Doeraene
*/
trait GenJSExports[G <: Global with Singleton] extends SubComponent {
self: GenJSCode[G] =>
import global._
import jsAddons._
import definitions._
import jsDefinitions._
import jsInterop.{jsNameOf, JSName}
trait JSExportsPhase { this: JSCodePhase =>
/** Generates exported methods and properties for a class.
*
* @param classSym symbol of the class we export for
*/
def genMemberExports(classSym: Symbol): List[js.JSMethodPropDef] = {
val allExports = classSym.info.members.filter(jsInterop.isExport(_))
val newlyDecldExports = if (classSym.superClass == NoSymbol) {
allExports
} else {
allExports.filterNot { sym =>
classSym.superClass.info.member(sym.name)
.filter(_.tpe =:= sym.tpe).exists
}
}
val newlyDecldExportNames =
newlyDecldExports.map(_.name.toTermName).toList.distinct
newlyDecldExportNames map { genMemberExport(classSym, _) }
}
def genJSClassDispatchers(classSym: Symbol,
dispatchMethodsNames: List[JSName]): List[js.JSMethodPropDef] = {
dispatchMethodsNames
.map(genJSClassDispatcher(classSym, _))
}
private sealed trait ExportKind
private object ExportKind {
case object Module extends ExportKind
case object JSClass extends ExportKind
case object Constructor extends ExportKind
case object Method extends ExportKind
case object Property extends ExportKind
case object Field extends ExportKind
def apply(sym: Symbol): ExportKind = {
if (isStaticModule(sym)) Module
else if (sym.isClass) JSClass
else if (sym.isConstructor) Constructor
else if (!sym.isMethod) Field
else if (jsInterop.isJSProperty(sym)) Property
else Method
}
}
private def checkSameKind(tups: List[(jsInterop.ExportInfo, Symbol)]): Option[ExportKind] = {
assert(tups.nonEmpty, "must have at least one export")
val firstSym = tups.head._2
val overallKind = ExportKind(firstSym)
var bad = false
for ((info, sym) <- tups.tail) {
val kind = ExportKind(sym)
if (kind != overallKind) {
bad = true
reporter.error(info.pos, "export overload conflicts with export of " +
s"$firstSym: they are of different types ($kind / $overallKind)")
}
}
if (bad) None
else Some(overallKind)
}
private def checkSingleField(tups: List[(jsInterop.ExportInfo, Symbol)]): Symbol = {
assert(tups.nonEmpty, "must have at least one export")
val firstSym = tups.head._2
for ((info, _) <- tups.tail) {
reporter.error(info.pos, "export overload conflicts with export of " +
s"$firstSym: a field may not share its exported name with another export")
}
firstSym
}
def genTopLevelExports(classSym: Symbol): List[js.TopLevelExportDef] = {
val exports = for {
sym <- List(classSym) ++ classSym.info.members
info <- jsInterop.topLevelExportsOf(sym)
} yield {
(info, sym)
}
for {
(info, tups) <- stableGroupByWithoutHashCode(exports)(_._1)
kind <- checkSameKind(tups)
} yield {
import ExportKind._
implicit val pos = info.pos
kind match {
case Module =>
js.TopLevelModuleExportDef(info.moduleID, info.jsName)
case JSClass =>
assert(isNonNativeJSClass(classSym), "found export on non-JS class")
js.TopLevelJSClassExportDef(info.moduleID, info.jsName)
case Constructor | Method =>
val methodDef = withNewLocalNameScope {
genExportMethod(tups.map(_._2), JSName.Literal(info.jsName), static = true,
allowCallsiteInlineSingle = false)
}
js.TopLevelMethodExportDef(info.moduleID, methodDef)
case Property =>
throw new AssertionError("found top-level exported property")
case Field =>
val sym = checkSingleField(tups)
js.TopLevelFieldExportDef(info.moduleID, info.jsName, encodeFieldSym(sym))
}
}
}
def genStaticExports(classSym: Symbol): (List[js.JSFieldDef], List[js.JSMethodPropDef]) = {
val exports = (for {
sym <- classSym.info.members
info <- jsInterop.staticExportsOf(sym)
} yield {
(info, sym)
}).toList
val fields = List.newBuilder[js.JSFieldDef]
val methodProps = List.newBuilder[js.JSMethodPropDef]
for {
(info, tups) <- stableGroupByWithoutHashCode(exports)(_._1)
kind <- checkSameKind(tups)
} {
def alts = tups.map(_._2)
implicit val pos = info.pos
import ExportKind._
kind match {
case Method =>
methodProps += genMemberExportOrDispatcher(
JSName.Literal(info.jsName), isProp = false, alts, static = true,
allowCallsiteInlineSingle = false)
case Property =>
methodProps += genMemberExportOrDispatcher(
JSName.Literal(info.jsName), isProp = true, alts, static = true,
allowCallsiteInlineSingle = false)
case Field =>
val sym = checkSingleField(tups)
// static fields must always be mutable
val flags = js.MemberFlags.empty
.withNamespace(js.MemberNamespace.PublicStatic)
.withMutable(true)
val name = js.StringLiteral(info.jsName)
val irTpe = genExposedFieldIRType(sym)
fields += js.JSFieldDef(flags, name, irTpe)
case kind =>
throw new AssertionError(s"unexpected static export kind: $kind")
}
}
(fields.result(), methodProps.result())
}
private def genMemberExport(classSym: Symbol, name: TermName): js.JSMethodPropDef = {
/* This used to be `.member(name)`, but it caused #3538, since we were
* sometimes selecting mixin forwarders, whose type history does not go
* far enough back in time to see varargs. We now explicitly exclude
* mixed-in members in addition to bridge methods (the latter are always
* excluded by `.member(name)`).
*/
val alts = classSym.info.memberBasedOnName(name,
excludedFlags = Flags.BRIDGE | Flags.MIXEDIN).alternatives
assert(!alts.isEmpty,
s"Ended up with no alternatives for ${classSym.fullName}::$name. " +
s"Original set was ${alts} with types ${alts.map(_.tpe)}")
val (jsName, isProp) = jsInterop.jsExportInfo(name)
// Check if we have a conflicting export of the other kind
val conflicting =
classSym.info.member(jsInterop.scalaExportName(jsName, !isProp))
if (conflicting != NoSymbol) {
val kind = if (isProp) "property" else "method"
val alts = conflicting.alternatives
reporter.error(alts.head.pos,
s"Exported $kind $jsName conflicts with ${alts.head.fullName}")
}
genMemberExportOrDispatcher(JSName.Literal(jsName), isProp, alts,
static = false, allowCallsiteInlineSingle = false)
}
private def genJSClassDispatcher(classSym: Symbol, name: JSName): js.JSMethodPropDef = {
val alts = classSym.info.members.toList.filter { sym =>
sym.isMethod &&
!sym.isBridge &&
/* #3939: Object is not a "real" superclass of JS types.
* as such, its methods do not participate in overload resolution.
* An exception is toString, which is handled specially in
* genExportMethod.
*/
sym.owner != ObjectClass &&
jsNameOf(sym) == name
}
assert(!alts.isEmpty,
s"Ended up with no alternatives for ${classSym.fullName}::$name.")
val (propSyms, methodSyms) = alts.partition(jsInterop.isJSProperty(_))
val isProp = propSyms.nonEmpty
if (isProp && methodSyms.nonEmpty) {
reporter.error(alts.head.pos,
s"Conflicting properties and methods for ${classSym.fullName}::$name.")
implicit val pos = alts.head.pos
js.JSPropertyDef(js.MemberFlags.empty, genExpr(name), None, None)(Unversioned)
} else {
genMemberExportOrDispatcher(name, isProp, alts, static = false,
allowCallsiteInlineSingle = true)
}
}
def genMemberExportOrDispatcher(jsName: JSName, isProp: Boolean,
alts: List[Symbol], static: Boolean,
allowCallsiteInlineSingle: Boolean): js.JSMethodPropDef = {
withNewLocalNameScope {
if (isProp)
genExportProperty(alts, jsName, static, allowCallsiteInlineSingle)
else
genExportMethod(alts, jsName, static, allowCallsiteInlineSingle)
}
}
private def genExportProperty(alts: List[Symbol], jsName: JSName,
static: Boolean, allowCallsiteInlineSingle: Boolean): js.JSPropertyDef = {
assert(!alts.isEmpty,
s"genExportProperty with empty alternatives for $jsName")
implicit val pos = alts.head.pos
val namespace =
if (static) js.MemberNamespace.PublicStatic
else js.MemberNamespace.Public
val flags = js.MemberFlags.empty.withNamespace(namespace)
// Separate getters and setters. Somehow isJSGetter doesn't work here. Hence
// we just check the parameter list length.
val (getter, setters) = alts.partition(_.tpe.params.isEmpty)
// We can have at most one getter
if (getter.size > 1)
reportCannotDisambiguateError(jsName, alts)
val getterBody = getter.headOption.map { getterSym =>
genApplyForSym(new FormalArgsRegistry(0, false), getterSym, static,
inline = allowCallsiteInlineSingle)
}
val setterArgAndBody = {
if (setters.isEmpty) {
None
} else {
val formalArgsRegistry = new FormalArgsRegistry(1, false)
val (List(arg), None) = formalArgsRegistry.genFormalArgs()
val body = {
if (setters.size == 1) {
genApplyForSym(formalArgsRegistry, setters.head, static,
inline = allowCallsiteInlineSingle)
} else {
genOverloadDispatchSameArgc(jsName, formalArgsRegistry,
alts = setters.map(new ExportedSymbol(_, static)), jstpe.AnyType,
paramIndex = 0)
}
}
Some((arg, body))
}
}
js.JSPropertyDef(flags, genExpr(jsName), getterBody, setterArgAndBody)(Unversioned)
}
/** generates the exporter function (i.e. exporter for non-properties) for
* a given name */
private def genExportMethod(alts0: List[Symbol], jsName: JSName,
static: Boolean, allowCallsiteInlineSingle: Boolean): js.JSMethodDef = {
assert(alts0.nonEmpty,
"need at least one alternative to generate exporter method")
implicit val pos = alts0.head.pos
val namespace =
if (static) js.MemberNamespace.PublicStatic
else js.MemberNamespace.Public
val flags = js.MemberFlags.empty.withNamespace(namespace)
val alts = {
// toString() is always exported. We might need to add it here
// to get correct overloading.
val needsToString =
jsName == JSName.Literal("toString") && alts0.forall(_.tpe.params.nonEmpty)
if (needsToString)
Object_toString :: alts0
else
alts0
}
val overloads = alts.map(new ExportedSymbol(_, static))
val (formalArgs, restParam, body) = {
if (overloads.size == 1) {
val trg = overloads.head
val minArgc = trg.params.lastIndexWhere(p => !p.hasDefault && !p.repeated) + 1
val formalArgsRegistry = new FormalArgsRegistry(minArgc,
needsRestParam = trg.params.size > minArgc)
val body = genApplyForSym(formalArgsRegistry, trg.sym, static,
inline = allowCallsiteInlineSingle)
val (formalArgs, restParam) = formalArgsRegistry.genFormalArgs()
(formalArgs, restParam, body)
} else {
genOverloadDispatch(jsName, overloads, jstpe.AnyType)
}
}
js.JSMethodDef(flags, genExpr(jsName), formalArgs, restParam, body)(
OptimizerHints.empty, Unversioned)
}
def genOverloadDispatch(jsName: JSName, alts: List[Exported], tpe: jstpe.Type)(
implicit pos: Position): (List[js.ParamDef], Option[js.ParamDef], js.Tree) = {
// Factor out methods with variable argument lists. Note that they can
// only be at the end of the lists as enforced by PrepJSExports
val (varArgMeths, normalMeths) = alts.partition(_.hasRepeatedParam)
// Highest non-repeated argument count
val maxArgc = (
// We have argc - 1, since a repeated parameter list may also be empty
// (unlike a normal parameter)
varArgMeths.map(_.params.size - 1) ++
normalMeths.map(_.params.size)
).max
// Calculates possible arg counts for normal method
def argCounts(ex: Exported) = {
val params = ex.params
// Find default param
val dParam = params.indexWhere(_.hasDefault)
if (dParam == -1) Seq(params.size)
else dParam to params.size
}
// Generate tuples (argc, method)
val methodArgCounts = {
// Normal methods
for {
method <- normalMeths
argc <- argCounts(method)
} yield (argc, method)
} ++ {
// Repeated parameter methods
for {
method <- varArgMeths
argc <- method.params.size - 1 to maxArgc
} yield (argc, method)
}
/** Like groupBy, but returns a sorted List instead of an unordered Map. */
def sortedGroupBy[A, K, O](xs: List[A])(grouper: A => K)(
sorter: ((K, List[A])) => O)(implicit ord: Ordering[O]): List[(K, List[A])] = {
xs.groupBy(grouper).toList.sortBy(sorter)
}
/* Create tuples: (argCount, methods).
* Methods may appear multiple times.
*
* The method lists preserve the order out of `methodArgCounts`, so if
* two such lists contain the same set of methods, they are equal.
*
* The resulting list is sorted by argCount. This is both for stability
* and because we then rely on the fact that the head is the minimum.
*/
val methodByArgCount: List[(Int, List[Exported])] = {
sortedGroupBy(methodArgCounts)(_._1)(_._1) // sort by the Int
.map(kv => kv._1 -> kv._2.map(_._2)) // preserves the relative order of the Exported's
}
// Create the formal args registry
val minArgc = methodByArgCount.head._1 // it is sorted by argCount, so the head is the minimum
val hasVarArg = varArgMeths.nonEmpty
val needsRestParam = maxArgc != minArgc || hasVarArg
val formalArgsRegistry = new FormalArgsRegistry(minArgc, needsRestParam)
// List of formal parameters
val (formalArgs, restParam) = formalArgsRegistry.genFormalArgs()
/* Create tuples: (methods, argCounts). These will be the cases we generate.
*
* Within each element, the argCounts are sorted. (This sort order is
* inherited from the sort order of `methodByArgCount`.)
*
* For stability, the list as a whole is sorted by the minimum (head) of
* argCounts.
*/
val caseDefinitions: List[(List[Exported], List[Int])] = {
sortedGroupBy(methodByArgCount)(_._2)(_._2.head._1) // sort by the minimum of the Ints
.map(kv => kv._1 -> kv._2.map(_._1)) // the Ints are still sorted from `methodByArgCount`
}
// Verify stuff about caseDefinitions
assert({
val argcs = caseDefinitions.map(_._2).flatten
argcs == argcs.distinct &&
argcs.forall(_ <= maxArgc)
}, "every argc should appear only once and be lower than max")
/* We will avoid generating cases where the set of methods is exactly the
* the set of varargs methods. Since all the `Exported` in `alts`, and
* hence in `varArgMeths` and `methods`, are distinct, we can do
* something faster than converting both sides to sets.
*/
def isSameAsVarArgMethods(methods: List[Exported]): Boolean =
methods.size == varArgMeths.size && methods.forall(varArgMeths.contains(_))
// Generate a case block for each (methods, argCounts) tuple
val cases: List[(List[js.IntLiteral], js.Tree)] = for {
(methods, argcs) <- caseDefinitions
if methods.nonEmpty && argcs.nonEmpty && !isSameAsVarArgMethods(methods)
} yield {
val argcAlternatives = argcs.map(argc => js.IntLiteral(argc - minArgc))
// body of case to disambiguate methods with current count
val maxUsableArgc = argcs.head // i.e., the *minimum* of the argcs here
val caseBody = genOverloadDispatchSameArgc(jsName, formalArgsRegistry,
methods, tpe, paramIndex = 0, Some(maxUsableArgc))
(argcAlternatives, caseBody)
}
def defaultCase = {
if (!hasVarArg) {
genThrowTypeError()
} else {
genOverloadDispatchSameArgc(jsName, formalArgsRegistry, varArgMeths,
tpe, paramIndex = 0)
}
}
val body = {
if (cases.isEmpty)
defaultCase
else if (cases.size == 1 && !hasVarArg)
cases.head._2
else {
assert(needsRestParam,
"Trying to read rest param length but needsRestParam is false")
val restArgRef = formalArgsRegistry.genRestArgRef()
js.Match(
js.AsInstanceOf(js.JSSelect(restArgRef, js.StringLiteral("length")), jstpe.IntType),
cases, defaultCase)(tpe)
}
}
(formalArgs, restParam, body)
}
/**
* Resolve method calls to [[alts]] while assuming they have the same
* parameter count.
* @param minArgc The minimum number of arguments that must be given
* @param alts Alternative methods
* @param paramIndex Index where to start disambiguation
* @param maxArgc only use that many arguments
*/
private def genOverloadDispatchSameArgc(jsName: JSName,
formalArgsRegistry: FormalArgsRegistry, alts: List[Exported],
tpe: jstpe.Type, paramIndex: Int, maxArgc: Option[Int] = None): js.Tree = {
implicit val pos = alts.head.sym.pos
if (alts.size == 1) {
alts.head.genBody(formalArgsRegistry)
} else if (maxArgc.exists(_ <= paramIndex) ||
!alts.exists(_.params.size > paramIndex)) {
// We reach here in three cases:
// 1. The parameter list has been exhausted
// 2. The optional argument count restriction has triggered
// 3. We only have (more than once) repeated parameters left
// Therefore, we should fail
reportCannotDisambiguateError(jsName, alts.map(_.sym))
js.Undefined()
} else {
val altsByTypeTest = stableGroupByWithoutHashCode(alts) { exported =>
typeTestForTpe(exported.exportArgTypeAt(paramIndex))
}
if (altsByTypeTest.size == 1) {
// Testing this parameter is not doing any us good
genOverloadDispatchSameArgc(jsName, formalArgsRegistry, alts, tpe,
paramIndex + 1, maxArgc)
} else {
// Sort them so that, e.g., isInstanceOf[String]
// comes before isInstanceOf[Object]
val sortedAltsByTypeTest = topoSortDistinctsWith(altsByTypeTest) { (lhs, rhs) =>
(lhs._1, rhs._1) match {
// NoTypeTest is always last
case (_, NoTypeTest) => true
case (NoTypeTest, _) => false
case (PrimitiveTypeTest(_, rank1), PrimitiveTypeTest(_, rank2)) =>
rank1 <= rank2
case (InstanceOfTypeTest(t1), InstanceOfTypeTest(t2)) =>
t1 <:< t2
case (_: PrimitiveTypeTest, _: InstanceOfTypeTest) => true
case (_: InstanceOfTypeTest, _: PrimitiveTypeTest) => false
}
}
val defaultCase = genThrowTypeError()
sortedAltsByTypeTest.foldRight[js.Tree](defaultCase) { (elem, elsep) =>
val (typeTest, subAlts) = elem
implicit val pos = subAlts.head.sym.pos
val paramRef = formalArgsRegistry.genArgRef(paramIndex)
val genSubAlts = genOverloadDispatchSameArgc(jsName, formalArgsRegistry,
subAlts, tpe, paramIndex + 1, maxArgc)
def hasDefaultParam = subAlts.exists { exported =>
val params = exported.params
params.size > paramIndex &&
params(paramIndex).hasDefault
}
val optCond = typeTest match {
case PrimitiveTypeTest(tpe, _) =>
Some(js.IsInstanceOf(paramRef, tpe))
case InstanceOfTypeTest(tpe) =>
Some(genIsInstanceOf(paramRef, tpe))
case NoTypeTest =>
None
}
optCond.fold[js.Tree] {
genSubAlts // note: elsep is discarded, obviously
} { cond =>
val condOrUndef = if (!hasDefaultParam) cond else {
js.If(cond, js.BooleanLiteral(true),
js.BinaryOp(js.BinaryOp.===, paramRef, js.Undefined()))(
jstpe.BooleanType)
}
js.If(condOrUndef, genSubAlts, elsep)(tpe)
}
}
}
}
}
private def reportCannotDisambiguateError(jsName: JSName,
alts: List[Symbol]): Unit = {
val currentClass = currentClassSym.get
/* Find a position that is in the current class for decent error reporting.
* If there are more than one, always use the "highest" one (i.e., the
* one coming last in the source text) so that we reliably display the
* same error in all compilers.
*/
val validPositions = alts.collect {
case alt if alt.owner == currentClass => alt.pos
}
val pos =
if (validPositions.isEmpty) currentClass.pos
else validPositions.maxBy(_.point)
val kind =
if (jsInterop.isJSGetter(alts.head)) "getter"
else if (jsInterop.isJSSetter(alts.head)) "setter"
else "method"
val fullKind =
if (isNonNativeJSClass(currentClass)) kind
else "exported " + kind
val displayName = jsName.displayName
val altsTypesInfo = alts.map(_.tpe.toString).sorted.mkString("\n ")
reporter.error(pos,
s"Cannot disambiguate overloads for $fullKind $displayName with types\n" +
s" $altsTypesInfo")
}
/**
* Generate a call to the method [[sym]] while using the formalArguments
* and potentially the argument array. Also inserts default parameters if
* required.
*/
private def genApplyForSym(formalArgsRegistry: FormalArgsRegistry,
sym: Symbol, static: Boolean, inline: Boolean): js.Tree = {
if (isNonNativeJSClass(currentClassSym) &&
sym.owner != currentClassSym.get) {
assert(!static, s"nonsensical JS super call in static export of $sym")
genApplyForSymJSSuperCall(formalArgsRegistry, sym)
} else {
genApplyForSymNonJSSuperCall(formalArgsRegistry, sym, static, inline)
}
}
private def genApplyForSymJSSuperCall(
formalArgsRegistry: FormalArgsRegistry, sym: Symbol): js.Tree = {
implicit val pos = sym.pos
assert(!sym.isClassConstructor,
"Trying to genApplyForSymJSSuperCall for the constructor " +
sym.fullName)
val allArgs = formalArgsRegistry.genAllArgsRefsForForwarder()
val superClass = {
val superClassSym = currentClassSym.superClass
if (isNestedJSClass(superClassSym)) {
js.VarRef(js.LocalIdent(JSSuperClassParamName))(jstpe.AnyType)
} else {
js.LoadJSConstructor(encodeClassName(superClassSym))
}
}
val receiver = js.This()(currentThisType)
val nameString = genExpr(jsNameOf(sym))
if (jsInterop.isJSGetter(sym)) {
assert(allArgs.isEmpty,
s"getter symbol $sym does not have a getter signature")
js.JSSuperSelect(superClass, receiver, nameString)
} else if (jsInterop.isJSSetter(sym)) {
assert(allArgs.size == 1 && allArgs.head.isInstanceOf[js.Tree],
s"setter symbol $sym does not have a setter signature")
js.Assign(js.JSSuperSelect(superClass, receiver, nameString),
allArgs.head.asInstanceOf[js.Tree])
} else {
js.JSSuperMethodCall(superClass, receiver, nameString, allArgs)
}
}
private def genApplyForSymNonJSSuperCall(
formalArgsRegistry: FormalArgsRegistry, sym: Symbol,
static: Boolean, inline: Boolean): js.Tree = {
implicit val pos = sym.pos
val varDefs = new mutable.ListBuffer[js.VarDef]
for ((param, i) <- jsParamInfos(sym).zipWithIndex) {
val rhs = genScalaArg(sym, i, formalArgsRegistry, param, static, captures = Nil)(
prevArgsCount => varDefs.take(prevArgsCount).toList.map(_.ref))
varDefs += js.VarDef(freshLocalIdent("prep" + i), NoOriginalName,
rhs.tpe, mutable = false, rhs)
}
val builtVarDefs = varDefs.result()
val jsResult = genResult(sym, builtVarDefs.map(_.ref), static, inline)
js.Block(builtVarDefs :+ jsResult)
}
/** Generates a Scala argument from dispatched JavaScript arguments
* (unboxing and default parameter handling).
*/
def genScalaArg(methodSym: Symbol, paramIndex: Int,
formalArgsRegistry: FormalArgsRegistry, param: JSParamInfo,
static: Boolean, captures: List[js.Tree])(
previousArgsValues: Int => List[js.Tree])(
implicit pos: Position): js.Tree = {
if (param.repeated) {
genJSArrayToVarArgs(formalArgsRegistry.genVarargRef(paramIndex))
} else {
val jsArg = formalArgsRegistry.genArgRef(paramIndex)
// Unboxed argument (if it is defined)
val unboxedArg = fromAny(jsArg, param.tpe)
if (param.hasDefault) {
// If argument is undefined and there is a default getter, call it
val default = genCallDefaultGetter(methodSym, paramIndex,
param.sym.pos, static, captures)(previousArgsValues)
js.If(js.BinaryOp(js.BinaryOp.===, jsArg, js.Undefined()),
default, unboxedArg)(unboxedArg.tpe)
} else {
// Otherwise, it is always the unboxed argument
unboxedArg
}
}
}
def genCallDefaultGetter(sym: Symbol, paramIndex: Int,
paramPos: Position, static: Boolean, captures: List[js.Tree])(
previousArgsValues: Int => List[js.Tree])(
implicit pos: Position): js.Tree = {
val owner = sym.owner
val isNested = owner.isLifted && !isStaticModule(owner.originalOwner)
val (trgSym, trgTree) = {
if (!sym.isClassConstructor && !static) {
/* Default getter is on the receiver.
*
* Since we only use this method for class internal exports
* dispatchers, we know the default getter is on `this`. This applies
* to both top-level and nested classes.
*/
(owner, js.This()(currentThisType))
} else if (isNested) {
assert(captures.size == 1,
s"expected exactly one capture got $captures ($sym at $pos)")
/* Find the module accessor.
*
* #4465: If owner is a nested class, the linked class is *not* a
* module value, but another class. In this case we need to call the
* module accessor on the enclosing class to retrieve this.
*
* #4526: If the companion module is private, linkedClassOfClass
* does not work (because the module name is prefixed with the full
* path). So we find the module accessor first and take its result
* type to be the companion module type.
*/
val outer = owner.originalOwner
val modAccessor = {
val name = enteringPhase(currentRun.typerPhase) {
owner.unexpandedName.toTermName
}
outer.info.members.find { sym =>
sym.isModule && sym.unexpandedName == name
}.getOrElse {
throw new AssertionError(
s"couldn't find module accessor for ${owner.fullName} at $pos")
}
}
val receiver = captures.head
val trgSym = modAccessor.tpe.resultType.typeSymbol
val trgTree = if (isJSType(outer)) {
genApplyJSClassMethod(receiver, modAccessor, Nil)
} else {
genApplyMethodMaybeStatically(receiver, modAccessor, Nil)
}
(trgSym, trgTree)
} else if (sym.isClassConstructor) {
assert(captures.isEmpty, "expected empty captures")
/* Get the companion module class.
* For classes nested inside modules the sym.owner.companionModule
* can be broken, therefore companionModule is fetched at
* uncurryPhase.
*/
val trgSym = enteringPhase(currentRun.uncurryPhase) {
owner.linkedClassOfClass
}
(trgSym, genLoadModule(trgSym))
} else {
assert(static, "expected static")
assert(captures.isEmpty, "expected empty captures")
(owner, genLoadModule(owner))
}
}
val defaultGetter = trgSym.tpe.member(
nme.defaultGetterName(sym.name, paramIndex + 1))
assert(defaultGetter.exists,
s"need default getter for method ${sym.fullName}")
assert(!defaultGetter.isOverloaded,
s"found overloaded default getter $defaultGetter")
// Pass previous arguments to defaultGetter
val defaultGetterArgs = previousArgsValues(defaultGetter.tpe.params.size)
val callGetter = if (isJSType(trgSym)) {
if (isNonNativeJSClass(defaultGetter.owner)) {
if (defaultGetter.hasAnnotation(JSOptionalAnnotation))
js.Undefined()
else
genApplyJSClassMethod(trgTree, defaultGetter, defaultGetterArgs)
} else if (defaultGetter.owner == trgSym) {
/* We get here if a non-native constructor has a native companion.
* This is reported on a per-class level.
*/
assert(sym.isClassConstructor,
s"got non-constructor method $sym with default method in JS native companion")
js.Undefined()
} else {
reporter.error(paramPos, "When overriding a native method " +
"with default arguments, the overriding method must " +
"explicitly repeat the default arguments.")
js.Undefined()
}
} else {
genApplyMethod(trgTree, defaultGetter, defaultGetterArgs)
}
// #4684 If the getter returns void, we must "box" it by returning undefined
if (callGetter.tpe == jstpe.NoType)
js.Block(callGetter, js.Undefined())
else
callGetter
}
/** Generate the final forwarding call to the exported method. */
private def genResult(sym: Symbol, args: List[js.Tree],
static: Boolean, inline: Boolean)(implicit pos: Position): js.Tree = {
def receiver = {
if (static)
genLoadModule(sym.owner)
else
js.This()(currentThisType)
}
if (isNonNativeJSClass(currentClassSym)) {
assert(sym.owner == currentClassSym.get, sym.fullName)
ensureResultBoxed(genApplyJSClassMethod(receiver, sym, args, inline = inline), sym)
} else {
if (sym.isClassConstructor)
genNew(currentClassSym, sym, args)
else if (sym.isPrivate)
ensureResultBoxed(genApplyMethodStatically(receiver, sym, args, inline = inline), sym)
else
ensureResultBoxed(genApplyMethod(receiver, sym, args, inline = inline), sym)
}
}
// Note: GenJSCode creates an anonymous subclass of Exported for JS class constructors.
abstract class Exported(val sym: Symbol,
// Parameters participating in overload resolution.
val params: immutable.IndexedSeq[JSParamInfo]) {
assert(!params.exists(_.capture), "illegal capture params in Exported")
final def exportArgTypeAt(paramIndex: Int): Type = {
if (paramIndex < params.length) {
params(paramIndex).tpe
} else {
assert(hasRepeatedParam,
s"$sym does not have varargs nor enough params for $paramIndex")
params.last.tpe
}
}
def genBody(formalArgsRegistry: FormalArgsRegistry): js.Tree
lazy val hasRepeatedParam = params.lastOption.exists(_.repeated)
}
private class ExportedSymbol(sym: Symbol, static: Boolean)
extends Exported(sym, jsParamInfos(sym).toIndexedSeq) {
def genBody(formalArgsRegistry: FormalArgsRegistry): js.Tree =
genApplyForSym(formalArgsRegistry, sym, static, inline = false)
}
}
private sealed abstract class RTTypeTest
private case class PrimitiveTypeTest(tpe: jstpe.Type, rank: Int)
extends RTTypeTest
// scalastyle:off equals.hash.code
private case class InstanceOfTypeTest(tpe: Type) extends RTTypeTest {
override def equals(that: Any): Boolean = {
that match {
case InstanceOfTypeTest(thatTpe) => tpe =:= thatTpe
case _ => false
}
}
}
// scalastyle:on equals.hash.code
private case object NoTypeTest extends RTTypeTest
// Very simple O(n²) topological sort for elements assumed to be distinct
private def topoSortDistinctsWith[A <: AnyRef](coll: List[A])(
lteq: (A, A) => Boolean): List[A] = {
@scala.annotation.tailrec
def loop(coll: List[A], acc: List[A]): List[A] = {
if (coll.isEmpty) acc
else if (coll.tail.isEmpty) coll.head :: acc
else {
val (lhs, rhs) = coll.span(x => !coll.forall(
y => (x eq y) || !lteq(x, y)))
assert(!rhs.isEmpty, s"cycle while ordering $coll")
loop(lhs ::: rhs.tail, rhs.head :: acc)
}
}
loop(coll, Nil)
}
private def typeTestForTpe(tpe: Type): RTTypeTest = {
tpe match {
case tpe: ErasedValueType =>
InstanceOfTypeTest(tpe.valueClazz.typeConstructor)
case _ =>
import org.scalajs.ir.Names
(toIRType(tpe): @unchecked) match {
case jstpe.AnyType => NoTypeTest
case jstpe.NoType => PrimitiveTypeTest(jstpe.UndefType, 0)
case jstpe.BooleanType => PrimitiveTypeTest(jstpe.BooleanType, 1)
case jstpe.CharType => PrimitiveTypeTest(jstpe.CharType, 2)
case jstpe.ByteType => PrimitiveTypeTest(jstpe.ByteType, 3)
case jstpe.ShortType => PrimitiveTypeTest(jstpe.ShortType, 4)
case jstpe.IntType => PrimitiveTypeTest(jstpe.IntType, 5)
case jstpe.LongType => PrimitiveTypeTest(jstpe.LongType, 6)
case jstpe.FloatType => PrimitiveTypeTest(jstpe.FloatType, 7)
case jstpe.DoubleType => PrimitiveTypeTest(jstpe.DoubleType, 8)
case jstpe.ClassType(Names.BoxedUnitClass) => PrimitiveTypeTest(jstpe.UndefType, 0)
case jstpe.ClassType(Names.BoxedStringClass) => PrimitiveTypeTest(jstpe.StringType, 9)
case jstpe.ClassType(_) => InstanceOfTypeTest(tpe)
case jstpe.ArrayType(_) => InstanceOfTypeTest(tpe)
}
}
}
/** Stable group-by that does not rely on hashCode(), only equals() - O(n²).
*
* In addition to preserving the relative order of elements in the value
* lists (like `groupBy`), this stable group-by also preserves the relative
* order of they keys, by their first appearance in the collection.
*/
private def stableGroupByWithoutHashCode[A, B](
coll: List[A])(f: A => B): List[(B, List[A])] = {
import scala.collection.mutable.{ArrayBuffer, Builder}
val m = new ArrayBuffer[(B, Builder[A, List[A]])]
m.sizeHint(coll.length)
for (elem <- coll) {
val key = f(elem)
val index = m.indexWhere(_._1 == key)
if (index < 0)
m += ((key, List.newBuilder[A] += elem))
else
m(index)._2 += elem
}
m.toList.map(kv => kv._1 -> kv._2.result())
}
private def genThrowTypeError(msg: String = "No matching overload")(
implicit pos: Position): js.Tree = {
js.Throw(js.StringLiteral(msg))
}
class FormalArgsRegistry(minArgc: Int, needsRestParam: Boolean) {
private val fixedParamNames: scala.collection.immutable.IndexedSeq[LocalName] =
(0 until minArgc).toIndexedSeq.map(_ => freshLocalIdent("arg")(NoPosition).name)
private val restParamName: LocalName =
if (needsRestParam) freshLocalIdent("rest")(NoPosition).name
else null
def genFormalArgs()(implicit pos: Position): (List[js.ParamDef], Option[js.ParamDef]) = {
val fixedParamDefs = fixedParamNames.toList.map { paramName =>
js.ParamDef(js.LocalIdent(paramName), NoOriginalName, jstpe.AnyType,
mutable = false)
}
val restParam = {
if (needsRestParam) {
Some(js.ParamDef(js.LocalIdent(restParamName),
NoOriginalName, jstpe.AnyType, mutable = false))
} else {
None
}
}
(fixedParamDefs, restParam)
}
def genArgRef(index: Int)(implicit pos: Position): js.Tree = {
if (index < minArgc)
js.VarRef(js.LocalIdent(fixedParamNames(index)))(jstpe.AnyType)
else
js.JSSelect(genRestArgRef(), js.IntLiteral(index - minArgc))
}
def genVarargRef(fixedParamCount: Int)(implicit pos: Position): js.Tree = {
val restParam = genRestArgRef()
assert(fixedParamCount >= minArgc,
s"genVarargRef($fixedParamCount) with minArgc = $minArgc at $pos")
if (fixedParamCount == minArgc) {
restParam
} else {
js.JSMethodApply(restParam, js.StringLiteral("slice"),
List(js.IntLiteral(fixedParamCount - minArgc)))
}
}
def genRestArgRef()(implicit pos: Position): js.Tree = {
assert(needsRestParam,
s"trying to generate a reference to non-existent rest param at $pos")
js.VarRef(js.LocalIdent(restParamName))(jstpe.AnyType)
}
def genAllArgsRefsForForwarder()(implicit pos: Position): List[js.TreeOrJSSpread] = {
val fixedArgRefs = fixedParamNames.toList.map { paramName =>
js.VarRef(js.LocalIdent(paramName))(jstpe.AnyType)
}
if (needsRestParam) {
val restArgRef = js.VarRef(js.LocalIdent(restParamName))(jstpe.AnyType)
fixedArgRefs :+ js.JSSpread(restArgRef)
} else {
fixedArgRefs
}
}
}
}