/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #![cfg_attr(feature = "nightly", feature(proc_macro_expand))] #![warn(rust_2018_idioms, unused_qualifications)] //! Macros for `uniffi`. #[cfg(feature = "trybuild")] use camino::Utf8Path; use proc_macro::TokenStream; use quote::quote; use syn::{ parse::{Parse, ParseStream}, parse_macro_input, Ident, LitStr, Path, Token, }; mod custom; mod default; mod enum_; mod error; mod export; mod fnsig; mod object; mod record; mod setup_scaffolding; mod test; mod util; use self::{ enum_::expand_enum, error::expand_error, export::expand_export, object::expand_object, record::expand_record, }; struct CustomTypeInfo { ident: Ident, builtin: Path, } impl Parse for CustomTypeInfo { fn parse(input: ParseStream<'_>) -> syn::Result { let ident = input.parse()?; input.parse::()?; let builtin = input.parse()?; Ok(Self { ident, builtin }) } } /// A macro to build testcases for a component's generated bindings. /// /// This macro provides some plumbing to write automated tests for the generated /// foreign language bindings of a component. As a component author, you can write /// script files in the target foreign language(s) that exercise you component API, /// and then call this macro to produce a `cargo test` testcase from each one. /// The generated code will execute your script file with appropriate configuration and /// environment to let it load the component bindings, and will pass iff the script /// exits successfully. /// /// To use it, invoke the macro with the name of a fixture/example crate as the first argument, /// then one or more file paths relative to the crate root directory. It will produce one `#[test]` /// function per file, in a manner designed to play nicely with `cargo test` and its test filtering /// options. #[proc_macro] pub fn build_foreign_language_testcases(tokens: TokenStream) -> TokenStream { test::build_foreign_language_testcases(tokens) } /// Top-level initialization macro /// /// The optional namespace argument is only used by the scaffolding templates to pass in the /// CI namespace. #[proc_macro] pub fn setup_scaffolding(tokens: TokenStream) -> TokenStream { let namespace = match syn::parse_macro_input!(tokens as Option) { Some(lit_str) => lit_str.value(), None => match util::mod_path() { Ok(v) => v, Err(e) => return e.into_compile_error().into(), }, }; setup_scaffolding::setup_scaffolding(namespace) .unwrap_or_else(syn::Error::into_compile_error) .into() } #[proc_macro_attribute] pub fn export(attr_args: TokenStream, input: TokenStream) -> TokenStream { do_export(attr_args, input, false) } fn do_export(attr_args: TokenStream, input: TokenStream, udl_mode: bool) -> TokenStream { let copied_input = (!udl_mode).then(|| proc_macro2::TokenStream::from(input.clone())); let gen_output = || { let item = syn::parse(input)?; expand_export(item, attr_args, udl_mode) }; let output = gen_output().unwrap_or_else(syn::Error::into_compile_error); quote! { #copied_input #output } .into() } #[proc_macro_derive(Record, attributes(uniffi))] pub fn derive_record(input: TokenStream) -> TokenStream { expand_record(parse_macro_input!(input), false) .unwrap_or_else(syn::Error::into_compile_error) .into() } #[proc_macro_derive(Enum)] pub fn derive_enum(input: TokenStream) -> TokenStream { expand_enum(parse_macro_input!(input), None, false) .unwrap_or_else(syn::Error::into_compile_error) .into() } #[proc_macro_derive(Object)] pub fn derive_object(input: TokenStream) -> TokenStream { expand_object(parse_macro_input!(input), false) .unwrap_or_else(syn::Error::into_compile_error) .into() } #[proc_macro_derive(Error, attributes(uniffi))] pub fn derive_error(input: TokenStream) -> TokenStream { expand_error(parse_macro_input!(input), None, false) .unwrap_or_else(syn::Error::into_compile_error) .into() } /// Generate the `FfiConverter` implementation for a Custom Type - ie, /// for a `` which implements `UniffiCustomTypeConverter`. #[proc_macro] pub fn custom_type(tokens: TokenStream) -> TokenStream { let input: CustomTypeInfo = syn::parse_macro_input!(tokens); custom::expand_ffi_converter_custom_type(&input.ident, &input.builtin, true) .unwrap_or_else(syn::Error::into_compile_error) .into() } /// Generate the `FfiConverter` and the `UniffiCustomTypeConverter` implementations for a /// Custom Type - ie, for a `` which implements `UniffiCustomTypeConverter` via the /// newtype idiom. #[proc_macro] pub fn custom_newtype(tokens: TokenStream) -> TokenStream { let input: CustomTypeInfo = syn::parse_macro_input!(tokens); custom::expand_ffi_converter_custom_newtype(&input.ident, &input.builtin, true) .unwrap_or_else(syn::Error::into_compile_error) .into() } // == derive_for_udl and export_for_udl == // // The Askama templates generate placeholder items wrapped with these attributes. The goal is to // have all scaffolding generation go through the same code path. // // The one difference is that derive-style attributes are not allowed inside attribute macro // inputs. Instead, we take the attributes from the macro invocation itself. // // Instead of: // // ``` // #[derive(Error) // #[uniffi(flat_error]) // enum { .. } // ``` // // We have: // // ``` // #[derive_error_for_udl(flat_error)] // enum { ... } // ``` // // # Differences between UDL-mode and normal mode // // ## Metadata symbols / checksum functions // // In UDL mode, we don't export the static metadata symbols or generate the checksum // functions. This could be changed, but there doesn't seem to be much benefit at this point. // // ## The FfiConverter parameter // // In UDL-mode, we only implement `FfiConverter` for the local tag (`FfiConverter`) // // The reason for this split is remote types, i.e. types defined in remote crates that we // don't control and therefore can't define a blanket impl on because of the orphan rules. // // With UDL, we handle this by only implementing `FfiConverter` for the // type. This gets around the orphan rules since a local type is in the trait, but requires // a `uniffi::ffi_converter_forward!` call if the type is used in a second local crate (an // External typedef). This is natural for UDL-based generation, since you always need to // define the external type in the UDL file. // // With proc-macros this system isn't so natural. Instead, we create a blanket implementation // for all UT and support for remote types is still TODO. #[doc(hidden)] #[proc_macro_attribute] pub fn derive_record_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenStream { expand_record(syn::parse_macro_input!(input), true) .unwrap_or_else(syn::Error::into_compile_error) .into() } #[doc(hidden)] #[proc_macro_attribute] pub fn derive_enum_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream { expand_enum( syn::parse_macro_input!(input), Some(syn::parse_macro_input!(attrs)), true, ) .unwrap_or_else(syn::Error::into_compile_error) .into() } #[doc(hidden)] #[proc_macro_attribute] pub fn derive_error_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream { expand_error( syn::parse_macro_input!(input), Some(syn::parse_macro_input!(attrs)), true, ) .unwrap_or_else(syn::Error::into_compile_error) .into() } #[doc(hidden)] #[proc_macro_attribute] pub fn derive_object_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenStream { expand_object(syn::parse_macro_input!(input), true) .unwrap_or_else(syn::Error::into_compile_error) .into() } #[doc(hidden)] #[proc_macro_attribute] pub fn export_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream { do_export(attrs, input, true) } /// A helper macro to include generated component scaffolding. /// /// This is a simple convenience macro to include the UniFFI component /// scaffolding as built by `uniffi_build::generate_scaffolding`. /// Use it like so: /// /// ```rs /// uniffi_macros::include_scaffolding!("my_component_name"); /// ``` /// /// This will expand to the appropriate `include!` invocation to include /// the generated `my_component_name.uniffi.rs` (which it assumes has /// been successfully built by your crate's `build.rs` script). #[proc_macro] pub fn include_scaffolding(udl_stem: TokenStream) -> TokenStream { let udl_stem = syn::parse_macro_input!(udl_stem as LitStr); if std::env::var("OUT_DIR").is_err() { quote! { compile_error!("This macro assumes the crate has a build.rs script, but $OUT_DIR is not present"); } } else { let toml_path = match util::manifest_path() { Ok(path) => path.display().to_string(), Err(_) => { return quote! { compile_error!("This macro assumes the crate has a build.rs script, but $OUT_DIR is not present"); }.into(); } }; quote! { // FIXME(HACK): // Include the `Cargo.toml` file into the build. // That way cargo tracks the file and other tools relying on file // tracking see it as well. // See https://bugzilla.mozilla.org/show_bug.cgi?id=1846223 // In the future we should handle that by using the `track_path::path` API, // see https://github.com/rust-lang/rust/pull/84029 #[allow(dead_code)] mod __unused { const _: &[u8] = include_bytes!(#toml_path); } include!(concat!(env!("OUT_DIR"), "/", #udl_stem, ".uniffi.rs")); } }.into() } // Use a UniFFI types from dependent crates that uses UDL files // See the derive_for_udl and export_for_udl section for a discussion of why this is needed. #[proc_macro] pub fn use_udl_record(tokens: TokenStream) -> TokenStream { use_udl_simple_type(tokens) } #[proc_macro] pub fn use_udl_enum(tokens: TokenStream) -> TokenStream { use_udl_simple_type(tokens) } #[proc_macro] pub fn use_udl_error(tokens: TokenStream) -> TokenStream { use_udl_simple_type(tokens) } fn use_udl_simple_type(tokens: TokenStream) -> TokenStream { let util::ExternalTypeItem { crate_ident, type_ident, .. } = parse_macro_input!(tokens); quote! { ::uniffi::ffi_converter_forward!(#type_ident, #crate_ident::UniFfiTag, crate::UniFfiTag); } .into() } #[proc_macro] pub fn use_udl_object(tokens: TokenStream) -> TokenStream { let util::ExternalTypeItem { crate_ident, type_ident, .. } = parse_macro_input!(tokens); quote! { ::uniffi::ffi_converter_arc_forward!(#type_ident, #crate_ident::UniFfiTag, crate::UniFfiTag); }.into() } /// A helper macro to generate and include component scaffolding. /// /// This is a convenience macro designed for writing `trybuild`-style tests and /// probably shouldn't be used for production code. Given the path to a `.udl` file, /// if will run `uniffi-bindgen` to produce the corresponding Rust scaffolding and then /// include it directly into the calling file. Like so: /// /// ```rs /// uniffi_macros::generate_and_include_scaffolding!("path/to/my/interface.udl"); /// ``` #[proc_macro] #[cfg(feature = "trybuild")] pub fn generate_and_include_scaffolding(udl_file: TokenStream) -> TokenStream { let udl_file = syn::parse_macro_input!(udl_file as LitStr); let udl_file_string = udl_file.value(); let udl_file_path = Utf8Path::new(&udl_file_string); if std::env::var("OUT_DIR").is_err() { quote! { compile_error!("This macro assumes the crate has a build.rs script, but $OUT_DIR is not present"); } } else if let Err(e) = uniffi_build::generate_scaffolding(udl_file_path) { let err = format!("{e:#}"); quote! { compile_error!(concat!("Failed to generate scaffolding from UDL file at ", #udl_file, ": ", #err)); } } else { // We know the filename is good because `generate_scaffolding` succeeded, // so this `unwrap` will never fail. let name = LitStr::new(udl_file_path.file_stem().unwrap(), udl_file.span()); quote! { uniffi_macros::include_scaffolding!(#name); } }.into() } /// An attribute for constructors. /// /// Constructors are in `impl` blocks which have a `#[uniffi::export]` attribute, /// /// This exists so `#[uniffi::export]` can emit its input verbatim without /// causing unexpected errors in the entire exported block. /// This happens very often when the proc-macro is run on an incomplete /// input by rust-analyzer while the developer is typing. /// /// So much better to do nothing here then let the impl block find the attribute. #[proc_macro_attribute] pub fn constructor(_attrs: TokenStream, input: TokenStream) -> TokenStream { input } /// An attribute for methods. /// /// Everything above applies here too. #[proc_macro_attribute] pub fn method(_attrs: TokenStream, input: TokenStream) -> TokenStream { input }