ide_completion/completions/
lifetime.rs

1//! Completes lifetimes and labels.
2//!
3//! These completions work a bit differently in that they are only shown when what the user types
4//! has a `'` preceding it, as our fake syntax tree is invalid otherwise (due to us not inserting
5//! a lifetime but an ident for obvious reasons).
6//! Due to this all the tests for lifetimes and labels live in this module for the time being as
7//! there is no value in lifting these out into the outline module test since they will either not
8//! show up for normal completions, or they won't show completions other than lifetimes depending
9//! on the fixture input.
10use hir::{Name, ScopeDef, sym};
11
12use crate::{
13    completions::Completions,
14    context::{CompletionContext, LifetimeContext, LifetimeKind},
15};
16
17/// Completes lifetimes.
18pub(crate) fn complete_lifetime(
19    acc: &mut Completions,
20    ctx: &CompletionContext<'_>,
21    lifetime_ctx: &LifetimeContext,
22) {
23    let &LifetimeContext { kind: LifetimeKind::Lifetime { in_lifetime_param_bound, def }, .. } =
24        lifetime_ctx
25    else {
26        return;
27    };
28
29    ctx.process_all_names_raw(&mut |name, res| {
30        if matches!(res, ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_))) {
31            acc.add_lifetime(ctx, name);
32        }
33    });
34    acc.add_lifetime(ctx, Name::new_symbol_root(sym::tick_static));
35    if !in_lifetime_param_bound
36        && def.is_some_and(|def| {
37            !matches!(def, hir::GenericDef::Function(_) | hir::GenericDef::Impl(_))
38        })
39    {
40        acc.add_lifetime(ctx, Name::new_symbol_root(sym::tick_underscore));
41    }
42}
43
44/// Completes labels.
45pub(crate) fn complete_label(
46    acc: &mut Completions,
47    ctx: &CompletionContext<'_>,
48    lifetime_ctx: &LifetimeContext,
49) {
50    if !matches!(lifetime_ctx, LifetimeContext { kind: LifetimeKind::LabelRef, .. }) {
51        return;
52    }
53    ctx.process_all_names_raw(&mut |name, res| {
54        if let ScopeDef::Label(_) = res {
55            acc.add_label(ctx, name);
56        }
57    });
58}
59
60#[cfg(test)]
61mod tests {
62    use expect_test::expect;
63
64    use crate::tests::{check, check_edit};
65
66    #[test]
67    fn check_lifetime_edit() {
68        check_edit(
69            "'lifetime",
70            r#"
71fn func<'lifetime>(foo: &'li$0) {}
72"#,
73            r#"
74fn func<'lifetime>(foo: &'lifetime) {}
75"#,
76        );
77        cov_mark::check!(completes_if_lifetime_without_idents);
78        check_edit(
79            "'lifetime",
80            r#"
81fn func<'lifetime>(foo: &'$0) {}
82"#,
83            r#"
84fn func<'lifetime>(foo: &'lifetime) {}
85"#,
86        );
87    }
88
89    #[test]
90    fn complete_lifetime_in_ref() {
91        check(
92            r#"
93fn foo<'lifetime>(foo: &'a$0 usize) {}
94"#,
95            expect![[r#"
96                lt 'lifetime
97                lt 'static
98            "#]],
99        );
100    }
101
102    #[test]
103    fn complete_lifetime_in_ref_missing_ty() {
104        check(
105            r#"
106fn foo<'lifetime>(foo: &'a$0) {}
107"#,
108            expect![[r#"
109                lt 'lifetime
110                lt 'static
111            "#]],
112        );
113    }
114    #[test]
115    fn complete_lifetime_in_self_ref() {
116        check(
117            r#"
118struct Foo;
119impl<'r#impl> Foo {
120    fn foo<'func>(&'a$0 self) {}
121}
122"#,
123            expect![[r#"
124                lt 'func
125                lt 'r#impl
126                lt 'static
127            "#]],
128        );
129    }
130
131    #[test]
132    fn complete_lifetime_in_arg_list() {
133        check(
134            r#"
135struct Foo<'lt>;
136fn foo<'lifetime>(_: Foo<'a$0>) {}
137"#,
138            expect![[r#"
139                lt 'lifetime
140                lt 'static
141            "#]],
142        );
143    }
144
145    #[test]
146    fn complete_lifetime_in_where_pred() {
147        check(
148            r#"
149fn foo2<'lifetime, T>() where 'a$0 {}
150"#,
151            expect![[r#"
152                lt 'lifetime
153                lt 'static
154            "#]],
155        );
156    }
157
158    #[test]
159    fn complete_lifetime_in_ty_bound() {
160        check(
161            r#"
162fn foo2<'lifetime, T>() where T: 'a$0 {}
163"#,
164            expect![[r#"
165                lt 'lifetime
166                lt 'static
167            "#]],
168        );
169        check(
170            r#"
171fn foo2<'lifetime, T>() where T: Trait<'a$0> {}
172"#,
173            expect![[r#"
174                lt 'lifetime
175                lt 'static
176            "#]],
177        );
178    }
179
180    #[test]
181    fn dont_complete_lifetime_in_assoc_ty_bound() {
182        check(
183            r#"
184fn foo2<'lifetime, T>() where T: Trait<Item = 'a$0> {}
185"#,
186            expect![[r#""#]],
187        );
188    }
189
190    #[test]
191    fn complete_lifetime_in_param_list() {
192        check(
193            r#"
194fn foo<'$0>() {}
195"#,
196            expect![[r#""#]],
197        );
198        check(
199            r#"
200fn foo<'a$0>() {}
201"#,
202            expect![[r#""#]],
203        );
204        check(
205            r#"
206fn foo<'footime, 'lifetime: 'a$0>() {}
207"#,
208            expect![[r#"
209                lt 'footime
210                lt 'lifetime
211                lt 'static
212            "#]],
213        );
214    }
215
216    #[test]
217    fn check_label_edit() {
218        check_edit(
219            "'label",
220            r#"
221fn foo() {
222    'label: loop {
223        break '$0
224    }
225}
226"#,
227            r#"
228fn foo() {
229    'label: loop {
230        break 'label
231    }
232}
233"#,
234        );
235    }
236
237    #[test]
238    fn complete_label_in_loop() {
239        check(
240            r#"
241fn foo() {
242    'foop: loop {
243        break '$0
244    }
245}
246"#,
247            expect![[r#"
248                lb 'foop
249            "#]],
250        );
251        check(
252            r#"
253fn foo() {
254    'foop: loop {
255        continue '$0
256    }
257}
258"#,
259            expect![[r#"
260                lb 'foop
261            "#]],
262        );
263    }
264
265    #[test]
266    fn complete_label_in_block_nested() {
267        check(
268            r#"
269fn foo() {
270    'foop: {
271        'baap: {
272            break '$0
273        }
274    }
275}
276"#,
277            expect![[r#"
278                lb 'baap
279                lb 'foop
280            "#]],
281        );
282    }
283
284    #[test]
285    fn complete_label_in_loop_with_value() {
286        check(
287            r#"
288fn foo() {
289    'foop: loop {
290        break '$0 i32;
291    }
292}
293"#,
294            expect![[r#"
295                lb 'foop
296            "#]],
297        );
298    }
299
300    #[test]
301    fn complete_label_in_while_cond() {
302        check(
303            r#"
304fn foo() {
305    'outer: while { 'inner: loop { break '$0 } } {}
306}
307"#,
308            expect![[r#"
309                lb 'inner
310                lb 'outer
311            "#]],
312        );
313    }
314
315    #[test]
316    fn complete_label_in_for_iterable() {
317        check(
318            r#"
319//- minicore: iterator
320fn foo() {
321    'outer: for _ in [{ 'inner: loop { break '$0 } }] {}
322}
323"#,
324            expect![[r#"
325                lb 'inner
326            "#]],
327        );
328    }
329}