Skip to content

Commit d6e17a2

Browse files
committed
fix #14432: check if scala 2 case class is accessible
1 parent d484926 commit d6e17a2

22 files changed

+261
-3
lines changed

compiler/src/dotty/tools/dotc/typer/Synthesizer.scala

+24-3
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,22 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
283283
case OrType(tp1, tp2) => acceptable(tp1, cls) && acceptable(tp2, cls)
284284
case _ => tp.classSymbol eq cls
285285

286+
/** for a case class, if it will have an anonymous mirror,
287+
* check that its constructor can be accessed
288+
* from the calling scope.
289+
*/
290+
def canAccessCtor(cls: Symbol): Boolean =
291+
!genAnonyousMirror(cls) || {
292+
def isAccessible(sym: Symbol): Boolean = ctx.owner.isContainedIn(sym)
293+
def isSub(sym: Symbol): Boolean = ctx.owner.ownersIterator.exists(_.derivesFrom(sym))
294+
val ctor = cls.primaryConstructor
295+
(!ctor.isOneOf(Private | Protected) || isSub(cls)) // we cant access the ctor because we do not extend cls
296+
&& (!ctor.privateWithin.exists || isAccessible(ctor.privateWithin)) // check scope is compatible
297+
}
298+
299+
def genAnonyousMirror(cls: Symbol): Boolean =
300+
cls.is(Scala2x) || cls.linkedClass.is(Case)
301+
286302
def makeProductMirror(cls: Symbol): Tree =
287303
val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal))
288304
val elemLabels = accessors.map(acc => ConstantType(Constant(acc.name.toString)))
@@ -300,7 +316,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
300316
.refinedWith(tpnme.MirroredElemTypes, TypeAlias(elemsType))
301317
.refinedWith(tpnme.MirroredElemLabels, TypeAlias(elemsLabels))
302318
val mirrorRef =
303-
if (cls.is(Scala2x) || cls.linkedClass.is(Case)) anonymousMirror(monoType, ExtendsProductMirror, span)
319+
if (genAnonyousMirror(cls)) anonymousMirror(monoType, ExtendsProductMirror, span)
304320
else companionPath(mirroredType, span)
305321
mirrorRef.cast(mirrorType)
306322
end makeProductMirror
@@ -321,8 +337,13 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
321337
modulePath.cast(mirrorType)
322338
else
323339
val cls = mirroredType.classSymbol
324-
if acceptable(mirroredType, cls) && cls.isGenericProduct then makeProductMirror(cls)
325-
else EmptyTree
340+
if acceptable(mirroredType, cls)
341+
&& cls.isGenericProduct
342+
&& canAccessCtor(cls)
343+
then
344+
makeProductMirror(cls)
345+
else
346+
EmptyTree
326347
end productMirror
327348

