Does anyone know why this code doesn’t work? I’m g...
# announcements
d
Does anyone know why this code doesn’t work? I’m getting errors saying that the when is not exhaustive and that the return types don’t match.
Copy code
sealed class Parent {
    class Child : Parent()
}

interface Factory<T : Parent>

class FactoryBuilder {
    fun <T : Parent> build(item: T): Factory<T> {
        return when(item) {
            is Parent.Child -> {
                object : Factory<Parent.Child> {

                }
            }
        }
    }
}
a
the
when
cannot be exhaustive, because you allow
T
to be any subclass of
Parent
and thus
T
must not be a sealed class anymore
👎 1
Copy code
sealed class Parent {
    open class Child : Parent()
}

class ChildChild : Parent.Child()

class ChildChild2 : Parent.Child()

FactoryBuilder().build(ChildChild2()) //Valid code, but ChildChild2 is not a sealed class
o
Type inference is simply not that smart to analyze such complex type interactions.
👆 1
@Andreas Sinz in the original code
Child
is not an
open
class
Factory
interface is invariant, so Factory<Parent.Child> is not assignable to Factory<T> (even if compiler could figure out, that Parent & Child are the only options)
@stanislav.erokhin take a look, looks like an interesting case for type inference
a
@orangy yes, but if the compiler would be smart enough we wouldn't need `sealed class`es 😉
o
not really,
sealed
classes system controls if the change will be in a single module.
d
Thanks for responding! I really, really wanted this to work. 🙂 Oh well it would have made my factory code a lot cleaner.
a
@orangy this can be emulated by an open class with a private constructor
o
@Andreas Sinz theoretically may be, but then tooling, performance… it really complicates things if closed set of inheritors should be calculated
@Devon Humes I think
when
exhaustiveness is a bug, please report it. If you write it as
when (item as Parent)
it works, and even highlights
as
as a redundant cast.
a
thats the reason
when
exhaustiveness can be determined only for sealed classses, right?
o
Then, it should be
interface Factory<out T : Parent>
so that
Factory<Child>
is assignable to
Factory<Parent>
. It doesn’t fix the error, which also looks like a problem I don’t understand (on a holiday evening at 00:35 🙂 ).
@Andreas Sinz right
d
@orangy Will do. Thanks for looking at this!
a
@orangy and I guess thats the reason his
when
is not exhaustive, because
T
can be any subclass and the compiler doesnt check whether all the subclasses are final
o
It doesn’t matter, we need to check only immediate subclasses, that’s how
is
works
👍 1
In your example even for
class ChildChild : Parent.Child()
, check for
is Parent.Child
will be
true
, and hence openness of
Parent.Child
is irrelevant.
I think this
when
is not exhaustive, because the check is checking only for the type itself (
T
in this case) and not its bounds.
a
if i change it to
item: Parent
it works fine
o
exactly
a
because
when
can only be exhaustive for a
sealed class
, but
T
can be
Child
too which is an ordinary class
d
It’s more complicated than that.
Copy code
sealed class Parent {
    class Child : Parent()
}

fun <T : Parent> test(item: T) {
    when(item) {
        is Parent.Child -> println()
    }
}
seems to compile just fine.
It realizes that the when is exhaustive.
a
nope, you do not use the return of
when
so it doesn't check exhaustiveness
@Devon Humes the code works if you use
item: Parent
and cast the objects to
Factory<T>
d
Hmmm…I didn’t realize that that was when worked. That’s good to know!
@Andreas Sinz I see that. It leaves me with an unchecked cast though. I know it’s right, but it leaves a dirty taste in my mouth.
a
yeah, thats what we have to live with right now ^^
but its still better then java 😛
d
Amen!
a
After giving it a second thought, exhaustiveness-check should actually work in your example as @orangy said. The compiler knows that
T
is a subclass of
Parent
and thus having branches for all direct subclasses of
Parent
indeed covers every possible case
it gets trickier when creating a
Factory<T>
because its invariant. In your example we know that inside
is Parent.Child ->
its actually a
Parent.Child
so
Factory<Parent.Child>
is fine, but if the class is open and your
item
is a subclass of
Parent.Child
it will fail because
Factory<Parent.Child> != Factory<ChildSubclass>