1 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
2 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
3 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
4 | |
5 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
6 | |
7 | #![doc = include_str!("README.md" )] |
8 | #![doc (html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg" )] |
9 | |
10 | extern crate proc_macro; |
11 | use proc_macro::TokenStream; |
12 | use quote::quote; |
13 | |
14 | mod slint_doc; |
15 | |
16 | /// This derive macro is used with structures in the run-time library that are meant |
17 | /// to be exposed to the language. The structure is introspected for properties and fields |
18 | /// marked with the `rtti_field` attribute and generates run-time type information for use |
19 | /// with the interpreter. |
20 | /// In addition all `Property<T> foo` fields get a convenient getter function generated |
21 | /// that works on a `Pin<&Self>` receiver. |
22 | #[proc_macro_derive (SlintElement, attributes(rtti_field))] |
23 | pub 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 | |
131 | fn normalize_identifier(name: &syn::Ident) -> String { |
132 | name.to_string().replace(from:'_' , to:"-" ) |
133 | } |
134 | |
135 | // Try to match `Property<Foo>` on the syn tree and return Foo if found |
136 | fn property_type(ty: &syn::Type) -> Option<&syn::Type> { |
137 | if let syn::Type::Path(syn::TypePath { path: syn::Path { segments: &Punctuated, .. }, .. }) = ty { |
138 | if let Some(syn::PathSegment { |
139 | ident: &Ident, |
140 | arguments: |
141 | syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { args: &Punctuated, .. }), |
142 | }) = segments.first() |
143 | { |
144 | match args.first() { |
145 | Some(syn::GenericArgument::Type(property_type: &Type)) if *ident == "Property" => { |
146 | return Some(property_type) |
147 | } |
148 | _ => {} |
149 | } |
150 | } |
151 | } |
152 | None |
153 | } |
154 | |
155 | // Try to match `Callback<Args, Ret>` on the syn tree and return Args and Ret if found |
156 | fn 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 | /// An attribute macro that simply return its input and ignore any arguments |
183 | #[proc_macro_attribute ] |
184 | pub fn identity (_attr: TokenStream, item: TokenStream) -> TokenStream { |
185 | item |
186 | } |
187 | |
188 | /// To be applied on any item that has documentation comment, it will convert link to `slint:Foo` to the link from the |
189 | /// documentation map from link-data.json |
190 | #[proc_macro_attribute ] |
191 | pub fn slint_doc (_attr: TokenStream, item: TokenStream) -> TokenStream { |
192 | use syn::visit_mut::VisitMut; |
193 | let mut visitor: Visitor = slint_doc::Visitor::new(); |
194 | let mut item: 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 | /// Same as `slint_doc` but for string literals instead of doc comments (useful for crate level documentation that cannot have an attribute) |
201 | #[proc_macro ] |
202 | pub fn slint_doc_str(input: TokenStream) -> TokenStream { |
203 | let input: LitStr = syn::parse_macro_input!(input as syn::LitStr); |
204 | let mut doc: String = input.value(); |
205 | let mut visitor: 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 | |