In Kotlin 1.5-RC I am unable to cast `Class<*&g...
# compiler
j
In Kotlin 1.5-RC I am unable to cast 
Class<*>
 to 
Class<Enum<*>>
  and use if in Java function requiring 
public static <T extends Enum<T>> EnumJsonAdapter<T> create(Class<T> enumType) {
Should I do it somehow differently, or is it a bug?
d
Class<Enum<Nothing>>
j
You mean like this? Still an issue.
Copy code
val enumClass = rawType as Class<Enum<Nothing>>

EnumJsonAdapter.create(enumClass)
v
It’s intended because
Enum<*>
isn’t subtype of
Enum<Enum<*>>
(you try to pass
Class<Enum<*>>
into Java method with bound on
T
Enum<T>
). You should introduce a type variable with the same bound:
Copy code
fun <T: Enum<T>> foo(enumClass: Class<T>) {
    EnumJsonAdapter.create(enumClass)
}
j
We are using casting because this triggered code is not generic. So ideally I'd love to be able to cast that. All I have is java.lang.Type or Class<?>.
We are creating instance of https://github.com/square/moshi/blob/master/adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java from our annotation which is defined above enum.
v
Unfortunately, in your initial variant, there was a type safety problem, that’s why the compiler has started reporting an error on such calls. So this place should be rewritten: you should pass either some specific instance of enum class or value of
Enum<T>
type, where
T
is bounded by
Enum<T>
(as in the example I wrote about above).
j
Simply said I have
Class<*>
and need to cast it to
Class<Enum<*>>
. I do not have an option making the code generic. I'm ok with runtime error when the class is not a class of enum.
v
BTW there is another option (only if you aren’t going to write something of type
Enum<*>
) – make it use site covariant: try casting to
Class<out Enum<*>>
.
j
That does not work. Trying to hack it with Java:
Copy code
class Helper {
  @SuppressWarnings("rawtypes")
  public static <T extends Enum<T>> EnumJsonAdapter<T> createEnumJsonAdapter(Class<?> rawType) {
    // noinspection unchecked
    return EnumJsonAdapter.create((Class<? extends Enum>) rawType);
  }
}
but that doesn't work either, since I am unable to tell what is the resulting generic type in Kotlin:
Simply said it seems there is no way to type non generic Enum.
j
cc @Zac Sweers
d
This is definitely a regression, in 1.4
Enum<Nothing>
is valid, in 1.5 it is not.
v
Simply said it seems there is no way to type non generic Enum.
It’s possible. Here the point is, what constraints are imposed on the declaration site of what you call (in your case, the call site does not comply with the constraints imposed on the declaration site). BTW out projection should work. Probably the problem is in something else. Look at three examples showing using enums including non-generic (out-projected):
Copy code
fun <E: Enum<E>> foo(x: Class<E>) {}

fun <T: Enum<T>> main(x: Class<Enum<*>>, y: Class<out Enum<*>>, z: Class<T>) {
    foo(x) // error because we pass Enum<*> as E but E has upper bound Enum<E> – Enum<*> doesn't have super type Enum<Enum<*>>
    foo(y) // OK
    foo(z) // OK
}
z
@Jan Skrasek move your typed parts to a generic function like this
Copy code
internal object DefaultToUnknownEnumJsonAdapterFactory : JsonAdapter.Factory {
  override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<*>? {
    if (annotations.isNotEmpty()) return null
    val rawType = Types.getRawType(type)
    if (!rawType.isEnum) return null
    if (!rawType.isAnnotationPresent(JsonClass::class.java)) return null
    @Suppress("UNCHECKED_CAST")
    return createAdapter(rawType as Class<out Enum<*>>)
  }
  // Separate function in order to get generics to play nice
  private fun <T : Enum<T>> createAdapter(clazz: Class<T>): JsonAdapter<T> {
    val unknown = clazz.enumConstants[0]
    check(unknown.name == "UNKNOWN") {
      "@JsonClass-annotated enums must reserve their first member as 'UNKNOWN'"
    }
    return EnumJsonAdapter.create(clazz)
      .withUnknownFallback(unknown)
      .nullSafe()
  }
}
❤️ 1
that works for us on 1.5.0-RC