summaryrefslogtreecommitdiff
path: root/compiler/src/main/scala/scala/scalajs/compiler/PrepJSExports.scala
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/src/main/scala/scala/scalajs/compiler/PrepJSExports.scala')
-rw-r--r--compiler/src/main/scala/scala/scalajs/compiler/PrepJSExports.scala251
1 files changed, 251 insertions, 0 deletions
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/PrepJSExports.scala b/compiler/src/main/scala/scala/scalajs/compiler/PrepJSExports.scala
new file mode 100644
index 0000000..9223061
--- /dev/null
+++ b/compiler/src/main/scala/scala/scalajs/compiler/PrepJSExports.scala
@@ -0,0 +1,251 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Tobias Schlatter
+ */
+
+package scala.scalajs.compiler
+
+import scala.annotation.tailrec
+
+import scala.tools.nsc.NoPhase
+
+/**
+ * Prepare export generation
+ *
+ * Helpers for transformation of @JSExport annotations
+ */
+trait PrepJSExports { this: PrepJSInterop =>
+
+ import global._
+ import jsAddons._
+ import definitions._
+ import jsDefinitions._
+
+ import scala.reflect.internal.Flags
+
+ /** Whether the given symbol has a visibility that allows exporting */
+ def hasLegalExportVisibility(sym: Symbol): Boolean =
+ sym.isPublic || sym.isProtected && !sym.isProtectedLocal
+
+ def genExportMember(ddef: DefDef): List[Tree] = {
+ val baseSym = ddef.symbol
+ val clsSym = baseSym.owner
+
+ val exports = jsInterop.exportsOf(baseSym)
+
+ // Helper function for errors
+ def err(msg: String) = { reporter.error(exports.head.pos, msg); Nil }
+ def memType = if (baseSym.isConstructor) "constructor" else "method"
+
+ if (exports.isEmpty)
+ Nil
+ else if (!hasLegalExportVisibility(baseSym))
+ err(s"You may only export public and protected ${memType}s")
+ else if (baseSym.isMacro)
+ err("You may not export a macro")
+ else if (scalaPrimitives.isPrimitive(baseSym))
+ err("You may not export a primitive")
+ else if (hasIllegalRepeatedParam(baseSym))
+ err(s"In an exported $memType, a *-parameter must come last " +
+ "(through all parameter lists)")
+ else if (hasIllegalDefaultParam(baseSym))
+ err(s"In an exported $memType, all parameters with defaults " +
+ "must be at the end")
+ else if (currentRun.uncurryPhase == NoPhase) {
+ /* When using scaladoc, the uncurry phase does not exist. This makes
+ * our code down below blow up (see bug #323). So we do not do anything
+ * more here if the phase does not exist. It's no big deal because we do
+ * not need exports for scaladoc.
+ */
+ Nil
+ } else if (baseSym.isConstructor) {
+ // we can generate constructors entirely in the backend, since they
+ // do not need inheritance and such. But we want to check their sanity
+ // here by previous tests and the following ones.
+
+ if (!hasLegalExportVisibility(clsSym))
+ err("You may only export public and protected classes")
+ else if (clsSym.isLocalToBlock)
+ err("You may not export a local class")
+ else if (clsSym.isNestedClass)
+ err("You may not export a nested class. Create an exported factory " +
+ "method in the outer class to work around this limitation.")
+ else Nil
+
+ } else {
+ assert(!baseSym.isBridge)
+
+ // Reset interface flag: Any trait will contain non-empty methods
+ clsSym.resetFlag(Flags.INTERFACE)
+
+ // Actually generate exporter methods
+ exports.flatMap { exp =>
+ if (exp.isNamed)
+ genNamedExport(baseSym, exp.jsName, exp.pos) :: Nil
+ else
+ genExportDefs(baseSym, exp.jsName, exp.pos)
+ }
+ }
+ }
+
+ /** generate an exporter for a DefDef including default parameter methods */
+ private def genExportDefs(defSym: Symbol, jsName: String, pos: Position) = {
+ val clsSym = defSym.owner
+ val scalaName =
+ jsInterop.scalaExportName(jsName, jsInterop.isJSProperty(defSym))
+
+ // Create symbol for new method
+ val expSym = defSym.cloneSymbol
+
+ // Set position of symbol
+ expSym.pos = pos
+
+ // Alter type for new method (lift return type to Any)
+ // The return type is lifted, in order to avoid bridge
+ // construction and to detect methods whose signature only differs
+ // in the return type.
+ // Attention: This will cause boxes for primitive value types and value
+ // classes. However, since we have restricted the return types, we can
+ // always safely remove these boxes again in the back-end.
+ if (!defSym.isConstructor)
+ expSym.setInfo(retToAny(expSym.tpe))
+
+ // Change name for new method
+ expSym.name = scalaName
+
+ // Update flags
+ expSym.setFlag(Flags.SYNTHETIC)
+ expSym.resetFlag(
+ Flags.DEFERRED | // We always have a body
+ Flags.ACCESSOR | // We are never a "direct" accessor
+ Flags.CASEACCESSOR | // And a fortiori not a case accessor
+ Flags.LAZY | // We are not a lazy val (even if we export one)
+ Flags.OVERRIDE // Synthetic methods need not bother with this
+ )
+
+ // Remove export annotations
+ expSym.removeAnnotation(JSExportAnnotation)
+ expSym.removeAnnotation(JSExportNamedAnnotation)
+
+ // Add symbol to class
+ clsSym.info.decls.enter(expSym)
+
+ // Construct exporter DefDef tree
+ val exporter = genProxyDefDef(clsSym, defSym, expSym, pos)
+
+ // Construct exporters for default getters
+ val defaultGetters = for {
+ (param, i) <- expSym.paramss.flatten.zipWithIndex
+ if param.hasFlag(Flags.DEFAULTPARAM)
+ } yield genExportDefaultGetter(clsSym, defSym, expSym, i + 1, pos)
+
+ exporter :: defaultGetters
+ }
+
+ /** Generate a dummy DefDef tree for a named export. This tree is captured
+ * by GenJSCode again to generate the required JavaScript logic.
+ */
+ private def genNamedExport(defSym: Symbol, jsName: String, pos: Position) = {
+ val clsSym = defSym.owner
+ val scalaName = jsInterop.scalaExportName(jsName, false)
+
+ // Create symbol for the new exporter method
+ val expSym = clsSym.newMethodSymbol(scalaName, pos,
+ Flags.SYNTHETIC | Flags.FINAL)
+
+ // Mark the symbol to be a named export
+ expSym.addAnnotation(JSExportNamedAnnotation)
+
+ // Create a single parameter of type Any
+ val param = expSym.newValueParameter(newTermName("namedArgs"), pos)
+ param.setInfo(AnyTpe)
+
+ // Set method type
+ expSym.setInfo(MethodType(param :: Nil, AnyClass.tpe))
+
+ // Register method to parent
+ clsSym.info.decls.enter(expSym)
+
+ // Placeholder tree
+ def ph = Ident(Predef_???)
+
+ // Create a call to the forwarded method with ??? as args
+ val sel: Tree = Select(This(clsSym), defSym)
+ val call = (sel /: defSym.paramss) {
+ (fun, params) => Apply(fun, List.fill(params.size)(ph))
+ }
+
+ // rhs is a block to prevent boxing of result
+ typer.typedDefDef(DefDef(expSym, Block(call, ph)))
+ }
+
+ private def genExportDefaultGetter(clsSym: Symbol, trgMethod: Symbol,
+ exporter: Symbol, paramPos: Int, pos: Position) = {
+
+ // Get default getter method we'll copy
+ val trgGetter =
+ clsSym.tpe.member(nme.defaultGetterName(trgMethod.name, paramPos))
+
+ assert(trgGetter.exists)
+
+ // Although the following must be true in a correct program, we cannot
+ // assert, since a graceful failure message is only generated later
+ if (!trgGetter.isOverloaded) {
+ val expGetter = trgGetter.cloneSymbol
+
+ expGetter.name = nme.defaultGetterName(exporter.name, paramPos)
+ expGetter.pos = pos
+
+ clsSym.info.decls.enter(expGetter)
+
+ genProxyDefDef(clsSym, trgGetter, expGetter, pos)
+
+ } else EmptyTree
+ }
+
+ /** generate a DefDef tree (from [[proxySym]]) that calls [[trgSym]] */
+ private def genProxyDefDef(clsSym: Symbol, trgSym: Symbol,
+ proxySym: Symbol, pos: Position) = atPos(pos) {
+
+ // Helper to ascribe repeated argument lists when calling
+ def spliceParam(sym: Symbol) = {
+ if (isRepeated(sym))
+ Typed(Ident(sym), Ident(tpnme.WILDCARD_STAR))
+ else
+ Ident(sym)
+ }
+
+ // Construct proxied function call
+ val sel: Tree = Select(This(clsSym), trgSym)
+ val rhs = (sel /: proxySym.paramss) {
+ (fun,params) => Apply(fun, params map spliceParam)
+ }
+
+ typer.typedDefDef(DefDef(proxySym, rhs))
+ }
+
+ /** changes the return type of the method type tpe to Any. returns new type */
+ private def retToAny(tpe: Type): Type = tpe match {
+ case MethodType(params, result) => MethodType(params, retToAny(result))
+ case NullaryMethodType(result) => NullaryMethodType(AnyClass.tpe)
+ case PolyType(tparams, result) => PolyType(tparams, retToAny(result))
+ case _ => AnyClass.tpe
+ }
+
+ /** checks whether this type has a repeated parameter elsewhere than at the end
+ * of all the params
+ */
+ private def hasIllegalRepeatedParam(sym: Symbol): Boolean = {
+ val params = sym.paramss.flatten
+ params.nonEmpty && params.init.exists(isRepeated _)
+ }
+
+ /** checks whether there are default parameters not at the end of
+ * the flattened parameter list
+ */
+ private def hasIllegalDefaultParam(sym: Symbol): Boolean = {
+ val isDefParam = (_: Symbol).hasFlag(Flags.DEFAULTPARAM)
+ sym.paramss.flatten.reverse.dropWhile(isDefParam).exists(isDefParam)
+ }
+
+}