]> git.lizzy.rs Git - rust.git/blob - crates/ra_ide_api/src/hover.rs
Hover for builtins
[rust.git] / crates / ra_ide_api / src / hover.rs
1 //! FIXME: write short doc here
2
3 use hir::{Adt, BuiltinType, HasSource, HirDisplay};
4 use ra_db::SourceDatabase;
5 use ra_syntax::{
6     algo::{ancestors_at_offset, find_covering_element, find_node_at_offset},
7     ast::{self, DocCommentsOwner},
8     match_ast, AstNode,
9 };
10
11 use crate::{
12     db::RootDatabase,
13     display::{
14         description_from_symbol, docs_from_symbol, macro_label, rust_code_markup,
15         rust_code_markup_with_doc, ShortLabel,
16     },
17     references::{classify_name_ref, NameKind::*},
18     FilePosition, FileRange, RangeInfo,
19 };
20
21 /// Contains the results when hovering over an item
22 #[derive(Debug, Clone)]
23 pub struct HoverResult {
24     results: Vec<String>,
25     exact: bool,
26 }
27
28 impl Default for HoverResult {
29     fn default() -> Self {
30         HoverResult::new()
31     }
32 }
33
34 impl HoverResult {
35     pub fn new() -> HoverResult {
36         HoverResult {
37             results: Vec::new(),
38             // We assume exact by default
39             exact: true,
40         }
41     }
42
43     pub fn extend(&mut self, item: Option<String>) {
44         self.results.extend(item);
45     }
46
47     pub fn is_exact(&self) -> bool {
48         self.exact
49     }
50
51     pub fn is_empty(&self) -> bool {
52         self.results.is_empty()
53     }
54
55     pub fn len(&self) -> usize {
56         self.results.len()
57     }
58
59     pub fn first(&self) -> Option<&str> {
60         self.results.first().map(String::as_str)
61     }
62
63     pub fn results(&self) -> &[String] {
64         &self.results
65     }
66
67     /// Returns the results converted into markup
68     /// for displaying in a UI
69     pub fn to_markup(&self) -> String {
70         let mut markup = if !self.exact {
71             let mut msg = String::from("Failed to exactly resolve the symbol. This is probably because rust_analyzer does not yet support traits.");
72             if !self.results.is_empty() {
73                 msg.push_str("  \nThese items were found instead:");
74             }
75             msg.push_str("\n\n---\n");
76             msg
77         } else {
78             String::new()
79         };
80
81         markup.push_str(&self.results.join("\n\n---\n"));
82
83         markup
84     }
85 }
86
87 fn hover_text(docs: Option<String>, desc: Option<String>) -> Option<String> {
88     match (desc, docs) {
89         (Some(desc), docs) => Some(rust_code_markup_with_doc(desc, docs)),
90         (None, Some(docs)) => Some(docs),
91         _ => None,
92     }
93 }
94
95 pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> {
96     let parse = db.parse(position.file_id);
97     let file = parse.tree();
98     let mut res = HoverResult::new();
99
100     let mut range = None;
101     if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) {
102         let mut no_fallback = false;
103         let name_kind = classify_name_ref(db, position.file_id, &name_ref).map(|d| d.kind);
104         match name_kind {
105             Some(Macro(it)) => {
106                 let src = it.source(db);
107                 res.extend(hover_text(src.ast.doc_comment_text(), Some(macro_label(&src.ast))));
108             }
109             Some(Field(it)) => {
110                 let src = it.source(db);
111                 if let hir::FieldSource::Named(it) = src.ast {
112                     res.extend(hover_text(it.doc_comment_text(), it.short_label()));
113                 }
114             }
115             Some(AssocItem(it)) => res.extend(match it {
116                 hir::AssocItem::Function(it) => from_def_source(db, it),
117                 hir::AssocItem::Const(it) => from_def_source(db, it),
118                 hir::AssocItem::TypeAlias(it) => from_def_source(db, it),
119             }),
120             Some(Def(it)) => match it {
121                 hir::ModuleDef::Module(it) => {
122                     if let hir::ModuleSource::Module(it) = it.definition_source(db).ast {
123                         res.extend(hover_text(it.doc_comment_text(), it.short_label()))
124                     }
125                 }
126                 hir::ModuleDef::Function(it) => res.extend(from_def_source(db, it)),
127                 hir::ModuleDef::Adt(Adt::Struct(it)) => res.extend(from_def_source(db, it)),
128                 hir::ModuleDef::Adt(Adt::Union(it)) => res.extend(from_def_source(db, it)),
129                 hir::ModuleDef::Adt(Adt::Enum(it)) => res.extend(from_def_source(db, it)),
130                 hir::ModuleDef::EnumVariant(it) => res.extend(from_def_source(db, it)),
131                 hir::ModuleDef::Const(it) => res.extend(from_def_source(db, it)),
132                 hir::ModuleDef::Static(it) => res.extend(from_def_source(db, it)),
133                 hir::ModuleDef::Trait(it) => res.extend(from_def_source(db, it)),
134                 hir::ModuleDef::TypeAlias(it) => res.extend(from_def_source(db, it)),
135                 hir::ModuleDef::BuiltinType(it) => {
136                     if let Some(b) = BuiltinType::ALL.iter().find(|(_, ty)| *ty == it) {
137                         res.extend(Some(b.0.to_string()))
138                     }
139                 }
140             },
141             Some(SelfType(ty)) => {
142                 if let Some((adt_def, _)) = ty.as_adt() {
143                     res.extend(match adt_def {
144                         hir::Adt::Struct(it) => from_def_source(db, it),
145                         hir::Adt::Union(it) => from_def_source(db, it),
146                         hir::Adt::Enum(it) => from_def_source(db, it),
147                     })
148                 }
149             }
150             Some(Pat(_)) | Some(SelfParam(_)) => {
151                 // Hover for these shows type names
152                 no_fallback = true;
153             }
154             Some(GenericParam(_)) => {
155                 // FIXME: Hover for generic param
156             }
157             None => {}
158         }
159
160         if res.is_empty() && !no_fallback {
161             // Fallback index based approach:
162             let symbols = crate::symbol_index::index_resolve(db, &name_ref);
163             for sym in symbols {
164                 let docs = docs_from_symbol(db, &sym);
165                 let desc = description_from_symbol(db, &sym);
166                 res.extend(hover_text(docs, desc));
167             }
168         }
169
170         if !res.is_empty() {
171             range = Some(name_ref.syntax().text_range())
172         }
173     } else if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) {
174         if let Some(parent) = name.syntax().parent() {
175             let text = match_ast! {
176                 match parent {
177                     ast::StructDef(it) => {
178                         hover_text(it.doc_comment_text(), it.short_label())
179                     },
180                     ast::EnumDef(it) => {
181                         hover_text(it.doc_comment_text(), it.short_label())
182                     },
183                     ast::EnumVariant(it) => {
184                         hover_text(it.doc_comment_text(), it.short_label())
185                     },
186                     ast::FnDef(it) => {
187                         hover_text(it.doc_comment_text(), it.short_label())
188                     },
189                     ast::TypeAliasDef(it) => {
190                         hover_text(it.doc_comment_text(), it.short_label())
191                     },
192                     ast::ConstDef(it) => {
193                         hover_text(it.doc_comment_text(), it.short_label())
194                     },
195                     ast::StaticDef(it) => {
196                         hover_text(it.doc_comment_text(), it.short_label())
197                     },
198                     ast::TraitDef(it) => {
199                         hover_text(it.doc_comment_text(), it.short_label())
200                     },
201                     ast::RecordFieldDef(it) => {
202                         hover_text(it.doc_comment_text(), it.short_label())
203                     },
204                     ast::Module(it) => {
205                         hover_text(it.doc_comment_text(), it.short_label())
206                     },
207                     ast::MacroCall(it) => {
208                         hover_text(it.doc_comment_text(), None)
209                     },
210                     _ => None,
211                 }
212             };
213             res.extend(text);
214         }
215
216         if !res.is_empty() && range.is_none() {
217             range = Some(name.syntax().text_range());
218         }
219     }
220
221     if range.is_none() {
222         let node = ancestors_at_offset(file.syntax(), position.offset).find(|n| {
223             ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some()
224         })?;
225         let frange = FileRange { file_id: position.file_id, range: node.text_range() };
226         res.extend(type_of(db, frange).map(rust_code_markup));
227         range = Some(node.text_range());
228     }
229
230     let range = range?;
231     if res.is_empty() {
232         return None;
233     }
234     let res = RangeInfo::new(range, res);
235     return Some(res);
236
237     fn from_def_source<A, D>(db: &RootDatabase, def: D) -> Option<String>
238     where
239         D: HasSource<Ast = A>,
240         A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel,
241     {
242         let src = def.source(db);
243         hover_text(src.ast.doc_comment_text(), src.ast.short_label())
244     }
245 }
246
247 pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> {
248     let parse = db.parse(frange.file_id);
249     let leaf_node = find_covering_element(parse.tree().syntax(), frange.range);
250     // if we picked identifier, expand to pattern/expression
251     let node = leaf_node
252         .ancestors()
253         .take_while(|it| it.text_range() == leaf_node.text_range())
254         .find(|it| ast::Expr::cast(it.clone()).is_some() || ast::Pat::cast(it.clone()).is_some())?;
255     let analyzer = hir::SourceAnalyzer::new(db, frange.file_id, &node, None);
256     let ty = if let Some(ty) = ast::Expr::cast(node.clone()).and_then(|e| analyzer.type_of(db, &e))
257     {
258         ty
259     } else if let Some(ty) = ast::Pat::cast(node).and_then(|p| analyzer.type_of_pat(db, &p)) {
260         ty
261     } else {
262         return None;
263     };
264     Some(ty.display(db).to_string())
265 }
266
267 #[cfg(test)]
268 mod tests {
269     use crate::mock_analysis::{
270         analysis_and_position, single_file_with_position, single_file_with_range,
271     };
272     use ra_syntax::TextRange;
273
274     fn trim_markup(s: &str) -> &str {
275         s.trim_start_matches("```rust\n").trim_end_matches("\n```")
276     }
277
278     fn trim_markup_opt(s: Option<&str>) -> Option<&str> {
279         s.map(trim_markup)
280     }
281
282     fn check_hover_result(fixture: &str, expected: &[&str]) {
283         let (analysis, position) = analysis_and_position(fixture);
284         let hover = analysis.hover(position).unwrap().unwrap();
285         let mut results = Vec::from(hover.info.results());
286         results.sort();
287
288         for (markup, expected) in
289             results.iter().zip(expected.iter().chain(std::iter::repeat(&"<missing>")))
290         {
291             assert_eq!(trim_markup(&markup), *expected);
292         }
293
294         assert_eq!(hover.info.len(), expected.len());
295     }
296
297     #[test]
298     fn hover_shows_type_of_an_expression() {
299         let (analysis, position) = single_file_with_position(
300             "
301             pub fn foo() -> u32 { 1 }
302
303             fn main() {
304                 let foo_test = foo()<|>;
305             }
306             ",
307         );
308         let hover = analysis.hover(position).unwrap().unwrap();
309         assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into()));
310         assert_eq!(trim_markup_opt(hover.info.first()), Some("u32"));
311     }
312
313     #[test]
314     fn hover_shows_fn_signature() {
315         // Single file with result
316         check_hover_result(
317             r#"
318             //- /main.rs
319             pub fn foo() -> u32 { 1 }
320
321             fn main() {
322                 let foo_test = fo<|>o();
323             }
324         "#,
325             &["pub fn foo() -> u32"],
326         );
327
328         // Multiple results
329         check_hover_result(
330             r#"
331             //- /a.rs
332             pub fn foo() -> u32 { 1 }
333
334             //- /b.rs
335             pub fn foo() -> &str { "" }
336
337             //- /c.rs
338             pub fn foo(a: u32, b: u32) {}
339
340             //- /main.rs
341             mod a;
342             mod b;
343             mod c;
344
345             fn main() {
346                 let foo_test = fo<|>o();
347             }
348         "#,
349             &["pub fn foo() -> &str", "pub fn foo() -> u32", "pub fn foo(a: u32, b: u32)"],
350         );
351     }
352
353     #[test]
354     fn hover_shows_fn_signature_with_type_params() {
355         check_hover_result(
356             r#"
357             //- /main.rs
358             pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str { }
359
360             fn main() {
361                 let foo_test = fo<|>o();
362             }
363         "#,
364             &["pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str"],
365         );
366     }
367
368     #[test]
369     fn hover_shows_fn_signature_on_fn_name() {
370         check_hover_result(
371             r#"
372             //- /main.rs
373             pub fn foo<|>(a: u32, b: u32) -> u32 {}
374
375             fn main() {
376             }
377         "#,
378             &["pub fn foo(a: u32, b: u32) -> u32"],
379         );
380     }
381
382     #[test]
383     fn hover_shows_struct_field_info() {
384         // Hovering over the field when instantiating
385         check_hover_result(
386             r#"
387             //- /main.rs
388             struct Foo {
389                 field_a: u32,
390             }
391
392             fn main() {
393                 let foo = Foo {
394                     field_a<|>: 0,
395                 };
396             }
397         "#,
398             &["field_a: u32"],
399         );
400
401         // Hovering over the field in the definition
402         check_hover_result(
403             r#"
404             //- /main.rs
405             struct Foo {
406                 field_a<|>: u32,
407             }
408
409             fn main() {
410                 let foo = Foo {
411                     field_a: 0,
412                 };
413             }
414         "#,
415             &["field_a: u32"],
416         );
417     }
418
419     #[test]
420     fn hover_const_static() {
421         check_hover_result(
422             r#"
423             //- /main.rs
424             fn main() {
425                 const foo<|>: u32 = 0;
426             }
427         "#,
428             &["const foo: u32"],
429         );
430
431         check_hover_result(
432             r#"
433             //- /main.rs
434             fn main() {
435                 static foo<|>: u32 = 0;
436             }
437         "#,
438             &["static foo: u32"],
439         );
440     }
441
442     #[test]
443     fn hover_some() {
444         let (analysis, position) = single_file_with_position(
445             "
446             enum Option<T> { Some(T) }
447             use Option::Some;
448
449             fn main() {
450                 So<|>me(12);
451             }
452             ",
453         );
454         let hover = analysis.hover(position).unwrap().unwrap();
455         assert_eq!(trim_markup_opt(hover.info.first()), Some("Some"));
456
457         let (analysis, position) = single_file_with_position(
458             "
459             enum Option<T> { Some(T) }
460             use Option::Some;
461
462             fn main() {
463                 let b<|>ar = Some(12);
464             }
465             ",
466         );
467         let hover = analysis.hover(position).unwrap().unwrap();
468         assert_eq!(trim_markup_opt(hover.info.first()), Some("Option<i32>"));
469     }
470
471     #[test]
472     fn hover_enum_variant() {
473         check_hover_result(
474             r#"
475             //- /main.rs
476             enum Option<T> {
477                 /// The None variant
478                 Non<|>e
479             }
480         "#,
481             &["
482 None
483 ```
484
485 The None variant
486             "
487             .trim()],
488         );
489
490         check_hover_result(
491             r#"
492             //- /main.rs
493             enum Option<T> {
494                 /// The Some variant
495                 Some(T)
496             }
497             fn main() {
498                 let s = Option::Som<|>e(12);
499             }
500         "#,
501             &["
502 Some
503 ```
504
505 The Some variant
506             "
507             .trim()],
508         );
509     }
510
511     #[test]
512     fn hover_for_local_variable() {
513         let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }");
514         let hover = analysis.hover(position).unwrap().unwrap();
515         assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
516     }
517
518     #[test]
519     fn hover_for_local_variable_pat() {
520         let (analysis, position) = single_file_with_position("fn func(fo<|>o: i32) {}");
521         let hover = analysis.hover(position).unwrap().unwrap();
522         assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
523     }
524
525     #[test]
526     fn hover_local_var_edge() {
527         let (analysis, position) = single_file_with_position(
528             "
529 fn func(foo: i32) { if true { <|>foo; }; }
530 ",
531         );
532         let hover = analysis.hover(position).unwrap().unwrap();
533         assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
534     }
535
536     #[test]
537     fn test_type_of_for_function() {
538         let (analysis, range) = single_file_with_range(
539             "
540             pub fn foo() -> u32 { 1 };
541
542             fn main() {
543                 let foo_test = <|>foo()<|>;
544             }
545             ",
546         );
547
548         let type_name = analysis.type_of(range).unwrap().unwrap();
549         assert_eq!("u32", &type_name);
550     }
551
552     #[test]
553     fn test_type_of_for_expr() {
554         let (analysis, range) = single_file_with_range(
555             "
556             fn main() {
557                 let foo: usize = 1;
558                 let bar = <|>1 + foo<|>;
559             }
560             ",
561         );
562
563         let type_name = analysis.type_of(range).unwrap().unwrap();
564         assert_eq!("usize", &type_name);
565     }
566
567     #[test]
568     fn test_hover_infer_associated_method_result() {
569         let (analysis, position) = single_file_with_position(
570             "
571             struct Thing { x: u32 }
572
573             impl Thing {
574                 fn new() -> Thing {
575                     Thing { x: 0 }
576                 }
577             }
578
579             fn main() {
580                 let foo_<|>test = Thing::new();
581             }
582             ",
583         );
584         let hover = analysis.hover(position).unwrap().unwrap();
585         assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing"));
586     }
587
588     #[test]
589     fn test_hover_infer_associated_method_exact() {
590         let (analysis, position) = single_file_with_position(
591             "
592             struct Thing { x: u32 }
593
594             impl Thing {
595                 fn new() -> Thing {
596                     Thing { x: 0 }
597                 }
598             }
599
600             fn main() {
601                 let foo_test = Thing::new<|>();
602             }
603             ",
604         );
605         let hover = analysis.hover(position).unwrap().unwrap();
606         assert_eq!(trim_markup_opt(hover.info.first()), Some("fn new() -> Thing"));
607         assert_eq!(hover.info.is_exact(), true);
608     }
609
610     #[test]
611     fn test_hover_infer_associated_const_in_pattern() {
612         let (analysis, position) = single_file_with_position(
613             "
614             struct X;
615             impl X {
616                 const C: u32 = 1;
617             }
618
619             fn main() {
620                 match 1 {
621                     X::C<|> => {},
622                     2 => {},
623                     _ => {}
624                 };
625             }
626             ",
627         );
628         let hover = analysis.hover(position).unwrap().unwrap();
629         assert_eq!(trim_markup_opt(hover.info.first()), Some("const C: u32"));
630         assert_eq!(hover.info.is_exact(), true);
631     }
632
633     #[test]
634     fn test_hover_self() {
635         let (analysis, position) = single_file_with_position(
636             "
637             struct Thing { x: u32 }
638             impl Thing {
639                 fn new() -> Self {
640                     Self<|> { x: 0 }
641                 }
642             }
643         ",
644         );
645         let hover = analysis.hover(position).unwrap().unwrap();
646         assert_eq!(trim_markup_opt(hover.info.first()), Some("struct Thing"));
647         assert_eq!(hover.info.is_exact(), true);
648
649         let (analysis, position) = single_file_with_position(
650             "
651             struct Thing { x: u32 }
652             impl Thing {
653                 fn new() -> Self<|> {
654                     Self { x: 0 }
655                 }
656             }
657             ",
658         );
659         let hover = analysis.hover(position).unwrap().unwrap();
660         assert_eq!(trim_markup_opt(hover.info.first()), Some("struct Thing"));
661         assert_eq!(hover.info.is_exact(), true);
662
663         let (analysis, position) = single_file_with_position(
664             "
665             enum Thing { A }
666             impl Thing {
667                 pub fn new() -> Self<|> {
668                     Thing::A
669                 }
670             }
671             ",
672         );
673         let hover = analysis.hover(position).unwrap().unwrap();
674         assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing"));
675         assert_eq!(hover.info.is_exact(), true);
676
677         let (analysis, position) = single_file_with_position(
678             "
679             enum Thing { A }
680             impl Thing {
681                 pub fn thing(a: Self<|>) {
682                 }
683             }
684             ",
685         );
686         let hover = analysis.hover(position).unwrap().unwrap();
687         assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing"));
688         assert_eq!(hover.info.is_exact(), true);
689     }
690
691     #[test]
692     fn test_hover_shadowing_pat() {
693         let (analysis, position) = single_file_with_position(
694             "
695             fn x() {}
696
697             fn y() {
698                 let x = 0i32;
699                 x<|>;
700             }
701             ",
702         );
703         let hover = analysis.hover(position).unwrap().unwrap();
704         assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
705         assert_eq!(hover.info.is_exact(), true);
706     }
707
708     #[test]
709     fn test_hover_macro_invocation() {
710         let (analysis, position) = single_file_with_position(
711             "
712             macro_rules! foo {
713                 () => {}
714             }
715
716             fn f() {
717                 fo<|>o!();
718             }
719             ",
720         );
721         let hover = analysis.hover(position).unwrap().unwrap();
722         assert_eq!(trim_markup_opt(hover.info.first()), Some("macro_rules! foo"));
723         assert_eq!(hover.info.is_exact(), true);
724     }
725
726     #[test]
727     fn test_hover_tuple_field() {
728         let (analysis, position) = single_file_with_position(
729             "
730             struct TS(String, i32<|>);
731             ",
732         );
733         let hover = analysis.hover(position).unwrap().unwrap();
734         assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
735         assert_eq!(hover.info.is_exact(), true);
736     }
737 }