Auto merge of #14690 - HKalbasi:closure-hover, r=HKalbasi
Add hover for closure
This commit is contained in:
commit
3a27518fee
@ -62,7 +62,7 @@
|
|||||||
mod expr;
|
mod expr;
|
||||||
mod pat;
|
mod pat;
|
||||||
mod coerce;
|
mod coerce;
|
||||||
mod closure;
|
pub(crate) mod closure;
|
||||||
mod mutability;
|
mod mutability;
|
||||||
|
|
||||||
/// The entry point of type inference.
|
/// The entry point of type inference.
|
||||||
@ -426,7 +426,7 @@ pub fn expr_type_mismatches(&self) -> impl Iterator<Item = (ExprId, &TypeMismatc
|
|||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub(crate) fn closure_info(&self, closure: &ClosureId) -> &(Vec<CapturedItem>, FnTrait) {
|
pub fn closure_info(&self, closure: &ClosureId) -> &(Vec<CapturedItem>, FnTrait) {
|
||||||
self.closure_info.get(closure).unwrap()
|
self.closure_info.get(closure).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use chalk_ir::{cast::Cast, AliasEq, AliasTy, FnSubst, Mutability, TyKind, WhereClause};
|
use chalk_ir::{cast::Cast, AliasEq, AliasTy, FnSubst, Mutability, TyKind, WhereClause};
|
||||||
use hir_def::{
|
use hir_def::{
|
||||||
|
data::adt::VariantData,
|
||||||
hir::{
|
hir::{
|
||||||
Array, BinaryOp, BindingAnnotation, BindingId, CaptureBy, Expr, ExprId, Pat, PatId,
|
Array, BinaryOp, BindingAnnotation, BindingId, CaptureBy, Expr, ExprId, Pat, PatId,
|
||||||
Statement, UnaryOp,
|
Statement, UnaryOp,
|
||||||
@ -18,6 +19,7 @@
|
|||||||
use stdx::never;
|
use stdx::never;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
db::HirDatabase,
|
||||||
mir::{BorrowKind, MirSpan, ProjectionElem},
|
mir::{BorrowKind, MirSpan, ProjectionElem},
|
||||||
static_lifetime, to_chalk_trait_id,
|
static_lifetime, to_chalk_trait_id,
|
||||||
traits::FnTrait,
|
traits::FnTrait,
|
||||||
@ -146,13 +148,81 @@ pub(crate) enum CaptureKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub(crate) struct CapturedItem {
|
pub struct CapturedItem {
|
||||||
pub(crate) place: HirPlace,
|
pub(crate) place: HirPlace,
|
||||||
pub(crate) kind: CaptureKind,
|
pub(crate) kind: CaptureKind,
|
||||||
pub(crate) span: MirSpan,
|
pub(crate) span: MirSpan,
|
||||||
pub(crate) ty: Ty,
|
pub(crate) ty: Ty,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CapturedItem {
|
||||||
|
pub fn display_kind(&self) -> &'static str {
|
||||||
|
match self.kind {
|
||||||
|
CaptureKind::ByRef(k) => match k {
|
||||||
|
BorrowKind::Shared => "immutable borrow",
|
||||||
|
BorrowKind::Shallow => {
|
||||||
|
never!("shallow borrow should not happen in closure captures");
|
||||||
|
"shallow borrow"
|
||||||
|
},
|
||||||
|
BorrowKind::Unique => "unique immutable borrow ([read more](https://doc.rust-lang.org/stable/reference/types/closure.html#unique-immutable-borrows-in-captures))",
|
||||||
|
BorrowKind::Mut { .. } => "mutable borrow",
|
||||||
|
},
|
||||||
|
CaptureKind::ByValue => "move",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_place(&self, owner: ClosureId, db: &dyn HirDatabase) -> String {
|
||||||
|
let owner = db.lookup_intern_closure(owner.into()).0;
|
||||||
|
let body = db.body(owner);
|
||||||
|
let mut result = body[self.place.local].name.to_string();
|
||||||
|
let mut field_need_paren = false;
|
||||||
|
for proj in &self.place.projections {
|
||||||
|
match proj {
|
||||||
|
ProjectionElem::Deref => {
|
||||||
|
result = format!("*{result}");
|
||||||
|
field_need_paren = true;
|
||||||
|
}
|
||||||
|
ProjectionElem::Field(f) => {
|
||||||
|
if field_need_paren {
|
||||||
|
result = format!("({result})");
|
||||||
|
}
|
||||||
|
let variant_data = f.parent.variant_data(db.upcast());
|
||||||
|
let field = match &*variant_data {
|
||||||
|
VariantData::Record(fields) => fields[f.local_id]
|
||||||
|
.name
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or("[missing field]")
|
||||||
|
.to_string(),
|
||||||
|
VariantData::Tuple(fields) => fields
|
||||||
|
.iter()
|
||||||
|
.position(|x| x.0 == f.local_id)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string(),
|
||||||
|
VariantData::Unit => "[missing field]".to_string(),
|
||||||
|
};
|
||||||
|
result = format!("{result}.{field}");
|
||||||
|
field_need_paren = false;
|
||||||
|
}
|
||||||
|
&ProjectionElem::TupleOrClosureField(field) => {
|
||||||
|
if field_need_paren {
|
||||||
|
result = format!("({result})");
|
||||||
|
}
|
||||||
|
result = format!("{result}.{field}");
|
||||||
|
field_need_paren = false;
|
||||||
|
}
|
||||||
|
ProjectionElem::Index(_)
|
||||||
|
| ProjectionElem::ConstantIndex { .. }
|
||||||
|
| ProjectionElem::Subslice { .. }
|
||||||
|
| ProjectionElem::OpaqueCast(_) => {
|
||||||
|
never!("Not happen in closure capture");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub(crate) struct CapturedItemWithoutTy {
|
pub(crate) struct CapturedItemWithoutTy {
|
||||||
pub(crate) place: HirPlace,
|
pub(crate) place: HirPlace,
|
||||||
|
@ -60,8 +60,8 @@ macro_rules! eprintln {
|
|||||||
pub use builder::{ParamKind, TyBuilder};
|
pub use builder::{ParamKind, TyBuilder};
|
||||||
pub use chalk_ext::*;
|
pub use chalk_ext::*;
|
||||||
pub use infer::{
|
pub use infer::{
|
||||||
could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode, InferenceDiagnostic,
|
closure::CapturedItem, could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode,
|
||||||
InferenceResult, OverloadedDeref, PointerCast,
|
InferenceDiagnostic, InferenceResult, OverloadedDeref, PointerCast,
|
||||||
};
|
};
|
||||||
pub use interner::Interner;
|
pub use interner::Interner;
|
||||||
pub use lower::{
|
pub use lower::{
|
||||||
|
@ -3174,6 +3174,46 @@ pub fn get_type_argument(&self, idx: usize) -> Option<Type> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Closure {
|
||||||
|
id: ClosureId,
|
||||||
|
subst: Substitution,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Closure> for ClosureId {
|
||||||
|
fn from(value: Closure) -> Self {
|
||||||
|
value.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Closure {
|
||||||
|
fn as_ty(self) -> Ty {
|
||||||
|
TyKind::Closure(self.id, self.subst).intern(Interner)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_with_id(&self, db: &dyn HirDatabase) -> String {
|
||||||
|
self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ClosureWithId).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_with_impl(&self, db: &dyn HirDatabase) -> String {
|
||||||
|
self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ImplFn).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn captured_items(&self, db: &dyn HirDatabase) -> Vec<hir_ty::CapturedItem> {
|
||||||
|
let owner = db.lookup_intern_closure((self.id).into()).0;
|
||||||
|
let infer = &db.infer(owner);
|
||||||
|
let info = infer.closure_info(&self.id);
|
||||||
|
info.0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fn_trait(&self, db: &dyn HirDatabase) -> FnTrait {
|
||||||
|
let owner = db.lookup_intern_closure((self.id).into()).0;
|
||||||
|
let infer = &db.infer(owner);
|
||||||
|
let info = infer.closure_info(&self.id);
|
||||||
|
info.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Type {
|
pub struct Type {
|
||||||
env: Arc<TraitEnvironment>,
|
env: Arc<TraitEnvironment>,
|
||||||
@ -3463,6 +3503,13 @@ pub fn is_closure(&self) -> bool {
|
|||||||
matches!(self.ty.kind(Interner), TyKind::Closure { .. })
|
matches!(self.ty.kind(Interner), TyKind::Closure { .. })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_closure(&self) -> Option<Closure> {
|
||||||
|
match self.ty.kind(Interner) {
|
||||||
|
TyKind::Closure(id, subst) => Some(Closure { id: *id, subst: subst.clone() }),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_fn(&self) -> bool {
|
pub fn is_fn(&self) -> bool {
|
||||||
matches!(self.ty.kind(Interner), TyKind::FnDef(..) | TyKind::Function { .. })
|
matches!(self.ty.kind(Interner), TyKind::FnDef(..) | TyKind::Function { .. })
|
||||||
}
|
}
|
||||||
@ -4016,6 +4063,10 @@ pub fn generic_params(&self, db: &dyn HirDatabase) -> FxHashSet<GenericParam> {
|
|||||||
.map(|id| TypeOrConstParam { id }.split(db).either_into())
|
.map(|id| TypeOrConstParam { id }.split(db).either_into())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn layout(&self, db: &dyn HirDatabase) -> Result<Layout, LayoutError> {
|
||||||
|
layout_of_ty(db, &self.ty, self.env.krate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Document this
|
// FIXME: Document this
|
||||||
|
@ -119,8 +119,8 @@ fn hover_simple(
|
|||||||
| T![crate]
|
| T![crate]
|
||||||
| T![Self]
|
| T![Self]
|
||||||
| T![_] => 4,
|
| T![_] => 4,
|
||||||
// index and prefix ops
|
// index and prefix ops and closure pipe
|
||||||
T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3,
|
T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] | T![|] => 3,
|
||||||
kind if kind.is_keyword() => 2,
|
kind if kind.is_keyword() => 2,
|
||||||
T!['('] | T![')'] => 2,
|
T!['('] | T![')'] => 2,
|
||||||
kind if kind.is_trivia() => 0,
|
kind if kind.is_trivia() => 0,
|
||||||
@ -219,6 +219,16 @@ fn hover_simple(
|
|||||||
};
|
};
|
||||||
render::type_info_of(sema, config, &Either::Left(call_expr))
|
render::type_info_of(sema, config, &Either::Left(call_expr))
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
// try closure
|
||||||
|
.or_else(|| {
|
||||||
|
descended().find_map(|token| {
|
||||||
|
if token.kind() != T![|] {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let c = token.parent().and_then(|x| x.parent()).and_then(ast::ClosureExpr::cast)?;
|
||||||
|
render::closure_expr(sema, c)
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
result.map(|mut res: HoverResult| {
|
result.map(|mut res: HoverResult| {
|
||||||
|
@ -42,6 +42,38 @@ pub(super) fn type_info_of(
|
|||||||
type_info(sema, _config, original, adjusted)
|
type_info(sema, _config, original, adjusted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn closure_expr(
|
||||||
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
|
c: ast::ClosureExpr,
|
||||||
|
) -> Option<HoverResult> {
|
||||||
|
let ty = &sema.type_of_expr(&c.into())?.original;
|
||||||
|
let layout = ty
|
||||||
|
.layout(sema.db)
|
||||||
|
.map(|x| format!(" // size = {}, align = {}", x.size.bytes(), x.align.abi.bytes()))
|
||||||
|
.unwrap_or_default();
|
||||||
|
let c = ty.as_closure()?;
|
||||||
|
let mut captures = c
|
||||||
|
.captured_items(sema.db)
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| {
|
||||||
|
format!("* `{}` by {}", x.display_place(c.clone().into(), sema.db), x.display_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(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
_config: &HoverConfig,
|
_config: &HoverConfig,
|
||||||
|
@ -198,6 +198,85 @@ pub fn foo() -> u32
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_closure() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- minicore: copy
|
||||||
|
fn main() {
|
||||||
|
let x = 2;
|
||||||
|
let y = $0|z| x + z;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
*|*
|
||||||
|
```rust
|
||||||
|
{closure#0} // size = 8, align = 8
|
||||||
|
impl Fn(i32) -> i32
|
||||||
|
```
|
||||||
|
|
||||||
|
## Captures
|
||||||
|
* `x` by immutable borrow
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- minicore: copy
|
||||||
|
fn foo(x: impl Fn(i32) -> i32) {
|
||||||
|
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
foo($0|x: i32| x)
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
*|*
|
||||||
|
```rust
|
||||||
|
{closure#0} // size = 0, align = 1
|
||||||
|
impl Fn(i32) -> i32
|
||||||
|
```
|
||||||
|
|
||||||
|
## Captures
|
||||||
|
This closure captures nothing
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- minicore: copy
|
||||||
|
|
||||||
|
struct Z { f: i32 }
|
||||||
|
|
||||||
|
struct Y(&'static mut Z)
|
||||||
|
|
||||||
|
struct X {
|
||||||
|
f1: Y,
|
||||||
|
f2: (Y, Y),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let x: X;
|
||||||
|
let y = $0|| {
|
||||||
|
x.f1;
|
||||||
|
&mut x.f2.0 .0.f;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
*|*
|
||||||
|
```rust
|
||||||
|
{closure#0} // size = 16, align = 8
|
||||||
|
impl FnOnce()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Captures
|
||||||
|
* `x.f1` by move
|
||||||
|
* `(*x.f2.0.0).f` by mutable borrow
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[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