diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 596b1d6bf45f..feaf5d134a09 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -60,6 +60,7 @@ class Compiler { protected def transformPhases: List[List[Phase]] = List(new FirstTransform, // Some transformations to put trees into a canonical form new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars + new ProtectedAccessors, // Add accessors for protected members new ElimPackagePrefixes) :: // Eliminate references to package prefixes in Select nodes List(new CheckStatic, // Check restrictions that apply to @static members new ElimRepeated, // Rewrite vararg parameters and arguments diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index 748795f360e8..9746b60bbd01 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -354,8 +354,8 @@ object NameKinds { val SuperAccessorName = new PrefixNameKind(SUPERACCESSOR, "super$") val InitializerName = new PrefixNameKind(INITIALIZER, "initial$") - val ProtectedAccessorName = new PrefixNameKind(PROTECTEDACCESSOR, "protected$") - val ProtectedSetterName = new PrefixNameKind(PROTECTEDSETTER, "protected$set") // dubious encoding, kept for Scala2 compatibility + val ProtectedGetterName = new PrefixNameKind(PROTECTEDGETTER, "protected_get$") + val ProtectedSetterName = new PrefixNameKind(PROTECTEDSETTER, "protected_set$") val InlineGetterName = new PrefixNameKind(INLINEGETTER, "inline_get$") val InlineSetterName = new PrefixNameKind(INLINESETTER, "inline_set$") @@ -389,9 +389,12 @@ object NameKinds { def infoString: String = "Signed" } - /** Possible name kinds of a method that comes from Scala2 pickling info. */ + /** Possible name kinds of a method that comes from Scala2 pickling info. + * and that need to be unmangled. Note: Scala2 protected accessors and setters + * can be left mangled, so they are not included in thus list. + */ val Scala2MethodNameKinds: List[NameKind] = - List(DefaultGetterName, ExtMethName, UniqueExtMethName, ProtectedAccessorName, ProtectedSetterName) + List(DefaultGetterName, ExtMethName, UniqueExtMethName) def simpleNameKindOfTag : collection.Map[Int, ClassifiedNameKind] = simpleNameKinds def qualifiedNameKindOfTag : collection.Map[Int, QualifiedNameKind] = qualifiedNameKinds diff --git a/compiler/src/dotty/tools/dotc/core/NameTags.scala b/compiler/src/dotty/tools/dotc/core/NameTags.scala index fd65c5243fab..0c3c30f73f9c 100644 --- a/compiler/src/dotty/tools/dotc/core/NameTags.scala +++ b/compiler/src/dotty/tools/dotc/core/NameTags.scala @@ -15,6 +15,10 @@ object NameTags extends TastyFormat.NameTags { // outer accessor that will be filled in by ExplicitOuter. // indicates the number of hops needed to select the outer field. + final val PROTECTEDGETTER = 24 // The name of a protected getter `protected_get$` created by ProtectedAccessors. + + final val PROTECTEDSETTER = 25 // The name of a protected setter `protected_set$` created by ProtectedAccessors. + final val INITIALIZER = 26 // A mixin initializer method final val AVOIDCLASH = 27 // Adds a suffix to avoid a name clash; @@ -47,7 +51,9 @@ object NameTags extends TastyFormat.NameTags { case OUTERSELECT => "OUTERSELECT" case SUPERACCESSOR => "SUPERACCESSOR" - case PROTECTEDACCESSOR => "PROTECTEDACCESSOR" + case INLINEGETTER => "INLINEGETTER" + case INLINESETTER => "INLINESETTER" + case PROTECTEDGETTER => "PROTECTEDGETTER" case PROTECTEDSETTER => "PROTECTEDSETTER" case INITIALIZER => "INITIALIZER" case AVOIDCLASH => "AVOIDCLASH" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index 962acf20b759..10fe328312d6 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -39,8 +39,8 @@ Macro-format: VARIANT Length underlying_NameRef variance_Nat // 0: Contravariant, 1: Covariant SUPERACCESSOR Length underlying_NameRef - PROTECTEDACCESSOR Length underlying_NameRef - PROTECTEDSETTER Length underlying_NameRef + INLINEGETTER Length underlying_NameRef + INLINESETTER Length underlying_NameRef OBJECTCLASS Length underlying_NameRef SIGNED Length original_NameRef resultSig_NameRef paramSig_NameRef* @@ -226,8 +226,8 @@ Standard Section: "Positions" Assoc* object TastyFormat { final val header = Array(0x5C, 0xA1, 0xAB, 0x1F) - val MajorVersion = 7 - val MinorVersion = 1 + val MajorVersion = 8 + val MinorVersion = 0 /** Tags used to serialize names */ class NameTags { @@ -252,18 +252,12 @@ object TastyFormat { final val SUPERACCESSOR = 20 // The name of a super accessor `super$name` created by SuperAccesors. - final val PROTECTEDACCESSOR = 21 // The name of a protected accessor `protected$` created by SuperAccesors. + final val INLINEGETTER = 21 // The name of an inline getter `inline_get$name` - final val PROTECTEDSETTER = 22 // The name of a protected setter `protected$set` created by SuperAccesors. - // This is a dubious encoding for its risk for ambiguity. - // It is kept for Scala-2 compatibility. + final val INLINESETTER = 22 // The name of an inline setter `inline_set$name` final val OBJECTCLASS = 23 // The name of an object class (or: module class) `$`. - final val INLINEGETTER = 24 // The name of an inline getter `inline_get$name` - - final val INLINESETTER = 25 // The name of an inline setter `inline_set$name` - final val SIGNED = 63 // A pair of a name and a signature, used to idenitfy // possibly overloaded methods. } diff --git a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala index 46352f0143d2..c596daee5e2d 100644 --- a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala +++ b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala @@ -14,12 +14,14 @@ import NameKinds.ClassifiedNameKind import ast.Trees._ import util.Property import util.Positions.Position +import config.Printers.transforms /** A utility class for generating access proxies. Currently used for * inline accessors and protected accessors. */ abstract class AccessProxies { import ast.tpd._ + import AccessProxies._ def getterName: ClassifiedNameKind def setterName: ClassifiedNameKind @@ -56,9 +58,11 @@ abstract class AccessProxies { def needsAccessor(sym: Symbol)(implicit ctx: Context): Boolean /** A fresh accessor symbol */ - def newAccessorSymbol(accessed: Symbol, name: TermName, info: Type)(implicit ctx: Context): TermSymbol = - ctx.newSymbol(accessed.owner.enclosingSubClass, name, Synthetic | Method, - info, coord = accessed.pos).entered + def newAccessorSymbol(owner: Symbol, name: TermName, info: Type, pos: Position)(implicit ctx: Context): TermSymbol = { + val sym = ctx.newSymbol(owner, name, Synthetic | Method, info, coord = pos).entered + if (sym.allOverriddenSymbols.exists(!_.is(Deferred))) sym.setFlag(Override) + sym + } /** Create an accessor unless one exists already, and replace the original * access with a reference to the accessor. @@ -73,14 +77,26 @@ abstract class AccessProxies { def refersToAccessed(sym: Symbol) = accessedBy.get(sym) == Some(accessed) - val accessorInfo = + var accessorClass = hostForAccessorOf(accessed: Symbol) + if (!accessorClass.exists) { + val curCls = ctx.owner.enclosingClass + transforms.println(i"${curCls.ownersIterator.toList}%, %") + ctx.error(i"illegal access to protected ${accessed.showLocated} from $curCls", + reference.pos) + accessorClass = curCls + } + + val accessorRawInfo = if (onLHS) MethodType(accessed.info :: Nil, defn.UnitType) else accessed.info.ensureMethodic + val accessorInfo = + accessorRawInfo.asSeenFrom(accessorClass.thisType, accessed.owner) val accessorName = nameKind(accessed.name) + val accessorSymbol = - accessed.owner.info.decl(accessorName).suchThat(refersToAccessed).symbol + accessorClass.info.decl(accessorName).suchThat(refersToAccessed).symbol .orElse { - val acc = newAccessorSymbol(accessed, accessorName, accessorInfo) + val acc = newAccessorSymbol(accessorClass, accessorName, accessorInfo, accessed.pos) accessedBy(acc) = accessed acc } @@ -106,4 +122,8 @@ abstract class AccessProxies { tree } } +} +object AccessProxies { + def hostForAccessorOf(accessed: Symbol)(implicit ctx: Context): Symbol = + ctx.owner.ownersIterator.findSymbol(_.derivesFrom(accessed.owner)) } \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 48080e8718a8..d401ce5aa3c4 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -20,7 +20,6 @@ import typer.ProtoTypes._ import typer.ErrorReporting._ import core.TypeErasure._ import core.Decorators._ -import core.NameKinds._ import dotty.tools.dotc.ast.{Trees, tpd, untpd} import ast.Trees._ import scala.collection.mutable.ListBuffer @@ -315,10 +314,6 @@ object Erasure { } } - /** The erasure typer. - * Also inserts protected accessors where needed. This logic is placed here - * since it is most naturally done in a macro transform. - */ class Typer extends typer.ReTyper with NoChecking { import Boxing._ @@ -327,23 +322,6 @@ object Erasure { if (tree.isTerm) erasedRef(tp) else valueErasure(tp) } - object ProtectedAccessors extends AccessProxies { - def getterName = ProtectedAccessorName - def setterName = ProtectedSetterName - - val insert = new Insert { - def needsAccessor(sym: Symbol)(implicit ctx: Context): Boolean = - false && - sym.isTerm && sym.is(Flags.Protected) && - ctx.owner.enclosingPackageClass != sym.enclosingPackageClass && - !ctx.owner.enclosingClass.derivesFrom(sym.owner) && - { println(i"need protected acc $sym accessed from ${ctx.owner}"); assert(false); false } - } - } - - override def addAccessorDefs(cls: Symbol, body: List[Tree])(implicit ctx: Context): List[Tree] = - ProtectedAccessors.addAccessorDefs(cls, body) - override def promote(tree: untpd.Tree)(implicit ctx: Context): tree.ThisTree[Type] = { assert(tree.hasType) val erasedTp = erasedType(tree) @@ -378,9 +356,6 @@ object Erasure { else super.typedLiteral(tree) - override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = - ProtectedAccessors.insert.accessorIfNeeded(super.typedIdent(tree, pt)) - /** Type check select nodes, applying the following rewritings exhaustively * on selections `e.m`, where `OT` is the type of the owner of `m` and `ET` * is the erased type of the selection's original qualifier expression. @@ -461,12 +436,9 @@ object Erasure { } } - ProtectedAccessors.insert.accessorIfNeeded(recur(typed(tree.qualifier, AnySelectionProto))) + recur(typed(tree.qualifier, AnySelectionProto)) } - override def typedAssign(tree: untpd.Assign, pt: Type)(implicit ctx: Context): Tree = - ProtectedAccessors.insert.accessorIfNeeded(super.typedAssign(tree, pt)) - override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree = if (tree.symbol == ctx.owner.lexicallyEnclosingClass || tree.symbol.isStaticOwner) promote(tree) else { diff --git a/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala index d5b1ec9cc72f..b617dbd1e854 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -45,7 +45,10 @@ class ExtensionMethods extends MiniPhase with DenotTransformer with FullParamete /** the following two members override abstract members in Transform */ override def phaseName: String = ExtensionMethods.name - override def runsAfter = Set(ElimRepeated.name) + override def runsAfter = Set( + ElimRepeated.name, + ProtectedAccessors.name // protected accessors cannot handle code that is moved from class to companion object + ) override def runsAfterGroupsOf = Set(FirstTransform.name) // need companion objects to exist diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index ad4885f14669..19d63314644f 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -216,12 +216,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase case sel: Select => val args1 = transform(args) val sel1 = transformSelect(sel, args1) - if (superAcc.isProtectedAccessor(sel1)) sel1 else cpy.TypeApply(tree1)(sel1, args1) + cpy.TypeApply(tree1)(sel1, args1) case _ => super.transform(tree1) } - case tree @ Assign(sel: Select, _) => - super.transform(superAcc.transformAssign(tree)) case Inlined(call, bindings, expansion) => // Leave only a call trace consisting of // - a reference to the top-level class from which the call was inlined, diff --git a/compiler/src/dotty/tools/dotc/transform/ProtectedAccessors.scala b/compiler/src/dotty/tools/dotc/transform/ProtectedAccessors.scala new file mode 100644 index 000000000000..84d3db353b49 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ProtectedAccessors.scala @@ -0,0 +1,88 @@ +package dotty.tools.dotc +package transform + +import core.Contexts.Context +import core.NameKinds._ +import core.Symbols._ +import core.Flags._ +import core.Decorators._ +import MegaPhase.MiniPhase +import ast.Trees._ +import util.Property + +/** Add accessors for all protected accesses. An accessor is needed if + * according to the rules of the JVM a protected class member is not accesissible + * from the point of access, but is accessible if the access is from an enclosing + * class. In this point a public access method is placed in that enclosing class. + */ +object ProtectedAccessors { + val name = "protectedAccessors" + + private val LHS = new Property.StickyKey[Unit] + + /** Is the current context's owner inside the access boundary established by `sym`? */ + def insideBoundaryOf(sym: Symbol)(implicit ctx: Context): Boolean = { + if (sym.is(JavaDefined)) { + sym.is(JavaStatic) || // Java's static protected definitions are treated as public + ctx.owner.enclosingPackageClass == sym.enclosingPackageClass + } + else { + // For Scala-defined symbols we currently allow private and protected accesses + // from inner packages, and compensate by widening accessibility of such symbols to public. + // It would be good if we could revisit this at some point. + val boundary = sym.accessBoundary(sym.enclosingPackageClass) + ctx.owner.isContainedIn(boundary) || ctx.owner.isContainedIn(boundary.linkedClass) + } + } + + /** Do we need a protected accessor if the current context's owner + * is not in a subclass or subtrait of `sym`? + */ + def needsAccessorIfNotInSubclass(sym: Symbol)(implicit ctx: Context): Boolean = + sym.isTerm && sym.is(Protected) && + !sym.owner.is(Trait) && // trait methods need to be handled specially, are currently always public + !insideBoundaryOf(sym) + + /** Do we need a protected accessor for accessing sym from the current context's owner? */ + def needsAccessor(sym: Symbol)(implicit ctx: Context): Boolean = + needsAccessorIfNotInSubclass(sym) && + !ctx.owner.enclosingClass.derivesFrom(sym.owner) +} + +class ProtectedAccessors extends MiniPhase { + import ast.tpd._ + import ProtectedAccessors._ + + override def phaseName = ProtectedAccessors.name + + object Accessors extends AccessProxies { + def getterName = ProtectedGetterName + def setterName = ProtectedSetterName + + val insert = new Insert { + def needsAccessor(sym: Symbol)(implicit ctx: Context) = ProtectedAccessors.needsAccessor(sym) + } + } + + override def prepareForAssign(tree: Assign)(implicit ctx: Context) = { + tree.lhs match { + case lhs: RefTree if needsAccessor(lhs.symbol) => lhs.putAttachment(LHS, ()) + case _ => + } + ctx + } + + private def isLHS(tree: RefTree) = tree.removeAttachment(LHS).isDefined + + override def transformIdent(tree: Ident)(implicit ctx: Context): Tree = + if (isLHS(tree)) tree else Accessors.insert.accessorIfNeeded(tree) + + override def transformSelect(tree: Select)(implicit ctx: Context): Tree = + if (isLHS(tree)) tree else Accessors.insert.accessorIfNeeded(tree) + + override def transformAssign(tree: Assign)(implicit ctx: Context): Tree = + Accessors.insert.accessorIfNeeded(tree) + + override def transformTemplate(tree: Template)(implicit ctx: Context): Tree = + cpy.Template(tree)(body = Accessors.addAccessorDefs(tree.symbol.owner, tree.body)) +} diff --git a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala index 1435a5aaf099..2cdc5b2c2525 100644 --- a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -1,27 +1,21 @@ package dotty.tools.dotc package transform -import dotty.tools.dotc.transform.MegaPhase._ import dotty.tools.dotc.ast.{Trees, tpd} -import scala.collection.{ mutable, immutable } -import ValueClasses._ -import scala.annotation.tailrec +import scala.collection.mutable +import ValueClasses.isMethodWithExtension import core._ -import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._ -import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._ +import Types._, Contexts._, Names._, Flags._, Symbols._, NameOps._, Trees._ +import TypeUtils._, SymUtils._ +import DenotTransformers.DenotTransformer +import Symbols._ import util.Positions._ import Decorators._ -import NameKinds.{ProtectedAccessorName, ProtectedSetterName, OuterSelectName, SuperAccessorName} -import Symbols._, TypeUtils._, SymUtils._ +import NameKinds.SuperAccessorName -/** This class performs the following functions: - * - * (1) Adds super accessors for all super calls that either +/** This class 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 @@ -144,273 +138,37 @@ class SuperAccessors(thisPhase: DenotTransformer) { (sym eq defn.Any_!=) || (sym eq defn.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 accPos = sel.pos.focus - val protectedAccessor = clazz.info.decl(accName).suchThat(_.signature == accType.signature).symbol orElse { - val newAcc = ctx.newSymbol( - clazz, accName, Artifact | Method, accType, coord = accPos).enteredAfter(thisPhase) - val code = polyDefDef(newAcc, trefs => vrefss => { - val (receiver :: _) :: tail = vrefss - val base = receiver.select(sym).appliedToTypes(trefs) - (base /: tail)(Apply(_, _)).withPos(accPos) - }) - 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 accPos = tree.pos.focus - val protectedAccessor = clazz.info.decl(accName).suchThat(_.signature == accType.signature).symbol orElse { - val newAcc = ctx.newSymbol( - clazz, accName, Artifact, accType, coord = accPos).enteredAfter(thisPhase) - val code = polyDefDef(newAcc, trefs => vrefss => { - val (receiver :: _) :: tail = vrefss - val base = receiver.select(sym).appliedToTypes(trefs) - (base /: vrefss)(Apply(_, _)).withPos(accPos) - }) - 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 accPos = tree.pos.focus - val protectedAccessor = clazz.info.decl(accName).symbol orElse { - val newAcc = ctx.newSymbol( - clazz, accName, Artifact | Method, accType, coord = accPos).enteredAfter(thisPhase) - val code = DefDef(newAcc, vrefss => { - val (receiver :: value :: Nil) :: Nil = vrefss - Assign(receiver.select(field), value).withPos(accPos) - }) - 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 - def accessibleThroughSubclassing = - validCurrentClass && clazz.classInfo.selfType.derivesFrom(sym.owner) && !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)) - ) - - if (!isCandidate) - return false - - val host = hostForAccessorOf(sym, clazz) - val hostSelfType = host.classInfo.selfType - - def isSelfType = !(host.appliedRef <:< hostSelfType) && { - if (hostSelfType.typeSymbol.is(JavaDefined)) - ctx.restrictionError( - s"cannot accesses protected $sym from within $clazz with host self type $hostSelfType", 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 - } - !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.appliedRef - || 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 + + /** If an accesses to protected member of a class comes from a trait, + * or would need a protected accessor placed in a trait, we cannot + * perform the access to the protected member directly since jvm access + * restrictions require the call site to be in an actual subclass and + * traits don't count as subclasses in this respect. In this case + * we generate a super accessor instead. See SI-2296. + */ + def needsSuperAccessor = + ProtectedAccessors.needsAccessorIfNotInSubclass(sym) && + AccessProxies.hostForAccessorOf(sym).is(Trait) qual match { - case _: This => + case _: This if needsSuperAccessor => /* * 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. + * because jvm access restrictions require the call site to be in + * an actual subclass and traits don't count as subclasses in this + * respect. We generate a super accessor itself, which will be fixed + * by the implementing class. 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) - + superAccessorCall(sel) 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) + sel } - else tree } /** Wrap template to template transform `op` with needed initialization and finalization */ diff --git a/tests/run/protectedSuper.scala b/tests/run/protectedSuper.scala new file mode 100644 index 000000000000..df21c9eb4541 --- /dev/null +++ b/tests/run/protectedSuper.scala @@ -0,0 +1,36 @@ +package p { + class A { + protected def foo(): Int = 1 + protected def fuzz(): Int = 2 + } +} +package q { + class B extends p.A { // protected accessor for foo + trait BInner { + def bar() = foo() + } + } + trait Inner extends p.A { + def bar() = foo() // shared super accessor for foo + // new super accessor for fuzz + class InnerInner { + def bar() = foo() + def baz() = fuzz() + } + } +} +object Test extends App { + val b = new q.B + val bi = new b.BInner {} + assert(bi.bar() == 1) + + class C extends p.A with q.Inner { + // implements super accessors for foo and fuzz + } + val c = new C + assert(c.bar() == 1) + + val d = new c.InnerInner + assert(d.bar() == 1) + assert(d.baz() == 2) +}