summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_macros/src/lib.rs
blob: 929400c885f111afef47bb12095972208cbd2669 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
/* 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<Self> {
        let ident = input.parse()?;
        input.parse::<Token![,]>()?;
        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<LitStr>) {
        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 `<T>` 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 `<T>` 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<UT> parameter
//
// In UDL-mode, we only implement `FfiConverter` for the local tag (`FfiConverter<crate::UniFfiTag>`)
//
// 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<crate::UniFfiTag>` 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
}