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    if !CAMEL.with(std::cell::Cell::get) {
234        return snake(ident);
235    }
236    let name = ident.to_string();
237    let mut chars = name.chars();
238    chars
239        .next()
240        .map(|first| first.to_ascii_lowercase().to_string() + chars.as_str())
241        .unwrap_or_default()
242}
243
244fn snake(ident: &Ident) -> String {
245    let mut out = String::new();
246    for (i, c) in ident.to_string().chars().enumerate() {
247        if c.is_ascii_uppercase() {
248            if i > 0 {
249                out.push('_');
250            }
251            out.push(c.to_ascii_lowercase());
252        } else {
253            out.push(c);
254        }
255    }
256    out
257}
258
259struct NamedField {
260    ident: Ident,
261    ty: Type,
262    key: String,
263    doc: String,
264    default: Option<Option<syn::Path>>,
265    skip: bool,
266    /// The codec: `with = "…"`, else `Plain` (the type's own schema).
267    with: Type,
268    /// Whether `with` names a codec (else it's the type's own schema).
269    codec: bool,
270    range: Option<(f64, f64)>,
271}
272
273fn named_fields(fields: &syn::FieldsNamed) -> syn::Result<Vec<NamedField>> {
274    let krate = krate();
275    fields
276        .named
277        .iter()
278        .map(|f| {
279            let a = attrs(&f.attrs)?;
280            let Some(ident) = f.ident.clone() else {
281                return Err(syn::Error::new_spanned(
282                    f,
283                    "Schema: a named field without a name",
284                ));
285            };
286            let key = a.rename.unwrap_or_else(|| field_key(&ident));
287            Ok(NamedField {
288                ident,
289                ty: f.ty.clone(),
290                key,
291                doc: docs(&f.attrs),
292                default: a.default,
293                skip: a.skip,
294                codec: a.with.is_some(),
295                with: a
296                    .with
297                    .unwrap_or_else(|| syn::parse_quote!(#krate::with::Plain)),
298                range: a.range,
299            })
300        })
301        .collect()
302}
303
304/// `Vec<Field>` describing `fields`.
305fn field_shapes(fields: &[NamedField]) -> Tokens {
306    let krate = krate();
307    let items = fields.iter().filter(|f| !f.skip).map(|f| {
308        let (key, doc, ty, w) = (&f.key, &f.doc, &f.ty, &f.with);
309        let has_default = f.default.is_some();
310        let range = match f.range {
311            Some((lo, hi)) => quote! { ::std::option::Option::Some((#lo, #hi)) },
312            None => quote! { ::std::option::Option::None },
313        };
314        quote! {
315            #krate::schema::Field {
316                name: #key,
317                doc: #doc,
318                shape: <#w as #krate::with::With<#ty>>::shape,
319                required: !#has_default && <#w as #krate::with::With<#ty>>::absent().is_none(),
320                range: #range,
321            }
322        }
323    });
324    quote! { ::std::vec![#(#items),*] }
325}
326
327/// Reads `fields` out of `map` (in scope) into `ctor { ... }`.
328fn read_fields(fields: &[NamedField], ctor: Tokens) -> Tokens {
329    let keys = fields.iter().filter(|f| !f.skip).map(|f| &f.key);
330    let lets = fields.iter().map(|f| {
331        let (ident, ty, key, w) = (&f.ident, &f.ty, &f.key, &f.with);
332        if f.skip {
333            return quote! { let #ident: ::std::option::Option<#ty> = ::std::option::Option::Some(::std::default::Default::default()); };
334        }
335        match &f.default {
336            None => quote! { let #ident = cx.field_with::<#w, #ty>(map, #key); },
337            Some(None) => quote! { let #ident = cx.field_or_with::<#w, #ty>(map, #key, ::std::default::Default::default); },
338            Some(Some(path)) => quote! { let #ident = cx.field_or_with::<#w, #ty>(map, #key, #path); },
339        }
340    });
341    let ranges = fields.iter().filter_map(|f| {
342        let (ident, key) = (&f.ident, &f.key);
343        let (lo, hi) = f.range?;
344        Some(quote! { let #ident = cx.in_range(#key, #ident, #lo, #hi); })
345    });
346    let inits = fields.iter().map(|f| {
347        let ident = &f.ident;
348        quote! { #ident: #ident? }
349    });
350    quote! {
351        cx.unknown_keys(map, &[#(#keys),*]);
352        cx.own_keys(|cx| {
353            #(#lets)*
354            #(#ranges)*
355            ::std::option::Option::Some(#ctor { #(#inits),* })
356        })
357    }
358}
359
360/// Inserts `fields` (bound by name) into `m` (in scope).
361fn write_fields(fields: &[NamedField]) -> Tokens {
362    let krate = krate();
363    let puts = fields.iter().filter(|f| !f.skip).map(|f| {
364        let (ident, key, ty, w) = (&f.ident, &f.key, &f.ty, &f.with);
365        quote! {
366            if !<#w as #krate::with::With<#ty>>::skip(#ident) {
367                m.insert(#key, <#w as #krate::with::With<#ty>>::write(#ident));
368            }
369        }
370    });
371    quote! { #(#puts)* }
372}
373
374/// `fields` (bound by name) written straight as a map.
375fn encode_fields(fields: &[NamedField]) -> Tokens {
376    let krate = krate();
377    let written: Vec<_> = fields.iter().filter(|f| !f.skip).collect();
378    let counts = written.iter().map(|f| {
379        let (ident, ty, w) = (&f.ident, &f.ty, &f.with);
380        quote! { + usize::from(!<#w as #krate::with::With<#ty>>::skip(#ident)) }
381    });
382    let puts = written.iter().map(|f| {
383        let (ident, key, ty, w) = (&f.ident, &f.key, &f.ty, &f.with);
384        quote! {
385            if !<#w as #krate::with::With<#ty>>::skip(#ident) {
386                __yog_w.key(#key);
387                <#w as #krate::with::With<#ty>>::encode(#ident, __yog_w);
388            }
389        }
390    });
391    quote! {
392        __yog_w.map(0 #(#counts)*);
393        #(#puts)*
394    }
395}
396
397/// `ctor { ... }` read straight from a map — `None` (the slow way) for a
398/// key it doesn't know, or a field with a range to check.
399fn decode_fields(fields: &[NamedField], ctor: Tokens) -> Option<Tokens> {
400    let krate = krate();
401    if fields.iter().any(|f| f.range.is_some()) {
402        return None;
403    }
404    let read: Vec<_> = fields.iter().filter(|f| !f.skip).collect();
405    // Each field's slot, named apart from anything a default may call.
406    let slot = |f: &NamedField| quote::format_ident!("__yog_field_{}", f.ident);
407    let slots = read.iter().map(|f| {
408        let (s, ty) = (slot(f), &f.ty);
409        quote! { let mut #s: ::std::option::Option<#ty> = ::std::option::Option::None; }
410    });
411    let arms = read.iter().map(|f| {
412        let (s, key, ty, w) = (slot(f), &f.key, &f.ty, &f.with);
413        quote! { #key => #s = ::std::option::Option::Some(<#w as #krate::with::With<#ty>>::decode(__yog_d)?), }
414    });
415    let inits = fields.iter().map(|f| {
416        let (ident, ty, w) = (&f.ident, &f.ty, &f.with);
417        let value = if f.skip {
418            quote! { ::std::default::Default::default() }
419        } else {
420            let missing = match &f.default {
421                None => quote! { <#w as #krate::with::With<#ty>>::absent()? },
422                Some(None) => quote! { ::std::default::Default::default() },
423                Some(Some(path)) => quote! { #path() },
424            };
425            let s = slot(f);
426            quote! { match #s { ::std::option::Option::Some(__yog_v) => __yog_v, ::std::option::Option::None => #missing } }
427        };
428        quote! { #ident: #value }
429    });
430    Some(quote! {
431        let __yog_n = __yog_d.map()?;
432        #(#slots)*
433        for _ in 0..__yog_n {
434            match __yog_d.key()? {
435                #(#arms)*
436                _ => return ::std::option::Option::None,
437            }
438        }
439        __yog_d.leave();
440        ::std::option::Option::Some(#ctor { #(#inits),* })
441    })
442}
443
444/// `fields` (bound by name) packed into a frame: a record, every field in order.
445fn pack_fields(fields: &[NamedField]) -> Tokens {
446    let krate = krate();
447    let puts = fields.iter().filter(|f| !f.skip).map(|f| {
448        let (ident, ty, w) = (&f.ident, &f.ty, &f.with);
449        quote! { <#w as #krate::with::With<#ty>>::pack(#ident, __yog_p); }
450    });
451    quote! {
452        let __yog_at = __yog_p.begin();
453        #(#puts)*
454        __yog_p.end(__yog_at);
455    }
456}
457
458/// `ctor { ... }` out of a frame's record: the fields it has in order, the
459/// rest their defaults; fields it has that this type doesn't know skipped.
460/// A frame comes from a writer of the same schema: ranges aren't checked again.
461fn unpack_fields(fields: &[NamedField], ctor: Tokens) -> Tokens {
462    let krate = krate();
463    let slot = |f: &NamedField| quote::format_ident!("__yog_field_{}", f.ident);
464    let gets = fields.iter().filter(|f| !f.skip).map(|f| {
465        let (s, ty, w) = (slot(f), &f.ty, &f.with);
466        quote! {
467            let #s: ::std::option::Option<#ty> = if __yog_u.more(__yog_end) {
468                ::std::option::Option::Some(<#w as #krate::with::With<#ty>>::unpack(__yog_u)?)
469            } else {
470                ::std::option::Option::None
471            };
472        }
473    });
474    let inits = fields.iter().map(|f| {
475        let (ident, ty, w) = (&f.ident, &f.ty, &f.with);
476        let value = if f.skip {
477            quote! { ::std::default::Default::default() }
478        } else {
479            let s = slot(f);
480            let missing = match &f.default {
481                None => quote! { <#w as #krate::with::With<#ty>>::absent()? },
482                Some(None) => quote! { ::std::default::Default::default() },
483                Some(Some(path)) => quote! { #path() },
484            };
485            quote! { match #s { ::std::option::Option::Some(__yog_v) => __yog_v, ::std::option::Option::None => #missing } }
486        };
487        quote! { #ident: #value }
488    });
489    quote! {
490        let __yog_end = __yog_u.begin()?;
491        #(#gets)*
492        __yog_u.leave(__yog_end)?;
493        ::std::option::Option::Some(#ctor { #(#inits),* })
494    }
495}
496
497/// A struct with named fields: an object.
498fn expand_named(named: &syn::FieldsNamed, shape_name: &str, doc: &str) -> syn::Result<Tokens> {
499    let krate = krate();
500    let fields = named_fields(named)?;
501    let shapes = field_shapes(&fields);
502    let read = read_fields(&fields, quote! { Self });
503    let idents: Vec<_> = fields.iter().map(|f| &f.ident).collect();
504    let write = write_fields(&fields);
505    let encode = encode_fields(&fields);
506    let decode = decode_fields(&fields, quote! { Self }).map(|body| {
507        quote! {
508            fn decode(__yog_d: &mut #krate::binary::Decoder<'_>) -> ::std::option::Option<Self> {
509                #body
510            }
511        }
512    });
513    let pack = pack_fields(&fields);
514    let unpack = unpack_fields(&fields, quote! { Self });
515    Ok(quote! {
516        // A skipped field is bound and not encoded.
517        #[allow(unused_variables)]
518        fn encode(&self, __yog_w: &mut #krate::binary::Writer) {
519            let Self { #(#idents),* } = self;
520            #encode
521        }
522        #decode
523        // A skipped field is bound and not packed.
524        #[allow(unused_variables)]
525        fn pack(&self, __yog_p: &mut #krate::frame::Packer) {
526            let Self { #(#idents),* } = self;
527            #pack
528        }
529        fn unpack(__yog_u: &mut #krate::frame::Unpacker<'_>) -> ::std::option::Option<Self> {
530            #unpack
531        }
532        fn shape() -> #krate::Shape {
533            #krate::Shape::Struct(#krate::schema::StructShape { name: #shape_name, doc: #doc, fields: #shapes })
534        }
535        fn read(v: &#krate::ValueRef<'_>, cx: &mut #krate::Reader) -> ::std::option::Option<Self> {
536            let map = cx.map(v)?;
537            #read
538        }
539        // A skipped field is bound and not written.
540        #[allow(unused_variables)]
541        fn write(&self) -> #krate::Value {
542            let Self { #(#idents),* } = self;
543            let mut m = #krate::Map::new();
544            #write
545            #krate::Value::Map(m)
546        }
547    })
548}
549
550/// `NameView<'a>` and `Name`'s `View` (`#[data(view)]`): every field
551/// viewed (a codec's field read whole), a field an older writer didn't
552/// have making the view `None` — read the owned way then.
553fn view_items(input: &DeriveInput, fields: &[NamedField]) -> syn::Result<Tokens> {
554    let krate = krate();
555    let (name, vis) = (&input.ident, &input.vis);
556    if !input.generics.params.is_empty() {
557        return Err(syn::Error::new_spanned(
558            name,
559            "Schema: `view` is for a struct without generics",
560        ));
561    }
562    let view = quote::format_ident!("{}View", name);
563    let shown: Vec<&NamedField> = fields.iter().filter(|f| !f.skip).collect();
564    let types = shown.iter().map(|f| {
565        let (ident, ty) = (&f.ident, &f.ty);
566        if f.codec {
567            quote! { pub #ident: #ty, }
568        } else {
569            quote! { pub #ident: <#ty as #krate::frame::View<'a>>::View, }
570        }
571    });
572    let reads = shown.iter().map(|f| {
573        let (ident, ty, w) = (&f.ident, &f.ty, &f.with);
574        let read = if f.codec {
575            quote! { <#w as #krate::with::With<#ty>>::unpack(__yog_u)? }
576        } else {
577            quote! { <#ty as #krate::frame::View<'a>>::unpack_view(__yog_u)? }
578        };
579        quote! { let #ident = if __yog_u.more(__yog_end) { #read } else { return ::std::option::Option::None }; }
580    });
581    let idents = shown.iter().map(|f| &f.ident);
582    let doc =
583        format!("[`{name}`] viewed in a frame: strings borrowed, packed numbers read in place.");
584    Ok(quote! {
585        #[doc = #doc]
586        #vis struct #view<'a> {
587            #(#types)*
588            _frame: ::std::marker::PhantomData<&'a ()>,
589        }
590
591        impl<'a> #krate::frame::View<'a> for #name {
592            type View = #view<'a>;
593            type ListView = #krate::frame::List<'a, Self>;
594            fn unpack_view(__yog_u: &mut #krate::frame::Unpacker<'a>) -> ::std::option::Option<#view<'a>> {
595                let __yog_end = __yog_u.begin()?;
596                #(#reads)*
597                __yog_u.leave(__yog_end)?;
598                ::std::option::Option::Some(#view { #(#idents,)* _frame: ::std::marker::PhantomData })
599            }
600            fn unpack_list_view(__yog_u: &mut #krate::frame::Unpacker<'a>) -> ::std::option::Option<Self::ListView> {
601                #krate::frame::list(__yog_u)
602            }
603        }
604    })
605}
606
607fn expand(input: &DeriveInput, top: &Attrs) -> syn::Result<Tokens> {
608    let krate = krate();
609    let name = &input.ident;
610    let (impl_g, ty_g, where_g) = input.generics.split_for_impl();
611    let shape_name = top.rename.clone().unwrap_or_else(|| name.to_string());
612    let doc = docs(&input.attrs);
613    let body = match &input.data {
614        Data::Struct(s) => match &s.fields {
615            Fields::Named(named) => expand_named(named, &shape_name, &doc)?,
616            Fields::Unnamed(u) if u.unnamed.len() == 1 => expand_newtype(&u.unnamed[0].ty),
617            _ => {
618                return Err(syn::Error::new_spanned(
619                    name,
620                    "Schema: a struct needs named fields or exactly one unnamed field",
621                ));
622            }
623        },
624        Data::Enum(e) => enums::expand_enum(e, top, &shape_name, &doc)?,
625        Data::Union(_) => {
626            return Err(syn::Error::new_spanned(
627                name,
628                "Schema: unions aren't supported",
629            ));
630        }
631    };
632    let view = view_of(input, top)?;
633    let fields = fields_of(input)?;
634    Ok(quote! {
635        impl #impl_g #krate::Schema for #name #ty_g #where_g {
636            #body
637        }
638        #view
639        #fields
640    })
641}
642
643/// A struct's fields by their Rust names, as files write them (`field!`).
644fn fields_of(input: &DeriveInput) -> syn::Result<Tokens> {
645    let krate = krate();
646    let name = &input.ident;
647    let (impl_g, ty_g, where_g) = input.generics.split_for_impl();
648    let Data::Struct(syn::DataStruct {
649        fields: Fields::Named(named),
650        ..
651    }) = &input.data
652    else {
653        return Ok(quote! {});
654    };
655    let pairs = named_fields(named)?.into_iter().map(|f| {
656        let ident = f.ident.to_string();
657        let ident = ident.trim_start_matches("r#");
658        let key = &f.key;
659        quote! { (#ident, #key) }
660    });
661    Ok(quote! {
662        impl #impl_g #krate::schema::Fields for #name #ty_g #where_g {
663            const FIELDS: &'static [(&'static str, &'static str)] = &[#(#pairs),*];
664        }
665    })
666}
667
668/// A struct of one unnamed field: its field's schema throughout.
669fn expand_newtype(ty: &Type) -> Tokens {
670    let krate = krate();
671    quote! {
672        fn shape() -> #krate::Shape { <#ty as #krate::Schema>::shape() }
673        fn read(v: &#krate::ValueRef<'_>, cx: &mut #krate::Reader) -> ::std::option::Option<Self> {
674            <#ty as #krate::Schema>::read(v, cx).map(Self)
675        }
676        fn write(&self) -> #krate::Value { #krate::Schema::write(&self.0) }
677        fn encode(&self, w: &mut #krate::binary::Writer) { #krate::Schema::encode(&self.0, w) }
678        fn decode(d: &mut #krate::binary::Decoder<'_>) -> ::std::option::Option<Self> {
679            <#ty as #krate::Schema>::decode(d).map(Self)
680        }
681        fn pack(&self, p: &mut #krate::frame::Packer) { #krate::Schema::pack(&self.0, p) }
682        fn unpack(u: &mut #krate::frame::Unpacker<'_>) -> ::std::option::Option<Self> {
683            <#ty as #krate::Schema>::unpack(u).map(Self)
684        }
685        fn absent() -> ::std::option::Option<Self> { <#ty as #krate::Schema>::absent().map(Self) }
686        fn skip(&self) -> bool { #krate::Schema::skip(&self.0) }
687    }
688}
689
690/// The type's view of frames: a struct's (`#[data(view)]`), an enum of names' (itself).
691fn view_of(input: &DeriveInput, top: &Attrs) -> syn::Result<Tokens> {
692    let krate = krate();
693    let name = &input.ident;
694    Ok(match (&input.data, top.view) {
695        (
696            Data::Struct(syn::DataStruct {
697                fields: Fields::Named(named),
698                ..
699            }),
700            true,
701        ) => view_items(input, &named_fields(named)?)?,
702        (_, true) => {
703            return Err(syn::Error::new_spanned(
704                name,
705                "Schema: `view` is for a struct with named fields",
706            ));
707        }
708        (Data::Enum(e), false)
709            if top.tag.is_none()
710                && input.generics.params.is_empty()
711                && e.variants.iter().all(|v| matches!(v.fields, Fields::Unit)) =>
712        {
713            quote! {
714                impl<'a> #krate::frame::View<'a> for #name {
715                    type View = Self;
716                    type ListView = ::std::vec::Vec<Self>;
717                    fn unpack_view(u: &mut #krate::frame::Unpacker<'a>) -> ::std::option::Option<Self> {
718                        <Self as #krate::Schema>::unpack(u)
719                    }
720                    fn unpack_list_view(u: &mut #krate::frame::Unpacker<'a>) -> ::std::option::Option<::std::vec::Vec<Self>> {
721                        <Self as #krate::Schema>::unpack_list(u)
722                    }
723                }
724            }
725        }
726        _ => quote! {},
727    })
728}