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