Search code examples
kotlingenericswildcard

Calling generic API method from a generic class


I'm relatively new to the Kotlin community and found this apparently simple error that I haven't been able to solve yet:

import com.akuleshov7.ktoml.Toml
import com.akuleshov7.ktoml.source.decodeFromStream
import java.io.File
import java.io.InputStream

class Something <out T> {
    private val file = File("file.toml").inputStream()
    private val content = Toml.decodeFromStream<T>(file)
}

Apparently Toml.decodeFromStream<T>(file) cannot use the wildcard from the class or it will return the error Cannot use T as reified type parameter. Use a class instead

I kind of found a workaround by implementing this function:

private inline fun <reified R : T> call() {
    // ...
}

Still that forces me to include the type when calling the function and even if it doesn't I still need to do this in the constructor. Has anyone found this error before?

I'm starting to think that what I want (Using the class wildcard in the method decodeFromStream) might not be possible in the constructor, o I just want to know if it's indeed not possible or, if it is, what should I change?


Solution

  • If syntax is important to you, you can spoof a constructor with the invoke() operator function, and use the method that @broot mentioned in his comment to get something like this. I've added a path constructor parameter because I'm not sure what the actual means of instantiating the inputStream is.

    This will give you an unchecked cast warning but should work. Example uses JSON.

    @OptIn(ExperimentalSerializationApi::class)
    class Something<out T> @PublishedApi internal constructor(path: Path, clazz: Class<T>) {
        private val file = path.inputStream()
        private val content = Json.decodeFromStream(Json.serializersModule.serializer(clazz) as KSerializer<T>, file)
    
        companion object {
            inline operator fun <reified T> invoke(path: Path): Something<T> {
                return Something(path, T::class.java)
            }
        }
    }
    
    val test = Something<MySerializableClass>("path/to/file.json")