uchuhimo
uchuhimo

Reputation: 493

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.

Upvotes: 39

Views: 3721

Answers (2)

Mahe
Mahe

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

erokhins
erokhins

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

Related Questions