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