use std::mem;
use hir::Mutability;
use ide_db::famous_defs::FamousDefs;
use ide_db::text_edit::TextEditBuilder;
use span::EditionedFileId;
use syntax::ast::{self, AstNode};
use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind};
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
_file_id: EditionedFileId,
pat: &ast::Pat,
) -> Option<()> {
if !config.binding_mode_hints {
return None;
}
let outer_paren_pat = pat.syntax().ancestors().skip(1).map_while(ast::ParenPat::cast).last();
let range = outer_paren_pat.as_ref().map_or_else(
|| match pat {
ast::Pat::IdentPat(it) => {
it.pat().map_or_else(|| it.syntax().text_range(), |it| it.syntax().text_range())
}
it => it.syntax().text_range(),
},
|it| it.syntax().text_range(),
);
let mut hint = InlayHint {
range,
kind: InlayKind::BindingMode,
label: InlayHintLabel::default(),
text_edit: None,
position: InlayHintPosition::Before,
pad_left: false,
pad_right: false,
resolve_parent: Some(pat.syntax().text_range()),
};
let pattern_adjustments = sema.pattern_adjustments(pat);
let mut was_mut_last = false;
pattern_adjustments.iter().for_each(|ty| {
let reference = ty.is_reference();
let mut_reference = ty.is_mutable_reference();
let r = match (reference, mut_reference) {
(true, true) => "&mut",
(true, false) => "&",
_ => return,
};
if mem::replace(&mut was_mut_last, mut_reference) {
hint.label.append_str(" ");
}
hint.label.append_str(r);
});
hint.pad_right = was_mut_last;
let acc_base = acc.len();
match pat {
ast::Pat::IdentPat(pat) if pat.ref_token().is_none() && pat.mut_token().is_none() => {
let bm = sema.binding_mode_of_pat(pat)?;
let bm = match bm {
hir::BindingMode::Move => None,
hir::BindingMode::Ref(Mutability::Mut) => Some("ref mut"),
hir::BindingMode::Ref(Mutability::Shared) => Some("ref"),
};
if let Some(bm) = bm {
acc.push(InlayHint {
range: pat.syntax().text_range(),
kind: InlayKind::BindingMode,
label: bm.into(),
text_edit: None,
position: InlayHintPosition::Before,
pad_left: false,
pad_right: true,
resolve_parent: Some(pat.syntax().text_range()),
});
}
}
ast::Pat::OrPat(pat) if !pattern_adjustments.is_empty() && outer_paren_pat.is_none() => {
hint.label.append_str("(");
acc.push(InlayHint::closing_paren_after(
InlayKind::BindingMode,
pat.syntax().text_range(),
));
}
_ => (),
}
if !hint.label.parts.is_empty() {
acc.push(hint);
}
if let hints @ [_, ..] = &mut acc[acc_base..] {
let mut edit = TextEditBuilder::default();
for h in &mut *hints {
edit.insert(
match h.position {
InlayHintPosition::Before => h.range.start(),
InlayHintPosition::After => h.range.end(),
},
h.label.parts.iter().map(|p| &*p.text).collect(),
);
}
let edit = edit.finish();
hints.iter_mut().for_each(|h| h.text_edit = Some(edit.clone()));
}
Some(())
}
#[cfg(test)]
mod tests {
use crate::{
inlay_hints::tests::{check_with_config, DISABLED_CONFIG},
InlayHintsConfig,
};
#[test]
fn hints_binding_modes() {
check_with_config(
InlayHintsConfig { binding_mode_hints: true, ..DISABLED_CONFIG },
r#"
fn __(
(x,): (u32,),
(x,): &(u32,),
//^^^^&
//^ ref
(x,): &mut (u32,)
//^^^^&mut
//^ ref mut
(x,): &mut &mut (u32,)
//^^^^&mut &mut
//^ ref mut
(x,): &&(u32,)
//^^^^&&
//^ ref
) {
let (x,) = (0,);
let (x,) = &(0,);
//^^^^ &
//^ ref
let (x,) = &mut (0,);
//^^^^ &mut
//^ ref mut
let &mut (x,) = &mut (0,);
let (ref mut x,) = &mut (0,);
//^^^^^^^^^^^^ &mut
let &mut (ref mut x,) = &mut (0,);
let (mut x,) = &mut (0,);
//^^^^^^^^ &mut
match (0,) {
(x,) => ()
}
match &(0,) {
(x,) | (x,) => (),
//^^^^^^^^^^^)
//^^^^^^^^^^^&(
//^ ref
//^ ref
((x,) | (x,)) => (),
//^^^^^^^^^^^^^&
//^ ref
//^ ref
}
match &mut (0,) {
(x,) => ()
//^^^^ &mut
//^ ref mut
}
}"#,
);
}
#[test]
fn hints_binding_modes_complex_ident_pat() {
check_with_config(
InlayHintsConfig { binding_mode_hints: true, ..DISABLED_CONFIG },
r#"
struct Struct {
field: &'static str,
}
fn foo(s @ Struct { field, .. }: &Struct) {}
//^^^^^^^^^^^^^^^^^^^^&
//^^^^^ref
"#,
);
}
}