]> git.proxmox.com Git - rustc.git/blame - src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs
New upstream version 1.74.1+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / ide-completion / src / render / macro_.rs
CommitLineData
064997fb
FG
1//! Renderer for macro invocations.
2
781aab86
FG
3use hir::HirDisplay;
4use ide_db::{documentation::Documentation, SymbolKind};
064997fb
FG
5use syntax::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 = profile::span("render_macro");
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 = profile::span("render_macro");
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
f2b60f7d 49 let (name, escaped_name) = (name.unescaped().to_smol_str(), name.to_smol_str());
064997fb
FG
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 => {
9c376795 69 let snippet = format!("{escaped_name}!{bra}$0{ket}");
064997fb
FG
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 _ => {
fe692bf9 77 cov_mark::hit!(dont_insert_macro_call_parens_unnecessary);
064997fb
FG
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
88fn 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
106fn banged_name(name: &str) -> SmolStr {
107 SmolStr::from_iter([name, "!"])
108}
109
110fn 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)]
139mod tests {
140 use crate::tests::check_edit;
141
142 #[test]
fe692bf9
FG
143 fn dont_insert_macro_call_parens_unnecessary() {
144 cov_mark::check!(dont_insert_macro_call_parens_unnecessary);
064997fb
FG
145 check_edit(
146 "frobnicate",
147 r#"
148//- /main.rs crate:main deps:foo
149use foo::$0;
150//- /foo/lib.rs crate:foo
151#[macro_export]
152macro_rules! frobnicate { () => () }
153"#,
154 r#"
155use foo::frobnicate;
156"#,
157 );
158
159 check_edit(
160 "frobnicate",
161 r#"
162macro_rules! frobnicate { () => () }
163fn main() { frob$0!(); }
164"#,
165 r#"
166macro_rules! frobnicate { () => () }
167fn main() { frobnicate!(); }
168"#,
169 );
170 }
171
172 #[test]
173 fn add_bang_to_parens() {
174 check_edit(
175 "frobnicate!",
176 r#"
177macro_rules! frobnicate { () => () }
178fn main() {
179 frob$0()
180}
181"#,
182 r#"
183macro_rules! frobnicate { () => () }
184fn main() {
185 frobnicate!()
186}
187"#,
188 );
189 }
190
191 #[test]
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/// ```
204macro_rules! vec { () => {} }
205
206fn 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/// ```
217macro_rules! vec { () => {} }
218
219fn 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 };`
230macro_rules! foo { () => {} }
231fn main() { $0 }
232"#,
233 r#"
234/// Foo
235///
236/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
237/// call as `let _=foo! { hello world };`
238macro_rules! foo { () => {} }
239fn main() { foo! {$0} }
240"#,
241 )
242 }
243
244 #[test]
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#"
251macro_rules! foo {
252 () => {}
253}
254
255fn main() {
256 foo!$0
257}
258"#,
259 r#"
260macro_rules! foo {
261 () => {}
262}
263
264fn main() {
265 foo!($0)
266}
353b0b11
FG
267"#,
268 );
269 }
270
271 #[test]
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#"
277macro_rules! foo {
278 ($val:ident, $val2: ident) => {
279 $val $val2
280 };
281}
282
283const BAR: u32 = 9;
284fn main() {
285 foo!(BAR, $0)
286}
287"#,
288 r#"
289macro_rules! foo {
290 ($val:ident, $val2: ident) => {
291 $val $val2
292 };
293}
294
295const BAR: u32 = 9;
296fn main() {
297 foo!(BAR, BAR)
298}
299"#,
300 );
301 check_edit(
302 "BAR",
303 r#"
304macro_rules! foo {
305 ($val:ident, $val2: ident) => {
306 $val $val2
307 };
308}
309
310const BAR: u32 = 9;
311fn main() {
312 foo!($0)
313}
314"#,
315 r#"
316macro_rules! foo {
317 ($val:ident, $val2: ident) => {
318 $val $val2
319 };
320}
321
322const BAR: u32 = 9;
323fn main() {
324 foo!(BAR)
325}
064997fb
FG
326"#,
327 );
328 }
329}