diff options
Diffstat (limited to 'examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala')
-rw-r--r-- | examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala new file mode 100644 index 0000000..3621050 --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala @@ -0,0 +1,244 @@ +/* Scala.js compiler + * Copyright 2013 LAMP/EPFL + * @author Sébastien Doeraene + */ + +package scala.scalajs.compiler + +import scala.tools.nsc._ + +import scala.collection.mutable + +/** Additions to Global meaningful for the JavaScript backend + * + * @author Sébastien Doeraene + */ +trait JSGlobalAddons extends JSDefinitions + with Compat210Component { + val global: Global + + import global._ + import jsDefinitions._ + import definitions._ + + /** JavaScript primitives, used in jscode */ + object jsPrimitives extends JSPrimitives { + val global: JSGlobalAddons.this.global.type = JSGlobalAddons.this.global + val jsAddons: ThisJSGlobalAddons = + JSGlobalAddons.this.asInstanceOf[ThisJSGlobalAddons] + } + + /** global javascript interop related helpers */ + object jsInterop { + import scala.reflect.NameTransformer + import scala.reflect.internal.Flags + + private val exportPrefix = "$js$exported$" + private val methodExportPrefix = exportPrefix + "meth$" + private val propExportPrefix = exportPrefix + "prop$" + + case class ExportInfo(jsName: String, pos: Position, isNamed: Boolean) + + /** retrieves the names a sym should be exported to from its annotations + * + * Note that for accessor symbols, the annotations of the accessed symbol + * are used, rather than the annotations of the accessor itself. + */ + def exportsOf(sym: Symbol): List[ExportInfo] = { + val exports = directExportsOf(sym) ++ inheritedExportsOf(sym) + + // Calculate the distinct exports for this symbol (eliminate double + // occurrences of (name, isNamed) pairs). + val buf = new mutable.ListBuffer[ExportInfo] + val seen = new mutable.HashSet[(String, Boolean)] + for (exp <- exports) { + if (!seen.contains((exp.jsName, exp.isNamed))) { + buf += exp + seen += ((exp.jsName, exp.isNamed)) + } + } + + buf.toList + } + + private def directExportsOf(sym: Symbol): List[ExportInfo] = { + val trgSym = { + // For accessors, look on the val/var def + if (sym.isAccessor) sym.accessed + // For primary class constructors, look on the class itself + else if (sym.isPrimaryConstructor && !sym.owner.isModuleClass) sym.owner + else sym + } + + // Annotations that are directly on the member + val directAnnots = for { + annot <- trgSym.annotations + if annot.symbol == JSExportAnnotation || + annot.symbol == JSExportNamedAnnotation + } yield annot + + // Annotations for this member on the whole unit + val unitAnnots = { + if (sym.isMethod && sym.isPublic && + !sym.isConstructor && !sym.isSynthetic) + sym.owner.annotations.filter(_.symbol == JSExportAllAnnotation) + else + Nil + } + + for { + annot <- directAnnots ++ unitAnnots + } yield { + // Is this a named export or a normal one? + val named = annot.symbol == JSExportNamedAnnotation + + def explicitName = annot.stringArg(0).getOrElse { + reporter.error(annot.pos, + s"The argument to ${annot.symbol.name} must be a literal string") + "dummy" + } + + val name = + if (annot.args.nonEmpty) explicitName + else if (sym.isConstructor) decodedFullName(sym.owner) + else if (sym.isModuleClass) decodedFullName(sym) + else sym.unexpandedName.decoded.stripSuffix("_=") + + // Enforce that methods ending with _= are exported as setters + if (sym.isMethod && !sym.isConstructor && + sym.name.decoded.endsWith("_=") && !isJSSetter(sym)) { + reporter.error(annot.pos, "A method ending in _= will be exported " + + s"as setter. But ${sym.name.decoded} does not have the right " + + "signature to do so (single argument, unit return type).") + } + + // Enforce no __ in name + if (name.contains("__")) { + // Get position for error message + val pos = if (annot.stringArg(0).isDefined) + annot.args.head.pos + else trgSym.pos + + reporter.error(pos, + "An exported name may not contain a double underscore (`__`)") + } + + // Make sure we do not override the default export of toString + if (!sym.isConstructor && name == "toString" && !named && + sym.name != nme.toString_ && sym.tpe.params.isEmpty && + !isJSGetter(sym)) { + reporter.error(annot.pos, "You may not export a zero-argument " + + "method named other than 'toString' under the name 'toString'") + } + + if (named && isJSProperty(sym)) { + reporter.error(annot.pos, + "You may not export a getter or a setter as a named export") + } + + ExportInfo(name, annot.pos, named) + } + } + + private def inheritedExportsOf(sym: Symbol): List[ExportInfo] = { + // The symbol from which we (potentially) inherit exports. It also + // gives the exports their name + val trgSym = { + if (sym.isModuleClass) + sym + else if (sym.isConstructor && sym.isPublic && + sym.owner.isConcreteClass && !sym.owner.isModuleClass) + sym.owner + else NoSymbol + } + + if (trgSym == NoSymbol) { + Nil + } else { + val trgAnnot = + if (sym.isModuleClass) JSExportDescendentObjectsAnnotation + else JSExportDescendentClassesAnnotation + + val forcingSym = + trgSym.ancestors.find(_.annotations.exists(_.symbol == trgAnnot)) + + val name = decodedFullName(trgSym) + + forcingSym.map { fs => + // Enfore no __ in name + if (name.contains("__")) { + // Get all annotation positions for error message + reporter.error(sym.pos, + s"""${trgSym.name} may not have a double underscore (`__`) in its fully qualified + |name, since it is forced to be exported by a @${trgAnnot.name} on ${fs}""".stripMargin) + } + + ExportInfo(name, sym.pos, false) + }.toList + } + } + + /** Just like sym.fullName, but does not encode components */ + private def decodedFullName(sym: Symbol): String = { + if (sym.isRoot || sym.isRootPackage || sym == NoSymbol) sym.name.decoded + else if (sym.owner.isEffectiveRoot) sym.name.decoded + else decodedFullName(sym.effectiveOwner.enclClass) + '.' + sym.name.decoded + } + + /** creates a name for an export specification */ + def scalaExportName(jsName: String, isProp: Boolean): TermName = { + val pref = if (isProp) propExportPrefix else methodExportPrefix + val encname = NameTransformer.encode(jsName) + newTermName(pref + encname) + } + + /** checks if the given symbol is a JSExport */ + def isExport(sym: Symbol): Boolean = + sym.unexpandedName.startsWith(exportPrefix) && + !sym.hasFlag(Flags.DEFAULTPARAM) + + /** retrieves the originally assigned jsName of this export and whether it + * is a property + */ + def jsExportInfo(name: Name): (String, Boolean) = { + def dropPrefix(prefix: String) ={ + if (name.startsWith(prefix)) { + // We can't decode right away due to $ separators + val enc = name.encoded.substring(prefix.length) + Some(NameTransformer.decode(enc)) + } else None + } + + dropPrefix(methodExportPrefix).map((_,false)) orElse + dropPrefix(propExportPrefix).map((_,true)) getOrElse + sys.error("non-exported name passed to jsInfoSpec") + } + + def isJSProperty(sym: Symbol): Boolean = isJSGetter(sym) || isJSSetter(sym) + + /** has this symbol to be translated into a JS getter (both directions)? */ + def isJSGetter(sym: Symbol): Boolean = { + sym.tpe.params.isEmpty && enteringPhase(currentRun.uncurryPhase) { + sym.tpe.isInstanceOf[NullaryMethodType] + } + } + + /** has this symbol to be translated into a JS setter (both directions)? */ + def isJSSetter(sym: Symbol) = { + sym.unexpandedName.decoded.endsWith("_=") && + sym.tpe.resultType.typeSymbol == UnitClass && + enteringPhase(currentRun.uncurryPhase) { + sym.tpe.paramss match { + case List(List(arg)) => !isScalaRepeatedParamType(arg.tpe) + case _ => false + } + } + } + + /** has this symbol to be translated into a JS bracket access (JS to Scala) */ + def isJSBracketAccess(sym: Symbol) = + sym.hasAnnotation(JSBracketAccessAnnotation) + + } + +} |