ide_completion/render/
macro_.rs

1//! Renderer for macro invocations.
2
3use hir::{HirDisplay, db::HirDatabase};
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 is_fn_like = macro_.is_fn_like(completion.db);
53    let (bra, ket) = if is_fn_like {
54        guess_macro_braces(ctx.db(), macro_, name, docs.as_ref())
55    } else {
56        ("", "")
57    };
58
59    let needs_bang = is_fn_like && !is_use_path && !has_macro_bang;
60
61    let mut item = CompletionItem::new(
62        SymbolKind::from(macro_.kind(completion.db)),
63        source_range,
64        label(&ctx, needs_bang, bra, ket, &name.to_smolstr()),
65        completion.edition,
66    );
67    item.set_deprecated(ctx.is_deprecated(macro_))
68        .detail(macro_.display(completion.db, completion.display_target).to_string())
69        .set_documentation(docs)
70        .set_relevance(ctx.completion_relevance());
71
72    match ctx.snippet_cap() {
73        Some(cap) if needs_bang && !has_call_parens => {
74            let snippet = format!("{escaped_name}!{bra}$0{ket}");
75            let lookup = banged_name(name);
76            item.insert_snippet(cap, snippet).lookup_by(lookup);
77        }
78        _ if needs_bang => {
79            item.insert_text(banged_name(&escaped_name)).lookup_by(banged_name(name));
80        }
81        _ => {
82            cov_mark::hit!(dont_insert_macro_call_parens_unnecessary);
83            item.insert_text(escaped_name);
84        }
85    };
86    if let Some(import_to_add) = ctx.import_to_add {
87        item.add_import(import_to_add);
88    }
89
90    item
91}
92
93fn label(
94    ctx: &RenderContext<'_>,
95    needs_bang: bool,
96    bra: &str,
97    ket: &str,
98    name: &SmolStr,
99) -> SmolStr {
100    if needs_bang {
101        if ctx.snippet_cap().is_some() {
102            format_smolstr!("{name}!{bra}…{ket}",)
103        } else {
104            banged_name(name)
105        }
106    } else {
107        name.clone()
108    }
109}
110
111fn banged_name(name: &str) -> SmolStr {
112    SmolStr::from_iter([name, "!"])
113}
114
115fn guess_macro_braces(
116    db: &dyn HirDatabase,
117    macro_: hir::Macro,
118    macro_name: &str,
119    docs: Option<&Documentation<'_>>,
120) -> (&'static str, &'static str) {
121    if let Some(style) = macro_.preferred_brace_style(db) {
122        return match style {
123            hir::MacroBraces::Braces => (" {", "}"),
124            hir::MacroBraces::Brackets => ("[", "]"),
125            hir::MacroBraces::Parentheses => ("(", ")"),
126        };
127    }
128
129    let orig_name = macro_.name(db);
130    let docs = docs.map(Documentation::as_str).unwrap_or_default();
131
132    let mut votes = [0, 0, 0];
133    for (idx, s) in docs.match_indices(macro_name).chain(docs.match_indices(orig_name.as_str())) {
134        let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
135        // Ensure to match the full word
136        if after.starts_with('!')
137            && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
138        {
139            // It may have spaces before the braces like `foo! {}`
140            match after[1..].chars().find(|&c| !c.is_whitespace()) {
141                Some('{') => votes[0] += 1,
142                Some('[') => votes[1] += 1,
143                Some('(') => votes[2] += 1,
144                _ => {}
145            }
146        }
147    }
148
149    // Insert a space before `{}`.
150    // We prefer the last one when some votes equal.
151    let (_vote, (bra, ket)) = votes
152        .iter()
153        .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
154        .max_by_key(|&(&vote, _)| vote)
155        .unwrap();
156    (*bra, *ket)
157}
158
159#[cfg(test)]
160mod tests {
161    use crate::tests::check_edit;
162
163    #[test]
164    fn dont_insert_macro_call_parens_unnecessary() {
165        cov_mark::check!(dont_insert_macro_call_parens_unnecessary);
166        check_edit(
167            "frobnicate",
168            r#"
169//- /main.rs crate:main deps:foo
170use foo::$0;
171//- /foo/lib.rs crate:foo
172#[macro_export]
173macro_rules! frobnicate { () => () }
174"#,
175            r#"
176use foo::frobnicate;
177"#,
178        );
179
180        check_edit(
181            "frobnicate",
182            r#"
183macro_rules! frobnicate { () => () }
184fn main() { frob$0!(); }
185"#,
186            r#"
187macro_rules! frobnicate { () => () }
188fn main() { frobnicate!(); }
189"#,
190        );
191    }
192
193    #[test]
194    fn add_bang_to_parens() {
195        check_edit(
196            "frobnicate!",
197            r#"
198macro_rules! frobnicate { () => () }
199fn main() {
200    frob$0()
201}
202"#,
203            r#"
204macro_rules! frobnicate { () => () }
205fn main() {
206    frobnicate!()
207}
208"#,
209        );
210    }
211
212    #[test]
213    fn preferred_macro_braces() {
214        check_edit(
215            "vec!",
216            r#"
217#[rust_analyzer::macro_style(brackets)]
218macro_rules! vec { () => {} }
219
220fn main() { v$0 }
221"#,
222            r#"
223#[rust_analyzer::macro_style(brackets)]
224macro_rules! vec { () => {} }
225
226fn main() { vec![$0] }
227"#,
228        );
229
230        check_edit(
231            "foo!",
232            r#"
233#[rust_analyzer::macro_style(braces)]
234macro_rules! foo { () => {} }
235fn main() { $0 }
236"#,
237            r#"
238#[rust_analyzer::macro_style(braces)]
239macro_rules! foo { () => {} }
240fn main() { foo! {$0} }
241"#,
242        );
243
244        check_edit(
245            "bar!",
246            r#"
247#[macro_export]
248#[rust_analyzer::macro_style(brackets)]
249macro_rules! foo { () => {} }
250pub use crate::foo as bar;
251fn main() { $0 }
252"#,
253            r#"
254#[macro_export]
255#[rust_analyzer::macro_style(brackets)]
256macro_rules! foo { () => {} }
257pub use crate::foo as bar;
258fn main() { bar![$0] }
259"#,
260        );
261    }
262
263    #[test]
264    fn guesses_macro_braces() {
265        check_edit(
266            "vec!",
267            r#"
268/// Creates a [`Vec`] containing the arguments.
269///
270/// ```
271/// let v = vec![1, 2, 3];
272/// assert_eq!(v[0], 1);
273/// assert_eq!(v[1], 2);
274/// assert_eq!(v[2], 3);
275/// ```
276macro_rules! vec { () => {} }
277
278fn main() { v$0 }
279"#,
280            r#"
281/// Creates a [`Vec`] containing the arguments.
282///
283/// ```
284/// let v = vec![1, 2, 3];
285/// assert_eq!(v[0], 1);
286/// assert_eq!(v[1], 2);
287/// assert_eq!(v[2], 3);
288/// ```
289macro_rules! vec { () => {} }
290
291fn main() { vec![$0] }
292"#,
293        );
294
295        check_edit(
296            "foo!",
297            r#"
298/// Foo
299///
300/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
301/// call as `let _=foo!  { hello world };`
302macro_rules! foo { () => {} }
303fn main() { $0 }
304"#,
305            r#"
306/// Foo
307///
308/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
309/// call as `let _=foo!  { hello world };`
310macro_rules! foo { () => {} }
311fn main() { foo! {$0} }
312"#,
313        );
314
315        check_edit(
316            "bar!",
317            r#"
318/// `foo![]`
319#[macro_export]
320macro_rules! foo { () => {} }
321pub use crate::foo as bar;
322fn main() { $0 }
323"#,
324            r#"
325/// `foo![]`
326#[macro_export]
327macro_rules! foo { () => {} }
328pub use crate::foo as bar;
329fn main() { bar![$0] }
330"#,
331        );
332    }
333
334    #[test]
335    fn completes_macro_call_if_cursor_at_bang_token() {
336        // Regression test for https://github.com/rust-lang/rust-analyzer/issues/9904
337        cov_mark::check!(completes_macro_call_if_cursor_at_bang_token);
338        check_edit(
339            "foo!",
340            r#"
341macro_rules! foo {
342    () => {}
343}
344
345fn main() {
346    foo!$0
347}
348"#,
349            r#"
350macro_rules! foo {
351    () => {}
352}
353
354fn main() {
355    foo!($0)
356}
357"#,
358        );
359    }
360
361    #[test]
362    fn complete_missing_macro_arg() {
363        // Regression test for https://github.com/rust-lang/rust-analyzer/issues/14246
364        check_edit(
365            "BAR",
366            r#"
367macro_rules! foo {
368    ($val:ident,  $val2: ident) => {
369        $val $val2
370    };
371}
372
373const BAR: u32 = 9;
374fn main() {
375    foo!(BAR, $0)
376}
377"#,
378            r#"
379macro_rules! foo {
380    ($val:ident,  $val2: ident) => {
381        $val $val2
382    };
383}
384
385const BAR: u32 = 9;
386fn main() {
387    foo!(BAR, BAR)
388}
389"#,
390        );
391        check_edit(
392            "BAR",
393            r#"
394macro_rules! foo {
395    ($val:ident,  $val2: ident) => {
396        $val $val2
397    };
398}
399
400const BAR: u32 = 9;
401fn main() {
402    foo!($0)
403}
404"#,
405            r#"
406macro_rules! foo {
407    ($val:ident,  $val2: ident) => {
408        $val $val2
409    };
410}
411
412const BAR: u32 = 9;
413fn main() {
414    foo!(BAR)
415}
416"#,
417        );
418    }
419}