diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs index 6d03c76eb6a..907c94ea6b1 100644 --- a/crates/hir-ty/src/infer/closure.rs +++ b/crates/hir-ty/src/infer/closure.rs @@ -166,6 +166,10 @@ pub fn local(&self) -> BindingId { self.place.local } + pub fn ty(&self, subst: &Substitution) -> Ty { + self.ty.clone().substitute(Interner, utils::ClosureSubst(subst).parent_subst()) + } + pub fn kind(&self) -> CaptureKind { self.kind } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 1fac95ae5e3..3e508747428 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -3221,6 +3221,20 @@ pub fn captured_items(&self, db: &dyn HirDatabase) -> Vec { .collect() } + pub fn capture_types(&self, db: &dyn HirDatabase) -> Vec { + let owner = db.lookup_intern_closure((self.id).into()).0; + let infer = &db.infer(owner); + let (captures, _) = infer.closure_info(&self.id); + captures + .iter() + .cloned() + .map(|capture| Type { + env: db.trait_environment_for_body(owner), + ty: capture.ty(&self.subst), + }) + .collect() + } + pub fn fn_trait(&self, db: &dyn HirDatabase) -> FnTrait { let owner = db.lookup_intern_closure((self.id).into()).0; let infer = &db.infer(owner); diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index 3a519fe65a1..3b639104efe 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -54,9 +54,10 @@ pub(crate) fn highlight_related( let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind { T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?` - T![->] | T![|] => 3, - kind if kind.is_keyword() => 2, - IDENT | INT_NUMBER => 1, + T![->] => 4, + kind if kind.is_keyword() => 3, + IDENT | INT_NUMBER => 2, + T![|] => 1, _ => 0, })?; // most if not all of these should be re-implemented with information seeded from hir diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index 53226db7ccd..4ab60c76ec1 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -35,11 +35,11 @@ pub(super) fn type_info_of( _config: &HoverConfig, expr_or_pat: &Either, ) -> Option { - let TypeInfo { original, adjusted } = match expr_or_pat { + let ty_info = match expr_or_pat { Either::Left(expr) => sema.type_of_expr(expr)?, Either::Right(pat) => sema.type_of_pat(pat)?, }; - type_info(sema, _config, original, adjusted) + type_info(sema, _config, ty_info) } pub(super) fn closure_expr( @@ -47,7 +47,16 @@ pub(super) fn closure_expr( config: &HoverConfig, c: ast::ClosureExpr, ) -> Option { - let ty = &sema.type_of_expr(&c.into())?.original; + let ty = sema.type_of_expr(&c.into())?; + closure_ty(sema, config, &ty.original) +} + +fn closure_ty( + sema: &Semantics<'_, RootDatabase>, + config: &HoverConfig, + ty: &hir::Type, +) -> Option { + let c = ty.as_closure()?; let layout = if config.memory_layout { ty.layout(sema.db) .map(|x| format!(" // size = {}, align = {}", x.size.bytes(), x.align.abi.bytes())) @@ -55,12 +64,10 @@ pub(super) fn closure_expr( } else { String::default() }; - let c = ty.as_closure()?; - let mut captures = c - .captured_items(sema.db) + let mut captures_rendered = c.captured_items(sema.db) .into_iter() .map(|it| { - let borrow_kind= match it.kind() { + let borrow_kind = match it.kind() { CaptureKind::SharedRef => "immutable borrow", CaptureKind::UniqueSharedRef => "unique immutable borrow ([read more](https://doc.rust-lang.org/stable/reference/types/closure.html#unique-immutable-borrows-in-captures))", CaptureKind::MutableRef => "mutable borrow", @@ -69,16 +76,28 @@ pub(super) fn closure_expr( format!("* `{}` by {}", it.display_place(sema.db), borrow_kind) }) .join("\n"); - if captures.trim().is_empty() { - captures = "This closure captures nothing".to_string(); + if captures_rendered.trim().is_empty() { + captures_rendered = "This closure captures nothing".to_string(); } + let mut targets: Vec = Vec::new(); + let mut push_new_def = |item: hir::ModuleDef| { + if !targets.contains(&item) { + targets.push(item); + } + }; + walk_and_push_ty(sema.db, ty, &mut push_new_def); + c.capture_types(sema.db).into_iter().for_each(|ty| { + walk_and_push_ty(sema.db, &ty, &mut push_new_def); + }); + let mut res = HoverResult::default(); + res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets)); res.markup = format!( "```rust\n{}{}\n{}\n```\n\n## Captures\n{}", c.display_with_id(sema.db), layout, c.display_with_impl(sema.db), - captures, + captures_rendered, ) .into(); Some(res) @@ -522,10 +541,12 @@ pub(super) fn definition( fn type_info( sema: &Semantics<'_, RootDatabase>, - _config: &HoverConfig, - original: hir::Type, - adjusted: Option, + config: &HoverConfig, + TypeInfo { original, adjusted }: TypeInfo, ) -> Option { + if let Some(res) = closure_ty(sema, config, &original) { + return Some(res); + } let mut res = HoverResult::default(); let mut targets: Vec = Vec::new(); let mut push_new_def = |item: hir::ModuleDef| { diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index a79e47dd67d..12beff3b900 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -114,6 +114,15 @@ fn check_hover_range(ra_fixture: &str, expect: Expect) { expect.assert_eq(hover.info.markup.as_str()) } +fn check_hover_range_actions(ra_fixture: &str, expect: Expect) { + let (analysis, range) = fixture::range(ra_fixture); + let hover = analysis + .hover(&HoverConfig { links_in_hover: true, ..HOVER_BASE_CONFIG }, range) + .unwrap() + .unwrap(); + expect.assert_debug_eq(&hover.info.actions); +} + fn check_hover_range_no_results(ra_fixture: &str) { let (analysis, range) = fixture::range(ra_fixture); let hover = analysis.hover(&HOVER_BASE_CONFIG, range).unwrap(); @@ -294,6 +303,72 @@ impl FnOnce() ); } +#[test] +fn hover_ranged_closure() { + check_hover_range( + r#" +struct S; +struct S2; +fn main() { + let x = &S; + let y = $0|| {x; S2}$0; +} +"#, + expect![[r#" + ```rust + {closure#0} // size = 8, align = 8 + impl FnOnce() -> S2 + ``` + + ## Captures + * `x` by move"#]], + ); + check_hover_range_actions( + r#" +struct S; +struct S2; +fn main() { + let x = &S; + let y = $0|| {x; S2}$0; +} +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::S2", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 10..20, + focus_range: 17..19, + name: "S2", + kind: Struct, + description: "struct S2", + }, + }, + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..9, + focus_range: 7..8, + name: "S", + kind: Struct, + description: "struct S", + }, + }, + ], + ), + ] + "#]], + ); +} + #[test] fn hover_shows_long_type_of_an_expression() { check(