diff options
Diffstat (limited to 'crates/ra_ide/src/hover.rs')
-rw-r--r-- | crates/ra_ide/src/hover.rs | 198 |
1 files changed, 122 insertions, 76 deletions
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 1c6ca36df..5073bb1cf 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -1,10 +1,11 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use hir::{db::AstDatabase, Adt, HasSource, HirDisplay, SourceBinder}; | 3 | use hir::{Adt, HasSource, HirDisplay, Semantics}; |
4 | use ra_db::SourceDatabase; | 4 | use ra_ide_db::{ |
5 | use ra_ide_db::{defs::NameDefinition, RootDatabase}; | 5 | defs::{classify_name, NameDefinition}, |
6 | RootDatabase, | ||
7 | }; | ||
6 | use ra_syntax::{ | 8 | use ra_syntax::{ |
7 | algo::find_covering_element, | ||
8 | ast::{self, DocCommentsOwner}, | 9 | ast::{self, DocCommentsOwner}, |
9 | match_ast, AstNode, | 10 | match_ast, AstNode, |
10 | SyntaxKind::*, | 11 | SyntaxKind::*, |
@@ -13,9 +14,8 @@ use ra_syntax::{ | |||
13 | 14 | ||
14 | use crate::{ | 15 | use crate::{ |
15 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, | 16 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, |
16 | expand::{descend_into_macros, original_range}, | 17 | references::classify_name_ref, |
17 | references::{classify_name, classify_name_ref}, | 18 | FilePosition, RangeInfo, |
18 | FilePosition, FileRange, RangeInfo, | ||
19 | }; | 19 | }; |
20 | 20 | ||
21 | /// Contains the results when hovering over an item | 21 | /// Contains the results when hovering over an item |
@@ -143,25 +143,25 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: NameDefinition) -> Option<S | |||
143 | } | 143 | } |
144 | 144 | ||
145 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | 145 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { |
146 | let file = db.parse_or_expand(position.file_id.into())?; | 146 | let sema = Semantics::new(db); |
147 | let file = sema.parse(position.file_id).syntax().clone(); | ||
147 | let token = pick_best(file.token_at_offset(position.offset))?; | 148 | let token = pick_best(file.token_at_offset(position.offset))?; |
148 | let token = descend_into_macros(db, position.file_id, token); | 149 | let token = sema.descend_into_macros(token); |
149 | 150 | ||
150 | let mut res = HoverResult::new(); | 151 | let mut res = HoverResult::new(); |
151 | 152 | ||
152 | let mut sb = SourceBinder::new(db); | ||
153 | if let Some((node, name_kind)) = match_ast! { | 153 | if let Some((node, name_kind)) = match_ast! { |
154 | match (token.value.parent()) { | 154 | match (token.parent()) { |
155 | ast::NameRef(name_ref) => { | 155 | ast::NameRef(name_ref) => { |
156 | classify_name_ref(&mut sb, token.with_value(&name_ref)).map(|d| (name_ref.syntax().clone(), d)) | 156 | classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d)) |
157 | }, | 157 | }, |
158 | ast::Name(name) => { | 158 | ast::Name(name) => { |
159 | classify_name(&mut sb, token.with_value(&name)).map(|d| (name.syntax().clone(), d)) | 159 | classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition())) |
160 | }, | 160 | }, |
161 | _ => None, | 161 | _ => None, |
162 | } | 162 | } |
163 | } { | 163 | } { |
164 | let range = original_range(db, token.with_value(&node)).range; | 164 | let range = sema.original_range(&node).range; |
165 | res.extend(hover_text_from_name_kind(db, name_kind)); | 165 | res.extend(hover_text_from_name_kind(db, name_kind)); |
166 | 166 | ||
167 | if !res.is_empty() { | 167 | if !res.is_empty() { |
@@ -170,17 +170,28 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
170 | } | 170 | } |
171 | 171 | ||
172 | let node = token | 172 | let node = token |
173 | .value | ||
174 | .ancestors() | 173 | .ancestors() |
175 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; | 174 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; |
176 | 175 | ||
177 | let frange = original_range(db, token.with_value(&node)); | 176 | let ty = match_ast! { |
178 | res.extend(type_of(db, frange).map(rust_code_markup)); | 177 | match node { |
179 | if res.is_empty() { | 178 | ast::MacroCall(_it) => { |
180 | return None; | 179 | // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. |
181 | } | 180 | // (e.g expanding a builtin macro). So we give up here. |
182 | let range = node.text_range(); | 181 | return None; |
182 | }, | ||
183 | ast::Expr(it) => { | ||
184 | sema.type_of_expr(&it) | ||
185 | }, | ||
186 | ast::Pat(it) => { | ||
187 | sema.type_of_pat(&it) | ||
188 | }, | ||
189 | _ => None, | ||
190 | } | ||
191 | }?; | ||
183 | 192 | ||
193 | res.extend(Some(rust_code_markup(ty.display_truncated(db, None).to_string()))); | ||
194 | let range = sema.original_range(&node).range; | ||
184 | Some(RangeInfo::new(range, res)) | 195 | Some(RangeInfo::new(range, res)) |
185 | } | 196 | } |
186 | 197 | ||
@@ -196,35 +207,13 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | |||
196 | } | 207 | } |
197 | } | 208 | } |
198 | 209 | ||
199 | pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> { | ||
200 | let parse = db.parse(frange.file_id); | ||
201 | let leaf_node = find_covering_element(parse.tree().syntax(), frange.range); | ||
202 | // if we picked identifier, expand to pattern/expression | ||
203 | let node = leaf_node | ||
204 | .ancestors() | ||
205 | .take_while(|it| it.text_range() == leaf_node.text_range()) | ||
206 | .find(|it| ast::Expr::cast(it.clone()).is_some() || ast::Pat::cast(it.clone()).is_some())?; | ||
207 | let analyzer = | ||
208 | hir::SourceAnalyzer::new(db, hir::InFile::new(frange.file_id.into(), &node), None); | ||
209 | let ty = if let Some(ty) = ast::Expr::cast(node.clone()).and_then(|e| analyzer.type_of(db, &e)) | ||
210 | { | ||
211 | ty | ||
212 | } else if let Some(ty) = ast::Pat::cast(node).and_then(|p| analyzer.type_of_pat(db, &p)) { | ||
213 | ty | ||
214 | } else { | ||
215 | return None; | ||
216 | }; | ||
217 | Some(ty.display_truncated(db, None).to_string()) | ||
218 | } | ||
219 | |||
220 | #[cfg(test)] | 210 | #[cfg(test)] |
221 | mod tests { | 211 | mod tests { |
222 | use crate::mock_analysis::{ | ||
223 | analysis_and_position, single_file_with_position, single_file_with_range, | ||
224 | }; | ||
225 | use ra_db::FileLoader; | 212 | use ra_db::FileLoader; |
226 | use ra_syntax::TextRange; | 213 | use ra_syntax::TextRange; |
227 | 214 | ||
215 | use crate::mock_analysis::{analysis_and_position, single_file_with_position}; | ||
216 | |||
228 | fn trim_markup(s: &str) -> &str { | 217 | fn trim_markup(s: &str) -> &str { |
229 | s.trim_start_matches("```rust\n").trim_end_matches("\n```") | 218 | s.trim_start_matches("```rust\n").trim_end_matches("\n```") |
230 | } | 219 | } |
@@ -251,6 +240,11 @@ mod tests { | |||
251 | content[hover.range].to_string() | 240 | content[hover.range].to_string() |
252 | } | 241 | } |
253 | 242 | ||
243 | fn check_hover_no_result(fixture: &str) { | ||
244 | let (analysis, position) = analysis_and_position(fixture); | ||
245 | assert!(analysis.hover(position).unwrap().is_none()); | ||
246 | } | ||
247 | |||
254 | #[test] | 248 | #[test] |
255 | fn hover_shows_type_of_an_expression() { | 249 | fn hover_shows_type_of_an_expression() { |
256 | let (analysis, position) = single_file_with_position( | 250 | let (analysis, position) = single_file_with_position( |
@@ -511,37 +505,6 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
511 | } | 505 | } |
512 | 506 | ||
513 | #[test] | 507 | #[test] |
514 | fn test_type_of_for_function() { | ||
515 | let (analysis, range) = single_file_with_range( | ||
516 | " | ||
517 | pub fn foo() -> u32 { 1 }; | ||
518 | |||
519 | fn main() { | ||
520 | let foo_test = <|>foo()<|>; | ||
521 | } | ||
522 | ", | ||
523 | ); | ||
524 | |||
525 | let type_name = analysis.type_of(range).unwrap().unwrap(); | ||
526 | assert_eq!("u32", &type_name); | ||
527 | } | ||
528 | |||
529 | #[test] | ||
530 | fn test_type_of_for_expr() { | ||
531 | let (analysis, range) = single_file_with_range( | ||
532 | " | ||
533 | fn main() { | ||
534 | let foo: usize = 1; | ||
535 | let bar = <|>1 + foo<|>; | ||
536 | } | ||
537 | ", | ||
538 | ); | ||
539 | |||
540 | let type_name = analysis.type_of(range).unwrap().unwrap(); | ||
541 | assert_eq!("usize", &type_name); | ||
542 | } | ||
543 | |||
544 | #[test] | ||
545 | fn test_hover_infer_associated_method_result() { | 508 | fn test_hover_infer_associated_method_result() { |
546 | let (analysis, position) = single_file_with_position( | 509 | let (analysis, position) = single_file_with_position( |
547 | " | 510 | " |
@@ -755,6 +718,89 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
755 | } | 718 | } |
756 | 719 | ||
757 | #[test] | 720 | #[test] |
721 | fn test_hover_through_expr_in_macro_recursive() { | ||
722 | let hover_on = check_hover_result( | ||
723 | " | ||
724 | //- /lib.rs | ||
725 | macro_rules! id_deep { | ||
726 | ($($tt:tt)*) => { $($tt)* } | ||
727 | } | ||
728 | macro_rules! id { | ||
729 | ($($tt:tt)*) => { id_deep!($($tt)*) } | ||
730 | } | ||
731 | fn foo(bar:u32) { | ||
732 | let a = id!(ba<|>r); | ||
733 | } | ||
734 | ", | ||
735 | &["u32"], | ||
736 | ); | ||
737 | |||
738 | assert_eq!(hover_on, "bar") | ||
739 | } | ||
740 | |||
741 | #[test] | ||
742 | fn test_hover_through_func_in_macro_recursive() { | ||
743 | let hover_on = check_hover_result( | ||
744 | " | ||
745 | //- /lib.rs | ||
746 | macro_rules! id_deep { | ||
747 | ($($tt:tt)*) => { $($tt)* } | ||
748 | } | ||
749 | macro_rules! id { | ||
750 | ($($tt:tt)*) => { id_deep!($($tt)*) } | ||
751 | } | ||
752 | fn bar() -> u32 { | ||
753 | 0 | ||
754 | } | ||
755 | fn foo() { | ||
756 | let a = id!([0u32, bar(<|>)] ); | ||
757 | } | ||
758 | ", | ||
759 | &["u32"], | ||
760 | ); | ||
761 | |||
762 | assert_eq!(hover_on, "bar()") | ||
763 | } | ||
764 | |||
765 | #[test] | ||
766 | fn test_hover_through_literal_string_in_macro() { | ||
767 | let hover_on = check_hover_result( | ||
768 | r#" | ||
769 | //- /lib.rs | ||
770 | macro_rules! arr { | ||
771 | ($($tt:tt)*) => { [$($tt)*)] } | ||
772 | } | ||
773 | fn foo() { | ||
774 | let mastered_for_itunes = ""; | ||
775 | let _ = arr!("Tr<|>acks", &mastered_for_itunes); | ||
776 | } | ||
777 | "#, | ||
778 | &["&str"], | ||
779 | ); | ||
780 | |||
781 | assert_eq!(hover_on, "\"Tracks\""); | ||
782 | } | ||
783 | |||
784 | #[test] | ||
785 | fn test_hover_through_literal_string_in_builtin_macro() { | ||
786 | check_hover_no_result( | ||
787 | r#" | ||
788 | //- /lib.rs | ||
789 | #[rustc_builtin_macro] | ||
790 | macro_rules! assert { | ||
791 | ($cond:expr) => {{ /* compiler built-in */ }}; | ||
792 | ($cond:expr,) => {{ /* compiler built-in */ }}; | ||
793 | ($cond:expr, $($arg:tt)+) => {{ /* compiler built-in */ }}; | ||
794 | } | ||
795 | |||
796 | fn foo() { | ||
797 | assert!("hel<|>lo"); | ||
798 | } | ||
799 | "#, | ||
800 | ); | ||
801 | } | ||
802 | |||
803 | #[test] | ||
758 | fn test_hover_non_ascii_space_doc() { | 804 | fn test_hover_non_ascii_space_doc() { |
759 | check_hover_result( | 805 | check_hover_result( |
760 | " | 806 | " |