https://kotlinlang.org logo
Title
t

twisterrob

07/26/2019, 6:22 PM
I'm trying to write a seemingly simple function, but Kotlin doesn't play dice, what am I doing wrong? declaration:
fun <E> Collection<E>.myContains(item: E) : Boolean
usage:
val isItInside: Boolean = listOf(1, 2).myContains("1")
expectation: usage fails to compile, because it's a
List<Int>
vs. a
String
https://stackoverflow.com/q/57224118/253468
s

Shawn

07/26/2019, 6:27 PM
What are you even trying to do?
E
gets inferred to be Int, so passing in a String is necessarily a compile-time error
What is the goal of your “myContains” method?
b

Bob Glamm

07/26/2019, 6:29 PM
it's not a compile-time error because Collection is "out E"
t

twisterrob

07/26/2019, 6:30 PM
@Shawn it says on SO "quite pointless", see the bottom for a full real life example
@Bob Glamm can you please elaborate? also is there a way to make it? in the full example on SO I have another class where I control the variance.
b

Bob Glamm

07/26/2019, 6:31 PM
If you control the variance it works
interface Container<E> {
    fun contains(item: E): Boolean
}

fun <X> Container<X>.myContains(item: X): Boolean = contains(item)

fun bar() {
    val q = object: Container<Int> {
        override fun contains(item: Int): Boolean {
            return item == 4
        }
    }
    q.myContains("1")
}
is correctly flagged as an error on the last line
r

radimir.sorokin

07/26/2019, 6:32 PM
Why not specify type of the collection explicitly, e.g.
listOf<Int>(1, 2)
b

Bob Glamm

07/26/2019, 6:33 PM
doesn't make a difference: because Collection is covariant in E both of the following compile:
fun <E> Collection<E>.myContains(item: E): Boolean = contains(item)

fun <E> itContains(coll: Collection<E>, item: E): Boolean = coll.contains(item)

fun foo() {
    val q: List<Int> = listOf(1, 2, 3)
    q.myContains("1")
    itContains(q, "1")
}
r

radimir.sorokin

07/26/2019, 6:34 PM
Ah, I see
t

twisterrob

07/26/2019, 6:35 PM
@Bob Glamm So in
fun <E, T : Collection<E>> Asserter<T>.contain(item: E)
, even though I control the variance of
class Asserter<T>
, the upper bound of
T
is still a covariant
Collection
?
b

Bob Glamm

07/26/2019, 6:36 PM
You'd have to ask a type expert, but I think that reasoning is correct
t

twisterrob

07/26/2019, 6:39 PM
You sounded like one 😉, do you know someone?
b

Bob Glamm

07/26/2019, 6:40 PM
Best bet is to hope the JB devs pop in and comment. But no, I'm not a type expert; I just happened to click into
kotlin.Collection
and noticed the variance annotation
t

twisterrob

07/26/2019, 6:42 PM
Cheers!
b

Bob Glamm

07/26/2019, 6:46 PM
🙂
k

karelpeeters

07/26/2019, 7:19 PM
It's pretty simple: you can't enforce this, which is why
kotlin.collections.contains
uses an internal annotation for this:
@kotlin.internal.OnlyInputTypes
.
Here's the issue for allowing us to use that annotation as well: https://youtrack.jetbrains.com/issue/KT-13198
t

twisterrob

07/26/2019, 7:27 PM
oh 😞, thank you, subscribed
k

karelpeeters

07/26/2019, 7:28 PM
That funny. This does compile:
fun <E> Collection<E>.myContains(item: E)
        = item in this
t

twisterrob

07/26/2019, 7:28 PM
it's the usage that works unexpectedly
try to call it wrong
k

karelpeeters

07/26/2019, 7:28 PM
So I guess that's why it's not available, it violates a bunch of type stuff, it's just a trick.
Yeah I know.
t

twisterrob

07/26/2019, 7:29 PM
but without it the language is weaker?
k

karelpeeters

07/26/2019, 7:29 PM
The point is that inlining this function to such a callsite doesn't compile anymore.
t

twisterrob

07/26/2019, 7:29 PM
hmm
nope, it still compiles with
inline
k

karelpeeters

07/26/2019, 7:34 PM
I'm talking about really inlining it,
Ctrl+Alt+N
style.
I don't think
inline
actually every changes the behavior.
t

twisterrob

07/26/2019, 7:35 PM
aaah 😄
g

Glen

07/29/2019, 3:44 AM
I believe the compiler is flagging the error due to the fact that you are calling your extension function on a collection of ints and you are passing a parameter of type string.
You may try something like this:val isItInside: Boolean = listOf(1, 2).myContains(1)
d

dmitriy.novozhilov

07/31/2019, 9:32 AM
fun <T> Collection<T>.myContains(element: T): T = TODO()

interface A
interface B

// interface Collection<out E>
fun foo(x: Collection<A>, b: B) {
    x.myContains(b)
}
In call
x.myContains(b)
compiler should determines type of type variable
T
there is following constraints in this call:
B <: T
Collection<A> <: Collection<T>
for that constraint system exists correct solution:
T == Any
it's correct since
Collection
has
out
variance, it's correct that
Collection<A> <: Collection<Any>
If you write similar example but instead
Collection
will use type invariant type parameter you will get correct type mismatch
t

twisterrob

08/13/2019, 11:30 AM
@Glen you missed my point there (see OP), I would expect the compiler to flag it, but it doesn't. It compiles just fine and blows at runtime.
@dmitriy.novozhilov yeah, that's we figured as well, thanks for confirming. The lack of ability to write a function like this is sad 😞, is there no workaround here?
d

dmitriy.novozhilov

08/13/2019, 1:38 PM
Unfortunately there is no good workaround, but we know about that problem and think about it https://youtrack.jetbrains.com/issue/KT-13198
👍 1
g

Glen

08/13/2019, 4:54 PM
OK @twisterrob. Thanks for clarifying.