Scala v2.13.11 issues a warning for the following code from the pureconfig orElse documentation (bottom of the page):
val csvIntListReader = ConfigReader[String].map(_.split(",").map(_.toInt).toList)
implicit val intListReader = ConfigReader[List[Int]].orElse(csvIntListReader)
case class IntListConf(list: List[Int])
The warning is:
[warn] Implicit definition should have explicit type (inferred pureconfig.ConfigReader[List[Int]])
[warn] implicit val intListReader = ConfigReader[List[Int]].orElse(csvIntListReader)
[warn] ^
When I add the inferred type, ConfigReader[List[Int]]
, it compiles without the warning but I get a NullPointerException
at run-time.
This raises the following questions for me:
intListReader
that will compile without a warning and run without error?@nowarn
"safe" (eg still work with Scala3)?Thanks for your insights.
PS: My run-time tests are also from the documentation:
ConfigSource.string("""{ list = [1,2,3] }""").load[IntListConf] ==> Right(IntListConf(List(1, 2, 3)))
and
ConfigSource.string("""{ list = "4,5,6" }""").load[IntListConf] ==> Right(IntListConf(List(4, 5, 6)))
It's an undocumented undefined behavior.
When you do:
implicit val x: X = implicitly[X]
compiler would generate
implicit val x: X = x
Which depending on context (where you have out it, is it val
, lazy val
or def
) will end up with:
Usually, you would like to have it resolved to:
// someCodeGenerator shouldn't use x
implicit val x: X = implicitly[X](someCodeGenerator)
which would use some mechanics to avoid using x
in the process of computing the implicit. E.g. by using some wrapper or subtype to unwrap/upcast (e.g. in Circe you ask for Encoder/Decoder derived with semiauto and what is obtained by implicit is some DerivedEncoder/DerivedDecoder to unwrap/upcast).
This is defined behavior coming from how implicits are resolved when all of implicit definitions in your scope are annotated.
What happens if some is not like this one?
ConfigReader[List[Int]]
while computing intListReader
intListReader
) is computed to be ConfigReader[List[Int]]
In general, library shouldn't rely on this behavior, there should be some semiautomatic derivation provided for you and in future versions on Scala (3.x) this code is illegal so I suggest rewriting it to semiauto.
In your particular case you can also use a trick, to summon different implicit of a different than the one being returned:
implicit val intListReader: ConfigReader[List[Int]] =
ConfigReader[Vector[Int]].map(_.toList) // avoids self-summoning
.orElse(
ConfigReader[String].map(_.split(",").map(_.toInt).toList)
)
but if it wasn't a type with a build-in support (they don't work with deriveReader
as it is only defined for sealed
and case class
) you could use:
import pureconfig.generic.semiauto._
implicit val intListConf: ConfigReader[IntListConf] =
deriveReader[IntListConf] // NOT refering intListConf