Fix handling of type variable bounds in `isSameSignature`

PiperOrigin-RevId: 433891768
diff --git a/java/com/google/turbine/processing/TurbineTypes.java b/java/com/google/turbine/processing/TurbineTypes.java
index 4eb4c87..d2068dd 100644
--- a/java/com/google/turbine/processing/TurbineTypes.java
+++ b/java/com/google/turbine/processing/TurbineTypes.java
@@ -219,8 +219,15 @@
     if (bounds.isEmpty()) {
       return true;
     }
-    ClassTy first = (ClassTy) bounds.get(0);
-    return factory.getSymbol(first.sym()).kind().equals(TurbineTyKind.INTERFACE);
+    Type bound = bounds.get(0);
+    switch (bound.tyKind()) {
+      case TY_VAR:
+        return false;
+      case CLASS_TY:
+        return factory.getSymbol(((ClassTy) bound).sym()).kind().equals(TurbineTyKind.INTERFACE);
+      default:
+        throw new AssertionError(bound.tyKind());
+    }
   }
 
   private boolean isSameWildType(WildTy a, Type other) {
@@ -366,8 +373,8 @@
   }
 
   private boolean isTyVarSubtype(TyVar a, Type b, boolean strict) {
-    if (b.tyKind() == TyKind.TY_VAR) {
-      return a.sym().equals(((TyVar) b).sym());
+    if (b.tyKind() == TyKind.TY_VAR && a.sym().equals(((TyVar) b).sym())) {
+      return true;
     }
     TyVarInfo tyVarInfo = factory.getTyVarInfo(a.sym());
     return isSubtype(tyVarInfo.upperBound(), b, strict);
diff --git a/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java b/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java
index d3b3836..7d8d479 100644
--- a/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java
+++ b/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java
@@ -345,6 +345,21 @@
                 "  void h(T t) {}",
                 "}"));
 
+    // type variable bounds
+    files.add(
+        Joiner.on('\n')
+            .join(
+                "import java.util.List;",
+                "class N<X, T extends X> {",
+                "  void h(T t) {}",
+                "}",
+                "class O<X extends Enum<X>, T extends X> {",
+                "  void h(T t) {}",
+                "}",
+                "class P<X extends List<?>, T extends X> {",
+                "  void h(T t) {}",
+                "}"));
+
     Context context = new Context();
     JavaFileManager fileManager = new JavacFileManager(context, true, UTF_8);
     idx.set(0);
diff --git a/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java b/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java
index cf84ec5..fee2c75 100644
--- a/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java
+++ b/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java
@@ -23,6 +23,8 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Objects.requireNonNull;
 import static java.util.stream.Collectors.joining;
+import static javax.lang.model.util.ElementFilter.methodsIn;
+import static javax.lang.model.util.ElementFilter.typesIn;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assume.assumeTrue;
 
@@ -55,7 +57,9 @@
 import javax.lang.model.SourceVersion;
 import javax.lang.model.element.AnnotationMirror;
 import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.ExecutableType;
 import javax.tools.Diagnostic;
 import javax.tools.JavaFileObject;
 import javax.tools.StandardLocation;
@@ -712,4 +716,92 @@
         .map(Parser::parse)
         .collect(toImmutableList());
   }
+
+  @SupportedAnnotationTypes("*")
+  public static class AllMethodsProcessor extends AbstractProcessor {
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+      return SourceVersion.latestSupported();
+    }
+
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+
+      ImmutableList<ExecutableElement> methods =
+          typesIn(roundEnv.getRootElements()).stream()
+              .flatMap(t -> methodsIn(t.getEnclosedElements()).stream())
+              .collect(toImmutableList());
+      for (ExecutableElement a : methods) {
+        for (ExecutableElement b : methods) {
+          if (a.equals(b)) {
+            continue;
+          }
+          ExecutableType ta = (ExecutableType) a.asType();
+          ExecutableType tb = (ExecutableType) b.asType();
+          boolean r = processingEnv.getTypeUtils().isSubsignature(ta, tb);
+          processingEnv
+              .getMessager()
+              .printMessage(
+                  Diagnostic.Kind.ERROR,
+                  String.format(
+                      "%s#%s%s <: %s#%s%s ? %s",
+                      a.getEnclosingElement(),
+                      a.getSimpleName(),
+                      ta,
+                      b.getEnclosingElement(),
+                      b.getSimpleName(),
+                      tb,
+                      r));
+        }
+      }
+      return false;
+    }
+  }
+
+  @Test
+  public void bound() {
+    ImmutableList<Tree.CompUnit> units =
+        parseUnit(
+            "=== A.java ===", //
+            "import java.util.List;",
+            "class A<T> {",
+            "  <U extends T> U f(List<U> list) {",
+            "    return list.get(0);",
+            "  }",
+            "}",
+            "class B extends A<String> {",
+            "  @Override",
+            "  <U extends String> U f(List<U> list) {",
+            "    return super.f(list);",
+            "  }",
+            "}",
+            "class C extends A<Object> {",
+            "  @Override",
+            "  <U> U f(List<U> list) {",
+            "    return super.f(list);",
+            "  }",
+            "}");
+    TurbineError e =
+        assertThrows(
+            TurbineError.class,
+            () ->
+                Binder.bind(
+                    units,
+                    ClassPathBinder.bindClasspath(ImmutableList.of()),
+                    ProcessorInfo.create(
+                        ImmutableList.of(new AllMethodsProcessor()),
+                        getClass().getClassLoader(),
+                        ImmutableMap.of(),
+                        SourceVersion.latestSupported()),
+                    TestClassPaths.TURBINE_BOOTCLASSPATH,
+                    Optional.empty()));
+    assertThat(e.diagnostics().stream().map(d -> d.message()))
+        .containsExactly(
+            "A#f<U>(java.util.List<U>)U <: B#f<U>(java.util.List<U>)U ? false",
+            "A#f<U>(java.util.List<U>)U <: C#f<U>(java.util.List<U>)U ? false",
+            "B#f<U>(java.util.List<U>)U <: A#f<U>(java.util.List<U>)U ? false",
+            "B#f<U>(java.util.List<U>)U <: C#f<U>(java.util.List<U>)U ? false",
+            "C#f<U>(java.util.List<U>)U <: A#f<U>(java.util.List<U>)U ? false",
+            "C#f<U>(java.util.List<U>)U <: B#f<U>(java.util.List<U>)U ? false");
+  }
 }