diff options
Diffstat (limited to 'compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala')
-rw-r--r-- | compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala | 424 |
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 +} |