Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame^] | 1 | // Copyright 2020 Google LLC |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 4 | // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 5 | // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your |
| 6 | // option. This file may not be copied, modified, or distributed |
| 7 | // except according to those terms. |
| 8 | |
| 9 | #![forbid(unsafe_code)] |
| 10 | |
| 11 | use autocxx_parser::{IncludeCpp, SubclassAttrs}; |
| 12 | use proc_macro::TokenStream; |
| 13 | use proc_macro2::{Ident, Span}; |
| 14 | use proc_macro_error::{abort, proc_macro_error}; |
| 15 | use quote::quote; |
| 16 | use syn::parse::Parser; |
| 17 | use syn::{parse_macro_input, parse_quote, Fields, Item, ItemStruct, Visibility}; |
| 18 | |
| 19 | /// Implementation of the `include_cpp` macro. See documentation for `autocxx` crate. |
| 20 | #[proc_macro_error] |
| 21 | #[proc_macro] |
| 22 | pub fn include_cpp_impl(input: TokenStream) -> TokenStream { |
| 23 | let include_cpp = parse_macro_input!(input as IncludeCpp); |
| 24 | TokenStream::from(include_cpp.generate_rs()) |
| 25 | } |
| 26 | |
| 27 | /// Attribute to state that a Rust `struct` is a C++ subclass. |
| 28 | /// This adds an additional field to the struct which autocxx uses to |
| 29 | /// track a C++ instantiation of this Rust subclass. |
| 30 | #[proc_macro_error] |
| 31 | #[proc_macro_attribute] |
| 32 | pub fn subclass(attr: TokenStream, item: TokenStream) -> TokenStream { |
| 33 | let mut s: ItemStruct = |
| 34 | syn::parse(item).unwrap_or_else(|_| abort!(Span::call_site(), "Expected a struct")); |
| 35 | if !matches!(s.vis, Visibility::Public(..)) { |
| 36 | use syn::spanned::Spanned; |
| 37 | abort!(s.vis.span(), "Rust subclasses of C++ types must by public"); |
| 38 | } |
| 39 | let id = &s.ident; |
| 40 | let cpp_ident = Ident::new(&format!("{}Cpp", id), Span::call_site()); |
| 41 | let input = quote! { |
| 42 | cpp_peer: autocxx::subclass::CppSubclassCppPeerHolder<ffi:: #cpp_ident> |
| 43 | }; |
| 44 | let parser = syn::Field::parse_named; |
| 45 | let new_field = parser.parse2(input).unwrap(); |
| 46 | s.fields = match &mut s.fields { |
| 47 | Fields::Named(fields) => { |
| 48 | fields.named.push(new_field); |
| 49 | s.fields |
| 50 | }, |
| 51 | Fields::Unit => Fields::Named(parse_quote! { |
| 52 | { |
| 53 | #new_field |
| 54 | } |
| 55 | }), |
| 56 | _ => abort!(Span::call_site(), "Expect a struct with named fields - use struct A{} or struct A; as opposed to struct A()"), |
| 57 | }; |
| 58 | let subclass_attrs: SubclassAttrs = syn::parse(attr) |
| 59 | .unwrap_or_else(|_| abort!(Span::call_site(), "Unable to parse attributes")); |
| 60 | let self_owned_bit = if subclass_attrs.self_owned { |
| 61 | Some(quote! { |
| 62 | impl autocxx::subclass::CppSubclassSelfOwned<ffi::#cpp_ident> for #id {} |
| 63 | }) |
| 64 | } else { |
| 65 | None |
| 66 | }; |
| 67 | let toks = quote! { |
| 68 | #s |
| 69 | |
| 70 | impl autocxx::subclass::CppSubclass<ffi::#cpp_ident> for #id { |
| 71 | fn peer_holder_mut(&mut self) -> &mut autocxx::subclass::CppSubclassCppPeerHolder<ffi::#cpp_ident> { |
| 72 | &mut self.cpp_peer |
| 73 | } |
| 74 | fn peer_holder(&self) -> &autocxx::subclass::CppSubclassCppPeerHolder<ffi::#cpp_ident> { |
| 75 | &self.cpp_peer |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | #self_owned_bit |
| 80 | }; |
| 81 | toks.into() |
| 82 | } |
| 83 | |
| 84 | /// Attribute to state that a Rust type is to be exported to C++ |
| 85 | /// in the `extern "Rust"` section of the generated `cxx` bindings. |
| 86 | #[proc_macro_error] |
| 87 | #[proc_macro_attribute] |
| 88 | pub fn extern_rust_type(attr: TokenStream, input: TokenStream) -> TokenStream { |
| 89 | if !attr.is_empty() { |
| 90 | abort!(Span::call_site(), "Expected no attributes"); |
| 91 | } |
| 92 | let i: Item = |
| 93 | syn::parse(input.clone()).unwrap_or_else(|_| abort!(Span::call_site(), "Expected an item")); |
| 94 | match i { |
| 95 | Item::Struct(..) | Item::Enum(..) | Item::Fn(..) => {} |
| 96 | _ => abort!(Span::call_site(), "Expected a struct or enum"), |
| 97 | } |
| 98 | input |
| 99 | } |
| 100 | |
| 101 | /// Attribute to state that a Rust function is to be exported to C++ |
| 102 | /// in the `extern "Rust"` section of the generated `cxx` bindings. |
| 103 | #[proc_macro_error] |
| 104 | #[proc_macro_attribute] |
| 105 | pub fn extern_rust_function(attr: TokenStream, input: TokenStream) -> TokenStream { |
| 106 | if !attr.is_empty() { |
| 107 | abort!(Span::call_site(), "Expected no attributes"); |
| 108 | } |
| 109 | let i: Item = |
| 110 | syn::parse(input.clone()).unwrap_or_else(|_| abort!(Span::call_site(), "Expected an item")); |
| 111 | match i { |
| 112 | Item::Fn(..) => {} |
| 113 | _ => abort!(Span::call_site(), "Expected a function"), |
| 114 | } |
| 115 | input |
| 116 | } |
| 117 | |
| 118 | /// Attribute which should never be encountered in real life. |
| 119 | /// This is something which features in the Rust source code generated |
| 120 | /// by autocxx-bindgen and passed to autocxx-engine, which should never |
| 121 | /// normally be compiled by rustc before it undergoes further processing. |
| 122 | #[proc_macro_error] |
| 123 | #[proc_macro_attribute] |
| 124 | pub fn cpp_semantics(_attr: TokenStream, _input: TokenStream) -> TokenStream { |
| 125 | abort!( |
| 126 | Span::call_site(), |
| 127 | "Please do not attempt to compile this code. \n\ |
| 128 | This code is the output from the autocxx-specific version of bindgen, \n\ |
| 129 | and should be interpreted by autocxx-engine before further usage." |
| 130 | ); |
| 131 | } |