Skip to content

Commit

Permalink
Case class copy and apply inherit access modifiers from constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
Jasper-M committed Feb 7, 2019
1 parent e40c95e commit 9b3a2cb
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 16 deletions.
20 changes: 17 additions & 3 deletions src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
Expand Up @@ -101,14 +101,16 @@ trait Unapplies extends ast.TreeDSL {
}
}

private def applyShouldInheritAccess(mods: Modifiers) = mods.hasFlag(PRIVATE) || (!mods.hasFlag(PROTECTED) && mods.hasAccessBoundary)

/** The module corresponding to a case class; overrides toString to show the module's name
*/
def caseModuleDef(cdef: ClassDef): ModuleDef = {
val params = constrParamss(cdef)
def inheritFromFun = !cdef.mods.hasAbstractFlag && cdef.tparams.isEmpty && (params match {
case List(ps) if ps.length <= MaxFunctionArity => true
case _ => false
})
}) && !applyShouldInheritAccess(constrMods(cdef))
def createFun = {
def primaries = params.head map (_.tpt)
gen.scalaFunctionConstr(primaries, toIdent(cdef), abstractFun = true)
Expand Down Expand Up @@ -146,9 +148,20 @@ trait Unapplies extends ast.TreeDSL {
)
}


private def constrMods(cdef: ClassDef): Modifiers = treeInfo.firstConstructorMods(cdef.impl.body)

/** The apply method corresponding to a case class
*/
def caseModuleApplyMeth(cdef: ClassDef): DefDef = factoryMeth(caseMods, nme.apply, cdef)
def caseModuleApplyMeth(cdef: ClassDef): DefDef = {
val inheritedMods = constrMods(cdef)
val mods =
if (applyShouldInheritAccess(inheritedMods))
(caseMods | (inheritedMods.flags & PRIVATE)).copy(privateWithin = inheritedMods.privateWithin)
else
caseMods
factoryMeth(mods, nme.apply, cdef)
}

/** The unapply method corresponding to a case class
*/
Expand Down Expand Up @@ -231,8 +244,9 @@ trait Unapplies extends ast.TreeDSL {
val classTpe = classType(cdef, tparams)
val argss = mmap(paramss)(toIdent)
val body: Tree = New(classTpe, argss)
val inheritedMods = constrMods(cdef)
val copyDefDef = atPos(cdef.pos.focus)(
DefDef(Modifiers(SYNTHETIC), nme.copy, tparams, paramss, TypeTree(), body)
DefDef(Modifiers(SYNTHETIC | (inheritedMods.flags & AccessFlags), inheritedMods.privateWithin), nme.copy, tparams, paramss, TypeTree(), body)
)
Some(copyDefDef)
}
Expand Down
6 changes: 6 additions & 0 deletions src/reflect/scala/reflect/internal/TreeInfo.scala
Expand Up @@ -529,6 +529,12 @@ abstract class TreeInfo {
case _ => Nil
}

/** The modifiers of the first constructor in `stats`. */
def firstConstructorMods(stats: List[Tree]): Modifiers = firstConstructor(stats) match {
case DefDef(mods, _, _, _, _, _) => mods
case _ => Modifiers()
}

/** The value definitions marked PRESUPER in this statement sequence */
def preSuperFields(stats: List[Tree]): List[ValDef] =
stats collect { case vd: ValDef if isEarlyValDef(vd) => vd }
Expand Down
45 changes: 45 additions & 0 deletions test/files/neg/caseclass_private_constructor.check
@@ -0,0 +1,45 @@
caseclass_private_constructor.scala:4: error: method apply in object A cannot be accessed in object A
error after rewriting to A.<apply: error>
possible cause: maybe a wrong Dynamic method signature?
def a1: A = A(1) // error: apply is private
^
caseclass_private_constructor.scala:5: error: method copy in class A cannot be accessed in A
def a2: A = a1.copy(2) // error: copy is private
^
caseclass_private_constructor.scala:10: error: method apply in object B cannot be accessed in object B
error after rewriting to B.<apply: error>
possible cause: maybe a wrong Dynamic method signature?
def b1: B = B(1) // error: apply is private
^
caseclass_private_constructor.scala:11: error: method copy in class B cannot be accessed in B
def b2: B = b1.copy(2) // error: copy is private
^
caseclass_private_constructor.scala:22: error: method apply in object C cannot be accessed in object qualified_private.C
error after rewriting to qualified_private.C.<apply: error>
possible cause: maybe a wrong Dynamic method signature?
def c1: C = C(1) // error: apply is private
^
caseclass_private_constructor.scala:23: error: method copy in class C cannot be accessed in qualified_private.C
def c2: C = c1.copy(2) // error: copy is private
^
caseclass_private_constructor.scala:25: error: method apply in object D cannot be accessed in object qualified_private.D
error after rewriting to qualified_private.D.<apply: error>
possible cause: maybe a wrong Dynamic method signature?
def d1: D = D(1) // error: apply is private
^
caseclass_private_constructor.scala:26: error: method copy in class D cannot be accessed in qualified_private.D
def d2: D = d1.copy(2) // error: copy is private
^
caseclass_private_constructor.scala:32: error: method copy in class E cannot be accessed in E
Access to protected method copy not permitted because
enclosing object ETest is not a subclass of
class E where target is defined
def e2: E = e2.copy(2) // error: copy is protected
^
caseclass_private_constructor.scala:41: error: method copy in class F cannot be accessed in qualified_protected.F
Access to protected method copy not permitted because
enclosing object QProtTest is not a subclass of
class F in object qualified_protected where target is defined
def f2: F = f2.copy(2) // error: copy is protected
^
10 errors found
42 changes: 42 additions & 0 deletions test/files/neg/caseclass_private_constructor.scala
@@ -0,0 +1,42 @@
case class A private (i: Int)
object A
object ATest {
def a1: A = A(1) // error: apply is private
def a2: A = a1.copy(2) // error: copy is private
}

