Skip to content

Commit

Permalink
fix scala#14432: check if scala 2 case class is accessible
Browse files Browse the repository at this point in the history
  • Loading branch information
bishabosha committed Oct 18, 2022
1 parent f1eed51 commit f2090c2
Show file tree
Hide file tree
Showing 22 changed files with 261 additions and 3 deletions.
27 changes: 24 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,22 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
case OrType(tp1, tp2) => acceptable(tp1, cls) && acceptable(tp2, cls)
case _ => tp.classSymbol eq cls

/** for a case class, if it will have an anonymous mirror,
* check that its constructor can be accessed
* from the calling scope.
*/
def canAccessCtor(cls: Symbol): Boolean =
!genAnonyousMirror(cls) || {
def isAccessible(sym: Symbol): Boolean = ctx.owner.isContainedIn(sym)
def isSub(sym: Symbol): Boolean = ctx.owner.ownersIterator.exists(_.derivesFrom(sym))
val ctor = cls.primaryConstructor
(!ctor.isOneOf(Private | Protected) || isSub(cls)) // we cant access the ctor because we do not extend cls
&& (!ctor.privateWithin.exists || isAccessible(ctor.privateWithin)) // check scope is compatible
}

def genAnonyousMirror(cls: Symbol): Boolean =
cls.is(Scala2x) || cls.linkedClass.is(Case)

def makeProductMirror(cls: Symbol): Tree =
val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal))
val elemLabels = accessors.map(acc => ConstantType(Constant(acc.name.toString)))
Expand All @@ -300,7 +316,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
.refinedWith(tpnme.MirroredElemTypes, TypeAlias(elemsType))
.refinedWith(tpnme.MirroredElemLabels, TypeAlias(elemsLabels))
val mirrorRef =
if (cls.is(Scala2x) || cls.linkedClass.is(Case)) anonymousMirror(monoType, ExtendsProductMirror, span)
if (genAnonyousMirror(cls)) anonymousMirror(monoType, ExtendsProductMirror, span)
else companionPath(mirroredType, span)
mirrorRef.cast(mirrorType)
end makeProductMirror
Expand All @@ -321,8 +337,13 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
modulePath.cast(mirrorType)
else
val cls = mirroredType.classSymbol
if acceptable(mirroredType, cls) && cls.isGenericProduct then makeProductMirror(cls)
else EmptyTree
if acceptable(mirroredType, cls)
&& cls.isGenericProduct
&& canAccessCtor(cls)
then
makeProductMirror(cls)
else
EmptyTree
end productMirror

private def sumMirror(mirroredType: Type, formal: Type, span: Span)(using Context): Tree =
Expand Down
3 changes: 3 additions & 0 deletions sbt-test/scala2-compat/i14432/app1fail/Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import deriving.Mirror

val mFoo = summon[Mirror.Of[Foo]] // error: `Foo.<init>(Int)` is not accessible from `<empty>`.
8 changes: 8 additions & 0 deletions sbt-test/scala2-compat/i14432/app1ok/Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import deriving.Mirror

package example {
val mFoo = summon[Mirror.Of[Foo]] // ok, we can access Foo's ctor from here.
}

@main def Test: Unit =
assert(example.mFoo.fromProduct(Some(23)) == example.Foo(23))
5 changes: 5 additions & 0 deletions sbt-test/scala2-compat/i14432/app2fail/Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package example

import deriving.Mirror

val mFoo = summon[Mirror.Of[Foo]] // error: `Foo.<init>(Int)` is not accessible from any class.
30 changes: 30 additions & 0 deletions sbt-test/scala2-compat/i14432/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
val scala3Version = sys.props("plugin.scalaVersion")
val scala2Version = sys.props("plugin.scala2Version")

lazy val lib1 = project.in(file("lib1"))
.settings(
scalaVersion := scala2Version
)

lazy val lib2 = project.in(file("lib2"))
.settings(
scalaVersion := scala2Version
)

lazy val app1fail = project.in(file("app1fail"))
.dependsOn(lib1)
.settings(
scalaVersion := scala3Version
)

lazy val app1ok = project.in(file("app1ok"))
.dependsOn(lib1)
.settings(
scalaVersion := scala3Version
)

lazy val app2fail = project.in(file("app2fail"))
.dependsOn(lib2)
.settings(
scalaVersion := scala3Version
)
3 changes: 3 additions & 0 deletions sbt-test/scala2-compat/i14432/lib1/Foo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package example

case class Foo private[example] (i: Int)
3 changes: 3 additions & 0 deletions sbt-test/scala2-compat/i14432/lib2/Foo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package example

case class Foo private (i: Int)
5 changes: 5 additions & 0 deletions sbt-test/scala2-compat/i14432/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
> lib1/compile
> lib2/compile
-> app1fail/compile
> app1ok/run
-> app2fail/compile
4 changes: 4 additions & 0 deletions tests/neg/i14432.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Error: tests/neg/i14432.scala:13:33 ---------------------------------------------------------------------------------
13 |val mFoo = summon[Mirror.Of[Foo]] // error: no mirror found
| ^
|no given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef
13 changes: 13 additions & 0 deletions tests/neg/i14432.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package example

import deriving.Mirror

case class Foo private (i: Int)

// case object companion here prevents Foo from caching
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
case object Foo

