/* NSC -- new Scala compiler
* Copyright 2005-2006 LAMP/EPFL
* @author Martin Odersky
*/
// $Id$
package scala.tools.nsc.typechecker
import scala.collection.mutable.ListBuffer
import nsc.symtab.Flags._
import scala.tools.nsc.util.{Position}
/** This phase adds super accessors for all super calls that
* either appear in a trait or have as a target a member of some outer class.
* It also replaces references to parameter accessors with aliases by super
* references to these aliases.
* The phase also checks that symbols accessed from super are not abstract,
* or are overridden by an abstract override.
* Finally, the phase also mangles the names of class-members which are private
* up to an enclosing non-package class, in order to avoid overriding conflicts.
*
* @author Martin Odersky
* @version 1.0
*/
abstract class SuperAccessors extends transform.Transform with transform.TypingTransformers {
// inherits abstract value `global' and class `Phase' from Transform
import global._
import posAssigner.atPos
import typer.typed
/** the following two members override abstract members in Transform */
val phaseName: String = "superaccessors"
protected def newTransformer(unit: CompilationUnit): Transformer =
new SuperAccTransformer(unit)
class SuperAccTransformer(unit: CompilationUnit) extends TypingTransformer(unit) {
private var validCurrentOwner = true
private var accDefs: List[(Symbol, ListBuffer[Tree])] = List()
private val typer = analyzer.newTyper(analyzer.rootContext(unit))
private def accDefBuf(clazz: Symbol) =
accDefs.dropWhile(_._1 != clazz).head._2
/*
private def transformArgs(args: List[Tree], formals: List[Type]) = {
if (!formals.isEmpty && formals.last.symbol == definitions.ByNameParamClass)
((args take (formals.length - 1) map transform) :::
withInvalidOwner { args drop (formals.length - 1) map transform })
else
args map transform
}
*/
private def transformArgs(args: List[Tree], formals: List[Type]) =
List.map2(args, formals){ (arg, formal) =>
if (formal.typeSymbol == definitions.ByNameParamClass)
withInvalidOwner { checkPackedConforms(transform(arg), formal.typeArgs.head) }
else transform(arg)
} :::
(args drop formals.length map transform)
private def checkPackedConforms(tree: Tree, pt: Type): Tree = {
if (tree.tpe exists (_.typeSymbol.isExistentialSkolem)) {
val packed = typer.packedType(tree, NoSymbol)
if (!(packed <:< pt)) typer.infer.typeError(tree.pos, packed, pt)
}
tree
}
/** Check that a class and its companion object to not both define
* a class or module with same name
*/
private def checkCompanionNameClashes(sym: Symbol) =
if (!sym.owner.isModuleClass) {
val linked = sym.owner.linkedClassOfClass
if (linked != NoSymbol) {
var other = linked.info.decl(sym.name.toTypeName).filter(_.isClass)
if (other == NoSymbol)
other = linked.info.decl(sym.name.toTermName).filter(_.isModule)
if (other != NoSymbol)
unit.error(sym.pos, "name clash: "+sym.owner+" defines "+sym+
"\nand its companion "+sym.owner.linkedModuleOfClass+" also defines "+
other)
}
}
private def transformSuperSelect(tree: Tree) = tree match {
case Select(sup @ Super(_, mix), name) =>
val sym = tree.symbol
val clazz = sup.symbol
if (sym.isDeferred) {
val member = sym.overridingSymbol(clazz);
if (mix != nme.EMPTY.toTypeName || member == NoSymbol ||
!((member hasFlag ABSOVERRIDE) && member.isIncompleteIn(clazz)))
unit.error(tree.pos, ""+sym+sym.locationString+" is accessed from super. It may not be abstract "+
"unless it is overridden by a member declared `abstract' and `override'");
}
if (tree.isTerm && mix == nme.EMPTY.toTypeName &&
(clazz.isTrait || clazz != currentOwner.enclClass || !validCurrentOwner)) {
val supername = nme.superName(sym.name)
var superAcc = clazz.info.decl(supername).suchThat(_.alias == sym)
if (superAcc == NoSymbol) {
if (settings.debug.value) log("add super acc " + sym + sym.locationString + " to `" + clazz);//debug
superAcc =
clazz.newMethod(tree.pos, supername)
.setFlag(SUPERACCESSOR | PRIVATE)
.setAlias(sym)
var superAccTpe = clazz.thisType.memberType(sym)
if (sym.isModule && !sym.isMethod) {
// the super accessor always needs to be a method. See #231
superAccTpe = PolyType(List(), superAccTpe)
}
superAcc.setInfo(superAccTpe.cloneInfo(superAcc))
//println("creating super acc "+superAcc+":"+superAcc.tpe)//DEBUG
clazz.info.decls enter superAcc;
accDefBuf(clazz) += typed(DefDef(superAcc, vparamss => EmptyTree))
}
atPos(sup.pos) {
Select(gen.mkAttributedThis(clazz), superAcc) setType tree.tpe;
}
} else {
tree
}
case _ =>
assert(tree.tpe.isError, tree)
tree
}
override def transform(tree: Tree): Tree = try { tree match {
case ClassDef(_, _, _, _) =>
checkCompanionNameClashes(tree.symbol)
val decls = tree.symbol.info.decls
for (val sym <- decls.toList) {
if (sym.privateWithin.isClass && !sym.privateWithin.isModuleClass &&
!sym.hasFlag(EXPANDEDNAME) && !sym.isConstructor) {
decls.unlink(sym)
sym.expandName(sym.privateWithin)
decls.enter(sym)
}
}
super.transform(tree)
case ModuleDef(_, _, _) =>
checkCompanionNameClashes(tree.symbol)
super.transform(tree)
case Template(parents, self, body) =>
val ownAccDefs = new ListBuffer[Tree];
accDefs = (currentOwner, ownAccDefs) :: accDefs;
// ugly hack... normally, the following line should not be
// necessary, the 'super' method taking care of that. but because
// that one is iterating through parents (and we dont want that here)
// we need to inline it.
curTree = tree
val body1 = atOwner(currentOwner) { transformTrees(body) }
accDefs = accDefs.tail;
copy.Template(tree, parents, self, ownAccDefs.toList ::: body1);
case TypeApply(sel @ Select(This(_), name), args) =>
val sym = tree.symbol
if (needsProtectedAccessor(sym, tree.pos)) {
if (settings.debug.value) log("Adding protected accessor for " + tree);
transform(makeAccessor(sel.asInstanceOf[Select], args))
} else
tree
case Select(qual @ This(_), name) =>
val sym = tree.symbol
if ((sym hasFlag PARAMACCESSOR) && (sym.alias != NoSymbol)) {
val result = typed {
Select(
Super(qual.symbol, nme.EMPTY.toTypeName/*qual.symbol.info.parents.head.symbol.name*/) setPos qual.pos,
sym.alias) setPos tree.pos
}
if (settings.debug.value)
Console.println("alias replacement: " + tree + " ==> " + result);//debug
transformSuperSelect(result)
} else {
if (needsProtectedAccessor(sym, tree.pos)) {
if (settings.debug.value) log("Adding protected accessor for " + tree);
transform(makeAccessor(tree.asInstanceOf[Select], List(EmptyTree)))
} else
tree
}
case Select(sup @ Super(_, mix), name) =>
val sym = tree.symbol
if (sym.isValue && !sym.isMethod || sym.hasFlag(ACCESSOR)) {
unit.error(tree.pos, "super may be not be used on "+sym)
}
transformSuperSelect(tree)
case TypeApply(sel @ Select(qual, name), args) =>
val sym = tree.symbol
if (needsProtectedAccessor(sym, tree.pos)) {
if (settings.debug.value) log("Adding protected accessor for tree: " + tree);
transform(makeAccessor(sel.asInstanceOf[Select], args))
} else
super.transform(tree)
case Select(qual, name) =>
val sym = tree.symbol
if (needsProtectedAccessor(sym, tree.pos)) {
if (currentOwner.enclClass.isTrait
&& sym.hasFlag(JAVA)
&& !qual.isInstanceOf[This])
unit.error(tree.pos,
"Implementation restriction: Cannot access Java protected member inside a trait,"
+ " when the qualifier is not 'this'.")
if (settings.debug.value) log("Adding protected accessor for tree: " + tree);
transform(makeAccessor(tree.asInstanceOf[Select], List(EmptyTree)))
} else
super.transform(tree)
case Assign(lhs @ Select(qual, name), rhs) =>
if (lhs.symbol.isVariable &&
lhs.symbol.hasFlag(JAVA) &&
needsProtectedAccessor(lhs.symbol, tree.pos)) {
if (settings.debug.value) log("Adding protected setter for " + tree)
val setter = makeSetter(lhs);
if (settings.debug.value)
log("Replaced " + tree + " with " + setter);
transform(typed(Apply(setter, List(qual, rhs))))
} else
super.transform(tree)
case Apply(fn, args) =>
assert(fn.tpe != null)
copy.Apply(tree, transform(fn), transformArgs(args, fn.tpe.paramTypes))
case Function(vparams, body) =>
withInvalidOwner {
copy.Function(tree, vparams, transform(body))
}
case _ =>
super.transform(tree)
}} catch {
case ex : AssertionError =>
if (tree.symbol != null && tree.symbol != NoSymbol)
Console.println("TRANSFORM: " + tree.symbol.sourceFile)
Console.println("TREE: " + tree)
throw ex
}
override def atOwner[A](owner: Symbol)(trans: => A): A = {
if (owner.isClass) validCurrentOwner = true
super.atOwner(owner)(trans)
}
private def withInvalidOwner[A](trans: => A): A = {
val prevValidCurrentOwner = validCurrentOwner
validCurrentOwner = false
val result = trans
validCurrentOwner = prevValidCurrentOwner
result
}
/** Add a protected accessor, if needed, and return a tree that calls
* the accessor and returns the the same member. The result is already
* typed.
*/
private def makeAccessor(tree: Select, targs: List[Tree]): Tree = {
val Select(qual, name) = tree
val sym = tree.symbol
val clazz = hostForAccessorOf(sym, currentOwner.enclClass)
/** Return a list of list of types of all value parameter sections. */
def allParamTypes(tpe: Type): List[List[Type]] = tpe match {
case PolyType(_, restpe) => allParamTypes(restpe)
case MethodType(pts, res) => pts :: allParamTypes(res)
case _ => Nil
}
assert(clazz != NoSymbol, sym)
if (settings.debug.value) log("Decided for host class: " + clazz)
val accName = nme.protName(sym.originalName)
val hasArgs = sym.tpe.paramTypes != Nil
val memberType = sym.tpe // transform(sym.tpe)
// if the result type depends on the 'this' type of an enclosing class, the accessor
// has to take a singleton type, otherwise it takes the type of this
val objType = if (isThisType(memberType.finalResultType)) clazz.thisType else clazz.typeOfThis
val accType = memberType match {
case PolyType(tparams, restpe) =>
PolyType(tparams, MethodType(List(objType), restpe.asSeenFrom(qual.tpe, sym.owner)))
case _ =>
MethodType(List(objType), memberType.asSeenFrom(qual.tpe, sym.owner))
}
if (settings.debug.value) log("accType: " + accType)
var protAcc = clazz.info.decl(accName).suchThat(_.tpe == accType)
if (protAcc == NoSymbol) {
protAcc = clazz.newMethod(tree.pos, accName)
.setInfo(accType)
.setFlag(SYNTHETIC | PROTACCESSOR | lateDEFERRED)
.setAlias(sym)
clazz.info.decls.enter(protAcc);
accDefBuf(clazz) += typers(clazz).typed(DefDef(protAcc, vparamss => EmptyTree))
}
var res: Tree = atPos(tree.pos) {
if (targs.head == EmptyTree)
Apply(Select(This(clazz), protAcc), List(qual))
else
Apply(TypeApply(Select(This(clazz), protAcc), targs), List(qual))
}
if (settings.debug.value)
log("Replaced " + tree + " with " + res)
if (hasArgs) typer.typedOperator(res) else typer.typed(res)
}
/** Adapt the given argument in call to protected member.
* Adaptation means adding a cast to a path-dependent type, for instance
*
* def prot$m(obj: Outer)(x: Inner) = obj.m(x.asInstanceOf[obj.Inner]).
*
* such a cast might be necessary when m expects an Outer.this.Inner (the
* outer of 'obj' and 'x' have to be the same). This restriction can't be
* expressed in the type system (but is implicit when defining method m).
*/
private def makeArg(v: Symbol, obj: Symbol, expectedTpe: Type): Tree = {
val res = Ident(v)
val sym = obj.tpe.typeSymbol
var ownerClass: Symbol = NoSymbol
val isDependentType = expectedTpe match {
case TypeRef(path, _, _) =>
ownerClass = thisTypeOfPath(path)
if (sym.isSubClass(ownerClass)) true else false
case _ => false
}
if (isDependentType) {
val preciseTpe = expectedTpe.asSeenFrom(singleType(NoPrefix, obj), ownerClass) //typeRef(singleType(NoPrefix, obj), v.tpe.symbol, List())
TypeApply(Select(res, definitions.Any_asInstanceOf),
List(TypeTree(preciseTpe)))
}
else
res
}
/** For a path-dependent type, return the this type. */
private def thisTypeOfPath(path: Type): Symbol = path match {
case ThisType(outerSym) => outerSym
case SingleType(rest, _) => thisTypeOfPath(rest)
case _ => NoSymbol
}
/** Add an accessor for field, if needed, and return a selection tree for it .
* The result is not typed.
*/
private def makeSetter(tree: Select): Tree = {
val field = tree.symbol
val clazz = hostForAccessorOf(field, currentOwner.enclClass)
assert(clazz != NoSymbol, field)
if (settings.debug.value)
log("Decided for host class: " + clazz)
val accName = nme.protSetterName(field.originalName)
var protAcc = clazz.info.decl(accName)
if (protAcc == NoSymbol) {
protAcc = clazz.newMethod(field.pos, nme.protSetterName(field.originalName))
.setInfo(MethodType(List(clazz.typeOfThis, field.tpe), definitions.UnitClass.tpe));
clazz.info.decls.enter(protAcc)
val code = DefDef(protAcc, vparamss => {
val obj :: value :: Nil = vparamss.head;
atPos(tree.pos) {
Assign(
Select(Ident(obj), field.name),
Ident(value))
}
})
if (settings.debug.value)
log(code);
accDefBuf(clazz) += typers(clazz).typed(code)
}
var res: Tree = atPos(tree.pos) { Select(This(clazz), protAcc) }
res
}
/** Does `sym' need an accessor when accessed from `currentOwner'?
* 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 system.
* (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.
*/
private def needsProtectedAccessor(sym: Symbol, pos: Position): Boolean = {
val accNeeded = /* settings.debug.value && */
((sym hasFlag PROTECTED)
&& (!validCurrentOwner
|| !(currentOwner.enclClass.thisSym isSubClass sym.owner)
|| currentOwner.enclClass.isTrait)
&& (enclPackage(sym.owner) != enclPackage(currentOwner))
&& (enclPackage(sym.owner) == enclPackage(sym.accessBoundary(sym.owner))))
if (accNeeded) {
val host = hostForAccessorOf(sym, currentOwner.enclClass)
if (host.thisSym != host) {
if (host.thisSym.tpe.typeSymbol.hasFlag(JAVA))
unit.error(pos, "Implementation restriction: " + currentOwner.enclClass + " accesses protected "
+ sym + " from self type " + host.thisSym.tpe)
false
} else accNeeded
} else accNeeded
}
/** Return the enclosing package of the given symbol. */
private def enclPackage(sym: Symbol): Symbol =
if ((sym == NoSymbol) || sym.isPackageClass) sym else enclPackage(sym.owner)
/** 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: Symbol): Symbol = {
if (referencingClass.isSubClass(sym.owner.enclClass)
|| referencingClass.thisSym.isSubClass(sym.owner.enclClass)
|| enclPackage(referencingClass) == enclPackage(sym.owner)) {
assert(referencingClass.isClass)
referencingClass
} else
hostForAccessorOf(sym, referencingClass.owner.enclClass)
}
/** Is 'tpe' the type of a member of an enclosing class? */
private def isThisType(tpe: Type): Boolean = tpe match {
case ThisType(sym) => (sym.isClass && !sym.isPackageClass)
case TypeRef(pref, _, _) => isThisType(pref)
case SingleType(pref, _) => isThisType(pref)
case RefinedType(parents, defs) =>
parents.exists(isThisType(_))
case AnnotatedType(_, tp, _) =>
isThisType(tp)
case _ => false
}
}
}