1use log::{debug, info};
2use proc_macro2::Span;
3use quote::ToTokens;
4use regex::Regex;
5use rem_utils::format_source;
6use serde::{Deserialize, Serialize};
7use std::borrow::BorrowMut;
8use std::collections::HashMap;
9use std::fs;
10use std::io::{BufWriter, Write};
11use std::process::Command;
12use syn::{
13 visit_mut::VisitMut, ExprCall, ExprMethodCall, FnArg, GenericArgument, GenericParam,
14 ItemFn, Lifetime, PredicateLifetime, ReturnType, Signature,
15 TypeReference, WhereClause, WherePredicate,
16};
17use std::fmt::{
18 self,
19 Debug,
20 Formatter
21};
22
23pub struct RepairerInput {
24 pub input_code: String,
25 pub fn_name: String,
26 pub repair_systems: Vec<Box<dyn RepairSystem>>,
27}
28
29impl Clone for RepairerInput {
30 fn clone(&self) -> Self {
31 Self {
32 input_code: self.input_code.clone(),
33 fn_name: self.fn_name.clone(),
34 repair_systems: self.repair_systems.iter().map(|system| system.clone()).collect(),
35 }
36 }
37}
38
39pub struct RepairResult {
43 pub success: bool,
44 pub repair_count: i32,
45 #[allow(dead_code)]
46 pub has_non_elidible_lifetime: bool,
47 #[allow(dead_code)]
48 pub has_struct_lt: bool,
49}
50
51pub trait RepairSystem: std::fmt::Debug {
52 fn name(&self) -> &str;
53 fn repair_project(&self, src_path: &str, manifest_path: &str, fn_name: &str) -> RepairResult;
54 fn repair_file(&self, file_name: &str, new_file_name: &str) -> RepairResult;
55 fn repair_function(&self, file_name: &str, new_file_name: &str, fn_name: &str) -> RepairResult;
56 fn clone_box(&self) -> Box<dyn RepairSystem>;
57}
58
59impl Clone for Box<dyn RepairSystem> {
60 fn clone(&self) -> Box<dyn RepairSystem> {
61 self.clone_box()
62 }
63}
64
65pub struct DebugRepairSystem<'a>(pub &'a dyn RepairSystem);
67
68impl<'a> Debug for DebugRepairSystem<'a> {
69 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
70 f.debug_struct("RepairSystem")
71 .field("name", &self.0.name())
72 .finish()
73 }
74}
75
76#[derive(Serialize, Deserialize, Debug)]
77pub struct RustcError {
78 pub rendered: String,
79 pub spans: Vec<RustcSpan>,
80}
81
82#[derive(Serialize, Deserialize, Debug)]
83pub struct RustcSpan {
84 pub file_name: String,
85}
86
87pub fn repair_standard_help(stderr: &str, new_file_name: &str) -> bool {
88 let deserializer = serde_json::Deserializer::from_str(stderr);
89 let stream = deserializer.into_iter::<RustcError>();
90 let mut helped = false;
91 for item in stream {
92 let rendered = match item {
93 Ok(i) => i.rendered,
94 Err(_) => stderr.to_string(),
95 };
96 let re = Regex::new(r"help: consider.+\n.*\n(?P<line_number>\d+) \| (?P<replacement>.+)\n")
97 .unwrap();
98 let help_lines = re.captures_iter(rendered.as_str());
99
100 let file_content: String = fs::read_to_string(&new_file_name).unwrap().parse().unwrap();
101
102 let lines = file_content.split("\n");
103 let mut lines_modifiable = Vec::new();
104 for (_, line) in lines.enumerate() {
105 lines_modifiable.push(line);
106 }
107
108 let mut current_line = 0;
109
110 let out_file = fs::File::create(&new_file_name).unwrap();
111 let mut writer = BufWriter::new(out_file);
112 for captured in help_lines {
113 let line_number = match captured["line_number"].parse::<usize>() {
121 Ok(n) => n,
122 Err(_) => continue,
123 };
124 let replacement = &captured["replacement"];
125 if replacement.contains("&'lifetime") {
126 continue;
127 }
128
129 helped = true;
130 while current_line < line_number - 1 {
131 writeln!(writer, "{}", lines_modifiable[current_line]).unwrap();
132 current_line += 1;
133 }
134 writeln!(writer, "{}", replacement).unwrap();
135 current_line += 1;
136 }
137 while current_line < lines_modifiable.len() {
138 writeln!(writer, "{}", lines_modifiable[current_line]).unwrap();
139 current_line += 1;
140 }
141 }
142 helped
143}
144
145struct FnLifetimeBounder<'a> {
146 fn_name: &'a str,
147 lifetime: &'a str,
148 bound: &'a str,
149 success: bool,
150}
151
152impl VisitMut for FnLifetimeBounder<'_> {
153 fn visit_impl_item_fn_mut(&mut self, i: &mut syn::ImplItemFn) {
154 let id = i.sig.ident.to_string();
155 match id == self.fn_name.to_string() {
157 false => (),
158 true => self.fn_lifetime_bounder(&mut i.sig),
159 }
160 syn::visit_mut::visit_impl_item_fn_mut(self, i);
161 }
162
163 fn visit_item_fn_mut(&mut self, i: &mut ItemFn) {
164 let id = i.sig.ident.to_string();
165 match id == self.fn_name.to_string() {
166 false => (),
167 true => self.fn_lifetime_bounder(&mut i.sig),
168 }
169 }
170
171 fn visit_trait_item_fn_mut(&mut self, i: &mut syn::TraitItemFn) {
172 let id = i.sig.ident.to_string();
173 match id == self.fn_name.to_string() {
175 false => (),
176 true => self.fn_lifetime_bounder(&mut i.sig),
177 }
178 syn::visit_mut::visit_trait_item_fn_mut(self, i);
179 }
180}
181
182impl FnLifetimeBounder<'_> {
183 fn fn_lifetime_bounder(&mut self, sig: &mut Signature) {
184 let gen = &mut sig.generics;
185 let wc = gen.where_clause.get_or_insert(WhereClause {
186 where_token: Default::default(),
187 predicates: Default::default(),
188 });
189 let mut wp = PredicateLifetime {
190 lifetime: Lifetime::new(self.lifetime, Span::call_site()),
191 colon_token: Default::default(),
192 bounds: Default::default(),
193 };
194 wp.bounds.push(Lifetime::new(self.bound, Span::call_site()));
195 wc.predicates.push(WherePredicate::Lifetime(wp));
196 self.success = true
197 }
198}
199
200pub fn repair_bounds_help(stderr: &str, new_file_name: &str, fn_name: &str) -> bool {
201 let deserializer = serde_json::Deserializer::from_str(stderr);
202 let stream = deserializer.into_iter::<RustcError>();
203 let mut helped = false;
204 for item in stream {
205 let rendered = match item {
206 Ok(i) => i.rendered,
207 Err(_) => stderr.to_string(),
208 };
209 let re = Regex::new(r"= help: consider.+bound: `(?P<constraint_lhs>'[a-z0-9]+): (?P<constraint_rhs>'[a-z0-9]+)`").unwrap();
210 let help_lines = re.captures_iter(rendered.as_str());
211 for captured in help_lines {
218 let file_content: String = fs::read_to_string(&new_file_name).unwrap().parse().unwrap();
222 let mut file = syn::parse_str::<syn::File>(file_content.as_str())
223 .map_err(|e| format!("{:?}", e))
224 .unwrap();
225 let mut visit = FnLifetimeBounder {
226 fn_name,
227 lifetime: &captured["constraint_lhs"],
228 bound: &captured["constraint_rhs"],
229 success: false,
230 };
231 visit.visit_file_mut(&mut file);
232 let file = file.into_token_stream().to_string();
233 match visit.success {
234 true => {
235 fs::write(new_file_name.to_string(), format_source(&file)).unwrap();
236 helped = true;
237 }
238 false => (),
239 }
240 }
241 }
242 helped
243}
244
245pub fn repair_iteration(
246 compile_cmd: &mut Command,
247 process_errors: &dyn Fn(&str) -> bool,
248 print_stats: bool,
249 max_iterations: Option<i32>,
250) -> RepairResult {
251 let mut count = 0;
252 let max_iterations = max_iterations.unwrap_or(25);
253 let mut repair_result = RepairResult {
254 success: false,
255 repair_count: 0,
256 has_non_elidible_lifetime: false,
257 has_struct_lt: false,
258 };
259
260 let success = loop {
261 let out = compile_cmd.output().unwrap();
262 let stderr = String::from_utf8_lossy(&out.stderr);
263 if out.status.success() {
264 break true;
265 }
266 count += 1;
267
268 let temp = stderr.to_string();
269 if !process_errors(temp.as_str()) {
270 break false;
271 }
272 if max_iterations == count {
273 break false;
274 }
275 };
276
277 if print_stats {
278 info!("repair count: {}", count);
279 info!("status: {}", success);
280 }
281
282 repair_result.success = success;
283 repair_result.repair_count = count;
284 repair_result
285}
286
287struct FnLifetimeEliderTypeHelper<'a> {
291 cannot_elide: &'a Vec<String>,
292 lt_count: &'a HashMap<&'a String, i32>,
293}
294
295impl VisitMut for FnLifetimeEliderTypeHelper<'_> {
296 fn visit_type_reference_mut(&mut self, i: &mut TypeReference) {
323 match &mut i.lifetime {
324 None => (),
325 Some(lt) => {
326 let id = lt.to_string();
327 if !self.cannot_elide.contains(&id)
328 && (!self.lt_count.contains_key(&id) || *self.lt_count.get(&id).unwrap() <= 1)
329 {
330 i.lifetime = None
331 }
332 }
333 };
334 syn::visit_mut::visit_type_reference_mut(self, i);
335 }
336}
337
338struct FnLifetimeEliderArgHelper<'a> {
339 cannot_elide: &'a Vec<String>,
340 lt_count: &'a HashMap<&'a String, i32>,
341}
342
343impl VisitMut for FnLifetimeEliderArgHelper<'_> {
344 fn visit_fn_arg_mut(&mut self, i: &mut FnArg) {
345 match i {
346 FnArg::Typed(t) => {
347 let mut type_helper = FnLifetimeEliderTypeHelper {
348 cannot_elide: self.cannot_elide,
349 lt_count: self.lt_count,
350 };
351 type_helper.visit_type_mut(t.ty.as_mut());
352 }
353 FnArg::Receiver(_) => (), }
355 }
356}
357
358struct FnLifetimeElider<'a> {
359 fn_name: &'a str,
360 annotations_left: bool,
361 has_struct_lt: bool,
362}
363
364struct LtGetterElider<'a> {
365 v: &'a mut Vec<String>,
366}
367impl VisitMut for LtGetterElider<'_> {
368 fn visit_lifetime_mut(&mut self, i: &mut Lifetime) {
369 let id = i.to_string();
370 self.v.push(id.to_string());
371 syn::visit_mut::visit_lifetime_mut(self, i)
372 }
373}
374
375struct ChangeLtHelperElider<'a> {
376 map: &'a HashMap<String, String>,
377 has_struct_lt: bool,
378}
379
380impl VisitMut for ChangeLtHelperElider<'_> {
381 fn visit_generic_argument_mut(&mut self, i: &mut GenericArgument) {
382 debug!("generic: {:?}", i);
383 match i {
384 GenericArgument::Lifetime(l) => {
385 let id = l.to_string();
386 debug!("generic lt: {:?}", &id);
387 self.has_struct_lt = true;
388 match self.map.get(&id) {
389 Some(new_lt) => *l = Lifetime::new(new_lt.as_str(), Span::call_site()),
390 None => *l = Lifetime::new("'_", Span::call_site()),
391 }
392 }
393 _ => syn::visit_mut::visit_generic_argument_mut(self, i),
394 }
395 }
396 fn visit_lifetime_mut(&mut self, i: &mut Lifetime) {
397 let id = i.to_string();
398 match self.map.get(&id) {
399 Some(new_lt) => *i = Lifetime::new(new_lt.as_str(), Span::call_site()),
400 None => (),
401 }
402 syn::visit_mut::visit_lifetime_mut(self, i)
403 }
404}
405
406impl VisitMut for FnLifetimeElider<'_> {
407 fn visit_impl_item_fn_mut(&mut self, i: &mut syn::ImplItemFn) {
408 let id = i.sig.ident.to_string();
409 match id == self.fn_name.to_string() {
411 false => (),
412 true => self.fn_lifetime_elider(&mut i.sig),
413 }
414 syn::visit_mut::visit_impl_item_fn_mut(self, i);
415 }
416
417 fn visit_item_fn_mut(&mut self, i: &mut ItemFn) {
418 let id = i.sig.ident.to_string();
419 match id == self.fn_name.to_string() {
420 false => (),
421 true => self.fn_lifetime_elider(&mut i.sig),
422 }
423 }
424
425 fn visit_trait_item_fn_mut(&mut self, i: &mut syn::TraitItemFn) {
426 let id = i.sig.ident.to_string();
427 match id == self.fn_name.to_string() {
429 false => (),
430 true => self.fn_lifetime_elider(&mut i.sig),
431 }
432 syn::visit_mut::visit_trait_item_fn_mut(self, i);
433 }
434}
435
436impl FnLifetimeElider<'_> {
437 fn fn_lifetime_elider(&mut self, sig: &mut Signature) {
438 let gen = &mut sig.generics;
440 let mut cannot_elide = vec![];
441 match &gen.where_clause {
442 None => (),
443 Some(wc) => wc.predicates.iter().for_each(|wp| match wp {
444 WherePredicate::Lifetime(lt) => {
445 cannot_elide.push(lt.lifetime.to_string());
446 cannot_elide.push(lt.bounds.first().unwrap().to_string())
447 }
448 _ => (),
449 }),
450 }
451 match sig.output.borrow_mut() {
452 ReturnType::Default => (),
453 ReturnType::Type(_, ty) => {
454 let mut get_lt = LtGetterElider {
455 v: &mut cannot_elide,
456 };
457 get_lt.visit_type_mut(ty.clone().as_mut());
458 }
459 };
460
461 let inputs = &mut sig.inputs;
462 let mut has_receiver = false;
463 let mut map = HashMap::new();
464 let mut v = vec![];
465 inputs.iter_mut().for_each(|fn_arg| {
466 match fn_arg {
467 FnArg::Receiver(_) => has_receiver = true,
468 FnArg::Typed(_) => {
469 let mut get_lt = LtGetterElider { v: &mut v };
470 get_lt.visit_fn_arg_mut(fn_arg)
471 }
472 };
473 });
474 match has_receiver {
475 true => (),
476 false => {
477 match sig.output.borrow_mut() {
478 ReturnType::Default => (),
479 ReturnType::Type(_, ty) => {
480 let mut get_lt = LtGetterElider { v: &mut v };
481 get_lt.visit_type_mut(ty.clone().as_mut());
482 }
483 };
484 gen.params.iter_mut().for_each(|gp| match gp {
485 GenericParam::Lifetime(_) => (),
486 gp => {
487 let mut get_lt = LtGetterElider { v: &mut v };
488 get_lt.visit_generic_param_mut(gp);
489 }
490 });
491 v.iter().for_each(|lt| {
492 match map.contains_key(lt) {
493 true => map.insert(lt, *map.get(lt).unwrap() + 1),
494 false => map.insert(lt, 1),
495 };
496 });
497 let mut fn_arg_helper = FnLifetimeEliderArgHelper {
498 cannot_elide: &cannot_elide,
499 lt_count: &map,
500 };
501 inputs
502 .iter_mut()
503 .for_each(|fn_arg| fn_arg_helper.visit_fn_arg_mut(fn_arg));
504
505 match sig.output.borrow_mut() {
506 ReturnType::Default => (),
507 ReturnType::Type(_, ty) => {
508 let mut type_helper = FnLifetimeEliderTypeHelper {
509 cannot_elide: &cannot_elide,
510 lt_count: &map,
511 };
512 type_helper.visit_type_mut(ty.as_mut());
513 }
514 };
515 gen.params.iter_mut().for_each(|gp| match gp {
516 GenericParam::Lifetime(_) => (),
517 gp => {
518 let mut type_helper = FnLifetimeEliderTypeHelper {
519 cannot_elide: &cannot_elide,
520 lt_count: &map,
521 };
522 type_helper.visit_generic_param_mut(gp);
523 }
524 });
525 gen.params = gen
526 .params
527 .iter()
528 .cloned()
529 .filter(|g| match g {
530 GenericParam::Lifetime(lt) => {
531 let id = lt.lifetime.to_string();
532 if !map.contains_key(&id) {
533 false
534 } else {
535 let result =
536 *map.get(&id).unwrap() > 1 || cannot_elide.contains(&id);
537 debug!("lt: {}, result: {}", id, result);
538 result
539 }
540 }
541 _ => true,
542 })
543 .collect();
544
545 let mut lt_count = 0;
546 let mut new_lts = HashMap::new();
547 gen.params.iter_mut().for_each(|gp| match gp {
548 GenericParam::Lifetime(lt) => {
549 let id = lt.lifetime.to_string();
550 self.annotations_left = true;
551 new_lts.insert(id, format!("'lt{}", lt_count));
552 lt.lifetime =
553 Lifetime::new(format!("'lt{}", lt_count).as_str(), Span::call_site());
554 lt_count += 1
555 }
556 _ => (),
557 });
558 gen.params.iter_mut().for_each(|gp| match gp {
559 GenericParam::Lifetime(_) => (),
560 gp => {
561 let mut change_lt = ChangeLtHelperElider {
562 map: &new_lts,
563 has_struct_lt: false,
564 };
565 if change_lt.has_struct_lt {
566 self.has_struct_lt = true;
567 }
568 change_lt.visit_generic_param_mut(gp);
569 }
570 });
571 match &mut gen.where_clause {
572 None => (),
573 Some(wc) => wc.predicates.iter_mut().for_each(|wp| match wp {
574 WherePredicate::Lifetime(lt) => {
575 let id = lt.lifetime.to_string();
576 match new_lts.get(&id) {
577 Some(new_lt) => {
578 lt.lifetime = Lifetime::new(new_lt.as_str(), Span::call_site())
579 }
580 None => (),
581 };
582 lt.bounds.iter_mut().for_each(|bound| {
583 let id = bound.to_string();
584 match new_lts.get(&id) {
585 Some(new_lt) => {
586 *bound = Lifetime::new(new_lt.as_str(), Span::call_site())
587 }
588 None => (),
589 }
590 })
591 }
592 _ => (),
593 }),
594 }
595 inputs.iter_mut().for_each(|fn_arg| match fn_arg {
596 FnArg::Receiver(_) => (),
597 FnArg::Typed(t) => {
598 let mut change_lt = ChangeLtHelperElider {
599 map: &new_lts,
600 has_struct_lt: false,
601 };
602 if change_lt.has_struct_lt {
603 self.has_struct_lt = true;
604 }
605 debug!("debugging input: {:?}", t);
606 change_lt.visit_pat_type_mut(t);
607 }
608 });
609 match sig.output.borrow_mut() {
610 ReturnType::Default => (),
611 ReturnType::Type(_, ty) => {
612 let mut change_lt = ChangeLtHelperElider {
613 map: &new_lts,
614 has_struct_lt: false,
615 };
616 if change_lt.has_struct_lt {
617 self.has_struct_lt = true;
618 }
619 change_lt.visit_type_mut(ty.as_mut());
620 }
621 }
622 }
623 }
624 }
625}
626
627pub struct ElideLifetimeResult {
628 #[allow(dead_code)]
629 pub success: bool,
630 pub annotations_left: bool,
631 pub has_struct_lt: bool,
632}
633
634pub fn elide_lifetimes_annotations(new_file_name: &str, fn_name: &str) -> ElideLifetimeResult {
642 let file_content: String = fs::read_to_string(&new_file_name).unwrap().parse().unwrap();
643 let mut file = syn::parse_str::<syn::File>(file_content.as_str())
644 .map_err(|e| format!("{:?}", e))
645 .unwrap();
646 let mut visit = FnLifetimeElider {
647 fn_name,
648 annotations_left: false,
649 has_struct_lt: false,
650 };
651 visit.visit_file_mut(&mut file);
652 let file = file.into_token_stream().to_string();
653 fs::write(new_file_name.to_string(), format_source(&file)).unwrap();
654 ElideLifetimeResult {
655 success: true,
656 annotations_left: visit.annotations_left,
657 has_struct_lt: visit.has_struct_lt,
658 }
659}
660
661pub struct RenameFn<'a> {
665 pub(crate) callee_name: &'a str,
666 pub(crate) callee_postfix: &'a str,
667}
668
669impl VisitMut for RenameFn<'_> {
670 fn visit_expr_method_call_mut(&mut self, i: &mut ExprMethodCall) {
671 let callee = i.clone().method.into_token_stream().to_string();
672 match callee.contains(self.callee_name) {
673 true => {
674 i.method = syn::parse_str(callee.replace(self.callee_postfix, "").as_str()).unwrap()
675 }
676 false => syn::visit_mut::visit_expr_method_call_mut(self, i),
677 }
678 }
679
680 fn visit_expr_call_mut(&mut self, i: &mut ExprCall) {
681 let callee = i.func.as_ref().into_token_stream().to_string();
682 match callee.contains(self.callee_name) {
683 true => {
684 debug!("callee: {} matched", &callee);
685 *i.func.as_mut() =
686 syn::parse_str(callee.replace(self.callee_postfix, "").as_str()).unwrap();
687 }
688 false => {}
689 }
690 syn::visit_mut::visit_expr_call_mut(self, i);
691 }
692 fn visit_impl_item_fn_mut(&mut self, i: &mut syn::ImplItemFn) {
693 let callee = i.sig.ident.to_string();
694 match callee.contains(self.callee_name) {
695 true => {
696 i.sig.ident =
697 syn::parse_str(callee.replace(self.callee_postfix, "").as_str()).unwrap();
698 }
699 false => {}
700 }
701 syn::visit_mut::visit_impl_item_fn_mut(self, i);
702 }
703
704 fn visit_item_fn_mut(&mut self, i: &mut ItemFn) {
705 let callee = i.sig.ident.to_string();
706 match callee.contains(self.callee_name) {
707 true => {
708 i.sig.ident =
709 syn::parse_str(callee.replace(self.callee_postfix, "").as_str()).unwrap();
710 }
711 false => {}
712 }
713 syn::visit_mut::visit_item_fn_mut(self, i);
714 }
715 fn visit_trait_item_fn_mut(&mut self, i: &mut syn::TraitItemFn) {
716 let callee = i.sig.ident.to_string();
717 match callee.contains(self.callee_name) {
718 true => {
719 i.sig.ident =
720 syn::parse_str(callee.replace(self.callee_postfix, "").as_str()).unwrap();
721 }
722 false => {}
723 }
724 syn::visit_mut::visit_trait_item_fn_mut(self, i);
725 }
726}
727
728pub fn callee_renamer(new_file_name: &str, fn_name: &str) {
729 let file_content: String = fs::read_to_string(&new_file_name).unwrap().parse().unwrap();
730 let mut file = syn::parse_str::<syn::File>(file_content.as_str())
731 .map_err(|e| format!("{:?}", e))
732 .unwrap();
733 let mut visitor = RenameFn {
734 callee_name: fn_name,
735 callee_postfix: "____EXTRACT_THIS",
736 };
737 visitor.visit_file_mut(&mut file);
738 let file = file.into_token_stream().to_string();
739 fs::write(new_file_name.to_string(), format_source(&file)).unwrap()
740}
741
742#[derive(Serialize, Deserialize, Debug)]
746pub struct CargoError {
747 pub message: Option<RustcError>,
748}
749
750pub fn repair_iteration_project(
751 compile_cmd: &mut Command,
752 src_path: &str,
753 process_errors: &dyn Fn(&RustcError) -> bool,
754 print_stats: bool,
755 max_iterations: Option<i32>,
756) -> RepairResult {
757 let mut count = 0;
758 let max_iterations = max_iterations.unwrap_or(25);
759 let mut repair_result = RepairResult {
760 success: false,
761 repair_count: 0,
762 has_non_elidible_lifetime: false,
763 has_struct_lt: false,
764 };
765 let success = loop {
766 let out = compile_cmd.output().unwrap();
767 if out.status.success() {
768 info!("repair succeeded");
769 break true;
770 }
771 let stdout = String::from_utf8_lossy(&out.stdout);
773 let binding = stdout.to_string();
774 let deserializer = serde_json::Deserializer::from_str(binding.as_str());
775 let stream = deserializer.into_iter::<CargoError>();
776 count += 1;
777
778 let mut help = false;
779 let mut last_failure = format!("");
780 for item in stream {
781 match &item {
782 Ok(item) => match &item.message {
783 None => {}
784 Some(message) => {
785 let spans = &message.spans;
786 debug!("message: {:?}", &message);
787 for span in spans {
788 if src_path.contains(&span.file_name) {
789 debug!("processing error: {}", &message.rendered);
790 last_failure = message.rendered.clone();
791 if process_errors(&message) {
792 help = true;
793 break;
794 }
795 }
796 }
797 }
798 },
799 Err(e) => {
800 debug!("error parsing cargo error:\n{}", e);
801 }
802 }
803 }
804
805 if !help {
806 debug!("last failure:\n{}", last_failure);
807 break false;
808 }
809
810 if max_iterations == count {
811 debug!("last failure:\n{}", last_failure);
812 break false;
813 }
814 };
815
816 if print_stats {
817 info!("repair count: {}", count);
818 info!("status: {}", success);
819 }
820
821 repair_result.success = success;
822 repair_result.repair_count = count;
823 repair_result
824}