aboutsummaryrefslogblamecommitdiff
path: root/src/dotty/tools/dotc/transform/SuperAccessors.scala
blob: ffad534b4aeba0a8430106afa6e958385f965ca8 (plain) (tree)
1
2
3
4
5
6
7





                                                                                                  
                     






                                                                                               
                             






















                                                                                         
                                                                                 






                                                                                 

                                                                                       


                                                                       



                                                                         
                                                   
 

                                                                           


                                                                    

                                                 










                                                                              
       










                                                                                     


                                                                 







                                                                                                      
                                                                        
                                
                                                                                          
                                                                                                   


                                                                                                                                                                                      
                                                     


           
                                                   

     
                                                                                
























                                                                                                                                                          
                                                                                        























                                                                                        
                                                                                      


                      

                              


                                               



                                                                          
                                                                


                               
                                                         
                                   
                              
                                                      
           
                                                  

























































                                                                                                                           
                                                                                             










                                                                                                 
                            
                           
                                                                                                                        

















                                                                  












                                                                   




























                                                                                                                 

                                                          
                                                               
                                       

                                                           
                              

              




                                  

















                                                                                              

                                                        
                                                                 

                                                         
                              

              
                                                             




















                                                                                                      
                                                                                 




































                                                                                                                   
                                                            





                                                                                   
                                                     




                                                       
package dotty.tools.dotc
package transform

import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer}
import dotty.tools.dotc.ast.{Trees, tpd}
import scala.collection.{ mutable, immutable }
import ValueClasses._
import mutable.ListBuffer
import scala.annotation.tailrec
import core._
import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._
import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._
import util.Positions._
import Decorators._
import Symbols._, TypeUtils._

/** This phase performs the following functions, each of which could be split out in a
 *  mini-phase:
 *
 *  (1) Adds super accessors for all super calls that either
 *  appear in a trait or have as a target a member of some outer class.
 *
 *  (2) Converts parameter fields that have the same name as a corresponding
 *  public parameter field in a superclass to a forwarder to the superclass
 *  field (corresponding = super class field is initialized with subclass field)
 *
 *  (3) Adds protected accessors if the access to the protected member happens
 *  in a class which is not a subclass of the member's owner.
 *
 *  (4) Finally, the phase used to mangle the names of class-members which are
 *  private up to an enclosing non-package class, in order to avoid overriding conflicts.
 *  This is currently disabled, and class-qualified private is deprecated.
 *
 *  It also checks that:
 *
 *  (1) Symbols accessed from super are not abstract, or are overridden by
 *  an abstract override.
 *
 *  (2) If a symbol accessed from super is defined in a real class (not a trait),
 *  there are no abstract members which override this member in Java's rules
 *  (see SI-4989; such an access would lead to illegal bytecode)
 *
 *  (3) Super calls do not go to some synthetic members of Any (see isDisallowed)
 *
 *  (4) Super calls do not go to synthetic field accessors
 *
 *  TODO: Rename phase to "Accessors" because it handles more than just super accessors
 */
