
scala.tools.nsc.transform.ExplicitOuter.scala Maven / Gradle / Ivy
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package scala
package tools.nsc
package transform
import symtab._
import Flags.{CASE => _, _}
import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer
import scala.tools.nsc.Reporting.WarningCategory
/** This class ...
*
* @author Martin Odersky
*/
abstract class ExplicitOuter extends InfoTransform
with TypingTransformers
with ast.TreeDSL
{
import global._
import definitions._
import CODE._
/** The following flags may be set by this phase: */
override def phaseNewFlags: Long = notPROTECTED
/** the name of the phase: */
val phaseName: String = "explicitouter"
/** This class does not change linearization */
override def changesBaseClasses = false
protected def newTransformer(unit: CompilationUnit): Transformer =
new ExplicitOuterTransformer(unit)
/** Is given clazz an inner class? */
private def isInner(clazz: Symbol) =
!clazz.isPackageClass && !clazz.outerClass.isStaticOwner
private def haveSameOuter(parent: Type, clazz: Symbol) = {
val owner = clazz.owner
val parentSym = parent.typeSymbol
parentSym.isClass && owner.isClass &&
(owner isSubClass parentSym.owner) &&
owner.thisType =:= parent.prefix
}
/** Does given clazz define an outer field? */
def hasOuterField(clazz: Symbol) = {
val parent = clazz.info.firstParent
// space optimization: inherit the $outer pointer from the parent class if
// we know that it will point to the correct instance.
def canReuseParentOuterField = !parent.typeSymbol.isJavaDefined && haveSameOuter(parent, clazz)
isInner(clazz) && !clazz.isTrait && !canReuseParentOuterField
}
private def outerField(clazz: Symbol): Symbol = {
val result = clazz.info.member(nme.OUTER_LOCAL)
assert(result != NoSymbol, "no outer field in "+clazz+" at "+phase)
result
}
class RemoveBindingsTransformer(toRemove: Set[Symbol]) extends Transformer {
override def transform(tree: Tree) = tree match {
case Bind(_, body) if toRemove(tree.symbol) => super.transform(body)
case _ => super.transform(tree)
}
}
def outerAccessor(clazz: Symbol): Symbol = {
val firstTry = clazz.info.decl(nme.expandedName(nme.OUTER, clazz))
if (firstTry != NoSymbol && firstTry.outerSource == clazz) firstTry
else findOrElse(clazz.info.decls)(_.outerSource == clazz)(NoSymbol)
}
def newOuterAccessor(clazz: Symbol) = {
val accFlags = SYNTHETIC | ARTIFACT | STABLE | ( if (clazz.isTrait) DEFERRED else 0 )
val sym = clazz.newMethod(nme.OUTER, clazz.pos, accFlags)
val restpe = if (clazz.isTrait) clazz.outerClass.tpe_* else clazz.outerClass.thisType
sym expandName clazz
sym.referenced = clazz
sym setInfo MethodType(Nil, restpe)
}
def newOuterField(clazz: Symbol) = {
val accFlags = SYNTHETIC | ARTIFACT | PARAMACCESSOR | ( if (clazz.isEffectivelyFinal) PrivateLocal else PROTECTED )
val sym = clazz.newValue(nme.OUTER_LOCAL, clazz.pos, accFlags)
sym setInfo clazz.outerClass.thisType
}
/**
* Will the outer accessor of the `clazz` subsume the outer accessor of
* `mixin`?
*
* This arises when an inner object mixes in its companion trait.
*
* {{{
* class C {
* trait T { C.this } // C\$T\$\$\$outer\$ : C
* object T extends T { C.this } // C\$T\$\$\$outer\$ : C.this.type
* }
* }}}
*
* See scala/bug#7242.
}}
*/
private def skipMixinOuterAccessor(clazz: Symbol, mixin: Symbol) = {
// Reliant on the current scheme for name expansion, the expanded name
// of the outer accessors in a trait and its companion object are the same.
// If the assumption is one day falsified, run/t7424.scala will let us know.
clazz.fullName == mixin.fullName
}
/**
* The type transformation method:
*
*
* -
* Add an outer parameter to the formal parameters of a constructor
* in an inner non-trait class;
*
* -
* Add a protected \$outer field to an inner class which is
* not a trait.
*
* -
*
* Add an outer accessor \$outer\$\$C to every inner class
* with fully qualified name C that is not an interface.
* The outer accessor is abstract for traits, concrete for other
* classes.
*
*
* 3a. Also add overriding accessor defs to every class that inherits
* mixin classes with outer accessor defs (unless the superclass
* already inherits the same mixin).
*
*
* -
* Make all super accessors and modules in traits non-private, mangling
* their names.
*
* -
* Remove protected flag from all members of traits.
*
*
* Note: this transformInfo need not be reflected as the JVM reflection already
* elides outer pointers.
*/
def transformInfo(sym: Symbol, tp: Type): Type = tp match {
case MethodType(params, resTp) =>
val resTpTransformed = transformInfo(sym, resTp)
val paramsWithOuter =
if (sym.isClassConstructor && isInner(sym.owner)) // 1
sym.newValueParameter(nme.OUTER_ARG, sym.pos, ARTIFACT).setInfo(sym.owner.outerClass.thisType) :: params
else params
if ((resTpTransformed ne resTp) || (paramsWithOuter ne params)) MethodType(paramsWithOuter, resTpTransformed)
else tp
case ClassInfoType(parents, decls, clazz) if !clazz.isJava =>
var decls1 = decls
if (isInner(clazz) && !clazz.isInterface) {
decls1 = decls.cloneScope
decls1 enter newOuterAccessor(clazz) // 3
if (hasOuterField(clazz)) //2
decls1 enter newOuterField(clazz)
}
if (!clazz.isTrait && !parents.isEmpty) {
for (mc <- clazz.mixinClasses) {
val mixinOuterAcc: Symbol = exitingExplicitOuter(outerAccessor(mc))
if (mixinOuterAcc != NoSymbol) {
if (skipMixinOuterAccessor(clazz, mc))
debuglog(s"Reusing outer accessor symbol of $clazz for the mixin outer accessor of $mc")
else {
if (decls1 eq decls) decls1 = decls.cloneScope
val newAcc = mixinOuterAcc.cloneSymbol(clazz, mixinOuterAcc.flags & ~DEFERRED).setPos(clazz.pos)
newAcc setInfo (clazz.thisType memberType mixinOuterAcc)
decls1 enter newAcc
}
}
}
}
if (decls1 eq decls) tp else ClassInfoType(parents, decls1, clazz)
case PolyType(tparams, restp) =>
val restp1 = transformInfo(sym, restp)
if (restp eq restp1) tp else PolyType(tparams, restp1)
case _ =>
tp
}
/** A base class for transformers that maintain outerParam
* values for outer parameters of constructors.
* The class provides methods for referencing via outer.
*/
abstract class OuterPathTransformer(initLocalTyper: analyzer.Typer) extends TypingTransformer(initLocalTyper) {
def this(unit: CompilationUnit) { this(newRootLocalTyper(unit)) }
/** The directly enclosing outer parameter, if we are in a constructor */
protected var outerParam: Symbol = NoSymbol
/** The first outer selection from currently transformed tree.
* The result is typed but not positioned.
*
* Will return `EmptyTree` if there is no outer accessor because of a premature self reference.
*/
protected def outerValue: Tree = outerParam match {
case NoSymbol => outerSelect(gen.mkAttributedThis(currentClass))
case outerParam => gen.mkAttributedIdent(outerParam)
}
/** Select and apply outer accessor from 'base'
* The result is typed but not positioned.
* If the outer access is from current class and current class is final
* take outer field instead of accessor
*
* Will return `EmptyTree` if there is no outer accessor because of a premature self reference.
*/
private def outerSelect(base: Tree): Tree = {
val baseSym = base.tpe.typeSymbol
val outerAcc = outerAccessor(baseSym)
if (outerAcc == NoSymbol) {
if (baseSym.ownersIterator.exists(isUnderConstruction)) {
// e.g neg/t6666.scala
// The caller will report the error with more information.
EmptyTree
} else {
globalError(currentOwner.pos, s"Internal error: unable to find the outer accessor symbol of $baseSym")
EmptyTree
}
} else {
val currentClass = this.currentClass //todo: !!! if this line is removed, we get a build failure that protected$currentClass need an override modifier
// outerFld is the $outer field of the current class, if the reference can
// use it (i.e. reference is allowed to be of the form this.$outer),
// otherwise it is NoSymbol
val outerFld =
if (outerAcc.owner == currentClass &&
!outerAcc.owner.isTrait &&
base.tpe =:= currentClass.thisType &&
outerAcc.owner.isEffectivelyFinal)
outerField(currentClass) suchThat (_.owner == currentClass)
else
NoSymbol
val path =
if (outerFld != NoSymbol) Select(base, outerFld)
else Apply(Select(base, outerAcc), Nil)
localTyper typed path
}
}
/** The path
* `base`.\$outer\$\$C1 ... .\$outer\$\$Cn
* which refers to the outer instance of class to of
* value base. The result is typed but not positioned.
*/
@tailrec
protected final def outerPath(base: Tree, from: Symbol, to: Symbol): Tree = {
//Console.println("outerPath from "+from+" to "+to+" at "+base+":"+base.tpe)
if (from == to) base
else {
val outerSel = outerSelect(base)
if (outerSel.isEmpty) EmptyTree
else outerPath(outerSel, from.outerClass, to)
}
}
/** The stack of class symbols in which a call to this() or to the super
* constructor, or early definition is active
*/
protected def isUnderConstruction(clazz: Symbol) = selfOrSuperCalls contains clazz
protected val selfOrSuperCalls = collection.mutable.Stack[Symbol]()
override def transform(tree: Tree): Tree = {
def sym = tree.symbol
val savedOuterParam = outerParam
try {
tree match {
case Template(_, _, _) =>
outerParam = NoSymbol
case DefDef(_, _, _, (param :: _) :: _, _, _) if sym.isClassConstructor && isInner(sym.owner) =>
outerParam = param.symbol
assert(outerParam.name startsWith nme.OUTER, outerParam.name)
case _ =>
}
if ((treeInfo isSelfOrSuperConstrCall tree) || (treeInfo isEarlyDef tree)) {
selfOrSuperCalls push currentOwner.owner
val transformed = super.transform(tree)
selfOrSuperCalls.pop()
transformed
} else
super.transform(tree)
}
finally outerParam = savedOuterParam
}
}
/** The phase performs the following transformations (more or less...):
*
* (1) An class which is not an interface and is not static gets an outer accessor (@see outerDefs).
* (1a) A class which is not a trait gets an outer field.
*
* (4) A constructor of a non-trait inner class gets an outer parameter.
*
* (5) A reference C.this where C refers to an outer class is replaced by a selection
* `this.\$outer\$\$C1 ... .\$outer\$\$Cn` (@see outerPath)
*
* (7) A call to a constructor Q.(args) or Q.\$init\$(args) where Q != this and
* the constructor belongs to a non-static class is augmented by an outer argument.
* E.g. Q.(OUTER, args) where OUTER
* is the qualifier corresponding to the singleton type Q.
*
* (8) A call to a constructor this.(args) in a
* secondary constructor is augmented to this.(OUTER, args)
* where OUTER is the last parameter of the secondary constructor.
*
* (9) Remove private modifier from class members M that are accessed from an inner class.
*
* (10) Remove protected modifier from class members M that are accessed
* without a super qualifier accessed from an inner class or trait.
*
* (11) Remove private and protected modifiers from type symbols
*
* Note: The whole transform is run in phase explicitOuter.next.
*
* TODO: Make this doc reflect what's actually going on.
* Some of the deviations are motivated by separate compilation
* (name mangling based on usage is inherently unstable).
* Now that traits are compiled 1:1 to interfaces, they can have private members,
* so there's also less need to make trait members non-private
* (they still may need to be implemented in subclasses, though we could make those protected...).
*/
class ExplicitOuterTransformer(unit: CompilationUnit) extends OuterPathTransformer(unit) {
transformer =>
/** The definition tree of the outer accessor of current class
*/
def outerFieldDef: Tree = ValDef(outerField(currentClass))
/** The definition tree of the outer accessor of current class
*/
def outerAccessorDef: Tree = localTyper typed {
val acc = outerAccessor(currentClass)
val rhs = if (acc.isDeferred) EmptyTree else Select(This(currentClass), outerField(currentClass))
DefDef(acc, rhs)
}
/** The definition tree of the outer accessor for class mixinClass.
*
* @param mixinClass The mixin class which defines the abstract outer
* accessor which is implemented by the generated one.
* @note Pre-condition: `mixinClass` is an inner class
*/
def mixinOuterAccessorDef(mixinClass: Symbol): Tree = {
val outerAcc = outerAccessor(mixinClass) overridingSymbol currentClass
def mixinPrefix = (currentClass.thisType baseType mixinClass).prefix
assert(outerAcc != NoSymbol, "No outer accessor for inner mixin " + mixinClass + " in " + currentClass)
assert(outerAcc.alternatives.size == 1, s"Multiple outer accessors match inner mixin $mixinClass in $currentClass : ${outerAcc.alternatives.map(_.defString)}")
// I added the mixinPrefix.typeArgs.nonEmpty condition to address the
// crash in scala/bug#4970. I feel quite sure this can be improved.
val path = (
if (mixinClass.owner.isTerm) gen.mkAttributedThis(mixinClass.owner.enclClass)
else if (mixinPrefix.typeArgs.nonEmpty) gen.mkAttributedThis(mixinPrefix.typeSymbol)
else gen.mkAttributedQualifier(mixinPrefix)
)
// Need to cast for nested outer refs in presence of self-types. See ticket #3274.
localTyper typed DefDef(outerAcc, gen.mkCast(transformer.transform(path), outerAcc.info.resultType))
}
/** The main transformation method */
override def transform(tree: Tree): Tree = {
val sym = tree.symbol
if (sym != null && sym.isType) { // (9)
if (sym.isPrivate) sym setFlag notPRIVATE
if (sym.isProtected && !sym.isJavaDefined) sym setFlag notPROTECTED
}
tree match {
case Template(parents, self, decls) =>
val newDefs = new ListBuffer[Tree]
atOwner(tree, currentOwner) {
if (!currentClass.isInterface) {
if (isInner(currentClass)) {
if (hasOuterField(currentClass))
newDefs += outerFieldDef // (1a)
newDefs += outerAccessorDef // (1)
}
if (!currentClass.isTrait)
for (mc <- currentClass.mixinClasses)
if (outerAccessor(mc) != NoSymbol && !skipMixinOuterAccessor(currentClass, mc))
newDefs += mixinOuterAccessorDef(mc)
}
}
super.transform(
deriveTemplate(tree)(decls =>
if (newDefs.isEmpty) decls
else decls ::: newDefs.toList
)
)
case DefDef(_, _, _, vparamss, _, rhs) =>
if (sym.isClassConstructor) {
val clazz = sym.owner
val vparamss1 =
if (isInner(clazz)) { // (4)
if (isUnderConstruction(clazz.outerClass)) {
reporter.error(tree.pos, s"Implementation restriction: ${clazz.fullLocationString} requires premature access to ${clazz.outerClass}.")
}
val outerParam =
sym.newValueParameter(nme.OUTER, sym.pos, ARTIFACT) setInfo clazz.outerClass.thisType
((ValDef(outerParam) setType NoType) :: vparamss.head) :: vparamss.tail
} else vparamss
super.transform(copyDefDef(tree)(vparamss = vparamss1))
} else
super.transform(tree)
case This(qual) =>
if (sym == currentClass || sym.hasModuleFlag && sym.isStatic) tree
else atPos(tree.pos)(outerPath(outerValue, currentClass.outerClass, sym)) // (5)
case Select(qual, name) =>
// make not private symbol accessed from inner classes, as well as
// symbols accessed from @inline methods
//
// See scala/bug#6552 for an example of why `sym.owner.enclMethod hasAnnotation ScalaInlineClass`
// is not suitable; if we make a method-local class non-private, it mangles outer pointer names.
def enclMethodIsInline = closestEnclMethod(currentOwner) hasAnnotation ScalaInlineClass
// scala/bug#8710 The extension method condition reflects our knowledge that a call to `new Meter(12).privateMethod`
// with later be rewritten (in erasure) to `Meter.privateMethod$extension(12)`.
if ((currentClass != sym.owner || enclMethodIsInline) && !sym.isMethodWithExtension)
sym.makeNotPrivate(sym.owner)
val qsym = qual.tpe.widen.typeSymbol
if (sym.isProtected && //(4)
(qsym.isTrait || !(qual.isInstanceOf[Super] || (qsym isSubClass currentClass))))
sym setFlag notPROTECTED
super.transform(tree)
case Apply(sel @ Select(qual, nme.CONSTRUCTOR), args) if isInner(sel.symbol.owner) =>
val outerVal = atPos(tree.pos)(qual match {
// it's a call between constructors of same class
case _: This =>
assert(outerParam != NoSymbol, tree)
outerValue
case _ =>
gen.mkAttributedQualifier(qual.tpe.prefix match {
case NoPrefix => sym.owner.outerClass.thisType
case x => x
})
})
super.transform(treeCopy.Apply(tree, sel, outerVal :: args))
// for the new pattern matcher
// base..eq(o) --> base.$outer().eq(o) if there's an accessor, else the whole tree becomes TRUE
// TODO remove the synthetic `` method from outerFor??
case Apply(eqsel@Select(eqapp@Apply(sel@Select(base, nme.OUTER_SYNTH), Nil), eq), args) =>
val outerFor = sel.symbol.owner
val acc = outerAccessor(outerFor)
if (acc == NoSymbol ||
// since we can't fix scala/bug#4440 properly (we must drop the outer accessors of final classes when there's no immediate reference to them in sight)
// at least don't crash... this duplicates maybeOmittable from constructors
(acc.owner.isEffectivelyFinal && !acc.isOverridingSymbol)) {
if (!base.tpe.hasAnnotation(UncheckedClass))
runReporting.warning(tree.pos, "The outer reference in this type test cannot be checked at run time.", WarningCategory.Unchecked, currentOwner)
transform(TRUE) // urgh... drop condition if there's no accessor (or if it may disappear after constructors)
} else {
// println("(base, acc)= "+(base, acc))
val outerSelect = localTyper typed Apply(Select(base, acc), Nil)
// achieves the same as: localTyper typed atPos(tree.pos)(outerPath(base, base.tpe.typeSymbol, outerFor.outerClass))
// println("(b, tpsym, outerForI, outerFor, outerClass)= "+ (base, base.tpe.typeSymbol, outerFor, sel.symbol.owner, outerFor.outerClass))
// println("outerSelect = "+ outerSelect)
transform(treeCopy.Apply(tree, treeCopy.Select(eqsel, outerSelect, eq), args))
}
case _ =>
val x = super.transform(tree)
if (x.tpe eq null) x
else x setType transformInfo(currentOwner, x.tpe)
}
}
/** The transformation method for whole compilation units */
override def transformUnit(unit: CompilationUnit): Unit = {
exitingExplicitOuter(super.transformUnit(unit))
}
}
override def newPhase(prev: scala.tools.nsc.Phase): StdPhase =
new Phase(prev)
class Phase(prev: scala.tools.nsc.Phase) extends super.Phase(prev) {
override val checkable = false
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy