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

ildl.plugin.transform.coerce.CoerceTreeTransformer.scala Maven / Gradle / Ivy

The newest version!
// NOTE: This file was adapted form the miniboxing repository:
//
//     _____   .__         .__ ___.                    .__ scala-miniboxing.org
//    /     \  |__|  ____  |__|\_ |__    ____  ___  ___|__|  ____     ____
//   /  \ /  \ |  | /    \ |  | | __ \  /  _ \ \  \/  /|  | /    \   / ___\
//  /    Y    \|  ||   |  \|  | | \_\ \(  <_> ) >    < |  ||   |  \ / /_/  >
//  \____|__  /|__||___|  /|__| |___  / \____/ /__/\_ \|__||___|  / \___  /
//          \/          \/          \/               \/         \/ /_____/
// Copyright (c) 2011-2015 Scala Team, École polytechnique fédérale de Lausanne
//
// Authors:
//    * Vlad Ureche
//    * Eugene Burmako
//
package ildl.plugin
package transform
package coerce

import scala.tools.nsc._
import scala.tools.nsc.typechecker._
import scala.tools.nsc.transform.TypingTransformers
import scala.util.DynamicVariable
import scala.collection.immutable.ListMap

trait CoerceTreeTransformer extends TypingTransformers {
  self: CoerceComponent =>

  import global._
  import helper._

  class CoercePhase(prev: StdPhase) extends StdPhase(prev) {
    override def name = CoerceTreeTransformer.this.phaseName
    override def checkable = false
    def apply(unit: CompilationUnit): Unit = {
      val tree = afterCoerce(new TreeAdapters().adapt(unit))
      tree.foreach(node => assert(node.tpe != null, node))
    }
  }

  class TreeAdapters extends Analyzer {
    var indent = 0
    override lazy val global: self.global.type = self.global

    def adapt(unit: CompilationUnit): Tree = {
      val context = rootContext(unit)
      // turnOffErrorReporting(this)(context)
      val checker = new TreeAdapter(context)
      unit.body = checker.typed(unit.body)
      unit.body
    }

    override def newTyper(context: Context): Typer =
      new TreeAdapter(context)

    def adaptdbg(ind: Int, msg: => String): Unit = {
//       println("  " * ind + msg)
    }


    object MaybeImplicit {
      abstract sealed trait ImplicitMetadata
      case class NonEmptyMetadata(method: Tree, targs: List[Type]) extends ImplicitMetadata
      case object EmptyMetadata extends ImplicitMetadata

      def unapply(tree: Tree): Option[(Tree, ImplicitMetadata, String)] = tree match {
        case Apply(impl, List(qual))                   if impl.symbol.isImplicit => Some((qual, NonEmptyMetadata(impl, Nil), "implicit_" + impl.symbol.name.decode))
        case Apply(TypeApply(impl, targs), List(qual)) if impl.symbol.isImplicit => Some((qual, NonEmptyMetadata(impl, targs.map(_.tpe)), "implicit_" + impl.symbol.name.decode))
        case _ => Some((tree, EmptyMetadata, "extension"))
      }

      def apply(qual: Tree, meta: ImplicitMetadata) = meta match {
        case meta: NonEmptyMetadata => gen.mkMethodCall(meta.method, /* meta.targs, */ List(qual))
        case EmptyMetadata => qual
      }
    }

    object FullApply {
      def unapply(tree: Tree): Option[(Tree, List[Tree], List[Tree])] =
        tree match {
          case Apply(sel@Select(_, _), args)                   if !sel.symbol.isImplicit => Some((sel, Nil, args))
          case Apply(TypeApply(sel@Select(_, _), targs), args) if !sel.symbol.isImplicit => Some((sel, targs, args))
          case _ => None
        }
      def apply(qual: Tree, targs: List[Tree], args: List[Tree]): Tree =
        if (targs.isEmpty)
          Apply(qual, args)
        else
          Apply(TypeApply(qual, targs), args)
    }

    class TreeAdapter(context0: Context) extends Typer(context0) {

      override val infer = new Inferencer {
        def context = TreeAdapter.this.context
        // As explained in #132, the inferencer can refer to private
        // members and we don't want to crash in the retyper due to
        // this => we just replace the check. :)
        override def checkAccessible(tree: Tree, sym: Symbol, pre: Type, site: Tree): Tree =
          tree.setSymbol(sym).setType(pre.memberType(sym))
      }

      override protected def finishMethodSynthesis(templ: Template, clazz: Symbol, context: Context): Template =
        templ

      def supertyped(tree: Tree, mode: Mode, pt: Type): Tree =
        super.typed(tree, mode, pt)

