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