aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala')
-rw-r--r--compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala424
1 files changed, 424 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala
new file mode 100644
index 000000000..fea478c9b
--- /dev/null
+++ b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala
@@ -0,0 +1,424 @@
+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 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 = name.superName
+ if (clazz is Trait) supername = supername.expandedName(clazz)
+
+ val superAcc = clazz.info.decl(supername).suchThat(_.signature == sym.signature).symbol orElse {
+ ctx.debuglog(s"add super acc ${sym.showLocated} to $clazz")
+ val deferredOrPrivate = if (clazz is Trait) Deferred | ExpandedName else Private
+ val acc = ctx.newSymbol(
+ clazz, supername, SuperAccessor | Artifact | Method | deferredOrPrivate,
+ 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)
+ }
+
+ /** 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.isOuterSelect && 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 = 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 = 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.isProtectedAccessorName
+ 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 = 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
+ 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)))
+ }
+ 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 = 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' 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
+}