How to handle nullable generics with Java interop - kotlin

I have a Java class that is out of my control, defined as:
public #interface ValueSource {
String[] strings() default {}
}
I am trying to use this class from a Kotlin file I control, like so:
class Thing {
#ValueSource(string = ["non-null", null])
fun performAction(value: String?) {
// Do stuff
}
}
I get a compiler error
Kotlin: Type inference failed. Expected type mismatch: inferred type is Array<String?> but Array<String> was expected.
I understand why the inferred type is Array<String?>, but why is the expected type not the same? Why is Kotlin interpreting the Java generic as String! rather than String?? And finally, is there a way to suppress the error?
Kotlin 1.2.61

This isn't a Kotlin issue - this code isn't valid either, because Java simply doesn't allow null values in annotation parameters:
public class Thing {
#ValueSource(strings = {"non-null", null}) // Error: Attribute value must be constant
void performAction(String value) {
// Do stuff
}
}
See this article and this question for more discussion on this.

Related

Having trouble with type erasure

I have something like this :
import kotlin.reflect.KClass
class Quantity<T> {
/* ... */
}
class Field<T : Any> {
val type: KClass<T> get() = TODO("This is initialized, don't worry about implentation details, just know that fields know their type.")
fun initValue(value: T) {
/* Do something very useful */
}
/* Other methods */
class Template<T : Any> {
fun initFieldWithValue(value: T): Field<T> {
return Field<T>().apply {
this.initValue(value)
}
}
}
}
class ComponentClass(
val fieldsTemplates: Map<String, Field.Template<*>>
) {
inner class Instance(field: Map<String, Field<*>>)
fun new(fieldValues: Map<String, Quantity<*>>): Instance {
val fields = mutableMapOf<String, Field<*>>()
for ((fieldName, template) in fieldsTemplates) {
fields[fieldName] = fieldsTemplates
.getValue(fieldName)
.initFieldWithValue(fieldValues.getValue(fieldName) /* Here a type error */)
}
return Instance(fields)
}
}
As you might guess, this is intended to work as a 'runtime way' of creating classes that own fields (Field<T> class), each one possessing a typed value (represented by a Quantity<T>).
The problem is that this code won't compile due to the fact that the quantity retrieved from fieldValues when creating the different fields of the future Instance in the new method isn't guaranteed to be of the required type for the field it is stuffed into.
The problem is that I would need a check since filling a Field<Quantity<String>> with a Quantity<Int> is obviously not a good idea, but because of the type erasure I cannot ensure that the quantities passed in are of the good type.
Any idea ? One more thought : Fields know what their type is thanks to their type attribute, but unfortunately I can't do the same for the Quantity class...
Your initFieldWithValue function is enforcing the type of the parameter to match the type known by the Template/Field. But inside your new function, your Template is a Template<*> since you retrieve it from a collection where the values are of this type.
The point of generics is to enforce compile time checks so casting can be done safely and automatically under the hood. This is only useful when your type is known at compile time. In this case, the type is not known at compile time, so the generics are preventing your code from compiling. This is what generics are supposed to do: prevent code from compiling if the compiler cannot check that they types match.
If you want this code to compile, you should change initFieldWithValue so it doesn't enforce generics. You can instead manually check the type and throw an error or exit early if it's incorrect. It will be up to your code elsewhere to ensure you aren't mixing and matching types.
Here's an example of a version that would work. The type check it does requires the Kotlin reflection library. If you're targeting JVM only, you can use the Java Class.isAssignableFrom method instead to do this check.
class Template<T : Any> {
val type: KClass<T> get() = TODO()
/**
* #throws IllegalStateException if [value] is not of the same type
* as this Template's [type].
*/
fun initFieldWithValue(value: Any): Field<T> {
if (!value::class.isSubclassOf(type)) {
error("Invalid value type for Field type of $type")
}
return Field<T>().apply {
#Suppress("UNCHECKED_CAST") // we manually checked it above
initValue(value as T)
}
}
}

Kotlin incorrectly infers nullable Enum return type when a function call is passed as an argument to another function

I may not have done a good job explaining the problem in the title, but here's an example:
fun main() {
acceptEnum(inferType())
}
fun acceptEnum(value: MyEnum?) {}
fun <R : Enum<R>?> inferType(): R = TODO()
enum class MyEnum {
VALUE
}
inferType() function infers its return type and bounds it to be a generic nullable enum. acceptEnum() function has a nullable enum parameter. When we write acceptEnum(inferType()), everything's fine. But if we add one more parameter to acceptEnum() and pass inferType() there again, here's what happens:
fun main() {
// first inferType() does not compile with an error:
// Type mismatch: inferred type is MyEnum? but MyEnum was expected
acceptEnum(inferType(), inferType())
}
fun acceptEnum(value: MyEnum?, value2: MyEnum?) {}
If we add more parameters, every inferType() call except the last one produces this error.
Is this a compiler bug or am I doing something wrong?
Update
Kotlin forum post: https://discuss.kotlinlang.org/t/kotlin-incorrectly-infers-nullable-enum-return-type-when-a-function-call-is-passed-as-an-argument-to-another-function/23650
Update
Kotlin issue https://youtrack.jetbrains.com/issue/KT-50232

