Skip to content

Commit 9b3a2cb

Browse files
committed
Case class copy and apply inherit access modifiers from constructor
Fixes scala/bug#7884
1 parent e40c95e commit 9b3a2cb

File tree

9 files changed

+170
-16
lines changed

9 files changed

+170
-16
lines changed

src/compiler/scala/tools/nsc/typechecker/Unapplies.scala

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,16 @@ trait Unapplies extends ast.TreeDSL {
101101
}
102102
}
103103

104+
private def applyShouldInheritAccess(mods: Modifiers) = mods.hasFlag(PRIVATE) || (!mods.hasFlag(PROTECTED) && mods.hasAccessBoundary)
105+
104106
/** The module corresponding to a case class; overrides toString to show the module's name
105107
*/
106108
def caseModuleDef(cdef: ClassDef): ModuleDef = {
107109
val params = constrParamss(cdef)
108110
def inheritFromFun = !cdef.mods.hasAbstractFlag && cdef.tparams.isEmpty && (params match {
109111
case List(ps) if ps.length <= MaxFunctionArity => true
110112
case _ => false
111-
})
113+
}) && !applyShouldInheritAccess(constrMods(cdef))
112114
def createFun = {
113115
def primaries = params.head map (_.tpt)
114116
gen.scalaFunctionConstr(primaries, toIdent(cdef), abstractFun = true)
@@ -146,9 +148,20 @@ trait Unapplies extends ast.TreeDSL {
146148
)
147149
}
148150

151+
152+
private def constrMods(cdef: ClassDef): Modifiers = treeInfo.firstConstructorMods(cdef.impl.body)
153+
149154
/** The apply method corresponding to a case class
150155
*/
151-
def caseModuleApplyMeth(cdef: ClassDef): DefDef = factoryMeth(caseMods, nme.apply, cdef)
156+
def caseModuleApplyMeth(cdef: ClassDef): DefDef = {
157+
val inheritedMods = constrMods(cdef)
158+
val mods =
159+
if (applyShouldInheritAccess(inheritedMods))
160+
(caseMods | (inheritedMods.flags & PRIVATE)).copy(privateWithin = inheritedMods.privateWithin)
161+
else
162+
caseMods
163+
factoryMeth(mods, nme.apply, cdef)
164+
}
152165

153166
/** The unapply method corresponding to a case class
154167
*/
@@ -231,8 +244,9 @@ trait Unapplies extends ast.TreeDSL {
231244
val classTpe = classType(cdef, tparams)
232245
val argss = mmap(paramss)(toIdent)
233246
val body: Tree = New(classTpe, argss)
247+
val inheritedMods = constrMods(cdef)
234248
val copyDefDef = atPos(cdef.pos.focus)(
235-
DefDef(Modifiers(SYNTHETIC), nme.copy, tparams, paramss, TypeTree(), body)
249+
DefDef(Modifiers(SYNTHETIC | (inheritedMods.flags & AccessFlags), inheritedMods.privateWithin), nme.copy, tparams, paramss, TypeTree(), body)
236250
)
237251
Some(copyDefDef)
238252
}

src/reflect/scala/reflect/internal/TreeInfo.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,12 @@ abstract class TreeInfo {
529529
case _ => Nil
530530
}
531531

532+
/** The modifiers of the first constructor in `stats`. */
533+
def firstConstructorMods(stats: List[Tree]): Modifiers = firstConstructor(stats) match {
534+
case DefDef(mods, _, _, _, _, _) => mods
535+
case _ => Modifiers()
536+
}
537+
532538
/** The value definitions marked PRESUPER in this statement sequence */
533539
def preSuperFields(stats: List[Tree]): List[ValDef] =
534540
stats collect { case vd: ValDef if isEarlyValDef(vd) => vd }
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
caseclass_private_constructor.scala:4: error: method apply in object A cannot be accessed in object A
2+
error after rewriting to A.<apply: error>
3+
possible cause: maybe a wrong Dynamic method signature?
4+
def a1: A = A(1) // error: apply is private
5+
^
6+
caseclass_private_constructor.scala:5: error: method copy in class A cannot be accessed in A
7+
def a2: A = a1.copy(2) // error: copy is private
8+
^
9+
caseclass_private_constructor.scala:10: error: method apply in object B cannot be accessed in object B
10+
error after rewriting to B.<apply: error>
11+
possible cause: maybe a wrong Dynamic method signature?
12+
def b1: B = B(1) // error: apply is private
13+
^
14+
caseclass_private_constructor.scala:11: error: method copy in class B cannot be accessed in B
15+
def b2: B = b1.copy(2) // error: copy is private
16+
^
17+
caseclass_private_constructor.scala:22: error: method apply in object C cannot be accessed in object qualified_private.C
18+
error after rewriting to qualified_private.C.<apply: error>
19+
possible cause: maybe a wrong Dynamic method signature?
20+
def c1: C = C(1) // error: apply is private
21+
^
22+
caseclass_private_constructor.scala:23: error: method copy in class C cannot be accessed in qualified_private.C
23+
def c2: C = c1.copy(2) // error: copy is private
24+
^
25+
caseclass_private_constructor.scala:25: error: method apply in object D cannot be accessed in object qualified_private.D
26+
error after rewriting to qualified_private.D.<apply: error>
27+
possible cause: maybe a wrong Dynamic method signature?
28+
def d1: D = D(1) // error: apply is private
29+
^
30+
caseclass_private_constructor.scala:26: error: method copy in class D cannot be accessed in qualified_private.D
31+
def d2: D = d1.copy(2) // error: copy is private
32+
^
33+
caseclass_private_constructor.scala:32: error: method copy in class E cannot be accessed in E
34+
Access to protected method copy not permitted because
35+
enclosing object ETest is not a subclass of
36+
class E where target is defined
37+
def e2: E = e2.copy(2) // error: copy is protected
38+
^
39+
caseclass_private_constructor.scala:41: error: method copy in class F cannot be accessed in qualified_protected.F
40+
Access to protected method copy not permitted because
41+
enclosing object QProtTest is not a subclass of
42+
class F in object qualified_protected where target is defined
43+
def f2: F = f2.copy(2) // error: copy is protected
44+
^
45+
10 errors found
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
case class A private (i: Int)
2+
object A
3+
object ATest {
4+
def a1: A = A(1) // error: apply is private
5+
def a2: A = a1.copy(2) // error: copy is private
6+
}
7+
8+
case class B private (i: Int) // no user-defined companion object, should compile
9+
object BTest {
10+
def b1: B = B(1) // error: apply is private
11+
def b2: B = b1.copy(2) // error: copy is private
12+
}
13+
14+
object qualified_private {
15+
case class C private[qualified_private] (i: Int)
16+
object C
17+
18+
case class D private[qualified_private] (i: Int) // no user-defined companion object, should compile
19+
}
20+
object QPrivTest {
21+
import qualified_private._
22+
def c1: C = C(1) // error: apply is private
23+
def c2: C = c1.copy(2) // error: copy is private
24+
25+
def d1: D = D(1) // error: apply is private
26+
def d2: D = d1.copy(2) // error: copy is private
27+
}
28+
29+
case class E protected (i: Int)
30+
object ETest {
31+
def e1: E = E(1)
32+
def e2: E = e2.copy(2) // error: copy is protected
33+
}
34+
35+
object qualified_protected {
36+
case class F protected[qualified_protected] (i: Int)
37+
}
38+
object QProtTest {
39+
import qualified_protected._
40+
def f1: F = F(1)
41+
def f2: F = f2.copy(2) // error: copy is protected
42+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
case class A private (i: Int)
2+
object A {
3+
def a = A(1).copy(2) // apply and copy are accessible in companion
4+
}
5+
6+
case class B private (i: Int) { // no user-defined companion object, should compile
7+
def b = B(1).copy(2) // apply and copy are accessible
8+
}
9+
10+
object qualified_private {
11+
case class A private[qualified_private] (i: Int)
12+
object A {
13+
def a = A(1).copy(2) // apply and copy are accessible in companion
14+
}
15+
16+
def a = A(1).copy(2) // apply and copy are accessible in qualified_private object
17+
18+
case class B private[qualified_private] (i: Int) { // no user-defined companion object, should compile
19+
def b = B(1).copy(2) // apply and copy are accessible
20+
}
21+
22+
def b = B(1).copy(2) // apply and copy are accessible in qualified_private object
23+
}
24+
25+
case class C protected (i: Int)
26+
class CSub extends C(1) {
27+
def c = copy(2) // copy is accessible in subclass
28+
}
29+
object CTest {
30+
def c = C(1) // apply is public
31+
}
32+
33+
object qualified_protected {
34+
case class C protected[qualified_protected] (i: Int)
35+
class CSub extends C(1) {
36+
def c = copy(2) // copy is accessible in subclass
37+
}
38+
object CTest {
39+
def c = C(1) // apply is public
40+
def checkExtendsFunction: Int => C = C // companion extends (Int => C)
41+
}
42+
43+
def c = C(1).copy(2)
44+
}
45+
object CQualifiedTest {
46+
def c = qualified_protected.C(1) // apply is public
47+
}

test/files/pos/t6734.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ package object p
66

77
package p {
88
import scala.concurrent.Future
9-
case class C private[p] (value: Future[Int]) // private to avoid rewriting C.apply to new C
9+
case class C protected[p] (value: Future[Int]) // protected to avoid rewriting C.apply to new C
1010
}
1111

1212
package client {
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
// NOTE: the companion inherits a public apply method from Function1!
2-
case class NeedsCompanion private (x: Int)
2+
case class NeedsCompanion protected (x: Int)
33

44
object ClashNoSig { // ok
55
private def apply(x: Int) = if (x > 0) new ClashNoSig(x) else ???
66
}
7-
case class ClashNoSig private (x: Int)
7+
case class ClashNoSig protected (x: Int)
88

99

1010
object Clash {
1111
private def apply(x: Int) = if (x > 0) new Clash(x) else ???
1212
}
13-
case class Clash private (x: Int)
13+
case class Clash protected (x: Int)
1414

1515
object ClashSig {
1616
private def apply(x: Int): ClashSig = if (x > 0) new ClashSig(x) else ???
1717
}
18-
case class ClashSig private (x: Int)
18+
case class ClashSig protected (x: Int)
1919

2020
object ClashOverload {
2121
private def apply(x: Int): ClashOverload = if (x > 0) new ClashOverload(x) else apply("")
2222
def apply(x: String): ClashOverload = ???
2323
}
24-
case class ClashOverload private (x: Int)
24+
case class ClashOverload protected (x: Int)
2525

2626
object NoClashSig {
2727
private def apply(x: Boolean): NoClashSig = if (x) NoClashSig(1) else ???
@@ -33,7 +33,7 @@ object NoClashOverload {
3333
private def apply(x: Boolean): NoClashOverload = if (x) NoClashOverload(1) else apply("")
3434
def apply(x: String): NoClashOverload = ???
3535
}
36-
case class NoClashOverload private (x: Int)
36+
case class NoClashOverload protected (x: Int)
3737

3838

3939

@@ -43,12 +43,12 @@ class BaseNCP[T] {
4343
}
4444

4545
object NoClashPoly extends BaseNCP[Boolean]
46-
case class NoClashPoly private(x: Int)
46+
case class NoClashPoly protected(x: Int)
4747

4848

4949
class BaseCP[T] {
5050
// error: overloaded method apply needs result type
5151
def apply(x: T): ClashPoly = if (???) ClashPoly(1) else ???
5252
}
5353
object ClashPoly extends BaseCP[Int]
54-
case class ClashPoly private(x: Int)
54+
case class ClashPoly protected(x: Int)

test/files/run/t9425.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
class C { case class Foo private (x: Int); Foo.apply(0) }
1+
class C { case class Foo protected (x: Int); Foo.apply(0) }
22

33
object Test {
44
def test(c: C) = {import c.Foo; Foo.apply(0)}
55
def main(args: Array[String]): Unit = {
66
test(new C)
7-
}
7+
}
88
}

test/files/run/t9546e.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
case class A private (x: Int)
2-
case class B private (x: Int)(y: Int)
1+
case class A protected (x: Int)
2+
case class B protected (x: Int)(y: Int)
33

44
class C {
55
def f = A(1)

0 commit comments

Comments
 (0)