From 2e78c42fac680ec942a56fba57405985931cde51 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Fri, 13 Nov 2026 18:27:46 +0800 Subject: [PATCH] perf(java): find implementations in the sources through the index (#3132) Go to Implementation on project code built a region type hierarchy over every source folder, which resolves every type in the region whatever the target; in a large multi-module project it took seconds. It now runs the operation IType.newTypeHierarchy uses, scoped to the source folders and the bridged Kotlin output, so the index narrows the subtypes to files that name a supertype. Results are unchanged, local and anonymous classes included (~230 ms -> ~7 ms on a 4,000-class test project). Refs #3101 --- java/java-usages/openidea-java-usages.jar | Bin 161726 -> 163185 bytes .../ImplementationsCommand.java | 15 ++++++++++-- java/java-usages/tests/implementations.py | 23 +++++++++++++++--- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/java/java-usages/openidea-java-usages.jar b/java/java-usages/openidea-java-usages.jar index 63ca14d948e66cacd2547d681c3593b7617b8b02..c2924301ea3c6584aed00195a6706bab00400a3c 100644 GIT binary patch delta 3138 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 diff --git a/java/java-usages/src/ai/openidea/implementations/ImplementationsCommand.java b/java/java-usages/src/ai/openidea/implementations/ImplementationsCommand.java index 821af09c2..50bd541af 100644 --- a/java/java-usages/src/ai/openidea/implementations/ImplementationsCommand.java +++ b/java/java-usages/src/ai/openidea/implementations/ImplementationsCommand.java @@ -8,6 +8,11 @@ import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.jdt.core.*; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.internal.core.CreateTypeHierarchyOperation; +import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner; +import org.eclipse.jdt.internal.core.JavaModelManager; import org.eclipse.jdt.internal.corext.util.MethodOverrideTester; import org.eclipse.jdt.ls.core.internal.IDelegateCommandHandler; import org.eclipse.jdt.ls.core.internal.JDTUtils; @@ -56,7 +61,7 @@ public final class ImplementationsCommand implements IDelegateCommandHandler { return false; } if (type == null || type.getJavaProject() == null) return false; - IRegion sources = JavaCore.newRegion(); + List sources = new ArrayList<>(); for (IJavaProject project : JavaCore.create(ResourcesPlugin.getWorkspace().getRoot()).getJavaProjects()) { for (IPackageFragmentRoot fragmentRoot : project.getPackageFragmentRoots()) { if (fragmentRoot.getKind() == IPackageFragmentRoot.K_SOURCE || isKotlinOutput(fragmentRoot)) { @@ -64,7 +69,13 @@ public final class ImplementationsCommand implements IDelegateCommandHandler { } } } - ITypeHierarchy hierarchy = type.getJavaProject().newTypeHierarchy(type, sources, monitor); + // IType.newTypeHierarchy's operation over the sources: the index narrows the subtypes to the + // files that name a supertype, where a region hierarchy resolves every type in the region. + ICompilationUnit[] workingCopies = JavaModelManager.getJavaModelManager().getWorkingCopies(DefaultWorkingCopyOwner.PRIMARY, true); + IJavaSearchScope scope = SearchEngine.createJavaSearchScope(sources.toArray(IJavaElement[]::new)); + CreateTypeHierarchyOperation operation = new CreateTypeHierarchyOperation(type, workingCopies, scope, true); + operation.runOperation(monitor); + ITypeHierarchy hierarchy = operation.getResult(); for (IType subtype : hierarchy.getAllSubtypes(type)) { if (monitor.isCanceled()) throw new OperationCanceledException(); IMember found; diff --git a/java/java-usages/tests/implementations.py b/java/java-usages/tests/implementations.py index ed39b519d..5655fb6a8 100644 --- a/java/java-usages/tests/implementations.py +++ b/java/java-usages/tests/implementations.py @@ -72,6 +72,21 @@ public class Task implements Runnable { public void run() { } } +''', + 'Anon.java': '''package zoo; + +public class Anon { + Repo make() { + class Local implements Repo { + public void save(Double item) { + } + } + return new Repo() { + public void save(Double item) { + } + }; + } +} ''', 'Use.java': '''package zoo; @@ -174,11 +189,11 @@ try: request('java/buildWorkspace', True) # (implementations in the sources, whether they are all there are) cases = { - # Classes, abstract ones too, and those of the bridged Kotlin output; not the sub-interface. - ('Repo.java', 'Repo'): (['Base.java:2', 'Longs.java:0', 'Numbers.java:2', 'Words.java:2'], True), + # Classes, abstract, local and anonymous ones too, and those of the bridged Kotlin output; not the sub-interface. + ('Repo.java', 'Repo'): (['Anon.java:4', 'Anon.java:8', 'Base.java:2', 'Longs.java:0', 'Numbers.java:2', 'Words.java:2'], True), # Through the type arguments; not the abstract re-declaration. - ('Repo.java', 'save(T'): (['Longs.java:0', 'Numbers.java:3', 'Words.java:3'], True), - ('Use.java', 'save("x")'): (['Longs.java:0', 'Numbers.java:3', 'Words.java:3'], True), + ('Repo.java', 'save(T'): (['Anon.java:5', 'Anon.java:9', 'Longs.java:0', 'Numbers.java:3', 'Words.java:3'], True), + ('Use.java', 'save("x")'): (['Anon.java:5', 'Anon.java:9', 'Longs.java:0', 'Numbers.java:3', 'Words.java:3'], True), ('Repo.java', 'name()'): (['Words.java:6'], True), # A library type's implementations in the sources; the library has more. ('Use.java', 'Runnable task'): (['Task.java:2'], False),