| 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 | |