From d652f071f345f9a50c8e4adbdec394605716d6ad Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 20 Apr 2022 18:32:38 +0200 Subject: [PATCH 1/3] fix #12919: use SingletonProxy if case obj has generic companion --- .../dotty/tools/dotc/transform/SymUtils.scala | 12 +++++++++++ .../dotc/transform/SyntheticMembers.scala | 2 +- .../dotty/tools/dotc/typer/Synthesizer.scala | 5 ++++- library/src/scala/deriving/Mirror.scala | 4 +++- tests/run/i12919.scala | 20 +++++++++++++++++++ tests/run/i12919a.scala | 19 ++++++++++++++++++ tests/run/i12919b.scala | 17 ++++++++++++++++ 7 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 tests/run/i12919.scala create mode 100644 tests/run/i12919a.scala create mode 100644 tests/run/i12919b.scala diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index 7694a7cc240a..aededd7a5f9e 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -117,6 +117,18 @@ object SymUtils: self.isOneOf(FinalOrInline, butNot = Mutable) && (!self.is(Method) || self.is(Accessor)) + /** This module must use a `Mirror.SingletonProxy` for its mirror if: + * - it is Scala 2 defined + * - the companion class is a generic product or generic sum and would cache + * its mirror in this module. + */ + def requiresSingletonProxyMirror(using Context): Boolean = + self.is(Scala2x) || { + val cls = self.companionClass + cls.isGenericProduct + || cls.useCompanionAsSumMirror && cls.isGenericSum(self) + } + def useCompanionAsSumMirror(using Context): Boolean = def companionExtendsSum(using Context): Boolean = self.linkedClass.isSubClass(defn.Mirror_SumClass) diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 32256e43d242..d12280d0e7ce 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -592,7 +592,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { } if (clazz.is(Module)) { - if (clazz.is(Case)) makeSingletonMirror() + if (clazz.is(Case) && !clazz.requiresSingletonProxyMirror) makeSingletonMirror() else if (linked.isGenericProduct) makeProductMirror(linked) else if (linked.isGenericSum(clazz)) makeSumMirror(linked) else if (linked.is(Sealed)) diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 73fc926b8893..66b8a69f514e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -282,7 +282,10 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): if mirroredType.termSymbol.is(CaseVal) then val module = mirroredType.termSymbol val modulePath = pathFor(mirroredType).withSpan(span) - if module.info.classSymbol.is(Scala2x) then + val requiresProxy = + val mClass = module.info.classSymbol + mClass.is(Module) && mClass.requiresSingletonProxyMirror + if requiresProxy then val mirrorType = mirrorCore(defn.Mirror_SingletonProxyClass, mirroredType, mirroredType, module.name, formal) val mirrorRef = New(defn.Mirror_SingletonProxyClass.typeRef, modulePath :: Nil) mirrorRef.cast(mirrorType) diff --git a/library/src/scala/deriving/Mirror.scala b/library/src/scala/deriving/Mirror.scala index d091f4ee559e..90be5969ce5f 100644 --- a/library/src/scala/deriving/Mirror.scala +++ b/library/src/scala/deriving/Mirror.scala @@ -37,7 +37,9 @@ object Mirror { def fromProduct(p: scala.Product): MirroredMonoType = this } - /** A proxy for Scala 2 singletons, which do not inherit `Singleton` directly */ + /** A proxy for Scala 2 singletons (which do not inherit `Singleton` directly), + * or case objects with a case class companion + */ class SingletonProxy(val value: AnyRef) extends Product { type MirroredMonoType = value.type type MirroredType = value.type diff --git a/tests/run/i12919.scala b/tests/run/i12919.scala new file mode 100644 index 000000000000..8a32d16e2977 --- /dev/null +++ b/tests/run/i12919.scala @@ -0,0 +1,20 @@ +case class Normal(value: String) +object Normal + +case class ClassWithCaseCompanion(value: String) +case object ClassWithCaseCompanion + +def instantiate[T](product: Product)(implicit mirror: scala.deriving.Mirror.ProductOf[T]) = + mirror.fromProduct(product) + +@main def Test: Unit = { + assert(instantiate[Normal](Tuple1("a")) == Normal("a")) // works as expected + + assert(instantiate[ClassWithCaseCompanion.type](EmptyTuple) == ClassWithCaseCompanion) // works as expected + + val c = instantiate[ClassWithCaseCompanion](Tuple1("b")) // throws java.lang.ClassCastException: class ClassWithCaseCompanion$ cannot be cast to class ClassWithCaseCompanion + assert(c == ClassWithCaseCompanion("b")) // desired behaviour + + val d = instantiate[ClassWithCaseCompanion.type](EmptyTuple) + assert(d == ClassWithCaseCompanion) +} diff --git a/tests/run/i12919a.scala b/tests/run/i12919a.scala new file mode 100644 index 000000000000..7e460c9ac48c --- /dev/null +++ b/tests/run/i12919a.scala @@ -0,0 +1,19 @@ +import scala.deriving.Mirror + +class Standalone +case object Standalone + +case class WithCompanionCaseClass(i: Int) +case object WithCompanionCaseClass + +@main def Test: Unit = + + val mStandalone = summon[Mirror.Of[Standalone.type]] + assert(mStandalone eq Standalone) // the object is its own mirror + assert(mStandalone.isInstanceOf[Mirror.Singleton]) // object extends Mirror.Singleton because its its own mirror. + + val mWithCompanion = summon[Mirror.Of[WithCompanionCaseClass.type]] + assert(mWithCompanion ne WithCompanionCaseClass) // the object is not its own mirror + assert(mWithCompanion.isInstanceOf[Mirror.SingletonProxy]) // its companion is a case class, so the mirror is a proxy + assert(!mWithCompanion.isInstanceOf[Mirror.Singleton]) // it can not extend Mirror.Singleton because its companion is a case class. + assert(WithCompanionCaseClass.isInstanceOf[Mirror.Product]) // its companion is a case class, so the mirror is a product. diff --git a/tests/run/i12919b.scala b/tests/run/i12919b.scala new file mode 100644 index 000000000000..7946bec7aac5 --- /dev/null +++ b/tests/run/i12919b.scala @@ -0,0 +1,17 @@ +import scala.deriving.Mirror + + +sealed trait WithCompanionSealedTrait +case object WithCompanionSealedTrait: + case class FirstChild(x: Int) extends WithCompanionSealedTrait + +@main def Test: Unit = + + val mWithCompanionSum = summon[Mirror.SumOf[WithCompanionSealedTrait]] + assert(mWithCompanionSum.ordinal(WithCompanionSealedTrait.FirstChild(1)) == 0) + assert(mWithCompanionSum eq WithCompanionSealedTrait) // case object caches sum mirror of its companion + + val mWithCompanionSingleton = summon[Mirror.ProductOf[WithCompanionSealedTrait.type]] + assert(mWithCompanionSingleton.fromProduct(EmptyTuple) == WithCompanionSealedTrait) + assert(mWithCompanionSingleton.isInstanceOf[Mirror.SingletonProxy]) + assert(mWithCompanionSingleton ne WithCompanionSealedTrait) // proxy mirror is never the object itself From f3e8d8360f769c1baef6a5d3c6f57053d1b11d14 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Thu, 21 Apr 2022 14:50:26 +0200 Subject: [PATCH 2/3] alternative: do not change parent type of companion --- .../dotty/tools/dotc/transform/SymUtils.scala | 17 +++-------------- .../dotc/transform/SyntheticMembers.scala | 2 +- .../dotty/tools/dotc/typer/Synthesizer.scala | 7 ++----- library/src/scala/deriving/Mirror.scala | 4 +--- tests/run/i12919a.scala | 19 +++++++++---------- tests/run/i12919b.scala | 6 +++--- 6 files changed, 19 insertions(+), 36 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index aededd7a5f9e..39c9aae52207 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -117,23 +117,12 @@ object SymUtils: self.isOneOf(FinalOrInline, butNot = Mutable) && (!self.is(Method) || self.is(Accessor)) - /** This module must use a `Mirror.SingletonProxy` for its mirror if: - * - it is Scala 2 defined - * - the companion class is a generic product or generic sum and would cache - * its mirror in this module. - */ - def requiresSingletonProxyMirror(using Context): Boolean = - self.is(Scala2x) || { - val cls = self.companionClass - cls.isGenericProduct - || cls.useCompanionAsSumMirror && cls.isGenericSum(self) - } - def useCompanionAsSumMirror(using Context): Boolean = def companionExtendsSum(using Context): Boolean = self.linkedClass.isSubClass(defn.Mirror_SumClass) - self.linkedClass.exists - && !self.is(Scala2x) + !self.is(Scala2x) + && self.linkedClass.exists + && !self.linkedClass.is(Case) && ( // If the sum type is compiled from source, and `self` is a "generic sum" // then its companion object will become a sum mirror in `posttyper`. (This method diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index d12280d0e7ce..32256e43d242 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -592,7 +592,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { } if (clazz.is(Module)) { - if (clazz.is(Case) && !clazz.requiresSingletonProxyMirror) makeSingletonMirror() + if (clazz.is(Case)) makeSingletonMirror() else if (linked.isGenericProduct) makeProductMirror(linked) else if (linked.isGenericSum(clazz)) makeSumMirror(linked) else if (linked.is(Sealed)) diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 66b8a69f514e..03554cbabea6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -282,10 +282,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): if mirroredType.termSymbol.is(CaseVal) then val module = mirroredType.termSymbol val modulePath = pathFor(mirroredType).withSpan(span) - val requiresProxy = - val mClass = module.info.classSymbol - mClass.is(Module) && mClass.requiresSingletonProxyMirror - if requiresProxy then + if module.info.classSymbol.is(Scala2x) then val mirrorType = mirrorCore(defn.Mirror_SingletonProxyClass, mirroredType, mirroredType, module.name, formal) val mirrorRef = New(defn.Mirror_SingletonProxyClass.typeRef, modulePath :: Nil) mirrorRef.cast(mirrorType) @@ -310,7 +307,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)) anonymousMirror(monoType, ExtendsProductMirror, span) + if (cls.is(Scala2x) || cls.linkedClass.is(Case)) anonymousMirror(monoType, ExtendsProductMirror, span) else companionPath(mirroredType, span) mirrorRef.cast(mirrorType) else EmptyTree diff --git a/library/src/scala/deriving/Mirror.scala b/library/src/scala/deriving/Mirror.scala index 90be5969ce5f..d091f4ee559e 100644 --- a/library/src/scala/deriving/Mirror.scala +++ b/library/src/scala/deriving/Mirror.scala @@ -37,9 +37,7 @@ object Mirror { def fromProduct(p: scala.Product): MirroredMonoType = this } - /** A proxy for Scala 2 singletons (which do not inherit `Singleton` directly), - * or case objects with a case class companion - */ + /** A proxy for Scala 2 singletons, which do not inherit `Singleton` directly */ class SingletonProxy(val value: AnyRef) extends Product { type MirroredMonoType = value.type type MirroredType = value.type diff --git a/tests/run/i12919a.scala b/tests/run/i12919a.scala index 7e460c9ac48c..1f7abcfdc4e9 100644 --- a/tests/run/i12919a.scala +++ b/tests/run/i12919a.scala @@ -1,19 +1,18 @@ import scala.deriving.Mirror -class Standalone -case object Standalone +case class Standalone(i: Int) +object Standalone case class WithCompanionCaseClass(i: Int) case object WithCompanionCaseClass @main def Test: Unit = - val mStandalone = summon[Mirror.Of[Standalone.type]] - assert(mStandalone eq Standalone) // the object is its own mirror - assert(mStandalone.isInstanceOf[Mirror.Singleton]) // object extends Mirror.Singleton because its its own mirror. + val mStandalone = summon[Mirror.ProductOf[Standalone]] + assert(mStandalone eq Standalone) // the companion object is the mirror for the case class - val mWithCompanion = summon[Mirror.Of[WithCompanionCaseClass.type]] - assert(mWithCompanion ne WithCompanionCaseClass) // the object is not its own mirror - assert(mWithCompanion.isInstanceOf[Mirror.SingletonProxy]) // its companion is a case class, so the mirror is a proxy - assert(!mWithCompanion.isInstanceOf[Mirror.Singleton]) // it can not extend Mirror.Singleton because its companion is a case class. - assert(WithCompanionCaseClass.isInstanceOf[Mirror.Product]) // its companion is a case class, so the mirror is a product. + val mWithCompanion = summon[Mirror.ProductOf[WithCompanionCaseClass]] + assert(mWithCompanion ne WithCompanionCaseClass) // A case object can not be the mirror of a companion case class. + + val mWithCompanionCaseObject = summon[Mirror.ProductOf[WithCompanionCaseClass.type]] + assert(mWithCompanionCaseObject eq WithCompanionCaseClass) // A case object is its own mirror. diff --git a/tests/run/i12919b.scala b/tests/run/i12919b.scala index 7946bec7aac5..84d9277c0a04 100644 --- a/tests/run/i12919b.scala +++ b/tests/run/i12919b.scala @@ -9,9 +9,9 @@ case object WithCompanionSealedTrait: val mWithCompanionSum = summon[Mirror.SumOf[WithCompanionSealedTrait]] assert(mWithCompanionSum.ordinal(WithCompanionSealedTrait.FirstChild(1)) == 0) - assert(mWithCompanionSum eq WithCompanionSealedTrait) // case object caches sum mirror of its companion + assert(mWithCompanionSum ne WithCompanionSealedTrait) // A case object can not be the mirror of a companion case class. val mWithCompanionSingleton = summon[Mirror.ProductOf[WithCompanionSealedTrait.type]] assert(mWithCompanionSingleton.fromProduct(EmptyTuple) == WithCompanionSealedTrait) - assert(mWithCompanionSingleton.isInstanceOf[Mirror.SingletonProxy]) - assert(mWithCompanionSingleton ne WithCompanionSealedTrait) // proxy mirror is never the object itself + assert(mWithCompanionSingleton.isInstanceOf[Mirror.Singleton]) // case object is its own mirror. + assert(mWithCompanionSingleton eq WithCompanionSealedTrait) // case object is its own mirror. From 6841f9ce117bafe3499239fb56f248572a89eb07 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 4 May 2022 15:22:56 +0200 Subject: [PATCH 3/3] fix #15101: add test --- tests/run/i15101.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/run/i15101.scala diff --git a/tests/run/i15101.scala b/tests/run/i15101.scala new file mode 100644 index 000000000000..3341adb2f962 --- /dev/null +++ b/tests/run/i15101.scala @@ -0,0 +1,10 @@ +trait Encoder[T] +object Encoder: + def derived[T](using scala.deriving.Mirror.Of[T]): Encoder[T] = new Encoder[T] {} + +case object Bar +enum Bar derives Encoder: + case A, B + +@main def Test: Unit = + summon[Encoder[Bar]]