Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
806d89c
Add call hierarchy LSP API.
toinehartman Jul 29, 2025
907f36a
Update call hierarchy API + example.
toinehartman Jul 29, 2025
d992f3b
Extend incoming/outgoing call signatures to carry more information.
toinehartman Jul 31, 2025
7759d83
Name relation fields.
toinehartman Jul 31, 2025
62a7172
Prevent type-check by using pre-computed summary.
toinehartman Aug 4, 2025
ce663e1
Group & simplify callhierarchy APIs.
toinehartman Aug 14, 2025
f039091
Rename call item constructor, remove default, document fields.
toinehartman Aug 14, 2025
77c8128
Return ordered hierarchy items and leave parsing to implementer.
toinehartman Aug 14, 2025
4ee3719
Document call hierarchy service.
toinehartman Aug 15, 2025
0e0e811
Implement parametric call hierarchies.
toinehartman Aug 26, 2025
1066f04
Change constructor name.
toinehartman Aug 27, 2025
4577d67
Generate source locations from ranges.
toinehartman Aug 29, 2025
e5a1c8a
Document call hierarchy ADT.
toinehartman Sep 1, 2025
298b108
Remove stale pico examples.
toinehartman Sep 2, 2025
bf0f990
Improved Rascal <-> LSP mapping.
toinehartman Oct 1, 2025
fa5d3ec
Data as ADT, serialize as string.
toinehartman Oct 1, 2025
0b587cc
Use list instead of relation.
toinehartman Oct 1, 2025
b8d687d
Recover exceptions.
toinehartman Oct 1, 2025
d55e1d0
Improve getStore.
toinehartman Oct 1, 2025
d0bf2bc
Small fixes.
toinehartman Oct 1, 2025
5c0a0b5
Merge remote-tracking branch 'origin/main' into feature/132-lsp-call-…
toinehartman Oct 1, 2025
9a09c2b
Merge remote-tracking branch 'origin/main' into feature/132-lsp-call-…
toinehartman Oct 13, 2025
025dd33
Always pass the store from the correct evaluator.
toinehartman Oct 13, 2025
fd7fb98
Convert missing data correctly.
toinehartman Oct 13, 2025
a2cefac
Convert text correctly.
toinehartman Oct 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.apache.commons.lang3.tuple.Pair;
import org.rascalmpl.values.IRascalValueFactory;
import org.rascalmpl.values.parsetrees.ITree;
import org.rascalmpl.vscode.lsp.util.concurrent.InterruptibleFuture;

