magnolia icon indicating copy to clipboard operation
magnolia copied to clipboard

Mirror-less Scala 3

Open KacperFKorban opened this issue 4 years ago • 11 comments

Draft of mirror-less implementation for Scala 3. Probably most of the code can be simplified a lot. But should be a good start for testing if recursive types can be handled properly this way. Related to #310

KacperFKorban avatar Jun 22 '21 20:06 KacperFKorban

Thanks!

I think it might turn out recursive types work as-is, but maybe we could support value classes and non-trivial sealed trait hierarchies, for which mirrors are currently not derived.

adamw avatar Jun 28 '21 05:06 adamw

FYI the base branch is now scala3

adamw avatar Jul 13 '21 10:07 adamw

Ok, so this branch should now pass at least as many tests as the scala3 branch does. So apart from the compatibility issues and overall being rough around the edges, it works.

@adamw will you be able to find some time to take a look at the changes and give some feedback? It would be great to test it on some real-world libraries that use magnolia - tapir?

KacperFKorban avatar Aug 22 '22 13:08 KacperFKorban

So one thing that breaks is calling magnolia from another macro: Expr.summon[Schema[T]] returns a None.

Even in a simple case:

    case class Test1(f1: Int)
    implicitly[Schema[Test1]]
    val codec = implicitly[MultipartCodec[Test1]].codec

the implicitly[Schema[Test1]] works, while the same lookup done as part of the multipart codec derivation macro fails

adamw avatar Aug 29 '22 09:08 adamw

But is it something that we actually care about? if Expr.summon[Schema[T]] returns None then we just use the same implicit method (Typeclass.derived) but by hand this time. The only thing I can think of that may be affected here is the inline depth. Am I missing something?

KacperFKorban avatar Aug 29 '22 09:08 KacperFKorban

Not sure I understand - Expr.summon used to work in the old implementation, returning a Some. It stopped with the new one (no idea why). Where would I use Typeclass.derived?

adamw avatar Aug 29 '22 10:08 adamw

This is how to reproduce, inside magnolia/test:

// test1.scala
package magnolia1.tests

import magnolia1.examples._

case class Data(value: String)

object Test1 {
  summon[Show[String, Data]]
  Test2.summonShow[Data]
}

// test2.scala
package magnolia1.tests

import magnolia1.examples._
import scala.quoted.*

object Test2 {
  inline def summonShow[T]: Show[String, T] = ${ summonShowImpl[T] }

  def summonShowImpl[T: Type](using Quotes): Expr[Show[String, T]] = {
    import quotes.reflect.*

    Expr.summon[Show[String, T]].getOrElse(report.throwError(s"Cannot find a Show for: ${Type.show[T]}."))
  }
}

The direct summon works, the indirect one - fails.

adamw avatar Aug 29 '22 10:08 adamw

there is a problem with current impl which i hope can be addressed as well with the rewrite here is a showcase https://github.com/softwaremill/magnolia/pull/421 basically, since subtypes contains only types directly inheriting root type, all of the logic relying on subtypes and choose breaks when you introduce non-leaf subtypes as far as i can see the rewrite also suffers from this right now can something be done about it?

OlegYch avatar Aug 29 '22 13:08 OlegYch

actually the problem seems to be easy to fix with old implementation, i submitted a pr

OlegYch avatar Aug 29 '22 22:08 OlegYch

@OlegYch You obviously can flatten the hierarchy in this rewrite too, but I'm not sure if that's what we want. Don't we lose some information, when we flatten the type hierarchy like that?

I'm pretty sure that there's a way to implement DecoderSafe derivation in a way that will work for those hierarchies. (choosing the instance that returns Right)

KacperFKorban avatar Aug 30 '22 09:08 KacperFKorban

@KacperFKorban i thought so too, but it appears to be no way to make choose work, or enumerate all subtypes trying out all instances will not work since multiple instances may succeed

OlegYch avatar Aug 30 '22 09:08 OlegYch