refptr_macros/
lib.rs

1extern crate proc_macro;
2
3use proc_macro2::{Span, TokenStream};
4use quote::quote;
5use syn::{
6    parse_macro_input, parse_quote, AttributeArgs, Data, DataStruct, DeriveInput, Error, Field,
7    Fields, Type, Visibility,
8};
9
10macro_rules! fail {
11    ($ts:expr, $err:expr) => {
12        return Err(Error::new_spanned($ts, $err));
13    };
14}
15
16#[derive(Debug, Eq, PartialEq, Copy, Clone)]
17enum RcKind {
18    Atomic,
19    Local,
20}
21
22#[derive(Debug, Eq, PartialEq, Copy, Clone)]
23enum WeakKind {
24    NonWeak,
25    Weak,
26}
27
28#[derive(Debug, Eq, PartialEq, Copy, Clone)]
29enum FinalizeKind {
30    Finalize,
31    Pod,
32}
33
34#[derive(Debug)]
35struct Config {
36    rc_kind: RcKind,
37    weak_kind: WeakKind,
38    finalize_kind: FinalizeKind,
39}
40
41fn parse_config(args: AttributeArgs) -> Result<Config, Error> {
42    let mut rc_kind: Option<RcKind> = None;
43    let mut weak_kind = WeakKind::NonWeak;
44    let mut finalize_kind = FinalizeKind::Pod;
45
46    for arg in args {
47        use syn::{Meta::*, NestedMeta::*};
48        match arg {
49            Meta(Path(path)) if path.is_ident("atomic") => {
50                if rc_kind.is_some() {
51                    fail!(path, "duplicate atomicity argument");
52                }
53                rc_kind = Some(RcKind::Atomic);
54            }
55            Meta(Path(path)) if path.is_ident("local") => {
56                if rc_kind.is_some() {
57                    fail!(path, "duplicate atomicity argument");
58                }
59                rc_kind = Some(RcKind::Local);
60            }
61            Meta(Path(path)) if path.is_ident("weak") => {
62                if weak_kind == WeakKind::Weak {
63                    fail!(path, "duplicate weak argument");
64                }
65                weak_kind = WeakKind::Weak;
66            }
67            Meta(Path(path)) if path.is_ident("finalize") => {
68                if finalize_kind == FinalizeKind::Finalize {
69                    fail!(path, "duplicate finalize argument");
70                }
71                finalize_kind = FinalizeKind::Finalize;
72            }
73            meta => fail!(meta, "unexpected refcounted argument"),
74        }
75    }
76
77    if let Some(rc_kind) = rc_kind {
78        Ok(Config {
79            rc_kind,
80            weak_kind,
81            finalize_kind,
82        })
83    } else {
84        return Err(Error::new(
85            Span::call_site(),
86            "must specify either `local` or `atomic` atomicity",
87        ));
88    }
89}
90
91fn refcounted_impl(args: AttributeArgs, mut item: DeriveInput) -> Result<TokenStream, Error> {
92    let cfg = parse_config(args)?;
93
94    use {FinalizeKind::*, RcKind::*, WeakKind::*};
95    let rc_ty = match (cfg.rc_kind, cfg.weak_kind, cfg.finalize_kind) {
96        (Local, NonWeak, Pod) => quote!(Local),
97        (Atomic, NonWeak, Pod) => quote!(Atomic),
98        (Local, Weak, Pod) => quote!(LocalWeak),
99        (Atomic, Weak, Pod) => quote!(AtomicWeak),
100        (Local, NonWeak, Finalize) => quote!(LocalFinalize),
101        (Atomic, NonWeak, Finalize) => quote!(AtomicFinalize),
102        (Local, Weak, Finalize) => quote!(LocalWeakFinalize),
103        (Atomic, Weak, Finalize) => quote!(AtomicWeakFinalize),
104    };
105
106    // Add our refcnt field, and extract the original fields
107    match &mut item.data {
108        Data::Struct(DataStruct {
109            fields: Fields::Named(fields),
110            ..
111        }) => {
112            let ty: Type = match cfg.rc_kind {
113                RcKind::Atomic => parse_quote!(::refptr::__rt::PhantomAtomicRefcnt<Self>),
114                RcKind::Local => parse_quote!(::refptr::__rt::PhantomLocalRefcnt<Self>),
115            };
116
117            let rc_field = Field {
118                attrs: Vec::new(),
119                vis: Visibility::Inherited,
120                ident: parse_quote!(_refcnt_marker),
121                colon_token: Default::default(),
122                ty,
123            };
124            fields.named.insert(0, rc_field);
125        }
126        _ => fail!(
127            item,
128            "refcounted must be used on a struct with named fields"
129        ),
130    }
131
132    let ident = &item.ident;
133    let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
134
135    let metadata_func = match cfg.finalize_kind {
136        Pod => quote! {
137            unsafe fn refcount_metadata(&self) { }
138        },
139        Finalize => quote! {
140            unsafe fn refcount_metadata(&self) -> unsafe fn (*const u8) {
141                unsafe fn finalize_helper #impl_generics (this: *const u8) #where_clause {
142                    let _assert_finalize_type: fn (&#ident #ty_generics) =
143                        <#ident #ty_generics>::finalize;
144                    <#ident #ty_generics>::finalize(&*(this as *const #ident #ty_generics));
145                }
146                finalize_helper #ty_generics
147            }
148        },
149    };
150
151    let impl_refcounted = quote! {
152        unsafe impl #impl_generics ::refptr::Refcounted for #ident #ty_generics #where_clause {
153            type Rc = ::refptr::refcnt::#rc_ty;
154
155            #metadata_func
156        }
157    };
158
159    // Prevent any other implementations of `Drop`, as they can be unsound (due
160    // to being able to create a `RefPtr` to a dropped struct).
161    let impl_drop = quote! {
162        impl #impl_generics Drop for #ident #ty_generics #where_clause {
163            fn drop(&mut self) { /* no-op */ }
164        }
165    };
166
167    Ok(quote! {
168        #item
169        #impl_refcounted
170        #impl_drop
171    })
172}
173
174#[proc_macro_attribute]
175pub fn refcounted(
176    args: proc_macro::TokenStream,
177    input: proc_macro::TokenStream,
178) -> proc_macro::TokenStream {
179    let args = parse_macro_input!(args as AttributeArgs);
180    let input = parse_macro_input!(input as DeriveInput);
181
182    match refcounted_impl(args, input) {
183        Ok(ts) => ts.into(),
184        Err(e) => e.to_compile_error().into(),
185    }
186}