]> git.lizzy.rs Git - rust.git/blob - crates/ra_ide_api/src/hover.rs
Fix test_missing_module_code_action_in_json_project on Windows
[rust.git] / crates / ra_ide_api / src / hover.rs
1 use ra_db::SourceDatabase;
2 use ra_syntax::{
3     AstNode, SyntaxNode, TreeArc, ast::{self, NameOwner, VisibilityOwner, TypeAscriptionOwner},
4     algo::{find_covering_node, find_node_at_offset, find_leaf_at_offset, visit::{visitor, Visitor}},
5 };
6
7 use crate::{db::RootDatabase, RangeInfo, FilePosition, FileRange, NavigationTarget};
8
9 /// Contains the results when hovering over an item
10 #[derive(Debug, Clone)]
11 pub struct HoverResult {
12     results: Vec<String>,
13     exact: bool,
14 }
15
16 impl HoverResult {
17     pub fn new() -> HoverResult {
18         HoverResult {
19             results: Vec::new(),
20             // We assume exact by default
21             exact: true,
22         }
23     }
24
25     pub fn extend(&mut self, item: Option<String>) {
26         self.results.extend(item);
27     }
28
29     pub fn is_exact(&self) -> bool {
30         self.exact
31     }
32
33     pub fn is_empty(&self) -> bool {
34         self.results.is_empty()
35     }
36
37     pub fn len(&self) -> usize {
38         self.results.len()
39     }
40
41     pub fn first(&self) -> Option<&str> {
42         self.results.first().map(String::as_str)
43     }
44
45     pub fn results(&self) -> &[String] {
46         &self.results
47     }
48
49     /// Returns the results converted into markup
50     /// for displaying in a UI
51     pub fn to_markup(&self) -> String {
52         let mut markup = if !self.exact {
53             let mut msg = String::from("Failed to exactly resolve the symbol. This is probably because rust_analyzer does not yet support traits.");
54             if !self.results.is_empty() {
55                 msg.push_str("  \nThese items were found instead:");
56             }
57             msg.push_str("\n\n---\n");
58             msg
59         } else {
60             String::new()
61         };
62
63         markup.push_str(&self.results.join("\n\n---\n"));
64
65         markup
66     }
67 }
68
69 pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> {
70     let file = db.parse(position.file_id);
71     let mut res = HoverResult::new();
72
73     let mut range = None;
74     if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) {
75         use crate::goto_definition::{ReferenceResult::*, reference_definition};
76         let ref_result = reference_definition(db, position.file_id, name_ref);
77         match ref_result {
78             Exact(nav) => res.extend(doc_text_for(db, nav)),
79             Approximate(navs) => {
80                 // We are no longer exact
81                 res.exact = false;
82
83                 for nav in navs {
84                     res.extend(doc_text_for(db, nav))
85                 }
86             }
87         }
88         if !res.is_empty() {
89             range = Some(name_ref.syntax().range())
90         }
91     } else if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) {
92         let navs = crate::goto_definition::name_definition(db, position.file_id, name);
93
94         if let Some(navs) = navs {
95             for nav in navs {
96                 res.extend(doc_text_for(db, nav))
97             }
98         }
99
100         if !res.is_empty() && range.is_none() {
101             range = Some(name.syntax().range());
102         }
103     }
104
105     if range.is_none() {
106         let node = find_leaf_at_offset(file.syntax(), position.offset).find_map(|leaf| {
107             leaf.ancestors().find(|n| ast::Expr::cast(*n).is_some() || ast::Pat::cast(*n).is_some())
108         })?;
109         let frange = FileRange { file_id: position.file_id, range: node.range() };
110         res.extend(type_of(db, frange).map(rust_code_markup));
111         range = Some(node.range());
112     }
113
114     let range = range?;
115     if res.is_empty() {
116         return None;
117     }
118     let res = RangeInfo::new(range, res);
119     Some(res)
120 }
121
122 pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> {
123     let file = db.parse(frange.file_id);
124     let syntax = file.syntax();
125     let leaf_node = find_covering_node(syntax, frange.range);
126     // if we picked identifier, expand to pattern/expression
127     let node = leaf_node
128         .ancestors()
129         .take_while(|it| it.range() == leaf_node.range())
130         .find(|&it| ast::Expr::cast(it).is_some() || ast::Pat::cast(it).is_some())
131         .unwrap_or(leaf_node);
132     let parent_fn = node.ancestors().find_map(ast::FnDef::cast)?;
133     let function = hir::source_binder::function_from_source(db, frange.file_id, parent_fn)?;
134     let infer = function.infer(db);
135     let source_map = function.body_source_map(db);
136     if let Some(expr) = ast::Expr::cast(node).and_then(|e| source_map.node_expr(e)) {
137         Some(infer[expr].to_string())
138     } else if let Some(pat) = ast::Pat::cast(node).and_then(|p| source_map.node_pat(p)) {
139         Some(infer[pat].to_string())
140     } else {
141         None
142     }
143 }
144
145 fn rust_code_markup<CODE: AsRef<str>>(val: CODE) -> String {
146     rust_code_markup_with_doc::<_, &str>(val, None)
147 }
148
149 fn rust_code_markup_with_doc<CODE, DOC>(val: CODE, doc: Option<DOC>) -> String
150 where
151     CODE: AsRef<str>,
152     DOC: AsRef<str>,
153 {
154     if let Some(doc) = doc {
155         format!("```rust\n{}\n```\n\n{}", val.as_ref(), doc.as_ref())
156     } else {
157         format!("```rust\n{}\n```", val.as_ref())
158     }
159 }
160
161 // FIXME: this should not really use navigation target. Rather, approximately
162 // resolved symbol should return a `DefId`.
163 fn doc_text_for(db: &RootDatabase, nav: NavigationTarget) -> Option<String> {
164     match (nav.description(db), nav.docs(db)) {
165         (Some(desc), docs) => Some(rust_code_markup_with_doc(desc, docs)),
166         (None, Some(docs)) => Some(docs),
167         _ => None,
168     }
169 }
170
171 impl NavigationTarget {
172     fn node(&self, db: &RootDatabase) -> Option<TreeArc<SyntaxNode>> {
173         let source_file = db.parse(self.file_id());
174         let source_file = source_file.syntax();
175         let node = source_file
176             .descendants()
177             .find(|node| node.kind() == self.kind() && node.range() == self.full_range())?
178             .to_owned();
179         Some(node)
180     }
181
182     fn docs(&self, db: &RootDatabase) -> Option<String> {
183         let node = self.node(db)?;
184         fn doc_comments<N: ast::DocCommentsOwner>(node: &N) -> Option<String> {
185             node.doc_comment_text()
186         }
187
188         visitor()
189             .visit(doc_comments::<ast::FnDef>)
190             .visit(doc_comments::<ast::StructDef>)
191             .visit(doc_comments::<ast::EnumDef>)
192             .visit(doc_comments::<ast::TraitDef>)
193             .visit(doc_comments::<ast::Module>)
194             .visit(doc_comments::<ast::TypeAliasDef>)
195             .visit(doc_comments::<ast::ConstDef>)
196             .visit(doc_comments::<ast::StaticDef>)
197             .visit(doc_comments::<ast::NamedFieldDef>)
198             .accept(&node)?
199     }
200
201     /// Get a description of this node.
202     ///
203     /// e.g. `struct Name`, `enum Name`, `fn Name`
204     fn description(&self, db: &RootDatabase) -> Option<String> {
205         // TODO: After type inference is done, add type information to improve the output
206         let node = self.node(db)?;
207
208         fn visit_ascribed_node<T>(node: &T, prefix: &str) -> Option<String>
209         where
210             T: NameOwner + VisibilityOwner + TypeAscriptionOwner,
211         {
212             let mut string = visit_node(node, prefix)?;
213
214             if let Some(type_ref) = node.ascribed_type() {
215                 string.push_str(": ");
216                 type_ref.syntax().text().push_to(&mut string);
217             }
218
219             Some(string)
220         }
221
222         fn visit_node<T>(node: &T, label: &str) -> Option<String>
223         where
224             T: NameOwner + VisibilityOwner,
225         {
226             let mut string =
227                 node.visibility().map(|v| format!("{} ", v.syntax().text())).unwrap_or_default();
228             string.push_str(label);
229             node.name()?.syntax().text().push_to(&mut string);
230             Some(string)
231         }
232
233         visitor()
234             .visit(crate::completion::function_label)
235             .visit(|node: &ast::StructDef| visit_node(node, "struct "))
236             .visit(|node: &ast::EnumDef| visit_node(node, "enum "))
237             .visit(|node: &ast::TraitDef| visit_node(node, "trait "))
238             .visit(|node: &ast::Module| visit_node(node, "mod "))
239             .visit(|node: &ast::TypeAliasDef| visit_node(node, "type "))
240             .visit(|node: &ast::ConstDef| visit_ascribed_node(node, "const "))
241             .visit(|node: &ast::StaticDef| visit_ascribed_node(node, "static "))
242             .visit(|node: &ast::NamedFieldDef| visit_ascribed_node(node, ""))
243             .accept(&node)?
244     }
245 }
246
247 #[cfg(test)]
248 mod tests {
249     use ra_syntax::TextRange;
250     use crate::mock_analysis::{single_file_with_position, single_file_with_range, analysis_and_position};
251
252     fn trim_markup(s: &str) -> &str {
253         s.trim_start_matches("```rust\n").trim_end_matches("\n```")
254     }
255
256     fn trim_markup_opt(s: Option<&str>) -> Option<&str> {
257         s.map(trim_markup)
258     }
259
260     fn check_hover_result(fixture: &str, expected: &[&str]) {
261         let (analysis, position) = analysis_and_position(fixture);
262         let hover = analysis.hover(position).unwrap().unwrap();
263
264         for (markup, expected) in
265             hover.info.results().iter().zip(expected.iter().chain(std::iter::repeat(&"<missing>")))
266         {
267             assert_eq!(trim_markup(&markup), *expected);
268         }
269
270         assert_eq!(hover.info.len(), expected.len());
271     }
272
273     #[test]
274     fn hover_shows_type_of_an_expression() {
275         let (analysis, position) = single_file_with_position(
276             "
277             pub fn foo() -> u32 { 1 }
278
279             fn main() {
280                 let foo_test = foo()<|>;
281             }
282             ",
283         );
284         let hover = analysis.hover(position).unwrap().unwrap();
285         assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into()));
286         assert_eq!(trim_markup_opt(hover.info.first()), Some("u32"));
287     }
288
289     #[test]
290     fn hover_shows_fn_signature() {
291         // Single file with result
292         check_hover_result(
293             r#"
294             //- /main.rs
295             pub fn foo() -> u32 { 1 }
296
297             fn main() {
298                 let foo_test = fo<|>o();
299             }
300         "#,
301             &["pub fn foo() -> u32"],
302         );
303
304         // Multiple results
305         check_hover_result(
306             r#"
307             //- /a.rs
308             pub fn foo() -> u32 { 1 }
309
310             //- /b.rs
311             pub fn foo() -> &str { "" }
312
313             //- /c.rs
314             pub fn foo(a: u32, b: u32) {}
315
316             //- /main.rs
317             mod a;
318             mod b;
319             mod c;
320
321             fn main() {
322                 let foo_test = fo<|>o();
323             }
324         "#,
325             &["pub fn foo() -> &str", "pub fn foo() -> u32", "pub fn foo(a: u32, b: u32)"],
326         );
327     }
328
329     #[test]
330     fn hover_shows_fn_signature_with_type_params() {
331         check_hover_result(
332             r#"
333             //- /main.rs
334             pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str { }
335
336             fn main() {
337                 let foo_test = fo<|>o();
338             }
339         "#,
340             &["pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str"],
341         );
342     }
343
344     #[test]
345     fn hover_shows_fn_signature_on_fn_name() {
346         check_hover_result(
347             r#"
348             //- /main.rs
349             pub fn foo<|>(a: u32, b: u32) -> u32 {}
350
351             fn main() {
352             }
353         "#,
354             &["pub fn foo(a: u32, b: u32) -> u32"],
355         );
356     }
357
358     #[test]
359     fn hover_shows_struct_field_info() {
360         // Hovering over the field when instantiating
361         check_hover_result(
362             r#"
363             //- /main.rs
364             struct Foo {
365                 field_a: u32,
366             }
367
368             fn main() {
369                 let foo = Foo {
370                     field_a<|>: 0,
371                 };
372             }
373         "#,
374             &["field_a: u32"],
375         );
376
377         // Hovering over the field in the definition
378         check_hover_result(
379             r#"
380             //- /main.rs
381             struct Foo {
382                 field_a<|>: u32,
383             }
384
385             fn main() {
386                 let foo = Foo {
387                     field_a: 0,
388                 };
389             }
390         "#,
391             &["field_a: u32"],
392         );
393     }
394
395     #[test]
396     fn hover_const_static() {
397         check_hover_result(
398             r#"
399             //- /main.rs
400             fn main() {
401                 const foo<|>: u32 = 0;
402             }
403         "#,
404             &["const foo: u32"],
405         );
406
407         check_hover_result(
408             r#"
409             //- /main.rs
410             fn main() {
411                 static foo<|>: u32 = 0;
412             }
413         "#,
414             &["static foo: u32"],
415         );
416     }
417
418     #[test]
419     fn hover_some() {
420         let (analysis, position) = single_file_with_position(
421             "
422             enum Option<T> { Some(T) }
423             use Option::Some;
424
425             fn main() {
426                 So<|>me(12);
427             }
428             ",
429         );
430         let hover = analysis.hover(position).unwrap().unwrap();
431         // not the nicest way to show it currently
432         assert_eq!(trim_markup_opt(hover.info.first()), Some("Some<i32>(T) -> Option<T>"));
433     }
434
435     #[test]
436     fn hover_for_local_variable() {
437         let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }");
438         let hover = analysis.hover(position).unwrap().unwrap();
439         assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
440     }
441
442     #[test]
443     fn hover_for_local_variable_pat() {
444         let (analysis, position) = single_file_with_position("fn func(fo<|>o: i32) {}");
445         let hover = analysis.hover(position).unwrap().unwrap();
446         assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
447     }
448
449     #[test]
450     fn test_type_of_for_function() {
451         let (analysis, range) = single_file_with_range(
452             "
453             pub fn foo() -> u32 { 1 };
454
455             fn main() {
456                 let foo_test = <|>foo()<|>;
457             }
458             ",
459         );
460
461         let type_name = analysis.type_of(range).unwrap().unwrap();
462         assert_eq!("u32", &type_name);
463     }
464
465     // FIXME: improve type_of to make this work
466     #[test]
467     fn test_type_of_for_expr_1() {
468         let (analysis, range) = single_file_with_range(
469             "
470             fn main() {
471                 let foo = <|>1 + foo_test<|>;
472             }
473             ",
474         );
475
476         let type_name = analysis.type_of(range).unwrap().unwrap();
477         assert_eq!("{unknown}", &type_name);
478     }
479
480     #[test]
481     fn test_type_of_for_expr_2() {
482         let (analysis, range) = single_file_with_range(
483             "
484             fn main() {
485                 let foo: usize = 1;
486                 let bar = <|>1 + foo<|>;
487             }
488             ",
489         );
490
491         let type_name = analysis.type_of(range).unwrap().unwrap();
492         assert_eq!("usize", &type_name);
493     }
494
495     #[test]
496     fn test_hover_infer_associated_method_result() {
497         let (analysis, position) = single_file_with_position(
498             "
499             struct Thing { x: u32 };
500
501             impl Thing {
502                 fn new() -> Thing {
503                     Thing { x: 0 }
504                 }
505             }
506
507             fn main() {
508                 let foo_<|>test = Thing::new();
509             }
510             ",
511         );
512         let hover = analysis.hover(position).unwrap().unwrap();
513         assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing"));
514     }
515
516     #[test]
517     fn test_hover_infer_associated_method_exact() {
518         let (analysis, position) = single_file_with_position(
519             "
520             struct Thing { x: u32 }
521
522             impl Thing {
523                 fn new() -> Thing {
524                     Thing { x: 0 }
525                 }
526             }
527
528             fn main() {
529                 let foo_test = Thing::new<|>();
530             }
531             ",
532         );
533         let hover = analysis.hover(position).unwrap().unwrap();
534         assert_eq!(trim_markup_opt(hover.info.first()), Some("fn new() -> Thing"));
535         assert_eq!(hover.info.is_exact(), true);
536     }
537
538     #[test]
539     fn test_hover_infer_associated_const_in_pattern() {
540         let (analysis, position) = single_file_with_position(
541             "
542             struct X;
543             impl X {
544                 const C: u32 = 1;
545             }
546
547             fn main() {
548                 match 1 {
549                     X::C<|> => {},
550                     2 => {},
551                     _ => {}
552                 };
553             }
554             ",
555         );
556         let hover = analysis.hover(position).unwrap().unwrap();
557         assert_eq!(trim_markup_opt(hover.info.first()), Some("const C: u32"));
558         assert_eq!(hover.info.is_exact(), true);
559     }
560 }