How do I call a method in Kotlin with a different upper bound? - kotlin

e.g. Given a Class<T> how do I call/invoke a method/constructor that requires Class<T> where T : Enum<T>?
fun <T : Any> handleAny(classOfT: Class<T>) {
if (classOfT.isEnum) {
handleEnum(classOfT)
}
}
fun <T : Enum<T>> handleEnum(classOfT: Class<T>) { /*...*/ }
Error: inferred type T is not a subtype of kotlin.Enum<T>
In Java I can do an unchecked call but I cannot seem to find a way to do anything similar in Kotlin.

As for now I found this quite hacky workaround for it:
private enum class DummyEnum
fun <T> handleAny(classOfT: Class<T>) {
if (classOfT.isEnum) {
handleEnum(classOfT as Class<DummyEnum>) //absolutely any enum goes
}
}
fun <T : Enum<T>> handleEnum(classOfT: Class<T>) {
println(classOfT.toString())
}
The idea is to make an unchecked cast to the type with any generic parameter satisfying the upper bound (let it be DummyEnum), which will then be erased at runtime anyway.
The limitation is that the solution doesn't work correctly with reified generics: if handleEnum had reified type parameter, it would be substituted for statically inferred type (DummyEnum).

Related

Dagger and Kotlin - Issue with binding a class to its generic supertype

I'm banging my head against the wall right now because I can't figure this out.
I have a generic Interface called Mapper which has two generic type parameters. Now I want to leverage multibinding and bind multiple implementations of this interface into a map of type Map<Class<out Any>, Provider<Mapper<Any, Any>>. My code looks as follows:
interface Mapper<DTO, Entity> {
fun toEntity(model: DTO): Entity
fun toDto(model: Entity): DTO
}
class PersistedIntakeEntryMapper #Inject constructor() : Mapper<PersistedIntakeEntry, IntakeEntry> {
override fun toEntity(model: PersistedIntakeEntry): IntakeEntry { TODO() }
override fun toDto(model: IntakeEntry): PersistedIntakeEntry { TODO() }
}
#Module
interface MapperModule {
#Binds
#IntoMap
#MapperKey(PersistedIntakeEntry::class)
#ModelMappers
fun bindPersistedIntakeEntryMapper(mapper: PersistedIntakeEntryMapper): Mapper<Any, Any>
}
#Singleton
class MapperFactory #Inject constructor(
#ModelMappers val mappers: Map<Class<out Any>, #JvmSuppressWildcards Provider<Mapper<Any, Any>>>,
) {
#Suppress("UNCHECKED_CAST")
inline fun <reified DTO: Any, Entity> get(): Mapper<DTO, Entity>? {
TODO()
}
}
Dagger is specifically complaining that PersistedIntakeEntryMapper is not assignable to Mapper<Any, Any>: MapperModule.java:13: error: #Binds methods' parameter type must be assignable to the return type.
However: the curious thing is that I have the same setup for another component which works like a charm:
interface ViewModelFactory<VM : ViewModel, SavedState, Parameters> {
fun create(savedState: SavedState?, parameters: Parameters?): VM
}
class SetCalorieGoalViewModelFactory #Inject constructor(
private val getCalorieGoalUseCase: GetCalorieGoalUseCase,
private val setCalorieGoalUseCase: SetCalorieGoalUseCase,
private val navigator: Navigator,
) : ViewModelFactory<SetCalorieGoalViewModel, SetCalorieGoalUiState, Nothing> {
override fun create(savedState: SetCalorieGoalUiState?, parameters: Nothing?): SetCalorieGoalViewModel {
TODO()
}
}
#Module
interface SetCalorieGoalUiModule {
#Binds
#IntoMap
#ViewModelKey(SetCalorieGoalViewModel::class)
fun bindSetCalorieGoalViewModelFactory(factory: SetCalorieGoalViewModelFactory)
: ViewModelFactory<ViewModel, Any, Any>
}
I can bind the SetCalorieGoalViewModelFactory to the ViewModelFactory<SetCalorieGoalViewModel, Any, Any> type without issue. What is the difference between these setups that makes one of them work and the other one not? I can't figure it out for the life of me. Big thanks in advance to anyone trying to solve this problem.
First of all, check out kotlin documentation on the generic variance topic as well as the related java topics (since dagger generates java code).
Generally the issue is that Mapper<PersistedIntakeEntry, IntakeEntry> and Mapper<Any, Any> are invariant, meaning that one is not subtype of the other. Basically this assignment val mapper: Mapper<Any, Any> = PersistedIntakeEntryMapper() will not compile and that's what dagger tells you. And that makes sense, since Mapper<Any, Any> must be able to map Any to Any and that's obviously not the case with PersistedIntakeEntryMapper - it expects PersistedIntakeEntry and IntakeEntry.
Following the documentation above, it would be possible if your declaration had out modifier specified like interface Mapper<out DTO, out Entity>, but that will not work in your case, since you have your type arguments in in positions.
The interesting question is why it works with ViewModelFactory. It seems to be a bug in KAPT, it just omits generic type parameters in the generated code when it sees Nothing. It makes it bypass the compiler checks (but it does not make it safe to use at runtime!), since generics are mostly compile-time things (see type erasure in java).

