Petter Måhlén
10/15/2021, 11:22 AMclass Container<T>(
val func: (T) -> Unit,
val aT: T
)
sealed class Foo {
object Bar : Foo()
object Baz : Foo()
}
fun main() {
val c = Container(
{ f: Foo -> print("Got $f")},
Foo.Bar
)
c.func(Foo.Baz) // doesn't compile, because c is Container<Foo.Bar>, not Container<Foo>
val c2 = Container(
{ f: Foo -> print("Got $f")},
Foo.Bar as Foo
)
c2.func(Foo.Baz) // compiles
}
so, the compiler infers c
to be of type Container<Foo.Bar>
, where I expected to get a Container<Foo>
. It seems to me that there are two possible choices - either use Foo
for type parameter T
, or use the fact that since f
accepts a Foo
, it can also accept all `Foo.Bar`s. But what I wanted was in fact the former (the real use case is complicated by me trying to implement a typesafe heterogenous map of sorts, so I didn’t even get a compilation error, just a runtime ClassCastException). It seems to me like the current behaviour might be surprising - is there a reason for it, is it somehow more correct?dmitriy.novozhilov
10/15/2021, 11:56 AMephemient
10/15/2021, 11:59 AMFoo
with the lower bound of Foo.Bar
, and picks the more specific type?Petter Måhlén
10/15/2021, 12:03 PMtynn
10/16/2021, 1:01 PMVictor Petukhov
10/19/2021, 9:09 AMfun <K> select(x: K, y: K) = x
fun test(foo: Foo) {
val x = select(foo, Foo.Bar) // inferred into Foo
}
In terms of constraints, we add two constraints: LOWER(Foo.Bar)
and LOWER(Foo)
. So it’s obvious that only Foo
is suitable here and at the same time it’s the most specific type.
But in your usage, one of the type is considered as contravariant because it’s located at lambda’s input types (see FunctionN
declarations like Function1<*in* P1, out R>
). It means, that the corresponding concrete type isn’t lower bound any more, it’s upper one now.
So here we have two constraints: LOWER(Foo.Bar)
and UPPER(Foo)
. Actually, both types are suitable, but Foo.Bar
is more specific, so we chose it.Petter Måhlén
10/19/2021, 9:25 AMFoo.Bar as Foo
, so no worries. 🙂