diff options
author | Aleksey Kladov <[email protected]> | 2018-12-21 15:13:21 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2018-12-21 15:13:21 +0000 |
commit | 45232dfa689bafadf98b92ef30fd32ea9a5e9e7a (patch) | |
tree | 4652bd1f393540f2f5abdaaab367cfb0b46a10d6 /crates | |
parent | d4ef07b2355df891d4f9b7641f0246ebe5bd6a6b (diff) |
organize completion tests better
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_analysis/src/completion.rs | 343 | ||||
-rw-r--r-- | crates/ra_analysis/src/completion/completion_item.rs | 80 | ||||
-rw-r--r-- | crates/ra_analysis/src/completion/reference_completion.rs | 376 | ||||
-rw-r--r-- | crates/ra_analysis/tests/tests.rs | 60 | ||||
-rw-r--r-- | crates/test_utils/src/lib.rs | 22 |
5 files changed, 488 insertions, 393 deletions
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index 09894d955..a11e98ac0 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs | |||
@@ -16,7 +16,7 @@ use hir::source_binder; | |||
16 | use crate::{ | 16 | use crate::{ |
17 | db, | 17 | db, |
18 | Cancelable, FilePosition, | 18 | Cancelable, FilePosition, |
19 | completion::completion_item::Completions, | 19 | completion::completion_item::{Completions, CompletionKind}, |
20 | }; | 20 | }; |
21 | 21 | ||
22 | pub use crate::completion::completion_item::{CompletionItem, InsertText}; | 22 | pub use crate::completion::completion_item::{CompletionItem, InsertText}; |
@@ -81,7 +81,12 @@ fn param_completions(acc: &mut Completions, ctx: SyntaxNodeRef) { | |||
81 | Some((label, lookup)) | 81 | Some((label, lookup)) |
82 | } | 82 | } |
83 | }) | 83 | }) |
84 | .for_each(|(label, lookup)| CompletionItem::new(label).lookup_by(lookup).add_to(acc)); | 84 | .for_each(|(label, lookup)| { |
85 | CompletionItem::new(label) | ||
86 | .lookup_by(lookup) | ||
87 | .kind(CompletionKind::Magic) | ||
88 | .add_to(acc) | ||
89 | }); | ||
85 | 90 | ||
86 | fn process<'a, N: ast::FnDefOwner<'a>>( | 91 | fn process<'a, N: ast::FnDefOwner<'a>>( |
87 | node: N, | 92 | node: N, |
@@ -105,341 +110,61 @@ fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool { | |||
105 | } | 110 | } |
106 | 111 | ||
107 | #[cfg(test)] | 112 | #[cfg(test)] |
108 | mod tests { | 113 | fn check_completion(code: &str, expected_completions: &str, kind: CompletionKind) { |
109 | use test_utils::assert_eq_dbg; | 114 | use crate::mock_analysis::{single_file_with_position, analysis_and_position}; |
110 | 115 | let (analysis, position) = if code.contains("//-") { | |
111 | 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 | } | ||
112 | 123 | ||
124 | #[cfg(test)] | ||
125 | mod tests { | ||
113 | use super::*; | 126 | use super::*; |
114 | 127 | ||
115 | fn is_snippet(completion_item: &CompletionItem) -> bool { | 128 | fn check_magic_completion(code: &str, expected_completions: &str) { |
116 | match completion_item.insert_text() { | 129 | check_completion(code, expected_completions, CompletionKind::Magic); |
117 | InsertText::Snippet { .. } => true, | ||
118 | _ => false, | ||
119 | } | ||
120 | } | ||
121 | |||
122 | fn check_scope_completion(code: &str, expected_completions: &str) { | ||
123 | let (analysis, position) = single_file_with_position(code); | ||
124 | let completions = completions(&analysis.imp.db, position) | ||
125 | .unwrap() | ||
126 | .unwrap() | ||
127 | .into_iter() | ||
128 | .filter(|c| !is_snippet(c)) | ||
129 | .collect::<Vec<_>>(); | ||
130 | assert_eq_dbg(expected_completions, &completions); | ||
131 | } | ||
132 | |||
133 | fn check_snippet_completion(code: &str, expected_completions: &str) { | ||
134 | let (analysis, position) = single_file_with_position(code); | ||
135 | let completions = completions(&analysis.imp.db, position) | ||
136 | .unwrap() | ||
137 | .unwrap() | ||
138 | .into_iter() | ||
139 | .filter(is_snippet) | ||
140 | .collect::<Vec<_>>(); | ||
141 | assert_eq_dbg(expected_completions, &completions); | ||
142 | } | ||
143 | |||
144 | #[test] | ||
145 | fn test_completion_let_scope() { | ||
146 | check_scope_completion( | ||
147 | r" | ||
148 | fn quux(x: i32) { | ||
149 | let y = 92; | ||
150 | 1 + <|>; | ||
151 | let z = (); | ||
152 | } | ||
153 | ", | ||
154 | r#"[CompletionItem { label: "y", lookup: None, snippet: None }, | ||
155 | CompletionItem { label: "x", lookup: None, snippet: None }, | ||
156 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, | ||
157 | ); | ||
158 | } | ||
159 | |||
160 | #[test] | ||
161 | fn test_completion_if_let_scope() { | ||
162 | check_scope_completion( | ||
163 | r" | ||
164 | fn quux() { | ||
165 | if let Some(x) = foo() { | ||
166 | let y = 92; | ||
167 | }; | ||
168 | if let Some(a) = bar() { | ||
169 | let b = 62; | ||
170 | 1 + <|> | ||
171 | } | ||
172 | } | ||
173 | ", | ||
174 | r#"[CompletionItem { label: "b", lookup: None, snippet: None }, | ||
175 | CompletionItem { label: "a", lookup: None, snippet: None }, | ||
176 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, | ||
177 | ); | ||
178 | } | ||
179 | |||
180 | #[test] | ||
181 | fn test_completion_for_scope() { | ||
182 | check_scope_completion( | ||
183 | r" | ||
184 | fn quux() { | ||
185 | for x in &[1, 2, 3] { | ||
186 | <|> | ||
187 | } | ||
188 | } | ||
189 | ", | ||
190 | r#"[CompletionItem { label: "x", lookup: None, snippet: None }, | ||
191 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, | ||
192 | ); | ||
193 | } | ||
194 | |||
195 | #[test] | ||
196 | fn test_completion_mod_scope() { | ||
197 | check_scope_completion( | ||
198 | r" | ||
199 | struct Foo; | ||
200 | enum Baz {} | ||
201 | fn quux() { | ||
202 | <|> | ||
203 | } | ||
204 | ", | ||
205 | r#"[CompletionItem { label: "quux", lookup: None, snippet: None }, | ||
206 | CompletionItem { label: "Foo", lookup: None, snippet: None }, | ||
207 | CompletionItem { label: "Baz", lookup: None, snippet: None }]"#, | ||
208 | ); | ||
209 | } | ||
210 | |||
211 | #[test] | ||
212 | fn test_completion_mod_scope_no_self_use() { | ||
213 | check_scope_completion( | ||
214 | r" | ||
215 | use foo<|>; | ||
216 | ", | ||
217 | r#"[]"#, | ||
218 | ); | ||
219 | } | ||
220 | |||
221 | #[test] | ||
222 | fn test_completion_self_path() { | ||
223 | check_scope_completion( | ||
224 | r" | ||
225 | use self::m::<|>; | ||
226 | |||
227 | mod m { | ||
228 | struct Bar; | ||
229 | } | ||
230 | ", | ||
231 | r#"[CompletionItem { label: "Bar", lookup: None, snippet: None }]"#, | ||
232 | ); | ||
233 | } | ||
234 | |||
235 | #[test] | ||
236 | fn test_completion_mod_scope_nested() { | ||
237 | check_scope_completion( | ||
238 | r" | ||
239 | struct Foo; | ||
240 | mod m { | ||
241 | struct Bar; | ||
242 | fn quux() { <|> } | ||
243 | } | ||
244 | ", | ||
245 | r#"[CompletionItem { label: "quux", lookup: None, snippet: None }, | ||
246 | CompletionItem { label: "Bar", lookup: None, snippet: None }]"#, | ||
247 | ); | ||
248 | } | ||
249 | |||
250 | #[test] | ||
251 | fn test_complete_type() { | ||
252 | check_scope_completion( | ||
253 | r" | ||
254 | struct Foo; | ||
255 | fn x() -> <|> | ||
256 | ", | ||
257 | r#"[CompletionItem { label: "Foo", lookup: None, snippet: None }, | ||
258 | CompletionItem { label: "x", lookup: None, snippet: None }]"#, | ||
259 | ) | ||
260 | } | ||
261 | |||
262 | #[test] | ||
263 | fn test_complete_shadowing() { | ||
264 | check_scope_completion( | ||
265 | r" | ||
266 | fn foo() -> { | ||
267 | let bar = 92; | ||
268 | { | ||
269 | let bar = 62; | ||
270 | <|> | ||
271 | } | ||
272 | } | ||
273 | ", | ||
274 | r#"[CompletionItem { label: "bar", lookup: None, snippet: None }, | ||
275 | CompletionItem { label: "foo", lookup: None, snippet: None }]"#, | ||
276 | ) | ||
277 | } | ||
278 | |||
279 | #[test] | ||
280 | fn test_complete_self() { | ||
281 | check_scope_completion( | ||
282 | r" | ||
283 | impl S { fn foo(&self) { <|> } } | ||
284 | ", | ||
285 | r#"[CompletionItem { label: "self", lookup: None, snippet: None }]"#, | ||
286 | ) | ||
287 | } | ||
288 | |||
289 | #[test] | ||
290 | fn test_completion_kewords() { | ||
291 | check_snippet_completion(r" | ||
292 | fn quux() { | ||
293 | <|> | ||
294 | } | ||
295 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
296 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
297 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
298 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
299 | CompletionItem { label: "return", lookup: None, snippet: Some("return") }, | ||
300 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
301 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
302 | } | ||
303 | |||
304 | #[test] | ||
305 | fn test_completion_else() { | ||
306 | check_snippet_completion(r" | ||
307 | fn quux() { | ||
308 | if true { | ||
309 | () | ||
310 | } <|> | ||
311 | } | ||
312 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
313 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
314 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
315 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
316 | CompletionItem { label: "else", lookup: None, snippet: Some("else {$0}") }, | ||
317 | CompletionItem { label: "else if", lookup: None, snippet: Some("else if $0 {}") }, | ||
318 | CompletionItem { label: "return", lookup: None, snippet: Some("return") }, | ||
319 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
320 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
321 | } | ||
322 | |||
323 | #[test] | ||
324 | fn test_completion_return_value() { | ||
325 | check_snippet_completion(r" | ||
326 | fn quux() -> i32 { | ||
327 | <|> | ||
328 | 92 | ||
329 | } | ||
330 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
331 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
332 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
333 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
334 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0;") }, | ||
335 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
336 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
337 | check_snippet_completion(r" | ||
338 | fn quux() { | ||
339 | <|> | ||
340 | 92 | ||
341 | } | ||
342 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
343 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
344 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
345 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
346 | CompletionItem { label: "return", lookup: None, snippet: Some("return;") }, | ||
347 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
348 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
349 | } | ||
350 | |||
351 | #[test] | ||
352 | fn test_completion_return_no_stmt() { | ||
353 | check_snippet_completion(r" | ||
354 | fn quux() -> i32 { | ||
355 | match () { | ||
356 | () => <|> | ||
357 | } | ||
358 | } | ||
359 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
360 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
361 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
362 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
363 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }, | ||
364 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
365 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
366 | } | ||
367 | |||
368 | #[test] | ||
369 | fn test_continue_break_completion() { | ||
370 | check_snippet_completion(r" | ||
371 | fn quux() -> i32 { | ||
372 | loop { <|> } | ||
373 | } | ||
374 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
375 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
376 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
377 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
378 | CompletionItem { label: "continue", lookup: None, snippet: Some("continue") }, | ||
379 | CompletionItem { label: "break", lookup: None, snippet: Some("break") }, | ||
380 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }, | ||
381 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
382 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
383 | check_snippet_completion(r" | ||
384 | fn quux() -> i32 { | ||
385 | loop { || { <|> } } | ||
386 | } | ||
387 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
388 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
389 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
390 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
391 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }, | ||
392 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
393 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
394 | } | 130 | } |
395 | 131 | ||
396 | #[test] | 132 | #[test] |
397 | fn test_param_completion_last_param() { | 133 | fn test_param_completion_last_param() { |
398 | check_scope_completion(r" | 134 | check_magic_completion( |
135 | r" | ||
399 | fn foo(file_id: FileId) {} | 136 | fn foo(file_id: FileId) {} |
400 | fn bar(file_id: FileId) {} | 137 | fn bar(file_id: FileId) {} |
401 | fn baz(file<|>) {} | 138 | fn baz(file<|>) {} |
402 | ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); | 139 | ", |
140 | r#"file_id "file_id: FileId""#, | ||
141 | ); | ||
403 | } | 142 | } |
404 | 143 | ||
405 | #[test] | 144 | #[test] |
406 | fn test_param_completion_nth_param() { | 145 | fn test_param_completion_nth_param() { |
407 | check_scope_completion(r" | 146 | check_magic_completion( |
147 | r" | ||
408 | fn foo(file_id: FileId) {} | 148 | fn foo(file_id: FileId) {} |
409 | fn bar(file_id: FileId) {} | 149 | fn bar(file_id: FileId) {} |
410 | fn baz(file<|>, x: i32) {} | 150 | fn baz(file<|>, x: i32) {} |
411 | ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); | 151 | ", |
152 | r#"file_id "file_id: FileId""#, | ||
153 | ); | ||
412 | } | 154 | } |
413 | 155 | ||
414 | #[test] | 156 | #[test] |
415 | fn test_param_completion_trait_param() { | 157 | fn test_param_completion_trait_param() { |
416 | check_scope_completion(r" | 158 | check_magic_completion( |
159 | r" | ||
417 | pub(crate) trait SourceRoot { | 160 | pub(crate) trait SourceRoot { |
418 | pub fn contains(&self, file_id: FileId) -> bool; | 161 | pub fn contains(&self, file_id: FileId) -> bool; |
419 | pub fn module_map(&self) -> &ModuleMap; | 162 | pub fn module_map(&self) -> &ModuleMap; |
420 | pub fn lines(&self, file_id: FileId) -> &LineIndex; | 163 | pub fn lines(&self, file_id: FileId) -> &LineIndex; |
421 | pub fn syntax(&self, file<|>) | 164 | pub fn syntax(&self, file<|>) |
422 | } | 165 | } |
423 | ", r#"[CompletionItem { label: "self", lookup: None, snippet: None }, | ||
424 | CompletionItem { label: "SourceRoot", lookup: None, snippet: None }, | ||
425 | CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); | ||
426 | } | ||
427 | |||
428 | #[test] | ||
429 | fn test_item_snippets() { | ||
430 | // check_snippet_completion(r" | ||
431 | // <|> | ||
432 | // ", | ||
433 | // r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##, | ||
434 | // ); | ||
435 | check_snippet_completion(r" | ||
436 | #[cfg(test)] | ||
437 | mod tests { | ||
438 | <|> | ||
439 | } | ||
440 | ", | 166 | ", |
441 | r##"[CompletionItem { label: "Test function", lookup: Some("tfn"), snippet: Some("#[test]\nfn ${1:feature}() {\n $0\n}") }, | 167 | r#"file_id "file_id: FileId""#, |
442 | CompletionItem { label: "pub(crate)", lookup: None, snippet: Some("pub(crate) $0") }]"##, | ||
443 | ); | 168 | ); |
444 | } | 169 | } |
445 | } | 170 | } |
diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs index 8aa9da005..d5d751759 100644 --- a/crates/ra_analysis/src/completion/completion_item.rs +++ b/crates/ra_analysis/src/completion/completion_item.rs | |||
@@ -6,6 +6,8 @@ pub struct CompletionItem { | |||
6 | label: String, | 6 | label: String, |
7 | lookup: Option<String>, | 7 | lookup: Option<String>, |
8 | snippet: Option<String>, | 8 | snippet: Option<String>, |
9 | /// Used only internally in test, to check only specific kind of completion. | ||
10 | kind: CompletionKind, | ||
9 | } | 11 | } |
10 | 12 | ||
11 | pub enum InsertText { | 13 | pub enum InsertText { |
@@ -13,6 +15,18 @@ pub enum InsertText { | |||
13 | Snippet { text: String }, | 15 | Snippet { text: String }, |
14 | } | 16 | } |
15 | 17 | ||
18 | #[derive(Debug, PartialEq, Eq)] | ||
19 | pub(crate) enum CompletionKind { | ||
20 | /// Parser-based keyword completion. | ||
21 | Keyword, | ||
22 | /// Your usual "complete all valid identifiers". | ||
23 | Reference, | ||
24 | /// "Secret sauce" completions. | ||
25 | Magic, | ||
26 | Snippet, | ||
27 | Unspecified, | ||
28 | } | ||
29 | |||
16 | impl CompletionItem { | 30 | impl CompletionItem { |
17 | pub(crate) fn new(label: impl Into<String>) -> Builder { | 31 | pub(crate) fn new(label: impl Into<String>) -> Builder { |
18 | let label = label.into(); | 32 | let label = label.into(); |
@@ -20,6 +34,7 @@ impl CompletionItem { | |||
20 | label, | 34 | label, |
21 | lookup: None, | 35 | lookup: None, |
22 | snippet: None, | 36 | snippet: None, |
37 | kind: CompletionKind::Unspecified, | ||
23 | } | 38 | } |
24 | } | 39 | } |
25 | /// What user sees in pop-up in the UI. | 40 | /// What user sees in pop-up in the UI. |
@@ -50,28 +65,34 @@ pub(crate) struct Builder { | |||
50 | label: String, | 65 | label: String, |
51 | lookup: Option<String>, | 66 | lookup: Option<String>, |
52 | snippet: Option<String>, | 67 | snippet: Option<String>, |
68 | kind: CompletionKind, | ||
53 | } | 69 | } |
54 | 70 | ||
55 | impl Builder { | 71 | impl Builder { |
56 | pub fn add_to(self, acc: &mut Completions) { | 72 | pub(crate) fn add_to(self, acc: &mut Completions) { |
57 | acc.add(self.build()) | 73 | acc.add(self.build()) |
58 | } | 74 | } |
59 | 75 | ||
60 | pub fn build(self) -> CompletionItem { | 76 | pub(crate) fn build(self) -> CompletionItem { |
61 | CompletionItem { | 77 | CompletionItem { |
62 | label: self.label, | 78 | label: self.label, |
63 | lookup: self.lookup, | 79 | lookup: self.lookup, |
64 | snippet: self.snippet, | 80 | snippet: self.snippet, |
81 | kind: self.kind, | ||
65 | } | 82 | } |
66 | } | 83 | } |
67 | pub fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { | 84 | pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { |
68 | self.lookup = Some(lookup.into()); | 85 | self.lookup = Some(lookup.into()); |
69 | self | 86 | self |
70 | } | 87 | } |
71 | pub fn snippet(mut self, snippet: impl Into<String>) -> Builder { | 88 | pub(crate) fn snippet(mut self, snippet: impl Into<String>) -> Builder { |
72 | self.snippet = Some(snippet.into()); | 89 | self.snippet = Some(snippet.into()); |
73 | self | 90 | self |
74 | } | 91 | } |
92 | pub(crate) fn kind(mut self, kind: CompletionKind) -> Builder { | ||
93 | self.kind = kind; | ||
94 | self | ||
95 | } | ||
75 | } | 96 | } |
76 | 97 | ||
77 | impl Into<CompletionItem> for Builder { | 98 | impl Into<CompletionItem> for Builder { |
@@ -97,6 +118,57 @@ impl Completions { | |||
97 | { | 118 | { |
98 | items.into_iter().for_each(|item| self.add(item.into())) | 119 | items.into_iter().for_each(|item| self.add(item.into())) |
99 | } | 120 | } |
121 | |||
122 | #[cfg(test)] | ||
123 | pub(crate) fn assert_match(&self, expected: &str, kind: CompletionKind) { | ||
124 | let expected = normalize(expected); | ||
125 | let actual = self.debug_render(kind); | ||
126 | test_utils::assert_eq_text!(expected.as_str(), actual.as_str(),); | ||
127 | |||
128 | /// Normalize the textual representation of `Completions`: | ||
129 | /// replace `;` with newlines, normalize whitespace | ||
130 | fn normalize(expected: &str) -> String { | ||
131 | use ra_syntax::{tokenize, TextUnit, TextRange, SyntaxKind::SEMI}; | ||
132 | let mut res = String::new(); | ||
133 | for line in expected.trim().lines() { | ||
134 | let line = line.trim(); | ||
135 | let mut start_offset: TextUnit = 0.into(); | ||
136 | // Yep, we use rust tokenize in completion tests :-) | ||
137 | for token in tokenize(line) { | ||
138 | let range = TextRange::offset_len(start_offset, token.len); | ||
139 | start_offset += token.len; | ||
140 | if token.kind == SEMI { | ||
141 | res.push('\n'); | ||
142 | } else { | ||
143 | res.push_str(&line[range]); | ||
144 | } | ||
145 | } | ||
146 | |||
147 | res.push('\n'); | ||
148 | } | ||
149 | res | ||
150 | } | ||
151 | } | ||
152 | |||
153 | #[cfg(test)] | ||
154 | fn debug_render(&self, kind: CompletionKind) -> String { | ||
155 | let mut res = String::new(); | ||
156 | for c in self.buf.iter() { | ||
157 | if c.kind == kind { | ||
158 | if let Some(lookup) = &c.lookup { | ||
159 | res.push_str(lookup); | ||
160 | res.push_str(&format!(" {:?}", c.label)); | ||
161 | } else { | ||
162 | res.push_str(&c.label); | ||
163 | } | ||
164 | if let Some(snippet) = &c.snippet { | ||
165 | res.push_str(&format!(" {:?}", snippet)); | ||
166 | } | ||
167 | res.push('\n'); | ||
168 | } | ||
169 | } | ||
170 | res | ||
171 | } | ||
100 | } | 172 | } |
101 | 173 | ||
102 | impl Into<Vec<CompletionItem>> for Completions { | 174 | impl Into<Vec<CompletionItem>> for Completions { |
diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs index c578e9e8b..c2a650b6d 100644 --- a/crates/ra_analysis/src/completion/reference_completion.rs +++ b/crates/ra_analysis/src/completion/reference_completion.rs | |||
@@ -13,7 +13,7 @@ use hir::{ | |||
13 | 13 | ||
14 | use crate::{ | 14 | use crate::{ |
15 | db::RootDatabase, | 15 | db::RootDatabase, |
16 | completion::{CompletionItem, Completions}, | 16 | completion::{CompletionItem, Completions, CompletionKind::*}, |
17 | Cancelable | 17 | Cancelable |
18 | }; | 18 | }; |
19 | 19 | ||
@@ -51,7 +51,11 @@ pub(super) fn completions( | |||
51 | } | 51 | } |
52 | } | 52 | } |
53 | }) | 53 | }) |
54 | .for_each(|(name, _res)| CompletionItem::new(name.to_string()).add_to(acc)); | 54 | .for_each(|(name, _res)| { |
55 | CompletionItem::new(name.to_string()) | ||
56 | .kind(Reference) | ||
57 | .add_to(acc) | ||
58 | }); | ||
55 | } | 59 | } |
56 | NameRefKind::Path(path) => complete_path(acc, db, module, path)?, | 60 | NameRefKind::Path(path) => complete_path(acc, db, module, path)?, |
57 | NameRefKind::BareIdentInMod => { | 61 | NameRefKind::BareIdentInMod => { |
@@ -123,9 +127,13 @@ fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Completions) | |||
123 | .scope_chain(name_ref.syntax()) | 127 | .scope_chain(name_ref.syntax()) |
124 | .flat_map(|scope| scopes.entries(scope).iter()) | 128 | .flat_map(|scope| scopes.entries(scope).iter()) |
125 | .filter(|entry| shadowed.insert(entry.name())) | 129 | .filter(|entry| shadowed.insert(entry.name())) |
126 | .for_each(|entry| CompletionItem::new(entry.name().to_string()).add_to(acc)); | 130 | .for_each(|entry| { |
131 | CompletionItem::new(entry.name().to_string()) | ||
132 | .kind(Reference) | ||
133 | .add_to(acc) | ||
134 | }); | ||
127 | if scopes.self_param.is_some() { | 135 | if scopes.self_param.is_some() { |
128 | CompletionItem::new("self").add_to(acc); | 136 | CompletionItem::new("self").kind(Reference).add_to(acc); |
129 | } | 137 | } |
130 | } | 138 | } |
131 | 139 | ||
@@ -148,9 +156,11 @@ fn complete_path( | |||
148 | _ => return Ok(()), | 156 | _ => return Ok(()), |
149 | }; | 157 | }; |
150 | let module_scope = target_module.scope(db)?; | 158 | let module_scope = target_module.scope(db)?; |
151 | module_scope | 159 | module_scope.entries().for_each(|(name, _res)| { |
152 | .entries() | 160 | CompletionItem::new(name.to_string()) |
153 | .for_each(|(name, _res)| CompletionItem::new(name.to_string()).add_to(acc)); | 161 | .kind(Reference) |
162 | .add_to(acc) | ||
163 | }); | ||
154 | Ok(()) | 164 | Ok(()) |
155 | } | 165 | } |
156 | 166 | ||
@@ -164,9 +174,11 @@ fn ${1:feature}() { | |||
164 | $0 | 174 | $0 |
165 | }", | 175 | }", |
166 | ) | 176 | ) |
177 | .kind(Snippet) | ||
167 | .add_to(acc); | 178 | .add_to(acc); |
168 | CompletionItem::new("pub(crate)") | 179 | CompletionItem::new("pub(crate)") |
169 | .snippet("pub(crate) $0") | 180 | .snippet("pub(crate) $0") |
181 | .kind(Snippet) | ||
170 | .add_to(acc); | 182 | .add_to(acc); |
171 | } | 183 | } |
172 | 184 | ||
@@ -249,14 +261,362 @@ fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option<Complet | |||
249 | } | 261 | } |
250 | 262 | ||
251 | fn keyword(kw: &str, snippet: &str) -> CompletionItem { | 263 | fn keyword(kw: &str, snippet: &str) -> CompletionItem { |
252 | CompletionItem::new(kw).snippet(snippet).build() | 264 | CompletionItem::new(kw) |
265 | .kind(Keyword) | ||
266 | .snippet(snippet) | ||
267 | .build() | ||
253 | } | 268 | } |
254 | 269 | ||
255 | fn complete_expr_snippets(acc: &mut Completions) { | 270 | fn complete_expr_snippets(acc: &mut Completions) { |
256 | CompletionItem::new("pd") | 271 | CompletionItem::new("pd") |
257 | .snippet("eprintln!(\"$0 = {:?}\", $0);") | 272 | .snippet("eprintln!(\"$0 = {:?}\", $0);") |
273 | .kind(Snippet) | ||
258 | .add_to(acc); | 274 | .add_to(acc); |
259 | CompletionItem::new("ppd") | 275 | CompletionItem::new("ppd") |
260 | .snippet("eprintln!(\"$0 = {:#?}\", $0);") | 276 | .snippet("eprintln!(\"$0 = {:#?}\", $0);") |
277 | .kind(Snippet) | ||
261 | .add_to(acc); | 278 | .add_to(acc); |
262 | } | 279 | } |
280 | |||
281 | #[cfg(test)] | ||
282 | mod tests { | ||
283 | use crate::completion::{CompletionKind, check_completion}; | ||
284 | |||
285 | fn check_reference_completion(code: &str, expected_completions: &str) { | ||
286 | check_completion(code, expected_completions, CompletionKind::Reference); | ||
287 | } | ||
288 | |||
289 | fn check_keyword_completion(code: &str, expected_completions: &str) { | ||
290 | check_completion(code, expected_completions, CompletionKind::Keyword); | ||
291 | } | ||
292 | |||
293 | fn check_snippet_completion(code: &str, expected_completions: &str) { | ||
294 | check_completion(code, expected_completions, CompletionKind::Snippet); | ||
295 | } | ||
296 | |||
297 | #[test] | ||
298 | fn test_completion_let_scope() { | ||
299 | check_reference_completion( | ||
300 | r" | ||
301 | fn quux(x: i32) { | ||
302 | let y = 92; | ||
303 | 1 + <|>; | ||
304 | let z = (); | ||
305 | } | ||
306 | ", | ||
307 | "y;x;quux", | ||
308 | ); | ||
309 | } | ||
310 | |||
311 | #[test] | ||
312 | fn test_completion_if_let_scope() { | ||
313 | check_reference_completion( | ||
314 | r" | ||
315 | fn quux() { | ||
316 | if let Some(x) = foo() { | ||
317 | let y = 92; | ||
318 | }; | ||
319 | if let Some(a) = bar() { | ||
320 | let b = 62; | ||
321 | 1 + <|> | ||
322 | } | ||
323 | } | ||
324 | ", | ||
325 | "b;a;quux", | ||
326 | ); | ||
327 | } | ||
328 | |||
329 | #[test] | ||
330 | fn test_completion_for_scope() { | ||
331 | check_reference_completion( | ||
332 | r" | ||
333 | fn quux() { | ||
334 | for x in &[1, 2, 3] { | ||
335 | <|> | ||
336 | } | ||
337 | } | ||
338 | ", | ||
339 | "x;quux", | ||
340 | ); | ||
341 | } | ||
342 | |||
343 | #[test] | ||
344 | fn test_completion_mod_scope() { | ||
345 | check_reference_completion( | ||
346 | r" | ||
347 | struct Foo; | ||
348 | enum Baz {} | ||
349 | fn quux() { | ||
350 | <|> | ||
351 | } | ||
352 | ", | ||
353 | "quux;Foo;Baz", | ||
354 | ); | ||
355 | } | ||
356 | |||
357 | #[test] | ||
358 | fn test_completion_mod_scope_no_self_use() { | ||
359 | check_reference_completion( | ||
360 | r" | ||
361 | use foo<|>; | ||
362 | ", | ||
363 | "", | ||
364 | ); | ||
365 | } | ||
366 | |||
367 | #[test] | ||
368 | fn test_completion_self_path() { | ||
369 | check_reference_completion( | ||
370 | r" | ||
371 | use self::m::<|>; | ||
372 | |||
373 | mod m { | ||
374 | struct Bar; | ||
375 | } | ||
376 | ", | ||
377 | "Bar", | ||
378 | ); | ||
379 | } | ||
380 | |||
381 | #[test] | ||
382 | fn test_completion_mod_scope_nested() { | ||
383 | check_reference_completion( | ||
384 | r" | ||
385 | struct Foo; | ||
386 | mod m { | ||
387 | struct Bar; | ||
388 | fn quux() { <|> } | ||
389 | } | ||
390 | ", | ||
391 | "quux;Bar", | ||
392 | ); | ||
393 | } | ||
394 | |||
395 | #[test] | ||
396 | fn test_complete_type() { | ||
397 | check_reference_completion( | ||
398 | r" | ||
399 | struct Foo; | ||
400 | fn x() -> <|> | ||
401 | ", | ||
402 | "Foo;x", | ||
403 | ) | ||
404 | } | ||
405 | |||
406 | #[test] | ||
407 | fn test_complete_shadowing() { | ||
408 | check_reference_completion( | ||
409 | r" | ||
410 | fn foo() -> { | ||
411 | let bar = 92; | ||
412 | { | ||
413 | let bar = 62; | ||
414 | <|> | ||
415 | } | ||
416 | } | ||
417 | ", | ||
418 | "bar;foo", | ||
419 | ) | ||
420 | } | ||
421 | |||
422 | #[test] | ||
423 | fn test_complete_self() { | ||
424 | check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self") | ||
425 | } | ||
426 | |||
427 | #[test] | ||
428 | fn test_complete_crate_path() { | ||
429 | check_reference_completion( | ||
430 | " | ||
431 | //- /lib.rs | ||
432 | mod foo; | ||
433 | struct Spam; | ||
434 | //- /foo.rs | ||
435 | use crate::Sp<|> | ||
436 | ", | ||
437 | "Spam;foo", | ||
438 | ); | ||
439 | } | ||
440 | |||
441 | #[test] | ||
442 | fn test_complete_crate_path_with_braces() { | ||
443 | check_reference_completion( | ||
444 | " | ||
445 | //- /lib.rs | ||
446 | mod foo; | ||
447 | struct Spam; | ||
448 | //- /foo.rs | ||
449 | use crate::{Sp<|>}; | ||
450 | ", | ||
451 | "Spam;foo", | ||
452 | ); | ||
453 | } | ||
454 | |||
455 | #[test] | ||
456 | fn test_complete_crate_path_in_nested_tree() { | ||
457 | check_reference_completion( | ||
458 | " | ||
459 | //- /lib.rs | ||
460 | mod foo; | ||
461 | pub mod bar { | ||
462 | pub mod baz { | ||
463 | pub struct Spam; | ||
464 | } | ||
465 | } | ||
466 | //- /foo.rs | ||
467 | use crate::{bar::{baz::Sp<|>}}; | ||
468 | ", | ||
469 | "Spam", | ||
470 | ); | ||
471 | } | ||
472 | |||
473 | #[test] | ||
474 | fn test_completion_kewords() { | ||
475 | check_keyword_completion( | ||
476 | r" | ||
477 | fn quux() { | ||
478 | <|> | ||
479 | } | ||
480 | ", | ||
481 | r#" | ||
482 | if "if $0 {}" | ||
483 | match "match $0 {}" | ||
484 | while "while $0 {}" | ||
485 | loop "loop {$0}" | ||
486 | return "return" | ||
487 | "#, | ||
488 | ); | ||
489 | } | ||
490 | |||
491 | #[test] | ||
492 | fn test_completion_else() { | ||
493 | check_keyword_completion( | ||
494 | r" | ||
495 | fn quux() { | ||
496 | if true { | ||
497 | () | ||
498 | } <|> | ||
499 | } | ||
500 | ", | ||
501 | r#" | ||
502 | if "if $0 {}" | ||
503 | match "match $0 {}" | ||
504 | while "while $0 {}" | ||
505 | loop "loop {$0}" | ||
506 | else "else {$0}" | ||
507 | else if "else if $0 {}" | ||
508 | return "return" | ||
509 | "#, | ||
510 | ); | ||
511 | } | ||
512 | |||
513 | #[test] | ||
514 | fn test_completion_return_value() { | ||
515 | check_keyword_completion( | ||
516 | r" | ||
517 | fn quux() -> i32 { | ||
518 | <|> | ||
519 | 92 | ||
520 | } | ||
521 | ", | ||
522 | r#" | ||
523 | if "if $0 {}" | ||
524 | match "match $0 {}" | ||
525 | while "while $0 {}" | ||
526 | loop "loop {$0}" | ||
527 | return "return $0;" | ||
528 | "#, | ||
529 | ); | ||
530 | check_keyword_completion( | ||
531 | r" | ||
532 | fn quux() { | ||
533 | <|> | ||
534 | 92 | ||
535 | } | ||
536 | ", | ||
537 | r#" | ||
538 | if "if $0 {}" | ||
539 | match "match $0 {}" | ||
540 | while "while $0 {}" | ||
541 | loop "loop {$0}" | ||
542 | return "return;" | ||
543 | "#, | ||
544 | ); | ||
545 | } | ||
546 | |||
547 | #[test] | ||
548 | fn test_completion_return_no_stmt() { | ||
549 | check_keyword_completion( | ||
550 | r" | ||
551 | fn quux() -> i32 { | ||
552 | match () { | ||
553 | () => <|> | ||
554 | } | ||
555 | } | ||
556 | ", | ||
557 | r#" | ||
558 | if "if $0 {}" | ||
559 | match "match $0 {}" | ||
560 | while "while $0 {}" | ||
561 | loop "loop {$0}" | ||
562 | return "return $0" | ||
563 | "#, | ||
564 | ); | ||
565 | } | ||
566 | |||
567 | #[test] | ||
568 | fn test_continue_break_completion() { | ||
569 | check_keyword_completion( | ||
570 | r" | ||
571 | fn quux() -> i32 { | ||
572 | loop { <|> } | ||
573 | } | ||
574 | ", | ||
575 | r#" | ||
576 | if "if $0 {}" | ||
577 | match "match $0 {}" | ||
578 | while "while $0 {}" | ||
579 | loop "loop {$0}" | ||
580 | continue "continue" | ||
581 | break "break" | ||
582 | return "return $0" | ||
583 | "#, | ||
584 | ); | ||
585 | check_keyword_completion( | ||
586 | r" | ||
587 | fn quux() -> i32 { | ||
588 | loop { || { <|> } } | ||
589 | } | ||
590 | ", | ||
591 | r#" | ||
592 | if "if $0 {}" | ||
593 | match "match $0 {}" | ||
594 | while "while $0 {}" | ||
595 | loop "loop {$0}" | ||
596 | return "return $0" | ||
597 | "#, | ||
598 | ); | ||
599 | } | ||
600 | |||
601 | #[test] | ||
602 | fn test_item_snippets() { | ||
603 | // check_snippet_completion(r" | ||
604 | // <|> | ||
605 | // ", | ||
606 | // r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##, | ||
607 | // ); | ||
608 | check_snippet_completion( | ||
609 | r" | ||
610 | #[cfg(test)] | ||
611 | mod tests { | ||
612 | <|> | ||
613 | } | ||
614 | ", | ||
615 | r##" | ||
616 | tfn "Test function" "#[test]\nfn ${1:feature}() {\n $0\n}" | ||
617 | pub(crate) "pub(crate) $0" | ||
618 | "##, | ||
619 | ); | ||
620 | } | ||
621 | |||
622 | } | ||
diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/tests.rs index 67738da48..938ca797a 100644 --- a/crates/ra_analysis/tests/tests.rs +++ b/crates/ra_analysis/tests/tests.rs | |||
@@ -452,63 +452,3 @@ fn test_find_all_refs_for_fn_param() { | |||
452 | let refs = get_all_refs(code); | 452 | let refs = get_all_refs(code); |
453 | assert_eq!(refs.len(), 2); | 453 | assert_eq!(refs.len(), 2); |
454 | } | 454 | } |
455 | |||
456 | #[test] | ||
457 | fn test_complete_crate_path() { | ||
458 | let (analysis, position) = analysis_and_position( | ||
459 | " | ||
460 | //- /lib.rs | ||
461 | mod foo; | ||
462 | struct Spam; | ||
463 | //- /foo.rs | ||
464 | use crate::Sp<|> | ||
465 | ", | ||
466 | ); | ||
467 | let completions = analysis.completions(position).unwrap().unwrap(); | ||
468 | assert_eq_dbg( | ||
469 | r#"[CompletionItem { label: "Spam", lookup: None, snippet: None }, | ||
470 | CompletionItem { label: "foo", lookup: None, snippet: None }]"#, | ||
471 | &completions, | ||
472 | ); | ||
473 | } | ||
474 | |||
475 | #[test] | ||
476 | fn test_complete_crate_path_with_braces() { | ||
477 | let (analysis, position) = analysis_and_position( | ||
478 | " | ||
479 | //- /lib.rs | ||
480 | mod foo; | ||
481 | struct Spam; | ||
482 | //- /foo.rs | ||
483 | use crate::{Sp<|>}; | ||
484 | ", | ||
485 | ); | ||
486 | let completions = analysis.completions(position).unwrap().unwrap(); | ||
487 | assert_eq_dbg( | ||
488 | r#"[CompletionItem { label: "Spam", lookup: None, snippet: None }, | ||
489 | CompletionItem { label: "foo", lookup: None, snippet: None }]"#, | ||
490 | &completions, | ||
491 | ); | ||
492 | } | ||
493 | |||
494 | #[test] | ||
495 | fn test_complete_crate_path_in_nested_tree() { | ||
496 | let (analysis, position) = analysis_and_position( | ||
497 | " | ||
498 | //- /lib.rs | ||
499 | mod foo; | ||
500 | pub mod bar { | ||
501 | pub mod baz { | ||
502 | pub struct Spam; | ||
503 | } | ||
504 | } | ||
505 | //- /foo.rs | ||
506 | use crate::{bar::{baz::Sp<|>}}; | ||
507 | ", | ||
508 | ); | ||
509 | let completions = analysis.completions(position).unwrap().unwrap(); | ||
510 | assert_eq_dbg( | ||
511 | r#"[CompletionItem { label: "Spam", lookup: None, snippet: None }]"#, | ||
512 | &completions, | ||
513 | ); | ||
514 | } | ||
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index 1ae800d7c..beb936c61 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs | |||
@@ -10,22 +10,20 @@ pub const CURSOR_MARKER: &str = "<|>"; | |||
10 | 10 | ||
11 | #[macro_export] | 11 | #[macro_export] |
12 | macro_rules! assert_eq_text { | 12 | macro_rules! assert_eq_text { |
13 | ($expected:expr, $actual:expr) => {{ | 13 | ($expected:expr, $actual:expr) => { |
14 | let expected = $expected; | 14 | assert_eq_text!($expected, $actual,) |
15 | let actual = $actual; | 15 | }; |
16 | if expected != actual { | ||
17 | let changeset = $crate::__Changeset::new(actual, expected, "\n"); | ||
18 | println!("Expected:\n{}\n\nActual:\n{}\nDiff:{}\n", expected, actual, changeset); | ||
19 | panic!("text differs"); | ||
20 | } | ||
21 | }}; | ||
22 | ($expected:expr, $actual:expr, $($tt:tt)*) => {{ | 16 | ($expected:expr, $actual:expr, $($tt:tt)*) => {{ |
23 | let expected = $expected; | 17 | let expected = $expected; |
24 | let actual = $actual; | 18 | let actual = $actual; |
25 | if expected != actual { | 19 | if expected != actual { |
26 | let changeset = $crate::__Changeset::new(actual, expected, "\n"); | 20 | if expected.trim() == actual.trim() { |
27 | println!("Expected:\n{}\n\nActual:\n{}\n\nDiff:\n{}\n", expected, actual, changeset); | 21 | eprintln!("Expected:\n{:?}\n\nActual:\n{:?}\n\nWhitespace difference\n", expected, actual); |
28 | println!($($tt)*); | 22 | } else { |
23 | let changeset = $crate::__Changeset::new(actual, expected, "\n"); | ||
24 | eprintln!("Expected:\n{}\n\nActual:\n{}\n\nDiff:\n{}\n", expected, actual, changeset); | ||
25 | } | ||
26 | eprintln!($($tt)*); | ||
29 | panic!("text differs"); | 27 | panic!("text differs"); |
30 | } | 28 | } |
31 | }}; | 29 | }}; |