diff options
Diffstat (limited to 'crates/ra_analysis/src/completion.rs')
-rw-r--r-- | crates/ra_analysis/src/completion.rs | 372 |
1 files changed, 49 insertions, 323 deletions
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index f480af611..a11e98ac0 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs | |||
@@ -1,3 +1,4 @@ | |||
1 | mod completion_item; | ||
1 | mod reference_completion; | 2 | mod reference_completion; |
2 | 3 | ||
3 | use ra_editor::find_node_at_offset; | 4 | use ra_editor::find_node_at_offset; |
@@ -14,23 +15,16 @@ use hir::source_binder; | |||
14 | 15 | ||
15 | use crate::{ | 16 | use crate::{ |
16 | db, | 17 | db, |
17 | Cancelable, FilePosition | 18 | Cancelable, FilePosition, |
19 | completion::completion_item::{Completions, CompletionKind}, | ||
18 | }; | 20 | }; |
19 | 21 | ||
20 | #[derive(Debug)] | 22 | pub use crate::completion::completion_item::{CompletionItem, InsertText}; |
21 | pub struct CompletionItem { | ||
22 | /// What user sees in pop-up | ||
23 | pub label: String, | ||
24 | /// What string is used for filtering, defaults to label | ||
25 | pub lookup: Option<String>, | ||
26 | /// What is inserted, defaults to label | ||
27 | pub snippet: Option<String>, | ||
28 | } | ||
29 | 23 | ||
30 | pub(crate) fn completions( | 24 | pub(crate) fn completions( |
31 | db: &db::RootDatabase, | 25 | db: &db::RootDatabase, |
32 | position: FilePosition, | 26 | position: FilePosition, |
33 | ) -> Cancelable<Option<Vec<CompletionItem>>> { | 27 | ) -> Cancelable<Option<Completions>> { |
34 | let original_file = db.source_file(position.file_id); | 28 | let original_file = db.source_file(position.file_id); |
35 | // Insert a fake ident to get a valid parse tree | 29 | // Insert a fake ident to get a valid parse tree |
36 | let file = { | 30 | let file = { |
@@ -40,15 +34,15 @@ pub(crate) fn completions( | |||
40 | 34 | ||
41 | let module = ctry!(source_binder::module_from_position(db, position)?); | 35 | let module = ctry!(source_binder::module_from_position(db, position)?); |
42 | 36 | ||
43 | let mut res = Vec::new(); | 37 | let mut acc = Completions::default(); |
44 | let mut has_completions = false; | 38 | let mut has_completions = false; |
45 | // First, let's try to complete a reference to some declaration. | 39 | // First, let's try to complete a reference to some declaration. |
46 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) { | 40 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) { |
47 | has_completions = true; | 41 | has_completions = true; |
48 | reference_completion::completions(&mut res, db, &module, &file, name_ref)?; | 42 | reference_completion::completions(&mut acc, db, &module, &file, name_ref)?; |
49 | // special case, `trait T { fn foo(i_am_a_name_ref) {} }` | 43 | // special case, `trait T { fn foo(i_am_a_name_ref) {} }` |
50 | if is_node::<ast::Param>(name_ref.syntax()) { | 44 | if is_node::<ast::Param>(name_ref.syntax()) { |
51 | param_completions(name_ref.syntax(), &mut res); | 45 | param_completions(&mut acc, name_ref.syntax()); |
52 | } | 46 | } |
53 | } | 47 | } |
54 | 48 | ||
@@ -56,14 +50,20 @@ pub(crate) fn completions( | |||
56 | if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) { | 50 | if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) { |
57 | if is_node::<ast::Param>(name.syntax()) { | 51 | if is_node::<ast::Param>(name.syntax()) { |
58 | has_completions = true; | 52 | has_completions = true; |
59 | param_completions(name.syntax(), &mut res); | 53 | param_completions(&mut acc, name.syntax()); |
60 | } | 54 | } |
61 | } | 55 | } |
62 | let res = if has_completions { Some(res) } else { None }; | 56 | if !has_completions { |
63 | Ok(res) | 57 | return Ok(None); |
58 | } | ||
59 | Ok(Some(acc)) | ||
64 | } | 60 | } |
65 | 61 | ||
66 | fn param_completions(ctx: SyntaxNodeRef, acc: &mut Vec<CompletionItem>) { | 62 | /// Complete repeated parametes, both name and type. For example, if all |
63 | /// functions in a file have a `spam: &mut Spam` parameter, a completion with | ||
64 | /// `spam: &mut Spam` insert text/label and `spam` lookup string will be | ||
65 | /// suggested. | ||
66 | fn param_completions(acc: &mut Completions, ctx: SyntaxNodeRef) { | ||
67 | let mut params = FxHashMap::default(); | 67 | let mut params = FxHashMap::default(); |
68 | for node in ctx.ancestors() { | 68 | for node in ctx.ancestors() { |
69 | let _ = visitor_ctx(&mut params) | 69 | let _ = visitor_ctx(&mut params) |
@@ -82,11 +82,10 @@ fn param_completions(ctx: SyntaxNodeRef, acc: &mut Vec<CompletionItem>) { | |||
82 | } | 82 | } |
83 | }) | 83 | }) |
84 | .for_each(|(label, lookup)| { | 84 | .for_each(|(label, lookup)| { |
85 | acc.push(CompletionItem { | 85 | CompletionItem::new(label) |
86 | label, | 86 | .lookup_by(lookup) |
87 | lookup: Some(lookup), | 87 | .kind(CompletionKind::Magic) |
88 | snippet: None, | 88 | .add_to(acc) |
89 | }) | ||
90 | }); | 89 | }); |
91 | 90 | ||
92 | fn process<'a, N: ast::FnDefOwner<'a>>( | 91 | fn process<'a, N: ast::FnDefOwner<'a>>( |
@@ -111,334 +110,61 @@ fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool { | |||
111 | } | 110 | } |
112 | 111 | ||
113 | #[cfg(test)] | 112 | #[cfg(test)] |
114 | mod tests { | 113 | fn check_completion(code: &str, expected_completions: &str, kind: CompletionKind) { |
115 | use test_utils::assert_eq_dbg; | 114 | use crate::mock_analysis::{single_file_with_position, analysis_and_position}; |
116 | 115 | let (analysis, position) = if code.contains("//-") { | |
117 | use crate::mock_analysis::single_file_with_position; | 116 | analysis_and_position(code) |
117 | } else { | ||
118 | single_file_with_position(code) | ||
119 | }; | ||
120 | let completions = completions(&analysis.imp.db, position).unwrap().unwrap(); | ||
121 | completions.assert_match(expected_completions, kind); | ||
122 | } | ||
118 | 123 | ||
124 | #[cfg(test)] | ||
125 | mod tests { | ||
119 | use super::*; | 126 | use super::*; |
120 | 127 | ||
121 | fn check_scope_completion(code: &str, expected_completions: &str) { | 128 | fn check_magic_completion(code: &str, expected_completions: &str) { |
122 | let (analysis, position) = single_file_with_position(code); | 129 | check_completion(code, expected_completions, CompletionKind::Magic); |
123 | let completions = completions(&analysis.imp.db, position) | ||
124 | .unwrap() | ||
125 | .unwrap() | ||
126 | .into_iter() | ||
127 | .filter(|c| c.snippet.is_none()) | ||
128 | .collect::<Vec<_>>(); | ||
129 | assert_eq_dbg(expected_completions, &completions); | ||
130 | } | ||
131 | |||
132 | fn check_snippet_completion(code: &str, expected_completions: &str) { | ||
133 | let (analysis, position) = single_file_with_position(code); | ||
134 | let completions = completions(&analysis.imp.db, position) | ||
135 | .unwrap() | ||
136 | .unwrap() | ||
137 | .into_iter() | ||
138 | .filter(|c| c.snippet.is_some()) | ||
139 | .collect::<Vec<_>>(); | ||
140 | assert_eq_dbg(expected_completions, &completions); | ||
141 | } | ||
142 | |||
143 | #[test] | ||
144 | fn test_completion_let_scope() { | ||
145 | check_scope_completion( | ||
146 | r" | ||
147 | fn quux(x: i32) { | ||
148 | let y = 92; | ||
149 | 1 + <|>; | ||
150 | let z = (); | ||
151 | } | ||
152 | ", | ||
153 | r#"[CompletionItem { label: "y", lookup: None, snippet: None }, | ||
154 | CompletionItem { label: "x", lookup: None, snippet: None }, | ||
155 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, | ||
156 | ); | ||
157 | } | ||
158 | |||
159 | #[test] | ||
160 | fn test_completion_if_let_scope() { | ||
161 | check_scope_completion( | ||
162 | r" | ||
163 | fn quux() { | ||
164 | if let Some(x) = foo() { | ||
165 | let y = 92; | ||
166 | }; | ||
167 | if let Some(a) = bar() { | ||
168 | let b = 62; | ||
169 | 1 + <|> | ||
170 | } | ||
171 | } | ||
172 | ", | ||
173 | r#"[CompletionItem { label: "b", lookup: None, snippet: None }, | ||
174 | CompletionItem { label: "a", lookup: None, snippet: None }, | ||
175 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, | ||
176 | ); | ||
177 | } | ||
178 | |||
179 | #[test] | ||
180 | fn test_completion_for_scope() { | ||
181 | check_scope_completion( | ||
182 | r" | ||
183 | fn quux() { | ||
184 | for x in &[1, 2, 3] { | ||
185 | <|> | ||
186 | } | ||
187 | } | ||
188 | ", | ||
189 | r#"[CompletionItem { label: "x", lookup: None, snippet: None }, | ||
190 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, | ||
191 | ); | ||
192 | } | ||
193 | |||
194 | #[test] | ||
195 | fn test_completion_mod_scope() { | ||
196 | check_scope_completion( | ||
197 | r" | ||
198 | struct Foo; | ||
199 | enum Baz {} | ||
200 | fn quux() { | ||
201 | <|> | ||
202 | } | ||
203 | ", | ||
204 | r#"[CompletionItem { label: "quux", lookup: None, snippet: None }, | ||
205 | CompletionItem { label: "Foo", lookup: None, snippet: None }, | ||
206 | CompletionItem { label: "Baz", lookup: None, snippet: None }]"#, | ||
207 | ); | ||
208 | } | ||
209 | |||
210 | #[test] | ||
211 | fn test_completion_mod_scope_no_self_use() { | ||
212 | check_scope_completion( | ||
213 | r" | ||
214 | use foo<|>; | ||
215 | ", | ||
216 | r#"[]"#, | ||
217 | ); | ||
218 | } | ||
219 | |||
220 | #[test] | ||
221 | fn test_completion_self_path() { | ||
222 | check_scope_completion( | ||
223 | r" | ||
224 | use self::m::<|>; | ||
225 | |||
226 | mod m { | ||
227 | struct Bar; | ||
228 | } | ||
229 | ", | ||
230 | r#"[CompletionItem { label: "Bar", lookup: None, snippet: None }]"#, | ||
231 | ); | ||
232 | } | ||
233 | |||
234 | #[test] | ||
235 | fn test_completion_mod_scope_nested() { | ||
236 | check_scope_completion( | ||
237 | r" | ||
238 | struct Foo; | ||
239 | mod m { | ||
240 | struct Bar; | ||
241 | fn quux() { <|> } | ||
242 | } | ||
243 | ", | ||
244 | r#"[CompletionItem { label: "quux", lookup: None, snippet: None }, | ||
245 | CompletionItem { label: "Bar", lookup: None, snippet: None }]"#, | ||
246 | ); | ||
247 | } | ||
248 | |||
249 | #[test] | ||
250 | fn test_complete_type() { | ||
251 | check_scope_completion( | ||
252 | r" | ||
253 | struct Foo; | ||
254 | fn x() -> <|> | ||
255 | ", | ||
256 | r#"[CompletionItem { label: "Foo", lookup: None, snippet: None }, | ||
257 | CompletionItem { label: "x", lookup: None, snippet: None }]"#, | ||
258 | ) | ||
259 | } | ||
260 | |||
261 | #[test] | ||
262 | fn test_complete_shadowing() { | ||
263 | check_scope_completion( | ||
264 | r" | ||
265 | fn foo() -> { | ||
266 | let bar = 92; | ||
267 | { | ||
268 | let bar = 62; | ||
269 | <|> | ||
270 | } | ||
271 | } | ||
272 | ", | ||
273 | r#"[CompletionItem { label: "bar", lookup: None, snippet: None }, | ||
274 | CompletionItem { label: "foo", lookup: None, snippet: None }]"#, | ||
275 | ) | ||
276 | } | ||
277 | |||
278 | #[test] | ||
279 | fn test_complete_self() { | ||
280 | check_scope_completion( | ||
281 | r" | ||
282 | impl S { fn foo(&self) { <|> } } | ||
283 | ", | ||
284 | r#"[CompletionItem { label: "self", lookup: None, snippet: None }]"#, | ||
285 | ) | ||
286 | } | ||
287 | |||
288 | #[test] | ||
289 | fn test_completion_kewords() { | ||
290 | check_snippet_completion(r" | ||
291 | fn quux() { | ||
292 | <|> | ||
293 | } | ||
294 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
295 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
296 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
297 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
298 | CompletionItem { label: "return", lookup: None, snippet: Some("return") }, | ||
299 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
300 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
301 | } | ||
302 | |||
303 | #[test] | ||
304 | fn test_completion_else() { | ||
305 | check_snippet_completion(r" | ||
306 | fn quux() { | ||
307 | if true { | ||
308 | () | ||
309 | } <|> | ||
310 | } | ||
311 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
312 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
313 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
314 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
315 | CompletionItem { label: "else", lookup: None, snippet: Some("else {$0}") }, | ||
316 | CompletionItem { label: "else if", lookup: None, snippet: Some("else if $0 {}") }, | ||
317 | CompletionItem { label: "return", lookup: None, snippet: Some("return") }, | ||
318 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
319 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
320 | } | ||
321 | |||
322 | #[test] | ||
323 | fn test_completion_return_value() { | ||
324 | check_snippet_completion(r" | ||
325 | fn quux() -> i32 { | ||
326 | <|> | ||
327 | 92 | ||
328 | } | ||
329 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
330 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
331 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
332 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
333 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0;") }, | ||
334 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
335 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
336 | check_snippet_completion(r" | ||
337 | fn quux() { | ||
338 | <|> | ||
339 | 92 | ||
340 | } | ||
341 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
342 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
343 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
344 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
345 | CompletionItem { label: "return", lookup: None, snippet: Some("return;") }, | ||
346 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
347 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
348 | } | ||
349 | |||
350 | #[test] | ||
351 | fn test_completion_return_no_stmt() { | ||
352 | check_snippet_completion(r" | ||
353 | fn quux() -> i32 { | ||
354 | match () { | ||
355 | () => <|> | ||
356 | } | ||
357 | } | ||
358 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
359 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
360 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
361 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
362 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }, | ||
363 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
364 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
365 | } | ||
366 | |||
367 | #[test] | ||
368 | fn test_continue_break_completion() { | ||
369 | check_snippet_completion(r" | ||
370 | fn quux() -> i32 { | ||
371 | loop { <|> } | ||
372 | } | ||
373 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
374 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
375 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
376 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
377 | CompletionItem { label: "continue", lookup: None, snippet: Some("continue") }, | ||
378 | CompletionItem { label: "break", lookup: None, snippet: Some("break") }, | ||
379 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }, | ||
380 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
381 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
382 | check_snippet_completion(r" | ||
383 | fn quux() -> i32 { | ||
384 | loop { || { <|> } } | ||
385 | } | ||
386 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
387 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
388 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
389 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
390 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }, | ||
391 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
392 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
393 | } | 130 | } |
394 | 131 | ||
395 | #[test] | 132 | #[test] |
396 | fn test_param_completion_last_param() { | 133 | fn test_param_completion_last_param() { |
397 | check_scope_completion(r" | 134 | check_magic_completion( |
135 | r" | ||
398 | fn foo(file_id: FileId) {} | 136 | fn foo(file_id: FileId) {} |
399 | fn bar(file_id: FileId) {} | 137 | fn bar(file_id: FileId) {} |
400 | fn baz(file<|>) {} | 138 | fn baz(file<|>) {} |
401 | ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); | 139 | ", |
140 | r#"file_id "file_id: FileId""#, | ||
141 | ); | ||
402 | } | 142 | } |
403 | 143 | ||
404 | #[test] | 144 | #[test] |
405 | fn test_param_completion_nth_param() { | 145 | fn test_param_completion_nth_param() { |
406 | check_scope_completion(r" | 146 | check_magic_completion( |
147 | r" | ||
407 | fn foo(file_id: FileId) {} | 148 | fn foo(file_id: FileId) {} |
408 | fn bar(file_id: FileId) {} | 149 | fn bar(file_id: FileId) {} |
409 | fn baz(file<|>, x: i32) {} | 150 | fn baz(file<|>, x: i32) {} |
410 | ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); | 151 | ", |
152 | r#"file_id "file_id: FileId""#, | ||
153 | ); | ||
411 | } | 154 | } |
412 | 155 | ||
413 | #[test] | 156 | #[test] |
414 | fn test_param_completion_trait_param() { | 157 | fn test_param_completion_trait_param() { |
415 | check_scope_completion(r" | 158 | check_magic_completion( |
159 | r" | ||
416 | pub(crate) trait SourceRoot { | 160 | pub(crate) trait SourceRoot { |
417 | pub fn contains(&self, file_id: FileId) -> bool; | 161 | pub fn contains(&self, file_id: FileId) -> bool; |
418 | pub fn module_map(&self) -> &ModuleMap; | 162 | pub fn module_map(&self) -> &ModuleMap; |
419 | pub fn lines(&self, file_id: FileId) -> &LineIndex; | 163 | pub fn lines(&self, file_id: FileId) -> &LineIndex; |
420 | pub fn syntax(&self, file<|>) | 164 | pub fn syntax(&self, file<|>) |
421 | } | 165 | } |
422 | ", r#"[CompletionItem { label: "self", lookup: None, snippet: None }, | ||
423 | CompletionItem { label: "SourceRoot", lookup: None, snippet: None }, | ||
424 | CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); | ||
425 | } | ||
426 | |||
427 | #[test] | ||
428 | fn test_item_snippets() { | ||
429 | // check_snippet_completion(r" | ||
430 | // <|> | ||
431 | // ", | ||
432 | // r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##, | ||
433 | // ); | ||
434 | check_snippet_completion(r" | ||
435 | #[cfg(test)] | ||
436 | mod tests { | ||
437 | <|> | ||
438 | } | ||
439 | ", | 166 | ", |
440 | r##"[CompletionItem { label: "Test function", lookup: Some("tfn"), snippet: Some("#[test]\nfn ${1:feature}() {\n$0\n}") }, | 167 | r#"file_id "file_id: FileId""#, |
441 | CompletionItem { label: "pub(crate)", lookup: None, snippet: Some("pub(crate) $0") }]"##, | ||
442 | ); | 168 | ); |
443 | } | 169 | } |
444 | } | 170 | } |