Maintain/Eliminate/Transform/
Count.rs1use proc_macro2::{TokenStream, TokenTree};
22use syn::{
23 Pat,
24 Stmt,
25 visit::{Visit, visit_block, visit_expr_closure, visit_expr_for_loop, visit_expr_loop, visit_expr_while},
26};
27
28pub fn CountReferences(Target:&str, Stmts:&[Stmt]) -> (usize, bool, bool) {
45 let mut TotalCount = 0usize;
46
47 let mut InClosure = false;
48
49 let mut InLoop = false;
50
51 for Stmt in Stmts {
52 if IsTopLevelShadow(Stmt, Target) {
53 break;
54 }
55
56 let mut Counter = ExprCounter { Target, Count:0, InClosure:false, InLoop:false, InsideLoop:false };
57
58 Counter.visit_stmt(Stmt);
59
60 TotalCount += Counter.Count;
61
62 if Counter.InClosure {
63 InClosure = true;
64 }
65
66 if Counter.InLoop {
67 InLoop = true;
68 }
69 }
70
71 (TotalCount, InClosure, InLoop)
72}
73
74fn IsTopLevelShadow(Stmt:&Stmt, Target:&str) -> bool {
79 if let Stmt::Local(Local) = Stmt {
80 if let Pat::Ident(P) = &Local.pat {
81 return P.ident == Target;
82 }
83 }
84
85 false
86}
87
88struct ExprCounter<'a> {
89 Target:&'a str,
90
91 Count:usize,
92
93 InClosure:bool,
95
96 InLoop:bool,
98
99 InsideLoop:bool,
102}
103
104impl<'ast> Visit<'ast> for ExprCounter<'ast> {
105 fn visit_expr_path(&mut self, Node:&'ast syn::ExprPath) {
106 if let Some(Ident) = Node.path.get_ident() {
107 if Ident == self.Target {
108 self.Count += 1;
109
110 if self.InsideLoop {
111 self.InLoop = true;
112 }
113 }
114 }
115 }
116
117 fn visit_expr_macro(&mut self, Node:&'ast syn::ExprMacro) {
118 let Found = CountIdentsInTokenStream(&Node.mac.tokens, self.Target);
119
120 if Found > 0 {
121 self.Count += Found;
122
123 if self.InsideLoop {
124 self.InLoop = true;
125 }
126 }
127 }
128
129 fn visit_stmt_macro(&mut self, Node:&'ast syn::StmtMacro) {
130 let Found = CountIdentsInTokenStream(&Node.mac.tokens, self.Target);
131
132 if Found > 0 {
133 self.Count += Found;
134
135 if self.InsideLoop {
136 self.InLoop = true;
137 }
138 }
139 }
140
141 fn visit_block(&mut self, Node:&'ast syn::Block) {
142 if BlockShadowsTarget(&Node.stmts, self.Target) {
143 return;
144 }
145
146 visit_block(self, Node);
147 }
148
149 fn visit_expr_closure(&mut self, Node:&'ast syn::ExprClosure) {
150 if ClosureParamShadows(Node, self.Target) {
151 return;
152 }
153
154 let CountBefore = self.Count;
155
156 visit_expr_closure(self, Node);
157
158 if self.Count > CountBefore {
159 self.InClosure = true;
160 }
161 }
162
163 fn visit_expr_for_loop(&mut self, Node:&'ast syn::ExprForLoop) {
166 let Saved = self.InsideLoop;
167
168 self.InsideLoop = true;
169
170 visit_expr_for_loop(self, Node);
171
172 self.InsideLoop = Saved;
173 }
174
175 fn visit_expr_while(&mut self, Node:&'ast syn::ExprWhile) {
176 let Saved = self.InsideLoop;
177
178 self.InsideLoop = true;
179
180 visit_expr_while(self, Node);
181
182 self.InsideLoop = Saved;
183 }
184
185 fn visit_expr_loop(&mut self, Node:&'ast syn::ExprLoop) {
186 let Saved = self.InsideLoop;
187
188 self.InsideLoop = true;
189
190 visit_expr_loop(self, Node);
191
192 self.InsideLoop = Saved;
193 }
194}
195
196pub fn CountIdentsInTokenStream(Tokens:&TokenStream, Target:&str) -> usize {
197 let mut Count = 0;
198
199 for Tree in Tokens.clone() {
200 match Tree {
201 TokenTree::Ident(I) if I.to_string() == Target => Count += 1,
202
203 TokenTree::Group(G) => Count += CountIdentsInTokenStream(&G.stream(), Target),
204
205 TokenTree::Literal(Lit) => Count += CountIdentInFormatLiteral(&Lit.to_string(), Target),
206
207 _ => {},
208 }
209 }
210
211 Count
212}
213
214pub fn CountIdentInFormatLiteral(Lit:&str, Target:&str) -> usize {
215 if !Lit.starts_with('"') && !Lit.starts_with('r') {
216 return 0;
217 }
218
219 let Inner:&str = if Lit.starts_with('"') { &Lit[1..Lit.len().saturating_sub(1)] } else { Lit };
220
221 let SearchFor = format!("{{{Target}");
222
223 let mut Count = 0;
224
225 let Bytes = Inner.as_bytes();
226
227 let PatBytes = SearchFor.as_bytes();
228
229 let mut Pos = 0usize;
230
231 while Pos + PatBytes.len() <= Bytes.len() {
232 if Bytes[Pos..].starts_with(PatBytes) {
233 let IsEscaped = Pos > 0 && Bytes[Pos - 1] == b'{';
234
235 if !IsEscaped {
236 let After = &Bytes[Pos + PatBytes.len()..];
237
238 if After.first().map_or(false, |&B| B == b'}' || B == b':' || B == b'!') {
239 Count += 1;
240 }
241 }
242 }
243
244 Pos += 1;
245 }
246
247 Count
248}
249
250fn BlockShadowsTarget(Stmts:&[Stmt], Target:&str) -> bool { Stmts.iter().any(|S| IsTopLevelShadow(S, Target)) }
251
252fn ClosureParamShadows(Closure:&syn::ExprClosure, Target:&str) -> bool {
253 Closure
254 .inputs
255 .iter()
256 .any(|P| if let Pat::Ident(PIdent) = P { PIdent.ident == Target } else { false })
257}
258
259#[cfg(test)]
264mod Tests {
265
266 use super::*;
267
268 fn Stmts(Src:&str) -> Vec<Stmt> {
269 let File:syn::File = syn::parse_str(Src).expect("parse");
270
271 if let syn::Item::Fn(F) = &File.items[0] {
272 return F.block.stmts.clone();
273 }
274
275 vec![]
276 }
277
278 #[test]
279 fn SingleUse() {
280 let S = Stmts("fn f() { let X = 1; g(X); }");
281
282 let (Count, InClosure, InLoop) = CountReferences("X", &S[1..]);
283
284 assert_eq!(Count, 1);
285
286 assert!(!InClosure);
287
288 assert!(!InLoop);
289 }
290
291 #[test]
292 fn MultiUse() {
293 let S = Stmts("fn f() { let X = foo(); bar(X); baz(X); }");
294
295 let (Count, ..) = CountReferences("X", &S[1..]);
296
297 assert_eq!(Count, 2);
298 }
299
300 #[test]
301 fn ZeroUse() {
302 let S = Stmts("fn f() { let X = 1; g(1); }");
303
304 let (Count, ..) = CountReferences("X", &S[1..]);
305
306 assert_eq!(Count, 0);
307 }
308
309 #[test]
310 fn ShadowStops() {
311 let S = Stmts("fn f() { let X = 1; f(X); let X = 2; g(X); }");
312
313 let (Count, ..) = CountReferences("X", &S[1..]);
314
315 assert_eq!(Count, 1);
316 }
317
318 #[test]
319 fn MacroCountsAsUse() {
320 let S = Stmts(r#"fn f() { let URI = "x"; dev_log!("{}", URI); }"#);
321
322 let (Count, ..) = CountReferences("URI", &S[1..]);
323
324 assert_eq!(Count, 1);
325 }
326
327 #[test]
328 fn MacroAndExprBothCounted() {
329 let S = Stmts(
330 r#"fn f() {
331 let URI = "x";
332 dev_log!("{}", URI);
333 let _ = Url::parse(URI);
334 }"#,
335 );
336
337 let (Count, ..) = CountReferences("URI", &S[1..]);
338
339 assert_eq!(Count, 2, "URI used in macro + expression must count as 2");
340 }
341
342 #[test]
343 fn MacroOnlyUse() {
344 let S = Stmts(
345 r#"fn f() {
346 let DataString = compute();
347 emit(json!({ "data": DataString }));
348 }"#,
349 );
350
351 let (Count, ..) = CountReferences("DataString", &S[1..]);
352
353 assert_eq!(Count, 1);
354 }
355
356 #[test]
357 fn MacroDoubleUse() {
358 let S = Stmts(
359 r#"fn f() {
360 let X = val();
361 json!({ "a": X, "b": X });
362 }"#,
363 );
364
365 let (Count, ..) = CountReferences("X", &S[1..]);
366
367 assert_eq!(Count, 2);
368 }
369
370 #[test]
371 fn ClosureCaptureDetected() {
372 let S = Stmts("fn f() { let X = heavy(); let F = move || X; call(F); }");
373
374 let (Count, InClosure, _) = CountReferences("X", &S[1..]);
375
376 assert_eq!(Count, 1);
377
378 assert!(InClosure, "X used inside closure should set InClosure");
379 }
380
381 #[test]
382 fn InnerBlockShadowSkipped() {
383 let S = Stmts(
384 r#"fn f() {
385 let X = 1;
386 { let X = 2; g(X); }
387 }"#,
388 );
389
390 let (Count, ..) = CountReferences("X", &S[1..]);
391
392 assert_eq!(Count, 0);
393 }
394
395 #[test]
396 fn ClosureParamShadowSkipped() {
397 let S = Stmts("fn f() { let X = 5; let _ = |X| X + 1; g(0); }");
398
399 let (Count, InClosure, _) = CountReferences("X", &S[1..]);
400
401 assert_eq!(Count, 0);
402
403 assert!(!InClosure);
404 }
405
406 #[test]
412 fn LoopBodySetsInLoop() {
413 let S = Stmts(
414 r#"fn f() {
415 let X = expensive();
416 for _ in &v { process(X); }
417 }"#,
418 );
419
420 let (Count, _, InLoop) = CountReferences("X", &S[1..]);
421
422 assert_eq!(Count, 1);
423
424 assert!(InLoop, "X used inside for body must set InLoop");
425 }
426
427 #[test]
428 fn WhileBodySetsInLoop() {
429 let S = Stmts(
430 r#"fn f() {
431 let X = expensive();
432 while cond { process(X); }
433 }"#,
434 );
435
436 let (Count, _, InLoop) = CountReferences("X", &S[1..]);
437
438 assert_eq!(Count, 1);
439
440 assert!(InLoop, "X used inside while body must set InLoop");
441 }
442
443 #[test]
444 fn LoopExprBodySetsInLoop() {
445 let S = Stmts(
446 r#"fn f() {
447 let X = expensive();
448 loop { process(X); break; }
449 }"#,
450 );
451
452 let (Count, _, InLoop) = CountReferences("X", &S[1..]);
453
454 assert_eq!(Count, 1);
455
456 assert!(InLoop, "X used inside loop body must set InLoop");
457 }
458
459 #[test]
461 fn OutsideLoopNotFlagged() {
462 let S = Stmts("fn f() { let X = 1; g(X); }");
463
464 let (_, _, InLoop) = CountReferences("X", &S[1..]);
465
466 assert!(!InLoop);
467 }
468
469 #[test]
472 fn UsedInsideAndOutsideLoop() {
473 let S = Stmts(
474 r#"fn f() {
475 let X = val();
476 g(X);
477 for _ in &v { h(X); }
478 }"#,
479 );
480
481 let (Count, _, InLoop) = CountReferences("X", &S[1..]);
482
483 assert_eq!(Count, 2);
484
485 assert!(InLoop);
486 }
487
488 #[test]
491 fn FormatLiteralBasicCapture() {
492 assert_eq!(CountIdentInFormatLiteral("\"{X}\"", "X"), 1);
493 }
494
495 #[test]
496 fn FormatLiteralWithSpec() {
497 assert_eq!(CountIdentInFormatLiteral("\"{X:.2}\"", "X"), 1);
498 }
499
500 #[test]
501 fn FormatLiteralWithAlt() {
502 assert_eq!(CountIdentInFormatLiteral("\"{X!r:}\"", "X"), 1);
503 }
504
505 #[test]
506 fn FormatLiteralTwoCaptures() {
507 assert_eq!(CountIdentInFormatLiteral("\"{X} and {X}\"", "X"), 2);
508 }
509
510 #[test]
511 fn FormatLiteralEscapedBraceNotCounted() {
512 assert_eq!(CountIdentInFormatLiteral("\"{{X}}\"", "X"), 0);
513 }
514
515 #[test]
516 fn FormatLiteralNumericNotCounted() {
517 assert_eq!(CountIdentInFormatLiteral("42", "X"), 0);
518 }
519
520 #[test]
521 fn FormatLiteralSubstringNotCounted() {
522 assert_eq!(CountIdentInFormatLiteral("\"{XY}\"", "X"), 0);
523 }
524
525 #[test]
526 fn ImplicitCaptureSingleUse() {
527 let S = Stmts(r#"fn f() { let X = 5; println!("{X}"); }"#);
528
529 let (Count, ..) = CountReferences("X", &S[1..]);
530
531 assert_eq!(Count, 1, "implicit capture {X} must count as one use");
532 }
533
534 #[test]
535 fn MixedImplicitAndExplicitCounts() {
536 let S = Stmts(
537 r#"fn f() {
538 let X = 5;
539 println!("{}", X);
540 println!("{X}");
541 }"#,
542 );
543
544 let (Count, ..) = CountReferences("X", &S[1..]);
545
546 assert_eq!(Count, 2, "old-style + implicit capture must count as 2");
547 }
548
549 #[test]
550 fn TwoImplicitCaptures() {
551 let S = Stmts(
552 r#"fn f() {
553 let X = 5;
554 log!("{X}");
555 log!("{X}");
556 }"#,
557 );
558
559 let (Count, ..) = CountReferences("X", &S[1..]);
560
561 assert_eq!(Count, 2);
562 }
563
564 #[test]
565 fn ImplicitCaptureWithPlainUseIsMulti() {
566 let S = Stmts(
567 r#"fn f() {
568 let URI = compute_uri();
569 log!("{URI}");
570 Url::parse(URI);
571 }"#,
572 );
573
574 let (Count, ..) = CountReferences("URI", &S[1..]);
575
576 assert_eq!(Count, 2);
577 }
578
579 #[test]
580 fn TwoSeparateStmtsMultiUse() {
581 let S = Stmts("fn f() { let X = foo(); bar(X); baz(X); }");
582
583 let (Count, ..) = CountReferences("X", &S[1..]);
584
585 assert_eq!(Count, 2);
586 }
587
588 #[test]
589 fn TwoUsesInSameCall() {
590 let S = Stmts("fn f() { let X = foo(); bar(X, X); }");
591
592 let (Count, ..) = CountReferences("X", &S[1..]);
593
594 assert_eq!(Count, 2);
595 }
596}