Skip to content

Commit 4ebda7f

Browse files
committed
feat: [EXPERIMENTAL] prune method bodies of files before parsing in JavaCompilerImpl
1 parent 3d606d1 commit 4ebda7f

File tree

9 files changed

+196
-51
lines changed

9 files changed

+196
-51
lines changed

lsp/java/src/main/java/com/itsaky/androidide/lsp/java/compiler/CompileBatch.java

+16-6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.itsaky.androidide.builder.model.IJavaCompilerSettings;
3131
import com.itsaky.androidide.javac.services.compiler.ReusableBorrow;
3232
import com.itsaky.androidide.javac.services.partial.DiagnosticListenerImpl;
33+
import com.itsaky.androidide.lsp.java.models.CompilationRequest;
3334
import com.itsaky.androidide.lsp.java.visitors.MethodRangeScanner;
3435
import com.itsaky.androidide.models.Range;
3536
import com.itsaky.androidide.projects.api.ModuleProject;
@@ -43,6 +44,7 @@
4344
import openjdk.tools.javac.api.ClientCodeWrapper;
4445
import openjdk.tools.javac.api.JavacTaskImpl;
4546
import openjdk.tools.javac.code.Kinds;
47+
import openjdk.tools.javac.util.Context;
4648
import openjdk.tools.javac.util.JCDiagnostic;
4749

