aboutsummaryrefslogtreecommitdiff
path: root/sjs/backend/sjs/JSEncoding.scala
diff options
context:
space:
mode:
Diffstat (limited to 'sjs/backend/sjs/JSEncoding.scala')
-rw-r--r--sjs/backend/sjs/JSEncoding.scala389
1 files changed, 389 insertions, 0 deletions
diff --git a/sjs/backend/sjs/JSEncoding.scala b/sjs/backend/sjs/JSEncoding.scala
new file mode 100644
index 000000000..e8ea3258b
--- /dev/null
+++ b/sjs/backend/sjs/JSEncoding.scala
@@ -0,0 +1,389 @@
+package dotty.tools.backend.sjs
+
+import scala.collection.mutable
+
+import dotty.tools.FatalError
+
+import dotty.tools.dotc.core._
+import Periods._
+import SymDenotations._
+import Contexts._
+import Types._
+import Symbols._
+import Denotations._
+import NameOps._
+import StdNames._
+
+import org.scalajs.core.ir
+import ir.{Trees => js, Types => jstpe}
+
+import ScopedVar.withScopedVars
+import JSDefinitions._
+import JSInterop._
+
+/** Encoding of symbol names for JavaScript
+ *
+ * Some issues that this encoding solves:
+ * * Overloading: encode the full signature in the JS name
+ * * Same scope for fields and methods of a class
+ * * Global access to classes and modules (by their full name)
+ *
+ * @author Sébastien Doeraene
+ */
+object JSEncoding {
+
+ /** Signature separator string (between parameter types) */
+ private final val SignatureSep = "__"
+
+ /** Name given to the local Scala.js environment variable */
+ private final val ScalaJSEnvironmentName = "ScalaJS"
+
+ implicit class SymOps(val self: Symbol) extends AnyVal {
+ def unexpandedName(implicit ctx: Context): Names.Name =
+ self.name.unexpandedName
+ }
+
+ implicit class MyNameOps(val self: Names.Name) extends AnyVal {
+ def decoded: String = self.decode.toString
+ }
+
+ // Fresh local name generator ----------------------------------------------
+
+ class LocalNameGenerator {
+ import LocalNameGenerator._
+
+ private val usedLocalNames = mutable.Set.empty[String]
+ private val localSymbolNames = mutable.Map.empty[Symbol, String]
+
+ def localSymbolName(sym: Symbol)(implicit ctx: Context): String =
+ localSymbolNames.getOrElseUpdate(sym, freshName(sym.name.toString))
+
+ def freshLocalIdent()(implicit pos: ir.Position): js.Ident =
+ js.Ident(freshName(), None)
+
+ def freshLocalIdent(base: String)(implicit pos: ir.Position): js.Ident =
+ js.Ident(freshName(base), Some(base))
+
+ private def freshName(base: String = "x"): String = {
+ var suffix = 1
+ var longName = base
+ while (usedLocalNames(longName) || isReserved(longName)) {
+ suffix += 1
+ longName = base+"$"+suffix
+ }
+ usedLocalNames += longName
+ mangleJSName(longName)
+ }
+ }
+
+ private object LocalNameGenerator {
+ private val isReserved =
+ Set("arguments", "eval", ScalaJSEnvironmentName)
+ }
+
+ // Encoding methods ----------------------------------------------------------
+
+ def encodeLabelSym(sym: Symbol)(
+ implicit ctx: Context, pos: ir.Position, localNames: LocalNameGenerator): js.Ident = {
+ require(sym.is(Flags.Label), "encodeLabelSym called with non-label symbol: " + sym)
+ js.Ident(localNames.localSymbolName(sym), Some(sym.unexpandedName.decoded))
+ }
+
+ private def allRefClasses(implicit ctx: Context): Set[Symbol] = {
+ //TODO
+ /*(Set(ObjectRefClass, VolatileObjectRefClass) ++
+ refClass.values ++ volatileRefClass.values)*/
+ Set()
+ }
+
+ def encodeFieldSym(sym: Symbol)(
+ implicit ctx: Context, pos: ir.Position): js.Ident = {
+ require(sym.owner.isClass && sym.isTerm && !sym.is(Flags.Method) && !sym.is(Flags.Module),
+ "encodeFieldSym called with non-field symbol: " + sym)
+
+ val name0 = encodeMemberNameInternal(sym)
+ val name =
+ if (name0.charAt(name0.length()-1) != ' ') name0
+ else name0.substring(0, name0.length()-1)
+
+ /* We have to special-case fields of Ref types (IntRef, ObjectRef, etc.)
+ * because they are emitted as private by our .scala source files, but
+ * they are considered public at use site since their symbols come from
+ * Java-emitted .class files.
+ */
+ val idSuffix =
+ if (sym.is(Flags.Private) || allRefClasses.contains(sym.owner))
+ sym.owner.asClass.baseClasses.size.toString
+ else
+ "f"
+
+ val encodedName = name + "$" + idSuffix
+ js.Ident(mangleJSName(encodedName), Some(sym.unexpandedName.decoded))
+ }
+
+ def encodeMethodSym(sym: Symbol, reflProxy: Boolean = false)(
+ implicit ctx: Context, pos: ir.Position): js.Ident = {
+ val (encodedName, paramsString) = encodeMethodNameInternal(sym, reflProxy)
+ js.Ident(encodedName + paramsString,
+ Some(sym.unexpandedName.decoded + paramsString))
+ }
+
+ def encodeMethodName(sym: Symbol, reflProxy: Boolean = false)(
+ implicit ctx: Context): String = {
+ val (encodedName, paramsString) = encodeMethodNameInternal(sym, reflProxy)
+ encodedName + paramsString
+ }
+
+ /** Encodes a method symbol of java.lang.String for use in RuntimeString.
+ *
+ * This basically means adding an initial parameter of type
+ * java.lang.String, which is the `this` parameter.
+ */
+ def encodeRTStringMethodSym(sym: Symbol)(
+ implicit ctx: Context, pos: ir.Position): js.Ident = {
+ require(sym.owner == defn.StringClass)
+ require(!sym.isClassConstructor && !sym.is(Flags.Private))
+
+ val (encodedName, paramsString) =
+ encodeMethodNameInternal(sym, inRTClass = true)
+ js.Ident(encodedName + paramsString,
+ Some(sym.unexpandedName.decoded + paramsString))
+ }
+
+ /** Encodes a constructor symbol of java.lang.String for use in RuntimeString.
+ *
+ * - The name is rerouted to `newString`
+ * - The result type is set to `java.lang.String`
+ */
+ def encodeRTStringCtorSym(sym: Symbol)(
+ implicit ctx: Context, pos: ir.Position): js.Ident = {
+ require(sym.owner == defn.StringClass)
+ require(sym.isClassConstructor && !sym.is(Flags.Private))
+
+ val paramTypeNames = sym.info.firstParamTypes.map(internalName(_))
+ val paramAndResultTypeNames = paramTypeNames :+ ir.Definitions.StringClass
+ val paramsString = makeParamsString(paramAndResultTypeNames)
+
+ js.Ident("newString" + paramsString,
+ Some(sym.unexpandedName.decoded + paramsString))
+ }
+
+ private def encodeMethodNameInternal(sym: Symbol,
+ reflProxy: Boolean = false, inRTClass: Boolean = false)(
+ implicit ctx: Context): (String, String) = {
+ require(sym.is(Flags.Method), "encodeMethodSym called with non-method symbol: " + sym)
+
+ def name = encodeMemberNameInternal(sym)
+
+ val encodedName = {
+ if (sym.isClassConstructor) {
+ "init_"
+ } else if (sym.is(Flags.Private)) {
+ (mangleJSName(name) + SignatureSep + "p" +
+ sym.owner.asClass.baseClasses.size.toString)
+ } else {
+ mangleJSName(name)
+ }
+ }
+
+ val paramsString = makeParamsString(sym, reflProxy, inRTClass)
+
+ (encodedName, paramsString)
+ }
+
+ def encodeStaticMemberSym(sym: Symbol)(
+ implicit ctx: Context, pos: ir.Position): js.Ident = {
+ require(sym.is(Flags.JavaStaticTerm),
+ "encodeStaticMemberSym called with non-static symbol: " + sym)
+ js.Ident(
+ mangleJSName(encodeMemberNameInternal(sym)) +
+ makeParamsString(List(internalName(sym.info))),
+ Some(sym.unexpandedName.decoded))
+ }
+
+ def encodeLocalSym(sym: Symbol)(
+ implicit ctx: Context, pos: ir.Position, localNames: LocalNameGenerator): js.Ident = {
+ require(!sym.owner.isClass && sym.isTerm && !sym.is(Flags.Method) && !sym.is(Flags.Module),
+ "encodeLocalSym called with non-local symbol: " + sym)
+ js.Ident(localNames.localSymbolName(sym), Some(sym.unexpandedName.decoded))
+ }
+
+ def foreignIsImplClass(sym: Symbol)(implicit ctx: Context): Boolean =
+ sym.name.isImplClassName
+
+ def encodeClassType(sym: Symbol)(implicit ctx: Context): jstpe.Type = {
+ if (sym == defn.ObjectClass) jstpe.AnyType
+ else if (isJSType(sym)) jstpe.AnyType
+ else {
+ assert(sym != defn.ArrayClass,
+ "encodeClassType() cannot be called with ArrayClass")
+ jstpe.ClassType(encodeClassFullName(sym))
+ }
+ }
+
+ def encodeClassFullNameIdent(sym: Symbol)(
+ implicit ctx: Context, pos: ir.Position): js.Ident = {
+ js.Ident(encodeClassFullName(sym), Some(sym.fullName.toString))
+ }
+
+ def encodeClassFullName(sym: Symbol)(implicit ctx: Context): String = {
+ if (sym == defn.NothingClass) ir.Definitions.RuntimeNothingClass
+ else if (sym == defn.NullClass) ir.Definitions.RuntimeNullClass
+ else ir.Definitions.encodeClassName(sym.fullName.toString)
+ }
+
+ private def encodeMemberNameInternal(sym: Symbol)(
+ implicit ctx: Context): String = {
+ sym.name.toString.replace("_", "$und").replace("~", "$tilde")
+ }
+
+ def toIRType(tp: Type)(implicit ctx: Context): jstpe.Type = {
+ val refType = toReferenceTypeInternal(tp)
+ refType._1 match {
+ case tpe: jstpe.ClassType =>
+ val sym = refType._2
+ if (sym.asClass.isPrimitiveValueClass) {
+ if (sym == defn.BooleanClass)
+ jstpe.BooleanType
+ else if (sym == defn.FloatClass)
+ jstpe.FloatType
+ else if (sym == defn.DoubleClass)
+ jstpe.DoubleType
+ else if (sym == defn.LongClass)
+ jstpe.LongType
+ else if (sym == defn.UnitClass)
+ jstpe.NoType
+ else
+ jstpe.IntType
+ } else {
+ if (sym == defn.ObjectClass || isJSType(sym))
+ jstpe.AnyType
+ else if (sym == defn.NothingClass)
+ jstpe.NothingType
+ else if (sym == defn.NullClass)
+ jstpe.NullType
+ else
+ tpe
+ }
+
+ case tpe: jstpe.ArrayType =>
+ tpe
+ }
+ }
+
+ def toReferenceType(tp: Type)(implicit ctx: Context): jstpe.ReferenceType =
+ toReferenceTypeInternal(tp)._1
+
+ private def toReferenceTypeInternal(tp: Type)(
+ implicit ctx: Context): (jstpe.ReferenceType, Symbol) = {
+
+ /**
+ * Primitive types are represented as TypeRefs to the class symbol of, for example, scala.Int.
+ * The `primitiveTypeMap` maps those class symbols to the corresponding PrimitiveBType.
+ */
+ def primitiveOrClassToRefType(sym: Symbol): (jstpe.ReferenceType, Symbol) = {
+ assert(sym.isClass, sym)
+ //assert(sym != defn.ArrayClass || isCompilingArray, sym)
+ (jstpe.ClassType(encodeClassFullName(sym)), sym)
+ }
+
+ /**
+ * When compiling Array.scala, the type parameter T is not erased and shows up in method
+ * signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference.
+ */
+ def nonClassTypeRefToRefType(sym: Symbol): (jstpe.ReferenceType, Symbol) = {
+ //assert(sym.isType && isCompilingArray, sym)
+ (jstpe.ClassType(ir.Definitions.ObjectClass), defn.ObjectClass)
+ }
+
+ tp.widenDealias match {
+ // Array type such as Array[Int] (kept by erasure)
+ case JavaArrayType(el) =>
+ val elRefType = toReferenceTypeInternal(el)
+ (jstpe.ArrayType(elRefType._1), elRefType._2)
+
+ case t: TypeRef =>
+ if (!t.symbol.isClass) nonClassTypeRefToRefType(t.symbol) // See comment on nonClassTypeRefToBType
+ else primitiveOrClassToRefType(t.symbol) // Common reference to a type such as scala.Int or java.lang.String
+
+ case Types.ClassInfo(_, sym, _, _, _) =>
+ /* We get here, for example, for genLoadModule, which invokes
+ * toTypeKind(moduleClassSymbol.info)
+ */
+ primitiveOrClassToRefType(sym)
+
+ case t: MethodType => // triggers for LabelDefs
+ toReferenceTypeInternal(t.resultType)
+
+ /* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for
+ * meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning.
+ * The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala.
+ */
+ case a @ AnnotatedType(t, _) =>
+ //debuglog(s"typeKind of annotated type $a")
+ toReferenceTypeInternal(t)
+ }
+ }
+
+ /** Patches the result type of a method symbol to sanitize it.
+ *
+ * For some reason, dotc thinks that the `info.resultType`of an
+ * `isConstructor` method (for classes or traits) is the enclosing class
+ * or trait, but the bodies and usages act as if the result type was `Unit`.
+ *
+ * This method returns `UnitType` for constructor methods, and otherwise
+ * `sym.info.resultType`.
+ */
+ def patchedResultType(sym: Symbol)(implicit ctx: Context): Type =
+ if (sym.isConstructor) defn.UnitType
+ else sym.info.resultType
+
+ // Encoding of method signatures
+
+ private def makeParamsString(sym: Symbol, reflProxy: Boolean,
+ inRTClass: Boolean)(
+ implicit ctx: Context): String = {
+ val tpe = sym.info
+
+ val paramTypeNames0 = tpe.firstParamTypes.map(internalName(_))
+
+ val hasExplicitThisParameter =
+ inRTClass || isScalaJSDefinedJSClass(sym.owner)
+ val paramTypeNames =
+ if (!hasExplicitThisParameter) paramTypeNames0
+ else encodeClassFullName(sym.owner) :: paramTypeNames0
+
+ val paramAndResultTypeNames = {
+ if (sym.isClassConstructor)
+ paramTypeNames
+ else if (reflProxy)
+ paramTypeNames :+ ""
+ else
+ paramTypeNames :+ internalName(patchedResultType(sym))
+ }
+ makeParamsString(paramAndResultTypeNames)
+ }
+
+ private def makeParamsString(paramAndResultTypeNames: List[String]) =
+ paramAndResultTypeNames.mkString(SignatureSep, SignatureSep, "")
+
+ /** Computes the internal name for a type. */
+ private def internalName(tpe: Type)(implicit ctx: Context): String =
+ encodeReferenceType(toReferenceType(tpe))
+
+ /** Encodes a [[Types.ReferenceType]], such as in an encoded method signature.
+ */
+ private def encodeReferenceType(refType: jstpe.ReferenceType): String = {
+ refType match {
+ case jstpe.ClassType(encodedName) => encodedName
+ case jstpe.ArrayType(base, depth) => "A" * depth + base
+ }
+ }
+
+ /** Mangles names that are illegal in JavaScript by prepending a `$`.
+ * Also mangles names that would collide with these mangled names.
+ */
+ private def mangleJSName(name: String) =
+ if (js.isKeyword(name) || name(0).isDigit || name(0) == '$') "$" + name
+ else name
+}