      override protected def adapt(tree: Tree, mode: Mode, pt: Type, original: Tree = EmptyTree): Tree = {
        val oldTpe = tree.tpe
        val newTpe = pt
        def superAdapt =
          if (oldTpe <:< newTpe)
            tree
          else
            super.adapt(tree, mode, pt, original)

        if ((tree.tpe.isInstanceOf[PolyType]) && (mode.inFunMode))
          super.adapt(tree, mode, pt, original)
        else if (tree.isTerm) {
          if ((oldTpe.hasReprAnnot ^ newTpe.hasReprAnnot) && (!pt.isWildcard)) {
            val descObject = if (oldTpe.hasReprAnnot) oldTpe.getAnnotDescrObject else newTpe.getAnnotDescrObject
            val conversion = if (oldTpe.hasReprAnnot) oldTpe.getAnnotDescrReprToHigh else newTpe.getAnnotDescrHighToRepr
            val (tpe, descr) =
              if (oldTpe.hasReprAnnot)
                (oldTpe.dealiasWiden.withoutReprAnnot, oldTpe.getAnnotDescrObject)
              else
                (newTpe.dealiasWiden.withoutReprAnnot, newTpe.getAnnotDescrObject)
            val convCall = gen.mkAttributedSelect(gen.mkAttributedRef(descObject), conversion)
            val tree1 = gen.mkMethodCall(convCall, List(tree.withTypedAnnot))
            val tree2 = super.typed(tree1, mode, pt)
            //assert(tree2.tpe != ErrorType, tree2)
            // super.adapt is automatically executed when calling super.typed
            tree2
          } else if (oldTpe.hasReprAnnot && (oldTpe.hasReprAnnot == newTpe.hasReprAnnot) && !(oldTpe <:< newTpe)) {
            val descr1 = oldTpe.getAnnotDescrObject
            val descr2 = newTpe.getAnnotDescrObject
            if (descr1 != descr2) {
              // representation mismatch -- TODO: WARN HERE
              val convCall1 = gen.mkAttributedSelect(gen.mkAttributedRef(oldTpe.getAnnotDescrObject), oldTpe.getAnnotDescrReprToHigh)
              val convCall2 = gen.mkAttributedSelect(gen.mkAttributedRef(newTpe.getAnnotDescrObject), newTpe.getAnnotDescrHighToRepr)
              val tree1 = gen.mkMethodCall(convCall2, gen.mkMethodCall(convCall1, List(tree.withTypedAnnot)) :: Nil)
              super.typed(tree1, mode, pt)
            } else {
              // workaround the isSubType issue with singleton types
              // and annotated types (see mb_erasure_torture10.scala)
              tree.setType(newTpe)
              tree
            }
          } else
            superAdapt
        } else {
          superAdapt
        }
      }

      def typechecks(candidate: Symbol, descObject: Symbol, tree: Tree, qual2: Tree, targs: List[Tree], args: List[Tree], mode: Mode, pt: Type): Boolean =
        typechecks(candidate, descObject, tree, targs, qual2 :: args, mode, pt)


      def typechecks(candidate: Symbol, descObject: Symbol, tree: Tree, targs: List[Tree], args: List[Tree], mode: Mode, pt: Type): Boolean = {
        val newQual = gen.mkAttributedRef(descObject)
        val extMeth = gen.mkAttributedSelect(newQual, candidate)
        val candTree = FullApply(extMeth, Nil, args.map(_.duplicate))

        // cleaned up typer
        val unit = global.currentUnit
        val contextZ = rootContext(unit, throwing = false, checking = false)
        contextZ.implicitsEnabled = false
        contextZ.macrosEnabled = false
        contextZ.enrichmentEnabled = false
        val localTyper = newTyper(contextZ)

        val result: Boolean =
          localTyper.silent(_.typed(candTree, mode, pt), reportAmbiguousErrors = false) match {
            case SilentResultValue(t: Tree) => t.tpe.withoutReprAnnotAggresive <:< pt.withoutReprAnnotAggresive
            case SilentTypeError(err) => false
          }

        result
      }

      case object AlreadyTyped
      case object ConstructorRedirectFailed
      implicit class WithAlreadyTyped(val tree: Tree) {
        def withTypedAnnot: Tree = tree.updateAttachment[AlreadyTyped.type](AlreadyTyped)
      }

