diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 6b930f705809..c7adca68e2af 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -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))) @@ -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 @@ -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 = diff --git a/sbt-test/scala2-compat/i14432/app1fail/Test.scala b/sbt-test/scala2-compat/i14432/app1fail/Test.scala new file mode 100644 index 000000000000..db6cc497515e --- /dev/null +++ b/sbt-test/scala2-compat/i14432/app1fail/Test.scala @@ -0,0 +1,3 @@ +import deriving.Mirror + +val mFoo = summon[Mirror.Of[Foo]] // error: `Foo.(Int)` is not accessible from ``. diff --git a/sbt-test/scala2-compat/i14432/app1ok/Test.scala b/sbt-test/scala2-compat/i14432/app1ok/Test.scala new file mode 100644 index 000000000000..d4eb82b873f5 --- /dev/null +++ b/sbt-test/scala2-compat/i14432/app1ok/Test.scala @@ -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)) diff --git a/sbt-test/scala2-compat/i14432/app2fail/Test.scala b/sbt-test/scala2-compat/i14432/app2fail/Test.scala new file mode 100644 index 000000000000..ec38a4fea0f5 --- /dev/null +++ b/sbt-test/scala2-compat/i14432/app2fail/Test.scala @@ -0,0 +1,5 @@ +package example + +import deriving.Mirror + +val mFoo = summon[Mirror.Of[Foo]] // error: `Foo.(Int)` is not accessible from any class. diff --git a/sbt-test/scala2-compat/i14432/build.sbt b/sbt-test/scala2-compat/i14432/build.sbt new file mode 100644 index 000000000000..2d215ed46a87 --- /dev/null +++ b/sbt-test/scala2-compat/i14432/build.sbt @@ -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 + ) diff --git a/sbt-test/scala2-compat/i14432/lib1/Foo.scala b/sbt-test/scala2-compat/i14432/lib1/Foo.scala new file mode 100644 index 000000000000..c8684bb9b6d8 --- /dev/null +++ b/sbt-test/scala2-compat/i14432/lib1/Foo.scala @@ -0,0 +1,3 @@ +package example + +case class Foo private[example] (i: Int) diff --git a/sbt-test/scala2-compat/i14432/lib2/Foo.scala b/sbt-test/scala2-compat/i14432/lib2/Foo.scala new file mode 100644 index 000000000000..49d55be5a74d --- /dev/null +++ b/sbt-test/scala2-compat/i14432/lib2/Foo.scala @@ -0,0 +1,3 @@ +package example + +case class Foo private (i: Int) diff --git a/sbt-test/scala2-compat/i14432/test b/sbt-test/scala2-compat/i14432/test new file mode 100644 index 000000000000..b6b9329d7502 --- /dev/null +++ b/sbt-test/scala2-compat/i14432/test @@ -0,0 +1,5 @@ +> lib1/compile +> lib2/compile +-> app1fail/compile +> app1ok/run +-> app2fail/compile diff --git a/tests/neg/i14432.check b/tests/neg/i14432.check new file mode 100644 index 000000000000..8fcd27da3879 --- /dev/null +++ b/tests/neg/i14432.check @@ -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 diff --git a/tests/neg/i14432.scala b/tests/neg/i14432.scala new file mode 100644 index 000000000000..dbe8360117b9 --- /dev/null +++ b/tests/neg/i14432.scala @@ -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 diff --git a/tests/neg/i14432a.check b/tests/neg/i14432a.check new file mode 100644 index 000000000000..8874c0f1f4c1 --- /dev/null +++ b/tests/neg/i14432a.check @@ -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 diff --git a/tests/neg/i14432a.scala b/tests/neg/i14432a.scala new file mode 100644 index 000000000000..d29c6775ec33 --- /dev/null +++ b/tests/neg/i14432a.scala @@ -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) diff --git a/tests/neg/i14432b.check b/tests/neg/i14432b.check new file mode 100644 index 000000000000..71a01cf42288 --- /dev/null +++ b/tests/neg/i14432b.check @@ -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 diff --git a/tests/neg/i14432b.scala b/tests/neg/i14432b.scala new file mode 100644 index 000000000000..bf6384a8a69c --- /dev/null +++ b/tests/neg/i14432b.scala @@ -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 +} diff --git a/tests/neg/i14432c.check b/tests/neg/i14432c.check new file mode 100644 index 000000000000..e90574737978 --- /dev/null +++ b/tests/neg/i14432c.check @@ -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 diff --git a/tests/neg/i14432c.scala b/tests/neg/i14432c.scala new file mode 100644 index 000000000000..1f023e53bd32 --- /dev/null +++ b/tests/neg/i14432c.scala @@ -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 + +} diff --git a/tests/neg/i14432d.check b/tests/neg/i14432d.check new file mode 100644 index 000000000000..7367b2a8f5d7 --- /dev/null +++ b/tests/neg/i14432d.check @@ -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 diff --git a/tests/neg/i14432d.scala b/tests/neg/i14432d.scala new file mode 100644 index 000000000000..77b48b82be75 --- /dev/null +++ b/tests/neg/i14432d.scala @@ -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 + } + +} diff --git a/tests/run/i14432.scala b/tests/run/i14432.scala new file mode 100644 index 000000000000..d7108e29fd34 --- /dev/null +++ b/tests/run/i14432.scala @@ -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) diff --git a/tests/run/i14432a.scala b/tests/run/i14432a.scala new file mode 100644 index 000000000000..62ee4285e427 --- /dev/null +++ b/tests/run/i14432a.scala @@ -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) diff --git a/tests/run/i14432b.scala b/tests/run/i14432b.scala new file mode 100644 index 000000000000..9f8f6e51dd26 --- /dev/null +++ b/tests/run/i14432b.scala @@ -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) diff --git a/tests/run/i14432c.scala b/tests/run/i14432c.scala new file mode 100644 index 000000000000..a71321e1769e --- /dev/null +++ b/tests/run/i14432c.scala @@ -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)