简体   繁体   中英

Why can't type parameter in Kotlin have any other bounds if it's bounded by another type parameter?

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.

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.

PS As far as I know Kotlin has decidable subtyping, and decidability isn't affected by this.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM