Skip to main content

aranya_capi_codegen/syntax/
attrs.rs

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