aranya_capi_codegen/syntax/
opaque.rs

1use proc_macro2::TokenStream;
2use quote::{quote, ToTokens};
3use syn::{
4    parse::{Parse, ParseStream, Result},
5    Error, Ident, LitBool, LitInt,
6};
7
8use crate::{
9    attr::{Attr, Symbol},
10    ctx::Ctx,
11    util::KeyValPair,
12};
13
14/// The `#[capi::opaque(size = 42, align = 12)]` attribute.
15///
16/// It can only be applied to aliases.
17#[derive(Clone, Debug, Eq, PartialEq)]
18pub struct Opaque {
19    /// The size in bytes of the type.
20    pub size: LitInt,
21    /// The alignment in bytes of the type.
22    pub align: LitInt,
23    /// Path to the `capi` crate.
24    pub capi: Option<Ident>,
25    /// Whether this attribute is in generated code.
26    ///
27    /// Will be enabled on generated aliases.
28    /// Determines whether to wrap in `Opaque` or apply `cfg(cbindgen)` def.
29    pub generated: bool,
30}
31
32impl Opaque {
33    pub(super) fn parse(ctx: Option<&Ctx>, input: ParseStream<'_>) -> Result<Self> {
34        mod kw {
35            syn::custom_keyword!(size);
36            syn::custom_keyword!(align);
37            syn::custom_keyword!(capi);
38            syn::custom_keyword!(generated);
39        }
40        const SIZE: Symbol = Symbol("size");
41        const ALIGN: Symbol = Symbol("align");
42        const CAPI: Symbol = Symbol("capi");
43        const GENERATED: Symbol = Symbol("generated");
44
45        let mut size = Attr::none(ALIGN);
46        let mut align = Attr::none(SIZE);
47        let mut capi = Attr::none(CAPI);
48        let mut generated = Attr::none(GENERATED);
49
50        while !input.is_empty() {
51            let lookahead = input.lookahead1();
52            if lookahead.peek(kw::size) {
53                let KeyValPair { key, val } = input.parse::<KeyValPair<kw::size, LitInt>>()?;
54                size.set(key, val)?;
55            } else if lookahead.peek(kw::align) {
56                let KeyValPair { key, val } = input.parse::<KeyValPair<kw::align, LitInt>>()?;
57                align.set(key, val)?;
58            } else if lookahead.peek(kw::capi) {
59                let KeyValPair { key, val } = input.parse::<KeyValPair<kw::capi, Ident>>()?;
60                capi.set(key, val)?;
61            } else if lookahead.peek(kw::generated) {
62                let KeyValPair { key, val } =
63                    input.parse::<KeyValPair<kw::generated, LitBool>>()?;
64                generated.set(key, val)?;
65            } else {
66                return Err(lookahead.error());
67            }
68        }
69
70        let size = size.get().ok_or(Error::new(
71            input.span(),
72            format!("missing `{SIZE}` argument"),
73        ))?;
74        let align = align.get().ok_or(Error::new(
75            input.span(),
76            format!("missing `{ALIGN}` argument"),
77        ))?;
78        let capi = capi.get().or_else(|| ctx.map(|ctx| ctx.capi.clone()));
79        let generated = generated.get().is_some_and(|a| a.value);
80        Ok(Self {
81            size,
82            align,
83            capi,
84            generated,
85        })
86    }
87}
88
89impl Parse for Opaque {
90    fn parse(input: ParseStream<'_>) -> Result<Self> {
91        Self::parse(None, input)
92    }
93}
94
95impl ToTokens for Opaque {
96    fn to_tokens(&self, tokens: &mut TokenStream) {
97        let size = &self.size;
98        let align = &self.align;
99        let capi = &self.capi;
100        let generated = self.generated;
101        // TODO(eric): `capi`?
102        tokens.extend(quote! {
103            #[#capi::opaque(size = #size, align = #align, generated = #generated)]
104        })
105    }
106}