cxx_async_macro/
lib.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under both the MIT license found in the
5 * LICENSE-MIT file in the root directory of this source tree and the Apache
6 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
7 * of this source tree.
8 */
9
10// cxx-async/macro/src/lib.rs
11//
12//! The definition of the `#[bridge]` macro.
13//!
14//! Don't depend on this crate directly; just use the reexported macro in `cxx-async`.
15
16use proc_macro::TokenStream;
17use quote::quote;
18use syn::parse::Parse;
19use syn::parse::ParseStream;
20use syn::parse::Result as ParseResult;
21use syn::spanned::Spanned;
22use syn::Error as SynError;
23use syn::Ident;
24use syn::ImplItem;
25use syn::ItemImpl;
26use syn::Lit;
27use syn::LitStr;
28use syn::Path;
29use syn::Result as SynResult;
30use syn::Token;
31use syn::Type;
32use syn::TypePath;
33use syn::Visibility;
34
35/// Defines a future or stream type that can be awaited from both Rust and C++.
36///
37/// Invoke this macro like so:
38///
39/// ```ignore
40/// #[cxx_async::bridge]
41/// unsafe impl Future for RustFutureString {
42///     type Output = String;
43/// }
44/// ```
45///
46/// Here, `RustFutureString` is the name of the future type declared inside the
47/// `extern Rust { ... }` block inside the `#[cxx::bridge]` module. `String` is the Rust type that
48/// the future yields when awaited; on the Rust side it will be automatically wrapped in a
49/// `CxxAsyncResult`. On the C++ side it will be converted to the appropriate type, following the
50/// `cxx` rules. Err returns are translated into C++ exceptions.
51///
52/// If the future is inside a C++ namespace, add a `namespace = ...` attribute to the
53/// `#[cxx_async::bridge]` attribute like so:
54///
55/// ```ignore
56/// #[cxx::bridge]
57/// #[namespace = mycompany::myproject]
58/// mod ffi {
59///     extern "Rust" {
60///         type RustFutureStringNamespaced;
61///     }
62/// }
63///
64/// #[cxx_async::bridge(namespace = mycompany::myproject)]
65/// unsafe impl Future for RustFutureStringNamespaced {
66///     type Output = String;
67/// }
68/// ```
69///
70/// ## Safety
71///
72/// It's the programmer's responsibility to ensure that the specified `Output` type correctly
73/// reflects the type of the value that any returned C++ future resolves to. `cxx_async` can't
74/// currently check to ensure that these types match. If the types don't match, undefined behavior
75/// can result. See the [cxx documentation] for information on the mapping between Rust types and
76/// C++ types.
77///
78/// [cxx documentation]: https://cxx.rs/bindings.html
79#[proc_macro_attribute]
80pub fn bridge(attribute: TokenStream, item: TokenStream) -> TokenStream {
81    match AstPieces::from_token_streams(attribute, item) {
82        Err(error) => error.into_compile_error().into(),
83        Ok(
84            pieces @ AstPieces {
85                bridge_trait: BridgeTrait::Future,
86                ..
87            },
88        ) => bridge_future(pieces),
89        Ok(
90            pieces @ AstPieces {
91                bridge_trait: BridgeTrait::Stream,
92                ..
93            },
94        ) => bridge_stream(pieces),
95    }
96}
97
98fn bridge_future(pieces: AstPieces) -> TokenStream {
99    let AstPieces {
100        bridge_trait: _,
101        future,
102        qualified_name,
103        output,
104        trait_path,
105        vtable_glue_ident,
106        vtable_glue_link_name,
107    } = pieces;
108    (quote! {
109        /// A future shared between Rust and C++.
110        #[repr(transparent)]
111        pub struct #future {
112            future: ::cxx_async::private::BoxFuture<'static, ::cxx_async::CxxAsyncResult<#output>>,
113        }
114
115        impl #future {
116            // SAFETY: See: https://docs.rs/pin-utils/0.1.0/pin_utils/macro.unsafe_pinned.html
117            // 1. The struct doesn't move fields in Drop. Our hogging the Drop implementation
118            //    ensures this.
119            // 2. The inner field implements Unpin. We ensure this in the `assert_field_is_unpin`
120            //    method.
121            // 3. The struct isn't `repr(packed)`. We define the struct and don't have this
122            //    attribute.
123            ::cxx_async::unsafe_pinned!(future: ::cxx_async::private::BoxFuture<'static,
124                ::cxx_async::CxxAsyncResult<#output>>);
125
126            #[doc(hidden)]
127            fn assert_field_is_unpin() {
128                fn check<T>() where T: Unpin {}
129                check::<#output>()
130            }
131        }
132
133        // Define a Drop implementation so that end users don't. If end users are allowed to define
134        // Drop, that could make our use of `unsafe_pinned!` unsafe.
135        impl Drop for #future {
136            fn drop(&mut self) {}
137        }
138
139        // Define how to box up a future.
140        impl ::cxx_async::IntoCxxAsyncFuture for #future {
141            type Output = #output;
142            fn fallible<Fut>(future: Fut) -> Self where Fut: ::std::future::Future<Output =
143                    ::cxx_async::CxxAsyncResult<#output>> + Send + 'static {
144                #future {
145                    future: Box::pin(future),
146                }
147            }
148        }
149
150        // Implement the Rust Future trait.
151        impl #trait_path for #future {
152            type Output = ::cxx_async::CxxAsyncResult<#output>;
153            fn poll(self: ::std::pin::Pin<&mut Self>, cx: &mut ::std::task::Context<'_>)
154                    -> ::std::task::Poll<Self::Output> {
155                self.future().poll(cx)
156            }
157        }
158
159        // Define how to wrap concrete receivers in the future type we're defining.
160        impl ::std::convert::From<::cxx_async::CxxAsyncReceiver<#output>> for #future {
161            fn from(receiver: ::cxx_async::CxxAsyncReceiver<#output>) -> Self {
162                Self {
163                    future: Box::pin(receiver),
164                }
165            }
166        }
167
168        // Make sure that the future type can be returned by value.
169        // See: https://github.com/dtolnay/cxx/pull/672
170        unsafe impl ::cxx::ExternType for #future {
171            type Id = ::cxx::type_id!(#qualified_name);
172            type Kind = ::cxx::kind::Trivial;
173        }
174
175        // Convenience wrappers so that client code doesn't have to import `IntoCxxAsyncFuture`.
176        impl #future {
177            pub fn infallible<Fut>(future: Fut) -> Self
178                    where Fut: ::std::future::Future<Output = #output> + Send + 'static {
179                <#future as ::cxx_async::IntoCxxAsyncFuture>::infallible(future)
180            }
181
182            pub fn fallible<Fut>(future: Fut) -> Self
183                    where Fut: ::std::future::Future<Output =
184                        ::cxx_async::CxxAsyncResult<#output>> + Send + 'static {
185                <#future as ::cxx_async::IntoCxxAsyncFuture>::fallible(future)
186            }
187        }
188
189        #[doc(hidden)]
190        #[allow(non_snake_case)]
191        #[export_name = #vtable_glue_link_name]
192        pub unsafe extern "C" fn #vtable_glue_ident() -> *const ::cxx_async::CxxAsyncVtable {
193            static VTABLE: ::cxx_async::CxxAsyncVtable = ::cxx_async::CxxAsyncVtable {
194                channel: ::cxx_async::future_channel::<#future, #output> as *mut u8,
195                sender_send: ::cxx_async::sender_future_send::<#output> as *mut u8,
196                sender_drop: ::cxx_async::sender_drop::<#output> as *mut u8,
197                future_poll: ::cxx_async::future_poll::<#future, #output> as *mut u8,
198                future_drop: ::cxx_async::future_drop::<#future> as *mut u8,
199            };
200            return &VTABLE;
201        }
202    })
203    .into()
204}
205
206/// Defines a C++ stream type that can be awaited from Rust.
207///
208/// The syntax to use is:
209///
210/// ```ignore
211/// #[cxx_async::bridge_stream]
212/// unsafe impl Stream for RustStreamString {
213///     type Item = String;
214/// }
215/// ```
216///
217/// Here, `RustStreamString` is the name of the stream type declared inside the
218/// `extern Rust { ... }` block inside the `#[cxx::bridge]` module. `String` is the Rust type that
219/// the stream yields when consumed; on the Rust side it will be automatically wrapped in a
220/// `CxxAsyncResult`. On the C++ side it will be converted to the appropriate type, following the
221/// `cxx` rules. Err returns are translated into C++ exceptions.
222///
223/// If the stream is inside a C++ namespace, add a `namespace = ...` attribute to the
224/// `#[cxx_async::bridge_stream]` attribute like so:
225///
226/// ```ignore
227/// #[cxx::bridge]
228/// #[namespace = mycompany::myproject]
229/// mod ffi {
230///     extern "Rust" {
231///         type RustStreamStringNamespaced;
232///     }
233/// }
234///
235/// #[cxx_async::bridge_stream(namespace = mycompany::myproject)]
236/// unsafe impl Stream for RustStreamStringNamespaced {
237///     type Item = String;
238/// }
239/// ```
240///
241/// ## Safety
242///
243/// It's the programmer's responsibility to ensure that the specified `Item` type correctly
244/// reflects the type of the values that any returned C++ streams resolves to. `cxx_async` can't
245/// currently check to ensure that these types match. If the types don't match, undefined behavior
246/// can result. See the [cxx documentation] for information on the mapping between Rust types and
247/// C++ types.
248///
249/// [cxx documentation]: https://cxx.rs/bindings.html
250fn bridge_stream(pieces: AstPieces) -> TokenStream {
251    let AstPieces {
252        bridge_trait: _,
253        future: stream,
254        qualified_name,
255        output: item,
256        trait_path,
257        vtable_glue_ident,
258        vtable_glue_link_name,
259    } = pieces;
260    (quote! {
261        /// A multi-shot stream shared between Rust and C++.
262        #[repr(transparent)]
263        pub struct #stream {
264            stream: ::cxx_async::private::BoxStream<'static, ::cxx_async::CxxAsyncResult<#item>>,
265        }
266
267        impl #stream {
268            // SAFETY: See: https://docs.rs/pin-utils/0.1.0/pin_utils/macro.unsafe_pinned.html
269            // 1. The struct doesn't move fields in Drop. Our hogging the Drop implementation
270            //    ensures this.
271            // 2. The inner field implements Unpin. We ensure this in the `assert_field_is_unpin`
272            //    method.
273            // 3. The struct isn't `repr(packed)`. We define the struct and don't have this
274            //    attribute.
275            ::cxx_async::unsafe_pinned!(stream: ::cxx_async::private::BoxStream<'static,
276                ::cxx_async::CxxAsyncResult<#item>>);
277
278            #[doc(hidden)]
279            fn assert_field_is_unpin() {
280                fn check<T>() where T: Unpin {}
281                check::<#item>()
282            }
283        }
284
285        // Define a Drop implementation so that end users don't. If end users are allowed to define
286        // Drop, that could make our use of `unsafe_pinned!` unsafe.
287        impl Drop for #stream {
288            fn drop(&mut self) {}
289        }
290
291        // Define how to box up a future.
292        impl ::cxx_async::IntoCxxAsyncStream for #stream {
293            type Item = #item;
294            fn fallible<Stm>(stream: Stm) -> Self where Stm: #trait_path<Item =
295                    ::cxx_async::CxxAsyncResult<#item>> + Send + 'static {
296                #stream {
297                    stream: Box::pin(stream),
298                }
299            }
300        }
301
302        // Implement the Rust Stream trait.
303        impl #trait_path for #stream {
304            type Item = ::cxx_async::CxxAsyncResult<#item>;
305            fn poll_next(mut self: ::std::pin::Pin<&mut Self>, cx: &mut ::std::task::Context<'_>)
306                    -> ::std::task::Poll<Option<Self::Item>> {
307                ::std::pin::Pin::new(&mut self.stream).poll_next(cx)
308            }
309        }
310
311        // Define how to wrap concrete receivers in the stream type we're defining.
312        impl ::std::convert::From<::cxx_async::CxxAsyncReceiver<#item>> for #stream {
313            fn from(receiver: ::cxx_async::CxxAsyncReceiver<#item>) -> Self {
314                Self {
315                    stream: Box::pin(receiver),
316                }
317            }
318        }
319
320        // Make sure that the stream type can be returned by value.
321        // See: https://github.com/dtolnay/cxx/pull/672
322        unsafe impl ::cxx::ExternType for #stream {
323            type Id = ::cxx::type_id!(#qualified_name);
324            type Kind = ::cxx::kind::Trivial;
325        }
326
327        // Convenience wrappers so that client code doesn't have to import `IntoCxxAsyncFuture`.
328        impl #stream {
329            pub fn infallible<Stm>(stream: Stm) -> Self
330                    where Stm: #trait_path<Item = #item> + Send + 'static {
331                <#stream as ::cxx_async::IntoCxxAsyncStream>::infallible(stream)
332            }
333
334            pub fn fallible<Stm>(stream: Stm) -> Self
335                    where Stm: #trait_path<Item =
336                        ::cxx_async::CxxAsyncResult<#item>> + Send + 'static {
337                <#stream as ::cxx_async::IntoCxxAsyncStream>::fallible(stream)
338            }
339        }
340
341        #[doc(hidden)]
342        #[allow(non_snake_case)]
343        #[export_name = #vtable_glue_link_name]
344        pub unsafe extern "C" fn #vtable_glue_ident() -> *const ::cxx_async::CxxAsyncVtable {
345            // TODO(pcwalton): Support C++ calling Rust Streams.
346            static VTABLE: ::cxx_async::CxxAsyncVtable = ::cxx_async::CxxAsyncVtable {
347                channel: ::cxx_async::stream_channel::<#stream, #item> as *mut u8,
348                sender_send: ::cxx_async::sender_stream_send::<#item> as *mut u8,
349                sender_drop: ::cxx_async::sender_drop::<#item> as *mut u8,
350                future_poll: ::std::ptr::null_mut(),
351                future_drop: ::cxx_async::future_drop::<#stream> as *mut u8,
352            };
353            return &VTABLE;
354        }
355    })
356    .into()
357}
358
359// Whether the programmer is implementing `Future` or `Stream`.
360enum BridgeTrait {
361    Future,
362    Stream,
363}
364
365// AST pieces generated for the `#[bridge]` macro.
366struct AstPieces {
367    // Whether the programmer is implementing `Future` or `Stream`.
368    bridge_trait: BridgeTrait,
369    // The name of the future or stream type.
370    future: Ident,
371    // The fully-qualified name (i.e. including C++ namespace if any) of the future or stream type,
372    // as a quoted string.
373    qualified_name: Lit,
374    // The output type of the future or the item type of the stream.
375    output: Type,
376    // The path to the trait being implemented, which must be `std::future::Future` or
377    // `futures::Stream`.
378    trait_path: Path,
379    // The internal Rust symbol name of the future/stream vtable.
380    vtable_glue_ident: Ident,
381    // The external C++ link name of the future/stream vtable.
382    vtable_glue_link_name: String,
383}
384
385impl AstPieces {
386    // Parses the macro arguments and returns the pieces, returning a `syn::Error` on error.
387    fn from_token_streams(attribute: TokenStream, item: TokenStream) -> SynResult<AstPieces> {
388        let namespace: NamespaceAttribute = syn::parse(attribute).map_err(|error| {
389            SynError::new(error.span(), "expected possible namespace attribute")
390        })?;
391
392        let impl_item: ItemImpl = syn::parse(item).map_err(|error| {
393            SynError::new(
394                error.span(),
395                "expected implementation of `Future` or `Stream`",
396            )
397        })?;
398        if impl_item.unsafety.is_none() {
399            return Err(SynError::new(
400                impl_item.span(),
401                "implementation must be marked `unsafe`",
402            ));
403        }
404        if impl_item.defaultness.is_some() {
405            return Err(SynError::new(
406                impl_item.span(),
407                "implementation must not be marked default",
408            ));
409        }
410        if !impl_item.generics.params.is_empty() {
411            return Err(SynError::new(
412                impl_item.generics.params[0].span(),
413                "generic bridged futures are unsupported",
414            ));
415        }
416        if let Some(where_clause) = impl_item.generics.where_clause {
417            return Err(SynError::new(
418                where_clause.where_token.span,
419                "generic bridged futures are unsupported",
420            ));
421        }
422
423        // We don't check to make sure that `path` is `std::future::Future` or `futures::Stream`
424        // here, even though that's ultimately a requirement, because we would have to perform name
425        // resolution here in the macro to do that. Instead, we take advantage of the fact that
426        // we're going to be generating an implementation of the appropriate trait anyway and simply
427        // supply whatever the user wrote as the name of the trait to be implemented in our final
428        // macro expansion. That way, the Rust compiler ends up checking that the trait that the
429        // user wrote is the right one.
430        let trait_path = match impl_item.trait_ {
431            Some((None, ref path, _)) => (*path).clone(),
432            _ => {
433                return Err(SynError::new(
434                    impl_item.span(),
435                    "must implement the `Future` or `Stream` trait",
436                ));
437            }
438        };
439
440        if impl_item.items.len() != 1 {
441            return Err(SynError::new(
442                impl_item.span(),
443                "expected implementation to contain a single item, `type Output = ...` or \
444                 `type Item = ...`",
445            ));
446        }
447
448        let (bridge_trait, output);
449        match impl_item.items[0] {
450            ImplItem::Type(ref impl_type) => {
451                if !impl_type.attrs.is_empty() {
452                    return Err(SynError::new(
453                        impl_type.attrs[0].span(),
454                        "attributes on the `type Output = ...` or `type Item = ...` declaration \
455                         are not supported",
456                    ));
457                }
458                match impl_type.vis {
459                    Visibility::Inherited => {}
460                    _ => {
461                        return Err(SynError::new(
462                            impl_type.vis.span(),
463                            "`pub` or `crate` visibility modifiers on the `type Output = ...` \
464                            or `type Item = ...` declaration are not supported",
465                        ));
466                    }
467                }
468                if let Some(defaultness) = impl_type.defaultness {
469                    return Err(SynError::new(
470                        defaultness.span(),
471                        "`default` specifier on the `type Output = ...` or `type Item = ...` \
472                         declaration is not supported",
473                    ));
474                }
475                if !impl_type.generics.params.is_empty() {
476                    return Err(SynError::new(
477                        impl_type.generics.params[0].span(),
478                        "generics on the `type Output = ...` or `type Item = ...` declaration are \
479                         not supported",
480                    ));
481                }
482
483                // We use the name of the associated type to disambiguate between a Future and a
484                // Stream implementation.
485                bridge_trait = if impl_type.ident == "Output" {
486                    BridgeTrait::Future
487                } else if impl_type.ident == "Item" {
488                    BridgeTrait::Stream
489                } else {
490                    return Err(SynError::new(
491                        impl_type.ident.span(),
492                        "implementation must contain an associated type definition named \
493                        `Output` or `Item`",
494                    ));
495                };
496                output = impl_type.ty.clone();
497            }
498            _ => {
499                return Err(SynError::new(
500                    impl_item.span(),
501                    "expected implementation to contain a single item, `type Output = ...` \
502                    or `type Stream = ...`",
503                ));
504            }
505        };
506
507        let future = match *impl_item.self_ty {
508            Type::Path(TypePath { qself: None, path }) => {
509                path.get_ident().cloned().ok_or_else(|| {
510                    SynError::new(
511                        path.span(),
512                        "expected `impl` declaration to implement a single type",
513                    )
514                })?
515            }
516            _ => {
517                return Err(SynError::new(
518                    impl_item.self_ty.span(),
519                    "expected `impl` declaration to implement a single type",
520                ));
521            }
522        };
523
524        let qualified_name = Lit::Str(LitStr::new(
525            &format!(
526                "{}{}",
527                namespace
528                    .0
529                    .iter()
530                    .fold(String::new(), |acc, piece| acc + piece + "::"),
531                future
532            ),
533            future.span(),
534        ));
535
536        let vtable_glue_ident = Ident::new(
537            &format!(
538                "cxxasync_{}{}_vtable",
539                namespace
540                    .0
541                    .iter()
542                    .fold(String::new(), |acc, piece| acc + piece + "_"),
543                future
544            ),
545            future.span(),
546        );
547        let vtable_glue_link_name = format!(
548            "cxxasync_{}{}_vtable",
549            namespace
550                .0
551                .iter()
552                .fold(String::new(), |acc, piece| acc + piece + "$"),
553            future
554        );
555
556        Ok(AstPieces {
557            bridge_trait,
558            future,
559            qualified_name,
560            output,
561            trait_path,
562            vtable_glue_ident,
563            vtable_glue_link_name,
564        })
565    }
566}
567
568mod keywords {
569    use syn::custom_keyword;
570    custom_keyword!(namespace);
571}
572
573struct NamespaceAttribute(Vec<String>);
574
575impl Parse for NamespaceAttribute {
576    fn parse(input: ParseStream) -> ParseResult<Self> {
577        if input.is_empty() {
578            return Ok(NamespaceAttribute(vec![]));
579        }
580        input.parse::<keywords::namespace>()?;
581        input.parse::<Token![=]>()?;
582        let path = input.call(Path::parse_mod_style)?;
583        Ok(NamespaceAttribute(
584            path.segments
585                .iter()
586                .map(|segment| segment.ident.to_string())
587                .collect(),
588        ))
589    }
590}