Skip to content

Conversation

@toinehartman
Copy link
Member

@toinehartman toinehartman commented Jul 31, 2025

This PR designs and implements textDocument/prepareCallHierarchy, callHierarchy/incomingCalls, and callHierarchy/outgoingCalls for parametric language servers, allowing DSLs to implement call hierarchies.

data CallHierarchyItem
    = callItem(
        str name,
        DocumentSymbolKind kind,
        loc src,
        loc selection,
        set[DocumentSymbolTag] tags = {},
        str detail = "",
        CallHierarchyData \data = none()
    );

data CallHierarchyData = none();

Design

This design collapses the incoming/outgoing calls services into, since they are very similar. It also groups prepareCallHierarchy in the same constructor, since neither can exist without the other.

data LanguageService
    = callHierarchy (
        list[CallHierarchyItem] (Focus _focus) callableItem,
        lrel[CallHierarchyItem item, loc call] (CallHierarchyItem _ci, CallDirection _dir) calculateCalls)
    ;

data CallDirection
    = incoming()
    | outgoing()
    ;

Since a single cursor can produce multiple hierarchy items (e.g. in the case of overloads), and the implementer might want to influence the order, we require a list/lrel.

Alternatives considered

  • A summary-based implementation, where we add information to Summary in an attempt to make call to the hierarchy services cheaper and simpler. This requries quite some additions to the summary, means that a hierarchy always requires a summary to be present, and additionally makes passing around the optional state (value data field) cumbersome.

An example of this implementation can be found here: https://github.com/SWAT-engineering/bird/blob/demo/call-hierarchy/bird-ide/src/main/rascal/lang/bird/LanguageServer.rsc

Closes #132

@toinehartman toinehartman self-assigned this Jul 31, 2025
@toinehartman toinehartman force-pushed the feature/132-lsp-call-hierarchy branch from 8b1c6c5 to 335278e Compare July 31, 2025 15:19
@toinehartman toinehartman added the enhancement New feature or request label Aug 6, 2025
Copy link
Member

@DavyLandman DavyLandman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've written down some thoughts, let me know what you think.

Copy link
Contributor

@sungshik sungshik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A small and a smaller comment.

@jurgenvinju
Copy link
Member

jurgenvinju commented Aug 18, 2025

IMHO it is essential for efficiency that we know in advance if we are looking for callers or callees.

  • Callees have typically already been resolved by the static checker, and can probably be retrieved from a summary. This is because the checker knows everything about what is being used in the current module.
    • After retrieval from the summary optionally some static filter can be applied (by arity, static type of the parameters, etc)
  • Callers require a reverse index and a "closed world assumption"
    • we need to know the callers of every other program/library in the {current workspace, current project, current ?}
    • that index must be reversed as a whole, and we can find the callers of our selected callee.
    • then the aforementioned static approximation filter must be applied (for example by arity or static type of the parameters, etc)

So those two are significantly different algorithms in general. If Bird accidentally does not make the same distinction, then that's an exception rather than a rule IMHO. Especially the "closed world" aspect has big impact on both usability (what else do you need it for?) and efficiency (Now we need up-to-date summaries of all possible caller files).

@jurgenvinju
Copy link
Member

I'm worried that with the current setup DSL developers will need to re-invent their reverse modular indexing again and again for every language. At the same time we already have the Summary concept for indexing a single file, and summaries have been designed to be modularly composable. So we are pretty close...

Let's take a step back and see what that means in general, and if we can factor out that aspect in a language contribution by itself? Or perhaps a general feature.. I don't know.

@jurgenvinju
Copy link
Member

Funny note: this indexing feature was why IBM was interested in vallang for making Eclipse more language parametric, called "Program Database" at the time. Vallang was then modelled after the Meta-Environment's "RScript" databases, and the ATerm library.

@DavyLandman
Copy link
Member

DavyLandman commented Aug 19, 2025

Okay, I had a call with @jurgenvinju and we decided:

  1. We want to add a "index" feature to our LSP abstraction, a bit like summaries but then linked. That index would help users implement call hierachy and other requests (such as references) a lot better. But that is not part of this PR, this PR is the "raw" mapping of the original request from LSP to Rascal, and leaves many of the heavy lifting on the semantics side to the end user
  2. There will be a separate design for indexing, that will be more cross cutting, and will help DSL implementors "do the right thing"
  3. we should improve the documentation of this (and also references) to try to sketch the semantics LSP expects from implementers.

@toinehartman toinehartman force-pushed the feature/132-lsp-call-hierarchy branch from cec9230 to 223345c Compare August 26, 2025 14:50
@toinehartman toinehartman changed the base branch from main to migrate-dap August 27, 2025 08:10
@toinehartman toinehartman changed the base branch from migrate-dap to feature/lsp-formatting August 27, 2025 08:10
@toinehartman toinehartman force-pushed the feature/lsp-formatting branch from 523c2fe to ab448fc Compare August 27, 2025 12:06
@toinehartman toinehartman force-pushed the feature/132-lsp-call-hierarchy branch from 223345c to 811779a Compare August 27, 2025 12:09
@toinehartman toinehartman force-pushed the feature/lsp-formatting branch from b79c77e to 7a9d722 Compare August 29, 2025 12:56
@toinehartman toinehartman force-pushed the feature/132-lsp-call-hierarchy branch from 811779a to e1a09ad Compare August 29, 2025 13:09
@toinehartman toinehartman changed the base branch from feature/lsp-formatting to main August 29, 2025 13:09
@toinehartman toinehartman force-pushed the feature/132-lsp-call-hierarchy branch 2 times, most recently from fb44e22 to 0e08fe4 Compare September 1, 2025 11:35
@toinehartman toinehartman marked this pull request as ready for review September 1, 2025 11:47
@toinehartman toinehartman force-pushed the feature/132-lsp-call-hierarchy branch 4 times, most recently from f57c97f to 6998a70 Compare September 3, 2025 12:29
@toinehartman toinehartman marked this pull request as draft September 9, 2025 10:09
@toinehartman toinehartman force-pushed the feature/132-lsp-call-hierarchy branch from 6998a70 to f71dbb6 Compare September 9, 2025 10:09
@toinehartman toinehartman changed the base branch from main to feature/lsp-range-to-loc September 9, 2025 10:09
@toinehartman toinehartman marked this pull request as ready for review September 15, 2025 11:23
Copy link
Member

@DavyLandman DavyLandman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just 2 small things, but this looks close to merging.

Also: please add an entry to the rascal-vscode-extension/CHANGELOG.md

import io.usethesource.vallang.type.TypeFactory;
import io.usethesource.vallang.type.TypeStore;

public class CallHierarchy {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a small comment on why this class exists, what its purpose is.

Comment on lines +59 to +73
private static final String NAME = "name";
private static final String KIND = "kind";
private static final String DEFINITION = "src";
private static final String SELECTION = "selection";
private static final String TAGS = "tags";
private static final String DETAIL = "detail";
private static final String DATA = "data";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if all these fields are used once, why are they in a separate static field? I do not see how that makes the code more readable? cons.get(NAME) vs cons.get("name")?

@toinehartman toinehartman marked this pull request as draft September 17, 2025 07:33
@toinehartman toinehartman force-pushed the feature/132-lsp-call-hierarchy branch from 94b7827 to 5c0a0b5 Compare October 1, 2025 15:02
@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose callHierachy LSP api for DSLs (and possible for rascal)

5 participants