Skip to content

Commit 480c9e4

Browse files
committed
chore: improve method completion
1 parent fb0248f commit 480c9e4

File tree

8 files changed

+120
-45
lines changed

8 files changed

+120
-45
lines changed

crates/els/completion.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -536,10 +536,10 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
536536
self.send_log(format!("CompletionKind: {comp_kind:?}"))?;
537537
let mut result: Vec<CompletionItem> = vec![];
538538
let mut already_appeared = Set::new();
539-
let contexts = if comp_kind.should_be_local() {
540-
self.get_local_ctx(&uri, pos)
539+
let (receiver_t, contexts) = if comp_kind.should_be_local() {
540+
(None, self.get_local_ctx(&uri, pos))
541541
} else {
542-
self.get_receiver_ctxs(&uri, pos)?
542+
self.get_receiver_and_ctxs(&uri, pos)?
543543
};
544544
let offset = match comp_kind {
545545
CompletionKind::RetriggerLocal => 0,
@@ -586,11 +586,6 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
586586
}
587587
_ => None,
588588
});
589-
let receiver_t = comp_kind
590-
.should_be_method()
591-
.then(|| self.get_min_expr(&uri, pos, -2))
592-
.flatten()
593-
.map(|(_, expr)| expr.t());
594589
let Some(mod_ctx) = self.get_mod_ctx(&uri) else {
595590
return Ok(None);
596591
};
@@ -600,10 +595,10 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
600595
}
601596
// only show static methods, if the receiver is a type
602597
if vi.t.is_method()
603-
&& receiver_t.as_ref().map_or(true, |t| {
598+
&& receiver_t.as_ref().map_or(true, |receiver| {
604599
!mod_ctx
605600
.context
606-
.subtype_of(t, vi.t.self_t().unwrap_or(Type::OBJ))
601+
.subtype_of(receiver, vi.t.self_t().unwrap_or(Type::OBJ))
607602
})
608603
{
609604
continue;

crates/els/file_cache.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use erg_common::set::Set;
1818
use erg_common::shared::Shared;
1919
use erg_common::traits::DequeStream;
2020
use erg_compiler::erg_parser::lex::Lexer;
21-
use erg_compiler::erg_parser::token::{Token, TokenCategory, TokenStream};
21+
use erg_compiler::erg_parser::token::{Token, TokenCategory, TokenKind, TokenStream};
2222

2323
use crate::_log;
2424
use crate::server::{ELSResult, RedirectableStdout};
@@ -176,12 +176,25 @@ impl FileCache {
176176
/// a{pos}\n -> \n -> a
177177
pub fn get_symbol(&self, uri: &NormalizedUrl, pos: Position) -> Option<Token> {
178178
let mut token = self.get_token(uri, pos)?;
179+
let mut offset = 0;
179180
while !matches!(token.category(), TokenCategory::Symbol) {
180-
token = self.get_token_relatively(uri, pos, -1)?;
181+
offset -= 1;
182+
token = self.get_token_relatively(uri, pos, offset)?;
181183
}
182184
Some(token)
183185
}
184186

187+
pub fn get_receiver(&self, uri: &NormalizedUrl, attr_marker_pos: Position) -> Option<Token> {
188+
let mut token = self.get_token(uri, attr_marker_pos)?;
189+
let mut offset = 0;
190+
while !matches!(token.kind, TokenKind::Dot | TokenKind::DblColon) {
191+
offset -= 1;
192+
token = self.get_token_relatively(uri, attr_marker_pos, offset)?;
193+
}
194+
offset -= 1;
195+
self.get_token_relatively(uri, attr_marker_pos, offset)
196+
}
197+
185198
pub fn get_token_relatively(
186199
&self,
187200
uri: &NormalizedUrl,
@@ -200,6 +213,11 @@ impl FileCache {
200213
return Some(i);
201214
}
202215
}
216+
for (i, tok) in tokens.iter().enumerate() {
217+
if util::roughly_pos_in_loc(tok, pos) {
218+
return Some(i);
219+
}
220+
}
203221
None
204222
})()?;
205223
let index = (index as isize + offset) as usize;

crates/els/hir_visitor.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ impl<'a> HIRVisitor<'a> {
280280
Expr::Record(record) => self.get_expr_from_record(expr, record, pos),
281281
Expr::Set(set) => self.get_expr_from_set(expr, set, pos),
282282
Expr::Tuple(tuple) => self.get_expr_from_tuple(expr, tuple, pos),
283-
Expr::TypeAsc(type_asc) => self.get_expr(&type_asc.expr, pos),
283+
Expr::TypeAsc(type_asc) => self.get_expr_from_type_asc(expr, type_asc, pos),
284284
Expr::Dummy(dummy) => self.get_expr_from_dummy(dummy, pos),
285285
Expr::Compound(block) | Expr::Code(block) => {
286286
self.get_expr_from_block(block.iter(), pos)
@@ -534,6 +534,17 @@ impl<'a> HIRVisitor<'a> {
534534
} // _ => None, // todo!(),
535535
}
536536
}
537+
538+
fn get_expr_from_type_asc<'e>(
539+
&'e self,
540+
expr: &'e Expr,
541+
type_asc: &'e TypeAscription,
542+
pos: Position,
543+
) -> Option<&Expr> {
544+
self.get_expr(&type_asc.expr, pos)
545+
.or_else(|| self.get_expr(&type_asc.spec.expr, pos))
546+
.or_else(|| self.return_expr_if_contains(expr, pos, type_asc))
547+
}
537548
}
538549

