Skip to main content

reflect_derive/
lib.rs

1//! # reflect-derive
2//!
3//! Proc macro crate providing `#[derive(Reflect)]`.
4//!
5//! Generates a `reify_reflect_core::Reflect` implementation that emits a
6//! `reify_reflect_core::RuntimeValue` description of the type's structural
7//! shape:
8//!
9//! - **Named struct** → `RuntimeValue::List` of `(field_name_bytes, field_value)` pairs.
10//! - **Tuple struct** → `RuntimeValue::List` of positional field values.
11//! - **Unit struct** (`struct X;`) → `RuntimeValue::Unit`.
12//! - **Empty named struct** (`struct X {}`) → `RuntimeValue::List(vec![])`.
13//! - **Enum** → `RuntimeValue::List` of variant entries, each `(variant_name_bytes, variant_payload)`,
14//!   where the payload mirrors the per-variant shape (unit / tuple / named).
15//!
16//! Fields whose types implement `Reflect<Value = RuntimeValue>` are
17//! reflected; fields annotated with `#[reflect(skip)]` are omitted.
18//!
19//! Note: enum derivation reflects the *type schema*, not a runtime
20//! instance. The generated `reflect()` is a static method (matching the
21//! `reify_reflect_core::Reflect` trait), so it walks every variant and every
22//! variant's field types.
23
24#![deny(unsafe_code)]
25
26extern crate proc_macro;
27
28use proc_macro::TokenStream;
29use proc_macro2::TokenStream as TokenStream2;
30use quote::quote;
31use syn::{parse_macro_input, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, Variant};
32
33/// Derives the `Reflect` trait for a struct or enum.
34///
35/// See the crate-level documentation for a description of the encoding.
36///
37/// Fields annotated with `#[reflect(skip)]` are excluded from the output.
38///
39/// # Examples
40///
41/// ```ignore
42/// use reflect_derive::Reflect;
43/// use reify_reflect_core::{Reflect, RuntimeValue};
44///
45/// #[derive(Reflect)]
46/// struct Point {
47///     #[reflect(skip)]
48///     label: String,
49///     x: MyNat,
50///     y: MyNat,
51/// }
52///
53/// #[derive(Reflect)]
54/// struct Pair(MyNat, MyNat);
55///
56/// #[derive(Reflect)]
57/// enum Shape {
58///     Point,
59///     Line(MyNat, MyNat),
60///     Box { w: MyNat, h: MyNat },
61/// }
62/// ```
63#[proc_macro_derive(Reflect, attributes(reflect))]
64pub fn derive_reflect(input: TokenStream) -> TokenStream {
65    let input = parse_macro_input!(input as DeriveInput);
66    match derive_reflect_impl(&input) {
67        Ok(tokens) => tokens.into(),
68        Err(e) => e.to_compile_error().into(),
69    }
70}
71
72fn derive_reflect_impl(input: &DeriveInput) -> syn::Result<TokenStream2> {
73    let name = &input.ident;
74    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
75
76    let body = match &input.data {
77        Data::Struct(data) => struct_body(data)?,
78        Data::Enum(data) => enum_body(data)?,
79        Data::Union(_) => {
80            return Err(syn::Error::new_spanned(
81                name,
82                "Reflect cannot be derived for unions",
83            ));
84        }
85    };
86
87    Ok(quote! {
88        impl #impl_generics reify_reflect_core::Reflect for #name #ty_generics #where_clause {
89            type Value = reify_reflect_core::RuntimeValue;
90
91            fn reflect() -> Self::Value {
92                #body
93            }
94        }
95    })
96}
97
98fn struct_body(data: &DataStruct) -> syn::Result<TokenStream2> {
99    match &data.fields {
100        Fields::Named(named) => {
101            let entries = named_field_entries(&named.named)?;
102            Ok(quote! {
103                reify_reflect_core::RuntimeValue::List(vec![#(#entries),*])
104            })
105        }
106        Fields::Unnamed(unnamed) => {
107            let entries = positional_field_entries(&unnamed.unnamed)?;
108            Ok(quote! {
109                reify_reflect_core::RuntimeValue::List(vec![#(#entries),*])
110            })
111        }
112        Fields::Unit => Ok(quote! {
113            reify_reflect_core::RuntimeValue::Unit
114        }),
115    }
116}
117
118fn enum_body(data: &DataEnum) -> syn::Result<TokenStream2> {
119    let mut variant_entries = Vec::with_capacity(data.variants.len());
120    for variant in &data.variants {
121        variant_entries.push(variant_entry(variant)?);
122    }
123    Ok(quote! {
124        reify_reflect_core::RuntimeValue::List(vec![#(#variant_entries),*])
125    })
126}
127
128fn variant_entry(variant: &Variant) -> syn::Result<TokenStream2> {
129    let name_str = variant.ident.to_string();
130    let name_lit = name_bytes_literal(&name_str);
131
132    let payload = match &variant.fields {
133        Fields::Unit => quote! { reify_reflect_core::RuntimeValue::Unit },
134        Fields::Named(named) => {
135            let entries = named_field_entries(&named.named)?;
136            quote! {
137                reify_reflect_core::RuntimeValue::List(vec![#(#entries),*])
138            }
139        }
140        Fields::Unnamed(unnamed) => {
141            let entries = positional_field_entries(&unnamed.unnamed)?;
142            quote! {
143                reify_reflect_core::RuntimeValue::List(vec![#(#entries),*])
144            }
145        }
146    };
147
148    Ok(quote! {
149        reify_reflect_core::RuntimeValue::List(vec![
150            #name_lit,
151            #payload,
152        ])
153    })
154}
155
156fn named_field_entries(
157    fields: &syn::punctuated::Punctuated<Field, syn::Token![,]>,
158) -> syn::Result<Vec<TokenStream2>> {
159    let mut entries = Vec::new();
160    for field in fields {
161        if has_skip_attr(field)? {
162            continue;
163        }
164        let name_str = field
165            .ident
166            .as_ref()
167            .expect("named field must have ident")
168            .to_string();
169        let name_lit = name_bytes_literal(&name_str);
170        let ty = &field.ty;
171        entries.push(quote! {
172            reify_reflect_core::RuntimeValue::List(vec![
173                #name_lit,
174                <#ty as reify_reflect_core::Reflect>::reflect(),
175            ])
176        });
177    }
178    Ok(entries)
179}
180
181fn positional_field_entries(
182    fields: &syn::punctuated::Punctuated<Field, syn::Token![,]>,
183) -> syn::Result<Vec<TokenStream2>> {
184    let mut entries = Vec::new();
185    for field in fields {
186        if has_skip_attr(field)? {
187            continue;
188        }
189        let ty = &field.ty;
190        entries.push(quote! {
191            <#ty as reify_reflect_core::Reflect>::reflect()
192        });
193    }
194    Ok(entries)
195}
196
197fn name_bytes_literal(s: &str) -> TokenStream2 {
198    quote! {
199        reify_reflect_core::RuntimeValue::List(
200            #s.bytes()
201                .map(|b| reify_reflect_core::RuntimeValue::Nat(b as u64))
202                .collect()
203        )
204    }
205}
206
207fn has_skip_attr(field: &Field) -> syn::Result<bool> {
208    for attr in &field.attrs {
209        if attr.path().is_ident("reflect") {
210            let mut skip = false;
211            attr.parse_nested_meta(|meta| {
212                if meta.path.is_ident("skip") {
213                    skip = true;
214                    Ok(())
215                } else {
216                    Err(meta.error("expected `skip`"))
217                }
218            })?;
219            if skip {
220                return Ok(true);
221            }
222        }
223    }
224    Ok(false)
225}