case class B private (i: Int) // no user-defined companion object, should compile
object BTest {
def b1: B = B(1) // error: apply is private
def b2: B = b1.copy(2) // error: copy is private
}

object qualified_private {
case class C private[qualified_private] (i: Int)
object C

case class D private[qualified_private] (i: Int) // no user-defined companion object, should compile
}
object QPrivTest {
import qualified_private._
def c1: C = C(1) // error: apply is private
def c2: C = c1.copy(2) // error: copy is private

def d1: D = D(1) // error: apply is private
def d2: D = d1.copy(2) // error: copy is private
}

case class E protected (i: Int)
object ETest {
def e1: E = E(1)
def e2: E = e2.copy(2) // error: copy is protected
}

object qualified_protected {
case class F protected[qualified_protected] (i: Int)
}
object QProtTest {
import qualified_protected._
def f1: F = F(1)
def f2: F = f2.copy(2) // error: copy is protected
}
47 changes: 47 additions & 0 deletions test/files/pos/caseclass_private_constructor.scala
@@ -0,0 +1,47 @@
case class A private (i: Int)
object A {
def a = A(1).copy(2) // apply and copy are accessible in companion
}

case class B private (i: Int) { // no user-defined companion object, should compile
def b = B(1).copy(2) // apply and copy are accessible
}

object qualified_private {
case class A private[qualified_private] (i: Int)
object A {
def a = A(1).copy(2) // apply and copy are accessible in companion
}

def a = A(1).copy(2) // apply and copy are accessible in qualified_private object

case class B private[qualified_private] (i: Int) { // no user-defined companion object, should compile
def b = B(1).copy(2) // apply and copy are accessible
}

def b = B(1).copy(2) // apply and copy are accessible in qualified_private object
}

case class C protected (i: Int)
class CSub extends C(1) {
def c = copy(2) // copy is accessible in subclass
}
object CTest {
def c = C(1) // apply is public
}

object qualified_protected {
case class C protected[qualified_protected] (i: Int)
class CSub extends C(1) {
def c = copy(2) // copy is accessible in subclass
}
object CTest {
def c = C(1) // apply is public
def checkExtendsFunction: Int => C = C // companion extends (Int => C)
}

def c = C(1).copy(2)
}
object CQualifiedTest {
def c = qualified_protected.C(1) // apply is public
}
2 changes: 1 addition & 1 deletion test/files/pos/t6734.scala
Expand Up @@ -6,7 +6,7 @@ package object p

package p {
import scala.concurrent.Future
case class C private[p] (value: Future[Int]) // private to avoid rewriting C.apply to new C
case class C protected[p] (value: Future[Int]) // protected to avoid rewriting C.apply to new C
}

package client {
Expand Down
16 changes: 8 additions & 8 deletions test/files/pos/userdefined_apply.scala
@@ -1,27 +1,27 @@
// NOTE: the companion inherits a public apply method from Function1!
case class NeedsCompanion private (x: Int)
case class NeedsCompanion protected (x: Int)

object ClashNoSig { // ok
private def apply(x: Int) = if (x > 0) new ClashNoSig(x) else ???
}
case class ClashNoSig private (x: Int)
case class ClashNoSig protected (x: Int)


object Clash {
private def apply(x: Int) = if (x > 0) new Clash(x) else ???
}
case class Clash private (x: Int)
case class Clash protected (x: Int)

object ClashSig {
private def apply(x: Int): ClashSig = if (x > 0) new ClashSig(x) else ???
}
case class ClashSig private (x: Int)
case class ClashSig protected (x: Int)

object ClashOverload {
private def apply(x: Int): ClashOverload = if (x > 0) new ClashOverload(x) else apply("")
def apply(x: String): ClashOverload = ???
}
case class ClashOverload private (x: Int)
case class ClashOverload protected (x: Int)

object NoClashSig {
private def apply(x: Boolean): NoClashSig = if (x) NoClashSig(1) else ???
Expand All @@ -33,7 +33,7 @@ object NoClashOverload {
private def apply(x: Boolean): NoClashOverload = if (x) NoClashOverload(1) else apply("")
def apply(x: String): NoClashOverload = ???
}
case class NoClashOverload private (x: Int)
case class NoClashOverload protected (x: Int)



Expand All @@ -43,12 +43,12 @@ class BaseNCP[T] {
}

object NoClashPoly extends BaseNCP[Boolean]
case class NoClashPoly private(x: Int)
case class NoClashPoly protected(x: Int)


class BaseCP[T] {
// error: overloaded method apply needs result type
def apply(x: T): ClashPoly = if (???) ClashPoly(1) else ???
}
object ClashPoly extends BaseCP[Int]
case class ClashPoly private(x: Int)
case class ClashPoly protected(x: Int)
4 changes: 2 additions & 2 deletions test/files/run/t9425.scala
@@ -1,8 +1,8 @@
class C { case class Foo private (x: Int); Foo.apply(0) }
class C { case class Foo protected (x: Int); Foo.apply(0) }

object Test {
def test(c: C) = {import c.Foo; Foo.apply(0)}
def main(args: Array[String]): Unit = {
test(new C)
}
}
}
4 changes: 2 additions & 2 deletions test/files/run/t9546e.scala
@@ -1,5 +1,5 @@
case class A private (x: Int)
case class B private (x: Int)(y: Int)
case class A protected (x: Int)
case class B protected (x: Int)(y: Int)

class C {
def f = A(1)
Expand Down

0 comments on commit 9b3a2cb

Please sign in to comment.