All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scala.scalajs.macroimpls.UseAsMacros.scala Maven / Gradle / Ivy

The newest version!
/*                     __                                               *\
**     ________ ___   / /  ___      __ ____  Scala.js API               **
**    / __/ __// _ | / /  / _ | __ / // __/  (c) 2013-2015, LAMP/EPFL   **
**  __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \    http://scala-lang.org/     **
** /____/\___/_/ |_/____/_/ | |__/ /____/                               **
**                          |/____/                                     **
\*                                                                      */


package scala.scalajs.macroimpls

import scala.annotation.tailrec
import scala.scalajs.js

import Compat210._

/** Macros for `js.use(x).as[T]`.
 *
 *  This implements a structural typechecker conforming to the JavaScript calling
 *  convention in Scala.js' export and facade system.
 *
 *  @author Tobias Schlatter
 */
@deprecated("Not actually deprecated, makes warnings go away", "")
private[scalajs] object UseAsMacros {
  // Import macros only here, otherwise we collide with Compat210._
  import scala.reflect.macros._
  import blackbox.Context

  def as_impl[A: c.WeakTypeTag, B <: js.Any: c.WeakTypeTag](
      c: Context { type PrefixType = js.Using[_] }): c.Expr[B] = {
    (new Macros[c.type](c)).as[A, B]
  }

  private class Macros[C <: Context { type PrefixType = js.Using[_] }](val c: C)
      extends JSMembers with Compat210Component {

    import c.universe._

    private val JSNameAnnotation = typeOf[js.annotation.JSName].typeSymbol
    private val JSBracketAccessAnnotation = typeOf[js.annotation.JSBracketAccess].typeSymbol
    private val JSBracketCallAnnotation = typeOf[js.annotation.JSBracketCall].typeSymbol
    private val JSExportAnnotation = typeOf[js.annotation.JSExport].typeSymbol
    private val JSExportAllAnnotation = typeOf[js.annotation.JSExportAll].typeSymbol

    /** Base classes that are allowed in a target type.
     *  These are also the classes whose methods do not need to be provided.
     */
    private val JSObjectAncestors = typeOf[js.Object].baseClasses.toSet

    type JSMemberSet = Map[JSMemberSelection, List[JSMember]]

    def as[A: WeakTypeTag, B <: js.Any: WeakTypeTag]: Expr[B] = {
      val trgTpe = verifyTargetType(weakTypeOf[B])
      val srcTpe = weakTypeOf[A]

      val srcSym = srcTpe.typeSymbol

      // Nothing and Null have everything
      if (srcSym != definitions.NothingClass &&
          srcSym != definitions.NullClass) {
        check(srcTpe, trgTpe)
      }

      reify { c.prefix.splice.x.asInstanceOf[B] }
    }

    /** Perform the actual structural typechecking.
     *
     *  Checks if [[srcTpe]] conforms to [[trgTpe]]. Reports errors otherwise.
     */
    private def check(srcTpe: Type, trgTpe: Type): Unit = {
      val requiredMembers = rawJSMembers(trgTpe)
      val isRawJSType = srcTpe <:< typeOf[js.Any]

      val definedMembers =
        if (isRawJSType) rawJSMembers(srcTpe)
        else exportedMembers(srcTpe)

      for {
        (jsMemberSelection, jsMembers) <- requiredMembers
        jsMember <- jsMembers
      } {
        // Fail for required unsupported members
        jsMember match {
          case UnsupportedMember(sym, tpe) =>
            val msg = tpe match {
              case _: PolyType =>
                "Polymorphic methods are currently " +
                s"not supported. Offending method: ${sym.fullName}"

              case _: ExistentialType =>
                "Methods with existential types are " +
                s"not supported. Offending method: ${sym.fullName}. This is " +
                "likely caused by an abstract type in the method signature"

              case _ =>
                sys.error("Unknown type in unsupported member. " +
                    "Report this as a bug.\n" +
                     s"Offending method: ${sym.fullName}\n" +
                     s"Offending type: ${showRaw(tpe)}")
            }

            c.error(c.enclosingPosition, msg)

          case _ =>
        }

        val hasConformingMember = {
          val overloads = definedMembers.getOrElse(jsMemberSelection, Nil)
          overloads.exists(_.conformsTo(jsMember))
        }

        if (!hasConformingMember) {
          // Error: A member is missing. Construct an informative error message

          def noSuchMember(memberName: String) = {
            val membershipStr = if (isRawJSType) "have" else "export"
            val memberStr = jsMember.displayStr(memberName)
            s"$srcTpe does not $membershipStr a $memberStr."
          }

          val errMsg = jsMemberSelection match {
            case JSNamedMember(name) =>
              noSuchMember(name)

            case JSMemberCall if !isRawJSType =>
              s"$trgTpe defines an apply method. This cannot be implemented " +
                  "by any Scala exported type, since it would need to chain " +
                  "Function's prototype."

            case JSMemberBracketAccess if !isRawJSType =>
              s"$trgTpe defines a @JSMemberBracketAccess method. Existence " +
                  "of such a method cannot be statically checked for any " +
                  "Scala exported type."

            case JSMemberBracketCall if !isRawJSType =>
              s"$trgTpe defines a @JSMemberBracketCall method. Existence of " +
                  "such a method cannot be statically checked for any Scala " +
                  "exported type."

            case JSMemberCall =>
              noSuchMember("") + " (type is not callable)"

            case JSMemberBracketAccess =>
              noSuchMember("") + " (type doesn't support " +
                  "member selection via []). Add @JSBracketAccess to use a " +
                  "method for member selection."

            case JSMemberBracketCall =>
              noSuchMember("") + " (type doesn't support " +
                  "dynamically calling methods). Add @JSBracketCall to use a " +
                  "method for dynamic calls."
          }

          c.error(c.enclosingPosition, errMsg)
        }
      }
    }

    /** Members that a facade type defines */
    private def rawJSMembers(tpe: Type): JSMemberSet = {

      def isAPIMember(member: Symbol) = {
        !JSObjectAncestors(member.owner) &&
        !member.isConstructor &&
        member.isMethod &&
        !member.asTerm.isParamWithDefault
      }

      val tups = for {
        member <- tpe.members
        if isAPIMember(member)
      } yield {
        val memberMethod = member.asMethod
        (jsMemberSelection(memberMethod), jsMemberFor(tpe, memberMethod))
      }

      // Group by member selection
      for {
        (selection, members) <- tups.groupBy(_._1)
      } yield {
        (selection, members.map(_._2).toList)
      }
    }

    /** Returns the way a member of a raw JS type is selected in JS */
    private def jsMemberSelection(sym: MethodSymbol): JSMemberSelection = {
      val annots = memberAnnotations(sym)

      def hasAnnot(annot: Symbol) = annots.exists(annotIs(_, annot))

      if (hasAnnot(JSBracketAccessAnnotation)) {
        JSMemberBracketAccess
      } else if (hasAnnot(JSBracketCallAnnotation)) {
        JSMemberBracketCall
      } else {
        val optAnnot = annots.find(annotIs(_, JSNameAnnotation))
        val optName = optAnnot.flatMap(annotStringArg)

        optName.fold {
          val name = defaultName(sym)
          if (name == "apply") JSMemberCall
          else JSNamedMember(name)
        } { name => JSNamedMember(name) }
      }
    }

    /** Returns all exported members of a type */
    private def exportedMembers(tpe: Type): JSMemberSet = {
      val exports = tpe.baseClasses.flatMap(exportedDecls(tpe, _))

      // Group exports by name
      for {
        (name, elems) <- exports.groupBy(_._1)
      } yield {
        (JSNamedMember(name), elems.map(_._2))
      }
    }

    /** All exported declarations of a class.
     *  (both @JSExportAll and @JSExport)
     */
    private def exportedDecls(origTpe: Type, sym: Symbol) = {
      require(sym.isClass)

      val exportAll = sym.annotations.exists(annotIs(_, JSExportAllAnnotation))

      for {
        decl <- sym.info.decls if decl.isMethod && !decl.isConstructor
        name <- exportNames(decl.asMethod, exportAll)
      } yield {
        (name, jsMemberFor(origTpe, decl.asMethod))
      }
    }

    /** Get the JS member for a method in [[origTpe]] */
    private def jsMemberFor(origTpe: Type, sym: MethodSymbol): JSMember = {
      sym.info.asSeenFrom(origTpe, sym.owner) match {
        case MethodType(List(param), resultType)
            if resultType.typeSymbol == definitions.UnitClass &&
               sym.name.decodedName.toString.endsWith("_=") =>
          JSSetter(param.info)

        case NullaryMethodType(returnType) =>
          JSGetter(returnType)

        case info: MethodType =>
          @tailrec
          def flatParams(tpe: Type, acc: List[JSMethodParam]): JSMethod = {
            tpe match {
              case MethodType(params, returnTpe) =>
                val ps = params map { p =>
                  JSMethodParam(p.info, p.asTerm.isParamWithDefault)
                }
                flatParams(returnTpe, ps reverse_::: acc)
              case tpe =>
                JSMethod(acc.reverse, tpe)
            }
          }

          flatParams(info, Nil)

        case tpe =>
          UnsupportedMember(sym, tpe)
      }
    }

    /** Names a method is exported to */
    private def exportNames(sym: MethodSymbol, exportAll: Boolean) = {
      lazy val default = defaultName(sym)

      val explicitNames = for {
        annot <- memberAnnotations(sym)
        if annotIs(annot, JSExportAnnotation)
      } yield {
        annotStringArg(annot).getOrElse(default)
      }

      if (exportAll && sym.isPublic) default :: explicitNames
      else explicitNames
    }

    /** Default JavaScript name of a method */
    private def defaultName(sym: MethodSymbol): String =
      sym.name.decodedName.toString.stripSuffix("_=")

    /** Verifies that the given type is a class type or a refined type,
     *  has no class ancestors lower than js.Object and does only
     *  refine type members.
     *  @returns dealiased tpe
     */
    private def verifyTargetType(tpe: Type): Type = {
      tpe.dealias match {
        case tpe @ TypeRef(_, sym0, _) if sym0.isClass =>
          val sym = sym0.asClass

          if (!sym.isTrait)
            c.abort(c.enclosingPosition, "Only traits can be used with as")

          def allowedParent(sym: Symbol) =
            sym.asClass.isTrait || JSObjectAncestors(sym)

          for (base <- sym.baseClasses if !allowedParent(base)) {
            c.abort(c.enclosingPosition, s"Supertype ${base.fullName} of $sym " +
                "is a class. Cannot be used with as.")
          }

          tpe

        case tpe @ RefinedType(parents, decls) =>
          parents.foreach(verifyTargetType)

          for (decl <- decls if !decl.isType) {
            c.abort(c.enclosingPosition, s"Refinement ${decl.name} " +
                "is not a type. Only types may be refined with as.")
          }

          tpe

        case tpe =>
          c.abort(c.enclosingPosition, "Only class types can be used with as")
      }
    }

    /** Annotations of a member symbol.
     *  Looks on accessed field if this is an accessor
     */
    private def memberAnnotations(sym: MethodSymbol): List[Annotation] = {
      val trgSym = if (sym.isAccessor) sym.accessed else sym

      // Force typeSignature to calculate annotations
      trgSym.typeSignature

      trgSym.annotations
    }

    /** Retrieve first argument to the annotation as literal string */
    private def annotStringArg(annot: Annotation): Option[String] = {
      val args = annot.tree.children.tail
      args match {
        case List(Literal(Constant(s: String))) => Some(s)
        case _                                  => None
      }
    }

    /** Checks if [[annot]] is of class [[clsSym]] */
    private def annotIs(annot: Annotation, clsSym: Symbol) =
      annot.tree.tpe.typeSymbol == clsSym

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy