Skip to content

Commit

Permalink
Better error message when accessing private members (#18690)
Browse files Browse the repository at this point in the history
Fixes #18686
  • Loading branch information
bishabosha authored Oct 17, 2023
2 parents 89fa247 + 6c1fce0 commit 36a9940
Show file tree
Hide file tree
Showing 14 changed files with 156 additions and 29 deletions.
2 changes: 1 addition & 1 deletion community-build/community-projects/stdLib213
Submodule stdLib213 updated 489 files
21 changes: 4 additions & 17 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,7 @@ object SymDenotations {
* As a side effect, drop Local flags of members that are not accessed via the ThisType
* of their owner.
*/
final def isAccessibleFrom(pre: Type, superAccess: Boolean = false, whyNot: StringBuffer | Null = null)(using Context): Boolean = {
final def isAccessibleFrom(pre: Type, superAccess: Boolean = false)(using Context): Boolean = {

/** Are we inside definition of `boundary`?
* If this symbol is Java defined, package structure is interpreted to be flat.
Expand All @@ -905,26 +905,13 @@ object SymDenotations {

/** Is protected access to target symbol permitted? */
def isProtectedAccessOK: Boolean =
inline def fail(str: String): false =
if whyNot != null then whyNot.nn.append(str)
false
val cls = owner.enclosingSubClass
if !cls.exists then
if pre.termSymbol.isPackageObject && accessWithin(pre.termSymbol.owner) then
true
else
val encl = if ctx.owner.isConstructor then ctx.owner.enclosingClass.owner.enclosingClass else ctx.owner.enclosingClass
val location = if owner.is(Final) then owner.showLocated else owner.showLocated + " or one of its subclasses"
fail(i"""
| Protected $this can only be accessed from $location.""")
else if isType || pre.derivesFrom(cls) || isConstructor || owner.is(ModuleClass) then
pre.termSymbol.isPackageObject && accessWithin(pre.termSymbol.owner)
else
// allow accesses to types from arbitrary subclasses fixes #4737
// don't perform this check for static members
true
else
val location = if cls.is(Final) then cls.showLocated else cls.showLocated + " or one of its subclasses"
fail(i"""
| Protected $this can only be accessed from $location.""")
isType || pre.derivesFrom(cls) || isConstructor || owner.is(ModuleClass)
end isProtectedAccessOK

if pre eq NoPrefix then true
Expand Down
21 changes: 19 additions & 2 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -876,7 +876,7 @@ extends Message(PatternMatchExhaustivityID) {

val pathes = List(
ActionPatch(
srcPos = endPos,
srcPos = endPos,
replacement = uncoveredCases.map(c => indent(s"case $c => ???", startColumn))
.mkString("\n", "\n", "")
),
Expand Down Expand Up @@ -2986,7 +2986,24 @@ extends ReferenceMsg(CannotBeAccessedID):
i"none of the overloaded alternatives named $name can"
val where = if (ctx.owner.exists) i" from ${ctx.owner.enclosingClass}" else ""
val whyNot = new StringBuffer
alts.foreach(_.isAccessibleFrom(pre, superAccess, whyNot))
for alt <- alts do
val cls = alt.owner.enclosingSubClass
val owner = if cls.exists then cls else alt.owner
val location: String =
if alt.is(Protected) then
if alt.privateWithin.exists && alt.privateWithin != owner then
if owner.is(Final) then alt.privateWithin.showLocated
else alt.privateWithin.showLocated + ", or " + owner.showLocated + " or one of its subclasses"
else
if owner.is(Final) then owner.showLocated
else owner.showLocated + " or one of its subclasses"
else
alt.privateWithin.orElse(owner).showLocated
val accessMod = if alt.is(Protected) then "protected" else "private"
val within = if alt.privateWithin.exists then i"[${alt.privateWithin.name}]"
else ""
whyNot.append(i"""
| $accessMod$within $alt can only be accessed from $location.""")
i"$whatCanNot be accessed as a member of $pre$where.$whyNot"
def explain(using Context) = ""

Expand Down
1 change: 1 addition & 0 deletions compiler/test-resources/repl/i1370
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ scala> object Lives { class Private { def foo1: Any = new Private.C1; def foo2:
1 | object Lives { class Private { def foo1: Any = new Private.C1; def foo2: Any = new Private.C2 }; object Private { class C1 private {}; private class C2 {} } }
| ^^^^^^^^^^
|constructor C1 cannot be accessed as a member of Lives.Private.C1 from class Private.
| private constructor C1 can only be accessed from class C1 in object Private.
1 error found
2 changes: 1 addition & 1 deletion tests/neg/i12573.check
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
|Extension methods were tried, but the search failed with:
|
| method getDFType cannot be accessed as a member of DFType.type from the top-level definitions in package <empty>.
| Protected method getDFType can only be accessed from object DFType.
| protected method getDFType can only be accessed from object DFType.
1 change: 1 addition & 0 deletions tests/neg/i14432c.check
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
12 |class Bar extends example.Foo(23) { // error: cant access private[example] ctor
| ^^^^^^^^^^^
| constructor Foo cannot be accessed as a member of example.Foo from class Bar.
| private[example] constructor Foo can only be accessed from package example.
-- [E172] Type Error: tests/neg/i14432c.scala:16:43 --------------------------------------------------------------------
16 | val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror
| ^
Expand Down
30 changes: 30 additions & 0 deletions tests/neg/i18686.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
-- [E173] Reference Error: tests/neg/i18686.scala:13:16 ----------------------------------------------------------------
13 | println(Foo.Bar1) // error
| ^^^^^^^^
| value Bar1 cannot be accessed as a member of Foo.type from object Main.
| private value Bar1 can only be accessed from object Foo.
-- [E173] Reference Error: tests/neg/i18686.scala:14:16 ----------------------------------------------------------------
14 | println(Foo.Bar2) // error
| ^^^^^^^^
| value Bar2 cannot be accessed as a member of Foo.type from object Main.
| private[Foo] value Bar2 can only be accessed from object Foo.
-- [E173] Reference Error: tests/neg/i18686.scala:15:16 ----------------------------------------------------------------
15 | println(Foo.Bar3) // error
| ^^^^^^^^
| value Bar3 cannot be accessed as a member of Foo.type from object Main.
| protected value Bar3 can only be accessed from object Foo.
-- [E173] Reference Error: tests/neg/i18686.scala:16:16 ----------------------------------------------------------------
16 | println(Foo.Bar4) // error
| ^^^^^^^^
| value Bar4 cannot be accessed as a member of Foo.type from object Main.
| protected[Foo] value Bar4 can only be accessed from object Foo.
-- [E173] Reference Error: tests/neg/i18686.scala:17:20 ----------------------------------------------------------------
17 | println(Foo.Baz.Bar5) // error
| ^^^^^^^^^^^^
| value Bar5 cannot be accessed as a member of Foo.Baz.type from object Main.
| private[Foo] value Bar5 can only be accessed from object Foo.
-- [E173] Reference Error: tests/neg/i18686.scala:18:20 ----------------------------------------------------------------
18 | println(Foo.Baz.Bar6) // error
| ^^^^^^^^^^^^
| value Bar6 cannot be accessed as a member of Foo.Baz.type from object Main.
| protected[Foo] value Bar6 can only be accessed from object Foo.
20 changes: 20 additions & 0 deletions tests/neg/i18686.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
object Foo:
private val Bar1: Int = 1
private[Foo] val Bar2: Int = 2
protected val Bar3: Int = 3
protected[Foo] val Bar4: Int = 5
object Baz:
private[Foo] val Bar5: Int = 5
protected[Foo] val Bar6: Int = 6
end Foo

object Main:
def main(args: Array[String]): Unit =
println(Foo.Bar1) // error
println(Foo.Bar2) // error
println(Foo.Bar3) // error
println(Foo.Bar4) // error
println(Foo.Baz.Bar5) // error
println(Foo.Baz.Bar6) // error
end main
end Main
28 changes: 28 additions & 0 deletions tests/neg/i18686b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-- [E173] Reference Error: tests/neg/i18686b.scala:15:16 ---------------------------------------------------------------
15 | println(foo.Bar1) // error
| ^^^^^^^^
| value Bar1 cannot be accessed as a member of Foo from object Main.
| private value Bar1 can only be accessed from class Foo.
-- [E173] Reference Error: tests/neg/i18686b.scala:16:16 ---------------------------------------------------------------
16 | println(foo.Bar2) // error
| ^^^^^^^^
| value Bar2 cannot be accessed as a member of Foo from object Main.
| private[Foo] value Bar2 can only be accessed from class Foo.
-- [E173] Reference Error: tests/neg/i18686b.scala:17:16 ---------------------------------------------------------------
17 | println(foo.Bar3) // error
| ^^^^^^^^
| value Bar3 cannot be accessed as a member of Foo from object Main.
| protected value Bar3 can only be accessed from class Foo or one of its subclasses.
-- [E173] Reference Error: tests/neg/i18686b.scala:18:16 ---------------------------------------------------------------
18 | println(foo.Bar4) // error
| ^^^^^^^^
| value Bar4 cannot be accessed as a member of Foo from object Main.
| protected[Foo] value Bar4 can only be accessed from class Foo or one of its subclasses.
-- [E008] Not Found Error: tests/neg/i18686b.scala:19:20 ---------------------------------------------------------------
19 | println(foo.Baz.Bar5) // error
| ^^^^^^^^^^^^
| value Bar5 is not a member of object Foo#Baz
-- [E008] Not Found Error: tests/neg/i18686b.scala:20:20 ---------------------------------------------------------------
20 | println(foo.Baz.Bar6) // error
| ^^^^^^^^^^^^
| value Bar6 is not a member of object Foo#Baz
22 changes: 22 additions & 0 deletions tests/neg/i18686b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class Foo:
private val Bar1: Int = 1
private[Foo] val Bar2: Int = 2
protected val Bar3: Int = 3
protected[Foo] val Bar4: Int = 5
class Baz:
private[Foo] val Bar5: Int = 5
protected[Foo] val Bar6: Int = 6
end Foo

def foo = new Foo

object Main:
def main(args: Array[String]): Unit =
println(foo.Bar1) // error
println(foo.Bar2) // error
println(foo.Bar3) // error
println(foo.Bar4) // error
println(foo.Baz.Bar5) // error
println(foo.Baz.Bar6) // error
end main
end Main
8 changes: 8 additions & 0 deletions tests/neg/i18686c.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- [E173] Reference Error: tests/neg/i18686c.scala:8:6 -----------------------------------------------------------------
8 | foo.foo // error
| ^^^^^^^
|method foo cannot be accessed as a member of (foo² : Bar.Foo) from the top-level definitions in package <empty>.
| protected[Bar] method foo can only be accessed from object Bar, or class Foo in object Bar or one of its subclasses.
|
|where: foo is a method in class Foo
| foo² is a parameter in method test
8 changes: 8 additions & 0 deletions tests/neg/i18686c.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
object Bar:
class Foo:
protected[Bar] def foo = 23
class Qux extends Foo:
val qux = foo

def test(foo: Bar.Foo) =
foo.foo // error
16 changes: 8 additions & 8 deletions tests/neg/i7709.check
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,39 @@
5 | class B extends X.Y // error
| ^^^
| class Y cannot be accessed as a member of X.type from class B.
| Protected class Y can only be accessed from object X.
| protected class Y can only be accessed from object X.
-- [E173] Reference Error: tests/neg/i7709.scala:6:21 ------------------------------------------------------------------
6 | class B2 extends X.Y: // error
| ^^^
| class Y cannot be accessed as a member of X.type from class B2.
| Protected class Y can only be accessed from object X.
| protected class Y can only be accessed from object X.
-- [E173] Reference Error: tests/neg/i7709.scala:9:28 ------------------------------------------------------------------
9 | class B4 extends B3(new X.Y) // error
| ^^^
| class Y cannot be accessed as a member of X.type from class B4.
| Protected class Y can only be accessed from object X.
| protected class Y can only be accessed from object X.
-- [E173] Reference Error: tests/neg/i7709.scala:11:34 -----------------------------------------------------------------
11 | def this(n: Int) = this(new X.Y().toString) // error
| ^^^
| class Y cannot be accessed as a member of X.type from class B5.
| Protected class Y can only be accessed from object X.
| protected class Y can only be accessed from object X.
-- [E173] Reference Error: tests/neg/i7709.scala:13:20 -----------------------------------------------------------------
13 | class B extends X.Y // error
| ^^^
| class Y cannot be accessed as a member of X.type from class B.
| Protected class Y can only be accessed from object X.
| protected class Y can only be accessed from object X.
-- [E173] Reference Error: tests/neg/i7709.scala:18:18 -----------------------------------------------------------------
18 | def y = new xx.Y // error
| ^^^^
| class Y cannot be accessed as a member of XX from class C.
| Protected class Y can only be accessed from class XX or one of its subclasses.
| protected class Y can only be accessed from class XX or one of its subclasses.
-- [E173] Reference Error: tests/neg/i7709.scala:23:20 -----------------------------------------------------------------
23 | def y = new xx.Y // error
| ^^^^
| class Y cannot be accessed as a member of XX from class D.
| Protected class Y can only be accessed from class XX or one of its subclasses.
| protected class Y can only be accessed from class XX or one of its subclasses.
-- [E173] Reference Error: tests/neg/i7709.scala:31:20 -----------------------------------------------------------------
31 | class Q extends X.Y // error
| ^^^
| class Y cannot be accessed as a member of p.X.type from class Q.
| Protected class Y can only be accessed from object X in package p.
| protected class Y can only be accessed from object X in package p.
5 changes: 5 additions & 0 deletions tests/neg/not-accessible.check
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@
8 | def test(a: A) = a.x // error
| ^^^
| value x cannot be accessed as a member of (a : foo.A) from class B.
| private[A] value x can only be accessed from class A in package foo.
-- [E173] Reference Error: tests/neg/not-accessible.scala:10:23 --------------------------------------------------------
10 | def test(a: A) = a.x // error
| ^^^
| value x cannot be accessed as a member of (a : foo.A) from object B.
| private[A] value x can only be accessed from class A in package foo.
-- [E173] Reference Error: tests/neg/not-accessible.scala:13:23 --------------------------------------------------------
13 | def test(a: A) = a.x // error
| ^^^
| value x cannot be accessed as a member of (a : foo.A) from the top-level definitions in package bar.
| private[A] value x can only be accessed from class A in package foo.
-- [E173] Reference Error: tests/neg/not-accessible.scala:5:21 ---------------------------------------------------------
5 | def test(a: A) = a.x // error
| ^^^
| value x cannot be accessed as a member of (a : foo.A) from the top-level definitions in package foo.
| private[A] value x can only be accessed from class A in package foo.
-- [E173] Reference Error: tests/neg/not-accessible.scala:15:23 --------------------------------------------------------
15 |def test(a: foo.A) = a.x // error
| ^^^
| value x cannot be accessed as a member of (a : foo.A) from the top-level definitions in package <empty>.
| private[A] value x can only be accessed from class A in package foo.

0 comments on commit 36a9940

Please sign in to comment.