feat(els): add goto implementation

This commit is contained in:
Shunsuke Shibayama 2023-09-03 14:36:56 +09:00
parent d8835fd169
commit 3f678ef5e3
8 changed files with 106 additions and 58 deletions

View file

@ -13,7 +13,7 @@ ELS is a language server for the [Erg](https://github.com/erg-lang/erg) programm
- [x] Diagnostics - [x] Diagnostics
- [x] Hover - [x] Hover
- [x] Go to definition - [x] Go to definition
- [ ] Go to implementation - [x] Go to implementation
- [x] Find references - [x] Find references
- [x] Renaming - [x] Renaming
- [x] Inlay hint - [x] Inlay hint

View file

@ -6,9 +6,10 @@ use erg_compiler::erg_parser::parse::Parsable;
use lsp_types::request::{ use lsp_types::request::{
CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare, CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare,
CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion, CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion,
DocumentSymbolRequest, ExecuteCommand, FoldingRangeRequest, GotoDefinition, HoverRequest, DocumentSymbolRequest, ExecuteCommand, FoldingRangeRequest, GotoDefinition, GotoImplementation,
InlayHintRequest, InlayHintResolveRequest, References, ResolveCompletionItem, GotoImplementationParams, HoverRequest, InlayHintRequest, InlayHintResolveRequest, References,
SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles, WorkspaceSymbol, ResolveCompletionItem, SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles,
WorkspaceSymbol,
}; };
use lsp_types::{ use lsp_types::{
CallHierarchyIncomingCallsParams, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, CallHierarchyIncomingCallsParams, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
@ -37,6 +38,7 @@ pub struct SendChannels {
completion: mpsc::Sender<WorkerMessage<CompletionParams>>, completion: mpsc::Sender<WorkerMessage<CompletionParams>>,
resolve_completion: mpsc::Sender<WorkerMessage<CompletionItem>>, resolve_completion: mpsc::Sender<WorkerMessage<CompletionItem>>,
goto_definition: mpsc::Sender<WorkerMessage<GotoDefinitionParams>>, goto_definition: mpsc::Sender<WorkerMessage<GotoDefinitionParams>>,
goto_implementation: mpsc::Sender<WorkerMessage<GotoImplementationParams>>,
semantic_tokens_full: mpsc::Sender<WorkerMessage<SemanticTokensParams>>, semantic_tokens_full: mpsc::Sender<WorkerMessage<SemanticTokensParams>>,
inlay_hint: mpsc::Sender<WorkerMessage<InlayHintParams>>, inlay_hint: mpsc::Sender<WorkerMessage<InlayHintParams>>,
inlay_hint_resolve: mpsc::Sender<WorkerMessage<InlayHint>>, inlay_hint_resolve: mpsc::Sender<WorkerMessage<InlayHint>>,
@ -62,6 +64,7 @@ impl SendChannels {
let (tx_completion, rx_completion) = mpsc::channel(); let (tx_completion, rx_completion) = mpsc::channel();
let (tx_resolve_completion, rx_resolve_completion) = mpsc::channel(); let (tx_resolve_completion, rx_resolve_completion) = mpsc::channel();
let (tx_goto_definition, rx_goto_definition) = 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_semantic_tokens_full, rx_semantic_tokens_full) = mpsc::channel();
let (tx_inlay_hint, rx_inlay_hint) = mpsc::channel(); let (tx_inlay_hint, rx_inlay_hint) = mpsc::channel();
let (tx_inlay_hint_resolve, rx_inlay_hint_resolve) = mpsc::channel(); let (tx_inlay_hint_resolve, rx_inlay_hint_resolve) = mpsc::channel();
@ -85,6 +88,7 @@ impl SendChannels {
completion: tx_completion, completion: tx_completion,
resolve_completion: tx_resolve_completion, resolve_completion: tx_resolve_completion,
goto_definition: tx_goto_definition, goto_definition: tx_goto_definition,
goto_implementation: tx_goto_implementation,
semantic_tokens_full: tx_semantic_tokens_full, semantic_tokens_full: tx_semantic_tokens_full,
inlay_hint: tx_inlay_hint, inlay_hint: tx_inlay_hint,
inlay_hint_resolve: tx_inlay_hint_resolve, inlay_hint_resolve: tx_inlay_hint_resolve,
@ -108,6 +112,7 @@ impl SendChannels {
completion: rx_completion, completion: rx_completion,
resolve_completion: rx_resolve_completion, resolve_completion: rx_resolve_completion,
goto_definition: rx_goto_definition, goto_definition: rx_goto_definition,
goto_implementation: rx_goto_implementation,
semantic_tokens_full: rx_semantic_tokens_full, semantic_tokens_full: rx_semantic_tokens_full,
inlay_hint: rx_inlay_hint, inlay_hint: rx_inlay_hint,
inlay_hint_resolve: rx_inlay_hint_resolve, inlay_hint_resolve: rx_inlay_hint_resolve,
@ -166,6 +171,7 @@ pub struct ReceiveChannels {
pub(crate) completion: mpsc::Receiver<WorkerMessage<CompletionParams>>, pub(crate) completion: mpsc::Receiver<WorkerMessage<CompletionParams>>,
pub(crate) resolve_completion: mpsc::Receiver<WorkerMessage<CompletionItem>>, pub(crate) resolve_completion: mpsc::Receiver<WorkerMessage<CompletionItem>>,
pub(crate) goto_definition: mpsc::Receiver<WorkerMessage<GotoDefinitionParams>>, pub(crate) goto_definition: mpsc::Receiver<WorkerMessage<GotoDefinitionParams>>,
pub(crate) goto_implementation: mpsc::Receiver<WorkerMessage<GotoImplementationParams>>,
pub(crate) semantic_tokens_full: mpsc::Receiver<WorkerMessage<SemanticTokensParams>>, pub(crate) semantic_tokens_full: mpsc::Receiver<WorkerMessage<SemanticTokensParams>>,
pub(crate) inlay_hint: mpsc::Receiver<WorkerMessage<InlayHintParams>>, pub(crate) inlay_hint: mpsc::Receiver<WorkerMessage<InlayHintParams>>,
pub(crate) inlay_hint_resolve: mpsc::Receiver<WorkerMessage<InlayHint>>, pub(crate) inlay_hint_resolve: mpsc::Receiver<WorkerMessage<InlayHint>>,
@ -212,6 +218,11 @@ macro_rules! impl_sendable {
impl_sendable!(Completion, CompletionParams, completion); impl_sendable!(Completion, CompletionParams, completion);
impl_sendable!(ResolveCompletionItem, CompletionItem, resolve_completion); impl_sendable!(ResolveCompletionItem, CompletionItem, resolve_completion);
impl_sendable!(GotoDefinition, GotoDefinitionParams, goto_definition); impl_sendable!(GotoDefinition, GotoDefinitionParams, goto_definition);
impl_sendable!(
GotoImplementation,
GotoImplementationParams,
goto_implementation
);
impl_sendable!( impl_sendable!(
SemanticTokensFullRequest, SemanticTokensFullRequest,
SemanticTokensParams, SemanticTokensParams,

View file

@ -4,11 +4,11 @@ use erg_compiler::artifact::BuildRunnable;
use erg_compiler::context::register::PylyzerStatus; use erg_compiler::context::register::PylyzerStatus;
use erg_compiler::erg_parser::parse::Parsable; use erg_compiler::erg_parser::parse::Parsable;
use erg_compiler::erg_parser::token::{Token, TokenCategory}; 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::ty::HasType;
use erg_compiler::varinfo::VarInfo; 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::server::{send_log, ELSResult, Server};
use crate::util::{self, NormalizedUrl}; use crate::util::{self, NormalizedUrl};
@ -30,58 +30,51 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
} }
} }
fn get_definition_response( pub(crate) fn get_definition_location(
&self, &self,
params: GotoDefinitionParams, uri: &NormalizedUrl,
) -> ELSResult<GotoDefinitionResponse> { pos: Position,
let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri); ) -> ELSResult<Option<Location>> {
let pos = params.text_document_position_params.position; if let Some(token) = self.file_cache.get_symbol(uri, pos) {
if let Some(token) = self.file_cache.get_symbol(&uri, pos) { if let Some(vi) = self.get_definition(uri, &token)? {
if let Some(vi) = self.get_definition(&uri, &token)? {
// If the target variable is an imported one, jump to the definition file. // 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. // Else if the target variable is an alias, jump to the definition of it.
// `foo = import "foo"` => jump to `foo.er` // `foo = import "foo"` => jump to `foo.er`
// `{x;} = import "foo"` => jump to `x` of `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() && 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::<Def>(uri, pos) {
if def.def_kind().is_import() { if def.def_kind().is_import() {
if vi.t.is_module() { if vi.t.is_module() {
if let Some(path) = self if let Some(path) = self
.get_local_ctx(&uri, pos) .get_local_ctx(uri, pos)
.first() .first()
.and_then(|ctx| ctx.get_path_with_mod_t(&vi.t)) .and_then(|ctx| ctx.get_path_with_mod_t(&vi.t))
{ {
let mod_uri = Url::from_file_path(path).unwrap(); let mod_uri = Url::from_file_path(path).unwrap();
let resp = GotoDefinitionResponse::Array(vec![ return Ok(Some(lsp_types::Location::new(
lsp_types::Location::new(
mod_uri, mod_uri,
lsp_types::Range::default(), lsp_types::Range::default(),
), )));
]);
return Ok(resp);
} }
} else { } else {
// line of module member definitions may no longer match after the desugaring process // line of module member definitions may no longer match after the desugaring process
let mod_t = def.body.ref_t(); let mod_t = def.body.ref_t();
if let Some((_, vi)) = self if let Some((_, vi)) = self
.get_local_ctx(&uri, pos) .get_local_ctx(uri, pos)
.first() .first()
.and_then(|ctx| ctx.get_mod_with_t(mod_t)) .and_then(|ctx| ctx.get_mod_with_t(mod_t))
.and_then(|mod_ctx| mod_ctx.get_var_info(token.inspect())) .and_then(|mod_ctx| mod_ctx.get_var_info(token.inspect()))
{ {
let Some(path) = vi.def_loc.module.as_ref() else { 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 def_uri = Url::from_file_path(path).unwrap();
let resp = GotoDefinitionResponse::Array(vec![ return Ok(Some(lsp_types::Location::new(
lsp_types::Location::new(
def_uri, def_uri,
util::loc_to_range(vi.def_loc.loc).unwrap(), util::loc_to_range(vi.def_loc.loc).unwrap(),
), )));
]);
return Ok(resp);
} }
} }
} else if let Expr::Accessor(acc) = def.body.block.last().unwrap() { } else if let Expr::Accessor(acc) = def.body.block.last().unwrap() {
@ -103,14 +96,11 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
path.clone() path.clone()
}; };
let def_uri = Url::from_file_path(def_file).unwrap(); let def_uri = Url::from_file_path(def_file).unwrap();
let resp = GotoDefinitionResponse::Array(vec![ return Ok(Some(lsp_types::Location::new(def_uri, range)));
lsp_types::Location::new(def_uri, range),
]);
return Ok(resp);
} }
_ => { _ => {
send_log("not found (maybe builtin)")?; send_log("not found (maybe builtin)")?;
return Ok(GotoDefinitionResponse::Array(vec![])); return Ok(None);
} }
} }
} }
@ -119,21 +109,19 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
match (vi.def_loc.module, util::loc_to_range(vi.def_loc.loc)) { match (vi.def_loc.module, util::loc_to_range(vi.def_loc.loc)) {
(Some(path), Some(range)) => { (Some(path), Some(range)) => {
let def_uri = Url::from_file_path(path).unwrap(); let def_uri = Url::from_file_path(path).unwrap();
Ok(GotoDefinitionResponse::Array(vec![ Ok(Some(lsp_types::Location::new(def_uri, range)))
lsp_types::Location::new(def_uri, range),
]))
} }
_ => { _ => {
send_log("not found (maybe builtin)")?; send_log("not found (maybe builtin)")?;
Ok(GotoDefinitionResponse::Array(vec![])) Ok(None)
} }
} }
} else { } else {
Ok(GotoDefinitionResponse::Array(vec![])) Ok(None)
} }
} else { } else {
send_log("lex error occurred")?; send_log("lex error occurred")?;
Ok(GotoDefinitionResponse::Array(vec![])) Ok(None)
} }
} }
@ -142,7 +130,9 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
params: GotoDefinitionParams, params: GotoDefinitionParams,
) -> ELSResult<Option<GotoDefinitionResponse>> { ) -> ELSResult<Option<GotoDefinitionResponse>> {
send_log(format!("definition requested: {params:?}"))?; send_log(format!("definition requested: {params:?}"))?;
let result = self.get_definition_response(params)?; let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri);
Ok(Some(result)) let pos = params.text_document_position_params.position;
let result = self.get_definition_location(&uri, pos)?;
Ok(result.map(GotoDefinitionResponse::Scalar))
} }
} }

