Maintain/Eliminate/Transform/
mod.rs1pub mod Collect;
29
30pub mod Count;
31
32pub mod Inline;
33
34pub mod Patch;
35
36pub mod Safe;
37
38use super::{Definition, Error};
39
40pub fn Run(Source:&str, Options:&Definition::Options) -> Error::Result<Option<String>> {
55 let mut Ast:syn::File = syn::parse_str(Source).map_err(|E| Error::Error::Parse { Path:String::new(), Source:E })?;
56
57 let mut AnyChanged = false;
58
59 for _ in 0..super::Constant::MaxIterations {
60 let mut Eliminator = Inline::Eliminator::new(Options);
61
62 syn::visit_mut::visit_file_mut(&mut Eliminator, &mut Ast);
63
64 if Eliminator.Changed {
65 AnyChanged = true;
66 } else {
67 break;
68 }
69 }
70
71 if !AnyChanged {
72 return Ok(None);
73 }
74
75 if let Some(Patched) = TryPatchSource(Source, &Ast) {
83 return Ok(Some(Patched));
84 }
85
86 Ok(Some(prettyplease::unparse(&Ast)))
88}
89
90fn TryPatchSource(Source:&str, MutatedAst:&syn::File) -> Option<String> {
97 let OriginalAst:syn::File = syn::parse_str(Source).ok()?;
99
100 let mut Patches:Vec<Patch::Patch> = Vec::new();
101
102 for (OrigItem, MutItem) in OriginalAst.items.iter().zip(MutatedAst.items.iter()) {
105 if let (syn::Item::Fn(OrigFn), syn::Item::Fn(MutFn)) = (OrigItem, MutItem) {
106 CollectBlockPatches(&OrigFn.block, &MutFn.block, Source, &mut Patches)?;
107 }
108 }
109
110 if Patches.is_empty() {
111 return None;
113 }
114
115 Patches.sort_by_key(|P| P.Start);
117
118 Patch::ApplyPatches(Source, &Patches)
119}
120
121fn CollectBlockPatches(
125 OrigBlock:&syn::Block,
126
127 MutBlock:&syn::Block,
128
129 Source:&str,
130
131 Patches:&mut Vec<Patch::Patch>,
132) -> Option<()> {
133 let mut OrigIdx = 0usize;
140
141 for MutStmt in &MutBlock.stmts {
142 while OrigIdx < OrigBlock.stmts.len() {
145 let OrigStmt = &OrigBlock.stmts[OrigIdx];
146
147 OrigIdx += 1;
148
149 if StmtTokensMatch(OrigStmt, MutStmt) {
150 break;
152 }
153
154 let (Start, End) = Patch::StmtLineRange(OrigStmt, Source)?;
157
158 Patches.push(Patch::Patch { Start, End, Replacement:String::new() });
159
160 if StmtTokensMatch(&OrigBlock.stmts[OrigIdx - 1 + 1], MutStmt) {
163 break;
164 }
165 }
166
167 CollectInnerBlockPatches(MutStmt, Source, Patches)?;
169 }
170
171 Some(())
172}
173
174fn CollectInnerBlockPatches(Stmt:&syn::Stmt, Source:&str, Patches:&mut Vec<Patch::Patch>) -> Option<()> {
176 use syn::{Expr, Stmt as S};
177
178 match Stmt {
179 S::Expr(Expr::Block(B), _) => {
180 for S_ in &B.block.stmts {
181 CollectInnerBlockPatches(S_, Source, Patches)?;
182 }
183 },
184
185 _ => {},
186 }
187
188 Some(())
189}
190
191fn StmtTokensMatch(A:&syn::Stmt, B:&syn::Stmt) -> bool {
193 use quote::ToTokens;
194
195 let mut Ta = proc_macro2::TokenStream::new();
196
197 let mut Tb = proc_macro2::TokenStream::new();
198
199 A.to_tokens(&mut Ta);
200
201 B.to_tokens(&mut Tb);
202
203 Ta.to_string() == Tb.to_string()
204}
205
206pub fn RunPreserve(Source:&str, Options:&Definition::Options) -> Error::Result<Option<String>> {
219 let mut Working = Source.to_owned();
220
221 let mut AnyChanged = false;
222
223 for _ in 0..super::Constant::MaxIterations {
224 match PreservePass(&Working, Options)? {
225 Some(Next) => {
226 Working = Next;
227
228 AnyChanged = true;
229 },
230
231 None => break,
232 }
233 }
234
235 if AnyChanged { Ok(Some(Working)) } else { Ok(None) }
236}
237
238fn PreservePass(Working:&str, Options:&Definition::Options) -> Error::Result<Option<String>> {
241 let Ast:syn::File = syn::parse_str(Working).map_err(|E| Error::Error::Parse { Path:String::new(), Source:E })?;
242
243 for Item in &Ast.items {
244 if let Some(Result) = TryItemPreserve(Item, Working, Options)? {
245 return Ok(Some(Result));
246 }
247 }
248
249 Ok(None)
250}
251
252fn TryItemPreserve(Item:&syn::Item, Working:&str, Options:&Definition::Options) -> Error::Result<Option<String>> {
253 match Item {
254 syn::Item::Fn(F) => TryBlockPreserve(&F.block, Working, Options),
255
256 syn::Item::Impl(I) => {
257 for ImplItem in &I.items {
258 if let syn::ImplItem::Fn(M) = ImplItem {
259 if let Some(R) = TryBlockPreserve(&M.block, Working, Options)? {
260 return Ok(Some(R));
261 }
262 }
263 }
264
265 Ok(None)
266 },
267
268 _ => Ok(None),
269 }
270}
271
272fn TryBlockPreserve(Block:&syn::Block, Working:&str, Options:&Definition::Options) -> Error::Result<Option<String>> {
273 let Candidates = Collect::Collect(Block, Options.InlineComments);
274
275 for Candidate in &Candidates {
276 if !Safe::IsSafe(&Candidate.Init, Options.MaxSize) {
277 continue;
278 }
279
280 let (RefCount, InClosure, InLoop) =
281 Count::CountReferences(&Candidate.Ident, &Block.stmts[Candidate.StmtIndex + 1..]);
282
283 if RefCount != 1 || InClosure || InLoop {
284 continue;
285 }
286
287 let StmtsAfter = &Block.stmts[Candidate.StmtIndex + 1..];
288
289 let SubstSiteOffset = Inline::FindSubstSite(StmtsAfter, &Candidate.Ident);
290
291 if !Safe::IsFreeVarSafe(&Candidate.Init, &StmtsAfter[..SubstSiteOffset]) {
292 continue;
293 }
294
295 let mut UseStmts:Vec<syn::Stmt> = Block.stmts[Candidate.StmtIndex + 1..].to_vec();
297
298 if !Inline::SubstituteRef(&mut UseStmts, &Candidate.Ident, &Candidate.Init) {
299 continue;
300 }
301
302 let LetText = StmtToText(&Block.stmts[Candidate.StmtIndex]);
305
306 let UseOrigText = StmtToText(&Block.stmts[Candidate.StmtIndex + 1 + SubstSiteOffset]);
307
308 let UseNewText = StmtToText(&UseStmts[SubstSiteOffset]);
309
310 let Some(LetPos) = Working.find(&LetText) else {
312 continue;
313 };
314
315 let SearchFrom = LetPos + LetText.len();
317
318 let Some(UseOffset) = Working[SearchFrom..].find(&UseOrigText) else {
319 continue;
320 };
321
322 let UsePos = SearchFrom + UseOffset;
323
324 let mut Out = Working.to_owned();
327
328 Out.replace_range(UsePos..UsePos + UseOrigText.len(), &UseNewText);
330
331 let LetEnd = LetPos + LetText.len();
333
334 let ExpandedLetEnd = if LetEnd < Out.len() && Out.as_bytes()[LetEnd] == b'\n' {
335 LetEnd + 1
336 } else {
337 LetEnd
338 };
339
340 Out.replace_range(LetPos..ExpandedLetEnd, "");
341
342 return Ok(Some(Out));
343 }
344
345 for Stmt in &Block.stmts {
347 if let Some(Nested) = StmtNestedBlock(Stmt) {
348 if let Some(R) = TryBlockPreserve(Nested, Working, Options)? {
349 return Ok(Some(R));
350 }
351 }
352 }
353
354 Ok(None)
355}
356
357fn StmtToText(Stmt:&syn::Stmt) -> String {
366 use quote::quote;
367
368 let Wrapped:syn::File = syn::parse_quote! { fn __d() { #Stmt } };
369
370 let Full = prettyplease::unparse(&Wrapped);
371
372 if let (Some(Open), Some(Close)) = (Full.find('{'), Full.rfind('}')) {
375 let Inner = Full[Open + 1..Close].trim_matches('\n');
376
377 return Inner
378 .lines()
379 .map(|L| L.strip_prefix('\t').or_else(|| L.strip_prefix(" ")).unwrap_or(L))
380 .collect::<Vec<_>>()
381 .join("\n");
382 }
383
384 Full
385}
386
387fn StmtNestedBlock(Stmt:&syn::Stmt) -> Option<&syn::Block> {
389 if let syn::Stmt::Expr(Expr, _) = Stmt {
390 match Expr {
391 syn::Expr::Block(B) => return Some(&B.block),
392
393 syn::Expr::If(I) => return Some(&I.then_branch),
394
395 syn::Expr::Loop(L) => return Some(&L.body),
396
397 syn::Expr::While(W) => return Some(&W.body),
398
399 syn::Expr::ForLoop(F) => return Some(&F.body),
400
401 syn::Expr::Unsafe(U) => return Some(&U.block),
402
403 _ => {},
404 }
405 }
406
407 None
408}