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