From 3c000c6364ebcf94652d221ee9ffe8970540589c Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 20 Mar 2021 22:43:42 +0100 Subject: [PATCH] Add basic lifetime completion --- crates/ide_completion/src/completions.rs | 29 ++- .../src/completions/lifetime.rs | 181 ++++++++++++++++++ .../ide_completion/src/completions/pattern.rs | 2 +- crates/ide_completion/src/context.rs | 34 +++- crates/ide_completion/src/lib.rs | 1 + 5 files changed, 233 insertions(+), 14 deletions(-) create mode 100644 crates/ide_completion/src/completions/lifetime.rs diff --git a/crates/ide_completion/src/completions.rs b/crates/ide_completion/src/completions.rs index 09882c4f310..cdac4e41a9d 100644 --- a/crates/ide_completion/src/completions.rs +++ b/crates/ide_completion/src/completions.rs @@ -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 @@ type_alias::render_type_alias, RenderContext, }, - CompletionContext, CompletionItem, + CompletionContext, CompletionItem, CompletionItemKind, }; /// Represents an in-progress set of completions being built. @@ -77,6 +79,13 @@ pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, 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, diff --git a/crates/ide_completion/src/completions/lifetime.rs b/crates/ide_completion/src/completions/lifetime.rs new file mode 100644 index 00000000000..74eb233602a --- /dev/null +++ b/crates/ide_completion/src/completions/lifetime.rs @@ -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 {} +"#, + 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 + "#]], + ); + } +} diff --git a/crates/ide_completion/src/completions/pattern.rs b/crates/ide_completion/src/completions/pattern.rs index 476eecff0e1..b06498e6da4 100644 --- a/crates/ide_completion/src/completions/pattern.rs +++ b/crates/ide_completion/src/completions/pattern.rs @@ -1,4 +1,4 @@ -//! Completes constats and paths in patterns. +//! Completes constants and paths in patterns. use crate::{CompletionContext, Completions}; diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index 6d57da06af9..3c31de9ada1 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs @@ -38,12 +38,15 @@ pub(crate) struct CompletionContext<'a> { pub(super) expected_name: Option, pub(super) expected_type: Option, pub(super) name_ref_syntax: Option, + pub(super) lifetime_syntax: Option, + pub(super) lifetime_param_syntax: Option, pub(super) function_syntax: Option, pub(super) use_item_syntax: Option, pub(super) record_lit_syntax: Option, pub(super) record_pat_syntax: Option, pub(super) record_field_syntax: Option, pub(super) impl_def: Option, + pub(super) lifetime_allowed: bool, /// FIXME: `ActiveParameter` is string-based, which is very very wrong pub(super) active_parameter: Option, pub(super) is_param: bool, @@ -136,9 +139,12 @@ pub(super) fn new( 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 @@ pub(crate) fn no_completion_required(&self) -> bool { 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 @@ fn fill( 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::(&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::(&file_with_fake_ident, offset) { // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. @@ -445,6 +456,23 @@ fn fill( } } + 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 @@ fn classify_name_ref( 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(); diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index d9ea7b7ea10..7a0eb6a9662 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs @@ -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) }