ide_completion/render/
macro_.rs

1//! Renderer for macro invocations.
2
3use hir::HirDisplay;
4use ide_db::{SymbolKind, documentation::Documentation};
5use syntax::{SmolStr, ToSmolStr, format_smolstr};
6
7use crate::{
8    context::{PathCompletionCtx, PathKind, PatternContext},
9    item::{Builder, CompletionItem},
10    render::RenderContext,
11};
12
13pub(crate) fn render_macro(
14    ctx: RenderContext<'_>,
15    PathCompletionCtx { kind, has_macro_bang, has_call_parens, .. }: &PathCompletionCtx<'_>,
16
17    name: hir::Name,
18    macro_: hir::Macro,
19) -> Builder {
20    let _p = tracing::info_span!("render_macro").entered();
21    render(ctx, *kind == PathKind::Use, *has_macro_bang, *has_call_parens, name, macro_)
22}
23
24pub(crate) fn render_macro_pat(
25    ctx: RenderContext<'_>,
26    _pattern_ctx: &PatternContext,
27    name: hir::Name,
28    macro_: hir::Macro,
29) -> Builder {
30    let _p = tracing::info_span!("render_macro_pat").entered();
31    render(ctx, false, false, false, name, macro_)
32}
33
34fn render(
35    ctx @ RenderContext { completion, .. }: RenderContext<'_>,
36    is_use_path: bool,
37    has_macro_bang: bool,
38    has_call_parens: bool,
39    name: hir::Name,
40    macro_: hir::Macro,
41) -> Builder {
42    let source_range = if ctx.is_immediately_after_macro_bang() {
43        cov_mark::hit!(completes_macro_call_if_cursor_at_bang_token);
44        completion.token.parent().map_or_else(|| ctx.source_range(), |it| it.text_range())
45    } else {
46        ctx.source_range()
47    };
48
49    let (name, escaped_name) =
50        (name.as_str(), name.display(ctx.db(), completion.edition).to_smolstr());
51    let docs = ctx.docs(macro_);
52    let docs_str = docs.as_ref().map(Documentation::as_str).unwrap_or_default();
53    let is_fn_like = macro_.is_fn_like(completion.db);
54    let (bra, ket) = if is_fn_like { guess_macro_braces(name, docs_str) } else { ("", "") };
55
56    let needs_bang = is_fn_like && !is_use_path && !has_macro_bang;
57
58    let mut item = CompletionItem::new(
59        SymbolKind::from(macro_.kind(completion.db)),
60        source_range,
61        label(&ctx, needs_bang, bra, ket, &name.to_smolstr()),
62        completion.edition,
63    );
64    item.set_deprecated(ctx.is_deprecated(macro_))
65        .detail(macro_.display(completion.db, completion.display_target).to_string())
66        .set_documentation(docs)
67        .set_relevance(ctx.completion_relevance());
68
69    match ctx.snippet_cap() {
70        Some(cap) if needs_bang && !has_call_parens => {
71            let snippet = format!("{escaped_name}!{bra}$0{ket}");
72            let lookup = banged_name(name);
73            item.insert_snippet(cap, snippet).lookup_by(lookup);
74        }
75        _ if needs_bang => {
76            item.insert_text(banged_name(&escaped_name)).lookup_by(banged_name(name));
77        }
78        _ => {
79            cov_mark::hit!(dont_insert_macro_call_parens_unnecessary);
80            item.insert_text(escaped_name);
81        }
82    };
83    if let Some(import_to_add) = ctx.import_to_add {
84        item.add_import(import_to_add);
85    }
86
87    item
88}
89
90fn label(
91    ctx: &RenderContext<'_>,
92    needs_bang: bool,
93    bra: &str,
94    ket: &str,
95    name: &SmolStr,
96) -> SmolStr {
97    if needs_bang {
98        if ctx.snippet_cap().is_some() {
99            format_smolstr!("{name}!{bra}…{ket}",)
100        } else {
101            banged_name(name)
102        }
103    } else {
104        name.clone()
105    }
106}
107
108fn banged_name(name: &str) -> SmolStr {
109    SmolStr::from_iter([name, "!"])
110}
111
112fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) {
113    let mut votes = [0, 0, 0];
114    for (idx, s) in docs.match_indices(&macro_name) {
115        let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
116        // Ensure to match the full word
117        if after.starts_with('!')
118            && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
119        {
120            // It may have spaces before the braces like `foo! {}`
121            match after[1..].chars().find(|&c| !c.is_whitespace()) {
122                Some('{') => votes[0] += 1,
123                Some('[') => votes[1] += 1,
124                Some('(') => votes[2] += 1,
125                _ => {}
126            }
127        }
128    }
129
130    // Insert a space before `{}`.
131    // We prefer the last one when some votes equal.
132    let (_vote, (bra, ket)) = votes
133        .iter()
134        .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
135        .max_by_key(|&(&vote, _)| vote)
136        .unwrap();
137    (*bra, *ket)
138}
139
140#[cfg(test)]
141mod tests {
142    use crate::tests::check_edit;
143
144    #[test]
145    fn dont_insert_macro_call_parens_unnecessary() {
146        cov_mark::check!(dont_insert_macro_call_parens_unnecessary);
147        check_edit(
148            "frobnicate",
149            r#"
150//- /main.rs crate:main deps:foo
151use foo::$0;
152//- /foo/lib.rs crate:foo
153#[macro_export]
154macro_rules! frobnicate { () => () }
155"#,
156            r#"
157use foo::frobnicate;
158"#,
159        );
160
161        check_edit(
162            "frobnicate",
163            r#"
164macro_rules! frobnicate { () => () }
165fn main() { frob$0!(); }
166"#,
167            r#"
168macro_rules! frobnicate { () => () }
169fn main() { frobnicate!(); }
170"#,
171        );
172    }
173
174    #[test]
175    fn add_bang_to_parens() {
176        check_edit(
177            "frobnicate!",
178            r#"
179macro_rules! frobnicate { () => () }
180fn main() {
181    frob$0()
182}
183"#,
184            r#"
185macro_rules! frobnicate { () => () }
186fn main() {
187    frobnicate!()
188}
189"#,
190        );
191    }
192
193    #[test]
194    fn guesses_macro_braces() {
195        check_edit(
196            "vec!",
197            r#"
198/// Creates a [`Vec`] containing the arguments.
199///
200/// ```
201/// let v = vec![1, 2, 3];
202/// assert_eq!(v[0], 1);
203/// assert_eq!(v[1], 2);
204/// assert_eq!(v[2], 3);
205/// ```
206macro_rules! vec { () => {} }
207
208fn main() { v$0 }
209"#,
210            r#"
211/// Creates a [`Vec`] containing the arguments.
212///
213/// ```
214/// let v = vec![1, 2, 3];
215/// assert_eq!(v[0], 1);
216/// assert_eq!(v[1], 2);
217/// assert_eq!(v[2], 3);
218/// ```
219macro_rules! vec { () => {} }
220
221fn main() { vec![$0] }
222"#,
223        );
224
225        check_edit(
226            "foo!",
227            r#"
228/// Foo
229///
230/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
231/// call as `let _=foo!  { hello world };`
232macro_rules! foo { () => {} }
233fn main() { $0 }
234"#,
235            r#"
236/// Foo
237///
238/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
239/// call as `let _=foo!  { hello world };`
240macro_rules! foo { () => {} }
241fn main() { foo! {$0} }
242"#,
243        )
244    }
245
246    #[test]
247    fn completes_macro_call_if_cursor_at_bang_token() {
248        // Regression test for https://github.com/rust-lang/rust-analyzer/issues/9904
249        cov_mark::check!(completes_macro_call_if_cursor_at_bang_token);
250        check_edit(
251            "foo!",
252            r#"
253macro_rules! foo {
254    () => {}
255}
256
257fn main() {
258    foo!$0
259}
260"#,
261            r#"
262macro_rules! foo {
263    () => {}
264}
265
266fn main() {
267    foo!($0)
268}
269"#,
270        );
271    }
272
273    #[test]
274    fn complete_missing_macro_arg() {
275        // Regression test for https://github.com/rust-lang/rust-analyzer/issues/14246
276        check_edit(
277            "BAR",
278            r#"
279macro_rules! foo {
280    ($val:ident,  $val2: ident) => {
281        $val $val2
282    };
283}
284
285const BAR: u32 = 9;
286fn main() {
287    foo!(BAR, $0)
288}
289"#,
290            r#"
291macro_rules! foo {
292    ($val:ident,  $val2: ident) => {
293        $val $val2
294    };
295}
296
297const BAR: u32 = 9;
298fn main() {
299    foo!(BAR, BAR)
300}
301"#,
302        );
303        check_edit(
304            "BAR",
305            r#"
306macro_rules! foo {
307    ($val:ident,  $val2: ident) => {
308        $val $val2
309    };
310}
311
312const BAR: u32 = 9;
313fn main() {
314    foo!($0)
315}
316"#,
317            r#"
318macro_rules! foo {
319    ($val:ident,  $val2: ident) => {
320        $val $val2
321    };
322}
323
324const BAR: u32 = 9;
325fn main() {
326    foo!(BAR)
327}
328"#,
329        );
330    }
331}