Is it a feature of Kotlin 1.4 that I don’t have to...
# eap
m
Is it a feature of Kotlin 1.4 that I don’t have to specify
<*>
when casting to the generic
GraphEnumDefinition
? Or a bug? 😄 It’s inferred correctly.
Oh, actually it’s inferred differently.
Copy code
class GraphEnumDefinition<Value : Enum<Value>>
<*>
would mean
<Enum<*>>
but it’s inferred to
<Any>
.
v
Could you share some context please? Especially what type
type.raptorTypeDefinition
has? It would be ideal if you shared an isolated reproducing example. I have an error about missed type argument with any left side of the
as
operator.
m
@Victor Petukhov
Copy code
fun main(foo: Base<*>) {
	val bar = foo as Derived
}

sealed class Base<T>
class Derived<T> : Base<T>()
v
I believe we can implicitly and safely substitute a type argument from the super type (in type check/cast expressions) if the corresponding type parameter is passed to the super type on the declaration site and they have the same number of type parameters. It’s old feature of Kotlin, it’s also available on Kotlin 1.3.72, for instance. Usually, we name such types (in the right side of the cast expression for given cases) as “bare types”. I’ll clarify how this feature is covered by the spec.
m
If it's supposed to work then it's broken for recursive
T: Enum<T>
In that case all functions/properties of
Enum
were missing because
*
was not inferred to
Enum<*>
v
So I’ve got it. Consider the following example:
Copy code
fun main(foo: Base<*>) {
    if (foo is Derived) {
        foo.x // Any?
    }
    if (foo is Derived<*>) {
        foo.x // Foo
    }
}

interface Base<T>
interface Foo

class Derived<T : Foo>(val x: T): Base<T>
It’s intended behaviour as the bare types implies substitution of a type argument of the super type (including its declaration site bounds) into casting/checking type. In given case, the super type is
Base
, it doesn’t have
Foo
upper bound on its type parameter so we get
Any?
instead of
Foo
. One more example to clarify:
Copy code
fun main(foo: Base<Bar>) {
    if (foo is Derived) {
        foo.x // Bar
    }
    if (foo is Derived<*>) {
        foo.x // Foo
    }
}

interface Base<T>
interface Foo
interface Bar

class Derived<T : Foo>(val x: T): Base<T>
Another side is that we can detect cases when type argument bounds of
Derived
and
Base
is incompatible, and report an error. You can create an issue about it in YouTrack if you want, and we’ll consider making such error in the future.
m
Thank you for the clarification. I’d probably make a YouTrack to disallow omitting
<*>
as it’s easy to miss, the result is quite unexpected and the resulting errors downstream are confusing. Also, inference shows both as
Derived<*>
but with different meaning (in my case).
It even leads to an incorrect “unnecessary cast” warning.
v
Yes, this is due to the fact that in
bar1
and
bar2
different upper bounds are hidden behind
*
(in my initial example, they would be
Any?
and
Foo
i.e.
Derived<out Any?>
and
Derived<out Foo>
for producers). I agree that there is implicit behavior here. Anyway we’ll try to do something with it, at least an inspection in IDE.
m