Repository: scalacenter/tasty-query (scala)

Issue:
Support references to type lambda parameters inside parameters themselves
A simple example:

```scala
class GrowableBuilder[Elem, To <: Growable[Elem]]
```

A harder example with the param, referencing itself:
```scala
class Boxxy[I <: Int, B <: Boxxy[I, B]]
```

Pull request description:
Closes #7, #267 and #288.

Patch:
diff --git a/tasty-query/shared/src/main/scala/tastyquery/Subtyping.scala b/tasty-query/shared/src/main/scala/tastyquery/Subtyping.scala
index 786a3943..555f95e2 100644
--- a/tasty-query/shared/src/main/scala/tastyquery/Subtyping.scala
+++ b/tasty-query/shared/src/main/scala/tastyquery/Subtyping.scala
@@ -203,6 +203,10 @@ private[tastyquery] object Subtyping:
       isSubType(tp1, tp2.bounds.low)
         || level4(tp1, tp2)
 
+    case tp2: TypeParamRef =>
+      isSubType(tp1, tp2.bounds.low)
+        || level4(tp1, tp2)
+
     case tp2: AppliedType =>
       compareAppliedType2(tp1, tp2)
 
@@ -432,6 +436,9 @@ private[tastyquery] object Subtyping:
     case tp1: TypeRef =>
       isSubType(tp1.bounds.high, tp2)
 
+    case tp1: TypeParamRef =>
+      isSubType(tp1.bounds.high, tp2)
+
     case tp1: AppliedType =>
       compareAppliedType1(tp1, tp2)
 


Test patch:
diff --git a/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala
index 6e65dd9d..13ff544f 100644
--- a/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala
+++ b/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala
@@ -404,6 +404,13 @@ class SignatureSuite extends UnrestrictedUnpicklingSuite:
     val takeEmptyTupleSig = TuplesClass.findNonOverloadedDecl(termName("takeEmptyTuple"))
     assertSigned(takeEmptyTupleSig, "(scala.Tuple$package.EmptyTuple):scala.Unit")
 
+    val takeConcreteGenTuple = TuplesClass.findNonOverloadedDecl(termName("takeConcreteGenTuple"))
+    assertSigned(takeConcreteGenTuple, "(scala.Product):scala.Unit")
+
+    val takeConcreteGenTupleThroughMatch =
+      TuplesClass.findNonOverloadedDecl(termName("takeConcreteGenTupleThroughMatch"))
+    assertSigned(takeConcreteGenTupleThroughMatch, "(scala.Product):scala.Unit")
+
     val TupleClass = ctx.findTopLevelClass("scala.Tuple")
 
     val colonStar = TupleClass.findNonOverloadedDecl(termName(":*"))
@@ -436,4 +443,14 @@ class SignatureSuite extends UnrestrictedUnpicklingSuite:
     assert(clue(fooSelect.symbol) == clue(fooMethod))
   }
 
+  testWithContext("context function types") {
+    val SpecialFunctionTypesClass = ctx.findTopLevelClass("simple_trees.SpecialFunctionTypes")
+
+    val contextFunction = SpecialFunctionTypesClass.findNonOverloadedDecl(termName("contextFunction"))
+    assertSigned(contextFunction, "(scala.Function1):scala.Unit")
+
+    val contextFunctionResult = SpecialFunctionTypesClass.findNonOverloadedDecl(termName("contextFunctionResult"))
+    assertSigned(contextFunctionResult, "(java.lang.String):scala.Function1")
+  }
+
 end SignatureSuite
diff --git a/tasty-query/shared/src/test/scala/tastyquery/SubtypingSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/SubtypingSuite.scala
index e71bbd5a..9e9163a0 100644
--- a/tasty-query/shared/src/test/scala/tastyquery/SubtypingSuite.scala
+++ b/tasty-query/shared/src/test/scala/tastyquery/SubtypingSuite.scala
@@ -939,6 +939,65 @@ class SubtypingSuite extends UnrestrictedUnpicklingSuite:
     assertNeitherSubtype(ConcreteSimplePathsChildClass.staticRef, refinedSomeOtherPolyMethodType)
   }
 
