Issue
I have a question regarding Kotlin and generics. I have a compile error, that I don't understand. I have a very simplified example below. Since Kotlin data classes can't inherit from other data classes, I've done a lot of my class design with composition. That leads to nested data classes. At some points I use generics to switch the type for parameters I can pass into an object. I need to modify values of these data classes and because they are immutable, I use the copy function and put that stuff into a Mutator class.
class Mutator<out P : Params>(val form: Form<P>) {
fun modifyName(name: String): Form<P> =
when (form.params) {
is DefaultParams -> form.copy(params = form.params.copy(name = name)) // compile error
is ExtendedParams -> form.copy(params = form.params.copy(name = name)) // compile error
else -> form // also.. why do I need that.. Params is sealed..
}
}
sealed interface Params
data class DefaultParams(val name: String) : Params
data class ExtendedParams(val name: String, val age: Int) : Params
data class Form<out P : Params>(val params: P)
fun main() {
val form: Form<DefaultParams> = Form(DefaultParams("John"))
val mutator: Mutator<DefaultParams> = Mutator(form)
val newForm: Form<DefaultParams> = mutator.modifyName("Joe")
val next_form: Form<ExtendedParams> = Form(ExtendedParams("John", 30))
val next_mutator: Mutator<ExtendedParams> = Mutator(next_form)
val next_newForm: Form<ExtendedParams> = next_mutator.modifyName("Joe")
}
I get errors on the first two branches of the when block.
Type mismatch. Required: P Found: DefaultParams
Type mismatch. Required: P Found: ExtendedParams
Shouldn't Params
be the upper bound of P
and thus fine to find DefaultParams
or ExtendedParams
?
And also Params
should be sealed, but I need an else block in the when.
Solution
I would suggest that you just use an unchecked cast here:
is DefaultParams -> form.copy(params = form.params.copy(name = name) as P)
is ExtendedParams -> form.copy(params = form.params.copy(name = name) as P)
Note that this unchecked cast is very safe, because of the semantics of the copy
method. copy
does not change the runtime type of its receiver. Therefore, it is no more unsafe to pass the copied form.params
than to pass the original form.params
. And the compiler allows you to pass the original form.params
here. The compiler is just not smart enough to work out that since form.params
is a specific Params
, P
must be that specific Params
too.
The when
statement is not exhaustive because the type of form.params
is not Params
, but P
. (Yes, the compiler is quite stupid sometimes). According to the spec, a condition for an exhaustive when
expression is:
The bound expression is of a sealed class or interface
Note that it doesn't say "a type parameter bounded to a sealed class or interface". So if you just add as Params
, it will become exhaustive:
fun modifyName(name: String): Form<P> =
(form.params as Params).let { formParams ->
when (formParams) {
is DefaultParams -> form.copy(params = formParams.copy(name = name) as P)
is ExtendedParams -> form.copy(params = formParams.copy(name = name) as P)
}
}
Answered By - Sweeper
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.