ide/inlay_hints/
binding_mode.rs

1//! Implementation of "binding mode" inlay hints:
2//! ```no_run
3//! let /* & */ (/* ref */ x,) = &(0,);
4//! ```
5use std::mem;
6
7use hir::Mutability;
8use ide_db::famous_defs::FamousDefs;
9
10use ide_db::text_edit::TextEditBuilder;
11use syntax::ast::{self, AstNode};
12
13use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind};
14
15pub(super) fn hints(
16    acc: &mut Vec<InlayHint>,
17    FamousDefs(sema, _): &FamousDefs<'_, '_>,
18    config: &InlayHintsConfig<'_>,
19    pat: &ast::Pat,
20) -> Option<()> {
21    if !config.binding_mode_hints {
22        return None;
23    }
24
25    let outer_paren_pat = pat.syntax().ancestors().skip(1).map_while(ast::ParenPat::cast).last();
26    let range = outer_paren_pat.as_ref().map_or_else(
27        || match pat {
28            // for ident patterns that @ bind a name, render the un-ref patterns in front of the inner pattern
29            // instead of the name as that makes it more clear and doesn't really change the outcome
30            ast::Pat::IdentPat(it) => {
31                it.pat().map_or_else(|| it.syntax().text_range(), |it| it.syntax().text_range())
32            }
33            it => it.syntax().text_range(),
34        },
35        |it| it.syntax().text_range(),
36    );
37    let mut hint = InlayHint {
38        range,
39        kind: InlayKind::BindingMode,
40        label: InlayHintLabel::default(),
41        text_edit: None,
42        position: InlayHintPosition::Before,
43        pad_left: false,
44        pad_right: false,
45        resolve_parent: Some(pat.syntax().text_range()),
46    };
47    let pattern_adjustments = sema.pattern_adjustments(pat);
48    let mut was_mut_last = false;
49    pattern_adjustments.iter().for_each(|ty| {
50        let reference = ty.is_reference();
51        let mut_reference = ty.is_mutable_reference();
52        let r = match (reference, mut_reference) {
53            (true, true) => "&mut",
54            (true, false) => "&",
55            _ => return,
56        };
57        if mem::replace(&mut was_mut_last, mut_reference) {
58            hint.label.append_str(" ");
59        }
60        hint.label.append_str(r);
61    });
62    let acc_base = acc.len();
63    match pat {
64        ast::Pat::IdentPat(pat) if pat.ref_token().is_none() && pat.mut_token().is_none() => {
65            let bm = sema.binding_mode_of_pat(pat)?;
66            let bm = match bm {
67                hir::BindingMode::Move => None,
68                hir::BindingMode::Ref(Mutability::Mut) => Some("ref mut"),
69                hir::BindingMode::Ref(Mutability::Shared) => Some("ref"),
70            };
71            if let Some(bm) = bm {
72                acc.push(InlayHint {
73                    range: pat.syntax().text_range(),
74                    kind: InlayKind::BindingMode,
75                    label: bm.into(),
76                    text_edit: None,
77                    position: InlayHintPosition::Before,
78                    pad_left: false,
79                    pad_right: true,
80                    resolve_parent: Some(pat.syntax().text_range()),
81                });
82            }
83        }
84        ast::Pat::OrPat(pat) if !pattern_adjustments.is_empty() && outer_paren_pat.is_none() => {
85            hint.label.append_str("(");
86            was_mut_last = false;
87            acc.push(InlayHint::closing_paren_after(
88                InlayKind::BindingMode,
89                pat.syntax().text_range(),
90            ));
91        }
92        _ => (),
93    }
94    if !hint.label.parts.is_empty() {
95        hint.pad_right = was_mut_last;
96        acc.push(hint);
97    }
98
99    if let hints @ [_, ..] = &mut acc[acc_base..] {
100        let edit = config.lazy_text_edit(|| {
101            let mut edit = TextEditBuilder::default();
102            for h in &mut *hints {
103                edit.insert(
104                    match h.position {
105                        InlayHintPosition::Before => h.range.start(),
106                        InlayHintPosition::After => h.range.end(),
107                    },
108                    h.label
109                        .parts
110                        .iter()
111                        .map(|p| &*p.text)
112                        .chain(h.pad_right.then_some(" "))
113                        .collect(),
114                );
115            }
116            edit.finish()
117        });
118        hints.iter_mut().for_each(|h| h.text_edit = Some(edit.clone()));
119    }
120
121    Some(())
122}
123
124#[cfg(test)]
125mod tests {
126    use expect_test::expect;
127
128    use crate::{
129        InlayHintsConfig,
130        inlay_hints::tests::{DISABLED_CONFIG, check_edit, check_with_config},
131    };
132
133    #[test]
134    fn hints_binding_modes() {
135        check_with_config(
136            InlayHintsConfig { binding_mode_hints: true, ..DISABLED_CONFIG },
137            r#"
138fn __(
139    (x,): (u32,),
140    (x,): &(u32,),
141  //^^^^&
142   //^ ref
143    (x,): &mut (u32,)
144  //^^^^&mut
145   //^ ref mut
146   (x,): &mut &mut (u32,)
147 //^^^^&mut &mut
148  //^ ref mut
149   (x,): &&(u32,)
150 //^^^^&&
151  //^ ref
152
153) {
154    let (x,) = (0,);
155    let (x,) = &(0,);
156      //^^^^ &
157       //^ ref
158    let (x,) = &mut (0,);
159      //^^^^ &mut
160       //^ ref mut
161    let &mut (x,) = &mut (0,);
162    let (ref mut x,) = &mut (0,);
163      //^^^^^^^^^^^^ &mut
164    let &mut (ref mut x,) = &mut (0,);
165    let (mut x,) = &mut (0,);
166      //^^^^^^^^ &mut
167    match (0,) {
168        (x,) => ()
169    }
170    match &(0,) {
171        (x,) | (x,) => (),
172      //^^^^^^^^^^^)
173      //^^^^^^^^^^^&(
174       //^ ref
175              //^ ref
176        ((x,) | (x,)) => (),
177      //^^^^^^^^^^^^^&
178        //^ ref
179               //^ ref
180    }
181    match &mut (0,) {
182        (x,) => ()
183      //^^^^ &mut
184       //^ ref mut
185    }
186}"#,
187        );
188    }
189
190    #[test]
191    fn hints_binding_modes_complex_ident_pat() {
192        check_with_config(
193            InlayHintsConfig { binding_mode_hints: true, ..DISABLED_CONFIG },
194            r#"
195struct Struct {
196    field: &'static str,
197}
198fn foo(s @ Struct { field, .. }: &Struct) {}
199         //^^^^^^^^^^^^^^^^^^^^&
200                  //^^^^^ref
201"#,
202        );
203    }
204
205    #[test]
206    fn edits() {
207        check_edit(
208            InlayHintsConfig { binding_mode_hints: true, ..DISABLED_CONFIG },
209            r#"
210fn main() {
211    match &(0,) {
212        (x,) | (x,) => (),
213        ((x,) | (x,)) => (),
214    }
215}
216"#,
217            expect![[r#"
218                fn main() {
219                    match &(0,) {
220                        &(&((ref x,) | (ref x,))) => (),
221                        &((ref x,) | (ref x,)) => (),
222                    }
223                }
224            "#]],
225        );
226    }
227}