Casts of generic classes in mutableMap

In my application I want to do something like this:
interface Binder<in VB: ViewBinding, in T: Any> {
fun bind(binding: VB, item: T)
}
class TypeInfoMap {
val map = mutableMapOf<Class<out Any>, Binder<ViewBinding, Any>>()
inline fun <reified VB: ViewBinding, reified T: Any> put(binder: Binder<VB, T>) {
map[T::class.java] = binder as Binder<ViewBinding, Any> // warning unchecked cast
}
inline fun <reified VB: ViewBinding, reified T: Any> get(cls: Class<T>): Binder<VB, T> {
return map[cls] as? Binder<VB, T> ?: throw IllegalStateException() // no warning
}
}
I get the warning unchecked cast in the put function. Why is that? I declared upper bounds for the generic types, shouldn't the cast be fine here? Also the cast in the get function does not produce any warning, even when I don't inline the function. I would have thought I would get a warning here and I'm actually surprised that I don't get one.
Is there a way in Kotlin to write all of this without warnings? I thought that's what reified is for.
First of all, it is incorrect to cast from Binder<VB, T> to Binder<ViewBinding, Any>. If binder is defined as a Binder<VB, T>, you can call binder.bind() with a VB instance, but not with ViewBinding instances that are not VBs. So a Binder<VB, T> is not a Binder<ViewBinding, Any>.
Second, the unchecked cast warning is not about whether the cast is valid or not. It's about the fact that you won't get a ClassCastException at runtime if the type is not correct. This is why it's dangerous.
You probably don't get an unchecked cast warning in the get() method because the cast is always valid anyway: a Binder<ViewBinding, Any> is always a Binder<VB, T> given the variance of Binder and the declared parent types of VB and T.
Is there a way in Kotlin to write all of this without warnings? I thought that's what reified is for.
reified allows to access the generic types at runtime, but only those of the reified function. So for instance they allow you to get the KClass of some instance without explicitly passing it. However, the internal map you're using will still give no information at runtime about what you put in it in the past.
The best you can do is ignore the unchecked cast warning because you won't be able to know the generic types contained in the map at runtime. However, you have a more type-safe approach if you make the map private because you can control what you put in it:
class TypeInfoMap {
private val map = mutableMapOf<Key<*, *>, Binder<*, *>>()
class Key<VB : ViewBinding, T : Any>(
val bindingClass: KClass<VB>,
val valueClass: KClass<T>,
)
fun <VB : ViewBinding, T : Any> put(key: Key<VB, T>, binder: Binder<VB, T>) {
map[key] = binder
}
#Suppress("UNCHECKED_CAST") // types are guaranteed by put()
fun <VB : ViewBinding, T : Any> get(key: Key<VB, T>): Binder<VB, T> {
val binder = map[key] ?: error("No binding of type ${key.bindingClass} found for class ${key.valueClass}")
return binder as Binder<VB, T>
}
inline fun <reified VB: ViewBinding, reified T: Any> put(binder: Binder<VB, T>) {
put(Key(VB::class, T::class), binder)
}
inline fun <reified VB: ViewBinding, reified T: Any> get(): Binder<VB, T> = get(Key(VB::class, T::class))
}
You can then use it safely with nice reified types:
val infoMap = TypeInfoMap()
val someBinder: Binder<MyViewBinding, MyType> = createSomeBinderSomewhere()
infoMap.put(someBinder)
// guaranteed type here (or runtime error if no binding of those types is found)
val binder = infoMap.get<MyViewBinding, MyType>()

