https://kotlinlang.org logo
#announcements
Title
# announcements
a

arekolek

08/02/2019, 11:41 AM
Am i wrong to expect this to compile?
Copy code
sealed class Item {
    data class Foo(val id: Int) : Item()
    object Bar : Item()
}

fun List<Item>.trim() = dropLastWhile { it is Item.Bar || it.id == IGNORED_ID }
It works if I do
it !is Item.Foo || it.id == IGNORED_ID
though, which is understandable
d

diesieben07

08/02/2019, 11:43 AM
Yes, you are wrong. The smart cast is not that smart.
a

arekolek

08/02/2019, 11:43 AM
I mean in principle, it could be that smart?
d

diesieben07

08/02/2019, 11:44 AM
In principle, yes. However: It follows Kotlin's idea of code being obvious and clear to understand. You check for a type: That type is smart-casted to. What you "propose" is some "spooky action at a distance", where smart casts behave differently in some situations
m

marstran

08/02/2019, 11:53 AM
I think the reason is that if
it !is Item.Foo
is false, then
it
must be
Item.Foo
. Even if you would later add another subclass to
Item
. If
it is Item.Bar
is false however, then
it
can be any other subclass of
Item
. The smart cast would break if you added a new subclass to
Item
.
So, the smart casting algorithm has no special case for a sealed class with exactly 2 subclasses.
d

diesieben07

08/02/2019, 11:55 AM
Yes. Like I said, it only smart-casts to the types you explicitly check for.
In your case
Item.Foo
is the only type mentioned in your type check, so it's the only type that can be smart-casted to
w

wbertan

08/02/2019, 12:34 PM
I helps, a little, when looking into the decompiled bytecode: For
it !is Item.Foo || it.id == 999
Copy code
if (it instanceof PathTest.Item.Foo && ((PathTest.Item.Foo)it).getId() != 999) { ... }
In my guess, the way it "optimizes" it, allows the smart cast to be in place and do the second check. While the
it is Item.Bar || it.id == IGNORED_ID
will probably "optimize" in a way it cannot "smart" cast to the second expression as it probably didn't rule out the type. Tweaking a little your example to:
Copy code
sealed class Item(val name: String) {
        data class Foo(val id: Int) : Item("foo")
        object Bar : Item("bar")
    }
and making
it is Item.Bar || it.name == "other"
it will compile and generate:
Copy code
if (!(it instanceof PathTest.Item.Bar) && !Intrinsics.areEqual(it.getName(), "other")) {...}
So we can see how it "optimizes" the first expression, while the one where it "works" it checks if it is an
instaceof Item.Foo
, this being true will check the second expression knowing the instance is a
Foo
. In this second example it simply check if isn't
Bar
, and when reaching the second expression what it knows is the class isn't a
Bar
but could be anything else (not exactly a
Foo
).
3 Views