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}