sixtyfps_corelib_macros/
lib.rs1extern crate proc_macro;
18use proc_macro::TokenStream;
19use quote::quote;
20
21#[proc_macro_derive(SixtyFPSElement, attributes(rtti_field))]
28pub fn sixtyfps_element(input: TokenStream) -> TokenStream {
29 let input = syn::parse_macro_input!(input as syn::DeriveInput);
30
31 let fields = match &input.data {
32 syn::Data::Struct(syn::DataStruct { fields: f @ syn::Fields::Named(..), .. }) => f,
33 _ => {
34 return syn::Error::new(
35 input.ident.span(),
36 "Only `struct` with named field are supported",
37 )
38 .to_compile_error()
39 .into()
40 }
41 };
42
43 let mut pub_prop_field_names = Vec::new();
44 let mut pub_prop_field_names_normalized = Vec::new();
45 let mut pub_prop_field_types = Vec::new();
46 let mut property_names = Vec::new();
47 let mut property_visibility = Vec::new();
48 let mut property_types = Vec::new();
49 for field in fields {
50 if let Some(property_type) = property_type(&field.ty) {
51 let name = field.ident.as_ref().unwrap();
52 if matches!(field.vis, syn::Visibility::Public(_)) {
53 pub_prop_field_names_normalized.push(normalize_identifier(name));
54 pub_prop_field_names.push(name);
55 pub_prop_field_types.push(&field.ty);
56 }
57 property_names.push(name);
58 property_visibility.push(field.vis.clone());
59 property_types.push(property_type);
60 }
61 }
62
63 let (plain_field_names, plain_field_types): (Vec<_>, Vec<_>) = fields
64 .iter()
65 .filter(|f| {
66 f.attrs.iter().any(|attr| {
67 attr.parse_meta()
68 .ok()
69 .map(|meta| match meta {
70 syn::Meta::Path(path) => {
71 path.get_ident().map(|ident| *ident == "rtti_field").unwrap_or(false)
72 }
73 _ => false,
74 })
75 .unwrap_or(false)
76 })
77 })
78 .map(|f| (f.ident.as_ref().unwrap(), &f.ty))
79 .unzip();
80 let plain_field_names_normalized =
81 plain_field_names.iter().map(|f| normalize_identifier(*f)).collect::<Vec<_>>();
82
83 let mut callback_field_names = Vec::new();
84 let mut callback_field_names_normalized = Vec::new();
85 let mut callback_args = Vec::new();
86 let mut callback_rets = Vec::new();
87 for field in fields {
88 if let Some((arg, ret)) = callback_arg(&field.ty) {
89 if matches!(field.vis, syn::Visibility::Public(_)) {
90 let name = field.ident.as_ref().unwrap();
91 callback_field_names_normalized.push(normalize_identifier(name));
92 callback_field_names.push(name);
93 callback_args.push(arg);
94 callback_rets.push(ret);
95 }
96 }
97 }
98
99 let item_name = &input.ident;
100
101 quote!(
102 #[allow(clippy::nonstandard_macro_braces)]
103 #[cfg(feature = "rtti")]
104 impl BuiltinItem for #item_name {
105 fn name() -> &'static str {
106 stringify!(#item_name)
107 }
108 fn properties<Value: ValueType>() -> ::alloc::vec::Vec<(&'static str, &'static dyn PropertyInfo<Self, Value>)> {
109 ::alloc::vec![#( {
110 const O : MaybeAnimatedPropertyInfoWrapper<#item_name, #pub_prop_field_types> =
111 MaybeAnimatedPropertyInfoWrapper(#item_name::FIELD_OFFSETS.#pub_prop_field_names);
112 (#pub_prop_field_names_normalized, (&O).as_property_info())
113 } ),*]
114 }
115 fn fields<Value: ValueType>() -> ::alloc::vec::Vec<(&'static str, &'static dyn FieldInfo<Self, Value>)> {
116 ::alloc::vec![#( {
117 const O : const_field_offset::FieldOffset<#item_name, #plain_field_types, const_field_offset::AllowPin> =
118 #item_name::FIELD_OFFSETS.#plain_field_names;
119 (#plain_field_names_normalized, &O as &'static dyn FieldInfo<Self, Value>)
120 } ),*]
121 }
122 fn callbacks<Value: ValueType>() -> ::alloc::vec::Vec<(&'static str, &'static dyn CallbackInfo<Self, Value>)> {
123 ::alloc::vec![#( {
124 const O : const_field_offset::FieldOffset<#item_name, Callback<#callback_args, #callback_rets>, const_field_offset::AllowPin> =
125 #item_name::FIELD_OFFSETS.#callback_field_names;
126 (#callback_field_names_normalized, &O as &'static dyn CallbackInfo<Self, Value>)
127 } ),*]
128 }
129 }
130
131 impl #item_name {
132 #(
133 #property_visibility fn #property_names(self: core::pin::Pin<&Self>) -> #property_types {
134 Self::FIELD_OFFSETS.#property_names.apply_pin(self).get()
135 }
136 )*
137 }
138 )
139 .into()
140}
141
142fn normalize_identifier(name: &syn::Ident) -> String {
143 name.to_string().replace('_', "-")
144}
145
146fn property_type(ty: &syn::Type) -> Option<&syn::Type> {
148 if let syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. }) = ty {
149 if let Some(syn::PathSegment {
150 ident,
151 arguments:
152 syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { args, .. }),
153 }) = segments.first()
154 {
155 match args.first() {
156 Some(syn::GenericArgument::Type(property_type)) if *ident == "Property" => {
157 return Some(property_type)
158 }
159 _ => {}
160 }
161 }
162 }
163 None
164}
165
166fn callback_arg(ty: &syn::Type) -> Option<(&syn::Type, Option<&syn::Type>)> {
168 if let syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. }) = ty {
169 if let Some(syn::PathSegment {
170 ident,
171 arguments:
172 syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { args, .. }),
173 }) = segments.first()
174 {
175 if ident != "Callback" {
176 return None;
177 }
178 let mut it = args.iter();
179 let first = match it.next() {
180 Some(syn::GenericArgument::Type(ty)) => ty,
181 _ => return None,
182 };
183 let sec = match it.next() {
184 Some(syn::GenericArgument::Type(ty)) => Some(ty),
185 _ => None,
186 };
187 return Some((first, sec));
188 }
189 }
190 None
191}