ide/inlay_hints/
closure_captures.rsuse ide_db::famous_defs::FamousDefs;
use ide_db::text_edit::{TextRange, TextSize};
use span::EditionedFileId;
use stdx::{never, TupleExt};
use syntax::ast::{self, AstNode};
use crate::{
InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintPosition, InlayHintsConfig, InlayKind,
};
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
_file_id: EditionedFileId,
closure: ast::ClosureExpr,
) -> Option<()> {
if !config.closure_capture_hints {
return None;
}
let ty = &sema.type_of_expr(&closure.clone().into())?.original;
let c = ty.as_closure()?;
let captures = c.captured_items(sema.db);
if captures.is_empty() {
return None;
}
let (range, label) = match closure.move_token() {
Some(t) => (t.text_range(), InlayHintLabel::default()),
None => {
let prev_token = closure.syntax().first_token()?.prev_token()?.text_range();
(
TextRange::new(prev_token.end() - TextSize::from(1), prev_token.end()),
InlayHintLabel::from("move"),
)
}
};
let mut hint = InlayHint {
range,
kind: InlayKind::ClosureCapture,
label,
text_edit: None,
position: InlayHintPosition::After,
pad_left: false,
pad_right: true,
resolve_parent: Some(closure.syntax().text_range()),
};
hint.label.append_str("(");
let last = captures.len() - 1;
for (idx, capture) in captures.into_iter().enumerate() {
let local = capture.local();
let source = local.primary_source(sema.db);
_ = sema.parse_or_expand(source.file());
let label = format!(
"{}{}",
match capture.kind() {
hir::CaptureKind::SharedRef => "&",
hir::CaptureKind::UniqueSharedRef => "&unique ",
hir::CaptureKind::MutableRef => "&mut ",
hir::CaptureKind::Move => "",
},
capture.display_place(sema.db)
);
if never!(label.is_empty()) {
continue;
}
hint.label.append_part(InlayHintLabelPart {
text: label,
linked_location: source.name().and_then(|name| {
name.syntax().original_file_range_opt(sema.db).map(TupleExt::head).map(Into::into)
}),
tooltip: None,
});
if idx != last {
hint.label.append_str(", ");
}
}
hint.label.append_str(")");
acc.push(hint);
Some(())
}
#[cfg(test)]
mod tests {
use crate::{
inlay_hints::tests::{check_with_config, DISABLED_CONFIG},
InlayHintsConfig,
};
#[test]
fn all_capture_kinds() {
check_with_config(
InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
r#"
//- minicore: copy, derive
#[derive(Copy, Clone)]
struct Copy;
struct NonCopy;
fn main() {
let foo = Copy;
let bar = NonCopy;
let mut baz = NonCopy;
let qux = &mut NonCopy;
|| {
// ^ move(&foo, bar, baz, qux)
foo;
bar;
baz;
qux;
};
|| {
// ^ move(&foo, &bar, &baz, &qux)
&foo;
&bar;
&baz;
&qux;
};
|| {
// ^ move(&mut baz)
&mut baz;
};
|| {
// ^ move(&mut baz, &mut *qux)
baz = NonCopy;
*qux = NonCopy;
};
}
"#,
);
}
#[test]
fn move_token() {
check_with_config(
InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
r#"
//- minicore: copy, derive
fn main() {
let foo = u32;
move || {
// ^^^^ (foo)
foo;
};
}
"#,
);
}
}