Type inference of class type parameter in abstract method - kotlin

// Bars.kt
abstract class Bar
class BarToo(/* fields */) : Bar()
// Foos.kt
abstract class Foo<T : Bar> {
abstract fun foo(bar: T)
}
class FooToo : Foo<BarToo>() {
override fun foo(bar: BarToo) { /* */ }
}
// FoosBars.kt
private val foos = HashMap<String, Foo<out Bar>>()
fun <T : Foo<out Bar>> putFoo(name: String, foo: T) {
foos.putIfAbsent(name, foo)
}
fun doFoo(name: String, bar: Bar) {
val foo = foos[name] ?: return
// Error: Type mismatch: inferred type is Bar but Nothing was expected
// https://pl.kotl.in/TSp3eO_Tj
foo.foo(bar)
}
If I manually specify the bounds of T at the method's declaration, the error in doFoo is resolved, e.g.:
abstract class Foo /* ... */ {
abstract <T : Bar> fun foo(bar: T)
}
but obviously prevents the subclasses from using the type parameter from the class declaration.
Is this type of hierarchy possible in Kotlin, or should I better explain what I am trying to accomplish in order to avoid an XY problem?
Thanks!

You need to use in instead of out. This allows child classes to be used as follows:
foo.foo(bar)
foo.foo(BarToo()) // no compile error
Ref: this

Related

Cloning object of subclass type in Kotlin

I wanted to be able to define a method to clone an object that is the same type of itself. I define the interface requesting such, but the following does not compile or run.
interface Foo {
fun <T: Foo> copy() : T
}
class Bar(private val v:Int) : Foo {
override fun copy():Bar = Bar(v)
}
main() {
val bar1 = Bar(1)
val bar2 = bar1.copy()
}
If however I write the implementing class in Java, it will compile
class Bar implements Foo {
private int v;
public Bar(int v) {this.v = v;}
public Bar copy() {
return new Bar(v);
}
}
I can rewrite the code like the following that compiles:
interface Foo<out Foo>{
fun copy(): Foo
}
class Bar(private val v:Int) : Foo<Bar> {
override fun copy(): Bar = Bar(v)
}
However the following will fail with error: no type arguments expected for fun copy(): Foo
val newF = f.copy()
fun <T: Foo> addFoo(
foo: T,
fooList: List<T>,
): MutableList<T> {
val result: MutableList<T> = arrayListOf()
for (f in fooList) {
val newF = f.copy<T>()
result.add(newF)
}
result.add(foo)
return result
}
Is there a good solution to the problem?
The problem here is that Foo doesn't know the exact type of the implementing class, so has no way to specify that its method returns that same type.
Unfortunately, Kotlin doesn't have self types (see this discussion), as they would handle this situation perfectly.
However, you can get close enough by using what C++ calls the curiously-recurring template pattern. In Kotlin (and Java) you do this by defining Foo with a type parameter explicitly extending itself (including its own type parameter):
interface Foo<T : Foo<T>> {
fun copy(): T
}
Then the implementing class can specify itself as the type argument:
class Bar(private val v: Int) : Foo<Bar> {
override fun copy(): Bar = Bar(v)
}
And because T is now the correct type, everything else works out. (In fact, the : Bar is redundant there, because it already knows what the type must be.)
Your addFoo() method will then compile with only a couple of changes: give it the same type parameter <T: Foo<T>>, and remove the (now wrong, but unnecessary) type parameter when calling f.copy(). A quick test suggests it does exactly what you want (creates a list with clones of fooList followed by foo).
Since it's often useful for a superclass or interface to refer to the implementing class, this pattern crops up quite often.
BTW, your code is easier to test if Bar has its own toString() implementation, as you can then simply print the returned list. You could make it a data class, or you could write your own, e.g.:
override fun toString() = "Bar($v)"

Why do I have to cast in this Kotlin code?

