From 806d89c110f0b958b745b38b26d9bdacab9dd355 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 29 Jul 2025 16:15:32 +0200 Subject: [PATCH 01/23] Add call hierarchy LSP API. --- .../ParametricTextDocumentService.java | 28 +++++++++++++++++++ .../rascal/library/util/LanguageServer.rsc | 14 ++++++++++ 2 files changed, 42 insertions(+) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java index 1fe0e6c9e..186ea5045 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java @@ -50,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; @@ -798,6 +804,28 @@ public CompletableFuture> foldingRange(FoldingRangeRequestPar ), Collections::emptyList); } + + + @Override + public CompletableFuture> callHierarchyIncomingCalls( + CallHierarchyIncomingCallsParams params) { + // TODO Auto-generated method stub + return IBaseTextDocumentService.super.callHierarchyIncomingCalls(params); + } + + @Override + public CompletableFuture> callHierarchyOutgoingCalls( + CallHierarchyOutgoingCallsParams params) { + // TODO Auto-generated method stub + return IBaseTextDocumentService.super.callHierarchyOutgoingCalls(params); + } + + @Override + public CompletableFuture> prepareCallHierarchy(CallHierarchyPrepareParams params) { + // TODO Auto-generated method stub + return IBaseTextDocumentService.super.prepareCallHierarchy(params); + } + @Override public CompletableFuture> selectionRange(SelectionRangeParams params) { logger.debug("Selection range: {} at {}", params.getTextDocument(), params.getPositions()); diff --git a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc index 2716255f5..0f34878ce 100644 --- a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc @@ -277,11 +277,25 @@ data LanguageService , loc (Focus _focus) prepareRenameService = defaultPrepareRenameService) | didRenameFiles(tuple[list[DocumentEdit], set[Message]] (list[DocumentEdit] fileRenames) didRenameFilesService) | selectionRange(list[loc](Focus _focus) selectionRangeService) + | callHierarchy (set[CallHierarchyItem] (Focus _focus) callHierarchyService) + | incomingCalls (set[loc] (loc src, value _data) incomingCallsService) + | outgoingCalls (set[loc] (loc src, value _data) outgoingCallsService) ; loc defaultPrepareRenameService(Focus _:[Tree tr, *_]) = tr.src when tr.src?; default loc defaultPrepareRenameService(Focus focus) { throw IllegalArgument(focus, "Element under cursor does not have source location"); } +data CallHierarchyItem + = item( + str name, + DocumentSymbolKind kind, + loc src, + loc selection, + list[DocumentSymbolTag] tags = [], + str detail = "", + value \data = () + ); + @deprecated{Backward compatible with ((parsing)).} @synopsis{Construct a `parsing` ((LanguageService))} LanguageService parser(Parser parser) = parsing(parser); From 907f36a94bab69c65d17fa7b217fb6037301820a Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 29 Jul 2025 17:44:58 +0200 Subject: [PATCH 02/23] Update call hierarchy API + example. --- .../library/demo/lang/pico/LanguageServer.rsc | 27 +++++++++++++++++-- .../rascal/library/util/LanguageServer.rsc | 6 ++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/rascal-lsp/src/main/rascal/library/demo/lang/pico/LanguageServer.rsc b/rascal-lsp/src/main/rascal/library/demo/lang/pico/LanguageServer.rsc index 1ec7af63b..c46aaab42 100644 --- a/rascal-lsp/src/main/rascal/library/demo/lang/pico/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/library/demo/lang/pico/LanguageServer.rsc @@ -38,9 +38,17 @@ import util::IDEServices; import ParseTree; import util::ParseErrorRecovery; import util::Reflective; -import lang::pico::\syntax::Main; +extend lang::pico::\syntax::Main; import DateTime; +syntax IdType + = func: Type returnType Id id "(" {IdType ","}* params ")" "=" Expression body ";" + ; + +syntax Expression + = call: Id id "(" {Expression ","}* args ")" + ; + private Tree (str _input, loc _origin) picoParser(bool allowRecovery) { return ParseTree::parser(#start[Program], allowRecovery=allowRecovery, filters=allowRecovery ? {createParseErrorFilter(false)} : {}); } @@ -61,7 +69,10 @@ set[LanguageService] picoLanguageServer(bool allowRecovery) = { codeAction(picoCodeActionService), rename(picoRenamingService, prepareRenameService = picoRenamePreparingService), didRenameFiles(picoFileRenameService), - selectionRange(picoSelectionRangeService) + selectionRange(picoSelectionRangeService), + callHierarchy(picoCallHierarchyService), + incomingCalls(picoIncomingCallsService), + outgoingCalls(picoOutgoingCallsService) }; set[LanguageService] picoLanguageServer() = picoLanguageServer(false); @@ -227,6 +238,18 @@ tuple[list[DocumentEdit],set[Message]] picoFileRenameService(list[DocumentEdit] list[loc] picoSelectionRangeService(Focus focus) = dup([t@\loc | t <- focus]); +set[CallHierarchyItem] picoCallHierarchyService(Focus _:[*_, call:(Expression) `(<{Expression ","}* _>)`, *_]) + = {item("", function(), call@\loc)}; + +default set[CallHierarchyItem] picoCallHierarchyService(Focus _) + = {}; + +set[loc] picoIncomingCallsService(Focus focus:[(Expression) `(<{Expression ","}* _>)`, *_], value _) + = {call@\loc | /call:(Expression) `(<{Expression ","}* _>)` := focus[-1], "" == ""}; + +set[loc] picoOutgoingCallsService(Focus focus:[(Expression) `(<{Expression ","}* _>)`, *_], value _) + = {call@\loc | /call:(Expression) `(<{Expression ","}* _>)` := focus[-1], "" == ""}; + @synopsis{The main function registers the Pico language with the IDE} @description{ Register the Pico language and the contributions that supply the IDE with features. diff --git a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc index 0f34878ce..1e99d1138 100644 --- a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc @@ -278,8 +278,8 @@ data LanguageService | didRenameFiles(tuple[list[DocumentEdit], set[Message]] (list[DocumentEdit] fileRenames) didRenameFilesService) | selectionRange(list[loc](Focus _focus) selectionRangeService) | callHierarchy (set[CallHierarchyItem] (Focus _focus) callHierarchyService) - | incomingCalls (set[loc] (loc src, value _data) incomingCallsService) - | outgoingCalls (set[loc] (loc src, value _data) outgoingCallsService) + | incomingCalls (set[loc] (Focus focus, value _data) incomingCallsService) + | outgoingCalls (set[loc] (Focus focus, value _data) outgoingCallsService) ; loc defaultPrepareRenameService(Focus _:[Tree tr, *_]) = tr.src when tr.src?; @@ -290,7 +290,7 @@ data CallHierarchyItem str name, DocumentSymbolKind kind, loc src, - loc selection, + loc selection = src, list[DocumentSymbolTag] tags = [], str detail = "", value \data = () From d992f3b04ab268b7e8908392938cbf3500b31b90 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 31 Jul 2025 15:22:46 +0200 Subject: [PATCH 03/23] Extend incoming/outgoing call signatures to carry more information. --- .../library/demo/lang/pico/LanguageServer.rsc | 16 +--------------- .../main/rascal/library/util/LanguageServer.rsc | 4 ++-- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/rascal-lsp/src/main/rascal/library/demo/lang/pico/LanguageServer.rsc b/rascal-lsp/src/main/rascal/library/demo/lang/pico/LanguageServer.rsc index c46aaab42..31b7cd2b1 100644 --- a/rascal-lsp/src/main/rascal/library/demo/lang/pico/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/library/demo/lang/pico/LanguageServer.rsc @@ -70,9 +70,7 @@ set[LanguageService] picoLanguageServer(bool allowRecovery) = { rename(picoRenamingService, prepareRenameService = picoRenamePreparingService), didRenameFiles(picoFileRenameService), selectionRange(picoSelectionRangeService), - callHierarchy(picoCallHierarchyService), - incomingCalls(picoIncomingCallsService), - outgoingCalls(picoOutgoingCallsService) + codeAction(picoCodeActionService) }; set[LanguageService] picoLanguageServer() = picoLanguageServer(false); @@ -238,18 +236,6 @@ tuple[list[DocumentEdit],set[Message]] picoFileRenameService(list[DocumentEdit] list[loc] picoSelectionRangeService(Focus focus) = dup([t@\loc | t <- focus]); -set[CallHierarchyItem] picoCallHierarchyService(Focus _:[*_, call:(Expression) `(<{Expression ","}* _>)`, *_]) - = {item("", function(), call@\loc)}; - -default set[CallHierarchyItem] picoCallHierarchyService(Focus _) - = {}; - -set[loc] picoIncomingCallsService(Focus focus:[(Expression) `(<{Expression ","}* _>)`, *_], value _) - = {call@\loc | /call:(Expression) `(<{Expression ","}* _>)` := focus[-1], "" == ""}; - -set[loc] picoOutgoingCallsService(Focus focus:[(Expression) `(<{Expression ","}* _>)`, *_], value _) - = {call@\loc | /call:(Expression) `(<{Expression ","}* _>)` := focus[-1], "" == ""}; - @synopsis{The main function registers the Pico language with the IDE} @description{ Register the Pico language and the contributions that supply the IDE with features. diff --git a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc index 1e99d1138..a26b97f17 100644 --- a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc @@ -278,8 +278,8 @@ data LanguageService | didRenameFiles(tuple[list[DocumentEdit], set[Message]] (list[DocumentEdit] fileRenames) didRenameFilesService) | selectionRange(list[loc](Focus _focus) selectionRangeService) | callHierarchy (set[CallHierarchyItem] (Focus _focus) callHierarchyService) - | incomingCalls (set[loc] (Focus focus, value _data) incomingCallsService) - | outgoingCalls (set[loc] (Focus focus, value _data) outgoingCallsService) + | incomingCalls (rel[CallHierarchyItem, loc] (CallHierarchyItem f, Focus focus) incomingCallsService) + | outgoingCalls (rel[CallHierarchyItem, loc] (CallHierarchyItem f, Focus focus) outgoingCallsService) ; loc defaultPrepareRenameService(Focus _:[Tree tr, *_]) = tr.src when tr.src?; From 7759d8375c181ae288220d175dfade547f059d15 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 31 Jul 2025 17:33:38 +0200 Subject: [PATCH 04/23] Name relation fields. --- rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc index a26b97f17..dbf4e6075 100644 --- a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc @@ -278,8 +278,8 @@ data LanguageService | didRenameFiles(tuple[list[DocumentEdit], set[Message]] (list[DocumentEdit] fileRenames) didRenameFilesService) | selectionRange(list[loc](Focus _focus) selectionRangeService) | callHierarchy (set[CallHierarchyItem] (Focus _focus) callHierarchyService) - | incomingCalls (rel[CallHierarchyItem, loc] (CallHierarchyItem f, Focus focus) incomingCallsService) - | outgoingCalls (rel[CallHierarchyItem, loc] (CallHierarchyItem f, Focus focus) outgoingCallsService) + | incomingCalls (rel[CallHierarchyItem def, loc calls] (CallHierarchyItem _f, Focus _focus) incomingCallsService) + | outgoingCalls (rel[CallHierarchyItem def, loc calls] (CallHierarchyItem _f, Focus _focus) outgoingCallsService) ; loc defaultPrepareRenameService(Focus _:[Tree tr, *_]) = tr.src when tr.src?; From 62a71721f369d8819f6879e8f8e0abcd0dd1da24 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 4 Aug 2025 15:42:45 +0200 Subject: [PATCH 05/23] Prevent type-check by using pre-computed summary. --- .../main/rascal/library/util/LanguageServer.rsc | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc index dbf4e6075..488d8976a 100644 --- a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc @@ -277,9 +277,9 @@ data LanguageService , loc (Focus _focus) prepareRenameService = defaultPrepareRenameService) | didRenameFiles(tuple[list[DocumentEdit], set[Message]] (list[DocumentEdit] fileRenames) didRenameFilesService) | selectionRange(list[loc](Focus _focus) selectionRangeService) - | callHierarchy (set[CallHierarchyItem] (Focus _focus) callHierarchyService) - | incomingCalls (rel[CallHierarchyItem def, loc calls] (CallHierarchyItem _f, Focus _focus) incomingCallsService) - | outgoingCalls (rel[CallHierarchyItem def, loc calls] (CallHierarchyItem _f, Focus _focus) outgoingCallsService) + | callHierarchy (set[loc] (Focus _focus, Summary _s) callHierarchyService) + | incomingCalls (rel[loc toDef, loc call] (CallHierarchyItem _f, Tree _input, Summary _s) incomingCallsService) + | outgoingCalls (rel[loc fromDef, loc call] (CallHierarchyItem _f, Tree _input, Summary _s) outgoingCallsService) ; loc defaultPrepareRenameService(Focus _:[Tree tr, *_]) = tr.src when tr.src?; @@ -290,10 +290,10 @@ data CallHierarchyItem str name, DocumentSymbolKind kind, loc src, - loc selection = src, - list[DocumentSymbolTag] tags = [], - str detail = "", - value \data = () + loc selection = src, // location of `name` typically needs to come from parse tree + set[DocumentSymbolTag] tags = {}, // as of now only `deprecated()`, probably unused often + str detail = "", // e.g. function signature + value \data = () // to share state between `prepareCallHierarchy` and `incomingCalls`/`outgoingCalls` ); @deprecated{Backward compatible with ((parsing)).} @@ -505,6 +505,7 @@ data Summary = summary(loc src, rel[loc, str] documentation = {}, rel[loc, str] hovers = documentation, rel[loc, loc] definitions = {}, + map[loc, tuple[str id, loc idLoc, DocumentSymbolKind kind, set[DocumentSymbolTag] tags]] definitionDetails = (), rel[loc, loc] references = {}, rel[loc, loc] implementations = {} ); From ce663e1011b4c6904243d1c74e472b069c565752 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 14 Aug 2025 11:57:27 +0200 Subject: [PATCH 06/23] Group & simplify callhierarchy APIs. --- .../src/main/rascal/library/util/LanguageServer.rsc | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc index 488d8976a..63dd021bd 100644 --- a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc @@ -277,9 +277,9 @@ data LanguageService , loc (Focus _focus) prepareRenameService = defaultPrepareRenameService) | didRenameFiles(tuple[list[DocumentEdit], set[Message]] (list[DocumentEdit] fileRenames) didRenameFilesService) | selectionRange(list[loc](Focus _focus) selectionRangeService) - | callHierarchy (set[loc] (Focus _focus, Summary _s) callHierarchyService) - | incomingCalls (rel[loc toDef, loc call] (CallHierarchyItem _f, Tree _input, Summary _s) incomingCallsService) - | outgoingCalls (rel[loc fromDef, loc call] (CallHierarchyItem _f, Tree _input, Summary _s) outgoingCallsService) + | callHierarchy ( + set[CallHierarchyItem] (Focus _focus) callableItem, + rel[loc item, loc call] (CallHierarchyItem _ci, Tree _input, CallDirection _dir) calculateCalls) ; loc defaultPrepareRenameService(Focus _:[Tree tr, *_]) = tr.src when tr.src?; @@ -296,6 +296,11 @@ data CallHierarchyItem value \data = () // to share state between `prepareCallHierarchy` and `incomingCalls`/`outgoingCalls` ); +data CallDirection + = incoming() + | outgoing() + ; + @deprecated{Backward compatible with ((parsing)).} @synopsis{Construct a `parsing` ((LanguageService))} LanguageService parser(Parser parser) = parsing(parser); @@ -505,7 +510,6 @@ data Summary = summary(loc src, rel[loc, str] documentation = {}, rel[loc, str] hovers = documentation, rel[loc, loc] definitions = {}, - map[loc, tuple[str id, loc idLoc, DocumentSymbolKind kind, set[DocumentSymbolTag] tags]] definitionDetails = (), rel[loc, loc] references = {}, rel[loc, loc] implementations = {} ); From f039091f8acad40c03ff5efbc3c7efec000d7882 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 14 Aug 2025 14:46:19 +0200 Subject: [PATCH 07/23] Rename call item constructor, remove default, document fields. --- .../src/main/rascal/library/util/LanguageServer.rsc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc index 63dd021bd..fe14ceb5e 100644 --- a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc @@ -286,14 +286,14 @@ loc defaultPrepareRenameService(Focus _:[Tree tr, *_]) = tr.src when tr.src?; default loc defaultPrepareRenameService(Focus focus) { throw IllegalArgument(focus, "Element under cursor does not have source location"); } data CallHierarchyItem - = item( + = callItem( str name, DocumentSymbolKind kind, - loc src, - loc selection = src, // location of `name` typically needs to come from parse tree - set[DocumentSymbolTag] tags = {}, // as of now only `deprecated()`, probably unused often - str detail = "", // e.g. function signature - value \data = () // to share state between `prepareCallHierarchy` and `incomingCalls`/`outgoingCalls` + loc src, // location of the definition + loc selection, // location of the name of the definition + set[DocumentSymbolTag] tags = {}, + str detail = "", // detailed description, e.g. the function signature + value \data = () // shared state between `callHierarchy::callableItem` and `callHierarchy::calculateCalls` ); data CallDirection From 77c81286455801db6518d4b8a6ab7fc4cad8f47c Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 14 Aug 2025 14:46:57 +0200 Subject: [PATCH 08/23] Return ordered hierarchy items and leave parsing to implementer. --- rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc index fe14ceb5e..1f68a6379 100644 --- a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc @@ -278,8 +278,8 @@ data LanguageService | didRenameFiles(tuple[list[DocumentEdit], set[Message]] (list[DocumentEdit] fileRenames) didRenameFilesService) | selectionRange(list[loc](Focus _focus) selectionRangeService) | callHierarchy ( - set[CallHierarchyItem] (Focus _focus) callableItem, - rel[loc item, loc call] (CallHierarchyItem _ci, Tree _input, CallDirection _dir) calculateCalls) + list[CallHierarchyItem] (Focus _focus) callableItem, + lrel[CallHierarchyItem item, loc call] (CallHierarchyItem _ci, CallDirection _dir) calculateCalls) ; loc defaultPrepareRenameService(Focus _:[Tree tr, *_]) = tr.src when tr.src?; From 4ee3719a0db198d2dea7c6e4aad0b448e12b16c6 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 15 Aug 2025 11:11:13 +0200 Subject: [PATCH 09/23] Document call hierarchy service. --- rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc index 1f68a6379..fce400285 100644 --- a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc @@ -159,7 +159,7 @@ alias Implementer = set[loc] (loc _origin, Tree _fullTree, Tree _lexicalAtCursor @synopsis{Each kind of service contibutes the implementation of one (or several) IDE features.} @description{ Each LanguageService constructor provides one aspect of definining the language server protocol (LSP). -Their names coincide exactly with the services which are documented [here](https://microsoft.github.io/language-server-protocol/). +Their names coincide with the services which are documented [here](https://microsoft.github.io/language-server-protocol/). * The ((parsing)) service that maps source code strings to a ((ParseTree::Tree)) is essential and non-optional. All other other services are optional. @@ -212,6 +212,9 @@ hover documentation, definition with uses, references to declarations, implement * The optional `prepareRename` service argument discovers places in the editor where a ((util::LanguageServer::rename)) is possible. If renameing the location is not supported, it should throw an exception. * The ((didRenameFiles)) service collects ((DocumentEdit))s corresponding to renamed files (e.g. to rename a class when the class file was renamed). The IDE applies the edits after moving the files. It might fail and report why in diagnostics. * The ((selectionRange)) service discovers selections around a cursor, that a user might want to select. It expects the list of source locations to be in ascending order of size (each location should be contained by the next) - similar to ((Focus)) trees. +* The ((callHierarchy)) service discovers callable definitions and call sites. It consists of two subservices. + 1. The first argument, `callableItem`, computes ((CallHierarchyItem))s (definitions) for a given cursor. + 2. The second argument, `calculateCalls`, computes ((incoming)) or ((outgoing)) calls (uses) of a given ((CallHierarchyItem)) `ci`. It returns a list relation of ((CallHierarchyItem))s and the location(s) of the call(s) to `ci` these definitions have. Many services receive a ((Focus)) parameter. The focus lists the syntactical constructs under the current cursor, from the current leaf all the way up to the root of the tree. This list helps to create functionality that is syntax-directed, and always relevant to the From 0e0e8110b1f01178814d6bd799aca2da972b69a2 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 26 Aug 2025 16:09:34 +0200 Subject: [PATCH 10/23] Implement parametric call hierarchies. --- .../parametric/ILanguageContributions.java | 5 + .../InterpretedLanguageContributions.java | 32 +++++- .../LanguageContributionsMultiplexer.java | 23 +++++ .../ParametricTextDocumentService.java | 73 +++++++++----- .../parametric/ParserOnlyContribution.java | 16 +++ .../lsp/parametric/model/RascalADTs.java | 1 + .../vscode/lsp/util/CallHierarchy.java | 98 +++++++++++++++++++ .../vscode/lsp/util/DocumentSymbols.java | 41 ++++++-- .../rascal/library/util/LanguageServer.rsc | 6 +- 9 files changed, 259 insertions(+), 36 deletions(-) create mode 100644 rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java index e26fb3fc2..962361760 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java @@ -34,8 +34,10 @@ 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.IRelation; import io.usethesource.vallang.ISet; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.ITuple; @@ -60,6 +62,8 @@ public interface ILanguageContributions { public InterruptibleFuture implementation(IList focus); public InterruptibleFuture codeAction(IList focus); public InterruptibleFuture selectionRange(IList focus); + public InterruptibleFuture prepareCallHierarchy(IList focus); + public InterruptibleFuture> incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction); public InterruptibleFuture prepareRename(IList focus); public InterruptibleFuture rename(IList focus, String name); @@ -81,6 +85,7 @@ public interface ILanguageContributions { public CompletableFuture hasCodeAction(); public CompletableFuture hasDidRenameFiles(); public CompletableFuture hasSelectionRange(); + public CompletableFuture hasCallHierarchy(); public CompletableFuture specialCaseHighlighting(); diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java index e08d3c027..a4ab495ea 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java @@ -31,6 +31,7 @@ import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.Nullable; @@ -52,9 +53,11 @@ 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; +import io.usethesource.vallang.IRelation; import io.usethesource.vallang.ISet; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.ITuple; @@ -93,6 +96,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 hasAnalysis; private final CompletableFuture hasBuild; @@ -108,6 +113,7 @@ public class InterpretedLanguageContributions implements ILanguageContributions private final CompletableFuture hasRename; private final CompletableFuture hasDidRenameFiles; private final CompletableFuture hasSelectionRange; + private final CompletableFuture hasCallHierarchy; private final CompletableFuture specialCaseHighlighting; @@ -154,6 +160,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); @@ -170,6 +178,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, @@ -285,7 +294,11 @@ private CompletableFuture parseCommand(String command) { } private static CompletableFuture<@Nullable IFunction> getFunctionFor(CompletableFuture 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 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 contributions, String cons, String kwParam) { @@ -389,6 +402,18 @@ public InterruptibleFuture selectionRange(IList focus) { return execFunction(LanguageContributions.SELECTION_RANGE, selectionRange, VF.list(), focus); } + @Override + public InterruptibleFuture prepareCallHierarchy(IList focus) { + debug(LanguageContributions.CALL_HIERARCHY, "prepare", focus.length()); + return execFunction(LanguageContributions.CALL_HIERARCHY, prepareCallHierarchy, VF.list(), focus); + } + + @Override + public InterruptibleFuture> incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction) { + debug(LanguageContributions.CALL_HIERARCHY, hierarchyItem.has("name") ? hierarchyItem.get("name") : "?", direction.getName()); + return execFunction(LanguageContributions.CALL_HIERARCHY, callHierarchyService, VF.list().asRelation(), hierarchyItem, direction); + } + private void debug(String name, Object param) { logger.debug("{}({})", name, param); } @@ -457,6 +482,11 @@ public CompletableFuture hasSelectionRange() { return hasSelectionRange; } + @Override + public CompletableFuture hasCallHierarchy() { + return hasCallHierarchy; + } + @Override public CompletableFuture hasAnalysis() { return hasAnalysis; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java index 4917cf8ac..e70b27849 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java @@ -36,8 +36,10 @@ 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.IRelation; import io.usethesource.vallang.ISet; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.ITuple; @@ -70,6 +72,8 @@ private static final CompletableFuture failedInitialization() { private volatile CompletableFuture rename = failedInitialization(); private volatile CompletableFuture didRenameFiles = failedInitialization(); private volatile CompletableFuture selectionRange = failedInitialization(); + private volatile CompletableFuture prepareCallHierarchy = failedInitialization(); + private volatile CompletableFuture incomingOutgoingCalls = failedInitialization(); private volatile CompletableFuture hasAnalysis = failedInitialization(); private volatile CompletableFuture hasBuild = failedInitialization(); @@ -85,6 +89,7 @@ private static final CompletableFuture failedInitialization() { private volatile CompletableFuture hasRename = failedInitialization(); private volatile CompletableFuture hasDidRenameFiles = failedInitialization(); private volatile CompletableFuture hasSelectionRange = failedInitialization(); + private volatile CompletableFuture hasCallHierarchy = failedInitialization(); private volatile CompletableFuture specialCaseHighlighting = failedInitialization(); @@ -162,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); @@ -177,6 +184,7 @@ private synchronized void calculateRouting() { 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 @@ -337,6 +345,16 @@ public InterruptibleFuture selectionRange(IList focus) { return flatten(selectionRange, c -> c.selectionRange(focus)); } + @Override + public InterruptibleFuture prepareCallHierarchy(IList focus) { + return flatten(prepareCallHierarchy, c -> c.prepareCallHierarchy(focus)); + } + + @Override + public InterruptibleFuture> incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction) { + return flatten(incomingOutgoingCalls, c -> c.incomingOutgoingCalls(hierarchyItem, direction)); + } + @Override public CompletableFuture hasCodeAction() { return hasCodeAction; @@ -402,6 +420,11 @@ public CompletableFuture hasDidRenameFiles() { return hasDidRenameFiles; } + @Override + public CompletableFuture hasCallHierarchy() { + return hasCallHierarchy; + } + @Override public CompletableFuture specialCaseHighlighting() { return specialCaseHighlighting; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java index 186ea5045..9ebd49b73 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java @@ -38,6 +38,7 @@ 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; @@ -130,6 +131,7 @@ 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; @@ -246,6 +248,7 @@ public void initializeServerCapabilities(ServerCapabilities result) { result.setInlayHintProvider(true); result.setSelectionRangeProvider(true); result.setFoldingRangeProvider(true); + result.setCallHierarchyProvider(true); } private String getRascalMetaCommandName() { @@ -414,7 +417,7 @@ private CompletableFuture computeRenameRange(final ILanguageCon public CompletableFuture rename(RenameParams params) { logger.trace("rename for: {}, new name: {}", params.getTextDocument().getUri(), params.getNewName()); final ILanguageContributions contribs = contributions(params.getTextDocument()); - final Position rascalPos = Locations.toRascalPosition(params.getTextDocument(), params.getPosition(), columns);; + final Position rascalPos = Locations.toRascalPosition(params.getTextDocument(), params.getPosition(), columns); return getFile(params.getTextDocument()) .getCurrentTreeAsync() .thenApply(Versioned::get) @@ -804,28 +807,6 @@ public CompletableFuture> foldingRange(FoldingRangeRequestPar ), Collections::emptyList); } - - - @Override - public CompletableFuture> callHierarchyIncomingCalls( - CallHierarchyIncomingCallsParams params) { - // TODO Auto-generated method stub - return IBaseTextDocumentService.super.callHierarchyIncomingCalls(params); - } - - @Override - public CompletableFuture> callHierarchyOutgoingCalls( - CallHierarchyOutgoingCallsParams params) { - // TODO Auto-generated method stub - return IBaseTextDocumentService.super.callHierarchyOutgoingCalls(params); - } - - @Override - public CompletableFuture> prepareCallHierarchy(CallHierarchyPrepareParams params) { - // TODO Auto-generated method stub - return IBaseTextDocumentService.super.prepareCallHierarchy(params); - } - @Override public CompletableFuture> selectionRange(SelectionRangeParams params) { logger.debug("Selection range: {} at {}", params.getTextDocument(), params.getPositions()); @@ -849,6 +830,52 @@ public CompletableFuture> selectionRange(SelectionRangePara Collections::emptyList); } + @Override + public CompletableFuture> prepareCallHierarchy(CallHierarchyPrepareParams params) { + final var doc = params.getTextDocument(); + final var contrib = contributions(doc); + final var file = getFile(doc); + + return recoverExceptions(file.getCurrentTreeAsync() + .thenApply(Versioned::get) + .thenCompose(t -> { + final var pos = Locations.toRascalPosition(doc, params.getPosition(), columns); + final var focus = TreeSearch.computeFocusList(t, pos.getLine(), pos.getCharacter()); + return contrib.prepareCallHierarchy(focus).get(); + }) + .thenApply(items -> items.stream() + .map(IConstructor.class::cast) + .map(ci -> CallHierarchy.toLSP(ci, columns)) + .collect(Collectors.toList())), Collections::emptyList); + } + + private CompletableFuture> incomingOutgoingCalls(BiFunction, T> constructor, CallHierarchyItem source, IConstructor direction) { + final var contrib = contributions(source.getUri()); + return contrib.incomingOutgoingCalls(CallHierarchy.toRascal(source, columns), direction) + .get() + .thenApply(callRel -> callRel.domain().stream() + .map(ci -> { + List callSites = callRel.index(ci) + .stream() + .map(ISourceLocation.class::cast) + .map(l -> Locations.toRange(l, columns)) + .collect(Collectors.toList()); + return constructor.apply(CallHierarchy.toLSP((IConstructor) ci, columns), callSites); + }) + .collect(Collectors.toList())); + } + + @Override + public CompletableFuture> callHierarchyIncomingCalls(CallHierarchyIncomingCallsParams params) { + return recoverExceptions(incomingOutgoingCalls(CallHierarchyIncomingCall::new, params.getItem(), CallHierarchy.INCOMING),Collections::emptyList); + } + + @Override + public CompletableFuture> callHierarchyOutgoingCalls(CallHierarchyOutgoingCallsParams params) { + return recoverExceptions(incomingOutgoingCalls(CallHierarchyOutgoingCall::new, params.getItem(), CallHierarchy.OUTGOING),Collections::emptyList); + } + + @Override public synchronized void registerLanguage(LanguageParameter lang) { logger.info("registerLanguage({})", lang.getName()); diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java index 3b0f6cdcb..73c79ed05 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java @@ -48,6 +48,7 @@ import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.IList; +import io.usethesource.vallang.IRelation; import io.usethesource.vallang.ISet; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.ITuple; @@ -199,6 +200,16 @@ public InterruptibleFuture selectionRange(IList focus) { return InterruptibleFuture.completedFuture(VF.list()); } + @Override + public InterruptibleFuture prepareCallHierarchy(IList focus) { + return InterruptibleFuture.completedFuture(VF.list()); + } + + @Override + public InterruptibleFuture> incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction) { + return InterruptibleFuture.completedFuture(VF.list().asRelation()); + } + @Override public CompletableFuture hasHover() { return CompletableFuture.completedFuture(false); @@ -269,6 +280,11 @@ public CompletableFuture hasSelectionRange() { return CompletableFuture.completedFuture(false); } + @Override + public CompletableFuture hasCallHierarchy() { + return CompletableFuture.completedFuture(false); + } + @Override public CompletableFuture specialCaseHighlighting() { return specialCaseHighlighting; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/RascalADTs.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/RascalADTs.java index 09c90f8a3..43b6d3bb6 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/RascalADTs.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/RascalADTs.java @@ -48,6 +48,7 @@ private LanguageContributions () {} public static final String IMPLEMENTATION = "implementation"; public static final String CODE_ACTION = "codeAction"; public static final String SELECTION_RANGE = "selectionRange"; + public static final String CALL_HIERARCHY = "callHierarchy"; public static final String RENAME_SERVICE = "renameService"; public static final String PREPARE_RENAME_SERVICE = "prepareRenameService"; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java new file mode 100644 index 000000000..739d5bbaa --- /dev/null +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2018-2025, NWO-I CWI and Swat.engineering + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.rascalmpl.vscode.lsp.util; + +import java.util.List; +import java.util.Map; + +import org.eclipse.lsp4j.CallHierarchyItem; +import org.rascalmpl.values.IRascalValueFactory; +import org.rascalmpl.vscode.lsp.util.locations.ColumnMaps; +import org.rascalmpl.vscode.lsp.util.locations.Locations; + +import io.usethesource.vallang.IConstructor; +import io.usethesource.vallang.ISet; +import io.usethesource.vallang.ISourceLocation; +import io.usethesource.vallang.IValue; +import io.usethesource.vallang.type.Type; +import io.usethesource.vallang.type.TypeFactory; +import io.usethesource.vallang.type.TypeStore; + +public class CallHierarchy { + private static final IRascalValueFactory VF = IRascalValueFactory.getInstance(); + private static final TypeFactory TF = TypeFactory.getInstance(); + private static final TypeStore store = new TypeStore(); + + private static final Type directionAdt = TF.abstractDataType(store, "CallDirection"); + + public static final IConstructor INCOMING = VF.constructor(TF.constructor(store, directionAdt, "incoming")); + public static final IConstructor OUTGOING = VF.constructor(TF.constructor(store, directionAdt, "outgoing")); + + private static final Type callHierarchyItemAdt = TF.abstractDataType(store, "CallHierarchyItem"); + private static final Type callHierarchyItemCons = TF.constructor(store, callHierarchyItemAdt, "callItem", + TF.stringType(), DocumentSymbols.symbolKindAdt, TF.sourceLocationType(), TF.sourceLocationType()); + + 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"; + + private CallHierarchy() { /* hide constructor */} + + public static CallHierarchyItem toLSP(IConstructor cons, ColumnMaps columns) { + var name = cons.get(NAME).toString(); + var kind = DocumentSymbols.symbolKindToLSP((IConstructor) cons.get(KIND)); + var def = (ISourceLocation) cons.get(DEFINITION); + var definitionRange = Locations.toRange(def, columns); + var selection = (ISourceLocation) cons.get(SELECTION); + var selectionRange = Locations.toRange(selection, columns); + + var ci = new CallHierarchyItem(name, kind, def.top().getURI().toString(), definitionRange, selectionRange); + var kws = cons.asWithKeywordParameters(); + ci.setTags(DocumentSymbols.symbolTagsToLSP((ISet) kws.getParameter(TAGS))); + ci.setDetail(kws.getParameter(DETAIL).toString()); + ci.setData(kws.getParameter(DATA)); + + return ci; + } + + public static IConstructor toRascal(CallHierarchyItem ci, ColumnMaps columns) { + return VF.constructor(callHierarchyItemCons, List.of( + VF.string(ci.getName()), + VF.constructor(TF.constructor(store, DocumentSymbols.symbolKindAdt, ci.getKind().name())), + null, // TODO Use generation code from https://github.com/usethesource/rascal-language-servers/pull/677 + null // TODO Use generation code from https://github.com/usethesource/rascal-language-servers/pull/677 + ).toArray(new IValue[0]), Map.of( + TAGS, DocumentSymbols.symbolTagsToRascal(ci.getTags()), + DETAIL, VF.string(ci.getDetail()), + DATA, (IValue) ci.getData() + )); + } +} diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/DocumentSymbols.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/DocumentSymbols.java index 0eb72f34e..0b82cea8f 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/DocumentSymbols.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/DocumentSymbols.java @@ -36,6 +36,7 @@ import org.eclipse.lsp4j.SymbolKind; import org.eclipse.lsp4j.SymbolTag; import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.vscode.lsp.util.locations.LineColumnOffsetMap; import org.rascalmpl.vscode.lsp.util.locations.Locations; @@ -45,8 +46,18 @@ import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.IString; import io.usethesource.vallang.IWithKeywordParameters; +import io.usethesource.vallang.type.Type; +import io.usethesource.vallang.type.TypeFactory; +import io.usethesource.vallang.type.TypeStore; public class DocumentSymbols { + private static final IRascalValueFactory VF = IRascalValueFactory.getInstance(); + private static final TypeFactory TF = TypeFactory.getInstance(); + private static final TypeStore store = new TypeStore(); + + public static final Type symbolKindAdt = TF.abstractDataType(store, "DocumentSymbolKind"); + public static final Type symbolTagAdt = TF.abstractDataType(store, "DocumentSymbolTag"); + // hide constructor for static class private DocumentSymbols() {} @@ -83,8 +94,7 @@ public static DocumentSymbol toLSP(IConstructor symbol, final LineColumnOffsetMa .collect(Collectors.toList()) : Collections.emptyList(); - String kindName = ((IConstructor) symbol.get("kind")).getName(); - SymbolKind kind = SymbolKind.valueOf(capitalize(kindName)); + SymbolKind kind = symbolKindToLSP((IConstructor) symbol.get("kind")); String symbolName = ((IString) symbol.get("name")).getValue(); Range range = Locations.toRange((ISourceLocation) symbol.get("range"), om); Range selection = kwp.hasParameter("selection") @@ -92,17 +102,30 @@ public static DocumentSymbol toLSP(IConstructor symbol, final LineColumnOffsetMa : range; String detail = kwp.hasParameter("detail") ? ((IString) kwp.getParameter("detail")).getValue() : null; List tags = kwp.hasParameter("tags") ? - ((ISet) kwp.getParameter("tags")) - .stream() - .map(IConstructor.class::cast) - .map(IConstructor::getName) - .map(DocumentSymbols::capitalize) - .map(SymbolTag::valueOf) - .collect(Collectors.toList()) + symbolTagsToLSP((ISet) kwp.getParameter("tags")) : Collections.emptyList(); var lspSymbol = new DocumentSymbol(symbolName, kind, range, selection, detail, children); lspSymbol.setTags(tags); // since 3.16 return lspSymbol; } + + public static SymbolKind symbolKindToLSP(IConstructor kind) { + return SymbolKind.valueOf(capitalize(kind.getName())); + } + + public static List symbolTagsToLSP(ISet tags) { + return tags.stream() + .map(IConstructor.class::cast) + .map(IConstructor::getName) + .map(DocumentSymbols::capitalize) + .map(SymbolTag::valueOf) + .collect(Collectors.toList()); + } + + public static ISet symbolTagsToRascal(List tags) { + return tags.stream() + .map(t -> VF.constructor(TF.constructor(store, symbolTagAdt, t.name().toLowerCase()))) + .collect(VF.setWriter()); + } } diff --git a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc index fce400285..4833826a4 100644 --- a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc @@ -281,8 +281,8 @@ data LanguageService | didRenameFiles(tuple[list[DocumentEdit], set[Message]] (list[DocumentEdit] fileRenames) didRenameFilesService) | selectionRange(list[loc](Focus _focus) selectionRangeService) | callHierarchy ( - list[CallHierarchyItem] (Focus _focus) callableItem, - lrel[CallHierarchyItem item, loc call] (CallHierarchyItem _ci, CallDirection _dir) calculateCalls) + list[CallHierarchyItem] (Focus _focus) prepareService, + lrel[CallHierarchyItem item, loc call] (CallHierarchyItem _ci, CallDirection _dir) callsService) ; loc defaultPrepareRenameService(Focus _:[Tree tr, *_]) = tr.src when tr.src?; @@ -296,7 +296,7 @@ data CallHierarchyItem loc selection, // location of the name of the definition set[DocumentSymbolTag] tags = {}, str detail = "", // detailed description, e.g. the function signature - value \data = () // shared state between `callHierarchy::callableItem` and `callHierarchy::calculateCalls` + value \data = () // shared state between `callHierarchy::prepareService` and `callHierarchy::callsService` ); data CallDirection From 1066f04960bf12dcae6822a5098ad254247bb81c Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 27 Aug 2025 14:03:01 +0200 Subject: [PATCH 11/23] Change constructor name. --- .../main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java | 2 +- rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java index 739d5bbaa..bf61c773f 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java @@ -53,7 +53,7 @@ public class CallHierarchy { public static final IConstructor OUTGOING = VF.constructor(TF.constructor(store, directionAdt, "outgoing")); private static final Type callHierarchyItemAdt = TF.abstractDataType(store, "CallHierarchyItem"); - private static final Type callHierarchyItemCons = TF.constructor(store, callHierarchyItemAdt, "callItem", + private static final Type callHierarchyItemCons = TF.constructor(store, callHierarchyItemAdt, "callHierarchyItem", TF.stringType(), DocumentSymbols.symbolKindAdt, TF.sourceLocationType(), TF.sourceLocationType()); private static final String NAME = "name"; diff --git a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc index 4833826a4..af634c048 100644 --- a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc @@ -289,7 +289,7 @@ loc defaultPrepareRenameService(Focus _:[Tree tr, *_]) = tr.src when tr.src?; default loc defaultPrepareRenameService(Focus focus) { throw IllegalArgument(focus, "Element under cursor does not have source location"); } data CallHierarchyItem - = callItem( + = callHierarchyItem( str name, DocumentSymbolKind kind, loc src, // location of the definition From 4577d67b7b95285dd3bc80e13db4b6c5f60f5e12 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Fri, 29 Aug 2025 15:08:51 +0200 Subject: [PATCH 12/23] Generate source locations from ranges. --- .../java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java index bf61c773f..361acef0b 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java @@ -87,8 +87,8 @@ public static IConstructor toRascal(CallHierarchyItem ci, ColumnMaps columns) { return VF.constructor(callHierarchyItemCons, List.of( VF.string(ci.getName()), VF.constructor(TF.constructor(store, DocumentSymbols.symbolKindAdt, ci.getKind().name())), - null, // TODO Use generation code from https://github.com/usethesource/rascal-language-servers/pull/677 - null // TODO Use generation code from https://github.com/usethesource/rascal-language-servers/pull/677 + Locations.setRange(Locations.toLoc(ci.getUri()), ci.getRange(), columns), + Locations.setRange(Locations.toLoc(ci.getUri()), ci.getSelectionRange(), columns) ).toArray(new IValue[0]), Map.of( TAGS, DocumentSymbols.symbolTagsToRascal(ci.getTags()), DETAIL, VF.string(ci.getDetail()), From e5a1c8af1d90c8e406041da0cebe33585d958f03 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 1 Sep 2025 13:26:20 +0200 Subject: [PATCH 13/23] Document call hierarchy ADT. --- .../src/main/rascal/library/util/LanguageServer.rsc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc index af634c048..bbc4fe0c3 100644 --- a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc @@ -288,6 +288,17 @@ data LanguageService loc defaultPrepareRenameService(Focus _:[Tree tr, *_]) = tr.src when tr.src?; default loc defaultPrepareRenameService(Focus focus) { throw IllegalArgument(focus, "Element under cursor does not have source location"); } +@synopsis{A node in a call hierarchy, either a caller or a callee.} +@description{ +A ((CallHierarchyItem)) represents a single function, method, or procedure in the call hierarchy. +* `name` is the name of the callable/calling entity. +* `kind` is the ((DocumentSymbolKind)) of the callable/calling entity, e.g., function, method, constructor, etc. +* `src` is the location of the definition of the callable/calling entity. +* `selection` is the location of the name of the definition of the callable/calling entity, or another range within `src` to select when the hierarchy item is clicked. +* `tags` are additional metadata tags for the item, e.g., `deprecated`. +* `detail` has additional information about the callable/calling entity, e.g., the function signature. +* `data` can be used to store state that is shared between the `prepareService` and `callsService`. +} data CallHierarchyItem = callHierarchyItem( str name, From 298b108e407a5066547991ae30a09255e7659078 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 2 Sep 2025 09:14:36 +0200 Subject: [PATCH 14/23] Remove stale pico examples. --- .../library/demo/lang/pico/LanguageServer.rsc | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/rascal-lsp/src/main/rascal/library/demo/lang/pico/LanguageServer.rsc b/rascal-lsp/src/main/rascal/library/demo/lang/pico/LanguageServer.rsc index 31b7cd2b1..1ec7af63b 100644 --- a/rascal-lsp/src/main/rascal/library/demo/lang/pico/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/library/demo/lang/pico/LanguageServer.rsc @@ -38,17 +38,9 @@ import util::IDEServices; import ParseTree; import util::ParseErrorRecovery; import util::Reflective; -extend lang::pico::\syntax::Main; +import lang::pico::\syntax::Main; import DateTime; -syntax IdType - = func: Type returnType Id id "(" {IdType ","}* params ")" "=" Expression body ";" - ; - -syntax Expression - = call: Id id "(" {Expression ","}* args ")" - ; - private Tree (str _input, loc _origin) picoParser(bool allowRecovery) { return ParseTree::parser(#start[Program], allowRecovery=allowRecovery, filters=allowRecovery ? {createParseErrorFilter(false)} : {}); } @@ -69,8 +61,7 @@ set[LanguageService] picoLanguageServer(bool allowRecovery) = { codeAction(picoCodeActionService), rename(picoRenamingService, prepareRenameService = picoRenamePreparingService), didRenameFiles(picoFileRenameService), - selectionRange(picoSelectionRangeService), - codeAction(picoCodeActionService) + selectionRange(picoSelectionRangeService) }; set[LanguageService] picoLanguageServer() = picoLanguageServer(false); From bf0f9902894403e123dff3872c510cface076e16 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 1 Oct 2025 14:38:52 +0200 Subject: [PATCH 15/23] Improved Rascal <-> LSP mapping. --- .../parametric/ILanguageContributions.java | 2 + .../InterpretedLanguageContributions.java | 5 ++ .../LanguageContributionsMultiplexer.java | 10 +++ .../ParametricTextDocumentService.java | 49 +++++++----- .../parametric/ParserOnlyContribution.java | 7 ++ .../vscode/lsp/util/CallHierarchy.java | 74 +++++++++++++++---- .../vscode/lsp/util/DocumentSymbols.java | 27 ++++++- 7 files changed, 135 insertions(+), 39 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java index 962361760..b97875a07 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java @@ -93,6 +93,8 @@ public interface ILanguageContributions { public CompletableFuture getBuilderSummaryConfig(); public CompletableFuture getOndemandSummaryConfig(); + public CompletableFuture getStore(); + public static class SummaryConfig { public final boolean providesHovers; public final boolean providesDefinitions; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java index a4ab495ea..c015b861c 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java @@ -563,4 +563,9 @@ private InterruptibleFuture execFunction(String name, CompletableFuture<@ public void cancelProgress(String progressId) { monitor.cancelProgress(progressId); } + + @Override + public CompletableFuture getStore() { + return store; + } } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java index e70b27849..0db8eff7a 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java @@ -32,6 +32,7 @@ import java.util.function.BinaryOperator; import java.util.function.Function; +import org.apache.commons.lang3.NotImplementedException; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.rascalmpl.values.parsetrees.ITree; @@ -44,6 +45,7 @@ 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 { @@ -449,4 +451,12 @@ public CompletableFuture getOndemandSummaryConfig() { public void cancelProgress(String progressId) { contributions.forEach(klc -> klc.contrib.cancelProgress(progressId)); } + + public CompletableFuture getStore() { + for (var c : contributions) { + return c.contrib.getStore(); + } + + return CompletableFuture.failedFuture(new NotImplementedException()); + } } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java index 9ebd49b73..ae312afeb 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java @@ -33,6 +33,7 @@ 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; @@ -137,6 +138,7 @@ 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; @@ -836,43 +838,50 @@ public CompletableFuture> prepareCallHierarchy(CallHiera final var contrib = contributions(doc); final var file = getFile(doc); - return recoverExceptions(file.getCurrentTreeAsync() + return /*recoverExceptions(*/file.getCurrentTreeAsync() .thenApply(Versioned::get) .thenCompose(t -> { final var pos = Locations.toRascalPosition(doc, params.getPosition(), columns); final var focus = TreeSearch.computeFocusList(t, pos.getLine(), pos.getCharacter()); return contrib.prepareCallHierarchy(focus).get(); }) - .thenApply(items -> items.stream() - .map(IConstructor.class::cast) - .map(ci -> CallHierarchy.toLSP(ci, columns)) - .collect(Collectors.toList())), Collections::emptyList); + .thenCompose(items -> contrib.getStore().thenApply(store -> { + var ch = new CallHierarchy(store); + return items.stream() + .map(IConstructor.class::cast) + .map(ci -> ch.toLSP(ci, columns)) + .collect(Collectors.toList()); + }))/*, Collections::emptyList)*/; } - private CompletableFuture> incomingOutgoingCalls(BiFunction, T> constructor, CallHierarchyItem source, IConstructor direction) { + private CompletableFuture> incomingOutgoingCalls(BiFunction, T> constructor, CallHierarchyItem source, CallHierarchy.Direction direction) { final var contrib = contributions(source.getUri()); - return contrib.incomingOutgoingCalls(CallHierarchy.toRascal(source, columns), direction) - .get() - .thenApply(callRel -> callRel.domain().stream() - .map(ci -> { - List callSites = callRel.index(ci) - .stream() - .map(ISourceLocation.class::cast) - .map(l -> Locations.toRange(l, columns)) - .collect(Collectors.toList()); - return constructor.apply(CallHierarchy.toLSP((IConstructor) ci, columns), callSites); - }) - .collect(Collectors.toList())); + return contrib.getStore().thenCompose(store -> { + var ch = new CallHierarchy(store); + return contrib.incomingOutgoingCalls(ch.toRascal(source, columns), ch.direction(direction)) + .get() + .thenApply(callRel -> callRel.asContainer().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> callHierarchyIncomingCalls(CallHierarchyIncomingCallsParams params) { - return recoverExceptions(incomingOutgoingCalls(CallHierarchyIncomingCall::new, params.getItem(), CallHierarchy.INCOMING),Collections::emptyList); + return recoverExceptions(incomingOutgoingCalls(CallHierarchyIncomingCall::new, params.getItem(), CallHierarchy.Direction.INCOMING), Collections::emptyList); } @Override public CompletableFuture> callHierarchyOutgoingCalls(CallHierarchyOutgoingCallsParams params) { - return recoverExceptions(incomingOutgoingCalls(CallHierarchyOutgoingCall::new, params.getItem(), CallHierarchy.OUTGOING),Collections::emptyList); + return recoverExceptions(incomingOutgoingCalls(CallHierarchyOutgoingCall::new, params.getItem(), CallHierarchy.Direction.OUTGOING), Collections::emptyList); } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java index 73c79ed05..8b834f9d8 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java @@ -32,6 +32,7 @@ import java.io.Writer; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.Nullable; @@ -55,6 +56,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); @@ -310,4 +312,9 @@ public void cancelProgress(String progressId) { // empty, since this contribution does not have any running tasks nor a monitor } + @Override + public CompletableFuture getStore() { + return CompletableFuture.completedFuture(new TypeStore()); + } + } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java index 361acef0b..7e948cfdc 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java @@ -26,14 +26,23 @@ */ package org.rascalmpl.vscode.lsp.util; +import java.io.IOException; +import java.io.StringReader; import java.util.List; import java.util.Map; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.eclipse.lsp4j.CallHierarchyItem; +import org.rascalmpl.interpreter.NullRascalMonitor; +import org.rascalmpl.library.lang.json.internal.JsonValueReader; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.vscode.lsp.util.locations.ColumnMaps; import org.rascalmpl.vscode.lsp.util.locations.Locations; +import com.google.gson.JsonObject; +import com.google.gson.stream.JsonReader; + import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.ISet; import io.usethesource.vallang.ISourceLocation; @@ -43,18 +52,20 @@ import io.usethesource.vallang.type.TypeStore; public class CallHierarchy { + private static final Logger logger = LogManager.getLogger(CallHierarchy.class); + private static final IRascalValueFactory VF = IRascalValueFactory.getInstance(); private static final TypeFactory TF = TypeFactory.getInstance(); - private static final TypeStore store = new TypeStore(); - private static final Type directionAdt = TF.abstractDataType(store, "CallDirection"); + public enum Direction { + INCOMING, + OUTGOING + } - public static final IConstructor INCOMING = VF.constructor(TF.constructor(store, directionAdt, "incoming")); - public static final IConstructor OUTGOING = VF.constructor(TF.constructor(store, directionAdt, "outgoing")); + private final IConstructor incoming; + private final IConstructor outgoing; - private static final Type callHierarchyItemAdt = TF.abstractDataType(store, "CallHierarchyItem"); - private static final Type callHierarchyItemCons = TF.constructor(store, callHierarchyItemAdt, "callHierarchyItem", - TF.stringType(), DocumentSymbols.symbolKindAdt, TF.sourceLocationType(), TF.sourceLocationType()); + private final Type callHierarchyItemCons; private static final String NAME = "name"; private static final String KIND = "kind"; @@ -63,10 +74,25 @@ public class CallHierarchy { private static final String TAGS = "tags"; private static final String DETAIL = "detail"; private static final String DATA = "data"; + private final TypeStore store; + + public CallHierarchy(TypeStore store) { + this.store = store; + Type directionAdt = store.lookupAbstractDataType("CallDirection"); + this.incoming = VF.constructor(store.lookupConstructor(directionAdt, "incoming", TF.tupleEmpty())); + this.outgoing = VF.constructor(store.lookupConstructor(directionAdt, "outgoing", TF.tupleEmpty())); + this.callHierarchyItemCons = store.lookupConstructor(store.lookupAbstractDataType("CallHierarchyItem"), "callHierarchyItem").iterator().next(); // first and only + } - private CallHierarchy() { /* hide constructor */} + public IConstructor direction(Direction dir) { + switch (dir) { + case INCOMING: return this.incoming; + case OUTGOING: return this.outgoing; + default: throw new IllegalArgumentException(); + } + } - public static CallHierarchyItem toLSP(IConstructor cons, ColumnMaps columns) { + public CallHierarchyItem toLSP(IConstructor cons, ColumnMaps columns) { var name = cons.get(NAME).toString(); var kind = DocumentSymbols.symbolKindToLSP((IConstructor) cons.get(KIND)); var def = (ISourceLocation) cons.get(DEFINITION); @@ -76,23 +102,41 @@ public static CallHierarchyItem toLSP(IConstructor cons, ColumnMaps columns) { var ci = new CallHierarchyItem(name, kind, def.top().getURI().toString(), definitionRange, selectionRange); var kws = cons.asWithKeywordParameters(); - ci.setTags(DocumentSymbols.symbolTagsToLSP((ISet) kws.getParameter(TAGS))); - ci.setDetail(kws.getParameter(DETAIL).toString()); - ci.setData(kws.getParameter(DATA)); + if (kws.hasParameter(TAGS)) { + ci.setTags(DocumentSymbols.symbolTagsToLSP((ISet) kws.getParameter(TAGS))); + } + if (kws.hasParameter(DETAIL)) { + ci.setDetail(kws.getParameter(DETAIL).toString()); + } + if (kws.hasParameter(DATA)) { + ci.setData(kws.getParameter(DATA)); + } return ci; } - public static IConstructor toRascal(CallHierarchyItem ci, ColumnMaps columns) { + public IConstructor toRascal(CallHierarchyItem ci, ColumnMaps columns) { + JsonValueReader reader = new JsonValueReader(VF, store, new NullRascalMonitor(), null); + final var dataString = ((JsonObject) ci.getData()).toString(); + IValue data = null; + try { + data = reader.read(new JsonReader(new StringReader(dataString)), TF.valueType()); + } catch (IOException e) { + data = VF.tuple(); + } + if (data == null) data = VF.tuple(); + + logger.debug("data: {}", data); + return VF.constructor(callHierarchyItemCons, List.of( VF.string(ci.getName()), - VF.constructor(TF.constructor(store, DocumentSymbols.symbolKindAdt, ci.getKind().name())), + DocumentSymbols.symbolKindToRascal(ci.getKind()), Locations.setRange(Locations.toLoc(ci.getUri()), ci.getRange(), columns), Locations.setRange(Locations.toLoc(ci.getUri()), ci.getSelectionRange(), columns) ).toArray(new IValue[0]), Map.of( TAGS, DocumentSymbols.symbolTagsToRascal(ci.getTags()), DETAIL, VF.string(ci.getDetail()), - DATA, (IValue) ci.getData() + DATA, data )); } } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/DocumentSymbols.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/DocumentSymbols.java index 0b82cea8f..a58a45003 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/DocumentSymbols.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/DocumentSymbols.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.stream.Collectors; +import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.DocumentSymbol; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.SymbolInformation; @@ -55,8 +56,8 @@ public class DocumentSymbols { private static final TypeFactory TF = TypeFactory.getInstance(); private static final TypeStore store = new TypeStore(); - public static final Type symbolKindAdt = TF.abstractDataType(store, "DocumentSymbolKind"); - public static final Type symbolTagAdt = TF.abstractDataType(store, "DocumentSymbolTag"); + private static final Type symbolKindAdt = TF.abstractDataType(store, "DocumentSymbolKind"); + private static final Type symbolTagAdt = TF.abstractDataType(store, "DocumentSymbolTag"); // hide constructor for static class private DocumentSymbols() {} @@ -114,7 +115,14 @@ public static SymbolKind symbolKindToLSP(IConstructor kind) { return SymbolKind.valueOf(capitalize(kind.getName())); } - public static List symbolTagsToLSP(ISet tags) { + public static IConstructor symbolKindToRascal(SymbolKind kind) { + return VF.constructor(TF.constructor(store, symbolKindAdt, kind.name().toLowerCase())); + } + + public static List symbolTagsToLSP(@Nullable ISet tags) { + if (tags == null) { + return Collections.emptyList(); + } return tags.stream() .map(IConstructor.class::cast) .map(IConstructor::getName) @@ -123,9 +131,20 @@ public static List symbolTagsToLSP(ISet tags) { .collect(Collectors.toList()); } - public static ISet symbolTagsToRascal(List tags) { + public static ISet symbolTagsToRascal(@Nullable List tags) { + if (tags == null) { + return VF.set(); + } return tags.stream() .map(t -> VF.constructor(TF.constructor(store, symbolTagAdt, t.name().toLowerCase()))) .collect(VF.setWriter()); } + + public static Type getSymbolKindType() { + return symbolKindAdt; + } + + public static TypeStore getStore() { + return store; + } } From fa5d3ece51b463dcca3141e752b17c464e17b7ac Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 1 Oct 2025 15:57:39 +0200 Subject: [PATCH 16/23] Data as ADT, serialize as string. --- .../vscode/lsp/util/CallHierarchy.java | 35 ++++++++++--------- .../rascal/library/util/LanguageServer.rsc | 4 ++- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java index 7e948cfdc..ba9a91e6a 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java @@ -33,20 +33,20 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.CallHierarchyItem; -import org.rascalmpl.interpreter.NullRascalMonitor; -import org.rascalmpl.library.lang.json.internal.JsonValueReader; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.vscode.lsp.util.locations.ColumnMaps; import org.rascalmpl.vscode.lsp.util.locations.Locations; -import com.google.gson.JsonObject; -import com.google.gson.stream.JsonReader; +import com.google.gson.JsonPrimitive; import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.ISet; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.IValue; +import io.usethesource.vallang.exceptions.FactTypeUseException; +import io.usethesource.vallang.io.StandardTextReader; import io.usethesource.vallang.type.Type; import io.usethesource.vallang.type.TypeFactory; import io.usethesource.vallang.type.TypeStore; @@ -66,6 +66,7 @@ public enum Direction { private final IConstructor outgoing; private final Type callHierarchyItemCons; + private @Nullable Type callHierarchyDataAdt; private static final String NAME = "name"; private static final String KIND = "kind"; @@ -76,12 +77,14 @@ public enum Direction { private static final String DATA = "data"; private final TypeStore store; + public CallHierarchy(TypeStore store) { this.store = store; Type directionAdt = store.lookupAbstractDataType("CallDirection"); this.incoming = VF.constructor(store.lookupConstructor(directionAdt, "incoming", TF.tupleEmpty())); this.outgoing = VF.constructor(store.lookupConstructor(directionAdt, "outgoing", TF.tupleEmpty())); this.callHierarchyItemCons = store.lookupConstructor(store.lookupAbstractDataType("CallHierarchyItem"), "callHierarchyItem").iterator().next(); // first and only + this.callHierarchyDataAdt = store.lookupAbstractDataType("CallHierarchyData"); } public IConstructor direction(Direction dir) { @@ -109,25 +112,25 @@ public CallHierarchyItem toLSP(IConstructor cons, ColumnMaps columns) { ci.setDetail(kws.getParameter(DETAIL).toString()); } if (kws.hasParameter(DATA)) { - ci.setData(kws.getParameter(DATA)); + ci.setData(serializeData((IConstructor) kws.getParameter(DATA))); } return ci; } - public IConstructor toRascal(CallHierarchyItem ci, ColumnMaps columns) { - JsonValueReader reader = new JsonValueReader(VF, store, new NullRascalMonitor(), null); - final var dataString = ((JsonObject) ci.getData()).toString(); - IValue data = null; + private String serializeData(IConstructor data) { + return data.toString(); + } + + private IConstructor deserializeData(Object data) { try { - data = reader.read(new JsonReader(new StringReader(dataString)), TF.valueType()); - } catch (IOException e) { - data = VF.tuple(); + return (IConstructor) new StandardTextReader().read(VF, store, callHierarchyDataAdt, new StringReader(((JsonPrimitive) data).getAsString())); + } catch (FactTypeUseException | IOException e) { + throw new IllegalArgumentException("The call hierarchy item data could not be parsed", e); } - if (data == null) data = VF.tuple(); - - logger.debug("data: {}", data); + } + public IConstructor toRascal(CallHierarchyItem ci, ColumnMaps columns) { return VF.constructor(callHierarchyItemCons, List.of( VF.string(ci.getName()), DocumentSymbols.symbolKindToRascal(ci.getKind()), @@ -136,7 +139,7 @@ public IConstructor toRascal(CallHierarchyItem ci, ColumnMaps columns) { ).toArray(new IValue[0]), Map.of( TAGS, DocumentSymbols.symbolTagsToRascal(ci.getTags()), DETAIL, VF.string(ci.getDetail()), - DATA, data + DATA, deserializeData(ci.getData()) )); } } diff --git a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc index bbc4fe0c3..2fb951492 100644 --- a/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/library/util/LanguageServer.rsc @@ -307,9 +307,11 @@ data CallHierarchyItem loc selection, // location of the name of the definition set[DocumentSymbolTag] tags = {}, str detail = "", // detailed description, e.g. the function signature - value \data = () // shared state between `callHierarchy::prepareService` and `callHierarchy::callsService` + CallHierarchyData \data = none() // shared state between `callHierarchy::prepareService` and `callHierarchy::callsService` ); +data CallHierarchyData = none(); + data CallDirection = incoming() | outgoing() From 0b587cc90093f0246a041f4e79f502337bdae704 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 1 Oct 2025 16:49:32 +0200 Subject: [PATCH 17/23] Use list instead of relation. --- .../vscode/lsp/parametric/ILanguageContributions.java | 3 +-- .../lsp/parametric/InterpretedLanguageContributions.java | 5 ++--- .../lsp/parametric/LanguageContributionsMultiplexer.java | 3 +-- .../vscode/lsp/parametric/ParametricTextDocumentService.java | 2 +- .../vscode/lsp/parametric/ParserOnlyContribution.java | 5 ++--- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java index b97875a07..69bef838d 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java @@ -37,7 +37,6 @@ import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.IList; -import io.usethesource.vallang.IRelation; import io.usethesource.vallang.ISet; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.ITuple; @@ -63,7 +62,7 @@ public interface ILanguageContributions { public InterruptibleFuture codeAction(IList focus); public InterruptibleFuture selectionRange(IList focus); public InterruptibleFuture prepareCallHierarchy(IList focus); - public InterruptibleFuture> incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction); + public InterruptibleFuture incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction); public InterruptibleFuture prepareRename(IList focus); public InterruptibleFuture rename(IList focus, String name); diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java index c015b861c..1bb54f8fe 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java @@ -57,7 +57,6 @@ import io.usethesource.vallang.IBool; import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.IList; -import io.usethesource.vallang.IRelation; import io.usethesource.vallang.ISet; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.ITuple; @@ -409,9 +408,9 @@ public InterruptibleFuture prepareCallHierarchy(IList focus) { } @Override - public InterruptibleFuture> incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction) { + public InterruptibleFuture incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction) { debug(LanguageContributions.CALL_HIERARCHY, hierarchyItem.has("name") ? hierarchyItem.get("name") : "?", direction.getName()); - return execFunction(LanguageContributions.CALL_HIERARCHY, callHierarchyService, VF.list().asRelation(), hierarchyItem, direction); + return execFunction(LanguageContributions.CALL_HIERARCHY, callHierarchyService, VF.list(), hierarchyItem, direction); } private void debug(String name, Object param) { diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java index 0db8eff7a..dd5991b23 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java @@ -40,7 +40,6 @@ import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.IList; -import io.usethesource.vallang.IRelation; import io.usethesource.vallang.ISet; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.ITuple; @@ -353,7 +352,7 @@ public InterruptibleFuture prepareCallHierarchy(IList focus) { } @Override - public InterruptibleFuture> incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction) { + public InterruptibleFuture incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction) { return flatten(incomingOutgoingCalls, c -> c.incomingOutgoingCalls(hierarchyItem, direction)); } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java index ae312afeb..0d44fa02d 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java @@ -860,7 +860,7 @@ private CompletableFuture> incomingOutgoingCalls(BiFunction callRel.asContainer().stream() + .thenApply(callRel -> callRel.stream() .map(ITuple.class::cast) .collect(Collectors.toMap( t -> ch.toLSP((IConstructor) t.get(0), columns), diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java index 8b834f9d8..f11f68b38 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java @@ -49,7 +49,6 @@ import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.IList; -import io.usethesource.vallang.IRelation; import io.usethesource.vallang.ISet; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.ITuple; @@ -208,8 +207,8 @@ public InterruptibleFuture prepareCallHierarchy(IList focus) { } @Override - public InterruptibleFuture> incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction) { - return InterruptibleFuture.completedFuture(VF.list().asRelation()); + public InterruptibleFuture incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction) { + return InterruptibleFuture.completedFuture(VF.list()); } @Override From b8d687d38128980e3b375efb2279336e78ce4638 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 1 Oct 2025 16:49:52 +0200 Subject: [PATCH 18/23] Recover exceptions. --- .../vscode/lsp/parametric/ParametricTextDocumentService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java index 0d44fa02d..3f22b0920 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java @@ -838,7 +838,7 @@ public CompletableFuture> prepareCallHierarchy(CallHiera final var contrib = contributions(doc); final var file = getFile(doc); - return /*recoverExceptions(*/file.getCurrentTreeAsync() + return recoverExceptions(file.getCurrentTreeAsync() .thenApply(Versioned::get) .thenCompose(t -> { final var pos = Locations.toRascalPosition(doc, params.getPosition(), columns); @@ -851,7 +851,7 @@ public CompletableFuture> prepareCallHierarchy(CallHiera .map(IConstructor.class::cast) .map(ci -> ch.toLSP(ci, columns)) .collect(Collectors.toList()); - }))/*, Collections::emptyList)*/; + })), Collections::emptyList); } private CompletableFuture> incomingOutgoingCalls(BiFunction, T> constructor, CallHierarchyItem source, CallHierarchy.Direction direction) { From d55e1d05df8e9cfc73dfc05c8ebfde47f5847142 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 1 Oct 2025 16:59:06 +0200 Subject: [PATCH 19/23] Improve getStore. --- .../lsp/parametric/LanguageContributionsMultiplexer.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java index dd5991b23..3555afbce 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java @@ -32,7 +32,6 @@ import java.util.function.BinaryOperator; import java.util.function.Function; -import org.apache.commons.lang3.NotImplementedException; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.rascalmpl.values.parsetrees.ITree; @@ -452,10 +451,6 @@ public void cancelProgress(String progressId) { } public CompletableFuture getStore() { - for (var c : contributions) { - return c.contrib.getStore(); - } - - return CompletableFuture.failedFuture(new NotImplementedException()); + return execution.thenApply(c -> c.getStore()).thenCompose(Function.identity()); } } From d0bf2bc1098419cf904b8ef9a0e99a30242b11e3 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 1 Oct 2025 16:59:21 +0200 Subject: [PATCH 20/23] Small fixes. --- .../lsp/parametric/InterpretedLanguageContributions.java | 2 +- .../lsp/parametric/LanguageContributionsMultiplexer.java | 2 +- .../java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java | 6 +----- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java index 1bb54f8fe..9614653c7 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java @@ -129,7 +129,7 @@ public InterpretedLanguageContributions(LanguageParameter lang, IBaseTextDocumen this.exec = exec; try { - var pcfg = new PathConfig().parse(lang.getPathConfig()); + var pcfg = PathConfig.parse(lang.getPathConfig()); pcfg = EvaluatorUtil.addLSPSources(pcfg, false); monitor = new RascalLSPMonitor(client, LogManager.getLogger(logger.getName() + "[" + lang.getName() + "]"), lang.getName() + ": "); diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java index 3555afbce..7c652649e 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java @@ -180,9 +180,9 @@ 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); diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java index ba9a91e6a..ab3eeadd1 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java @@ -31,8 +31,6 @@ import java.util.List; import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.CallHierarchyItem; import org.rascalmpl.values.IRascalValueFactory; @@ -52,8 +50,6 @@ import io.usethesource.vallang.type.TypeStore; public class CallHierarchy { - private static final Logger logger = LogManager.getLogger(CallHierarchy.class); - private static final IRascalValueFactory VF = IRascalValueFactory.getInstance(); private static final TypeFactory TF = TypeFactory.getInstance(); @@ -66,7 +62,7 @@ public enum Direction { private final IConstructor outgoing; private final Type callHierarchyItemCons; - private @Nullable Type callHierarchyDataAdt; + private final @Nullable Type callHierarchyDataAdt; private static final String NAME = "name"; private static final String KIND = "kind"; From 025dd3322093358201d307d20253c0795d37f5bb Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 13 Oct 2025 17:32:35 +0200 Subject: [PATCH 21/23] Always pass the store from the correct evaluator. --- .../parametric/ILanguageContributions.java | 7 ++- .../InterpretedLanguageContributions.java | 28 +++++----- .../LanguageContributionsMultiplexer.java | 8 ++- .../ParametricTextDocumentService.java | 51 +++++++++++-------- .../parametric/ParserOnlyContribution.java | 16 +++--- .../vscode/lsp/util/CallHierarchy.java | 10 ++++ 6 files changed, 67 insertions(+), 53 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java index 87dfb5daf..daaaf0a46 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ILanguageContributions.java @@ -29,6 +29,7 @@ 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; @@ -59,8 +60,8 @@ public interface ILanguageContributions { public InterruptibleFuture implementation(IList focus); public InterruptibleFuture codeAction(IList focus); public InterruptibleFuture selectionRange(IList focus); - public InterruptibleFuture prepareCallHierarchy(IList focus); - public InterruptibleFuture incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction); + public InterruptibleFuture> prepareCallHierarchy(IList focus); + public InterruptibleFuture> incomingOutgoingCalls(Function hierarchyItem, Function direction); public InterruptibleFuture prepareRename(IList focus); public InterruptibleFuture rename(IList focus, String name); @@ -90,8 +91,6 @@ public interface ILanguageContributions { public CompletableFuture getBuilderSummaryConfig(); public CompletableFuture getOndemandSummaryConfig(); - public CompletableFuture getStore(); - public static class SummaryConfig { public final boolean providesHovers; public final boolean providesDefinitions; diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java index f1e3b2ebc..e70c26348 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/InterpretedLanguageContributions.java @@ -31,11 +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; @@ -418,15 +419,23 @@ public InterruptibleFuture selectionRange(IList focus) { } @Override - public InterruptibleFuture prepareCallHierarchy(IList focus) { + public InterruptibleFuture> prepareCallHierarchy(IList focus) { debug(LanguageContributions.CALL_HIERARCHY, "prepare", focus.length()); - return execFunction(LanguageContributions.CALL_HIERARCHY, prepareCallHierarchy, VF.list(), focus); + return withStore(execFunction(LanguageContributions.CALL_HIERARCHY, prepareCallHierarchy, VF.list(), focus)); } @Override - public InterruptibleFuture incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction) { - debug(LanguageContributions.CALL_HIERARCHY, hierarchyItem.has("name") ? hierarchyItem.get("name") : "?", direction.getName()); - return execFunction(LanguageContributions.CALL_HIERARCHY, callHierarchyService, VF.list(), hierarchyItem, direction); + public InterruptibleFuture> incomingOutgoingCalls(Function computeHierarchyItem, Function 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 InterruptibleFuture> withStore(InterruptibleFuture contrib) { + return contrib.thenCombineAsync(store, Pair::of, exec); } private void debug(String name, Object param) { @@ -579,9 +588,4 @@ public InterruptibleFuture execution(String command) { public void cancelProgress(String progressId) { monitor.cancelProgress(progressId); } - - @Override - public CompletableFuture getStore() { - return store; - } } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java index 10bd8b025..e38b502a4 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/LanguageContributionsMultiplexer.java @@ -31,6 +31,7 @@ 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; @@ -345,12 +346,12 @@ public InterruptibleFuture selectionRange(IList focus) { } @Override - public InterruptibleFuture prepareCallHierarchy(IList focus) { + public InterruptibleFuture> prepareCallHierarchy(IList focus) { return flatten(prepareCallHierarchy, c -> c.prepareCallHierarchy(focus)); } @Override - public InterruptibleFuture incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction) { + public InterruptibleFuture> incomingOutgoingCalls(Function hierarchyItem, Function direction) { return flatten(incomingOutgoingCalls, c -> c.incomingOutgoingCalls(hierarchyItem, direction)); } @@ -449,7 +450,4 @@ public void cancelProgress(String progressId) { contributions.forEach(klc -> klc.contrib.cancelProgress(progressId)); } - public CompletableFuture getStore() { - return execution.thenApply(c -> c.getStore()).thenCompose(Function.identity()); - } } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java index 4be03cbcd..6e6ca82df 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java @@ -850,35 +850,42 @@ public CompletableFuture> prepareCallHierarchy(CallHiera .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(); - }) - .thenCompose(items -> contrib.getStore().thenApply(store -> { - var ch = new CallHierarchy(store); - return items.stream() - .map(IConstructor.class::cast) - .map(ci -> ch.toLSP(ci, columns)) - .collect(Collectors.toList()); - })), Collections::emptyList); + 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 CompletableFuture> incomingOutgoingCalls(BiFunction, T> constructor, CallHierarchyItem source, CallHierarchy.Direction direction) { final var contrib = contributions(Locations.toLoc(source.getUri())); - return contrib.getStore().thenCompose(store -> { - var ch = new CallHierarchy(store); - return contrib.incomingOutgoingCalls(ch.toRascal(source, columns), ch.direction(direction)) - .get() - .thenApply(callRel -> 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 - ))) + 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 diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java index 3a5559706..3fa51e336 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParserOnlyContribution.java @@ -32,7 +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; @@ -204,13 +205,13 @@ public InterruptibleFuture selectionRange(IList focus) { } @Override - public InterruptibleFuture prepareCallHierarchy(IList focus) { - return InterruptibleFuture.completedFuture(VF.list()); + public InterruptibleFuture> prepareCallHierarchy(IList focus) { + return InterruptibleFuture.completedFuture(Pair.of(VF.list(), new TypeStore())); } @Override - public InterruptibleFuture incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction) { - return InterruptibleFuture.completedFuture(VF.list()); + public InterruptibleFuture> incomingOutgoingCalls(Function hierarchyItem, Function direction) { + return InterruptibleFuture.completedFuture(Pair.of(VF.list(), new TypeStore())); } @Override @@ -313,9 +314,4 @@ public void cancelProgress(String progressId) { // empty, since this contribution does not have any running tasks nor a monitor } - @Override - public CompletableFuture getStore() { - return CompletableFuture.completedFuture(new TypeStore()); - } - } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java index ab3eeadd1..07d25d691 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java @@ -83,6 +83,11 @@ public CallHierarchy(TypeStore store) { this.callHierarchyDataAdt = store.lookupAbstractDataType("CallHierarchyData"); } + public static IConstructor direction(TypeStore store, CallHierarchy.Direction direction) { + var ch = new CallHierarchy(store); + return ch.direction(direction); + } + public IConstructor direction(Direction dir) { switch (dir) { case INCOMING: return this.incoming; @@ -126,6 +131,11 @@ private IConstructor deserializeData(Object data) { } } + public static IConstructor toRascal(TypeStore store, CallHierarchyItem source, ColumnMaps columns) { + var ch = new CallHierarchy(store); + return ch.toRascal(source, columns); + } + public IConstructor toRascal(CallHierarchyItem ci, ColumnMaps columns) { return VF.constructor(callHierarchyItemCons, List.of( VF.string(ci.getName()), From fd7fb989f842d677cf09e91ea18ceea32b6b51ee Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 13 Oct 2025 17:33:23 +0200 Subject: [PATCH 22/23] Convert missing data correctly. --- .../org/rascalmpl/vscode/lsp/util/CallHierarchy.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java index 07d25d691..39c7dc296 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java @@ -26,19 +26,17 @@ */ package org.rascalmpl.vscode.lsp.util; +import com.google.gson.JsonPrimitive; import java.io.IOException; import java.io.StringReader; import java.util.List; import java.util.Map; - import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.CallHierarchyItem; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.vscode.lsp.util.locations.ColumnMaps; import org.rascalmpl.vscode.lsp.util.locations.Locations; -import com.google.gson.JsonPrimitive; - import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.ISet; import io.usethesource.vallang.ISourceLocation; @@ -63,6 +61,7 @@ public enum Direction { private final Type callHierarchyItemCons; private final @Nullable Type callHierarchyDataAdt; + private final @Nullable Type callHierarchyDataCons; private static final String NAME = "name"; private static final String KIND = "kind"; @@ -81,6 +80,7 @@ public CallHierarchy(TypeStore store) { this.outgoing = VF.constructor(store.lookupConstructor(directionAdt, "outgoing", TF.tupleEmpty())); this.callHierarchyItemCons = store.lookupConstructor(store.lookupAbstractDataType("CallHierarchyItem"), "callHierarchyItem").iterator().next(); // first and only this.callHierarchyDataAdt = store.lookupAbstractDataType("CallHierarchyData"); + this.callHierarchyDataCons = store.lookupConstructor(callHierarchyDataAdt, "none").iterator().next(); } public static IConstructor direction(TypeStore store, CallHierarchy.Direction direction) { @@ -123,7 +123,10 @@ private String serializeData(IConstructor data) { return data.toString(); } - private IConstructor deserializeData(Object data) { + private IConstructor deserializeData(@Nullable Object data) { + if (data == null) { + return VF.constructor(callHierarchyDataCons); + } try { return (IConstructor) new StandardTextReader().read(VF, store, callHierarchyDataAdt, new StringReader(((JsonPrimitive) data).getAsString())); } catch (FactTypeUseException | IOException e) { From a2cefac5a470d0ce9314f19ca9d91646b243cc52 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 13 Oct 2025 17:33:37 +0200 Subject: [PATCH 23/23] Convert text correctly. --- .../java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java index 39c7dc296..08713d389 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java @@ -40,6 +40,7 @@ import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.ISet; import io.usethesource.vallang.ISourceLocation; +import io.usethesource.vallang.IString; import io.usethesource.vallang.IValue; import io.usethesource.vallang.exceptions.FactTypeUseException; import io.usethesource.vallang.io.StandardTextReader; @@ -97,7 +98,7 @@ public IConstructor direction(Direction dir) { } public CallHierarchyItem toLSP(IConstructor cons, ColumnMaps columns) { - var name = cons.get(NAME).toString(); + var name = ((IString) cons.get(NAME)).getValue(); var kind = DocumentSymbols.symbolKindToLSP((IConstructor) cons.get(KIND)); var def = (ISourceLocation) cons.get(DEFINITION); var definitionRange = Locations.toRange(def, columns); @@ -110,7 +111,7 @@ public CallHierarchyItem toLSP(IConstructor cons, ColumnMaps columns) { ci.setTags(DocumentSymbols.symbolTagsToLSP((ISet) kws.getParameter(TAGS))); } if (kws.hasParameter(DETAIL)) { - ci.setDetail(kws.getParameter(DETAIL).toString()); + ci.setDetail(((IString) kws.getParameter(DETAIL)).getValue()); } if (kws.hasParameter(DATA)) { ci.setData(serializeData((IConstructor) kws.getParameter(DATA)));