1use std::iter::zip;
8
9use either::Either;
10use hir::{EditionedFileId, Semantics, name};
11use ide_db::{RootDatabase, famous_defs::FamousDefs};
12
13use stdx::to_lower_snake_case;
14use syntax::T;
15use syntax::ast::{self, AstNode, HasArgList, HasName, UnaryOp};
16
17use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind};
18
19pub(super) fn hints(
20 acc: &mut Vec<InlayHint>,
21 FamousDefs(sema, krate): &FamousDefs<'_, '_>,
22 config: &InlayHintsConfig<'_>,
23 file_id: EditionedFileId,
24 expr: ast::Expr,
25) -> Option<()> {
26 if !config.parameter_hints {
27 return None;
28 }
29
30 let (callable, arg_list) = get_callable(sema, &expr)?;
31 let unary_function = callable.n_params() == 1;
32 let function_name = match callable.kind() {
33 hir::CallableKind::Function(function) => Some(function.name(sema.db)),
34 _ => None,
35 };
36 let function_name = function_name.as_ref().map(|it| it.as_str());
37 let hints = callable
38 .params()
39 .into_iter()
40 .zip(arg_list.args_maybe_empty())
41 .filter_map(|(p, arg)| {
42 let arg = arg?;
43 let range = sema.original_range_opt(arg.syntax())?;
45 if range.file_id != file_id {
46 return None;
47 }
48 let param_name = p.name(sema.db)?;
49 Some((p, param_name, arg, range))
50 })
51 .filter(|(_, param_name, arg, _)| {
52 !should_hide_param_name_hint(
53 sema,
54 unary_function,
55 function_name,
56 param_name.as_str(),
57 arg,
58 )
59 })
60 .map(|(param, param_name, _, hir::FileRange { range, .. })| {
61 let colon = if config.render_colons { ":" } else { "" };
62 let label = InlayHintLabel::simple(
63 format!("{}{colon}", param_name.display(sema.db, krate.edition(sema.db))),
64 None,
65 config.lazy_location_opt(|| {
66 let source = sema.source(param)?;
67 let name_syntax = match source.value.as_ref() {
68 Either::Left(pat) => pat.name(),
69 Either::Right(param) => match param.pat()? {
70 ast::Pat::IdentPat(it) => it.name(),
71 _ => None,
72 },
73 }?;
74 sema.original_range_opt(name_syntax.syntax()).map(|frange| ide_db::FileRange {
75 file_id: frange.file_id.file_id(sema.db),
76 range: frange.range,
77 })
78 }),
79 );
80 InlayHint {
81 range,
82 kind: InlayKind::Parameter,
83 label,
84 text_edit: None,
85 position: InlayHintPosition::Before,
86 pad_left: false,
87 pad_right: true,
88 resolve_parent: Some(expr.syntax().text_range()),
89 }
90 });
91
92 acc.extend(hints);
93
94 if config.parameter_hints_for_missing_arguments {
96 let provided_args_count = arg_list.args().count();
97 let params = callable.params();
98 let total_params = params.len();
99
100 if provided_args_count < total_params
101 && let Some(next_param) = params.get(provided_args_count)
102 && let Some(param_name) = next_param.name(sema.db)
103 {
104 if should_hide_missing_param_hint(unary_function, function_name, param_name.as_str()) {
106 return Some(());
107 }
108
109 if let Some(hint_range) = missing_arg_hint_position(&arg_list) {
111 let colon = if config.render_colons { ":" } else { "" };
112 let label = InlayHintLabel::simple(
113 format!("{}{}", param_name.display(sema.db, krate.edition(sema.db)), colon),
114 None,
115 config.lazy_location_opt(|| {
116 let source = sema.source(next_param.clone())?;
117 let name_syntax = match source.value.as_ref() {
118 Either::Left(pat) => pat.name(),
119 Either::Right(param) => match param.pat()? {
120 ast::Pat::IdentPat(it) => it.name(),
121 _ => None,
122 },
123 }?;
124 sema.original_range_opt(name_syntax.syntax()).map(|frange| {
125 ide_db::FileRange {
126 file_id: frange.file_id.file_id(sema.db),
127 range: frange.range,
128 }
129 })
130 }),
131 );
132 acc.push(InlayHint {
133 range: hint_range,
134 kind: InlayKind::Parameter,
135 label,
136 text_edit: None,
137 position: InlayHintPosition::Before,
138 pad_left: true,
139 pad_right: false,
140 resolve_parent: Some(expr.syntax().text_range()),
141 });
142 }
143 }
144 }
145
146 Some(())
147}
148
149fn missing_arg_hint_position(arg_list: &ast::ArgList) -> Option<syntax::TextRange> {
152 arg_list
155 .syntax()
156 .children_with_tokens()
157 .filter_map(|it| it.into_token())
158 .find(|t| t.kind() == T![')'])
159 .map(|t| t.text_range())
160}
161
162fn get_callable<'db>(
163 sema: &Semantics<'db, RootDatabase>,
164 expr: &ast::Expr,
165) -> Option<(hir::Callable<'db>, ast::ArgList)> {
166 match expr {
167 ast::Expr::CallExpr(expr) => {
168 let descended = sema.descend_node_into_attributes(expr.clone()).pop();
169 let expr = descended.as_ref().unwrap_or(expr);
170 sema.type_of_expr(&expr.expr()?)?.original.as_callable(sema.db).zip(expr.arg_list())
171 }
172 ast::Expr::MethodCallExpr(expr) => {
173 let descended = sema.descend_node_into_attributes(expr.clone()).pop();
174 let expr = descended.as_ref().unwrap_or(expr);
175 sema.resolve_method_call_as_callable(expr).zip(expr.arg_list())
176 }
177 _ => None,
178 }
179}
180
181const INSIGNIFICANT_METHOD_NAMES: &[&str] = &["clone", "as_ref", "into"];
182const INSIGNIFICANT_PARAMETER_NAMES: &[&str] =
183 &["predicate", "value", "pat", "rhs", "other", "msg", "op"];
184
185fn should_hide_param_name_hint(
186 sema: &Semantics<'_, RootDatabase>,
187 unary_function: bool,
188 function_name: Option<&str>,
189 param_name: &str,
190 argument: &ast::Expr,
191) -> bool {
192 let param_name = param_name.trim_matches('_');
203 if param_name.is_empty() {
204 return true;
205 }
206
207 if param_name.starts_with("ra_fixture") || name::is_generated(param_name) {
208 return true;
209 }
210
211 if unary_function {
212 if let Some(function_name) = function_name
213 && is_param_name_suffix_of_fn_name(param_name, function_name)
214 {
215 return true;
216 }
217 if is_obvious_param(param_name) {
218 return true;
219 }
220 }
221
222 is_argument_expr_similar_to_param_name(sema, argument, param_name)
223}
224
225fn should_hide_missing_param_hint(
229 unary_function: bool,
230 function_name: Option<&str>,
231 param_name: &str,
232) -> bool {
233 let param_name = param_name.trim_matches('_');
234 if param_name.is_empty() {
235 return true;
236 }
237
238 if param_name.starts_with("ra_fixture") {
239 return true;
240 }
241
242 if unary_function {
243 if let Some(function_name) = function_name
244 && is_param_name_suffix_of_fn_name(param_name, function_name)
245 {
246 return true;
247 }
248 if is_obvious_param(param_name) {
249 return true;
250 }
251 }
252
253 false
254}
255
256fn is_param_name_suffix_of_fn_name(param_name: &str, fn_name: &str) -> bool {
261 fn_name == param_name
262 || fn_name
263 .len()
264 .checked_sub(param_name.len())
265 .and_then(|at| fn_name.is_char_boundary(at).then(|| fn_name.split_at(at)))
266 .is_some_and(|(prefix, suffix)| {
267 suffix.eq_ignore_ascii_case(param_name) && prefix.ends_with('_')
268 })
269}
270
271fn is_argument_expr_similar_to_param_name(
272 sema: &Semantics<'_, RootDatabase>,
273 argument: &ast::Expr,
274 param_name: &str,
275) -> bool {
276 match get_segment_representation(argument) {
277 Some(Either::Left(argument)) => is_argument_similar_to_param_name(&argument, param_name),
278 Some(Either::Right(path)) => {
279 path.segment()
280 .and_then(|it| it.name_ref())
281 .is_some_and(|name_ref| name_ref.text().eq_ignore_ascii_case(param_name))
282 || is_adt_constructor_similar_to_param_name(sema, &path, param_name)
283 }
284 None => false,
285 }
286}
287
288pub(super) fn is_argument_similar_to_param_name(
291 argument: &[ast::NameRef],
292 param_name: &str,
293) -> bool {
294 debug_assert!(!argument.is_empty());
295 debug_assert!(!param_name.is_empty());
296 let param_name = param_name.split('_');
297 let argument = argument.iter().flat_map(|it| it.text_non_mutable().split('_'));
298
299 let prefix_match = zip(argument.clone(), param_name.clone())
300 .all(|(arg, param)| arg.eq_ignore_ascii_case(param));
301 let postfix_match = || {
302 zip(argument.rev(), param_name.rev()).all(|(arg, param)| arg.eq_ignore_ascii_case(param))
303 };
304 prefix_match || postfix_match()
305}
306
307pub(super) fn get_segment_representation(
308 expr: &ast::Expr,
309) -> Option<Either<Vec<ast::NameRef>, ast::Path>> {
310 match expr {
311 ast::Expr::MethodCallExpr(method_call_expr) => {
312 let receiver =
313 method_call_expr.receiver().and_then(|expr| get_segment_representation(&expr));
314 let name_ref = method_call_expr.name_ref()?;
315 if INSIGNIFICANT_METHOD_NAMES.contains(&name_ref.text().as_str()) {
316 return receiver;
317 }
318 Some(Either::Left(match receiver {
319 Some(Either::Left(mut left)) => {
320 left.push(name_ref);
321 left
322 }
323 Some(Either::Right(_)) | None => vec![name_ref],
324 }))
325 }
326 ast::Expr::FieldExpr(field_expr) => {
327 let expr = field_expr.expr().and_then(|expr| get_segment_representation(&expr));
328 let name_ref = field_expr.name_ref()?;
329 let res = match expr {
330 Some(Either::Left(mut left)) => {
331 left.push(name_ref);
332 left
333 }
334 Some(Either::Right(_)) | None => vec![name_ref],
335 };
336 Some(Either::Left(res))
337 }
338 ast::Expr::MacroExpr(macro_expr) => macro_expr.macro_call()?.path().map(Either::Right),
340 ast::Expr::RecordExpr(record_expr) => record_expr.path().map(Either::Right),
341 ast::Expr::PathExpr(path_expr) => {
342 let path = path_expr.path()?;
343 Some(match path.as_single_name_ref() {
345 None => Either::Right(path),
346 Some(name_ref) => Either::Left(vec![name_ref]),
347 })
348 }
349 ast::Expr::PrefixExpr(prefix_expr) if prefix_expr.op_kind() == Some(UnaryOp::Not) => None,
350 ast::Expr::PrefixExpr(prefix_expr) => get_segment_representation(&prefix_expr.expr()?),
352 ast::Expr::RefExpr(ref_expr) => get_segment_representation(&ref_expr.expr()?),
353 ast::Expr::CastExpr(cast_expr) => get_segment_representation(&cast_expr.expr()?),
354 ast::Expr::CallExpr(call_expr) => get_segment_representation(&call_expr.expr()?),
355 ast::Expr::AwaitExpr(await_expr) => get_segment_representation(&await_expr.expr()?),
356 ast::Expr::IndexExpr(index_expr) => get_segment_representation(&index_expr.base()?),
357 ast::Expr::ParenExpr(paren_expr) => get_segment_representation(&paren_expr.expr()?),
358 ast::Expr::TryExpr(try_expr) => get_segment_representation(&try_expr.expr()?),
359 _ => None,
361 }
362}
363
364fn is_obvious_param(param_name: &str) -> bool {
365 param_name.len() == 1 || INSIGNIFICANT_PARAMETER_NAMES.contains(¶m_name)
368}
369
370fn is_adt_constructor_similar_to_param_name(
371 sema: &Semantics<'_, RootDatabase>,
372 path: &ast::Path,
373 param_name: &str,
374) -> bool {
375 (|| match sema.resolve_path(path)? {
376 hir::PathResolution::Def(hir::ModuleDef::Adt(_)) => {
377 Some(to_lower_snake_case(&path.segment()?.name_ref()?.text()) == param_name)
378 }
379 hir::PathResolution::Def(hir::ModuleDef::Function(_) | hir::ModuleDef::EnumVariant(_)) => {
380 if to_lower_snake_case(&path.segment()?.name_ref()?.text()) == param_name {
381 return Some(true);
382 }
383 let qual = path.qualifier()?;
384 match sema.resolve_path(&qual)? {
385 hir::PathResolution::Def(hir::ModuleDef::Adt(_)) => {
386 Some(to_lower_snake_case(&qual.segment()?.name_ref()?.text()) == param_name)
387 }
388 _ => None,
389 }
390 }
391 _ => None,
392 })()
393 .unwrap_or(false)
394}
395
396#[cfg(test)]
397mod tests {
398 use crate::{
399 InlayHintsConfig,
400 inlay_hints::tests::{DISABLED_CONFIG, check_with_config},
401 };
402
403 #[track_caller]
404 fn check_params(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
405 check_with_config(
406 InlayHintsConfig { parameter_hints: true, ..DISABLED_CONFIG },
407 ra_fixture,
408 );
409 }
410
411 #[test]
412 fn param_hints_only() {
413 check_params(
414 r#"
415fn foo(a: i32, b: i32) -> i32 { a + b }
416fn main() {
417 let _x = foo(
418 4,
419 //^ a
420 4,
421 //^ b
422 );
423}"#,
424 );
425 }
426
427 #[test]
428 fn param_hints_on_closure() {
429 check_params(
430 r#"
431//- minicore: fn
432fn main() {
433 let clo = |a: u8, b: u8| a + b;
434 clo(
435 1,
436 //^ a
437 2,
438 //^ b
439 );
440}
441 "#,
442 );
443 }
444
445 #[test]
446 fn param_name_similar_to_fn_name_still_hints() {
447 check_params(
448 r#"
449fn max(x: i32, y: i32) -> i32 { x + y }
450fn main() {
451 let _x = max(
452 4,
453 //^ x
454 4,
455 //^ y
456 );
457}"#,
458 );
459 }
460
461 #[test]
462 fn param_name_similar_to_fn_name() {
463 check_params(
464 r#"
465fn param_with_underscore(with_underscore: i32) -> i32 { with_underscore }
466fn main() {
467 let _x = param_with_underscore(
468 4,
469 );
470}"#,
471 );
472 check_params(
473 r#"
474fn param_with_underscore(underscore: i32) -> i32 { underscore }
475fn main() {
476 let _x = param_with_underscore(
477 4,
478 );
479}"#,
480 );
481 }
482
483 #[test]
484 fn param_name_same_as_fn_name() {
485 check_params(
486 r#"
487fn foo(foo: i32) -> i32 { foo }
488fn main() {
489 let _x = foo(
490 4,
491 );
492}"#,
493 );
494 }
495
496 #[test]
497 fn never_hide_param_when_multiple_params() {
498 check_params(
499 r#"
500fn foo(foo: i32, bar: i32) -> i32 { bar + baz }
501fn main() {
502 let _x = foo(
503 4,
504 //^ foo
505 8,
506 //^ bar
507 );
508}"#,
509 );
510 }
511
512 #[test]
513 fn param_hints_look_through_as_ref_and_clone() {
514 check_params(
515 r#"
516fn foo(bar: i32, baz: f32) {}
517
518fn main() {
519 let bar = 3;
520 let baz = &"baz";
521 let fez = 1.0;
522 foo(bar.clone(), bar.clone());
523 //^^^^^^^^^^^ baz
524 foo(bar.as_ref(), bar.as_ref());
525 //^^^^^^^^^^^^ baz
526}
527"#,
528 );
529 }
530
531 #[test]
532 fn self_param_hints() {
533 check_params(
534 r#"
535struct Foo;
536
537impl Foo {
538 fn foo(self: Self) {}
539 fn bar(self: &Self) {}
540}
541
542fn main() {
543 Foo::foo(Foo);
544 //^^^ self
545 Foo::bar(&Foo);
546 //^^^^ self
547}
548"#,
549 )
550 }
551
552 #[test]
553 fn param_name_hints_show_for_literals() {
554 check_params(
555 r#"pub fn test(a: i32, b: i32) -> [i32; 2] { [a, b] }
556fn main() {
557 test(
558 0xa_b,
559 //^^^^^ a
560 0xa_b,
561 //^^^^^ b
562 );
563}"#,
564 )
565 }
566
567 #[test]
568 fn param_name_hints_show_after_empty_arg() {
569 check_params(
570 r#"pub fn test(a: i32, b: i32, c: i32) {}
571fn main() {
572 test(, 2,);
573 //^ b
574 test(, , 3);
575 //^ c
576}"#,
577 )
578 }
579
580 #[test]
581 fn function_call_parameter_hint() {
582 check_params(
583 r#"
584//- minicore: option
585struct FileId {}
586struct SmolStr {}
587
588struct TextRange {}
589struct SyntaxKind {}
590struct NavigationTarget {}
591
592struct Test {}
593
594impl Test {
595 fn method(&self, mut param: i32) -> i32 { param * 2 }
596
597 fn from_syntax(
598 file_id: FileId,
599 name: SmolStr,
600 focus_range: Option<TextRange>,
601 full_range: TextRange,
602 kind: SyntaxKind,
603 docs: Option<String>,
604 ) -> NavigationTarget {
605 NavigationTarget {}
606 }
607}
608
609fn test_func(mut foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 {
610 foo + bar
611}
612async fn test_async(foo: i32, _: i32) {}
613
614fn main() {
615 let not_literal = 1;
616 let _: i32 = test_func(1, 2, "hello", 3, not_literal);
617 //^ foo ^ bar ^^^^^^^ msg ^^^^^^^^^^^ last
618 let t: Test = Test {};
619 t.method(123);
620 //^^^ param
621 Test::method(&t, 3456);
622 //^^ self ^^^^ param
623 Test::from_syntax(
624 FileId {},
625 "impl".into(),
626 //^^^^^^^^^^^^^ name
627 None,
628 //^^^^ focus_range
629 TextRange {},
630 //^^^^^^^^^^^^ full_range
631 SyntaxKind {},
632 //^^^^^^^^^^^^^ kind
633 None,
634 //^^^^ docs
635 );
636 test_async(1, 2)
637 //^ foo
638}"#,
639 );
640 }
641
642 #[test]
643 fn parameter_hint_heuristics() {
644 check_params(
645 r#"
646fn check(ra_fixture_thing: &str) {}
647
648fn map(f: i32) {}
649fn filter(predicate: i32) {}
650
651fn strip_suffix(suffix: &str) {}
652fn stripsuffix(suffix: &str) {}
653fn same(same: u32) {}
654fn same2(_same2: u32) {}
655
656fn enum_matches_param_name(completion_kind: CompletionKind) {}
657
658fn foo(param: u32) {}
659fn bar(param_eter: u32) {}
660fn baz(a_d_e: u32) {}
661fn far(loop_: u32) {}
662fn faz(r#loop: u32) {}
663
664enum CompletionKind {
665 Keyword,
666}
667
668fn non_ident_pat((a, b): (u32, u32)) {}
669
670fn main() {
671 const PARAM: u32 = 0;
672 foo(PARAM);
673 foo(!PARAM);
674 // ^^^^^^ param
675 check("");
676
677 map(0);
678 filter(0);
679
680 strip_suffix("");
681 stripsuffix("");
682 //^^ suffix
683 same(0);
684 same2(0);
685
686 enum_matches_param_name(CompletionKind::Keyword);
687
688 let param = 0;
689 foo(param);
690 foo(param as _);
691 let param_end = 0;
692 foo(param_end);
693 let start_param = 0;
694 foo(start_param);
695 let param2 = 0;
696 foo(param2);
697 //^^^^^^ param
698
699 macro_rules! param {
700 () => {};
701 };
702 foo(param!());
703
704 let param_eter = 0;
705 bar(param_eter);
706 let param_eter_end = 0;
707 bar(param_eter_end);
708 let start_param_eter = 0;
709 bar(start_param_eter);
710 let param_eter2 = 0;
711 bar(param_eter2);
712 //^^^^^^^^^^^ param_eter
713 let loop_level = 0;
714 far(loop_level);
715 faz(loop_level);
716
717 non_ident_pat((0, 0));
718
719 baz(a.d.e);
720 baz(a.dc.e);
721 // ^^^^^^ a_d_e
722 baz(ac.d.e);
723 // ^^^^^^ a_d_e
724 baz(a.d.ec);
725 // ^^^^^^ a_d_e
726}"#,
727 );
728 }
729
730 #[track_caller]
731 fn check_missing_params(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
732 check_with_config(
733 InlayHintsConfig {
734 parameter_hints: true,
735 parameter_hints_for_missing_arguments: true,
736 ..DISABLED_CONFIG
737 },
738 ra_fixture,
739 );
740 }
741
742 #[test]
743 fn missing_param_hint_empty_call() {
744 check_missing_params(
746 r#"
747fn foo(a: i32, b: i32) -> i32 { a + b }
748fn main() {
749 foo();
750 //^ a
751}"#,
752 );
753 }
754
755 #[test]
756 fn missing_param_hint_after_first_arg() {
757 check_missing_params(
759 r#"
760fn foo(a: i32, b: i32) -> i32 { a + b }
761fn main() {
762 foo(1,);
763 //^ a
764 //^ b
765}"#,
766 );
767 }
768
769 #[test]
770 fn missing_param_hint_partial_args() {
771 check_missing_params(
773 r#"
774fn foo(a: i32, b: i32, c: i32) -> i32 { a + b + c }
775fn main() {
776 foo(1, 2,);
777 //^ a
778 //^ b
779 //^ c
780}"#,
781 );
782 }
783
784 #[test]
785 fn missing_param_hint_method_call() {
786 check_missing_params(
788 r#"
789struct S;
790impl S {
791 fn foo(&self, a: i32, b: i32) -> i32 { a + b }
792}
793fn main() {
794 S.foo(1,);
795 //^ a
796 //^ b
797}"#,
798 );
799 }
800
801 #[test]
802 fn missing_param_hint_no_hint_when_complete() {
803 check_missing_params(
805 r#"
806fn foo(a: i32, b: i32) -> i32 { a + b }
807fn main() {
808 foo(1, 2);
809 //^ a
810 //^ b
811}"#,
812 );
813 }
814
815 #[test]
816 fn missing_param_hint_respects_heuristics() {
817 check_missing_params(
819 r#"
820fn foo(foo: i32) -> i32 { foo }
821fn main() {
822 foo();
823}"#,
824 );
825 }
826}