aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/inlay_hints.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/inlay_hints.rs')
-rw-r--r--crates/ide/src/inlay_hints.rs927
1 files changed, 927 insertions, 0 deletions
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
new file mode 100644
index 000000000..002adf915
--- /dev/null
+++ b/crates/ide/src/inlay_hints.rs
@@ -0,0 +1,927 @@
1use hir::{Adt, Callable, HirDisplay, Semantics, Type};
2use ide_db::RootDatabase;
3use stdx::to_lower_snake_case;
4use syntax::{
5 ast::{self, ArgListOwner, AstNode},
6 match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, TextRange, T,
7};
8
9use crate::FileId;
10use ast::NameOwner;
11use either::Either;
12
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub 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
21impl 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)]
28pub enum InlayKind {
29 TypeHint,
30 ParameterHint,
31 ChainingHint,
32}
33
34#[derive(Debug)]
35pub 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 hits 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// |===
61pub(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
88fn 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
138fn 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
180fn 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
204fn 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
217fn 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
259fn 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
285fn 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
302fn 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
313fn 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
323fn 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
329fn 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)]
338mod tests {
339 use expect::{expect, Expect};
340 use test_utils::extract_annotations;
341
342 use crate::{inlay_hints::InlayHintsConfig, mock_analysis::single_file};
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) = single_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) = single_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#"
373fn foo(a: i32, b: i32) -> i32 { a + b }
374fn 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#"
395fn foo(a: i32, b: i32) -> i32 { a + b }
396fn 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#"
412fn foo(a: i32, b: i32) -> i32 { a + b }
413fn 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#"
424struct Test<K, T = u8> { k: K, t: T }
425
426fn 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)]
442enum Option<T> { None, Some(T) }
443
444#[derive(PartialEq)]
445struct Test { a: Option<u32>, b: u8 }
446
447fn 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#"
476fn 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#"
502fn 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#"
515enum Option<T> { None, Some(T) }
516use Option::*;
517
518struct Test { a: Option<u32>, b: u8 }
519
520fn 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#"
547enum Option<T> { None, Some(T) }
548use Option::*;
549
550struct Test { a: Option<u32>, b: u8 }
551
552fn 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#"
565enum Option<T> { None, Some(T) }
566use Option::*;
567
568struct Test { a: Option<u32>, b: u8 }
569
570fn 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#"
588struct Smol<T>(T);
589
590struct VeryLongOuterName<T>(T);
591
592fn 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#"
607enum Option<T> { None, Some(T) }
608use Option::*;
609
610struct FileId {}
611struct SmolStr {}
612
613struct TextRange {}
614struct SyntaxKind {}
615struct NavigationTarget {}
616
617struct Test {}
618
619impl 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
634fn test_func(mut foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 {
635 foo + bar
636}
637
638fn 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#"
671fn map(f: i32) {}
672fn filter(predicate: i32) {}
673
674struct TestVarContainer {
675 test_var: i32,
676}
677
678impl TestVarContainer {
679 fn test_var(&self) -> i32 {
680 self.test_var
681 }
682}
683
684struct Test {}
685
686impl 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
704struct Param {}
705
706fn different_order(param: &Param) {}
707fn different_order_mut(param: &mut Param) {}
708fn has_underscore(_param: bool) {}
709fn enum_matches_param_name(completion_kind: CompletionKind) {}
710fn param_destructuring_omitted_1((a, b): (u32, u32)) {}
711fn param_destructuring_omitted_2(TestVarContainer { test_var: _ }: TestVarContainer) {}
712
713fn twiddle(twiddle: bool) {}
714fn doo(_doo: bool) {}
715
716enum CompletionKind {
717 Keyword,
718}
719
720fn 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#"
764enum Result<T, E> { Ok(T), Err(E) }
765use Result::*;
766
767struct SyntheticSyntax;
768
769fn 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#"
788struct A(B);
789impl A { fn into_b(self) -> B { self.0 } }
790struct B(C);
791impl B { fn into_c(self) -> C { self.0 } }
792struct C;
793
794fn 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#"
827struct A(B);
828impl A { fn into_b(self) -> B { self.0 } }
829struct B(C);
830impl B { fn into_c(self) -> C { self.0 } }
831struct C;
832
833fn 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#"
849struct A { pub b: B }
850struct B { pub c: C }
851struct C(pub bool);
852struct D;
853
854impl D {
855 fn foo(&self) -> i32 { 42 }
856}
857
858fn 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#"
893struct A<T>(T);
894struct B<T>(T);
895struct C<T>(T);
896struct X<T,R>(T, R);
897
898impl<T> A<T> {
899 fn new(t: T) -> Self { A(t) }
900 fn into_b(self) -> B<T> { B(self.0) }
901}
902impl<T> B<T> {
903 fn into_c(self) -> C<T> { C(self.0) }
904}
905fn 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}