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}