Add basic lifetime completion

This commit is contained in:
Lukas Wirth 2021-03-20 22:43:42 +01:00
parent 5cc8ad0c4a
commit 3c000c6364
5 changed files with 233 additions and 14 deletions

View file

@ -2,25 +2,27 @@
pub(crate) mod attribute;
pub(crate) mod dot;
pub(crate) mod record;
pub(crate) mod pattern;
pub(crate) mod flyimport;
pub(crate) mod fn_param;
pub(crate) mod keyword;
pub(crate) mod snippet;
pub(crate) mod qualified_path;
pub(crate) mod unqualified_path;
pub(crate) mod postfix;
pub(crate) mod lifetime;
pub(crate) mod macro_in_item_position;
pub(crate) mod trait_impl;
pub(crate) mod mod_;
pub(crate) mod flyimport;
pub(crate) mod pattern;
pub(crate) mod postfix;
pub(crate) mod qualified_path;
pub(crate) mod record;
pub(crate) mod snippet;
pub(crate) mod trait_impl;
pub(crate) mod unqualified_path;
use std::iter;
use hir::{known, ModPath, ScopeDef, Type};
use ide_db::SymbolKind;
use crate::{
item::Builder,
item::{Builder, CompletionKind},
render::{
const_::render_const,
enum_variant::render_variant,
@ -31,7 +33,7 @@ use crate::{
type_alias::render_type_alias,
RenderContext,
},
CompletionContext, CompletionItem,
CompletionContext, CompletionItem, CompletionItemKind,
};
/// Represents an in-progress set of completions being built.
@ -77,6 +79,13 @@ impl Completions {
self.add(item);
}
pub(crate) fn add_static_lifetime(&mut self, ctx: &CompletionContext) {
let mut item =
CompletionItem::new(CompletionKind::Reference, ctx.source_range(), "'static");
item.kind(CompletionItemKind::SymbolKind(SymbolKind::LifetimeParam));
self.add(item.build());
}
pub(crate) fn add_resolution(
&mut self,
ctx: &CompletionContext,

View file

@ -0,0 +1,181 @@
//! Completes lifetimes.
use hir::ScopeDef;
use crate::{completions::Completions, context::CompletionContext};
/// Completes lifetimes.
pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext) {
if !ctx.lifetime_allowed {
return;
}
let param_lifetime = match (
&ctx.lifetime_syntax,
ctx.lifetime_param_syntax.as_ref().and_then(|lp| lp.lifetime()),
) {
(Some(lt), Some(lp)) if lp == lt.clone() => return,
(Some(_), Some(lp)) => Some(lp.to_string()),
_ => None,
};
ctx.scope.process_all_names(&mut |name, res| {
if let ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) = res {
if param_lifetime != Some(name.to_string()) {
acc.add_resolution(ctx, name.to_string(), &res);
}
}
});
if param_lifetime.is_none() {
acc.add_static_lifetime(ctx);
}
}
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
use crate::{
test_utils::{check_edit, completion_list_with_config, TEST_CONFIG},
CompletionConfig, CompletionKind,
};
fn check(ra_fixture: &str, expect: Expect) {
check_with_config(TEST_CONFIG, ra_fixture, expect);
}
fn check_with_config(config: CompletionConfig, ra_fixture: &str, expect: Expect) {
let actual = completion_list_with_config(config, ra_fixture, CompletionKind::Reference);
expect.assert_eq(&actual)
}
#[test]
fn check_lifetime_edit() {
check_edit(
"'lifetime",
r#"
fn func<'lifetime>(foo: &'li$0) {}
"#,
r#"
fn func<'lifetime>(foo: &'lifetime) {}
"#,
);
}
#[test]
fn complete_lifetime_in_ref() {
check(
r#"
fn foo<'lifetime>(foo: &'a$0 usize) {}
"#,
expect![[r#"
lt 'lifetime
lt 'static
"#]],
);
}
#[test]
fn complete_lifetime_in_ref_missing_ty() {
check(
r#"
fn foo<'lifetime>(foo: &'a$0) {}
"#,
expect![[r#"
lt 'lifetime
lt 'static
"#]],
);
}
#[test]
fn complete_lifetime_in_self_ref() {
check(
r#"
struct Foo;
impl<'impl> Foo {
fn foo<'func>(&'a$0 self) {}
}
"#,
expect![[r#"
lt 'func
lt 'impl
lt 'static
"#]],
);
}
#[test]
fn complete_lifetime_in_arg_list() {
check(
r#"
struct Foo<'lt>;
fn foo<'lifetime>(_: Foo<'a$0>) {}
"#,
expect![[r#"
lt 'lifetime
lt 'static
"#]],
);
}
#[test]
fn complete_lifetime_in_where_pred() {
check(
r#"
fn foo2<'lifetime, T>() where 'a$0 {}
"#,
expect![[r#"
lt 'lifetime
lt 'static
"#]],
);
}
#[test]
fn complete_lifetime_in_ty_bound() {
check(
r#"
fn foo2<'lifetime, T>() where T: 'a$0 {}
"#,
expect![[r#"
lt 'lifetime
lt 'static
"#]],
);
check(
r#"
fn foo2<'lifetime, T>() where T: Trait<'a$0> {}
"#,
expect![[r#"
lt 'lifetime
lt 'static
"#]],
);
}
#[test]
fn dont_complete_lifetime_in_assoc_ty_bound() {
check(
r#"
fn foo2<'lifetime, T>() where T: Trait<Item = 'a$0> {}
"#,
expect![[r#""#]],
);
}
#[test]
fn complete_lifetime_in_param_list() {
check(
r#"
fn foo<'a$0>() {}
"#,
expect![[r#""#]],
);
check(
r#"
fn foo<'footime, 'lifetime: 'a$0>() {}
"#,
expect![[r#"
lt 'footime
"#]],
);
}
}

View file

@ -1,4 +1,4 @@
//! Completes constats and paths in patterns.
//! Completes constants and paths in patterns.
use crate::{CompletionContext, Completions};

View file

@ -38,12 +38,15 @@ pub(crate) struct CompletionContext<'a> {
pub(super) expected_name: Option<String>,
pub(super) expected_type: Option<Type>,
pub(super) name_ref_syntax: Option<ast::NameRef>,
pub(super) lifetime_syntax: Option<ast::Lifetime>,
pub(super) lifetime_param_syntax: Option<ast::LifetimeParam>,
pub(super) function_syntax: Option<ast::Fn>,
pub(super) use_item_syntax: Option<ast::Use>,
pub(super) record_lit_syntax: Option<ast::RecordExpr>,
pub(super) record_pat_syntax: Option<ast::RecordPat>,
pub(super) record_field_syntax: Option<ast::RecordExprField>,
pub(super) impl_def: Option<ast::Impl>,
pub(super) lifetime_allowed: bool,
/// FIXME: `ActiveParameter` is string-based, which is very very wrong
pub(super) active_parameter: Option<ActiveParameter>,
pub(super) is_param: bool,
@ -136,9 +139,12 @@ impl<'a> CompletionContext<'a> {
original_token,
token,
krate,
lifetime_allowed: false,
expected_name: None,
expected_type: None,
name_ref_syntax: None,
lifetime_syntax: None,
lifetime_param_syntax: None,
function_syntax: None,
use_item_syntax: None,
record_lit_syntax: None,
@ -241,7 +247,7 @@ impl<'a> CompletionContext<'a> {
pub(crate) fn source_range(&self) -> TextRange {
// check kind of macro-expanded token, but use range of original token
let kind = self.token.kind();
if kind == IDENT || kind == UNDERSCORE || kind.is_keyword() {
if kind == IDENT || kind == LIFETIME_IDENT || kind == UNDERSCORE || kind.is_keyword() {
cov_mark::hit!(completes_if_prefix_is_keyword);
self.original_token.text_range()
} else {
@ -386,6 +392,11 @@ impl<'a> CompletionContext<'a> {
self.expected_name = expected.1;
self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset);
if let Some(lifetime) = find_node_at_offset::<ast::Lifetime>(&file_with_fake_ident, offset)
{
self.classify_lifetime(original_file, lifetime, offset);
}
// First, let's try to complete a reference to some declaration.
if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) {
// Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
@ -445,6 +456,23 @@ impl<'a> CompletionContext<'a> {
}
}
fn classify_lifetime(
&mut self,
original_file: &SyntaxNode,
lifetime: ast::Lifetime,
offset: TextSize,
) {
self.lifetime_syntax =
find_node_at_offset(original_file, lifetime.syntax().text_range().start());
if lifetime.syntax().parent().map_or(false, |p| p.kind() != syntax::SyntaxKind::ERROR) {
self.lifetime_allowed = true;
}
if let Some(_) = lifetime.syntax().parent().and_then(ast::LifetimeParam::cast) {
self.lifetime_param_syntax =
self.sema.find_node_at_offset_with_macros(original_file, offset);
}
}
fn classify_name_ref(
&mut self,
original_file: &SyntaxNode,
@ -452,11 +480,11 @@ impl<'a> CompletionContext<'a> {
offset: TextSize,
) {
self.name_ref_syntax =
find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
find_node_at_offset(original_file, name_ref.syntax().text_range().start());
let name_range = name_ref.syntax().text_range();
if ast::RecordExprField::for_field_name(&name_ref).is_some() {
self.record_lit_syntax =
self.sema.find_node_at_offset_with_macros(&original_file, offset);
self.sema.find_node_at_offset_with_macros(original_file, offset);
}
self.fill_impl_def();

View file

@ -130,6 +130,7 @@ pub fn completions(
completions::trait_impl::complete_trait_impl(&mut acc, &ctx);
completions::mod_::complete_mod(&mut acc, &ctx);
completions::flyimport::import_on_the_fly(&mut acc, &ctx);
completions::lifetime::complete_lifetime(&mut acc, &ctx);
Some(acc)
}