diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index cc320227f8..e17bd9594c 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -225,7 +225,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.type_of_pat(pat) } - pub fn type_of_pat_with_coercion(&self, expr: &ast::Pat) -> Option { + pub fn type_of_pat_with_coercion(&self, expr: &ast::Pat) -> Option<(Type, bool)> { self.imp.type_of_pat_with_coercion(expr) } @@ -577,7 +577,7 @@ impl<'db> SemanticsImpl<'db> { self.analyze(pat.syntax()).type_of_pat(self.db, pat) } - fn type_of_pat_with_coercion(&self, pat: &ast::Pat) -> Option { + fn type_of_pat_with_coercion(&self, pat: &ast::Pat) -> Option<(Type, bool)> { self.analyze(pat.syntax()).type_of_pat_with_coercion(self.db, pat) } diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index b1792f2ab0..e18d27e722 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -147,15 +147,15 @@ impl SourceAnalyzer { &self, db: &dyn HirDatabase, pat: &ast::Pat, - ) -> Option { + ) -> Option<(Type, bool)> { let pat_id = self.pat_id(pat)?; let infer = self.infer.as_ref()?; - let ty = infer + let (ty, coerced) = infer .pat_adjustments .get(&pat_id) - .and_then(|adjusts| adjusts.last().map(|adjust| &adjust.target)) - .unwrap_or_else(|| &infer[pat_id]); - Type::new_with_resolver(db, &self.resolver, ty.clone()) + .and_then(|adjusts| adjusts.last().map(|adjust| (&adjust.target, true))) + .unwrap_or_else(|| (&infer[pat_id], false)); + Type::new_with_resolver(db, &self.resolver, ty.clone()).zip(Some(coerced)) } pub(crate) fn type_of_self( diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 35ba266f56..82afae7674 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -12,12 +12,8 @@ use ide_db::{ use itertools::Itertools; use stdx::format_to; use syntax::{ - algo::{self, find_node_at_range}, - ast, - display::fn_as_proc_macro_label, - match_ast, AstNode, AstToken, Direction, - SyntaxKind::*, - SyntaxToken, T, + algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, AstToken, Direction, + SyntaxKind::*, SyntaxToken, T, }; use crate::{ @@ -79,34 +75,28 @@ pub struct HoverResult { // image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[] pub(crate) fn hover( db: &RootDatabase, - range: FileRange, + FileRange { file_id, range }: FileRange, config: &HoverConfig, ) -> Option> { let sema = hir::Semantics::new(db); - let file = sema.parse(range.file_id).syntax().clone(); + let file = sema.parse(file_id).syntax().clone(); - // This means we're hovering over a range. - if !range.range.is_empty() { - let expr = find_node_at_range::(&file, range.range)?; - let ty = sema.type_of_expr(&expr)?; + let offset = if range.is_empty() { + range.start() + } else { + let expr = file.covering_element(range).ancestors().find_map(|it| { + match_ast! { + match it { + ast::Expr(expr) => Some(Either::Left(expr)), + ast::Pat(pat) => Some(Either::Right(pat)), + _ => None, + } + } + })?; + return hover_type_info(&sema, config, expr).map(|it| RangeInfo::new(range, it)); + }; - if ty.is_unknown() { - return None; - } - - let mut res = HoverResult::default(); - - res.markup = if config.markdown() { - Markup::fenced_block(&ty.display(db)) - } else { - ty.display(db).to_string().into() - }; - - return Some(RangeInfo::new(range.range, res)); - } - - let position = FilePosition { file_id: range.file_id, offset: range.range.start() }; - let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind { + let token = pick_best_token(file.token_at_offset(offset), |kind| match kind { IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3, T!['('] | T![')'] => 2, kind if kind.is_trivia() => 0, @@ -114,8 +104,6 @@ pub(crate) fn hover( })?; let token = sema.descend_into_macros(token); - let mut res = HoverResult::default(); - let node = token.parent()?; let mut range = None; let definition = match_ast! { @@ -146,8 +134,8 @@ pub(crate) fn hover( let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?; let (idl_range, link, ns) = extract_definitions_from_docs(&docs).into_iter().find_map(|(range, link, ns)| { - let hir::InFile { file_id, value: mapped_range } = doc_mapping.map(range)?; - (file_id == position.file_id.into() && mapped_range.contains(position.offset)).then(||(mapped_range, link, ns)) + let mapped = doc_mapping.map(range)?; + (mapped.file_id == file_id.into() && mapped.value.contains(offset)).then(||(mapped.value, link, ns)) })?; range = Some(idl_range); Some(match resolve_doc_path_for_def(db,def, &link,ns)? { @@ -176,6 +164,7 @@ pub(crate) fn hover( _ => None, }; if let Some(markup) = hover_for_definition(db, definition, famous_defs.as_ref(), config) { + let mut res = HoverResult::default(); res.markup = process_markup(sema.db, definition, &markup, config); if let Some(action) = show_implementations_action(db, definition) { res.actions.push(action); @@ -185,7 +174,7 @@ pub(crate) fn hover( res.actions.push(action); } - if let Some(action) = runnable_action(&sema, definition, position.file_id) { + if let Some(action) = runnable_action(&sema, definition, file_id) { res.actions.push(action); } @@ -207,10 +196,10 @@ pub(crate) fn hover( .take_while(|it| !ast::Item::can_cast(it.kind())) .find(|n| ast::Expr::can_cast(n.kind()) || ast::Pat::can_cast(n.kind()))?; - let ty = match_ast! { + let expr_or_pat = match_ast! { match node { - ast::Expr(it) => sema.type_of_expr(&it)?, - ast::Pat(it) => sema.type_of_pat(&it)?, + ast::Expr(it) => Either::Left(it), + ast::Pat(it) => Either::Right(it), // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. // (e.g expanding a builtin macro). So we give up here. ast::MacroCall(_it) => return None, @@ -218,16 +207,48 @@ pub(crate) fn hover( } }; - res.markup = if config.markdown() { - Markup::fenced_block(&ty.display(db)) - } else { - ty.display(db).to_string().into() - }; - + let res = hover_type_info(&sema, config, expr_or_pat)?; let range = sema.original_range(&node).range; Some(RangeInfo::new(range, res)) } +fn hover_type_info( + sema: &Semantics, + config: &HoverConfig, + expr_or_pat: Either, +) -> Option { + let (ty, coerced) = match &expr_or_pat { + Either::Left(expr) => sema.type_of_expr_with_coercion(expr)?, + Either::Right(pat) => sema.type_of_pat_with_coercion(pat)?, + }; + + let mut res = HoverResult::default(); + res.markup = if coerced { + let uncoerced_ty = match &expr_or_pat { + Either::Left(expr) => sema.type_of_expr(expr)?, + Either::Right(pat) => sema.type_of_pat(pat)?, + }; + let uncoerced = uncoerced_ty.display(sema.db).to_string(); + let coerced = ty.display(sema.db).to_string(); + format!( + "```text\nType: {:>upad$}\nCoerced to: {:>cpad$}\n```\n", + uncoerced = uncoerced, + coerced = coerced, + // 6 base padding for static text prefix of each line + upad = 6 + coerced.len().max(uncoerced.len()), + cpad = uncoerced.len(), + ) + .into() + } else { + if config.markdown() { + Markup::fenced_block(&ty.display(sema.db)) + } else { + ty.display(sema.db).to_string().into() + } + }; + Some(res) +} + fn try_hover_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option> { let (path, tt) = attr.as_simple_call()?; if !tt.syntax().text_range().contains(token.text_range().start()) { @@ -1192,7 +1213,7 @@ impl Thing { } fn main() { let foo_$0test = Thing::new(); } - "#, +"#, expect![[r#" *foo_test* @@ -1562,7 +1583,7 @@ fn foo() { fn foo() { format!("hel$0lo {}", 0); } - "#, +"#, ); } @@ -1670,7 +1691,7 @@ extern crate st$0d; //! //! Printed? //! abc123 - "#, +"#, expect![[r#" *std* @@ -1695,7 +1716,7 @@ extern crate std as ab$0c; //! //! Printed? //! abc123 - "#, +"#, expect![[r#" *abc* @@ -2214,7 +2235,7 @@ mod tests$0 { struct S{ f1: u32 } fn main() { let s$0t = S{ f1:0 }; } - "#, +"#, expect![[r#" [ GoToType( @@ -2293,7 +2314,7 @@ struct Arg(u32); struct S{ f1: T } fn main() { let s$0t = S{ f1: S{ f1: Arg(0) } }; } - "#, +"#, expect![[r#" [ GoToType( @@ -2482,7 +2503,7 @@ trait Bar {} fn foo() -> impl Foo + Bar {} fn main() { let s$0t = foo(); } - "#, +"#, expect![[r#" [ GoToType( @@ -2915,7 +2936,7 @@ struct B {} struct S {} fn foo(a$0rg: &impl ImplTrait>>>) {} - "#, +"#, expect![[r#" [ GoToType( @@ -3711,7 +3732,7 @@ mod string { /// This is `alloc::String`. pub struct String; } - "#, +"#, expect![[r#" *String* @@ -3830,7 +3851,7 @@ pub fn foo() {} //- /lib.rs crate:main.rs deps:foo #[fo$0o::bar()] struct Foo; - "#, +"#, expect![[r#" *foo* @@ -3846,7 +3867,7 @@ struct Foo; check( r#" use self as foo$0; - "#, +"#, expect![[r#" *foo* @@ -3859,7 +3880,7 @@ use self as foo$0; r#" mod bar {} use bar::{self as foo$0}; - "#, +"#, expect![[r#" *foo* @@ -3877,7 +3898,7 @@ use bar::{self as foo$0}; mod bar { use super as foo$0; } - "#, +"#, expect![[r#" *foo* @@ -3889,7 +3910,7 @@ mod bar { check( r#" use crate as foo$0; - "#, +"#, expect![[r#" *foo* @@ -3908,7 +3929,7 @@ use crate as foo$0; pub macro Copy {} #[derive(Copy$0)] struct Foo; - "#, +"#, expect![[r#" *Copy* @@ -3929,7 +3950,7 @@ mod foo { } #[derive(foo::Copy$0)] struct Foo; - "#, +"#, expect![[r#" *Copy* @@ -3949,7 +3970,7 @@ struct Foo; check_hover_range( r#" fn f() { let expr = $01 + 2 * 3$0 } - "#, +"#, expect![[r#" ```rust i32 @@ -3959,7 +3980,7 @@ fn f() { let expr = $01 + 2 * 3$0 } check_hover_range( r#" fn f() { let expr = 1 $0+ 2 * $03 } - "#, +"#, expect![[r#" ```rust i32 @@ -3969,7 +3990,7 @@ fn f() { let expr = 1 $0+ 2 * $03 } check_hover_range( r#" fn f() { let expr = 1 + $02 * 3$0 } - "#, +"#, expect![[r#" ```rust i32 @@ -3982,7 +4003,7 @@ fn f() { let expr = 1 + $02 * 3$0 } check_hover_range( r#" fn f() { let expr = $0[1, 2, 3, 4]$0 } - "#, +"#, expect![[r#" ```rust [i32; 4] @@ -3992,7 +4013,7 @@ fn f() { let expr = $0[1, 2, 3, 4]$0 } check_hover_range( r#" fn f() { let expr = [1, 2, $03, 4]$0 } - "#, +"#, expect![[r#" ```rust [i32; 4] @@ -4002,7 +4023,7 @@ fn f() { let expr = [1, 2, $03, 4]$0 } check_hover_range( r#" fn f() { let expr = [1, 2, $03$0, 4] } - "#, +"#, expect![[r#" ```rust i32 @@ -4016,7 +4037,7 @@ fn f() { let expr = [1, 2, $03$0, 4] } r#" fn f(a: &[T]) { } fn b() { $0f$0(&[1, 2, 3, 4, 5]); } - "#, +"#, expect![[r#" ```rust fn f(&[i32]) @@ -4027,7 +4048,7 @@ fn b() { $0f$0(&[1, 2, 3, 4, 5]); } r#" fn f(a: &[T]) { } fn b() { f($0&[1, 2, 3, 4, 5]$0); } - "#, +"#, expect![[r#" ```rust &[i32; 5] @@ -4041,20 +4062,20 @@ fn b() { f($0&[1, 2, 3, 4, 5]$0); } r#" fn f(a: &[T]) { } fn b()$0 { f(&[1, 2, 3, 4, 5]); }$0 - "#, +"#, ); check_hover_range_no_results( r#" fn f$0(a: &[T]) { } fn b() { f(&[1, 2, 3,$0 4, 5]); } - "#, +"#, ); check_hover_range_no_results( r#" fn $0f() { let expr = [1, 2, 3, 4]$0 } - "#, +"#, ); } @@ -4064,7 +4085,7 @@ fn $0f() { let expr = [1, 2, 3, 4]$0 } r#" fn f(a: &[T]) { } fn b() { $0f(&[1, 2, 3, 4, 5]); }$0 - "#, +"#, expect![[r#" ```rust () @@ -4074,11 +4095,68 @@ fn b() { $0f(&[1, 2, 3, 4, 5]); }$0 check_hover_range( r#" fn f() { let expr$0 = $0[1, 2, 3, 4] } - "#, +"#, expect![[r#" ```rust () ```"#]], ); } + + #[test] + fn hover_range_for_pat() { + check_hover_range( + r#" +fn foo() { + let $0x$0 = 0; +} +"#, + expect![[r#" + ```rust + i32 + ```"#]], + ); + + check_hover_range( + r#" +fn foo() { + let $0x$0 = ""; +} +"#, + expect![[r#" + ```rust + &str + ```"#]], + ); + } + + #[test] + fn hover_range_shows_coercions_if_applicable_expr() { + check_hover_range( + r#" +fn foo() { + let x: &u32 = $0&&&&&0$0; +} +"#, + expect![[r#" + ```text + Type: &&&&&u32 + Coerced to: &u32 + ``` + "#]], + ); + check_hover_range( + r#" +fn foo() { + let x: *const u32 = $0&0$0; +} +"#, + expect![[r#" + ```text + Type: &u32 + Coerced to: *const u32 + ``` + "#]], + ); + } }