How to cast generic type after isAssignableFrom check in Kotlin?

See the example:
class MyTypeAdapter<T : Throwable>
(private val gson: Gson, private val skipPast: TypeAdapterFactory) : TypeAdapter<T>() {
// :Throwable is needed to access the stackTrace field
}
private class ThrowableTypeAdapterFactory : TypeAdapterFactory {
override fun <T> create(gson: Gson, typeToken: TypeToken<T>): TypeAdapter<T>? {
if (Throwable::class.java.isAssignableFrom(typeToken.rawType)) {
return MyTypeAdapter<T>(gson, this) // compile error: Type argument is not within its bound
}
return null
}
}
So in Java we have raw use parameterized class but Kotlin doesn't allow it anymore.
I tried to find something from https://kotlinlang.org/docs/reference/generics.html
but couldn't get a clue. Please advice.
You should be able to cheat thanks to type erasure:
return MyTypeAdapter<Throwable>(gson, this) as MyTypeAdapter<T>
It looks wrong, but the class can't actually do anything different depending on T.
Or if Kotlin won't accept this cast directly (can't check at the moment) something like
return (MyTypeAdapter<Throwable>(gson, this) as MyTypeAdapter<*>) as MyTypeAdapter<T>
or even
return (MyTypeAdapter<Throwable>(gson, this) as Any) as MyTypeAdapter<T>
should work.

kotlinFunction returns null if method is defined on an interface with a type parameter

A quick demo of a problem:
import kotlin.reflect.jvm.kotlinFunction
interface A<T> {
fun aaa(t: T): String {
return ""
}
}
class B : A<String>
fun main() {
println(B::class.java.methods[0].kotlinFunction) // returns null
}
Calling kotlinFunction on a method without type parameter returns an instance of KFunction as expected.
The reason is type erasure, that occurs in Java, but not in Kotlin:
Java:
public java.lang.String B.aaa(java.lang.Object)
Kotlin:
public java.lang.String B.aaa(java.lang.String)
https://github.com/JetBrains/kotlin/blob/master/core/reflection.jvm/src/kotlin/reflect/jvm/ReflectJvmMapping.kt#L134
Note that it's just Kotlin compiler preserving some more information for reflection, types will be still erased by JVM at runtime, Kotlin or not.
If you need to access Kotlin method, do it directly:
println(B::class.functions.first())

How to create compile-time constant in Kotlin from enum?

I have an annotation that requires defaultValue to be compile-time constant. I take defaultValue from enum below:
enum class RaceType {
MARATHON,
SPRINT;
companion object {
fun apply(type: RaceType): RaceDto {
return when (type) {
MARATHON -> MarathonDto()
SPRINT -> SprintDto()
}
}
}
}
My dtos are the following:
interface RaceDto {
}
data class MarathonDto: RaceDto
data class SprintDto: RaceDto
when I use annotation #QraphQLArgument(defaultValue = RaceType.SPRINT.name) Kotlin requires RaceType.SPRINT.name to be compile-time constant.
Annotation implementation itself:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.PARAMETER})
public #interface GraphQLArgument {
String NONE = "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
String NULL = "\n\t\t\n\t\t\n\ue000\ue001\ue002\ue003\n\t\t\t\t\n";
String name();
String description() default "";
String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
Class<? extends DefaultValueProvider> defaultValueProvider() default JsonDefaultValueProvider.class;
}
I looked through similar questions but don't see a way how it can be resolved. I also found article related to the topic but nothing worked so far.
Side note: I cannot change annotation since it is from the library and I cannot change the library as well.
To summarize, is there a way to make from enum compile-time constant in Kotlin to use in an annotation?
is there a way to make from enum compile-time constant in Kotlin to use in an annotation?
No, because formally enums aren't compile-time constants in Java.
However please consider the sealed classes:
sealed class RaceType {
object MARATHON: RaceType() {
const val name = "MARATHON" // copy-paste is required here until https://youtrack.jetbrains.com/issue/KT-16304
}
object SPRINT: RaceType()
companion object {
fun apply(type: RaceType): RaceDto {
return when (type) { // the check is in compile time, because of sealed class
MARATHON -> MarathonDto()
SPRINT -> SprintDto()
}
}
}
}
A little part of copy-paste is still required. Please vote on kotlin compiler bug or follow this thread.
However, as I understand, this doesn't solve your issue with #QraphQLArgument(defaultValue = RaceType.SPRINT.name) unfortunately, because the name of class is not the same with value. In the other words, with sealed classes you need to write code to convert input strings to them.