diff --git a/crates/els/README.md b/crates/els/README.md index 177dc72b..2811a401 100644 --- a/crates/els/README.md +++ b/crates/els/README.md @@ -13,7 +13,7 @@ ELS is a language server for the [Erg](https://github.com/erg-lang/erg) programm - [x] Diagnostics - [x] Hover - [x] Go to definition -- [ ] Go to implementation +- [x] Go to implementation - [x] Find references - [x] Renaming - [x] Inlay hint diff --git a/crates/els/channels.rs b/crates/els/channels.rs index afdfe1b5..d30d7df2 100644 --- a/crates/els/channels.rs +++ b/crates/els/channels.rs @@ -6,9 +6,10 @@ use erg_compiler::erg_parser::parse::Parsable; use lsp_types::request::{ CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare, CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion, - DocumentSymbolRequest, ExecuteCommand, FoldingRangeRequest, GotoDefinition, HoverRequest, - InlayHintRequest, InlayHintResolveRequest, References, ResolveCompletionItem, - SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles, WorkspaceSymbol, + DocumentSymbolRequest, ExecuteCommand, FoldingRangeRequest, GotoDefinition, GotoImplementation, + GotoImplementationParams, HoverRequest, InlayHintRequest, InlayHintResolveRequest, References, + ResolveCompletionItem, SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles, + WorkspaceSymbol, }; use lsp_types::{ CallHierarchyIncomingCallsParams, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, @@ -37,6 +38,7 @@ pub struct SendChannels { completion: mpsc::Sender>, resolve_completion: mpsc::Sender>, goto_definition: mpsc::Sender>, + goto_implementation: mpsc::Sender>, semantic_tokens_full: mpsc::Sender>, inlay_hint: mpsc::Sender>, inlay_hint_resolve: mpsc::Sender>, @@ -62,6 +64,7 @@ impl SendChannels { let (tx_completion, rx_completion) = mpsc::channel(); let (tx_resolve_completion, rx_resolve_completion) = mpsc::channel(); let (tx_goto_definition, rx_goto_definition) = mpsc::channel(); + let (tx_goto_implementation, rx_goto_implementation) = mpsc::channel(); let (tx_semantic_tokens_full, rx_semantic_tokens_full) = mpsc::channel(); let (tx_inlay_hint, rx_inlay_hint) = mpsc::channel(); let (tx_inlay_hint_resolve, rx_inlay_hint_resolve) = mpsc::channel(); @@ -85,6 +88,7 @@ impl SendChannels { completion: tx_completion, resolve_completion: tx_resolve_completion, goto_definition: tx_goto_definition, + goto_implementation: tx_goto_implementation, semantic_tokens_full: tx_semantic_tokens_full, inlay_hint: tx_inlay_hint, inlay_hint_resolve: tx_inlay_hint_resolve, @@ -108,6 +112,7 @@ impl SendChannels { completion: rx_completion, resolve_completion: rx_resolve_completion, goto_definition: rx_goto_definition, + goto_implementation: rx_goto_implementation, semantic_tokens_full: rx_semantic_tokens_full, inlay_hint: rx_inlay_hint, inlay_hint_resolve: rx_inlay_hint_resolve, @@ -166,6 +171,7 @@ pub struct ReceiveChannels { pub(crate) completion: mpsc::Receiver>, pub(crate) resolve_completion: mpsc::Receiver>, pub(crate) goto_definition: mpsc::Receiver>, + pub(crate) goto_implementation: mpsc::Receiver>, pub(crate) semantic_tokens_full: mpsc::Receiver>, pub(crate) inlay_hint: mpsc::Receiver>, pub(crate) inlay_hint_resolve: mpsc::Receiver>, @@ -212,6 +218,11 @@ macro_rules! impl_sendable { impl_sendable!(Completion, CompletionParams, completion); impl_sendable!(ResolveCompletionItem, CompletionItem, resolve_completion); impl_sendable!(GotoDefinition, GotoDefinitionParams, goto_definition); +impl_sendable!( + GotoImplementation, + GotoImplementationParams, + goto_implementation +); impl_sendable!( SemanticTokensFullRequest, SemanticTokensParams, diff --git a/crates/els/definition.rs b/crates/els/definition.rs index e280198e..74d03138 100644 --- a/crates/els/definition.rs +++ b/crates/els/definition.rs @@ -4,11 +4,11 @@ use erg_compiler::artifact::BuildRunnable; use erg_compiler::context::register::PylyzerStatus; use erg_compiler::erg_parser::parse::Parsable; use erg_compiler::erg_parser::token::{Token, TokenCategory}; -use erg_compiler::hir::Expr; +use erg_compiler::hir::{Def, Expr}; use erg_compiler::ty::HasType; use erg_compiler::varinfo::VarInfo; -use lsp_types::{GotoDefinitionParams, GotoDefinitionResponse, Url}; +use lsp_types::{GotoDefinitionParams, GotoDefinitionResponse, Location, Position, Url}; use crate::server::{send_log, ELSResult, Server}; use crate::util::{self, NormalizedUrl}; @@ -30,58 +30,51 @@ impl Server { } } - fn get_definition_response( + pub(crate) fn get_definition_location( &self, - params: GotoDefinitionParams, - ) -> ELSResult { - let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri); - let pos = params.text_document_position_params.position; - if let Some(token) = self.file_cache.get_symbol(&uri, pos) { - if let Some(vi) = self.get_definition(&uri, &token)? { + uri: &NormalizedUrl, + pos: Position, + ) -> ELSResult> { + if let Some(token) = self.file_cache.get_symbol(uri, pos) { + if let Some(vi) = self.get_definition(uri, &token)? { // If the target variable is an imported one, jump to the definition file. // Else if the target variable is an alias, jump to the definition of it. // `foo = import "foo"` => jump to `foo.er` // `{x;} = import "foo"` => jump to `x` of `foo.er` - if vi.def_loc.module == Some(util::uri_to_path(&uri)) + if vi.def_loc.module == Some(util::uri_to_path(uri)) && vi.def_loc.loc == token.loc() { - if let Some((_, Expr::Def(def))) = self.get_min_expr(&uri, pos, 0) { + if let Some(def) = self.get_min::(uri, pos) { if def.def_kind().is_import() { if vi.t.is_module() { if let Some(path) = self - .get_local_ctx(&uri, pos) + .get_local_ctx(uri, pos) .first() .and_then(|ctx| ctx.get_path_with_mod_t(&vi.t)) { let mod_uri = Url::from_file_path(path).unwrap(); - let resp = GotoDefinitionResponse::Array(vec![ - lsp_types::Location::new( - mod_uri, - lsp_types::Range::default(), - ), - ]); - return Ok(resp); + return Ok(Some(lsp_types::Location::new( + mod_uri, + lsp_types::Range::default(), + ))); } } else { // line of module member definitions may no longer match after the desugaring process let mod_t = def.body.ref_t(); if let Some((_, vi)) = self - .get_local_ctx(&uri, pos) + .get_local_ctx(uri, pos) .first() .and_then(|ctx| ctx.get_mod_with_t(mod_t)) .and_then(|mod_ctx| mod_ctx.get_var_info(token.inspect())) { let Some(path) = vi.def_loc.module.as_ref() else { - return Ok(GotoDefinitionResponse::Array(vec![])); + return Ok(None); }; let def_uri = Url::from_file_path(path).unwrap(); - let resp = GotoDefinitionResponse::Array(vec![ - lsp_types::Location::new( - def_uri, - util::loc_to_range(vi.def_loc.loc).unwrap(), - ), - ]); - return Ok(resp); + return Ok(Some(lsp_types::Location::new( + def_uri, + util::loc_to_range(vi.def_loc.loc).unwrap(), + ))); } } } else if let Expr::Accessor(acc) = def.body.block.last().unwrap() { @@ -103,14 +96,11 @@ impl Server { path.clone() }; let def_uri = Url::from_file_path(def_file).unwrap(); - let resp = GotoDefinitionResponse::Array(vec![ - lsp_types::Location::new(def_uri, range), - ]); - return Ok(resp); + return Ok(Some(lsp_types::Location::new(def_uri, range))); } _ => { send_log("not found (maybe builtin)")?; - return Ok(GotoDefinitionResponse::Array(vec![])); + return Ok(None); } } } @@ -119,21 +109,19 @@ impl Server { match (vi.def_loc.module, util::loc_to_range(vi.def_loc.loc)) { (Some(path), Some(range)) => { let def_uri = Url::from_file_path(path).unwrap(); - Ok(GotoDefinitionResponse::Array(vec![ - lsp_types::Location::new(def_uri, range), - ])) + Ok(Some(lsp_types::Location::new(def_uri, range))) } _ => { send_log("not found (maybe builtin)")?; - Ok(GotoDefinitionResponse::Array(vec![])) + Ok(None) } } } else { - Ok(GotoDefinitionResponse::Array(vec![])) + Ok(None) } } else { send_log("lex error occurred")?; - Ok(GotoDefinitionResponse::Array(vec![])) + Ok(None) } } @@ -142,7 +130,9 @@ impl Server { params: GotoDefinitionParams, ) -> ELSResult> { send_log(format!("definition requested: {params:?}"))?; - let result = self.get_definition_response(params)?; - Ok(Some(result)) + let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri); + let pos = params.text_document_position_params.position; + let result = self.get_definition_location(&uri, pos)?; + Ok(result.map(GotoDefinitionResponse::Scalar)) } } diff --git a/crates/els/implementation.rs b/crates/els/implementation.rs new file mode 100644 index 00000000..238f23b9 --- /dev/null +++ b/crates/els/implementation.rs @@ -0,0 +1,39 @@ +use erg_compiler::artifact::BuildRunnable; +use erg_compiler::erg_parser::parse::Parsable; +use lsp_types::request::{GotoImplementationParams, GotoImplementationResponse}; + +use crate::_log; +use crate::server::{ELSResult, Server}; +use crate::util::{loc_to_pos, NormalizedUrl}; + +impl Server { + pub(crate) fn handle_goto_implementation( + &mut self, + params: GotoImplementationParams, + ) -> ELSResult> { + _log!("implementation requested: {params:?}"); + let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri); + let pos = params.text_document_position_params.position; + let Some(symbol) = self.file_cache.get_symbol(&uri, pos) else { + return Ok(None); + }; + // {x #[ ← this is not the impl ]#;} = import "foo" + // print! x # ← symbol + if let Some(vi) = self.get_definition(&uri, &symbol)? { + let Some(uri) = vi + .def_loc + .module + .and_then(|p| NormalizedUrl::from_file_path(p).ok()) + else { + return Ok(None); + }; + let Some(pos) = loc_to_pos(vi.def_loc.loc) else { + return Ok(None); + }; + if let Some(location) = self.get_definition_location(&uri, pos)? { + return Ok(Some(GotoImplementationResponse::Scalar(location))); + } + } + Ok(None) + } +} diff --git a/crates/els/lib.rs b/crates/els/lib.rs index 166a1b3a..cbf36ea6 100644 --- a/crates/els/lib.rs +++ b/crates/els/lib.rs @@ -11,6 +11,7 @@ mod file_cache; mod folding_range; mod hir_visitor; mod hover; +mod implementation; mod inlay_hint; mod message; mod references; diff --git a/crates/els/main.rs b/crates/els/main.rs index 5430cc72..e498dd32 100644 --- a/crates/els/main.rs +++ b/crates/els/main.rs @@ -11,6 +11,7 @@ mod file_cache; mod folding_range; mod hir_visitor; mod hover; +mod implementation; mod inlay_hint; mod message; mod references; diff --git a/crates/els/server.rs b/crates/els/server.rs index 9bfc518d..7f9ff968 100644 --- a/crates/els/server.rs +++ b/crates/els/server.rs @@ -29,19 +29,20 @@ use erg_compiler::ty::HasType; use lsp_types::request::{ CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare, CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion, - DocumentSymbolRequest, ExecuteCommand, FoldingRangeRequest, GotoDefinition, HoverRequest, - InlayHintRequest, InlayHintResolveRequest, References, Rename, Request, ResolveCompletionItem, - SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles, WorkspaceSymbol, + DocumentSymbolRequest, ExecuteCommand, FoldingRangeRequest, GotoDefinition, GotoImplementation, + HoverRequest, InlayHintRequest, InlayHintResolveRequest, References, Rename, Request, + ResolveCompletionItem, SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles, + WorkspaceSymbol, }; use lsp_types::{ CallHierarchyServerCapability, CodeActionKind, CodeActionOptions, CodeActionProviderCapability, CodeLensOptions, CompletionOptions, ConfigurationItem, ConfigurationParams, DidChangeTextDocumentParams, DidOpenTextDocumentParams, ExecuteCommandOptions, - FoldingRangeProviderCapability, HoverProviderCapability, InitializeParams, InitializeResult, - InlayHintOptions, InlayHintServerCapabilities, OneOf, Position, SemanticTokenType, - SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions, - SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelpOptions, - WorkDoneProgressOptions, + FoldingRangeProviderCapability, HoverProviderCapability, ImplementationProviderCapability, + InitializeParams, InitializeResult, InlayHintOptions, InlayHintServerCapabilities, OneOf, + Position, SemanticTokenType, SemanticTokensFullOptions, SemanticTokensLegend, + SemanticTokensOptions, SemanticTokensServerCapabilities, ServerCapabilities, + SignatureHelpOptions, WorkDoneProgressOptions, }; use serde::{Deserialize, Serialize}; @@ -430,6 +431,7 @@ impl Server { capabilities.rename_provider = Some(OneOf::Left(true)); capabilities.references_provider = Some(OneOf::Left(true)); capabilities.definition_provider = Some(OneOf::Left(true)); + capabilities.implementation_provider = Some(ImplementationProviderCapability::Simple(true)); capabilities.hover_provider = self .disabled_features .contains(&DefaultFeatures::Hover) @@ -538,6 +540,10 @@ impl Server { receivers.goto_definition, Self::handle_goto_definition, ); + self.start_service::( + receivers.goto_implementation, + Self::handle_goto_implementation, + ); self.start_service::( receivers.semantic_tokens_full, Self::handle_semantic_tokens_full, @@ -755,6 +761,7 @@ impl Server { Completion::METHOD => self.parse_send::(id, msg), ResolveCompletionItem::METHOD => self.parse_send::(id, msg), GotoDefinition::METHOD => self.parse_send::(id, msg), + GotoImplementation::METHOD => self.parse_send::(id, msg), HoverRequest::METHOD => self.parse_send::(id, msg), References::METHOD => self.parse_send::(id, msg), SemanticTokensFullRequest::METHOD => { diff --git a/crates/els/tests/test.rs b/crates/els/tests/test.rs index d5d7aa79..1ca23680 100644 --- a/crates/els/tests/test.rs +++ b/crates/els/tests/test.rs @@ -367,7 +367,7 @@ impl DummyClient { uri: Url, line: u32, col: u32, - ) -> Result, Box> { + ) -> Result> { let params = GotoDefinitionParams { text_document_position_params: abs_pos(uri, line, col), work_done_progress_params: Default::default(), @@ -380,7 +380,7 @@ impl DummyClient { "params": params, }); self.server.dispatch(msg)?; - self.wait_for::>() + self.wait_for::() } fn request_folding_range( @@ -540,9 +540,8 @@ fn test_goto_definition() -> Result<(), Box> { client.request_initialize()?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; client.notify_open(FILE_A)?; - let locations = client.request_goto_definition(uri.raw(), 1, 4)?; - assert_eq!(locations.len(), 1); - assert_eq!(&locations[0].range, &single_range(0, 0, 1)); + let location = client.request_goto_definition(uri.raw(), 1, 4)?; + assert_eq!(&location.range, &single_range(0, 0, 1)); Ok(()) }