328349
private def sumMirror(mirroredType: Type, formal: Type, span: Span)(using Context): Tree =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import deriving.Mirror
2+
3+
val mFoo = summon[Mirror.Of[Foo]] // error: `Foo.<init>(Int)` is not accessible from `<empty>`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import deriving.Mirror
2+
3+
package example {
4+
val mFoo = summon[Mirror.Of[Foo]] // ok, we can access Foo's ctor from here.
5+
}
6+
7+
@main def Test: Unit =
8+
assert(example.mFoo.fromProduct(Some(23)) == example.Foo(23))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package example
2+
3+
import deriving.Mirror
4+
5+
val mFoo = summon[Mirror.Of[Foo]] // error: `Foo.<init>(Int)` is not accessible from any class.
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
val scala3Version = sys.props("plugin.scalaVersion")
2+
val scala2Version = sys.props("plugin.scala2Version")
3+
4+
lazy val lib1 = project.in(file("lib1"))
5+
.settings(
6+
scalaVersion := scala2Version
7+
)
8+
9+
lazy val lib2 = project.in(file("lib2"))
10+
.settings(
11+
scalaVersion := scala2Version
12+
)
13+
14+
lazy val app1fail = project.in(file("app1fail"))
15+
.dependsOn(lib1)
16+
.settings(
17+
scalaVersion := scala3Version
18+
)
19+
20+
lazy val app1ok = project.in(file("app1ok"))
21+
.dependsOn(lib1)
22+
.settings(
23+
scalaVersion := scala3Version
24+
)
25+
26+
lazy val app2fail = project.in(file("app2fail"))
27+
.dependsOn(lib2)
28+
.settings(
29+
scalaVersion := scala3Version
30+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package example
2+
3+
case class Foo private[example] (i: Int)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package example
2+
3+
case class Foo private (i: Int)

sbt-test/scala2-compat/i14432/test

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
> lib1/compile
2+
> lib2/compile
3+
-> app1fail/compile
4+
> app1ok/run
5+
-> app2fail/compile

tests/neg/i14432.check

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Error: tests/neg/i14432.scala:13:33 ---------------------------------------------------------------------------------
2+
13 |val mFoo = summon[Mirror.Of[Foo]] // error: no mirror found
3+
| ^
4+
|no given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef

tests/neg/i14432.scala

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package example
2+
3+
import deriving.Mirror
4+
5+
case class Foo private (i: Int)
6+
7+
// case object companion here prevents Foo from caching
8+
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
9+
case object Foo
10+
11+
// however we can not provide an anonymous mirror
12+
// at this call site because the constructor is not accessible.
13+
val mFoo = summon[Mirror.Of[Foo]] // error: no mirror found

tests/neg/i14432a.check

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Error: tests/neg/i14432a.scala:14:43 --------------------------------------------------------------------------------
2+
14 | val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror found
3+
| ^
4+
|no given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef

tests/neg/i14432a.scala

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import deriving.Mirror
2+
3+
package example {
4+
case class Foo private[example] (val i: Int)
5+
6+
// case object companion here prevents Foo from caching
7+
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
8+
case object Foo
9+
}
10+
11+
@main def Test: Unit =
12+
// however we can not provide an anonymous mirror
13+
// at this call site because the constructor is not accessible.
14+
val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror found
15+
assert(mFoo.fromProduct(Tuple1(1)).i == 1)

tests/neg/i14432b.check

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Error: tests/neg/i14432b.scala:15:43 --------------------------------------------------------------------------------
2+
15 | val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror found
3+
| ^
4+
|no given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef

tests/neg/i14432b.scala

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import deriving.Mirror
2+
3+
package example {
4+
case class Foo protected [example] (val i: Int)
5+
6+
// case object companion here prevents Foo from caching
7+
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
8+
case object Foo
9+
10+
}
11+
12+
class Bar extends example.Foo(23) {
13+
// however we can not provide an anonymous mirror
14+
// at this call site because the constructor is not accessible.
15+
val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror found
16+
}

tests/neg/i14432c.check

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- Error: tests/neg/i14432c.scala:12:18 --------------------------------------------------------------------------------
2+
12 |class Bar extends example.Foo(23) { // error // error: cant access private[example] ctor
3+
| ^^^^^^^^^^^
4+
| constructor Foo cannot be accessed as a member of example.Foo from class Bar.
5+
-- Error: tests/neg/i14432c.scala:12:6 ---------------------------------------------------------------------------------
6+
12 |class Bar extends example.Foo(23) { // error // error: cant access private[example] ctor
7+
| ^
8+
| constructor Foo cannot be accessed as a member of example.Foo from class Bar.
9+
-- Error: tests/neg/i14432c.scala:16:43 --------------------------------------------------------------------------------
10+
16 | val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror
11+
| ^
12+
|no given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef

tests/neg/i14432c.scala

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import deriving.Mirror
2+
3+
package example {
4+
case class Foo private [example] (val i: Int)
5+
6+
// case object companion here prevents Foo from caching
7+
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
8+
case object Foo
9+
10+
}
11+
12+
class Bar extends example.Foo(23) { // error // error: cant access private[example] ctor
13+
14+
// however we can not provide an anonymous mirror
15+
// at this call site because the constructor is not accessible.
16+
val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror
17+
18+
}

tests/neg/i14432d.check

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Error: tests/neg/i14432d.scala:17:45 --------------------------------------------------------------------------------
2+
17 | val mFoo = summon[Mirror.Of[example.Foo]] // error
3+
| ^
4+
|no given instance of type deriving.Mirror.Of[example.Foo] was found for parameter x of method summon in object Predef

tests/neg/i14432d.scala

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import deriving.Mirror
2+
3+
package example {
4+
case class Foo protected [example] (val i: Int)
5+
6+
// case object companion here prevents Foo from caching
7+
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
8+
case object Foo
9+
10+
}
11+
12+
class Bar extends example.Foo(23) {
13+
14+
class Inner {
15+
// however we can not provide an anonymous mirror
16+
// at this call site because the constructor is not accessible.
17+
val mFoo = summon[Mirror.Of[example.Foo]] // error
18+
}
19+
20+
}

tests/run/i14432.scala

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import deriving.Mirror
2+
3+
package example {
4+
// Foo caches the mirror in its companion,
5+
// which can still access the constructor.
6+
case class Foo private (val i: Int)
7+
}
8+
9+
@main def Test: Unit =
10+
val mFoo = summon[Mirror.Of[example.Foo]]
11+
assert(mFoo.fromProduct(Tuple1(1)).i == 1)

tests/run/i14432a.scala

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import deriving.Mirror
2+
3+
package example {
4+
case class Foo private[example] (val i: Int)
5+
6+
// case object companion here prevents Foo from caching
7+
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
8+
case object Foo
9+
10+
// here, we can synthesize an anonymous mirror
11+
// because at this call site the constructor is accessible.
12+
val mFoo = summon[Mirror.Of[example.Foo]]
13+
}
14+
15+
@main def Test: Unit =
16+
assert(example.mFoo.fromProduct(Tuple1(1)).i == 1)

tests/run/i14432b.scala

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import deriving.Mirror
2+
3+
package example {
4+
case class Foo protected [example] (val i: Int)
5+
6+
// case object companion here prevents Foo from caching
7+
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
8+
case object Foo
9+
10+
class Bar extends Foo(23) {
11+
// here, we can synthesize an anonymous mirror
12+
// because at this call site the constructor is accessible.
13+
val mFoo = summon[Mirror.Of[example.Foo]]
14+
}
15+
16+
}
17+
18+
@main def Test: Unit =
19+
val bar = new example.Bar
20+
assert(bar.mFoo.fromProduct(Tuple1(1)).i == 1)

tests/run/i14432c.scala

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import deriving.Mirror
2+
3+
package example {
4+
case class Foo protected [example] (val i: Int)
5+
6+
// case object companion here prevents Foo from caching
7+
// the mirror in its companion, so all potential mirrors for Foo will be anonymous.
8+
case object Foo
9+
10+
class Bar extends Foo(23) {
11+
class Inner {
12+
// here, we can synthesize an anonymous mirror
13+
// because at this call site the constructor is accessible.
14+
val mFoo = summon[Mirror.Of[example.Foo]]
15+
}
16+
val inner = Inner()
17+
}
18+
19+
}
20+
21+
@main def Test: Unit =
22+
val bar = new example.Bar
23+
assert(bar.inner.mFoo.fromProduct(Tuple1(1)).i == 1)

0 commit comments

Comments
 (0)