Reputation: 32073
I have the following SSCCE:
class Foo(val bars: Map<Int, Bar<*>>) {
fun <Baz> qux(baz: Baz) {
val bar2 = bars[2]!!
bar2.bazes += baz
}
interface Bar<Baz> {
var bazes: MutableList<Baz>
}
}
This seems fine to me, but the compiler complains that:
Error:(5, 9) Kotlin: Setter for 'bazes' is removed by type projection
I have no idea what this even means, much less how to correct it. What's going on here and how do I get around it?
Upvotes: 4
Views: 3208
Reputation: 81859
The issue: Your bars
property is using "star projection", which, put simply, just means: "I don't know the generic type of Bar
". This in turn leads to a problem: You'll only be able to get something from the MutableList
, adding is prohibited (Thus the error message: No Setter for bazes
!).
A possible solution:
class Foo<in Baz>(private val bars: Map<Int, Bar<in Baz>>) {
fun qux(baz: Baz) {
val bar2 = bars[2]!!
bar2.bazes.add(baz)
}
interface Bar<Baz> {
var bazes: MutableList<Baz>
}
}
What I changed here, is that I made Foo
typed with in Baz
, which means Foo
is made contravariant in its type parameter, thus Baz
s can only be consumed. The same type is then used for the input parameter bars
. As a result, you're now able to add values into bars.bazes
, because the associated MutableList
is said to be a "Consumer" of Bar
s.
btw: Using Baz
as a name for the generic type isn't recommended - You should rather use T
which is more obvious to be a generic type.
I know, it's a complex topic and I really advise to consult the good documentation as a further step :)
The plusAssign issue
The usage of +=
(plusAssign
operator) on the other hand is a bit weird:
The compiler complains not to be able to choose the correct operator:
Assignment operators ambiguity:
public operator fun Collection.plus(element: Any?): List >defined in kotlin.collections@InlineOnly public operator inline fun MutableCollection.plusAssign(element: Baz): Unit defined in kotlin.collections
I tried to explicitly cast bazes
to MutableList
which in fact solves the issue. The compiler then complains about an unecessary cast though. I don't know how to handle this properly to be honest except using add
instead ;-)
Upvotes: 4
Reputation: 204678
There's a couple little issues. Temporarily using bars[2]!! as Bar<Baz>
,
w: (4, 20): Unchecked cast: Foo.Bar<Any?> to Foo.Bar<Baz>
e: (5, 20): Assignment operators ambiguity:
public operator fun <T> Collection<Baz>.plus(element: Baz): List<Baz> defined in kotlin.collections
@InlineOnly public operator inline fun <T> MutableCollection<in Baz>.plusAssign(element: Baz): Unit defined in kotlin.collections
Kotlin doesn't know whether to handle it as bar2.bazes = bar2.bazes.plus(baz)
or bar2.bazes.plusAssign(baz)
. If you change it to var2.bazes.add(baz)
or val bazes
the ambiguity goes away.
Fixing that and removing the unsafe cast brings up
e: (5, 20): Out-projected type 'MutableList<out Any?>' prohibits the use of 'public abstract fun add(element: E): Boolean defined in kotlin.collections.MutableList'
the question of what can safely be done with the type projection. It gets treated like out Any?
, so you can read from the list, but in Nothing
, which means you can't add anything to the list.
It's unclear from this example why you're using *
. If it doesn't cause any other issues, perhaps you could lift out the <Baz>
parameter, e.g.
class Foo<Baz>(val bars: Map<Int, Bar<Baz>>)
Upvotes: 1