package scala.tools.nsc
package typechecker
import scala.collection.mutable.ListBuffer
import symtab.Flags._
abstract class SuperAccessors extends transform.Transform with transform.TypingTransformers {
import global._
import definitions.{ UnitClass, isRepeatedParamType, isByNameParamType, Any_asInstanceOf }
import analyzer.{ restrictionError }
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 def accDefBuf(clazz: Symbol) =
accDefs collectFirst { case (`clazz`, buf) => buf } getOrElse sys.error("no acc def buf for "+clazz)
private def transformArgs(args: List[Tree], params: List[Symbol]) =
((args, params).zipped map { (arg, param) =>
if (isByNameParamType(param.tpe))
withInvalidOwner { checkPackedConforms(transform(arg), param.tpe.typeArgs.head) }
else transform(arg)
}) :::
(args drop params.length map transform)
private def checkPackedConforms(tree: Tree, pt: Type): Tree = {
if (tree.tpe exists (_.typeSymbol.isExistentialSkolem)) {
val packed = localTyper.packedType(tree, NoSymbol)
if (!(packed <:< pt)) {
val errorContext = localTyper.context.make(localTyper.context.tree)
errorContext.reportGeneralErrors = true
analyzer.newTyper(errorContext).infer.typeError(tree.pos, packed, pt)
}
}
tree
}
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.companionModule+" also defines "+
other)
}
}
private def transformSuperSelect(tree: 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 != tpnme.EMPTY || 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 == tpnme.EMPTY &&
(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);
superAcc =
clazz.newMethod(tree.pos, supername)
.setFlag(SUPERACCESSOR | PRIVATE)
.setAlias(sym)
var superAccTpe = clazz.thisType.memberType(sym)
if (sym.isModule && !sym.isMethod) {
superAccTpe = NullaryMethodType(superAccTpe)
}
superAcc.setInfo(superAccTpe.cloneInfo(superAcc))
clazz.info.decls enter superAcc;
accDefBuf(clazz) += typers(clazz).typed(DefDef(superAcc, EmptyTree))
}
atPos(sup.pos) {
Select(gen.mkAttributedThis(clazz), superAcc) setType tree.tpe;
}
} else {
tree
}
case _ =>
assert(tree.tpe.isError, tree)
tree
}
private lazy val isDisallowed = {
import definitions._
Set(Any_isInstanceOf, Object_isInstanceOf, Any_asInstanceOf, Object_asInstanceOf, Object_==, Object_!=, Object_##)
}
override def transform(tree: Tree): Tree = {
val sym = tree.symbol
def mayNeedProtectedAccessor(sel: Select, args: List[Tree], goToSuper: Boolean) =
if (needsProtectedAccessor(sym, tree.pos)) {
if (settings.debug.value)
log("Adding protected accessor for " + tree)
transform(makeAccessor(sel, args))
}
else if (goToSuper) super.transform(tree)
else tree
try tree match {
case CaseDef(pat, guard, body) =>
treeCopy.CaseDef(tree, pat, transform(guard), transform(body))
case ClassDef(_, _, _, _) =>
checkCompanionNameClashes(sym)
val decls = sym.info.decls
for (s <- decls.toList) {
if (s.privateWithin.isClass && !s.privateWithin.isModuleClass &&
!s.hasFlag(EXPANDEDNAME) && !s.isConstructor) {
decls.unlink(s)
s.expandName(s.privateWithin)
decls.enter(s)
}
}
if (settings.verbose.value && forScaladoc && !sym.isAnonymousClass) {
println("========== scaladoc of "+sym+" =============================")
println(toJavaDoc(expandedDocComment(sym)))
for (member <- sym.info.members) {
println(member+":"+sym.thisType.memberInfo(member)+"\n"+
toJavaDoc(expandedDocComment(member, sym)))
for ((useCase, comment, pos) <- useCases(member, sym)) {
println("usecase "+useCase+":"+useCase.info)
println(toJavaDoc(comment))
}
}
}
super.transform(tree)
case ModuleDef(_, _, _) =>
checkCompanionNameClashes(sym)
super.transform(tree)
case Template(parents, self, body) =>
val ownAccDefs = new ListBuffer[Tree];
accDefs = (currentOwner, ownAccDefs) :: accDefs;
curTree = tree
val body1 = atOwner(currentOwner) { transformTrees(body) }
accDefs = accDefs.tail;
treeCopy.Template(tree, parents, self, ownAccDefs.toList ::: body1)
case TypeApply(sel @ Select(This(_), name), args) =>
mayNeedProtectedAccessor(sel, args, false)
case sel @ Select(qual @ This(_), name) =>
if (sym.isParamAccessor && sym.alias != NoSymbol) {
val result = localTyper.typed {
Select(
Super(qual, tpnme.EMPTY) setPos qual.pos,
sym.alias) setPos tree.pos
}
if (settings.debug.value)
log("alias replacement: " + tree + " ==> " + result);
localTyper.typed(gen.maybeMkAsInstanceOf(transformSuperSelect(result), sym.tpe, sym.alias.tpe, true))
}
else mayNeedProtectedAccessor(sel, List(EmptyTree), false)
case Select(Super(_, mix), name) =>
if (sym.isValue && !sym.isMethod || sym.hasAccessorFlag) {
unit.error(tree.pos, "super may be not be used on "+
(if (sym.hasAccessorFlag) sym.accessed else sym))
}
else if (isDisallowed(sym)) {
unit.error(tree.pos, "super not allowed here: use this." + name.decode + " instead")
}
transformSuperSelect(tree)
case TypeApply(sel @ Select(qual, name), args) =>
mayNeedProtectedAccessor(sel, args, true)
case sel @ Select(qual, name) =>
mayNeedProtectedAccessor(sel, List(EmptyTree), true)
case Assign(lhs @ Select(qual, name), rhs) =>
if (lhs.symbol.isVariable &&
lhs.symbol.isJavaDefined &&
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(localTyper.typed(Apply(setter, List(qual, rhs))))
} else
super.transform(tree)
case Apply(fn, args) =>
assert(fn.tpe != null, tree)
treeCopy.Apply(tree, transform(fn), transformArgs(args, fn.tpe.params))
case Function(vparams, body) =>
withInvalidOwner {
treeCopy.Function(tree, vparams, transform(body))
}
case _ =>
super.transform(tree)
}
catch {
case ex : AssertionError =>
if (sym != null && sym != 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
}
private def makeAccessor(tree: Select, targs: List[Tree]): Tree = {
val Select(qual, name) = tree
val sym = tree.symbol
val clazz = hostForAccessorOf(sym, currentOwner.enclClass)
def allParamTypes(tpe: Type): List[List[Type]] = tpe match {
case PolyType(_, restpe) => allParamTypes(restpe)
case MethodType(params, res) => params.map(_.tpe) :: 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 = refchecks.toScalaRepeatedParam(sym.tpe)
val objType = if (isThisType(memberType.finalResultType)) clazz.thisType else clazz.typeOfThis
val accType = (protAcc: Symbol) => memberType match {
case PolyType(tparams, restpe) =>
PolyType(tparams, MethodType(List(protAcc.newSyntheticValueParam(objType)),
restpe.cloneInfo(protAcc).asSeenFrom(qual.tpe, sym.owner)))
case _ =>
MethodType(List(protAcc.newSyntheticValueParam(objType)),
memberType.cloneInfo(protAcc).asSeenFrom(qual.tpe, sym.owner))
}
var protAcc = clazz.info.decl(accName).suchThat(s => s == NoSymbol || s.tpe =:= accType(s))
if (protAcc == NoSymbol) {
protAcc = clazz.newMethod(tree.pos, nme.protName(sym.originalName))
protAcc.setInfo(accType(protAcc))
clazz.info.decls.enter(protAcc);
val code = DefDef(protAcc, {
val obj = protAcc.paramss.head.head
protAcc.paramss.tail.zip(allParamTypes(sym.tpe)).foldLeft(Select(Ident(obj), sym): Tree) (
(fun, pvparams) => {
Apply(fun, (pvparams._1, pvparams._2).zipped map (makeArg(_, obj, _)))
})
})
if (settings.debug.value)
log(code)
accDefBuf(clazz) += typers(clazz).typed(code)
}
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) localTyper.typedOperator(res) else localTyper.typed(res)
}
private def makeArg(v: Symbol, obj: Symbol, expectedTpe: Type): Tree = {
var res: Tree = 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 (isRepeatedParamType(v.info)) {
res = gen.wildcardStar(res)
log("adapted to wildcard star: " + res)
}
if (isDependentType) {
val preciseTpe = expectedTpe.asSeenFrom(singleType(NoPrefix, obj), ownerClass)
TypeApply(Select(res, Any_asInstanceOf),
List(TypeTree(preciseTpe)))
} else res
}
private def thisTypeOfPath(path: Type): Symbol = path match {
case ThisType(outerSym) => outerSym
case SingleType(rest, _) => thisTypeOfPath(rest)
case _ => NoSymbol
}
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))
protAcc.setInfo(MethodType(protAcc.newSyntheticValueParams(List(clazz.typeOfThis, field.tpe)),
UnitClass.tpe))
clazz.info.decls.enter(protAcc)
val code = DefDef(protAcc, {
val obj :: value :: Nil = protAcc.paramss.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
}
private def needsProtectedAccessor(sym: Symbol, pos: Position): Boolean = {
val clazz = currentOwner.enclClass
def accessibleThroughSubclassing =
validCurrentOwner && clazz.thisSym.isSubClass(sym.owner) && !clazz.isTrait
def packageAccessBoundry(sym: Symbol) = {
val b = sym.accessBoundary(sym.owner)
if (b.isPackageClass) b
else b.enclosingPackageClass
}
val isCandidate = (
sym.isProtected
&& sym.isJavaDefined
&& !sym.definedInPackage
&& !accessibleThroughSubclassing
&& (sym.owner.enclosingPackageClass != currentOwner.enclosingPackageClass)
&& (sym.owner.enclosingPackageClass == packageAccessBoundry(sym))
)
val host = hostForAccessorOf(sym, clazz)
def isSelfType = !(host.tpe <:< host.typeOfThis) && {
if (host.typeOfThis.typeSymbol.isJavaDefined)
restrictionError(pos, unit,
"%s accesses protected %s from self type %s.".format(clazz, sym, host.typeOfThis)
)
true
}
def isJavaProtected = host.isTrait && sym.isJavaDefined && {
restrictionError(pos, unit,
"""|%s accesses protected %s inside a concrete trait method.
|Add an accessor in a class extending %s as a workaround.""".stripMargin.format(
clazz, sym, sym.enclClass)
)
true
}
isCandidate && !host.isPackageClass && !isSelfType && !isJavaProtected
}
private def hostForAccessorOf(sym: Symbol, referencingClass: Symbol): Symbol = {
if (referencingClass.isSubClass(sym.owner.enclClass)
|| referencingClass.thisSym.isSubClass(sym.owner.enclClass)
|| referencingClass.enclosingPackageClass == sym.owner.enclosingPackageClass) {
assert(referencingClass.isClass)
referencingClass
} else if(referencingClass.owner.enclClass != NoSymbol)
hostForAccessorOf(sym, referencingClass.owner.enclClass)
else referencingClass
}
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
}
}
}