Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions crates/ide_completion/src/completions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
181 changes: 181 additions & 0 deletions crates/ide_completion/src/completions/lifetime.rs
Original file line number Diff line number Diff line change
@@ -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
"#]],
);
}
}
2 changes: 1 addition & 1 deletion crates/ide_completion/src/completions/pattern.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Completes constats and paths in patterns.
//! Completes constants and paths in patterns.

use crate::{CompletionContext, Completions};

Expand Down
34 changes: 31 additions & 3 deletions crates/ide_completion/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {} }`.
Expand Down Expand Up @@ -445,18 +456,35 @@ 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,
name_ref: ast::NameRef,
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();
Expand Down
1 change: 1 addition & 0 deletions crates/ide_completion/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down