      override def typed(tree: Tree, mode: Mode, pt: Type): Tree = {
        val ind = indent
        indent += 1
        adaptdbg(ind, " <== " + tree + " now: " + tree.tpe + "  expected: " + pt)

        if (tree.hasAttachment[AlreadyTyped.type] && (pt == WildcardType) && (tree.tpe != null))
          return tree

        val res = tree match {
          case EmptyTree | TypeTree() =>
            super.typed(tree, mode, pt)

          // Don't retype transformation description objects
          case tpl: ClassDef =>
            val isDescrObject = tpl.symbol.isTransfDescriptionObject
            if (isDescrObject)
              tpl
            else {
              tpl.setType(null)
              super.typed(tpl, mode, pt)
            }

          case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) if (pt.hasReprAnnot) && !tree.hasAttachment[ConstructorRedirectFailed.type] =>
            val descObject = pt.getAnnotDescrObject
            val tpe = tpt.tpe
            if (matchesDescrHighType(descObject, tpe)) {
              // try to find a constructor
              val ctorName = TermName("ctor_" + tpt.tpe.typeSymbol.nameString)
              val publicCandidates = descObject.info.member(ctorName).alternatives.filter(mb => mb.isPublic && !mb.isDeferred)
              val matchingCandidates = publicCandidates.filter(typechecks(_, descObject, tree, Nil, args, mode, pt))
              matchingCandidates match {
                case List(candidate) =>
                  val newQual = gen.mkAttributedRef(descObject)
                  val extMeth = gen.mkAttributedSelect(newQual, candidate)
                  super.typed(FullApply(extMeth, Nil, args), mode, pt)
                case _ =>
                  CoerceTreeTransformer.this.global.reporter.warning(tree.pos,
                    "The new operator can be optimized if you define a public, non-overloaded " +
                    "and matching constructor method for it in " + descObject + ", with the name " + ctorName.decoded +
                    (if (matchingCandidates.isEmpty) "." else " (the method is overloaded).\n"))

                  // bail out to conversions:
                  typed(tree.updateAttachment(ConstructorRedirectFailed), mode, pt)
              }
            } else
              // bail out to conversions:
              typed(tree.updateAttachment(ConstructorRedirectFailed), mode, pt)

          case FullApply(sel@Select(MaybeImplicit(qual, implData, prefix), meth), targs, args) if qual.isTerm && tree.symbol.isMethod =>
            val qual2 = super.typedQualifier(qual.setType(null), mode, WildcardType).withTypedAnnot

            import helper._
            val global = CoerceTreeTransformer.this.global
            if (qual2.hasReprAnnot) {
              val tpe2 = if (qual2.tpe.hasAnnotation(reprClass)) qual2.tpe else qual2.tpe.widen
              val tpe3 = tpe2.removeAnnotation(reprClass)
              val descObject = qual2.tpe.getAnnotDescrObject

              val extName = TermName(prefix + "_" + meth)
              val publicCandidates = descObject.info.member(extName).alternatives.filter(mb => mb.isPublic && !mb.isDeferred)
              val matchingCandidates = publicCandidates.filter(typechecks(_, descObject, tree, qual2, Nil, args, mode, pt))

              matchingCandidates match {
                case List(candidate) =>
                  val newQual = gen.mkAttributedRef(descObject)
                  val extMeth = gen.mkAttributedSelect(newQual, candidate)
                  super.typed(FullApply(extMeth, Nil, qual2 :: args), mode, pt)
                case _ =>
                  CoerceTreeTransformer.this.global.reporter.warning(tree.pos,
                    "The " + sel.symbol + " can be optimized if you define a public, non-overloaded " +
                    "and matching exension method for it in " + descObject + ", with the name " + extName.decoded +
                    (if (matchingCandidates.isEmpty) "." else " (the method is overloaded).\n"))
                  val conversion = qual2.tpe.getAnnotDescrReprToHigh
                  val convCall = gen.mkAttributedSelect(gen.mkAttributedRef(descObject), conversion)
                  val qual3 =  gen.mkMethodCall(convCall, List(qual2))
                  super.typed(FullApply(Select(MaybeImplicit(qual3, implData), meth) setSymbol tree.symbol, targs, args).withTypedAnnot, mode, pt)
              }
            } else {
              tree.setType(null)
              super.typed(tree, mode, pt)
            }

          case _ =>
            tree.setType(null)
            super.typed(tree, mode, pt)
        }

        // Stupid hack to get rid of an error when typing the 
        // reference - the typer set the Outer.type as type instead of
        // ()Outer.type. There, I fixed it:
        if (tree.hasSymbolField && tree.symbol.name.decoded == "" && !tree.isInstanceOf[Apply])
          tree.tpe match {
            case MethodType(Nil, _) => // ok
            case _ => tree.setType(MethodType(Nil, tree.tpe))
          }

        adaptdbg(ind, " ==> " + res + ": " + res.tpe)
//        if (res.tpe == ErrorType)
//          adaptdbg(ind, "ERRORS: " + context.errBuffer)
        indent -= 1
        res
      }
    }
  }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy