Skip to content

Commit d6774a8

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

File tree

9 files changed

+218
-16
lines changed

9 files changed

+218
-16
lines changed

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

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,17 @@ trait Unapplies extends ast.TreeDSL {
9797
}
9898

9999

100+
private def applyShouldInheritAccess(mods: Modifiers) =
101+
currentRun.isScala214 && (mods.hasFlag(PRIVATE) || (!mods.hasFlag(PROTECTED) && mods.hasAccessBoundary))
102+
100103
/** The module corresponding to a case class; overrides toString to show the module's name
101104
*/
102105
def caseModuleDef(cdef: ClassDef): ModuleDef = {
103106
val params = constrParamss(cdef)
104107
def inheritFromFun = !cdef.mods.hasAbstractFlag && cdef.tparams.isEmpty && (params match {
105108
case List(ps) if ps.length <= MaxFunctionArity => true
106109
case _ => false
107-
})
110+
}) && !applyShouldInheritAccess(constrMods(cdef))
108111
def createFun = {
109112
def primaries = params.head map (_.tpt)
110113
gen.scalaFunctionConstr(primaries, toIdent(cdef), abstractFun = true)
@@ -142,9 +145,20 @@ trait Unapplies extends ast.TreeDSL {
142145
)
143146
}
144147

148+
149+
private def constrMods(cdef: ClassDef): Modifiers = treeInfo.firstConstructorMods(cdef.impl.body)
150+
145151
/** The apply method corresponding to a case class
146152
*/
147-
def caseModuleApplyMeth(cdef: ClassDef): DefDef = factoryMeth(caseMods, nme.apply, cdef)
153+
def caseModuleApplyMeth(cdef: ClassDef): DefDef = {
154+
val inheritedMods = constrMods(cdef)
155+
val mods =
156+
if (applyShouldInheritAccess(inheritedMods))
157+
(caseMods | (inheritedMods.flags & PRIVATE)).copy(privateWithin = inheritedMods.privateWithin)
158+
else
159+
caseMods
160+
factoryMeth(mods, nme.apply, cdef)
161+
}
148162

149163
/** The unapply method corresponding to a case class
150164
*/
@@ -257,8 +271,14 @@ trait Unapplies extends ast.TreeDSL {
257271
val classTpe = classType(cdef, tparams)
258272
val argss = mmap(paramss)(toIdent)
259273
val body: Tree = New(classTpe, argss)
274+
val copyMods =
275+
if (currentRun.isScala214) {
276+
val inheritedMods = constrMods(cdef)
277+
Modifiers(SYNTHETIC | (inheritedMods.flags & AccessFlags), inheritedMods.privateWithin)
278+
}
279+
else Modifiers(SYNTHETIC)
260280
val copyDefDef = atPos(cdef.pos.focus)(
261-
DefDef(Modifiers(SYNTHETIC), nme.copy, tparams, paramss, TypeTree(), body)
281+
DefDef(copyMods, nme.copy, tparams, paramss, TypeTree(), body)
262282
)
263283
Some(copyDefDef)
264284
}

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

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

534+
/** The modifiers of the first constructor in `stats`. */
535+
def firstConstructorMods(stats: List[Tree]): Modifiers = firstConstructor(stats) match {
536+
case DefDef(mods, _, _, _, _, _) => mods
537+
case _ => Modifiers()
538+
}
539+
534540
/** The value definitions marked PRESUPER in this statement sequence */
535541
def preSuperFields(stats: List[Tree]): List[ValDef] =
536542
stats collect { case vd: ValDef if isEarlyValDef(vd) => vd }
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
caseclass_private_constructor.scala:6: 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:7: 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:12: 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:13: 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:24: 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:25: 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:27: 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:28: 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:34: 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:43: 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+
caseclass_private_constructor.scala:57: error: method copy in class OverrideApply cannot be accessed in OverrideApply
46+
def oa = OverrideApply(42).copy(24) // error: copy is still private
47+
^
48+
caseclass_private_constructor.scala:58: error: method apply in object OverrideCopy cannot be accessed in object OverrideCopy
49+
error after rewriting to OverrideCopy.<apply: error>
50+
possible cause: maybe a wrong Dynamic method signature?
51+
def oc = OverrideCopy(42) // error: apply is still private
52+
^
53+
12 errors found
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// scalac: -Xsource:2.14
2+
3+
case class A private (i: Int)
4+
object A
5+
object ATest {
6+
def a1: A = A(1) // error: apply is private
7+
def a2: A = a1.copy(2) // error: copy is private
8+
}
9+
10+
case class B private (i: Int) // no user-defined companion object, should compile
11+
object BTest {
12+
def b1: B = B(1) // error: apply is private
13+
def b2: B = b1.copy(2) // error: copy is private
14+
}
15+
16+
object qualified_private {
17+
case class C private[qualified_private] (i: Int)
18+
object C
19+
20+
case class D private[qualified_private] (i: Int) // no user-defined companion object, should compile
21+
}
22+
object QPrivTest {
23+
import qualified_private._
24+
def c1: C = C(1) // error: apply is private
25+
def c2: C = c1.copy(2) // error: copy is private
26+
27+
def d1: D = D(1) // error: apply is private
28+
def d2: D = d1.copy(2) // error: copy is private
29+
}
30+
31+
case class E protected (i: Int)
32+
object ETest {
33+
def e1: E = E(1)
34+
def e2: E = e2.copy(2) // error: copy is protected
35+
}
36+
37+
object qualified_protected {
38+
case class F protected[qualified_protected] (i: Int)
39+
}
40+
object QProtTest {
41+
import qualified_protected._
42+
def f1: F = F(1)
43+
def f2: F = f2.copy(2) // error: copy is protected
44+
}
45+
46+
47+
case class OverrideApply private (i: Int)
48+
object OverrideApply {
49+
def apply(i: Int): OverrideApply = new OverrideApply(i)
50+
}
51+
52+
case class OverrideCopy private (i: Int) {
53+
def copy(i: Int = i): OverrideCopy = OverrideCopy(i)
54+
}
55+
56+
object OverrideTest {
57+
def oa = OverrideApply(42).copy(24) // error: copy is still private
58+
def oc = OverrideCopy(42) // error: apply is still private
59+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// scalac: -Xsource:2.14
2+
3+
case class A private (i: Int)
4+
object A {
5+
def a = A(1).copy(2) // apply and copy are accessible in companion
6+
}
7+
8+
case class B private (i: Int) { // no user-defined companion object, should compile
9+
def b = B(1).copy(2) // apply and copy are accessible
10+
}
11+
12+
object qualified_private {
13+
case class A private[qualified_private] (i: Int)
14+
object A {
15+
def a = A(1).copy(2) // apply and copy are accessible in companion
16+
}
17+
18+
def a = A(1).copy(2) // apply and copy are accessible in qualified_private object
19+
20+
case class B private[qualified_private] (i: Int) { // no user-defined companion object, should compile
21+
def b = B(1).copy(2) // apply and copy are accessible
22+
}
23+
24+
def b = B(1).copy(2) // apply and copy are accessible in qualified_private object
25+
}
26+
27+
case class C protected (i: Int)
28+
class CSub extends C(1) {
29+
def c = copy(2) // copy is accessible in subclass
30+
}
31+
object CTest {
32+
def c = C(1) // apply is public
33+
}
34+
35+
object qualified_protected {
36+
case class C protected[qualified_protected] (i: Int)
37+
class CSub extends C(1) {
38+
def c = copy(2) // copy is accessible in subclass
39+
}
40+
object CTest {
41+
def c = C(1) // apply is public
42+
def checkExtendsFunction: Int => C = C // companion extends (Int => C)
43+
}
44+
45+
def c = C(1).copy(2)
46+
}
47+
object CQualifiedTest {
48+
def c = qualified_protected.C(1) // apply is public
49+
}
50+
51+
52+
case class OverrideApply private (i: Int)
53+
object OverrideApply {
54+
def apply(i: Int): OverrideApply = new OverrideApply(i)
55+
}
56+
57+
case class OverrideCopy private (i: Int) {
58+
def copy(i: Int = i): OverrideCopy = OverrideCopy(i)
59+
}
60+
61+
object OverrideTest {
62+
def oa = OverrideApply(42) // overridden apply is public
63+
def oc(o: OverrideCopy) = o.copy(42) // overridden copy is public
64+
}

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)