Oto minimalne kodu demo, który pokazuje ten problem:Dlaczego parametr wpisu w Kotlin nie ma żadnych innych ograniczeń, jeśli jest ograniczony innym parametrem?
interface A
fun <T1, T2> test() where T2 : T1, T2 : A {}
Gdy próbuję go skompilować, kompilator będzie narzekać:
Error:(81, 25) Kotlin: Type parameter cannot have any other bounds if it's bounded by another type parameter
czytam Kotlin Language Specification, ale tylko znaleźć następujące związanego ograniczenie:
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.
Nie wyjaśnia ograniczenia, które spotykam.
Badam tracker problemów z Kotlin, i znajduję problem dotyczący tego ograniczenia: Allow to inherit a type parameter from another type parameter and a class : KT-13768. Jednak kwestia ta została odrzucona przez następującego powodu (aktualizacja na 6 maja, 2017: kwestia ta została ponownie otwarta przez Stanislav Erokhin):
I don't think we can compile the code correctly to JVM if we remove this restriction.
By Andrey Breslav
Więc pytanie brzmi: dlaczego nie możemy poprawnie skompilować kod do JVM, jeśli usuniemy to ograniczenie?
To samo demo działa w Scala:
trait A
def test[T1, T2 <: T1 with A](): Unit = {}
To oznacza, że Scala można skompilować kod poprawnie JVM. Dlaczego nie może Kotlin? Czy jest to ograniczenie gwarantujące rozstrzygające podtypy w Kotlin (Chyba Podtyp jest nierozstrzygalny dla Scali (Scala ma system typu Turing-complete) Kotlin może chcieć decydującego podtypu jak C#.)?
Aktualizacja po odpowiedział @erokhins (https://stackoverflow.com/a/43807444/7964561):
Istnieją pewne subtelne kwestie gdy wspierające coś zakazanego przez Java, ale dozwolony przez JVM, zwłaszcza interoperacyjność Java. Znajduję interesujący problem podczas wkopywania do kodu bajtowego generowanego przez skalak. Zmodyfikować kod Scala w demo następująco:
trait A
trait B
def test[T1 <: B, T2 <: T1 with A](t1: T1, t2: T2): Unit = {}
class AB extends A with B
Scalac wygeneruje następujący podpis:
// 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
z test(new AB, new AB)
w Scala uda, ponieważ Scalas powołać podpis (LB;LB;)V
; ale wywołanie z test(new AB(), new AB());
w Javie zakończy się niepowodzeniem, ponieważ Java wywołuje podpis (LB;Ljava/lang/Object;)V
, powodując w środowisku wykonawczym java.lang.NoSuchMethodError
. Oznacza to, że skalak generuje coś, czego nie można wywołać w Javie po złagodzeniu tego ograniczenia. Kotlin może spotkać ten sam problem po złagodzeniu go.