rem_repairer/
repair_lifetime_tightest_bound_first.rs

1use proc_macro2::Span;
2use quote::ToTokens;
3use regex::Regex;
4
5use log::debug;
6use std::fs;
7use syn::{visit_mut::VisitMut, FnArg, Lifetime, LifetimeParam, Type};
8
9use crate::common::{
10    callee_renamer, elide_lifetimes_annotations, repair_bounds_help, repair_iteration,
11    repair_iteration_project, RepairResult, RepairSystem, RustcError,
12};
13use crate::repair_lifetime_simple;
14use rem_utils::{check_project, compile_file, format_source};
15
16#[derive(Debug, Clone)]
17pub struct Repairer {}
18
19impl RepairSystem for Repairer {
20    fn name(&self) -> &str {
21        "_tightest_bounds_first_repairer"
22    }
23
24    fn repair_project(&self, src_path: &str, manifest_path: &str, fn_name: &str) -> RepairResult {
25        annotate_tight_named_lifetime(src_path, fn_name);
26        let mut compile_cmd = check_project(manifest_path, &vec![]);
27        let process_errors = |ce: &RustcError| {
28            if repair_bounds_help(ce.rendered.as_str(), src_path, fn_name) {
29                true
30            } else {
31                loosen_bounds(ce.rendered.as_str(), src_path, fn_name)
32            }
33        };
34        match repair_iteration_project(&mut compile_cmd, src_path, &process_errors, true, Some(50))
35        {
36            RepairResult {
37                success: true,
38                repair_count,
39                ..
40            } => {
41                debug!("pre elision: {}", fs::read_to_string(&src_path).unwrap());
42                let elide_res = elide_lifetimes_annotations(src_path, fn_name);
43                callee_renamer(src_path, fn_name);
44                RepairResult {
45                    success: true,
46                    repair_count,
47                    has_non_elidible_lifetime: elide_res.annotations_left,
48                    has_struct_lt: elide_res.has_struct_lt,
49                }
50            }
51            result => result,
52        }
53    }
54
55    fn repair_file(&self, file_name: &str, new_file_name: &str) -> RepairResult {
56        repair_lifetime_simple::Repairer {}.repair_file(file_name, new_file_name)
57    }
58
59    fn repair_function(&self, file_name: &str, new_file_name: &str, fn_name: &str) -> RepairResult {
60        fs::copy(file_name, &new_file_name).unwrap();
61        annotate_tight_named_lifetime(&new_file_name, fn_name);
62        //println!("annotated: {}", fs::read_to_string(&new_file_name).unwrap());
63        let args: Vec<&str> = vec!["--error-format=json"];
64
65        let mut compile_cmd = compile_file(&new_file_name, &args);
66
67        let process_errors = |stderr: &str| {
68            if repair_bounds_help(stderr, new_file_name, fn_name) {
69                true
70            } else {
71                loosen_bounds(stderr, new_file_name, fn_name)
72            }
73        };
74
75        match repair_iteration(&mut compile_cmd, &process_errors, true, Some(50)) {
76            RepairResult {
77                success: true,
78                repair_count,
79                ..
80            } => {
81                // println!("repaired: {}", fs::read_to_string(&new_file_name).unwrap());
82                let elide_res = elide_lifetimes_annotations(new_file_name, fn_name);
83                RepairResult {
84                    success: true,
85                    repair_count,
86                    has_non_elidible_lifetime: elide_res.annotations_left,
87                    has_struct_lt: elide_res.has_struct_lt,
88                }
89            }
90            result => result,
91        }
92    }
93
94    fn clone_box(&self) -> Box<dyn RepairSystem> {
95        Box::new((*self).clone())
96    }
97}
98
99struct TightLifetimeAnnotatorTypeHelper {}
100
101impl VisitMut for TightLifetimeAnnotatorTypeHelper {
102    fn visit_type_mut(&mut self, i: &mut Type) {
103        match i {
104            Type::Reference(r) => {
105                r.lifetime = Some(Lifetime::new("'lt0", Span::call_site()));
106                self.visit_type_mut(r.elem.as_mut());
107            }
108            _ => (),
109        }
110    }
111}
112
113struct TightLifetimeAnnotatorFnArgHelper {}
114
115impl VisitMut for TightLifetimeAnnotatorFnArgHelper {
116    fn visit_fn_arg_mut(&mut self, i: &mut FnArg) {
117        match i {
118            FnArg::Receiver(r) => match &mut r.reference {
119                None => {}
120                Some((_, lt)) => {
121                    *lt = Some(Lifetime::new("'lt0", Span::call_site()));
122                }
123            },
124            FnArg::Typed(t) => {
125                let mut type_helper = TightLifetimeAnnotatorTypeHelper {};
126                type_helper.visit_type_mut(t.ty.as_mut())
127            }
128        }
129    }
130}
131
132struct TightLifetimeAnnotator<'a> {
133    fn_name: &'a str,
134    success: bool,
135}
136
137impl VisitMut for TightLifetimeAnnotator<'_> {
138    fn visit_item_fn_mut(&mut self, i: &mut syn::ItemFn) {
139        let id = i.sig.ident.to_string();
140        match id == self.fn_name.to_string() {
141            false => (),
142            true => match (&mut i.sig.inputs, &mut i.sig.generics, &mut i.sig.output) {
143                (inputs, _, _) if inputs.len() == 0 => self.success = true,
144                (_, gen, _)
145                    if gen.params.iter().any(|x| match x {
146                        syn::GenericParam::Lifetime(_) => true,
147                        _ => false,
148                    }) =>
149                {
150                    self.success = false
151                }
152                (inputs, gen, out) => {
153                    let lifetime = Lifetime::new("'lt0", Span::call_site());
154                    gen.params.push(syn::GenericParam::Lifetime(LifetimeParam {
155                        attrs: vec![],
156                        lifetime,
157                        colon_token: None,
158                        bounds: Default::default(),
159                    }));
160                    inputs.iter_mut().for_each(|arg| {
161                        let mut fn_arg_helper = TightLifetimeAnnotatorFnArgHelper {};
162                        fn_arg_helper.visit_fn_arg_mut(arg)
163                    });
164                    match out {
165                        syn::ReturnType::Type(_, ty) => match ty.as_mut() {
166                            Type::Reference(r) => {
167                                r.lifetime = Some(Lifetime::new("'lt0", Span::call_site()))
168                            }
169                            _ => (),
170                        },
171                        _ => (),
172                    };
173                    self.success = true
174                }
175            },
176        }
177    }
178}
179
180pub fn annotate_tight_named_lifetime(new_file_name: &str, fn_name: &str) -> bool {
181    let file_content: String = fs::read_to_string(&new_file_name).unwrap().parse().unwrap();
182    let mut file = syn::parse_str::<syn::File>(file_content.as_str())
183        .map_err(|e| format!("{:?}", e))
184        .unwrap();
185    let mut visit = TightLifetimeAnnotator {
186        fn_name,
187        success: false,
188    };
189    visit.visit_file_mut(&mut file);
190    let file = file.into_token_stream().to_string();
191    match visit.success {
192        true => {
193            fs::write(new_file_name.to_string(), format_source(&file)).unwrap();
194            true
195        }
196        false => false,
197    }
198}
199
200struct BoundsLoosener<'a> {
201    fn_name: &'a str,
202    arg_name: &'a str,
203    success: bool,
204}
205
206struct ArgBoundLoosener<'a> {
207    arg_name: &'a str,
208    lt: &'a str,
209    success: bool,
210}
211
212impl VisitMut for ArgBoundLoosener<'_> {
213    fn visit_fn_arg_mut(&mut self, i: &mut FnArg) {
214        match i {
215            FnArg::Receiver(_) => (), // don't modify receiver yet (&self)
216            FnArg::Typed(t) => match t.pat.as_mut() {
217                syn::Pat::Ident(id) if id.ident.to_string() == self.arg_name => {
218                    match t.ty.as_mut() {
219                        Type::Reference(r) => {
220                            r.lifetime = Some(Lifetime::new(self.lt, Span::call_site()));
221                            self.success = true
222                        }
223                        _ => (),
224                    }
225                }
226                _ => (),
227            },
228        }
229    }
230}
231
232impl VisitMut for BoundsLoosener<'_> {
233    fn visit_item_fn_mut(&mut self, i: &mut syn::ItemFn) {
234        let id = i.sig.ident.to_string();
235        match id == self.fn_name.to_string() {
236            false => (),
237            true => {
238                let mut lt_count = 0;
239                let gen = &mut i.sig.generics;
240                for i in &gen.params {
241                    match i {
242                        syn::GenericParam::Lifetime(LifetimeParam { .. }) => lt_count += 1,
243                        _ => (),
244                    }
245                }
246                let lt = format!("'lt{}", lt_count);
247                let lifetime = Lifetime::new(lt.as_str(), Span::call_site());
248                gen.params.push(syn::GenericParam::Lifetime(LifetimeParam {
249                    attrs: vec![],
250                    lifetime,
251                    colon_token: None,
252                    bounds: Default::default(),
253                }));
254                let mut arg_loosener = ArgBoundLoosener {
255                    arg_name: self.arg_name,
256                    lt: lt.as_str(),
257                    success: false,
258                };
259                let inputs = &mut i.sig.inputs;
260                inputs
261                    .iter_mut()
262                    .for_each(|arg| arg_loosener.visit_fn_arg_mut(arg));
263                match arg_loosener.success {
264                    true => self.success = true,
265                    false => (),
266                }
267            }
268        }
269    }
270}
271
272pub fn loosen_bounds(stderr: &str, new_file_name: &str, fn_name: &str) -> bool {
273    let deserializer = serde_json::Deserializer::from_str(stderr);
274    let stream = deserializer.into_iter::<RustcError>();
275    let mut helped = false;
276    for item in stream {
277        let rendered = match item {
278            Ok(item) => item.rendered,
279            Err(_) => stderr.to_string(),
280        };
281        let reference_re = Regex::new(r"error.*`(?P<ref_full>\**(?P<ref>[a-z]+))`").unwrap();
282        let error_lines = reference_re.captures_iter(rendered.as_str());
283
284        for captured in error_lines {
285            //println!("ref_full: {}, ref: {}", &captured["ref_full"], &captured["ref"]);
286            let file_content: String = fs::read_to_string(&new_file_name).unwrap().parse().unwrap();
287            let mut file = syn::parse_str::<syn::File>(file_content.as_str())
288                .map_err(|e| format!("{:?}", e))
289                .unwrap();
290            let mut visit = BoundsLoosener {
291                fn_name,
292                arg_name: &captured["ref"],
293                success: false,
294            };
295            visit.visit_file_mut(&mut file);
296            let file = file.into_token_stream().to_string();
297            match visit.success {
298                true => {
299                    fs::write(new_file_name.to_string(), format_source(&file)).unwrap();
300                    helped = true
301                }
302                false => (),
303            }
304        }
305    }
306    helped
307}