chimney icon indicating copy to clipboard operation
chimney copied to clipboard

Locally defined implicit transformers are ignored

Open jonathan-ostrander opened this issue 3 years ago • 1 comments

Chimney version: 0.6.1 (Latest)

This issue is best demonstrated with the following example:

import io.scalaland.chimney.dsl._
import io.scalaland.chimney.Transformer

case class Foo(a: Int, b: Int)
case class Bar(c: Int = 0)

case class FooWrapper(foo: Foo)
case class BarWrapper(bar: Option[Bar])

// warning below that localT is never used
implicit val t: Transformer[FooWrapper, BarWrapper] = {
  implicit val localT: Transformer[Foo, Bar] = foo => Bar(foo.a + foo.b)
  Transformer.define[FooWrapper, BarWrapper].withFieldRenamed(_.foo, _.baz).buildTransformer
}
// warning: local val localT in value t is never used
//         implicit val localT: Transformer[Foo, Bar] = foo => Bar(foo.a + foo.b)
//                      ^
// error: No warnings can be incurred under -Xfatal-warnings.

// no warning because Bar has a default value in its default constructor
implicit val t: Transformer[FooWrapper, BarWrapper] =
  Transformer.define[FooWrapper, BarWrapper].withFieldRenamed(_.foo, _.baz).buildTransformer

FooWrapper(Foo(1, 2)).transformInto[BarWrapper]
// BarWrapper(Bar(0))

// redefine wrapper transformer with a Transformer[Foo, Bar] outside of a local scope
implicit val fooBarT: Transformer[Foo, Bar] = foo => Bar(foo.a + foo.b)
implicit val newT: Transformer[FooWrapper, BarWrapper] =
  Transformer.define[FooWrapper, BarWrapper].withFieldRenamed(_.foo, _.baz).buildTransformer

FooWrapper(Foo(1, 2)).transformInto[BarWrapper]
// BarWrapper(Bar(3))

There are obvious workarounds. disableDefaultValues can be used when building the wrapper transformer, but this is annoying when defining transformers with code generated by ScalaPB because the unknownFields argument must have a default value specified. Transformers can always be defined outside of a local scope, but sometimes a local scope is desired if multiple transformers require different transformation behavior for a given type in the same package.

jonathan-ostrander avatar Apr 02 '21 13:04 jonathan-ostrander

This issue can be solved adding a flag to scalapb:

option (scalapb.options) = {
  preserve_unknown_fields: false // true by default in Proto 3
}

Then your protobufs won't have unknownField generated in the first place.

This will also enable you to use

option (scalapb.options) = {
  no_default_values_in_constructor: true
}

MateuszKubuszok avatar Aug 09 '22 12:08 MateuszKubuszok

From what I checked, this seem to be fixes on our scala-3 branch. I checked in console:

chimney-build(scala-3)> chimney/console
[info] Starting scala interpreter...
Welcome to Scala 2.13.11 (OpenJDK 64-Bit Server VM, Java 17.0.3).
Type in expressions for evaluation. Or try :help.
import io.scalaland.chimney._
import io.scalaland.chimney.dsl._

scala> case class Foo(a: Int, b: Int)
     | case class Bar(c: Int = 0)
     |
     | case class FooWrapper(foo: Foo)
     | case class BarWrapper(bar: Option[Bar])
class Foo
class Bar
class FooWrapper
class BarWrapper

scala> locally {
     |   implicit val localT: Transformer[Foo, Bar] = foo => Bar(foo.a + foo.b)
     |   implicit val t = Transformer.define[FooWrapper, BarWrapper].withFieldRenamed(_.foo, _.bar).buildTransformer
     |   FooWrapper(Foo(1, 2)).transformInto[BarWrapper]
     | }
val res3: BarWrapper = BarWrapper(Some(Bar(3)))

and I didn't feel the need to add a test case for it as there is already one. Once #325 is published if you still find that there is a bug, please reopen.

MateuszKubuszok avatar Jul 30 '23 10:07 MateuszKubuszok