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
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![forbid(unsafe_code)]

use autocxx_parser::{IncludeCpp, SubclassAttrs};
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use proc_macro_error::{abort, proc_macro_error};
use quote::quote;
use syn::parse::Parser;
use syn::{parse_macro_input, parse_quote, Fields, Item, ItemStruct, Visibility};

/// Implementation of the `include_cpp` macro. See documentation for `autocxx` crate.
#[proc_macro_error]
#[proc_macro]
pub fn include_cpp_impl(input: TokenStream) -> TokenStream {
    let include_cpp = parse_macro_input!(input as IncludeCpp);
    TokenStream::from(include_cpp.generate_rs())
}

/// Attribute to state that a Rust `struct` is a C++ subclass.
/// This adds an additional field to the struct which autocxx uses to
/// track a C++ instantiation of this Rust subclass.
#[proc_macro_error]
#[proc_macro_attribute]
pub fn subclass(attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut s: ItemStruct =
        syn::parse(item).unwrap_or_else(|_| abort!(Span::call_site(), "Expected a struct"));
    if !matches!(s.vis, Visibility::Public(..)) {
        use syn::spanned::Spanned;
        abort!(s.vis.span(), "Rust subclasses of C++ types must by public");
    }
    let id = &s.ident;
    let cpp_ident = Ident::new(&format!("{id}Cpp"), Span::call_site());
    let input = quote! {
        cpp_peer: autocxx::subclass::CppSubclassCppPeerHolder<ffi:: #cpp_ident>
    };
    let parser = syn::Field::parse_named;
    let new_field = parser.parse2(input).unwrap();
    s.fields = match &mut s.fields {
        Fields::Named(fields) => {
            fields.named.push(new_field);
            s.fields
        },
        Fields::Unit => Fields::Named(parse_quote! {
            {
                #new_field
            }
        }),
        _ => abort!(Span::call_site(), "Expect a struct with named fields - use struct A{} or struct A; as opposed to struct A()"),
    };
    let subclass_attrs: SubclassAttrs = syn::parse(attr)
        .unwrap_or_else(|_| abort!(Span::call_site(), "Unable to parse attributes"));
    let self_owned_bit = if subclass_attrs.self_owned {
        Some(quote! {
            impl autocxx::subclass::CppSubclassSelfOwned<ffi::#cpp_ident> for #id {}
        })
    } else {
        None
    };
    let toks = quote! {
        #s

        impl autocxx::subclass::CppSubclass<ffi::#cpp_ident> for #id {
            fn peer_holder_mut(&mut self) -> &mut autocxx::subclass::CppSubclassCppPeerHolder<ffi::#cpp_ident> {
                &mut self.cpp_peer
            }
            fn peer_holder(&self) -> &autocxx::subclass::CppSubclassCppPeerHolder<ffi::#cpp_ident> {
                &self.cpp_peer
            }
        }

        #self_owned_bit
    };
    toks.into()
}

/// Attribute to state that a Rust type is to be exported to C++
/// in the `extern "Rust"` section of the generated `cxx` bindings.
#[proc_macro_error]
#[proc_macro_attribute]
pub fn extern_rust_type(attr: TokenStream, input: TokenStream) -> TokenStream {
    if !attr.is_empty() {
        abort!(Span::call_site(), "Expected no attributes");
    }
    let i: Item =
        syn::parse(input.clone()).unwrap_or_else(|_| abort!(Span::call_site(), "Expected an item"));
    match i {
        Item::Struct(..) | Item::Enum(..) | Item::Fn(..) => {}
        _ => abort!(Span::call_site(), "Expected a struct or enum"),
    }
    input
}

/// Attribute to state that a Rust function is to be exported to C++
/// in the `extern "Rust"` section of the generated `cxx` bindings.
#[proc_macro_error]
#[proc_macro_attribute]
pub fn extern_rust_function(attr: TokenStream, input: TokenStream) -> TokenStream {
    if !attr.is_empty() {
        abort!(Span::call_site(), "Expected no attributes");
    }
    let i: Item =
        syn::parse(input.clone()).unwrap_or_else(|_| abort!(Span::call_site(), "Expected an item"));
    match i {
        Item::Fn(..) => {}
        _ => abort!(Span::call_site(), "Expected a function"),
    }
    input
}

/// Attribute which should never be encountered in real life.
/// This is something which features in the Rust source code generated
/// by autocxx-bindgen and passed to autocxx-engine, which should never
/// normally be compiled by rustc before it undergoes further processing.
#[proc_macro_error]
#[proc_macro_attribute]
pub fn cpp_semantics(_attr: TokenStream, _input: TokenStream) -> TokenStream {
    abort!(
        Span::call_site(),
        "Please do not attempt to compile this code. \n\
        This code is the output from the autocxx-specific version of bindgen, \n\
        and should be interpreted by autocxx-engine before further usage."
    );
}