1 use hir::{Adt, Callable, HirDisplay, Semantics, Type};
2 use ide_db::RootDatabase;
3 use stdx::to_lower_snake_case;
5 ast::{self, ArgListOwner, AstNode},
6 match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, TextRange, T,
13 #[derive(Clone, Debug, PartialEq, Eq)]
14 pub struct InlayHintsConfig {
16 pub parameter_hints: bool,
17 pub chaining_hints: bool,
18 pub max_length: Option<usize>,
21 impl Default for InlayHintsConfig {
22 fn default() -> Self {
23 Self { type_hints: true, parameter_hints: true, chaining_hints: true, max_length: None }
27 #[derive(Clone, Debug, PartialEq, Eq)]
35 pub struct InlayHint {
41 // Feature: Inlay Hints
43 // rust-analyzer shows additional information inline with the source code.
44 // Editors usually render this using read-only virtual text snippets interspersed with code.
46 // rust-analyzer shows hints for
48 // * types of local variables
49 // * names of function arguments
50 // * types of chained expressions
52 // **Note:** VS Code does not have native support for inlay hints https://github.com/microsoft/vscode/issues/16221[yet] and the hints are implemented using decorations.
53 // This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird:
54 // https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2].
57 // | Editor | Action Name
59 // | VS Code | **Rust Analyzer: Toggle inlay hints*
61 pub(crate) fn inlay_hints(
64 config: &InlayHintsConfig,
66 let _p = profile::span("inlay_hints");
67 let sema = Semantics::new(db);
68 let file = sema.parse(file_id);
70 let mut res = Vec::new();
71 for node in file.syntax().descendants() {
72 if let Some(expr) = ast::Expr::cast(node.clone()) {
73 get_chaining_hints(&mut res, &sema, config, expr);
78 ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it)); },
79 ast::MethodCallExpr(it) => { get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it)); },
80 ast::IdentPat(it) => { get_bind_pat_hints(&mut res, &sema, config, it); },
88 fn get_chaining_hints(
89 acc: &mut Vec<InlayHint>,
90 sema: &Semantics<RootDatabase>,
91 config: &InlayHintsConfig,
94 if !config.chaining_hints {
98 if matches!(expr, ast::Expr::RecordExpr(_)) {
102 let mut tokens = expr
104 .siblings_with_tokens(Direction::Next)
105 .filter_map(NodeOrToken::into_token)
106 .filter(|t| match t.kind() {
107 SyntaxKind::WHITESPACE if !t.text().contains('\n') => false,
108 SyntaxKind::COMMENT => false,
112 // Chaining can be defined as an expression whose next sibling tokens are newline and dot
113 // Ignoring extra whitespace and comments
114 let next = tokens.next()?.kind();
115 let next_next = tokens.next()?.kind();
116 if next == SyntaxKind::WHITESPACE && next_next == T![.] {
117 let ty = sema.type_of_expr(&expr)?;
121 if matches!(expr, ast::Expr::PathExpr(_)) {
122 if let Some(Adt::Struct(st)) = ty.as_adt() {
123 if st.fields(sema.db).is_empty() {
128 let label = ty.display_truncated(sema.db, config.max_length).to_string();
130 range: expr.syntax().text_range(),
131 kind: InlayKind::ChainingHint,
138 fn get_param_name_hints(
139 acc: &mut Vec<InlayHint>,
140 sema: &Semantics<RootDatabase>,
141 config: &InlayHintsConfig,
144 if !config.parameter_hints {
148 let args = match &expr {
149 ast::Expr::CallExpr(expr) => expr.arg_list()?.args(),
150 ast::Expr::MethodCallExpr(expr) => expr.arg_list()?.args(),
154 let callable = get_callable(sema, &expr)?;
159 .filter_map(|((param, _ty), arg)| {
160 let param_name = match param? {
161 Either::Left(self_param) => self_param.to_string(),
162 Either::Right(pat) => match pat {
163 ast::Pat::IdentPat(it) => it.name()?.to_string(),
167 Some((param_name, arg))
169 .filter(|(param_name, arg)| should_show_param_name_hint(sema, &callable, ¶m_name, &arg))
170 .map(|(param_name, arg)| InlayHint {
171 range: arg.syntax().text_range(),
172 kind: InlayKind::ParameterHint,
173 label: param_name.into(),
180 fn get_bind_pat_hints(
181 acc: &mut Vec<InlayHint>,
182 sema: &Semantics<RootDatabase>,
183 config: &InlayHintsConfig,
186 if !config.type_hints {
190 let ty = sema.type_of_pat(&pat.clone().into())?;
192 if should_not_display_type_hint(sema.db, &pat, &ty) {
197 range: pat.syntax().text_range(),
198 kind: InlayKind::TypeHint,
199 label: ty.display_truncated(sema.db, config.max_length).to_string().into(),
204 fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &Type) -> bool {
205 if let Some(Adt::Enum(enum_data)) = pat_ty.as_adt() {
206 let pat_text = bind_pat.to_string();
210 .map(|variant| variant.name(db).to_string())
211 .any(|enum_name| enum_name == pat_text)
217 fn should_not_display_type_hint(
219 bind_pat: &ast::IdentPat,
222 if pat_ty.is_unknown() {
226 if let Some(Adt::Struct(s)) = pat_ty.as_adt() {
227 if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.to_string() {
232 for node in bind_pat.syntax().ancestors() {
235 ast::LetStmt(it) => {
236 return it.ty().is_some()
239 return it.ty().is_some()
241 ast::MatchArm(_it) => {
242 return pat_is_enum_variant(db, bind_pat, pat_ty);
245 return it.condition().and_then(|condition| condition.pat()).is_some()
246 && pat_is_enum_variant(db, bind_pat, pat_ty);
248 ast::WhileExpr(it) => {
249 return it.condition().and_then(|condition| condition.pat()).is_some()
250 && pat_is_enum_variant(db, bind_pat, pat_ty);
259 fn should_show_param_name_hint(
260 sema: &Semantics<RootDatabase>,
263 argument: &ast::Expr,
265 let param_name = param_name.trim_start_matches('_');
266 let fn_name = match callable.kind() {
267 hir::CallableKind::Function(it) => Some(it.name(sema.db).to_string()),
268 hir::CallableKind::TupleStruct(_)
269 | hir::CallableKind::TupleEnumVariant(_)
270 | hir::CallableKind::Closure => None,
272 if param_name.is_empty()
273 || Some(param_name) == fn_name.as_ref().map(|s| s.trim_start_matches('_'))
274 || is_argument_similar_to_param_name(sema, argument, param_name)
275 || param_name.starts_with("ra_fixture")
280 // avoid displaying hints for common functions like map, filter, etc.
281 // or other obvious words used in std
282 !(callable.n_params() == 1 && is_obvious_param(param_name))
285 fn is_argument_similar_to_param_name(
286 sema: &Semantics<RootDatabase>,
287 argument: &ast::Expr,
290 if is_enum_name_similar_to_param_name(sema, argument, param_name) {
293 match get_string_representation(argument) {
296 let argument_string = repr.trim_start_matches('_');
297 argument_string.starts_with(param_name) || argument_string.ends_with(param_name)
302 fn is_enum_name_similar_to_param_name(
303 sema: &Semantics<RootDatabase>,
304 argument: &ast::Expr,
307 match sema.type_of_expr(argument).and_then(|t| t.as_adt()) {
308 Some(Adt::Enum(e)) => to_lower_snake_case(&e.name(sema.db).to_string()) == param_name,
313 fn get_string_representation(expr: &ast::Expr) -> Option<String> {
315 ast::Expr::MethodCallExpr(method_call_expr) => {
316 Some(method_call_expr.name_ref()?.to_string())
318 ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?),
319 _ => Some(expr.to_string()),
323 fn is_obvious_param(param_name: &str) -> bool {
324 let is_obvious_param_name =
325 matches!(param_name, "predicate" | "value" | "pat" | "rhs" | "other");
326 param_name.len() == 1 || is_obvious_param_name
329 fn get_callable(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<Callable> {
331 ast::Expr::CallExpr(expr) => sema.type_of_expr(&expr.expr()?)?.as_callable(sema.db),
332 ast::Expr::MethodCallExpr(expr) => sema.resolve_method_call_as_callable(expr),
339 use expect_test::{expect, Expect};
340 use test_utils::extract_annotations;
342 use crate::{fixture, inlay_hints::InlayHintsConfig};
344 fn check(ra_fixture: &str) {
345 check_with_config(InlayHintsConfig::default(), ra_fixture);
348 fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) {
349 let (analysis, file_id) = fixture::file(ra_fixture);
350 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
351 let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap();
353 inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>();
354 assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
357 fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
358 let (analysis, file_id) = fixture::file(ra_fixture);
359 let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap();
360 expect.assert_debug_eq(&inlay_hints)
364 fn param_hints_only() {
367 parameter_hints: true,
369 chaining_hints: false,
373 fn foo(a: i32, b: i32) -> i32 { a + b }
386 fn hints_disabled() {
390 parameter_hints: false,
391 chaining_hints: false,
395 fn foo(a: i32, b: i32) -> i32 { a + b }
403 fn type_hints_only() {
407 parameter_hints: false,
408 chaining_hints: false,
412 fn foo(a: i32, b: i32) -> i32 { a + b }
421 fn default_generic_types_should_not_be_displayed() {
424 struct Test<K, T = u8> { k: K, t: T }
427 let zz = Test { t: 23u8, k: 33 };
432 //^^^^ || -> Test<i32>
442 enum Option<T> { None, Some(T) }
445 struct Test { a: Option<u32>, b: u8 }
448 struct InnerStruct {}
458 let test = InnerStruct {};
460 let test = unresolved();
462 let test = (42, 'a');
464 let (a, (b, (c,)) = (2, (3, (9.2,));
473 fn closure_parameters() {
479 (0..2).for_each(|increment| { start += increment; });
483 //^^^^^^^^ |…| -> i32
488 let _: i32 = multiply(1, 2);
489 let multiply_ref = &multiply;
490 //^^^^^^^^^^^^ &|…| -> i32
492 let return_42 = || 42;
493 //^^^^^^^^^ || -> i32
499 fn for_expression() {
505 for increment in 0..2 { start += increment; }
515 enum Option<T> { None, Some(T) }
518 struct Test { a: Option<u32>, b: u8 }
521 let test = Some(Test { a: Some(3), b: 1 });
523 if let None = &test {};
524 if let test = &test {};
526 if let Some(test) = &test {};
528 if let Some(Test { a, b }) = &test {};
529 //^ &Option<u32> ^ &u8
530 if let Some(Test { a: x, b: y }) = &test {};
531 //^ &Option<u32> ^ &u8
532 if let Some(Test { a: Some(x), b: y }) = &test {};
534 if let Some(Test { a: None, b: y }) = &test {};
536 if let Some(Test { b: y, .. }) = &test {};
547 enum Option<T> { None, Some(T) }
550 struct Test { a: Option<u32>, b: u8 }
553 let test = Some(Test { a: Some(3), b: 1 });
555 while let Some(Test { a: Some(x), b: y }) = &test {};
562 fn match_arm_list() {
565 enum Option<T> { None, Some(T) }
568 struct Test { a: Option<u32>, b: u8 }
571 match Some(Test { a: Some(3), b: 1 }) {
575 Some(Test { a: Some(x), b: y }) => (),
584 fn hint_truncation() {
586 InlayHintsConfig { max_length: Some(8), ..Default::default() },
590 struct VeryLongOuterName<T>(T);
595 let b = VeryLongOuterName(0usize);
596 //^ VeryLongOuterName<…>
597 let c = Smol(Smol(0u32))
604 fn function_call_parameter_hint() {
607 enum Option<T> { None, Some(T) }
615 struct NavigationTarget {}
620 fn method(&self, mut param: i32) -> i32 { param * 2 }
625 focus_range: Option<TextRange>,
626 full_range: TextRange,
628 docs: Option<String>,
629 ) -> NavigationTarget {
634 fn test_func(mut foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 {
641 let _: i32 = test_func(1, 2, "hello", 3, not_literal);
642 //^ foo ^ bar ^^^^^^^ msg ^^^^^^^^^^^ last
643 let t: Test = Test {};
646 Test::method(&t, 3456);
647 //^^ &self ^^^^ param
656 //^^^^^^^^^^^^ full_range
667 fn omitted_parameters_hints_heuristics() {
669 InlayHintsConfig { max_length: Some(8), ..Default::default() },
672 fn filter(predicate: i32) {}
674 struct TestVarContainer {
678 impl TestVarContainer {
679 fn test_var(&self) -> i32 {
687 fn map(self, f: i32) -> Self {
691 fn filter(self, predicate: i32) -> Self {
695 fn field(self, value: i32) -> Self {
699 fn no_hints_expected(&self, _: i32, test_var: i32) {}
701 fn frob(&self, frob: bool) {}
706 fn different_order(param: &Param) {}
707 fn different_order_mut(param: &mut Param) {}
708 fn has_underscore(_param: bool) {}
709 fn enum_matches_param_name(completion_kind: CompletionKind) {}
710 fn param_destructuring_omitted_1((a, b): (u32, u32)) {}
711 fn param_destructuring_omitted_2(TestVarContainer { test_var: _ }: TestVarContainer) {}
713 fn twiddle(twiddle: bool) {}
714 fn doo(_doo: bool) {}
716 enum CompletionKind {
721 let container: TestVarContainer = TestVarContainer { test_var: 42 };
722 let test: Test = Test {};
727 let test_processed: Test = test.map(1).filter(2).field(3);
729 let test_var: i32 = 55;
730 test_processed.no_hints_expected(22, test_var);
731 test_processed.no_hints_expected(33, container.test_var);
732 test_processed.no_hints_expected(44, container.test_var());
733 test_processed.frob(false);
738 let mut param_begin: Param = Param {};
739 different_order(¶m_begin);
740 different_order(&mut param_begin);
742 let param: bool = true;
743 has_underscore(param);
745 enum_matches_param_name(CompletionKind::Keyword);
749 let _: f64 = a.div_euclid(b);
750 let _: f64 = a.abs_sub(b);
752 let range: (u32, u32) = (3, 5);
753 param_destructuring_omitted_1(range);
754 param_destructuring_omitted_2(container);
760 fn unit_structs_have_no_type_hints() {
762 InlayHintsConfig { max_length: Some(8), ..Default::default() },
764 enum Result<T, E> { Ok(T), Err(E) }
767 struct SyntheticSyntax;
772 Err(SyntheticSyntax) => (),
779 fn chaining_hints_ignore_comments() {
782 parameter_hints: false,
784 chaining_hints: true,
789 impl A { fn into_b(self) -> B { self.0 } }
791 impl B { fn into_c(self) -> C { self.0 } }
796 .into_b() // This is a comment
818 fn chaining_hints_without_newlines() {
821 parameter_hints: false,
823 chaining_hints: true,
828 impl A { fn into_b(self) -> B { self.0 } }
830 impl B { fn into_c(self) -> C { self.0 } }
834 let c = A(B(C)).into_b().into_c();
840 fn struct_access_chaining_hints() {
843 parameter_hints: false,
845 chaining_hints: true,
849 struct A { pub b: B }
850 struct B { pub c: C }
855 fn foo(&self) -> i32 { 42 }
859 let x = A { b: B { c: C(true) } }
884 fn generic_chaining_hints() {
887 parameter_hints: false,
889 chaining_hints: true,
899 fn new(t: T) -> Self { A(t) }
900 fn into_b(self) -> B<T> { B(self.0) }
903 fn into_c(self) -> C<T> { C(self.0) }
906 let c = A::new(X(42, true))
916 label: "B<X<i32, bool>>",
921 label: "A<X<i32, bool>>",