Skip to content

Commit 8afe82c

Browse files
committed
Disallow private methods "overriding" non-private ones.
Java requires this. Scala 2 is even stricter: it disallows all provite members to override, no matter whether they are methods or not.
1 parent b8b32be commit 8afe82c

File tree

7 files changed

+61
-10
lines changed

7 files changed

+61
-10
lines changed

compiler/src/dotty/tools/dotc/core/Flags.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,7 @@ object Flags {
552552
val JavaProtected: FlagSet = JavaDefined | Protected
553553
val MethodOrLazy: FlagSet = Lazy | Method
554554
val MutableOrLazy: FlagSet = Lazy | Mutable
555+
val MethodOrLazyOrMutable: FlagSet = Lazy | Method | Mutable
555556
val LiftedMethod: FlagSet = Lifted | Method
556557
val LocalParam: FlagSet = Local | Param
557558
val LocalParamAccessor: FlagSet = Local | ParamAccessor | Private

compiler/src/dotty/tools/dotc/typer/RefChecks.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,30 @@ object RefChecks {
866866
}
867867
}
868868

869+
/** Check that we do not "override" anything with a private method
870+
* or something that becomes a private method. According to the Scala
871+
* modeling this is non-sensical since private members don't override.
872+
* But Java and the JVM disagree, if the private member is a method.
873+
* A test case is neg/i7926b.scala.
874+
* Note: The compiler could possibly silently rename the offending private
875+
* instead of flagging it as an error. But that might mean we see some
876+
* surprising names at runtime. E.g. in neg/i4564a.scala, a private
877+
* case class `apply` method would have to be renamed to something else.
878+
*/
879+
def checkNoPrivateOverrides(tree: Tree)(using ctx: Context): Unit =
880+
val sym = tree.symbol
881+
if sym.owner.isClass
882+
&& sym.is(Private)
883+
&& (sym.isOneOf(MethodOrLazyOrMutable) || !sym.is(Local)) // in these cases we'll produce a getter later
884+
&& !sym.isConstructor
885+
then
886+
val cls = sym.owner.asClass
887+
for bc <- cls.baseClasses.tail do
888+
val other = sym.matchingDecl(bc, cls.thisType)
889+
if other.exists then
890+
ctx.error(i"private $sym cannot override ${other.showLocated}", sym.sourcePos)
891+
end checkNoPrivateOverrides
892+
869893
type LevelAndIndex = immutable.Map[Symbol, (LevelInfo, Int)]
870894

871895
class OptLevelInfo {
@@ -957,6 +981,7 @@ class RefChecks extends MiniPhase { thisPhase =>
957981
else ctx
958982

959983
override def transformValDef(tree: ValDef)(implicit ctx: Context): ValDef = {
984+
checkNoPrivateOverrides(tree)
960985
checkDeprecatedOvers(tree)
961986
val sym = tree.symbol
962987
if (sym.exists && sym.owner.isTerm) {
@@ -976,6 +1001,7 @@ class RefChecks extends MiniPhase { thisPhase =>
9761001
}
9771002

9781003
override def transformDefDef(tree: DefDef)(implicit ctx: Context): DefDef = {
1004+
checkNoPrivateOverrides(tree)
9791005
checkDeprecatedOvers(tree)
9801006
tree
9811007
}

tests/neg/i4564a.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
class BaseCP[T] {
3+
def apply(x: T): ClashPoly = if (???) ClashPoly(1) else ???
4+
}
5+
object ClashPoly extends BaseCP[Int]
6+
case class ClashPoly private(x: Int) // private method apply cannot override method apply in class BaseCP

tests/neg/i7926b.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
trait A {
2+
def p: Int
3+
def getP = p
4+
}
5+
class B extends A {
6+
def p: Int = 22
7+
}
8+
class C extends B {
9+
private def p: Int = super.p // error
10+
}
11+
class D extends B {
12+
private val p: Int = super.p // OK
13+
}
14+
class E extends B {
15+
private var p: Int = 0 // error
16+
}
17+
class F extends B {
18+
private lazy val p: Int = 0 // error
19+
}
20+
object Test {
21+
def main(args: Array[String]): Unit =
22+
println(new C().getP) // would give illegal access error at runtime if C compiled
23+
println(new D().getP) // OK
24+
println(new E().getP) // would give illegal access error at runtime if E compiled
25+
println(new F().getP) // would give illegal access error at runtime if F compiled
26+
}

tests/neg/i8005.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ abstract class Buffer {
88
class ByteBuffer extends Buffer { // error: ByteBufer needs to be abstract since `bar` is not defined
99
private[nio] val isReadOnly: Boolean = false // error
1010
protected def foo(): Unit = () // error
11-
private def bar(): Unit = ()
11+
private def bar(): Unit = () // error
1212
}

tests/pos/i4564.scala

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,9 @@ object NoClashOverload {
4646
case class NoClashOverload private (x: Int)
4747

4848
class BaseNCP[T] {
49-
// error: overloaded method apply needs result type
5049
def apply(x: T): NoClashPoly = if (???) NoClashPoly(1) else ???
5150
}
5251

5352
object NoClashPoly extends BaseNCP[Boolean]
5453
case class NoClashPoly (x: Int)
5554

56-
57-
class BaseCP[T] {
58-
// error: overloaded method apply needs result type
59-
def apply(x: T): ClashPoly = if (???) ClashPoly(1) else ???
60-
}
61-
object ClashPoly extends BaseCP[Int]
62-
case class ClashPoly private(x: Int)

tests/pos/typers.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ object typers {
9999
}
100100

101101
class B extends A {
102-
private def x: Int = 1
102+
private val x: Int = 1
103103
}
104104

105105
val b: B = new B

0 commit comments

Comments
 (0)