blob: b02f079b7213d59ec0a0ff85220e77251b0e2c38 [file] [log] [blame]
//! 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::{Name, ScopeDef, sym};
use crate::{
completions::Completions,
context::{CompletionContext, LifetimeContext, LifetimeKind},
};
/// Completes lifetimes.
pub(crate) fn complete_lifetime(
acc: &mut Completions,
ctx: &CompletionContext<'_>,
lifetime_ctx: &LifetimeContext,
) {
let &LifetimeContext { kind: LifetimeKind::Lifetime { in_lifetime_param_bound, def }, .. } =
lifetime_ctx
else {
return;
};
ctx.process_all_names_raw(&mut |name, res| {
if matches!(res, ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_))) {
acc.add_lifetime(ctx, name);
}
});
acc.add_lifetime(ctx, Name::new_symbol_root(sym::tick_static));
if !in_lifetime_param_bound
&& def.is_some_and(|def| {
!matches!(def, hir::GenericDef::Function(_) | hir::GenericDef::Impl(_))
})
{
acc.add_lifetime(ctx, Name::new_symbol_root(sym::tick_underscore));
}
}
/// 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;
use crate::tests::{check, check_edit};
#[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
lt 'lifetime
lt 'static
"#]],
);
}
#[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#"
//- minicore: iterator
fn foo() {
'outer: for _ in [{ 'inner: loop { break '$0 } }] {}
}
"#,
expect![[r#"
lb 'inner
"#]],
);
}
}