Auto merge of #14811 - Veykril:closure-hover, r=HKalbasi
feat: Render hover actions for closure captures and sig Also special cases closures for ranged type hover to render the closure hover instead
This commit is contained in:
commit
88d2125d2f
@ -166,6 +166,10 @@ pub fn local(&self) -> BindingId {
|
|||||||
self.place.local
|
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 {
|
pub fn kind(&self) -> CaptureKind {
|
||||||
self.kind
|
self.kind
|
||||||
}
|
}
|
||||||
|
@ -3221,6 +3221,20 @@ pub fn captured_items(&self, db: &dyn HirDatabase) -> Vec<ClosureCapture> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn capture_types(&self, db: &dyn HirDatabase) -> Vec<Type> {
|
||||||
|
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 {
|
pub fn fn_trait(&self, db: &dyn HirDatabase) -> FnTrait {
|
||||||
let owner = db.lookup_intern_closure((self.id).into()).0;
|
let owner = db.lookup_intern_closure((self.id).into()).0;
|
||||||
let infer = &db.infer(owner);
|
let infer = &db.infer(owner);
|
||||||
|
@ -54,9 +54,10 @@ pub(crate) fn highlight_related(
|
|||||||
|
|
||||||
let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
|
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![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`
|
||||||
T![->] | T![|] => 3,
|
T![->] => 4,
|
||||||
kind if kind.is_keyword() => 2,
|
kind if kind.is_keyword() => 3,
|
||||||
IDENT | INT_NUMBER => 1,
|
IDENT | INT_NUMBER => 2,
|
||||||
|
T![|] => 1,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
})?;
|
})?;
|
||||||
// most if not all of these should be re-implemented with information seeded from hir
|
// most if not all of these should be re-implemented with information seeded from hir
|
||||||
|
@ -35,11 +35,11 @@ pub(super) fn type_info_of(
|
|||||||
_config: &HoverConfig,
|
_config: &HoverConfig,
|
||||||
expr_or_pat: &Either<ast::Expr, ast::Pat>,
|
expr_or_pat: &Either<ast::Expr, ast::Pat>,
|
||||||
) -> Option<HoverResult> {
|
) -> Option<HoverResult> {
|
||||||
let TypeInfo { original, adjusted } = match expr_or_pat {
|
let ty_info = match expr_or_pat {
|
||||||
Either::Left(expr) => sema.type_of_expr(expr)?,
|
Either::Left(expr) => sema.type_of_expr(expr)?,
|
||||||
Either::Right(pat) => sema.type_of_pat(pat)?,
|
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(
|
pub(super) fn closure_expr(
|
||||||
@ -47,41 +47,8 @@ pub(super) fn closure_expr(
|
|||||||
config: &HoverConfig,
|
config: &HoverConfig,
|
||||||
c: ast::ClosureExpr,
|
c: ast::ClosureExpr,
|
||||||
) -> Option<HoverResult> {
|
) -> Option<HoverResult> {
|
||||||
let ty = &sema.type_of_expr(&c.into())?.original;
|
let TypeInfo { original, .. } = sema.type_of_expr(&c.into())?;
|
||||||
let layout = if config.memory_layout {
|
closure_ty(sema, config, &TypeInfo { original, adjusted: None })
|
||||||
ty.layout(sema.db)
|
|
||||||
.map(|x| format!(" // size = {}, align = {}", x.size.bytes(), x.align.abi.bytes()))
|
|
||||||
.unwrap_or_default()
|
|
||||||
} else {
|
|
||||||
String::default()
|
|
||||||
};
|
|
||||||
let c = ty.as_closure()?;
|
|
||||||
let mut captures = c
|
|
||||||
.captured_items(sema.db)
|
|
||||||
.into_iter()
|
|
||||||
.map(|it| {
|
|
||||||
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",
|
|
||||||
CaptureKind::Move => "move",
|
|
||||||
};
|
|
||||||
format!("* `{}` by {}", it.display_place(sema.db), borrow_kind)
|
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
if captures.trim().is_empty() {
|
|
||||||
captures = "This closure captures nothing".to_string();
|
|
||||||
}
|
|
||||||
let mut res = HoverResult::default();
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
.into();
|
|
||||||
Some(res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn try_expr(
|
pub(super) fn try_expr(
|
||||||
@ -522,10 +489,13 @@ pub(super) fn definition(
|
|||||||
|
|
||||||
fn type_info(
|
fn type_info(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
_config: &HoverConfig,
|
config: &HoverConfig,
|
||||||
original: hir::Type,
|
ty: TypeInfo,
|
||||||
adjusted: Option<hir::Type>,
|
|
||||||
) -> Option<HoverResult> {
|
) -> Option<HoverResult> {
|
||||||
|
if let Some(res) = closure_ty(sema, config, &ty) {
|
||||||
|
return Some(res);
|
||||||
|
};
|
||||||
|
let TypeInfo { original, adjusted } = ty;
|
||||||
let mut res = HoverResult::default();
|
let mut res = HoverResult::default();
|
||||||
let mut targets: Vec<hir::ModuleDef> = Vec::new();
|
let mut targets: Vec<hir::ModuleDef> = Vec::new();
|
||||||
let mut push_new_def = |item: hir::ModuleDef| {
|
let mut push_new_def = |item: hir::ModuleDef| {
|
||||||
@ -555,6 +525,69 @@ fn type_info(
|
|||||||
Some(res)
|
Some(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn closure_ty(
|
||||||
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
|
config: &HoverConfig,
|
||||||
|
TypeInfo { original, adjusted }: &TypeInfo,
|
||||||
|
) -> Option<HoverResult> {
|
||||||
|
let c = original.as_closure()?;
|
||||||
|
let layout = if config.memory_layout {
|
||||||
|
original
|
||||||
|
.layout(sema.db)
|
||||||
|
.map(|x| format!(" // size = {}, align = {}", x.size.bytes(), x.align.abi.bytes()))
|
||||||
|
.unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
String::default()
|
||||||
|
};
|
||||||
|
let mut captures_rendered = c.captured_items(sema.db)
|
||||||
|
.into_iter()
|
||||||
|
.map(|it| {
|
||||||
|
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",
|
||||||
|
CaptureKind::Move => "move",
|
||||||
|
};
|
||||||
|
format!("* `{}` by {}", it.display_place(sema.db), borrow_kind)
|
||||||
|
})
|
||||||
|
.join("\n");
|
||||||
|
if captures_rendered.trim().is_empty() {
|
||||||
|
captures_rendered = "This closure captures nothing".to_string();
|
||||||
|
}
|
||||||
|
let mut targets: Vec<hir::ModuleDef> = Vec::new();
|
||||||
|
let mut push_new_def = |item: hir::ModuleDef| {
|
||||||
|
if !targets.contains(&item) {
|
||||||
|
targets.push(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
walk_and_push_ty(sema.db, original, &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 adjusted = if let Some(adjusted_ty) = adjusted {
|
||||||
|
walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
|
||||||
|
format!(
|
||||||
|
"\nCoerced to: {}",
|
||||||
|
adjusted_ty.display(sema.db).with_closure_style(hir::ClosureStyle::ImplFn)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut res = HoverResult::default();
|
||||||
|
res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
|
||||||
|
res.markup = format!(
|
||||||
|
"```rust\n{}{}\n{}\n```{adjusted}\n\n## Captures\n{}",
|
||||||
|
c.display_with_id(sema.db),
|
||||||
|
layout,
|
||||||
|
c.display_with_impl(sema.db),
|
||||||
|
captures_rendered,
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
Some(res)
|
||||||
|
}
|
||||||
|
|
||||||
fn render_builtin_attr(db: &RootDatabase, attr: hir::BuiltinAttr) -> Option<Markup> {
|
fn render_builtin_attr(db: &RootDatabase, attr: hir::BuiltinAttr) -> Option<Markup> {
|
||||||
let name = attr.name(db);
|
let name = attr.name(db);
|
||||||
let desc = format!("#[{name}]");
|
let desc = format!("#[{name}]");
|
||||||
|
@ -114,6 +114,15 @@ fn check_hover_range(ra_fixture: &str, expect: Expect) {
|
|||||||
expect.assert_eq(hover.info.markup.as_str())
|
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) {
|
fn check_hover_range_no_results(ra_fixture: &str) {
|
||||||
let (analysis, range) = fixture::range(ra_fixture);
|
let (analysis, range) = fixture::range(ra_fixture);
|
||||||
let hover = analysis.hover(&HOVER_BASE_CONFIG, range).unwrap();
|
let hover = analysis.hover(&HOVER_BASE_CONFIG, range).unwrap();
|
||||||
@ -294,6 +303,75 @@ impl FnOnce()
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_ranged_closure() {
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
//- minicore: fn
|
||||||
|
struct S;
|
||||||
|
struct S2;
|
||||||
|
fn main() {
|
||||||
|
let x = &S;
|
||||||
|
let y = ($0|| {x; S2}$0).call();
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```rust
|
||||||
|
{closure#0} // size = 8, align = 8
|
||||||
|
impl FnOnce() -> S2
|
||||||
|
```
|
||||||
|
Coerced to: &impl FnOnce() -> S2
|
||||||
|
|
||||||
|
## Captures
|
||||||
|
* `x` by move"#]],
|
||||||
|
);
|
||||||
|
check_hover_range_actions(
|
||||||
|
r#"
|
||||||
|
//- minicore: fn
|
||||||
|
struct S;
|
||||||
|
struct S2;
|
||||||
|
fn main() {
|
||||||
|
let x = &S;
|
||||||
|
let y = ($0|| {x; S2}$0).call();
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
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]
|
#[test]
|
||||||
fn hover_shows_long_type_of_an_expression() {
|
fn hover_shows_long_type_of_an_expression() {
|
||||||
check(
|
check(
|
||||||
|
Loading…
Reference in New Issue
Block a user