In 1.3.1, Magnolia usages in Tapir generate warnings like:
[warn] /xxx/src/core/impl.scala: the type test for Option[com.softwaremill.Endpoints.UserId]
cannot be checked at runtime because its type arguments can't be determined from Any
When deriving default values for case class members, Magnolia checks if there's a user-defined default, otherwise it falls back to a default based on field's type. For example, for case class Example(field: Int) it would derive a default of Example(0). However, there's an issue when dealing with generic classes with generic field types, where user defined default exists:
case class Example[A](field: Option[A] = Some("A"))
val defaultVal = HasDefault.derived[Example[Int]].defaultValue.right.get
// defaultVal == Example(Some("A")), wrong! Should be Example(None)
val fieldVal: Int = defaultVal.field.get // compiles, but throws a ClassCastException
Consequences
The Example(Some("A")) default value is incorrect, because object's type is Example[Int]. This would throw a ClassCastException on resolving the field's wrapped value. Using generic types with default values like this doesn't seem like a common scenario, so severity of this issue is very low.
Cause
In core.paramsFromMaps default values for case class fields are passed in an argument of type defaults: Map[String, Option[() => Any]]. These Any values are unsafely casted to type parameters in the safeCast method, which may cause casting for example an Option[String] to an Option[Int]. Instead, for a default param that doesn't match the type, a None should be returned, causing a fallback type's proper default value.
Possible solution
A proper resolution might require enriching defaults: Map[String, Option[() => Any]] with type information, which, compared to actual type (in the method called p) would allow to detect a mismatch, causing a fallback to None. This requires nontrivial changes to the macro which generates defaults.
In 1.3.1, Magnolia usages in Tapir generate warnings like:
I have added a workaround with suppression in https://github.com/softwaremill/magnolia/pull/473, but a proper fix may be needed in the future.
Detailed problem description
When deriving default values for case class members, Magnolia checks if there's a user-defined default, otherwise it falls back to a default based on field's type. For example, for
case class Example(field: Int)
it would derive a default ofExample(0)
. However, there's an issue when dealing with generic classes with generic field types, where user defined default exists:Consequences
The
Example(Some("A"))
default value is incorrect, because object's type isExample[Int]
. This would throw aClassCastException
on resolving the field's wrapped value. Using generic types with default values like this doesn't seem like a common scenario, so severity of this issue is very low.Cause
In core.paramsFromMaps default values for case class fields are passed in an argument of type
defaults: Map[String, Option[() => Any]]
. TheseAny
values are unsafely casted to type parameters in thesafeCast
method, which may cause casting for example anOption[String]
to anOption[Int]
. Instead, for a default param that doesn't match the type, aNone
should be returned, causing a fallback type's proper default value.Possible solution
A proper resolution might require enriching
defaults: Map[String, Option[() => Any]]
with type information, which, compared to actual type (in the method calledp
) would allow to detect a mismatch, causing a fallback toNone
. This requires nontrivial changes to the macro which generatesdefaults
.