feat(els): support inlay hint resolve

This commit is contained in:
Shunsuke Shibayama 2023-08-16 19:06:37 +09:00
parent a292d2e3d7
commit efbad81475
5 changed files with 200 additions and 107 deletions

View file

@ -5,13 +5,13 @@ use erg_compiler::erg_parser::parse::Parsable;
use lsp_types::request::{
CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion, ExecuteCommand,
GotoDefinition, HoverRequest, InlayHintRequest, References, ResolveCompletionItem,
SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles,
GotoDefinition, HoverRequest, InlayHintRequest, InlayHintResolveRequest, References,
ResolveCompletionItem, SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles,
};
use lsp_types::{
CodeAction, CodeActionParams, CodeLensParams, CompletionItem, CompletionParams,
ExecuteCommandParams, GotoDefinitionParams, HoverParams, InlayHintParams, ReferenceParams,
RenameFilesParams, SemanticTokensParams, SignatureHelpParams,
ExecuteCommandParams, GotoDefinitionParams, HoverParams, InlayHint, InlayHintParams,
ReferenceParams, RenameFilesParams, SemanticTokensParams, SignatureHelpParams,
};
use crate::server::Server;
@ -23,6 +23,7 @@ pub struct SendChannels {
goto_definition: mpsc::Sender<(i64, GotoDefinitionParams)>,
semantic_tokens_full: mpsc::Sender<(i64, SemanticTokensParams)>,
inlay_hint: mpsc::Sender<(i64, InlayHintParams)>,
inlay_hint_resolve: mpsc::Sender<(i64, InlayHint)>,
hover: mpsc::Sender<(i64, HoverParams)>,
references: mpsc::Sender<(i64, ReferenceParams)>,
code_lens: mpsc::Sender<(i64, CodeLensParams)>,
@ -40,6 +41,7 @@ impl SendChannels {
let (tx_goto_definition, rx_goto_definition) = 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();
let (tx_hover, rx_hover) = mpsc::channel();
let (tx_references, rx_references) = mpsc::channel();
let (tx_code_lens, rx_code_lens) = mpsc::channel();
@ -55,6 +57,7 @@ impl SendChannels {
goto_definition: tx_goto_definition,
semantic_tokens_full: tx_semantic_tokens_full,
inlay_hint: tx_inlay_hint,
inlay_hint_resolve: tx_inlay_hint_resolve,
hover: tx_hover,
references: tx_references,
code_lens: tx_code_lens,
@ -70,6 +73,7 @@ impl SendChannels {
goto_definition: rx_goto_definition,
semantic_tokens_full: rx_semantic_tokens_full,
inlay_hint: rx_inlay_hint,
inlay_hint_resolve: rx_inlay_hint_resolve,
hover: rx_hover,
references: rx_references,
code_lens: rx_code_lens,
@ -90,6 +94,7 @@ pub struct ReceiveChannels {
pub(crate) goto_definition: mpsc::Receiver<(i64, GotoDefinitionParams)>,
pub(crate) semantic_tokens_full: mpsc::Receiver<(i64, SemanticTokensParams)>,
pub(crate) inlay_hint: mpsc::Receiver<(i64, InlayHintParams)>,
pub(crate) inlay_hint_resolve: mpsc::Receiver<(i64, InlayHint)>,
pub(crate) hover: mpsc::Receiver<(i64, HoverParams)>,
pub(crate) references: mpsc::Receiver<(i64, ReferenceParams)>,
pub(crate) code_lens: mpsc::Receiver<(i64, CodeLensParams)>,
@ -130,6 +135,7 @@ impl_sendable!(
semantic_tokens_full
);
impl_sendable!(InlayHintRequest, InlayHintParams, inlay_hint);
impl_sendable!(InlayHintResolveRequest, InlayHint, inlay_hint_resolve);
impl_sendable!(HoverRequest, HoverParams, hover);
impl_sendable!(References, ReferenceParams, references);
impl_sendable!(CodeLensRequest, CodeLensParams, code_lens);

View file

@ -1,6 +1,8 @@
#![allow(unused_imports)]
use erg_compiler::erg_parser::parse::Parsable;
use erg_compiler::varinfo::AbsLocation;
use lsp_types::InlayHintLabelPart;
use serde::Deserialize;
use serde_json::json;
use serde_json::Value;
@ -12,97 +14,99 @@ use erg_common::traits::{Locational, Runnable, Stream};
use erg_compiler::artifact::{BuildRunnable, IncompleteArtifact};
use erg_compiler::hir::{Block, Call, ClassDef, Def, Expr, Lambda, Params, PatchDef, Signature};
use erg_compiler::ty::HasType;
use lsp_types::{InlayHint, InlayHintKind, InlayHintLabel, InlayHintParams, Position};
use lsp_types::{
InlayHint, InlayHintKind, InlayHintLabel, InlayHintParams, InlayHintTooltip, Position,
};
use crate::_log;
use crate::server::{send, send_log, ELSResult, Server};
use crate::util::abs_loc_to_lsp_loc;
use crate::util::{self, loc_to_range, NormalizedUrl};
fn anot(ln: u32, col: u32, cont: String) -> InlayHint {
let position = Position::new(ln - 1, col);
let label = InlayHintLabel::String(cont);
let kind = Some(InlayHintKind::TYPE);
InlayHint {
position,
label,
kind,
text_edits: None,
tooltip: None,
padding_left: Some(false),
padding_right: Some(false),
data: None,
}
pub struct InlayHintGenerator<'s, C: BuildRunnable, P: Parsable> {
_server: &'s Server<C, P>,
uri: Value,
}
fn type_anot<D: std::fmt::Display>(ln_end: u32, col_end: u32, ty: D, return_t: bool) -> InlayHint {
let position = Position::new(ln_end - 1, col_end);
let string = if return_t {
format!("): {ty}")
} else {
format!(": {ty}")
};
let label = InlayHintLabel::String(string);
let kind = Some(InlayHintKind::TYPE);
InlayHint {
position,
label,
kind,
text_edits: None,
tooltip: None,
padding_left: Some(return_t),
padding_right: Some(false),
data: None,
}
}
fn type_bounds_anot(ln_end: u32, col_end: u32, ty_bounds: String) -> InlayHint {
let position = Position::new(ln_end - 1, col_end);
let label = InlayHintLabel::String(ty_bounds);
let kind = Some(InlayHintKind::TYPE);
InlayHint {
position,
label,
kind,
text_edits: None,
tooltip: None,
padding_left: Some(false),
padding_right: Some(false),
data: None,
}
}
fn param_anot<D: std::fmt::Display>(ln_begin: u32, col_begin: u32, name: D) -> InlayHint {
let position = Position::new(ln_begin - 1, col_begin);
let label = InlayHintLabel::String(format!("{name}:= "));
let kind = Some(InlayHintKind::PARAMETER);
InlayHint {
position,
label,
kind,
text_edits: None,
tooltip: None,
padding_left: Some(false),
padding_right: Some(false),
data: None,
}
}
impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
pub(crate) fn handle_inlay_hint(
&mut self,
params: InlayHintParams,
) -> ELSResult<Option<Vec<InlayHint>>> {
send_log(format!("inlay hint request: {params:?}"))?;
let uri = NormalizedUrl::new(params.text_document.uri);
let mut result = vec![];
if let Some(IncompleteArtifact {
object: Some(hir), ..
}) = self.analysis_result.get_artifact(&uri).as_deref()
{
for chunk in hir.module.iter() {
result.extend(self.get_expr_hint(chunk));
}
impl<'s, C: BuildRunnable, P: Parsable> InlayHintGenerator<'s, C, P> {
fn anot(&self, ln: u32, col: u32, cont: String) -> InlayHint {
let position = Position::new(ln - 1, col);
let label = InlayHintLabel::String(cont);
let kind = Some(InlayHintKind::TYPE);
InlayHint {
position,
label,
kind,
text_edits: None,
tooltip: None,
padding_left: Some(false),
padding_right: Some(false),
data: Some(self.uri.clone()),
}
}
fn type_anot<D: std::fmt::Display>(
&self,
ln_end: u32,
col_end: u32,
ty: D,
return_t: bool,
) -> InlayHint {
let position = Position::new(ln_end - 1, col_end);
let string = if return_t {
format!("): {ty}")
} else {
format!(": {ty}")
};
let label = InlayHintLabel::String(string);
let kind = Some(InlayHintKind::TYPE);
InlayHint {
position,
label,
kind,
text_edits: None,
tooltip: None,
padding_left: Some(return_t),
padding_right: Some(false),
data: Some(self.uri.clone()),
}
}
fn type_bounds_anot(&self, ln_end: u32, col_end: u32, ty_bounds: String) -> InlayHint {
let position = Position::new(ln_end - 1, col_end);
let label = InlayHintLabel::String(ty_bounds);
let kind = Some(InlayHintKind::TYPE);
InlayHint {
position,
label,
kind,
text_edits: None,
tooltip: None,
padding_left: Some(false),
padding_right: Some(false),
data: Some(self.uri.clone()),
}
}
fn param_anot<D: std::fmt::Display>(
&self,
ln_begin: u32,
col_begin: u32,
name: D,
) -> InlayHint {
let position = Position::new(ln_begin - 1, col_begin);
let label = InlayHintLabel::String(format!("{name}:= "));
let kind = Some(InlayHintKind::PARAMETER);
InlayHint {
position,
label,
kind,
text_edits: None,
tooltip: None,
padding_left: Some(false),
padding_right: Some(false),
data: Some(self.uri.clone()),
}
Ok(Some(result))
}
fn get_expr_hint(&self, expr: &Expr) -> Vec<InlayHint> {
@ -126,7 +130,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
let (Some(ln_end), Some(col_end)) = (nd_param.ln_end(), nd_param.col_end()) else {
continue;
};
let hint = type_anot(ln_end, col_end, &nd_param.vi.t, false);
let hint = self.type_anot(ln_end, col_end, &nd_param.vi.t, false);
result.push(hint);
}
if let Some(var_params) = &params.var_params {
@ -134,7 +138,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
return result;
}
if let (Some(ln_end), Some(col_end)) = (var_params.ln_end(), var_params.col_end()) {
let hint = type_anot(ln_end, col_end, &var_params.vi.t, false);
let hint = self.type_anot(ln_end, col_end, &var_params.vi.t, false);
result.push(hint);
}
}
@ -145,7 +149,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
let (Some(ln_end), Some(col_end)) = (d_param.sig.ln_end(), d_param.sig.col_end()) else {
continue;
};
let hint = type_anot(ln_end, col_end, &d_param.sig.vi.t, false);
let hint = self.type_anot(ln_end, col_end, &d_param.sig.vi.t, false);
result.push(hint);
}
result
@ -160,7 +164,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
let ty_bounds = format!("|{}|", subr.split('|').nth(1).unwrap_or(""));
let ident = def.sig.ident();
if let Some((ln, col)) = ident.ln_end().zip(ident.col_end()) {
let hint = type_bounds_anot(ln, col, ty_bounds);
let hint = self.type_bounds_anot(ln, col, ty_bounds);
result.push(hint);
}
}
@ -170,12 +174,12 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
return result;
};
if let Some((ln, col)) = def.sig.ln_end().zip(def.sig.col_end()) {
let hint = type_anot(ln, col, return_t, subr.params.parens.is_none());
let hint = self.type_anot(ln, col, return_t, subr.params.parens.is_none());
result.push(hint);
}
if subr.params.parens.is_none() {
if let Some((ln, col)) = subr.params.ln_begin().zip(subr.params.col_begin()) {
let hint = anot(ln, col, "(".to_string());
let hint = self.anot(ln, col, "(".to_string());
result.push(hint);
}
}
@ -188,7 +192,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
// don't show hints for compiler internal variables
if def.sig.t_spec().is_none() && !def.sig.ident().inspect().starts_with(['%']) {
if let Some((ln, col)) = def.sig.ln_begin().zip(def.sig.col_end()) {
let hint = type_anot(ln, col, def.sig.ident().ref_t(), false);
let hint = self.type_anot(ln, col, def.sig.ident().ref_t(), false);
result.push(hint);
}
}
@ -201,7 +205,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
result.extend(self.get_param_hint(&lambda.params));
if lambda.params.parens.is_none() {
if let Some((ln, col)) = lambda.params.ln_begin().zip(lambda.params.col_begin()) {
let hint = anot(ln, col, "(".to_string());
let hint = self.anot(ln, col, "(".to_string());
result.push(hint);
}
}
@ -211,7 +215,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
.zip(lambda.params.col_end())
.zip(lambda.ref_t().return_t())
{
let hint = type_anot(ln, col, return_t, lambda.params.parens.is_none());
let hint = self.type_anot(ln, col, return_t, lambda.params.parens.is_none());
result.push(hint);
}
result
@ -266,7 +270,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
} else {
(name.to_string(), col_begin)
};
let hint = param_anot(ln_begin, col_begin, name);
let hint = self.param_anot(ln_begin, col_begin, name);
result.push(hint);
}
}
@ -280,3 +284,56 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
.collect()
}
}
impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
pub(crate) fn handle_inlay_hint(
&mut self,
params: InlayHintParams,
) -> ELSResult<Option<Vec<InlayHint>>> {
send_log(format!("inlay hint request: {params:?}"))?;
let uri = NormalizedUrl::new(params.text_document.uri);
let mut result = vec![];
let gen = InlayHintGenerator {
_server: self,
uri: uri.clone().raw().to_string().into(),
};
if let Some(IncompleteArtifact {
object: Some(hir), ..
}) = self.analysis_result.get_artifact(&uri).as_deref()
{
for chunk in hir.module.iter() {
result.extend(gen.get_expr_hint(chunk));
}
}
Ok(Some(result))
}
pub(crate) fn handle_inlay_hint_resolve(
&mut self,
mut hint: InlayHint,
) -> ELSResult<InlayHint> {
send_log(format!("inlay hint resolve request: {hint:?}"))?;
if let Some(data) = &hint.data {
let Ok(uri) = data.as_str().unwrap().parse::<NormalizedUrl>() else {
return Ok(hint);
};
if let Some(module) = self.modules.get(&uri) {
let InlayHintLabel::String(label) = &hint.label else {
return Ok(hint);
};
let name = label.trim_start_matches("): ").trim_start_matches(": ");
if let Some((_, vi)) = module.context.get_type_info_by_str(name) {
let location = abs_loc_to_lsp_loc(&vi.def_loc);
let parts = InlayHintLabelPart {
value: label.clone(),
tooltip: None,
location,
command: None,
};
hint.label = InlayHintLabel::LabelParts(vec![parts]);
}
}
}
Ok(hint)
}
}