import io.usethesource.vallang.IConstructor;
import io.usethesource.vallang.IList;
import io.usethesource.vallang.ISet;
Expand All @@ -58,6 +60,8 @@ public interface ILanguageContributions {
public InterruptibleFuture<ISet> implementation(IList focus);
public InterruptibleFuture<IList> codeAction(IList focus);
public InterruptibleFuture<IList> selectionRange(IList focus);
public InterruptibleFuture<Pair<IList, TypeStore>> prepareCallHierarchy(IList focus);
public InterruptibleFuture<Pair<IList, TypeStore>> incomingOutgoingCalls(Function<TypeStore, IConstructor> hierarchyItem, Function<TypeStore, IConstructor> direction);

public InterruptibleFuture<ISourceLocation> prepareRename(IList focus);
public InterruptibleFuture<ITuple> rename(IList focus, String name);
Expand All @@ -79,6 +83,7 @@ public interface ILanguageContributions {
public CompletableFuture<Boolean> hasCodeAction();
public CompletableFuture<Boolean> hasDidRenameFiles();
public CompletableFuture<Boolean> hasSelectionRange();
public CompletableFuture<Boolean> hasCallHierarchy();

public CompletableFuture<Boolean> specialCaseHighlighting();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.function.Function;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.rascalmpl.interpreter.Evaluator;
import org.rascalmpl.interpreter.env.ModuleEnvironment;
import org.rascalmpl.library.util.PathConfig;
Expand All @@ -53,6 +55,7 @@
import org.rascalmpl.vscode.lsp.util.EvaluatorUtil;
import org.rascalmpl.vscode.lsp.util.EvaluatorUtil.LSPContext;
import org.rascalmpl.vscode.lsp.util.concurrent.InterruptibleFuture;

import io.usethesource.vallang.IBool;
import io.usethesource.vallang.IConstructor;
import io.usethesource.vallang.IList;
Expand Down Expand Up @@ -94,6 +97,8 @@ public class InterpretedLanguageContributions implements ILanguageContributions
private final CompletableFuture<@Nullable IFunction> rename;
private final CompletableFuture<@Nullable IFunction> didRenameFiles;
private final CompletableFuture<@Nullable IFunction> selectionRange;
private final CompletableFuture<@Nullable IFunction> prepareCallHierarchy;
private final CompletableFuture<@Nullable IFunction> callHierarchyService;

private final CompletableFuture<Boolean> hasAnalysis;
private final CompletableFuture<Boolean> hasBuild;
Expand All @@ -109,6 +114,7 @@ public class InterpretedLanguageContributions implements ILanguageContributions
private final CompletableFuture<Boolean> hasRename;
private final CompletableFuture<Boolean> hasDidRenameFiles;
private final CompletableFuture<Boolean> hasSelectionRange;
private final CompletableFuture<Boolean> hasCallHierarchy;

private final CompletableFuture<Boolean> specialCaseHighlighting;

Expand Down Expand Up @@ -155,6 +161,8 @@ public InterpretedLanguageContributions(LanguageParameter lang, IBaseTextDocumen
this.rename = getFunctionFor(contributions, LanguageContributions.RENAME);
this.didRenameFiles = getFunctionFor(contributions, LanguageContributions.DID_RENAME_FILES);
this.selectionRange = getFunctionFor(contributions, LanguageContributions.SELECTION_RANGE);
this.prepareCallHierarchy = getFunctionFor(contributions, LanguageContributions.CALL_HIERARCHY, 0);
this.callHierarchyService = getFunctionFor(contributions, LanguageContributions.CALL_HIERARCHY, 1);

// assign boolean properties once instead of wasting futures all the time
this.hasAnalysis = nonNull(this.analysis);
Expand All @@ -171,6 +179,7 @@ public InterpretedLanguageContributions(LanguageParameter lang, IBaseTextDocumen
this.hasRename = nonNull(this.rename);
this.hasDidRenameFiles = nonNull(this.didRenameFiles);
this.hasSelectionRange = nonNull(this.selectionRange);
this.hasCallHierarchy = nonNull(this.prepareCallHierarchy);

this.specialCaseHighlighting = getContributionParameter(contributions,
LanguageContributions.PARSING,
Expand Down Expand Up @@ -301,7 +310,11 @@ private static CompletableFuture<IFunction> requireFunction(CompletableFuture<IS
}

private static CompletableFuture<@Nullable IFunction> getFunctionFor(CompletableFuture<ISet> contributions, String cons) {
return getContribution(contributions, cons).thenApply(contribution -> contribution != null ? (IFunction) contribution.get(0) : null);
return getFunctionFor(contributions, cons, 0);
}

private static CompletableFuture<@Nullable IFunction> getFunctionFor(CompletableFuture<ISet> contributions, String cons, int argumentPos) {
return getContribution(contributions, cons).thenApply(contribution -> contribution != null ? (IFunction) contribution.get(argumentPos) : null);
}

private static CompletableFuture<@Nullable IFunction> getKeywordParamFunctionFor(CompletableFuture<ISet> contributions, String cons, String kwParam) {
Expand Down Expand Up @@ -405,6 +418,26 @@ public InterruptibleFuture<IList> selectionRange(IList focus) {
return execFunction(LanguageContributions.SELECTION_RANGE, selectionRange, VF.list(), focus);
}

@Override
public InterruptibleFuture<Pair<IList, TypeStore>> prepareCallHierarchy(IList focus) {
debug(LanguageContributions.CALL_HIERARCHY, "prepare", focus.length());
return withStore(execFunction(LanguageContributions.CALL_HIERARCHY, prepareCallHierarchy, VF.list(), focus));
}

@Override
public InterruptibleFuture<Pair<IList, TypeStore>> incomingOutgoingCalls(Function<TypeStore, IConstructor> computeHierarchyItem, Function<TypeStore, IConstructor> computeDirection) {
return withStore(InterruptibleFuture.flatten(store.thenApply(store -> {
var hierarchyItem = computeHierarchyItem.apply(store);
var direction = computeDirection.apply(store);
debug(LanguageContributions.CALL_HIERARCHY, hierarchyItem.has("name") ? hierarchyItem.get("name") : "?", direction.getName());
return execFunction(LanguageContributions.CALL_HIERARCHY, callHierarchyService, VF.list(), hierarchyItem, direction);
}), exec));
}

private <V> InterruptibleFuture<Pair<V, TypeStore>> withStore(InterruptibleFuture<V> contrib) {
return contrib.thenCombineAsync(store, Pair::of, exec);
}

private void debug(String name, Object param) {
logger.debug("{}({})", name, param);
}
Expand Down Expand Up @@ -473,6 +506,11 @@ public CompletableFuture<Boolean> hasSelectionRange() {
return hasSelectionRange;
}

@Override
public CompletableFuture<Boolean> hasCallHierarchy() {
return hasCallHierarchy;
}

@Override
public CompletableFuture<Boolean> hasAnalysis() {
return hasAnalysis;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,19 @@
import java.util.concurrent.ExecutorService;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import org.apache.commons.lang3.tuple.Pair;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.rascalmpl.values.parsetrees.ITree;
import org.rascalmpl.vscode.lsp.util.concurrent.InterruptibleFuture;

import io.usethesource.vallang.IConstructor;
import io.usethesource.vallang.IList;
import io.usethesource.vallang.ISet;
import io.usethesource.vallang.ISourceLocation;
import io.usethesource.vallang.ITuple;
import io.usethesource.vallang.IValue;
import io.usethesource.vallang.type.TypeStore;

@SuppressWarnings("java:S3077") // Fields in this class are read/written sequentially
public class LanguageContributionsMultiplexer implements ILanguageContributions {
Expand Down Expand Up @@ -69,6 +72,8 @@ private static final <T> CompletableFuture<T> failedInitialization() {
private volatile CompletableFuture<ILanguageContributions> rename = failedInitialization();
private volatile CompletableFuture<ILanguageContributions> didRenameFiles = failedInitialization();
private volatile CompletableFuture<ILanguageContributions> selectionRange = failedInitialization();
private volatile CompletableFuture<ILanguageContributions> prepareCallHierarchy = failedInitialization();
private volatile CompletableFuture<ILanguageContributions> incomingOutgoingCalls = failedInitialization();

private volatile CompletableFuture<Boolean> hasAnalysis = failedInitialization();
private volatile CompletableFuture<Boolean> hasBuild = failedInitialization();
Expand All @@ -84,6 +89,7 @@ private static final <T> CompletableFuture<T> failedInitialization() {
private volatile CompletableFuture<Boolean> hasRename = failedInitialization();
private volatile CompletableFuture<Boolean> hasDidRenameFiles = failedInitialization();
private volatile CompletableFuture<Boolean> hasSelectionRange = failedInitialization();
private volatile CompletableFuture<Boolean> hasCallHierarchy = failedInitialization();

private volatile CompletableFuture<Boolean> specialCaseHighlighting = failedInitialization();

Expand Down Expand Up @@ -161,6 +167,8 @@ private synchronized void calculateRouting() {
prepareRename = findFirstOrDefault(ILanguageContributions::hasRename);
didRenameFiles = findFirstOrDefault(ILanguageContributions::hasDidRenameFiles);
selectionRange = findFirstOrDefault(ILanguageContributions::hasSelectionRange);
prepareCallHierarchy = findFirstOrDefault(ILanguageContributions::hasCallHierarchy);
incomingOutgoingCalls = findFirstOrDefault(ILanguageContributions::hasCallHierarchy);

hasAnalysis = anyTrue(ILanguageContributions::hasAnalysis);
hasBuild = anyTrue(ILanguageContributions::hasBuild);
Expand All @@ -172,10 +180,11 @@ private synchronized void calculateRouting() {
hasDefinition = anyTrue(ILanguageContributions::hasDefinition);
hasReferences = anyTrue(ILanguageContributions::hasReferences);
hasImplementation = anyTrue(ILanguageContributions::hasImplementation);
hasCodeAction = anyTrue(ILanguageContributions::hasCodeAction);
hasRename = anyTrue(ILanguageContributions::hasRename);
hasDidRenameFiles = anyTrue(ILanguageContributions::hasDidRenameFiles);
hasCodeAction = anyTrue(ILanguageContributions::hasCodeAction);
hasSelectionRange = anyTrue(ILanguageContributions::hasSelectionRange);
hasCallHierarchy = anyTrue(ILanguageContributions::hasCallHierarchy);

// Always use the special-case highlighting status of *the first*
// contribution (possibly using the default value in the Rascal ADT if
Expand Down Expand Up @@ -336,6 +345,16 @@ public InterruptibleFuture<IList> selectionRange(IList focus) {
return flatten(selectionRange, c -> c.selectionRange(focus));
}

@Override
public InterruptibleFuture<Pair<IList, TypeStore>> prepareCallHierarchy(IList focus) {
return flatten(prepareCallHierarchy, c -> c.prepareCallHierarchy(focus));
}

@Override
public InterruptibleFuture<Pair<IList, TypeStore>> incomingOutgoingCalls(Function<TypeStore, IConstructor> hierarchyItem, Function<TypeStore, IConstructor> direction) {
return flatten(incomingOutgoingCalls, c -> c.incomingOutgoingCalls(hierarchyItem, direction));
}

@Override
public CompletableFuture<Boolean> hasCodeAction() {
return hasCodeAction;
Expand Down Expand Up @@ -401,6 +420,11 @@ public CompletableFuture<Boolean> hasDidRenameFiles() {
return hasDidRenameFiles;
}

@Override
public CompletableFuture<Boolean> hasCallHierarchy() {
return hasCallHierarchy;
}

@Override
public CompletableFuture<Boolean> specialCaseHighlighting() {
return specialCaseHighlighting;
Expand All @@ -425,4 +449,5 @@ public CompletableFuture<SummaryConfig> getOndemandSummaryConfig() {
public void cancelProgress(String progressId) {
contributions.forEach(klc -> klc.contrib.cancelProgress(progressId));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
Expand All @@ -48,6 +50,12 @@
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
import org.eclipse.lsp4j.CallHierarchyIncomingCall;
import org.eclipse.lsp4j.CallHierarchyIncomingCallsParams;
import org.eclipse.lsp4j.CallHierarchyItem;
import org.eclipse.lsp4j.CallHierarchyOutgoingCall;
import org.eclipse.lsp4j.CallHierarchyOutgoingCallsParams;
import org.eclipse.lsp4j.CallHierarchyPrepareParams;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.CodeLens;
Expand Down Expand Up @@ -123,11 +131,13 @@
import org.rascalmpl.vscode.lsp.parametric.model.ParametricSummary.SummaryLookup;
import org.rascalmpl.vscode.lsp.terminal.ITerminalIDEServer.LanguageParameter;
import org.rascalmpl.vscode.lsp.uri.FallbackResolver;
import org.rascalmpl.vscode.lsp.util.CallHierarchy;
import org.rascalmpl.vscode.lsp.util.CodeActions;
import org.rascalmpl.vscode.lsp.util.Diagnostics;
import org.rascalmpl.vscode.lsp.util.DocumentChanges;
import org.rascalmpl.vscode.lsp.util.DocumentSymbols;
import org.rascalmpl.vscode.lsp.util.FoldingRanges;
import org.rascalmpl.vscode.lsp.util.Lists;
import org.rascalmpl.vscode.lsp.util.SelectionRanges;
import org.rascalmpl.vscode.lsp.util.SemanticTokenizer;
import org.rascalmpl.vscode.lsp.util.Versioned;
Expand Down Expand Up @@ -239,6 +249,7 @@ public void initializeServerCapabilities(ServerCapabilities result) {
result.setInlayHintProvider(true);
result.setSelectionRangeProvider(true);
result.setFoldingRangeProvider(true);
result.setCallHierarchyProvider(true);
}

private String getRascalMetaCommandName() {
Expand Down Expand Up @@ -828,6 +839,66 @@ public CompletableFuture<List<SelectionRange>> selectionRange(SelectionRangePara
Collections::emptyList);
}

@Override
public CompletableFuture<List<CallHierarchyItem>> prepareCallHierarchy(CallHierarchyPrepareParams params) {
final var loc = Locations.toLoc(params.getTextDocument());
final var contrib = contributions(loc);
final var file = getFile(loc);

return recoverExceptions(file.getCurrentTreeAsync(true)
.thenApply(Versioned::get)
.thenCompose(t -> {
final var pos = Locations.toRascalPosition(loc, params.getPosition(), columns);
final var focus = TreeSearch.computeFocusList(t, pos.getLine(), pos.getCharacter());
return contrib.prepareCallHierarchy(focus)
.get()
.thenApply(p -> {
var items = p.getLeft();
var store = p.getRight();
var ch = new CallHierarchy(store);
return items.stream()
.map(IConstructor.class::cast)
.map(ci -> ch.toLSP(ci, columns))
.collect(Collectors.toList());
});
}), Collections::emptyList);
}

private <T> CompletableFuture<List<T>> incomingOutgoingCalls(BiFunction<CallHierarchyItem, List<Range>, T> constructor, CallHierarchyItem source, CallHierarchy.Direction direction) {
final var contrib = contributions(Locations.toLoc(source.getUri()));
return contrib.incomingOutgoingCalls(
store -> CallHierarchy.toRascal(store, source, columns),
store -> CallHierarchy.direction(store, direction)
).get()
.thenApply(res -> {
var callRel = res.getLeft();
var store = res.getRight();
var ch = new CallHierarchy(store);
return callRel.stream()
.map(ITuple.class::cast)
.collect(Collectors.toMap(
t -> ch.toLSP((IConstructor) t.get(0), columns),
t -> List.of(Locations.toRange((ISourceLocation) t.get(1), columns)),
Lists::union,
LinkedHashMap::new
));
})
.thenApply(map -> map.entrySet().stream()
.map(e -> constructor.apply(e.getKey(), e.getValue()))
.collect(Collectors.toList()));
}

@Override
public CompletableFuture<List<CallHierarchyIncomingCall>> callHierarchyIncomingCalls(CallHierarchyIncomingCallsParams params) {
return recoverExceptions(incomingOutgoingCalls(CallHierarchyIncomingCall::new, params.getItem(), CallHierarchy.Direction.INCOMING), Collections::emptyList);
}

@Override
public CompletableFuture<List<CallHierarchyOutgoingCall>> callHierarchyOutgoingCalls(CallHierarchyOutgoingCallsParams params) {
return recoverExceptions(incomingOutgoingCalls(CallHierarchyOutgoingCall::new, params.getItem(), CallHierarchy.Direction.OUTGOING), Collections::emptyList);
}


@Override
public synchronized void registerLanguage(LanguageParameter lang) {
logger.info("registerLanguage({})", lang.getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import java.io.Writer;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.function.Function;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
Expand All @@ -55,6 +57,7 @@
import io.usethesource.vallang.IValue;
import io.usethesource.vallang.IValueFactory;
import io.usethesource.vallang.exceptions.FactTypeUseException;
import io.usethesource.vallang.type.TypeStore;

public class ParserOnlyContribution implements ILanguageContributions {
private static final Logger logger = LogManager.getLogger(ParserOnlyContribution.class);
Expand Down Expand Up @@ -201,6 +204,16 @@ public InterruptibleFuture<IList> selectionRange(IList focus) {
return InterruptibleFuture.completedFuture(VF.list());
}

@Override
public InterruptibleFuture<Pair<IList, TypeStore>> prepareCallHierarchy(IList focus) {
return InterruptibleFuture.completedFuture(Pair.of(VF.list(), new TypeStore()));
}

@Override
public InterruptibleFuture<Pair<IList, TypeStore>> incomingOutgoingCalls(Function<TypeStore, IConstructor> hierarchyItem, Function<TypeStore, IConstructor> direction) {
return InterruptibleFuture.completedFuture(Pair.of(VF.list(), new TypeStore()));
}

@Override
public CompletableFuture<Boolean> hasHover() {
return CompletableFuture.completedFuture(false);
Expand Down Expand Up @@ -271,6 +284,11 @@ public CompletableFuture<Boolean> hasSelectionRange() {
return CompletableFuture.completedFuture(false);
}

@Override
public CompletableFuture<Boolean> hasCallHierarchy() {
return CompletableFuture.completedFuture(false);
}

@Override
public CompletableFuture<Boolean> specialCaseHighlighting() {
return specialCaseHighlighting;
Expand Down
Loading
Loading