Can I omit type in generics? - Kotlin

If I have a following interface:
interface BaseDataRemote<T, in Params> {
fun getData(params: Params? = null): Single<T>
}
Would it be possible have implementation of this interface that does not take Params?
To have effectively something like:
interface BaseDataRemote<T> {
fun getData(): Single<T>
}
Implementation is as follows:
class RemoteSellerDataSource #Inject constructor(
private val sellerApi: SellerApi,
#Named("LANG") private val lang: String
) : BaseDataRemote<SellerEntity, Nothing> {
override fun getData(params: Nothing?): Single<SellerEntity> {
return sellerApi.getSeller(lang).map { it.fromApiEntity() }
}
}
I use Dagger 2 to module to bind this implementation:
#Module
internal interface RemoteModule {
#Binds
#CoreScope
fun bindsSellerRemote(remoteSellerDataSource: RemoteSellerDataSource): BaseDataRemote<SellerEntity, Nothing>
}
I tried using Nothing as second type parameter, but it does not seem to work
(I'm getting required: class or interface without bounds error
Full error message:
RemoteSellerDataSource.java:6: error: unexpected type
public final class RemoteSellerDataSource implements com.bigchangedev.stamps.business.sdk.data.base.data.BaseDataRemote<SellerEntity, ?> {
^
required: class or interface without bounds
found:?
Thanks.
EDIT: the original answer was a pure Kotlin answer because the OP didn't mention Dagger.
Using Nothing is correct and works in pure Kotlin. However, Dagger seems to convert your code to Java, and in doing so it uses wildcards for the generics (which it doesn't like because it wants exact type matches). To avoid this issue, you can try using #JvmSuppressWildcards on your generic type parameters:
class RemoteSellerDataSource #Inject constructor(
private val sellerApi: SellerApi,
#Named("LANG") private val lang: String
) : BaseDataRemote<SellerEntity, #JvmSuppressWildcards Nothing> {
override fun getData(params: Nothing?): Single<SellerEntity> {
return sellerApi.getSeller(lang).map { it.fromApiEntity() }
}
}
Although I'm not sure what will happen in Java with Nothing in that case. I guess this should have the same effect on the Java code as removing the in variance for the second type param in the interface declaration, but without weakening your Kotlin types.
Another workaround would be to use Unit instead of Nothing, which Dagger will most likely convert to Void in this case. This is not great for your types, though.
Original answer:
You can technically already call getData() without arguments thanks to the default value. An implementation that doesn't care about the params argument can simply expect null all the time.
The Kotlin type that only contains null and no other value is technically Nothing?, and since getData is defined with Params? (note the ?) as input, it should be correct to specify Nothing (even without ?) as second type argument. So you should be able to define an implementation like this:
interface BaseDataRemote<T, in Params> {
fun getData(params: Params? = null): Single<T>
}
class ImplementationWithoutParams<T> : BaseDataRemote<T, Nothing> {
override fun getData(params: Nothing?): Single<T> {
// params will always be null here
}
}
To avoid confusion for the users, this implementation may additionally provide a getData() method without arguments at all:
class ImplementationWithoutParams<T> : BaseDataRemote<T, Nothing> {
override fun getData(params: Nothing?): Single<T> = getData()
fun getData(): Single<T> {
TODO("implementation")
}
}

why the translated kotlin code complains about a Array<BaseData>? to be a Array<out BaseData>

Having a java class, using androidStudio to translate to kotlin.
Got a error and not sure how to correctly translate it.
The java code:
public class BaseDataImpl extends BaseData {
private final BaseData[] translators;
public BaseDataImpl(final BaseData... translators) {
this.translators = cloneArray(translators);
}
public static <T> T[] cloneArray(final T[] array) {
if (array == null) {
return null;
}
return array.clone();
}
}
after the code translation, got error: required Array<BaseData>?, found Array<out BaseData>, but the translators in the cloneArray<BaseData>(translators) call is defined as val translators: Array<BaseData>?,
anyone could help to explain?
class BaseDataImpl(vararg translators: BaseData) : BaseData() {
private val translators: Array<BaseData>?
init {
this.translators = cloneArray<BaseData>(translators) //<=== error: required Array<BaseData>?, found Array<out BaseData>
}
companion object {
fun <T> cloneArray(array: Array<T>?): Array<T>? {
return array?.clone()
}
}
}
It is written in the Kotlin function reference regarding varargs:
Inside a function a vararg-parameter of type T is visible as an array of T, i.e. the ts variable in the example above has type Array<out T>.
where the referenced function was:
function <T> asList(vararg ts: T): List<T>
So in your case you actually pass an Array<out BaseData> but you only accept an array of type Array<T>? (in your case Array<BaseData>). Either you adapt all of the types to Array<out T> (which basically is similar as saying List<? extends BaseData> in Java) or you take care that you are only dealing with Ts instead, e.g. with:
inline fun <reified T> cloneArray(array: Array<out T>?): Array<T>? {
return array?.clone()?.map { it }?.toTypedArray()
}
But look up the documentation regarding this: Kotlin generics reference - type projections. You probably can accomplish this even easier.

kotlin: list properties of any object - variance error

I'm trying to write a function that produces map of properties and values for any type
inline fun <reified T : Any> T.propertiesMap() {
for (property in this::class.memberProperties) {
property.get(this)
}
}
i get a compilation error in property.get(this) about
out-projected type [...] prohibits the use of 'public abstract fun get(receiver...
The issue is that this::class produces a KClass<out T> instead of KClass<T> which is what would be needed to use anything of type T in the property.get(...) call. So you can do an unchecked cast to do what you want:
fun <T : Any> T.propertiesMap() {
#Suppress("UNCHECKED_CAST")
for (property in (this::class as KClass<T>).memberProperties) {
property.get(this)
}
}
Which does not require the function to be inline nor reified type parameter. Otherwise you can change your function to use T::class instead of this::class to create a matching KClass<T>.
inline fun <reified T : Any> T.propertiesMap() {
for (property in T::class.memberProperties) {
property.get(this)
}
}
If you use the type you are reifying rather than an instance of that type the variance issue will go away. When you call T::class.memberProperties you get back a Collection<KProperty<T, *>> which is what I believe you want. On the other hand, if you call that on an instance (this) rather than a type, you get back a Collection<KProperty<out T, Any?>>, which is where your out-variance issue comes from.
inline fun <reified T : Any> T.propertiesMap() {
for (property in T::class.memberProperties) {
property.get(this)
}
}
Essentially, you need to do T::class rather than this::class in order to get the right kind of collection back. I've left your code as-is otherwise because I'm not clear on what you want this function to do, but I suspect you could drop the for loop in favor of a map call.