View file

@ -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<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
pub(crate) fn handle_goto_implementation(
&mut self,
params: GotoImplementationParams,
) -> ELSResult<Option<GotoImplementationResponse>> {
_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)
}
}

View file

@ -11,6 +11,7 @@ mod file_cache;
mod folding_range; mod folding_range;
mod hir_visitor; mod hir_visitor;
mod hover; mod hover;
mod implementation;
mod inlay_hint; mod inlay_hint;
mod message; mod message;
mod references; mod references;

View file

@ -11,6 +11,7 @@ mod file_cache;
mod folding_range; mod folding_range;
mod hir_visitor; mod hir_visitor;
mod hover; mod hover;
mod implementation;
mod inlay_hint; mod inlay_hint;
mod message; mod message;
mod references; mod references;

View file

@ -29,19 +29,20 @@ use erg_compiler::ty::HasType;
use lsp_types::request::{ use lsp_types::request::{
CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare, CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare,
CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion, CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion,
DocumentSymbolRequest, ExecuteCommand, FoldingRangeRequest, GotoDefinition, HoverRequest, DocumentSymbolRequest, ExecuteCommand, FoldingRangeRequest, GotoDefinition, GotoImplementation,
InlayHintRequest, InlayHintResolveRequest, References, Rename, Request, ResolveCompletionItem, HoverRequest, InlayHintRequest, InlayHintResolveRequest, References, Rename, Request,
SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles, WorkspaceSymbol, ResolveCompletionItem, SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles,
WorkspaceSymbol,
}; };
use lsp_types::{ use lsp_types::{
CallHierarchyServerCapability, CodeActionKind, CodeActionOptions, CodeActionProviderCapability, CallHierarchyServerCapability, CodeActionKind, CodeActionOptions, CodeActionProviderCapability,
CodeLensOptions, CompletionOptions, ConfigurationItem, ConfigurationParams, CodeLensOptions, CompletionOptions, ConfigurationItem, ConfigurationParams,
DidChangeTextDocumentParams, DidOpenTextDocumentParams, ExecuteCommandOptions, DidChangeTextDocumentParams, DidOpenTextDocumentParams, ExecuteCommandOptions,
FoldingRangeProviderCapability, HoverProviderCapability, InitializeParams, InitializeResult, FoldingRangeProviderCapability, HoverProviderCapability, ImplementationProviderCapability,
InlayHintOptions, InlayHintServerCapabilities, OneOf, Position, SemanticTokenType, InitializeParams, InitializeResult, InlayHintOptions, InlayHintServerCapabilities, OneOf,
SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions, Position, SemanticTokenType, SemanticTokensFullOptions, SemanticTokensLegend,
SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelpOptions, SemanticTokensOptions, SemanticTokensServerCapabilities, ServerCapabilities,
WorkDoneProgressOptions, SignatureHelpOptions, WorkDoneProgressOptions,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -430,6 +431,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
capabilities.rename_provider = Some(OneOf::Left(true)); capabilities.rename_provider = Some(OneOf::Left(true));
capabilities.references_provider = Some(OneOf::Left(true)); capabilities.references_provider = Some(OneOf::Left(true));
capabilities.definition_provider = Some(OneOf::Left(true)); capabilities.definition_provider = Some(OneOf::Left(true));
capabilities.implementation_provider = Some(ImplementationProviderCapability::Simple(true));
capabilities.hover_provider = self capabilities.hover_provider = self
.disabled_features .disabled_features
.contains(&DefaultFeatures::Hover) .contains(&DefaultFeatures::Hover)
@ -538,6 +540,10 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
receivers.goto_definition, receivers.goto_definition,
Self::handle_goto_definition, Self::handle_goto_definition,
); );
self.start_service::<GotoImplementation>(
receivers.goto_implementation,
Self::handle_goto_implementation,
);
self.start_service::<SemanticTokensFullRequest>( self.start_service::<SemanticTokensFullRequest>(
receivers.semantic_tokens_full, receivers.semantic_tokens_full,
Self::handle_semantic_tokens_full, Self::handle_semantic_tokens_full,
@ -755,6 +761,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
Completion::METHOD => self.parse_send::<Completion>(id, msg), Completion::METHOD => self.parse_send::<Completion>(id, msg),
ResolveCompletionItem::METHOD => self.parse_send::<ResolveCompletionItem>(id, msg), ResolveCompletionItem::METHOD => self.parse_send::<ResolveCompletionItem>(id, msg),
GotoDefinition::METHOD => self.parse_send::<GotoDefinition>(id, msg), GotoDefinition::METHOD => self.parse_send::<GotoDefinition>(id, msg),
GotoImplementation::METHOD => self.parse_send::<GotoImplementation>(id, msg),
HoverRequest::METHOD => self.parse_send::<HoverRequest>(id, msg), HoverRequest::METHOD => self.parse_send::<HoverRequest>(id, msg),
References::METHOD => self.parse_send::<References>(id, msg), References::METHOD => self.parse_send::<References>(id, msg),
SemanticTokensFullRequest::METHOD => { SemanticTokensFullRequest::METHOD => {

View file

@ -367,7 +367,7 @@ impl DummyClient {
uri: Url, uri: Url,
line: u32, line: u32,
col: u32, col: u32,
) -> Result<Vec<Location>, Box<dyn std::error::Error>> { ) -> Result<Location, Box<dyn std::error::Error>> {
let params = GotoDefinitionParams { let params = GotoDefinitionParams {
text_document_position_params: abs_pos(uri, line, col), text_document_position_params: abs_pos(uri, line, col),
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
@ -380,7 +380,7 @@ impl DummyClient {
"params": params, "params": params,
}); });
self.server.dispatch(msg)?; self.server.dispatch(msg)?;
self.wait_for::<Vec<Location>>() self.wait_for::<Location>()
} }
fn request_folding_range( fn request_folding_range(
@ -540,9 +540,8 @@ fn test_goto_definition() -> Result<(), Box<dyn std::error::Error>> {
client.request_initialize()?; client.request_initialize()?;
let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?;
client.notify_open(FILE_A)?; client.notify_open(FILE_A)?;
let locations = client.request_goto_definition(uri.raw(), 1, 4)?; let location = client.request_goto_definition(uri.raw(), 1, 4)?;
assert_eq!(locations.len(), 1); assert_eq!(&location.range, &single_range(0, 0, 1));
assert_eq!(&locations[0].range, &single_range(0, 0, 1));
Ok(()) Ok(())
} }