Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

fromProduct on Mirror.Product broken for case class with companion case object #12919

Closed
barnardb opened this issue Jun 23, 2021 · 4 comments · Fixed by #14986
Closed

fromProduct on Mirror.Product broken for case class with companion case object #12919

barnardb opened this issue Jun 23, 2021 · 4 comments · Fixed by #14986

Comments

@barnardb
Copy link

barnardb commented Jun 23, 2021

Compiler version

3.0.0 and 3.0.2-RC1-bin-20210622-a059dbc-NIGHTLY

Minimized code

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() = {
  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
}

Expectation

The Mirror.Product[ClassWithCaseCompanion] synthesised by the compiler should correctly instantiate the case class it claims to mirror, not its companion object. It seems the compiler gets confused when the companion object is a case object.

@bishabosha bishabosha self-assigned this Jun 23, 2021
@bishabosha
Copy link
Member

bishabosha commented Jun 23, 2021

maybe the companion should not cache the Mirror if it itself is a generic product / singleton (aka change to instead create a new mirror at each callsite)

Edit: "generic product" here means the check the compiler does to see if a product mirror should be generated:
https://github.com/lampepfl/dotty/blob/fb86c0515a7004d90322c8485f958a3f50d48b9c/compiler/src/dotty/tools/dotc/transform/SymUtils.scala#L81

@barnardb
Copy link
Author

barnardb commented Jun 23, 2021

@bishabosha Note that the product (ClassWithCaseCompanion) in my example is not generic. I haven't looked at the implementation at all, but I don't think caching the mirror is problematic per se. Perhaps the same name/field is being used to cache the mirror for the case class and its companion case object, in which case the solution might be to use a different name/field for the object's mirror as for the class's mirror.

@barnardb
Copy link
Author

@bishabosha Ah, I now understand that you used "generic product" to signify a type for which the compiler synthesises Mirror.Product. Yes, if the object is only able to cache one Mirror, perhaps it should cache its own and leave the other to be synthesised at call sites, though perhaps an object should be able to cache both its own Mirror and that of the class for which it is a companion. Thanks for taking a look, BTW.

@bishabosha
Copy link
Member

@bishabosha Ah, I now understand that you used "generic product" to signify a type for which the compiler synthesises Mirror.Product.

Yes sorry, my comment was a bit confusing, I have updated my comment to link to the compiler code I was implying here.

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants