Skip to main content

ravenrs_live_derive/
lib.rs

1//! `#[ravenrs_live::expose]`: registers what an `impl` block, a struct, an enum or a module
2//! offers with a `ravenrs_live::Registry`, so a playground can call it without a compiler.
3
4use 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/// Registers the public functions and constants of an inherent `impl` block, the public fields
16/// of a struct, the variants of an enum or the public functions of an inline module, behind the
17/// `live` feature of the crate it is used in. It generates `__live_expose` (or
18/// `__live_expose_<part>` with `part = "..."`) on the type, or inside the module, which the crate
19/// calls when it builds its registry. `skip = "a, b"` leaves functions out.
20///
21/// Generic parameters bounded by `View` become `AnyView`, and by `ViewSeq` a `Vec<AnyView>`;
22/// `with = "A = (); A = Other"` registers a block once for each of these types. A function's
23/// own parameters bounded by `Into`, `AsRef`, `Fn` or `IntoIterator` take what those take, and
24/// ones bounded only by `Clone`, `PartialEq` or `Debug` take any value. What it cannot call with
25/// interpreter values is left out without failing the build.
26#[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
111/// Whether a bound only says what any interpreter value already is.
112fn 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(&param.ident)
127            {
128                bounds.extend(typed.bounds.iter());
129            }
130        }
131    }
132    bounds
133}
134
135/// What a generic parameter bounded by `View` or `ViewSeq` becomes.
136fn 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
149/// Puts concrete types in place of generic parameters.
150struct 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
173/// The generic parameters of an `impl` block as concrete types, with `given` for the ones not
174/// bounded by `View`, or `None` when one cannot be made concrete.
175fn 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
193/// The generic parameters of a function: as their bounds take, `impl Bounds` for `Into`, `Fn`
194/// and the like, or any value for marker bounds.
195fn 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
219/// Whether a type mentions `impl Trait` anywhere inside, which no binding can name.
220fn 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
232/// Whether `impl Trait` asks for `Send` or `Sync`, which the interpreter's closures are not.
233fn 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
266/// How an argument of type `ty` is made and passed.
267struct Param {
268    shape: Shape,
269    by_ref: bool,
270}
271
272/// How a value becomes a Rust value of a type, decided from how the type is written.
273enum Shape {
274    /// Through the autoref levels of `bridge::Arg`.
275    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    /// Made as `inner`, then turned into `ty` with `.into()`, as a `Vec` into an `Rc<[T]>`.
286    Into(Box<Shape>, Type),
287    /// A `&'static str`, kept for as long as the program runs.
288    Static,
289    /// `impl Into<T>` of a host type: a `T`, or a text, number or bool `T` is made from.
290    IntoOf(Type),
291}
292
293/// What a closure the host calls is given.
294enum 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    /// An expression making this type from `value`, a `Value`, inside code that returns
335    /// `Result<_, String>`.
336    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(&quote!(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            // Small texts such as labels, which a component keeps as `&'static str`.
372            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(&quote!(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(&quote!(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(&quote!(inner)), err.make(&quote!(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(&quote!(#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(&params)
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(&params)
448                    .zip(&values)
449                    .map(|((input, p), v)| {
450                        let made = input.to_value(&quote!(#p));
451                        quote!(let #v: ::ravenrs_live::Value = #made;)
452                    })
453                    .collect();
454                let merges: Vec<Tokens> = inputs
455                    .iter()
456                    .zip(&params)
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(&quote!(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
527/// An expression turning `value`, of type `ty`, into a `Value`.
528fn 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, &quote!(#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, &quote!(item));
558                    quote!(::ravenrs_live::Value::List((#value).into_iter().map(|item| #inner).collect()))
559                }
560                ("Option", [item]) => {
561                    let inner = to_value(item, &quote!(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, &quote!(item)), to_value(err, &quote!(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
580/// How a result of type `ty` becomes a value, written out, or `None` when it cannot.
581fn 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, &quote!(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, &quote!(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
634/// The shape a value is made into for a parameter or part written `ty`.
635fn 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
712/// Whether the interpreter has the type as a value of its own: text, a number or a bool.
713fn 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
793/// Whether a type mentions a reference or a lifetime, which a result cannot carry out.
794fn 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    // The methods of a trait it implements are all there is to see, as public as the trait.
816    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        // A block whose generics cannot be made concrete offers nothing, and fails nothing.
828        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), &quote!(<#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/// Who a function belongs to.
878#[derive(Clone, Copy)]
879enum Callee<'a> {
880    /// An associated function or method of this type.
881    Type(&'a Type),
882    /// A free function of the module the registration is written in.
883    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    // A parameter no argument mentions cannot be inferred from what the call is given.
901    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(&param.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(&quote!(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, &quote!(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(&quote!(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), &quote!(#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(&quote!(args[#i].clone()));
1016                    let t = s.ty();
1017                    quote!(let #n: #t = #made;)
1018                });
1019                let value = to_value(&parse_quote!(#ty), &quote!(#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
1045/// From how many fields a struct literal is built field by field instead of in one expression.
1046const LARGE_STRUCT: usize = 32;
1047
1048/// `Name { field: .., ..base }` for a struct with many fields. One expression with every field,
1049/// as smaller structs get, becomes a function that an optimized build takes hours to compile
1050/// (`Labels` has hundreds). Here the struct starts from `..base`, or from its `Default` when
1051/// every field is given, and each field is changed by a function of its own.
1052fn 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(&quote!(base));
1073    let made = to_value(&parse_quote!(#ty), &quote!(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
1104/// A struct's public fields: read with `value.field`, changed with `value.field = ..`, and the
1105/// whole struct written as `Name { field: .., ..base }` when every field is public.
1106fn 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), &quote!(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, &quote!(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(&quote!(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(&quote!(base));
1179        let made = to_value(&parse_quote!(#ty), &quote!(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
1216/// The public functions of an inline module, called as `module::function(..)`.
1217fn 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}