Reputation: 493
Here is the minimal demo code that shows this problem:
interface A
fun <T1, T2> test() where T2 : T1, T2 : A {}
When I try to compile it, compiler will complain:
Error:(81, 25) Kotlin: Type parameter cannot have any other bounds if it's bounded by another type parameter
I read Kotlin Language Specification, but only find the following bound restriction:
A type-parameter cannot specify itself as its own bound, and several type-parameters cannot specify each other as a bound in a cyclic manner.
It doesn't explain the restriction I meet.
I explore Kotlin's issue tracker, and I find an issue about this restriction: Allow to inherit a type parameter from another type parameter and a class : KT-13768. However, this issue has been rejected by the following reason (update on May 6th, 2017: this issue has been reopened by Stanislav Erokhin):
I don't think we can compile the code correctly to JVM if we remove this restriction.
By Andrey Breslav
So the question is: why can't we compile the code correctly to JVM if we remove this restriction?
The same demo works in Scala:
trait A
def test[T1, T2 <: T1 with A](): Unit = {}
It indicates that Scala can compile the code correctly to JVM. Why can't Kotlin? Is it a restriction to guarantee a decidable subtyping in Kotlin (I guess. Subtyping is undecidable for Scala (Scala has a Turing-complete type system). Kotlin may want decidable subtyping like C#.)?
Update after answered by @erokhins (https://stackoverflow.com/a/43807444/7964561):
There are some subtle issues when supporting something forbidden by Java but allowed by JVM, especially in Java interoperability. I find an interesting issue when digging into bytecode generated by scalac. I modify Scala code in demo as follow:
trait A
trait B
def test[T1 <: B, T2 <: T1 with A](t1: T1, t2: T2): Unit = {}
class AB extends A with B
Scalac will generate the following signature:
// signature <T1::LB;T2:TT1;:LA;>(TT1;TT2;)V
// descriptor: (LB;LB;)V
public <T1 extends B, T2 extends T1 & A> void test(T1, T2);
Invoke test
with test(new AB, new AB)
in Scala will succeed, since Scalas invoke signature (LB;LB;)V
; but invoke with test(new AB(), new AB());
in Java will fail, since Java invokes signature (LB;Ljava/lang/Object;)V
, causing java.lang.NoSuchMethodError
in runtime. It means scalac generates something cannot be invoked in Java after relaxing this restriction. Kotlin may meet the same issue after relaxing it.
Upvotes: 39
Views: 3721
Reputation: 809
If you add the following suppress annotation, it should work:
interface A
@Suppress("BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER")
fun <T1, T2> test() where T2 : T1, T2 : A {}
Reference: https://discuss.kotlinlang.org/t/bug-in-where/25011/2
Upvotes: 11
Reputation: 629
This restrictions was made because java(language) has it:
interface A {}
// Error:(7, 26) java: a type variable may not be followed by other bounds
<T1, T2 extends T1 & A> void test() {}
And we suppose that this forbidden also in bytecode level. I dig into it and seems like it is allowed, and scalac generate the following signature:
// access flags 0x1
// signature <T1:Ljava/lang/Object;T2:TT1;:LA;>()V
// declaration: void test<T1, T2T1 extends A>()
public test()V
So, we probably can support such cases it in the future versions of kotlin.
P.S. As far as I know Kotlin has decidable subtyping, and decidability isn't affected by this.
Upvotes: 20