4850
import java.io.File;
@@ -76,18 +78,26 @@ public class CompileBatch implements AutoCloseable {
7678
boolean closed;
7779

7880
CompileBatch(
79-
JavaCompilerService parent,
80-
Collection<? extends JavaFileObject> files,
81-
CompilationTaskProcessor taskProcessor) {
81+
JavaCompilerService parent,
82+
Collection<? extends JavaFileObject> files,
83+
CompilationRequest compilationRequest) {
8284
this.parent = parent;
8385
this.borrow = batchTask(parent, files);
8486
this.task = borrow.task;
8587
this.roots = new ArrayList<>();
86-
87-
Objects.requireNonNull(taskProcessor, "A task processor is required");
88+
89+
final var context = task.getContext();
90+
final var config = JavaCompilerConfig.instance(context);
91+
config.setFiles(files);
92+
93+
if (compilationRequest.configureContext != null) {
94+
compilationRequest.configureContext.accept(context);
95+
}
96+
97+
Objects.requireNonNull(compilationRequest, "A task processor is required");
8898

8999
try {
90-
taskProcessor.process(borrow.task, this::processCompilationUnit);
100+
compilationRequest.compilationTaskProcessor.process(borrow.task, this::processCompilationUnit);
91101
} catch (Throwable e) {
92102
throw new RuntimeException(e);
93103
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* This file is part of AndroidIDE.
3+
*
4+
* AndroidIDE is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* AndroidIDE is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.itsaky.androidide.lsp.java.compiler
19+
20+
import com.itsaky.androidide.models.Position
21+
import jdkx.tools.JavaFileObject
22+
import openjdk.tools.javac.util.Context
23+
24+
/**
25+
* Configuration for the [JavaCompilerImpl].
26+
*
27+
* @property files The main files that are being compiled/parsed.
28+
* @property completionInfo Information about the completion
29+
* @author Akash Yadav
30+
*/
31+
class JavaCompilerConfig(context: Context) {
32+
init {
33+
context.put(compilerConfigKey, this)
34+
}
35+
36+
var files: Collection<JavaFileObject>? = null
37+
var completionInfo: CompletionInfo? = null
38+
39+
companion object {
40+
41+
@JvmField val compilerConfigKey = Context.Key<JavaCompilerConfig>()
42+
43+
@JvmStatic
44+
fun instance(context: Context): JavaCompilerConfig {
45+
var instance = context.get(compilerConfigKey)
46+
if (instance == null) {
47+
instance = JavaCompilerConfig(context)
48+
}
49+
return instance
50+
}
51+
}
52+
}
53+
54+
/**
55+
* Information about the completion request initiated by the Java completion provider.
56+
*
57+
* @property cursor The cursor position for the completion.
58+
* @author Akash Yadav
59+
*/
60+
data class CompletionInfo(val cursor: Position)

lsp/java/src/main/java/com/itsaky/androidide/lsp/java/compiler/JavaCompilerImpl.kt

+46-2
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,61 @@
1717

1818
package com.itsaky.androidide.lsp.java.compiler
1919

20+
import android.util.Log
2021
import com.itsaky.androidide.javac.services.compiler.ReusableContext
2122
import com.itsaky.androidide.javac.services.compiler.ReusableJavaCompiler
23+
import com.itsaky.androidide.lsp.java.parser.ts.TSJavaParser
24+
import com.itsaky.androidide.lsp.java.parser.ts.TSMethodPruner.prune
25+
import com.itsaky.androidide.projects.FileManager
26+
import com.itsaky.androidide.utils.ILogger
27+
import com.itsaky.androidide.utils.StopWatch
2228
import jdkx.tools.JavaFileObject
29+
import jdkx.tools.JavaFileObject.Kind.SOURCE
30+
import kotlin.io.path.name
31+
import openjdk.tools.javac.api.ClientCodeWrapper
2332
import openjdk.tools.javac.tree.JCTree.JCCompilationUnit
2433
import openjdk.tools.javac.util.Context
2534

2635
class JavaCompilerImpl(context: Context?) : ReusableJavaCompiler(context) {
2736

37+
private val _log = ILogger.newInstance("JavaCompilerImpl")
38+
2839
override fun parse(filename: JavaFileObject?, content: CharSequence?): JCCompilationUnit {
29-
// TODO Prune unnecessary methods to speed up compilation
30-
return super.parse(filename, content)
40+
val file = ClientCodeWrapper.instance(context).unwrap(filename)
41+
val compilerConfig = JavaCompilerConfig.instance(context)
42+
43+
// Preconditions
44+
if (
45+
content == null ||
46+
compilerConfig.files == null ||
47+
filename?.kind != SOURCE ||
48+
compilerConfig.files?.contains(file) == false
49+
) {
50+
return super.parse(filename, content)
51+
}
52+
53+
// If the file is NOT being parsed for a completion request,
54+
// we should not prune method bodies of active documents
55+
if (compilerConfig.completionInfo == null && FileManager.isActive(filename.toUri())) {
56+
return super.parse(filename, content)
57+
}
58+
59+
val pruned =
60+
StopWatch("${if(file is SourceFileObject) "[${file.path.name}] " else ""}Prune method bodies")
61+
.let {
62+
val contentBuilder = StringBuilder(content)
63+
val parseResult = TSJavaParser.parse(file)
64+
prune(
65+
contentBuilder,
66+
parseResult.tree,
67+
compilerConfig.completionInfo?.cursor?.index ?: -1
68+
)
69+
it.log()
70+
Log.d("JavaMethodPruner", "Pruned contents for file: $file\n$contentBuilder")
71+
return@let contentBuilder
72+
}
73+
74+
return super.parse(filename, pruned)
3175
}
3276

3377
companion object {

lsp/java/src/main/java/com/itsaky/androidide/lsp/java/compiler/JavaCompilerService.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ private CompileBatch performCompilation(CompilationRequest request) {
413413
throw new RuntimeException("empty sources");
414414
}
415415

416-
CompileBatch firstAttempt = new CompileBatch(this, sources, request.compilationTaskProcessor);
416+
CompileBatch firstAttempt = new CompileBatch(this, sources, request);
417417
Set<Path> addFiles = firstAttempt.needsAdditionalSources();
418418

419419
if (addFiles.isEmpty()) {
@@ -430,7 +430,7 @@ private CompileBatch performCompilation(CompilationRequest request) {
430430
moreSources.add(new SourceFileObject(add));
431431
}
432432

433-
return new CompileBatch(this, moreSources, request.compilationTaskProcessor);
433+
return new CompileBatch(this, moreSources, request);
434434
}
435435

436436
private boolean containsWord(Path file, String word) {

lsp/java/src/main/java/com/itsaky/androidide/lsp/java/compiler/SourceFileObject.java

+27-13
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@
2828
import java.io.StringReader;
2929
import java.io.Writer;
3030
import java.net.URI;
31+
import java.nio.file.Files;
3132
import java.nio.file.Path;
3233
import java.time.Instant;
34+
import java.util.Objects;
3335

3436
import jdkx.lang.model.element.Modifier;
3537
import jdkx.lang.model.element.NestingKind;
@@ -53,19 +55,7 @@ public SourceFileObject(Path path, String contents, Instant modified) {
5355
this.contents = contents;
5456
this.modified = modified;
5557
}
56-
57-
@Override
58-
public int hashCode() {
59-
return this.path.hashCode();
60-
}
61-
62-
@Override
63-
public boolean equals(Object other) {
64-
if (other.getClass() != SourceFileObject.class) return false;
65-
SourceFileObject that = (SourceFileObject) other;
66-
return DocumentUtils.isSameFile(this.path, that.path);
67-
}
68-
58+
6959
@Override
7060
public String toString() {
7161
return MoreObjects.toStringHelper(this).add("path", this.path.toString()).toString();
@@ -158,4 +148,28 @@ public long getLastModified() {
158148
public boolean delete() {
159149
throw new UnsupportedOperationException();
160150
}
151+
152+
@Override
153+
public boolean equals(final Object o) {
154+
if (this == o) {
155+
return true;
156+
}
157+
if (!(o instanceof SourceFileObject)) {
158+
return false;
159+
}
160+
final SourceFileObject that = (SourceFileObject) o;
161+
try {
162+
return this.path != null && that.path != null
163+
&& Files.isSameFile(this.path, that.path)
164+
&& Objects.equals(contents, that.contents)
165+
&& Objects.equals(modified, that.modified);
166+
} catch (Exception e) {
167+
return false;
168+
}
169+
}
170+
171+
@Override
172+
public int hashCode() {
173+
return Objects.hash(path, contents, modified);
174+
}
161175
}

lsp/java/src/main/java/com/itsaky/androidide/lsp/java/models/CompilationRequest.kt

+5-2
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,23 @@ package com.itsaky.androidide.lsp.java.models
1919

2020
import com.itsaky.androidide.lsp.java.compiler.CompilationTaskProcessor
2121
import com.itsaky.androidide.lsp.java.compiler.DefaultCompilationTaskProcessor
22+
import java.util.function.Consumer
2223
import jdkx.tools.JavaFileObject
24+
import openjdk.tools.javac.util.Context
2325

2426
/**
2527
* Data sent to compiler to request a compilation.
2628
*
27-
* @author Akash Yadav
2829
* @param sources The source files to compile.
2930
* @param partialRequest Data that will be used to a partial reparse.
31+
* @author Akash Yadav
3032
*/
3133
data class CompilationRequest
3234
@JvmOverloads
3335
constructor(
3436
@JvmField val sources: Collection<JavaFileObject>,
3537
@JvmField val partialRequest: PartialReparseRequest? = null,
3638
@JvmField
37-
val compilationTaskProcessor: CompilationTaskProcessor = DefaultCompilationTaskProcessor()
39+
val compilationTaskProcessor: CompilationTaskProcessor = DefaultCompilationTaskProcessor(),
40+
@JvmField var configureContext: Consumer<Context>? = null
3841
)

lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/CompletionProvider.java

+26-21
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@
2828
import com.itsaky.androidide.lsp.api.IServerSettings;
2929
import com.itsaky.androidide.lsp.internal.model.CachedCompletion;
3030
import com.itsaky.androidide.lsp.java.compiler.CompileTask;
31+
import com.itsaky.androidide.lsp.java.compiler.CompletionInfo;
32+
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerConfig;
3133
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService;
3234
import com.itsaky.androidide.lsp.java.compiler.SourceFileObject;
3335
import com.itsaky.androidide.lsp.java.compiler.SynchronizedTask;
3436
import com.itsaky.androidide.lsp.java.models.CompilationRequest;
3537
import com.itsaky.androidide.lsp.java.models.PartialReparseRequest;
36-
import com.itsaky.androidide.lsp.java.parser.ts.TSJavaParser;
37-
import com.itsaky.androidide.lsp.java.parser.ts.TSMethodPruner;
3838
import com.itsaky.androidide.lsp.java.providers.completion.IJavaCompletionProvider;
3939
import com.itsaky.androidide.lsp.java.providers.completion.IdentifierCompletionProvider;
4040
import com.itsaky.androidide.lsp.java.providers.completion.ImportCompletionProvider;
@@ -50,8 +50,6 @@
5050
import com.itsaky.androidide.lsp.models.CompletionResult;
5151
import com.itsaky.androidide.utils.DocumentUtils;
5252
import com.itsaky.androidide.utils.ILogger;
53-
import com.itsaky.androidide.utils.StopWatch;
54-
import com.itsaky.androidide.utils.VMUtils;
5553

5654
import java.nio.file.Path;
5755
import java.time.Duration;
@@ -156,23 +154,15 @@ private CompletionResult completeInternal(final @NonNull CompletionParams params
156154
final var sourceObject = new SourceFileObject(file);
157155
final var contentBuilder = new StringBuilder(sourceObject.getCharContent(true));
158156

159-
if (false && !VMUtils.isJvm()) {
160-
// Cannot use tree sitter in tests
161-
// TODO(itsaky): Should we use the legacy method pruner for tests?
162-
final StopWatch watch = new StopWatch("Prune method bodies");
163-
final var parseResult = TSJavaParser.INSTANCE.parse(sourceObject);
164-
TSMethodPruner.INSTANCE.prune(contentBuilder, parseResult.getTree(), (int) cursor);
165-
watch.log();
166-
}
167-
168157
int endOfLine = endOfLine(contentBuilder, (int) cursor);
169158
contentBuilder.insert(endOfLine, ';');
170159

171160
final StringBuilder contents;
172-
if (compiler.compiler.currentContext != null) {
161+
final var context = compiler.compiler.currentContext;
162+
if (context != null) {
173163
abortIfCancelled();
174164
abortCompletionIfCancelled();
175-
contents = new ASTFixer(compiler.compiler.currentContext).fix(contentBuilder);
165+
contents = new ASTFixer(context).fix(contentBuilder);
176166
} else {
177167
contents = contentBuilder;
178168
}
@@ -182,7 +172,8 @@ private CompletionResult completeInternal(final @NonNull CompletionParams params
182172
new PartialReparseRequest(cursor - params.requirePrefix().length(), contentString);
183173
abortIfCancelled();
184174
abortCompletionIfCancelled();
185-
CompletionResult result = compileAndComplete(file, contentString, cursor, partialRequest);
175+
176+
CompletionResult result = compileAndComplete(contentString, params, partialRequest);
186177
if (result == null) {
187178
result = CompletionResult.EMPTY;
188179
}
@@ -231,17 +222,24 @@ private int endOfLine(@NonNull CharSequence contents, int cursor) {
231222
}
232223

233224
private CompletionResult compileAndComplete(
234-
Path file, String contents, final long cursor, PartialReparseRequest partialRequest) {
235-
final Instant started = Instant.now();
236-
final SourceFileObject source = new SourceFileObject(file, contents, Instant.now());
237-
final String partial = partialIdentifier(contents, (int) cursor);
238-
final boolean endsWithParen = endsWithParen(contents, (int) cursor);
225+
String contents, CompletionParams params, PartialReparseRequest partialRequest) {
226+
final long cursor = params.getPosition().requireIndex();
227+
final var file = params.getFile();
228+
final var started = Instant.now();
229+
final var source = new SourceFileObject(file, contents, Instant.now());
230+
final var partial = partialIdentifier(contents, (int) cursor);
231+
final var endsWithParen = endsWithParen(contents, (int) cursor);
239232

240233
abortIfCancelled();
241234
abortCompletionIfCancelled();
242235

243236
final CompilationRequest request =
244237
new CompilationRequest(Collections.singletonList(source), partialRequest);
238+
request.configureContext = ctx -> {
239+
final var config = JavaCompilerConfig.instance(ctx);
240+
config.setCompletionInfo(new CompletionInfo(params.getPosition()));
241+
};
242+
245243
SynchronizedTask synchronizedTask = compiler.compile(request);
246244
return synchronizedTask.get(
247245
task -> {
@@ -266,6 +264,13 @@ private CompletionResult compileAndComplete(
266264

267265
final var result = doComplete(file, contents, cursor, newPartial, endsWithParen, task, path);
268266
new TopLevelSnippetsProvider().complete(partial, task.root(), result);
267+
268+
// IMPORTANT: Unregister the completion info from the compiler configuration
269+
if (task.task.getContext() != null) {
270+
final var compilerConfig = JavaCompilerConfig.instance(task.task.getContext());
271+
compilerConfig.setCompletionInfo(null);
272+
}
273+
269274
return result;
270275
});
271276
}

0 commit comments

Comments
 (0)