1#![deny(
7 missing_docs,
8 clippy::unwrap_used,
9 rustdoc::broken_intra_doc_links,
10 rustdoc::invalid_rust_codeblocks,
11 rustdoc::invalid_html_tags
12)]
13
14use proc_macro::TokenStream;
15use quote::{format_ident, quote};
16use syn::{
17 Block, Expr, FnArg, GenericArgument, GenericParam, Generics, Ident, ItemFn, Pat, Path,
18 PathArguments, Token, Type, parse::Parse, parse_macro_input, parse_quote, visit_mut::VisitMut,
19};
20
21fn parse_crate_path(attr: proc_macro::TokenStream) -> syn::Result<syn::Path> {
22 if attr.is_empty() {
23 Ok(syn::parse_quote!(::tessera_ui))
24 } else {
25 let tokens: proc_macro2::TokenStream = attr.clone().into();
26 syn::parse(attr).map_err(|_| {
27 syn::Error::new_spanned(tokens, "expected a crate path like `crate` or `tessera_ui`")
28 })
29 }
30}
31
32struct ComponentAttr {
33 crate_path: Path,
34 layout: bool,
35}
36
37impl Parse for ComponentAttr {
38 fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
39 if input.is_empty() {
40 return Ok(Self {
41 crate_path: parse_quote!(::tessera_ui),
42 layout: false,
43 });
44 }
45 let crate_path: Path = input.parse()?;
46 let layout = if input.is_empty() {
47 false
48 } else {
49 input.parse::<Token![,]>()?;
50 let role: Ident = input.parse()?;
51 if role != "layout" {
52 return Err(syn::Error::new_spanned(role, "expected `layout`"));
53 }
54 true
55 };
56 Ok(Self { crate_path, layout })
57 }
58}
59
60#[derive(Clone, Copy, Debug, Default)]
61struct PropAttrConfig {
62 into: bool,
63 skip_setter: bool,
64 skip_eq: bool,
65}
66#[derive(Clone, Debug)]
72enum PropHelperKind {
73 Callback,
74 CallbackWith { arg: Type, ret: Type },
75 RenderSlot,
76 RenderSlotWith { arg: Type },
77}
78
79fn parse_prop_attr(attrs: &[syn::Attribute]) -> syn::Result<PropAttrConfig> {
80 let mut config = PropAttrConfig::default();
81 for attr in attrs {
82 if !attr.path().is_ident("prop") {
83 continue;
84 }
85
86 match &attr.meta {
87 syn::Meta::Path(_) => {
88 return Err(syn::Error::new_spanned(
89 attr,
90 "expected `#[prop(...)]`; supported options are `into`, `skip_setter`, and `skip_eq`",
91 ));
92 }
93 syn::Meta::List(_) => {
94 attr.parse_nested_meta(|meta| {
95 if meta.path.is_ident("into") {
96 if config.into {
97 return Err(meta.error("duplicate `into` in #[prop(...)]"));
98 }
99 config.into = true;
100 return Ok(());
101 }
102 if meta.path.is_ident("skip_setter") {
103 if config.skip_setter {
104 return Err(meta.error("duplicate `skip_setter` in #[prop(...)]"));
105 }
106 config.skip_setter = true;
107 return Ok(());
108 }
109 if meta.path.is_ident("skip_eq") {
110 if config.skip_eq {
111 return Err(meta.error("duplicate `skip_eq` in #[prop(...)]"));
112 }
113 config.skip_eq = true;
114 return Ok(());
115 }
116 if meta.path.is_ident("crate_path") {
117 return Err(meta.error(
118 "`crate_path` is only valid on the `#[tessera]` attribute, not on component parameters",
119 ));
120 }
121
122 Err(meta.error(
123 "unsupported #[prop(...)] option; supported options are `into`, `skip_setter`, and `skip_eq`",
124 ))
125 })?;
126 }
127 syn::Meta::NameValue(_) => {
128 return Err(syn::Error::new_spanned(
129 attr,
130 "unsupported `#[prop = ...]` form; use `#[prop(...)]`",
131 ));
132 }
133 }
134 }
135 Ok(config)
136}
137
138fn option_inner_type(ty: &Type) -> Option<Type> {
139 let Type::Path(type_path) = ty else {
140 return None;
141 };
142 let segment = type_path.path.segments.last()?;
143 if segment.ident != "Option" {
144 return None;
145 }
146 let PathArguments::AngleBracketed(arguments) = &segment.arguments else {
147 return None;
148 };
149 arguments.args.iter().find_map(|arg| match arg {
150 GenericArgument::Type(inner) => Some(inner.clone()),
151 _ => None,
152 })
153}
154
155fn parse_functor_signature(ty: &Type, type_name: &str) -> Option<(Type, Type)> {
156 let Type::Path(type_path) = ty else {
157 return None;
158 };
159 let segment = type_path.path.segments.last()?;
160 if segment.ident != type_name {
161 return None;
162 }
163 let PathArguments::AngleBracketed(arguments) = &segment.arguments else {
164 return None;
165 };
166 let mut types = arguments.args.iter().filter_map(|arg| match arg {
167 GenericArgument::Type(ty) => Some(ty.clone()),
168 _ => None,
169 });
170 let arg = types.next()?;
171 let ret = types.next().unwrap_or_else(|| parse_quote!(()));
172 Some((arg, ret))
173}
174
175fn is_unit_type(ty: &Type) -> bool {
176 matches!(ty, Type::Tuple(tuple) if tuple.elems.is_empty())
177}
178
179fn type_last_segment_ident(ty: &Type) -> Option<&Ident> {
180 let Type::Path(type_path) = ty else {
181 return None;
182 };
183 type_path.path.segments.last().map(|segment| &segment.ident)
184}
185
186fn infer_prop_helper_kind(ty: &Type) -> Option<PropHelperKind> {
187 let value_ty = option_inner_type(ty).unwrap_or_else(|| ty.clone());
188 let ident = type_last_segment_ident(&value_ty)?;
189 if ident == "Callback" {
190 return Some(PropHelperKind::Callback);
191 }
192 if ident == "CallbackWith" {
193 let (arg, ret) = parse_functor_signature(&value_ty, "CallbackWith")?;
194 return Some(PropHelperKind::CallbackWith { arg, ret });
195 }
196 if ident == "RenderSlot" {
197 return Some(PropHelperKind::RenderSlot);
198 }
199 if ident == "RenderSlotWith" {
200 let (arg, _ret) = parse_functor_signature(&value_ty, "RenderSlotWith")?;
201 return Some(PropHelperKind::RenderSlotWith { arg });
202 }
203 None
204}
205
206fn is_arc_type(ty: &Type) -> bool {
207 type_last_segment_ident(ty).is_some_and(|ident| ident == "Arc")
208}
209
210fn is_rc_type(ty: &Type) -> bool {
211 type_last_segment_ident(ty).is_some_and(|ident| ident == "Rc")
212}
213
214fn field_compare_expr(field_ident: &Ident, ty: &Type) -> proc_macro2::TokenStream {
215 if is_arc_type(ty) {
216 return quote! { ::std::sync::Arc::ptr_eq(&self.#field_ident, &other.#field_ident) };
217 }
218 if is_rc_type(ty) {
219 return quote! { ::std::rc::Rc::ptr_eq(&self.#field_ident, &other.#field_ident) };
220 }
221 if let Some(inner_ty) = option_inner_type(ty) {
222 if is_arc_type(&inner_ty) {
223 return quote! {
224 match (&self.#field_ident, &other.#field_ident) {
225 (Some(lhs), Some(rhs)) => ::std::sync::Arc::ptr_eq(lhs, rhs),
226 (None, None) => true,
227 _ => false,
228 }
229 };
230 }
231 if is_rc_type(&inner_ty) {
232 return quote! {
233 match (&self.#field_ident, &other.#field_ident) {
234 (Some(lhs), Some(rhs)) => ::std::rc::Rc::ptr_eq(lhs, rhs),
235 (None, None) => true,
236 _ => false,
237 }
238 };
239 }
240 }
241 quote! { self.#field_ident == other.#field_ident }
242}
243
244#[cfg(feature = "shard")]
245#[derive(Default)]
246struct ShardMacroArgs {
247 crate_path: Option<Path>,
248 shard_crate_path: Option<Path>,
249 state_type: Option<Type>,
250 lifecycle: Option<Ident>,
251}
252
253#[cfg(feature = "shard")]
254struct ShardParam {
255 ident: Ident,
256 ty: Type,
257 is_router: bool,
258}
259
260#[cfg(feature = "shard")]
261fn parse_shard_params(sig: &syn::Signature) -> syn::Result<Vec<ShardParam>> {
262 let mut params = Vec::with_capacity(sig.inputs.len());
263 for arg in &sig.inputs {
264 match arg {
265 FnArg::Receiver(receiver) => {
266 return Err(syn::Error::new_spanned(
267 receiver,
268 "#[shard] does not support methods; use a free function",
269 ));
270 }
271 FnArg::Typed(pat_type) => {
272 let Pat::Ident(pat_ident) = pat_type.pat.as_ref() else {
273 return Err(syn::Error::new_spanned(
274 &pat_type.pat,
275 "#[shard] parameters must be simple named bindings like `foo: T`",
276 ));
277 };
278 let is_router = pat_type
279 .attrs
280 .iter()
281 .any(|attr| attr.path().is_ident("router"));
282 params.push(ShardParam {
283 ident: pat_ident.ident.clone(),
284 ty: (*pat_type.ty).clone(),
285 is_router,
286 });
287 }
288 }
289 }
290 Ok(params)
291}
292
293#[cfg(feature = "shard")]
294fn is_state_router_controller_type(ty: &Type) -> bool {
295 let Type::Path(type_path) = ty else {
296 return false;
297 };
298 let Some(state_segment) = type_path.path.segments.last() else {
299 return false;
300 };
301 if state_segment.ident != "State" {
302 return false;
303 }
304 let syn::PathArguments::AngleBracketed(args) = &state_segment.arguments else {
305 return false;
306 };
307 let Some(syn::GenericArgument::Type(Type::Path(controller_path))) = args.args.first() else {
308 return false;
309 };
310 controller_path
311 .path
312 .segments
313 .last()
314 .is_some_and(|segment| segment.ident == "RouterController")
315}
316
317#[cfg(feature = "shard")]
318impl Parse for ShardMacroArgs {
319 fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
320 let mut args = ShardMacroArgs::default();
321 while !input.is_empty() {
322 let key: Ident = input.parse()?;
323 input.parse::<Token![=]>()?;
324 match key.to_string().as_str() {
325 "crate_path" => {
326 if args.crate_path.is_some() {
327 return Err(syn::Error::new(
328 key.span(),
329 "duplicate `crate_path` argument",
330 ));
331 }
332 args.crate_path = Some(input.parse::<Path>()?);
333 }
334 "shard_crate_path" => {
335 if args.shard_crate_path.is_some() {
336 return Err(syn::Error::new(
337 key.span(),
338 "duplicate `shard_crate_path` argument",
339 ));
340 }
341 args.shard_crate_path = Some(input.parse::<Path>()?);
342 }
343 "state" => {
344 if args.state_type.is_some() {
345 return Err(syn::Error::new(key.span(), "duplicate `state` argument"));
346 }
347 args.state_type = Some(input.parse::<Type>()?);
348 }
349 "lifecycle" => {
350 if args.lifecycle.is_some() {
351 return Err(syn::Error::new(
352 key.span(),
353 "duplicate `lifecycle` argument",
354 ));
355 }
356 args.lifecycle = Some(input.parse::<Ident>()?);
357 }
358 _ => {
359 return Err(syn::Error::new(
360 key.span(),
361 "unsupported #[shard(...)] argument; expected `state`, `lifecycle`, `crate_path`, or `shard_crate_path`",
362 ));
363 }
364 }
365
366 if input.peek(Token![,]) {
367 input.parse::<Token![,]>()?;
368 }
369 }
370 Ok(args)
371 }
372}
373
374enum ComponentPropSignature {
377 Unit,
378 Params(Vec<PropFieldSpec>),
379}
380
381fn strict_prop_signature(sig: &syn::Signature) -> Result<ComponentPropSignature, syn::Error> {
382 if sig.inputs.is_empty() {
383 return Ok(ComponentPropSignature::Unit);
384 }
385
386 let mut fields = Vec::new();
387 for arg in &sig.inputs {
388 let FnArg::Typed(arg) = arg else {
389 return Err(syn::Error::new_spanned(
390 arg,
391 "#[tessera] methods are not supported; use free functions with named parameters",
392 ));
393 };
394
395 let Pat::Ident(pat_ident) = arg.pat.as_ref() else {
396 return Err(syn::Error::new_spanned(
397 &arg.pat,
398 "component parameters must be named identifiers",
399 ));
400 };
401
402 if pat_ident.by_ref.is_some() || pat_ident.mutability.is_some() {
403 return Err(syn::Error::new_spanned(
404 pat_ident,
405 "component parameters must be plain named bindings like `foo: T`",
406 ));
407 }
408
409 if matches!(arg.ty.as_ref(), Type::Reference(_)) {
410 return Err(syn::Error::new_spanned(
411 &arg.ty,
412 "component parameters must be owned types; borrowed parameter types are not supported",
413 ));
414 }
415
416 fields.push(PropFieldSpec {
417 ident: pat_ident.ident.clone(),
418 ty: (*arg.ty).clone(),
419 attrs: parse_prop_attr(&arg.attrs)?,
420 helper: infer_prop_helper_kind(arg.ty.as_ref()),
421 });
422 }
423
424 Ok(ComponentPropSignature::Params(fields))
425}
426
427fn prop_assert_tokens(crate_path: &syn::Path, prop_type: &syn::Type) -> proc_macro2::TokenStream {
428 quote! {
429 {
430 fn __tessera_assert_prop<T: #crate_path::__private::Prop>() {}
431 __tessera_assert_prop::<#prop_type>();
432 }
433 }
434}
435
436struct PropFieldSpec {
437 ident: Ident,
438 ty: Type,
439 attrs: PropAttrConfig,
440 helper: Option<PropHelperKind>,
441}
442
443fn is_required_component_field(field: &PropFieldSpec) -> bool {
444 option_inner_type(&field.ty).is_none()
445}
446
447fn stored_value_ty(field_ty: &Type) -> Type {
448 option_inner_type(field_ty).unwrap_or_else(|| field_ty.clone())
449}
450
451fn stored_field_ty(field_ty: &Type) -> Type {
452 let value_ty = stored_value_ty(field_ty);
453 parse_quote!(::core::option::Option<#value_ty>)
454}
455
456fn generate_constructor_param_and_assignment(
457 field: &PropFieldSpec,
458 crate_path: &Path,
459) -> syn::Result<(proc_macro2::TokenStream, proc_macro2::TokenStream)> {
460 let ident = &field.ident;
461 let field_ty = &field.ty;
462 let helper_uses_into = field.helper.is_some();
463
464 if let Some(helper) = &field.helper {
465 match helper {
466 PropHelperKind::CallbackWith { arg, ret } if is_unit_type(arg) => {
467 let param = quote! {
468 #ident: impl Fn() -> #ret + Send + Sync + 'static
469 };
470 let assignment = quote! {
471 __tessera_call.configure_deferred(stringify!(#ident), move |__tessera_pending_props| {
472 __tessera_pending_props.#ident = Some(#crate_path::CallbackWith::new(move |()| #ident()));
473 });
474 };
475 return Ok((param, assignment));
476 }
477 PropHelperKind::RenderSlotWith { arg } if is_unit_type(arg) => {
478 let output = format_ident!("__Tessera{}Output", pascal_case_ident(ident, ""));
479 let param = quote! {
480 #ident: impl Fn() -> #output + Send + Sync + 'static
481 };
482 let assignment = quote! {
483 __tessera_call.configure_deferred(stringify!(#ident), move |__tessera_pending_props| {
484 __tessera_pending_props.#ident = Some(#crate_path::RenderSlotWith::new(move |()| #ident()));
485 });
486 };
487 return Ok((param, assignment));
488 }
489 PropHelperKind::Callback
490 | PropHelperKind::CallbackWith { .. }
491 | PropHelperKind::RenderSlot
492 | PropHelperKind::RenderSlotWith { .. } => {}
493 }
494 }
495
496 let constructor_param = if helper_uses_into {
497 quote!(#ident: impl Into<#field_ty> + Send + Sync + 'static)
498 } else if field.attrs.into {
499 quote!(#ident: impl Into<#field_ty>)
500 } else {
501 quote!(#ident: #field_ty)
502 };
503 let constructor_assignment = if helper_uses_into {
504 quote! {
505 __tessera_call.configure_deferred(stringify!(#ident), move |__tessera_pending_props| {
506 __tessera_pending_props.#ident = Some(#ident.into());
507 });
508 }
509 } else {
510 let value = if field.attrs.into {
511 quote!(#ident.into())
512 } else {
513 quote!(#ident)
514 };
515 quote! {
516 let #ident = #value;
517 __tessera_call.configure(stringify!(#ident), move |__tessera_pending_props| __tessera_pending_props.#ident = Some(#ident));
518 }
519 };
520
521 Ok((constructor_param, constructor_assignment))
522}
523
524fn pascal_case_ident(base: &Ident, suffix: &str) -> Ident {
525 let mut output = String::new();
526 for segment in base
527 .to_string()
528 .split('_')
529 .filter(|segment| !segment.is_empty())
530 {
531 let mut chars = segment.chars();
532 if let Some(first) = chars.next() {
533 output.extend(first.to_uppercase());
534 output.push_str(chars.as_str());
535 }
536 }
537 output.push_str(suffix);
538 format_ident!("{output}")
539}
540
541fn hidden_props_ident(fn_name: &Ident) -> Ident {
542 format_ident!("__Tessera{}Props", pascal_case_ident(fn_name, ""))
543}
544
545fn builder_ident(fn_name: &Ident) -> Ident {
546 pascal_case_ident(fn_name, "Builder")
547}
548
549fn hidden_component_impl_ident(fn_name: &Ident) -> Ident {
550 format_ident!("__tessera_{}_impl", fn_name)
551}
552
553fn hidden_const_marker_ident(fn_name: &Ident, const_ident: &Ident) -> Ident {
554 format_ident!(
555 "__Tessera{}ConstMarker{}",
556 pascal_case_ident(fn_name, ""),
557 pascal_case_ident(const_ident, "")
558 )
559}
560
561fn validate_component_generics(generics: &Generics) -> syn::Result<()> {
562 for param in &generics.params {
563 if let GenericParam::Lifetime(param) = param {
564 return Err(syn::Error::new_spanned(
565 param,
566 "#[tessera] components do not support lifetime generics; use owned props instead",
567 ));
568 }
569 }
570 Ok(())
571}
572
573fn generic_marker_tokens(
574 fn_name: &Ident,
575 generics: &Generics,
576) -> (
577 Vec<proc_macro2::TokenStream>,
578 Option<proc_macro2::TokenStream>,
579) {
580 let mut const_marker_defs = Vec::new();
581 let mut marker_parts = Vec::new();
582
583 for param in &generics.params {
584 match param {
585 GenericParam::Type(param) => {
586 let ident = ¶m.ident;
587 marker_parts.push(quote!(#ident));
588 }
589 GenericParam::Const(param) => {
590 let ident = ¶m.ident;
591 let ty = ¶m.ty;
592 let marker_ident = hidden_const_marker_ident(fn_name, ident);
593 const_marker_defs.push(quote! {
594 struct #marker_ident<const #ident: #ty>;
595 });
596 marker_parts.push(quote!(#marker_ident<#ident>));
597 }
598 GenericParam::Lifetime(_) => unreachable!("lifetime generics are rejected earlier"),
599 }
600 }
601
602 if marker_parts.is_empty() {
603 return (const_marker_defs, None);
604 }
605
606 let marker_ty = if marker_parts.len() == 1 {
607 let marker_part = &marker_parts[0];
608 quote!(#marker_part)
609 } else {
610 quote!((#(#marker_parts,)*))
611 };
612
613 (
614 const_marker_defs,
615 Some(quote! {
616 __tessera_generic_marker: ::core::marker::PhantomData<#marker_ty>
617 }),
618 )
619}
620
621fn generate_props_clone_default_impls(
622 type_name: &Ident,
623 generics: &Generics,
624 field_idents: &[Ident],
625 has_generic_marker: bool,
626) -> proc_macro2::TokenStream {
627 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
628 let clone_fields: Vec<_> = field_idents
629 .iter()
630 .map(|ident| quote!(#ident: self.#ident.clone()))
631 .collect();
632 let default_fields: Vec<_> = field_idents
633 .iter()
634 .map(|ident| quote!(#ident: ::core::default::Default::default()))
635 .collect();
636 let clone_marker = has_generic_marker.then(|| {
637 quote! {
638 __tessera_generic_marker: ::core::marker::PhantomData,
639 }
640 });
641 let default_marker = has_generic_marker.then(|| {
642 quote! {
643 __tessera_generic_marker: ::core::marker::PhantomData,
644 }
645 });
646
647 quote! {
648 impl #impl_generics ::core::clone::Clone for #type_name #ty_generics #where_clause {
649 fn clone(&self) -> Self {
650 Self {
651 #(#clone_fields,)*
652 #clone_marker
653 }
654 }
655 }
656
657 impl #impl_generics ::core::default::Default for #type_name #ty_generics #where_clause {
658 fn default() -> Self {
659 Self {
660 #(#default_fields,)*
661 #default_marker
662 }
663 }
664 }
665 }
666}
667
668fn generate_prop_like_impls(
669 type_name: &Ident,
670 component_impl: &Ident,
671 generics: &Generics,
672 fields: &[PropFieldSpec],
673 crate_path: &Path,
674) -> proc_macro2::TokenStream {
675 let compare_fields: Vec<_> = fields
676 .iter()
677 .filter(|field| !field.attrs.skip_eq)
678 .map(|field| field_compare_expr(&field.ident, &stored_field_ty(&field.ty)))
679 .collect();
680 let prop_eq_expr = if compare_fields.is_empty() {
681 quote! { true }
682 } else {
683 quote! { true #(&& #compare_fields)* }
684 };
685 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
686
687 quote! {
688 impl #impl_generics ::core::cmp::PartialEq for #type_name #ty_generics #where_clause {
689 fn eq(&self, other: &Self) -> bool {
690 #prop_eq_expr
691 }
692 }
693
694 impl #impl_generics #crate_path::__private::Prop for #type_name #ty_generics #where_clause {
695 fn prop_eq(&self, other: &Self) -> bool {
696 <Self as ::core::cmp::PartialEq>::eq(self, other)
697 }
698
699 fn run_component(self: ::std::sync::Arc<Self>) {
700 #component_impl(&self);
701 }
702 }
703 }
704}
705
706fn generate_builder_methods(
707 fields: &[PropFieldSpec],
708 crate_path: &syn::Path,
709) -> syn::Result<Vec<proc_macro2::TokenStream>> {
710 let mut methods = Vec::new();
711 for field in fields {
712 if field.attrs.skip_setter || is_required_component_field(field) {
713 continue;
714 }
715 let ident = &field.ident;
716 let ty = stored_value_ty(&field.ty);
717 let method_doc = format!("Configure `{ident}` before this component executes.");
718 if let Some(helper) = &field.helper {
719 let shared = format_ident!("{}_shared", ident);
720 let (generics, bound, value) = match helper {
721 PropHelperKind::Callback => (
722 quote!(<F>),
723 quote!(F: Fn() + Send + Sync + 'static),
724 quote!(#crate_path::Callback::new(value)),
725 ),
726 PropHelperKind::CallbackWith { arg, ret } if is_unit_type(arg) => (
727 quote!(<F>),
728 quote!(F: Fn() -> #ret + Send + Sync + 'static),
729 quote!(#crate_path::CallbackWith::new(move |()| value())),
730 ),
731 PropHelperKind::CallbackWith { arg, ret } => (
732 quote!(<F>),
733 quote!(F: Fn(#arg) -> #ret + Send + Sync + 'static),
734 quote!(#crate_path::CallbackWith::new(value)),
735 ),
736 PropHelperKind::RenderSlot => (
737 quote!(<F, Output>),
738 quote!(F: Fn() -> Output + Send + Sync + 'static),
739 quote!(#crate_path::RenderSlot::new(value)),
740 ),
741 PropHelperKind::RenderSlotWith { arg } if is_unit_type(arg) => (
742 quote!(<F, Output>),
743 quote!(F: Fn() -> Output + Send + Sync + 'static),
744 quote!(#crate_path::RenderSlotWith::new(move |()| value())),
745 ),
746 PropHelperKind::RenderSlotWith { arg } => (
747 quote!(<F, Output>),
748 quote!(F: Fn(#arg) -> Output + Send + Sync + 'static),
749 quote!(#crate_path::RenderSlotWith::new(value)),
750 ),
751 };
752 methods.push(quote! {
753 #[doc = #method_doc]
754 #[track_caller]
755 pub fn #ident #generics (&self, value: F) -> Self where #bound {
756 self.props.configure_deferred(stringify!(#ident), move |props| {
757 props.#ident = Some(#value);
758 });
759 Self { props: self.props }
760 }
761 #[doc = #method_doc]
762 #[track_caller]
763 pub fn #shared(&self, value: impl Into<#ty> + Send + Sync + 'static) -> Self {
764 self.props.configure_deferred(stringify!(#ident), move |props| props.#ident = Some(value.into()));
765 Self { props: self.props }
766 }
767 });
768 } else {
769 let parameter = if field.attrs.into {
770 quote!(impl Into<#ty>)
771 } else {
772 quote!(#ty)
773 };
774 let value = if field.attrs.into {
775 quote!(value.into())
776 } else {
777 quote!(value)
778 };
779 methods.push(quote! {
780 #[doc = #method_doc]
781 #[track_caller]
782 pub fn #ident(&self, value: #parameter) -> Self {
783 let value = #value;
784 self.props.configure(stringify!(#ident), move |props| props.#ident = Some(value));
785 Self { props: self.props }
786 }
787 });
788 }
789 let optional = format_ident!("{}_optional", ident);
790 methods.push(quote! {
791 #[doc = #method_doc]
792 #[track_caller]
793 pub fn #optional(&self, value: Option<#ty>) -> Self {
794 self.props.configure(stringify!(#ident), move |props| props.#ident = value);
795 Self { props: self.props }
796 }
797 });
798 }
799 Ok(methods)
800}
801
802fn component_type_id_tokens(
803 fn_name: &syn::Ident,
804 prop_type: &syn::Type,
805) -> proc_macro2::TokenStream {
806 quote! {
807 {
808 use std::{any::TypeId, hash::{Hash, Hasher}};
809 let mut hasher = std::collections::hash_map::DefaultHasher::new();
810 module_path!().hash(&mut hasher);
811 stringify!(#fn_name).hash(&mut hasher);
812 TypeId::of::<#prop_type>().hash(&mut hasher);
813 hasher.finish()
814 }
815 }
816}
817
818struct ControlFlowInstrumenter {
819 counter: u64,
820}
821
822impl ControlFlowInstrumenter {
823 fn new() -> Self {
824 Self { counter: 1 }
825 }
826
827 fn next_group_id(&mut self) -> u64 {
828 let id = self.counter;
829 self.counter += 1;
830 id
831 }
832
833 fn wrap_expr_in_group(&mut self, expr: &mut Expr) {
834 self.visit_expr_mut(expr);
835 let group_id = self.next_group_id();
836 let original_expr = &expr;
839 let new_expr: Expr = parse_quote! {
840 {
841 let _group_guard = ::tessera_ui::__private::GroupGuard::new(#group_id);
842 #original_expr
843 }
844 };
845 *expr = new_expr;
846 }
847
848 fn wrap_block_in_group(&mut self, block: &mut Block) {
849 self.visit_block_mut(block);
850
851 let group_id = self.next_group_id();
852 let original_stmts = &block.stmts;
853
854 let new_block: Block = parse_quote! {
855 {
856 let _group_guard = ::tessera_ui::__private::GroupGuard::new(#group_id);
857 #(#original_stmts)*
858 }
859 };
860
861 *block = new_block;
862 }
863
864 fn wrap_block_in_path_group(&mut self, block: &mut Block) {
865 self.visit_block_mut(block);
866
867 let group_id = self.next_group_id();
868 let original_stmts = &block.stmts;
869
870 let new_block: Block = parse_quote! {
871 {
872 let _group_guard = ::tessera_ui::__private::PathGroupGuard::new(#group_id);
873 #(#original_stmts)*
874 }
875 };
876
877 *block = new_block;
878 }
879}
880
881impl VisitMut for ControlFlowInstrumenter {
882 fn visit_expr_if_mut(&mut self, i: &mut syn::ExprIf) {
883 self.visit_expr_mut(&mut i.cond);
884 self.wrap_block_in_group(&mut i.then_branch);
885 if let Some((_, else_branch)) = &mut i.else_branch {
886 match &mut **else_branch {
887 Expr::Block(block_expr) => {
888 self.wrap_block_in_group(&mut block_expr.block);
889 }
890 Expr::If(_) => {
891 self.visit_expr_mut(else_branch);
892 }
893 _ => {
894 self.wrap_expr_in_group(else_branch);
895 }
896 }
897 }
898 }
899
900 fn visit_expr_match_mut(&mut self, m: &mut syn::ExprMatch) {
901 self.visit_expr_mut(&mut m.expr);
902 for arm in &mut m.arms {
903 self.wrap_expr_in_group(&mut arm.body);
904 }
905 }
906
907 fn visit_expr_for_loop_mut(&mut self, f: &mut syn::ExprForLoop) {
908 self.visit_expr_mut(&mut f.expr);
909 self.wrap_block_in_path_group(&mut f.body);
910 }
911
912 fn visit_expr_while_mut(&mut self, w: &mut syn::ExprWhile) {
913 self.visit_expr_mut(&mut w.cond);
914 self.wrap_block_in_path_group(&mut w.body);
915 }
916
917 fn visit_expr_loop_mut(&mut self, l: &mut syn::ExprLoop) {
918 self.wrap_block_in_path_group(&mut l.body);
919 }
920}
921
922#[proc_macro_attribute]
946pub fn tessera(attr: TokenStream, item: TokenStream) -> TokenStream {
947 let ComponentAttr { crate_path, layout } = parse_macro_input!(attr as ComponentAttr);
948 let begin_scope = if layout {
949 quote!(#crate_path::__private::begin_layout_component_node)
950 } else {
951 quote!(#crate_path::__private::begin_component_node)
952 };
953
954 let mut input_fn = parse_macro_input!(item as ItemFn);
955 if let Some(conflicting_attr) = input_fn
956 .attrs
957 .iter()
958 .find(|attr| attr.path().is_ident("shard"))
959 {
960 return syn::Error::new_spanned(
961 conflicting_attr,
962 "#[tessera] and #[shard] cannot be combined on the same function; use #[shard] only",
963 )
964 .to_compile_error()
965 .into();
966 }
967
968 if input_fn.sig.constness.is_some()
969 || input_fn.sig.asyncness.is_some()
970 || !matches!(input_fn.sig.safety, syn::Safety::Default)
971 || input_fn.sig.abi.is_some()
972 || input_fn.sig.variadic.is_some()
973 {
974 return syn::Error::new_spanned(
975 &input_fn.sig,
976 "#[tessera] components must be plain free functions",
977 )
978 .to_compile_error()
979 .into();
980 }
981 if !matches!(input_fn.sig.output, syn::ReturnType::Default) {
982 return syn::Error::new_spanned(
983 &input_fn.sig.output,
984 "#[tessera] components must not return a value",
985 )
986 .to_compile_error()
987 .into();
988 }
989
990 let fn_name = &input_fn.sig.ident;
991 let fn_vis = &input_fn.vis;
992 let fn_attrs = &input_fn.attrs;
993 if let Err(err) = validate_component_generics(&input_fn.sig.generics) {
994 return err.to_compile_error().into();
995 }
996 let prop_signature = match strict_prop_signature(&input_fn.sig) {
997 Ok(v) => v,
998 Err(err) => return err.to_compile_error().into(),
999 };
1000
1001 let mut instrumenter = ControlFlowInstrumenter::new();
1002 instrumenter.visit_block_mut(&mut input_fn.block);
1003 let fn_block = &input_fn.block;
1004 let generics = &input_fn.sig.generics;
1005 let mut item_generics = generics.clone();
1006 item_generics.where_clause = None;
1007 let where_clause = &generics.where_clause;
1008 let (impl_generics, ty_generics, _) = generics.split_for_impl();
1009
1010 let fields = match &prop_signature {
1011 ComponentPropSignature::Unit => &[][..],
1012 ComponentPropSignature::Params(fields) => fields.as_slice(),
1013 };
1014 let expanded = {
1015 let props_ident = hidden_props_ident(fn_name);
1016 let builder_ident = builder_ident(fn_name);
1017 let impl_ident = hidden_component_impl_ident(fn_name);
1018 let (const_marker_defs, marker_field_def) = generic_marker_tokens(fn_name, generics);
1019 let has_generic_marker = marker_field_def.is_some();
1020 let prop_type: syn::Type = syn::parse_quote!(#props_ident #ty_generics);
1021 let uncached_type_id = component_type_id_tokens(fn_name, &prop_type);
1022 let (metadata, component_type_id_tokens) = if generics.params.is_empty() {
1023 (
1024 quote! {
1025 impl #props_ident {
1026 fn __tessera_type_id() -> u64 {
1027 static TYPE_ID: ::std::sync::OnceLock<u64> = ::std::sync::OnceLock::new();
1028 *TYPE_ID.get_or_init(|| #uncached_type_id)
1029 }
1030 }
1031 },
1032 quote! { #props_ident::__tessera_type_id() },
1033 )
1034 } else {
1035 (quote! {}, uncached_type_id)
1036 };
1037 let field_idents: Vec<_> = fields.iter().map(|field| field.ident.clone()).collect();
1038 let required_fields: Vec<_> = fields
1039 .iter()
1040 .filter(|field| is_required_component_field(field))
1041 .collect();
1042 let mut entry_generics = item_generics.clone();
1043 for field in &required_fields {
1044 if matches!(&field.helper, Some(PropHelperKind::RenderSlotWith { arg }) if is_unit_type(arg))
1045 {
1046 let output =
1047 format_ident!("__Tessera{}Output", pascal_case_ident(&field.ident, ""));
1048 entry_generics.params.push(syn::parse_quote!(#output));
1049 }
1050 }
1051 let initial_count = required_fields
1054 .iter()
1055 .take_while(|field| field.helper.is_none() && !field.attrs.into)
1056 .count();
1057 let (initial_fields, pending_fields) = required_fields.split_at(initial_count);
1058 let constructor_params_and_assignments: Vec<_> = match pending_fields
1059 .iter()
1060 .map(|field| generate_constructor_param_and_assignment(field, &crate_path))
1061 .collect::<syn::Result<Vec<_>>>()
1062 {
1063 Ok(entries) => entries,
1064 Err(err) => return err.to_compile_error().into(),
1065 };
1066 let constructor_params: Vec<_> = initial_fields
1067 .iter()
1068 .map(|field| {
1069 let ident = &field.ident;
1070 let ty = &field.ty;
1071 quote!(#ident: #ty)
1072 })
1073 .chain(
1074 constructor_params_and_assignments
1075 .iter()
1076 .map(|(param, _)| param.clone()),
1077 )
1078 .collect();
1079 let constructor_assignments: Vec<_> = constructor_params_and_assignments
1080 .iter()
1081 .map(|(_, assignment)| assignment.clone())
1082 .collect();
1083 let initial_props = if initial_fields.is_empty() {
1084 quote!(<#props_ident #ty_generics>::default())
1085 } else {
1086 let initializers = initial_fields.iter().map(|field| {
1087 let ident = &field.ident;
1088 quote!(#ident: Some(#ident),)
1089 });
1090 quote!(#props_ident {
1091 #(#initializers)*
1092 ..<#props_ident #ty_generics>::default()
1093 })
1094 };
1095 let field_defs: Vec<_> = fields
1096 .iter()
1097 .map(|field| {
1098 let ident = &field.ident;
1099 let ty = stored_field_ty(&field.ty);
1100 quote!(#ident: #ty)
1101 })
1102 .collect();
1103 let field_resolutions: Vec<_> = fields
1104 .iter()
1105 .map(|field| {
1106 let ident = &field.ident;
1107 let ty = &field.ty;
1108 if option_inner_type(ty).is_some() {
1109 return quote! {
1110 let #ident: #ty = __tessera_props.#ident.clone();
1111 };
1112 }
1113
1114 let missing_prop_message = format!(
1115 "missing required prop `{}` for component `{}`",
1116 ident, fn_name
1117 );
1118 quote! {
1119 let #ident: #ty = __tessera_props.#ident.clone().unwrap_or_else(|| {
1120 panic!(#missing_prop_message)
1121 });
1122 }
1123 })
1124 .collect();
1125 let prop_impl_tokens =
1126 generate_prop_like_impls(&props_ident, &impl_ident, generics, fields, &crate_path);
1127 let builder_methods = match generate_builder_methods(fields, &crate_path) {
1128 Ok(methods) => methods,
1129 Err(err) => return err.to_compile_error().into(),
1130 };
1131 let prop_assert_tokens = prop_assert_tokens(&crate_path, &prop_type);
1132 let marker_field_def = marker_field_def
1133 .map(|field| quote!(#field,))
1134 .unwrap_or_default();
1135 let props_clone_default_impls = generate_props_clone_default_impls(
1136 &props_ident,
1137 generics,
1138 &field_idents,
1139 has_generic_marker,
1140 );
1141
1142 quote! {
1143 #(#const_marker_defs)*
1144
1145 struct #props_ident #item_generics #where_clause {
1146 #(#field_defs,)*
1147 #marker_field_def
1148 }
1149
1150 #props_clone_default_impls
1151 #prop_impl_tokens
1152 #metadata
1153
1154 #[doc = concat!("Configuration handle returned by [`", stringify!(#fn_name), "`].")]
1155 #fn_vis struct #builder_ident #item_generics #where_clause {
1156 props: #crate_path::__private::ComponentCall<#props_ident #ty_generics>,
1157 }
1158
1159 impl #impl_generics #builder_ident #ty_generics #where_clause {
1160 #(#builder_methods)*
1161 }
1162
1163 #(#fn_attrs)*
1164 #fn_vis fn #fn_name #entry_generics(
1165 #(#constructor_params),*
1166 ) -> #builder_ident #ty_generics #where_clause {
1167 let __tessera_call = #crate_path::__private::register_component_call(
1168 stringify!(#fn_name), #component_type_id_tokens,
1169 #initial_props,
1170 );
1171 #(#constructor_assignments)*
1172 #builder_ident { props: __tessera_call }
1173 }
1174
1175 fn #impl_ident #item_generics(__tessera_props: &::std::sync::Arc<#props_ident #ty_generics>) #where_clause {
1176 #prop_assert_tokens
1177 let __tessera_scope = #begin_scope(
1178 stringify!(#fn_name),
1179 #component_type_id_tokens,
1180 __tessera_props,
1181 );
1182 if __tessera_scope.is_retained() {
1183 return;
1184 }
1185 #crate_path::__private::with_pending_scope(|| {
1186 #crate_path::__private::record_component_execution(
1187 stringify!(#fn_name),
1188 __tessera_scope.instance_logic_id(),
1189 );
1190 #(#field_resolutions)*
1191 #fn_block;
1192 });
1193 }
1194 }
1195 };
1196
1197 TokenStream::from(expanded)
1198}
1199
1200#[proc_macro_attribute]
1208pub fn lint(_attr: TokenStream, item: TokenStream) -> TokenStream {
1209 item
1210}
1211
1212#[proc_macro_attribute]
1219pub fn entry(attr: TokenStream, item: TokenStream) -> TokenStream {
1220 let crate_path: syn::Path = match parse_crate_path(attr) {
1221 Ok(path) => path,
1222 Err(err) => return err.to_compile_error().into(),
1223 };
1224 let mut input_fn = parse_macro_input!(item as ItemFn);
1225
1226 if !input_fn.sig.inputs.is_empty() {
1227 return syn::Error::new_spanned(
1228 &input_fn.sig.inputs,
1229 "entry functions must not accept arguments",
1230 )
1231 .to_compile_error()
1232 .into();
1233 }
1234
1235 input_fn.attrs.retain(|attr| !attr.path().is_ident("entry"));
1236 let fn_name = &input_fn.sig.ident;
1237
1238 let expanded = quote! {
1239 #input_fn
1240
1241 #[cfg(target_os = "android")]
1242 #[unsafe(no_mangle)]
1243 fn android_main(android_app: #crate_path::winit::platform::android::activity::AndroidApp) {
1244 if let Err(err) = #fn_name().run_android(android_app) {
1245 eprintln!("App failed to run: {err}");
1246 }
1247 }
1248 };
1249
1250 expanded.into()
1251}
1252
1253#[cfg(feature = "shard")]
1288#[proc_macro_attribute]
1289pub fn shard(attr: TokenStream, input: TokenStream) -> TokenStream {
1290 use heck::ToUpperCamelCase;
1291 let shard_args = if attr.is_empty() {
1292 ShardMacroArgs::default()
1293 } else {
1294 match syn::parse::<ShardMacroArgs>(attr) {
1295 Ok(value) => value,
1296 Err(err) => return err.to_compile_error().into(),
1297 }
1298 };
1299
1300 let ui_crate_path: syn::Path = shard_args
1301 .crate_path
1302 .unwrap_or_else(|| syn::parse_quote!(::tessera_ui));
1303 let shard_crate_path: syn::Path = shard_args
1304 .shard_crate_path
1305 .unwrap_or_else(|| syn::parse_quote!(::tessera_shard));
1306
1307 let func = parse_macro_input!(input as ItemFn);
1308 if let Some(conflicting_attr) = func
1309 .attrs
1310 .iter()
1311 .find(|attr| attr.path().is_ident("tessera"))
1312 {
1313 return syn::Error::new_spanned(
1314 conflicting_attr,
1315 "#[shard] already defines a component boundary; do not combine #[shard] with #[tessera]",
1316 )
1317 .to_compile_error()
1318 .into();
1319 }
1320
1321 let shard_params = match parse_shard_params(&func.sig) {
1322 Ok(params) => params,
1323 Err(err) => return err.to_compile_error().into(),
1324 };
1325 let router_params: Vec<_> = shard_params
1326 .iter()
1327 .filter(|param| param.is_router)
1328 .collect();
1329 if router_params.len() > 1 {
1330 return syn::Error::new_spanned(
1331 &func.sig.inputs,
1332 "#[shard] supports at most one `#[router]` parameter",
1333 )
1334 .to_compile_error()
1335 .into();
1336 }
1337 if let Some(router_param) = router_params.first()
1338 && !is_state_router_controller_type(&router_param.ty)
1339 {
1340 return syn::Error::new_spanned(
1341 &router_param.ty,
1342 "`#[router]` parameters must have type `State<RouterController>`",
1343 )
1344 .to_compile_error()
1345 .into();
1346 }
1347
1348 let state_type = shard_args.state_type;
1349 let state_lifecycle_tokens = match shard_args.lifecycle {
1350 Some(lifecycle) => {
1351 let lifecycle_name = lifecycle.to_string().to_lowercase();
1352 if state_type.is_none() {
1353 return syn::Error::new_spanned(
1354 lifecycle,
1355 "`lifecycle` requires `state` in #[shard(...)]",
1356 )
1357 .to_compile_error()
1358 .into();
1359 }
1360 if lifecycle_name == "scope" {
1361 quote! { #shard_crate_path::ShardStateLifeCycle::Scope }
1362 } else if lifecycle_name == "shard" {
1363 quote! { #shard_crate_path::ShardStateLifeCycle::Shard }
1364 } else {
1365 return syn::Error::new_spanned(
1366 lifecycle,
1367 "unsupported `lifecycle` in #[shard(...)]: expected `scope` or `shard`",
1368 )
1369 .to_compile_error()
1370 .into();
1371 }
1372 }
1373 None => quote! { #shard_crate_path::ShardStateLifeCycle::Shard },
1374 };
1375
1376 let func_body = func.block;
1377 let func_name_str = func.sig.ident.to_string();
1378
1379 let func_attrs = &func.attrs;
1380 let func_vis = &func.vis;
1381 let router_controller_resolution = quote! {
1382 __tessera_router_controller
1383 };
1384
1385 let router_binding =
1386 router_params
1387 .first()
1388 .map_or_else(proc_macro2::TokenStream::new, |param| {
1389 let router_ident = ¶m.ident;
1390 quote! {
1391 let #router_ident = #router_ident
1392 .unwrap_or_else(|| #router_controller_resolution);
1393 }
1394 });
1395 let mut func_sig_modified = func.sig.clone();
1396 func_sig_modified.inputs.insert(0, parse_quote!(
1397 #[prop(skip_setter)] __tessera_router_controller: #ui_crate_path::State<#shard_crate_path::router::RouterController>
1398 ));
1399 for input in &mut func_sig_modified.inputs {
1400 let FnArg::Typed(pat_type) = input else {
1401 continue;
1402 };
1403 let is_router = pat_type
1404 .attrs
1405 .iter()
1406 .any(|attr| attr.path().is_ident("router"));
1407 if !is_router {
1408 continue;
1409 }
1410 let original_ty = (*pat_type.ty).clone();
1411 *pat_type.ty = parse_quote!(::core::option::Option<#original_ty>);
1412 pat_type
1413 .attrs
1414 .retain(|attr| !attr.path().is_ident("router"));
1415 pat_type.attrs.push(syn::parse_quote!(#[prop(skip_setter)]));
1416 }
1417
1418 let func_name = func.sig.ident.clone();
1420 let struct_name = syn::Ident::new(
1421 &format!("{}Destination", func_name_str.to_upper_camel_case()),
1422 func_name.span(),
1423 );
1424 let dest_fields = shard_params
1427 .iter()
1428 .filter(|param| !param.is_router)
1429 .map(|param| {
1430 let ident = ¶m.ident;
1431 let ty = ¶m.ty;
1432 quote! { pub #ident: #ty }
1433 });
1434
1435 let expanded = {
1436 let required_args = shard_params
1437 .iter()
1438 .filter(|param| !param.is_router && option_inner_type(¶m.ty).is_none())
1439 .map(|param| {
1440 let ident = ¶m.ident;
1441 quote! { self.#ident.clone() }
1442 });
1443 let destination_builder_setters = shard_params
1444 .iter()
1445 .filter(|param| !param.is_router && option_inner_type(¶m.ty).is_some())
1446 .map(|param| {
1447 let ident = ¶m.ident;
1448 quote! {
1449 if let Some(value) = self.#ident.clone() {
1450 __tessera_builder.#ident(value);
1451 }
1452 }
1453 });
1454
1455 if let Some(state_type) = state_type {
1456 quote! {
1457 #[derive(Clone, PartialEq)]
1458 #func_vis struct #struct_name {
1459 #(#dest_fields),*
1460 }
1461
1462 impl #shard_crate_path::router::RouterDestination for #struct_name {
1463 fn exec_component(&self, controller: #ui_crate_path::State<#shard_crate_path::router::RouterController>) {
1464 let __tessera_builder = #func_name(controller #(, #required_args)*);
1465 #(#destination_builder_setters)*
1466 }
1467
1468 fn exec_component_handle(&self) -> std::sync::Arc<dyn Fn(#ui_crate_path::State<#shard_crate_path::router::RouterController>) + Send + Sync> {
1469 let destination = self.clone();
1470 std::sync::Arc::new(move |controller| destination.exec_component(controller))
1471 }
1472
1473 fn destination_id() -> &'static str {
1474 concat!(module_path!(), "::", #func_name_str)
1475 }
1476 }
1477
1478 #(#func_attrs)*
1479 #[#ui_crate_path::tessera(#ui_crate_path)]
1480 #func_vis #func_sig_modified {
1481 const SHARD_ID: &str = concat!(module_path!(), "::", #func_name_str);
1482 let __router_controller = #router_controller_resolution;
1483 #router_binding
1484 let __route_scope = #ui_crate_path::use_context::<#shard_crate_path::__private::RouteRenderScope>().map(|scope| scope.get());
1485 #shard_crate_path::__private::with_rendered_router_shard_state::<#state_type, _, _>(
1486 SHARD_ID,
1487 #state_lifecycle_tokens,
1488 __router_controller,
1489 __route_scope,
1490 |state| {
1491 #func_body
1492 },
1493 )
1494 }
1495 }
1496 } else {
1497 quote! {
1498 #[derive(Clone, PartialEq)]
1499 #func_vis struct #struct_name {
1500 #(#dest_fields),*
1501 }
1502
1503 impl #shard_crate_path::router::RouterDestination for #struct_name {
1504 fn exec_component(&self, controller: #ui_crate_path::State<#shard_crate_path::router::RouterController>) {
1505 let __tessera_builder = #func_name(controller #(, #required_args)*);
1506 #(#destination_builder_setters)*
1507 }
1508
1509 fn exec_component_handle(&self) -> std::sync::Arc<dyn Fn(#ui_crate_path::State<#shard_crate_path::router::RouterController>) + Send + Sync> {
1510 let destination = self.clone();
1511 std::sync::Arc::new(move |controller| destination.exec_component(controller))
1512 }
1513
1514 fn destination_id() -> &'static str {
1515 concat!(module_path!(), "::", #func_name_str)
1516 }
1517 }
1518
1519 #(#func_attrs)*
1520 #[#ui_crate_path::tessera(#ui_crate_path)]
1521 #func_vis #func_sig_modified {
1522 #router_binding
1523 #func_body
1524 }
1525 }
1526 }
1527 };
1528
1529 TokenStream::from(expanded)
1530}