1use proc_macro::TokenStream;
5use proc_macro2::TokenStream as Tokens;
6use quote::{format_ident, quote};
7use std::collections::HashMap;
8use syn::spanned::Spanned;
9use syn::visit_mut::VisitMut;
10use syn::{
11 FnArg, GenericArgument, GenericParam, ImplItem, Item, PathArguments, ReturnType, Type, TypeParamBound,
12 parse_macro_input, parse_quote,
13};
14
15#[proc_macro_attribute]
27pub fn expose(attr: TokenStream, item: TokenStream) -> TokenStream {
28 let options = parse_macro_input!(attr as Options);
29 let original: Tokens = item.clone().into();
30 let item = parse_macro_input!(item as Item);
31 let generated = match &item {
32 Item::Impl(block) => expose_impl(block, &options),
33 Item::Enum(choice) => expose_enum(choice, &options),
34 Item::Struct(declared) => expose_struct(declared, &options),
35 Item::Mod(module) => {
36 return expose_mod(module, &options)
37 .unwrap_or_else(|e| {
38 let error = e.into_compile_error();
39 quote!(#original #error)
40 })
41 .into();
42 }
43 other => Err(syn::Error::new(
44 other.span(),
45 "#[expose] goes on an inherent impl block, a struct, an enum or a module",
46 )),
47 };
48 match generated {
49 Ok(generated) => quote!(#original #generated).into(),
50 Err(error) => {
51 let error = error.into_compile_error();
52 quote!(#original #error).into()
53 }
54 }
55}
56
57#[derive(Default)]
58struct Options {
59 part: Option<String>,
60 skip: Vec<String>,
61 with: Vec<Vec<(String, Type)>>,
62}
63
64impl syn::parse::Parse for Options {
65 fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
66 let mut options = Options::default();
67 let pairs =
68 syn::punctuated::Punctuated::<syn::MetaNameValue, syn::Token![,]>::parse_terminated(input)?;
69 for pair in pairs {
70 let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(text), .. }) = &pair.value else {
71 return Err(syn::Error::new(pair.value.span(), "a text goes here"));
72 };
73 if pair.path.is_ident("part") {
74 options.part = Some(text.value());
75 } else if pair.path.is_ident("skip") {
76 options.skip =
77 text.value().split(',').map(|s| s.trim().to_owned()).filter(|s| !s.is_empty()).collect();
78 } else if pair.path.is_ident("with") {
79 for alternative in text.value().split(';').filter(|a| !a.trim().is_empty()) {
80 let mut set = Vec::new();
81 for pair in alternative.split(',') {
82 let (name, ty) = pair
83 .split_once('=')
84 .ok_or_else(|| syn::Error::new(text.span(), "write `A = Type`"))?;
85 set.push((name.trim().to_owned(), syn::parse_str::<Type>(ty.trim())?));
86 }
87 options.with.push(set);
88 }
89 } else {
90 return Err(syn::Error::new(
91 pair.path.span(),
92 "use part = \"...\", skip = \"a, b\" or with = \"A = T\"",
93 ));
94 }
95 }
96 Ok(options)
97 }
98}
99
100fn entry_name(options: &Options) -> syn::Ident {
101 match &options.part {
102 Some(part) => format_ident!("__live_expose_{}", part),
103 None => format_ident!("__live_expose"),
104 }
105}
106
107fn last_ident(path: &syn::Path) -> String {
108 path.segments.last().map(|s| s.ident.to_string()).unwrap_or_default()
109}
110
111fn is_marker(bound: &TypeParamBound) -> bool {
113 match bound {
114 TypeParamBound::Lifetime(_) => true,
115 TypeParamBound::Trait(t) => matches!(last_ident(&t.path).as_str(), "Clone" | "PartialEq" | "Debug"),
116 _ => false,
117 }
118}
119
120fn bounds_of<'a>(param: &'a syn::TypeParam, generics: &'a syn::Generics) -> Vec<&'a TypeParamBound> {
121 let mut bounds: Vec<&TypeParamBound> = param.bounds.iter().collect();
122 if let Some(clause) = &generics.where_clause {
123 for predicate in &clause.predicates {
124 if let syn::WherePredicate::Type(typed) = predicate
125 && let Type::Path(path) = &typed.bounded_ty
126 && path.path.is_ident(¶m.ident)
127 {
128 bounds.extend(typed.bounds.iter());
129 }
130 }
131 }
132 bounds
133}
134
135fn view_type(bounds: &[&TypeParamBound]) -> Option<Type> {
137 for bound in bounds {
138 if let TypeParamBound::Trait(trait_bound) = bound {
139 match last_ident(&trait_bound.path).as_str() {
140 "View" => return Some(parse_quote!(crate::AnyView)),
141 "ViewSeq" => return Some(parse_quote!(::std::vec::Vec<crate::AnyView>)),
142 _ => {}
143 }
144 }
145 }
146 None
147}
148
149struct Substitute<'a>(&'a HashMap<String, Type>);
151
152impl VisitMut for Substitute<'_> {
153 fn visit_type_mut(&mut self, ty: &mut Type) {
154 if let Type::Path(path) = ty
155 && path.qself.is_none()
156 && path.path.segments.len() == 1
157 && path.path.segments[0].arguments.is_empty()
158 && let Some(concrete) = self.0.get(&path.path.segments[0].ident.to_string())
159 {
160 *ty = concrete.clone();
161 return;
162 }
163 syn::visit_mut::visit_type_mut(self, ty);
164 }
165}
166
167fn substituted(ty: &Type, map: &HashMap<String, Type>) -> Type {
168 let mut ty = ty.clone();
169 Substitute(map).visit_type_mut(&mut ty);
170 ty
171}
172
173fn block_substitutions(generics: &syn::Generics, given: &[(String, Type)]) -> Option<HashMap<String, Type>> {
176 let mut map = HashMap::new();
177 for param in &generics.params {
178 match param {
179 GenericParam::Type(param) => {
180 let name = param.ident.to_string();
181 let ty = match given.iter().find(|(n, _)| *n == name) {
182 Some((_, ty)) => ty.clone(),
183 None => view_type(&bounds_of(param, generics))?,
184 };
185 map.insert(name, ty);
186 }
187 GenericParam::Lifetime(_) | GenericParam::Const(_) => return None,
188 }
189 }
190 Some(map)
191}
192
193fn function_substitutions(generics: &syn::Generics) -> Option<HashMap<String, Type>> {
196 let mut map = HashMap::new();
197 for param in &generics.params {
198 match param {
199 GenericParam::Type(param) => {
200 let bounds = bounds_of(param, generics);
201 let ty = if let Some(view) = view_type(&bounds) {
202 view
203 } else if bounds.iter().all(|b| is_marker(b)) {
204 parse_quote!(::ravenrs_live::Value)
205 } else {
206 let kept: Vec<&TypeParamBound> =
207 bounds.iter().copied().filter(|b| !is_marker(b)).collect();
208 parse_quote!(impl #(#kept)+*)
209 };
210 map.insert(param.ident.to_string(), ty);
211 }
212 GenericParam::Lifetime(_) => {}
213 GenericParam::Const(_) => return None,
214 }
215 }
216 Some(map)
217}
218
219fn has_impl(ty: &Type) -> bool {
221 struct Finder(bool);
222 impl<'ast> syn::visit::Visit<'ast> for Finder {
223 fn visit_type_impl_trait(&mut self, _: &'ast syn::TypeImplTrait) {
224 self.0 = true;
225 }
226 }
227 let mut finder = Finder(false);
228 syn::visit::Visit::visit_type(&mut finder, ty);
229 finder.0
230}
231
232fn needs_threads(ty: &Type) -> bool {
234 let Type::ImplTrait(implied) = ty else { return false };
235 implied.bounds.iter().any(
236 |b| matches!(b, TypeParamBound::Trait(t) if matches!(last_ident(&t.path).as_str(), "Send" | "Sync")),
237 )
238}
239
240fn impl_bound(ty: &Type) -> Option<&syn::TraitBound> {
241 let Type::ImplTrait(implied) = ty else { return None };
242 implied.bounds.iter().find_map(|b| match b {
243 TypeParamBound::Trait(t)
244 if !matches!(last_ident(&t.path).as_str(), "Clone" | "PartialEq" | "Debug") =>
245 {
246 Some(t)
247 }
248 _ => None,
249 })
250}
251
252fn angle_types(segment: &syn::PathSegment) -> Vec<&Type> {
253 match &segment.arguments {
254 PathArguments::AngleBracketed(args) => args
255 .args
256 .iter()
257 .filter_map(|a| match a {
258 GenericArgument::Type(t) => Some(t),
259 _ => None,
260 })
261 .collect(),
262 _ => Vec::new(),
263 }
264}
265
266struct Param {
268 shape: Shape,
269 by_ref: bool,
270}
271
272enum Shape {
274 Leaf(Type),
276 List(Box<Shape>),
277 Optional(Box<Shape>),
278 Outcome(Box<Shape>, Box<Shape>),
279 Tuple(Vec<Shape>),
280 Closure {
281 inputs: Vec<Input>,
282 output: Box<Shape>,
283 shared: bool,
284 },
285 Into(Box<Shape>, Type),
287 Static,
289 IntoOf(Type),
291}
292
293enum Input {
295 Str,
296 Scratch(Type),
297 Borrowed(Type),
298 Owned(Type),
299}
300
301impl Shape {
302 fn ty(&self) -> Type {
303 match self {
304 Self::Leaf(ty) | Self::Into(_, ty) | Self::IntoOf(ty) => ty.clone(),
305 Self::Static => parse_quote!(&'static str),
306 Self::List(item) => {
307 let item = item.ty();
308 parse_quote!(::std::vec::Vec<#item>)
309 }
310 Self::Optional(item) => {
311 let item = item.ty();
312 parse_quote!(::std::option::Option<#item>)
313 }
314 Self::Outcome(ok, err) => {
315 let (ok, err) = (ok.ty(), err.ty());
316 parse_quote!(::std::result::Result<#ok, #err>)
317 }
318 Self::Tuple(items) => {
319 let items: Vec<Type> = items.iter().map(Shape::ty).collect();
320 parse_quote!((#(#items,)*))
321 }
322 Self::Closure { inputs, output, shared } => {
323 let inputs: Vec<Type> = inputs.iter().map(Input::ty).collect();
324 let output = output.ty();
325 if *shared {
326 parse_quote!(::std::rc::Rc<dyn Fn(#(#inputs),*) -> #output>)
327 } else {
328 parse_quote!(::std::boxed::Box<dyn Fn(#(#inputs),*) -> #output>)
329 }
330 }
331 }
332 }
333
334 fn make(&self, value: &Tokens) -> Tokens {
337 match self {
338 Self::Into(inner, ty) => {
339 let inner = inner.make(value);
340 quote!({ let made: #ty = (#inner).into(); made })
341 }
342 Self::IntoOf(ty) => {
343 let leaf = Self::Leaf(ty.clone()).make("e!(value.clone()));
344 let from = |source: Tokens, made: Tokens| quote!((&&::ravenrs_live::bridge::IntoFrom::<#ty, #source>::new(#made)).get());
345 let (text, whole, small, float, single, truth) = (
346 from(quote!(::std::string::String), quote!(text.clone())),
347 from(quote!(i64), quote!(*n)),
348 from(quote!(i32), quote!(*n as i32)),
349 from(quote!(f64), quote!(*n as f64)),
350 from(quote!(f32), quote!(*n as f32)),
351 from(quote!(bool), quote!(*b)),
352 );
353 let (wide, narrow) = (from(quote!(f64), quote!(*n)), from(quote!(f32), quote!(*n as f32)));
354 quote! {{
355 #[allow(unused_imports)]
356 use ::ravenrs_live::bridge::{IntoFromNone as _, IntoFromSome as _};
357 let value: ::ravenrs_live::Value = #value;
358 match (|| -> ::std::result::Result<#ty, ::std::string::String> { ::std::result::Result::Ok(#leaf) })() {
359 ::std::result::Result::Ok(made) => made,
360 ::std::result::Result::Err(problem) => match &value.read() {
361 ::ravenrs_live::Value::Str(text) => #text,
362 ::ravenrs_live::Value::Int(n) => #whole.or_else(|| #small).or_else(|| #float).or_else(|| #single),
363 ::ravenrs_live::Value::Float(n) => #wide.or_else(|| #narrow),
364 ::ravenrs_live::Value::Bool(b) => #truth,
365 _ => ::std::option::Option::None,
366 }
367 .ok_or(problem)?,
368 }
369 }}
370 }
371 Self::Static => {
373 let text = Self::Leaf(parse_quote!(::std::string::String)).make(value);
374 quote!(::std::boxed::Box::leak((#text).into_boxed_str()))
375 }
376 Self::Leaf(ty) => quote! {{
377 #[allow(unused_imports)]
378 use ::ravenrs_live::bridge::{
379 ArgFromHost as _, ArgFromLive as _, ArgHostBinding as _, ArgHostBindingOf as _,
380 ArgHostClosure as _, ArgHostOption as _, ArgHostVec as _,
381 };
382 (&&&&&&::ravenrs_live::bridge::Arg::<#ty>::of()).make(#value)?
383 }},
384 Self::List(item) => {
385 let ty = item.ty();
386 let inner = item.make("e!(item));
387 quote! {
388 ::ravenrs_live::bridge::list(&#value)?
389 .into_iter()
390 .map(|item| -> ::std::result::Result<#ty, ::std::string::String> { ::std::result::Result::Ok(#inner) })
391 .collect::<::std::result::Result<::std::vec::Vec<#ty>, ::std::string::String>>()?
392 }
393 }
394 Self::Optional(item) => {
395 let inner = item.make("e!(inner));
396 quote! {
397 match ::ravenrs_live::bridge::option(&#value) {
398 ::std::option::Option::Some(inner) => ::std::option::Option::Some(#inner),
399 ::std::option::Option::None => ::std::option::Option::None,
400 }
401 }
402 }
403 Self::Outcome(ok, err) => {
404 let (ok, err) = (ok.make("e!(inner)), err.make("e!(inner)));
405 quote! {
406 match ::ravenrs_live::bridge::result(&#value) {
407 ::std::result::Result::Ok(inner) => ::std::result::Result::Ok(#ok),
408 ::std::result::Result::Err(inner) => ::std::result::Result::Err(#err),
409 }
410 }
411 }
412 Self::Tuple(items) => {
413 let count = items.len();
414 let names: Vec<syn::Ident> = (0..count).map(|i| format_ident!("t{}", i)).collect();
415 let makes: Vec<Tokens> = items.iter().zip(&names).map(|(s, n)| s.make("e!(#n))).collect();
416 quote! {{
417 let mut items = ::ravenrs_live::bridge::tuple(&#value, #count)?.into_iter();
418 #(let #names = items.next().unwrap_or(::ravenrs_live::Value::Unit);)*
419 (#(#makes,)*)
420 }}
421 }
422 Self::Closure { inputs, output, shared } => {
423 let out_ty = output.ty();
424 let params: Vec<syn::Ident> = (0..inputs.len()).map(|i| format_ident!("p{}", i)).collect();
425 let typed: Vec<Tokens> = inputs
426 .iter()
427 .zip(¶ms)
428 .map(|(input, p)| {
429 let ty = input.ty();
430 quote!(#p: #ty)
431 })
432 .collect();
433 let checks: Vec<Tokens> = inputs
434 .iter()
435 .filter_map(|input| match input {
436 Input::Scratch(ty) => Some(quote! {{
437 #[allow(unused_imports)]
438 use ::ravenrs_live::bridge::{ScratchNone as _, ScratchSome as _};
439 (&&::ravenrs_live::bridge::ScratchOf::<#ty>::of()).check()?;
440 }}),
441 _ => None,
442 })
443 .collect();
444 let values: Vec<syn::Ident> = (0..inputs.len()).map(|i| format_ident!("v{}", i)).collect();
445 let lets: Vec<Tokens> = inputs
446 .iter()
447 .zip(¶ms)
448 .zip(&values)
449 .map(|((input, p), v)| {
450 let made = input.to_value("e!(#p));
451 quote!(let #v: ::ravenrs_live::Value = #made;)
452 })
453 .collect();
454 let merges: Vec<Tokens> = inputs
455 .iter()
456 .zip(¶ms)
457 .zip(&values)
458 .filter_map(|((input, p), v)| match input {
459 Input::Scratch(ty) => Some(quote! {{
460 #[allow(unused_imports)]
461 use ::ravenrs_live::bridge::{ScratchNone as _, ScratchSome as _};
462 (&&::ravenrs_live::bridge::ScratchOf::<#ty>::of()).merge(&#v, #p);
463 }}),
464 _ => None,
465 })
466 .collect();
467 let made = output.make("e!(out));
468 let wrap = if *shared { quote!(::std::rc::Rc::new) } else { quote!(::std::boxed::Box::new) };
469 let fn_ty = self.ty();
470 quote! {{
471 let closure = ::ravenrs_live::bridge::closure_of(&#value)?;
472 let fallback: fn() -> #out_ty = {
473 #[allow(unused_imports)]
474 use ::ravenrs_live::bridge::{FallbackDefault as _, FallbackNone as _, FallbackOwn as _};
475 (&&&::ravenrs_live::bridge::FallbackOf::<#out_ty>::of()).get()?
476 };
477 #(#checks)*
478 let made: #fn_ty = #wrap(move |#(#typed),*| -> #out_ty {
479 #(#lets)*
480 let out = ::ravenrs_live::bridge::call_back(
481 &closure,
482 ::std::vec![#(#values.clone()),*],
483 |out: ::ravenrs_live::Value| -> ::std::result::Result<#out_ty, ::std::string::String> {
484 ::std::result::Result::Ok(#made)
485 },
486 fallback,
487 );
488 #(#merges)*
489 out
490 });
491 made
492 }}
493 }
494 }
495 }
496}
497
498impl Input {
499 fn ty(&self) -> Type {
500 match self {
501 Self::Str => parse_quote!(&str),
502 Self::Scratch(ty) => parse_quote!(&mut #ty),
503 Self::Borrowed(ty) => parse_quote!(&#ty),
504 Self::Owned(ty) => ty.clone(),
505 }
506 }
507
508 fn to_value(&self, value: &Tokens) -> Tokens {
509 match self {
510 Self::Str => quote!(::ravenrs_live::Value::Str(#value.to_owned())),
511 Self::Scratch(ty) => quote! {{
512 #[allow(unused_imports)]
513 use ::ravenrs_live::bridge::{ScratchNone as _, ScratchSome as _};
514 let _ = &#value;
515 (&&::ravenrs_live::bridge::ScratchOf::<#ty>::of()).fresh()
516 }},
517 Self::Borrowed(_) => quote! {{
518 #[allow(unused_imports)]
519 use ::ravenrs_live::bridge::{BorrowedCopy as _, BorrowedLive as _, BorrowedNone as _};
520 (&&&::ravenrs_live::bridge::Borrowed(#value)).live()
521 }},
522 Self::Owned(ty) => to_value(ty, value),
523 }
524 }
525}
526
527fn to_value(ty: &Type, value: &Tokens) -> Tokens {
529 let leaf = || {
530 quote! {{
531 #[allow(unused_imports)]
532 use ::ravenrs_live::bridge::{
533 RetCloneable as _, RetHost as _, RetIntoLive as _, RetOptionCopied as _, RetOptionMoved as _,
534 RetVecCopied as _, RetVecMoved as _,
535 };
536 let out = ::ravenrs_live::bridge::Ret::new(#value);
537 (&&&&&&&out).live()
538 }}
539 };
540 match ty {
541 Type::Paren(inner) => to_value(&inner.elem, value),
542 Type::Group(inner) => to_value(&inner.elem, value),
543 Type::Tuple(tuple) if !tuple.elems.is_empty() => {
544 let names: Vec<syn::Ident> = (0..tuple.elems.len()).map(|i| format_ident!("t{}", i)).collect();
545 let values: Vec<Tokens> =
546 tuple.elems.iter().zip(&names).map(|(t, n)| to_value(t, "e!(#n))).collect();
547 quote! {{
548 let (#(#names,)*) = #value;
549 ::ravenrs_live::Value::Tuple(::std::vec![#(#values),*])
550 }}
551 }
552 Type::Path(path) if path.qself.is_none() => {
553 let Some(segment) = path.path.segments.last() else { return leaf() };
554 let args = angle_types(segment);
555 match (segment.ident.to_string().as_str(), args.as_slice()) {
556 ("Vec", [item]) => {
557 let inner = to_value(item, "e!(item));
558 quote!(::ravenrs_live::Value::List((#value).into_iter().map(|item| #inner).collect()))
559 }
560 ("Option", [item]) => {
561 let inner = to_value(item, "e!(item));
562 quote!(::ravenrs_live::Value::Option((#value).map(|item| ::std::boxed::Box::new(#inner))))
563 }
564 ("Result", [ok, err]) => {
565 let (ok, err) = (to_value(ok, "e!(item)), to_value(err, "e!(item)));
566 quote! {
567 ::ravenrs_live::Value::Result(match #value {
568 ::std::result::Result::Ok(item) => ::std::result::Result::Ok(::std::boxed::Box::new(#ok)),
569 ::std::result::Result::Err(item) => ::std::result::Result::Err(::std::boxed::Box::new(#err)),
570 })
571 }
572 }
573 _ => leaf(),
574 }
575 }
576 _ => leaf(),
577 }
578}
579
580fn result_value(ty: &Type, value: &Tokens, map: &HashMap<String, Type>) -> Option<Tokens> {
582 if let Some(bound) = impl_bound(ty) {
583 let segment = bound.path.segments.last()?;
584 return match segment.ident.to_string().as_str() {
585 "View" => Some(quote!({
586 let view: crate::AnyView = ::std::boxed::Box::new(#value);
587 ::ravenrs_live::Value::Host(::ravenrs_live::Host::new("AnyView", view))
588 })),
589 "Iterator" | "IntoIterator" | "DoubleEndedIterator" | "ExactSizeIterator" => {
590 let item = assoc_item(segment)?;
591 let item = substituted(item, map);
592 if has_impl(&item) || borrows(&item) {
593 return None;
594 }
595 let inner = to_value(&item, "e!(item));
596 Some(quote!(::ravenrs_live::Value::List((#value).into_iter().map(|item| #inner).collect())))
597 }
598 _ => None,
599 };
600 }
601 match ty {
602 Type::Reference(reference) => match &*reference.elem {
603 Type::Path(path) if path.path.is_ident("str") => {
604 Some(quote!(::ravenrs_live::Value::Str((#value).to_owned())))
605 }
606 Type::Slice(slice) if !borrows(&slice.elem) && !has_impl(&slice.elem) => {
607 let inner = to_value(&slice.elem, "e!(item));
608 Some(quote! {{
609 #[allow(unused_imports)]
610 use ::ravenrs_live::bridge::{CloneOfNone as _, CloneOfSome as _};
611 ::ravenrs_live::Value::List((#value).iter().filter_map(|item| (&&::ravenrs_live::bridge::CloneOf(item)).cloned()).map(|item| #inner).collect())
612 }})
613 }
614 elem if !borrows(elem) && !has_impl(elem) => Some(quote! {{
615 #[allow(unused_imports)]
616 use ::ravenrs_live::bridge::{BorrowedCopy as _, BorrowedLive as _, BorrowedNone as _};
617 (&&&::ravenrs_live::bridge::Borrowed(#value)).live()
618 }}),
619 _ => None,
620 },
621 _ if borrows(ty) || has_impl(ty) => None,
622 _ => Some(to_value(&substituted(ty, map), value)),
623 }
624}
625
626fn assoc_item(segment: &syn::PathSegment) -> Option<&Type> {
627 let PathArguments::AngleBracketed(args) = &segment.arguments else { return None };
628 args.args.iter().find_map(|a| match a {
629 GenericArgument::AssocType(assoc) if assoc.ident == "Item" => Some(&assoc.ty),
630 _ => None,
631 })
632}
633
634fn shape(ty: &Type, map: &HashMap<String, Type>) -> Option<Shape> {
636 if needs_threads(ty) {
637 return None;
638 }
639 if let Some(bound) = impl_bound(ty) {
640 let segment = bound.path.segments.last()?;
641 return match segment.ident.to_string().as_str() {
642 "Into" => match shape(angle_types(segment).first()?, map)? {
643 Shape::Leaf(ty) if !plain(&ty) => Some(Shape::IntoOf(ty)),
644 other => Some(other),
645 },
646 "AsRef" | "ToString" | "Display" => Some(Shape::Leaf(parse_quote!(::std::string::String))),
647 "View" => Some(Shape::Leaf(parse_quote!(crate::AnyView))),
648 "ViewSeq" => Some(Shape::Leaf(parse_quote!(::std::vec::Vec<crate::AnyView>))),
649 "Fn" | "FnMut" | "FnOnce" => closure_shape(segment, map, false),
650 "IntoIterator" => Some(Shape::List(Box::new(shape(assoc_item(segment)?, map)?))),
651 _ => None,
652 };
653 }
654 match ty {
655 Type::Paren(inner) => shape(&inner.elem, map),
656 Type::Group(inner) => shape(&inner.elem, map),
657 Type::Tuple(tuple) if tuple.elems.is_empty() => Some(Shape::Leaf(parse_quote!(()))),
658 Type::Tuple(tuple) => {
659 Some(Shape::Tuple(tuple.elems.iter().map(|t| shape(t, map)).collect::<Option<_>>()?))
660 }
661 Type::Path(path) if path.qself.is_none() => {
662 if path.path.segments.len() == 1
663 && let Some(concrete) = map.get(&path.path.segments[0].ident.to_string())
664 && path.path.segments[0].arguments.is_empty()
665 {
666 return shape(concrete, map);
667 }
668 let segment = path.path.segments.last()?;
669 let args = angle_types(segment);
670 match (segment.ident.to_string().as_str(), args.as_slice()) {
671 ("Vec", [item]) => Some(Shape::List(Box::new(shape(item, map)?))),
672 ("Option", [item]) => Some(Shape::Optional(Box::new(shape(item, map)?))),
673 ("Result", [ok, err]) => {
674 Some(Shape::Outcome(Box::new(shape(ok, map)?), Box::new(shape(err, map)?)))
675 }
676 ("Rc" | "Arc", [Type::Slice(slice)]) => Some(Shape::Into(
677 Box::new(Shape::List(Box::new(shape(&slice.elem, map)?))),
678 substituted(ty, map),
679 )),
680 ("Box" | "Rc", [Type::TraitObject(object)]) => {
681 let segment = object.bounds.iter().find_map(|b| match b {
682 TypeParamBound::Trait(t) => t.path.segments.last(),
683 _ => None,
684 })?;
685 if !matches!(segment.ident.to_string().as_str(), "Fn" | "FnMut" | "FnOnce") {
686 return None;
687 }
688 if object.bounds.iter().any(|b| matches!(b, TypeParamBound::Trait(t) if matches!(last_ident(&t.path).as_str(), "Send" | "Sync"))) {
689 return None;
690 }
691 closure_shape(segment, map, segment.ident == "Fn" && last_ident(&path.path) == "Rc")
692 }
693 _ if has_impl(ty) => None,
694 _ => {
695 let ty = substituted(ty, map);
696 if has_impl(&ty) { None } else { Some(Shape::Leaf(ty)) }
697 }
698 }
699 }
700 Type::Array(_) if !has_impl(ty) => Some(Shape::Leaf(substituted(ty, map))),
701 Type::Reference(reference)
702 if reference.mutability.is_none()
703 && reference.lifetime.as_ref().is_some_and(|l| l.ident == "static")
704 && matches!(&*reference.elem, Type::Path(p) if p.path.is_ident("str")) =>
705 {
706 Some(Shape::Static)
707 }
708 _ => None,
709 }
710}
711
712fn plain(ty: &Type) -> bool {
714 let Type::Path(path) = ty else { return matches!(ty, Type::Tuple(t) if t.elems.is_empty()) };
715 if path.path.segments.iter().any(|s| s.ident == "ravenrs_live") {
716 return true;
717 }
718 if last_ident(&path.path) == "Value" {
719 return false;
720 }
721 matches!(
722 last_ident(&path.path).as_str(),
723 "String"
724 | "bool"
725 | "char"
726 | "f32"
727 | "f64"
728 | "i8"
729 | "i16"
730 | "i32"
731 | "i64"
732 | "u8"
733 | "u16"
734 | "u32"
735 | "u64"
736 | "usize"
737 | "isize"
738 | "AnyView"
739 | "Value"
740 )
741}
742
743fn closure_shape(segment: &syn::PathSegment, map: &HashMap<String, Type>, shared: bool) -> Option<Shape> {
744 let PathArguments::Parenthesized(args) = &segment.arguments else { return None };
745 let mut inputs = Vec::new();
746 for input in &args.inputs {
747 let input = substituted(input, map);
748 inputs.push(match &input {
749 Type::Reference(reference) => {
750 let elem = (*reference.elem).clone();
751 if matches!(elem, Type::TraitObject(_)) {
752 return None;
753 }
754 if reference.mutability.is_some() {
755 Input::Scratch(elem)
756 } else if matches!(&elem, Type::Path(p) if p.path.is_ident("str")) {
757 Input::Str
758 } else if has_impl(&elem) || borrows(&elem) || matches!(elem, Type::Slice(_)) {
759 return None;
760 } else {
761 Input::Borrowed(elem)
762 }
763 }
764 other if has_impl(other) || borrows(other) => return None,
765 other => Input::Owned(other.clone()),
766 });
767 }
768 let output = match &args.output {
769 ReturnType::Default => Shape::Leaf(parse_quote!(())),
770 ReturnType::Type(_, t) => shape(t, map)?,
771 };
772 Some(Shape::Closure { inputs, output: Box::new(output), shared })
773}
774
775fn param(ty: &Type, map: &HashMap<String, Type>) -> Option<Param> {
776 if let Type::Reference(reference) = ty {
777 if reference.mutability.is_some() || matches!(&*reference.elem, Type::TraitObject(_)) {
778 return None;
779 }
780 if reference.lifetime.as_ref().is_some_and(|l| l.ident == "static") {
781 return shape(ty, map).map(|shape| Param { shape, by_ref: false });
782 }
783 let shape = match &*reference.elem {
784 Type::Path(path) if path.path.is_ident("str") => Shape::Leaf(parse_quote!(::std::string::String)),
785 Type::Slice(slice) => Shape::List(Box::new(shape(&slice.elem, map)?)),
786 other => shape(other, map)?,
787 };
788 return Some(Param { shape, by_ref: true });
789 }
790 Some(Param { shape: shape(ty, map)?, by_ref: false })
791}
792
793fn borrows(ty: &Type) -> bool {
795 struct Finder(bool);
796 impl<'ast> syn::visit::Visit<'ast> for Finder {
797 fn visit_type_reference(&mut self, _: &'ast syn::TypeReference) {
798 self.0 = true;
799 }
800 fn visit_lifetime(&mut self, _: &'ast syn::Lifetime) {
801 self.0 = true;
802 }
803 }
804 let mut finder = Finder(false);
805 syn::visit::Visit::visit_type(&mut finder, ty);
806 finder.0
807}
808
809fn conditional_registration(attrs: &[syn::Attribute], registration: Tokens) -> Tokens {
810 let conditions = attrs.iter().filter(|attribute| attribute.path().is_ident("cfg"));
811 quote!(#(#conditions)* { #registration })
812}
813
814fn expose_impl(block: &syn::ItemImpl, options: &Options) -> syn::Result<Tokens> {
815 let of_trait = block.trait_.is_some();
817 let Type::Path(self_path) = &*block.self_ty else {
818 return Err(syn::Error::new(block.self_ty.span(), "#[expose] needs a named type"));
819 };
820 let name = last_ident(&self_path.path);
821 let entry = entry_name(options);
822 let alternatives: Vec<&[(String, Type)]> =
823 if options.with.is_empty() { vec![&[]] } else { options.with.iter().map(Vec::as_slice).collect() };
824 let mut bodies = Vec::new();
825 let mut first_self = None;
826 for given in alternatives {
827 let Some(map) = block_substitutions(&block.generics, given) else { continue };
829 let self_ty = substituted(&block.self_ty, &map);
830 let mut registrations = Vec::new();
831 for item in &block.items {
832 match item {
833 ImplItem::Fn(function) => {
834 if !of_trait && !matches!(function.vis, syn::Visibility::Public(_)) {
835 continue;
836 }
837 let call = Callee::Type(&self_ty);
838 if let Some(registration) =
839 function_registration(&function.sig, &name, call, &map, options)
840 {
841 registrations.push(conditional_registration(&function.attrs, registration));
842 }
843 }
844 ImplItem::Const(constant) if matches!(constant.vis, syn::Visibility::Public(_)) => {
845 if borrows(&constant.ty) {
846 continue;
847 }
848 let ident = &constant.ident;
849 let text = ident.to_string();
850 let value = to_value(&substituted(&constant.ty, &map), "e!(<#self_ty>::#ident));
851 registrations.push(conditional_registration(
852 &constant.attrs,
853 quote! {
854 registry.constant(#name, #text, || #value);
855 },
856 ));
857 }
858 _ => {}
859 }
860 }
861 bodies.push(quote!(#(#registrations)*));
862 first_self.get_or_insert(self_ty);
863 }
864 let Some(self_ty) = first_self else { return Ok(Tokens::new()) };
865 Ok(quote! {
866 #[cfg(feature = "live")]
867 impl #self_ty {
868 #[doc(hidden)]
869 #[allow(clippy::all, clippy::pedantic, unused_variables, unused_mut)]
870 pub fn #entry(registry: &mut ::ravenrs_live::Registry) {
871 #(#bodies)*
872 }
873 }
874 })
875}
876
877#[derive(Clone, Copy)]
879enum Callee<'a> {
880 Type(&'a Type),
882 Module,
884}
885
886fn function_registration(
887 sig: &syn::Signature,
888 name: &str,
889 callee: Callee<'_>,
890 outer: &HashMap<String, Type>,
891 options: &Options,
892) -> Option<Tokens> {
893 if sig.asyncness.is_some() || sig.unsafety.is_some() {
894 return None;
895 }
896 let fn_name = sig.ident.to_string();
897 if options.skip.contains(&fn_name) || fn_name.starts_with("__") {
898 return None;
899 }
900 let inputs = sig.inputs.iter().map(|i| quote!(#i).to_string()).collect::<Vec<_>>().join(" ");
902 let words: Vec<&str> = inputs.split(|c: char| !c.is_alphanumeric() && c != '_').collect();
903 for param in sig.generics.type_params() {
904 if !words.contains(¶m.ident.to_string().as_str()) {
905 return None;
906 }
907 }
908 let mut map = outer.clone();
909 map.extend(function_substitutions(&sig.generics)?);
910 if let Callee::Type(self_ty) = callee {
911 map.insert("Self".to_owned(), self_ty.clone());
912 }
913 let mut receiver = None;
914 let mut params = Vec::new();
915 for input in &sig.inputs {
916 match input {
917 FnArg::Receiver(r) => {
918 if r.colon_token.is_some() {
919 return None;
920 }
921 receiver = Some((r.reference.is_some(), r.mutability.is_some()));
922 }
923 FnArg::Typed(typed) => params.push(param(&typed.ty, &map)?),
924 }
925 }
926 let self_ty = match callee {
927 Callee::Type(ty) => Some(ty),
928 Callee::Module => None,
929 };
930 if receiver.is_some() && self_ty.is_none() {
931 return None;
932 }
933 let offset = usize::from(receiver.is_some());
934 let count = params.len() + offset;
935 let ident = &sig.ident;
936 let names: Vec<syn::Ident> = (0..params.len()).map(|i| format_ident!("a{}", i)).collect();
937 let makes: Vec<Tokens> = params
938 .iter()
939 .zip(&names)
940 .enumerate()
941 .map(|(i, (p, n))| {
942 let index = i + offset;
943 let made = p.shape.make("e!(args[#index].clone()));
944 let ty = p.shape.ty();
945 quote!(let #n: #ty = #made;)
946 })
947 .collect();
948 let passed: Vec<Tokens> =
949 params.iter().zip(&names).map(|(p, n)| if p.by_ref { quote!(&#n) } else { quote!(#n) }).collect();
950 let out = match &sig.output {
951 ReturnType::Default => quote!({
952 let _: () = out;
953 ::ravenrs_live::Value::Unit
954 }),
955 ReturnType::Type(_, ty) => result_value(ty, "e!(out), &map)?,
956 };
957 let call = match (receiver, self_ty) {
958 (None, Some(self_ty)) => quote!({ let out = <#self_ty>::#ident(#(#passed),*); #out }),
959 (None, None) => quote!({ let out = #ident(#(#passed),*); #out }),
960 (Some((false, _)), Some(self_ty)) => {
961 let this = Shape::Leaf(self_ty.clone()).make("e!(args[0].clone()));
962 quote!({ let this: #self_ty = #this; let out = this.#ident(#(#passed),*); #out })
963 }
964 (Some((true, false)), Some(self_ty)) => quote! {
965 ::ravenrs_live::bridge::with_ref::<#self_ty, _>(&args[0], |this| { let out = this.#ident(#(#passed),*); #out })?
966 },
967 (Some((true, true)), Some(self_ty)) => quote! {
968 ::ravenrs_live::bridge::with_mut::<#self_ty, _>(&args[0], |this| { let out = this.#ident(#(#passed),*); #out })?
969 },
970 (Some(_), None) => return None,
971 };
972 let register = match (receiver, self_ty) {
973 (Some(_), Some(self_ty)) => quote!(method_for::<#self_ty>),
974 _ => quote!(function),
975 };
976 Some(quote! {
977 registry.#register(#name, #fn_name, |args: ::std::vec::Vec<::ravenrs_live::Value>| {
978 if args.len() != #count {
979 return ::std::result::Result::Err(::std::format!("`{}` takes {} arguments, and got {}", #fn_name, #count, args.len()));
980 }
981 #(#makes)*
982 ::std::result::Result::Ok(#call)
983 });
984 })
985}
986
987fn expose_enum(choice: &syn::ItemEnum, options: &Options) -> syn::Result<Tokens> {
988 if !choice.generics.params.is_empty() {
989 return Ok(Tokens::new());
990 }
991 let ty = &choice.ident;
992 let name = ty.to_string();
993 let entry = entry_name(options);
994 let map = HashMap::new();
995 let mut registrations = Vec::new();
996 for variant in &choice.variants {
997 let ident = &variant.ident;
998 let text = ident.to_string();
999 match &variant.fields {
1000 syn::Fields::Unit => {
1001 let value = to_value(&parse_quote!(#ty), "e!(#ty::#ident));
1002 registrations.push(quote! {
1003 registry.constant(#name, #text, || #value);
1004 });
1005 }
1006 syn::Fields::Unnamed(fields) => {
1007 let Some(shapes) =
1008 fields.unnamed.iter().map(|f| shape(&f.ty, &map)).collect::<Option<Vec<Shape>>>()
1009 else {
1010 continue;
1011 };
1012 let count = shapes.len();
1013 let names: Vec<syn::Ident> = (0..count).map(|i| format_ident!("a{}", i)).collect();
1014 let makes = shapes.iter().zip(&names).enumerate().map(|(i, (s, n))| {
1015 let made = s.make("e!(args[#i].clone()));
1016 let t = s.ty();
1017 quote!(let #n: #t = #made;)
1018 });
1019 let value = to_value(&parse_quote!(#ty), "e!(#ty::#ident(#(#names),*)));
1020 registrations.push(quote! {
1021 registry.function(#name, #text, |args: ::std::vec::Vec<::ravenrs_live::Value>| {
1022 if args.len() != #count {
1023 return ::std::result::Result::Err(::std::format!("`{}` takes {} values", #text, #count));
1024 }
1025 #(#makes)*
1026 ::std::result::Result::Ok(#value)
1027 });
1028 });
1029 }
1030 syn::Fields::Named(_) => {}
1031 }
1032 }
1033 Ok(quote! {
1034 #[cfg(feature = "live")]
1035 impl #ty {
1036 #[doc(hidden)]
1037 #[allow(clippy::all, clippy::pedantic, unused_variables, unused_mut)]
1038 pub fn #entry(registry: &mut ::ravenrs_live::Registry) {
1039 #(#registrations)*
1040 }
1041 }
1042 })
1043}
1044
1045const LARGE_STRUCT: usize = 32;
1047
1048fn large_record(ty: &syn::Ident, name: &str, literal: &[(syn::Ident, String, Type, Tokens)]) -> Tokens {
1053 let texts: Vec<&String> = literal.iter().map(|(_, t, ..)| t).collect();
1054 let setters = literal.iter().map(|(ident, text, field_ty, make)| {
1055 quote! {
1056 {
1057 #[inline(never)]
1058 fn set(
1059 made: &mut #ty,
1060 fields: &mut ::std::collections::HashMap<::std::string::String, ::ravenrs_live::Value>,
1061 ) -> ::std::result::Result<(), ::std::string::String> {
1062 if let ::std::option::Option::Some(value) = fields.remove(#text) {
1063 let value: #field_ty = #make;
1064 made.#ident = value;
1065 }
1066 ::std::result::Result::Ok(())
1067 }
1068 set(&mut made, &mut fields)?;
1069 }
1070 }
1071 });
1072 let base = Shape::Leaf(parse_quote!(#ty)).make("e!(base));
1073 let made = to_value(&parse_quote!(#ty), "e!(made));
1074 quote! {
1075 registry.record(#name, |fields: ::std::vec::Vec<(::std::string::String, ::ravenrs_live::Value)>, base: ::std::option::Option<::ravenrs_live::Value>| {
1076 let mut fields: ::std::collections::HashMap<::std::string::String, ::ravenrs_live::Value> = fields.into_iter().collect();
1077 let mut made: #ty = match base {
1078 ::std::option::Option::Some(base) => #base,
1079 ::std::option::Option::None => {
1080 for text in [#(#texts),*] {
1081 if !fields.contains_key(text) {
1082 return ::std::result::Result::Err(::std::format!("the field `{}` of `{}` is missing", text, #name));
1083 }
1084 }
1085 #[allow(unused_imports)]
1086 use ::ravenrs_live::bridge::{DefaultNone as _, DefaultSome as _};
1087 match (&&::ravenrs_live::bridge::DefaultOf::<#ty>::of()).make() {
1088 ::std::option::Option::Some(made) => made,
1089 ::std::option::Option::None => {
1090 return ::std::result::Result::Err(::std::format!("`{}` has too many fields to be written whole: start it from `..` another", #name));
1091 }
1092 }
1093 }
1094 };
1095 #(#setters)*
1096 if let ::std::option::Option::Some(extra) = fields.keys().next() {
1097 return ::std::result::Result::Err(::std::format!("`{}` has no field `{}`", #name, extra));
1098 }
1099 ::std::result::Result::Ok(#made)
1100 });
1101 }
1102}
1103
1104fn expose_struct(declared: &syn::ItemStruct, options: &Options) -> syn::Result<Tokens> {
1107 if !declared.generics.params.is_empty() {
1108 return Ok(Tokens::new());
1109 }
1110 let ty = &declared.ident;
1111 let name = ty.to_string();
1112 let entry = entry_name(options);
1113 let map = HashMap::new();
1114 let made_default = to_value(&parse_quote!(#ty), "e!(made));
1115 let mut registrations = vec![quote! {
1116 registry.function(#name, "default", |args: ::std::vec::Vec<::ravenrs_live::Value>| {
1117 #[allow(unused_imports)]
1118 use ::ravenrs_live::bridge::{DefaultNone as _, DefaultSome as _};
1119 match (&&::ravenrs_live::bridge::DefaultOf::<#ty>::of()).make() {
1120 ::std::option::Option::Some(made) => ::std::result::Result::Ok(#made_default),
1121 ::std::option::Option::None => ::std::result::Result::Err(::std::format!("`{}` has no default", #name)),
1122 }
1123 });
1124 }];
1125 let empty = syn::punctuated::Punctuated::new();
1126 let named = match &declared.fields {
1127 syn::Fields::Named(fields) => &fields.named,
1128 _ => &empty,
1129 };
1130 let mut all_public = true;
1131 let mut literal = Vec::new();
1132 for field in named {
1133 let Some(ident) = &field.ident else { continue };
1134 let text = ident.to_string();
1135 if !matches!(field.vis, syn::Visibility::Public(_)) {
1136 all_public = false;
1137 continue;
1138 }
1139 if borrows(&field.ty) || has_impl(&field.ty) {
1140 all_public = false;
1141 continue;
1142 }
1143 let read = to_value(&field.ty, "e!(item));
1144 registrations.push(quote! {
1145 registry.field(#name, #text, |value: &::ravenrs_live::Value| {
1146 #[allow(unused_imports)]
1147 use ::ravenrs_live::bridge::{CloneOfNone as _, CloneOfSome as _};
1148 let copy = ::ravenrs_live::bridge::with_ref::<#ty, _>(value, |this| (&&::ravenrs_live::bridge::CloneOf(&this.#ident)).cloned())?;
1149 match copy {
1150 ::std::option::Option::Some(item) => ::std::result::Result::Ok(#read),
1151 ::std::option::Option::None => ::std::result::Result::Err(::std::format!("the field `{}` does not copy", #text)),
1152 }
1153 });
1154 });
1155 match shape(&field.ty, &map) {
1156 Some(made) => {
1157 let field_ty = made.ty();
1158 let make = made.make("e!(value));
1159 registrations.push(quote! {
1160 registry.set_field(#name, #text, |this: &::ravenrs_live::Value, value: ::ravenrs_live::Value| {
1161 let made: #field_ty = #make;
1162 ::ravenrs_live::bridge::with_mut::<#ty, _>(this, |this| this.#ident = made)
1163 });
1164 });
1165 literal.push((ident.clone(), text, field_ty, make));
1166 }
1167 None => all_public = false,
1168 }
1169 }
1170 if all_public && literal.len() > LARGE_STRUCT {
1171 registrations.push(large_record(ty, &name, &literal));
1172 } else if all_public && !literal.is_empty() {
1173 let idents: Vec<&syn::Ident> = literal.iter().map(|(i, ..)| i).collect();
1174 let kept: Vec<syn::Ident> = idents.iter().map(|i| format_ident!("base_{}", i)).collect();
1175 let texts: Vec<&String> = literal.iter().map(|(_, t, ..)| t).collect();
1176 let types: Vec<&Type> = literal.iter().map(|(_, _, t, _)| t).collect();
1177 let makes: Vec<&Tokens> = literal.iter().map(|(.., m)| m).collect();
1178 let base = Shape::Leaf(parse_quote!(#ty)).make("e!(base));
1179 let made = to_value(&parse_quote!(#ty), "e!(made));
1180 registrations.push(quote! {
1181 registry.record(#name, |fields: ::std::vec::Vec<(::std::string::String, ::ravenrs_live::Value)>, base: ::std::option::Option<::ravenrs_live::Value>| {
1182 let mut fields: ::std::collections::HashMap<::std::string::String, ::ravenrs_live::Value> = fields.into_iter().collect();
1183 let base: ::std::option::Option<#ty> = match base {
1184 ::std::option::Option::Some(base) => ::std::option::Option::Some(#base),
1185 ::std::option::Option::None => ::std::option::Option::None,
1186 };
1187 #(let mut #kept: ::std::option::Option<#types> = ::std::option::Option::None;)*
1188 if let ::std::option::Option::Some(#ty { #(#idents),* }) = base {
1189 #(#kept = ::std::option::Option::Some(#idents);)*
1190 }
1191 let made = #ty {
1192 #(#idents: match fields.remove(#texts) {
1193 ::std::option::Option::Some(value) => { let made: #types = #makes; made }
1194 ::std::option::Option::None => #kept.take().ok_or_else(|| ::std::format!("the field `{}` of `{}` is missing", #texts, #name))?,
1195 },)*
1196 };
1197 if let ::std::option::Option::Some(extra) = fields.keys().next() {
1198 return ::std::result::Result::Err(::std::format!("`{}` has no field `{}`", #name, extra));
1199 }
1200 ::std::result::Result::Ok(#made)
1201 });
1202 });
1203 }
1204 Ok(quote! {
1205 #[cfg(feature = "live")]
1206 impl #ty {
1207 #[doc(hidden)]
1208 #[allow(clippy::all, clippy::pedantic, unused_variables, unused_mut)]
1209 pub fn #entry(registry: &mut ::ravenrs_live::Registry) {
1210 #(#registrations)*
1211 }
1212 }
1213 })
1214}
1215
1216fn expose_mod(module: &syn::ItemMod, options: &Options) -> syn::Result<Tokens> {
1218 let Some((brace, items)) = &module.content else {
1219 return Err(syn::Error::new(module.span(), "#[expose] goes on a module written inline"));
1220 };
1221 let name = module.ident.to_string();
1222 let entry = entry_name(options);
1223 let map = HashMap::new();
1224 let registrations: Vec<Tokens> = items
1225 .iter()
1226 .filter_map(|item| match item {
1227 Item::Fn(function) if matches!(function.vis, syn::Visibility::Public(_)) => {
1228 function_registration(&function.sig, &name, Callee::Module, &map, options)
1229 .map(|registration| conditional_registration(&function.attrs, registration))
1230 }
1231 _ => None,
1232 })
1233 .collect();
1234 let mut module = module.clone();
1235 let added: Item = parse_quote! {
1236 #[cfg(feature = "live")]
1237 #[doc(hidden)]
1238 #[allow(clippy::all, clippy::pedantic, unused_variables, unused_mut)]
1239 pub fn #entry(registry: &mut ::ravenrs_live::Registry) {
1240 #(#registrations)*
1241 }
1242 };
1243 let _ = brace;
1244 if let Some((_, items)) = &mut module.content {
1245 items.push(added);
1246 }
1247 Ok(quote!(#module))
1248}