Skip to main content

reflect_derive/
lib.rs

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