1use proc_macro::TokenStream;
2use proc_macro_crate::{FoundCrate, crate_name};
3use proc_macro2::{Span, TokenStream as TokenStream2};
4use quote::quote;
5use syn::{FnArg, Ident, ItemFn, Pat, PatType, ReturnType, Type, parse_macro_input};
6
7mod branch_groups;
8
9fn is_fn_like_type(ty: &Type) -> bool {
10 match ty {
11 Type::ImplTrait(impl_trait) => impl_trait.bounds.iter().any(|bound| {
12 if let syn::TypeParamBound::Trait(trait_bound) = bound {
13 let path = &trait_bound.path;
14 if let Some(segment) = path.segments.last() {
15 let ident_str = segment.ident.to_string();
16 return ident_str == "FnMut" || ident_str == "Fn" || ident_str == "FnOnce";
17 }
18 }
19 false
20 }),
21 Type::Path(type_path) => {
22 if let Some(segment) = type_path.path.segments.last()
23 && segment.ident == "Box"
24 && let syn::PathArguments::AngleBracketed(args) = &segment.arguments
25 && let Some(syn::GenericArgument::Type(Type::TraitObject(trait_obj))) =
26 args.args.first()
27 {
28 return trait_obj.bounds.iter().any(|bound| {
29 if let syn::TypeParamBound::Trait(trait_bound) = bound {
30 let path = &trait_bound.path;
31 if let Some(segment) = path.segments.last() {
32 let ident_str = segment.ident.to_string();
33 return ident_str == "FnMut"
34 || ident_str == "Fn"
35 || ident_str == "FnOnce";
36 }
37 }
38 false
39 });
40 }
41 false
42 }
43 Type::BareFn(_) => true,
44 _ => false,
45 }
46}
47
48fn is_generic_fn_like(ty: &Type, generics: &syn::Generics) -> bool {
49 let type_ident = match ty {
50 Type::Path(type_path) if type_path.path.segments.len() == 1 => {
51 &type_path.path.segments[0].ident
52 }
53 _ => return false,
54 };
55
56 for param in &generics.params {
57 if let syn::GenericParam::Type(type_param) = param
58 && type_param.ident == *type_ident
59 {
60 for bound in &type_param.bounds {
61 if let syn::TypeParamBound::Trait(trait_bound) = bound
62 && let Some(segment) = trait_bound.path.segments.last()
63 {
64 let ident_str = segment.ident.to_string();
65 if ident_str == "FnMut" || ident_str == "Fn" || ident_str == "FnOnce" {
66 return true;
67 }
68 }
69 }
70 }
71 }
72
73 if let Some(where_clause) = &generics.where_clause {
74 for predicate in &where_clause.predicates {
75 if let syn::WherePredicate::Type(pred) = predicate
76 && let Type::Path(bounded_type) = &pred.bounded_ty
77 && bounded_type.path.segments.len() == 1
78 && bounded_type.path.segments[0].ident == *type_ident
79 {
80 for bound in &pred.bounds {
81 if let syn::TypeParamBound::Trait(trait_bound) = bound
82 && let Some(segment) = trait_bound.path.segments.last()
83 {
84 let ident_str = segment.ident.to_string();
85 if ident_str == "FnMut" || ident_str == "Fn" || ident_str == "FnOnce" {
86 return true;
87 }
88 }
89 }
90 }
91 }
92 }
93
94 false
95}
96
97fn is_fn_param(ty: &Type, generics: &syn::Generics) -> bool {
98 is_fn_like_type(ty) || is_generic_fn_like(ty, generics)
99}
100
101fn is_zero_arg_fn_impl_trait(ty: &Type) -> bool {
102 if let Type::ImplTrait(impl_trait) = ty {
103 impl_trait.bounds.iter().any(|bound| {
104 if let syn::TypeParamBound::Trait(trait_bound) = bound
105 && let Some(segment) = trait_bound.path.segments.last()
106 {
107 let ident_str = segment.ident.to_string();
108 if (ident_str == "Fn" || ident_str == "FnMut")
109 && let syn::PathArguments::Parenthesized(args) = &segment.arguments
110 {
111 return args.inputs.is_empty();
112 }
113 }
114 false
115 })
116 } else {
117 false
118 }
119}
120
121fn type_bare_generic_ident(ty: &Type) -> Option<&Ident> {
122 match ty {
123 Type::Path(type_path)
124 if type_path.qself.is_none()
125 && type_path.path.segments.len() == 1
126 && type_path.path.segments[0].arguments.is_none() =>
127 {
128 Some(&type_path.path.segments[0].ident)
129 }
130 _ => None,
131 }
132}
133
134fn stream_mentions_ident(tokens: &TokenStream2, name: &str) -> bool {
135 tokens.clone().into_iter().any(|tt| match tt {
136 proc_macro2::TokenTree::Ident(ident) => ident == name,
137 proc_macro2::TokenTree::Group(group) => stream_mentions_ident(&group.stream(), name),
138 _ => false,
139 })
140}
141
142fn filter_generics(
143 generics: &syn::Generics,
144 strip: &std::collections::HashSet<String>,
145) -> syn::Generics {
146 let mut filtered = generics.clone();
147 filtered.params = filtered
148 .params
149 .into_iter()
150 .filter(|param| match param {
151 syn::GenericParam::Type(type_param) => !strip.contains(&type_param.ident.to_string()),
152 _ => true,
153 })
154 .collect();
155 if let Some(where_clause) = &mut filtered.where_clause {
156 where_clause.predicates = where_clause
157 .predicates
158 .clone()
159 .into_iter()
160 .filter(|predicate| {
161 if let syn::WherePredicate::Type(pred) = predicate
162 && let Some(ident) = type_bare_generic_ident(&pred.bounded_ty)
163 {
164 return !strip.contains(&ident.to_string());
165 }
166 true
167 })
168 .collect();
169 if where_clause.predicates.is_empty() {
170 filtered.where_clause = None;
171 }
172 }
173 filtered
174}
175
176fn is_node_id_return(ty: &Type) -> bool {
177 matches!(
178 ty,
179 Type::Path(type_path)
180 if type_path
181 .path
182 .segments
183 .last()
184 .is_some_and(|segment| segment.ident == "NodeId")
185 )
186}
187
188fn core_crate_path() -> TokenStream2 {
189 let crate_name = crate_name("cranpose")
190 .ok()
191 .or_else(|| crate_name("cranpose-core").ok());
192
193 match crate_name {
194 Some(FoundCrate::Itself) => quote!(crate),
195 Some(FoundCrate::Name(name)) => {
196 let ident = Ident::new(&name, Span::call_site());
197 quote!(#ident)
198 }
199 None => quote!(cranpose_core),
200 }
201}
202
203#[proc_macro_attribute]
204pub fn composable(attr: TokenStream, item: TokenStream) -> TokenStream {
205 let attr_tokens = TokenStream2::from(attr);
206 let mut enable_skip = true;
207 let core_path = core_crate_path();
208 if !attr_tokens.is_empty() {
209 match syn::parse2::<Ident>(attr_tokens) {
210 Ok(ident) if ident == "no_skip" => enable_skip = false,
211 Ok(other) => {
212 return syn::Error::new_spanned(other, "unsupported composable attribute")
213 .to_compile_error()
214 .into();
215 }
216 Err(err) => {
217 return err.to_compile_error().into();
218 }
219 }
220 }
221
222 let mut func = parse_macro_input!(item as ItemFn);
223
224 struct ParamInfo {
225 ident: Ident,
226 pat: Box<Pat>,
227 ty: Type,
228 pat_is_mut: bool,
229 is_impl_trait: bool,
230 }
231
232 let mut param_info: Vec<ParamInfo> = Vec::new();
233
234 for (index, arg) in func.sig.inputs.iter_mut().enumerate() {
235 if let FnArg::Typed(PatType { pat, ty, .. }) = arg {
236 if let Some(reserved) = find_reserved_pattern_ident(pat) {
237 let name = reserved.to_string();
238 return syn::Error::new(
239 reserved.span(),
240 format!("`{name}` is reserved by #[composable]"),
241 )
242 .to_compile_error()
243 .into();
244 }
245 let pat_is_mut = matches!(
246 pat.as_ref(),
247 Pat::Ident(pat_ident) if pat_ident.mutability.is_some()
248 );
249 let is_impl_trait = matches!(**ty, Type::ImplTrait(_));
250
251 if is_impl_trait {
252 let original_pat: Box<Pat> = pat.clone();
253 if let Pat::Ident(pat_ident) = &**pat {
254 param_info.push(ParamInfo {
255 ident: pat_ident.ident.clone(),
256 pat: original_pat,
257 ty: ty.as_ref().clone(),
258 pat_is_mut,
259 is_impl_trait: true,
260 });
261 } else {
262 param_info.push(ParamInfo {
263 ident: Ident::new(&format!("__arg{}", index), Span::mixed_site()),
264 pat: original_pat,
265 ty: ty.as_ref().clone(),
266 pat_is_mut,
267 is_impl_trait: true,
268 });
269 }
270 } else {
271 let ident = Ident::new(&format!("__arg{}", index), Span::mixed_site());
272 let original_pat: Box<Pat> = pat.clone();
273 **pat = syn::parse_quote! { #ident };
274 param_info.push(ParamInfo {
275 ident,
276 pat: original_pat,
277 ty: ty.as_ref().clone(),
278 pat_is_mut,
279 is_impl_trait: false,
280 });
281 }
282 }
283 }
284
285 branch_groups::inject_branch_groups(&core_path, &mut func.block);
286 let has_rust_abi = match &func.sig.abi {
287 None => true,
288 Some(abi) => abi.name.as_ref().is_some_and(|name| name.value() == "Rust"),
289 };
290 if has_rust_abi {
291 func.attrs.push(syn::parse_quote!(#[track_caller]));
292 }
293
294 let scope_label_ident = func.sig.ident.clone();
295 let original_block = func.block.clone();
296 let helper_block = original_block.clone();
297 let recompose_block = original_block.clone();
298 let composer_ident = Ident::new("__composer", Span::mixed_site());
299 let outer_composer_ident = Ident::new("__outer_composer", Span::mixed_site());
300 let caller_key_ident = Ident::new("__cranpose_caller_key", Span::mixed_site());
301 let current_scope_ident = Ident::new("__current_scope", Span::mixed_site());
302 let result_slot_index_ident = Ident::new("__result_slot_index", Span::mixed_site());
303 let has_previous_ident = Ident::new("__has_previous", Span::mixed_site());
304 let result_ident = Ident::new("__result", Span::mixed_site());
305 let value_ident = Ident::new("__value", Span::mixed_site());
306 let key_expr = quote! { #caller_key_ident };
307 let caller_key_stmt = quote! {
308 let #caller_key_ident = #core_path::composable_identity_key({
309 struct __CranposeDefinitionMarker;
310 static __CRANPOSE_DEFINITION_KEY: ::std::sync::OnceLock<#core_path::Key> =
311 ::std::sync::OnceLock::new();
312 *__CRANPOSE_DEFINITION_KEY.get_or_init(|| {
313 #core_path::composable_definition_key(
314 file!(),
315 line!(),
316 column!(),
317 ::std::any::TypeId::of::<__CranposeDefinitionMarker>(),
318 )
319 })
320 });
321 };
322
323 let rebinds_for_no_skip: Vec<_> = param_info
324 .iter()
325 .map(|info| {
326 let ident = &info.ident;
327 let pat = &info.pat;
328 quote! { let #pat = #ident; }
329 })
330 .collect();
331
332 let return_ty: syn::Type = match &func.sig.output {
333 ReturnType::Default => syn::parse_quote! { () },
334 ReturnType::Type(_, ty) => ty.as_ref().clone(),
335 };
336 let returns_unit = match &func.sig.output {
337 ReturnType::Default => true,
338 ReturnType::Type(_, ty) => {
339 matches!(ty.as_ref(), Type::Tuple(tuple) if tuple.elems.is_empty())
340 }
341 };
342 let invalidate_return_consumer = if returns_unit || is_node_id_return(&return_ty) {
343 quote! {}
344 } else {
345 quote! { #composer_ident.__invalidate_return_consumer_scope(); }
346 };
347 let _helper_ident = Ident::new(
348 &format!("__cranpose_impl_{}", func.sig.ident),
349 Span::mixed_site(),
350 );
351 let generics = func.sig.generics.clone();
352 let (_impl_generics, _ty_generics, _where_clause) = generics.split_for_impl();
353
354 let _helper_inputs: Vec<TokenStream2> = param_info
355 .iter()
356 .map(|info| {
357 let ident = &info.ident;
358 let ty = &info.ty;
359 quote! { #ident: #ty }
360 })
361 .collect();
362
363 let has_unhandled_impl_trait = param_info
364 .iter()
365 .any(|info| info.is_impl_trait && !is_zero_arg_fn_impl_trait(&info.ty));
366
367 if enable_skip && !has_unhandled_impl_trait {
368 let helper_ident = Ident::new(
369 &format!("__cranpose_impl_{}", func.sig.ident),
370 Span::mixed_site(),
371 );
372 let generics = func.sig.generics.clone();
373
374 let param_erased: Vec<bool> = param_info
375 .iter()
376 .map(|info| {
377 (info.is_impl_trait && is_zero_arg_fn_impl_trait(&info.ty))
378 || (!info.is_impl_trait
379 && type_bare_generic_ident(&info.ty).is_some()
380 && is_generic_fn_like(&info.ty, &generics))
381 })
382 .collect();
383
384 let mut strippable: std::collections::HashSet<String> = param_info
385 .iter()
386 .zip(¶m_erased)
387 .filter(|(info, erased)| **erased && !info.is_impl_trait)
388 .filter_map(|(info, _)| type_bare_generic_ident(&info.ty))
389 .map(Ident::to_string)
390 .collect();
391 loop {
392 use quote::ToTokens;
393 let mut used_elsewhere: Vec<TokenStream2> = Vec::new();
394 for (info, erased) in param_info.iter().zip(¶m_erased) {
395 if !*erased {
396 used_elsewhere.push(info.ty.to_token_stream());
397 }
398 }
399 used_elsewhere.push(return_ty.to_token_stream());
400 for param in &generics.params {
401 match param {
402 syn::GenericParam::Type(type_param) => {
403 if !strippable.contains(&type_param.ident.to_string()) {
404 used_elsewhere.push(type_param.bounds.to_token_stream());
405 if let Some(default) = &type_param.default {
406 used_elsewhere.push(default.to_token_stream());
407 }
408 }
409 }
410 syn::GenericParam::Const(const_param) => {
411 used_elsewhere.push(const_param.ty.to_token_stream());
412 }
413 syn::GenericParam::Lifetime(_) => {}
414 }
415 }
416 if let Some(where_clause) = &generics.where_clause {
417 for predicate in &where_clause.predicates {
418 if let syn::WherePredicate::Type(pred) = predicate
419 && let Some(ident) = type_bare_generic_ident(&pred.bounded_ty)
420 && strippable.contains(&ident.to_string())
421 {
422 continue;
423 }
424 used_elsewhere.push(predicate.to_token_stream());
425 }
426 }
427 let before = strippable.len();
428 strippable.retain(|name| {
429 !used_elsewhere
430 .iter()
431 .any(|tokens| stream_mentions_ident(tokens, name))
432 });
433 if strippable.len() == before {
434 break;
435 }
436 }
437
438 let helper_generics = filter_generics(&generics, &strippable);
439 let (impl_generics, ty_generics, where_clause) = helper_generics.split_for_impl();
440 let ty_generics_turbofish = ty_generics.as_turbofish();
441
442 let helper_inputs: Vec<TokenStream2> = param_info
443 .iter()
444 .zip(¶m_erased)
445 .filter_map(|(info, erased)| {
446 if info.is_impl_trait && !is_zero_arg_fn_impl_trait(&info.ty) {
447 None
448 } else if *erased {
449 let ident = &info.ident;
450 Some(quote! { #ident: ::std::boxed::Box<dyn ::core::ops::FnMut() + 'static> })
451 } else {
452 let ident = &info.ident;
453 let ty = &info.ty;
454 Some(quote! { #ident: #ty })
455 }
456 })
457 .collect();
458
459 let param_state_slots: Vec<Ident> = (0..param_info.len())
460 .map(|index| Ident::new(&format!("__param_state_slot{}", index), Span::mixed_site()))
461 .collect();
462
463 let param_setup: Vec<TokenStream2> = param_info
464 .iter()
465 .zip(param_state_slots.iter())
466 .zip(¶m_erased)
467 .map(|((info, slot_ident), erased)| {
468 if (info.is_impl_trait && is_zero_arg_fn_impl_trait(&info.ty))
469 || (!info.is_impl_trait && is_fn_param(&info.ty, &generics))
470 {
471 let ident = &info.ident;
472 let update = if *erased {
473 quote! { holder.update_boxed(#ident); }
474 } else {
475 quote! { holder.update(#ident); }
476 };
477 quote! {
478 let #slot_ident = #composer_ident
479 .__use_param_slot(|| #core_path::CallbackHolder::new());
480 #composer_ident.with_slot_value::<#core_path::CallbackHolder, _>(
481 #slot_ident,
482 |holder| {
483 #update
484 },
485 );
486 __changed = true;
487 }
488 } else if info.is_impl_trait {
489 quote! { __changed = true; }
490 } else {
491 let ident = &info.ident;
492 let ty = &info.ty;
493 quote! {
494 let #slot_ident = #composer_ident
495 .__use_param_slot(|| #core_path::ParamState::<#ty>::default());
496 if #composer_ident.with_slot_value_mut::<#core_path::ParamState<#ty>, _>(
497 #slot_ident,
498 |state| state.update(&#ident),
499 )
500 {
501 __changed = true;
502 }
503 }
504 }
505 })
506 .collect();
507
508 let param_setup_recompose: Vec<TokenStream2> = param_info
509 .iter()
510 .zip(param_state_slots.iter())
511 .map(|(info, slot_ident)| {
512 if (info.is_impl_trait && is_zero_arg_fn_impl_trait(&info.ty))
513 || (!info.is_impl_trait && is_fn_param(&info.ty, &generics))
514 {
515 quote! {
516 let #slot_ident = #composer_ident
517 .__use_param_slot(|| #core_path::CallbackHolder::new());
518 }
519 } else if info.is_impl_trait {
520 quote! {}
521 } else {
522 let ty = &info.ty;
523 quote! {
524 let #slot_ident = #composer_ident
525 .__use_param_slot(|| #core_path::ParamState::<#ty>::default());
526 }
527 }
528 })
529 .collect();
530
531 let rebinds: Vec<TokenStream2> = param_info
532 .iter()
533 .zip(param_state_slots.iter())
534 .map(|(info, slot_ident)| {
535 if (info.is_impl_trait && is_zero_arg_fn_impl_trait(&info.ty))
536 || (!info.is_impl_trait && is_fn_param(&info.ty, &generics))
537 {
538 let pat = &info.pat;
539 let can_add_mut = matches!(pat.as_ref(), Pat::Ident(_));
540 if can_add_mut && !info.pat_is_mut {
541 quote! {
542 #[allow(unused_mut)]
543 let mut #pat = #composer_ident
544 .with_slot_value::<#core_path::CallbackHolder, _>(
545 #slot_ident,
546 |holder| holder.clone_rc(),
547 );
548 }
549 } else {
550 quote! {
551 #[allow(unused_mut)]
552 let #pat = #composer_ident
553 .with_slot_value::<#core_path::CallbackHolder, _>(
554 #slot_ident,
555 |holder| holder.clone_rc(),
556 );
557 }
558 }
559 } else if info.is_impl_trait {
560 quote! {}
561 } else {
562 let pat = &info.pat;
563 let ident = &info.ident;
564 quote! {
565 let #pat = #ident;
566 }
567 }
568 })
569 .collect();
570
571 let rebinds_for_recompose: Vec<TokenStream2> = param_info
572 .iter()
573 .zip(param_state_slots.iter())
574 .map(|(info, slot_ident)| {
575 if (info.is_impl_trait && is_zero_arg_fn_impl_trait(&info.ty))
576 || (!info.is_impl_trait && is_fn_param(&info.ty, &generics))
577 {
578 let pat = &info.pat;
579 let can_add_mut = matches!(pat.as_ref(), Pat::Ident(_));
580 if can_add_mut && !info.pat_is_mut {
581 quote! {
582 #[allow(unused_mut)]
583 let mut #pat = #composer_ident
584 .with_slot_value::<#core_path::CallbackHolder, _>(
585 #slot_ident,
586 |holder| holder.clone_rc(),
587 );
588 }
589 } else {
590 quote! {
591 #[allow(unused_mut)]
592 let #pat = #composer_ident
593 .with_slot_value::<#core_path::CallbackHolder, _>(
594 #slot_ident,
595 |holder| holder.clone_rc(),
596 );
597 }
598 }
599 } else if info.is_impl_trait {
600 quote! {}
601 } else {
602 let pat = &info.pat;
603 let ty = &info.ty;
604 quote! {
605 let #pat = #composer_ident
606 .with_slot_value::<#core_path::ParamState<#ty>, _>(
607 #slot_ident,
608 |state| {
609 state
610 .value()
611 .expect("composable parameter missing for recomposition")
612 },
613 );
614 }
615 }
616 })
617 .collect();
618
619 let recompose_fn_ident = Ident::new(
620 &format!("__cranpose_recompose_{}", func.sig.ident),
621 Span::mixed_site(),
622 );
623
624 let recompose_setter = quote! {
625 {
626 #composer_ident.set_recompose_callback(move |
627 #composer_ident: &#core_path::Composer|
628 {
629 let _ = #recompose_fn_ident #ty_generics_turbofish (
630 #composer_ident
631 );
632 });
633 }
634 };
635
636 let helper_body = if returns_unit {
637 quote! {
638 #core_path::debug_label_current_scope(stringify!(#scope_label_ident));
639 let #current_scope_ident = #composer_ident
640 .current_recompose_scope()
641 .expect("missing recompose scope");
642 let mut __changed = #current_scope_ident.should_recompose();
643 #(#param_setup)*
644 #recompose_setter
645 if !__changed && #current_scope_ident.has_composed_once() {
646 #composer_ident.skip_current_group();
647 return;
648 }
649 #(#rebinds)*
650 #helper_block
651 }
652 } else {
653 quote! {
654 #core_path::debug_label_current_scope(stringify!(#scope_label_ident));
655 let #current_scope_ident = #composer_ident
656 .current_recompose_scope()
657 .expect("missing recompose scope");
658 let mut __changed = #current_scope_ident.should_recompose();
659 #(#param_setup)*
660 #recompose_setter
661 let #result_slot_index_ident = #composer_ident
662 .__use_return_slot(|| #core_path::ReturnSlot::<#return_ty>::default());
663 let #has_previous_ident = #composer_ident
664 .with_slot_value::<#core_path::ReturnSlot<#return_ty>, _>(
665 #result_slot_index_ident,
666 |slot| slot.get().is_some(),
667 );
668 if !__changed && #has_previous_ident {
669 #composer_ident.skip_current_group();
670 let #result_ident = #composer_ident
671 .with_slot_value::<#core_path::ReturnSlot<#return_ty>, _>(
672 #result_slot_index_ident,
673 |slot| {
674 slot.get()
675 .expect("composable return value missing during skip")
676 },
677 );
678 return #result_ident;
679 }
680 let #value_ident: #return_ty = {
681 #(#rebinds)*
682 #helper_block
683 };
684 #composer_ident.with_slot_value_mut::<#core_path::ReturnSlot<#return_ty>, _>(
685 #result_slot_index_ident,
686 |slot| {
687 slot.store(#value_ident.clone());
688 },
689 );
690 #value_ident
691 }
692 };
693
694 let recompose_fn_body = if returns_unit {
695 quote! {
696 #(#param_setup_recompose)*
697 #(#rebinds_for_recompose)*
698 #recompose_block
699 #recompose_setter
700 }
701 } else {
702 quote! {
703 #(#param_setup_recompose)*
704 let #result_slot_index_ident = #composer_ident
705 .__use_return_slot(|| #core_path::ReturnSlot::<#return_ty>::default());
706 #(#rebinds_for_recompose)*
707 let #value_ident: #return_ty = {
708 #recompose_block
709 };
710 #composer_ident.with_slot_value_mut::<#core_path::ReturnSlot<#return_ty>, _>(
711 #result_slot_index_ident,
712 |slot| {
713 slot.store(#value_ident.clone());
714 },
715 );
716 #recompose_setter
717 #invalidate_return_consumer
718 #value_ident
719 }
720 };
721
722 let recompose_fn = quote! {
723 #[allow(non_snake_case)]
724 fn #recompose_fn_ident #impl_generics (
725 #composer_ident: &#core_path::Composer
726 ) -> #return_ty #where_clause {
727 #recompose_fn_body
728 }
729 };
730
731 let helper_fn = quote! {
732 #[allow(non_snake_case, clippy::too_many_arguments)]
733 fn #helper_ident #impl_generics (
734 #composer_ident: &#core_path::Composer
735 #(, #helper_inputs)*
736 ) -> #return_ty #where_clause {
737 #helper_body
738 }
739 };
740
741 let wrapper_args: Vec<TokenStream2> = param_info
742 .iter()
743 .zip(¶m_erased)
744 .filter_map(|(info, erased)| {
745 if info.is_impl_trait && !is_zero_arg_fn_impl_trait(&info.ty) {
746 None
747 } else if *erased {
748 let ident = &info.ident;
749 Some(quote! { ::std::boxed::Box::new(#ident) })
750 } else {
751 let ident = &info.ident;
752 Some(quote! { #ident })
753 }
754 })
755 .collect();
756
757 let wrapped = quote!({
758 #caller_key_stmt
759 #core_path::with_current_composer(|#composer_ident: &#core_path::Composer| {
760 #composer_ident.with_group(#key_expr, |#composer_ident: &#core_path::Composer| {
761 #helper_ident(#composer_ident #(, #wrapper_args)*)
762 })
763 })
764 });
765 *func.block = syn::parse2(wrapped).expect("failed to build block");
766 TokenStream::from(quote! {
767 #recompose_fn
768 #helper_fn
769 #func
770 })
771 } else {
772 let wrapped = quote!({
773 #caller_key_stmt
774 #core_path::with_current_composer(|#outer_composer_ident: &#core_path::Composer| {
775 #outer_composer_ident.with_group(#key_expr, |#composer_ident: &#core_path::Composer| {
776 #core_path::debug_label_current_scope(stringify!(#scope_label_ident));
777 #(#rebinds_for_no_skip)*
778 #original_block
779 })
780 })
781 });
782 *func.block = syn::parse2(wrapped).expect("failed to build block");
783 TokenStream::from(quote! { #func })
784 }
785}
786
787fn find_reserved_pattern_ident(pat: &Pat) -> Option<&Ident> {
788 use syn::visit::Visit;
789
790 struct Scan<'ast> {
791 found: Option<&'ast Ident>,
792 }
793 impl<'ast> syn::visit::Visit<'ast> for Scan<'ast> {
794 fn visit_pat_ident(&mut self, node: &'ast syn::PatIdent) {
795 if self.found.is_none() {
796 let name = node.ident.to_string();
797 if name == "__composer" || name.starts_with("__cranpose") {
798 self.found = Some(&node.ident);
799 }
800 }
801 syn::visit::visit_pat_ident(self, node);
802 }
803 }
804 let mut scan = Scan { found: None };
805 scan.visit_pat(pat);
806 scan.found
807}