Include the supertype closure of used types in jdeps

MOE_MIGRATED_REVID=138526825
diff --git a/java/com/google/turbine/deps/Dependencies.java b/java/com/google/turbine/deps/Dependencies.java
index 740ae2b..b6aeb6a 100644
--- a/java/com/google/turbine/deps/Dependencies.java
+++ b/java/com/google/turbine/deps/Dependencies.java
@@ -19,10 +19,16 @@
 import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableSet;
 import com.google.turbine.binder.Binder.BindingResult;
+import com.google.turbine.binder.bound.TypeBoundClass;
 import com.google.turbine.binder.bytecode.BytecodeBoundClass;
+import com.google.turbine.binder.env.CompoundEnv;
+import com.google.turbine.binder.env.Env;
+import com.google.turbine.binder.env.SimpleEnv;
 import com.google.turbine.binder.sym.ClassSymbol;
 import com.google.turbine.lower.Lower.Lowered;
 import com.google.turbine.proto.DepsProto;
+import java.util.LinkedHashSet;
+import java.util.Set;
 
 /** Support for Bazel jdeps dependency output. */
 public class Dependencies {
@@ -33,7 +39,9 @@
       BindingResult bound,
       Lowered lowered) {
     DepsProto.Dependencies.Builder deps = DepsProto.Dependencies.newBuilder();
-    for (ClassSymbol sym : lowered.symbols()) {
+    Set<ClassSymbol> closure = superTypeClosure(bound, lowered);
+    Set<String> jars = new LinkedHashSet<>();
+    for (ClassSymbol sym : closure) {
       BytecodeBoundClass info = bound.classPathEnv().get(sym);
       if (info == null) {
         // the symbol wasn't loaded from the classpath
@@ -44,6 +52,9 @@
         // bootclasspath deps are not tracked
         continue;
       }
+      jars.add(jarFile);
+    }
+    for (String jarFile : jars) {
       deps.addDependency(
           DepsProto.Dependency.newBuilder()
               .setPath(jarFile)
@@ -56,4 +67,32 @@
     }
     return deps.build();
   }
+
+  private static Set<ClassSymbol> superTypeClosure(BindingResult bound, Lowered lowered) {
+    Env<ClassSymbol, TypeBoundClass> env =
+        CompoundEnv.<ClassSymbol, TypeBoundClass>of(new SimpleEnv<>(bound.units()))
+            .append(bound.classPathEnv());
+    Set<ClassSymbol> closure = new LinkedHashSet<>();
+    for (ClassSymbol sym : lowered.symbols()) {
+      addSuperTypes(closure, env, sym);
+    }
+    return closure;
+  }
+
+  private static void addSuperTypes(
+      Set<ClassSymbol> closure, Env<ClassSymbol, TypeBoundClass> env, ClassSymbol sym) {
+    if (!closure.add(sym)) {
+      return;
+    }
+    TypeBoundClass info = env.get(sym);
+    if (info == null) {
+      return;
+    }
+    if (info.superclass() != null) {
+      addSuperTypes(closure, env, info.superclass());
+    }
+    for (ClassSymbol i : info.interfaces()) {
+      addSuperTypes(closure, env, i);
+    }
+  }
 }
diff --git a/javatests/com/google/turbine/deps/DependenciesTest.java b/javatests/com/google/turbine/deps/DependenciesTest.java
index 9932d0e..71579f5 100644
--- a/javatests/com/google/turbine/deps/DependenciesTest.java
+++ b/javatests/com/google/turbine/deps/DependenciesTest.java
@@ -143,7 +143,11 @@
     Path libb =
         new LibraryBuilder()
             .setClasspath(liba)
-            .addSourceLines("B.java", "class B extends A {}")
+            .addSourceLines(
+                "B.java", //
+                "class B {",
+                "  public static final A a = new A();",
+                "}")
             .compileToJar("libb.jar");
     DepsProto.Dependencies deps =
         new DepsBuilder()
@@ -184,4 +188,65 @@
                 libb, DepsProto.Dependency.Kind.EXPLICIT,
                 liba, DepsProto.Dependency.Kind.EXPLICIT));
   }
+
+  @Test
+  public void closure() throws Exception {
+    Path libi =
+        new LibraryBuilder()
+            .addSourceLines(
+                "i/I.java",
+                "package i;", //
+                "public interface I {}")
+            .compileToJar("libi.jar");
+    Path liba =
+        new LibraryBuilder()
+            .setClasspath(libi)
+            .addSourceLines(
+                "a/A.java", //
+                "package a;",
+                "import i.I;",
+                "public class A implements I {}")
+            .compileToJar("liba.jar");
+    Path libb =
+        new LibraryBuilder()
+            .setClasspath(liba, libi)
+            .addSourceLines(
+                "b/B.java", //
+                "package b;",
+                "import a.A;",
+                "public class B extends A {}")
+            .compileToJar("libb.jar");
+    {
+      DepsProto.Dependencies deps =
+          new DepsBuilder()
+              .setClasspath(liba, libb, libi)
+              .addSourceLines(
+                  "Test.java", //
+                  "import b.B;",
+                  "class Test extends B {}")
+              .run();
+      assertThat(depsMap(deps))
+          .isEqualTo(
+              ImmutableMap.of(
+                  libi, DepsProto.Dependency.Kind.EXPLICIT,
+                  libb, DepsProto.Dependency.Kind.EXPLICIT,
+                  liba, DepsProto.Dependency.Kind.EXPLICIT));
+    }
+    {
+      // partial classpath
+      DepsProto.Dependencies deps =
+          new DepsBuilder()
+              .setClasspath(liba, libb)
+              .addSourceLines(
+                  "Test.java", //
+                  "import b.B;",
+                  "class Test extends B {}")
+              .run();
+      assertThat(depsMap(deps))
+          .isEqualTo(
+              ImmutableMap.of(
+                  libb, DepsProto.Dependency.Kind.EXPLICIT,
+                  liba, DepsProto.Dependency.Kind.EXPLICIT));
+    }
+  }
 }