I have a somewhat weird question. say I have follo...
# announcements
c
I have a somewhat weird question. say I have following:
Copy code
fun <E : Enum<E>> foo(clazz: Class<E>): Value<E> = TODO()

fun <T> bar(clazz: Class<T>) {
    if (Enum::class.java.isAssignableFrom(clazz)) {
        foo(clazz) //fails since T is not Enum<T>
    }
}
how do I call foo here? I tried
foo<Enum<*>>(clazz as Class<Enum<*>>)
but it fails since * is also not Enum<*> and it just keeps going recursively. In java I'd just use a raw Enum type, but I can't do that here
d
clazz as Class<Enum<T>>
?
c
nope. I think the issue is that * doesn't work properly with a recursive upper bound. For now I just moved the unsafe call to java code
m
The code as it is makes not sense. Yet here a way to make it compile:
Copy code
fun <T> bar(clazz: Class<T>) {
	if (Enum::class.java.isAssignableFrom(clazz)) {
		foo(clazz as Class<Nothing>)
	}
}
Nothing
is a (theoretical) subclass of every type.
c
this is a minimal example of what I'm doing in my actual code, so I don't intend for it to make sense beyond "call a function if T is enum". And yes, Nothing technically works, but I'd prefer not to use that approach, since it's just abusing type erasure and can cause failed casts or, even better, wrong compiler optimizations since Kotlin assumes that any point where a Nothing value is available will never be executed, and removes any code after that as unreachable
e.g. in this hypothetical case
Copy code
fun <E : Enum<E>> foo(a: () -> E) = a()

fun <T> bar(b: () -> T): T {
    return foo(b as (() -> Nothing))
}
bar will actually throw an exception since it assumes foo can never actually return a value, so it replaces the return with
throw null
m
You can use any random enum class instead of
Nothing
if you're uncertain. In your initial
bar
function you've lost some generic type information. You cannot get around an unsafe cast in that situation. if you want to have a safe cast, use
T: Enum<T>
in
bar
- but that makes
Enum::class.java.isAssignableFrom
unnecessary and is probably not your use case.
c
yeah, the problem in my use case is that compile-time info on whether it's an enum or not was lost earlier up the call chain. I think I found a way to rewrite that to avoid it, but the question still bothers me a bit
and using a different enum would still have some of the issues, since casting A to B could fail at a couple points even when not intended. I guess it's just a sign to rethink what you're doing at that point 🤷
m
It's how you have to work with generics in a non-generic context.
With recursive type parameters there are only two options in such cases: • cast to generic Nothing (but make sure that no
Nothing
value is actually used at the generic site) • cast to another type which fulfills the same recursive constraint The latter should not cause any issues because at runtime it's all
Any
anyway.
s
... but make sure that no
Nothing
value is used ...
Thankfully, that is super easy: There are no values of type
Nothing
. It's impossible to use/create/receive one.
m
t
There is a difference between: fun <E: Enum<E>> foo(clazz: Class<E>) and fun <E: Enum<*>> foo(clazz: Class<E>) The first method will only accept concrete Enum classes whereas the second one will accept any Enum class. example: val concrete: Class<TimeUnit> = TimeUnit::class.java val notErased: Class<Enum<TimeUnit>> = TimeUnit::class.java as Class<Enum<TimeUnit>> val erased: Class<Enum<*>> = TimeUnit::class.java as Class<Enum<*>> foo(concrete) foo(notErased) //won't compile foo(erased) //won't compile Since the type parameter in the Enum class describes itself it should be enough for foo to accept any Class that is an Enum. The implementaion below just does that. fun <E : Enum<*>> foo(clazz: Class<E>) { println("foo") } fun <T> bar(clazz: Class<T>) { if (Enum::class.java.isAssignableFrom(clazz)) { println("bar: enum") foo(clazz as Class<Enum<*>>) } else { println("bar: not an enum") } } (The cast in bar is marked as unchecked by my IDE but we do check it.) (I have no idea how to mark something as a code block)
s
@tDnamein (to have a clode block, use three back-ticks in a row as the first line and three back-ticks in a row as the last line)
👍 1