]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/inlay_hints.rs
rename mock_analysis -> fixture
[rust.git] / crates / ide / src / inlay_hints.rs
1 use hir::{Adt, Callable, HirDisplay, Semantics, Type};
2 use ide_db::RootDatabase;
3 use stdx::to_lower_snake_case;
4 use syntax::{
5     ast::{self, ArgListOwner, AstNode},
6     match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, TextRange, T,
7 };
8
9 use crate::FileId;
10 use ast::NameOwner;
11 use either::Either;
12
13 #[derive(Clone, Debug, PartialEq, Eq)]
14 pub struct InlayHintsConfig {
15     pub type_hints: bool,
16     pub parameter_hints: bool,
17     pub chaining_hints: bool,
18     pub max_length: Option<usize>,
19 }
20
21 impl Default for InlayHintsConfig {
22     fn default() -> Self {
23         Self { type_hints: true, parameter_hints: true, chaining_hints: true, max_length: None }
24     }
25 }
26
27 #[derive(Clone, Debug, PartialEq, Eq)]
28 pub enum InlayKind {
29     TypeHint,
30     ParameterHint,
31     ChainingHint,
32 }
33
34 #[derive(Debug)]
35 pub struct InlayHint {
36     pub range: TextRange,
37     pub kind: InlayKind,
38     pub label: SmolStr,
39 }
40
41 // Feature: Inlay Hints
42 //
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.
45 //
46 // rust-analyzer shows hints for
47 //
48 // * types of local variables
49 // * names of function arguments
50 // * types of chained expressions
51 //
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].
55 //
56 // |===
57 // | Editor  | Action Name
58 //
59 // | VS Code | **Rust Analyzer: Toggle inlay hints*
60 // |===
61 pub(crate) fn inlay_hints(
62     db: &RootDatabase,
63     file_id: FileId,
64     config: &InlayHintsConfig,
65 ) -> Vec<InlayHint> {
66     let _p = profile::span("inlay_hints");
67     let sema = Semantics::new(db);
68     let file = sema.parse(file_id);
69
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);
74         }
75
76         match_ast! {
77             match node {
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); },
81                 _ => (),
82             }
83         }
84     }
85     res
86 }
87
88 fn get_chaining_hints(
89     acc: &mut Vec<InlayHint>,
90     sema: &Semantics<RootDatabase>,
91     config: &InlayHintsConfig,
92     expr: ast::Expr,
93 ) -> Option<()> {
94     if !config.chaining_hints {
95         return None;
96     }
97
98     if matches!(expr, ast::Expr::RecordExpr(_)) {
99         return None;
100     }
101
102     let mut tokens = expr
103         .syntax()
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,
109             _ => true,
110         });
111
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)?;
118         if ty.is_unknown() {
119             return None;
120         }
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() {
124                     return None;
125                 }
126             }
127         }
128         let label = ty.display_truncated(sema.db, config.max_length).to_string();
129         acc.push(InlayHint {
130             range: expr.syntax().text_range(),
131             kind: InlayKind::ChainingHint,
132             label: label.into(),
133         });
134     }
135     Some(())
136 }
137
138 fn get_param_name_hints(
139     acc: &mut Vec<InlayHint>,
140     sema: &Semantics<RootDatabase>,
141     config: &InlayHintsConfig,
142     expr: ast::Expr,
143 ) -> Option<()> {
144     if !config.parameter_hints {
145         return None;
146     }
147
148     let args = match &expr {
149         ast::Expr::CallExpr(expr) => expr.arg_list()?.args(),
150         ast::Expr::MethodCallExpr(expr) => expr.arg_list()?.args(),
151         _ => return None,
152     };
153
154     let callable = get_callable(sema, &expr)?;
155     let hints = callable
156         .params(sema.db)
157         .into_iter()
158         .zip(args)
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(),
164                     _ => return None,
165                 },
166             };
167             Some((param_name, arg))
168         })
169         .filter(|(param_name, arg)| should_show_param_name_hint(sema, &callable, &param_name, &arg))
170         .map(|(param_name, arg)| InlayHint {
171             range: arg.syntax().text_range(),
172             kind: InlayKind::ParameterHint,
173             label: param_name.into(),
174         });
175
176     acc.extend(hints);
177     Some(())
178 }
179
180 fn get_bind_pat_hints(
181     acc: &mut Vec<InlayHint>,
182     sema: &Semantics<RootDatabase>,
183     config: &InlayHintsConfig,
184     pat: ast::IdentPat,
185 ) -> Option<()> {
186     if !config.type_hints {
187         return None;
188     }
189
190     let ty = sema.type_of_pat(&pat.clone().into())?;
191
192     if should_not_display_type_hint(sema.db, &pat, &ty) {
193         return None;
194     }
195
196     acc.push(InlayHint {
197         range: pat.syntax().text_range(),
198         kind: InlayKind::TypeHint,
199         label: ty.display_truncated(sema.db, config.max_length).to_string().into(),
200     });
201     Some(())
202 }
203
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();
207         enum_data
208             .variants(db)
209             .into_iter()
210             .map(|variant| variant.name(db).to_string())
211             .any(|enum_name| enum_name == pat_text)
212     } else {
213         false
214     }
215 }
216
217 fn should_not_display_type_hint(
218     db: &RootDatabase,
219     bind_pat: &ast::IdentPat,
220     pat_ty: &Type,
221 ) -> bool {
222     if pat_ty.is_unknown() {
223         return true;
224     }
225
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() {
228             return true;
229         }
230     }
231
232     for node in bind_pat.syntax().ancestors() {
233         match_ast! {
234             match node {
235                 ast::LetStmt(it) => {
236                     return it.ty().is_some()
237                 },
238                 ast::Param(it) => {
239                     return it.ty().is_some()
240                 },
241                 ast::MatchArm(_it) => {
242                     return pat_is_enum_variant(db, bind_pat, pat_ty);
243                 },
244                 ast::IfExpr(it) => {
245                     return it.condition().and_then(|condition| condition.pat()).is_some()
246                         && pat_is_enum_variant(db, bind_pat, pat_ty);
247                 },
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);
251                 },
252                 _ => (),
253             }
254         }
255     }
256     false
257 }
258
259 fn should_show_param_name_hint(
260     sema: &Semantics<RootDatabase>,
261     callable: &Callable,
262     param_name: &str,
263     argument: &ast::Expr,
264 ) -> bool {
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,
271     };
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")
276     {
277         return false;
278     }
279
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))
283 }
284
285 fn is_argument_similar_to_param_name(
286     sema: &Semantics<RootDatabase>,
287     argument: &ast::Expr,
288     param_name: &str,
289 ) -> bool {
290     if is_enum_name_similar_to_param_name(sema, argument, param_name) {
291         return true;
292     }
293     match get_string_representation(argument) {
294         None => false,
295         Some(repr) => {
296             let argument_string = repr.trim_start_matches('_');
297             argument_string.starts_with(param_name) || argument_string.ends_with(param_name)
298         }
299     }
300 }
301
302 fn is_enum_name_similar_to_param_name(
303     sema: &Semantics<RootDatabase>,
304     argument: &ast::Expr,
305     param_name: &str,
306 ) -> bool {
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,
309         _ => false,
310     }
311 }
312
313 fn get_string_representation(expr: &ast::Expr) -> Option<String> {
314     match expr {
315         ast::Expr::MethodCallExpr(method_call_expr) => {
316             Some(method_call_expr.name_ref()?.to_string())
317         }
318         ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?),
319         _ => Some(expr.to_string()),
320     }
321 }
322
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
327 }
328
329 fn get_callable(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<Callable> {
330     match expr {
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),
333         _ => None,
334     }
335 }
336
337 #[cfg(test)]
338 mod tests {
339     use expect_test::{expect, Expect};
340     use test_utils::extract_annotations;
341
342     use crate::{fixture, inlay_hints::InlayHintsConfig};
343
344     fn check(ra_fixture: &str) {
345         check_with_config(InlayHintsConfig::default(), ra_fixture);
346     }
347
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();
352         let actual =
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);
355     }
356
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)
361     }
362
363     #[test]
364     fn param_hints_only() {
365         check_with_config(
366             InlayHintsConfig {
367                 parameter_hints: true,
368                 type_hints: false,
369                 chaining_hints: false,
370                 max_length: None,
371             },
372             r#"
373 fn foo(a: i32, b: i32) -> i32 { a + b }
374 fn main() {
375     let _x = foo(
376         4,
377       //^ a
378         4,
379       //^ b
380     );
381 }"#,
382         );
383     }
384
385     #[test]
386     fn hints_disabled() {
387         check_with_config(
388             InlayHintsConfig {
389                 type_hints: false,
390                 parameter_hints: false,
391                 chaining_hints: false,
392                 max_length: None,
393             },
394             r#"
395 fn foo(a: i32, b: i32) -> i32 { a + b }
396 fn main() {
397     let _x = foo(4, 4);
398 }"#,
399         );
400     }
401
402     #[test]
403     fn type_hints_only() {
404         check_with_config(
405             InlayHintsConfig {
406                 type_hints: true,
407                 parameter_hints: false,
408                 chaining_hints: false,
409                 max_length: None,
410             },
411             r#"
412 fn foo(a: i32, b: i32) -> i32 { a + b }
413 fn main() {
414     let _x = foo(4, 4);
415       //^^ i32
416 }"#,
417         );
418     }
419
420     #[test]
421     fn default_generic_types_should_not_be_displayed() {
422         check(
423             r#"
424 struct Test<K, T = u8> { k: K, t: T }
425
426 fn main() {
427     let zz = Test { t: 23u8, k: 33 };
428       //^^ Test<i32>
429     let zz_ref = &zz;
430       //^^^^^^ &Test<i32>
431     let test = || zz;
432       //^^^^ || -> Test<i32>
433 }"#,
434         );
435     }
436
437     #[test]
438     fn let_statement() {
439         check(
440             r#"
441 #[derive(PartialEq)]
442 enum Option<T> { None, Some(T) }
443
444 #[derive(PartialEq)]
445 struct Test { a: Option<u32>, b: u8 }
446
447 fn main() {
448     struct InnerStruct {}
449
450     let test = 54;
451       //^^^^ i32
452     let test: i32 = 33;
453     let mut test = 33;
454       //^^^^^^^^ i32
455     let _ = 22;
456     let test = "test";
457       //^^^^ &str
458     let test = InnerStruct {};
459
460     let test = unresolved();
461
462     let test = (42, 'a');
463       //^^^^ (i32, char)
464     let (a,    (b,     (c,)) = (2, (3, (9.2,));
465        //^ i32  ^ i32   ^ f64
466     let &x = &92;
467        //^ i32
468 }"#,
469         );
470     }
471
472     #[test]
473     fn closure_parameters() {
474         check(
475             r#"
476 fn main() {
477     let mut start = 0;
478       //^^^^^^^^^ i32
479     (0..2).for_each(|increment| { start += increment; });
480                    //^^^^^^^^^ i32
481
482     let multiply =
483       //^^^^^^^^ |…| -> i32
484       | a,     b| a * b
485       //^ i32  ^ i32
486     ;
487
488     let _: i32 = multiply(1, 2);
489     let multiply_ref = &multiply;
490       //^^^^^^^^^^^^ &|…| -> i32
491
492     let return_42 = || 42;
493       //^^^^^^^^^ || -> i32
494 }"#,
495         );
496     }
497
498     #[test]
499     fn for_expression() {
500         check(
501             r#"
502 fn main() {
503     let mut start = 0;
504       //^^^^^^^^^ i32
505     for increment in 0..2 { start += increment; }
506       //^^^^^^^^^ i32
507 }"#,
508         );
509     }
510
511     #[test]
512     fn if_expr() {
513         check(
514             r#"
515 enum Option<T> { None, Some(T) }
516 use Option::*;
517
518 struct Test { a: Option<u32>, b: u8 }
519
520 fn main() {
521     let test = Some(Test { a: Some(3), b: 1 });
522       //^^^^ Option<Test>
523     if let None = &test {};
524     if let test = &test {};
525          //^^^^ &Option<Test>
526     if let Some(test) = &test {};
527               //^^^^ &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 {};
533                              //^ &u32  ^ &u8
534     if let Some(Test { a: None,  b: y }) = &test {};
535                                   //^ &u8
536     if let Some(Test { b: y, .. }) = &test {};
537                         //^ &u8
538     if test == None {}
539 }"#,
540         );
541     }
542
543     #[test]
544     fn while_expr() {
545         check(
546             r#"
547 enum Option<T> { None, Some(T) }
548 use Option::*;
549
550 struct Test { a: Option<u32>, b: u8 }
551
552 fn main() {
553     let test = Some(Test { a: Some(3), b: 1 });
554       //^^^^ Option<Test>
555     while let Some(Test { a: Some(x),  b: y }) = &test {};
556                                 //^ &u32  ^ &u8
557 }"#,
558         );
559     }
560
561     #[test]
562     fn match_arm_list() {
563         check(
564             r#"
565 enum Option<T> { None, Some(T) }
566 use Option::*;
567
568 struct Test { a: Option<u32>, b: u8 }
569
570 fn main() {
571     match Some(Test { a: Some(3), b: 1 }) {
572         None => (),
573         test => (),
574       //^^^^ Option<Test>
575         Some(Test { a: Some(x), b: y }) => (),
576                           //^ u32  ^ u8
577         _ => {}
578     }
579 }"#,
580         );
581     }
582
583     #[test]
584     fn hint_truncation() {
585         check_with_config(
586             InlayHintsConfig { max_length: Some(8), ..Default::default() },
587             r#"
588 struct Smol<T>(T);
589
590 struct VeryLongOuterName<T>(T);
591
592 fn main() {
593     let a = Smol(0u32);
594       //^ Smol<u32>
595     let b = VeryLongOuterName(0usize);
596       //^ VeryLongOuterName<…>
597     let c = Smol(Smol(0u32))
598       //^ Smol<Smol<…>>
599 }"#,
600         );
601     }
602
603     #[test]
604     fn function_call_parameter_hint() {
605         check(
606             r#"
607 enum Option<T> { None, Some(T) }
608 use Option::*;
609
610 struct FileId {}
611 struct SmolStr {}
612
613 struct TextRange {}
614 struct SyntaxKind {}
615 struct NavigationTarget {}
616
617 struct Test {}
618
619 impl Test {
620     fn method(&self, mut param: i32) -> i32 { param * 2 }
621
622     fn from_syntax(
623         file_id: FileId,
624         name: SmolStr,
625         focus_range: Option<TextRange>,
626         full_range: TextRange,
627         kind: SyntaxKind,
628         docs: Option<String>,
629     ) -> NavigationTarget {
630         NavigationTarget {}
631     }
632 }
633
634 fn test_func(mut foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 {
635     foo + bar
636 }
637
638 fn main() {
639     let not_literal = 1;
640       //^^^^^^^^^^^ i32
641     let _: i32 = test_func(1,    2,      "hello", 3,  not_literal);
642                          //^ foo ^ bar   ^^^^^^^ msg  ^^^^^^^^^^^ last
643     let t: Test = Test {};
644     t.method(123);
645            //^^^ param
646     Test::method(&t,      3456);
647                //^^ &self ^^^^ param
648     Test::from_syntax(
649         FileId {},
650       //^^^^^^^^^ file_id
651         "impl".into(),
652       //^^^^^^^^^^^^^ name
653         None,
654       //^^^^ focus_range
655         TextRange {},
656       //^^^^^^^^^^^^ full_range
657         SyntaxKind {},
658       //^^^^^^^^^^^^^ kind
659         None,
660       //^^^^ docs
661     );
662 }"#,
663         );
664     }
665
666     #[test]
667     fn omitted_parameters_hints_heuristics() {
668         check_with_config(
669             InlayHintsConfig { max_length: Some(8), ..Default::default() },
670             r#"
671 fn map(f: i32) {}
672 fn filter(predicate: i32) {}
673
674 struct TestVarContainer {
675     test_var: i32,
676 }
677
678 impl TestVarContainer {
679     fn test_var(&self) -> i32 {
680         self.test_var
681     }
682 }
683
684 struct Test {}
685
686 impl Test {
687     fn map(self, f: i32) -> Self {
688         self
689     }
690
691     fn filter(self, predicate: i32) -> Self {
692         self
693     }
694
695     fn field(self, value: i32) -> Self {
696         self
697     }
698
699     fn no_hints_expected(&self, _: i32, test_var: i32) {}
700
701     fn frob(&self, frob: bool) {}
702 }
703
704 struct Param {}
705
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) {}
712
713 fn twiddle(twiddle: bool) {}
714 fn doo(_doo: bool) {}
715
716 enum CompletionKind {
717     Keyword,
718 }
719
720 fn main() {
721     let container: TestVarContainer = TestVarContainer { test_var: 42 };
722     let test: Test = Test {};
723
724     map(22);
725     filter(33);
726
727     let test_processed: Test = test.map(1).filter(2).field(3);
728
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);
734
735     twiddle(true);
736     doo(true);
737
738     let mut param_begin: Param = Param {};
739     different_order(&param_begin);
740     different_order(&mut param_begin);
741
742     let param: bool = true;
743     has_underscore(param);
744
745     enum_matches_param_name(CompletionKind::Keyword);
746
747     let a: f64 = 7.0;
748     let b: f64 = 4.0;
749     let _: f64 = a.div_euclid(b);
750     let _: f64 = a.abs_sub(b);
751
752     let range: (u32, u32) = (3, 5);
753     param_destructuring_omitted_1(range);
754     param_destructuring_omitted_2(container);
755 }"#,
756         );
757     }
758
759     #[test]
760     fn unit_structs_have_no_type_hints() {
761         check_with_config(
762             InlayHintsConfig { max_length: Some(8), ..Default::default() },
763             r#"
764 enum Result<T, E> { Ok(T), Err(E) }
765 use Result::*;
766
767 struct SyntheticSyntax;
768
769 fn main() {
770     match Ok(()) {
771         Ok(_) => (),
772         Err(SyntheticSyntax) => (),
773     }
774 }"#,
775         );
776     }
777
778     #[test]
779     fn chaining_hints_ignore_comments() {
780         check_expect(
781             InlayHintsConfig {
782                 parameter_hints: false,
783                 type_hints: false,
784                 chaining_hints: true,
785                 max_length: None,
786             },
787             r#"
788 struct A(B);
789 impl A { fn into_b(self) -> B { self.0 } }
790 struct B(C);
791 impl B { fn into_c(self) -> C { self.0 } }
792 struct C;
793
794 fn main() {
795     let c = A(B(C))
796         .into_b() // This is a comment
797         .into_c();
798 }
799 "#,
800             expect![[r#"
801                 [
802                     InlayHint {
803                         range: 147..172,
804                         kind: ChainingHint,
805                         label: "B",
806                     },
807                     InlayHint {
808                         range: 147..154,
809                         kind: ChainingHint,
810                         label: "A",
811                     },
812                 ]
813             "#]],
814         );
815     }
816
817     #[test]
818     fn chaining_hints_without_newlines() {
819         check_with_config(
820             InlayHintsConfig {
821                 parameter_hints: false,
822                 type_hints: false,
823                 chaining_hints: true,
824                 max_length: None,
825             },
826             r#"
827 struct A(B);
828 impl A { fn into_b(self) -> B { self.0 } }
829 struct B(C);
830 impl B { fn into_c(self) -> C { self.0 } }
831 struct C;
832
833 fn main() {
834     let c = A(B(C)).into_b().into_c();
835 }"#,
836         );
837     }
838
839     #[test]
840     fn struct_access_chaining_hints() {
841         check_expect(
842             InlayHintsConfig {
843                 parameter_hints: false,
844                 type_hints: false,
845                 chaining_hints: true,
846                 max_length: None,
847             },
848             r#"
849 struct A { pub b: B }
850 struct B { pub c: C }
851 struct C(pub bool);
852 struct D;
853
854 impl D {
855     fn foo(&self) -> i32 { 42 }
856 }
857
858 fn main() {
859     let x = A { b: B { c: C(true) } }
860         .b
861         .c
862         .0;
863     let x = D
864         .foo();
865 }"#,
866             expect![[r#"
867                 [
868                     InlayHint {
869                         range: 143..190,
870                         kind: ChainingHint,
871                         label: "C",
872                     },
873                     InlayHint {
874                         range: 143..179,
875                         kind: ChainingHint,
876                         label: "B",
877                     },
878                 ]
879             "#]],
880         );
881     }
882
883     #[test]
884     fn generic_chaining_hints() {
885         check_expect(
886             InlayHintsConfig {
887                 parameter_hints: false,
888                 type_hints: false,
889                 chaining_hints: true,
890                 max_length: None,
891             },
892             r#"
893 struct A<T>(T);
894 struct B<T>(T);
895 struct C<T>(T);
896 struct X<T,R>(T, R);
897
898 impl<T> A<T> {
899     fn new(t: T) -> Self { A(t) }
900     fn into_b(self) -> B<T> { B(self.0) }
901 }
902 impl<T> B<T> {
903     fn into_c(self) -> C<T> { C(self.0) }
904 }
905 fn main() {
906     let c = A::new(X(42, true))
907         .into_b()
908         .into_c();
909 }
910 "#,
911             expect![[r#"
912                 [
913                     InlayHint {
914                         range: 246..283,
915                         kind: ChainingHint,
916                         label: "B<X<i32, bool>>",
917                     },
918                     InlayHint {
919                         range: 246..265,
920                         kind: ChainingHint,
921                         label: "A<X<i32, bool>>",
922                     },
923                 ]
924             "#]],
925         );
926     }
927 }