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, Path,
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    /// `#[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        // `#[no_mangle]`
138        if path.is_ident("no_mangle") {
139            if let Some(v) = &mut parser.no_mangle {
140                **v = Some(NoMangle(attr.span()));
141                continue;
142            }
143        }
144
145        // `#[repr(...)]`
146        if path.is_ident("repr") {
147            match attr.parse_args::<Repr>() {
148                Ok(attr) => {
149                    if let Some(v) = &mut parser.repr {
150                        **v = Some(attr);
151                        continue;
152                    }
153                }
154                Err(err) => {
155                    ctx.push(err);
156                    break;
157                }
158            }
159        }
160
161        passthru.push(attr);
162    }
163    passthru
164}
165
166/// Parses a `#[capi::xxx]` attribute.
167///
168/// Returns `false` if the attribute should be passed through.
169#[instrument(skip_all)]
170fn parse_capi_attr(ctx: &Ctx, attr: &Attribute, parser: &mut Parser<'_>) -> bool {
171    assert_eq!(attr.path().segments.len(), 2);
172    assert!(
173        attr.path().segments[0].ident == ctx.capi
174            || attr.path().segments[0].ident == "capi"
175            || attr.path().segments[0].ident == "aranya_capi_core"
176    );
177
178    let span = attr.span();
179    let ident = &attr.path().segments[1].ident;
180    if ident == "builds" {
181        match attr.parse_args_with(|attr: ParseStream<'_>| Builds::parse(attr)) {
182            Ok(builds) => {
183                if let Some(v) = &mut parser.capi_builds {
184                    **v = Some(builds);
185                    return true;
186                }
187            }
188            Err(err) => {
189                ctx.push(err);
190                return true;
191            }
192        }
193    } else if ident == "derive" {
194        match attr.parse_args_with(|attr: ParseStream<'_>| Derives::parse(ctx, attr)) {
195            Ok(attrs) => {
196                if let Some(derives) = &mut parser.derives {
197                    derives.append(attrs);
198                    return true;
199                }
200            }
201            Err(err) => {
202                ctx.push(err);
203                return true;
204            }
205        }
206    } else if ident == "error" {
207        if let Some(v) = &mut parser.capi_error {
208            **v = Some(Error(span));
209            return true;
210        }
211    } else if ident == "ext_error" {
212        if let Some(v) = &mut parser.capi_ext_error {
213            **v = Some(ExtError(span));
214            return true;
215        }
216    } else if ident == "generated" {
217        if let Some(v) = &mut parser.capi_generated {
218            **v = Some(Generated(span));
219            return true;
220        }
221    } else if ident == "opaque" {
222        match attr.parse_args_with(|attr: ParseStream<'_>| Opaque::parse(Some(ctx), attr)) {
223            Ok(attr) => {
224                if let Some(v) = &mut parser.capi_opaque {
225                    **v = Some(attr);
226                    return false; // passthrough
227                }
228            }
229            Err(err) => {
230                ctx.push(err);
231                return false; // passthrough
232            }
233        }
234    } else if ident == "no_ext_error" {
235        if let Some(v) = &mut parser.capi_no_ext_error {
236            **v = Some(NoExtError(span));
237            return true;
238        }
239    } else {
240        ctx.error(ident, format!("unknown `capi::` attribute: {ident}"));
241        return true;
242    }
243
244    ctx.error(ident, "invalid `capi::` attribute for context");
245    true
246}
247
248/// A `#[doc]` attribute.
249enum DocAttr {
250    /// `#[doc = "..."]`.
251    Doc(LitStr),
252    /// `#[doc(hidden)]`
253    Hidden,
254}
255
256/// Taken from [`cxx`].
257///
258/// [`cxx`]: https://github.com/dtolnay/cxx/blob/afd4aa3f3d4e5d5e9a3a41d09df3408f5f86a469/syntax/attrs.rs#L196C1-L212C2
259fn parse_doc_attr(meta: &Meta) -> Result<DocAttr> {
260    match meta {
261        Meta::NameValue(meta) => {
262            if let Expr::Lit(expr) = &meta.value {
263                if let Lit::Str(lit) = &expr.lit {
264                    return Ok(DocAttr::Doc(lit.clone()));
265                }
266            }
267        }
268        Meta::List(meta) => {
269            meta.parse_args::<kw::hidden>()?;
270            return Ok(DocAttr::Hidden);
271        }
272        Meta::Path(_) => {}
273    }
274    Err(syn::Error::new_spanned(meta, "unsupported doc attribute"))
275}
276
277/// `#[repr(...)]`.
278#[derive(
279    Copy, Clone, Debug, Eq, PartialEq, strum::AsRefStr, strum::EnumString, strum::IntoStaticStr,
280)]
281#[strum(serialize_all = "snake_case")]
282pub enum Repr {
283    #[strum(serialize = "C")]
284    C,
285    Transparent,
286    U8,
287    U16,
288    U32,
289    U64,
290    U128,
291    Usize,
292    I8,
293    I16,
294    I32,
295    I64,
296    I128,
297    Isize,
298}
299
300impl Repr {
301    /// Returns the repr as a string.
302    pub fn to_str(self) -> &'static str {
303        self.into()
304    }
305}
306
307impl PartialEq<Repr> for &Ident {
308    fn eq(&self, repr: &Repr) -> bool {
309        *self == repr
310    }
311}
312
313impl fmt::Display for Repr {
314    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315        write!(f, "#[repr({})]", self.to_str())
316    }
317}
318
319impl Parse for Repr {
320    fn parse(input: ParseStream<'_>) -> Result<Repr> {
321        // Taken from [`cxx`].
322        // https://github.com/dtolnay/cxx/blob/afd4aa3f3d4e5d5e9a3a41d09df3408f5f86a469/syntax/attrs.rs#L230
323        let begin = input.cursor();
324        let ident = input.parse::<Ident>()?;
325        ident
326            .to_string()
327            .parse()
328            .map_err(|_| syn::Error::new_spanned(begin.token_stream(), "unrecognized repr"))
329    }
330}
331
332impl ToTokens for Repr {
333    fn to_tokens(&self, tokens: &mut TokenStream) {
334        let ident = format_ident!("{}", self.to_str());
335        tokens.extend(quote! {
336            #[repr(#ident)]
337        })
338    }
339}
340
341/// Extension trait for accessing attributes.
342pub trait AttrsExt {
343    /// Returns a shared ref to the attrs.
344    fn get(&self) -> &[Attribute];
345
346    /// Dumps all attributes.
347    #[allow(dead_code)] // for debugging
348    fn dump(&self) {
349        println!("found {} attrs:", self.get().len());
350        for attr in self.get() {
351            println!("\t{}", quote!(#attr));
352        }
353    }
354
355    /// Returns an iterator over the outer attributes.
356    fn outer(&self) -> impl Iterator<Item = &Attribute> {
357        fn is_outer(attr: &&Attribute) -> bool {
358            match attr.style {
359                AttrStyle::Outer => true,
360                AttrStyle::Inner(_) => false,
361            }
362        }
363        self.get().iter().filter(is_outer)
364    }
365
366    /// Returns an iterator over the inner attributes.
367    fn inner(&self) -> impl Iterator<Item = &Attribute> {
368        fn is_inner(attr: &&Attribute) -> bool {
369            match attr.style {
370                AttrStyle::Inner(_) => true,
371                AttrStyle::Outer => false,
372            }
373        }
374        self.get().iter().filter(is_inner)
375    }
376
377    /// Returns the `#[repr(...)]` attribute, if any.
378    fn repr(&self) -> Option<&Attribute> {
379        self.get().iter().find(|attr| attr.path().is_ident("repr"))
380    }
381}
382
383impl AttrsExt for Vec<Attribute> {
384    /// Returns a shared ref to the attrs.
385    fn get(&self) -> &[Attribute] {
386        self
387    }
388}
389
390macro_rules! simple_outer_attr {
391    ($name:ident, $value:literal) => {
392        #[doc = concat!("The `#[", $value, "]` attribute.")]
393        #[derive(Clone)]
394        pub(crate) struct $name(pub Span);
395
396        impl $name {
397            /// Creates an attribute with a specific span.
398            #[allow(dead_code)] // not always used
399            pub fn with_span(span: Span) -> Self {
400                Self(span)
401            }
402
403            #[allow(dead_code)] // not always used
404            fn parse(_ctx: &Ctx, input: ParseStream<'_>) -> Result<Self> {
405                Ok(Self(input.span()))
406            }
407        }
408
409        impl Eq for $name {}
410        impl PartialEq for $name {
411            fn eq(&self, _other: &Self) -> bool {
412                true
413            }
414        }
415
416        impl fmt::Display for $name {
417            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418                write!(f, "{}", $value)
419            }
420        }
421
422        impl fmt::Debug for $name {
423            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424                write!(f, "{}", $value)
425            }
426        }
427
428        impl TryFrom<Attribute> for $name {
429            type Error = syn::Error;
430            fn try_from(attr: Attribute) -> Result<Self> {
431                Ok(Self(attr.span()))
432            }
433        }
434
435        impl ToTokens for $name {
436            fn to_tokens(&self, tokens: &mut TokenStream) {
437                // TODO(eric): Avoid calling unwrawp.
438                let path = syn::parse_str::<Path>($value).unwrap();
439                tokens.extend(quote_spanned! {self.0=>
440                    #[#path]
441                })
442            }
443        }
444    };
445}
446simple_outer_attr!(Error, "capi::error");
447simple_outer_attr!(ExtError, "capi::ext_error");
448simple_outer_attr!(Generated, "capi::generated");
449simple_outer_attr!(NoExtError, "capi::no_ext_error");
450simple_outer_attr!(NoMangle, "no_mangle");