blob: 35504ffa7859fe9b5bacf3f2bdf741f7da41543b [file] [log] [blame]
//! Implementation of "binding mode" inlay hints:
//! ```no_run
//! let /* & */ (/* ref */ x,) = &(0,);
//! ```
use hir::{Mutability, Semantics};
use ide_db::RootDatabase;
use syntax::ast::{self, AstNode};
use crate::{InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind};
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
sema: &Semantics<'_, RootDatabase>,
config: &InlayHintsConfig,
pat: &ast::Pat,
) -> Option<()> {
if !config.binding_mode_hints {
return None;
}
let outer_paren_pat = pat
.syntax()
.ancestors()
.skip(1)
.map_while(ast::Pat::cast)
.map_while(|pat| match pat {
ast::Pat::ParenPat(pat) => Some(pat),
_ => None,
})
.last();
let range = outer_paren_pat.as_ref().map_or_else(
|| match pat {
// for ident patterns that @ bind a name, render the un-ref patterns in front of the inner pattern
// instead of the name as that makes it more clear and doesn't really change the outcome
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 pattern_adjustments = sema.pattern_adjustments(pat);
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,
};
acc.push(InlayHint {
needs_resolve: false,
range,
kind: InlayKind::BindingMode,
label: r.into(),
text_edit: None,
position: InlayHintPosition::Before,
pad_left: false,
pad_right: mut_reference,
});
});
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 => return None,
hir::BindingMode::Ref(Mutability::Mut) => "ref mut",
hir::BindingMode::Ref(Mutability::Shared) => "ref",
};
acc.push(InlayHint {
needs_resolve: false,
range: pat.syntax().text_range(),
kind: InlayKind::BindingMode,
label: bm.into(),
text_edit: None,
position: InlayHintPosition::Before,
pad_left: false,
pad_right: true,
});
}
ast::Pat::OrPat(pat) if !pattern_adjustments.is_empty() && outer_paren_pat.is_none() => {
acc.push(InlayHint::opening_paren_before(
InlayKind::BindingMode,
pat.syntax().text_range(),
));
acc.push(InlayHint::closing_paren_after(
InlayKind::BindingMode,
pat.syntax().text_range(),
));
}
_ => (),
}
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
) {
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
"#,
);
}
}