interface Foo<T: Bar> {
fun example(bar: T)
}
interface Bar
class Bar1 : Bar
class Bar2 : Bar
class FooEx1 : Foo<Bar1> {
override fun example(bar: Bar1) { }
}
class FooEx2 : Foo<Bar2> {
override fun example(bar: Bar2) { }
}
// Won't compile
// Even though FooEx1 and FooEx2 *are* Foo<Bar>
class ExampleDoesntCompile {
val collection = mutableListOf<Foo<Bar>>().apply {
this.add(FooEx1())
this.add(FooEx2())
}
}
// Will compile
// But have to cast FooEx1 and FooEx2 to Foo<Bar>
class ExampleDoesCompileButRequiresCast {
val collection = mutableListOf<Foo<Bar>>().apply {
this.add(FooEx1() as Foo<Bar>)
this.add(FooEx2() as Foo<Bar>)
}
}
So, I could for instance, state that Foo's parameterized type is out, but then I get a compile error for the function example:
interface Foo<out T: Bar> {
fun example(bar: T)
}
Error: Type parameter T is declared as 'out' but occurs in 'in' position in type T
Because generic types in Java / Kotlin are invariant by default. variance
interface Foo<out T: Bar>
If you can't make it covariant, then make the list items covariant
val collection = mutableListOf<Foo<out Bar>>().apply {
this.add(FooEx1())
this.add(FooEx2())
}
//or val collection = mutableListOf(FooEx1(), FooEx2())
So it'll crash at run time with the cast?
Here is example code that would crash:
val foo: Foo<Bar> = collection[0]
foo.example(Bar2())
So if you could create collection without a cast as in your ExampleDoesntCompile, you'd get code without any casts which throws a ClassCastExcepion.
This also shows why the interface can't be declared with out:
val foo: Foo<Bar> = FooEx1() // out would make this legal
foo.example(Bar2())
It would make sense to declare your interface with in, but this would mean a Foo<Bar> is a Foo<Bar1> and a Foo<Bar2>, not vice versa, so still wouldn't let you put FooEx1/2 into a collection of Foo<Bar>s.

How does recursive type checking work in Kotlin?

Suppose we have the following typing scenario in Kotlin:
abstract class Baz<T : Baz<T>> : Foo<T> {
override fun bar(t: T): T = this
}
interface Foo<Y> {
fun bar(t: Y): Y
}
However, the compiler rejects the second line with the following error:
Type mismatch: inferred type is Baz<T> but T was expected
The IDE plugin suggests changing the return type from Baz.bar to Baz<T>, producing:
abstract class Baz<T : Baz<T>> : Foo<T> {
override fun bar(t: T): Baz<T> = this
}
However this also fails to compile with the following error:
Return type of 'bar' is not a subtype of the return type of the overridden member 'public abstract fun bar(t: Y): Y defined in Foo'
To satisfy the compiler, we must either cast this as T or modify the type definition:
abstract class Baz<T : Baz<T>> : Foo<Baz<T>> {
override fun bar(t: Baz<T>): Baz<T> = this
}
But why is this necessary? Shouldn't Baz<T> be replaceable by T?
Consider the following (simpler) case.
abstract class Baz<T: Baz<T>> {
fun bar(): T = this
}
class Bat: Baz<Bat>() {}
class Cat: Baz<Bat>() {}
According to the type signature of Baz.bar(), Cat.bar() should return an instance of Bat (because T is Bat in the definition of Cat class). However, according to the definition of Baz.bar(), an instance of Cat will be returned because this is an instance of Cat. This is an error as Cat is not a subclass of Bat.
Since any subclass X of Baz can be used as T when defining a subclass Y of Baz, there is no way to guarantee this in bar will be of type X; hence, the compiler error.
Let's go over this one by one.
class Baz<T : Baz<T>> : Foo<T> {
override fun bar(t: T): T = this
}
interface Foo<T> {
fun bar(t: T): T
}
Here, you get the type mismatch error by compiler on the overriden bar function, which is obvious, as the return type is T, but you're returning Baz<T>. The compiler suggests you to change the return type as Baz<T> for you to assign this to the function and you get the below.
class Baz<T : Baz<T>> : Foo<T> {
override fun bar(t: T): Baz<T> = this
}
interface Foo<T> {
fun bar(t: T): T
}
Here, you again get a compiler error since Baz<T> is not a subtype of T. It is not possible for the compiler to ever know if Baz<T> can be a subtype of T unless you explicitly cast it, which can potentially lead to a cast exception. For eg, List<String> is not a subtype of String, or Baz<Integer> is not a subtype of Integer, unless you have implemented the inheritance. And hence the compiler can never know for sure if Baz<T> can be a subtype of T for it to be replaceable.
Since the interface is defined as below
interface Foo<T> {
fun bar(t: T): T
}
Only the below are possible
class Baz<T : Baz<T>> : Foo<Baz<T>> {
override fun bar(t: Baz<T>): Baz<T> = this
}
Or
class Baz<T : Baz<T>> : Foo<T> {
override fun bar(t: T): T = this as T
}
But the second option has a possibility of cast exception if Baz<T> cannot be cast as T

Kotlin generics with in produces Type mismatch when compiling

