Skip to main content

aphyn_reflection_macros/
lib.rs

1//! # aphyn_reflection_macros
2//!
3//! Derive macro that gives a type a compile-time description of its own shape,
4//! so the engine's reflection system can inspect fields and variants without
5//! hand-written boilerplate.
6//!
7//! # How to use
8//!
9//! This crate is not used directly — it is re-exported by `aphyn_reflection`,
10//! which also supplies the [`ReflectTy`] trait and the `ReflTy`, `ReflTyStruct`,
11//! `ReflTyId`, and `ReflTyEnum` types the generated code refers to. Use it
12//! through that crate.
13//!
14//! #### Describing a struct
15//!
16//! Derive [`ReflectTy`] on a struct with named fields to obtain a `refl_ty()`
17//! that returns the struct's fields as `(name, type id)` pairs.
18//!
19//! ```ignore
20//! use aphyn_reflection::{ReflectTy, ReflTy, ReflTyStruct};
21//!
22//! #[derive(ReflectTy)]
23//! struct Player {
24//!     health: u8,
25//!     name: String,
26//! }
27//!
28//! // The derive implements `ReflectTy`, so the shape is available at runtime.
29//! let ReflTy::Struct(ReflTyStruct::Named(fields)) = Player::refl_ty() else {
30//!     unreachable!()
31//! };
32//! assert_eq!(fields[0].0, "health");
33//! assert_eq!(fields[1].0, "name");
34//! ```
35//!
36//! # How to maintain
37//!
38//! The single entry point is [`derive_reflect_ty`], the `ReflectTy` derive. It
39//! does **not** use `quote!` to assemble its output: it writes the `impl` body
40//! into a `String` with `write!` and then `parse()`s that string back into a
41//! `TokenStream`. Every emitted fragment is therefore plain Rust source text,
42//! which is why the generated code names the `aphyn_reflection` types by bare
43//! identifier and relies on them being in scope at the use site (this crate has
44//! no dependency on `aphyn_reflection`, so those names cannot be intra-doc
45//! linked from here).
46//!
47//! Generics are reproduced verbatim: the full generic clause becomes the
48//! `impl<..>` header, while type and const parameter idents are collected into
49//! the `Type<..>` reference. A non-generic type produces an empty `Type<>`,
50//! which is valid Rust. Lifetime parameters are rejected with a panic
51//! (`"Non-static types are not supported"`), matching the `'static` bound on the
52//! [`ReflectTy`] trait. Unions panic as well.
53//!
54//! The struct branch always emits a `ReflTyStruct::Named` list, pairing each
55//! field's identifier with `ReflTyId::of::<FieldType>()`. It does not branch on
56//! the field kind, so only structs with named fields are fully supported; tuple
57//! and unit structs are not handled. The enum branch reproduces each variant
58//! name and maps unit variants to `ReflTyStruct::Unit`; the named- and
59//! tuple-variant arms are still incomplete and should be revisited before
60//! relying on enums with fields. `ReflTyId::of` itself currently derives its id
61//! from `std::any::type_name`, so type identity is only as stable as that name.
62
63use proc_macro::TokenStream;
64use quote::ToTokens;
65use std::fmt::Write;
66use syn::DeriveInput;
67
68#[proc_macro_derive(ReflectTy)]
69pub fn derive_reflect_ty(stream: TokenStream) -> TokenStream {
70    let input = syn::parse_macro_input!(stream as DeriveInput);
71
72    let mut result = String::new();
73
74    let impl_generics = input.generics.to_token_stream().to_string();
75    let mut type_generics = String::new();
76
77    for param in input.generics.params {
78        match param {
79            syn::GenericParam::Lifetime(_) => panic!("Non-static types are not supported"),
80            syn::GenericParam::Type(type_param) => write!(type_generics, "{},", type_param.ident.to_string()).unwrap(),
81            syn::GenericParam::Const(const_param) => write!(type_generics, "{},", const_param.ident.to_string()).unwrap(),
82        }
83    }
84
85    let type_name = input.ident.to_string();
86
87    write!(
88        result,
89        r#"impl{impl_generics} ReflectTy for {type_name}<{type_generics}> {{ "#
90    )
91    .unwrap();
92    write!(result, r#"fn refl_ty() -> ReflTy {{ "#).unwrap();
93
94    match input.data {
95        syn::Data::Struct(data_struct) => {
96            result.push_str("ReflTy::Struct(ReflTyStruct::Named(vec![");
97
98            for field in data_struct.fields {
99                let field_name = field.ident.to_token_stream().to_string();
100                let field_type = field.ty.to_token_stream().to_string();
101
102                write!(result, r#"(String::from("{field_name}"), ReflTyId::of::<{field_type}>()),"#).unwrap();
103            }
104
105            result.push_str(" ]))");
106        }
107        syn::Data::Enum(data_enum) => {
108            result.push_str("ReflTy::Enum(ReflTyEnum { variants: vec![ ");
109
110            for variant in data_enum.variants {
111                let variant_name = variant.ident.to_string();
112
113                write!(result, r#"(String::from("{variant_name}"), "#).unwrap();
114
115                match variant.fields {
116                    syn::Fields::Named(fields_named) => {
117                        write!(result, r#"ReflTyStruct::Named(vec!["#).unwrap();
118
119                        for field in fields_named.named {
120                            let field_type = field.ty.to_token_stream().to_string();
121
122                            write!(result, r#"ReflTyId::of::<{field_type}>(),"#).unwrap();
123                        }
124
125                        write!(result, r#"])"#).unwrap();
126                    }
127                    syn::Fields::Unnamed(fields_unnamed) => {
128                        write!(result, r#"ReflTyStruct::Tuple(vec!["#).unwrap();
129
130                        for field in fields_unnamed.unnamed {
131                            let field_name = field.ident.to_token_stream().to_string();
132                            let field_type = field.ty.to_token_stream().to_string();
133
134                            write!(result, r#"(String::from("{field_name}"), ReflTyId::of::<{field_type}>()),"#).unwrap();
135                        }
136
137                        write!(result, r#"])"#).unwrap();
138                    }
139                    syn::Fields::Unit => write!(result, r#"ReflTyStruct::Unit"#).unwrap(),
140                }
141
142                write!(result, r#" ),"#).unwrap();
143            }
144        }
145        syn::Data::Union(_) => panic!("Union types are not supported."),
146    }
147
148    result.push_str(" } ");
149    result.push_str(" } ");
150
151    result.parse().unwrap()
152}