fix(els): signature help

This commit is contained in:
Shunsuke Shibayama 2023-08-30 04:13:58 +09:00
parent 1332b009b7
commit b746cd38bd
8 changed files with 148 additions and 56 deletions

View file

@ -543,7 +543,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
.and_then(|(token, expr)| match expr {
Expr::Call(call) => {
let sig_t = call.obj.t();
let nth = self.nth(&uri, call.args.loc(), &token);
let nth = self.nth(&uri, &call, pos);
let additional = if matches!(token.kind, Comma) { 1 } else { 0 };
let nth = nth + additional;
sig_t.non_default_params()?.get(nth).cloned()

View file

@ -162,6 +162,9 @@ impl FileCache {
pos: Position,
offset: isize,
) -> Option<Token> {
if offset == 0 {
return self.get_token(uri, pos);
}
let _ = self.load_once(uri);
let ent = self.files.borrow_mut();
let tokens = ent.get(uri)?.token_stream.as_ref()?;

View file

@ -10,6 +10,21 @@ use lsp_types::Position;
use crate::file_cache::FileCache;
use crate::util::{self, NormalizedUrl};
#[derive(Debug)]
pub enum ExprKind {
Call,
Expr,
}
impl ExprKind {
pub const fn matches(&self, expr: &Expr) -> bool {
matches!(
(self, expr),
(ExprKind::Call, Expr::Call(_)) | (ExprKind::Expr, _)
)
}
}
/// This struct provides:
/// * namespace where the cursor is located (`get_namespace`)
/// * cursor(`Token`) -> `Expr` mapping (`get_min_expr`)
@ -19,6 +34,7 @@ pub struct HIRVisitor<'a> {
file_cache: &'a FileCache,
uri: NormalizedUrl,
strict_cmp: bool,
search: ExprKind,
}
impl<'a> HIRVisitor<'a> {
@ -32,6 +48,22 @@ impl<'a> HIRVisitor<'a> {
file_cache,
uri,
strict_cmp: ERG_MODE,
search: ExprKind::Expr,
}
}
pub fn new_searcher(
hir: MappedRwLockReadGuard<'a, HIR>,
file_cache: &'a FileCache,
uri: NormalizedUrl,
search: ExprKind,
) -> Self {
Self {
hir,
file_cache,
uri,
strict_cmp: ERG_MODE,
search,
}
}
@ -176,6 +208,9 @@ impl<'a> HIRVisitor<'a> {
}
fn return_expr_if_same<'e>(&'e self, expr: &'e Expr, l: &Token, r: &Token) -> Option<&Expr> {
if !self.search.matches(expr) {
return None;
}
if self.strict_cmp {
if l.deep_eq(r) {
Some(expr)
@ -189,6 +224,15 @@ impl<'a> HIRVisitor<'a> {
}
}
fn return_expr_if_contains<'e>(
&'e self,
expr: &'e Expr,
token: &Token,
loc: &impl Locational,
) -> Option<&Expr> {
(loc.loc().contains(token.loc()) && self.search.matches(expr)).then_some(expr)
}
fn get_expr<'e>(&'e self, expr: &'e Expr, token: &Token) -> Option<&'e Expr> {
if expr.ln_end() < token.ln_begin() || expr.ln_begin() > token.ln_end() {
return None;
@ -236,7 +280,7 @@ impl<'a> HIRVisitor<'a> {
bin: &'e BinOp,
token: &Token,
) -> Option<&Expr> {
if &bin.op == token {
if &bin.op == token && self.search.matches(expr) {
return Some(expr);
}
self.get_expr(&bin.lhs, token)
@ -255,13 +299,22 @@ impl<'a> HIRVisitor<'a> {
.as_ref()
.and_then(|(_, end)| self.return_expr_if_same(expr, end, token))
.or_else(|| {
call.attr_name
.as_ref()
.and_then(|attr| self.return_expr_if_same(expr, attr.raw.name.token(), token))
call.attr_name.as_ref().and_then(|attr| {
self.return_expr_if_same(expr, attr.raw.name.token(), token)
.map(|e| {
crate::_log!("{e}");
e
})
})
})
.or_else(|| self.get_expr(&call.obj, token))
.or_else(|| self.get_expr_from_args(&call.args, token))
.or_else(|| call.loc().contains(token.loc()).then_some(expr))
.or_else(|| {
self.get_expr_from_args(&call.args, token).map(|e| {
crate::_log!("{:?} / {e}", self.search);
e
})
})
.or_else(|| self.return_expr_if_contains(expr, token, call))
}
fn get_expr_from_args<'e>(&'e self, args: &'e Args, token: &Token) -> Option<&Expr> {
@ -291,7 +344,7 @@ impl<'a> HIRVisitor<'a> {
) -> Option<&Expr> {
self.return_expr_if_same(expr, def.sig.ident().raw.name.token(), token)
.or_else(|| self.get_expr_from_block(&def.body.block, token))
.or_else(|| def.loc().contains(token.loc()).then_some(expr))
.or_else(|| self.return_expr_if_contains(expr, token, def))
}
fn get_expr_from_class_def<'e>(
@ -305,7 +358,7 @@ impl<'a> HIRVisitor<'a> {
.as_ref()
.and_then(|req_sup| self.get_expr(req_sup, token))
.or_else(|| self.get_expr_from_block(&class_def.methods, token))
.or_else(|| class_def.loc().contains(token.loc()).then_some(expr))
.or_else(|| self.return_expr_if_contains(expr, token, class_def))
}
fn get_expr_from_block<'e>(&'e self, block: &'e Block, token: &Token) -> Option<&Expr> {
@ -345,7 +398,7 @@ impl<'a> HIRVisitor<'a> {
self.return_expr_if_same(expr, patch_def.sig.name().token(), token)
.or_else(|| self.get_expr(&patch_def.base, token))
.or_else(|| self.get_expr_from_block(&patch_def.methods, token))
.or_else(|| patch_def.loc().contains(token.loc()).then_some(expr))
.or_else(|| self.return_expr_if_contains(expr, token, patch_def))
}
fn get_expr_from_lambda<'e>(
@ -354,7 +407,9 @@ impl<'a> HIRVisitor<'a> {
lambda: &'e Lambda,
token: &Token,
) -> Option<&Expr> {
if util::pos_in_loc(&lambda.params, util::loc_to_pos(token.loc())?) {
if util::pos_in_loc(&lambda.params, util::loc_to_pos(token.loc())?)
&& self.search.matches(expr)
{
return Some(expr);
}
self.get_expr_from_block(&lambda.body, token)
@ -366,7 +421,7 @@ impl<'a> HIRVisitor<'a> {
arr: &'e Array,
token: &Token,
) -> Option<&Expr> {
if arr.ln_end() == token.ln_end() {
if arr.ln_end() == token.ln_end() && self.search.matches(expr) {
// arr: `[1, 2]`, token: `]`
return Some(expr);
}
@ -382,7 +437,7 @@ impl<'a> HIRVisitor<'a> {
dict: &'e Dict,
token: &Token,
) -> Option<&Expr> {
if dict.ln_end() == token.ln_end() {
if dict.ln_end() == token.ln_end() && self.search.matches(expr) {
// arr: `{...}`, token: `}`
return Some(expr);
}
@ -408,7 +463,7 @@ impl<'a> HIRVisitor<'a> {
record: &'e Record,
token: &Token,
) -> Option<&Expr> {
if record.ln_end() == token.ln_end() {
if record.ln_end() == token.ln_end() && self.search.matches(expr) {
// arr: `{...}`, token: `}`
return Some(expr);
}
@ -426,7 +481,7 @@ impl<'a> HIRVisitor<'a> {
set: &'e Set,
token: &Token,
) -> Option<&Expr> {
if set.ln_end() == token.ln_end() {
if set.ln_end() == token.ln_end() && self.search.matches(expr) {
// arr: `{...}`, token: `}`
return Some(expr);
}
@ -537,18 +592,18 @@ impl<'a> HIRVisitor<'a> {
fn get_args_info(&self, args: &Args, token: &Token) -> Option<VarInfo> {
for arg in args.pos_args.iter() {
if let Some(expr) = self.get_expr_info(&arg.expr, token) {
return Some(expr);
if let Some(vi) = self.get_expr_info(&arg.expr, token) {
return Some(vi);
}
}
if let Some(var) = &args.var_args {
if let Some(expr) = self.get_expr_info(&var.expr, token) {
return Some(expr);
if let Some(vi) = self.get_expr_info(&var.expr, token) {
return Some(vi);
}
}
for arg in args.kw_args.iter() {
if let Some(expr) = self.get_expr_info(&arg.expr, token) {
return Some(expr);
if let Some(vi) = self.get_expr_info(&arg.expr, token) {
return Some(vi);
}
}
None

View file

@ -20,7 +20,7 @@ use erg_compiler::build_hir::HIRBuilder;
use erg_compiler::context::{Context, ModuleContext};
use erg_compiler::erg_parser::ast::Module;
use erg_compiler::erg_parser::parse::{Parsable, SimpleParser};
use erg_compiler::hir::{Expr, HIR};
use erg_compiler::hir::HIR;
use erg_compiler::lower::ASTLowerer;
use erg_compiler::module::{SharedCompilerResource, SharedModuleGraph, SharedModuleIndex};
use erg_compiler::ty::HasType;
@ -48,7 +48,7 @@ use serde_json::Value;
use crate::channels::{SendChannels, Sendable, WorkerMessage};
use crate::completion::CompletionCache;
use crate::file_cache::FileCache;
use crate::hir_visitor::HIRVisitor;
use crate::hir_visitor::{ExprKind, HIRVisitor};
use crate::message::{ErrorMessage, LSPResult, LogMessage, ShowMessage};
use crate::util::{self, NormalizedUrl};
@ -323,7 +323,6 @@ pub struct Server<Checker: BuildRunnable = HIRBuilder, Parser: Parsable = Simple
// TODO: remove modules, analysis_result, and add `shared: SharedCompilerResource`
pub(crate) modules: ModuleCache,
pub(crate) analysis_result: AnalysisResultCache,
pub(crate) current_sig: Option<Expr>,
pub(crate) channels: Option<SendChannels>,
pub(crate) _parser: std::marker::PhantomData<fn() -> Parser>,
pub(crate) _checker: std::marker::PhantomData<fn() -> Checker>,
@ -343,7 +342,6 @@ impl<C: BuildRunnable, P: Parsable> Clone for Server<C, P> {
comp_cache: self.comp_cache.clone(),
modules: self.modules.clone(),
analysis_result: self.analysis_result.clone(),
current_sig: self.current_sig.clone(),
channels: self.channels.clone(),
_parser: std::marker::PhantomData,
_checker: std::marker::PhantomData,
@ -365,7 +363,6 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
file_cache: FileCache::new(),
modules: ModuleCache::new(),
analysis_result: AnalysisResultCache::new(),
current_sig: None,
channels: None,
_parser: std::marker::PhantomData,
_checker: std::marker::PhantomData,
@ -592,7 +589,6 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
self.comp_cache.clear();
self.modules = ModuleCache::new();
self.analysis_result = AnalysisResultCache::new();
self.current_sig = None;
self.channels.as_ref().unwrap().close();
self.start_language_services();
}
@ -833,6 +829,12 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
.map(|hir| HIRVisitor::new(hir, &self.file_cache, uri.clone()))
}
pub(crate) fn get_searcher(&self, uri: &NormalizedUrl, kind: ExprKind) -> Option<HIRVisitor> {
self.analysis_result
.get_hir(uri)
.map(|hir| HIRVisitor::new_searcher(hir, &self.file_cache, uri.clone(), kind))
}
pub(crate) fn get_local_ctx(&self, uri: &NormalizedUrl, pos: Position) -> Vec<&Context> {
let mut ctxs = vec![];
if let Some(mod_ctx) = &self.modules.get(uri) {

View file

@ -1,8 +1,8 @@
use erg_common::traits::{DequeStream, LimitedDisplay, Locational, NoTypeDisplay};
use erg_compiler::artifact::BuildRunnable;
use erg_compiler::erg_parser::parse::Parsable;
use erg_compiler::erg_parser::token::{Token, TokenKind};
use erg_compiler::hir::Expr;
use erg_compiler::erg_parser::token::{Token, TokenCategory, TokenKind};
use erg_compiler::hir::{Call, Expr};
use erg_compiler::ty::{HasType, ParamTy};
use lsp_types::{
@ -10,8 +10,9 @@ use lsp_types::{
SignatureHelpParams, SignatureHelpTriggerKind, SignatureInformation,
};
use crate::hir_visitor::ExprKind;
use crate::server::{send_log, ELSResult, Server};
use crate::util::NormalizedUrl;
use crate::util::{pos_to_loc, NormalizedUrl};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Trigger {
@ -84,35 +85,58 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
let token = self.file_cache.get_token_relatively(uri, pos, offset)?;
crate::_log!("token: {token}");
if let Some(visitor) = self.get_visitor(uri) {
#[allow(clippy::single_match)]
match visitor.get_min_expr(&token) {
Some(expr) => {
return Some((token, expr.clone()));
}
_ => {}
if let Some(expr) = visitor.get_min_expr(&token) {
return Some((token, expr.clone()));
}
}
None
}
pub(crate) fn nth(
pub(crate) fn get_min_call(
&self,
uri: &NormalizedUrl,
args_loc: erg_common::error::Location,
token: &Token,
) -> usize {
pos: Position,
offset: isize,
) -> Option<(Token, Expr)> {
let token = self.file_cache.get_token_relatively(uri, pos, offset)?;
crate::_log!("token: {token}");
if let Some(visitor) = self.get_searcher(uri, ExprKind::Call) {
if let Some(expr) = visitor.get_min_expr(&token) {
return Some((token, expr.clone()));
}
}
None
}
pub(crate) fn nth(&self, uri: &NormalizedUrl, call: &Call, pos: Position) -> usize {
let origin_loc = call
.args
.paren
.as_ref()
.map(|(l, _)| l.loc())
.unwrap_or_else(|| call.obj.loc());
let loc = pos_to_loc(pos);
let tks = self.file_cache.get_token_stream(uri).unwrap_or_default();
let mut paren = 0usize;
// we should use the latest commas
let commas = tks
.iter()
.skip_while(|&tk| tk.loc() < args_loc)
.filter(|tk| tk.is(TokenKind::Comma) && args_loc.ln_end() >= tk.ln_begin())
.skip_while(|&tk| tk.loc() <= origin_loc)
.filter(|tk| {
// skip `,` of [1, ...]
match tk.category() {
TokenCategory::LEnclosure => {
paren += 1;
}
TokenCategory::REnclosure => {
paren = paren.saturating_sub(1);
}
_ => {}
}
paren == 0 && tk.is(TokenKind::Comma) && tk.loc() <= loc
})
.collect::<Vec<_>>();
let argc = commas.len();
commas
.iter()
.position(|c| c.col_end() >= token.col_end())
.unwrap_or(argc) // `commas.col_end() < token.col_end()` means the token is the last argument
commas.len()
}
fn resend_help(
@ -123,13 +147,14 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
) -> Option<SignatureHelp> {
if let Some(token) = self.file_cache.get_token(uri, pos) {
crate::_log!("token: {token}");
if let Some(Expr::Call(call)) = &self.current_sig {
if let Some((_, Expr::Call(call))) = self.get_min_call(uri, pos, 0) {
if call.ln_begin() > token.ln_begin() || call.ln_end() < token.ln_end() {
self.current_sig = None;
return None;
}
let nth = self.nth(uri, call.args.loc(), &token) as u32;
let nth = self.nth(uri, &call, pos) as u32;
return self.make_sig_help(call.obj.as_ref(), nth);
} else {
crate::_log!("failed to get the call");
}
} else {
crate::_log!("failed to get the token");
@ -147,10 +172,9 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
}
fn get_continuous_help(&mut self, uri: &NormalizedUrl, pos: Position) -> Option<SignatureHelp> {
if let Some((comma, Expr::Call(call))) = self.get_min_expr(uri, pos, -1) {
let nth = self.nth(uri, call.args.loc(), &comma) as u32 + 1;
if let Some((_, Expr::Call(call))) = self.get_min_call(uri, pos, -1) {
let nth = self.nth(uri, &call, pos) as u32 + 1;
let help = self.make_sig_help(call.obj.as_ref(), nth);
self.current_sig = Some(Expr::Call(call));
return help;
} else {
crate::_log!("failed to get continuous help");

View file

@ -96,7 +96,7 @@ pub(crate) fn loc_to_pos(loc: erg_common::error::Location) -> Option<Position> {
Some(start)
}
pub fn _pos_to_loc(pos: Position) -> erg_common::error::Location {
pub fn pos_to_loc(pos: Position) -> erg_common::error::Location {
erg_common::error::Location::range(
pos.line + 1,
pos.character.saturating_sub(1),

View file

@ -314,9 +314,9 @@ impl Ord for Location {
} else if self.ln_begin() == self.ln_end() && other.ln_begin() == other.ln_end() {
// assert_eq!(self.line_begin, other.line_begin);
// assert_eq!(self.line_end, other.line_end);
if self.col_end() < other.col_begin() {
if self.col_end() <= other.col_begin() {
Ordering::Less
} else if other.col_end() < self.col_begin() {
} else if other.col_end() <= self.col_begin() {
Ordering::Greater
} else {
Ordering::Equal

View file

@ -332,6 +332,14 @@ impl Args {
}
}
pub fn last(&self) -> Option<&Expr> {
if self.kw_args.is_empty() {
self.pos_args.last().map(|a| &a.expr)
} else {
self.kw_args.last().map(|a| &a.expr)
}
}
pub fn remove_left_or_key(&mut self, key: &str) -> Option<Expr> {
if !self.pos_args.is_empty() {
Some(self.pos_args.remove(0).expr)