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 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 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(_) => (), 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 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}