diff --git a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala index 2bada7fe363e..b4df808a104a 100644 --- a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala +++ b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala @@ -510,7 +510,31 @@ object GenericSignatures { } private object RefOrAppliedType { - def unapply(tp: Type)(using Context): Option[(Symbol, Type, List[Type])] = tp match { + private enum ResolvedAppliedType: + case Resolved(t: Type) + case NotResolved + case Bail + // In the special case where we see a type parameter applied to type parameters, + // such as `K[X, Y]` given `[X, Y, K <: Iterable[(X, Y)]]`, we must find its bound + // and instantiate it, otherwise in our example we end up with `Iterable[X, Y]` which is nonsensical. + private def resolveAppliedType(a: AppliedType)(using Context): ResolvedAppliedType = + a.tycon match + case TypeParamRef(binder, paramNum) => + binder.paramInfos(paramNum).hi match + case hkt @ HKTypeLambda(_, _) => + val instantiated = hkt.instantiate(a.args).dealias + // However, since Java doesn't have a way to refer to HKTs in generic signatures, + // we must trade precision for termination by only resolving one level, + // otherwise we end up in infinite loops, + // e.g., in `X[A] <: Thing[X[A]]` or `X[A] <: X[Thing[A]]` we keep resolving `X`. + // In that case we must completely give up on the genericity, i.e., + // in `X[A] <: Y[X[Z[A]]]` it would not be correct to use `Y[A]` as a type signature! + if instantiated.existsPart(_ == a.tycon) then ResolvedAppliedType.Bail + else ResolvedAppliedType.Resolved(instantiated) + case _ => ResolvedAppliedType.NotResolved + case _ => ResolvedAppliedType.NotResolved + + def unapply(tp: Type)(using Context): Option[(Symbol, Type, List[Type])] = tp match case TypeParamRef(_, _) => Some((tp.typeSymbol, tp, Nil)) case TermParamRef(_, _) => @@ -518,11 +542,13 @@ object GenericSignatures { case TypeRef(pre, _) if !tp.typeSymbol.isAliasType => val sym = tp.typeSymbol Some((sym, pre, Nil)) - case AppliedType(pre, args) => - Some((pre.typeSymbol, pre, args)) + case a @ AppliedType(pre, args) => + resolveAppliedType(a) match + case ResolvedAppliedType.Resolved(resolved) => unapply(resolved) + case ResolvedAppliedType.NotResolved => Some((pre.typeSymbol, pre, args)) + case ResolvedAppliedType.Bail => None case _ => None - } } private def needsJavaSig(tp: Type, throwsArgs: List[Type])(using Context): Boolean = !ctx.settings.XnoGenericSig.value && { diff --git a/tests/run/i15903.check b/tests/run/i15903.check new file mode 100644 index 000000000000..b87ad2f0d655 --- /dev/null +++ b/tests/run/i15903.check @@ -0,0 +1 @@ +scala.collection.Iterable> diff --git a/tests/run/i15903.scala b/tests/run/i15903.scala new file mode 100644 index 000000000000..5cadd05d4754 --- /dev/null +++ b/tests/run/i15903.scala @@ -0,0 +1,10 @@ +// scalajs: --skip +// (JVM-only, generic signatures) + +trait Working: + type M[X,Y] = Iterable[(X, Y)] + def example[K, A, T[X,Y] <: M[X,Y]](ab: String): T[K,A] = ??? + +object Test: + def main(args: Array[String]): Unit = + classOf[Working].getMethods.filter(_.getName == "example").map(_.getGenericReturnType).foreach(println) diff --git a/tests/run/i15903b.check b/tests/run/i15903b.check new file mode 100644 index 000000000000..b87ad2f0d655 --- /dev/null +++ b/tests/run/i15903b.check @@ -0,0 +1 @@ +scala.collection.Iterable> diff --git a/tests/run/i15903b.scala b/tests/run/i15903b.scala new file mode 100644 index 000000000000..9afb2bbd3239 --- /dev/null +++ b/tests/run/i15903b.scala @@ -0,0 +1,9 @@ +// scalajs: --skip +// (JVM-only, generic signatures) + +trait NotWorking: + def example[K, A, T[X,Y] <: Iterable[(X, Y)]](ab: String): T[K,A] = ??? + +object Test: + def main(args: Array[String]): Unit = + classOf[NotWorking].getMethods.filter(_.getName == "example").map(_.getGenericReturnType).foreach(println) diff --git a/tests/run/i15903c.check b/tests/run/i15903c.check new file mode 100644 index 000000000000..802b4d822f7a --- /dev/null +++ b/tests/run/i15903c.check @@ -0,0 +1 @@ +scala.collection.Iterable> diff --git a/tests/run/i15903c.scala b/tests/run/i15903c.scala new file mode 100644 index 000000000000..15950d0a5f5d --- /dev/null +++ b/tests/run/i15903c.scala @@ -0,0 +1,10 @@ +// scalajs: --skip +// (JVM-only, generic signatures) + +trait NotWorking: + // return type args reversed compared to test case (b) + def example[K, A, T[X,Y] <: Iterable[(X, Y)]](ab: String): T[A,K] = ??? + +object Test: + def main(args: Array[String]): Unit = + classOf[NotWorking].getMethods.filter(_.getName == "example").map(_.getGenericReturnType).foreach(println) diff --git a/tests/run/i15903d.check b/tests/run/i15903d.check new file mode 100644 index 000000000000..725b2a507393 --- /dev/null +++ b/tests/run/i15903d.check @@ -0,0 +1 @@ +interface scala.collection.Iterable diff --git a/tests/run/i15903d.scala b/tests/run/i15903d.scala new file mode 100644 index 000000000000..3c16cab56c42 --- /dev/null +++ b/tests/run/i15903d.scala @@ -0,0 +1,9 @@ +// scalajs: --skip +// (JVM-only, generic signatures) + +trait NotWorking: + def example[A, X[T] <: Iterable[X[List[T]]]](s: String): X[A] = ??? + +object Test: + def main(args: Array[String]): Unit = + classOf[NotWorking].getMethods.filter(_.getName == "example").map(_.getGenericReturnType).foreach(println)