Maintain/Eliminate/Transform/
Inline.rs1use proc_macro2::{Group, TokenStream, TokenTree};
25use quote::ToTokens;
26use syn::{
27 Expr,
28 Stmt,
29 visit_mut::{VisitMut, visit_block_mut, visit_expr_mut},
30};
31
32use super::{Collect, Count, Safe};
33
34pub struct Eliminator<'a> {
39 pub Changed:bool,
40
41 Options:&'a crate::Eliminate::Definition::Options,
42}
43
44impl<'a> Eliminator<'a> {
45 pub fn new(Options:&'a crate::Eliminate::Definition::Options) -> Self { Self { Changed:false, Options } }
46
47 fn EliminateBlock(&mut self, Block:&mut syn::Block) {
48 loop {
49 let Candidates = Collect::Collect(Block, self.Options.InlineComments);
50
51 let mut DidChange = false;
52
53 for Candidate in &Candidates {
54 if !Safe::IsSafe(&Candidate.Init, self.Options.MaxSize) {
55 continue;
56 }
57
58 let StmtsAfter = &Block.stmts[Candidate.StmtIndex + 1..];
59
60 let (RefCount, InClosure, InLoop) = Count::CountReferences(&Candidate.Ident, StmtsAfter);
61
62 if RefCount != 1 || InClosure || InLoop {
63 continue;
64 }
65
66 let SubstSiteOffset = FindSubstSite(StmtsAfter, &Candidate.Ident);
71
72 let StmtsBetween = &StmtsAfter[..SubstSiteOffset];
73
74 if !Safe::IsFreeVarSafe(&Candidate.Init, StmtsBetween) {
75 continue;
76 }
77
78 let Substituted =
79 SubstituteRef(&mut Block.stmts[Candidate.StmtIndex + 1..], &Candidate.Ident, &Candidate.Init);
80
81 if Substituted {
82 Block.stmts.remove(Candidate.StmtIndex);
83
84 self.Changed = true;
85
86 DidChange = true;
87
88 break;
89 }
90 }
91
92 if !DidChange {
93 break;
94 }
95 }
96 }
97}
98
99impl<'a> VisitMut for Eliminator<'a> {
100 fn visit_block_mut(&mut self, Block:&mut syn::Block) {
101 visit_block_mut(self, Block);
103
104 self.EliminateBlock(Block);
105 }
106}
107
108pub fn SubstituteRef(Stmts:&mut [Stmt], Target:&str, Replacement:&Expr) -> bool {
116 let mut Sub = Substitutor { Target, Replacement, Substituted:false, InBinaryOperandPosition:false };
117
118 for Stmt in Stmts.iter_mut() {
119 if Sub.Substituted {
120 break;
121 }
122
123 Sub.visit_stmt_mut(Stmt);
124 }
125
126 Sub.Substituted
127}
128
129pub fn FindSubstSite(Stmts:&[Stmt], Target:&str) -> usize {
138 for (I, Stmt) in Stmts.iter().enumerate() {
139 let (Count, ..) = Count::CountReferences(Target, std::slice::from_ref(Stmt));
140
141 if Count > 0 {
142 return I;
143 }
144 }
145
146 Stmts.len()
147}
148
149struct Substitutor<'a> {
154 Target:&'a str,
155
156 Replacement:&'a Expr,
157
158 Substituted:bool,
159
160 InBinaryOperandPosition:bool,
163}
164
165impl<'a> VisitMut for Substitutor<'a> {
166 fn visit_expr_mut(&mut self, Node:&mut Expr) {
167 if self.Substituted {
168 return;
169 }
170
171 if IsTargetIdent(Node, self.Target) {
172 let NeedsWrapping = self.InBinaryOperandPosition && NeedsParen(self.Replacement);
173
174 *Node = if NeedsWrapping {
175 Expr::Paren(syn::ExprParen {
176 attrs:vec![],
177 paren_token:Default::default(),
178 expr:Box::new(self.Replacement.clone()),
179 })
180 } else {
181 self.Replacement.clone()
182 };
183
184 self.Substituted = true;
185
186 return;
187 }
188
189 match Node {
191 Expr::Binary(B) => {
192 let Saved = self.InBinaryOperandPosition;
193
194 self.InBinaryOperandPosition = true;
195
196 self.visit_expr_mut(&mut B.left);
197
198 if !self.Substituted {
199 self.visit_expr_mut(&mut B.right);
200 }
201
202 self.InBinaryOperandPosition = Saved;
203 },
204
205 Expr::Unary(U) => {
206 let Saved = self.InBinaryOperandPosition;
207
208 self.InBinaryOperandPosition = true;
209
210 self.visit_expr_mut(&mut U.expr);
211
212 self.InBinaryOperandPosition = Saved;
213 },
214
215 _ => {
216 let Saved = self.InBinaryOperandPosition;
217
218 self.InBinaryOperandPosition = false;
219
220 visit_expr_mut(self, Node);
221
222 self.InBinaryOperandPosition = Saved;
223 },
224 }
225 }
226
227 fn visit_expr_macro_mut(&mut self, Node:&mut syn::ExprMacro) {
230 if self.Substituted {
231 return;
232 }
233
234 let ReplacementTokens = ExprToTokenStream(self.Replacement);
235
236 let (NewTokens, Found) = SubstituteInTokenStream(Node.mac.tokens.clone(), self.Target, &ReplacementTokens);
237
238 if Found {
239 Node.mac.tokens = NewTokens;
240
241 self.Substituted = true;
242 }
243 }
244
245 fn visit_stmt_macro_mut(&mut self, Node:&mut syn::StmtMacro) {
249 if self.Substituted {
250 return;
251 }
252
253 let ReplacementTokens = ExprToTokenStream(self.Replacement);
254
255 let (NewTokens, Found) = SubstituteInTokenStream(Node.mac.tokens.clone(), self.Target, &ReplacementTokens);
256
257 if Found {
258 Node.mac.tokens = NewTokens;
259
260 self.Substituted = true;
261 }
262 }
263
264 fn visit_block_mut(&mut self, Block:&mut syn::Block) {
266 if BlockShadowsTarget(&Block.stmts, self.Target) {
267 return;
268 }
269
270 syn::visit_mut::visit_block_mut(self, Block);
271 }
272
273 fn visit_expr_closure_mut(&mut self, Node:&mut syn::ExprClosure) {
275 if ClosureParamShadows(Node, self.Target) {
276 return;
277 }
278
279 syn::visit_mut::visit_expr_closure_mut(self, Node);
280 }
281}
282
283fn ExprToTokenStream(E:&Expr) -> TokenStream {
288 let mut Tokens = TokenStream::new();
289
290 E.to_tokens(&mut Tokens);
291
292 Tokens
293}
294
295fn SubstituteInTokenStream(Tokens:TokenStream, Target:&str, Replacement:&TokenStream) -> (TokenStream, bool) {
296 let mut Result:Vec<TokenTree> = Vec::new();
297
298 let mut Found = false;
299
300 for Tree in Tokens {
301 if Found {
302 Result.push(Tree);
303
304 continue;
305 }
306
307 match Tree {
308 TokenTree::Ident(ref I) if I.to_string() == Target => {
309 Result.extend(Replacement.clone());
310
311 Found = true;
312 },
313
314 TokenTree::Group(G) => {
315 let (NewStream, F) = SubstituteInTokenStream(G.stream(), Target, Replacement);
316
317 if F {
318 Found = true;
319 }
320
321 Result.push(TokenTree::Group(Group::new(G.delimiter(), NewStream)));
322 },
323
324 Other => Result.push(Other),
325 }
326 }
327
328 (Result.into_iter().collect(), Found)
329}
330
331fn IsTargetIdent(E:&Expr, Target:&str) -> bool {
336 if let Expr::Path(ExprPath) = E {
337 if ExprPath.qself.is_none() {
338 if let Some(Ident) = ExprPath.path.get_ident() {
339 return Ident == Target;
340 }
341 }
342 }
343
344 false
345}
346
347fn NeedsParen(E:&Expr) -> bool { matches!(E, Expr::Binary(_) | Expr::Range(_) | Expr::Closure(_) | Expr::Cast(_)) }
348
349fn BlockShadowsTarget(Stmts:&[Stmt], Target:&str) -> bool {
350 Stmts.iter().any(|S| {
351 if let Stmt::Local(L) = S {
352 if let syn::Pat::Ident(P) = &L.pat {
353 return P.ident == Target;
354 }
355 }
356
357 false
358 })
359}
360
361fn ClosureParamShadows(Closure:&syn::ExprClosure, Target:&str) -> bool {
362 Closure
363 .inputs
364 .iter()
365 .any(|P| if let syn::Pat::Ident(P) = P { P.ident == Target } else { false })
366}
367
368#[cfg(test)]
373mod Tests {
374
375 use super::*;
376
377 fn Transform(Src:&str) -> String {
378 let Opts = crate::Eliminate::Definition::Options::default();
379
380 crate::Eliminate::Transform::Run(Src, &Opts)
381 .expect("transform failed")
382 .unwrap_or_else(|| {
383 let Ast:syn::File = syn::parse_str(Src).unwrap();
384
385 prettyplease::unparse(&Ast)
386 })
387 }
388
389 fn Normalise(Src:&str) -> String {
390 let Ast:syn::File = syn::parse_str(Src).unwrap();
391
392 prettyplease::unparse(&Ast)
393 }
394
395 fn AssertEliminates(Input:&str, Expected:&str) {
396 assert_eq!(Transform(Input), Normalise(Expected));
397 }
398
399 fn AssertUnchanged(Input:&str) {
400 let Opts = crate::Eliminate::Definition::Options::default();
401
402 let Result = crate::Eliminate::Transform::Run(Input, &Opts).expect("transform");
403
404 assert!(Result.is_none(), "expected no change but got:\n{}", Result.unwrap());
405 }
406
407 #[test]
410 fn SimpleInline() {
411 AssertEliminates(
412 r#"fn f() { let X = 5; println!("{}", X); }"#,
413 r#"fn f() { println!("{}", 5); }"#,
414 );
415 }
416
417 #[test]
418 fn ChainInline() { AssertEliminates("fn f() { let A = 1; let B = A + 1; g(B); }", "fn f() { g(1 + 1); }"); }
419
420 #[test]
421 fn BinaryExprParens() {
422 AssertEliminates("fn f() { let X = A + B; let _ = Y * X; }", "fn f() { let _ = Y * (A + B); }");
423 }
424
425 #[test]
426 fn BinaryExprNoParensInFnArg() { AssertEliminates("fn f() { let X = A + B; foo(X); }", "fn f() { foo(A + B); }"); }
427
428 #[test]
429 fn QuestionMarkInlined() {
430 AssertEliminates(
431 "async fn f() -> Result<(), E> { let X = foo().map_err(|e| e)?; bar(X); Ok(()) }",
432 "async fn f() -> Result<(), E> { bar(foo().map_err(|e| e)?); Ok(()) }",
433 );
434 }
435
436 #[test]
439 fn InlineIntoJsonMacro() {
440 AssertEliminates(
441 r#"fn f() {
442 let DataString = compute_data();
443 emit(json!({ "data": DataString }));
444 }"#,
445 r#"fn f() {
446 emit(json!({ "data": compute_data() }));
447 }"#,
448 );
449 }
450
451 #[test]
452 fn MacroAndExprMultiUseKept() {
453 AssertUnchanged(
454 r#"fn f() {
455 let URI = compute_uri();
456 dev_log!("{}", URI);
457 let _ = Url::parse(URI);
458 }"#,
459 );
460 }
461
462 #[test]
463 fn MacroDoubleUseKept() {
464 AssertUnchanged(
465 r#"fn f() {
466 let X = val();
467 emit(json!({ "a": X, "b": X }));
468 }"#,
469 );
470 }
471
472 #[test]
479 fn LoopBodyBindingKept() {
480 AssertUnchanged(
481 r#"fn f() {
482 let X = expensive();
483 for item in &collection { process(item, X); }
484 }"#,
485 );
486 }
487
488 #[test]
489 fn WhileBodyBindingKept() {
490 AssertUnchanged(
491 r#"fn f() {
492 let X = expensive();
493 while cond { process(X); }
494 }"#,
495 );
496 }
497
498 #[test]
499 fn LoopExprBindingKept() {
500 AssertUnchanged(
501 r#"fn f() {
502 let X = expensive();
503 loop { process(X); break; }
504 }"#,
505 );
506 }
507
508 #[test]
510 fn OutsideLoopStillInlined() {
511 AssertEliminates("fn f() { let X = compute(); g(X); }", "fn f() { g(compute()); }");
512 }
513
514 #[test]
521 fn DisplayCloneKeptWhenOriginalMovedFirst() {
522 AssertUnchanged(
523 r#"
524 pub async fn get_file_info(request_id: String, path: String) -> Result<(), E> {
525 let path_display = path.clone();
526
527 client
528 .get_file_info(Request::new(FileInfoRequest { request_id, path }))
529 .await?;
530
531 devlog!("{}", path_display, path.clone());
532
533 Ok(())
534 }
535 "#,
536 );
537 }
538
539 #[test]
542 fn SectionDisplayCloneKeptWhenOriginalMovedFirst() {
543 AssertUnchanged(
544 r#"
545 pub async fn get_configuration(request_id: String, section: String) -> Result<(), E> {
546 let section_display = section.clone();
547
548 client
549 .get_configuration(Request::new(ConfigurationRequest { request_id, section }))
550 .await?;
551
552 devlog!("{}", section_display, section.clone());
553
554 Ok(())
555 }
556 "#,
557 );
558 }
559
560 #[test]
564 fn DisplayCloneInlinedWhenOriginalNotMoved() {
565 AssertEliminates(
566 r#"
567 pub async fn log_path(path: String) -> Result<(), E> {
568 let path_display = path.clone();
569
570 devlog!("{}", path_display);
571
572 Ok(())
573 }
574 "#,
575 r#"
576 pub async fn log_path(path: String) -> Result<(), E> {
577 devlog!("{}", path.clone());
578
579 Ok(())
580 }
581 "#,
582 );
583 }
584
585 #[test]
588 fn UrlPatternInlined() {
589 let Input = r#"
590 pub async fn Fn(
591 Service: &CocoonServiceImpl,
592
593 Request: ProvideCodeLensesRequest,
594 ) -> Result<Response<ProvideCodeLensesResponse>, Status> {
595 let URI = Request.uri.as_ref().map(|U| U.value.as_str()).unwrap_or("");
596
597 let DocumentURI = Url::parse(URI)
598 .map_err(|E| Status::invalid_argument(format!("Invalid URI: {}", E)))?;
599
600 match Service.environment.ProvideCodeLenses(DocumentURI).await {
601 Ok(_) => Ok(Response::new(ProvideCodeLensesResponse::default())),
602
603 Err(Error) => Err(Status::internal(format!("Code lenses failed: {}", Error))),
604 }
605 }
606
607 "#;
608
609 let Expected = r#"
610 pub async fn Fn(
611 Service: &CocoonServiceImpl,
612
613 Request: ProvideCodeLensesRequest,
614 ) -> Result<Response<ProvideCodeLensesResponse>, Status> {
615 match Service
616 .environment
617 .ProvideCodeLenses(
618 Url::parse(
619 Request.uri.as_ref().map(|U| U.value.as_str()).unwrap_or(""),
620 )
621 .map_err(|E| Status::invalid_argument(format!("Invalid URI: {}", E)))?,
622 )
623 .await
624 {
625 Ok(_) => Ok(Response::new(ProvideCodeLensesResponse::default())),
626
627 Err(Error) => Err(Status::internal(format!("Code lenses failed: {}", Error))),
628 }
629 }
630
631 "#;
632
633 let Got = Transform(Input);
634
635 let Norm = Normalise(Expected);
636
637 assert_eq!(Got, Norm, "URL pattern not inlined as expected");
638 }
639
640 #[test]
643 fn MultiUseKept() { AssertUnchanged("fn f() { let X = foo(); bar(X); baz(X); }"); }
644
645 #[test]
646 fn MutKept() { AssertUnchanged("fn f() { let mut X = 5; X += 1; g(X); }"); }
647
648 #[test]
649 fn ClosureCaptureKept() {
650 AssertEliminates(
651 "fn f() { let X = heavy(); let F = move || X; call(F); }",
652 "fn f() { let X = heavy(); call(move || X); }",
653 );
654 }
655
656 #[test]
659 fn Idempotent() {
660 let Opts = crate::Eliminate::Definition::Options::default();
661
662 let Src = r#"fn f() { let X = 5; println!("{}", X); }"#;
663
664 let First = crate::Eliminate::Transform::Run(Src, &Opts)
665 .unwrap()
666 .expect("first pass should change");
667
668 let Second = crate::Eliminate::Transform::Run(&First, &Opts).unwrap();
669
670 assert!(Second.is_none(), "second pass must be a no-op:\n{}", First);
671 }
672}