539550
impl<'a> HIRVisitor<'a> {

crates/els/server.rs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use erg_compiler::error::CompileWarning;
2525
use erg_compiler::hir::HIR;
2626
use erg_compiler::lower::ASTLowerer;
2727
use erg_compiler::module::{IRs, ModuleEntry, SharedCompilerResource};
28-
use erg_compiler::ty::HasType;
28+
use erg_compiler::ty::{HasType, Type};
2929

3030
pub use molc::RedirectableStdout;
3131
use molc::{FakeClient, LangServer};
@@ -137,7 +137,7 @@ impl From<&str> for OptionalFeatures {
137137
macro_rules! _log {
138138
($self:ident, $($arg:tt)*) => {
139139
let s = format!($($arg)*);
140-
$self.send_log(format!("{}@{}: {s}", file!(), line!())).unwrap();
140+
let _ = $self.send_log(format!("{}@{}: {s}", file!(), line!()));
141141
};
142142
}
143143

@@ -873,21 +873,19 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
873873
ctxs
874874
}
875875

876-
pub(crate) fn get_receiver_ctxs(
876+
pub(crate) fn get_receiver_and_ctxs(
877877
&self,
878878
uri: &NormalizedUrl,
879879
attr_marker_pos: Position,
880-
) -> ELSResult<Vec<&Context>> {
880+
) -> ELSResult<(Option<Type>, Vec<&Context>)> {
881881
let Some(module) = self.raw_get_mod_ctx(uri) else {
882-
return Ok(vec![]);
882+
return Ok((None, vec![]));
883883
};
884-
let maybe_token = self
885-
.file_cache
886-
.get_token_relatively(uri, attr_marker_pos, -2);
884+
let maybe_token = self.file_cache.get_receiver(uri, attr_marker_pos);
887885
if let Some(token) = maybe_token {
888-
// self.send_log(format!("token: {token}"))?;
886+
// _log!(self, "token: {token}");
889887
let mut ctxs = vec![];
890-
if let Some(visitor) = self.get_visitor(uri) {
888+
let expr = if let Some(visitor) = self.get_visitor(uri) {
891889
if let Some(expr) =
892890
loc_to_pos(token.loc()).and_then(|pos| visitor.get_min_expr(pos))
893891
{
@@ -902,14 +900,18 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
902900
{
903901
ctxs.extend(singular_ctxs);
904902
}
903+
Some(expr.t())
905904
} else {
906905
_log!(self, "expr not found: {token}");
906+
None
907907
}
908-
}
909-
Ok(ctxs)
908+
} else {
909+
None
910+
};
911+
Ok((expr, ctxs))
910912
} else {
911913
self.send_log("token not found")?;
912-
Ok(vec![])
914+
Ok((None, vec![]))
913915
}
914916
}
915917

crates/els/tests/test.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const FILE_C: &str = "tests/c.er";
1111
const FILE_IMPORTS: &str = "tests/imports.er";
1212
const FILE_INVALID_SYNTAX: &str = "tests/invalid_syntax.er";
1313
const FILE_RETRIGGER: &str = "tests/retrigger.er";
14+
const FILE_TOLERANT_COMPLETION: &str = "tests/tolerant_completion.er";
1415

1516
use els::{NormalizedUrl, Server};
1617
use erg_proc_macros::exec_new_thread;
@@ -129,6 +130,41 @@ fn test_completion_retrigger() -> Result<(), Box<dyn std::error::Error>> {
129130
Ok(())
130131
}
131132

133+
#[test]
134+
fn test_tolerant_completion() -> Result<(), Box<dyn std::error::Error>> {
135+
let mut client = Server::bind_fake_client();
136+
client.request_initialize()?;
137+
client.notify_initialized()?;
138+
let uri = NormalizedUrl::from_file_path(Path::new(FILE_TOLERANT_COMPLETION).canonicalize()?)?;
139+
client.notify_open(FILE_TOLERANT_COMPLETION)?;
140+
let _ = client.wait_diagnostics()?;
141+
client.notify_change(uri.clone().raw(), add_char(5, 16, "."))?;
142+
let resp = client.request_completion(uri.clone().raw(), 5, 17, ".")?;
143+
if let Some(CompletionResponse::Array(items)) = resp {
144+
assert!(items.len() >= 40);
145+
assert!(items.iter().any(|item| item.label == "capitalize"));
146+
} else {
147+
return Err(format!("not items: {resp:?}").into());
148+
}
149+
client.notify_change(uri.clone().raw(), add_char(5, 14, "."))?;
150+
let resp = client.request_completion(uri.clone().raw(), 5, 15, ".")?;
151+
if let Some(CompletionResponse::Array(items)) = resp {
152+
assert!(items.len() >= 40);
153+
assert!(items.iter().any(|item| item.label == "abs"));
154+
} else {
155+
return Err(format!("not items: {resp:?}").into());
156+
}
157+
client.notify_change(uri.clone().raw(), add_char(2, 9, "."))?;
158+
let resp = client.request_completion(uri.raw(), 2, 10, ".")?;
159+
if let Some(CompletionResponse::Array(items)) = resp {
160+
assert!(items.len() >= 10);
161+
assert!(items.iter().any(|item| item.label == "tqdm"));
162+
Ok(())
163+
} else {
164+
Err(format!("not items: {resp:?}").into())
165+
}
166+
}
167+
132168
#[test]
133169
#[exec_new_thread]
134170
fn test_rename() -> Result<(), Box<dyn std::error::Error>> {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
tqdm = pyimport "tqdm"
2+
3+
f _: tqdm
4+
i = 1
5+
s = "a"
6+
g() = None + i s i

crates/erg_compiler/declare.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,7 @@ impl<A: ASTBuildable> GenericASTLowerer<A> {
990990
.map(|op| op.is_import())
991991
.unwrap_or(false) =>
992992
{
993-
Ok(hir::Expr::Call(self.lower_call(call, None)?))
993+
Ok(hir::Expr::Call(self.lower_call(call, None)))
994994
}
995995
other => Err(LowerErrors::from(LowerError::declare_error(
996996
self.cfg().input.clone(),

crates/erg_compiler/lower.rs

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,11 +1267,7 @@ impl<A: ASTBuildable> GenericASTLowerer<A> {
12671267

12681268
/// returning `Ok(call)` does not mean the call is valid, just means it is syntactically valid
12691269
/// `ASTLowerer` is designed to cause as little information loss in HIR as possible
1270-
pub(crate) fn lower_call(
1271-
&mut self,
1272-
call: ast::Call,
1273-
expect: Option<&Type>,
1274-
) -> LowerResult<hir::Call> {
1270+
pub(crate) fn lower_call(&mut self, call: ast::Call, expect: Option<&Type>) -> hir::Call {
12751271
log!(info "entered {}({}{}(...))", fn_name!(), call.obj, fmt_option!(call.attr_name));
12761272
let pushed = if let (Some(name), None) = (call.obj.get_name(), &call.attr_name) {
12771273
self.module.context.higher_order_caller.push(name.clone());
@@ -1288,7 +1284,7 @@ impl<A: ASTBuildable> GenericASTLowerer<A> {
12881284
self.module.context.higher_order_caller.pop();
12891285
}
12901286
errs.extend(es);
1291-
return Err(errs);
1287+
hir::Expr::Dummy(hir::Dummy::empty())
12921288
}
12931289
};
12941290
let opt_vi = self.module.context.get_call_t_without_args(
@@ -1375,10 +1371,12 @@ impl<A: ASTBuildable> GenericASTLowerer<A> {
13751371
self.module.context.higher_order_caller.pop();
13761372
}
13771373
if errs.is_empty() {
1378-
self.exec_additional_op(&mut call)?;
1374+
if let Err(es) = self.exec_additional_op(&mut call) {
1375+
errs.extend(es);
1376+
}
13791377
}
13801378
self.errs.extend(errs);
1381-
Ok(call)
1379+
call
13821380
}
13831381

13841382
/// importing is done in [preregister](https://github.com/erg-lang/erg/blob/ffd33015d540ff5a0b853b28c01370e46e0fcc52/crates/erg_compiler/context/register.rs#L819)
@@ -2676,14 +2674,27 @@ impl<A: ASTBuildable> GenericASTLowerer<A> {
26762674
) -> LowerResult<hir::TypeAscription> {
26772675
log!(info "entered {}({tasc})", fn_name!());
26782676
let kind = tasc.kind();
2679-
let spec_t = self
2677+
let spec_t = match self
26802678
.module
26812679
.context
2682-
.instantiate_typespec(&tasc.t_spec.t_spec)?;
2680+
.instantiate_typespec(&tasc.t_spec.t_spec)
2681+
{
2682+
Ok(spec_t) => spec_t,
2683+
Err(errs) => {
2684+
self.errs.extend(errs);
2685+
Type::Failure
2686+
}
2687+
};
26832688
let expect = expect.map_or(Some(&spec_t), |exp| {
26842689
self.module.context.min(exp, &spec_t).ok().or(Some(&spec_t))
26852690
});
2686-
let expr = self.lower_expr(*tasc.expr, expect)?;
2691+
let expr = match self.lower_expr(*tasc.expr, expect) {
2692+
Ok(expr) => expr,
2693+
Err(errs) => {
2694+
self.errs.extend(errs);
2695+
hir::Expr::Dummy(hir::Dummy::new(vec![]))
2696+
}
2697+
};
26872698
match kind {
26882699
AscriptionKind::TypeOf | AscriptionKind::AsCast => {
26892700
self.module.context.sub_unify(
@@ -2825,7 +2836,7 @@ impl<A: ASTBuildable> GenericASTLowerer<A> {
28252836
ast::Expr::Accessor(acc) => hir::Expr::Accessor(self.lower_acc(acc, expect)?),
28262837
ast::Expr::BinOp(bin) => hir::Expr::BinOp(self.lower_bin(bin, expect)),
28272838
ast::Expr::UnaryOp(unary) => hir::Expr::UnaryOp(self.lower_unary(unary, expect)),
2828-
ast::Expr::Call(call) => hir::Expr::Call(self.lower_call(call, expect)?),
2839+
ast::Expr::Call(call) => hir::Expr::Call(self.lower_call(call, expect)),
28292840
ast::Expr::DataPack(pack) => hir::Expr::Call(self.lower_pack(pack, expect)?),
28302841
ast::Expr::Lambda(lambda) => hir::Expr::Lambda(self.lower_lambda(lambda, expect)?),
28312842
ast::Expr::TypeAscription(tasc) => {
@@ -2835,7 +2846,7 @@ impl<A: ASTBuildable> GenericASTLowerer<A> {
28352846
// Checking is also performed for expressions in Dummy. However, it has no meaning in code generation
28362847
ast::Expr::Dummy(dummy) => hir::Expr::Dummy(self.lower_dummy(dummy, expect)?),
28372848
ast::Expr::InlineModule(inline) => {
2838-
hir::Expr::Call(self.lower_inline_module(inline, expect)?)
2849+
hir::Expr::Call(self.lower_inline_module(inline, expect))
28392850
}
28402851
other => {
28412852
log!(err "unreachable: {other}");
@@ -2926,11 +2937,7 @@ impl<A: ASTBuildable> GenericASTLowerer<A> {
29262937
Ok(hir::Dummy::new(hir_dummy))
29272938
}
29282939

2929-
fn lower_inline_module(
2930-
&mut self,
2931-
inline: InlineModule,
2932-
expect: Option<&Type>,
2933-
) -> LowerResult<hir::Call> {
2940+
fn lower_inline_module(&mut self, inline: InlineModule, expect: Option<&Type>) -> hir::Call {
29342941
log!(info "entered {}", fn_name!());
29352942
let Some(ast::Expr::Literal(mod_name)) = inline.import.args.get_left_or_key("Path") else {
29362943
unreachable!();

0 commit comments

Comments
 (0)