+  testWithContext("dependent-class-type-parameters") {
+    val GenericClassWithTypeParamDependenciesClass =
+      ctx.findTopLevelClass("simple_trees.GenericClassWithTypeParamDependencies")
+
+    val List(a, b, c, d) = GenericClassWithTypeParamDependenciesClass.typeParams: @unchecked
+
+    assertEquiv(a.localRef, a.localRef)
+    assertEquiv(b.localRef, b.localRef)
+
+    assertStrictSubtype(b.localRef, a.localRef)
+    assertStrictSubtype(c.localRef, a.localRef)
+    assertNeitherSubtype(a.localRef, d.localRef)
+
+    assertStrictSubtype(c.localRef, b.localRef)
+    assertStrictSubtype(d.localRef, b.localRef)
+
+    assertNeitherSubtype(c.localRef, d.localRef)
+  }
+
+  testWithContext("dependent-method-type-parameters") {
+    val GenericMethodWithTypeParamDependenciesClass =
+      ctx.findTopLevelClass("simple_trees.GenericMethodWithTypeParamDependencies")
+
+    val foo = GenericMethodWithTypeParamDependenciesClass.findNonOverloadedDecl(termName("foo"))
+    val DefDef(_, Right(typeParams) :: Left(Nil) :: Nil, _, _, _$1) = foo.tree.get: @unchecked
+
+    locally {
+      val List(a, b, c, d) = typeParams.map(_.symbol): @unchecked
+
+      assertEquiv(a.localRef, a.localRef)
+      assertEquiv(b.localRef, b.localRef)
+
+      assertStrictSubtype(b.localRef, a.localRef)
+      assertStrictSubtype(c.localRef, a.localRef)
+      assertNeitherSubtype(a.localRef, d.localRef)
+
+      assertStrictSubtype(c.localRef, b.localRef)
+      assertStrictSubtype(d.localRef, b.localRef)
+
+      assertNeitherSubtype(c.localRef, d.localRef)
+    }
+
+    locally {
+      val List(a, b, c, d) = foo.declaredType.asInstanceOf[PolyType].paramRefs: @unchecked
+
+      assertEquiv(a, a)
+      assertEquiv(b, b)
+
+      assertStrictSubtype(b, a)
+      assertStrictSubtype(c, a)
+      assertNeitherSubtype(a, d)
+
+      assertStrictSubtype(c, b)
+      assertStrictSubtype(d, b)
+
+      assertNeitherSubtype(c, d)
+    }
+  }
+
   testWithContext("intersection-types") {
     assertStrictSubtype(Types.AndType.make(defn.IntType, defn.StringType), defn.IntType).withRef[Int & String, Int]
 
diff --git a/test-sources/src/main/scala/simple_trees/GenericClassWithTypeParamDependencies.scala b/test-sources/src/main/scala/simple_trees/GenericClassWithTypeParamDependencies.scala
new file mode 100644
index 00000000..3871afa7
--- /dev/null
+++ b/test-sources/src/main/scala/simple_trees/GenericClassWithTypeParamDependencies.scala
@@ -0,0 +1,3 @@
+package simple_trees
+
+abstract class GenericClassWithTypeParamDependencies[A, B >: D <: A, C <: B, D]
diff --git a/test-sources/src/main/scala/simple_trees/GenericMethodWithTypeParamDependencies.scala b/test-sources/src/main/scala/simple_trees/GenericMethodWithTypeParamDependencies.scala
new file mode 100644
index 00000000..2d20889d
--- /dev/null
+++ b/test-sources/src/main/scala/simple_trees/GenericMethodWithTypeParamDependencies.scala
@@ -0,0 +1,5 @@
+package simple_trees
+
+class GenericMethodWithTypeParamDependencies:
+  def foo[A, B >: D <: A, C <: B, D](): Int = 1
+end GenericMethodWithTypeParamDependencies
diff --git a/test-sources/src/main/scala/simple_trees/SpecialFunctionTypes.scala b/test-sources/src/main/scala/simple_trees/SpecialFunctionTypes.scala
index b81a1290..8719e6eb 100644
--- a/test-sources/src/main/scala/simple_trees/SpecialFunctionTypes.scala
+++ b/test-sources/src/main/scala/simple_trees/SpecialFunctionTypes.scala
@@ -2,4 +2,16 @@ package simple_trees
 
 class SpecialFunctionTypes:
   def contextFunction(f: String ?=> Unit): Unit = f(using "Hello")
+
+  def contextFunctionResult(s: String): String ?=> Int = summon[String].toInt
+
+  def calls(): Unit =
+    contextFunction {
+      summon[String].toInt
+    }
+
+    contextFunctionResult("hello")(using "foo")
+    val f: String ?=> Int = contextFunctionResult("hello")
+    f(using "foo")
+  end calls
 end SpecialFunctionTypes
diff --git a/test-sources/src/main/scala/simple_trees/Tuples.scala b/test-sources/src/main/scala/simple_trees/Tuples.scala
index f1535254..134446a4 100644
--- a/test-sources/src/main/scala/simple_trees/Tuples.scala
+++ b/test-sources/src/main/scala/simple_trees/Tuples.scala
@@ -8,4 +8,17 @@ class Tuples:
   def takeStarColon(t: *:[Any, Tuple]): Unit = ()
 
   def takeEmptyTuple(t: EmptyTuple): Unit = ()
+
+  def takeConcreteGenTuple(t: Int *: String *: EmptyTuple): Unit = ()
+
+  def takeConcreteGenTupleThroughMatch(t: Tuple.Map[(Int, String), Option]): Unit = ()
+
+  def calls(): Unit =
+    takeTuple((1, 2))
+    takeNonEmptyTuple((1, 2))
+    takeStarColon((1, 2))
+    takeEmptyTuple(EmptyTuple)
+    takeConcreteGenTuple((1, "foo"))
+    takeConcreteGenTupleThroughMatch((None, Some("foo")))
+  end calls
 end Tuples