arainko / ducktape

Automatic and customizable compile time transformations between similar case classes and sealed traits/enums, essentially a thing that glues your code. Scala 3 only. Or is it duct 🤔
https://arainko.github.io/ducktape/
Other
405 stars 7 forks source link

Misleading error message when trying to use `_.into.transform()` or `_.to` in a `given Transformer` definition #165

Closed BenFradet closed 5 months ago

BenFradet commented 5 months ago

I must be dense but the following:

  final case class A(a: Int)
  final case class B(b: Int)
  given Transformer[A, B] =
    _.into[B]
      .transform(Field.renamed(_.b, _.a))

gives me The path segment 'b' is not valid as it is not a field of a case class or an argument of a function @ B

What am I missing?

arainko commented 5 months ago

Thanks for the report!

So the problem here is that _.into[B].transform() (you can see that the code will also compile without specifying Field.renamed) basically loops on itself since it's declared with a Transformer[A, B] in scope so it resolves to that transformer, which will fail with a SO at runtime.

The error message is misleading in this case (underneath it looks for a Plan.BetweenProducts node to try and dive into but the node is actually Plan.UserDefined i.e. a transformation with a user-defined Transformer in scope which is not traversable with a path). There should be a way of making that error message actually friendly for the user, I'll try and tackle that.

As for a proper fix for your use case, when building out Transformer instances you should use Transformer.define[A, B].build(Field.renamed(_.a, _.b) - this construct is built NOT to loop on itself in presence of a toplevel transformer in scope.

tl;dr

final case class A(a: Int)
final case class B(b: Int)
given Transformer[A, B] = Transformer.define[A, B].build(Field.renamed(_.b, _.a))
BenFradet commented 5 months ago

Thanks a lot :+1: