From 806d89c110f0b958b745b38b26d9bdacab9dd355 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 29 Jul 2025 16:15:32 +0200 Subject: [PATCH 01/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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))); From 78c5a1097b9044ac42cc5425dd820bfc78aa0774 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 29 Oct 2025 16:06:02 +0100 Subject: [PATCH 24/53] Only delegate data parsing to specific store. --- .../parametric/ILanguageContributions.java | 6 +- .../InterpretedLanguageContributions.java | 45 +++++++---- .../LanguageContributionsMultiplexer.java | 11 ++- .../ParametricTextDocumentService.java | 40 ++++------ .../parametric/ParserOnlyContribution.java | 16 ++-- .../vscode/lsp/util/CallHierarchy.java | 77 +++++++------------ 6 files changed, 94 insertions(+), 101 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 daaaf0a46..e330b62ae 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,7 +29,6 @@ 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; @@ -60,14 +59,15 @@ 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(Function hierarchyItem, Function direction); + public InterruptibleFuture prepareCallHierarchy(IList focus); + public InterruptibleFuture incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction); public InterruptibleFuture prepareRename(IList focus); public InterruptibleFuture rename(IList focus, String name); public InterruptibleFuture didRenameFiles(IList fileRenames); public CompletableFuture parseCodeActions(String command); + public CompletableFuture parseCallHierarchyData(String data); public CompletableFuture hasAnalysis(); public CompletableFuture hasBuild(); 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 e70c26348..3f635d47e 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,8 +31,6 @@ 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.NonNull; @@ -272,6 +270,31 @@ public CompletableFuture parseCodeActions(String command) { }); } + @Override + public CompletableFuture parseCallHierarchyData(String data) { + return store.thenApply(completionStore -> { + try { + var callHierarchyDataAdt = completionStore.lookupAbstractDataType("CallHierarchyData"); + if (callHierarchyDataAdt == null) { + throw new IllegalArgumentException("CallHierarchyData is not defined in environment"); + } + if (data == null || data == "") { + var none = completionStore.lookupConstructor(callHierarchyDataAdt, "none", TypeFactory.getInstance().tupleEmpty()); + if (none == null) { + throw new IllegalArgumentException("CallHierarchyData::none() is not defined in environment"); + } + return VF.constructor(none); + } + return (IConstructor) new StandardTextReader().read(VF, completionStore, callHierarchyDataAdt, new StringReader(data)); + } catch (FactTypeUseException | IOException e) { + // this should never happen as long as the Rascal code + // for creating errors is type-correct. So it _might_ happen + // when running the interpreter on broken code. + throw new IllegalArgumentException("The call hierarchy item data could not be parsed", e); + } + }); + } + private CompletableFuture parseCommand(String command) { return store.thenApply(commandStore -> { try { @@ -419,23 +442,15 @@ public InterruptibleFuture selectionRange(IList focus) { } @Override - public InterruptibleFuture> prepareCallHierarchy(IList focus) { + public InterruptibleFuture prepareCallHierarchy(IList focus) { debug(LanguageContributions.CALL_HIERARCHY, "prepare", focus.length()); - return withStore(execFunction(LanguageContributions.CALL_HIERARCHY, prepareCallHierarchy, VF.list(), focus)); + return execFunction(LanguageContributions.CALL_HIERARCHY, prepareCallHierarchy, VF.list(), focus); } @Override - 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); + 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); } 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 e38b502a4..f8b2deff6 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,7 +31,6 @@ 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; @@ -43,7 +42,6 @@ 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 { @@ -290,6 +288,11 @@ public CompletableFuture parseCodeActions(String command) { return execution.thenApply(c -> c.parseCodeActions(command)).thenCompose(Function.identity()); } + @Override + public CompletableFuture parseCallHierarchyData(String data) { + return incomingOutgoingCalls.thenApply(c -> c.parseCallHierarchyData(data)).thenCompose(Function.identity()); + } + @Override public InterruptibleFuture inlayHint(ITree input) { return flatten(inlayHint, c -> c.inlayHint(input)); @@ -346,12 +349,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(Function hierarchyItem, Function 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 6e6ca82df..39fbd3f2e 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 @@ -852,10 +852,8 @@ public CompletableFuture> prepareCallHierarchy(CallHiera final var focus = TreeSearch.computeFocusList(t, pos.getLine(), pos.getCharacter()); return contrib.prepareCallHierarchy(focus) .get() - .thenApply(p -> { - var items = p.getLeft(); - var store = p.getRight(); - var ch = new CallHierarchy(store); + .thenApply(items -> { + var ch = new CallHierarchy(); return items.stream() .map(IConstructor.class::cast) .map(ci -> ch.toLSP(ci, columns)) @@ -866,26 +864,20 @@ public CompletableFuture> prepareCallHierarchy(CallHiera private CompletableFuture> incomingOutgoingCalls(BiFunction, T> constructor, CallHierarchyItem source, CallHierarchy.Direction direction) { final var contrib = contributions(Locations.toLoc(source.getUri())); - return contrib.incomingOutgoingCalls( - store -> CallHierarchy.toRascal(store, source, columns), - store -> CallHierarchy.direction(store, direction) - ).get() - .thenApply(res -> { - var callRel = res.getLeft(); - var store = res.getRight(); - var ch = new CallHierarchy(store); - return callRel.stream() - .map(ITuple.class::cast) - .collect(Collectors.toMap( - t -> ch.toLSP((IConstructor) t.get(0), columns), - t -> List.of(Locations.toRange((ISourceLocation) t.get(1), columns)), - Lists::union, - LinkedHashMap::new - )); - }) - .thenApply(map -> map.entrySet().stream() - .map(e -> constructor.apply(e.getKey(), e.getValue())) - .collect(Collectors.toList())); + var ch = new CallHierarchy(); + return ch.toRascal(source, contrib::parseCallHierarchyData, columns) + .thenCompose(sourceItem -> contrib.incomingOutgoingCalls(sourceItem, 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 + ))) + .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 3fa51e336..4ddc3031d 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,8 +32,6 @@ 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; @@ -57,7 +55,6 @@ 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); @@ -154,6 +151,11 @@ public CompletableFuture parseCodeActions(String commands) { return CompletableFuture.completedFuture(VF.list()); } + @Override + public CompletableFuture parseCallHierarchyData(String commands) { + throw new IllegalStateException("This method should not be called; this contribution only has a parser"); + } + @Override public InterruptibleFuture inlayHint(ITree input) { return InterruptibleFuture.completedFuture(VF.list()); @@ -205,13 +207,13 @@ public InterruptibleFuture selectionRange(IList focus) { } @Override - public InterruptibleFuture> prepareCallHierarchy(IList focus) { - return InterruptibleFuture.completedFuture(Pair.of(VF.list(), new TypeStore())); + public InterruptibleFuture prepareCallHierarchy(IList focus) { + return InterruptibleFuture.completedFuture(VF.list()); } @Override - public InterruptibleFuture> incomingOutgoingCalls(Function hierarchyItem, Function direction) { - return InterruptibleFuture.completedFuture(Pair.of(VF.list(), new TypeStore())); + public InterruptibleFuture incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction) { + return InterruptibleFuture.completedFuture(VF.list()); } @Override 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 08713d389..3875082ee 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 @@ -27,11 +27,10 @@ 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 java.util.concurrent.CompletableFuture; +import java.util.function.Function; import org.eclipse.lsp4j.CallHierarchyItem; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.vscode.lsp.util.locations.ColumnMaps; @@ -42,8 +41,6 @@ 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; import io.usethesource.vallang.type.Type; import io.usethesource.vallang.type.TypeFactory; import io.usethesource.vallang.type.TypeStore; @@ -61,8 +58,6 @@ public enum Direction { private final IConstructor outgoing; 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"; @@ -71,22 +66,20 @@ public enum Direction { 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 - this.callHierarchyDataAdt = store.lookupAbstractDataType("CallHierarchyData"); - this.callHierarchyDataCons = store.lookupConstructor(callHierarchyDataAdt, "none").iterator().next(); - } - - public static IConstructor direction(TypeStore store, CallHierarchy.Direction direction) { - var ch = new CallHierarchy(store); - return ch.direction(direction); + public CallHierarchy() { + var store = new TypeStore(); + Type directionAdt = TF.abstractDataType(store, "CallDirection"); + this.incoming = VF.constructor(TF.constructor(store, directionAdt, "incoming")); + this.outgoing = VF.constructor(TF.constructor(store, directionAdt, "outgoing")); + var callHierarchyItemAdt = TF.abstractDataType(store, "CallHierarchyItem"); + this.callHierarchyItemCons = TF.constructor(store, callHierarchyItemAdt, "callHierarchyItem", + TF.stringType(), NAME, + DocumentSymbols.getSymbolKindType(), KIND, + TF.sourceLocationType(), DEFINITION, + TF.sourceLocationType(), SELECTION + ); } public IConstructor direction(Direction dir) { @@ -124,32 +117,20 @@ private String serializeData(IConstructor data) { return data.toString(); } - 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) { - throw new IllegalArgumentException("The call hierarchy item data could not be parsed", e); - } - } - - 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()), - 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, deserializeData(ci.getData()) - )); + public CompletableFuture toRascal(CallHierarchyItem ci, Function> dataParser, ColumnMaps columns) { + var serializedData = ci.getData() != null + ? ((JsonPrimitive) ci.getData()).getAsString() + : ""; + return dataParser.apply(serializedData).thenApply(data -> + VF.constructor(callHierarchyItemCons, List.of( + VF.string(ci.getName()), + 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, data + ))); } } From b6fe2f4639fb3b182f0b864aa8c42546b7106a80 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 29 Oct 2025 17:33:55 +0100 Subject: [PATCH 25/53] Inlining. --- .../lsp/parametric/ParametricTextDocumentService.java | 7 +++---- 1 file changed, 3 insertions(+), 4 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 39fbd3f2e..be21d974c 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 @@ -849,8 +849,7 @@ public CompletableFuture> prepareCallHierarchy(CallHiera .thenApply(Versioned::get) .thenCompose(t -> { final var pos = Locations.toRascalPosition(loc, params.getPosition(), columns); - final var focus = TreeSearch.computeFocusList(t, pos.getLine(), pos.getCharacter()); - return contrib.prepareCallHierarchy(focus) + return contrib.prepareCallHierarchy(TreeSearch.computeFocusList(t, pos.getLine(), pos.getCharacter())) .get() .thenApply(items -> { var ch = new CallHierarchy(); @@ -874,8 +873,8 @@ private CompletableFuture> incomingOutgoingCalls(BiFunction List.of(Locations.toRange((ISourceLocation) t.get(1), columns)), Lists::union, LinkedHashMap::new - ))) - .thenApply(map -> map.entrySet().stream() + )) + .entrySet().stream() .map(e -> constructor.apply(e.getKey(), e.getValue())) .collect(Collectors.toList())); } From b4a42e78631980ca51e9cd38eacbb14925c7125e Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 29 Oct 2025 17:44:04 +0100 Subject: [PATCH 26/53] Move field names to common database. --- .../lsp/parametric/model/RascalADTs.java | 12 +++++ .../vscode/lsp/util/CallHierarchy.java | 45 ++++++++----------- 2 files changed, 30 insertions(+), 27 deletions(-) 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 43b6d3bb6..4f775d8e1 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 @@ -91,4 +91,16 @@ private CodeActionFields() { } public static final String TITLE = "title"; public static final String KIND = "kind"; } + + public static class CallHierarchyFields { + private CallHierarchyFields() { } + + public static final String NAME = "name"; + public static final String KIND = "kind"; + public static final String DEFINITION = "src"; + public static final String SELECTION = "selection"; + public static final String TAGS = "tags"; + public static final String DETAIL = "detail"; + public static final String DATA = "data"; + } } 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 3875082ee..d9bf78782 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,6 +33,7 @@ import java.util.function.Function; import org.eclipse.lsp4j.CallHierarchyItem; import org.rascalmpl.values.IRascalValueFactory; +import org.rascalmpl.vscode.lsp.parametric.model.RascalADTs.CallHierarchyFields; import org.rascalmpl.vscode.lsp.util.locations.ColumnMaps; import org.rascalmpl.vscode.lsp.util.locations.Locations; @@ -56,18 +57,8 @@ public enum Direction { private final IConstructor incoming; private final IConstructor outgoing; - private final Type callHierarchyItemCons; - 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"; - - public CallHierarchy() { var store = new TypeStore(); Type directionAdt = TF.abstractDataType(store, "CallDirection"); @@ -75,10 +66,10 @@ public CallHierarchy() { this.outgoing = VF.constructor(TF.constructor(store, directionAdt, "outgoing")); var callHierarchyItemAdt = TF.abstractDataType(store, "CallHierarchyItem"); this.callHierarchyItemCons = TF.constructor(store, callHierarchyItemAdt, "callHierarchyItem", - TF.stringType(), NAME, - DocumentSymbols.getSymbolKindType(), KIND, - TF.sourceLocationType(), DEFINITION, - TF.sourceLocationType(), SELECTION + TF.stringType(), CallHierarchyFields.NAME, + DocumentSymbols.getSymbolKindType(), CallHierarchyFields.KIND, + TF.sourceLocationType(), CallHierarchyFields.DEFINITION, + TF.sourceLocationType(), CallHierarchyFields.SELECTION ); } @@ -91,23 +82,23 @@ public IConstructor direction(Direction dir) { } public CallHierarchyItem toLSP(IConstructor cons, ColumnMaps columns) { - var name = ((IString) cons.get(NAME)).getValue(); - var kind = DocumentSymbols.symbolKindToLSP((IConstructor) cons.get(KIND)); - var def = (ISourceLocation) cons.get(DEFINITION); + var name = ((IString) cons.get(CallHierarchyFields.NAME)).getValue(); + var kind = DocumentSymbols.symbolKindToLSP((IConstructor) cons.get(CallHierarchyFields.KIND)); + var def = (ISourceLocation) cons.get(CallHierarchyFields.DEFINITION); var definitionRange = Locations.toRange(def, columns); - var selection = (ISourceLocation) cons.get(SELECTION); + var selection = (ISourceLocation) cons.get(CallHierarchyFields.SELECTION); var selectionRange = Locations.toRange(selection, columns); var ci = new CallHierarchyItem(name, kind, def.top().getURI().toString(), definitionRange, selectionRange); var kws = cons.asWithKeywordParameters(); - if (kws.hasParameter(TAGS)) { - ci.setTags(DocumentSymbols.symbolTagsToLSP((ISet) kws.getParameter(TAGS))); + if (kws.hasParameter(CallHierarchyFields.TAGS)) { + ci.setTags(DocumentSymbols.symbolTagsToLSP((ISet) kws.getParameter(CallHierarchyFields.TAGS))); } - if (kws.hasParameter(DETAIL)) { - ci.setDetail(((IString) kws.getParameter(DETAIL)).getValue()); + if (kws.hasParameter(CallHierarchyFields.DETAIL)) { + ci.setDetail(((IString) kws.getParameter(CallHierarchyFields.DETAIL)).getValue()); } - if (kws.hasParameter(DATA)) { - ci.setData(serializeData((IConstructor) kws.getParameter(DATA))); + if (kws.hasParameter(CallHierarchyFields.DATA)) { + ci.setData(serializeData((IConstructor) kws.getParameter(CallHierarchyFields.DATA))); } return ci; @@ -128,9 +119,9 @@ public CompletableFuture toRascal(CallHierarchyItem ci, Function Date: Wed, 29 Oct 2025 17:44:18 +0100 Subject: [PATCH 27/53] Document call hierarchy conversion. --- .../org/rascalmpl/vscode/lsp/util/CallHierarchy.java | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 d9bf78782..0aca3f4a1 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 @@ -46,6 +46,13 @@ import io.usethesource.vallang.type.TypeFactory; import io.usethesource.vallang.type.TypeStore; +/** + * Converts call hierarchy items from Rascal to LSP and vice versa. + * + * LSP requires and produces {@link CallHierarchyItem} and {@link CallHierarchyItem.Direction}. + * In Rascal, those are modeled as {@link IConstructor}, possibly with keyword fields. + * This class serves to convert one to the other. + */ public class CallHierarchy { private static final IRascalValueFactory VF = IRascalValueFactory.getInstance(); private static final TypeFactory TF = TypeFactory.getInstance(); @@ -104,6 +111,10 @@ public CallHierarchyItem toLSP(IConstructor cons, ColumnMaps columns) { return ci; } + /** + * Serializes the call hierarchy item data field as a {@link String}. + * The deserialization counterpart is {@link org.rascalmpl.vscode.lsp.parametric.ILanguageContributions#parseCallHierarchyData}. + */ private String serializeData(IConstructor data) { return data.toString(); } From 7731e1c4ab5f48b92506ddf2a0653f2f2412aa13 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 30 Oct 2025 14:40:03 +0100 Subject: [PATCH 28/53] Add new interface methods. --- .../lsp/parametric/NoContributions.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/NoContributions.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/NoContributions.java index 76ee5c2a6..832f5f2ef 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/NoContributions.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/NoContributions.java @@ -145,11 +145,26 @@ public InterruptibleFuture didRenameFiles(IList fileRenames) { throw new NoContributionException("didRenameFiles"); } + @Override + public InterruptibleFuture prepareCallHierarchy(IList focus) { + throw new NoContributionException("prepareCallHierarchy"); + } + + @Override + public InterruptibleFuture incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction) { + throw new NoContributionException("incomingOutgoingCalls"); + } + @Override public CompletableFuture parseCodeActions(String command) { throw new NoContributionException("parseCodeActions"); } + @Override + public CompletableFuture parseCallHierarchyData(String data) { + throw new NoContributionException("parseCallHierarchyData"); + } + @Override public CompletableFuture hasAnalysis() { return FALSE; @@ -220,6 +235,11 @@ public CompletableFuture hasSelectionRange() { return FALSE; } + @Override + public CompletableFuture hasCallHierarchy() { + return FALSE; + } + @Override public CompletableFuture specialCaseHighlighting() { return FALSE; From 22d921778ca028e5e5b33d373a063f1556d1ce86 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 30 Oct 2025 14:55:46 +0100 Subject: [PATCH 29/53] Use equals. --- .../vscode/lsp/parametric/InterpretedLanguageContributions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3f635d47e..154357c85 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 @@ -278,7 +278,7 @@ public CompletableFuture parseCallHierarchyData(String data) { if (callHierarchyDataAdt == null) { throw new IllegalArgumentException("CallHierarchyData is not defined in environment"); } - if (data == null || data == "") { + if (data == null || "".equals(data)) { var none = completionStore.lookupConstructor(callHierarchyDataAdt, "none", TypeFactory.getInstance().tupleEmpty()); if (none == null) { throw new IllegalArgumentException("CallHierarchyData::none() is not defined in environment"); From f37eefbf1f365e1fe1f28d96e2d3506cf3abd54d Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 30 Oct 2025 17:25:37 +0100 Subject: [PATCH 30/53] Add roundtrip test for source location conversions. --- .../swat/rascal/lsp/util/LocationsTest.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LocationsTest.java diff --git a/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LocationsTest.java b/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LocationsTest.java new file mode 100644 index 000000000..8c66dc351 --- /dev/null +++ b/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LocationsTest.java @@ -0,0 +1,56 @@ +/* + * 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 engineering.swat.rascal.lsp.util; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.rascalmpl.values.IRascalValueFactory; +import org.rascalmpl.vscode.lsp.util.locations.ColumnMaps; +import org.rascalmpl.vscode.lsp.util.locations.Locations; + +public class LocationsTest { + + @Test + public void roundtrip() { + var VF = IRascalValueFactory.getInstance(); + var fileName = "PNG.bird"; + var contents = "module images::PNG\r\n" + // + "\r\n" + // + "struct Signature {\r\n" + // + " u8 _ ?(== 0x89)\r\n" + // + " byte[] _[3] ?(== \"PNG\")\r\n" + // + " byte[] _[4] ?(== <0x0d, 0x0a, 0x1a, 0x0a>)\r\n" + // + "}\r\n" + // + ""; + var columns = new ColumnMaps(f -> contents); + var in = VF.sourceLocation(fileName, 22, 119, 3, 7, 0, 1); // the structure + var range = Locations.toRange(in, columns); + var out = Locations.setRange(in, range, columns); + assertEquals(in, out); + } +} From d99030ade588d9511745bb64c8e22c56c1538fda Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 30 Oct 2025 18:19:17 +0100 Subject: [PATCH 31/53] Add tests for several edge cases. --- .../swat/rascal/lsp/util/LocationsTest.java | 62 +++++++++++++++++-- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LocationsTest.java b/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LocationsTest.java index 8c66dc351..10816ee66 100644 --- a/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LocationsTest.java +++ b/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LocationsTest.java @@ -35,10 +35,15 @@ public class LocationsTest { + private static IRascalValueFactory VF = IRascalValueFactory.getInstance(); + private String fileName = "PNG.bird"; + + private static ColumnMaps columns(String contents) { + return new ColumnMaps(f -> contents); + } + @Test - public void roundtrip() { - var VF = IRascalValueFactory.getInstance(); - var fileName = "PNG.bird"; + public void roundtripWindowsEmptyLine() { var contents = "module images::PNG\r\n" + // "\r\n" + // "struct Signature {\r\n" + // @@ -47,10 +52,55 @@ public void roundtrip() { " byte[] _[4] ?(== <0x0d, 0x0a, 0x1a, 0x0a>)\r\n" + // "}\r\n" + // ""; - var columns = new ColumnMaps(f -> contents); + var columns = columns(contents); var in = VF.sourceLocation(fileName, 22, 119, 3, 7, 0, 1); // the structure - var range = Locations.toRange(in, columns); - var out = Locations.setRange(in, range, columns); + var out = Locations.setRange(in, Locations.toRange(in, columns), columns); + assertEquals(in, out); + } + + @Test + public void roundtripWindows() { + var contents = "module images::PNG\r\n" + // + "struct Signature {\r\n" + // + " u8 _ ?(== 0x89)\r\n" + // + " byte[] _[3] ?(== \"PNG\")\r\n" + // + " byte[] _[4] ?(== <0x0d, 0x0a, 0x1a, 0x0a>)\r\n" + // + "}\r\n" + // + ""; + var columns = columns(contents); + var in = VF.sourceLocation(fileName, 20, 119, 2, 6, 0, 1); // the structure + var out = Locations.setRange(in, Locations.toRange(in, columns), columns); + assertEquals(in, out); + } + + @Test + public void roundtripUnixEmptyLine() { + var contents = "module images::PNG\n" + // + "\n" + // + "struct Signature {\n" + // + " u8 _ ?(== 0x89)\n" + // + " byte[] _[3] ?(== \"PNG\")\n" + // + " byte[] _[4] ?(== <0x0d, 0x0a, 0x1a, 0x0a>)\n" + // + "}\n" + // + ""; + var columns = columns(contents); + var in = VF.sourceLocation(fileName, 20, 115, 3, 7, 0, 1); // the structure + var out = Locations.setRange(in, Locations.toRange(in, columns), columns); + assertEquals(in, out); + } + + @Test + public void roundtripUnix() { + var contents = "module images::PNG\n" + // + "struct Signature {\n" + // + " u8 _ ?(== 0x89)\n" + // + " byte[] _[3] ?(== \"PNG\")\n" + // + " byte[] _[4] ?(== <0x0d, 0x0a, 0x1a, 0x0a>)\n" + // + "}\n" + // + ""; + var columns = columns(contents); + var in = VF.sourceLocation(fileName, 19, 115, 2, 6, 0, 1); // the structure + var out = Locations.setRange(in, Locations.toRange(in, columns), columns); assertEquals(in, out); } } From 87578222af82753c4771beaff6cbacba50aa0bd6 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 3 Nov 2025 11:56:16 +0100 Subject: [PATCH 32/53] Use column maps from Rascal. --- .../main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java | 2 +- .../java/engineering/swat/rascal/lsp/util/LocationsTest.java | 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 0aca3f4a1..27d68c793 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 @@ -32,9 +32,9 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Function; import org.eclipse.lsp4j.CallHierarchyItem; +import org.rascalmpl.util.locations.ColumnMaps; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.vscode.lsp.parametric.model.RascalADTs.CallHierarchyFields; -import org.rascalmpl.vscode.lsp.util.locations.ColumnMaps; import org.rascalmpl.vscode.lsp.util.locations.Locations; import io.usethesource.vallang.IConstructor; diff --git a/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LocationsTest.java b/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LocationsTest.java index 10816ee66..f3f01fa01 100644 --- a/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LocationsTest.java +++ b/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LocationsTest.java @@ -29,8 +29,8 @@ import static org.junit.Assert.assertEquals; import org.junit.Test; +import org.rascalmpl.util.locations.ColumnMaps; import org.rascalmpl.values.IRascalValueFactory; -import org.rascalmpl.vscode.lsp.util.locations.ColumnMaps; import org.rascalmpl.vscode.lsp.util.locations.Locations; public class LocationsTest { From 3f3bf612ddf62de5cdf61a6bdb58cd999b90c5c0 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 3 Nov 2025 15:19:50 +0100 Subject: [PATCH 33/53] Add pico call hierarchy example. --- .../library/demo/lang/pico/LanguageServer.rsc | 58 ++++++++++++++++++- .../library/demo/lang/pico/examples/fac.pico | 4 +- 2 files changed, 59 insertions(+), 3 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 660abd354..7ebfe8d3b 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 + = function: Id id "(" {IdType ","}* args ")" ":" Type retType ":=" 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,8 @@ set[LanguageService] picoLanguageServer(bool allowRecovery) = { codeAction(picoCodeActionService), rename(picoRenamingService, prepareRenameService = picoRenamePreparingService), didRenameFiles(picoFileRenameService), - selectionRange(picoSelectionRangeService) + selectionRange(picoSelectionRangeService), + callHierarchy(picoPrepareCallHierarchy, picoIncomingOutgoingCalls) }; set[LanguageService] picoLanguageServer() = picoLanguageServer(false); @@ -227,6 +236,51 @@ tuple[list[DocumentEdit],set[Message]] picoFileRenameService(list[DocumentEdit] list[loc] picoSelectionRangeService(Focus focus) = dup([t@\loc | t <- focus]); +list[CallHierarchyItem] picoPrepareCallHierarchy(Focus focus: [*_, e:(Expression) `(<{Expression ","}* args>)`, *_, start[Program] prog]) + = [callHierarchyItem(prog, findDefinition(prog, e))]; + +list[CallHierarchyItem] picoPrepareCallHierarchy(Focus _: [*_, d:(IdType) `(<{IdType ","}* args>): := `, *_, start[Program] prog]) + = [callHierarchyItem(prog, d)]; + +IdType findDefinition(start[Program] prog, e:(Expression) `(<{Expression ","}* args>)`) { + for (/d:(IdType) `(<{IdType ","}* dargs>): := ` := prog + , signatureMatches(d, e)) { + return d; + } + fail; +} + +bool signatureMatches( + (IdType) `(<{IdType ","}* fargs>): := `, + (Expression) `(<{Expression ","}* args>)`) + = "" == "" && size(args) == size(fargs); + +default bool signatureMatches(_, _) = false; + +int size({IdType ","}* args) = size([a | a <- args]); +int size({Expression ","}* args) = size([a | a <- args]); + +CallHierarchyItem callHierarchyItem(start[Program] prog, d:(IdType) `(<{IdType ","}* args>): := `) + = callHierarchyItem( + "", + function(), + d.src, + id.src, + detail = "(): ", + \data = \data(prog, d) + ); + +data CallHierarchyData = \data(start[Program] prog, IdType def); + +str typeOf((IdType) `: `) = ""; +str typeOf((IdType) `(<{IdType ","}* args>): := `) + = "(): "; + +lrel[CallHierarchyItem, loc] picoIncomingOutgoingCalls(CallHierarchyItem ci, incoming()) + = [ | /c:(Expression) `(<{Expression ","}* _>)` := ci.\data.prog, signatureMatches(ci.\data.def, c)]; +lrel[CallHierarchyItem, loc] picoIncomingOutgoingCalls(CallHierarchyItem ci, outgoing()) + = [ | /c:(Expression) `(<{Expression ","}* _>)` := ci.\data.def]; + @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/demo/lang/pico/examples/fac.pico b/rascal-lsp/src/main/rascal/library/demo/lang/pico/examples/fac.pico index ad231294e..297869c20 100644 --- a/rascal-lsp/src/main/rascal/library/demo/lang/pico/examples/fac.pico +++ b/rascal-lsp/src/main/rascal/library/demo/lang/pico/examples/fac.pico @@ -3,7 +3,9 @@ begin input : natural, output : natural, repnr : natural, - rep : natural; + rep : natural, + add(x: natural, y: natural): natural := x + y, + multiply(x: natural, y: natural): natural := add(x, multiply(x - 1, y)); input := 6; while input - 1 do From 84c640eb7a79c15d82393fa9ed271f69930ff9b7 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 4 Nov 2025 14:58:45 +0100 Subject: [PATCH 34/53] Simplify & fix warnings. --- .../src/main/rascal/library/demo/lang/pico/LanguageServer.rsc | 4 ++-- .../java/engineering/swat/rascal/lsp/util/LocationsTest.java | 2 +- 2 files changed, 3 insertions(+), 3 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 7ebfe8d3b..ddce5db4c 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 @@ -242,8 +242,8 @@ list[CallHierarchyItem] picoPrepareCallHierarchy(Focus focus: [*_, e:(Expression list[CallHierarchyItem] picoPrepareCallHierarchy(Focus _: [*_, d:(IdType) `(<{IdType ","}* args>): := `, *_, start[Program] prog]) = [callHierarchyItem(prog, d)]; -IdType findDefinition(start[Program] prog, e:(Expression) `(<{Expression ","}* args>)`) { - for (/d:(IdType) `(<{IdType ","}* dargs>): := ` := prog +IdType findDefinition(start[Program] prog, e:(Expression) `(<{Expression ","}* _>)`) { + for (/d:(IdType) `(<{IdType ","}* _>): := ` := prog , signatureMatches(d, e)) { return d; } diff --git a/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LocationsTest.java b/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LocationsTest.java index f3f01fa01..5dac9ff97 100644 --- a/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LocationsTest.java +++ b/rascal-lsp/src/test/java/engineering/swat/rascal/lsp/util/LocationsTest.java @@ -35,7 +35,7 @@ public class LocationsTest { - private static IRascalValueFactory VF = IRascalValueFactory.getInstance(); + private static final IRascalValueFactory VF = IRascalValueFactory.getInstance(); private String fileName = "PNG.bird"; private static ColumnMaps columns(String contents) { From 61320c4e5af9555d10a4862313c29b927638f390 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 6 Nov 2025 10:49:51 +0100 Subject: [PATCH 35/53] Fix incoming calls. --- .../src/main/rascal/library/demo/lang/pico/LanguageServer.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ddce5db4c..b9db9765f 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 @@ -277,7 +277,7 @@ str typeOf((IdType) `(<{IdType ","}* args>): := (): "; lrel[CallHierarchyItem, loc] picoIncomingOutgoingCalls(CallHierarchyItem ci, incoming()) - = [ | /c:(Expression) `(<{Expression ","}* _>)` := ci.\data.prog, signatureMatches(ci.\data.def, c)]; + = [ | /caller:(IdType) `(<{IdType ","}* args>): := ` := ci.\data.prog, /c:(Expression) `(<{Expression ","}* _>)` := caller, signatureMatches(ci.\data.def, c)]; lrel[CallHierarchyItem, loc] picoIncomingOutgoingCalls(CallHierarchyItem ci, outgoing()) = [ | /c:(Expression) `(<{Expression ","}* _>)` := ci.\data.def]; From 7ba00c19a8fbe4898301bf7884d274188241c723 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 6 Nov 2025 10:59:59 +0100 Subject: [PATCH 36/53] Fix and reuse typeOf. --- .../src/main/rascal/library/demo/lang/pico/LanguageServer.rsc | 4 ++-- 1 file changed, 2 insertions(+), 2 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 b9db9765f..c336f05a0 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 @@ -266,7 +266,7 @@ CallHierarchyItem callHierarchyItem(start[Program] prog, d:(IdType) `(<{I function(), d.src, id.src, - detail = "(): ", + detail = typeOf(d), \data = \data(prog, d) ); @@ -274,7 +274,7 @@ data CallHierarchyData = \data(start[Program] prog, IdType def); str typeOf((IdType) `: `) = ""; str typeOf((IdType) `(<{IdType ","}* args>): := `) - = "(): "; + = "(): "; lrel[CallHierarchyItem, loc] picoIncomingOutgoingCalls(CallHierarchyItem ci, incoming()) = [ | /caller:(IdType) `(<{IdType ","}* args>): := ` := ci.\data.prog, /c:(Expression) `(<{Expression ","}* _>)` := caller, signatureMatches(ci.\data.def, c)]; From 16cceef8b9c28895770e5ce958e4748704700dfe Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 6 Nov 2025 11:00:13 +0100 Subject: [PATCH 37/53] Use id only as call site. --- .../src/main/rascal/library/demo/lang/pico/LanguageServer.rsc | 4 ++-- 1 file changed, 2 insertions(+), 2 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 c336f05a0..fb5aabebb 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 @@ -277,9 +277,9 @@ str typeOf((IdType) `(<{IdType ","}* args>): := (): "; lrel[CallHierarchyItem, loc] picoIncomingOutgoingCalls(CallHierarchyItem ci, incoming()) - = [ | /caller:(IdType) `(<{IdType ","}* args>): := ` := ci.\data.prog, /c:(Expression) `(<{Expression ","}* _>)` := caller, signatureMatches(ci.\data.def, c)]; + = [ | /caller:(IdType) `(<{IdType ","}* args>): := ` := ci.\data.prog, /c:(Expression) `(<{Expression ","}* _>)` := caller, signatureMatches(ci.\data.def, c)]; lrel[CallHierarchyItem, loc] picoIncomingOutgoingCalls(CallHierarchyItem ci, outgoing()) - = [ | /c:(Expression) `(<{Expression ","}* _>)` := ci.\data.def]; + = [ | /c:(Expression) `(<{Expression ","}* _>)` := ci.\data.def]; @synopsis{The main function registers the Pico language with the IDE} @description{ From 4aad0e81f1e2bf431b2e1aae81d45a6009ae91c4 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 10 Nov 2025 15:45:46 +0100 Subject: [PATCH 38/53] Fix nulls (h/t @DavyLandman). --- .../InterpretedLanguageContributions.java | 2 +- .../vscode/lsp/util/CallHierarchy.java | 23 +++++++++++++------ 2 files changed, 17 insertions(+), 8 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 154357c85..de0f82714 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 @@ -278,7 +278,7 @@ public CompletableFuture parseCallHierarchyData(String data) { if (callHierarchyDataAdt == null) { throw new IllegalArgumentException("CallHierarchyData is not defined in environment"); } - if (data == null || "".equals(data)) { + if (data.isEmpty()) { var none = completionStore.lookupConstructor(callHierarchyDataAdt, "none", TypeFactory.getInstance().tupleEmpty()); if (none == null) { throw new IllegalArgumentException("CallHierarchyData::none() is not defined in environment"); 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 27d68c793..5bf07f496 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 @@ -27,6 +27,7 @@ package org.rascalmpl.vscode.lsp.util; import com.google.gson.JsonPrimitive; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -123,16 +124,24 @@ public CompletableFuture toRascal(CallHierarchyItem ci, Function - VF.constructor(callHierarchyItemCons, List.of( + + return dataParser.apply(serializedData).thenApply(data -> { + Map kwArgs = new HashMap<>(); + var tags = ci.getTags(); + if (tags != null) { + kwArgs.put(CallHierarchyFields.TAGS, DocumentSymbols.symbolTagsToRascal(tags)); + } + var detail = ci.getDetail(); + if (detail != null) { + kwArgs.put(CallHierarchyFields.DETAIL, VF.string(detail)); + } + kwArgs.put(CallHierarchyFields.DATA, data); + return VF.constructor(callHierarchyItemCons, List.of( VF.string(ci.getName()), 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( - CallHierarchyFields.TAGS, DocumentSymbols.symbolTagsToRascal(ci.getTags()), - CallHierarchyFields.DETAIL, VF.string(ci.getDetail()), - CallHierarchyFields.DATA, data - ))); + ).toArray(new IValue[0]), kwArgs); + }); } } From c0e5e8e1190ea068d7c2faf820d5280d03e6481f Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 10 Nov 2025 15:50:25 +0100 Subject: [PATCH 39/53] Naming. --- .../main/rascal/library/demo/lang/pico/LanguageServer.rsc | 6 +++--- 1 file changed, 3 insertions(+), 3 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 fb5aabebb..e1f3d3192 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,7 +70,7 @@ set[LanguageService] picoLanguageServer(bool allowRecovery) = { rename(picoRenamingService, prepareRenameService = picoRenamePreparingService), didRenameFiles(picoFileRenameService), selectionRange(picoSelectionRangeService), - callHierarchy(picoPrepareCallHierarchy, picoIncomingOutgoingCalls) + callHierarchy(picoPrepareCallHierarchy, picoCallsService) }; set[LanguageService] picoLanguageServer() = picoLanguageServer(false); @@ -276,9 +276,9 @@ str typeOf((IdType) `: `) = ""; str typeOf((IdType) `(<{IdType ","}* args>): := `) = "(): "; -lrel[CallHierarchyItem, loc] picoIncomingOutgoingCalls(CallHierarchyItem ci, incoming()) +lrel[CallHierarchyItem, loc] picoCallsService(CallHierarchyItem ci, incoming()) = [ | /caller:(IdType) `(<{IdType ","}* args>): := ` := ci.\data.prog, /c:(Expression) `(<{Expression ","}* _>)` := caller, signatureMatches(ci.\data.def, c)]; -lrel[CallHierarchyItem, loc] picoIncomingOutgoingCalls(CallHierarchyItem ci, outgoing()) +lrel[CallHierarchyItem, loc] picoCallsService(CallHierarchyItem ci, outgoing()) = [ | /c:(Expression) `(<{Expression ","}* _>)` := ci.\data.def]; @synopsis{The main function registers the Pico language with the IDE} From 3654e18c75e4ab0ec992cb32dfb20c7e4a23fe8b Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 10 Nov 2025 15:51:03 +0100 Subject: [PATCH 40/53] Clarifications. --- .../vscode/lsp/parametric/ParametricTextDocumentService.java | 1 + .../main/java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java | 2 +- .../src/main/rascal/library/demo/lang/pico/LanguageServer.rsc | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) 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 e383f3c86..f2f79019d 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 @@ -886,6 +886,7 @@ private CompletableFuture> incomingOutgoingCalls(BiFunction contrib.incomingOutgoingCalls(sourceItem, ch.direction(direction)).get()) .thenApply(callRel -> callRel.stream() .map(ITuple.class::cast) + // Collect call sites (value) by associated definition (key) as a map .collect(Collectors.toMap( t -> ch.toLSP((IConstructor) t.get(0), columns), t -> List.of(Locations.toRange((ISourceLocation) t.get(1), columns)), 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 5bf07f496..4a9dd19d6 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 @@ -85,7 +85,7 @@ public IConstructor direction(Direction dir) { switch (dir) { case INCOMING: return this.incoming; case OUTGOING: return this.outgoing; - default: throw new IllegalArgumentException(); + default: throw new IllegalArgumentException("Unknown call direction: " + dir.name().toLowerCase()); } } 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 e1f3d3192..411ee3472 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 @@ -41,6 +41,8 @@ import util::Reflective; extend lang::pico::\syntax::Main; import DateTime; +// We extend the grammar with functions and calls, so we can demo call hierarchy functionality. +// For most use-cases, one should not extend the grammar in the language server implementation syntax IdType = function: Id id "(" {IdType ","}* args ")" ":" Type retType ":=" Expression body ; From d7a1727d62a8cfc502faa6f8ac943756d6e82a80 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 10 Nov 2025 15:51:19 +0100 Subject: [PATCH 41/53] Functions in outline. --- .../src/main/rascal/library/demo/lang/pico/LanguageServer.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 411ee3472..543635115 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 @@ -100,7 +100,7 @@ symbol search in the editor. } list[DocumentSymbol] picoDocumentSymbolService(start[Program] input) = [symbol("", DocumentSymbolKind::\file(), input.src, children=[ - *[symbol("", \variable(), var.src) | /IdType var := input, var.id?] + *[symbol("", var is function ? \function() : \variable(), var.src) | /IdType var := input, var.id?] ])]; @synopsis{The analyzer maps pico syntax trees to error messages and references} From 2672f718f55b389d696f4e27c61584905a53bfcc Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Mon, 10 Nov 2025 15:51:37 +0100 Subject: [PATCH 42/53] Use wildcards for unused vars. --- .../src/main/rascal/library/demo/lang/pico/LanguageServer.rsc | 4 ++-- 1 file changed, 2 insertions(+), 2 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 543635115..c0f50565f 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 @@ -262,7 +262,7 @@ default bool signatureMatches(_, _) = false; int size({IdType ","}* args) = size([a | a <- args]); int size({Expression ","}* args) = size([a | a <- args]); -CallHierarchyItem callHierarchyItem(start[Program] prog, d:(IdType) `(<{IdType ","}* args>): := `) +CallHierarchyItem callHierarchyItem(start[Program] prog, d:(IdType) `(<{IdType ","}* _>): := `) = callHierarchyItem( "", function(), @@ -279,7 +279,7 @@ str typeOf((IdType) `(<{IdType ","}* args>): := (): "; lrel[CallHierarchyItem, loc] picoCallsService(CallHierarchyItem ci, incoming()) - = [ | /caller:(IdType) `(<{IdType ","}* args>): := ` := ci.\data.prog, /c:(Expression) `(<{Expression ","}* _>)` := caller, signatureMatches(ci.\data.def, c)]; + = [ | /caller:(IdType) `(<{IdType ","}* _>): := ` := ci.\data.prog, /c:(Expression) `(<{Expression ","}* _>)` := caller, signatureMatches(ci.\data.def, c)]; lrel[CallHierarchyItem, loc] picoCallsService(CallHierarchyItem ci, outgoing()) = [ | /c:(Expression) `(<{Expression ","}* _>)` := ci.\data.def]; From 488305b7e94422d8156b48214c633f418c48cba1 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 11 Nov 2025 10:47:04 +0100 Subject: [PATCH 43/53] Add changelog entry for call hierarchies. --- rascal-vscode-extension/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rascal-vscode-extension/CHANGELOG.md b/rascal-vscode-extension/CHANGELOG.md index fbeff6aef..f12574904 100644 --- a/rascal-vscode-extension/CHANGELOG.md +++ b/rascal-vscode-extension/CHANGELOG.md @@ -18,7 +18,7 @@ We only list significant changes, for a full changelog [please review the commit * Error recovery support for DSLs is opt-in (the parse function should be constructed with `allowRecovery=true`); your code might have to be updated to deal with these trees with skipped/ambiguous parts. * DSLs can contribute their own rename refactoring using the `LanguageService::rename` contribution. * DSLs can contribute their own selection ranges using the `LanguageService::selectionRange` contribution. - +* DSLs can contribute their own call hierarchies using the `LanguageServer::callHierachy` contribution. ### Improved features From 9d7cc5105a1699c74d7abbed0e2ac9f9f0a56425 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Tue, 11 Nov 2025 14:44:21 +0100 Subject: [PATCH 44/53] Rewrite call hierarchies using summary. --- .../library/demo/lang/pico/LanguageServer.rsc | 88 +++++++++++++------ 1 file changed, 61 insertions(+), 27 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 c0f50565f..c783a6b74 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 @@ -40,6 +40,7 @@ import util::ParseErrorRecovery; import util::Reflective; extend lang::pico::\syntax::Main; import DateTime; +import Location; // We extend the grammar with functions and calls, so we can demo call hierarchy functionality. // For most use-cases, one should not extend the grammar in the language server implementation @@ -115,15 +116,31 @@ data PicoSummarizerMode | build() ; +rel[DocumentSymbolKind, loc, Id, str] findDefinitions(Tree input, bool funcScope = false) { + rel[DocumentSymbolKind, loc, Id, str] defs = {}; + top-down-break visit (input) { + case var:(IdType) `: `: defs += ; + case func:(IdType) `(<{IdType ","}* args>): := `: { + defs += ; + defs += findDefinitions(args, funcScope = true); + } + } + return defs; +} + +data Summary(rel[DocumentSymbolKind, loc, Id, str] definitionsByKind = {}); + @synopsis{Translates a pico syntax tree to a model (Summary) of everything we need to know about the program in the IDE.} +@memo{maximumSize(10), expireAfter(minutes=5)} Summary picoSummaryService(loc l, start[Program] input, PicoSummarizerMode mode) { Summary s = summary(l); // definitions of variables - rel[str, loc] defs = {<"", var.src> | /IdType var := input, var.id?}; + s.definitionsByKind = findDefinitions(input); + rel[str, loc] defs = {<"", d> | <- s.definitionsByKind<1, 2>}; // uses of identifiers - rel[loc, str] uses = {"> | /Id id := input}; + rel[loc, str] uses = {"> | /Id id := input, id notin s.definitionsByKind<2>}; // documentation strings for identifier uses rel[loc, str] docs = {"> | /IdType var := input}; @@ -144,7 +161,7 @@ Summary picoSummaryService(loc l, start[Program] input, PicoSummarizerMode mode) // Provide warnings (expensive to compute) only in build mode if (build() := mode) { rel[loc, str] asgn = {"> | /Statement stmt := input, (Statement) ` := ` := stmt}; - s.messages += { is not assigned", src)> | <- defs, id notin asgn<1>}; + s.messages += { is not assigned", src)> | <- s.definitionsByKind[variable()], "" notin asgn<1>}; } return s; @@ -239,28 +256,24 @@ list[loc] picoSelectionRangeService(Focus focus) = dup([t@\loc | t <- focus]); list[CallHierarchyItem] picoPrepareCallHierarchy(Focus focus: [*_, e:(Expression) `(<{Expression ","}* args>)`, *_, start[Program] prog]) - = [callHierarchyItem(prog, findDefinition(prog, e))]; + = [ callHierarchyItem(s, prog, id, d, tp) + | s := picoSummaryService(prog.src.top, prog, analyze()) + , d <- s.definitions[e.src] + , <- s.definitionsByKind[function(), d] + ]; list[CallHierarchyItem] picoPrepareCallHierarchy(Focus _: [*_, d:(IdType) `(<{IdType ","}* args>): := `, *_, start[Program] prog]) = [callHierarchyItem(prog, d)]; -IdType findDefinition(start[Program] prog, e:(Expression) `(<{Expression ","}* _>)`) { - for (/d:(IdType) `(<{IdType ","}* _>): := ` := prog - , signatureMatches(d, e)) { - return d; - } - fail; -} - -bool signatureMatches( - (IdType) `(<{IdType ","}* fargs>): := `, - (Expression) `(<{Expression ","}* args>)`) - = "" == "" && size(args) == size(fargs); - -default bool signatureMatches(_, _) = false; - -int size({IdType ","}* args) = size([a | a <- args]); -int size({Expression ","}* args) = size([a | a <- args]); +CallHierarchyItem callHierarchyItem(Summary s, start[Program] prog, Id id, loc decl, str tp) + = callHierarchyItem( + "", + function(), + decl, + id.src, + detail = tp, + \data = \data(prog) + ); CallHierarchyItem callHierarchyItem(start[Program] prog, d:(IdType) `(<{IdType ","}* _>): := `) = callHierarchyItem( @@ -269,19 +282,40 @@ CallHierarchyItem callHierarchyItem(start[Program] prog, d:(IdType) `(<{I d.src, id.src, detail = typeOf(d), - \data = \data(prog, d) + \data = \data(prog) ); -data CallHierarchyData = \data(start[Program] prog, IdType def); +data CallHierarchyData = \data(start[Program] prog); str typeOf((IdType) `: `) = ""; str typeOf((IdType) `(<{IdType ","}* args>): := `) = "(): "; -lrel[CallHierarchyItem, loc] picoCallsService(CallHierarchyItem ci, incoming()) - = [ | /caller:(IdType) `(<{IdType ","}* _>): := ` := ci.\data.prog, /c:(Expression) `(<{Expression ","}* _>)` := caller, signatureMatches(ci.\data.def, c)]; -lrel[CallHierarchyItem, loc] picoCallsService(CallHierarchyItem ci, outgoing()) - = [ | /c:(Expression) `(<{Expression ","}* _>)` := ci.\data.def]; +lrel[CallHierarchyItem, loc] picoCallsService(CallHierarchyItem ci, incoming()) { + s = picoSummaryService(ci.\data.prog.src.top, ci.\data.prog, analyze()); + calls = []; + for ( <- s.definitionsByKind[function()]) { + caller = callHierarchyItem(s, ci.\data.prog, id, d, t); + for (use <- s.references[ci.src], isContainedIn(use, caller.src)) { + calls += ; + } + }; + + return calls; +} + +lrel[CallHierarchyItem, loc] picoCallsService(CallHierarchyItem ci, outgoing()) { + s = picoSummaryService(ci.\data.prog.src.top, ci.\data.prog, analyze()); + calls = []; + for ( <- s.definitionsByKind[function()]) { + callee = callHierarchyItem(s, ci.\data.prog, id, d, t); + for (use <- s.references[callee.src], isContainedIn(use, ci.src)) { + calls += ; + } + }; + + return calls; +} @synopsis{The main function registers the Pico language with the IDE} @description{ From c32c1dc931f1d5f0db064c1e8a3dfc3639e96de3 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 12 Nov 2025 17:19:51 +0100 Subject: [PATCH 45/53] Only set data when it was present. --- .../java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 4a9dd19d6..15b6c925b 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 @@ -135,7 +135,9 @@ public CompletableFuture toRascal(CallHierarchyItem ci, Function Date: Wed, 12 Nov 2025 17:36:29 +0100 Subject: [PATCH 46/53] Only call data parser when data is present. --- .../ParametricTextDocumentService.java | 4 ++-- .../vscode/lsp/util/CallHierarchy.java | 22 +++++++++++-------- 2 files changed, 15 insertions(+), 11 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 f2f79019d..dd0500d4b 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 @@ -870,7 +870,7 @@ public CompletableFuture> prepareCallHierarchy(CallHiera return contrib.prepareCallHierarchy(TreeSearch.computeFocusList(t, pos.getLine(), pos.getCharacter())) .get() .thenApply(items -> { - var ch = new CallHierarchy(); + var ch = new CallHierarchy(ownExecuter); return items.stream() .map(IConstructor.class::cast) .map(ci -> ch.toLSP(ci, columns)) @@ -881,7 +881,7 @@ public CompletableFuture> prepareCallHierarchy(CallHiera private CompletableFuture> incomingOutgoingCalls(BiFunction, T> constructor, CallHierarchyItem source, CallHierarchy.Direction direction) { final var contrib = contributions(Locations.toLoc(source.getUri())); - var ch = new CallHierarchy(); + var ch = new CallHierarchy(ownExecuter); return ch.toRascal(source, contrib::parseCallHierarchyData, columns) .thenCompose(sourceItem -> contrib.incomingOutgoingCalls(sourceItem, ch.direction(direction)).get()) .thenApply(callRel -> callRel.stream() 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 15b6c925b..bd7f5bfeb 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,6 +31,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import java.util.function.Function; import org.eclipse.lsp4j.CallHierarchyItem; import org.rascalmpl.util.locations.ColumnMaps; @@ -67,7 +68,11 @@ public enum Direction { private final IConstructor outgoing; private final Type callHierarchyItemCons; - public CallHierarchy() { + private final Executor exec; + + public CallHierarchy(Executor exec) { + this.exec = exec; + var store = new TypeStore(); Type directionAdt = TF.abstractDataType(store, "CallDirection"); this.incoming = VF.constructor(TF.constructor(store, directionAdt, "incoming")); @@ -121,11 +126,7 @@ private String serializeData(IConstructor data) { } public CompletableFuture toRascal(CallHierarchyItem ci, Function> dataParser, ColumnMaps columns) { - var serializedData = ci.getData() != null - ? ((JsonPrimitive) ci.getData()).getAsString() - : ""; - - return dataParser.apply(serializedData).thenApply(data -> { + return CompletableFuture.supplyAsync(() -> { Map kwArgs = new HashMap<>(); var tags = ci.getTags(); if (tags != null) { @@ -135,15 +136,18 @@ public CompletableFuture toRascal(CallHierarchyItem ci, Function { + if (ci.getData() == null) { + return CompletableFuture.completedFuture(c); + } + return dataParser.apply(((JsonPrimitive) ci.getData()).getAsString()) + .thenApply(data -> c.asWithKeywordParameters().setParameter(CallHierarchyFields.DATA, data)); }); } } From fd8e41844ca8b11db7150c76930c63fa1f18bf0f Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Wed, 12 Nov 2025 17:57:58 +0100 Subject: [PATCH 47/53] Fix call hierarchy from call site & warnings. --- .../library/demo/lang/pico/LanguageServer.rsc | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 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 0828ca892..6b48f4591 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 @@ -255,17 +255,20 @@ tuple[list[DocumentEdit],set[Message]] picoFileRenameService(list[DocumentEdit] list[loc] picoSelectionRangeService(Focus focus) = dup([t@\loc | t <- focus]); -list[CallHierarchyItem] picoPrepareCallHierarchy(Focus focus: [*_, e:(Expression) `(<{Expression ","}* args>)`, *_, start[Program] prog]) - = [ callHierarchyItem(s, prog, id, d, tp) - | s := picoSummaryService(prog.src.top, prog, analyze()) - , d <- s.definitions[e.src] +list[CallHierarchyItem] picoPrepareCallHierarchy(Focus focus: [*_, e:(Expression) `(<{Expression ","}* _>)`, *_, start[Program] prog]) { + s = picoSummaryService(prog.src.top, prog, analyze()); + return [ callHierarchyItem(prog, id, d, tp) + | d <- s.definitions[callId.src] , <- s.definitionsByKind[function(), d] ]; +} -list[CallHierarchyItem] picoPrepareCallHierarchy(Focus _: [*_, d:(IdType) `(<{IdType ","}* args>): := `, *_, start[Program] prog]) +list[CallHierarchyItem] picoPrepareCallHierarchy(Focus _: [*_, d:(IdType) `(<{IdType ","}* _>): := `, *_, start[Program] prog]) = [callHierarchyItem(prog, d)]; -CallHierarchyItem callHierarchyItem(Summary s, start[Program] prog, Id id, loc decl, str tp) +default list[CallHierarchyItem] picoPrepareCallHierarchy(Focus _) = []; + +CallHierarchyItem callHierarchyItem(start[Program] prog, Id id, loc decl, str tp) = callHierarchyItem( "", function(), @@ -295,7 +298,7 @@ lrel[CallHierarchyItem, loc] picoCallsService(CallHierarchyItem ci, incoming()) s = picoSummaryService(ci.\data.prog.src.top, ci.\data.prog, analyze()); calls = []; for ( <- s.definitionsByKind[function()]) { - caller = callHierarchyItem(s, ci.\data.prog, id, d, t); + caller = callHierarchyItem(ci.\data.prog, id, d, t); for (use <- s.references[ci.src], isContainedIn(use, caller.src)) { calls += ; } @@ -308,7 +311,7 @@ lrel[CallHierarchyItem, loc] picoCallsService(CallHierarchyItem ci, outgoing()) s = picoSummaryService(ci.\data.prog.src.top, ci.\data.prog, analyze()); calls = []; for ( <- s.definitionsByKind[function()]) { - callee = callHierarchyItem(s, ci.\data.prog, id, d, t); + callee = callHierarchyItem(ci.\data.prog, id, d, t); for (use <- s.references[callee.src], isContainedIn(use, ci.src)) { calls += ; } From cf398cbf8068feec7889c0b4dc710d514ee45b8e Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 13 Nov 2025 14:32:30 +0100 Subject: [PATCH 48/53] Improve data parse future flow. --- .../rascalmpl/vscode/lsp/util/CallHierarchy.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 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 bd7f5bfeb..b604f5313 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,6 +33,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.Function; +import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.CallHierarchyItem; import org.rascalmpl.util.locations.ColumnMaps; import org.rascalmpl.values.IRascalValueFactory; @@ -126,7 +127,11 @@ private String serializeData(IConstructor data) { } public CompletableFuture toRascal(CallHierarchyItem ci, Function> dataParser, ColumnMaps columns) { - return CompletableFuture.supplyAsync(() -> { + CompletableFuture<@Nullable IConstructor> parseData = ci.getData() != null + ? dataParser.apply(((JsonPrimitive) ci.getData()).getAsString()) + : CompletableFuture.completedFuture(null); + + return parseData.thenApply(data -> { Map kwArgs = new HashMap<>(); var tags = ci.getTags(); if (tags != null) { @@ -136,18 +141,15 @@ public CompletableFuture toRascal(CallHierarchyItem ci, Function { - if (ci.getData() == null) { - return CompletableFuture.completedFuture(c); - } - return dataParser.apply(((JsonPrimitive) ci.getData()).getAsString()) - .thenApply(data -> c.asWithKeywordParameters().setParameter(CallHierarchyFields.DATA, data)); }); } } From 9b30765c7d1d64ffbcfb3753ee6e84ad9bbc08c8 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 13 Nov 2025 14:59:02 +0100 Subject: [PATCH 49/53] Fix review comments by @DavyLandman. --- .../vscode/lsp/util/CallHierarchy.java | 4 +- .../library/demo/lang/pico/LanguageServer.rsc | 42 ++++--------------- 2 files changed, 10 insertions(+), 36 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 b604f5313..ca473efb9 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 @@ -144,12 +144,12 @@ public CompletableFuture toRascal(CallHierarchyItem ci, Function", - function(), - decl, - id.src, - detail = tp, - \data = \data(prog) - ); + = callHierarchyItem("", function(), decl, id.src, detail = tp, \data = \data(prog)); CallHierarchyItem callHierarchyItem(start[Program] prog, d:(IdType) `(<{IdType ","}* _>): := `) - = callHierarchyItem( - "", - function(), - d.src, - id.src, - detail = typeOf(d), - \data = \data(prog) - ); + = callHierarchyItem("", function(), d.src, id.src, detail = typeOf(d), \data = \data(prog)); data CallHierarchyData = \data(start[Program] prog); @@ -294,26 +279,15 @@ str typeOf((IdType) `: `) = ""; str typeOf((IdType) `(<{IdType ","}* args>): := `) = "(): "; -lrel[CallHierarchyItem, loc] picoCallsService(CallHierarchyItem ci, incoming()) { - s = picoSummaryService(ci.\data.prog.src.top, ci.\data.prog, analyze()); - calls = []; - for ( <- s.definitionsByKind[function()]) { - caller = callHierarchyItem(ci.\data.prog, id, d, t); - for (use <- s.references[ci.src], isContainedIn(use, caller.src)) { - calls += ; - } - }; - - return calls; -} - -lrel[CallHierarchyItem, loc] picoCallsService(CallHierarchyItem ci, outgoing()) { +lrel[CallHierarchyItem, loc] picoCallsService(CallHierarchyItem ci, CallDirection dir) { s = picoSummaryService(ci.\data.prog.src.top, ci.\data.prog, analyze()); calls = []; for ( <- s.definitionsByKind[function()]) { - callee = callHierarchyItem(ci.\data.prog, id, d, t); - for (use <- s.references[callee.src], isContainedIn(use, ci.src)) { - calls += ; + newItem = callHierarchyItem(ci.\data.prog, id, d, t); + callee = dir is outgoing ? ci : newItem; + caller = dir is incoming ? newItem : ci; + for (use <- s.references[callee.src], isContainedIn(use, caller.src)) { + calls += ; } }; From e82790d8d5a33276a0ab6c28c5f1567e4a41905a Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 13 Nov 2025 15:35:33 +0100 Subject: [PATCH 50/53] Clean up unused. --- .../lsp/parametric/ParametricTextDocumentService.java | 4 ++-- .../java/org/rascalmpl/vscode/lsp/util/CallHierarchy.java | 8 +------- 2 files changed, 3 insertions(+), 9 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 dd0500d4b..f2f79019d 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 @@ -870,7 +870,7 @@ public CompletableFuture> prepareCallHierarchy(CallHiera return contrib.prepareCallHierarchy(TreeSearch.computeFocusList(t, pos.getLine(), pos.getCharacter())) .get() .thenApply(items -> { - var ch = new CallHierarchy(ownExecuter); + var ch = new CallHierarchy(); return items.stream() .map(IConstructor.class::cast) .map(ci -> ch.toLSP(ci, columns)) @@ -881,7 +881,7 @@ public CompletableFuture> prepareCallHierarchy(CallHiera private CompletableFuture> incomingOutgoingCalls(BiFunction, T> constructor, CallHierarchyItem source, CallHierarchy.Direction direction) { final var contrib = contributions(Locations.toLoc(source.getUri())); - var ch = new CallHierarchy(ownExecuter); + var ch = new CallHierarchy(); return ch.toRascal(source, contrib::parseCallHierarchyData, columns) .thenCompose(sourceItem -> contrib.incomingOutgoingCalls(sourceItem, ch.direction(direction)).get()) .thenApply(callRel -> callRel.stream() 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 ca473efb9..5e175fc88 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 @@ -28,10 +28,8 @@ import com.google.gson.JsonPrimitive; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; import java.util.function.Function; import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.lsp4j.CallHierarchyItem; @@ -69,11 +67,7 @@ public enum Direction { private final IConstructor outgoing; private final Type callHierarchyItemCons; - private final Executor exec; - - public CallHierarchy(Executor exec) { - this.exec = exec; - + public CallHierarchy() { var store = new TypeStore(); Type directionAdt = TF.abstractDataType(store, "CallDirection"); this.incoming = VF.constructor(TF.constructor(store, directionAdt, "incoming")); From ea6a89db406d45728c4c6e33788daccfa36af9a8 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 13 Nov 2025 15:41:14 +0100 Subject: [PATCH 51/53] Fix caller/callee computation. --- .../main/rascal/library/demo/lang/pico/LanguageServer.rsc | 6 ++++-- 1 file changed, 4 insertions(+), 2 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 60a938bd5..cb615e6de 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 @@ -284,8 +284,10 @@ lrel[CallHierarchyItem, loc] picoCallsService(CallHierarchyItem ci, CallDirectio calls = []; for ( <- s.definitionsByKind[function()]) { newItem = callHierarchyItem(ci.\data.prog, id, d, t); - callee = dir is outgoing ? ci : newItem; - caller = dir is incoming ? newItem : ci; + = dir is incoming + ? + : + ; for (use <- s.references[callee.src], isContainedIn(use, caller.src)) { calls += ; } From 87b207a3a7b052cf5d5539174ad4d6e4ab539db3 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 13 Nov 2025 17:23:11 +0100 Subject: [PATCH 52/53] Add call hierarchy UI test. --- .../src/test/vscode-suite/dsl.test.ts | 29 ++++++++++++++++--- .../src/test/vscode-suite/utils.ts | 1 + .../test-project/src/main/pico/calls.pico | 5 ++++ 3 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 rascal-vscode-extension/test-workspace/test-project/src/main/pico/calls.pico diff --git a/rascal-vscode-extension/src/test/vscode-suite/dsl.test.ts b/rascal-vscode-extension/src/test/vscode-suite/dsl.test.ts index 75f50499f..22aa7aae8 100644 --- a/rascal-vscode-extension/src/test/vscode-suite/dsl.test.ts +++ b/rascal-vscode-extension/src/test/vscode-suite/dsl.test.ts @@ -25,13 +25,13 @@ * POSSIBILITY OF SUCH DAMAGE. */ -import { VSBrowser, WebDriver, Workbench } from 'vscode-extension-tester'; -import { Delays, IDEOperations, RascalREPL, TestWorkspace, ignoreFails, printRascalOutputOnFailure, sleep } from './utils'; +import { By, until, VSBrowser, WebDriver, Workbench } from 'vscode-extension-tester'; +import { Delays, IDEOperations, ignoreFails, printRascalOutputOnFailure, RascalREPL, sleep, TestWorkspace } from './utils'; +import { expect } from 'chai'; import * as fs from 'fs/promises'; -import * as path from 'path'; import { Suite } from 'mocha'; -import { expect } from 'chai'; +import * as path from 'path'; function parameterizedDescribe(body: (this: Suite, errorRecovery: boolean) => void) { describe('DSL', function() { body.apply(this, [false]); }); @@ -258,4 +258,25 @@ parameterizedDescribe(function (errorRecovery: boolean) { await fs.rm(newDir, {recursive: true, force: true}); }); + + it("call hierarchy works", async function() { + const editor = await ide.openModule(TestWorkspace.picoCallsFile); + await editor.selectText("multiply"); + await bench.executeCommand("view.showCallHierarchy"); + await driver.wait(until.elementLocated(By.xpath("//div[contains(@class, 'title-label')]/h2[contains(text(), 'References')]"))); + + await bench.executeCommand("view.showOutgoingCalls"); + await driver.wait(until.elementLocated(By.xpath("//div[contains(@class, 'title-label')]/h2[contains(text(), 'Calls From')]"))); + await driver.wait(async () => { + const hieraryItems = await bench.getSideBar().findElements(By.xpath("//div[@role='treeitem']")); + return hieraryItems.length === 3; + }, Delays.normal, "Call hierarchy should show `multiply` and its two outgoing calls."); + + await bench.executeCommand("view.showIncomingCalls"); + await driver.wait(until.elementLocated(By.xpath("//div[contains(@class, 'title-label')]/h2[contains(text(), 'Callers Of')]"))); + await driver.wait(async () => { + const hieraryItems = await bench.getSideBar().findElements(By.xpath("//div[@role='treeitem']")); + return hieraryItems.length === 2; + }, Delays.normal, "Call hierarchy should show `multiply` and its recursive call."); + }); }); diff --git a/rascal-vscode-extension/src/test/vscode-suite/utils.ts b/rascal-vscode-extension/src/test/vscode-suite/utils.ts index 39117c249..10e788324 100644 --- a/rascal-vscode-extension/src/test/vscode-suite/utils.ts +++ b/rascal-vscode-extension/src/test/vscode-suite/utils.ts @@ -68,6 +68,7 @@ export class TestWorkspace { public static readonly picoFile = path.join(src(this.testProject, 'pico'), 'testing.pico'); public static readonly picoNewFile = path.join(src(this.testProject, 'pico'), 'testing.pico-new'); + public static readonly picoCallsFile = path.join(src(this.testProject, 'pico'), 'calls.pico'); } diff --git a/rascal-vscode-extension/test-workspace/test-project/src/main/pico/calls.pico b/rascal-vscode-extension/test-workspace/test-project/src/main/pico/calls.pico new file mode 100644 index 000000000..103d49644 --- /dev/null +++ b/rascal-vscode-extension/test-workspace/test-project/src/main/pico/calls.pico @@ -0,0 +1,5 @@ +begin + declare + add(x: natural, y: natural): natural := x + y, + multiply(x: natural, y: natural): natural := add(x, multiply(x - 1, y)); +end From d494f28d219aface3399a526cc4c5516295da1a2 Mon Sep 17 00:00:00 2001 From: Toine Hartman Date: Thu, 13 Nov 2025 18:36:24 +0100 Subject: [PATCH 53/53] Wait for call hierarchy to load before querying calls. --- rascal-vscode-extension/src/test/vscode-suite/dsl.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rascal-vscode-extension/src/test/vscode-suite/dsl.test.ts b/rascal-vscode-extension/src/test/vscode-suite/dsl.test.ts index 22aa7aae8..af96de235 100644 --- a/rascal-vscode-extension/src/test/vscode-suite/dsl.test.ts +++ b/rascal-vscode-extension/src/test/vscode-suite/dsl.test.ts @@ -264,8 +264,10 @@ parameterizedDescribe(function (errorRecovery: boolean) { await editor.selectText("multiply"); await bench.executeCommand("view.showCallHierarchy"); await driver.wait(until.elementLocated(By.xpath("//div[contains(@class, 'title-label')]/h2[contains(text(), 'References')]"))); + await ide.screenshot("Show call hierarchy"); - await bench.executeCommand("view.showOutgoingCalls"); + // Wait until the call hierarchy finished loading, and request outgoing calls + await driver.wait(ignoreFails(bench.executeCommand("view.showOutgoingCalls"))); await driver.wait(until.elementLocated(By.xpath("//div[contains(@class, 'title-label')]/h2[contains(text(), 'Calls From')]"))); await driver.wait(async () => { const hieraryItems = await bench.getSideBar().findElements(By.xpath("//div[@role='treeitem']"));