Skip to content

Commit 688ff73

Browse files
committed
fix #14432: check if scala 2 case class is accessible
1 parent 06a8f22 commit 688ff73

File tree

8 files changed

+93
-19
lines changed

8 files changed

+93
-19
lines changed

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

+36-19
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,21 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
275275
monoMap(mirroredType.resultType)
276276

277277
private def productMirror(mirroredType: Type, formal: Type, span: Span)(using Context): Tree =
278+
279+
/** for a case class, if it is Scala2x then
280+
* check if its constructor can be accessed
281+
* from the calling scope.
282+
*/
283+
def canAccessCtor(cls: Symbol): Boolean =
284+
!genAnonyousMirror(cls) || {
285+
val ctor = cls.primaryConstructor
286+
!ctor.isOneOf(Private | Protected) // we will never generate the mirror inside a Scala 2 class
287+
&& (!ctor.privateWithin.exists || ctx.owner.isContainedIn(ctor.privateWithin)) // check scope is compatible
288+
}
289+
290+
def genAnonyousMirror(cls: Symbol): Boolean =
291+
cls.is(Scala2x) || cls.linkedClass.is(Case)
292+
278293
mirroredType match
279294
case AndType(tp1, tp2) =>
280295
productMirror(tp1, formal, span).orElse(productMirror(tp2, formal, span))
@@ -291,25 +306,27 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
291306
modulePath.cast(mirrorType)
292307
else if mirroredType.classSymbol.isGenericProduct then
293308
val cls = mirroredType.classSymbol
294-
val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal))
295-
val elemLabels = accessors.map(acc => ConstantType(Constant(acc.name.toString)))
296-
val nestedPairs = TypeOps.nestedPairs(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr))
297-
val (monoType, elemsType) = mirroredType match
298-
case mirroredType: HKTypeLambda =>
299-
(mkMirroredMonoType(mirroredType), mirroredType.derivedLambdaType(resType = nestedPairs))
300-
case _ =>
301-
(mirroredType, nestedPairs)
302-
val elemsLabels = TypeOps.nestedPairs(elemLabels)
303-
checkRefinement(formal, tpnme.MirroredElemTypes, elemsType, span)
304-
checkRefinement(formal, tpnme.MirroredElemLabels, elemsLabels, span)
305-
val mirrorType =
306-
mirrorCore(defn.Mirror_ProductClass, monoType, mirroredType, cls.name, formal)
307-
.refinedWith(tpnme.MirroredElemTypes, TypeAlias(elemsType))
308-
.refinedWith(tpnme.MirroredElemLabels, TypeAlias(elemsLabels))
309-
val mirrorRef =
310-
if (cls.is(Scala2x) || cls.linkedClass.is(Case)) anonymousMirror(monoType, ExtendsProductMirror, span)
311-
else companionPath(mirroredType, span)
312-
mirrorRef.cast(mirrorType)
309+
if !canAccessCtor(cls) then EmptyTree
310+
else
311+
val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal))
312+
val elemLabels = accessors.map(acc => ConstantType(Constant(acc.name.toString)))
313+
val nestedPairs = TypeOps.nestedPairs(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr))
314+
val (monoType, elemsType) = mirroredType match
315+
case mirroredType: HKTypeLambda =>
316+
(mkMirroredMonoType(mirroredType), mirroredType.derivedLambdaType(resType = nestedPairs))
317+
case _ =>
318+
(mirroredType, nestedPairs)
319+
val elemsLabels = TypeOps.nestedPairs(elemLabels)
320+
checkRefinement(formal, tpnme.MirroredElemTypes, elemsType, span)
321+
checkRefinement(formal, tpnme.MirroredElemLabels, elemsLabels, span)
322+
val mirrorType =
323+
mirrorCore(defn.Mirror_ProductClass, monoType, mirroredType, cls.name, formal)
324+
.refinedWith(tpnme.MirroredElemTypes, TypeAlias(elemsType))
325+
.refinedWith(tpnme.MirroredElemLabels, TypeAlias(elemsLabels))
326+
val mirrorRef =
327+
if (genAnonyousMirror(cls)) anonymousMirror(monoType, ExtendsProductMirror, span)
328+
else companionPath(mirroredType, span)
329+
mirrorRef.cast(mirrorType)
313330
else EmptyTree
314331
end productMirror
315332

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

0 commit comments

Comments
 (0)