Skip to content

Conversation

@PieterOlivier
Copy link
Contributor

@PieterOlivier PieterOlivier commented Aug 22, 2025

Design & implementation of completion support for the parametric language server.

This PR also includes support for dynamically registering capabilities.

Copy link
Member

@toinehartman toinehartman left a comment

Choose a reason for hiding this comment

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

I am excited to see this in action! Since we already discussed this design together, my comments mostly consider documentation and naming.

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.

It's looking good, it's not a small API, but it's looking good.

PieterOlivier and others added 10 commits August 26, 2025 11:40
Co-authored-by: Toine Hartman <toinehartman@users.noreply.github.com>
Co-authored-by: Toine Hartman <toinehartman@users.noreply.github.com>
Co-authored-by: Toine Hartman <toinehartman@users.noreply.github.com>
Co-authored-by: Toine Hartman <toinehartman@users.noreply.github.com>
Co-authored-by: Toine Hartman <toinehartman@users.noreply.github.com>
Co-authored-by: Toine Hartman <toinehartman@users.noreply.github.com>
Co-authored-by: Toine Hartman <toinehartman@users.noreply.github.com>
…al-language-servers into completions/parametric
@jurgenvinju
Copy link
Member

It would perhaps be good to test drive this contribution with a generic function that takes a reified grammar and produces a simplified syntax directed completion contribution function (based on error trees or normal trees). To be used as completion(syntaxDirectedCompletion(#start[Program])) for example for Pico?

That way we can see if the LSP side fits on the Rascal side with what we have in mind. Or if we still need to think more about the Rascal side.

@DavyLandman
Copy link
Member

I've discussed the design choice of merging the 2 LSP enums into one ADT with @jurgenvinju and he agrees that in the end it's the cleanest solution. It's not 100% ideal, but the alternatives are worse for the end users.

He indicated he would take a final look at the rest of the API today, as he had some concerns and wanted to verify how they were addressed.

PieterOlivier and others added 3 commits September 22, 2025 09:48
Co-authored-by: Toine Hartman <toinehartman@users.noreply.github.com>
…al-language-servers into completions/parametric
Comment on lines +1 to +17
<?xml version="1.0" encoding="UTF-8"?>
<Configuration xmlns="https://logging.apache.org/xml/ns"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
https://logging.apache.org/xml/ns
https://logging.apache.org/xml/ns/log4j-config-2.xsd">
<Appenders>
<Console name="CONSOLE">
<PatternLayout pattern="%d [%t] %p - %c %m%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="DEBUG">
<AppenderRef ref="CONSOLE"/>
</Root>
</Loggers>
</Configuration>
Copy link
Member

Choose a reason for hiding this comment

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

Currently we don't have any logging from LSP, since log4j is disabled. We do have a lot of logs coming from the Rascal monitor though.

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.

Looks a lot better, but I have some remaining concerns about the merging of capabilities.

public CompletableFuture<Void> updateCapabilities(Collection<ILanguageContributions> contribs) {
// Copy the contributions so we know we are looking at a stable set of elements.
// If the contributions change, we expect our caller to call again.
var stableContribs = new LinkedHashSet<>(contribs);
Copy link
Member

Choose a reason for hiding this comment

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

Good protection against modifying contribs.

Is there a specific reason we're using LinkedHashSet here? and not List.copyOf for example? or ArrayList for that matter?

Copy link
Member

@toinehartman toinehartman Dec 18, 2025

Choose a reason for hiding this comment

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

My rationale was that this should conceptually be a set (unique, unordered). But if we want that, we should enforce that higher up and shift responsibility to our caller.

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.

This is a lot more readable, I have some small concerns about the timing of updates on the map, especially if there is a window where the map might run ahead of registered reality. I don't know what the trade-off is, but at least that is not documented in the code right now.

Comment on lines 158 to 161
// If our administration contains exactly this registration, remove it and inform the client
if (!currentRegistrations.remove(reg.getMethod(), reg)) {
return falsy.get();
}
Copy link
Member

Choose a reason for hiding this comment

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

I think this should only be done after the unregisterCapability has been completed succesfully?

(else this happens 3/4 statements after the .get)

Copy link
Member

@toinehartman toinehartman Dec 23, 2025

Choose a reason for hiding this comment

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

See the answer here #706 (comment)

Comment on lines 176 to 179
// If our administration contains no registration, inform the client
if (currentRegistrations.putIfAbsent(reg.getMethod(), reg) != null) {
return falsy.get();
}
Copy link
Member

Choose a reason for hiding this comment

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

not sure if we should do this here, or after a succesfull register.

Copy link
Member

@toinehartman toinehartman Dec 23, 2025

Choose a reason for hiding this comment

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

The idea here (and similarly for unregister) is the following:

Initial assumption: the client and server have the same administration of registrations.

  1. Atomically verify-and-update the state of the map with the new registration. If someone raced us and we lost, verification fails, no update will happen, and the next steps will be skipped.
  2. Send registerCapability request.
    • If this succeeds: done.
    • If this fails: the capability is not registered on the client. Remove it from our map (verifying the exact value being removed again). Done.
  3. The client and server (currentRegistrations) now have the same administration of registrations.

if (!b.booleanValue()) {
return falsy.get();
}
return register(newRegistration);
Copy link
Member

Choose a reason for hiding this comment

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

this reads better, but what if the unregister fails, shouldn't we at least try to register? as in, what is the state we're leaving ourselves in. I don't know the right answer, but since there is no comment in the code wrt this trade-off, I'm confused about the guarantees.

Copy link
Member

Choose a reason for hiding this comment

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

Added a comment. At lest in VS Code, registration only explicitly fails when registering a capability that is not supported:
https://github.com/microsoft/vscode-languageserver-node/blob/00392bf903f6b6de92ae6f722408d41c8b921637/client/src/common/client.ts#L2187-L2199

In our case, that means programmer error, since AbstractDynamicCapability::isProvidedBy should check this.

Since we are talking about unregister specifically, this should never happen; we only unregister after a previous register succeeded (which requires the capability to be supported).

@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.

4 participants