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
10extern crate proc_macro;
11use proc_macro::TokenStream;
12use quote::quote;
13
14mod 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))]
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(from:'_', to:"-")
133}
134
135// Try to match `Property<Foo>` on the syn tree and return Foo if found
136fn 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
156fn 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]
184pub 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]
191pub 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]
202pub 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