1use std::iter;
6
7use ide_db::{FxHashMap, famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty};
8use itertools::Itertools;
9use syntax::{SmolStr, format_smolstr};
10use syntax::{
11 SyntaxKind, SyntaxToken,
12 ast::{self, AstNode, HasGenericParams, HasName},
13};
14
15use crate::{
16 InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind, LifetimeElisionHints,
17 inlay_hints::InlayHintCtx,
18};
19
20pub(super) fn fn_hints(
21 acc: &mut Vec<InlayHint>,
22 ctx: &mut InlayHintCtx,
23 fd: &FamousDefs<'_, '_>,
24 config: &InlayHintsConfig<'_>,
25 func: ast::Fn,
26) -> Option<()> {
27 if config.lifetime_elision_hints == LifetimeElisionHints::Never {
28 return None;
29 }
30
31 let param_list = func.param_list()?;
32 let generic_param_list = func.generic_param_list();
33 let ret_type = func.ret_type();
34 let self_param = param_list.self_param().filter(|it| it.amp_token().is_some());
35 let gpl_append_range = func.name()?.syntax().text_range();
36 hints_(
37 acc,
38 ctx,
39 fd,
40 config,
41 param_list.params().filter_map(|it| {
42 Some((
43 it.pat().and_then(|it| match it {
44 ast::Pat::IdentPat(p) => p.name(),
45 _ => None,
46 }),
47 it.ty()?,
48 ))
49 }),
50 generic_param_list,
51 ret_type,
52 self_param,
53 |acc, allocated_lifetimes| {
54 acc.push(InlayHint {
55 range: gpl_append_range,
56 kind: InlayKind::GenericParamList,
57 label: format!("<{}>", allocated_lifetimes.iter().format(", "),).into(),
58 text_edit: None,
59 position: InlayHintPosition::After,
60 pad_left: false,
61 pad_right: false,
62 resolve_parent: None,
63 })
64 },
65 true,
66 )
67}
68
69pub(super) fn fn_ptr_hints(
70 acc: &mut Vec<InlayHint>,
71 ctx: &mut InlayHintCtx,
72 fd: &FamousDefs<'_, '_>,
73 config: &InlayHintsConfig<'_>,
74 func: ast::FnPtrType,
75) -> Option<()> {
76 if config.lifetime_elision_hints == LifetimeElisionHints::Never {
77 return None;
78 }
79
80 let parent_for_binder = func
81 .syntax()
82 .ancestors()
83 .skip(1)
84 .take_while(|it| matches!(it.kind(), SyntaxKind::PAREN_TYPE | SyntaxKind::FOR_TYPE))
85 .find_map(ast::ForType::cast)
86 .and_then(|it| it.for_binder());
87
88 let param_list = func.param_list()?;
89 let generic_param_list = parent_for_binder.as_ref().and_then(|it| it.generic_param_list());
90 let ret_type = func.ret_type();
91 let for_kw = parent_for_binder.as_ref().and_then(|it| it.for_token());
92 hints_(
93 acc,
94 ctx,
95 fd,
96 config,
97 param_list.params().filter_map(|it| {
98 Some((
99 it.pat().and_then(|it| match it {
100 ast::Pat::IdentPat(p) => p.name(),
101 _ => None,
102 }),
103 it.ty()?,
104 ))
105 }),
106 generic_param_list,
107 ret_type,
108 None,
109 |acc, allocated_lifetimes| {
110 let has_for = for_kw.is_some();
111 let for_ = if has_for { "" } else { "for" };
112 acc.push(InlayHint {
113 range: for_kw.map_or_else(
114 || func.syntax().first_token().unwrap().text_range(),
115 |it| it.text_range(),
116 ),
117 kind: InlayKind::GenericParamList,
118 label: format!("{for_}<{}>", allocated_lifetimes.iter().format(", "),).into(),
119 text_edit: None,
120 position: if has_for {
121 InlayHintPosition::After
122 } else {
123 InlayHintPosition::Before
124 },
125 pad_left: false,
126 pad_right: true,
127 resolve_parent: None,
128 });
129 },
130 false,
131 )
132}
133
134pub(super) fn fn_path_hints(
135 acc: &mut Vec<InlayHint>,
136 ctx: &mut InlayHintCtx,
137 fd: &FamousDefs<'_, '_>,
138 config: &InlayHintsConfig<'_>,
139 func: &ast::PathType,
140) -> Option<()> {
141 if config.lifetime_elision_hints == LifetimeElisionHints::Never {
142 return None;
143 }
144
145 let (param_list, ret_type) = func.path().as_ref().and_then(path_as_fn)?;
147 let parent_for_binder = func
148 .syntax()
149 .ancestors()
150 .skip(1)
151 .take_while(|it| matches!(it.kind(), SyntaxKind::PAREN_TYPE | SyntaxKind::FOR_TYPE))
152 .find_map(ast::ForType::cast)
153 .and_then(|it| it.for_binder());
154
155 let generic_param_list = parent_for_binder.as_ref().and_then(|it| it.generic_param_list());
156 let for_kw = parent_for_binder.as_ref().and_then(|it| it.for_token());
157 hints_(
158 acc,
159 ctx,
160 fd,
161 config,
162 param_list.type_args().filter_map(|it| Some((None, it.ty()?))),
163 generic_param_list,
164 ret_type,
165 None,
166 |acc, allocated_lifetimes| {
167 let has_for = for_kw.is_some();
168 let for_ = if has_for { "" } else { "for" };
169 acc.push(InlayHint {
170 range: for_kw.map_or_else(
171 || func.syntax().first_token().unwrap().text_range(),
172 |it| it.text_range(),
173 ),
174 kind: InlayKind::GenericParamList,
175 label: format!("{for_}<{}>", allocated_lifetimes.iter().format(", "),).into(),
176 text_edit: None,
177 position: if has_for {
178 InlayHintPosition::After
179 } else {
180 InlayHintPosition::Before
181 },
182 pad_left: false,
183 pad_right: true,
184 resolve_parent: None,
185 });
186 },
187 false,
188 )
189}
190
191fn path_as_fn(path: &ast::Path) -> Option<(ast::ParenthesizedArgList, Option<ast::RetType>)> {
192 path.segment().and_then(|it| it.parenthesized_arg_list().zip(Some(it.ret_type())))
193}
194
195fn hints_(
196 acc: &mut Vec<InlayHint>,
197 ctx: &mut InlayHintCtx,
198 FamousDefs(_, _): &FamousDefs<'_, '_>,
199 config: &InlayHintsConfig<'_>,
200 params: impl Iterator<Item = (Option<ast::Name>, ast::Type)>,
201 generic_param_list: Option<ast::GenericParamList>,
202 ret_type: Option<ast::RetType>,
203 self_param: Option<ast::SelfParam>,
204 on_missing_gpl: impl FnOnce(&mut Vec<InlayHint>, &[SmolStr]),
205 mut is_trivial: bool,
206) -> Option<()> {
207 let is_elided = |lt: &Option<ast::Lifetime>| match lt {
208 Some(lt) => matches!(lt.text().as_str(), "'_"),
209 None => true,
210 };
211
212 let mk_lt_hint = |t: SyntaxToken, label: String| InlayHint {
213 range: t.text_range(),
214 kind: InlayKind::Lifetime,
215 label: label.into(),
216 text_edit: None,
217 position: InlayHintPosition::After,
218 pad_left: false,
219 pad_right: true,
220 resolve_parent: None,
221 };
222
223 let potential_lt_refs = {
224 let mut acc: Vec<_> = vec![];
225 if let Some(self_param) = &self_param {
226 let lifetime = self_param.lifetime();
227 let is_elided = is_elided(&lifetime);
228 acc.push((None, self_param.amp_token(), lifetime, is_elided));
229 }
230 params.for_each(|(name, ty)| {
231 walk_ty(&ty, &mut |ty| match ty {
233 ast::Type::RefType(r) => {
234 let lifetime = r.lifetime();
235 let is_elided = is_elided(&lifetime);
236 acc.push((name.clone(), r.amp_token(), lifetime, is_elided));
237 false
238 }
239 ast::Type::FnPtrType(_) => {
240 is_trivial = false;
241 true
242 }
243 ast::Type::PathType(t) => {
244 if t.path()
245 .and_then(|it| it.segment())
246 .and_then(|it| it.parenthesized_arg_list())
247 .is_some()
248 {
249 is_trivial = false;
250 true
251 } else {
252 false
253 }
254 }
255 _ => false,
256 })
257 });
258 acc
259 };
260
261 let mut used_names: FxHashMap<SmolStr, usize> =
262 ctx.lifetime_stacks.iter().flat_map(|it| it.iter()).cloned().zip(iter::repeat(0)).collect();
263 let mut gen_idx_name = {
265 let mut generic = (0u8..).map(|idx| match idx {
266 idx if idx < 10 => SmolStr::from_iter(['\'', (idx + 48) as char]),
267 idx => format_smolstr!("'{idx}"),
268 });
269 let ctx = &*ctx;
270 move || {
271 generic
272 .by_ref()
273 .find(|s| ctx.lifetime_stacks.iter().flat_map(|it| it.iter()).all(|n| n != s))
274 .unwrap_or_default()
275 }
276 };
277 let mut allocated_lifetimes = vec![];
278
279 {
280 let mut potential_lt_refs = potential_lt_refs.iter().filter(|&&(.., is_elided)| is_elided);
281 if self_param.is_some() && potential_lt_refs.next().is_some() {
282 allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {
283 "'self".into()
285 } else {
286 gen_idx_name()
287 });
288 }
289 potential_lt_refs.for_each(|(name, ..)| {
290 let name = match name {
291 Some(it) if config.param_names_for_lifetime_elision_hints => {
292 if let Some(c) = used_names.get_mut(it.text().as_str()) {
293 *c += 1;
294 format_smolstr!("'{}{c}", it.text().as_str())
295 } else {
296 used_names.insert(it.text().as_str().into(), 0);
297 format_smolstr!("'{}", it.text().as_str())
298 }
299 }
300 _ => gen_idx_name(),
301 };
302 allocated_lifetimes.push(name);
303 });
304 }
305
306 let output = match potential_lt_refs.as_slice() {
308 [(_, _, lifetime, _), ..] if self_param.is_some() || potential_lt_refs.len() == 1 => {
309 match lifetime {
310 Some(lt) => match lt.text().as_str() {
311 "'_" => allocated_lifetimes.first().cloned(),
312 "'static" => None,
313 name => Some(name.into()),
314 },
315 None => allocated_lifetimes.first().cloned(),
316 }
317 }
318 [..] => None,
319 };
320
321 if allocated_lifetimes.is_empty() && output.is_none() {
322 return None;
323 }
324
325 if let (Some(output_lt), Some(r)) = (&output, ret_type)
328 && let Some(ty) = r.ty()
329 {
330 walk_ty(&ty, &mut |ty| match ty {
331 ast::Type::RefType(ty) if ty.lifetime().is_none() => {
332 if let Some(amp) = ty.amp_token() {
333 is_trivial = false;
334 acc.push(mk_lt_hint(amp, output_lt.to_string()));
335 }
336 false
337 }
338 ast::Type::FnPtrType(_) => {
339 is_trivial = false;
340 true
341 }
342 ast::Type::PathType(t) => {
343 if t.path()
344 .and_then(|it| it.segment())
345 .and_then(|it| it.parenthesized_arg_list())
346 .is_some()
347 {
348 is_trivial = false;
349 true
350 } else {
351 false
352 }
353 }
354 _ => false,
355 })
356 }
357
358 if config.lifetime_elision_hints == LifetimeElisionHints::SkipTrivial && is_trivial {
359 return None;
360 }
361
362 let mut a = allocated_lifetimes.iter();
363 for (_, amp_token, _, is_elided) in potential_lt_refs {
364 if is_elided {
365 let t = amp_token?;
366 let lt = a.next()?;
367 acc.push(mk_lt_hint(t, lt.to_string()));
368 }
369 }
370
371 match (generic_param_list, allocated_lifetimes.as_slice()) {
373 (_, []) => (),
374 (Some(gpl), allocated_lifetimes) => {
375 let angle_tok = gpl.l_angle_token()?;
376 let is_empty = gpl.generic_params().next().is_none();
377 acc.push(InlayHint {
378 range: angle_tok.text_range(),
379 kind: InlayKind::Lifetime,
380 label: format!(
381 "{}{}",
382 allocated_lifetimes.iter().format(", "),
383 if is_empty { "" } else { ", " }
384 )
385 .into(),
386 text_edit: None,
387 position: InlayHintPosition::After,
388 pad_left: false,
389 pad_right: true,
390 resolve_parent: None,
391 });
392 }
393 (None, allocated_lifetimes) => on_missing_gpl(acc, allocated_lifetimes),
394 }
395 if let Some(stack) = ctx.lifetime_stacks.last_mut() {
396 stack.extend(allocated_lifetimes);
397 }
398 Some(())
399}
400
401#[cfg(test)]
402mod tests {
403 use crate::{
404 InlayHintsConfig, LifetimeElisionHints,
405 inlay_hints::tests::{TEST_CONFIG, check, check_with_config},
406 };
407
408 #[test]
409 fn hints_lifetimes() {
410 check(
411 r#"
412fn empty() {}
413
414fn no_gpl(a: &()) {}
415 //^^^^^^<'0>
416 // ^'0
417fn empty_gpl<>(a: &()) {}
418 // ^'0 ^'0
419fn partial<'b>(a: &(), b: &'b ()) {}
420// ^'0, $ ^'0
421fn partial<'a>(a: &'a (), b: &()) {}
422// ^'0, $ ^'0
423
424fn single_ret(a: &()) -> &() {}
425// ^^^^^^^^^^<'0>
426 // ^'0 ^'0
427fn full_mul(a: &(), b: &()) {}
428// ^^^^^^^^<'0, '1>
429 // ^'0 ^'1
430
431fn foo<'c>(a: &'c ()) -> &() {}
432 // ^'c
433
434fn nested_in(a: & &X< &()>) {}
435// ^^^^^^^^^<'0, '1, '2>
436 //^'0 ^'1 ^'2
437fn nested_out(a: &()) -> & &X< &()>{}
438// ^^^^^^^^^^<'0>
439 //^'0 ^'0 ^'0 ^'0
440
441impl () {
442 fn foo(&self) {}
443 // ^^^<'0>
444 // ^'0
445 fn foo(&self) -> &() {}
446 // ^^^<'0>
447 // ^'0 ^'0
448 fn foo(&self, a: &()) -> &() {}
449 // ^^^<'0, '1>
450 // ^'0 ^'1 ^'0
451}
452"#,
453 );
454 }
455
456 #[test]
457 fn hints_lifetimes_named() {
458 check_with_config(
459 InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG },
460 r#"
461fn nested_in<'named>(named: & &X< &()>) {}
462// ^'named1, 'named2, 'named3, $
463 //^'named1 ^'named2 ^'named3
464"#,
465 );
466 }
467
468 #[test]
469 fn hints_lifetimes_trivial_skip() {
470 check_with_config(
471 InlayHintsConfig {
472 lifetime_elision_hints: LifetimeElisionHints::SkipTrivial,
473 ..TEST_CONFIG
474 },
475 r#"
476fn no_gpl(a: &()) {}
477fn empty_gpl<>(a: &()) {}
478fn partial<'b>(a: &(), b: &'b ()) {}
479fn partial<'a>(a: &'a (), b: &()) {}
480
481fn single_ret(a: &()) -> &() {}
482// ^^^^^^^^^^<'0>
483 // ^'0 ^'0
484fn full_mul(a: &(), b: &()) {}
485
486fn foo<'c>(a: &'c ()) -> &() {}
487 // ^'c
488
489fn nested_in(a: & &X< &()>) {}
490fn nested_out(a: &()) -> & &X< &()>{}
491// ^^^^^^^^^^<'0>
492 //^'0 ^'0 ^'0 ^'0
493
494impl () {
495 fn foo(&self) {}
496 fn foo(&self) -> &() {}
497 // ^^^<'0>
498 // ^'0 ^'0
499 fn foo(&self, a: &()) -> &() {}
500 // ^^^<'0, '1>
501 // ^'0 ^'1 ^'0
502}
503"#,
504 );
505 }
506
507 #[test]
508 fn no_collide() {
509 check_with_config(
510 InlayHintsConfig {
511 lifetime_elision_hints: LifetimeElisionHints::Always,
512 param_names_for_lifetime_elision_hints: true,
513 ..TEST_CONFIG
514 },
515 r#"
516impl<'foo> {
517 fn foo(foo: &()) {}
518 // ^^^ <'foo1>
519 // ^ 'foo1
520}
521"#,
522 );
523 }
524
525 #[test]
526 fn hints_lifetimes_fn_ptr() {
527 check_with_config(
528 InlayHintsConfig {
529 lifetime_elision_hints: LifetimeElisionHints::Always,
530 ..TEST_CONFIG
531 },
532 r#"
533fn fn_ptr(a: fn(&()) -> &fn(&()) -> &()) {}
534 //^^ for<'0>
535 //^'0
536 //^'0
537 //^^ for<'1>
538 //^'1
539 //^'1
540fn fn_ptr2(a: for<'a> fn(&()) -> &()) {}
541 //^'0, $
542 //^'0
543 //^'0
544fn fn_trait(a: &impl Fn(&()) -> &()) {}
545// ^^^^^^^^<'0>
546 // ^'0
547 // ^^ for<'1>
548 //^'1
549 // ^'1
550"#,
551 );
552 }
553
554 #[test]
555 fn hints_in_non_gen_defs() {
556 check_with_config(
557 InlayHintsConfig {
558 lifetime_elision_hints: LifetimeElisionHints::Always,
559 ..TEST_CONFIG
560 },
561 r#"
562const _: fn(&()) -> &();
563 //^^ for<'0>
564 //^'0
565 //^'0
566"#,
567 );
568 }
569}