aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/hover.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/hover.rs')
-rw-r--r--crates/ra_ide/src/hover.rs198
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
3use hir::{db::AstDatabase, Adt, HasSource, HirDisplay, SourceBinder}; 3use hir::{Adt, HasSource, HirDisplay, Semantics};
4use ra_db::SourceDatabase; 4use ra_ide_db::{
5use ra_ide_db::{defs::NameDefinition, RootDatabase}; 5 defs::{classify_name, NameDefinition},
6 RootDatabase,
7};
6use ra_syntax::{ 8use 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
14use crate::{ 15use 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
145pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { 145pub(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
199pub(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)]
221mod tests { 211mod 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 "