aranya_capi_codegen/syntax/
attrs.rs

1use std::fmt;
2
3use proc_macro2::{Span, TokenStream};
4use quote::{format_ident, quote, quote_spanned, ToTokens};
5use syn::{
6    parse::{Parse, ParseStream, Result},
7    spanned::Spanned,
8    AttrStyle, Attribute, Expr, Ident, Lit, LitStr, Meta,
9};
10use tracing::{debug, instrument};
11
12use super::{builds::Builds, derive::Derives, doc::Doc, opaque::Opaque, util::Trimmed};
13use crate::ctx::Ctx;
14
15mod kw {
16    use syn::custom_keyword;
17
18    custom_keyword!(Init);
19    custom_keyword!(Cleanup);
20    custom_keyword!(hidden);
21}
22
23/// Parses [`Attribute`]s.
24///
25/// Taken from [`cxx`].
26///
27/// [`cxx`]: https://github.com/dtolnay/cxx/blob/afd4aa3f3d4e5d5e9a3a41d09df3408f5f86a469/syntax/attrs.rs#L30
28#[derive(Default)]
29pub(crate) struct Parser<'a> {
30    /// `#[doc(...)]` or `#[doc = ...]`
31    pub doc: Option<&'a mut Doc>,
32    /// `#[repr(...)]`
33    pub repr: Option<&'a mut Option<Repr>>,
34    /// `#[derive(...)]`
35    pub derives: Option<&'a mut Derives>,
36    /// `#[unsafe(no_mangle)]`
37    pub no_mangle: Option<&'a mut Option<NoMangle>>,
38    /// `#[capi::builds(...)]`
39    pub capi_builds: Option<&'a mut Option<Builds>>,
40    /// `#[capi::opaque(...)]`
41    pub capi_opaque: Option<&'a mut Option<Opaque>>,
42    /// `#[capi::error]`
43    pub capi_error: Option<&'a mut Option<Error>>,
44    /// `#[capi::ext_error]`
45    pub capi_ext_error: Option<&'a mut Option<ExtError>>,
46    /// `#[capi::no_ext_error]`
47    pub capi_no_ext_error: Option<&'a mut Option<NoExtError>>,
48    /// `#[capi::generated]`
49    pub capi_generated: Option<&'a mut Option<Generated>>,
50}
51
52/// Parses attributes.
53///
54/// It returns the remaining "other" attributes.
55///
56/// # Example
57///
58/// ```ignore
59/// let mut doc = Doc::new();
60/// let mut repr = None;
61/// let mut derives = Derives::new();
62/// let mut error = None;
63/// let attrs = attrs::parse(
64///     ctx,
65///     e.attrs,
66///     Parser {
67///         doc: Some(&mut doc),
68///         repr: Some(&mut repr),
69///         derives: Some(&mut derives),
70///         capi_error: Some(&mut error),
71///         ..Default::default()
72///     },
73/// );
74/// ```
75#[instrument(skip_all)]
76pub(crate) fn parse(ctx: &Ctx, attrs: Vec<Attribute>, mut parser: Parser<'_>) -> Vec<Attribute> {
77    let n = attrs.len();
78
79    let mut passthru = Vec::new();
80    for (i, attr) in attrs.into_iter().enumerate() {
81        let path = attr.path();
82
83        debug!(
84            path = %Trimmed(path),
85            "parsing attr {}/{n}",
86            i.saturating_add(1)
87        );
88
89        // `#[aranya_capi_core::xxx]`
90        // `#[capi::xxx]`
91        if path.segments.len() == 2
92            && (path.segments[0].ident == ctx.capi
93                || path.segments[0].ident == "aranya_capi_core"
94                || path.segments[0].ident == "capi")
95        {
96            if !parse_capi_attr(ctx, &attr, &mut parser) {
97                passthru.push(attr);
98            }
99            continue;
100        }
101
102        // `#[derive(...)]`
103        if path.is_ident("derive") {
104            match attr.parse_args_with(|attr: ParseStream<'_>| Derives::parse(ctx, attr)) {
105                Ok(attrs) => {
106                    if let Some(derives) = &mut parser.derives {
107                        derives.append(attrs);
108                        continue;
109                    }
110                }
111                Err(err) => {
112                    ctx.push(err);
113                    break;
114                }
115            }
116        }
117
118        // `#[doc(...)]` or `#[doc = ...]`
119        if path.is_ident("doc") {
120            match parse_doc_attr(&attr.meta) {
121                Ok(attr) => {
122                    if let Some(doc) = &mut parser.doc {
123                        match attr {
124                            DocAttr::Doc(lit) => doc.push(lit),
125                            DocAttr::Hidden => doc.hidden = true,
126                        }
127                        continue;
128                    }
129                }
130                Err(err) => {
131                    ctx.push(err);
132                    break;
133                }
134            }
135        }
136
137        // `#[unsafe(...)]`
138        if path.is_ident("unsafe") {
139            let attr = match attr.parse_args::<Ident>() {
140                Ok(attr) => attr,
141                Err(err) => {
142                    ctx.push(err);
143                    break;
144                }
145            };
146
147            // `#[unsafe(no_mangle)]`
148            if attr == "no_mangle" {
149                if let Some(v) = &mut parser.no_mangle {
150                    **v = Some(NoMangle(attr.span()));
151                    continue;
152                }
153            }
154        }
155
156        // `#[repr(...)]`
157        if path.is_ident("repr") {
158            match attr.parse_args::<Repr>() {
159                Ok(attr) => {
160                    if let Some(v) = &mut parser.repr {
161                        **v = Some(attr);
162                        continue;
163                    }
164                }
165                Err(err) => {
166                    ctx.push(err);
167                    break;
168                }
169            }
170        }
171
172        passthru.push(attr);
173    }
174    passthru
175}
176
177/// Parses a `#[capi::xxx]` attribute.
178///
179/// Returns `false` if the attribute should be passed through.
180#[instrument(skip_all)]
181fn parse_capi_attr(ctx: &Ctx, attr: &Attribute, parser: &mut Parser<'_>) -> bool {
182    assert_eq!(attr.path().segments.len(), 2);
183    assert!(
184        attr.path().segments[0].ident == ctx.capi
185            || attr.path().segments[0].ident == "capi"
186            || attr.path().segments[0].ident == "aranya_capi_core"
187    );
188
189    let span = attr.span();
190    let ident = &attr.path().segments[1].ident;
191    if ident == "builds" {
192        match attr.parse_args_with(|attr: ParseStream<'_>| Builds::parse(attr)) {
193            Ok(builds) => {
194                if let Some(v) = &mut parser.capi_builds {
195                    **v = Some(builds);
196                    return true;
197                }
198            }
199            Err(err) => {
200                ctx.push(err);
201                return true;
202            }
203        }
204    } else if ident == "derive" {
205        match attr.parse_args_with(|attr: ParseStream<'_>| Derives::parse(ctx, attr)) {
206            Ok(attrs) => {
207                if let Some(derives) = &mut parser.derives {
208                    derives.append(attrs);
209                    return true;
210                }
211            }
212            Err(err) => {
213                ctx.push(err);
214                return true;
215            }
216        }
217    } else if ident == "error" {
218        if let Some(v) = &mut parser.capi_error {
219            **v = Some(Error(span));
220            return true;
221        }
222    } else if ident == "ext_error" {
223        if let Some(v) = &mut parser.capi_ext_error {
224            **v = Some(ExtError(span));
225            return true;
226        }
227    } else if ident == "generated" {
228        if let Some(v) = &mut parser.capi_generated {
229            **v = Some(Generated(span));
230            return true;
231        }
232    } else if ident == "opaque" {
233        match attr.parse_args_with(|attr: ParseStream<'_>| Opaque::parse(Some(ctx), attr)) {
234            Ok(attr) => {
235                if let Some(v) = &mut parser.capi_opaque {
236                    **v = Some(attr);
237                    return true;
238                }
239            }
240            Err(err) => {
241                ctx.push(err);
242                return false; // passthrough
243            }
244        }
245    } else if ident == "no_ext_error" {
246        if let Some(v) = &mut parser.capi_no_ext_error {
247            **v = Some(NoExtError(span));
248            return true;
249        }
250    } else {
251        ctx.error(ident, format!("unknown `capi::` attribute: {ident}"));
252        return true;
253    }
254
255    ctx.error(ident, "invalid `capi::` attribute for context");
256    true
257}
258
259/// A `#[doc]` attribute.
260enum DocAttr {
261    /// `#[doc = "..."]`.
262    Doc(LitStr),
263    /// `#[doc(hidden)]`
264    Hidden,
265}
266
267/// Taken from [`cxx`].
268///
269/// [`cxx`]: https://github.com/dtolnay/cxx/blob/afd4aa3f3d4e5d5e9a3a41d09df3408f5f86a469/syntax/attrs.rs#L196C1-L212C2
270fn parse_doc_attr(meta: &Meta) -> Result<DocAttr> {
271    match meta {
272        Meta::NameValue(meta) => {
273            if let Expr::Lit(expr) = &meta.value {
274                if let Lit::Str(lit) = &expr.lit {
275                    return Ok(DocAttr::Doc(lit.clone()));
276                }
277            }
278        }
279        Meta::List(meta) => {
280            meta.parse_args::<kw::hidden>()?;
281            return Ok(DocAttr::Hidden);
282        }
283        Meta::Path(_) => {}
284    }
285    Err(syn::Error::new_spanned(meta, "unsupported doc attribute"))
286}
287
288/// `#[repr(...)]`.
289#[derive(
290    Copy, Clone, Debug, Eq, PartialEq, strum::AsRefStr, strum::EnumString, strum::IntoStaticStr,
291)]
292#[strum(serialize_all = "snake_case")]
293pub enum Repr {
294    #[strum(serialize = "C")]
295    C,
296    Transparent,
297    U8,
298    U16,
299    U32,
300    U64,
301    U128,
302    Usize,
303    I8,
304    I16,
305    I32,
306    I64,
307    I128,
308    Isize,
309}
310
311impl Repr {
312    /// Returns the repr as a string.
313    pub fn to_str(self) -> &'static str {
314        self.into()
315    }
316}
317
318impl PartialEq<Repr> for &Ident {
319    fn eq(&self, repr: &Repr) -> bool {
320        *self == repr
321    }
322}
323
324impl fmt::Display for Repr {
325    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326        write!(f, "#[repr({})]", self.to_str())
327    }
328}
329
330impl Parse for Repr {
331    fn parse(input: ParseStream<'_>) -> Result<Repr> {
332        // Taken from [`cxx`].
333        // https://github.com/dtolnay/cxx/blob/afd4aa3f3d4e5d5e9a3a41d09df3408f5f86a469/syntax/attrs.rs#L230
334        let begin = input.cursor();
335        let ident = input.parse::<Ident>()?;
336        ident
337            .to_string()
338            .parse()
339            .map_err(|_| syn::Error::new_spanned(begin.token_stream(), "unrecognized repr"))
340    }
341}
342
343impl ToTokens for Repr {
344    fn to_tokens(&self, tokens: &mut TokenStream) {
345        let ident = format_ident!("{}", self.to_str());
346        tokens.extend(quote! {
347            #[repr(#ident)]
348        })
349    }
350}
351
352/// Extension trait for accessing attributes.
353pub trait AttrsExt {
354    /// Returns a shared ref to the attrs.
355    fn get(&self) -> &[Attribute];
356
357    /// Dumps all attributes.
358    #[allow(dead_code)] // for debugging
359    fn dump(&self) {
360        println!("found {} attrs:", self.get().len());
361        for attr in self.get() {
362            println!("\t{}", quote!(#attr));
363        }
364    }
365
366    /// Returns an iterator over the outer attributes.
367    fn outer(&self) -> impl Iterator<Item = &Attribute> {
368        fn is_outer(attr: &&Attribute) -> bool {
369            match attr.style {
370                AttrStyle::Outer => true,
371                AttrStyle::Inner(_) => false,
372            }
373        }
374        self.get().iter().filter(is_outer)
375    }
376
377    /// Returns an iterator over the inner attributes.
378    fn inner(&self) -> impl Iterator<Item = &Attribute> {
379        fn is_inner(attr: &&Attribute) -> bool {
380            match attr.style {
381                AttrStyle::Inner(_) => true,
382                AttrStyle::Outer => false,
383            }
384        }
385        self.get().iter().filter(is_inner)
386    }
387
388    /// Returns the `#[repr(...)]` attribute, if any.
389    fn repr(&self) -> Option<&Attribute> {
390        self.get().iter().find(|attr| attr.path().is_ident("repr"))
391    }
392}
393
394impl AttrsExt for Vec<Attribute> {
395    /// Returns a shared ref to the attrs.
396    fn get(&self) -> &[Attribute] {
397        self
398    }
399}
400
401macro_rules! simple_outer_attr {
402    ($name:ident, $value:literal) => {
403        #[doc = concat!("The `#[", $value, "]` attribute.")]
404        #[derive(Clone)]
405        pub(crate) struct $name(pub Span);
406
407        impl $name {
408            /// Creates an attribute with a specific span.
409            #[allow(dead_code)] // not always used
410            pub fn with_span(span: Span) -> Self {
411                Self(span)
412            }
413
414            #[allow(dead_code)] // not always used
415            fn parse(_ctx: &Ctx, input: ParseStream<'_>) -> Result<Self> {
416                Ok(Self(input.span()))
417            }
418        }
419
420        impl Eq for $name {}
421        impl PartialEq for $name {
422            fn eq(&self, _other: &Self) -> bool {
423                true
424            }
425        }
426
427        impl fmt::Display for $name {
428            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
429                write!(f, "{}", $value)
430            }
431        }
432
433        impl fmt::Debug for $name {
434            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
435                write!(f, "{}", $value)
436            }
437        }
438
439        impl TryFrom<Attribute> for $name {
440            type Error = syn::Error;
441            fn try_from(attr: Attribute) -> Result<Self> {
442                Ok(Self(attr.span()))
443            }
444        }
445
446        impl ToTokens for $name {
447            fn to_tokens(&self, tokens: &mut TokenStream) {
448                // TODO(eric): Avoid calling unwrawp.
449                let path = syn::parse_str::<Meta>($value).unwrap();
450                tokens.extend(quote_spanned! {self.0=>
451                    #[#path]
452                })
453            }
454        }
455    };
456}
457simple_outer_attr!(Error, "capi::error");
458simple_outer_attr!(ExtError, "capi::ext_error");
459simple_outer_attr!(Generated, "capi::generated");
460simple_outer_attr!(NoExtError, "capi::no_ext_error");
461simple_outer_attr!(NoMangle, "unsafe(no_mangle)");