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 #![cfg_attr(feature = "nightly", feature(proc_macro_expand))]
5 #![warn(rust_2018_idioms, unused_qualifications)]
6 
7 //! Macros for `uniffi`.
8 
9 #[cfg(feature = "trybuild")]
10 use camino::Utf8Path;
11 use proc_macro::TokenStream;
12 use quote::quote;
13 use syn::{
14     parse::{Parse, ParseStream},
15     parse_macro_input, Ident, LitStr, Path, Token,
16 };
17 
18 mod custom;
19 mod default;
20 mod enum_;
21 mod error;
22 mod export;
23 mod fnsig;
24 mod object;
25 mod record;
26 mod setup_scaffolding;
27 mod test;
28 mod util;
29 
30 use self::{
31     enum_::expand_enum, error::expand_error, export::expand_export, object::expand_object,
32     record::expand_record,
33 };
34 
35 struct CustomTypeInfo {
36     ident: Ident,
37     builtin: Path,
38 }
39 
40 impl Parse for CustomTypeInfo {
parse(input: ParseStream<'_>) -> syn::Result<Self>41     fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
42         let ident = input.parse()?;
43         input.parse::<Token![,]>()?;
44         let builtin = input.parse()?;
45         Ok(Self { ident, builtin })
46     }
47 }
48 
49 /// A macro to build testcases for a component's generated bindings.
50 ///
51 /// This macro provides some plumbing to write automated tests for the generated
52 /// foreign language bindings of a component. As a component author, you can write
53 /// script files in the target foreign language(s) that exercise you component API,
54 /// and then call this macro to produce a `cargo test` testcase from each one.
55 /// The generated code will execute your script file with appropriate configuration and
56 /// environment to let it load the component bindings, and will pass iff the script
57 /// exits successfully.
58 ///
59 /// To use it, invoke the macro with the name of a fixture/example crate as the first argument,
60 /// then one or more file paths relative to the crate root directory. It will produce one `#[test]`
61 /// function per file, in a manner designed to play nicely with `cargo test` and its test filtering
62 /// options.
63 #[proc_macro]
build_foreign_language_testcases(tokens: TokenStream) -> TokenStream64 pub fn build_foreign_language_testcases(tokens: TokenStream) -> TokenStream {
65     test::build_foreign_language_testcases(tokens)
66 }
67 
68 /// Top-level initialization macro
69 ///
70 /// The optional namespace argument is only used by the scaffolding templates to pass in the
71 /// CI namespace.
72 #[proc_macro]
setup_scaffolding(tokens: TokenStream) -> TokenStream73 pub fn setup_scaffolding(tokens: TokenStream) -> TokenStream {
74     let namespace = match syn::parse_macro_input!(tokens as Option<LitStr>) {
75         Some(lit_str) => lit_str.value(),
76         None => match util::mod_path() {
77             Ok(v) => v,
78             Err(e) => return e.into_compile_error().into(),
79         },
80     };
81     setup_scaffolding::setup_scaffolding(namespace)
82         .unwrap_or_else(syn::Error::into_compile_error)
83         .into()
84 }
85 
86 #[proc_macro_attribute]
export(attr_args: TokenStream, input: TokenStream) -> TokenStream87 pub fn export(attr_args: TokenStream, input: TokenStream) -> TokenStream {
88     do_export(attr_args, input, false)
89 }
90 
do_export(attr_args: TokenStream, input: TokenStream, udl_mode: bool) -> TokenStream91 fn do_export(attr_args: TokenStream, input: TokenStream, udl_mode: bool) -> TokenStream {
92     let copied_input = (!udl_mode).then(|| proc_macro2::TokenStream::from(input.clone()));
93 
94     let gen_output = || {
95         let item = syn::parse(input)?;
96         expand_export(item, attr_args, udl_mode)
97     };
98     let output = gen_output().unwrap_or_else(syn::Error::into_compile_error);
99 
100     quote! {
101         #copied_input
102         #output
103     }
104     .into()
105 }
106 
107 #[proc_macro_derive(Record, attributes(uniffi))]
derive_record(input: TokenStream) -> TokenStream108 pub fn derive_record(input: TokenStream) -> TokenStream {
109     expand_record(parse_macro_input!(input), false)
110         .unwrap_or_else(syn::Error::into_compile_error)
111         .into()
112 }
113 
114 #[proc_macro_derive(Enum)]
derive_enum(input: TokenStream) -> TokenStream115 pub fn derive_enum(input: TokenStream) -> TokenStream {
116     expand_enum(parse_macro_input!(input), None, false)
117         .unwrap_or_else(syn::Error::into_compile_error)
118         .into()
119 }
120 
121 #[proc_macro_derive(Object)]
derive_object(input: TokenStream) -> TokenStream122 pub fn derive_object(input: TokenStream) -> TokenStream {
123     expand_object(parse_macro_input!(input), false)
124         .unwrap_or_else(syn::Error::into_compile_error)
125         .into()
126 }
127 
128 #[proc_macro_derive(Error, attributes(uniffi))]
derive_error(input: TokenStream) -> TokenStream129 pub fn derive_error(input: TokenStream) -> TokenStream {
130     expand_error(parse_macro_input!(input), None, false)
131         .unwrap_or_else(syn::Error::into_compile_error)
132         .into()
133 }
134 
135 /// Generate the `FfiConverter` implementation for a Custom Type - ie,
136 /// for a `<T>` which implements `UniffiCustomTypeConverter`.
137 #[proc_macro]
custom_type(tokens: TokenStream) -> TokenStream138 pub fn custom_type(tokens: TokenStream) -> TokenStream {
139     let input: CustomTypeInfo = syn::parse_macro_input!(tokens);
140     custom::expand_ffi_converter_custom_type(&input.ident, &input.builtin, true)
141         .unwrap_or_else(syn::Error::into_compile_error)
142         .into()
143 }
144 
145 /// Generate the `FfiConverter` and the `UniffiCustomTypeConverter` implementations for a
146 /// Custom Type - ie, for a `<T>` which implements `UniffiCustomTypeConverter` via the
147 /// newtype idiom.
148 #[proc_macro]
custom_newtype(tokens: TokenStream) -> TokenStream149 pub fn custom_newtype(tokens: TokenStream) -> TokenStream {
150     let input: CustomTypeInfo = syn::parse_macro_input!(tokens);
151     custom::expand_ffi_converter_custom_newtype(&input.ident, &input.builtin, true)
152         .unwrap_or_else(syn::Error::into_compile_error)
153         .into()
154 }
155 
156 // == derive_for_udl and export_for_udl ==
157 //
158 // The Askama templates generate placeholder items wrapped with these attributes. The goal is to
159 // have all scaffolding generation go through the same code path.
160 //
161 // The one difference is that derive-style attributes are not allowed inside attribute macro
162 // inputs.  Instead, we take the attributes from the macro invocation itself.
163 //
164 // Instead of:
165 //
166 // ```
167 // #[derive(Error)
168 // #[uniffi(flat_error])
169 // enum { .. }
170 // ```
171 //
172 // We have:
173 //
174 // ```
175 // #[derive_error_for_udl(flat_error)]
176 // enum { ... }
177 //  ```
178 //
179 // # Differences between UDL-mode and normal mode
180 //
181 // ## Metadata symbols / checksum functions
182 //
183 // In UDL mode, we don't export the static metadata symbols or generate the checksum
184 // functions.  This could be changed, but there doesn't seem to be much benefit at this point.
185 //
186 // ## The FfiConverter<UT> parameter
187 //
188 // In UDL-mode, we only implement `FfiConverter` for the local tag (`FfiConverter<crate::UniFfiTag>`)
189 //
190 // The reason for this split is remote types, i.e. types defined in remote crates that we
191 // don't control and therefore can't define a blanket impl on because of the orphan rules.
192 //
193 // With UDL, we handle this by only implementing `FfiConverter<crate::UniFfiTag>` for the
194 // type.  This gets around the orphan rules since a local type is in the trait, but requires
195 // a `uniffi::ffi_converter_forward!` call if the type is used in a second local crate (an
196 // External typedef).  This is natural for UDL-based generation, since you always need to
197 // define the external type in the UDL file.
198 //
199 // With proc-macros this system isn't so natural.  Instead, we create a blanket implementation
200 // for all UT and support for remote types is still TODO.
201 
202 #[doc(hidden)]
203 #[proc_macro_attribute]
derive_record_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenStream204 pub fn derive_record_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenStream {
205     expand_record(syn::parse_macro_input!(input), true)
206         .unwrap_or_else(syn::Error::into_compile_error)
207         .into()
208 }
209 
210 #[doc(hidden)]
211 #[proc_macro_attribute]
derive_enum_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream212 pub fn derive_enum_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream {
213     expand_enum(
214         syn::parse_macro_input!(input),
215         Some(syn::parse_macro_input!(attrs)),
216         true,
217     )
218     .unwrap_or_else(syn::Error::into_compile_error)
219     .into()
220 }
221 
222 #[doc(hidden)]
223 #[proc_macro_attribute]
derive_error_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream224 pub fn derive_error_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream {
225     expand_error(
226         syn::parse_macro_input!(input),
227         Some(syn::parse_macro_input!(attrs)),
228         true,
229     )
230     .unwrap_or_else(syn::Error::into_compile_error)
231     .into()
232 }
233 
234 #[doc(hidden)]
235 #[proc_macro_attribute]
derive_object_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenStream236 pub fn derive_object_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenStream {
237     expand_object(syn::parse_macro_input!(input), true)
238         .unwrap_or_else(syn::Error::into_compile_error)
239         .into()
240 }
241 
242 #[doc(hidden)]
243 #[proc_macro_attribute]
export_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream244 pub fn export_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream {
245     do_export(attrs, input, true)
246 }
247 
248 /// A helper macro to include generated component scaffolding.
249 ///
250 /// This is a simple convenience macro to include the UniFFI component
251 /// scaffolding as built by `uniffi_build::generate_scaffolding`.
252 /// Use it like so:
253 ///
254 /// ```rs
255 /// uniffi_macros::include_scaffolding!("my_component_name");
256 /// ```
257 ///
258 /// This will expand to the appropriate `include!` invocation to include
259 /// the generated `my_component_name.uniffi.rs` (which it assumes has
260 /// been successfully built by your crate's `build.rs` script).
261 #[proc_macro]
include_scaffolding(udl_stem: TokenStream) -> TokenStream262 pub fn include_scaffolding(udl_stem: TokenStream) -> TokenStream {
263     let udl_stem = syn::parse_macro_input!(udl_stem as LitStr);
264     if std::env::var("OUT_DIR").is_err() {
265         quote! {
266             compile_error!("This macro assumes the crate has a build.rs script, but $OUT_DIR is not present");
267         }
268     } else {
269         let toml_path = match util::manifest_path() {
270             Ok(path) => path.display().to_string(),
271             Err(_) => {
272                 return quote! {
273                     compile_error!("This macro assumes the crate has a build.rs script, but $OUT_DIR is not present");
274                 }.into();
275             }
276         };
277 
278         quote! {
279             // FIXME(HACK):
280             // Include the `Cargo.toml` file into the build.
281             // That way cargo tracks the file and other tools relying on file
282             // tracking see it as well.
283             // See https://bugzilla.mozilla.org/show_bug.cgi?id=1846223
284             // In the future we should handle that by using the `track_path::path` API,
285             // see https://github.com/rust-lang/rust/pull/84029
286             #[allow(dead_code)]
287             mod __unused {
288                 const _: &[u8] = include_bytes!(#toml_path);
289             }
290 
291             include!(concat!(env!("OUT_DIR"), "/", #udl_stem, ".uniffi.rs"));
292         }
293     }.into()
294 }
295 
296 // Use a UniFFI types from dependent crates that uses UDL files
297 // See the derive_for_udl and export_for_udl section for a discussion of why this is needed.
298 #[proc_macro]
use_udl_record(tokens: TokenStream) -> TokenStream299 pub fn use_udl_record(tokens: TokenStream) -> TokenStream {
300     use_udl_simple_type(tokens)
301 }
302 
303 #[proc_macro]
use_udl_enum(tokens: TokenStream) -> TokenStream304 pub fn use_udl_enum(tokens: TokenStream) -> TokenStream {
305     use_udl_simple_type(tokens)
306 }
307 
308 #[proc_macro]
use_udl_error(tokens: TokenStream) -> TokenStream309 pub fn use_udl_error(tokens: TokenStream) -> TokenStream {
310     use_udl_simple_type(tokens)
311 }
312 
use_udl_simple_type(tokens: TokenStream) -> TokenStream313 fn use_udl_simple_type(tokens: TokenStream) -> TokenStream {
314     let util::ExternalTypeItem {
315         crate_ident,
316         type_ident,
317         ..
318     } = parse_macro_input!(tokens);
319     quote! {
320         ::uniffi::ffi_converter_forward!(#type_ident, #crate_ident::UniFfiTag, crate::UniFfiTag);
321     }
322     .into()
323 }
324 
325 #[proc_macro]
use_udl_object(tokens: TokenStream) -> TokenStream326 pub fn use_udl_object(tokens: TokenStream) -> TokenStream {
327     let util::ExternalTypeItem {
328         crate_ident,
329         type_ident,
330         ..
331     } = parse_macro_input!(tokens);
332     quote! {
333         ::uniffi::ffi_converter_arc_forward!(#type_ident, #crate_ident::UniFfiTag, crate::UniFfiTag);
334     }.into()
335 }
336 
337 /// A helper macro to generate and include component scaffolding.
338 ///
339 /// This is a convenience macro designed for writing `trybuild`-style tests and
340 /// probably shouldn't be used for production code. Given the path to a `.udl` file,
341 /// if will run `uniffi-bindgen` to produce the corresponding Rust scaffolding and then
342 /// include it directly into the calling file. Like so:
343 ///
344 /// ```rs
345 /// uniffi_macros::generate_and_include_scaffolding!("path/to/my/interface.udl");
346 /// ```
347 #[proc_macro]
348 #[cfg(feature = "trybuild")]
generate_and_include_scaffolding(udl_file: TokenStream) -> TokenStream349 pub fn generate_and_include_scaffolding(udl_file: TokenStream) -> TokenStream {
350     let udl_file = syn::parse_macro_input!(udl_file as LitStr);
351     let udl_file_string = udl_file.value();
352     let udl_file_path = Utf8Path::new(&udl_file_string);
353     if std::env::var("OUT_DIR").is_err() {
354         quote! {
355             compile_error!("This macro assumes the crate has a build.rs script, but $OUT_DIR is not present");
356         }
357     } else if let Err(e) = uniffi_build::generate_scaffolding(udl_file_path) {
358         let err = format!("{e:#}");
359         quote! {
360             compile_error!(concat!("Failed to generate scaffolding from UDL file at ", #udl_file, ": ", #err));
361         }
362     } else {
363         // We know the filename is good because `generate_scaffolding` succeeded,
364         // so this `unwrap` will never fail.
365         let name = LitStr::new(udl_file_path.file_stem().unwrap(), udl_file.span());
366         quote! {
367             uniffi_macros::include_scaffolding!(#name);
368         }
369     }.into()
370 }
371 
372 /// An attribute for constructors.
373 ///
374 /// Constructors are in `impl` blocks which have a `#[uniffi::export]` attribute,
375 ///
376 /// This exists so `#[uniffi::export]` can emit its input verbatim without
377 /// causing unexpected errors in the entire exported block.
378 /// This happens very often when the proc-macro is run on an incomplete
379 /// input by rust-analyzer while the developer is typing.
380 ///
381 /// So much better to do nothing here then let the impl block find the attribute.
382 #[proc_macro_attribute]
constructor(_attrs: TokenStream, input: TokenStream) -> TokenStream383 pub fn constructor(_attrs: TokenStream, input: TokenStream) -> TokenStream {
384     input
385 }
386 
387 /// An attribute for methods.
388 ///
389 /// Everything above applies here too.
390 #[proc_macro_attribute]
method(_attrs: TokenStream, input: TokenStream) -> TokenStream391 pub fn method(_attrs: TokenStream, input: TokenStream) -> TokenStream {
392     input
393 }
394