I´m working on a code with generics and when I use an in I got a TypeMismatch when compiling.
The code is the following:
open class A
class B:A()
data class DataContainer(val a:String,
val b:A)
interface Repo<T:A>{
fun setParam(param:T)
fun getParam():T
}
abstract class RepoImp<T:A>:Repo<T>{
private lateinit var parameter:T
override fun setParam(param: T) {
parameter = param
}
override fun getParam(): T {
return parameter
}
}
class BRepo:RepoImp<B>()
class Repo2(val repo: Repo<in A>){
fun process(b:DataContainer){
repo.setParam(b.b)
}
}
val repoB = BRepo()
val repo2 = Repo2(repoB)// Here I got: Type mismatch: inferred type is BRepo but Repo<in A> was expected
I also tried changing the attribute repo from Repo2 to Repo<*>
Since BRepo is a Repo<B>, it is not a Repo<in A>, (but it would satisfy Repo<out A>).
In other words, a Repo<in A> must be able to accept setParam(A()), but BRepo.setParam() can only accept a B or subclass of B.
Or to put it another way, BRepo is a Repo<B>, which is a tighter restriction on the type than Repo<A> when it comes to writing values (but looser restriction when reading values).
The reason class Repo2(val repo: Repo<*>) doesn't work is that Repo<*> is essentially a Repo<in Nothing/out A>. You can't call setParam() on a Repo<*> with any kind of object.
There's a design flaw in your code that you can't fix simply by changing Repo2's constructor signature. As it stands now, Repo2 needs to be able write A's to the object you pass to it, and a BRepo by definition does not support writing A's, only B's. You will need to make at least one of your class's definitions more flexible about types.
It might be easier to understand the covariance limitation with more common classes:
val stringList: MutableList<String> = ArrayList()
var anyList: MutableList<in Any> = ArrayList()
anyList.add(5) // ok
anyList = stringList // Compiler error.
// You wouldn't be able to call add(5) on an ArrayList<String>
Basically MutableList<String> is not a MutableList<in Any> the same way Repo<B> is not a Repo<in A>.
The Repo2 class expect to consume only type A, use Repo2<T : A>(val repo: Repo<in T>)
open class A
class B : A()
class C : A()
class D : A()
class BRepo : RepoImp<B>()
class CRepo : RepoImp<C>()
class DRepo : RepoImp<D>()
interface Repo<T : A> {
fun setParam(param: T)
fun getParam(): T
}
abstract class RepoImp<T : A> : Repo<T> {
private lateinit var parameter: T
override fun setParam(param: T) {
parameter = param
}
override fun getParam(): T {
return parameter
}
}
class Repo2<T : A>(val repo: Repo<in T>) {
fun process(b: DataContainer<T>) {
repo.setParam(b.b)
}
}
data class DataContainer<T : A>(
val a: String,
val b: T
)
fun main() {
val repoB = BRepo()
val repoC = CRepo()
val repoD = DRepo()
val repo2 = Repo2(repoB)
val repo3 = Repo2(repoC)
val repo4 = Repo2(repoD)
repo2.process(DataContainer("Process B type", B()))
repo3.process(DataContainer("Process C type", C()))
repo4.process(DataContainer("Process D type", D()))
println(repo2.repo.getParam())
println(repo3.repo.getParam())
println(repo4.repo.getParam())
}

Type inference only works for extension function

The following code works fine and the call to the foo.get() extension function returns the correct type BarImpl.
open class Bar
class BarImpl: Bar()
class Foo<T : Bar>
inline fun <reified T : Bar> Foo<T>.get(): T {
return SomeMap(this).get(T::class)
}
class Activity {
lateinit var foo: Foo<BarImpl>
val barImpl = foo.get()
}
But when I try to move Foo<T>.get() into the class the type inference fails
class Foo<T : Bar> {
inline fun <reified T : Bar> get(): T {
return SomeMap(this).get(T::class)
}
}
class Activity {
lateinit var foo: Foo<BarImpl>
val barImpl = foo.get()
}
error: type inference failed: Not enough information to infer parameter T in inline fun get(): T
Please specify it explicitly.
val vm = foo.get()
^
How can I move the function into the class?
The extension function returns the result of the Foo type parameter. So the result type can be inferred from the receiver type.
And the member function result type has nothing in common with Foo type parameter except the name, which means nothing for a compiler. You can see that T in method and T in class are different types by writing and compiling the following code:
Foo<BarImpl>().get<BarImpl2>()
If you want to make get to be a member function which returns the result of Foo type parameter, you should remove type parameter from function and inject class instance via the constructor:
class Foo<T : Bar>(private val clazz: KClass<T>) {
fun get(): T {
return SomeMap(this).get(clazz)
}
companion object {
inline operator fun <reified T : Bar> invoke() = Foo(T::class)
}
}