I just ran into a seeming shortcoming in the type ...
# announcements
d
I just ran into a seeming shortcoming in the type inference system, can't find any tickets on youtrack, has anyone else run in to this?
Copy code
// both Foo and Bar extend Item
val item: Any? = getSomething()
if(item is Foo || item is Bar) {
  // I would expect this to smart cast to Item -- defining Item as a sealed class doesn't seem to help either
  doThingWithItem(item) //Error - Type mismatch, required: Item, found Any?
}
d
That's not how smart casts work. Smart casts will only cast to types you explicitly checked for.
s
also, why not just check if
item is Item
?
unless, I guess, you only want to perform an action if type of the object is one of those two subclasses
still, maybe kind of a smell ¯\_(ツ)_/¯
d
Because I believe there are cases where I don't want to call
doThingWithItem()
with any Item subclass (I'm actually going to check though-- this line came about during a Java -> Kotlin conversion).
I am not a fan of doing type checking myself if I can avoid it.
r
@dalexander Could you elaborate on that?
if(item is Foo || item is Bar)
is type checking. Do you mean you don't like casting if you can avoid it?
d
Yeah, definitely there are kinds of
Items
that should be excluded from that call.
d
The reason this doesn't work is to prevent "spooky action at a distance" situations. If you were to add additional subclasses of
Item
suddenly your smart-cast no longer compiles (even worse, it's in a seperately compiled library and is now incorrect!)
g
If you add additional sublasses, still Foo and Bar respond to Item, where is the problem?
d
That's not true. Adding new subclasses of
Item
doesn't mean if something is a
Foo
or
Bar
that it is also no longer an
Item
.
d
Yes I misspoke there, what I described was a different situation. However there are still situations where this breaks (e.g. the superclass is changed)
My original point still stands: Kotlin will only smart-cast to types that you explicitly checked for
No implicit handling is done
d
It seems like it's just a limitation of smart casting, which I think is a fine answer. The follow up question is if the Kotlin teams want to consider this use case.
g
Copy code
abstract class Parent
class Child1 : Parent()

fun main() {
    val a: Any? = ""
    if (a is Child1)
        funOnParent(a)
}

fun funOnParent(parent: Parent) = println(parent.javaClass)
This is working for me
d
Okay, so if I change the if statement to
Copy code
if(item is Foo) {
        doThingWithItem(item)
    }
it works by the way.
d
Yes, because then you get a smart-cast to
Foo
.
g
So basically with more than one the compiler is still unsure on the exact type, even though both are children of
Item
d
You can't smart-cast to two separate types. Kotlin does not have union types
d
Right-- presumably because it doesn't have internal representations for this kind of composite type. Which is fine.
k
mmh. I feel like these should be equivalent no?
Copy code
if(item is Foo || item is Bar) {
  doThingWithItem(item)
}
Copy code
if(item is Foo) {
  doThingWithItem(item)
} elseif(item is Bar){
  doThingWithItem(item)
}
and
Copy code
when(item) {
  is Foo -> doThingWithItem(item)
  is Bar -> doThingWithItem(item)
}
🚫 1
Copy code
interface Item

class Foo:Item
class Bar:Item

fun getValue(): Any? = TODO()

fun main(){
	// a:Any
	val a = getValue().let{if(it is Foo || it is Bar) it else TODO()}

	// b:Item
	val b = when(val t = getValue()){
		is Foo -> t
		is Bar -> t
		else -> TODO()
	}

	// c:Item
	val c = getValue().let{if(it is Foo) it else (if(it is Bar) it else TODO())}
}
m
Right, we don't have union types inside the compiler but using common supertype of
Foo
and
Bar
after a check like
item is Foo || item is Bar
for
item
can be a good approximation. I think it can be a nice feature, @dalexander can you please create a YT ticket?
👍 1
d
Sure, I can see about putting together a use case and a ticket.