1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 use proc_macro2::{Ident, Span, TokenStream};
6 use quote::{format_ident, quote, ToTokens};
7 use std::path::{Path as StdPath, PathBuf};
8 use syn::{
9     ext::IdentExt,
10     parse::{Parse, ParseStream},
11     Attribute, Expr, Lit, Token,
12 };
13 
manifest_path() -> Result<PathBuf, String>14 pub fn manifest_path() -> Result<PathBuf, String> {
15     let manifest_dir =
16         std::env::var_os("CARGO_MANIFEST_DIR").ok_or("`CARGO_MANIFEST_DIR` is not set")?;
17 
18     Ok(StdPath::new(&manifest_dir).join("Cargo.toml"))
19 }
20 
21 #[cfg(soong)]
mod_path() -> syn::Result<String>22 pub fn mod_path() -> syn::Result<String> {
23     Ok(std::env::var("CARGO_CRATE_NAME")
24         .expect("`CARGO_CRATE_NAME` should be set when building with Soong"))
25 }
26 
27 #[cfg(all(not(soong), not(feature = "nightly")))]
mod_path() -> syn::Result<String>28 pub fn mod_path() -> syn::Result<String> {
29     // Without the nightly feature and TokenStream::expand_expr, just return the crate name
30 
31     use fs_err as fs;
32     use once_cell::sync::Lazy;
33     use serde::Deserialize;
34 
35     #[derive(Deserialize)]
36     struct CargoToml {
37         package: Package,
38         #[serde(default)]
39         lib: Lib,
40     }
41 
42     #[derive(Deserialize)]
43     struct Package {
44         name: String,
45     }
46 
47     #[derive(Default, Deserialize)]
48     struct Lib {
49         name: Option<String>,
50     }
51 
52     static LIB_CRATE_MOD_PATH: Lazy<Result<String, String>> = Lazy::new(|| {
53         let file = manifest_path()?;
54         let cargo_toml_bytes = fs::read(file).map_err(|e| e.to_string())?;
55         let cargo_toml_string = String::from_utf8(cargo_toml_bytes)
56             .map_err(|e| format!("Could not UTF-8 decode `Cargo.toml`: {e}"))?;
57         let cargo_toml = toml::from_str::<CargoToml>(&cargo_toml_string)
58             .map_err(|e| format!("Failed to parse `Cargo.toml`: {e}"))?;
59 
60         let lib_crate_name = cargo_toml
61             .lib
62             .name
63             .unwrap_or_else(|| cargo_toml.package.name.replace('-', "_"));
64 
65         Ok(lib_crate_name)
66     });
67 
68     LIB_CRATE_MOD_PATH
69         .clone()
70         .map_err(|e| syn::Error::new(Span::call_site(), e))
71 }
72 
73 #[cfg(all(not(soong), feature = "nightly"))]
mod_path() -> syn::Result<String>74 pub fn mod_path() -> syn::Result<String> {
75     use proc_macro::TokenStream;
76 
77     let module_path_invoc = TokenStream::from(quote! { ::core::module_path!() });
78     // We ask the compiler what `module_path!()` expands to here.
79     // This is a nightly feature, tracked at https://github.com/rust-lang/rust/issues/90765
80     let expanded_module_path = TokenStream::expand_expr(&module_path_invoc)
81         .map_err(|e| syn::Error::new(Span::call_site(), e))?;
82     Ok(syn::parse::<syn::LitStr>(expanded_module_path)?.value())
83 }
84 
try_read_field(f: &syn::Field) -> TokenStream85 pub fn try_read_field(f: &syn::Field) -> TokenStream {
86     let ident = &f.ident;
87     let ty = &f.ty;
88 
89     match ident {
90         Some(ident) => quote! {
91             #ident: <#ty as ::uniffi::Lift<crate::UniFfiTag>>::try_read(buf)?,
92         },
93         None => quote! {
94             <#ty as ::uniffi::Lift<crate::UniFfiTag>>::try_read(buf)?,
95         },
96     }
97 }
98 
ident_to_string(ident: &Ident) -> String99 pub fn ident_to_string(ident: &Ident) -> String {
100     ident.unraw().to_string()
101 }
102 
crate_name() -> String103 pub fn crate_name() -> String {
104     std::env::var("CARGO_CRATE_NAME").unwrap().replace('-', "_")
105 }
106 
create_metadata_items( kind: &str, name: &str, metadata_expr: TokenStream, checksum_fn_name: Option<String>, ) -> TokenStream107 pub fn create_metadata_items(
108     kind: &str,
109     name: &str,
110     metadata_expr: TokenStream,
111     checksum_fn_name: Option<String>,
112 ) -> TokenStream {
113     let crate_name = crate_name();
114     let crate_name_upper = crate_name.to_uppercase();
115     let kind_upper = kind.to_uppercase();
116     let name_upper = name.to_uppercase();
117     let const_ident =
118         format_ident!("UNIFFI_META_CONST_{crate_name_upper}_{kind_upper}_{name_upper}");
119     let static_ident = format_ident!("UNIFFI_META_{crate_name_upper}_{kind_upper}_{name_upper}");
120 
121     let checksum_fn = checksum_fn_name.map(|name| {
122         let ident = Ident::new(&name, Span::call_site());
123         quote! {
124             #[doc(hidden)]
125             #[no_mangle]
126             pub extern "C" fn #ident() -> u16 {
127                 #const_ident.checksum()
128             }
129         }
130     });
131 
132     quote! {
133         const #const_ident: ::uniffi::MetadataBuffer = #metadata_expr;
134         #[no_mangle]
135         #[doc(hidden)]
136         pub static #static_ident: [u8; #const_ident.size] = #const_ident.into_array();
137 
138         #checksum_fn
139     }
140 }
141 
try_metadata_value_from_usize(value: usize, error_message: &str) -> syn::Result<u8>142 pub fn try_metadata_value_from_usize(value: usize, error_message: &str) -> syn::Result<u8> {
143     value
144         .try_into()
145         .map_err(|_| syn::Error::new(Span::call_site(), error_message))
146 }
147 
chain<T>( a: impl IntoIterator<Item = T>, b: impl IntoIterator<Item = T>, ) -> impl Iterator<Item = T>148 pub fn chain<T>(
149     a: impl IntoIterator<Item = T>,
150     b: impl IntoIterator<Item = T>,
151 ) -> impl Iterator<Item = T> {
152     a.into_iter().chain(b)
153 }
154 
155 pub trait UniffiAttributeArgs: Default {
parse_one(input: ParseStream<'_>) -> syn::Result<Self>156     fn parse_one(input: ParseStream<'_>) -> syn::Result<Self>;
merge(self, other: Self) -> syn::Result<Self>157     fn merge(self, other: Self) -> syn::Result<Self>;
158 }
159 
parse_comma_separated<T: UniffiAttributeArgs>(input: ParseStream<'_>) -> syn::Result<T>160 pub fn parse_comma_separated<T: UniffiAttributeArgs>(input: ParseStream<'_>) -> syn::Result<T> {
161     let punctuated = input.parse_terminated(T::parse_one, Token![,])?;
162     punctuated.into_iter().try_fold(T::default(), T::merge)
163 }
164 
165 #[derive(Default)]
166 struct ArgumentNotAllowedHere;
167 
168 impl UniffiAttributeArgs for ArgumentNotAllowedHere {
parse_one(input: ParseStream<'_>) -> syn::Result<Self>169     fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
170         Err(syn::Error::new(
171             input.span(),
172             "attribute arguments are not currently recognized in this position",
173         ))
174     }
175 
merge(self, _other: Self) -> syn::Result<Self>176     fn merge(self, _other: Self) -> syn::Result<Self> {
177         Ok(Self)
178     }
179 }
180 
181 pub trait AttributeSliceExt {
parse_uniffi_attr_args<T: UniffiAttributeArgs>(&self) -> syn::Result<T>182     fn parse_uniffi_attr_args<T: UniffiAttributeArgs>(&self) -> syn::Result<T>;
uniffi_attr_args_not_allowed_here(&self) -> Option<syn::Error>183     fn uniffi_attr_args_not_allowed_here(&self) -> Option<syn::Error> {
184         self.parse_uniffi_attr_args::<ArgumentNotAllowedHere>()
185             .err()
186     }
187 }
188 
189 impl AttributeSliceExt for [Attribute] {
parse_uniffi_attr_args<T: UniffiAttributeArgs>(&self) -> syn::Result<T>190     fn parse_uniffi_attr_args<T: UniffiAttributeArgs>(&self) -> syn::Result<T> {
191         self.iter()
192             .filter(|attr| attr.path().is_ident("uniffi"))
193             .try_fold(T::default(), |res, attr| {
194                 let parsed = attr.parse_args_with(parse_comma_separated)?;
195                 res.merge(parsed)
196             })
197     }
198 }
199 
either_attribute_arg<T: ToTokens>(a: Option<T>, b: Option<T>) -> syn::Result<Option<T>>200 pub fn either_attribute_arg<T: ToTokens>(a: Option<T>, b: Option<T>) -> syn::Result<Option<T>> {
201     match (a, b) {
202         (None, None) => Ok(None),
203         (Some(val), None) | (None, Some(val)) => Ok(Some(val)),
204         (Some(a), Some(b)) => {
205             let mut error = syn::Error::new_spanned(a, "redundant attribute argument");
206             error.combine(syn::Error::new_spanned(b, "note: first one here"));
207             Err(error)
208         }
209     }
210 }
211 
tagged_impl_header( trait_name: &str, ident: &impl ToTokens, udl_mode: bool, ) -> TokenStream212 pub(crate) fn tagged_impl_header(
213     trait_name: &str,
214     ident: &impl ToTokens,
215     udl_mode: bool,
216 ) -> TokenStream {
217     let trait_name = Ident::new(trait_name, Span::call_site());
218     if udl_mode {
219         quote! { impl ::uniffi::#trait_name<crate::UniFfiTag> for #ident }
220     } else {
221         quote! { impl<T> ::uniffi::#trait_name<T> for #ident }
222     }
223 }
224 
derive_all_ffi_traits(ty: &Ident, udl_mode: bool) -> TokenStream225 pub(crate) fn derive_all_ffi_traits(ty: &Ident, udl_mode: bool) -> TokenStream {
226     if udl_mode {
227         quote! { ::uniffi::derive_ffi_traits!(local #ty); }
228     } else {
229         quote! { ::uniffi::derive_ffi_traits!(blanket #ty); }
230     }
231 }
232 
derive_ffi_traits( ty: impl ToTokens, udl_mode: bool, trait_names: &[&str], ) -> TokenStream233 pub(crate) fn derive_ffi_traits(
234     ty: impl ToTokens,
235     udl_mode: bool,
236     trait_names: &[&str],
237 ) -> TokenStream {
238     let trait_idents = trait_names
239         .iter()
240         .map(|name| Ident::new(name, Span::call_site()));
241     if udl_mode {
242         quote! {
243             #(
244                 ::uniffi::derive_ffi_traits!(impl #trait_idents<crate::UniFfiTag> for #ty);
245             )*
246         }
247     } else {
248         quote! {
249             #(
250                 ::uniffi::derive_ffi_traits!(impl<UT> #trait_idents<UT> for #ty);
251             )*
252         }
253     }
254 }
255 
256 /// Custom keywords
257 pub mod kw {
258     syn::custom_keyword!(async_runtime);
259     syn::custom_keyword!(callback_interface);
260     syn::custom_keyword!(with_foreign);
261     syn::custom_keyword!(default);
262     syn::custom_keyword!(flat_error);
263     syn::custom_keyword!(None);
264     syn::custom_keyword!(Some);
265     syn::custom_keyword!(with_try_read);
266     syn::custom_keyword!(name);
267     syn::custom_keyword!(non_exhaustive);
268     syn::custom_keyword!(Debug);
269     syn::custom_keyword!(Display);
270     syn::custom_keyword!(Eq);
271     syn::custom_keyword!(Hash);
272     // Not used anymore
273     syn::custom_keyword!(handle_unknown_callback_error);
274 }
275 
276 /// Specifies a type from a dependent crate
277 pub struct ExternalTypeItem {
278     pub crate_ident: Ident,
279     pub sep: Token![,],
280     pub type_ident: Ident,
281 }
282 
283 impl Parse for ExternalTypeItem {
parse(input: ParseStream<'_>) -> syn::Result<Self>284     fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
285         Ok(Self {
286             crate_ident: input.parse()?,
287             sep: input.parse()?,
288             type_ident: input.parse()?,
289         })
290     }
291 }
292 
extract_docstring(attrs: &[Attribute]) -> syn::Result<String>293 pub(crate) fn extract_docstring(attrs: &[Attribute]) -> syn::Result<String> {
294     return attrs
295         .iter()
296         .filter(|attr| attr.path().is_ident("doc"))
297         .map(|attr| {
298             let name_value = attr.meta.require_name_value()?;
299             if let Expr::Lit(expr) = &name_value.value {
300                 if let Lit::Str(lit_str) = &expr.lit {
301                     return Ok(lit_str.value().trim().to_owned());
302                 }
303             }
304             Err(syn::Error::new_spanned(attr, "Cannot parse doc attribute"))
305         })
306         .collect::<syn::Result<Vec<_>>>()
307         .map(|lines| lines.join("\n"));
308 }
309