aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/Cargo.toml2
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs140
-rw-r--r--crates/ra_ide/src/completion/complete_record_literal.rs62
-rw-r--r--crates/ra_ide/src/completion/complete_trait_impl.rs2
-rw-r--r--crates/ra_ide/src/expand_macro.rs12
-rw-r--r--crates/ra_ide/src/goto_definition.rs24
-rw-r--r--crates/ra_ide/src/inlay_hints.rs176
7 files changed, 385 insertions, 33 deletions
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml
index 7235c944c..36eec0e60 100644
--- a/crates/ra_ide/Cargo.toml
+++ b/crates/ra_ide/Cargo.toml
@@ -14,7 +14,7 @@ wasm = []
14either = "1.5.3" 14either = "1.5.3"
15format-buf = "1.0.0" 15format-buf = "1.0.0"
16indexmap = "1.3.2" 16indexmap = "1.3.2"
17itertools = "0.8.2" 17itertools = "0.9.0"
18join_to_string = "0.1.3" 18join_to_string = "0.1.3"
19log = "0.4.8" 19log = "0.4.8"
20rustc-hash = "1.1.0" 20rustc-hash = "1.1.0"
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs
index 0ba382165..0a00054b2 100644
--- a/crates/ra_ide/src/completion/complete_postfix.rs
+++ b/crates/ra_ide/src/completion/complete_postfix.rs
@@ -1,6 +1,9 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use ra_syntax::{ast::AstNode, TextRange, TextUnit}; 3use ra_syntax::{
4 ast::{self, AstNode},
5 TextRange, TextUnit,
6};
4use ra_text_edit::TextEdit; 7use ra_text_edit::TextEdit;
5 8
6use crate::{ 9use crate::{
@@ -21,13 +24,8 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
21 None => return, 24 None => return,
22 }; 25 };
23 26
24 let receiver_text = if ctx.dot_receiver_is_ambiguous_float_literal { 27 let receiver_text =
25 let text = dot_receiver.syntax().text(); 28 get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
26 let without_dot = ..text.len() - TextUnit::of_char('.');
27 text.slice(without_dot).to_string()
28 } else {
29 dot_receiver.syntax().text().to_string()
30 };
31 29
32 let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { 30 let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) {
33 Some(it) => it, 31 Some(it) => it,
@@ -35,10 +33,17 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
35 }; 33 };
36 34
37 if receiver_ty.is_bool() || receiver_ty.is_unknown() { 35 if receiver_ty.is_bool() || receiver_ty.is_unknown() {
38 postfix_snippet(ctx, "if", "if expr {}", &format!("if {} {{$0}}", receiver_text))
39 .add_to(acc);
40 postfix_snippet( 36 postfix_snippet(
41 ctx, 37 ctx,
38 &dot_receiver,
39 "if",
40 "if expr {}",
41 &format!("if {} {{$0}}", receiver_text),
42 )
43 .add_to(acc);
44 postfix_snippet(
45 ctx,
46 &dot_receiver,
42 "while", 47 "while",
43 "while expr {}", 48 "while expr {}",
44 &format!("while {} {{\n$0\n}}", receiver_text), 49 &format!("while {} {{\n$0\n}}", receiver_text),
@@ -46,28 +51,70 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
46 .add_to(acc); 51 .add_to(acc);
47 } 52 }
48 53
49 postfix_snippet(ctx, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc); 54 // !&&&42 is a compiler error, ergo process it before considering the references
55 postfix_snippet(ctx, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc);
50 56
51 postfix_snippet(ctx, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc); 57 postfix_snippet(ctx, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc);
52 postfix_snippet(ctx, "refm", "&mut expr", &format!("&mut {}", receiver_text)).add_to(acc); 58 postfix_snippet(ctx, &dot_receiver, "refm", "&mut expr", &format!("&mut {}", receiver_text))
59 .add_to(acc);
60
61 // The rest of the postfix completions create an expression that moves an argument,
62 // so it's better to consider references now to avoid breaking the compilation
63 let dot_receiver = include_references(dot_receiver);
64 let receiver_text =
65 get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
53 66
54 postfix_snippet( 67 postfix_snippet(
55 ctx, 68 ctx,
69 &dot_receiver,
56 "match", 70 "match",
57 "match expr {}", 71 "match expr {}",
58 &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text), 72 &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text),
59 ) 73 )
60 .add_to(acc); 74 .add_to(acc);
61 75
62 postfix_snippet(ctx, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc); 76 postfix_snippet(
77 ctx,
78 &dot_receiver,
79 "box",
80 "Box::new(expr)",
81 &format!("Box::new({})", receiver_text),
82 )
83 .add_to(acc);
63 84
64 postfix_snippet(ctx, "box", "Box::new(expr)", &format!("Box::new({})", receiver_text)) 85 postfix_snippet(ctx, &dot_receiver, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text))
65 .add_to(acc); 86 .add_to(acc);
66} 87}
67 88
68fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder { 89fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
90 if receiver_is_ambiguous_float_literal {
91 let text = receiver.syntax().text();
92 let without_dot = ..text.len() - TextUnit::of_char('.');
93 text.slice(without_dot).to_string()
94 } else {
95 receiver.to_string()
96 }
97}
98
99fn include_references(initial_element: &ast::Expr) -> ast::Expr {
100 let mut resulting_element = initial_element.clone();
101 while let Some(parent_ref_element) =
102 resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
103 {
104 resulting_element = ast::Expr::from(parent_ref_element);
105 }
106 resulting_element
107}
108
109fn postfix_snippet(
110 ctx: &CompletionContext,
111 receiver: &ast::Expr,
112 label: &str,
113 detail: &str,
114 snippet: &str,
115) -> Builder {
69 let edit = { 116 let edit = {
70 let receiver_syntax = ctx.dot_receiver.as_ref().expect("no receiver available").syntax(); 117 let receiver_syntax = receiver.syntax();
71 let receiver_range = ctx.sema.original_range(receiver_syntax).range; 118 let receiver_range = ctx.sema.original_range(receiver_syntax).range;
72 let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end()); 119 let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end());
73 TextEdit::replace(delete_range, snippet.to_string()) 120 TextEdit::replace(delete_range, snippet.to_string())
@@ -340,4 +387,63 @@ mod tests {
340 "### 387 "###
341 ); 388 );
342 } 389 }
390
391 #[test]
392 fn postfix_completion_for_references() {
393 assert_debug_snapshot!(
394 do_postfix_completion(
395 r#"
396 fn main() {
397 &&&&42.<|>
398 }
399 "#,
400 ),
401 @r###"
402 [
403 CompletionItem {
404 label: "box",
405 source_range: [56; 56),
406 delete: [49; 56),
407 insert: "Box::new(&&&&42)",
408 detail: "Box::new(expr)",
409 },
410 CompletionItem {
411 label: "dbg",
412 source_range: [56; 56),
413 delete: [49; 56),
414 insert: "dbg!(&&&&42)",
415 detail: "dbg!(expr)",
416 },
417 CompletionItem {
418 label: "match",
419 source_range: [56; 56),
420 delete: [49; 56),
421 insert: "match &&&&42 {\n ${1:_} => {$0\\},\n}",
422 detail: "match expr {}",
423 },
424 CompletionItem {
425 label: "not",
426 source_range: [56; 56),
427 delete: [53; 56),
428 insert: "!42",
429 detail: "!expr",
430 },
431 CompletionItem {
432 label: "ref",
433 source_range: [56; 56),
434 delete: [53; 56),
435 insert: "&42",
436 detail: "&expr",
437 },
438 CompletionItem {
439 label: "refm",
440 source_range: [56; 56),
441 delete: [53; 56),
442 insert: "&mut 42",
443 detail: "&mut expr",
444 },
445 ]
446 "###
447 );
448 }
343} 449}
diff --git a/crates/ra_ide/src/completion/complete_record_literal.rs b/crates/ra_ide/src/completion/complete_record_literal.rs
index 83ed1d52c..e4e764f58 100644
--- a/crates/ra_ide/src/completion/complete_record_literal.rs
+++ b/crates/ra_ide/src/completion/complete_record_literal.rs
@@ -1,6 +1,7 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use crate::completion::{CompletionContext, Completions}; 3use crate::completion::{CompletionContext, Completions};
4use ra_syntax::SmolStr;
4 5
5/// Complete fields in fields literals. 6/// Complete fields in fields literals.
6pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionContext) { 7pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionContext) {
@@ -11,8 +12,24 @@ pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionCon
11 _ => return, 12 _ => return,
12 }; 13 };
13 14
15 let already_present_names: Vec<SmolStr> = ctx
16 .record_lit_syntax
17 .as_ref()
18 .and_then(|record_literal| record_literal.record_field_list())
19 .map(|field_list| field_list.fields())
20 .map(|fields| {
21 fields
22 .into_iter()
23 .filter_map(|field| field.name_ref())
24 .map(|name_ref| name_ref.text().clone())
25 .collect()
26 })
27 .unwrap_or_default();
28
14 for (field, field_ty) in ty.variant_fields(ctx.db, variant) { 29 for (field, field_ty) in ty.variant_fields(ctx.db, variant) {
15 acc.add_field(ctx, field, &field_ty); 30 if !already_present_names.contains(&SmolStr::from(field.name(ctx.db).to_string())) {
31 acc.add_field(ctx, field, &field_ty);
32 }
16 } 33 }
17} 34}
18 35
@@ -178,4 +195,47 @@ mod tests {
178 ] 195 ]
179 "###); 196 "###);
180 } 197 }
198
199 #[test]
200 fn only_missing_fields_are_completed() {
201 let completions = complete(
202 r"
203 struct S {
204 foo1: u32,
205 foo2: u32,
206 bar: u32,
207 baz: u32,
208 }
209
210 fn main() {
211 let foo1 = 1;
212 let s = S {
213 foo1,
214 foo2: 5,
215 <|>
216 }
217 }
218 ",
219 );
220 assert_debug_snapshot!(completions, @r###"
221 [
222 CompletionItem {
223 label: "bar",
224 source_range: [302; 302),
225 delete: [302; 302),
226 insert: "bar",
227 kind: Field,
228 detail: "u32",
229 },
230 CompletionItem {
231 label: "baz",
232 source_range: [302; 302),
233 delete: [302; 302),
234 insert: "baz",
235 kind: Field,
236 detail: "u32",
237 },
238 ]
239 "###);
240 }
181} 241}
diff --git a/crates/ra_ide/src/completion/complete_trait_impl.rs b/crates/ra_ide/src/completion/complete_trait_impl.rs
index 7fefa2c7a..ded1ff3bc 100644
--- a/crates/ra_ide/src/completion/complete_trait_impl.rs
+++ b/crates/ra_ide/src/completion/complete_trait_impl.rs
@@ -193,7 +193,7 @@ fn add_const_impl(
193} 193}
194 194
195fn make_const_compl_syntax(const_: &ast::ConstDef) -> String { 195fn make_const_compl_syntax(const_: &ast::ConstDef) -> String {
196 let const_ = edit::strip_attrs_and_docs(const_); 196 let const_ = edit::remove_attrs_and_docs(const_);
197 197
198 let const_start = const_.syntax().text_range().start(); 198 let const_start = const_.syntax().text_range().start();
199 let const_end = const_.syntax().text_range().end(); 199 let const_end = const_.syntax().text_range().end();
diff --git a/crates/ra_ide/src/expand_macro.rs b/crates/ra_ide/src/expand_macro.rs
index e58526f31..f536ba3e7 100644
--- a/crates/ra_ide/src/expand_macro.rs
+++ b/crates/ra_ide/src/expand_macro.rs
@@ -3,10 +3,9 @@
3use hir::Semantics; 3use hir::Semantics;
4use ra_ide_db::RootDatabase; 4use ra_ide_db::RootDatabase;
5use ra_syntax::{ 5use ra_syntax::{
6 algo::{find_node_at_offset, replace_descendants}, 6 algo::{find_node_at_offset, SyntaxRewriter},
7 ast, AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, WalkEvent, T, 7 ast, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent, T,
8}; 8};
9use rustc_hash::FxHashMap;
10 9
11use crate::FilePosition; 10use crate::FilePosition;
12 11
@@ -37,7 +36,7 @@ fn expand_macro_recur(
37 let mut expanded = sema.expand(macro_call)?; 36 let mut expanded = sema.expand(macro_call)?;
38 37
39 let children = expanded.descendants().filter_map(ast::MacroCall::cast); 38 let children = expanded.descendants().filter_map(ast::MacroCall::cast);
40 let mut replaces: FxHashMap<SyntaxElement, SyntaxElement> = FxHashMap::default(); 39 let mut rewriter = SyntaxRewriter::default();
41 40
42 for child in children.into_iter() { 41 for child in children.into_iter() {
43 if let Some(new_node) = expand_macro_recur(sema, &child) { 42 if let Some(new_node) = expand_macro_recur(sema, &child) {
@@ -47,12 +46,13 @@ fn expand_macro_recur(
47 if expanded == *child.syntax() { 46 if expanded == *child.syntax() {
48 expanded = new_node; 47 expanded = new_node;
49 } else { 48 } else {
50 replaces.insert(child.syntax().clone().into(), new_node.into()); 49 rewriter.replace(child.syntax(), &new_node)
51 } 50 }
52 } 51 }
53 } 52 }
54 53
55 Some(replace_descendants(&expanded, |n| replaces.get(n).cloned())) 54 let res = rewriter.rewrite(&expanded);
55 Some(res)
56} 56}
57 57
58// FIXME: It would also be cool to share logic here and in the mbe tests, 58// FIXME: It would also be cool to share logic here and in the mbe tests,
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs
index 502fcb0cf..8aed94d16 100644
--- a/crates/ra_ide/src/goto_definition.rs
+++ b/crates/ra_ide/src/goto_definition.rs
@@ -104,6 +104,9 @@ mod tests {
104 let (analysis, pos) = analysis_and_position(ra_fixture); 104 let (analysis, pos) = analysis_and_position(ra_fixture);
105 105
106 let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info; 106 let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info;
107 if navs.len() == 0 {
108 panic!("unresolved reference")
109 }
107 assert_eq!(navs.len(), 1); 110 assert_eq!(navs.len(), 1);
108 111
109 let nav = navs.pop().unwrap(); 112 let nav = navs.pop().unwrap();
@@ -359,7 +362,7 @@ mod tests {
359 fn goto_def_for_fields() { 362 fn goto_def_for_fields() {
360 covers!(ra_ide_db::goto_def_for_fields); 363 covers!(ra_ide_db::goto_def_for_fields);
361 check_goto( 364 check_goto(
362 " 365 r"
363 //- /lib.rs 366 //- /lib.rs
364 struct Foo { 367 struct Foo {
365 spam: u32, 368 spam: u32,
@@ -378,7 +381,7 @@ mod tests {
378 fn goto_def_for_record_fields() { 381 fn goto_def_for_record_fields() {
379 covers!(ra_ide_db::goto_def_for_record_fields); 382 covers!(ra_ide_db::goto_def_for_record_fields);
380 check_goto( 383 check_goto(
381 " 384 r"
382 //- /lib.rs 385 //- /lib.rs
383 struct Foo { 386 struct Foo {
384 spam: u32, 387 spam: u32,
@@ -396,6 +399,23 @@ mod tests {
396 } 399 }
397 400
398 #[test] 401 #[test]
402 fn goto_def_for_record_fields_macros() {
403 check_goto(
404 r"
405 //- /lib.rs
406 macro_rules! m { () => { 92 };}
407 struct Foo { spam: u32 }
408
409 fn bar() -> Foo {
410 Foo { spam<|>: m!() }
411 }
412 ",
413 "spam RECORD_FIELD_DEF FileId(1) [45; 54) [45; 49)",
414 "spam: u32|spam",
415 );
416 }
417
418 #[test]
399 fn goto_for_tuple_fields() { 419 fn goto_for_tuple_fields() {
400 check_goto( 420 check_goto(
401 " 421 "
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs
index ecd615cf4..f4f0751c0 100644
--- a/crates/ra_ide/src/inlay_hints.rs
+++ b/crates/ra_ide/src/inlay_hints.rs
@@ -5,7 +5,7 @@ use ra_ide_db::RootDatabase;
5use ra_prof::profile; 5use ra_prof::profile;
6use ra_syntax::{ 6use ra_syntax::{
7 ast::{self, ArgListOwner, AstNode, TypeAscriptionOwner}, 7 ast::{self, ArgListOwner, AstNode, TypeAscriptionOwner},
8 match_ast, SmolStr, TextRange, 8 match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, TextRange,
9}; 9};
10 10
11use crate::{FileId, FunctionSignature}; 11use crate::{FileId, FunctionSignature};
@@ -14,12 +14,13 @@ use crate::{FileId, FunctionSignature};
14pub struct InlayHintsOptions { 14pub struct InlayHintsOptions {
15 pub type_hints: bool, 15 pub type_hints: bool,
16 pub parameter_hints: bool, 16 pub parameter_hints: bool,
17 pub chaining_hints: bool,
17 pub max_length: Option<usize>, 18 pub max_length: Option<usize>,
18} 19}
19 20
20impl Default for InlayHintsOptions { 21impl Default for InlayHintsOptions {
21 fn default() -> Self { 22 fn default() -> Self {
22 Self { type_hints: true, parameter_hints: true, max_length: None } 23 Self { type_hints: true, parameter_hints: true, chaining_hints: true, max_length: None }
23 } 24 }
24} 25}
25 26
@@ -27,6 +28,7 @@ impl Default for InlayHintsOptions {
27pub enum InlayKind { 28pub enum InlayKind {
28 TypeHint, 29 TypeHint,
29 ParameterHint, 30 ParameterHint,
31 ChainingHint,
30} 32}
31 33
32#[derive(Debug)] 34#[derive(Debug)]
@@ -47,6 +49,10 @@ pub(crate) fn inlay_hints(
47 49
48 let mut res = Vec::new(); 50 let mut res = Vec::new();
49 for node in file.syntax().descendants() { 51 for node in file.syntax().descendants() {
52 if let Some(expr) = ast::Expr::cast(node.clone()) {
53 get_chaining_hints(&mut res, &sema, options, expr);
54 }
55
50 match_ast! { 56 match_ast! {
51 match node { 57 match node {
52 ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, options, ast::Expr::from(it)); }, 58 ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, options, ast::Expr::from(it)); },
@@ -59,6 +65,46 @@ pub(crate) fn inlay_hints(
59 res 65 res
60} 66}
61 67
68fn get_chaining_hints(
69 acc: &mut Vec<InlayHint>,
70 sema: &Semantics<RootDatabase>,
71 options: &InlayHintsOptions,
72 expr: ast::Expr,
73) -> Option<()> {
74 if !options.chaining_hints {
75 return None;
76 }
77
78 let ty = sema.type_of_expr(&expr)?;
79 if ty.is_unknown() {
80 return None;
81 }
82
83 let mut tokens = expr
84 .syntax()
85 .siblings_with_tokens(Direction::Next)
86 .filter_map(NodeOrToken::into_token)
87 .filter(|t| match t.kind() {
88 SyntaxKind::WHITESPACE if !t.text().contains('\n') => false,
89 SyntaxKind::COMMENT => false,
90 _ => true,
91 });
92
93 // Chaining can be defined as an expression whose next sibling tokens are newline and dot
94 // Ignoring extra whitespace and comments
95 let next = tokens.next()?.kind();
96 let next_next = tokens.next()?.kind();
97 if next == SyntaxKind::WHITESPACE && next_next == SyntaxKind::DOT {
98 let label = ty.display_truncated(sema.db, options.max_length).to_string();
99 acc.push(InlayHint {
100 range: expr.syntax().text_range(),
101 kind: InlayKind::ChainingHint,
102 label: label.into(),
103 });
104 }
105 Some(())
106}
107
62fn get_param_name_hints( 108fn get_param_name_hints(
63 acc: &mut Vec<InlayHint>, 109 acc: &mut Vec<InlayHint>,
64 sema: &Semantics<RootDatabase>, 110 sema: &Semantics<RootDatabase>,
@@ -238,7 +284,7 @@ mod tests {
238 let _x = foo(4, 4); 284 let _x = foo(4, 4);
239 }"#, 285 }"#,
240 ); 286 );
241 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: true, type_hints: false, max_length: None}).unwrap(), @r###" 287 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: true, type_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###"
242 [ 288 [
243 InlayHint { 289 InlayHint {
244 range: [106; 107), 290 range: [106; 107),
@@ -262,7 +308,7 @@ mod tests {
262 let _x = foo(4, 4); 308 let _x = foo(4, 4);
263 }"#, 309 }"#,
264 ); 310 );
265 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: false, parameter_hints: false, max_length: None}).unwrap(), @r###"[]"###); 311 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: false, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###"[]"###);
266 } 312 }
267 313
268 #[test] 314 #[test]
@@ -274,7 +320,7 @@ mod tests {
274 let _x = foo(4, 4); 320 let _x = foo(4, 4);
275 }"#, 321 }"#,
276 ); 322 );
277 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: true, parameter_hints: false, max_length: None}).unwrap(), @r###" 323 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: true, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###"
278 [ 324 [
279 InlayHint { 325 InlayHint {
280 range: [97; 99), 326 range: [97; 99),
@@ -1052,4 +1098,124 @@ fn main() {
1052 "### 1098 "###
1053 ); 1099 );
1054 } 1100 }
1101
1102 #[test]
1103 fn chaining_hints_ignore_comments() {
1104 let (analysis, file_id) = single_file(
1105 r#"
1106 struct A(B);
1107 impl A { fn into_b(self) -> B { self.0 } }
1108 struct B(C);
1109 impl B { fn into_c(self) -> C { self.0 } }
1110 struct C;
1111
1112 fn main() {
1113 let c = A(B(C))
1114 .into_b() // This is a comment
1115 .into_c();
1116 }"#,
1117 );
1118 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"
1119 [
1120 InlayHint {
1121 range: [232; 269),
1122 kind: ChainingHint,
1123 label: "B",
1124 },
1125 InlayHint {
1126 range: [232; 239),
1127 kind: ChainingHint,
1128 label: "A",
1129 },
1130 ]"###);
1131 }
1132
1133 #[test]
1134 fn chaining_hints_without_newlines() {
1135 let (analysis, file_id) = single_file(
1136 r#"
1137 struct A(B);
1138 impl A { fn into_b(self) -> B { self.0 } }
1139 struct B(C);
1140 impl B { fn into_c(self) -> C { self.0 } }
1141 struct C;
1142
1143 fn main() {
1144 let c = A(B(C)).into_b().into_c();
1145 }"#,
1146 );
1147 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"[]"###);
1148 }
1149
1150 #[test]
1151 fn struct_access_chaining_hints() {
1152 let (analysis, file_id) = single_file(
1153 r#"
1154 struct A { pub b: B }
1155 struct B { pub c: C }
1156 struct C(pub bool);
1157
1158 fn main() {
1159 let x = A { b: B { c: C(true) } }
1160 .b
1161 .c
1162 .0;
1163 }"#,
1164 );
1165 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"
1166 [
1167 InlayHint {
1168 range: [150; 221),
1169 kind: ChainingHint,
1170 label: "C",
1171 },
1172 InlayHint {
1173 range: [150; 198),
1174 kind: ChainingHint,
1175 label: "B",
1176 },
1177 InlayHint {
1178 range: [150; 175),
1179 kind: ChainingHint,
1180 label: "A",
1181 },
1182 ]"###);
1183 }
1184
1185 #[test]
1186 fn generic_chaining_hints() {
1187 let (analysis, file_id) = single_file(
1188 r#"
1189 struct A<T>(T);
1190 struct B<T>(T);
1191 struct C<T>(T);
1192 struct X<T,R>(T, R);
1193
1194 impl<T> A<T> {
1195 fn new(t: T) -> Self { A(t) }
1196 fn into_b(self) -> B<T> { B(self.0) }
1197 }
1198 impl<T> B<T> {
1199 fn into_c(self) -> C<T> { C(self.0) }
1200 }
1201 fn main() {
1202 let c = A::new(X(42, true))
1203 .into_b()
1204 .into_c();
1205 }"#,
1206 );
1207 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"
1208 [
1209 InlayHint {
1210 range: [403; 452),
1211 kind: ChainingHint,
1212 label: "B<X<i32, bool>>",
1213 },
1214 InlayHint {
1215 range: [403; 422),
1216 kind: ChainingHint,
1217 label: "A<X<i32, bool>>",
1218 },
1219 ]"###);
1220 }
1055} 1221}