View file

@ -27,16 +27,17 @@ use erg_compiler::ty::HasType;
use lsp_types::request::{
CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion, ExecuteCommand,
GotoDefinition, HoverRequest, InlayHintRequest, References, Rename, Request,
ResolveCompletionItem, SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles,
GotoDefinition, HoverRequest, InlayHintRequest, InlayHintResolveRequest, References, Rename,
Request, ResolveCompletionItem, SemanticTokensFullRequest, SignatureHelpRequest,
WillRenameFiles,
};
use lsp_types::{
ClientCapabilities, CodeActionKind, CodeActionOptions, CodeActionProviderCapability,
CodeLensOptions, CompletionOptions, DidChangeTextDocumentParams, DidOpenTextDocumentParams,
ExecuteCommandOptions, HoverProviderCapability, InitializeResult, OneOf, Position,
SemanticTokenType, SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions,
SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelpOptions,
WorkDoneProgressOptions,
ExecuteCommandOptions, HoverProviderCapability, InitializeResult, InlayHintOptions,
InlayHintServerCapabilities, OneOf, Position, SemanticTokenType, SemanticTokensFullOptions,
SemanticTokensLegend, SemanticTokensOptions, SemanticTokensServerCapabilities,
ServerCapabilities, SignatureHelpOptions, WorkDoneProgressOptions,
};
use serde::{Deserialize, Serialize};
@ -421,7 +422,12 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
.disabled_features
.contains(&DefaultFeatures::InlayHint)
.not()
.then_some(OneOf::Left(true));
.then_some(OneOf::Right(InlayHintServerCapabilities::Options(
InlayHintOptions {
resolve_provider: Some(true),
..Default::default()
},
)));
let mut sema_options = SemanticTokensOptions::default();
sema_options.range = Some(false);
sema_options.full = Some(SemanticTokensFullOptions::Bool(true));
@ -506,6 +512,10 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
Self::handle_semantic_tokens_full,
);
self.start_service::<InlayHintRequest>(receivers.inlay_hint, Self::handle_inlay_hint);
self.start_service::<InlayHintResolveRequest>(
receivers.inlay_hint_resolve,
Self::handle_inlay_hint_resolve,
);
self.start_service::<HoverRequest>(receivers.hover, Self::handle_hover);
self.start_service::<References>(receivers.references, Self::handle_references);
self.start_service::<CodeLensRequest>(receivers.code_lens, Self::handle_code_lens);
@ -669,6 +679,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
self.parse_send::<SemanticTokensFullRequest>(id, msg)
}
InlayHintRequest::METHOD => self.parse_send::<InlayHintRequest>(id, msg),
InlayHintResolveRequest::METHOD => self.parse_send::<InlayHintResolveRequest>(id, msg),
CodeActionRequest::METHOD => self.parse_send::<CodeActionRequest>(id, msg),
CodeActionResolveRequest::METHOD => {
self.parse_send::<CodeActionResolveRequest>(id, msg)

View file

@ -1,6 +1,7 @@
use std::fmt;
use std::fs::{metadata, Metadata};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use erg_common::consts::CASE_SENSITIVE;
use erg_common::normalize_path;
@ -8,6 +9,7 @@ use erg_common::traits::{DequeStream, Locational};
use erg_compiler::erg_parser::token::{Token, TokenStream};
use erg_compiler::varinfo::AbsLocation;
use lsp_types::{Position, Range, Url};
use crate::server::ELSResult;
@ -22,6 +24,13 @@ impl fmt::Display for NormalizedUrl {
}
}
impl FromStr for NormalizedUrl {
type Err = Box<dyn std::error::Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
NormalizedUrl::parse(s)
}
}
impl std::ops::Deref for NormalizedUrl {
type Target = Url;
@ -156,3 +165,9 @@ pub(crate) fn uri_to_path(uri: &NormalizedUrl) -> PathBuf {
pub(crate) fn denormalize(uri: Url) -> Url {
Url::parse(&uri.as_str().replace("c:", "file:///c%3A")).unwrap()
}
pub(crate) fn abs_loc_to_lsp_loc(loc: &AbsLocation) -> Option<lsp_types::Location> {
let uri = Url::from_file_path(loc.module.as_ref()?).ok()?;
let range = loc_to_range(loc.loc)?;
Some(lsp_types::Location::new(uri, range))
}

View file

@ -2671,7 +2671,7 @@ impl Context {
pub(crate) fn get_namespace(&self, namespace: &Str) -> Option<&Context> {
if &namespace[..] == "global" {
return self.get_builtins();
} else if &namespace[..] == "module" {
} else if &namespace[..] == "module" || namespace.is_empty() {
return self.get_module();
}
self.get_mod_with_path(self.get_namespace_path(namespace)?.as_path())
@ -2777,7 +2777,7 @@ impl Context {
}
}
pub(crate) fn get_type(&self, name: &Str) -> Option<(&Type, &Context)> {
pub(crate) fn get_type(&self, name: &str) -> Option<(&Type, &Context)> {
if let Some((t, ctx)) = self.rec_local_get_type(name) {
return Some((t, ctx));
}
@ -2795,6 +2795,10 @@ impl Context {
None
}
pub fn get_type_info_by_str(&self, name: &str) -> Option<(&VarName, &VarInfo)> {
self.get_type(name).and_then(|(t, _)| self.get_type_info(t))
}
/// you should use `get_type` instead of this
pub(crate) fn rec_local_get_type(&self, name: &str) -> Option<(&Type, &Context)> {
#[cfg(feature = "py_compat")]