Skip to main content

yog_data_derive/
lib.rs

1//! `#[derive(Schema)]` — see `yog_data::Schema`.
2//!
3//! - A struct with named fields is an object. Field attributes:
4//!   `#[data(rename = "name")]`, `#[data(default)]` /
5//!   `#[data(default = "path::to_fn")]`, `#[data(skip)]` (never read or
6//!   written; `Default`). `Option` fields may be left out.
7//! - A struct with one unnamed field is that field's format.
8//! - An enum of unit variants is a string: the variant's name in
9//!   snake_case, or `#[data(rename = "...")]`.
10//! - `#[data(tag = "type")]` on an enum: an object whose `type` names the
11//!   variant; a variant is a unit, has named fields, or wraps one value —
12//!   a struct's fields sit next to the tag, anything else under `value`
13//!   (`yog_data::schema::WRAPPED`).
14//!
15//! - `#[data(view)]` on a struct: `NameView<'a>` too, the struct read out
16//!   of a frame without copying (`yog_data::frame::View`).
17//!
18//! Every type also packs itself into frames (`pack` / `unpack`: fields by
19//! position, unit enums by index) and writes the binary form straight
20//! (`encode` / `decode`).
21//!
22//! Doc comments become the shape's docs (Yog-IDLE's hover text and the
23//! docs' field tables).
24
25mod attr_name;
26mod enums;
27
28use proc_macro::TokenStream;
29use proc_macro2::TokenStream as Tokens;
30use quote::quote;
31use syn::{Data, DeriveInput, Fields, Ident, LitStr, Type, parse_macro_input};
32
33thread_local! {
34    /// The path of `yog_data` for the derive running now: `::yog_data`, or
35    /// the container's `#[data(crate = "...")]` (a crate that re-exports it).
36    static KRATE: std::cell::RefCell<Tokens> = std::cell::RefCell::new(quote!(::yog_data));
37    /// Whether the container says `#[data(rename_all = "camelCase")]`.
38    static CAMEL: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
39}
40
41/// The path of `yog_data` the generated code uses.
42fn krate() -> Tokens {
43    KRATE.with(|k| k.borrow().clone())
44}
45
46#[proc_macro_derive(Schema, attributes(data))]
47pub fn derive_schema(input: TokenStream) -> TokenStream {
48    let input = parse_macro_input!(input as DeriveInput);
49    let container = match attrs(&input.attrs) {
50        Ok(a) => a,
51        Err(e) => return e.to_compile_error().into(),
52    };
53    let camel = match container.rename_all.as_deref() {
54        None => false,
55        Some(attr_name::CAMEL_CASE) => true,
56        Some(_) => {
57            return syn::Error::new_spanned(
58                &input.ident,
59                "rename_all = \"camelCase\" is the one case",
60            )
61            .to_compile_error()
62            .into();
63        }
64    };
65    CAMEL.with(|c| c.set(camel));
66    let path = container
67        .krate
68        .unwrap_or_else(|| syn::parse_quote!(::yog_data));
69    KRATE.with(|k| *k.borrow_mut() = quote!(#path));
70    match expand(&input) {
71        Ok(t) => t.into(),
72        Err(e) => e.to_compile_error().into(),
73    }
74}
75
76#[derive(Default)]
77struct Attrs {
78    rename: Option<String>,
79    /// The container's case for its fields' or variants' names.
80    rename_all: Option<String>,
81    default: Option<Option<syn::Path>>,
82    skip: bool,
83    tag: Option<String>,
84    with: Option<Type>,
85    range: Option<(f64, f64)>,
86    /// A tagged enum's variant for every tag the others don't take.
87    other: bool,
88    /// The container's path of `yog_data` (a crate re-exporting it).
89    krate: Option<syn::Path>,
90    /// A struct gets a view of frames (`yog_data::frame::View`).
91    view: bool,
92}
93
94fn attrs(list: &[syn::Attribute]) -> syn::Result<Attrs> {
95    let mut a = Attrs::default();
96    for attr in list.iter().filter(|a| a.path().is_ident(attr_name::DATA)) {
97        attr.parse_nested_meta(|meta| {
98            if meta.path.is_ident(attr_name::RENAME) {
99                a.rename = Some(meta.value()?.parse::<LitStr>()?.value());
100            } else if meta.path.is_ident(attr_name::RENAME_ALL) {
101                a.rename_all = Some(meta.value()?.parse::<LitStr>()?.value());
102            } else if meta.path.is_ident(attr_name::TAG) {
103                a.tag = Some(meta.value()?.parse::<LitStr>()?.value());
104            } else if meta.path.is_ident(attr_name::DEFAULT) {
105                a.default = Some(if meta.input.peek(syn::Token![=]) {
106                    Some(meta.value()?.parse::<LitStr>()?.parse()?)
107                } else {
108                    None
109                });
110            } else if meta.path.is_ident(attr_name::RANGE) {
111                let lit = meta.value()?.parse::<LitStr>()?;
112                let text = lit.value();
113                let parsed = text.split_once("..=").and_then(|(lo, hi)| {
114                    Some((
115                        lo.trim().parse::<f64>().ok()?,
116                        hi.trim().parse::<f64>().ok()?,
117                    ))
118                });
119                a.range = Some(parsed.ok_or_else(|| {
120                    syn::Error::new_spanned(&lit, "range = \"lo..=hi\" (numbers)")
121                })?);
122            } else if meta.path.is_ident(attr_name::WITH) {
123                a.with = Some(meta.value()?.parse::<LitStr>()?.parse()?);
124            } else if meta.path.is_ident(attr_name::SKIP) {
125                a.skip = true;
126            } else if meta.path.is_ident(attr_name::VIEW) {
127                a.view = true;
128            } else if meta.path.is_ident(attr_name::OTHER) {
129                a.other = true;
130            } else if meta.path.is_ident(attr_name::CRATE) {
131                a.krate = Some(meta.value()?.parse::<LitStr>()?.parse()?);
132            } else {
133                return Err(meta
134                    .error("unknown `data` attribute (rename, rename_all, default, skip, tag, with, range, other, crate, view)"));
135            }
136            Ok(())
137        })?;
138    }
139    Ok(a)
140}
141
142fn docs(list: &[syn::Attribute]) -> String {
143    let mut lines = Vec::new();
144    for attr in list.iter().filter(|a| a.path().is_ident(attr_name::DOC)) {
145        if let syn::Meta::NameValue(nv) = &attr.meta
146            && let syn::Expr::Lit(syn::ExprLit {
147                lit: syn::Lit::Str(s),
148                ..
149            }) = &nv.value
150        {
151            lines.push(s.value().trim().to_string());
152        }
153    }
154    // Paragraphs keep their breaks; lines of one paragraph join with spaces.
155    let mut out = String::new();
156    for line in lines {
157        if line.is_empty() {
158            if !out.ends_with("\n\n") && !out.is_empty() {
159                out.push_str("\n\n");
160            }
161        } else {
162            if !out.is_empty() && !out.ends_with('\n') {
163                out.push(' ');
164            }
165            out.push_str(&line);
166        }
167    }
168    out.trim_end().to_string()
169}
170
171/// A field's name as written: as in Rust, or in camel case (`rename_all`).
172fn field_key(ident: &Ident) -> String {
173    let name = ident.to_string().trim_start_matches("r#").to_string();
174    if !CAMEL.with(std::cell::Cell::get) {
175        return name;
176    }
177    let mut out = String::new();
178    let mut upper = false;
179    for c in name.chars() {
180        match c {
181            '_' => upper = !out.is_empty(),
182            c if upper => {
183                out.push(c.to_ascii_uppercase());
184                upper = false;
185            }
186            c => out.push(c),
187        }
188    }
189    out
190}
191
192/// `f` with fields named as in Rust: an enum's `rename_all` names its
193/// variants, not their fields.
194fn fields_as_written<T>(f: impl FnOnce() -> T) -> T {
195    let camel = CAMEL.with(|c| c.replace(false));
196    let out = f();
197    CAMEL.with(|c| c.set(camel));
198    out
199}
200
201/// A variant's name as written: in snake case, or camel case (`rename_all`).
202fn variant_key(ident: &Ident) -> String {
203    if !CAMEL.with(std::cell::Cell::get) {
204        return snake(ident);
205    }
206    let name = ident.to_string();
207    let mut chars = name.chars();
208    chars
209        .next()
210        .map(|first| first.to_ascii_lowercase().to_string() + chars.as_str())
211        .unwrap_or_default()
212}
213
214fn snake(ident: &Ident) -> String {
215    let mut out = String::new();
216    for (i, c) in ident.to_string().chars().enumerate() {
217        if c.is_ascii_uppercase() {
218            if i > 0 {
219                out.push('_');
220            }
221            out.push(c.to_ascii_lowercase());
222        } else {
223            out.push(c);
224        }
225    }
226    out
227}
228
229struct NamedField {
230    ident: Ident,
231    ty: Type,
232    key: String,
233    doc: String,
234    default: Option<Option<syn::Path>>,
235    skip: bool,
236    /// The codec: `with = "…"`, else `Plain` (the type's own schema).
237    with: Type,
238    /// Whether `with` names a codec (else it's the type's own schema).
239    codec: bool,
240    range: Option<(f64, f64)>,
241}
242
243fn named_fields(fields: &syn::FieldsNamed) -> syn::Result<Vec<NamedField>> {
244    let krate = krate();
245    fields
246        .named
247        .iter()
248        .map(|f| {
249            let a = attrs(&f.attrs)?;
250            let Some(ident) = f.ident.clone() else {
251                return Err(syn::Error::new_spanned(
252                    f,
253                    "Schema: a named field without a name",
254                ));
255            };
256            let key = a.rename.unwrap_or_else(|| field_key(&ident));
257            Ok(NamedField {
258                ident,
259                ty: f.ty.clone(),
260                key,
261                doc: docs(&f.attrs),
262                default: a.default,
263                skip: a.skip,
264                codec: a.with.is_some(),
265                with: a
266                    .with
267                    .unwrap_or_else(|| syn::parse_quote!(#krate::with::Plain)),
268                range: a.range,
269            })
270        })
271        .collect()
272}
273
274/// `Vec<Field>` describing `fields`.
275fn field_shapes(fields: &[NamedField]) -> Tokens {
276    let krate = krate();
277    let items = fields.iter().filter(|f| !f.skip).map(|f| {
278        let (key, doc, ty, w) = (&f.key, &f.doc, &f.ty, &f.with);
279        let has_default = f.default.is_some();
280        let range = match f.range {
281            Some((lo, hi)) => quote! { ::std::option::Option::Some((#lo, #hi)) },
282            None => quote! { ::std::option::Option::None },
283        };
284        quote! {
285            #krate::schema::Field {
286                name: #key,
287                doc: #doc,
288                shape: <#w as #krate::with::With<#ty>>::shape,
289                required: !#has_default && <#w as #krate::with::With<#ty>>::absent().is_none(),
290                range: #range,
291            }
292        }
293    });
294    quote! { ::std::vec![#(#items),*] }
295}
296
297/// Reads `fields` out of `map` (in scope) into `ctor { ... }`.
298fn read_fields(fields: &[NamedField], ctor: Tokens) -> Tokens {
299    let keys = fields.iter().filter(|f| !f.skip).map(|f| &f.key);
300    let lets = fields.iter().map(|f| {
301        let (ident, ty, key, w) = (&f.ident, &f.ty, &f.key, &f.with);
302        if f.skip {
303            return quote! { let #ident: ::std::option::Option<#ty> = ::std::option::Option::Some(::std::default::Default::default()); };
304        }
305        match &f.default {
306            None => quote! { let #ident = cx.field_with::<#w, #ty>(map, #key); },
307            Some(None) => quote! { let #ident = cx.field_or_with::<#w, #ty>(map, #key, ::std::default::Default::default); },
308            Some(Some(path)) => quote! { let #ident = cx.field_or_with::<#w, #ty>(map, #key, #path); },
309        }
310    });
311    let ranges = fields.iter().filter_map(|f| {
312        let (ident, key) = (&f.ident, &f.key);
313        let (lo, hi) = f.range?;
314        Some(quote! { let #ident = cx.in_range(#key, #ident, #lo, #hi); })
315    });
316    let inits = fields.iter().map(|f| {
317        let ident = &f.ident;
318        quote! { #ident: #ident? }
319    });
320    quote! {
321        cx.unknown_keys(map, &[#(#keys),*]);
322        cx.own_keys(|cx| {
323            #(#lets)*
324            #(#ranges)*
325            ::std::option::Option::Some(#ctor { #(#inits),* })
326        })
327    }
328}
329
330/// Inserts `fields` (bound by name) into `m` (in scope).
331fn write_fields(fields: &[NamedField]) -> Tokens {
332    let krate = krate();
333    let puts = fields.iter().filter(|f| !f.skip).map(|f| {
334        let (ident, key, ty, w) = (&f.ident, &f.key, &f.ty, &f.with);
335        quote! {
336            if !<#w as #krate::with::With<#ty>>::skip(#ident) {
337                m.insert(#key, <#w as #krate::with::With<#ty>>::write(#ident));
338            }
339        }
340    });
341    quote! { #(#puts)* }
342}
343
344/// `fields` (bound by name) written straight as a map.
345fn encode_fields(fields: &[NamedField]) -> Tokens {
346    let krate = krate();
347    let written: Vec<_> = fields.iter().filter(|f| !f.skip).collect();
348    let counts = written.iter().map(|f| {
349        let (ident, ty, w) = (&f.ident, &f.ty, &f.with);
350        quote! { + usize::from(!<#w as #krate::with::With<#ty>>::skip(#ident)) }
351    });
352    let puts = written.iter().map(|f| {
353        let (ident, key, ty, w) = (&f.ident, &f.key, &f.ty, &f.with);
354        quote! {
355            if !<#w as #krate::with::With<#ty>>::skip(#ident) {
356                __yog_w.key(#key);
357                <#w as #krate::with::With<#ty>>::encode(#ident, __yog_w);
358            }
359        }
360    });
361    quote! {
362        __yog_w.map(0 #(#counts)*);
363        #(#puts)*
364    }
365}
366
367/// `ctor { ... }` read straight from a map — `None` (the slow way) for a
368/// key it doesn't know, or a field with a range to check.
369fn decode_fields(fields: &[NamedField], ctor: Tokens) -> Option<Tokens> {
370    let krate = krate();
371    if fields.iter().any(|f| f.range.is_some()) {
372        return None;
373    }
374    let read: Vec<_> = fields.iter().filter(|f| !f.skip).collect();
375    // Each field's slot, named apart from anything a default may call.
376    let slot = |f: &NamedField| quote::format_ident!("__yog_field_{}", f.ident);
377    let slots = read.iter().map(|f| {
378        let (s, ty) = (slot(f), &f.ty);
379        quote! { let mut #s: ::std::option::Option<#ty> = ::std::option::Option::None; }
380    });
381    let arms = read.iter().map(|f| {
382        let (s, key, ty, w) = (slot(f), &f.key, &f.ty, &f.with);
383        quote! { #key => #s = ::std::option::Option::Some(<#w as #krate::with::With<#ty>>::decode(__yog_d)?), }
384    });
385    let inits = fields.iter().map(|f| {
386        let (ident, ty, w) = (&f.ident, &f.ty, &f.with);
387        let value = if f.skip {
388            quote! { ::std::default::Default::default() }
389        } else {
390            let missing = match &f.default {
391                None => quote! { <#w as #krate::with::With<#ty>>::absent()? },
392                Some(None) => quote! { ::std::default::Default::default() },
393                Some(Some(path)) => quote! { #path() },
394            };
395            let s = slot(f);
396            quote! { match #s { ::std::option::Option::Some(__yog_v) => __yog_v, ::std::option::Option::None => #missing } }
397        };
398        quote! { #ident: #value }
399    });
400    Some(quote! {
401        let __yog_n = __yog_d.map()?;
402        #(#slots)*
403        for _ in 0..__yog_n {
404            match __yog_d.key()? {
405                #(#arms)*
406                _ => return ::std::option::Option::None,
407            }
408        }
409        __yog_d.leave();
410        ::std::option::Option::Some(#ctor { #(#inits),* })
411    })
412}
413
414/// `fields` (bound by name) packed into a frame: a record, every field in order.
415fn pack_fields(fields: &[NamedField]) -> Tokens {
416    let krate = krate();
417    let puts = fields.iter().filter(|f| !f.skip).map(|f| {
418        let (ident, ty, w) = (&f.ident, &f.ty, &f.with);
419        quote! { <#w as #krate::with::With<#ty>>::pack(#ident, __yog_p); }
420    });
421    quote! {
422        let __yog_at = __yog_p.begin();
423        #(#puts)*
424        __yog_p.end(__yog_at);
425    }
426}
427
428/// `ctor { ... }` out of a frame's record: the fields it has in order, the
429/// rest their defaults; fields it has that this type doesn't know skipped.
430/// A frame comes from a writer of the same schema: ranges aren't checked again.
431fn unpack_fields(fields: &[NamedField], ctor: Tokens) -> Tokens {
432    let krate = krate();
433    let slot = |f: &NamedField| quote::format_ident!("__yog_field_{}", f.ident);
434    let gets = fields.iter().filter(|f| !f.skip).map(|f| {
435        let (s, ty, w) = (slot(f), &f.ty, &f.with);
436        quote! {
437            let #s: ::std::option::Option<#ty> = if __yog_u.more(__yog_end) {
438                ::std::option::Option::Some(<#w as #krate::with::With<#ty>>::unpack(__yog_u)?)
439            } else {
440                ::std::option::Option::None
441            };
442        }
443    });
444    let inits = fields.iter().map(|f| {
445        let (ident, ty, w) = (&f.ident, &f.ty, &f.with);
446        let value = if f.skip {
447            quote! { ::std::default::Default::default() }
448        } else {
449            let s = slot(f);
450            let missing = match &f.default {
451                None => quote! { <#w as #krate::with::With<#ty>>::absent()? },
452                Some(None) => quote! { ::std::default::Default::default() },
453                Some(Some(path)) => quote! { #path() },
454            };
455            quote! { match #s { ::std::option::Option::Some(__yog_v) => __yog_v, ::std::option::Option::None => #missing } }
456        };
457        quote! { #ident: #value }
458    });
459    quote! {
460        let __yog_end = __yog_u.begin()?;
461        #(#gets)*
462        __yog_u.leave(__yog_end)?;
463        ::std::option::Option::Some(#ctor { #(#inits),* })
464    }
465}
466
467/// A struct with named fields: an object.
468fn expand_named(named: &syn::FieldsNamed, shape_name: &str, doc: &str) -> syn::Result<Tokens> {
469    let krate = krate();
470    let fields = named_fields(named)?;
471    let shapes = field_shapes(&fields);
472    let read = read_fields(&fields, quote! { Self });
473    let idents: Vec<_> = fields.iter().map(|f| &f.ident).collect();
474    let write = write_fields(&fields);
475    let encode = encode_fields(&fields);
476    let decode = decode_fields(&fields, quote! { Self }).map(|body| {
477        quote! {
478            fn decode(__yog_d: &mut #krate::binary::Decoder<'_>) -> ::std::option::Option<Self> {
479                #body
480            }
481        }
482    });
483    let pack = pack_fields(&fields);
484    let unpack = unpack_fields(&fields, quote! { Self });
485    Ok(quote! {
486        // A skipped field is bound and not encoded.
487        #[allow(unused_variables)]
488        fn encode(&self, __yog_w: &mut #krate::binary::Writer) {
489            let Self { #(#idents),* } = self;
490            #encode
491        }
492        #decode
493        // A skipped field is bound and not packed.
494        #[allow(unused_variables)]
495        fn pack(&self, __yog_p: &mut #krate::frame::Packer) {
496            let Self { #(#idents),* } = self;
497            #pack
498        }
499        fn unpack(__yog_u: &mut #krate::frame::Unpacker<'_>) -> ::std::option::Option<Self> {
500            #unpack
501        }
502        fn shape() -> #krate::Shape {
503            #krate::Shape::Struct(#krate::schema::StructShape { name: #shape_name, doc: #doc, fields: #shapes })
504        }
505        fn read(v: &#krate::ValueRef<'_>, cx: &mut #krate::Reader) -> ::std::option::Option<Self> {
506            let map = cx.map(v)?;
507            #read
508        }
509        // A skipped field is bound and not written.
510        #[allow(unused_variables)]
511        fn write(&self) -> #krate::Value {
512            let Self { #(#idents),* } = self;
513            let mut m = #krate::Map::new();
514            #write
515            #krate::Value::Map(m)
516        }
517    })
518}
519
520/// `NameView<'a>` and `Name`'s `View` (`#[data(view)]`): every field
521/// viewed (a codec's field read whole), a field an older writer didn't
522/// have making the view `None` — read the owned way then.
523fn view_items(input: &DeriveInput, fields: &[NamedField]) -> syn::Result<Tokens> {
524    let krate = krate();
525    let (name, vis) = (&input.ident, &input.vis);
526    if !input.generics.params.is_empty() {
527        return Err(syn::Error::new_spanned(
528            name,
529            "Schema: `view` is for a struct without generics",
530        ));
531    }
532    let view = quote::format_ident!("{}View", name);
533    let shown: Vec<&NamedField> = fields.iter().filter(|f| !f.skip).collect();
534    let types = shown.iter().map(|f| {
535        let (ident, ty) = (&f.ident, &f.ty);
536        if f.codec {
537            quote! { pub #ident: #ty, }
538        } else {
539            quote! { pub #ident: <#ty as #krate::frame::View<'a>>::View, }
540        }
541    });
542    let reads = shown.iter().map(|f| {
543        let (ident, ty, w) = (&f.ident, &f.ty, &f.with);
544        let read = if f.codec {
545            quote! { <#w as #krate::with::With<#ty>>::unpack(__yog_u)? }
546        } else {
547            quote! { <#ty as #krate::frame::View<'a>>::unpack_view(__yog_u)? }
548        };
549        quote! { let #ident = if __yog_u.more(__yog_end) { #read } else { return ::std::option::Option::None }; }
550    });
551    let idents = shown.iter().map(|f| &f.ident);
552    let doc =
553        format!("[`{name}`] viewed in a frame: strings borrowed, packed numbers read in place.");
554    Ok(quote! {
555        #[doc = #doc]
556        #vis struct #view<'a> {
557            #(#types)*
558            _frame: ::std::marker::PhantomData<&'a ()>,
559        }
560
561        impl<'a> #krate::frame::View<'a> for #name {
562            type View = #view<'a>;
563            type ListView = #krate::frame::List<'a, Self>;
564            fn unpack_view(__yog_u: &mut #krate::frame::Unpacker<'a>) -> ::std::option::Option<#view<'a>> {
565                let __yog_end = __yog_u.begin()?;
566                #(#reads)*
567                __yog_u.leave(__yog_end)?;
568                ::std::option::Option::Some(#view { #(#idents,)* _frame: ::std::marker::PhantomData })
569            }
570            fn unpack_list_view(__yog_u: &mut #krate::frame::Unpacker<'a>) -> ::std::option::Option<Self::ListView> {
571                #krate::frame::list(__yog_u)
572            }
573        }
574    })
575}
576
577fn expand(input: &DeriveInput) -> syn::Result<Tokens> {
578    let krate = krate();
579    let name = &input.ident;
580    let (impl_g, ty_g, where_g) = input.generics.split_for_impl();
581    let top = attrs(&input.attrs)?;
582    let shape_name = top.rename.clone().unwrap_or_else(|| name.to_string());
583    let doc = docs(&input.attrs);
584    let body = match &input.data {
585        Data::Struct(s) => match &s.fields {
586            Fields::Named(named) => expand_named(named, &shape_name, &doc)?,
587            Fields::Unnamed(u) if u.unnamed.len() == 1 => expand_newtype(&u.unnamed[0].ty),
588            _ => {
589                return Err(syn::Error::new_spanned(
590                    name,
591                    "Schema: a struct needs named fields or exactly one unnamed field",
592                ));
593            }
594        },
595        Data::Enum(e) => enums::expand_enum(e, &top, &shape_name, &doc)?,
596        Data::Union(_) => {
597            return Err(syn::Error::new_spanned(
598                name,
599                "Schema: unions aren't supported",
600            ));
601        }
602    };
603    let view = view_of(input, &top)?;
604    let fields = fields_of(input)?;
605    Ok(quote! {
606        impl #impl_g #krate::Schema for #name #ty_g #where_g {
607            #body
608        }
609        #view
610        #fields
611    })
612}
613
614/// A struct's fields by their Rust names, as files write them (`field!`).
615fn fields_of(input: &DeriveInput) -> syn::Result<Tokens> {
616    let krate = krate();
617    let name = &input.ident;
618    let (impl_g, ty_g, where_g) = input.generics.split_for_impl();
619    let Data::Struct(syn::DataStruct {
620        fields: Fields::Named(named),
621        ..
622    }) = &input.data
623    else {
624        return Ok(quote! {});
625    };
626    let pairs = named_fields(named)?.into_iter().map(|f| {
627        let ident = f.ident.to_string();
628        let ident = ident.trim_start_matches("r#");
629        let key = &f.key;
630        quote! { (#ident, #key) }
631    });
632    Ok(quote! {
633        impl #impl_g #krate::schema::Fields for #name #ty_g #where_g {
634            const FIELDS: &'static [(&'static str, &'static str)] = &[#(#pairs),*];
635        }
636    })
637}
638
639/// A struct of one unnamed field: its field's schema throughout.
640fn expand_newtype(ty: &Type) -> Tokens {
641    let krate = krate();
642    quote! {
643        fn shape() -> #krate::Shape { <#ty as #krate::Schema>::shape() }
644        fn read(v: &#krate::ValueRef<'_>, cx: &mut #krate::Reader) -> ::std::option::Option<Self> {
645            <#ty as #krate::Schema>::read(v, cx).map(Self)
646        }
647        fn write(&self) -> #krate::Value { #krate::Schema::write(&self.0) }
648        fn encode(&self, w: &mut #krate::binary::Writer) { #krate::Schema::encode(&self.0, w) }
649        fn decode(d: &mut #krate::binary::Decoder<'_>) -> ::std::option::Option<Self> {
650            <#ty as #krate::Schema>::decode(d).map(Self)
651        }
652        fn pack(&self, p: &mut #krate::frame::Packer) { #krate::Schema::pack(&self.0, p) }
653        fn unpack(u: &mut #krate::frame::Unpacker<'_>) -> ::std::option::Option<Self> {
654            <#ty as #krate::Schema>::unpack(u).map(Self)
655        }
656        fn absent() -> ::std::option::Option<Self> { <#ty as #krate::Schema>::absent().map(Self) }
657        fn skip(&self) -> bool { #krate::Schema::skip(&self.0) }
658    }
659}
660
661/// The type's view of frames: a struct's (`#[data(view)]`), an enum of names' (itself).
662fn view_of(input: &DeriveInput, top: &Attrs) -> syn::Result<Tokens> {
663    let krate = krate();
664    let name = &input.ident;
665    Ok(match (&input.data, top.view) {
666        (
667            Data::Struct(syn::DataStruct {
668                fields: Fields::Named(named),
669                ..
670            }),
671            true,
672        ) => view_items(input, &named_fields(named)?)?,
673        (_, true) => {
674            return Err(syn::Error::new_spanned(
675                name,
676                "Schema: `view` is for a struct with named fields",
677            ));
678        }
679        (Data::Enum(e), false)
680            if top.tag.is_none()
681                && input.generics.params.is_empty()
682                && e.variants.iter().all(|v| matches!(v.fields, Fields::Unit)) =>
683        {
684            quote! {
685                impl<'a> #krate::frame::View<'a> for #name {
686                    type View = Self;
687                    type ListView = ::std::vec::Vec<Self>;
688                    fn unpack_view(u: &mut #krate::frame::Unpacker<'a>) -> ::std::option::Option<Self> {
689                        <Self as #krate::Schema>::unpack(u)
690                    }
691                    fn unpack_list_view(u: &mut #krate::frame::Unpacker<'a>) -> ::std::option::Option<::std::vec::Vec<Self>> {
692                        <Self as #krate::Schema>::unpack_list(u)
693                    }
694                }
695            }
696        }
697        _ => quote! {},
698    })
699}