summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2013-10-05 16:46:46 -0700
committerPaul Phillips <paulp@improving.org>2013-10-05 16:46:46 -0700
commite609f1f20b0dce4905271b92aebd0298c7862859 (patch)
tree70575372a93261f656805ab9f709f967f24f38b6
parent90a312669b37d6e3e3f08685953ded24759e6102 (diff)
downloadscala-e609f1f20b0dce4905271b92aebd0298c7862859.tar.gz
scala-e609f1f20b0dce4905271b92aebd0298c7862859.tar.bz2
scala-e609f1f20b0dce4905271b92aebd0298c7862859.zip
Generalize OverridingPairs to SymbolPairs.
Increases your chance of knowing what is going on in OverridingPairs. Introduces some new abstractions which I hope for your own sakes you will put to use in some way: RelativeTo: operations relative to a prefix SymbolPair: two symbols being compared for something, and the enclosing class where the comparison is being performed Fixed a minor bug with access by accident by way of more principled pair analysis. See run/private-override.scala. Upgraded the error message issued on certain conflicts to give the line numbers of both conflicting methods, as opposed to just one and you go hunting.
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/Erasure.scala136
-rw-r--r--src/compiler/scala/tools/nsc/transform/OverridingPairs.scala224
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/RefChecks.scala144
-rw-r--r--src/reflect/scala/reflect/internal/SymbolPairs.scala302
-rw-r--r--src/reflect/scala/reflect/internal/Symbols.scala4
-rw-r--r--test/files/neg/accesses.check6
-rw-r--r--test/files/neg/accesses2.check4
-rw-r--r--test/files/neg/accesses2.scala11
-rw-r--r--test/files/neg/t0259.check4
-rw-r--r--test/files/neg/t3653.check6
-rw-r--r--test/files/neg/t588.check10
-rw-r--r--test/files/neg/t6443c.check4
-rw-r--r--test/files/neg/t663.check6
-rw-r--r--test/files/neg/valueclasses-doubledefs.check4
-rw-r--r--test/files/neg/valueclasses-pavlov.check4
-rw-r--r--test/files/res/t687.check6
-rw-r--r--test/files/run/private-override.check1
-rw-r--r--test/files/run/private-override.scala17
19 files changed, 514 insertions, 381 deletions
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala
index 92bfe6e0fe..1cd3e0ec4b 100644
--- a/src/compiler/scala/tools/nsc/Global.scala
+++ b/src/compiler/scala/tools/nsc/Global.scala
@@ -135,6 +135,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
val global: Global.this.type = Global.this
} with OverridingPairs
+ type SymbolPair = overridingPairs.SymbolPair
+
// Optimizer components
/** ICode analysis for optimization */
diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala
index 2d005b26e3..e2902a74b8 100644
--- a/src/compiler/scala/tools/nsc/transform/Erasure.scala
+++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala
@@ -371,17 +371,14 @@ abstract class Erasure extends AddInterfaces
val opc = enteringExplicitOuter {
new overridingPairs.Cursor(root) {
override def parents = List(root.info.firstParent)
- override def exclude(sym: Symbol) = !sym.isMethod || sym.isPrivate || super.exclude(sym)
+ override def exclude(sym: Symbol) = !sym.isMethod || super.exclude(sym)
}
}
def compute(): (List[Tree], immutable.Set[Symbol]) = {
while (opc.hasNext) {
- val member = opc.overriding
- val other = opc.overridden
- //println("bridge? " + member + ":" + member.tpe + member.locationString + " to " + other + ":" + other.tpe + other.locationString)//DEBUG
- if (enteringExplicitOuter(!member.isDeferred))
- checkPair(member, other)
+ if (enteringExplicitOuter(!opc.low.isDeferred))
+ checkPair(opc.currentPair)
opc.next()
}
@@ -438,8 +435,15 @@ abstract class Erasure extends AddInterfaces
noclash
}
- def checkPair(member: Symbol, other: Symbol) {
- val otpe = specialErasure(root)(other.tpe)
+ /** TODO - work through this logic with a fine-toothed comb, incorporating
+ * into SymbolPairs where appropriate.
+ */
+ def checkPair(pair: SymbolPair) {
+ import pair._
+ val member = low
+ val other = high
+ val otpe = highErased
+
val bridgeNeeded = exitingErasure (
!member.isMacro &&
!(other.tpe =:= member.tpe) &&
@@ -844,88 +848,75 @@ abstract class Erasure extends AddInterfaces
/** The erasure transformer */
class ErasureTransformer(unit: CompilationUnit) extends Transformer {
- /** Emit an error if there is a double definition. This can happen if:
- *
- * - A template defines two members with the same name and erased type.
- * - A template defines and inherits two members `m` with different types,
- * but their erased types are the same.
- * - A template inherits two members `m` with different types,
- * but their erased types are the same.
- */
- private def checkNoDoubleDefs(root: Symbol) {
- def doubleDefError(sym1: Symbol, sym2: Symbol) {
- // the .toString must also be computed at the earlier phase
- val tpe1 = exitingRefchecks(root.thisType.memberType(sym1))
- val tpe2 = exitingRefchecks(root.thisType.memberType(sym2))
- if (!tpe1.isErroneous && !tpe2.isErroneous)
- unit.error(
- if (sym1.owner == root) sym1.pos else root.pos,
- (if (sym1.owner == sym2.owner) "double definition:\n"
- else if (sym1.owner == root) "name clash between defined and inherited member:\n"
- else "name clash between inherited members:\n") +
- sym1 + ":" + exitingRefchecks(tpe1.toString) +
- (if (sym1.owner == root) "" else sym1.locationString) + " and\n" +
- sym2 + ":" + exitingRefchecks(tpe2.toString) +
- (if (sym2.owner == root) " at line " + (sym2.pos).line else sym2.locationString) +
- "\nhave same type" +
- (if (exitingRefchecks(tpe1 =:= tpe2)) "" else " after erasure: " + exitingPostErasure(sym1.tpe)))
- sym1.setInfo(ErrorType)
+ import overridingPairs.Cursor
+
+ private def doubleDefError(pair: SymbolPair) {
+ import pair._
+
+ if (!pair.isErroneous) {
+ val what = (
+ if (low.owner == high.owner) "double definition"
+ else if (low.owner == base) "name clash between defined and inherited member"
+ else "name clash between inherited members"
+ )
+ val when = if (exitingRefchecks(lowType matches highType)) "" else " after erasure: " + exitingPostErasure(highType)
+
+ unit.error(pos,
+ s"""|$what:
+ |${exitingRefchecks(highString)} and
+ |${exitingRefchecks(lowString)}
+ |have same type$when""".trim.stripMargin
+ )
}
+ low setInfo ErrorType
+ }
- val decls = root.info.decls
+ /** TODO - adapt SymbolPairs so it can be used here. */
+ private def checkNoDeclaredDoubleDefs(base: Symbol) {
+ val decls = base.info.decls
var e = decls.elems
while (e ne null) {
if (e.sym.isTerm) {
- var e1 = decls.lookupNextEntry(e)
+ var e1 = decls lookupNextEntry e
while (e1 ne null) {
- if (exitingPostErasure(e1.sym.info =:= e.sym.info)) doubleDefError(e.sym, e1.sym)
- e1 = decls.lookupNextEntry(e1)
+ if (exitingPostErasure(e1.sym.info =:= e.sym.info))
+ doubleDefError(new SymbolPair(base, e.sym, e1.sym))
+
+ e1 = decls lookupNextEntry e1
}
}
e = e.next
}
+ }
- val opc = new overridingPairs.Cursor(root) {
+ /** Emit an error if there is a double definition. This can happen if:
+ *
+ * - A template defines two members with the same name and erased type.
+ * - A template defines and inherits two members `m` with different types,
+ * but their erased types are the same.
+ * - A template inherits two members `m` with different types,
+ * but their erased types are the same.
+ */
+ private def checkNoDoubleDefs(root: Symbol) {
+ checkNoDeclaredDoubleDefs(root)
+ object opc extends Cursor(root) {
+ // specialized members have no type history before 'specialize', causing double def errors for curried defs
override def exclude(sym: Symbol): Boolean = (
- !sym.isTerm || sym.isPrivate || super.exclude(sym)
- // specialized members have no type history before 'specialize', causing double def errors for curried defs
+ sym.isType
+ || sym.isPrivate
+ || super.exclude(sym)
|| !sym.hasTypeAt(currentRun.refchecksPhase.id)
)
-
- override def matches(sym1: Symbol, sym2: Symbol): Boolean =
- exitingPostErasure(sym1.tpe =:= sym2.tpe)
+ override def matches(sym1: Symbol, sym2: Symbol) = true
}
- while (opc.hasNext) {
- if (!exitingRefchecks(
- root.thisType.memberType(opc.overriding) matches
- root.thisType.memberType(opc.overridden))) {
- debuglog("" + opc.overriding.locationString + " " +
- opc.overriding.infosString +
- opc.overridden.locationString + " " +
- opc.overridden.infosString)
- doubleDefError(opc.overriding, opc.overridden)
- }
- opc.next()
+ def sameTypeAfterErasure(pair: SymbolPair) = {
+ import pair._
+ log(s"Considering for erasure clash:\n$pair")
+ !exitingRefchecks(lowType matches highType) && exitingPostErasure(low.tpe =:= high.tpe)
}
+ opc.iterator filter sameTypeAfterErasure foreach doubleDefError
}
-/*
- for (bc <- root.info.baseClasses.tail; other <- bc.info.decls.toList) {
- if (other.isTerm && !other.isConstructor && !(other hasFlag (PRIVATE | BRIDGE))) {
- for (member <- root.info.nonPrivateMember(other.name).alternatives) {
- if (member != other &&
- !(member hasFlag BRIDGE) &&
- exitingErasure(member.tpe =:= other.tpe) &&
- !exitingRefchecks(
- root.thisType.memberType(member) matches root.thisType.memberType(other))) {
- debuglog("" + member.locationString + " " + member.infosString + other.locationString + " " + other.infosString);
- doubleDefError(member, other)
- }
- }
- }
- }
-*/
-
/** Add bridge definitions to a template. This means:
*
* If there is a concrete member `m` which overrides a member in a base
@@ -940,7 +931,6 @@ abstract class Erasure extends AddInterfaces
*/
private def bridgeDefs(owner: Symbol): (List[Tree], immutable.Set[Symbol]) = {
assert(phase == currentRun.erasurePhase, phase)
- debuglog("computing bridges for " + owner)
new ComputeBridges(unit, owner) compute()
}
diff --git a/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala b/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala
index 2610679542..4222c4d8c8 100644
--- a/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala
+++ b/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala
@@ -6,221 +6,33 @@
package scala.tools.nsc
package transform
-import scala.collection.mutable
import symtab.Flags._
-import util.HashSet
-import scala.annotation.tailrec
+import scala.reflect.internal.SymbolPairs
/** A class that yields a kind of iterator (`Cursor`),
- * which yields all pairs of overriding/overridden symbols
- * that are visible in some baseclass, unless there's a parent class
- * that already contains the same pairs.
- * @author Martin Odersky
- * @version 1.0
+ * which yields pairs of corresponding symbols visible in some base class,
+ * unless there's a parent class that already contains the same pairs.
+ * Most of the logic is in SymbolPairs, which contains generic
+ * pair-oriented traversal logic.
*/
-abstract class OverridingPairs {
-
- val global: Global
+abstract class OverridingPairs extends SymbolPairs {
import global._
- /** The cursor class
- * @param base the base class that contains the overriding pairs
- */
- class Cursor(base: Symbol) {
-
- private val self = base.thisType
-
- /** Symbols to exclude: Here these are constructors, private locals,
- * and hidden symbols, including bridges. But it may be refined in subclasses.
- *
- */
- protected def exclude(sym: Symbol): Boolean =
- sym.isConstructor || sym.isPrivateLocal || sym.isArtifact
-
- /** The parents of base (may also be refined).
- */
- protected def parents: List[Type] = base.info.parents
-
- /** Does `sym1` match `sym2` so that it qualifies as overriding.
- * Types always match. Term symbols match if their membertypes
- * relative to <base>.this do
- */
- protected def matches(sym1: Symbol, sym2: Symbol): Boolean = {
- def tp_s(s: Symbol) = self.memberType(s) + "/" + self.memberType(s).getClass
- val result = sym1.isType || (self.memberType(sym1) matches self.memberType(sym2))
- debuglog("overriding-pairs? %s matches %s (%s vs. %s) == %s".format(
- sym1.fullLocationString, sym2.fullLocationString, tp_s(sym1), tp_s(sym2), result))
-
- result
- }
+ class Cursor(base: Symbol) extends super.Cursor(base) {
+ lazy val relatively = new RelativeTo(base.thisType)
- /** An implementation of BitSets as arrays (maybe consider collection.BitSet
- * for that?) The main purpose of this is to implement
- * intersectionContainsElement efficiently.
+ /** Symbols to exclude: Here these are constructors and private/artifact symbols,
+ * including bridges. But it may be refined in subclasses.
*/
- private type BitSet = Array[Int]
-
- private def include(bs: BitSet, n: Int) {
- val nshifted = n >> 5
- val nmask = 1 << (n & 31)
- bs(nshifted) = bs(nshifted) | nmask
- }
-
- /** Implements `bs1 * bs2 * {0..n} != 0.
- * Used in hasCommonParentAsSubclass */
- private def intersectionContainsElementLeq(bs1: BitSet, bs2: BitSet, n: Int): Boolean = {
- val nshifted = n >> 5
- val nmask = 1 << (n & 31)
- var i = 0
- while (i < nshifted) {
- if ((bs1(i) & bs2(i)) != 0) return true
- i += 1
- }
- (bs1(nshifted) & bs2(nshifted) & (nmask | nmask - 1)) != 0
- }
-
- /** The symbols that can take part in an overriding pair */
- private val decls = newScope
+ override protected def exclude(sym: Symbol) = (sym hasFlag PRIVATE | ARTIFACT) || sym.isConstructor
- // fill `decls` with overriding shadowing overridden */
- { def fillDecls(bcs: List[Symbol], deferredflag: Int) {
- if (!bcs.isEmpty) {
- fillDecls(bcs.tail, deferredflag)
- var e = bcs.head.info.decls.elems
- while (e ne null) {
- if (e.sym.getFlag(DEFERRED) == deferredflag.toLong && !exclude(e.sym))
- decls enter e.sym
- e = e.next
- }
- }
- }
- // first, deferred (this wil need to change if we change lookup rules!
- fillDecls(base.info.baseClasses, DEFERRED)
- // then, concrete.
- fillDecls(base.info.baseClasses, 0)
- }
-
- private val size = base.info.baseClasses.length
-
- /** A map from baseclasses of <base> to ints, with smaller ints meaning lower in
- * linearization order.
- * symbols that are not baseclasses map to -1.
+ /** Types always match. Term symbols match if their member types
+ * relative to `self` match.
*/
- private val index = new mutable.HashMap[Symbol, Int] {
- override def default(key: Symbol) = -1
- }
-
- // Note: overridingPairs can be called at odd instances by the Eclipse plugin
- // Soemtimes symbols are not yet defined and we get missing keys.
- // The implementation here is hardened so that it does not crash on a missing key.
-
- { var i = 0
- for (bc <- base.info.baseClasses) {
- index(bc) = i
- i += 1
- }
- }
-
- /** A mapping from all base class indices to a bitset
- * which indicates whether parents are subclasses.
- *
- * i \in subParents(j) iff
- * exists p \in parents, b \in baseClasses:
- * i = index(p)
- * j = index(b)
- * p isSubClass b
- * p.baseType(b) == self.baseType(b)
- */
- private val subParents = new Array[BitSet](size)
-
- { for (i <- List.range(0, size))
- subParents(i) = new BitSet(size)
- for (p <- parents) {
- val pIndex = index(p.typeSymbol)
- if (pIndex >= 0)
- for (bc <- p.baseClasses)
- if (p.baseType(bc) =:= self.baseType(bc)) {
- val bcIndex = index(bc)
- if (bcIndex >= 0)
- include(subParents(bcIndex), pIndex)
- }
- }
- }
-
- /** Do `sym1` and `sym2` have a common subclass in `parents`?
- * In that case we do not follow their overriding pairs
- */
- private def hasCommonParentAsSubclass(sym1: Symbol, sym2: Symbol) = {
- val index1 = index(sym1.owner)
- (index1 >= 0) && {
- val index2 = index(sym2.owner)
- (index2 >= 0) && {
- intersectionContainsElementLeq(
- subParents(index1), subParents(index2), index1 min index2)
- }
- }
- }
-
- /** The scope entries that have already been visited as overridden
- * (maybe excluded because of hasCommonParentAsSubclass).
- * These will not appear as overriding
- */
- private val visited = HashSet[ScopeEntry]("visited", 64)
-
- /** The current entry candidate for overriding
- */
- private var curEntry = decls.elems
-
- /** The current entry candidate for overridden */
- private var nextEntry = curEntry
-
- /** The current candidate symbol for overriding */
- var overriding: Symbol = _
-
- /** If not null: The symbol overridden by overriding */
- var overridden: Symbol = _
-
- //@M: note that next is called once during object initialization
- def hasNext: Boolean = curEntry ne null
-
- @tailrec
- final def next() {
- if (curEntry ne null) {
- overriding = curEntry.sym
- if (nextEntry ne null) {
- do {
- do {
- nextEntry = decls.lookupNextEntry(nextEntry)
- /* DEBUG
- if ((nextEntry ne null) &&
- !(nextEntry.sym hasFlag PRIVATE) &&
- !(overriding.owner == nextEntry.sym.owner) &&
- !matches(overriding, nextEntry.sym))
- println("skipping "+overriding+":"+self.memberType(overriding)+overriding.locationString+" to "+nextEntry.sym+":"+self.memberType(nextEntry.sym)+nextEntry.sym.locationString)
- */
- } while ((nextEntry ne null) &&
- ((nextEntry.sym hasFlag PRIVATE) ||
- (overriding.owner == nextEntry.sym.owner) ||
- (!matches(overriding, nextEntry.sym)) ||
- (exclude(overriding))))
- if (nextEntry ne null) visited addEntry nextEntry
- // skip nextEntry if a class in `parents` is a subclass of the owners of both
- // overriding and nextEntry.sym
- } while ((nextEntry ne null) && (hasCommonParentAsSubclass(overriding, nextEntry.sym)))
- if (nextEntry ne null) {
- overridden = nextEntry.sym
- //Console.println("yield: " + overriding + overriding.locationString + " / " + overridden + overridden.locationString);//DEBUG
- } else {
- do {
- curEntry = curEntry.next
- } while ((curEntry ne null) && (visited contains curEntry))
- nextEntry = curEntry
- next()
- }
- }
- }
- }
-
- next()
+ override protected def matches(sym1: Symbol, sym2: Symbol) = sym1.isType || (
+ (sym1.owner != sym2.owner)
+ && !exclude(sym2)
+ && relatively.matches(sym1, sym2)
+ )
}
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
index d60e61df1b..a9a7f6a954 100644
--- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
@@ -305,18 +305,23 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
/* Check that all conditions for overriding `other` by `member`
* of class `clazz` are met.
*/
- def checkOverride(member: Symbol, other: Symbol) {
+ def checkOverride(pair: SymbolPair) {
+ import pair._
+ val member = low
+ val other = high
+ def memberTp = lowType
+ def otherTp = highType
+
debuglog("Checking validity of %s overriding %s".format(member.fullLocationString, other.fullLocationString))
- def memberTp = self.memberType(member)
- def otherTp = self.memberType(other)
- def noErrorType = other.tpe != ErrorType && member.tpe != ErrorType
+ def noErrorType = !pair.isErroneous
def isRootOrNone(sym: Symbol) = sym != null && sym.isRoot || sym == NoSymbol
- def isNeitherInClass = (member.owner != clazz) && (other.owner != clazz)
+ def isNeitherInClass = member.owner != pair.base && other.owner != pair.base
+
def objectOverrideErrorMsg = (
- "overriding " + other.fullLocationString + " with " + member.fullLocationString + ":\n" +
+ "overriding " + high.fullLocationString + " with " + low.fullLocationString + ":\n" +
"an overriding object must conform to the overridden object's class bound" +
- analyzer.foundReqMsg(classBoundAsSeen(member.tpe), classBoundAsSeen(other.tpe))
+ analyzer.foundReqMsg(pair.lowClassBound, pair.highClassBound)
)
def overrideErrorMsg(msg: String): String = {
@@ -460,79 +465,72 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
}
}
- def checkOverrideTypes() {
- if (other.isAliasType) {
- //if (!member.typeParams.isEmpty) (1.5) @MAT
- // overrideError("may not be parameterized");
- //if (!other.typeParams.isEmpty) (1.5) @MAT
- // overrideError("may not override parameterized type");
- // @M: substSym
-
- if( !(sameLength(member.typeParams, other.typeParams) && (memberTp.substSym(member.typeParams, other.typeParams) =:= otherTp)) ) // (1.6)
- overrideTypeError()
+ //if (!member.typeParams.isEmpty) (1.5) @MAT
+ // overrideError("may not be parameterized");
+ //if (!other.typeParams.isEmpty) (1.5) @MAT
+ // overrideError("may not override parameterized type");
+ // @M: substSym
+ def checkOverrideAlias() {
+ if (pair.sameKind && lowType.substSym(low.typeParams, high.typeParams) =:= highType) ()
+ else overrideTypeError() // (1.6)
+ }
+ //if (!member.typeParams.isEmpty) // (1.7) @MAT
+ // overrideError("may not be parameterized");
+ def checkOverrideAbstract() {
+ if (!(highInfo.bounds containsType lowType)) { // (1.7.1)
+ overrideTypeError(); // todo: do an explaintypes with bounds here
+ explainTypes(_.bounds containsType _, highInfo, lowType)
}
- else if (other.isAbstractType) {
- //if (!member.typeParams.isEmpty) // (1.7) @MAT
- // overrideError("may not be parameterized");
- val otherTp = self.memberInfo(other)
-
- if (!(otherTp.bounds containsType memberTp)) { // (1.7.1)
- overrideTypeError(); // todo: do an explaintypes with bounds here
- explainTypes(_.bounds containsType _, otherTp, memberTp)
- }
-
- // check overriding (abstract type --> abstract type or abstract type --> concrete type member (a type alias))
- // making an abstract type member concrete is like passing a type argument
- val kindErrors = typer.infer.checkKindBounds(List(other), List(memberTp), self, member.owner) // (1.7.2)
-
- if(!kindErrors.isEmpty)
+ // check overriding (abstract type --> abstract type or abstract type --> concrete type member (a type alias))
+ // making an abstract type member concrete is like passing a type argument
+ typer.infer.checkKindBounds(high :: Nil, lowType :: Nil, rootType, low.owner) match { // (1.7.2)
+ case Nil =>
+ case kindErrors =>
unit.error(member.pos,
"The kind of "+member.keyString+" "+member.varianceString + member.nameString+
" does not conform to the expected kind of " + other.defString + other.locationString + "." +
kindErrors.toList.mkString("\n", ", ", ""))
-
- // check a type alias's RHS corresponds to its declaration
- // this overlaps somewhat with validateVariance
- if(member.isAliasType) {
- // println("checkKindBounds" + ((List(member), List(memberTp.dealiasWiden), self, member.owner)))
- val kindErrors = typer.infer.checkKindBounds(List(member), List(memberTp.dealiasWiden), self, member.owner)
-
- if(!kindErrors.isEmpty)
+ }
+ // check a type alias's RHS corresponds to its declaration
+ // this overlaps somewhat with validateVariance
+ if (low.isAliasType) {
+ typer.infer.checkKindBounds(low :: Nil, lowType.normalize :: Nil, rootType, low.owner) match {
+ case Nil =>
+ case kindErrors =>
unit.error(member.pos,
- "The kind of the right-hand side "+memberTp.dealiasWiden+" of "+member.keyString+" "+
- member.varianceString + member.nameString+ " does not conform to its expected kind."+
+ "The kind of the right-hand side "+lowType.normalize+" of "+low.keyString+" "+
+ low.varianceString + low.nameString+ " does not conform to its expected kind."+
kindErrors.toList.mkString("\n", ", ", ""))
- } else if (member.isAbstractType) {
- if (memberTp.isVolatile && !otherTp.bounds.hi.isVolatile)
- overrideError("is a volatile type; cannot override a type with non-volatile upper bound")
}
- } else if (other.isTerm) {
- other.cookJavaRawInfo() // #2454
- val memberTp = self.memberType(member)
- val otherTp = self.memberType(other)
- if (!overridesTypeInPrefix(memberTp, otherTp, self)) { // 8
- overrideTypeError()
- explainTypes(memberTp, otherTp)
- }
-
- if (member.isStable && !otherTp.isVolatile) {
- // (1.4), pt 2 -- member.isStable && memberTp.isVolatile started being possible after SI-6815
- // (before SI-6815, !symbol.tpe.isVolatile was implied by symbol.isStable)
- // TODO: allow overriding when @uncheckedStable?
- if (memberTp.isVolatile)
- overrideError("has a volatile type; cannot override a member with non-volatile type")
- else memberTp.dealiasWiden.resultType match {
- case rt: RefinedType if !(rt =:= otherTp) && !(checkedCombinations contains rt.parents) =>
- // might mask some inconsistencies -- check overrides
- checkedCombinations += rt.parents
- val tsym = rt.typeSymbol
- if (tsym.pos == NoPosition) tsym setPos member.pos
- checkAllOverrides(tsym, typesOnly = true)
- case _ =>
- }
+ }
+ else if (low.isAbstractType && lowType.isVolatile && !highInfo.bounds.hi.isVolatile)
+ overrideError("is a volatile type; cannot override a type with non-volatile upper bound")
+ }
+ def checkOverrideTerm() {
+ other.cookJavaRawInfo() // #2454
+ if (!overridesTypeInPrefix(lowType, highType, rootType)) { // 8
+ overrideTypeError()
+ explainTypes(lowType, highType)
+ }
+ if (low.isStable && !highType.isVolatile) {
+ if (lowType.isVolatile)
+ overrideError("has a volatile type; cannot override a member with non-volatile type")
+ else lowType.normalize.resultType match {
+ case rt: RefinedType if !(rt =:= highType) && !(checkedCombinations contains rt.parents) =>
+ // might mask some inconsistencies -- check overrides
+ checkedCombinations += rt.parents
+ val tsym = rt.typeSymbol
+ if (tsym.pos == NoPosition) tsym setPos member.pos
+ checkAllOverrides(tsym, typesOnly = true)
+ case _ =>
}
}
}
+ def checkOverrideTypes() {
+ if (high.isAliasType) checkOverrideAlias()
+ else if (high.isAbstractType) checkOverrideAbstract()
+ else if (high.isTerm) checkOverrideTerm()
+ }
def checkOverrideDeprecated() {
if (other.hasDeprecatedOverridingAnnotation) {
@@ -545,8 +543,8 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
val opc = new overridingPairs.Cursor(clazz)
while (opc.hasNext) {
- //Console.println(opc.overriding/* + ":" + opc.overriding.tpe*/ + " in "+opc.overriding.fullName + " overrides " + opc.overridden/* + ":" + opc.overridden.tpe*/ + " in "+opc.overridden.fullName + "/"+ opc.overridden.hasFlag(DEFERRED));//debug
- if (!opc.overridden.isClass) checkOverride(opc.overriding, opc.overridden)
+ if (!opc.high.isClass)
+ checkOverride(opc.currentPair)
opc.next()
}
@@ -585,10 +583,10 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
def ignoreDeferred(member: Symbol) = (
(member.isAbstractType && !member.isFBounded) || (
- member.isJavaDefined &&
// the test requires exitingErasure so shouldn't be
// done if the compiler has no erasure phase available
- (currentRun.erasurePhase == NoPhase || javaErasedOverridingSym(member) != NoSymbol)
+ member.isJavaDefined
+ && (currentRun.erasurePhase == NoPhase || javaErasedOverridingSym(member) != NoSymbol)
)
)
diff --git a/src/reflect/scala/reflect/internal/SymbolPairs.scala b/src/reflect/scala/reflect/internal/SymbolPairs.scala
new file mode 100644
index 0000000000..b538648b36
--- /dev/null
+++ b/src/reflect/scala/reflect/internal/SymbolPairs.scala
@@ -0,0 +1,302 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2013 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala
+package reflect
+package internal
+
+import scala.collection.mutable
+import Flags._
+import util.HashSet
+import scala.annotation.tailrec
+
+/** An abstraction for considering symbol pairs.
+ * One of the greatest sources of compiler bugs is that symbols can
+ * trivially lose their prefixes and turn into some completely different
+ * type with the smallest of errors. It is the exception not the rule
+ * that type comparisons are done correctly.
+ *
+ * This offers a small step toward coherence with two abstractions
+ * which come up over and over again:
+ *
+ * RelativeTo: operations relative to a prefix
+ * SymbolPair: two symbols being related somehow, plus the class
+ * in which the relation is being performed
+ *
+ * This is only a start, but it is a start.
+ */
+abstract class SymbolPairs {
+ val global: SymbolTable
+ import global._
+
+ /** Type operations relative to a prefix. All operations work on Symbols,
+ * and the types are the member types of those symbols in the prefix.
+ */
+ class RelativeTo(val prefix: Type) {
+ def this(clazz: Symbol) = this(clazz.thisType)
+ import scala.language.implicitConversions // geez, it even has to hassle me when it's private
+ private implicit def symbolToType(sym: Symbol): Type = prefix memberType sym
+
+ def erasureOf(sym: Symbol): Type = erasure.erasure(sym)(sym: Type)
+ def signature(sym: Symbol): String = sym defStringSeenAs (sym: Type)
+ def erasedSignature(sym: Symbol): String = sym defStringSeenAs erasureOf(sym)
+
+ def isSameType(sym1: Symbol, sym2: Symbol): Boolean = sym1 =:= sym2
+ def isSubType(sym1: Symbol, sym2: Symbol): Boolean = sym1 <:< sym2
+ def isSuperType(sym1: Symbol, sym2: Symbol): Boolean = sym2 <:< sym1
+ def isSameErasure(sym1: Symbol, sym2: Symbol): Boolean = erasureOf(sym1) =:= erasureOf(sym2)
+ def matches(sym1: Symbol, sym2: Symbol): Boolean = (sym1: Type) matches (sym2: Type)
+
+ override def toString = s"RelativeTo($prefix)"
+ }
+
+ /** Are types tp1 and tp2 equivalent seen from the perspective
+ * of `baseClass`? For instance List[Int] and Seq[Int] are =:=
+ * when viewed from IterableClass.
+ */
+ def sameInBaseClass(baseClass: Symbol)(tp1: Type, tp2: Type) =
+ (tp1 baseType baseClass) =:= (tp2 baseType baseClass)
+
+ case class SymbolPair(base: Symbol, low: Symbol, high: Symbol) {
+ def pos = if (low.owner == base) low.pos else if (high.owner == base) high.pos else base.pos
+ def self: Type = base.thisType
+ def rootType: Type = base.thisType
+
+ def lowType: Type = self memberType low
+ def lowErased: Type = erasure.specialErasure(base)(low.tpe)
+ def lowClassBound: Type = classBoundAsSeen(low.tpe.typeSymbol)
+
+ def highType: Type = self memberType high
+ def highInfo: Type = self memberInfo high
+ def highErased: Type = erasure.specialErasure(base)(high.tpe)
+ def highClassBound: Type = classBoundAsSeen(high.tpe.typeSymbol)
+
+ def isErroneous = low.tpe.isErroneous || high.tpe.isErroneous
+ def sameKind = sameLength(low.typeParams, high.typeParams)
+
+ private def classBoundAsSeen(tsym: Symbol) =
+ tsym.classBound.asSeenFrom(rootType, tsym.owner)
+
+ private def memberDefString(sym: Symbol, where: Boolean) = {
+ val def_s = (
+ if (sym.isConstructor) s"$sym: ${self memberType sym}"
+ else sym defStringSeenAs (self memberType sym)
+ )
+ def_s + whereString(sym)
+ }
+ /** A string like ' at line 55' if the symbol is defined in the class
+ * under consideration, or ' in trait Foo' if defined elsewhere.
+ */
+ private def whereString(sym: Symbol) =
+ if (sym.owner == base) " at line " + sym.pos.line else sym.locationString
+
+ def lowString = memberDefString(low, where = true)
+ def highString = memberDefString(high, where = true)
+
+ override def toString = sm"""
+ |Cursor(in $base) {
+ | high $highString
+ | erased $highErased
+ | infos ${high.infosString}
+ | low $lowString
+ | erased $lowErased
+ | infos ${low.infosString}
+ |}""".trim
+ }
+
+ /** The cursor class
+ * @param base the base class containing the participating symbols
+ */
+ abstract class Cursor(val base: Symbol) {
+ cursor =>
+
+ final val self = base.thisType // The type relative to which symbols are seen.
+ private val decls = newScope // all the symbols which can take part in a pair.
+ private val size = bases.length
+
+ /** A symbol for which exclude returns true will not appear as
+ * either end of a pair.
+ */
+ protected def exclude(sym: Symbol): Boolean
+
+ /** Does `sym1` match `sym2` such that (sym1, sym2) should be
+ * considered as a (lo, high) pair? Types always match. Term symbols
+ * match if their member types relative to `self` match.
+ */
+ protected def matches(sym1: Symbol, sym2: Symbol): Boolean
+
+ /** The parents and base classes of `base`. Can be refined in subclasses.
+ */
+ protected def parents: List[Type] = base.info.parents
+ protected def bases: List[Symbol] = base.info.baseClasses
+
+ /** An implementation of BitSets as arrays (maybe consider collection.BitSet
+ * for that?) The main purpose of this is to implement
+ * intersectionContainsElement efficiently.
+ */
+ private type BitSet = Array[Int]
+
+ /** A mapping from all base class indices to a bitset
+ * which indicates whether parents are subclasses.
+ *
+ * i \in subParents(j) iff
+ * exists p \in parents, b \in baseClasses:
+ * i = index(p)
+ * j = index(b)
+ * p isSubClass b
+ * p.baseType(b) == self.baseType(b)
+ */
+ private val subParents = new Array[BitSet](size)
+
+ /** A map from baseclasses of <base> to ints, with smaller ints meaning lower in
+ * linearization order. Symbols that are not baseclasses map to -1.
+ */
+ private val index = new mutable.HashMap[Symbol, Int] { override def default(key: Symbol) = -1 }
+
+ /** The scope entries that have already been visited as highSymbol
+ * (but may have been excluded via hasCommonParentAsSubclass.)
+ * These will not appear as lowSymbol.
+ */
+ private val visited = HashSet[ScopeEntry]("visited", 64)
+
+ /** Initialization has to run now so decls is populated before
+ * the declaration of curEntry.
+ */
+ init()
+
+ // The current low and high symbols; the high may be null.
+ private[this] var lowSymbol: Symbol = _
+ private[this] var highSymbol: Symbol = _
+
+ // The current entry candidates for low and high symbol.
+ private[this] var curEntry = decls.elems
+ private[this] var nextEntry = curEntry
+
+ // These fields are initially populated with a call to next().
+ next()
+
+ // populate the above data structures
+ private def init() {
+ // Fill `decls` with lower symbols shadowing higher ones
+ def fillDecls(bcs: List[Symbol], deferred: Boolean) {
+ if (!bcs.isEmpty) {
+ fillDecls(bcs.tail, deferred)
+ var e = bcs.head.info.decls.elems
+ while (e ne null) {
+ if (e.sym.initialize.isDeferred == deferred && !exclude(e.sym))
+ decls enter e.sym
+ e = e.next
+ }
+ }
+ }
+ var i = 0
+ for (bc <- bases) {
+ index(bc) = i
+ subParents(i) = new BitSet(size)
+ i += 1
+ }
+ for (p <- parents) {
+ val pIndex = index(p.typeSymbol)
+ if (pIndex >= 0)
+ for (bc <- p.baseClasses ; if sameInBaseClass(bc)(p, self)) {
+ val bcIndex = index(bc)
+ if (bcIndex >= 0)
+ include(subParents(bcIndex), pIndex)
+ }
+ }
+ // first, deferred (this will need to change if we change lookup rules!)
+ fillDecls(bases, deferred = true)
+ // then, concrete.
+ fillDecls(bases, deferred = false)
+ }
+
+ private def include(bs: BitSet, n: Int) {
+ val nshifted = n >> 5
+ val nmask = 1 << (n & 31)
+ bs(nshifted) |= nmask
+ }
+
+ /** Implements `bs1 * bs2 * {0..n} != 0.
+ * Used in hasCommonParentAsSubclass */
+ private def intersectionContainsElementLeq(bs1: BitSet, bs2: BitSet, n: Int): Boolean = {
+ val nshifted = n >> 5
+ val nmask = 1 << (n & 31)
+ var i = 0
+ while (i < nshifted) {
+ if ((bs1(i) & bs2(i)) != 0) return true
+ i += 1
+ }
+ (bs1(nshifted) & bs2(nshifted) & (nmask | nmask - 1)) != 0
+ }
+
+ /** Do `sym1` and `sym2` have a common subclass in `parents`?
+ * In that case we do not follow their pairs.
+ */
+ private def hasCommonParentAsSubclass(sym1: Symbol, sym2: Symbol) = {
+ val index1 = index(sym1.owner)
+ (index1 >= 0) && {
+ val index2 = index(sym2.owner)
+ (index2 >= 0) && {
+ intersectionContainsElementLeq(
+ subParents(index1), subParents(index2), index1 min index2)
+ }
+ }
+ }
+
+ @tailrec private def advanceNextEntry() {
+ if (nextEntry ne null) {
+ nextEntry = decls lookupNextEntry nextEntry
+ if (nextEntry ne null) {
+ val high = nextEntry.sym
+ val isMatch = matches(lowSymbol, high) && { visited addEntry nextEntry ; true } // side-effect visited on all matches
+
+ // skip nextEntry if a class in `parents` is a subclass of the
+ // owners of both low and high.
+ if (isMatch && !hasCommonParentAsSubclass(lowSymbol, high))
+ highSymbol = high
+ else
+ advanceNextEntry()
+ }
+ }
+ }
+ @tailrec private def advanceCurEntry() {
+ if (curEntry ne null) {
+ curEntry = curEntry.next
+ if (curEntry ne null) {
+ if (visited(curEntry) || exclude(curEntry.sym))
+ advanceCurEntry()
+ else
+ nextEntry = curEntry
+ }
+ }
+ }
+
+ /** The `low` and `high` symbol. In the context of overriding pairs,
+ * low == overriding and high == overridden.
+ */
+ def low = lowSymbol
+ def high = highSymbol
+
+ def hasNext = curEntry ne null
+ def currentPair = new SymbolPair(base, low, high)
+ def iterator = new Iterator[SymbolPair] {
+ def hasNext = cursor.hasNext
+ def next() = try cursor.currentPair finally cursor.next()
+ }
+
+ // Note that next is called once during object initialization to
+ // populate the fields tracking the current symbol pair.
+ def next() {
+ if (curEntry ne null) {
+ lowSymbol = curEntry.sym
+ advanceNextEntry() // sets highSymbol
+ if (nextEntry eq null) {
+ advanceCurEntry()
+ next()
+ }
+ }
+ }
+ }
+}
diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala
index 14d742fc2c..efdc8f7435 100644
--- a/src/reflect/scala/reflect/internal/Symbols.scala
+++ b/src/reflect/scala/reflect/internal/Symbols.scala
@@ -3471,8 +3471,8 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
assert((prev eq null) || phaseId(validFrom) > phaseId(prev.validFrom), this)
assert(validFrom != NoPeriod, this)
- override def toString() =
- "TypeHistory(" + phaseOf(validFrom)+":"+runId(validFrom) + "," + info + "," + prev + ")"
+ private def phaseString = "%s: %s".format(phaseOf(validFrom), info)
+ override def toString = toList reverseMap (_.phaseString) mkString ", "
def toList: List[TypeHistory] = this :: ( if (prev eq null) Nil else prev.toList )
diff --git a/test/files/neg/accesses.check b/test/files/neg/accesses.check
index db58af12ce..5a5e03233e 100644
--- a/test/files/neg/accesses.check
+++ b/test/files/neg/accesses.check
@@ -1,7 +1,3 @@
-accesses.scala:23: error: overriding method f2 in class A of type ()Unit;
- method f2 has weaker access privileges; it should not be private
- private def f2(): Unit = ()
- ^
accesses.scala:24: error: overriding method f3 in class A of type ()Unit;
method f3 has weaker access privileges; it should be at least protected
private[p2] def f3(): Unit = ()
@@ -14,4 +10,4 @@ accesses.scala:26: error: overriding method f5 in class A of type ()Unit;
method f5 has weaker access privileges; it should be at least protected[p1]
protected[p2] def f5(): Unit
^
-four errors found
+three errors found
diff --git a/test/files/neg/accesses2.check b/test/files/neg/accesses2.check
new file mode 100644
index 0000000000..554a7b4c81
--- /dev/null
+++ b/test/files/neg/accesses2.check
@@ -0,0 +1,4 @@
+accesses2.scala:5: error: class B1 needs to be abstract, since method f2 in class A of type ()Int is not defined
+ class B1 extends A {
+ ^
+one error found
diff --git a/test/files/neg/accesses2.scala b/test/files/neg/accesses2.scala
new file mode 100644
index 0000000000..c7640f84b5
--- /dev/null
+++ b/test/files/neg/accesses2.scala
@@ -0,0 +1,11 @@
+package p2 {
+ abstract class A {
+ private[p2] def f2(): Int
+ }
+ class B1 extends A {
+ private def f2(): Int = 1
+ }
+ abstract class B2 extends A {
+ private def f2(): Int = 1
+ }
+}
diff --git a/test/files/neg/t0259.check b/test/files/neg/t0259.check
index 24e35e6176..8c15d98419 100644
--- a/test/files/neg/t0259.check
+++ b/test/files/neg/t0259.check
@@ -1,6 +1,6 @@
t0259.scala:4: error: double definition:
-constructor TestCase3:(groups: String*)test.TestCase3 and
-constructor TestCase3:(groups: (String, Int)*)test.TestCase3 at line 3
+constructor TestCase3: (groups: (String, Int)*)test.TestCase3 at line 3 and
+constructor TestCase3: (groups: String*)test.TestCase3 at line 4
have same type after erasure: (groups: Seq)test.TestCase3
def this( groups: String*) = this()
^
diff --git a/test/files/neg/t3653.check b/test/files/neg/t3653.check
index ac6e2ca9dc..ad68e29fb4 100644
--- a/test/files/neg/t3653.check
+++ b/test/files/neg/t3653.check
@@ -1,7 +1,7 @@
t3653.scala:3: error: double definition:
-method x:(implicit x: Int)Int and
-method x:(i: Int)Int at line 2
-have same type after erasure: (x: Int)Int
+def x(i: Int): Int at line 2 and
+def x(implicit x: Int): Int at line 3
+have same type after erasure: (i: Int)Int
def x(implicit x: Int) = 5
^
one error found
diff --git a/test/files/neg/t588.check b/test/files/neg/t588.check
index f8b5516fdc..ff08f77a6f 100644
--- a/test/files/neg/t588.check
+++ b/test/files/neg/t588.check
@@ -1,13 +1,13 @@
t588.scala:3: error: double definition:
-method visit:(f: Int => String)Boolean and
-method visit:(f: Int => Unit)Boolean at line 2
+def visit(f: Int => Unit): Boolean at line 2 and
+def visit(f: Int => String): Boolean at line 3
have same type after erasure: (f: Function1)Boolean
def visit(f: Int => String): Boolean
^
t588.scala:10: error: double definition:
-method f:(brac: Test.this.TypeB)Unit and
-method f:(node: Test.this.TypeA)Unit at line 9
-have same type after erasure: (brac: Test#TraitA)Unit
+def f(node: Test.this.TypeA): Unit at line 9 and
+def f(brac: Test.this.TypeB): Unit at line 10
+have same type after erasure: (node: Test#TraitA)Unit
def f(brac : TypeB) : Unit;
^
two errors found
diff --git a/test/files/neg/t6443c.check b/test/files/neg/t6443c.check
index 7cf8d23f4b..7b7f419f6c 100644
--- a/test/files/neg/t6443c.check
+++ b/test/files/neg/t6443c.check
@@ -1,6 +1,6 @@
t6443c.scala:16: error: double definition:
-method foo:(d: B.D)(a: Any)(d2: d.type)Unit and
-method foo:(d: B.D)(a: Any, d2: d.type)Unit at line 11
+def foo(d: B.D)(a: Any,d2: d.type): Unit at line 11 and
+def foo(d: B.D)(a: Any)(d2: d.type): Unit at line 16
have same type after erasure: (d: B.D, a: Object, d2: B.D)Unit
def foo(d: D)(a: Any)(d2: d.type): Unit = ()
^
diff --git a/test/files/neg/t663.check b/test/files/neg/t663.check
index 40161fb3e3..633e27ee12 100644
--- a/test/files/neg/t663.check
+++ b/test/files/neg/t663.check
@@ -1,7 +1,7 @@
t663.scala:11: error: name clash between defined and inherited member:
-method asMatch:(m: Test.this.Node)Any and
-method asMatch:(node: Test.this.Matchable)Any in trait MatchableImpl
-have same type after erasure: (m: test.Test#NodeImpl)Object
+def asMatch(node: Test.this.Matchable): Any in trait MatchableImpl and
+def asMatch(m: Test.this.Node): Any at line 11
+have same type after erasure: (node: test.Test#NodeImpl)Object
def asMatch(m : Node) : Any = {
^
one error found
diff --git a/test/files/neg/valueclasses-doubledefs.check b/test/files/neg/valueclasses-doubledefs.check
index 556d7a0900..ec513aca6b 100644
--- a/test/files/neg/valueclasses-doubledefs.check
+++ b/test/files/neg/valueclasses-doubledefs.check
@@ -1,6 +1,6 @@
valueclasses-doubledefs.scala:5: error: double definition:
-method apply:(x: Meter)String and
-method apply:(x: Double)String at line 4
+def apply(x: Double): String at line 4 and
+def apply(x: Meter): String at line 5
have same type after erasure: (x: Double)String
def apply(x: Meter) = x.toString
^
diff --git a/test/files/neg/valueclasses-pavlov.check b/test/files/neg/valueclasses-pavlov.check
index 031589edad..17102a0c68 100644
--- a/test/files/neg/valueclasses-pavlov.check
+++ b/test/files/neg/valueclasses-pavlov.check
@@ -1,6 +1,6 @@
valueclasses-pavlov.scala:8: error: double definition:
-method foo:(x: Box2)String and
-method foo:(x: String)String at line 7
+def foo(x: String): String at line 7 and
+def foo(x: Box2): String at line 8
have same type after erasure: (x: String)String
def foo(x: Box2) = "foo(Box2): ok"
^
diff --git a/test/files/res/t687.check b/test/files/res/t687.check
index b741b262b9..5f72c98636 100644
--- a/test/files/res/t687.check
+++ b/test/files/res/t687.check
@@ -1,8 +1,8 @@
nsc>
nsc> t687/QueryB.scala:3: error: name clash between defined and inherited member:
-method equals:(o: Object)Boolean and
-method equals:(x$1: Any)Boolean in class Any
-have same type after erasure: (o: Object)Boolean
+def equals(x$1: Any): Boolean in class Any and
+override def equals(o: Object): Boolean at line 3
+have same type after erasure: (x$1: Object)Boolean
override def equals(o : Object) = false;
^
diff --git a/test/files/run/private-override.check b/test/files/run/private-override.check
new file mode 100644
index 0000000000..00750edc07
--- /dev/null
+++ b/test/files/run/private-override.check
@@ -0,0 +1 @@
+3
diff --git a/test/files/run/private-override.scala b/test/files/run/private-override.scala
new file mode 100644
index 0000000000..0a3f57f97c
--- /dev/null
+++ b/test/files/run/private-override.scala
@@ -0,0 +1,17 @@
+package test.p1.p2 {
+ abstract class A {
+ private[p2] def f2(): Int = 1
+ }
+ abstract class Other extends A {
+ // It's a private method - not a private[p2] method. Not a failed
+ // "weaker access privileges" override, a different namespace.
+ private def f2(): Int = super.f2() + 2
+ def go() = f2()
+ }
+}
+
+object Test extends test.p1.p2.Other {
+ def main(args: Array[String]): Unit = {
+ println(go())
+ }
+}