diff options
Diffstat (limited to 'crates/ra_ide')
-rw-r--r-- | crates/ra_ide/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/complete_postfix.rs | 140 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/complete_record_literal.rs | 62 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/complete_trait_impl.rs | 2 | ||||
-rw-r--r-- | crates/ra_ide/src/expand_macro.rs | 12 | ||||
-rw-r--r-- | crates/ra_ide/src/goto_definition.rs | 24 | ||||
-rw-r--r-- | crates/ra_ide/src/inlay_hints.rs | 176 |
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 = [] | |||
14 | either = "1.5.3" | 14 | either = "1.5.3" |
15 | format-buf = "1.0.0" | 15 | format-buf = "1.0.0" |
16 | indexmap = "1.3.2" | 16 | indexmap = "1.3.2" |
17 | itertools = "0.8.2" | 17 | itertools = "0.9.0" |
18 | join_to_string = "0.1.3" | 18 | join_to_string = "0.1.3" |
19 | log = "0.4.8" | 19 | log = "0.4.8" |
20 | rustc-hash = "1.1.0" | 20 | rustc-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 | ||
3 | use ra_syntax::{ast::AstNode, TextRange, TextUnit}; | 3 | use ra_syntax::{ |
4 | ast::{self, AstNode}, | ||
5 | TextRange, TextUnit, | ||
6 | }; | ||
4 | use ra_text_edit::TextEdit; | 7 | use ra_text_edit::TextEdit; |
5 | 8 | ||
6 | use crate::{ | 9 | use 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 | ||
68 | fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder { | 89 | fn 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 | |||
99 | fn 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 | |||
109 | fn 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 | ||
3 | use crate::completion::{CompletionContext, Completions}; | 3 | use crate::completion::{CompletionContext, Completions}; |
4 | use ra_syntax::SmolStr; | ||
4 | 5 | ||
5 | /// Complete fields in fields literals. | 6 | /// Complete fields in fields literals. |
6 | pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionContext) { | 7 | pub(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 | ||
195 | fn make_const_compl_syntax(const_: &ast::ConstDef) -> String { | 195 | fn 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 @@ | |||
3 | use hir::Semantics; | 3 | use hir::Semantics; |
4 | use ra_ide_db::RootDatabase; | 4 | use ra_ide_db::RootDatabase; |
5 | use ra_syntax::{ | 5 | use 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 | }; |
9 | use rustc_hash::FxHashMap; | ||
10 | 9 | ||
11 | use crate::FilePosition; | 10 | use 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; | |||
5 | use ra_prof::profile; | 5 | use ra_prof::profile; |
6 | use ra_syntax::{ | 6 | use 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 | ||
11 | use crate::{FileId, FunctionSignature}; | 11 | use crate::{FileId, FunctionSignature}; |
@@ -14,12 +14,13 @@ use crate::{FileId, FunctionSignature}; | |||
14 | pub struct InlayHintsOptions { | 14 | pub 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 | ||
20 | impl Default for InlayHintsOptions { | 21 | impl 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 { | |||
27 | pub enum InlayKind { | 28 | pub 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 | ||
68 | fn 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 | |||
62 | fn get_param_name_hints( | 108 | fn 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 | } |