i_slint_core_macros/
lib.rs1#![doc = include_str!("README.md")]
8#![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")]
9
10extern crate proc_macro;
11use proc_macro::TokenStream;
12use quote::quote;
13
14mod slint_doc;
15
16#[proc_macro_derive(SlintElement, attributes(rtti_field))]
23pub fn slint_element(input: TokenStream) -> TokenStream {
24 let input = syn::parse_macro_input!(input as syn::DeriveInput);
25
26 let fields = match &input.data {
27 syn::Data::Struct(syn::DataStruct { fields: f @ syn::Fields::Named(..), .. }) => f,
28 _ => {
29 return syn::Error::new(
30 input.ident.span(),
31 "Only `struct` with named field are supported",
32 )
33 .to_compile_error()
34 .into()
35 }
36 };
37
38 let mut pub_prop_field_names = Vec::new();
39 let mut pub_prop_field_names_normalized = Vec::new();
40 let mut pub_prop_field_types = Vec::new();
41 let mut property_names = Vec::new();
42 let mut property_visibility = Vec::new();
43 let mut property_types = Vec::new();
44
45 for field in fields {
46 if let Some(property_type) = property_type(&field.ty) {
47 let name = field.ident.as_ref().unwrap();
48 if matches!(field.vis, syn::Visibility::Public(_)) {
49 pub_prop_field_names_normalized.push(normalize_identifier(name));
50 pub_prop_field_names.push(name);
51 pub_prop_field_types.push(&field.ty);
52 }
53
54 property_names.push(name);
55 property_visibility.push(field.vis.clone());
56 property_types.push(property_type);
57 }
58 }
59
60 let (plain_field_names, plain_field_types): (Vec<_>, Vec<_>) = fields
61 .iter()
62 .filter(|f| {
63 f.attrs.iter().any(|attr| {
64 matches!(&attr.meta, syn::Meta::Path(path) if path.get_ident().map(|ident| *ident == "rtti_field").unwrap_or(false))
65 })
66 })
67 .map(|f| (f.ident.as_ref().unwrap(), &f.ty))
68 .unzip();
69 let plain_field_names_normalized =
70 plain_field_names.iter().map(|f| normalize_identifier(f)).collect::<Vec<_>>();
71
72 let mut callback_field_names = Vec::new();
73 let mut callback_field_names_normalized = Vec::new();
74 let mut callback_args = Vec::new();
75 let mut callback_rets = Vec::new();
76 for field in fields {
77 if let Some((arg, ret)) = callback_arg(&field.ty) {
78 if matches!(field.vis, syn::Visibility::Public(_)) {
79 let name = field.ident.as_ref().unwrap();
80 callback_field_names_normalized.push(normalize_identifier(name));
81 callback_field_names.push(name);
82 callback_args.push(arg);
83 callback_rets.push(ret);
84 }
85 }
86 }
87
88 let item_name = &input.ident;
89
90 quote!(
91 #[allow(clippy::nonstandard_macro_braces)]
92 #[cfg(feature = "rtti")]
93 impl BuiltinItem for #item_name {
94 fn name() -> &'static str {
95 stringify!(#item_name)
96 }
97 fn properties<Value: ValueType>() -> ::alloc::vec::Vec<(&'static str, &'static dyn PropertyInfo<Self, Value>)> {
98 ::alloc::vec![#( {
99 const O : MaybeAnimatedPropertyInfoWrapper<#item_name, #pub_prop_field_types> =
100 MaybeAnimatedPropertyInfoWrapper(#item_name::FIELD_OFFSETS.#pub_prop_field_names);
101 (#pub_prop_field_names_normalized, (&O).as_property_info())
102 } ),*]
103 }
104 fn fields<Value: ValueType>() -> ::alloc::vec::Vec<(&'static str, &'static dyn FieldInfo<Self, Value>)> {
105 ::alloc::vec![#( {
106 const O : const_field_offset::FieldOffset<#item_name, #plain_field_types, const_field_offset::AllowPin> =
107 #item_name::FIELD_OFFSETS.#plain_field_names;
108 (#plain_field_names_normalized, &O as &'static dyn FieldInfo<Self, Value>)
109 } ),*]
110 }
111 fn callbacks<Value: ValueType>() -> ::alloc::vec::Vec<(&'static str, &'static dyn CallbackInfo<Self, Value>)> {
112 ::alloc::vec![#( {
113 const O : const_field_offset::FieldOffset<#item_name, Callback<#callback_args, #callback_rets>, const_field_offset::AllowPin> =
114 #item_name::FIELD_OFFSETS.#callback_field_names;
115 (#callback_field_names_normalized, &O as &'static dyn CallbackInfo<Self, Value>)
116 } ),*]
117 }
118 }
119
120 impl #item_name {
121 #(
122 #property_visibility fn #property_names(self: core::pin::Pin<&Self>) -> #property_types {
123 Self::FIELD_OFFSETS.#property_names.apply_pin(self).get()
124 }
125 )*
126 }
127 )
128 .into()
129}
130
131fn normalize_identifier(name: &syn::Ident) -> String {
132 name.to_string().replace('_', "-")
133}
134
135fn property_type(ty: &syn::Type) -> Option<&syn::Type> {
137 if let syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. }) = ty {
138 if let Some(syn::PathSegment {
139 ident,
140 arguments:
141 syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { args, .. }),
142 }) = segments.first()
143 {
144 match args.first() {
145 Some(syn::GenericArgument::Type(property_type)) if *ident == "Property" => {
146 return Some(property_type)
147 }
148 _ => {}
149 }
150 }
151 }
152 None
153}
154
155fn callback_arg(ty: &syn::Type) -> Option<(&syn::Type, Option<&syn::Type>)> {
157 if let syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. }) = ty {
158 if let Some(syn::PathSegment {
159 ident,
160 arguments:
161 syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { args, .. }),
162 }) = segments.first()
163 {
164 if ident != "Callback" {
165 return None;
166 }
167 let mut it = args.iter();
168 let first = match it.next() {
169 Some(syn::GenericArgument::Type(ty)) => ty,
170 _ => return None,
171 };
172 let sec = match it.next() {
173 Some(syn::GenericArgument::Type(ty)) => Some(ty),
174 _ => None,
175 };
176 return Some((first, sec));
177 }
178 }
179 None
180}
181
182#[proc_macro_attribute]
184pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
185 item
186}
187
188#[proc_macro_attribute]
191pub fn slint_doc(_attr: TokenStream, item: TokenStream) -> TokenStream {
192 use syn::visit_mut::VisitMut;
193 let mut visitor = slint_doc::Visitor::new();
194 let mut item = syn::parse_macro_input!(item as syn::Item);
195 visitor.visit_item_mut(&mut item);
196 assert!(visitor.1, "No slint link found");
197 quote!(#item).into()
198}
199
200#[proc_macro]
202pub fn slint_doc_str(input: TokenStream) -> TokenStream {
203 let input = syn::parse_macro_input!(input as syn::LitStr);
204 let mut doc = input.value();
205 let mut visitor = slint_doc::Visitor::new();
206 visitor.process_string(&mut doc);
207 assert!(visitor.1, "No slint link found");
208 quote!(#doc).into()
209}
210
211#[proc_macro_attribute]
218pub fn remove_extern(_attr: TokenStream, item: TokenStream) -> TokenStream {
219 let mut input = syn::parse_macro_input!(item as syn::Item);
220
221 match &mut input {
222 syn::Item::Fn(item_fn) => {
223 item_fn.sig.abi.take();
224 }
225 syn::Item::Struct(item_struct) => {
226 for f in item_struct.fields.iter_mut() {
227 if let syn::Type::BareFn(f) = &mut f.ty {
228 f.abi.take();
229 }
230 }
231 }
232 _ => (),
233 }
234
235 quote!(#input).into()
236}