Given the following class:
data class Foo(val bar: Int)
How would I obtain a Class<T> for Foo...
val prop = Foo::bar
...from this property expression?
val receiver = prop.parameters[0]
val receiverClass = receiver.type.jvmErasure.java
Note that you get KType and KClass on the way, which you may prefer to Class.
The above works because parameters documentation says
If this callable requires a this instance or an extension receiver parameter, they come first in the list in that order.
so it might be worth a comment in your code.
The receiver parameter can also be obtained more explicitly by
val receiver = (prop.instanceParameter ?: prop.extensionReceiverParameter)!!
Related
I'm creating a property mapper. I'd like to be able to specify an associative relationship between KProperty and it's value. I'd like to have something like that:
infix fun <TProperty> KProperty1<out Changeable, TProperty>.to(that: TProperty) = Pair(this, that)
but for the following class:
interface Changeable
data class Person(val name: String, val age: Number, val salary: Double) : Changeable
when I declare potentially faulty map:
val entry = (Person::age to "9999") //String instead of Number
this type is resolved to:
Pair<KProperty1<out Changeable, Serializable>, Serializable>
While it looks all nice and compiles, this is dangerous in runtime. How can I restrict TProperty to be exactly the same type as KProperty1<out Changeable, TProperty> without looking for super type (Serializable instead of Int)
There is an internal annotation #InputTypesOnly that solves exactly this problem. It causes type inference to fail if the inferred type (like Serializable) is not any of the type parameters. However, you cannot use it for obvious reasons. Follow KT-13198, which is about making it public.
However, there seems to be a trick that you can do (I learned this from here) - if you put the type you want "correctly" inferred (TProperty) into both a lambda parameter and lambda return position, then it does get "corretly" inferred.
infix fun <TProperty> KProperty1<out Changeable, TProperty>.to(
that: (TProperty) -> TProperty) =
Pair(this, that(TODO()))
val entry = Person::age to { "" } // error
val entry = Person::age to { 1 } // ok
I guess by having TProperty in both positions, the compiler adds enough constraints for it to stay "fixed".
Of course, we have no instances of TProperty to pass to the that lambda, so a better idea would be to use the receiver's type KProperty1<out Changeable, TProperty>:
infix fun <TProperty> KProperty1<out Changeable, TProperty>.to(
that: (KProperty1<out Changeable, TProperty>) -> TProperty) =
Pair(this, that(this))
Note that with this signature, entry would have a type of:
Pair<KProperty1<out Changeable, Number>, Number>
If you want
Pair<KProperty1<Person, Number>, Number>
instead, you should add another type parameter for the first type argument of KProperty1 rather than using out Changeable:
infix fun <TReceiver: Changeable, TProperty> KProperty1<TReceiver, TProperty>.to(
that: (KProperty1<TReceiver, TProperty>) -> TProperty) =
Pair(this, that(this))
Type Hierarchy
open class Fruit()
open class CitrusFruit : Fruit()
class Orange : CitrusFruit()
Declaration-site Variance
The Crate is used as a producer or consumer of Fruits.
Invariant class
class Crate<T>(private val elements: MutableList<T>) {
fun add(t: T) = elements.add(t) // Consumer allowed
fun last(): T = elements.last() // Producer allowed
}
Covariant classout
class Crate<out T>(private val elements: MutableList<T>) {
fun add(t: T) = elements.add(t) // Consumer not allowed: Error
fun last(): T = elements.last() // Producer allowed
}
Contravariant classin
class Crate<in T>(private val elements: MutableList<T>) {
fun add(t: T) = elements.add(t) // Consumer allowed
fun last(): T = elements.last() // Producer not allowed: Error
}
Use-site Variance
All these use-site projections are for the invariant class Crate<T> defined above.
No Projection
No subtyping allowed: Only the Crate<Fruit> can be assigned to a Crate<Fruit>.
fun main() {
val invariantCrate: Crate<Fruit> = Crate<Fruit>(mutableListOf(Fruit(), Orange()))
invariantCrate.add(Orange()) // Consumer allowed
invariantCrate.last() // Producer allowed
}
Covariant Projectionout
Subtyping allowed: Crate<CitrusFruit> can be assigned to Crate<Fruit> when CitrusFruit is a subtype of Fruit.
fun main() {
val covariantCrate: Crate<out Fruit> = Crate<CitrusFruit>(mutableListOf(Orange()))
covariantCrate.add(Orange()) // Consumer not allowed: Error
covariantCrate.last() // Producer allowed
}
Contravariant Projectionin
Subtyping allowed: Crate<CitrusFruit> can be assigned to Crate<Orange> when the CitrusFruit is a supertype of Orange.
fun main() {
val contravariantCrate: Crate<in Orange> = Crate<CitrusFruit>(mutableListOf(Orange()))
contravariantCrate.add(Orange()) // Consumer allowed
contravariantCrate.last() // Producer allowed: No Error?
}
Questions
Is my understanding and the use of type projection correct in the given example?
For contravariance: why is the last()(producer) function not allowed at declaration-site but allowed at use-site? Shouldn't the compiler show an error like it shows in the declaration-site example? Maybe I'm missing something? If the producer is allowed for contravariance only at use-site, what could be the use case for it?
I prefer detailed answers with examples but any kind input will be much appreciated.
Let's start with the use-site.
When you write
val contravariantCrate: Crate<in Orange> = ...
the right side could be a Crate<Orange>, Crate<Fruit>, Crate<Any?>, etc. So the basic rule is that any use of contravariantCrate should work if it had any of these types.
In particular, for all of them
contravariantCrate.last()
is legal (with type Orange, Fruit, and Any? respectively). So it's legal for Crate<in Orange> and has type Any?.
Similarly for covariantCrate; calling the consumer method technically is allowed, just not with Orange. The problem is that a Crate<Nothing> is a Crate<out Fruit>, and you couldn't do
val covariantCrate: Crate<Nothing> = ...
covariantCrate.add(Orange())
Instead the parameter type is the greatest common subtype of Fruit, CitrusFruit, Nothing, etc. which is Nothing. And
covariantCrate.add(TODO())
is indeed legal because the return type of TODO() is Nothing (but will give warnings about unreachable code).
Declaration-site in or out effectively say that all uses are in/out. So for a contravariant class Crate<in T>, all calls to last() return Any?. So you should just declare it with that type.
My guess is that the difference between declaration-site and use-site contravariance is that delcaration-site can be statically checked by the compiler, but when using projections there is always the original, unprojected object in existence at run-time. Therefore, it is not possible to prevent the creation of the producer methods for in projections.
When you write:
class Crate<in T>(private val elements: MutableList<T>) {
fun add(t: T) = elements.add(t) // Consumer allowed
fun last(): T = elements.last() // Producer not allowed: Error
}
The compiler can know at compile-time that no method on Crate<T> should exist that produces a T, so the definition of fun last(): T is invalid.
But when you write:
val contravariantCrate: Crate<in Orange> = Crate<CitrusFruit>(mutableListOf(Orange()))
What has actually been created is a Crate<Any?>, because generics are erased by the compiler. Although you specified that you don't care about producing an item, the generic-erased Crate object still exists with the fun last(): Any? method.
One would expect the projected method to be fun last(): Nothing, in order to give you a compiler-time error if you try to call it. Perhaps that is not possible because of the need for the object to exist, and therefore be able to return something from the last() method.
The following example is perfectly legal in Kotlin 1.3.21:
fun <T> foo(bar: T): T = bar
val t: Int = foo(1) // No need to declare foo<Int>(1) explicitly
But why doesn't type inference work for higher order functions?
fun <T> foo() = fun(bar: T): T = bar
val t: Int = foo()(1) // Compile error: Type inference failed...
When using higher order functions, Kotlin forces the call site to be:
val t = foo<Int>()(1)
Even if the return type of foo is specified explicitly, type inference still fails:
fun <T> foo(): (T) -> T = fun(bar: T): T = bar
val t: Int = foo()(1) // Compile error: Type inference failed...
However, when the generic type parameter is shared with the outer function, it works!
fun <T> foo(baz: T) = fun (bar: T): T = bar
val t: Int = foo(1)(1) // Horray! But I want to write foo()(1) instead...
How do I write the function foo so that foo()(1) will compile, where bar is a generic type?
I am not an expert on how type inference works, but the basic rule is: At the point of use the compiler must know all types in the expression being used.
So from my understanding is that:
foo() <- using type information here
foo()(1) <- providing the information here
Looks like type inference doesn't work 'backward'
val foo = foo<Int>()//create function
val bar = foo(1)//call function
To put it in simple (possibly over-simplified) terms, when you call a dynamically generated function, such as the return value of a higher-order function, it's not actually a function call, it's just syntactic sugar for the invoke function.
At the syntax level, Kotlin treats objects with return types like () -> A and (A, B) -> C like they are normal functions - it allows you to call them by just attaching arguments in parenthesis. This is why you can do foo<Int>()(1) - foo<Int>() returns an object of type (Int) -> (Int), which is then called with 1 as an argument.
However, under the hood, these "function objects" aren't really functions, they are just plain objects with an invoke operator method. So for example, function objects that take 1 argument and return a value are really just instances of the special interface Function1 which looks something like this
interface Function1<A, R> {
operator fun invoke(a: A): R
}
Any class with operator fun invoke can be called like a function i.e. instead of foo.invoke(bar, baz) you can just call foo(bar, baz). Kotlin has several built-in classes like this named Function, Function1, Function2, Function<number of args> etc. used to represent function objects. So when you call foo<Int>()(1), what you are actually calling is foo<Int>().invoke(1). You can confirm this by decompiling the bytecode.
So what does this have to do with type inference? Well when you call foo()(1), you are actually calling foo().invoke(1) with a little syntactic sugar, which makes it a bit easier to see why inference fails. The right hand side of the dot operator cannot be used to infer types for the left hand side, because the left hand side has to be evaluated first. So the type for foo has to be explicitly stated as foo<Int>.
Just played around with it a bit and sharing some thoughts, basically answering the last question "How do I write the function foo so that foo()(1) will compile, where bar is a generic type?":
A simple workaround but then you give up your higher order function (or you need to wrap it) is to have an intermediary object in place, e.g.:
object FooOp {
operator fun <T> invoke(t : T) = t
}
with a foo-method similar as to follows:
fun foo() = FooOp
Of course that's not really the same, as you basically work around the first generic function. It's basically nearly the same as just having 1 function that returns the type we want and therefore it's also able to infer the type again.
An alternative to your problem could be the following. Just add another function that actually specifies the type:
fun <T> foo() = fun(bar: T): T = bar
#JvmName("fooInt")
fun foo() = fun(bar : Int) = bar
The following two will then succeed:
val t: Int = foo()(1)
val t2: String = foo<String>()("...")
but... (besides potentially needing lots of overloads) it isn't possible to define another function similar to the following:
#JvmName("fooString")
fun foo() = fun(bar : String) = bar
If you define that function it will give you an error similar as to follows:
Conflicting overloads: #JvmName public final fun foo(): (Int) -> Int defined in XXX, #JvmName public final fun foo(): (String) -> String defined in XXX
But maybe you are able to construct something with that?
Otherwise I do not have an answer to why it is infered and why it is not.
I have a val built like this
val qs = hashMapOf<KProperty1<ProfileModel.PersonalInfo, *> ,Question>()
How can I obtain the class of ProfileModel.PersonalInfo from this variable?
In other words what expression(involving qs of course) should replace Any so that this test passes.
#Test
fun obtaionResultTypeFromQuestionList(){
val resultType = Any()
assertEquals(ProfileModel.PersonalInfo::class, resultType)
}
Thank you for your attention
There is no straight way to get such information due to Java type erasure.
To be short - all information about generics (in your case) is unavailable at runtime and HashMap<String, String> becomes HashMap.
But if you do some changes on JVM-level, like defining new class, information about actual type parameters is kept. It gives you ability to do some hacks like this:
val toResolve = object : HashMap<KProperty1<ProfileModel.PersonalInfo, *> ,Question>() {
init {
//fill your data here
}
}
val parameterized = toResolve::class.java.genericSuperclass as ParameterizedType
val property = parameterized.actualTypeArguments[0] as ParameterizedType
print(property.actualTypeArguments[0])
prints ProfileModel.PersonalInfo.
Explanation:
We define new anonymous class which impacts JVM-level, not only runtime, so info about generic is left
We get generic supperclass of our new anonymous class instance what results in HashMap< ... , ... >
We get first type which is passed to HashMap generic brackets. It gives us KProperty1< ... , ... >
Do previous step with KProperty1
Kotlin is tied to the JVM type erasure as well as Java does. You can do a code a bit nice by moving creation of hash map to separate function:
inline fun <reified K, reified V> genericHashMapOf(
vararg pairs: Pair<K, V>
): HashMap<K, V> = object : HashMap<K, V>() {
init {
putAll(pairs)
}
}
...
val hashMap = genericHashMapOf(something to something)
To initialize my logger apparently I need:
val LOGGER : Logger = LoggerFactory.getLogger(Foo::class.java);
If I do:
val LOGGER : Logger = LoggerFactory.getLogger(Foo::javaClass);
It complains that the parameter type is not compatible with getLogger. However according to the API, both are Class<Foo>. How are they different?
The javaClass is an extension property that returns the runtime Java class of an instantiated object. In your case, it is being used as a property reference, which will give you a KProperty1<Foo, Class<Foo>> representing the extension function itself:
val T.javaClass: java.lang.Class<T>
You could use this in combination with a receiver, e.g. if Foo provided a default constructor you could say:
Foo::javaClass.get(Foo())
which may be simplified to:
Foo().javaClass
Using ::class.java on the other hand, gives you the Java Class<?> as described in "class references" directly. All three possibilities in a simple example:
val kProperty1: KProperty1<Foo, Class<Foo>> = Foo::javaClass
kProperty1.get(Foo()) //class de.swirtz.kotlin.misc.Foo
Foo::class.java //class de.swirtz.kotlin.misc.Foo
Foo().javaClass //class de.swirtz.kotlin.misc.Foo
javaClass is an extension property which returns the runtime Java class of an object.
/**
* Returns the runtime Java class of this object.
*/
public inline val <T: Any> T.javaClass : Class<T>
#Suppress("UsePropertyAccessSyntax")
get() = (this as java.lang.Object).getClass() as Class<T>
It can be called on an instance of a class, for example:
println(Foo().javaClass) //class Foo
However, Foo::javaClass give you a property reference of type KProperty1<Foo, Class<Foo>> instead of a Java class instance which can be used to get the class of an instance of Foo through reflection:
val p: KProperty1<Foo, Class<Foo>> = Foo::javaClass
println(p.get(Foo())) //p.get(Foo()) returns a Java class Foo
Therefore, it is wrong to pass a KProperty to LoggerFactory.getLogger() which accepts a Java class.
Foo::javaClass is a reference to a val defined as
inline val <T : Any> T.javaClass: Class<T>
So you'd have to call it on an instance of Foo like foo.javaClass.
Foo::class gives you the actual KClass of Foo and java is a property of KClass defined as
val <T> KClass<T>.java: Class<T>