Maintain/Eliminate/Transform/
Collect.rs1use syn::{Block, Pat, Stmt};
12
13#[derive(Debug, Clone)]
15pub struct Candidate {
16 pub Ident:String,
18
19 pub StmtIndex:usize,
21
22 pub Init:syn::Expr,
26}
27
28pub fn Collect(Block:&Block, InlineComments:bool) -> Vec<Candidate> {
40 let Len = Block.stmts.len();
41
42 Block
43 .stmts
44 .iter()
45 .enumerate()
46
47 .filter(|(Index, _)| *Index + 1 < Len)
49 .filter_map(|(Index, Stmt)| {
50 let Stmt::Local(Local) = Stmt else {
51 return None;
52 };
53
54 if !InlineComments && !Local.attrs.is_empty() {
57 return None;
58 }
59
60 let Some(Init) = &Local.init else {
62 return None;
63 };
64
65 if Init.diverge.is_some() {
67 return None;
68 }
69
70 let PatIdent = match &Local.pat {
74 Pat::Ident(P) => P,
75
76 Pat::Type(syn::PatType { pat, .. }) => {
77 if let Pat::Ident(P) = pat.as_ref() {
78 P
79 } else {
80 return None;
81 }
82 },
83
84 _ => return None,
85 };
86
87 if PatIdent.by_ref.is_some()
88
89 || PatIdent.mutability.is_some()
90
91 || PatIdent.subpat.is_some()
92
93 {
94 return None;
95 }
96
97 Some(Candidate {
98 Ident: PatIdent.ident.to_string(),
99 StmtIndex: Index,
100 Init: *Init.expr.clone(),
101 })
102 })
103 .collect()
104}
105
106#[cfg(test)]
111mod Tests {
112
113 use super::*;
114
115 fn CollectFrom(Src:&str) -> Vec<Candidate> {
116 let File:syn::File = syn::parse_str(Src).expect("parse");
117
118 for Item in &File.items {
120 if let syn::Item::Fn(F) = Item {
121 return Collect(&F.block, false);
122 }
123 }
124
125 vec![]
126 }
127
128 #[test]
129 fn SimpleLetIsCandidate() {
130 let Candidates = CollectFrom("fn f() { let X = 5; f(X); }");
131
132 assert_eq!(Candidates.len(), 1);
133
134 assert_eq!(Candidates[0].Ident, "X");
135 }
136
137 #[test]
138 fn MutLetExcluded() {
139 let Candidates = CollectFrom("fn f() { let mut X = 5; f(X); }");
140
141 assert!(Candidates.is_empty());
142 }
143
144 #[test]
145 fn DestructuringExcluded() {
146 let Candidates = CollectFrom("fn f() { let (A, B) = pair; f(A); }");
147
148 assert!(Candidates.is_empty());
149 }
150
151 #[test]
152 fn NoInitExcluded() {
153 let Candidates = CollectFrom("fn f() { let X: i32; X = 5; f(X); }");
154
155 assert!(Candidates.is_empty());
156 }
157
158 #[test]
159 fn LastStmtExcluded() {
160 let Candidates = CollectFrom("fn f() { f(1); let X = 5; }");
162
163 assert!(Candidates.is_empty());
164 }
165
166 #[test]
167 fn LetElseExcluded() {
168 let Candidates = CollectFrom(
169 r#"fn f() {
170 let Ok(X) = foo() else { return; };
171 f(X);
172 }"#,
173 );
174
175 assert!(Candidates.is_empty());
176 }
177
178 #[test]
179 fn TwoConsecutiveCandidates() {
180 let Candidates = CollectFrom(
181 r#"fn f() {
182 let A = 1;
183 let B = A + 1;
184 g(B);
185 }"#,
186 );
187
188 assert_eq!(Candidates.len(), 2);
189
190 assert_eq!(Candidates[0].Ident, "A");
191
192 assert_eq!(Candidates[1].Ident, "B");
193 }
194
195 #[test]
196 fn AttributedLetExcludedByDefault() {
197 let Candidates = CollectFrom(
198 r#"fn f() {
199 #[allow(unused)]
200 let X = 5;
201 g(X);
202 }"#,
203 );
204
205 assert!(Candidates.is_empty());
206 }
207
208 #[test]
209 fn AttributedLetIncludedWhenOptIn() {
210 let File:syn::File = syn::parse_str(
211 r#"fn f() {
212 #[allow(unused)]
213 let X = 5;
214 g(X);
215 }"#,
216 )
217 .unwrap();
218
219 let Block = match &File.items[0] {
220 syn::Item::Fn(F) => &F.block,
221
222 _ => panic!(),
223 };
224
225 let Candidates = Collect(Block, true);
226
227 assert_eq!(Candidates.len(), 1);
228 }
229}