softwaremill / magnolia

Easy, fast, transparent generic derivation of typeclass instances
https://softwaremill.com/open-source/
Apache License 2.0
764 stars 116 forks source link

scala 3 auto derivation not totally automatic #527

Closed BusyByte closed 4 months ago

BusyByte commented 5 months ago

I'm not understanding why with the following it its making me define Scalacheck arbitraries for nested models:

object ArbDerivation extends AutoDerivation[Arbitrary] {
  override def join[T](ctx: CaseClass[Arbitrary, T]): Arbitrary[T] =
    Arbitrary {
      Gen.lzy(ctx.constructMonadic(param => param.typeclass.arbitrary))
    }

  override def split[T](ctx: SealedTrait[Arbitrary, T]): Arbitrary[T] =
    Arbitrary {
      Gen.oneOf(ctx.subtypes.map(_.typeclass.arbitrary)).flatMap(identity)
    }

  implicit private val monadicGen: Monadic[Gen] = new Monadic[Gen] {
    override def point[A](value: A): Gen[A] = Gen.const(value)

    override def flatMap[A, B](from: Gen[A])(fn: A => Gen[B]): Gen[B] = from.flatMap(fn)

    override def map[A, B](from: Gen[A])(fn: A => B): Gen[B] = from.map(fn)
  }

}

object ArbitraryInstances {
implicit val arbChange: Arbitrary[Event.Change] = ArbDerivation.autoDerived // not sure why this is required, it's just a case class
 val arbEvent: Arbitrary[Event] = ArbDerivation.autoDerived
}

For example we have sealed trait Event And one of the Events has a list of Change like so:

sealed trait Event
object Event {
case class Change(
      theType: String,
      theId: String,
      anotherType: String,
      name: Option[String],
      lastModifiedTime: Option[Long],
      creationTime: Option[Long]
  )
case class MyEventA
      trace: String,
      time: Instant,
      auxInfo: String,
      changes: List[Change]
  ) extends Event

case class MyEventB
      trace: String,
      time: Instant,
      auxInfo: String,
      changes: List[Change]
  ) extends Event
}

I don't understand why these supporting case classes are not autoDerived with when auto deriving the sealed trait Event. There are 105 event case classes. 7 events reference the same list of changes. Am I doing something wrong?
Could Magnolia be auto deriving the typeclass for Change for one of the events but then not finding it for the others or something?

adamw commented 5 months ago

Shouldn't you do an import ArbDerivation.{*, given} inside of ArbitraryInstances so that the given is visible?

BusyByte commented 5 months ago

@adamw I'll try that out and see if it works. I wasn't thinking this was the way it was working in Scala 2 but perhaps I was wrong in thinking this.

adamw commented 5 months ago

The APIs aren't 1:1 identical, as the way macros / implicits in Scala 2 and Scala 3 work differ in some details

BusyByte commented 5 months ago

@adamw I added import ArbDerivation.{*, given} and it now works as expected, thank you for the help.

BusyByte commented 5 months ago

@adamw I ran into another issue with this as well, autoDerived doesn't seem to handle value classes, is that true or is my ArbDerivation missing something? Here's what I see:

[error]     |But Failed to synthesize an instance of type scala.deriving.Mirror.Of[
[error]     |  com.mycopackage.events.LastModifiedTime]:
[error]     |   * class LastModifiedTime is not a generic product because it is a value class
[error]     |   * class LastModifiedTime is not a generic sum because it is not a sealed class

It is indeed a value class around instant and there is an implicit arbitrary instance for Instant in scope where autoDerived is called.

adamw commented 5 months ago

I'm afraid value classes aren't supported: https://github.com/softwaremill/magnolia/blob/6b4ef79e102e045649e38c8b9212931b0c86b087/test/src/test/scala/magnolia1/tests/ValueClassesTests.scala#L7