1use proc_macro2::{Span, TokenStream};
2use syn::{ext::IdentExt, parse::Parse, spanned::Spanned};
3
4pub mod is_hook {
5
6 pub fn ident(ident: &syn::Ident) -> bool {
7 ident.to_string().starts_with("use_")
8 }
9
10 pub fn expr_path(path: &syn::ExprPath) -> bool {
11 if let Some(last) = path.path.segments.last() {
12 ident(&last.ident)
13 } else {
14 false
15 }
16 }
17
18 pub fn expr_call_with_path(path: &syn::ExprPath) -> bool {
19 expr_path(path)
20 }
21
22 pub fn expr_method_call(expr: &syn::ExprMethodCall) -> bool {
23 ident(&expr.method)
24 }
25
26 pub fn expr_macro(expr: &syn::ExprMacro) -> bool {
27 expr.mac
28 .path
29 .get_ident()
30 .map_or(false, |ident| ident == "h")
31 }
32}
33
34pub fn attr_is_not_hook(attr: &syn::Attribute) -> bool {
36 match &attr.meta {
37 syn::Meta::Path(path) => path.get_ident().map_or(false, |ident| ident == "not_hook"),
38 _ => false,
39 }
40}
41
42struct ExprOfStmtMut<'a> {
43 expr: &'a mut syn::Expr,
44 stmt_attrs: Option<&'a mut Vec<syn::Attribute>>,
45}
46
47impl<'a> ExprOfStmtMut<'a> {
48 fn try_from(stmt: &'a mut syn::Stmt) -> Option<Self> {
49 match stmt {
50 syn::Stmt::Local(local) => {
51 if let Some(syn::LocalInit {
52 eq_token: _,
53 expr,
54 diverge: _, }) = &mut local.init
56 {
57 Some(Self {
58 expr,
59 stmt_attrs: Some(&mut local.attrs),
60 })
61 } else {
62 None
63 }
64 }
65 syn::Stmt::Item(_) => {
66 None
68 }
69 syn::Stmt::Expr(expr, _) => Some(Self {
70 expr,
71 stmt_attrs: None,
72 }),
73 syn::Stmt::Macro(_) => None,
75 }
76 }
77}
78
79pub struct DetectedHooks {
80 pub hooks: Vec<crate::DetectedHook>,
81 pub not_hook_attrs: Vec<syn::Attribute>,
82}
83
84pub fn detect_hooks<'a>(
85 stmts: impl Iterator<Item = &'a mut syn::Stmt>,
86 hooks_core_path: &syn::Path,
87) -> DetectedHooks {
88 let mut used_hooks = vec![];
89
90 let mut mutate = MutateHookExpr::new(|expr| {
91 let mut expr_attrs = vec![];
92 let mut h_ident = None;
93 let mut paren_token = None;
94 let mut hook_id = None;
95
96 if let syn::Expr::Macro(m) = expr {
97 expr_attrs = std::mem::take(&mut m.attrs);
98
99 h_ident = Some(m.mac.path.get_ident().unwrap().clone());
100
101 paren_token = Some(match &mut m.mac.delimiter {
102 syn::MacroDelimiter::Paren(p) => *p,
103 syn::MacroDelimiter::Brace(d) => syn::token::Paren(d.span),
104 syn::MacroDelimiter::Bracket(d) => syn::token::Paren(d.span),
105 });
106
107 let HMacroContent {
108 explicit_hook_id,
109 expr: actual_expr,
110 } = syn::parse2(std::mem::take(&mut m.mac.tokens)).unwrap();
111
112 hook_id = explicit_hook_id.map(|h| h.0);
113
114 *expr = syn::Expr::Verbatim(actual_expr);
115 }
116
117 let span = Span::call_site().located_at(expr.span());
118
119 let actual_expr = std::mem::replace(
120 expr,
121 syn::Expr::Call(syn::ExprCall {
122 attrs: expr_attrs,
123 func: Box::new(syn::Expr::Path(syn::ExprPath {
124 attrs: vec![],
125 qself: None,
126 path: {
127 let mut p = hooks_core_path.clone();
128 p.segments
129 .push(syn::Ident::new("UpdateHookUninitialized", span).into());
130 p.segments
131 .push(h_ident.unwrap_or_else(|| syn::Ident::new("h", span)).into());
132 p
133 },
134 })),
135 paren_token: paren_token.unwrap_or_default(),
136 args: Default::default(),
137 }),
138 );
139
140 let hook_id = hook_id.unwrap_or_else(|| {
141 let idx = used_hooks.len();
142 syn::Ident::new(&format!("__hooks_hook_{idx}"), span)
143 });
144
145 if let syn::Expr::Call(syn::ExprCall { args, .. }) = expr {
146 args.extend([
147 actual_expr,
148 syn::Expr::Path(syn::ExprPath {
149 attrs: vec![],
150 qself: None,
151 path: hook_id.clone().into(),
152 }),
153 ]);
154 } else {
155 unreachable!()
156 };
157
158 used_hooks.push(crate::DetectedHook { ident: hook_id })
159 });
160
161 for stmt in stmts {
162 if let Some(ExprOfStmtMut { expr, stmt_attrs }) = ExprOfStmtMut::try_from(stmt) {
163 if stmt_attrs.map_or(true, |attrs| mutate.not_hook_attrs.might_be_hook(attrs)) {
164 mutate.mutate_if_expr_is_hook(expr);
165 }
166 }
167 }
168
169 DetectedHooks {
170 not_hook_attrs: mutate.unwrap_not_hook_attrs(),
171 hooks: used_hooks,
172 }
173}
174
175struct HMacroContent {
177 explicit_hook_id: Option<(syn::Ident, syn::Token![=])>,
178 expr: TokenStream,
179}
180
181impl Parse for HMacroContent {
182 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
183 Ok(Self {
184 explicit_hook_id: if input.peek(syn::Ident::peek_any) && input.peek2(syn::Token![=]) {
185 Some((input.parse()?, input.parse()?))
186 } else {
187 None
188 },
189 expr: input.parse()?,
190 })
191 }
192}
193
194struct NotHookAttrs(Vec<syn::Attribute>);
196
197impl NotHookAttrs {
198 pub fn might_be_hook(&mut self, attrs: &mut Vec<syn::Attribute>) -> bool {
202 let mut nothing_is_removed = true;
203 attrs.retain_mut(|attr| {
204 if attr_is_not_hook(attr) {
205 let src = syn::Attribute {
206 pound_token: attr.pound_token,
207 style: attr.style,
208 bracket_token: attr.bracket_token,
209 meta: syn::Meta::Path(syn::Path {
210 leading_colon: None,
211 segments: Default::default(),
212 }),
213 };
214 nothing_is_removed = false;
215 self.0.push(std::mem::replace(attr, src));
216 false
217 } else {
218 true
219 }
220 });
221
222 nothing_is_removed
223 }
224}
225
226pub struct MutateHookExpr<F: FnMut(&mut syn::Expr)> {
227 mutate_hook_expr: F,
228 not_hook_attrs: NotHookAttrs,
229}
230
231impl<F: FnMut(&mut syn::Expr)> MutateHookExpr<F> {
232 pub fn new(mutate_hook_expr: F) -> Self {
233 Self {
234 mutate_hook_expr,
235 not_hook_attrs: NotHookAttrs(vec![]),
236 }
237 }
238
239 pub fn mutate_if_expr_is_hook(&mut self, expr: &mut syn::Expr) {
240 macro_rules! process_inner_expressions {
241 ($e:ident . $field:ident) => {
242 process_inner_expressions! { $e { $field } }
243 };
244 ($e:ident { $($field:ident),+ $(,)? }) => {
245 if self.not_hook_attrs.might_be_hook(&mut $e.attrs) {
246 $(
247 self.mutate_if_expr_is_hook(&mut $e.$field);
248 )+
249 }
250 };
251 }
252
253 match expr {
254 syn::Expr::Array(array) => {
255 if self.not_hook_attrs.might_be_hook(&mut array.attrs) {
256 for elem in array.elems.iter_mut() {
257 self.mutate_if_expr_is_hook(elem);
258 }
259 }
260 }
261 syn::Expr::Assign(e) => process_inner_expressions!(e { left, right }),
262 syn::Expr::Async(_) => {
263 }
265 syn::Expr::Await(e) => process_inner_expressions!(e.base),
266 syn::Expr::Binary(e) => process_inner_expressions!(e { left, right }),
267 syn::Expr::Block(_) => {
268 }
270 syn::Expr::Break(_) => {
271 }
274 syn::Expr::Call(c) => {
275 if self.not_hook_attrs.might_be_hook(&mut c.attrs) {
276 for arg in c.args.iter_mut() {
277 self.mutate_if_expr_is_hook(arg);
278 }
279
280 if let syn::Expr::Path(path) = &*c.func {
281 if is_hook::expr_call_with_path(path) {
282 (self.mutate_hook_expr)(expr);
283 }
284 } else {
285 self.mutate_if_expr_is_hook(&mut c.func);
286 }
287 }
288 }
289 syn::Expr::Cast(e) => process_inner_expressions!(e.expr),
290 syn::Expr::Closure(_) => {
291 }
294 syn::Expr::Continue(_) => {
295 }
298 syn::Expr::Field(e) => process_inner_expressions!(e.base),
299 syn::Expr::ForLoop(e) => process_inner_expressions!(e.expr),
300 syn::Expr::Group(e) => process_inner_expressions!(e.expr),
301 syn::Expr::If(e) => process_inner_expressions!(e.cond),
302 syn::Expr::Index(e) => process_inner_expressions!(e { expr, index }),
303 syn::Expr::Let(e) => process_inner_expressions!(e.expr),
304 syn::Expr::Lit(_) => {
305 }
308 syn::Expr::Loop(_) => {
309 }
312 syn::Expr::Macro(m) => {
313 if self.not_hook_attrs.might_be_hook(&mut m.attrs) && is_hook::expr_macro(m) {
314 (self.mutate_hook_expr)(expr);
315 }
316 }
317 syn::Expr::Match(e) => process_inner_expressions!(e.expr),
318 syn::Expr::MethodCall(m) => {
319 if self.not_hook_attrs.might_be_hook(&mut m.attrs) {
320 for arg in m.args.iter_mut() {
321 self.mutate_if_expr_is_hook(arg);
322 }
323 self.mutate_if_expr_is_hook(&mut m.receiver);
324
325 if is_hook::ident(&m.method) {
326 (self.mutate_hook_expr)(expr);
327 }
328 }
329 }
330 syn::Expr::Paren(e) => process_inner_expressions!(e.expr),
331 syn::Expr::Path(_) => {
332 }
335 syn::Expr::Range(r) => {
336 if (self.not_hook_attrs).might_be_hook(&mut r.attrs) {
337 if let Some(e) = &mut r.start {
338 self.mutate_if_expr_is_hook(e);
339 }
340 if let Some(e) = &mut r.end {
341 self.mutate_if_expr_is_hook(e);
342 }
343 }
344 }
345 syn::Expr::Reference(e) => process_inner_expressions!(e.expr),
346 syn::Expr::Repeat(_) => {
347 }
350 syn::Expr::Return(e) => {
351 if self.not_hook_attrs.might_be_hook(&mut e.attrs) {
352 if let Some(expr) = &mut e.expr {
353 self.mutate_if_expr_is_hook(expr);
354 }
355 }
356 }
357 syn::Expr::Struct(s) => {
358 if self.not_hook_attrs.might_be_hook(&mut s.attrs) {
359 for field in s.fields.iter_mut() {
360 process_inner_expressions!(field.expr);
361 }
362 if let Some(e) = &mut s.rest {
363 self.mutate_if_expr_is_hook(e);
364 }
365 }
366 }
367 syn::Expr::Try(e) => process_inner_expressions!(e.expr),
368 syn::Expr::TryBlock(_) => {
369 }
372 syn::Expr::Tuple(t) => {
373 if self.not_hook_attrs.might_be_hook(&mut t.attrs) {
374 for elem in t.elems.iter_mut() {
375 self.mutate_if_expr_is_hook(elem);
376 }
377 }
378 }
379 syn::Expr::Unary(e) => process_inner_expressions!(e.expr),
380 syn::Expr::Unsafe(_) => {
381 }
384 syn::Expr::Verbatim(_) => {
385 }
387 syn::Expr::While(e) => process_inner_expressions!(e.cond),
388 syn::Expr::Yield(_) => {
389 }
392 syn::Expr::Const(_) => {}
393 syn::Expr::Infer(_) => {}
394 _ => {
395 }
399 }
400 }
401
402 pub fn unwrap_not_hook_attrs(self) -> Vec<syn::Attribute> {
403 self.not_hook_attrs.0
404 }
405}