Skip to content

Commit d652f07

Browse files
committed
fix #12919: use SingletonProxy if case obj has generic companion
1 parent 9ed0762 commit d652f07

File tree

7 files changed

+76
-3
lines changed

7 files changed

+76
-3
lines changed

compiler/src/dotty/tools/dotc/transform/SymUtils.scala

+12
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,18 @@ object SymUtils:
117117
self.isOneOf(FinalOrInline, butNot = Mutable)
118118
&& (!self.is(Method) || self.is(Accessor))
119119

120+
/** This module must use a `Mirror.SingletonProxy` for its mirror if:
121+
* - it is Scala 2 defined
122+
* - the companion class is a generic product or generic sum and would cache
123+
* its mirror in this module.
124+
*/
125+
def requiresSingletonProxyMirror(using Context): Boolean =
126+
self.is(Scala2x) || {
127+
val cls = self.companionClass
128+
cls.isGenericProduct
129+
|| cls.useCompanionAsSumMirror && cls.isGenericSum(self)
130+
}
131+
120132
def useCompanionAsSumMirror(using Context): Boolean =
121133
def companionExtendsSum(using Context): Boolean =
122134
self.linkedClass.isSubClass(defn.Mirror_SumClass)

compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
592592
}
593593

594594
if (clazz.is(Module)) {
595-
if (clazz.is(Case)) makeSingletonMirror()
595+
if (clazz.is(Case) && !clazz.requiresSingletonProxyMirror) makeSingletonMirror()
596596
else if (linked.isGenericProduct) makeProductMirror(linked)
597597
else if (linked.isGenericSum(clazz)) makeSumMirror(linked)
598598
else if (linked.is(Sealed))

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,10 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
282282
if mirroredType.termSymbol.is(CaseVal) then
283283
val module = mirroredType.termSymbol
284284
val modulePath = pathFor(mirroredType).withSpan(span)
285-
if module.info.classSymbol.is(Scala2x) then
285+
val requiresProxy =
286+
val mClass = module.info.classSymbol
287+
mClass.is(Module) && mClass.requiresSingletonProxyMirror
288+
if requiresProxy then
286289
val mirrorType = mirrorCore(defn.Mirror_SingletonProxyClass, mirroredType, mirroredType, module.name, formal)
287290
val mirrorRef = New(defn.Mirror_SingletonProxyClass.typeRef, modulePath :: Nil)
288291
mirrorRef.cast(mirrorType)

library/src/scala/deriving/Mirror.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ object Mirror {
3737
def fromProduct(p: scala.Product): MirroredMonoType = this
3838
}
3939

40-
/** A proxy for Scala 2 singletons, which do not inherit `Singleton` directly */
40+
/** A proxy for Scala 2 singletons (which do not inherit `Singleton` directly),
41+
* or case objects with a case class companion
42+
*/
4143
class SingletonProxy(val value: AnyRef) extends Product {
4244
type MirroredMonoType = value.type
4345
type MirroredType = value.type

tests/run/i12919.scala

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
case class Normal(value: String)
2+
object Normal
3+
4+
case class ClassWithCaseCompanion(value: String)
5+
case object ClassWithCaseCompanion
6+
7+
def instantiate[T](product: Product)(implicit mirror: scala.deriving.Mirror.ProductOf[T]) =
8+
mirror.fromProduct(product)
9+
10+
@main def Test: Unit = {
11+
assert(instantiate[Normal](Tuple1("a")) == Normal("a")) // works as expected
12+
13+
assert(instantiate[ClassWithCaseCompanion.type](EmptyTuple) == ClassWithCaseCompanion) // works as expected
14+
15+
val c = instantiate[ClassWithCaseCompanion](Tuple1("b")) // throws java.lang.ClassCastException: class ClassWithCaseCompanion$ cannot be cast to class ClassWithCaseCompanion
16+
assert(c == ClassWithCaseCompanion("b")) // desired behaviour
17+
18+
val d = instantiate[ClassWithCaseCompanion.type](EmptyTuple)
19+
assert(d == ClassWithCaseCompanion)
20+
}

tests/run/i12919a.scala

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import scala.deriving.Mirror
2+
3+
class Standalone
4+
case object Standalone
5+
6+
case class WithCompanionCaseClass(i: Int)
7+
case object WithCompanionCaseClass
8+
9+
@main def Test: Unit =
10+
11+
val mStandalone = summon[Mirror.Of[Standalone.type]]
12+
assert(mStandalone eq Standalone) // the object is its own mirror
13+
assert(mStandalone.isInstanceOf[Mirror.Singleton]) // object extends Mirror.Singleton because its its own mirror.
14+
15+
val mWithCompanion = summon[Mirror.Of[WithCompanionCaseClass.type]]
16+
assert(mWithCompanion ne WithCompanionCaseClass) // the object is not its own mirror
17+
assert(mWithCompanion.isInstanceOf[Mirror.SingletonProxy]) // its companion is a case class, so the mirror is a proxy
18+
assert(!mWithCompanion.isInstanceOf[Mirror.Singleton]) // it can not extend Mirror.Singleton because its companion is a case class.
19+
assert(WithCompanionCaseClass.isInstanceOf[Mirror.Product]) // its companion is a case class, so the mirror is a product.

tests/run/i12919b.scala

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import scala.deriving.Mirror
2+
3+
4+
sealed trait WithCompanionSealedTrait
5+
case object WithCompanionSealedTrait:
6+
case class FirstChild(x: Int) extends WithCompanionSealedTrait
7+
8+
@main def Test: Unit =
9+
10+
val mWithCompanionSum = summon[Mirror.SumOf[WithCompanionSealedTrait]]
11+
assert(mWithCompanionSum.ordinal(WithCompanionSealedTrait.FirstChild(1)) == 0)
12+
assert(mWithCompanionSum eq WithCompanionSealedTrait) // case object caches sum mirror of its companion
13+
14+
val mWithCompanionSingleton = summon[Mirror.ProductOf[WithCompanionSealedTrait.type]]
15+
assert(mWithCompanionSingleton.fromProduct(EmptyTuple) == WithCompanionSealedTrait)
16+
assert(mWithCompanionSingleton.isInstanceOf[Mirror.SingletonProxy])
17+
assert(mWithCompanionSingleton ne WithCompanionSealedTrait) // proxy mirror is never the object itself

0 commit comments

Comments
 (0)