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