diff --git a/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifSemanticdb.java b/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifSemanticdb.java index 2a01a10e..e9431a9b 100644 --- a/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifSemanticdb.java +++ b/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifSemanticdb.java @@ -136,6 +136,20 @@ private Integer processDocument( writer.emitHoverEdge(ids.resultSet, hoverId); } } + + // Overrides + if (symbolInformation.getOverriddenSymbolsCount() > 0) { + int[] overriddenReferenceResultIds = new int[symbolInformation.getOverriddenSymbolsCount()]; + for (int i = 0; i < symbolInformation.getOverriddenSymbolsCount(); i++) { + String overriddenSymbol = symbolInformation.getOverriddenSymbols(i); + ResultIds overriddenIds = results.getOrInsertResultSet(overriddenSymbol); + overriddenReferenceResultIds[i] = overriddenIds.referenceResult; + writer.emitReferenceResultsItemEdge( + overriddenIds.referenceResult, new int[] {rangeId}, doc.id); + } + writer.emitReferenceResultsItemEdge( + ids.referenceResult, overriddenReferenceResultIds, doc.id); + } } writer.emitContains(doc.id, new ArrayList<>(rangeIds)); writer.flush(); diff --git a/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifWriter.java b/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifWriter.java index 4929478d..0f7e29c4 100644 --- a/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifWriter.java +++ b/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifWriter.java @@ -139,6 +139,16 @@ public void emitItem(int outV, int inV, int document) { emitObject(lsifEdge("item").setOutV(outV).addInVs(inV).setDocument(document)); } + public void emitReferenceResultsItemEdge(int outV, int[] inVs, int document) { + List ints = Arrays.stream(inVs).boxed().collect(Collectors.toList()); + emitObject( + lsifEdge("item") + .setOutV(outV) + .addAllInVs(ints) + .setDocument(document) + .setProperty("referenceResults")); + } + public void build() throws IOException { close(); Files.move(tmp, options.output, StandardCopyOption.REPLACE_EXISTING); diff --git a/lsif-semanticdb/src/main/protobuf/lsif.proto b/lsif-semanticdb/src/main/protobuf/lsif.proto index 7cc6fbc7..947e8528 100644 --- a/lsif-semanticdb/src/main/protobuf/lsif.proto +++ b/lsif-semanticdb/src/main/protobuf/lsif.proto @@ -25,6 +25,7 @@ message LsifObject { LsifPosition end = 20; string name = 21; string manager = 22; + string property = 23; } message LsifToolInfo { diff --git a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbPlugin.java b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbPlugin.java index 83881c69..4d4915eb 100644 --- a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbPlugin.java +++ b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbPlugin.java @@ -1,6 +1,8 @@ package com.sourcegraph.semanticdb_javac; import com.sun.source.util.*; +import com.sun.tools.javac.api.BasicJavacTask; +import com.sun.tools.javac.model.JavacTypes; /** Entrypoint of the semanticdb-javac compiler plugin. */ public class SemanticdbPlugin implements Plugin { @@ -15,12 +17,14 @@ public void init(JavacTask task, String... args) { SemanticdbReporter reporter = new SemanticdbReporter(); SemanticdbJavacOptions options = SemanticdbJavacOptions.parse(args); GlobalSymbolsCache globals = new GlobalSymbolsCache(options); + JavacTypes javacTypes = JavacTypes.instance(((BasicJavacTask) task).getContext()); if (!options.errors.isEmpty()) { for (String error : options.errors) { reporter.error(error); } } else { - task.addTaskListener(new SemanticdbTaskListener(options, task, globals, reporter)); + task.addTaskListener( + new SemanticdbTaskListener(options, task, globals, reporter, javacTypes)); } } } diff --git a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTaskListener.java b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTaskListener.java index ce7d8a92..de632542 100644 --- a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTaskListener.java +++ b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTaskListener.java @@ -4,6 +4,7 @@ import com.sun.source.util.TaskEvent; import com.sun.source.util.TaskListener; import com.sourcegraph.semanticdb_javac.Semanticdb; +import com.sun.tools.javac.model.JavacTypes; import java.io.IOException; import java.nio.file.Files; @@ -19,16 +20,19 @@ public final class SemanticdbTaskListener implements TaskListener { private final JavacTask task; private final GlobalSymbolsCache globals; private final SemanticdbReporter reporter; + private final JavacTypes javacTypes; public SemanticdbTaskListener( SemanticdbJavacOptions options, JavacTask task, GlobalSymbolsCache globals, - SemanticdbReporter reporter) { + SemanticdbReporter reporter, + JavacTypes javacTypes) { this.options = options; this.task = task; this.globals = globals; this.reporter = reporter; + this.javacTypes = javacTypes; } @Override @@ -51,7 +55,7 @@ private void onFinishedAnalyze(TaskEvent e) { Result path = semanticdbOutputPath(options, e); if (path.isOk()) { Semanticdb.TextDocument textDocument = - new SemanticdbVisitor(task, globals, e, options) + new SemanticdbVisitor(task, globals, e, options, javacTypes) .buildTextDocument(e.getCompilationUnit()); writeSemanticdb(path.getOrThrow(), textDocument); } else { diff --git a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbVisitor.java b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbVisitor.java index ba7b5d5a..218d9142 100644 --- a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbVisitor.java +++ b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbVisitor.java @@ -5,6 +5,7 @@ import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.model.JavacTypes; import com.sun.tools.javac.tree.EndPosTable; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.util.JCDiagnostic; @@ -19,10 +20,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; import static com.sourcegraph.semanticdb_javac.SemanticdbTypeVisitor.ARRAY_SYMBOL; @@ -34,6 +32,7 @@ public class SemanticdbVisitor extends TreePathScanner { private final LocalSymbolsCache locals; private final JavacTask task; private final TaskEvent event; + private final JavacTypes javacTypes; private final Trees trees; private final SemanticdbJavacOptions options; private final EndPosTable endPosTable; @@ -42,12 +41,17 @@ public class SemanticdbVisitor extends TreePathScanner { private String source; public SemanticdbVisitor( - JavacTask task, GlobalSymbolsCache globals, TaskEvent event, SemanticdbJavacOptions options) { + JavacTask task, + GlobalSymbolsCache globals, + TaskEvent event, + SemanticdbJavacOptions options, + JavacTypes javacTypes) { this.task = task; this.globals = globals; // Reused cache between compilation units. this.locals = new LocalSymbolsCache(); // Fresh cache per compilation unit. this.event = event; this.options = options; + this.javacTypes = javacTypes; this.trees = Trees.instance(task); if (event.getCompilationUnit() instanceof JCTree.JCCompilationUnit) { this.endPosTable = ((JCTree.JCCompilationUnit) event.getCompilationUnit()).endPositions; @@ -113,6 +117,7 @@ private void emitSymbolInformation(Symbol sym, JCTree tree) { break; case METHOD: builder.setKind(Kind.METHOD); + builder.addAllOverriddenSymbols(semanticdbOverrides(sym)); break; case CONSTRUCTOR: builder.setKind(Kind.CONSTRUCTOR); @@ -502,6 +507,17 @@ private int semanticdbSymbolInfoProperties(Symbol sym) { return properties; } + private List semanticdbOverrides(Symbol sym) { + ArrayList overriddenSymbols = new ArrayList<>(); + Set overriddenMethods = javacTypes.getOverriddenMethods(sym); + + for (Symbol.MethodSymbol meth : overriddenMethods) { + overriddenSymbols.add(semanticdbSymbol(meth)); + } + + return overriddenSymbols; + } + private Semanticdb.Access semanticdbAccess(Symbol sym) { switch ((int) sym.flags() & Flags.AccessFlags) { case Flags.PRIVATE: diff --git a/tests/unit/src/test/scala/tests/OverridesSuite.scala b/tests/unit/src/test/scala/tests/OverridesSuite.scala new file mode 100644 index 00000000..e3f2886a --- /dev/null +++ b/tests/unit/src/test/scala/tests/OverridesSuite.scala @@ -0,0 +1,144 @@ +package tests + +import java.util.stream.Collectors + +import scala.meta.Input + +import com.sourcegraph.lsif_semanticdb.Symtab +import munit.FunSuite +import munit.TestOptions + +class OverridesSuite extends FunSuite with TempDirectories { + + val targetroot = new DirectoryFixture() + + override def munitFixtures: Seq[Fixture[_]] = + super.munitFixtures ++ List(targetroot) + + def checkOverrides( + options: TestOptions, + source: String, + extractSymbol: String, + expectedSymbols: String* + ): Unit = { + test(options) { + val compiler = new TestCompiler(targetroot()) + val relativePath = "example.Parent".replace('.', '/') + ".java" + val input = Input.VirtualFile(relativePath, source) + val result = compiler.compileSemanticdb(List(input)) + val symtab = new Symtab(result.textDocument) + + val expectedSyms = expectedSymbols.mkString("\n") + val syms = symtab + .symbols + .get(extractSymbol) + .getOverriddenSymbolsList + .stream + .collect(Collectors.joining("\n")) + assertNoDiff(expectedSyms, syms) + } + } + + checkOverrides( + "same file", + """package example; + | + |class Parent { + | public void stuff() {} + | + | class Child extends Parent { + | @Override + | public void stuff() {} + | } + |} + |""".stripMargin, + "example/Parent#Child#stuff().", + "example/Parent#stuff()." + ) + + checkOverrides( + "external override", + """package example; + | + |class Parent { + | @Override + | public String toString() { return ""; } + |} + |""".stripMargin, + "example/Parent#toString().", + "java/lang/Object#toString()." + ) + + checkOverrides( + "implement interface", + """package example; + | + |class Parent { + | interface Test { + | public void stuff(); + | } + | + | class Child implements Test { + | @Override + | public void stuff() {} + | } + |} + |""".stripMargin, + "example/Parent#Child#stuff().", + "example/Parent#Test#stuff()." + ) + + checkOverrides( + "type params", + """package example; + | + |class Parent { + | interface Haha { + | void add(T elem); + | } + | class IntHaha implements Haha { + | void add(Integer elem) {} + | } + |} + |""".stripMargin, + "example/Parent#IntHaha#add().", + "example/Parent#Haha#add()." + ) + + checkOverrides( + "multiple", + """package example; + | + |class Parent { + | @Override + | public String toString() { return ""; } + | + | class Child extends Parent { + | @Override + | public String toString() { return ""; } + | } + |} + |""".stripMargin, + "example/Parent#Child#toString().", + """example/Parent#toString(). + |java/lang/Object#toString(). + |""".stripMargin + ) + + checkOverrides( + "abstract", + """package example; + | + |abstract class Parent { + | public abstract void stuff(); + | + | class Child extends Parent { + | @Override + | public void stuff() {} + | } + |} + |""".stripMargin, + "example/Parent#Child#stuff().", + "example/Parent#stuff()." + ) +}