summaryrefslogblamecommitdiff
path: root/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSExports.scala
blob: 922306173f722891c317a2db3fea84f88089fdd1 (plain) (tree)


























































































































































































































































                                                                                 
/* 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)
  }

}