summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/ide-completion/src/completions/lifetime.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-completion/src/completions/lifetime.rs')
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/lifetime.rs341
1 files changed, 341 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/lifetime.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/lifetime.rs
new file mode 100644
index 000000000..3b79def63
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/lifetime.rs
@@ -0,0 +1,341 @@
+//! Completes lifetimes and labels.
+//!
+//! These completions work a bit differently in that they are only shown when what the user types
+//! has a `'` preceding it, as our fake syntax tree is invalid otherwise (due to us not inserting
+//! a lifetime but an ident for obvious reasons).
+//! Due to this all the tests for lifetimes and labels live in this module for the time being as
+//! there is no value in lifting these out into the outline module test since they will either not
+//! show up for normal completions, or they won't show completions other than lifetimes depending
+//! on the fixture input.
+use hir::{known, ScopeDef};
+use syntax::{ast, TokenText};
+
+use crate::{
+ completions::Completions,
+ context::{CompletionContext, LifetimeContext, LifetimeKind},
+};
+
+/// Completes lifetimes.
+pub(crate) fn complete_lifetime(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ lifetime_ctx: &LifetimeContext,
+) {
+ let (lp, lifetime) = match lifetime_ctx {
+ LifetimeContext { kind: LifetimeKind::Lifetime, lifetime } => (None, lifetime),
+ LifetimeContext {
+ kind: LifetimeKind::LifetimeParam { is_decl: false, param },
+ lifetime,
+ } => (Some(param), lifetime),
+ _ => return,
+ };
+ let param_lifetime = match (lifetime, lp.and_then(|lp| lp.lifetime())) {
+ (Some(lt), Some(lp)) if lp == lt.clone() => return,
+ (Some(_), Some(lp)) => Some(lp),
+ _ => None,
+ };
+ let param_lifetime = param_lifetime.as_ref().map(ast::Lifetime::text);
+ let param_lifetime = param_lifetime.as_ref().map(TokenText::as_str);
+
+ ctx.process_all_names_raw(&mut |name, res| {
+ if matches!(
+ res,
+ ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_))
+ if param_lifetime != Some(&*name.to_smol_str())
+ ) {
+ acc.add_lifetime(ctx, name);
+ }
+ });
+ if param_lifetime.is_none() {
+ acc.add_lifetime(ctx, known::STATIC_LIFETIME);
+ }
+}
+
+/// Completes labels.
+pub(crate) fn complete_label(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ lifetime_ctx: &LifetimeContext,
+) {
+ if !matches!(lifetime_ctx, LifetimeContext { kind: LifetimeKind::LabelRef, .. }) {
+ return;
+ }
+ ctx.process_all_names_raw(&mut |name, res| {
+ if let ScopeDef::Label(_) = res {
+ acc.add_label(ctx, name);
+ }
+ });
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::tests::{check_edit, completion_list};
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ 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) {}
+"#,
+ );
+ cov_mark::check!(completes_if_lifetime_without_idents);
+ check_edit(
+ "'lifetime",
+ r#"
+fn func<'lifetime>(foo: &'$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<'$0>() {}
+"#,
+ expect![[r#""#]],
+ );
+ check(
+ r#"
+fn foo<'a$0>() {}
+"#,
+ expect![[r#""#]],
+ );
+ check(
+ r#"
+fn foo<'footime, 'lifetime: 'a$0>() {}
+"#,
+ expect![[r#"
+ lt 'footime
+ "#]],
+ );
+ }
+
+ #[test]
+ fn check_label_edit() {
+ check_edit(
+ "'label",
+ r#"
+fn foo() {
+ 'label: loop {
+ break '$0
+ }
+}
+"#,
+ r#"
+fn foo() {
+ 'label: loop {
+ break 'label
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn complete_label_in_loop() {
+ check(
+ r#"
+fn foo() {
+ 'foop: loop {
+ break '$0
+ }
+}
+"#,
+ expect![[r#"
+ lb 'foop
+ "#]],
+ );
+ check(
+ r#"
+fn foo() {
+ 'foop: loop {
+ continue '$0
+ }
+}
+"#,
+ expect![[r#"
+ lb 'foop
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_label_in_block_nested() {
+ check(
+ r#"
+fn foo() {
+ 'foop: {
+ 'baap: {
+ break '$0
+ }
+ }
+}
+"#,
+ expect![[r#"
+ lb 'baap
+ lb 'foop
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_label_in_loop_with_value() {
+ check(
+ r#"
+fn foo() {
+ 'foop: loop {
+ break '$0 i32;
+ }
+}
+"#,
+ expect![[r#"
+ lb 'foop
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_label_in_while_cond() {
+ check(
+ r#"
+fn foo() {
+ 'outer: while { 'inner: loop { break '$0 } } {}
+}
+"#,
+ expect![[r#"
+ lb 'inner
+ lb 'outer
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_label_in_for_iterable() {
+ check(
+ r#"
+fn foo() {
+ 'outer: for _ in [{ 'inner: loop { break '$0 } }] {}
+}
+"#,
+ expect![[r#"
+ lb 'inner
+ "#]],
+ );
+ }
+}