magnolia icon indicating copy to clipboard operation
magnolia copied to clipboard

scala 3 auto derivation not totally automatic

Open BusyByte opened this issue 1 year ago • 6 comments

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?

BusyByte avatar Apr 22 '24 14:04 BusyByte

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

adamw avatar Apr 24 '24 13:04 adamw

@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.

BusyByte avatar Apr 24 '24 13:04 BusyByte

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

adamw avatar Apr 24 '24 13:04 adamw

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

BusyByte avatar Apr 24 '24 14:04 BusyByte

@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.

BusyByte avatar Apr 24 '24 20:04 BusyByte

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

adamw avatar Apr 25 '24 08:04 adamw