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.rs918
1 files changed, 0 insertions, 918 deletions
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs
deleted file mode 100644
index 4bbbcd258..000000000
--- a/crates/ra_ide/src/inlay_hints.rs
+++ /dev/null
@@ -1,918 +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::BindPat(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::BindPat(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::BindPat,
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::BindPat, 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(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ty: &Type) -> bool {
219 if pat_ty.is_unknown() {
220 return true;
221 }
222
223 if let Some(Adt::Struct(s)) = pat_ty.as_adt() {
224 if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.to_string() {
225 return true;
226 }
227 }
228
229 for node in bind_pat.syntax().ancestors() {
230 match_ast! {
231 match node {
232 ast::LetStmt(it) => {
233 return it.ty().is_some()
234 },
235 ast::Param(it) => {
236 return it.ty().is_some()
237 },
238 ast::MatchArm(_it) => {
239 return pat_is_enum_variant(db, bind_pat, pat_ty);
240 },
241 ast::IfExpr(it) => {
242 return it.condition().and_then(|condition| condition.pat()).is_some()
243 && pat_is_enum_variant(db, bind_pat, pat_ty);
244 },
245 ast::WhileExpr(it) => {
246 return it.condition().and_then(|condition| condition.pat()).is_some()
247 && pat_is_enum_variant(db, bind_pat, pat_ty);
248 },
249 _ => (),
250 }
251 }
252 }
253 false
254}
255
256fn should_show_param_name_hint(
257 sema: &Semantics<RootDatabase>,
258 callable: &Callable,
259 param_name: &str,
260 argument: &ast::Expr,
261) -> bool {
262 let param_name = param_name.trim_start_matches('_');
263 let fn_name = match callable.kind() {
264 hir::CallableKind::Function(it) => Some(it.name(sema.db).to_string()),
265 hir::CallableKind::TupleStruct(_)
266 | hir::CallableKind::TupleEnumVariant(_)
267 | hir::CallableKind::Closure => None,
268 };
269 if param_name.is_empty()
270 || Some(param_name) == fn_name.as_ref().map(|s| s.trim_start_matches('_'))
271 || is_argument_similar_to_param_name(sema, argument, param_name)
272 || param_name.starts_with("ra_fixture")
273 {
274 return false;
275 }
276
277 // avoid displaying hints for common functions like map, filter, etc.
278 // or other obvious words used in std
279 !(callable.n_params() == 1 && is_obvious_param(param_name))
280}
281
282fn is_argument_similar_to_param_name(
283 sema: &Semantics<RootDatabase>,
284 argument: &ast::Expr,
285 param_name: &str,
286) -> bool {
287 if is_enum_name_similar_to_param_name(sema, argument, param_name) {
288 return true;
289 }
290 match get_string_representation(argument) {
291 None => false,
292 Some(repr) => {
293 let argument_string = repr.trim_start_matches('_');
294 argument_string.starts_with(param_name) || argument_string.ends_with(param_name)
295 }
296 }
297}
298
299fn is_enum_name_similar_to_param_name(
300 sema: &Semantics<RootDatabase>,
301 argument: &ast::Expr,
302 param_name: &str,
303) -> bool {
304 match sema.type_of_expr(argument).and_then(|t| t.as_adt()) {
305 Some(Adt::Enum(e)) => to_lower_snake_case(&e.name(sema.db).to_string()) == param_name,
306 _ => false,
307 }
308}
309
310fn get_string_representation(expr: &ast::Expr) -> Option<String> {
311 match expr {
312 ast::Expr::MethodCallExpr(method_call_expr) => {
313 Some(method_call_expr.name_ref()?.to_string())
314 }
315 ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?),
316 _ => Some(expr.to_string()),
317 }
318}
319
320fn is_obvious_param(param_name: &str) -> bool {
321 let is_obvious_param_name =
322 matches!(param_name, "predicate" | "value" | "pat" | "rhs" | "other");
323 param_name.len() == 1 || is_obvious_param_name
324}
325
326fn get_callable(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<Callable> {
327 match expr {
328 ast::Expr::CallExpr(expr) => sema.type_of_expr(&expr.expr()?)?.as_callable(sema.db),
329 ast::Expr::MethodCallExpr(expr) => sema.resolve_method_call_as_callable(expr),
330 _ => None,
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use expect::{expect, Expect};
337 use test_utils::extract_annotations;
338
339 use crate::{inlay_hints::InlayHintsConfig, mock_analysis::single_file};
340
341 fn check(ra_fixture: &str) {
342 check_with_config(InlayHintsConfig::default(), ra_fixture);
343 }
344
345 fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) {
346 let (analysis, file_id) = single_file(ra_fixture);
347 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
348 let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap();
349 let actual =
350 inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>();
351 assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
352 }
353
354 fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
355 let (analysis, file_id) = single_file(ra_fixture);
356 let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap();
357 expect.assert_debug_eq(&inlay_hints)
358 }
359
360 #[test]
361 fn param_hints_only() {
362 check_with_config(
363 InlayHintsConfig {
364 parameter_hints: true,
365 type_hints: false,
366 chaining_hints: false,
367 max_length: None,
368 },
369 r#"
370fn foo(a: i32, b: i32) -> i32 { a + b }
371fn main() {
372 let _x = foo(
373 4,
374 //^ a
375 4,
376 //^ b
377 );
378}"#,
379 );
380 }
381
382 #[test]
383 fn hints_disabled() {
384 check_with_config(
385 InlayHintsConfig {
386 type_hints: false,
387 parameter_hints: false,
388 chaining_hints: false,
389 max_length: None,
390 },
391 r#"
392fn foo(a: i32, b: i32) -> i32 { a + b }
393fn main() {
394 let _x = foo(4, 4);
395}"#,
396 );
397 }
398
399 #[test]
400 fn type_hints_only() {
401 check_with_config(
402 InlayHintsConfig {
403 type_hints: true,
404 parameter_hints: false,
405 chaining_hints: false,
406 max_length: None,
407 },
408 r#"
409fn foo(a: i32, b: i32) -> i32 { a + b }
410fn main() {
411 let _x = foo(4, 4);
412 //^^ i32
413}"#,
414 );
415 }
416
417 #[test]
418 fn default_generic_types_should_not_be_displayed() {
419 check(
420 r#"
421struct Test<K, T = u8> { k: K, t: T }
422
423fn main() {
424 let zz = Test { t: 23u8, k: 33 };
425 //^^ Test<i32>
426 let zz_ref = &zz;
427 //^^^^^^ &Test<i32>
428 let test = || zz;
429 //^^^^ || -> Test<i32>
430}"#,
431 );
432 }
433
434 #[test]
435 fn let_statement() {
436 check(
437 r#"
438#[derive(PartialEq)]
439enum Option<T> { None, Some(T) }
440
441#[derive(PartialEq)]
442struct Test { a: Option<u32>, b: u8 }
443
444fn main() {
445 struct InnerStruct {}
446
447 let test = 54;
448 //^^^^ i32
449 let test: i32 = 33;
450 let mut test = 33;
451 //^^^^^^^^ i32
452 let _ = 22;
453 let test = "test";
454 //^^^^ &str
455 let test = InnerStruct {};
456
457 let test = unresolved();
458
459 let test = (42, 'a');
460 //^^^^ (i32, char)
461 let (a, (b, (c,)) = (2, (3, (9.2,));
462 //^ i32 ^ i32 ^ f64
463 let &x = &92;
464 //^ i32
465}"#,
466 );
467 }
468
469 #[test]
470 fn closure_parameters() {
471 check(
472 r#"
473fn main() {
474 let mut start = 0;
475 //^^^^^^^^^ i32
476 (0..2).for_each(|increment| { start += increment; });
477 //^^^^^^^^^ i32
478
479 let multiply =
480 //^^^^^^^^ |…| -> i32
481 | a, b| a * b
482 //^ i32 ^ i32
483 ;
484
485 let _: i32 = multiply(1, 2);
486 let multiply_ref = &multiply;
487 //^^^^^^^^^^^^ &|…| -> i32
488
489 let return_42 = || 42;
490 //^^^^^^^^^ || -> i32
491}"#,
492 );
493 }
494
495 #[test]
496 fn for_expression() {
497 check(
498 r#"
499fn main() {
500 let mut start = 0;
501 //^^^^^^^^^ i32
502 for increment in 0..2 { start += increment; }
503 //^^^^^^^^^ i32
504}"#,
505 );
506 }
507
508 #[test]
509 fn if_expr() {
510 check(
511 r#"
512enum Option<T> { None, Some(T) }
513use Option::*;
514
515struct Test { a: Option<u32>, b: u8 }
516
517fn main() {
518 let test = Some(Test { a: Some(3), b: 1 });
519 //^^^^ Option<Test>
520 if let None = &test {};
521 if let test = &test {};
522 //^^^^ &Option<Test>
523 if let Some(test) = &test {};
524 //^^^^ &Test
525 if let Some(Test { a, b }) = &test {};
526 //^ &Option<u32> ^ &u8
527 if let Some(Test { a: x, b: y }) = &test {};
528 //^ &Option<u32> ^ &u8
529 if let Some(Test { a: Some(x), b: y }) = &test {};
530 //^ &u32 ^ &u8
531 if let Some(Test { a: None, b: y }) = &test {};
532 //^ &u8
533 if let Some(Test { b: y, .. }) = &test {};
534 //^ &u8
535 if test == None {}
536}"#,
537 );
538 }
539
540 #[test]
541 fn while_expr() {
542 check(
543 r#"
544enum Option<T> { None, Some(T) }
545use Option::*;
546
547struct Test { a: Option<u32>, b: u8 }
548
549fn main() {
550 let test = Some(Test { a: Some(3), b: 1 });
551 //^^^^ Option<Test>
552 while let Some(Test { a: Some(x), b: y }) = &test {};
553 //^ &u32 ^ &u8
554}"#,
555 );
556 }
557
558 #[test]
559 fn match_arm_list() {
560 check(
561 r#"
562enum Option<T> { None, Some(T) }
563use Option::*;
564
565struct Test { a: Option<u32>, b: u8 }
566
567fn main() {
568 match Some(Test { a: Some(3), b: 1 }) {
569 None => (),
570 test => (),
571 //^^^^ Option<Test>
572 Some(Test { a: Some(x), b: y }) => (),
573 //^ u32 ^ u8
574 _ => {}
575 }
576}"#,
577 );
578 }
579
580 #[test]
581 fn hint_truncation() {
582 check_with_config(
583 InlayHintsConfig { max_length: Some(8), ..Default::default() },
584 r#"
585struct Smol<T>(T);
586
587struct VeryLongOuterName<T>(T);
588
589fn main() {
590 let a = Smol(0u32);
591 //^ Smol<u32>
592 let b = VeryLongOuterName(0usize);
593 //^ VeryLongOuterName<…>
594 let c = Smol(Smol(0u32))
595 //^ Smol<Smol<…>>
596}"#,
597 );
598 }
599
600 #[test]
601 fn function_call_parameter_hint() {
602 check(
603 r#"
604enum Option<T> { None, Some(T) }
605use Option::*;
606
607struct FileId {}
608struct SmolStr {}
609
610struct TextRange {}
611struct SyntaxKind {}
612struct NavigationTarget {}
613
614struct Test {}
615
616impl Test {
617 fn method(&self, mut param: i32) -> i32 { param * 2 }
618
619 fn from_syntax(
620 file_id: FileId,
621 name: SmolStr,
622 focus_range: Option<TextRange>,
623 full_range: TextRange,
624 kind: SyntaxKind,
625 docs: Option<String>,
626 ) -> NavigationTarget {
627 NavigationTarget {}
628 }
629}
630
631fn test_func(mut foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 {
632 foo + bar
633}
634
635fn main() {
636 let not_literal = 1;
637 //^^^^^^^^^^^ i32
638 let _: i32 = test_func(1, 2, "hello", 3, not_literal);
639 //^ foo ^ bar ^^^^^^^ msg ^^^^^^^^^^^ last
640 let t: Test = Test {};
641 t.method(123);
642 //^^^ param
643 Test::method(&t, 3456);
644 //^^ &self ^^^^ param
645 Test::from_syntax(
646 FileId {},
647 //^^^^^^^^^ file_id
648 "impl".into(),
649 //^^^^^^^^^^^^^ name
650 None,
651 //^^^^ focus_range
652 TextRange {},
653 //^^^^^^^^^^^^ full_range
654 SyntaxKind {},
655 //^^^^^^^^^^^^^ kind
656 None,
657 //^^^^ docs
658 );
659}"#,
660 );
661 }
662
663 #[test]
664 fn omitted_parameters_hints_heuristics() {
665 check_with_config(
666 InlayHintsConfig { max_length: Some(8), ..Default::default() },
667 r#"
668fn map(f: i32) {}
669fn filter(predicate: i32) {}
670
671struct TestVarContainer {
672 test_var: i32,
673}
674
675impl TestVarContainer {
676 fn test_var(&self) -> i32 {
677 self.test_var
678 }
679}
680
681struct Test {}
682
683impl Test {
684 fn map(self, f: i32) -> Self {
685 self
686 }
687
688 fn filter(self, predicate: i32) -> Self {
689 self
690 }
691
692 fn field(self, value: i32) -> Self {
693 self
694 }
695
696 fn no_hints_expected(&self, _: i32, test_var: i32) {}
697
698 fn frob(&self, frob: bool) {}
699}
700
701struct Param {}
702
703fn different_order(param: &Param) {}
704fn different_order_mut(param: &mut Param) {}
705fn has_underscore(_param: bool) {}
706fn enum_matches_param_name(completion_kind: CompletionKind) {}
707
708fn twiddle(twiddle: bool) {}
709fn doo(_doo: bool) {}
710
711enum CompletionKind {
712 Keyword,
713}
714
715fn main() {
716 let container: TestVarContainer = TestVarContainer { test_var: 42 };
717 let test: Test = Test {};
718
719 map(22);
720 filter(33);
721
722 let test_processed: Test = test.map(1).filter(2).field(3);
723
724 let test_var: i32 = 55;
725 test_processed.no_hints_expected(22, test_var);
726 test_processed.no_hints_expected(33, container.test_var);
727 test_processed.no_hints_expected(44, container.test_var());
728 test_processed.frob(false);
729
730 twiddle(true);
731 doo(true);
732
733 let mut param_begin: Param = Param {};
734 different_order(&param_begin);
735 different_order(&mut param_begin);
736
737 let param: bool = true;
738 has_underscore(param);
739
740 enum_matches_param_name(CompletionKind::Keyword);
741
742 let a: f64 = 7.0;
743 let b: f64 = 4.0;
744 let _: f64 = a.div_euclid(b);
745 let _: f64 = a.abs_sub(b);
746}"#,
747 );
748 }
749
750 #[test]
751 fn unit_structs_have_no_type_hints() {
752 check_with_config(
753 InlayHintsConfig { max_length: Some(8), ..Default::default() },
754 r#"
755enum Result<T, E> { Ok(T), Err(E) }
756use Result::*;
757
758struct SyntheticSyntax;
759
760fn main() {
761 match Ok(()) {
762 Ok(_) => (),
763 Err(SyntheticSyntax) => (),
764 }
765}"#,
766 );
767 }
768
769 #[test]
770 fn chaining_hints_ignore_comments() {
771 check_expect(
772 InlayHintsConfig {
773 parameter_hints: false,
774 type_hints: false,
775 chaining_hints: true,
776 max_length: None,
777 },
778 r#"
779struct A(B);
780impl A { fn into_b(self) -> B { self.0 } }
781struct B(C);
782impl B { fn into_c(self) -> C { self.0 } }
783struct C;
784
785fn main() {
786 let c = A(B(C))
787 .into_b() // This is a comment
788 .into_c();
789}
790"#,
791 expect![[r#"
792 [
793 InlayHint {
794 range: 147..172,
795 kind: ChainingHint,
796 label: "B",
797 },
798 InlayHint {
799 range: 147..154,
800 kind: ChainingHint,
801 label: "A",
802 },
803 ]
804 "#]],
805 );
806 }
807
808 #[test]
809 fn chaining_hints_without_newlines() {
810 check_with_config(
811 InlayHintsConfig {
812 parameter_hints: false,
813 type_hints: false,
814 chaining_hints: true,
815 max_length: None,
816 },
817 r#"
818struct A(B);
819impl A { fn into_b(self) -> B { self.0 } }
820struct B(C);
821impl B { fn into_c(self) -> C { self.0 } }
822struct C;
823
824fn main() {
825 let c = A(B(C)).into_b().into_c();
826}"#,
827 );
828 }
829
830 #[test]
831 fn struct_access_chaining_hints() {
832 check_expect(
833 InlayHintsConfig {
834 parameter_hints: false,
835 type_hints: false,
836 chaining_hints: true,
837 max_length: None,
838 },
839 r#"
840struct A { pub b: B }
841struct B { pub c: C }
842struct C(pub bool);
843struct D;
844
845impl D {
846 fn foo(&self) -> i32 { 42 }
847}
848
849fn main() {
850 let x = A { b: B { c: C(true) } }
851 .b
852 .c
853 .0;
854 let x = D
855 .foo();
856}"#,
857 expect![[r#"
858 [
859 InlayHint {
860 range: 143..190,
861 kind: ChainingHint,
862 label: "C",
863 },
864 InlayHint {
865 range: 143..179,
866 kind: ChainingHint,
867 label: "B",
868 },
869 ]
870 "#]],
871 );
872 }
873
874 #[test]
875 fn generic_chaining_hints() {
876 check_expect(
877 InlayHintsConfig {
878 parameter_hints: false,
879 type_hints: false,
880 chaining_hints: true,
881 max_length: None,
882 },
883 r#"
884struct A<T>(T);
885struct B<T>(T);
886struct C<T>(T);
887struct X<T,R>(T, R);
888
889impl<T> A<T> {
890 fn new(t: T) -> Self { A(t) }
891 fn into_b(self) -> B<T> { B(self.0) }
892}
893impl<T> B<T> {
894 fn into_c(self) -> C<T> { C(self.0) }
895}
896fn main() {
897 let c = A::new(X(42, true))
898 .into_b()
899 .into_c();
900}
901"#,
902 expect![[r#"
903 [
904 InlayHint {
905 range: 246..283,
906 kind: ChainingHint,
907 label: "B<X<i32, bool>>",
908 },
909 InlayHint {
910 range: 246..265,
911 kind: ChainingHint,
912 label: "A<X<i32, bool>>",
913 },
914 ]
915 "#]],
916 );
917 }
918}