class SuperAccessors extends MacroTransform 
                        with IdentityDenotTransformer 
                        with ForwardParamAccessors { thisTransformer =>

  import tpd._

  /** the following two members override abstract members in Transform */
  override def phaseName: String = "superaccessors"

  override def transformPhase(implicit ctx: Context) = thisTransformer.next

  protected def newTransformer(implicit ctx: Context): Transformer =
    new SuperAccTransformer

  class SuperAccTransformer extends Transformer {

    /** Some parts of trees will get a new owner in subsequent phases.
     *  These are value class methods, which will become extension methods.
     *  (By-name arguments used to be included also, but these
     *  don't get a new class anymore, they are just wrapped in a new method).
     *  
     *  These regions will have to be treated specially for the purpose
     *  of adding accessors. For instance, super calls from these regions
     *  always have to go through an accessor.
     *  
     *  The `invalidOwner` field, if different from NoSymbol,
     *  contains the symbol that is not a valid owner.
     */
    private var invalidEnclClass: Symbol = NoSymbol

    private def withInvalidCurrentClass[A](trans: => A)(implicit ctx: Context): A = {
      val saved = invalidEnclClass
      invalidEnclClass = ctx.owner
      try trans
      finally invalidEnclClass = saved
    }
    
    private def validCurrentClass(implicit ctx: Context): Boolean = 
      ctx.owner.enclosingClass != invalidEnclClass

    private val accDefs = mutable.Map[Symbol, ListBuffer[Tree]]()

    private def ensureAccessor(sel: Select)(implicit ctx: Context) = {
      val Select(qual, name) = sel
      val sym                = sel.symbol
      val clazz              = qual.symbol.asClass
      val supername          = name.superName

      val superAcc = clazz.info.decl(supername).suchThat(_.signature == sym.signature).symbol orElse {
        ctx.debuglog(s"add super acc ${sym.showLocated} to $clazz")
        val maybeDeferred = if (clazz is Trait) Deferred else EmptyFlags
        val acc = ctx.newSymbol(
            clazz, supername, SuperAccessor | Private | Artifact | Method | maybeDeferred,
            sel.tpe.widenSingleton.ensureMethodic, coord = sym.coord).enteredAfter(thisTransformer)
        // Diagnostic for SI-7091
        if (!accDefs.contains(clazz))
          ctx.error(s"Internal error: unable to store accessor definition in ${clazz}. clazz.hasPackageFlag=${clazz is Package}. Accessor required for ${sel} (${sel.show})", sel.pos)
        else accDefs(clazz) += DefDef(acc, EmptyTree)
        acc
      }

      This(clazz).select(superAcc).withPos(sel.pos)
    }

  private def transformSuperSelect(sel: Select)(implicit ctx: Context): Tree = {
      val Select(sup @ Super(_, mix), name) = sel
      val sym   = sel.symbol
      assert(sup.symbol.exists, s"missing symbol in $sel: ${sup.tpe}")
      val clazz = sup.symbol.asClass

      if (sym is Deferred) {
        val member = sym.overridingSymbol(clazz)
        if (mix != tpnme.EMPTY ||
            !member.exists ||
            !(member is AbsOverride) && member.isIncompleteIn(clazz))
          ctx.error(
              i"${sym.showLocated} is accessed from super. It may not be abstract unless it is overridden by a member declared `abstract' and `override'",
              sel.pos)
      }
      else if (mix == tpnme.EMPTY && !(sym.owner is Trait))
        // SI-4989 Check if an intermediate class between `clazz` and `sym.owner` redeclares the method as abstract.
        for (intermediateClass <- clazz.info.baseClasses.tail.takeWhile(_ != sym.owner)) {
          val overriding = sym.overridingSymbol(intermediateClass)
          if ((overriding is (Deferred, butNot = AbsOverride)) && !(overriding.owner is Trait))
            ctx.error(
                s"${sym.showLocated} cannot be directly accessed from ${clazz} because ${overriding.owner} redeclares it as abstract",
                sel.pos)

        }
      if (name.isTermName && mix == tpnme.EMPTY &&
          ((clazz is Trait) || clazz != ctx.owner.enclosingClass || !validCurrentClass))
        ensureAccessor(sel)(ctx.withPhase(thisTransformer.next))
      else sel
    }

    // Disallow some super.XX calls targeting Any methods which would
    // otherwise lead to either a compiler crash or runtime failure.
    private def isDisallowed(sym: Symbol)(implicit ctx: Context) = {
      val d = defn
      import d._
      (sym eq Any_isInstanceOf) ||
      (sym eq Any_asInstanceOf) ||
      (sym eq Any_==) ||
      (sym eq Any_!=) ||
      (sym eq Any_##)
    }

    override def transform(tree: Tree)(implicit ctx: Context): Tree = {
      val sym = tree.symbol

      def mayNeedProtectedAccessor(sel: Select, targs: List[Tree], goToSuper: Boolean) =
        if (sym.exists && needsProtectedAccessor(sym, tree.pos)) {
          ctx.debuglog("Adding protected accessor for " + tree)
          transform(makeAccessor(sel, targs))
        }
        else if (goToSuper) super.transform(tree)(ctx.withPhase(thisTransformer.next))
        else tree

      try tree match {
        case impl: Template =>

          def transformTemplate = {
            val ownStats = new ListBuffer[Tree]
            accDefs(currentClass) = ownStats
            // write super accessors after parameters and type aliases (so
            // that order is stable under pickling/unpickling)
            val (params, rest) = impl.body span {
              case td: TypeDef => !td.isClassDef
              case vd: ValOrDefDef => vd.symbol is ParamAccessor
              case _ => false
            }
            ownStats ++= params
            val rest1 = transformStats(rest, tree.symbol)
            accDefs -= currentClass
            ownStats ++= rest1
            cpy.Template(impl)(body = ownStats.toList)
          }
          forwardParamAccessors(transformTemplate)

        case TypeApply(sel @ Select(This(_), name), args) =>
          mayNeedProtectedAccessor(sel, args, goToSuper = false)

        case sel @ Select(qual, name) =>
          def transformSelect = {

            qual match {
              case This(_) =>
                // warn if they are selecting a private[this] member which
                // also exists in a superclass, because they may be surprised
                // to find out that a constructor parameter will shadow a
                // field. See SI-4762.
                /* to be added
                if (settings.lint) {
                  if (sym.isPrivateLocal && sym.paramss.isEmpty) {
                    qual.symbol.ancestors foreach { parent =>
                      parent.info.decls filterNot (x => x.isPrivate || x.isLocalToThis) foreach { m2 =>
                        if (sym.name == m2.name && m2.isGetter && m2.accessed.isMutable) {
                          unit.warning(sel.pos,
                            sym.accessString + " " + sym.fullLocationString + " shadows mutable " + m2.name
                              + " inherited from " + m2.owner + ".  Changes to " + m2.name + " will not be visible within "
                              + sym.owner + " - you may want to give them distinct names.")
                        }
                      }
                    }
                  }
                }
                */

                /*
                 * A trait which extends a class and accesses a protected member
                 *  of that class cannot implement the necessary accessor method
                 *  because its implementation is in an implementation class (e.g.
                 *  Foo$class) which inherits nothing, and jvm access restrictions
                 *  require the call site to be in an actual subclass. So non-trait
                 *  classes inspect their ancestors for any such situations and
                 *  generate the accessors.  See SI-2296.
                 */
                // FIXME - this should be unified with needsProtectedAccessor, but some
                // subtlety which presently eludes me is foiling my attempts.
                val shouldEnsureAccessor = (
                  (currentClass is Trait)
                  && (sym is Protected)
                  && sym.enclosingClass != currentClass
                  && !(sym.owner is PackageClass) // SI-7091 no accessor needed package owned (ie, top level) symbols
                  && !(sym.owner is Trait)
                  && sym.owner.enclosingPackageClass != currentClass.enclosingPackageClass
                  && qual.symbol.info.member(sym.name).exists
                  && !needsProtectedAccessor(sym, tree.pos))
                if (shouldEnsureAccessor) {
                  ctx.log("Ensuring accessor for call to protected " + sym.showLocated + " from " + currentClass)
                  ensureAccessor(sel)
                } else
                  mayNeedProtectedAccessor(sel, Nil, goToSuper = false)

              case Super(_, mix) =>
                if ((sym.isTerm) && !(sym is Method) || (sym is Accessor)) {
                  ctx.error(s"super may be not be used on ${sym.underlyingSymbol}", tree.pos)
                } else if (isDisallowed(sym)) {
                  ctx.error(s"super not allowed here: use this.${name.decode} instead", tree.pos)
                }
                transformSuperSelect(sel)

              case _ =>
                mayNeedProtectedAccessor(sel, Nil, goToSuper = true)
            }
          }
          transformSelect

        case tree: DefDef =>
          cpy.DefDef(tree)(
            rhs = if (isMethodWithExtension(sym)) withInvalidCurrentClass(transform(tree.rhs)) else transform(tree.rhs))

        case TypeApply(sel @ Select(qual, name), args) =>
          mayNeedProtectedAccessor(sel, args, goToSuper = true)

        case Assign(lhs @ Select(qual, name), rhs) =>
          def transformAssign = {
            if ((lhs.symbol is Mutable) &&
              (lhs.symbol is JavaDefined) &&
              needsProtectedAccessor(lhs.symbol, tree.pos)) {
              ctx.debuglog("Adding protected setter for " + tree)
              val setter = makeSetter(lhs)
              ctx.debuglog("Replaced " + tree + " with " + setter)
              transform(Apply(setter, qual :: rhs :: Nil))
            } else
              super.transform(tree)
          }
          transformAssign

        case _ =>
          super.transform(tree)
      }
      catch {
        case ex : AssertionError =>
          if (sym != null && sym != NoSymbol)
            Console.println("TRANSFORM: " + tree.symbol.sourceFile)

          Console.println("TREE: " + tree)
          throw ex
      }
    }

    /** Add a protected accessor, if needed, and return a tree that calls
     *  the accessor and returns the same member. The result is already
     *  typed.
     *  TODO why is targs needed? It looks like we can do without.
     */
    private def makeAccessor(tree: Select, targs: List[Tree])(implicit ctx: Context): Tree = {
      val Select(qual, _) = tree
      val sym = tree.symbol.asTerm
      val clazz = hostForAccessorOf(sym, currentClass)
      assert(clazz.exists, sym)
      ctx.debuglog("Decided for host class: " + clazz)

      val accName    = sym.name.protectedAccessorName

      // if the result type depends on the this type of an enclosing class, the accessor
      // has to take an object of exactly this type, otherwise it's more general
      val receiverType = if (isThisType(sym.info.finalResultType)) clazz.thisType else clazz.classInfo.selfType
      val accType = {
        def accTypeOf(tpe: Type): Type = tpe match {
          case tpe: PolyType =>
            tpe.derivedPolyType(tpe.paramNames, tpe.paramBounds, accTypeOf(tpe.resultType))
          case _ =>
            MethodType(receiverType :: Nil)(mt => tpe.substThis(sym.owner.asClass, MethodParam(mt, 0)))
        }
        accTypeOf(sym.info)
      }
      val protectedAccessor = clazz.info.decl(accName).suchThat(_.signature == accType.signature).symbol orElse {
        val newAcc = ctx.newSymbol(
            clazz, accName, Artifact, accType, coord = tree.pos).enteredAfter(thisTransformer)
        val code = polyDefDef(newAcc, trefs => vrefss => {
          val (receiver :: _) :: tail = vrefss
          val base = receiver.select(sym).appliedToTypes(trefs)
          (base /: vrefss)(Apply(_, _))
        })
        ctx.debuglog("created protected accessor: " + code)
        accDefs(clazz) += code
        newAcc
      }
      val res = This(clazz)
        .select(protectedAccessor)
        .appliedToTypeTrees(targs)
        .appliedTo(qual)
        .withPos(tree.pos)
      ctx.debuglog(s"Replaced $tree with $res")
      res
    }

    /** Add an accessor for field, if needed, and return a selection tree for it .
     *  The result is not typed.
     */
    private def makeSetter(tree: Select)(implicit ctx: Context): Tree = {
      val field = tree.symbol.asTerm
      val clazz = hostForAccessorOf(field, currentClass)
      assert(clazz.exists, field)
      ctx.debuglog("Decided for host class: " + clazz)

      val accName = field.name.protectedSetterName
      val accType = MethodType(clazz.classInfo.selfType :: field.info :: Nil, defn.UnitType)
      val protectedAccessor = clazz.info.decl(accName).symbol orElse {
        val newAcc = ctx.newSymbol(
            clazz, accName, Artifact, accType, coord = tree.pos).enteredAfter(thisTransformer)
        val code = DefDef(newAcc, vrefss => {
          val (receiver :: value :: Nil) :: Nil = vrefss
          Assign(receiver.select(field), value).withPos(tree.pos)
        })
        ctx.debuglog("created protected setter: " + code)
        accDefs(clazz) += code
        newAcc
      }
      This(clazz).select(protectedAccessor).withPos(tree.pos)
    }

    /** Does `sym` need an accessor when accessed from `currentClass`?
     *  A special case arises for classes with explicit self-types. If the
     *  self type is a Java class, and a protected accessor is needed, we issue
     *  an error. If the self type is a Scala class, we don't add an accessor.
     *  An accessor is not needed if the access boundary is larger than the
     *  enclosing package, since that translates to 'public' on the host sys.
     *  (as Java has no real package nesting).
     *
     * If the access happens inside a 'trait', access is more problematic since
     * the implementation code is moved to an '$class' class which does not
     * inherit anything. Since we can't (yet) add accessors for 'required'
     * classes, this has to be signaled as error.
     * FIXME Need to better understand this logic
     */
    private def needsProtectedAccessor(sym: Symbol, pos: Position)(implicit ctx: Context): Boolean = {
      val clazz = currentClass
      val host = hostForAccessorOf(sym, clazz)
      val selfType = host.classInfo.selfType
      def accessibleThroughSubclassing =
        validCurrentClass && (selfType <:< sym.owner.typeRef) && !clazz.is(Trait)

      val isCandidate = (
           sym.is(Protected)
        && sym.is(JavaDefined)
        && !sym.effectiveOwner.is(Package)
        && !accessibleThroughSubclassing
        && (sym.enclosingPackageClass != currentClass.enclosingPackageClass)
        && (sym.enclosingPackageClass == sym.accessBoundary(sym.enclosingPackageClass))
      )
      def isSelfType = !(host.typeRef <:< selfType) && {
        if (selfType.typeSymbol.is(JavaDefined))
          ctx.restrictionError(s"cannot accesses protected $sym from within $clazz with self type $selfType", pos)
        true
      }
      def isJavaProtected = host.is(Trait) && sym.is(JavaDefined) && {
        ctx.restrictionError(
          s"""$clazz accesses protected $sym inside a concrete trait method.
             |Add an accessor in a class extending ${sym.enclosingClass} as a workaround.""".stripMargin,
          pos
        )
        true
      }
      isCandidate && !host.is(Package) && !isSelfType && !isJavaProtected
    }

    /** Return the innermost enclosing class C of referencingClass for which either
     *  of the following holds:
     *     - C is a subclass of sym.owner or
     *     - C is declared in the same package as sym's owner
     */
    private def hostForAccessorOf(sym: Symbol, referencingClass: ClassSymbol)(implicit ctx: Context): ClassSymbol =
      if (referencingClass.derivesFrom(sym.owner)
          || referencingClass.classInfo.selfType <:< sym.owner.typeRef
          || referencingClass.enclosingPackageClass == sym.owner.enclosingPackageClass) {
        assert(referencingClass.isClass, referencingClass)
        referencingClass
      }
      else if (referencingClass.owner.enclosingClass.exists)
        hostForAccessorOf(sym, referencingClass.owner.enclosingClass.asClass)
      else
        referencingClass

    /** Is 'tpe' the type of a member of an enclosing class? */
    private def isThisType(tpe: Type)(implicit ctx: Context): Boolean = tpe match {
      case tpe: ThisType => !tpe.cls.is(PackageClass)
      case tpe: TypeProxy => isThisType(tpe.underlying)
      case _ => false
    }
  }
}