diff options
Diffstat (limited to 'crates/ra_ide/src/diagnostics.rs')
-rw-r--r-- | crates/ra_ide/src/diagnostics.rs | 652 |
1 files changed, 652 insertions, 0 deletions
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs new file mode 100644 index 000000000..cc1ccab4b --- /dev/null +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -0,0 +1,652 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use std::cell::RefCell; | ||
4 | |||
5 | use hir::diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}; | ||
6 | use itertools::Itertools; | ||
7 | use ra_db::{RelativePath, SourceDatabase, SourceDatabaseExt}; | ||
8 | use ra_prof::profile; | ||
9 | use ra_syntax::{ | ||
10 | algo, | ||
11 | ast::{self, make, AstNode}, | ||
12 | Location, SyntaxNode, TextRange, T, | ||
13 | }; | ||
14 | use ra_text_edit::{TextEdit, TextEditBuilder}; | ||
15 | |||
16 | use crate::{db::RootDatabase, Diagnostic, FileId, FileSystemEdit, SourceChange, SourceFileEdit}; | ||
17 | |||
18 | #[derive(Debug, Copy, Clone)] | ||
19 | pub enum Severity { | ||
20 | Error, | ||
21 | WeakWarning, | ||
22 | } | ||
23 | |||
24 | pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> { | ||
25 | let _p = profile("diagnostics"); | ||
26 | let parse = db.parse(file_id); | ||
27 | let mut res = Vec::new(); | ||
28 | |||
29 | res.extend(parse.errors().iter().map(|err| Diagnostic { | ||
30 | range: location_to_range(err.location()), | ||
31 | message: format!("Syntax Error: {}", err), | ||
32 | severity: Severity::Error, | ||
33 | fix: None, | ||
34 | })); | ||
35 | |||
36 | for node in parse.tree().syntax().descendants() { | ||
37 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); | ||
38 | check_struct_shorthand_initialization(&mut res, file_id, &node); | ||
39 | } | ||
40 | let res = RefCell::new(res); | ||
41 | let mut sink = DiagnosticSink::new(|d| { | ||
42 | res.borrow_mut().push(Diagnostic { | ||
43 | message: d.message(), | ||
44 | range: d.highlight_range(), | ||
45 | severity: Severity::Error, | ||
46 | fix: None, | ||
47 | }) | ||
48 | }) | ||
49 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { | ||
50 | let original_file = d.source().file_id.original_file(db); | ||
51 | let source_root = db.file_source_root(original_file); | ||
52 | let path = db | ||
53 | .file_relative_path(original_file) | ||
54 | .parent() | ||
55 | .unwrap_or_else(|| RelativePath::new("")) | ||
56 | .join(&d.candidate); | ||
57 | let create_file = FileSystemEdit::CreateFile { source_root, path }; | ||
58 | let fix = SourceChange::file_system_edit("create module", create_file); | ||
59 | res.borrow_mut().push(Diagnostic { | ||
60 | range: d.highlight_range(), | ||
61 | message: d.message(), | ||
62 | severity: Severity::Error, | ||
63 | fix: Some(fix), | ||
64 | }) | ||
65 | }) | ||
66 | .on::<hir::diagnostics::MissingFields, _>(|d| { | ||
67 | let mut field_list = d.ast(db); | ||
68 | for f in d.missed_fields.iter() { | ||
69 | let field = make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); | ||
70 | field_list = field_list.append_field(&field); | ||
71 | } | ||
72 | |||
73 | let mut builder = TextEditBuilder::default(); | ||
74 | algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); | ||
75 | |||
76 | let fix = | ||
77 | SourceChange::source_file_edit_from("fill struct fields", file_id, builder.finish()); | ||
78 | res.borrow_mut().push(Diagnostic { | ||
79 | range: d.highlight_range(), | ||
80 | message: d.message(), | ||
81 | severity: Severity::Error, | ||
82 | fix: Some(fix), | ||
83 | }) | ||
84 | }) | ||
85 | .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { | ||
86 | let node = d.ast(db); | ||
87 | let replacement = format!("Ok({})", node.syntax()); | ||
88 | let edit = TextEdit::replace(node.syntax().text_range(), replacement); | ||
89 | let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit); | ||
90 | res.borrow_mut().push(Diagnostic { | ||
91 | range: d.highlight_range(), | ||
92 | message: d.message(), | ||
93 | severity: Severity::Error, | ||
94 | fix: Some(fix), | ||
95 | }) | ||
96 | }); | ||
97 | let source_file = db.parse(file_id).tree(); | ||
98 | let src = | ||
99 | hir::Source { file_id: file_id.into(), value: hir::ModuleSource::SourceFile(source_file) }; | ||
100 | if let Some(m) = hir::Module::from_definition(db, src) { | ||
101 | m.diagnostics(db, &mut sink); | ||
102 | }; | ||
103 | drop(sink); | ||
104 | res.into_inner() | ||
105 | } | ||
106 | fn location_to_range(location: Location) -> TextRange { | ||
107 | match location { | ||
108 | Location::Offset(offset) => TextRange::offset_len(offset, 1.into()), | ||
109 | Location::Range(range) => range, | ||
110 | } | ||
111 | } | ||
112 | |||
113 | fn check_unnecessary_braces_in_use_statement( | ||
114 | acc: &mut Vec<Diagnostic>, | ||
115 | file_id: FileId, | ||
116 | node: &SyntaxNode, | ||
117 | ) -> Option<()> { | ||
118 | let use_tree_list = ast::UseTreeList::cast(node.clone())?; | ||
119 | if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { | ||
120 | let range = use_tree_list.syntax().text_range(); | ||
121 | let edit = | ||
122 | text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree) | ||
123 | .unwrap_or_else(|| { | ||
124 | let to_replace = single_use_tree.syntax().text().to_string(); | ||
125 | let mut edit_builder = TextEditBuilder::default(); | ||
126 | edit_builder.delete(range); | ||
127 | edit_builder.insert(range.start(), to_replace); | ||
128 | edit_builder.finish() | ||
129 | }); | ||
130 | |||
131 | acc.push(Diagnostic { | ||
132 | range, | ||
133 | message: "Unnecessary braces in use statement".to_string(), | ||
134 | severity: Severity::WeakWarning, | ||
135 | fix: Some(SourceChange::source_file_edit( | ||
136 | "Remove unnecessary braces", | ||
137 | SourceFileEdit { file_id, edit }, | ||
138 | )), | ||
139 | }); | ||
140 | } | ||
141 | |||
142 | Some(()) | ||
143 | } | ||
144 | |||
145 | fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | ||
146 | single_use_tree: &ast::UseTree, | ||
147 | ) -> Option<TextEdit> { | ||
148 | let use_tree_list_node = single_use_tree.syntax().parent()?; | ||
149 | if single_use_tree.path()?.segment()?.syntax().first_child_or_token()?.kind() == T![self] { | ||
150 | let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start(); | ||
151 | let end = use_tree_list_node.text_range().end(); | ||
152 | let range = TextRange::from_to(start, end); | ||
153 | return Some(TextEdit::delete(range)); | ||
154 | } | ||
155 | None | ||
156 | } | ||
157 | |||
158 | fn check_struct_shorthand_initialization( | ||
159 | acc: &mut Vec<Diagnostic>, | ||
160 | file_id: FileId, | ||
161 | node: &SyntaxNode, | ||
162 | ) -> Option<()> { | ||
163 | let record_lit = ast::RecordLit::cast(node.clone())?; | ||
164 | let record_field_list = record_lit.record_field_list()?; | ||
165 | for record_field in record_field_list.fields() { | ||
166 | if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) { | ||
167 | let field_name = name_ref.syntax().text().to_string(); | ||
168 | let field_expr = expr.syntax().text().to_string(); | ||
169 | if field_name == field_expr { | ||
170 | let mut edit_builder = TextEditBuilder::default(); | ||
171 | edit_builder.delete(record_field.syntax().text_range()); | ||
172 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); | ||
173 | let edit = edit_builder.finish(); | ||
174 | |||
175 | acc.push(Diagnostic { | ||
176 | range: record_field.syntax().text_range(), | ||
177 | message: "Shorthand struct initialization".to_string(), | ||
178 | severity: Severity::WeakWarning, | ||
179 | fix: Some(SourceChange::source_file_edit( | ||
180 | "use struct shorthand initialization", | ||
181 | SourceFileEdit { file_id, edit }, | ||
182 | )), | ||
183 | }); | ||
184 | } | ||
185 | } | ||
186 | } | ||
187 | Some(()) | ||
188 | } | ||
189 | |||
190 | #[cfg(test)] | ||
191 | mod tests { | ||
192 | use insta::assert_debug_snapshot; | ||
193 | use join_to_string::join; | ||
194 | use ra_syntax::SourceFile; | ||
195 | use test_utils::assert_eq_text; | ||
196 | |||
197 | use crate::mock_analysis::{analysis_and_position, single_file}; | ||
198 | |||
199 | use super::*; | ||
200 | |||
201 | type DiagnosticChecker = fn(&mut Vec<Diagnostic>, FileId, &SyntaxNode) -> Option<()>; | ||
202 | |||
203 | fn check_not_applicable(code: &str, func: DiagnosticChecker) { | ||
204 | let parse = SourceFile::parse(code); | ||
205 | let mut diagnostics = Vec::new(); | ||
206 | for node in parse.tree().syntax().descendants() { | ||
207 | func(&mut diagnostics, FileId(0), &node); | ||
208 | } | ||
209 | assert!(diagnostics.is_empty()); | ||
210 | } | ||
211 | |||
212 | fn check_apply(before: &str, after: &str, func: DiagnosticChecker) { | ||
213 | let parse = SourceFile::parse(before); | ||
214 | let mut diagnostics = Vec::new(); | ||
215 | for node in parse.tree().syntax().descendants() { | ||
216 | func(&mut diagnostics, FileId(0), &node); | ||
217 | } | ||
218 | let diagnostic = | ||
219 | diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); | ||
220 | let mut fix = diagnostic.fix.unwrap(); | ||
221 | let edit = fix.source_file_edits.pop().unwrap().edit; | ||
222 | let actual = edit.apply(&before); | ||
223 | assert_eq_text!(after, &actual); | ||
224 | } | ||
225 | |||
226 | /// Takes a multi-file input fixture with annotated cursor positions, | ||
227 | /// and checks that: | ||
228 | /// * a diagnostic is produced | ||
229 | /// * this diagnostic touches the input cursor position | ||
230 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied | ||
231 | fn check_apply_diagnostic_fix_from_position(fixture: &str, after: &str) { | ||
232 | let (analysis, file_position) = analysis_and_position(fixture); | ||
233 | let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap(); | ||
234 | let mut fix = diagnostic.fix.unwrap(); | ||
235 | let edit = fix.source_file_edits.pop().unwrap().edit; | ||
236 | let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); | ||
237 | let actual = edit.apply(&target_file_contents); | ||
238 | |||
239 | // Strip indent and empty lines from `after`, to match the behaviour of | ||
240 | // `parse_fixture` called from `analysis_and_position`. | ||
241 | let margin = fixture | ||
242 | .lines() | ||
243 | .filter(|it| it.trim_start().starts_with("//-")) | ||
244 | .map(|it| it.len() - it.trim_start().len()) | ||
245 | .next() | ||
246 | .expect("empty fixture"); | ||
247 | let after = join(after.lines().filter_map(|line| { | ||
248 | if line.len() > margin { | ||
249 | Some(&line[margin..]) | ||
250 | } else { | ||
251 | None | ||
252 | } | ||
253 | })) | ||
254 | .separator("\n") | ||
255 | .suffix("\n") | ||
256 | .to_string(); | ||
257 | |||
258 | assert_eq_text!(&after, &actual); | ||
259 | assert!( | ||
260 | diagnostic.range.start() <= file_position.offset | ||
261 | && diagnostic.range.end() >= file_position.offset, | ||
262 | "diagnostic range {} does not touch cursor position {}", | ||
263 | diagnostic.range, | ||
264 | file_position.offset | ||
265 | ); | ||
266 | } | ||
267 | |||
268 | fn check_apply_diagnostic_fix(before: &str, after: &str) { | ||
269 | let (analysis, file_id) = single_file(before); | ||
270 | let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); | ||
271 | let mut fix = diagnostic.fix.unwrap(); | ||
272 | let edit = fix.source_file_edits.pop().unwrap().edit; | ||
273 | let actual = edit.apply(&before); | ||
274 | assert_eq_text!(after, &actual); | ||
275 | } | ||
276 | |||
277 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics | ||
278 | /// apply to the file containing the cursor. | ||
279 | fn check_no_diagnostic_for_target_file(fixture: &str) { | ||
280 | let (analysis, file_position) = analysis_and_position(fixture); | ||
281 | let diagnostics = analysis.diagnostics(file_position.file_id).unwrap(); | ||
282 | assert_eq!(diagnostics.len(), 0); | ||
283 | } | ||
284 | |||
285 | fn check_no_diagnostic(content: &str) { | ||
286 | let (analysis, file_id) = single_file(content); | ||
287 | let diagnostics = analysis.diagnostics(file_id).unwrap(); | ||
288 | assert_eq!(diagnostics.len(), 0); | ||
289 | } | ||
290 | |||
291 | #[test] | ||
292 | fn test_wrap_return_type() { | ||
293 | let before = r#" | ||
294 | //- /main.rs | ||
295 | use std::{string::String, result::Result::{self, Ok, Err}}; | ||
296 | |||
297 | fn div(x: i32, y: i32) -> Result<i32, String> { | ||
298 | if y == 0 { | ||
299 | return Err("div by zero".into()); | ||
300 | } | ||
301 | x / y<|> | ||
302 | } | ||
303 | |||
304 | //- /std/lib.rs | ||
305 | pub mod string { | ||
306 | pub struct String { } | ||
307 | } | ||
308 | pub mod result { | ||
309 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
310 | } | ||
311 | "#; | ||
312 | let after = r#" | ||
313 | use std::{string::String, result::Result::{self, Ok, Err}}; | ||
314 | |||
315 | fn div(x: i32, y: i32) -> Result<i32, String> { | ||
316 | if y == 0 { | ||
317 | return Err("div by zero".into()); | ||
318 | } | ||
319 | Ok(x / y) | ||
320 | } | ||
321 | "#; | ||
322 | check_apply_diagnostic_fix_from_position(before, after); | ||
323 | } | ||
324 | |||
325 | #[test] | ||
326 | fn test_wrap_return_type_handles_generic_functions() { | ||
327 | let before = r#" | ||
328 | //- /main.rs | ||
329 | use std::result::Result::{self, Ok, Err}; | ||
330 | |||
331 | fn div<T>(x: T) -> Result<T, i32> { | ||
332 | if x == 0 { | ||
333 | return Err(7); | ||
334 | } | ||
335 | <|>x | ||
336 | } | ||
337 | |||
338 | //- /std/lib.rs | ||
339 | pub mod result { | ||
340 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
341 | } | ||
342 | "#; | ||
343 | let after = r#" | ||
344 | use std::result::Result::{self, Ok, Err}; | ||
345 | |||
346 | fn div<T>(x: T) -> Result<T, i32> { | ||
347 | if x == 0 { | ||
348 | return Err(7); | ||
349 | } | ||
350 | Ok(x) | ||
351 | } | ||
352 | "#; | ||
353 | check_apply_diagnostic_fix_from_position(before, after); | ||
354 | } | ||
355 | |||
356 | #[test] | ||
357 | fn test_wrap_return_type_handles_type_aliases() { | ||
358 | let before = r#" | ||
359 | //- /main.rs | ||
360 | use std::{string::String, result::Result::{self, Ok, Err}}; | ||
361 | |||
362 | type MyResult<T> = Result<T, String>; | ||
363 | |||
364 | fn div(x: i32, y: i32) -> MyResult<i32> { | ||
365 | if y == 0 { | ||
366 | return Err("div by zero".into()); | ||
367 | } | ||
368 | x <|>/ y | ||
369 | } | ||
370 | |||
371 | //- /std/lib.rs | ||
372 | pub mod string { | ||
373 | pub struct String { } | ||
374 | } | ||
375 | pub mod result { | ||
376 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
377 | } | ||
378 | "#; | ||
379 | let after = r#" | ||
380 | use std::{string::String, result::Result::{self, Ok, Err}}; | ||
381 | |||
382 | type MyResult<T> = Result<T, String>; | ||
383 | fn div(x: i32, y: i32) -> MyResult<i32> { | ||
384 | if y == 0 { | ||
385 | return Err("div by zero".into()); | ||
386 | } | ||
387 | Ok(x / y) | ||
388 | } | ||
389 | "#; | ||
390 | check_apply_diagnostic_fix_from_position(before, after); | ||
391 | } | ||
392 | |||
393 | #[test] | ||
394 | fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { | ||
395 | let content = r#" | ||
396 | //- /main.rs | ||
397 | use std::{string::String, result::Result::{self, Ok, Err}}; | ||
398 | |||
399 | fn foo() -> Result<String, i32> { | ||
400 | 0<|> | ||
401 | } | ||
402 | |||
403 | //- /std/lib.rs | ||
404 | pub mod string { | ||
405 | pub struct String { } | ||
406 | } | ||
407 | pub mod result { | ||
408 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
409 | } | ||
410 | "#; | ||
411 | check_no_diagnostic_for_target_file(content); | ||
412 | } | ||
413 | |||
414 | #[test] | ||
415 | fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() { | ||
416 | let content = r#" | ||
417 | //- /main.rs | ||
418 | use std::{string::String, result::Result::{self, Ok, Err}}; | ||
419 | |||
420 | enum SomeOtherEnum { | ||
421 | Ok(i32), | ||
422 | Err(String), | ||
423 | } | ||
424 | |||
425 | fn foo() -> SomeOtherEnum { | ||
426 | 0<|> | ||
427 | } | ||
428 | |||
429 | //- /std/lib.rs | ||
430 | pub mod string { | ||
431 | pub struct String { } | ||
432 | } | ||
433 | pub mod result { | ||
434 | pub enum Result<T, E> { Ok(T), Err(E) } | ||
435 | } | ||
436 | "#; | ||
437 | check_no_diagnostic_for_target_file(content); | ||
438 | } | ||
439 | |||
440 | #[test] | ||
441 | fn test_fill_struct_fields_empty() { | ||
442 | let before = r" | ||
443 | struct TestStruct { | ||
444 | one: i32, | ||
445 | two: i64, | ||
446 | } | ||
447 | |||
448 | fn test_fn() { | ||
449 | let s = TestStruct{}; | ||
450 | } | ||
451 | "; | ||
452 | let after = r" | ||
453 | struct TestStruct { | ||
454 | one: i32, | ||
455 | two: i64, | ||
456 | } | ||
457 | |||
458 | fn test_fn() { | ||
459 | let s = TestStruct{ one: (), two: ()}; | ||
460 | } | ||
461 | "; | ||
462 | check_apply_diagnostic_fix(before, after); | ||
463 | } | ||
464 | |||
465 | #[test] | ||
466 | fn test_fill_struct_fields_partial() { | ||
467 | let before = r" | ||
468 | struct TestStruct { | ||
469 | one: i32, | ||
470 | two: i64, | ||
471 | } | ||
472 | |||
473 | fn test_fn() { | ||
474 | let s = TestStruct{ two: 2 }; | ||
475 | } | ||
476 | "; | ||
477 | let after = r" | ||
478 | struct TestStruct { | ||
479 | one: i32, | ||
480 | two: i64, | ||
481 | } | ||
482 | |||
483 | fn test_fn() { | ||
484 | let s = TestStruct{ two: 2, one: () }; | ||
485 | } | ||
486 | "; | ||
487 | check_apply_diagnostic_fix(before, after); | ||
488 | } | ||
489 | |||
490 | #[test] | ||
491 | fn test_fill_struct_fields_no_diagnostic() { | ||
492 | let content = r" | ||
493 | struct TestStruct { | ||
494 | one: i32, | ||
495 | two: i64, | ||
496 | } | ||
497 | |||
498 | fn test_fn() { | ||
499 | let one = 1; | ||
500 | let s = TestStruct{ one, two: 2 }; | ||
501 | } | ||
502 | "; | ||
503 | |||
504 | check_no_diagnostic(content); | ||
505 | } | ||
506 | |||
507 | #[test] | ||
508 | fn test_fill_struct_fields_no_diagnostic_on_spread() { | ||
509 | let content = r" | ||
510 | struct TestStruct { | ||
511 | one: i32, | ||
512 | two: i64, | ||
513 | } | ||
514 | |||
515 | fn test_fn() { | ||
516 | let one = 1; | ||
517 | let s = TestStruct{ ..a }; | ||
518 | } | ||
519 | "; | ||
520 | |||
521 | check_no_diagnostic(content); | ||
522 | } | ||
523 | |||
524 | #[test] | ||
525 | fn test_unresolved_module_diagnostic() { | ||
526 | let (analysis, file_id) = single_file("mod foo;"); | ||
527 | let diagnostics = analysis.diagnostics(file_id).unwrap(); | ||
528 | assert_debug_snapshot!(diagnostics, @r###" | ||
529 | [ | ||
530 | Diagnostic { | ||
531 | message: "unresolved module", | ||
532 | range: [0; 8), | ||
533 | fix: Some( | ||
534 | SourceChange { | ||
535 | label: "create module", | ||
536 | source_file_edits: [], | ||
537 | file_system_edits: [ | ||
538 | CreateFile { | ||
539 | source_root: SourceRootId( | ||
540 | 0, | ||
541 | ), | ||
542 | path: "foo.rs", | ||
543 | }, | ||
544 | ], | ||
545 | cursor_position: None, | ||
546 | }, | ||
547 | ), | ||
548 | severity: Error, | ||
549 | }, | ||
550 | ] | ||
551 | "###); | ||
552 | } | ||
553 | |||
554 | #[test] | ||
555 | fn test_check_unnecessary_braces_in_use_statement() { | ||
556 | check_not_applicable( | ||
557 | " | ||
558 | use a; | ||
559 | use a::{c, d::e}; | ||
560 | ", | ||
561 | check_unnecessary_braces_in_use_statement, | ||
562 | ); | ||
563 | check_apply("use {b};", "use b;", check_unnecessary_braces_in_use_statement); | ||
564 | check_apply("use a::{c};", "use a::c;", check_unnecessary_braces_in_use_statement); | ||
565 | check_apply("use a::{self};", "use a;", check_unnecessary_braces_in_use_statement); | ||
566 | check_apply( | ||
567 | "use a::{c, d::{e}};", | ||
568 | "use a::{c, d::e};", | ||
569 | check_unnecessary_braces_in_use_statement, | ||
570 | ); | ||
571 | } | ||
572 | |||
573 | #[test] | ||
574 | fn test_check_struct_shorthand_initialization() { | ||
575 | check_not_applicable( | ||
576 | r#" | ||
577 | struct A { | ||
578 | a: &'static str | ||
579 | } | ||
580 | |||
581 | fn main() { | ||
582 | A { | ||
583 | a: "hello" | ||
584 | } | ||
585 | } | ||
586 | "#, | ||
587 | check_struct_shorthand_initialization, | ||
588 | ); | ||
589 | |||
590 | check_apply( | ||
591 | r#" | ||
592 | struct A { | ||
593 | a: &'static str | ||
594 | } | ||
595 | |||
596 | fn main() { | ||
597 | let a = "haha"; | ||
598 | A { | ||
599 | a: a | ||
600 | } | ||
601 | } | ||
602 | "#, | ||
603 | r#" | ||
604 | struct A { | ||
605 | a: &'static str | ||
606 | } | ||
607 | |||
608 | fn main() { | ||
609 | let a = "haha"; | ||
610 | A { | ||
611 | a | ||
612 | } | ||
613 | } | ||
614 | "#, | ||
615 | check_struct_shorthand_initialization, | ||
616 | ); | ||
617 | |||
618 | check_apply( | ||
619 | r#" | ||
620 | struct A { | ||
621 | a: &'static str, | ||
622 | b: &'static str | ||
623 | } | ||
624 | |||
625 | fn main() { | ||
626 | let a = "haha"; | ||
627 | let b = "bb"; | ||
628 | A { | ||
629 | a: a, | ||
630 | b | ||
631 | } | ||
632 | } | ||
633 | "#, | ||
634 | r#" | ||
635 | struct A { | ||
636 | a: &'static str, | ||
637 | b: &'static str | ||
638 | } | ||
639 | |||
640 | fn main() { | ||
641 | let a = "haha"; | ||
642 | let b = "bb"; | ||
643 | A { | ||
644 | a, | ||
645 | b | ||
646 | } | ||
647 | } | ||
648 | "#, | ||
649 | check_struct_shorthand_initialization, | ||
650 | ); | ||
651 | } | ||
652 | } | ||