sixtyfps_corelib_macros/
lib.rs

1// Copyright © SixtyFPS GmbH <info@sixtyfps.io>
2// SPDX-License-Identifier: (GPL-3.0-only OR LicenseRef-SixtyFPS-commercial)
3
4/*!
5    This crate contains the internal procedural macros
6    used by the sixtyfps-corelib crate
7
8**NOTE**: This library is an **internal** crate for the [SixtyFPS project](https://sixtyfps.io).
9This crate should **not be used directly** by applications using SixtyFPS.
10You should use the `sixtyfps` crate instead.
11
12**WARNING**: This crate does not follow the semver convention for versioning and can
13only be used with `version = "=x.y.z"` in Cargo.toml.
14
15*/
16
17extern crate proc_macro;
18use proc_macro::TokenStream;
19use quote::quote;
20
21/// This derive macro is used with structures in the run-time library that are meant
22/// to be exposed to the language. The structure is introspected for properties and fields
23/// marked with the `rtti_field` attribute and generates run-time type information for use
24/// with the interpreter.
25/// In addition all `Property<T> foo` fields get a convenient getter function generated
26/// that works on a `Pin<&Self>` receiver.
27#[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
146// Try to match `Property<Foo>` on the syn tree and return Foo if found
147fn 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
166// Try to match `Callback<Args, Ret>` on the syn tree and return Args and Ret if found
167fn 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}