Why <*> generics are casted to Nothing by th...
# announcements
a
Why <*> generics are casted to Nothing by the compiler instead of Any?, e.g. why can't I implement such a function without unchecked casts?
Copy code
fun <T: Any> T.toValuesSkipping(vararg skip: KProperty1<T, *>): Map<String, Any?> =
  (this::class.memberProperties - skip)
  .filter { it.javaField != null }.map { it.name to it.get(this) }.toMap()
The compiler forces me to cast
Copy code
(it as KProperty1<T, Any?>).get(this)
r
a
Otherwise it sees the .get(receiver: Nothing) function, not .get(receiver: Any?) or even .get(receiver: T)
If .memberProperties would be declared to return Collection<KProperty<T, Any?>> instead of <T, *>, the above code would compile and work... * is not quite variance
So maybe this is a more reflection-related question
m
What is
.memberProperties
?
a
KClass.memberProperties, like in the code above
Actually, KProperty1<T, *> uses star projection for return type only, KProperty.get(T) should still accept T as argument, but compiler still accepts only Nothing
m
The problem is the first argument, not the second. It’s actually
out T
, which means that
get(…)
expects either
T
or a specific subclass of
T
. And the only valid type for that is
Nothing
.
a
Even simpler version:
Copy code
fun <T: Any> T.toValues(): Map<String, Any?> = this::class.memberProperties.map { it.name to it.get(this) }.toMap()
How to compile this without casts?
m
You can’t. As soon as you use
this::class
you lose type information.
e
Copy code
inline fun <reified T : Any> T.toValues(): Map<String, Any?> =
    T::class.memberProperties.associate { it.name to it.get(this) }
is one way to keep type information
☝️ 2
m
this
is
T
(or any subclass) and
this::class
is
KClass<out T>
means the
KClass
of
T
(or any subclass).
get()
expects “T or any subclass” and
this
is “T or any subclass”. The compiler doesn’t know that both are the same subclass. If you are able to use
reified
and thus
T::class
then you get
KClass<T>
which means the
KClass
of exactly
T
.
a
Thanks, Marc! Actually, I even recall this fact now, but it's not obvious unless you think of it deeply. reified and non-reified versions could behave the same in this case, imho...
e
no, they shouldn't.
Copy code
fun T.getClass(): KClass<T> = this::class
cannot be correct, as if you call it within a
Copy code
val foo: Any = 1
val fooClass = foo.getClass()
you'd end up with
fooClass: KClass<Any>
which is not correct
m
In @antonkeks’s case both reified and non-reified are both correct. The compiler just cannot see a relation that’s actually there.