aboutsummaryrefslogblamecommitdiff
path: root/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala
blob: 84a32f93bf081e29dfb3c69b62791e0b86af9eda (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 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 NameKinds.{ProtectedAccessorName, ProtectedSetterName, OuterSelectName, SuperAccessorName}
import Symbols._, TypeUtils._

/** This class performs the following functions:
 *
 *  (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) Adds protected accessors if the access to the protected member happens
 *  in a class which is not a subclass of the member's owner.
 *
 *  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
 */
class SuperAccessors(thisTransformer: DenotTransformer) {

  import tpd._


    /** 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 `invalidEnclClass` 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

    /** List buffers for new accessor definitions, indexed by class */
    private val accDefs = mutable.Map[Symbol, mutable.ListBuffer[Tree]]()

    /** A super accessor call corresponding to `sel` */
    private def superAccessorCall(sel: Select)(implicit ctx: Context) = {
      val Select(qual, name) = sel
      val sym = sel.symbol
      val clazz = qual.symbol.asClass
      var superName = SuperAccessorName(name.asTermName)
      if (clazz is Trait) superName = superName.expandedName(clazz)
      val superInfo = sel.tpe.widenSingleton.ensureMethodic

      val superAcc = clazz.info.decl(superName)
        .suchThat(_.signature == superInfo.signature).symbol
        .orElse {
          ctx.debuglog(s"add super acc ${sym.showLocated} to $clazz")
          val deferredOrPrivate = if (clazz is Trait) Deferred else Private
          val acc = ctx.newSymbol(
              clazz, superName, Artifact | Method | deferredOrPrivate,
              superInfo, 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)
    }

    /** Check selection `super.f` for conforming to rules. If necessary,
     *  replace by a super accessor call.
     */
    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.isTerm && !sym.is(Method, butNot = Accessor) && !ctx.owner.is(ParamForwarder))
        // ParamForwaders as installed ParamForwarding.scala do use super calls to vals
        ctx.error(s"super may be not be used on ${sym.underlyingSymbol}", sel.pos)
      else if (isDisallowed(sym))
        ctx.error(s"super not allowed here: use this.${sel.name.decode} instead", sel.pos)
      else if (sym is Deferred) {
        val member = sym.overridingSymbol(clazz)
        if (!mix.name.isEmpty ||
            !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 ctx.log(i"ok super $sel ${sym.showLocated} $member $clazz ${member.isIncompleteIn(clazz)}")
      }
      else if (mix.name.isEmpty && !(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.name.isEmpty &&
          ((clazz is Trait) || clazz != ctx.owner.enclosingClass || !validCurrentClass))
        superAccessorCall(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_##)
    }

    /** Replace `sel` (or `sel[targs]` if `targs` is nonempty) with a protected accessor
     *  call, if necessary.
     */
    private def ensureProtectedAccessOK(sel: Select, targs: List[Tree])(implicit ctx: Context) = {
      val sym = sel.symbol
      if (sym.isTerm && !sel.name.is(OuterSelectName) && needsProtectedAccessor(sym, sel.pos)) {
        ctx.debuglog("Adding protected accessor for " + sel)
        protectedAccessorCall(sel, targs)
      } else sel
    }

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

      val accName = ProtectedAccessorName(sym.name)

      // 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.derivedLambdaType(tpe.paramNames, tpe.paramInfos, accTypeOf(tpe.resultType))
          case _ =>
            MethodType(receiverType :: Nil)(mt => tpe.substThis(sym.owner.asClass, mt.newParamRef(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 = sel.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(sel.pos)
      ctx.debuglog(s"Replaced $sel with $res")
      res
    }

    def isProtectedAccessor(tree: Tree)(implicit ctx: Context): Boolean = tree match {
      case Apply(TypeApply(Select(_, name), _), qual :: Nil) =>
        name.is(ProtectedAccessorName) || name.is(ProtectedSetterName)
      case _ => false
    }

    /** Add a protected accessor, if needed, and return a tree that calls
     *  the accessor and returns the same member. The result is already
     *  typed.
     */
    private def protectedAccessor(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 = ProtectedAccessorName(sym.name)

      // 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
      def accTypeOf(tpe: Type): Type = tpe match {
        case tpe: PolyType =>
          tpe.derivedLambdaType(tpe.paramNames, tpe.paramInfos, accTypeOf(tpe.resultType))
        case _ =>
          MethodType(receiverType :: Nil)(mt => tpe.substThis(sym.owner.asClass, mt.newParamRef(0)))
      }
      val accType = 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 protectedSetter(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 = ProtectedSetterName(field.name)
      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' a ThisType, or a type proxy with a ThisType as transitively underlying type? */
    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
    }

    /** Transform select node, adding super and protected accessors as needed */
    def transformSelect(tree: Tree, targs: List[Tree])(implicit ctx: Context) = {
      val sel @ Select(qual, name) = tree
      val sym = sel.symbol
      qual match {
        case _: This =>
          /*
           * 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 (from scalac's SuperAccessors)
          // - 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, sel.pos))
          if (shouldEnsureAccessor) {
            ctx.log("Ensuring accessor for call to protected " + sym.showLocated + " from " + currentClass)
            superAccessorCall(sel)
          } else
            ensureProtectedAccessOK(sel, targs)

        case Super(_, mix) =>
          transformSuperSelect(sel)

        case _ =>
          ensureProtectedAccessOK(sel, targs)
      }
    }

    /** Transform assignment, adding a protected setter if needed */
    def transformAssign(tree: Tree)(implicit ctx: Context) = {
      val Assign(lhs @ Select(qual, name), rhs) = tree
      if ((lhs.symbol is Mutable) &&
        (lhs.symbol is JavaDefined) &&
        needsProtectedAccessor(lhs.symbol, tree.pos)) {
        ctx.debuglog("Adding protected setter for " + tree)
        val setter = protectedSetter(lhs)
        ctx.debuglog("Replaced " + tree + " with " + setter)
        setter.appliedTo(qual, rhs)
      }
      else tree
    }

    /** Wrap template to template transform `op` with needed initialization and finalization */
    def wrapTemplate(tree: Template)(op: Template => Template)(implicit ctx: Context) = {
      accDefs(currentClass) = new mutable.ListBuffer[Tree]
      val impl = op(tree)
      val accessors = accDefs.remove(currentClass).get
      if (accessors.isEmpty) impl
      else {
        val (params, rest) = impl.body span {
          case td: TypeDef => !td.isClassDef
          case vd: ValOrDefDef => vd.symbol.flags is ParamAccessor
          case _ => false
        }
        cpy.Template(impl)(body = params ++ accessors ++ rest)
      }
    }

    /** Wrap `DefDef` producing operation `op`, potentially setting `invalidClass` info */
    def wrapDefDef(ddef: DefDef)(op: => DefDef)(implicit ctx: Context) =
      if (isMethodWithExtension(ddef.symbol)) withInvalidCurrentClass(op) else op
}