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