// however we can not provide an anonymous mirror
// at this call site because the constructor is not accessible.
val mFoo = summon[Mirror.Of[Foo]] // error: no mirror found
4 changes: 4 additions & 0 deletions tests/neg/i14432a.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Error: tests/neg/i14432a.scala:14:43 --------------------------------------------------------------------------------
14 | val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror found
| ^
|no given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef
15 changes: 15 additions & 0 deletions tests/neg/i14432a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import deriving.Mirror

package example {
case class Foo private[example] (val i: Int)

// case object companion here prevents Foo from caching
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
case object Foo
}

@main def Test: Unit =
// however we can not provide an anonymous mirror
// at this call site because the constructor is not accessible.
val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror found
assert(mFoo.fromProduct(Tuple1(1)).i == 1)
4 changes: 4 additions & 0 deletions tests/neg/i14432b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Error: tests/neg/i14432b.scala:15:43 --------------------------------------------------------------------------------
15 | val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror found
| ^
|no given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef
16 changes: 16 additions & 0 deletions tests/neg/i14432b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import deriving.Mirror

package example {
case class Foo protected [example] (val i: Int)

// case object companion here prevents Foo from caching
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
case object Foo

}

class Bar extends example.Foo(23) {
// however we can not provide an anonymous mirror
// at this call site because the constructor is not accessible.
val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror found
}
12 changes: 12 additions & 0 deletions tests/neg/i14432c.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- Error: tests/neg/i14432c.scala:12:18 --------------------------------------------------------------------------------
12 |class Bar extends example.Foo(23) { // error // error: cant access private[example] ctor
| ^^^^^^^^^^^
| constructor Foo cannot be accessed as a member of example.Foo from class Bar.
-- Error: tests/neg/i14432c.scala:12:6 ---------------------------------------------------------------------------------
12 |class Bar extends example.Foo(23) { // error // error: cant access private[example] ctor
| ^
| constructor Foo cannot be accessed as a member of example.Foo from class Bar.
-- Error: tests/neg/i14432c.scala:16:43 --------------------------------------------------------------------------------
16 | val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror
| ^
|no given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef
18 changes: 18 additions & 0 deletions tests/neg/i14432c.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import deriving.Mirror

package example {
case class Foo private [example] (val i: Int)

// case object companion here prevents Foo from caching
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
case object Foo

}

class Bar extends example.Foo(23) { // error // error: cant access private[example] ctor

// however we can not provide an anonymous mirror
// at this call site because the constructor is not accessible.
val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror

}
4 changes: 4 additions & 0 deletions tests/neg/i14432d.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Error: tests/neg/i14432d.scala:17:45 --------------------------------------------------------------------------------
17 | val mFoo = summon[Mirror.Of[example.Foo]] // error
| ^
|no given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef
20 changes: 20 additions & 0 deletions tests/neg/i14432d.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import deriving.Mirror

package example {
case class Foo protected [example] (val i: Int)

// case object companion here prevents Foo from caching
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
case object Foo

}

class Bar extends example.Foo(23) {

class Inner {
// however we can not provide an anonymous mirror
// at this call site because the constructor is not accessible.
val mFoo = summon[Mirror.Of[example.Foo]] // error
}

}
11 changes: 11 additions & 0 deletions tests/run/i14432.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import deriving.Mirror

package example {
// Foo caches the mirror in its companion,
// which can still access the constructor.
case class Foo private (val i: Int)
}

@main def Test: Unit =
val mFoo = summon[Mirror.Of[example.Foo]]
assert(mFoo.fromProduct(Tuple1(1)).i == 1)
16 changes: 16 additions & 0 deletions tests/run/i14432a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import deriving.Mirror

package example {
case class Foo private[example] (val i: Int)

// case object companion here prevents Foo from caching
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
case object Foo

// here, we can synthesize an anonymous mirror
// because at this call site the constructor is accessible.
val mFoo = summon[Mirror.Of[example.Foo]]
}

@main def Test: Unit =
assert(example.mFoo.fromProduct(Tuple1(1)).i == 1)
20 changes: 20 additions & 0 deletions tests/run/i14432b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import deriving.Mirror

package example {
case class Foo protected [example] (val i: Int)

// case object companion here prevents Foo from caching
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
case object Foo

class Bar extends Foo(23) {
// here, we can synthesize an anonymous mirror
// because at this call site the constructor is accessible.
val mFoo = summon[Mirror.Of[example.Foo]]
}

}

@main def Test: Unit =
val bar = new example.Bar
assert(bar.mFoo.fromProduct(Tuple1(1)).i == 1)
23 changes: 23 additions & 0 deletions tests/run/i14432c.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import deriving.Mirror

package example {
case class Foo protected [example] (val i: Int)

// case object companion here prevents Foo from caching
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
case object Foo

class Bar extends Foo(23) {
class Inner {
// here, we can synthesize an anonymous mirror
// because at this call site the constructor is accessible.
val mFoo = summon[Mirror.Of[example.Foo]]
}
val inner = Inner()
}

}

@main def Test: Unit =
val bar = new example.Bar
assert(bar.inner.mFoo.fromProduct(Tuple1(1)).i == 1)

0 comments on commit f2090c2

Please sign in to comment.