diff options
-rw-r--r-- | src/dotty/tools/dotc/Compiler.scala | 1 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/Denotations.scala | 1 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/NameOps.scala | 9 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/Signature.scala | 2 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/SymDenotations.scala | 45 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/Symbols.scala | 12 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/Types.scala | 9 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/pickling/ClassfileParser.scala | 2 | ||||
-rw-r--r-- | src/dotty/tools/dotc/transform/MacroTransform.scala | 15 | ||||
-rw-r--r-- | src/dotty/tools/dotc/transform/SuperAccessors.scala | 557 | ||||
-rw-r--r-- | test/dotc/tests.scala | 2 | ||||
-rw-r--r-- | tests/pos/paramAliases.scala | 11 |
12 files changed, 651 insertions, 15 deletions
diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index a4a8fbbc8..55452d6ff 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -20,6 +20,7 @@ class Compiler { def phases: List[List[Phase]] = List( List(new FrontEnd), + List(new SuperAccessors), List(new LazyValsCreateCompanionObjects, new TailRec), //force separataion between lazyVals and LVCreateCO List(new PatternMatcher, diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 264f9aa46..120f8e0f8 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -574,6 +574,7 @@ object Denotations { /** Install this denotation to be the result of the given denotation transformer. * This is the implementation of the same-named method in SymDenotations. * It's placed here because it needs access to private fields of SingleDenotation. + * @pre Can only be called in `phase.next`. */ protected def installAfter(phase: DenotTransformer)(implicit ctx: Context): Unit = { val targetId = phase.next.id diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index 7c10bfd4d..404a0844a 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -132,6 +132,9 @@ object NameOps { if (flags is (ModuleClass, butNot = Package)) name.asTypeName.moduleClassName.asInstanceOf[N] else name + /** The superaccessor for method with given name */ + def superName: TermName = (nme.SUPER_PREFIX ++ name).toTermName + /** The expanded name of `name` relative to this class `base` with given `separator` */ def expandedName(base: Symbol, separator: Name = nme.EXPAND_SEPARATOR)(implicit ctx: Context): N = { @@ -255,11 +258,11 @@ object NameOps { /** The name of an accessor for protected symbols. */ def protectedAccessorName: TermName = - PROTECTED_PREFIX ++ name + PROTECTED_PREFIX ++ name.unexpandedName() /** The name of a setter for protected symbols. Used for inherited Java fields. */ - def protectedSetterName(name: Name): TermName = - PROTECTED_SET_PREFIX ++ name + def protectedSetterName: TermName = + PROTECTED_SET_PREFIX ++ name.unexpandedName() def moduleVarName: TermName = name ++ MODULE_VAR_SUFFIX diff --git a/src/dotty/tools/dotc/core/Signature.scala b/src/dotty/tools/dotc/core/Signature.scala index eb85fbb99..22d038d11 100644 --- a/src/dotty/tools/dotc/core/Signature.scala +++ b/src/dotty/tools/dotc/core/Signature.scala @@ -49,7 +49,7 @@ object Signature { * a type different from PolyType, MethodType, or ExprType. */ val NotAMethod = Signature(List(), EmptyTypeName) - + /** The signature of an overloaded denotation. */ val OverloadedSignature = Signature(List(tpnme.OVERLOADED), EmptyTypeName) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 802762045..643237038 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -395,6 +395,14 @@ object SymDenotations { /** Is this a user defined "def" method? Excluded are accessors. */ final def isSourceMethod(implicit ctx: Context) = this is (Method, butNot = Accessor) + /** This this a method in a value class that is implemented as an extension method? */ + final def isMethodWithExtension(implicit ctx: Context) = + isSourceMethod && + owner.isDerivedValueClass && + !isConstructor && + !is(SuperAccessor) && + !is(Macro) + /** Is this a setter? */ final def isGetter(implicit ctx: Context) = (this is Accessor) && !originalName.isSetterName @@ -447,7 +455,7 @@ object SymDenotations { def accessWithin(boundary: Symbol) = ctx.owner.isContainedIn(boundary) && (!(this is JavaDefined) || // disregard package nesting for Java - ctx.owner.enclosingPackage == boundary.enclosingPackage) + ctx.owner.enclosingPackageClass == boundary.enclosingPackageClass) /** Are we within definition of linked class of `boundary`? */ def accessWithinLinked(boundary: Symbol) = { @@ -572,6 +580,12 @@ object SymDenotations { NoSymbol } + /** The field accessed by this getter or setter */ + def accessedField(implicit ctx: Context): Symbol = { + val fieldName = if (isSetter) name.asTermName.setterToGetter else name + owner.info.decl(fieldName).suchThat(d => !(d is Method)).symbol + } + /** The chain of owners of this denotation, starting with the denoting symbol itself */ final def ownersIterator(implicit ctx: Context) = new Iterator[Symbol] { private[this] var current = symbol @@ -624,8 +638,8 @@ object SymDenotations { } /** The package class containing this denotation */ - final def enclosingPackage(implicit ctx: Context): Symbol = - if (this is PackageClass) symbol else owner.enclosingPackage + final def enclosingPackageClass(implicit ctx: Context): Symbol = + if (this is PackageClass) symbol else owner.enclosingPackageClass /** The module object with the same (term-) name as this class or module class, * and which is also defined in the same scope and compilation unit. @@ -747,7 +761,6 @@ object SymDenotations { loop(base.info.baseClasses.dropWhile(owner != _).tail) } - /** A a member of class `base` is incomplete if * (1) it is declared deferred or * (2) it is abstract override and its super symbol in `base` is @@ -895,6 +908,15 @@ object SymDenotations { case _ => Nil } + /** The symbol of the superclass, NoSymbol if no superclass exists */ + def superClass(implicit ctx: Context): Symbol = classParents match { + case parent :: _ => + val cls = parent.classSymbol + if (cls is Trait) NoSymbol else cls + case _ => + NoSymbol + } + /** The denotation is fully completed: all attributes are fully defined. * ClassDenotations compiled from source are first completed, then fully completed. * @see Namer#ClassCompleter @@ -1292,6 +1314,21 @@ object SymDenotations { def underlyingOfValueClass: Type = ??? def valueClassUnbox: Symbol = ??? + + /** If this class has the same `decls` scope reference in `phase` and + * `phase.next`, install a new denotation with a cloned scope in `phase.next`. + * @pre Can only be called in `phase.next`. + */ + def ensureFreshScopeAfter(phase: DenotTransformer)(implicit ctx: Context): Unit = { + assert(ctx.phaseId == phase.next.id) + val prevCtx = ctx.withPhase(phase) + val ClassInfo(pre, _, ps, decls, selfInfo) = classInfo + if (classInfo(prevCtx).decls eq decls) { + copySymDenotation( + info = ClassInfo(pre, classSymbol, ps, decls.cloneScope, selfInfo), + initFlags = this.flags &~ Frozen).installAfter(phase) + } + } } /** The denotation of a package class. diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index 26553ddff..cfd5bdf23 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -16,6 +16,7 @@ import printing.Printer import Types._ import Annotations._ import util.Positions._ +import DenotTransformers._ import StdNames._ import NameOps._ import ast.tpd.{TreeTypeMap, Tree} @@ -372,6 +373,17 @@ object Symbols { this } + /** Enter this symbol in its class owner after given `phase`. Create a fresh + * denotation for its owner class if the class has not yet already one + * that starts being valid after `phase`. + * @pre Symbol is a class member + */ + def enteredAfter(phase: DenotTransformer)(implicit ctx: Context): this.type = { + val nextCtx = ctx.withPhase(phase.next) + this.owner.asClass.ensureFreshScopeAfter(phase)(nextCtx) + entered(nextCtx) + } + /** This symbol, if it exists, otherwise the result of evaluating `that` */ def orElse(that: => Symbol)(implicit ctx: Context) = if (this.exists) this else that diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index dad88bc60..a92b252b5 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1609,10 +1609,15 @@ object Types { protected def computeSignature(implicit ctx: Context): Signature - protected def resultSignature(implicit ctx: Context) = resultType match { + protected def resultSignature(implicit ctx: Context) = try resultType match { case rtp: SignedType => rtp.signature case tp => Signature(tp, isJava = false) } + catch { + case ex: AssertionError => + println(i"failure while taking result signture of $resultType") + throw ex + } final override def signature(implicit ctx: Context): Signature = { if (ctx.runId != mySignatureRunId) { @@ -1717,6 +1722,8 @@ object Types { def apply(paramNames: List[TermName], paramTypes: List[Type])(resultTypeExp: MethodType => Type)(implicit ctx: Context): MethodType def apply(paramNames: List[TermName], paramTypes: List[Type], resultType: Type)(implicit ctx: Context): MethodType = apply(paramNames, paramTypes)(_ => resultType) + def apply(paramTypes: List[Type])(resultTypeExp: MethodType => Type)(implicit ctx: Context): MethodType = + apply(nme.syntheticParamNames(paramTypes.length), paramTypes)(resultTypeExp) def apply(paramTypes: List[Type], resultType: Type)(implicit ctx: Context): MethodType = apply(nme.syntheticParamNames(paramTypes.length), paramTypes, resultType) def fromSymbols(params: List[Symbol], resultType: Type)(implicit ctx: Context) = { diff --git a/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala b/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala index 0ed301732..0f0747597 100644 --- a/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala +++ b/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala @@ -752,7 +752,7 @@ class ClassfileParser( private def setPrivateWithin(denot: SymDenotation, jflags: Int)(implicit ctx: Context): Unit = { if ((jflags & (JAVA_ACC_PRIVATE | JAVA_ACC_PUBLIC)) == 0) - denot.privateWithin = denot.enclosingPackage + denot.privateWithin = denot.enclosingPackageClass } private def isPrivate(flags: Int) = (flags & JAVA_ACC_PRIVATE) != 0 diff --git a/src/dotty/tools/dotc/transform/MacroTransform.scala b/src/dotty/tools/dotc/transform/MacroTransform.scala index 4113b2d8e..eacbd1717 100644 --- a/src/dotty/tools/dotc/transform/MacroTransform.scala +++ b/src/dotty/tools/dotc/transform/MacroTransform.scala @@ -21,9 +21,17 @@ abstract class MacroTransform extends Phase { unit.tpdTree = newTransformer.transform(unit.tpdTree) } - def newTransformer: TransformerMap + protected def newTransformer(implicit ctx: Context): Transformer - class TransformerMap extends TreeMap { + class Transformer extends TreeMap { + + protected def localCtx(tree: Tree)(implicit ctx: Context) = + ctx.fresh.setTree(tree).setOwner(tree.symbol) + + /** The current enclosing class + * @pre We must be inside a class + */ + def currentClass(implicit ctx: Context): ClassSymbol = ctx.owner.enclosingClass.asClass def transformStats(trees: List[Tree], exprOwner: Symbol)(implicit ctx: Context): List[Tree] = { val exprCtx = ctx.withOwner(exprOwner) @@ -36,10 +44,9 @@ abstract class MacroTransform extends Phase { } override def transform(tree: Tree)(implicit ctx: Context): Tree = { - def localCtx = ctx.fresh.setTree(tree).setOwner(tree.symbol) tree match { case _: PackageDef | _: MemberDef => - super.transform(tree)(localCtx) + super.transform(tree)(localCtx(tree)) case Template(constr, parents, self, body) => cpy.Template(tree, transformSub(constr), diff --git a/src/dotty/tools/dotc/transform/SuperAccessors.scala b/src/dotty/tools/dotc/transform/SuperAccessors.scala new file mode 100644 index 000000000..fd784862e --- /dev/null +++ b/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -0,0 +1,557 @@ +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 mutable.ListBuffer +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._ + +/** This phase performs the following functions, each of which could be split out in a + * mini-phase: + * + * (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) Converts parameter fields that have the same name as a corresponding + * public parameter field in a superclass to a forwarder to the superclass + * field (corresponding = super class field is initialized with subclass field) + * + * (3) Adds protected accessors if the access to the protected member happens + * in a class which is not a subclass of the member's owner. + * + * (4) Finally, the phase used to mangle the names of class-members which are + * private up to an enclosing non-package class, in order to avoid overriding conflicts. + * This is currently disabled, and class-qualified private is deprecated. + * + * It also checks that: + * + * (1) Symbols accessed from super are not abstract, or are overridden by + * an abstract override. + * + * (2) If a symbol accessed 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 + * + * (5) A class and its companion object do not both define a class or module with the + * same name. + * + * TODO: Rename phase to "Accessors" because it handles more than just super accessors + */ +class SuperAccessors extends MacroTransform with DenotTransformer { thisTransformer => + + import tpd._ + + /** the following two members override abstract members in Transform */ + val name: String = "superaccessors" + + protected def newTransformer(implicit ctx: Context): Transformer = + new SuperAccTransformer + + /** No transformation here, but new denotations are installed by the tree traversal */ + def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref + + class SuperAccTransformer extends Transformer { + + /** validCurrentOwner arrives undocumented, but I reverse engineer it to be + * a flag for needsProtectedAccessor which is false while transforming either + * a by-name argument block or a closure. This excludes them from being + * considered able to access protected members via subclassing (why?) which in turn + * increases the frequency with which needsProtectedAccessor will be true. + */ + private var validCurrentOwner = true + + private val accDefs = mutable.Map[Symbol, ListBuffer[Tree]]() + + private def storeAccessorDefinition(clazz: Symbol, tree: Tree) = { + val buf = accDefs.getOrElse(clazz, sys.error("no acc def buf for "+clazz)) + buf += tree + } + + private def ensureSigned(tpe: Type)(implicit ctx: Context) = tpe match { + case tpe: SignedType => tpe + case _ => ExprType(tpe) + } + + private def ensureAccessor(sel: Select)(implicit ctx: Context) = { + val Select(qual, name) = sel + val sym = sel.symbol + val clazz = qual.symbol.asClass + val supername = name.superName + + val superAcc = clazz.info.decl(supername).suchThat(_.signature == sym.signature).symbol orElse { + ctx.debuglog(s"add super acc ${sym.showLocated} to $clazz") + val acc = ctx.newSymbol( + clazz, supername, SuperAccessor | Private | Artifact, + ensureSigned(sel.tpe.widenSingleton), 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 storeAccessorDefinition(clazz, DefDef(acc, EmptyTree)) + acc + } + + Select(This(clazz), superAcc) withPos sel.pos + } + + private def transformArgs(formals: List[Type], args: List[Tree])(implicit ctx: Context) = + args.zipWithConserve(formals) {(arg, formal) => + formal match { + case _: ExprType => withInvalidOwner(transform(arg)) + case _ => transform(arg) + } + } + + /** Check that a class and its companion object to not both define + * a class or module with same name + */ + private def checkCompanionNameClashes(cls: ClassSymbol)(implicit ctx: Context): Unit = + if (!(cls.owner is ModuleClass)) { + val other = cls.owner.linkedClass.info.decl(cls.name) + if (other.symbol.isClass) + ctx.error(s"name clash: ${cls.owner} defines $cls" + "\n" + + s"and its companion ${cls.owner.companionModule} also defines $other", + cls.pos) + } + + /** Expand all declarations in this class which are private within a class. + * Note: It's not sure whether this is the right way. Persumably, we expand + * qualified privates to prvent them from overriding or be overridden by + * symbols that are defined in classes where the qualified private is not + * visible. But it seems a bit dubiuous to do this between type checking + * and refchecks. + */ + def expandQualifiedPrivates(cls: ClassSymbol)(implicit ctx: Context) = { + val decls = cls.info.decls + val decls1: MutableScope = newScope + def needsExpansion(sym: Symbol) = + sym.privateWithin.isClass && + !(sym is Protected) && + !(sym.privateWithin is ModuleClass) && + !(sym is ExpandedName) && + !sym.isConstructor + val nextCtx = ctx.withPhase(thisTransformer.next) + for (s <- decls) { + // !!! hacky to do this by mutation; would be better to do with an infotransformer + // !!! also, why is this done before pickling? + if (needsExpansion(s)) { + ctx.deprecationWarning(s"private qualified with a class has been deprecated, use package enclosing ${s.privateWithin} instead", s.pos) + /* disabled for now + decls.asInstanceOf[MutableScope].unlink(s) + s.copySymDenotation(name = s.name.expandedName(s.privateWithin)) + .installAfter(thisTransformer) + decls1.enter(s)(nextCtx) + ctx.log(i"Expanded ${s.name}, ${s.name(nextCtx)}, sym") + */ + } + } + /* Disabled for now: + if (decls1.nonEmpty) { + for (s <- decls) + if (!needsExpansion(s)) decls1.enter(s)(nextCtx) + val ClassInfo(pre, _, ps, _, selfInfo) = cls.classInfo + cls.copySymDenotation(info = ClassInfo(pre, cls, ps, decls1, selfInfo)) + .installAfter(thisTransformer) + } + */ + } + + 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 is Deferred) { + val member = sym.overridingSymbol(clazz) + if (mix != tpnme.EMPTY || + !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 if (mix == tpnme.EMPTY && !(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 == tpnme.EMPTY && + ((clazz is Trait) || clazz != ctx.owner.enclosingClass || !validCurrentOwner)) + ensureAccessor(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_##) + } + + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + val sym = tree.symbol + + def mayNeedProtectedAccessor(sel: Select, targs: List[Tree], goToSuper: Boolean) = + if (sym.exists && needsProtectedAccessor(sym, tree.pos)) { + ctx.debuglog("Adding protected accessor for " + tree) + transform(makeAccessor(sel, targs)) + } + else if (goToSuper) super.transform(tree) + else tree + + try tree match { + // Don't transform patterns or strange trees will reach the matcher (ticket #4062) + // TODO Drop once this runs after pattern matcher + case CaseDef(pat, guard, body) => + cpy.CaseDef(tree, pat, transform(guard), transform(body)) + + case TypeDef(_, _, impl: Template) => + val cls = sym.asClass + checkCompanionNameClashes(cls) + expandQualifiedPrivates(cls) + super.transform(tree) + + case impl: Template => + + /** For all parameter accessors + * + * val x: T = ... + * + * if + * (1) x is forwarded in the supercall to a parameter that's also named `x` + * (2) the superclass parameter accessor for `x` is accessible from the current class to + * change the accessor to + * + * def x: T = super.x.asInstanceOf[T] + * + * Do the same also if there are intermediate inaccessible parameter accessor forwarders. + * The aim of this transformation is to avoid redundant parameter accessor fields. + */ + def forwardParamAccessors(stats: List[Tree]): List[Tree] = { + val (superArgs, superParamNames) = impl.parents match { + case superCall @ Apply(fn, args) :: _ => + fn.tpe.widen match { + case MethodType(paramNames, _) => (args, paramNames) + case _ => (Nil, Nil) + } + case _ => (Nil, Nil) + } + def inheritedAccessor(sym: Symbol): Symbol = { + val candidate = sym.owner.asClass.superClass + .info.decl(sym.name).suchThat(_ is (ParamAccessor, butNot = Mutable)).symbol + if (candidate.isAccessibleFrom(currentClass.thisType, superAccess = true)) candidate + else if (candidate is Method) inheritedAccessor(candidate) + else NoSymbol + } + def forwardParamAccessor(stat: Tree): Tree = { + stat match { + case stat: ValDef => + val sym = stat.symbol.asTerm + if (sym is (PrivateLocalParamAccessor, butNot = Mutable)) { + val idx = superArgs.indexWhere(_.symbol == sym) + if (idx >= 0 && superParamNames(idx) == stat.name) { // supercall to like-named parameter + val alias = inheritedAccessor(sym) + if (alias.exists) { + def forwarder(implicit ctx: Context) = { + sym.copySymDenotation(initFlags = sym.flags | Method, info = ensureSigned(sym.info)) + .installAfter(thisTransformer) + val superAcc = + Select(Super(This(currentClass), tpnme.EMPTY, inConstrCall = false), alias) + DefDef(sym, ensureConforms(superAcc, sym.info.widen)) + } + return forwarder(ctx.withPhase(thisTransformer.next)) + } + } + } + case _ => + } + stat + } + stats map forwardParamAccessor + } + + def transformTemplate = { + val ownStats = new ListBuffer[Tree] + accDefs(currentClass) = ownStats + val body1 = forwardParamAccessors(transformStats(impl.body, tree.symbol)) + accDefs -= currentClass + ownStats ++= body1 + cpy.Template(tree, impl.constr, impl.parents, impl.self, body1) + } + transformTemplate + + case TypeApply(sel @ Select(This(_), name), args) => + mayNeedProtectedAccessor(sel, args, goToSuper = false) + + case sel @ Select(qual, name) => + def transformSelect = { + + qual match { + case This(_) => + // warn if they are selecting a private[this] member which + // also exists in a superclass, because they may be surprised + // to find out that a constructor parameter will shadow a + // field. See SI-4762. + /* to be added + if (settings.lint) { + if (sym.isPrivateLocal && sym.paramss.isEmpty) { + qual.symbol.ancestors foreach { parent => + parent.info.decls filterNot (x => x.isPrivate || x.isLocalToThis) foreach { m2 => + if (sym.name == m2.name && m2.isGetter && m2.accessed.isMutable) { + unit.warning(sel.pos, + sym.accessString + " " + sym.fullLocationString + " shadows mutable " + m2.name + + " inherited from " + m2.owner + ". Changes to " + m2.name + " will not be visible within " + + sym.owner + " - you may want to give them distinct names.") + } + } + } + } + } + */ + + /* + * 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 - 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, tree.pos)) + if (shouldEnsureAccessor) { + ctx.log("Ensuring accessor for call to protected " + sym.showLocated + " from " + currentClass) + ensureAccessor(sel) + } else + mayNeedProtectedAccessor(sel, Nil, goToSuper = false) + + case Super(_, mix) => + if ((sym.isTerm) && !(sym is Method) || (sym is Accessor)) { + ctx.error(s"super may be not be used on ${sym.accessedField orElse sym}", tree.pos) + } else if (isDisallowed(sym)) { + ctx.error(s"super not allowed here: use this.${name.decode} instead", tree.pos) + } + transformSuperSelect(sel) + + case _ => + mayNeedProtectedAccessor(sel, Nil, goToSuper = true) + } + } + transformSelect + + case DefDef(mods, name, tparams, vparamss, tpt, rhs) => + val rhs1 = if (sym.isMethodWithExtension) withInvalidOwner(transform(rhs)) else transform(rhs) + cpy.DefDef(tree, mods, name, tparams, vparamss, tpt, rhs1) + + case TypeApply(sel @ Select(qual, name), args) => + mayNeedProtectedAccessor(sel, args, goToSuper = true) + + case Assign(lhs @ Select(qual, name), rhs) => + def transformAssign = { + if ((lhs.symbol is Mutable) && + (lhs.symbol is JavaDefined) && + needsProtectedAccessor(lhs.symbol, tree.pos)) { + ctx.debuglog("Adding protected setter for " + tree) + val setter = makeSetter(lhs) + ctx.debuglog("Replaced " + tree + " with " + setter) + transform(Apply(setter, qual :: rhs :: Nil)) + } else + super.transform(tree) + } + transformAssign + + case Apply(fn, args) => + val MethodType(_, formals) = fn.tpe.widen + cpy.Apply(tree, transform(fn), transformArgs(formals, args)) + + 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 + } + } + + private def withInvalidOwner[A](trans: => A): A = { + val saved = validCurrentOwner + validCurrentOwner = false + try trans + finally validCurrentOwner = saved + } + + /** Add a protected accessor, if needed, and return a tree that calls + * the accessor and returns the same member. The result is already + * typed. + * TODO why is targs needed? It looks like we can do without. + */ + private def makeAccessor(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 + 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 = tree.pos).enteredAfter(thisTransformer) + val code = polyDefDef(newAcc, targs => argss => { + val (receiver :: _) :: tail = argss + val base = Select(receiver, sym).appliedToTypes(targs) + (base /: argss)(Apply(_, _)) + }) + ctx.debuglog("created protected accessor: " + code) + storeAccessorDefinition(clazz, code) + newAcc + } + val res = + Apply(Select(This(clazz), protectedAccessor).appliedToTypeTrees(targs), qual :: Nil) + .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 makeSetter(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, argss => { + val (receiver :: value :: Nil) :: Nil = argss + Assign(Select(receiver, field), value).withPos(tree.pos) + }) + ctx.debuglog("created protected setter: " + code) + storeAccessorDefinition(clazz, code) + newAcc + } + Select(This(clazz), 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 = + validCurrentOwner && (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' the type of a member of an enclosing class? */ + private def isThisType(tpe: Type)(implicit ctx: Context): Boolean = tpe match { + case ThisType(cls) => !cls.is(PackageClass) + case tpe: TypeProxy => isThisType(tpe.underlying) + case _ => false + } + } +} diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index d25288548..92aa7240a 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -14,7 +14,7 @@ class tests extends CompilerTest { "-pagewidth", "160") implicit val defaultOptions = noCheckOptions ++ List( - "-Ycheck:front"//, "-Ystop-before:terminal" + "-Ycheck:super"//, "-Ystop-before:terminal" ) val twice = List("#runs", "2", "-YnoDoubleBindings") diff --git a/tests/pos/paramAliases.scala b/tests/pos/paramAliases.scala new file mode 100644 index 000000000..e7787864a --- /dev/null +++ b/tests/pos/paramAliases.scala @@ -0,0 +1,11 @@ +// This tests that the subclass parameter is not +// translated to a field, but is forwarded to the +// superclass parameter. Right now we need to verify +// this by inspecting the output with -Xprint:super +// TODO: Make a test that does this automatically. +class Base(val x: Int) + +class Sub(x: Int) extends Base(x) { + println(x) +} + |