diplomat_core/ast/
attrs.rs

1//! This module contains utilities for dealing with Rust attributes
2
3use serde::ser::{SerializeStruct, Serializer};
4use serde::Serialize;
5use std::borrow::Cow;
6use std::convert::Infallible;
7use std::str::FromStr;
8use syn::parse::{Error as ParseError, Parse, ParseStream};
9use syn::{Attribute, Expr, Ident, Lit, LitStr, Meta, MetaList, Token};
10
11/// The list of attributes on a type. All attributes except `attrs` (HIR attrs) are
12/// potentially read by the diplomat macro and the AST backends, anything that is not should
13/// be added as an HIR attribute ([`crate::hir::Attrs`]).
14///
15/// # Inheritance
16///
17/// Attributes are typically "inherited": the attributes on a module
18/// apply to all types and methods with it, the attributes on an impl apply to all
19/// methods in it, and the attributes on an enum apply to all variants within it.
20/// This allows the user to specify a single attribute to affect multiple fields.
21///
22/// However, the details of inheritance are not always the same for each attribute. For example, rename attributes
23/// on a module only apply to the types within it (others methods would get doubly renamed).
24///
25/// Each attribute here documents its inheritance behavior. Note that the HIR attributes do not get inherited
26/// during AST construction, since at that time it's unclear which of those attributes are actually available.
27#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)]
28#[non_exhaustive]
29pub struct Attrs {
30    /// The regular #[cfg()] attributes. Inherited, though the inheritance onto methods is the
31    /// only relevant one here.
32    pub cfg: Vec<Attribute>,
33    /// HIR backend attributes.
34    ///
35    /// Inherited, but only during lowering. See [`crate::hir::Attrs`] for details on which HIR attributes are inherited.
36    ///
37    /// During AST attribute inheritance, HIR backend attributes are copied over from impls to their methods since the HIR does
38    /// not see the impl blocks.
39    pub attrs: Vec<DiplomatBackendAttr>,
40    /// AST backends only. For using features that may panic AST backends, like returning references.
41    ///
42    /// This isn't a regular attribute since AST backends do not handle regular attributes. Do not use
43    /// in HIR backends,
44    ///
45    /// Not inherited
46    pub skip_if_ast: bool,
47
48    /// Renames to apply to the underlying C symbol. Can be found on methods, impls, and bridge modules, and is inherited.
49    ///
50    /// Affects method names when inherited onto methods.
51    ///
52    /// Affects destructor names when inherited onto types.
53    ///
54    /// Inherited.
55    pub abi_rename: RenameAttr,
56}
57
58impl Attrs {
59    fn add_attr(&mut self, attr: Attr) {
60        match attr {
61            Attr::Cfg(attr) => self.cfg.push(attr),
62            Attr::DiplomatBackend(attr) => self.attrs.push(attr),
63            Attr::SkipIfAst => self.skip_if_ast = true,
64            Attr::CRename(rename) => self.abi_rename.extend(&rename),
65        }
66    }
67
68    /// Get a copy of these attributes for use in inheritance, masking out things
69    /// that should not be inherited
70    pub(crate) fn attrs_for_inheritance(&self, context: AttrInheritContext) -> Self {
71        // These attributes are inherited during lowering (since that's when they're parsed)
72        //
73        // Except for impls: lowering has no concept of impls so these get inherited early. This
74        // is fine since impls have no inherent behavior and all attributes on impls are necessarily
75        // only there for inheritance
76        let attrs = if context == AttrInheritContext::MethodFromImpl {
77            self.attrs.clone()
78        } else {
79            Vec::new()
80        };
81
82        let abi_rename = self.abi_rename.attrs_for_inheritance(context, true);
83        Self {
84            cfg: self.cfg.clone(),
85
86            attrs,
87            // HIR only, for methods only. not inherited
88            skip_if_ast: false,
89            abi_rename,
90        }
91    }
92
93    pub(crate) fn add_attrs(&mut self, attrs: &[Attribute]) {
94        for attr in syn_attr_to_ast_attr(attrs) {
95            self.add_attr(attr)
96        }
97    }
98    pub(crate) fn from_attrs(attrs: &[Attribute]) -> Self {
99        let mut this = Self::default();
100        this.add_attrs(attrs);
101        this
102    }
103}
104
105impl From<&[Attribute]> for Attrs {
106    fn from(other: &[Attribute]) -> Self {
107        Self::from_attrs(other)
108    }
109}
110
111enum Attr {
112    Cfg(Attribute),
113    DiplomatBackend(DiplomatBackendAttr),
114    SkipIfAst,
115    CRename(RenameAttr),
116    // More goes here
117}
118
119fn syn_attr_to_ast_attr(attrs: &[Attribute]) -> impl Iterator<Item = Attr> + '_ {
120    let cfg_path: syn::Path = syn::parse_str("cfg").unwrap();
121    let dattr_path: syn::Path = syn::parse_str("diplomat::attr").unwrap();
122    let crename_attr: syn::Path = syn::parse_str("diplomat::abi_rename").unwrap();
123    let skipast: syn::Path = syn::parse_str("diplomat::skip_if_ast").unwrap();
124    attrs.iter().filter_map(move |a| {
125        if a.path() == &cfg_path {
126            Some(Attr::Cfg(a.clone()))
127        } else if a.path() == &dattr_path {
128            Some(Attr::DiplomatBackend(
129                a.parse_args()
130                    .expect("Failed to parse malformed diplomat::attr"),
131            ))
132        } else if a.path() == &crename_attr {
133            Some(Attr::CRename(RenameAttr::from_meta(&a.meta).unwrap()))
134        } else if a.path() == &skipast {
135            Some(Attr::SkipIfAst)
136        } else {
137            None
138        }
139    })
140}
141
142impl Serialize for Attrs {
143    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
144    where
145        S: Serializer,
146    {
147        // 1 is the number of fields in the struct.
148        let mut state = serializer.serialize_struct("Attrs", 1)?;
149        if !self.cfg.is_empty() {
150            let cfg: Vec<_> = self
151                .cfg
152                .iter()
153                .map(|a| quote::quote!(#a).to_string())
154                .collect();
155            state.serialize_field("cfg", &cfg)?;
156        }
157        if !self.attrs.is_empty() {
158            state.serialize_field("attrs", &self.attrs)?;
159        }
160        if self.skip_if_ast {
161            state.serialize_field("skip_if_ast", &self.skip_if_ast)?;
162        }
163        if !self.abi_rename.is_empty() {
164            state.serialize_field("abi_rename", &self.abi_rename)?;
165        }
166        state.end()
167    }
168}
169
170/// A `#[diplomat::attr(...)]` attribute
171///
172/// Its contents must start with single element that is a CFG-expression
173/// (so it may contain `foo = bar`, `foo = "bar"`, `ident`, `*` atoms,
174/// and `all()`, `not()`, and `any()` combiners), and then be followed by one
175/// or more backend-specific attributes, which can be any valid meta-item
176#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize)]
177#[non_exhaustive]
178pub struct DiplomatBackendAttr {
179    pub cfg: DiplomatBackendAttrCfg,
180    #[serde(serialize_with = "serialize_meta")]
181    pub meta: Meta,
182}
183
184fn serialize_meta<S>(m: &Meta, s: S) -> Result<S::Ok, S::Error>
185where
186    S: Serializer,
187{
188    quote::quote!(#m).to_string().serialize(s)
189}
190
191#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize)]
192#[non_exhaustive]
193pub enum DiplomatBackendAttrCfg {
194    Not(Box<DiplomatBackendAttrCfg>),
195    Any(Vec<DiplomatBackendAttrCfg>),
196    All(Vec<DiplomatBackendAttrCfg>),
197    Star,
198    BackendName(String),
199    NameValue(String, String),
200}
201
202impl Parse for DiplomatBackendAttrCfg {
203    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
204        let lookahead = input.lookahead1();
205        if lookahead.peek(Ident) {
206            let name: Ident = input.parse()?;
207            if name == "not" {
208                let content;
209                let _paren = syn::parenthesized!(content in input);
210                Ok(DiplomatBackendAttrCfg::Not(Box::new(content.parse()?)))
211            } else if name == "any" || name == "all" {
212                let content;
213                let _paren = syn::parenthesized!(content in input);
214                let list = content.parse_terminated(Self::parse, Token![,])?;
215                let vec = list.into_iter().collect();
216                if name == "any" {
217                    Ok(DiplomatBackendAttrCfg::Any(vec))
218                } else {
219                    Ok(DiplomatBackendAttrCfg::All(vec))
220                }
221            } else if input.peek(Token![=]) {
222                let _t: Token![=] = input.parse()?;
223                if input.peek(Ident) {
224                    let value: Ident = input.parse()?;
225                    Ok(DiplomatBackendAttrCfg::NameValue(
226                        name.to_string(),
227                        value.to_string(),
228                    ))
229                } else {
230                    let value: LitStr = input.parse()?;
231                    Ok(DiplomatBackendAttrCfg::NameValue(
232                        name.to_string(),
233                        value.value(),
234                    ))
235                }
236            } else {
237                Ok(DiplomatBackendAttrCfg::BackendName(name.to_string()))
238            }
239        } else if lookahead.peek(Token![*]) {
240            let _t: Token![*] = input.parse()?;
241            Ok(DiplomatBackendAttrCfg::Star)
242        } else {
243            Err(ParseError::new(
244                input.span(),
245                "CFG portion of #[diplomat::attr] fails to parse",
246            ))
247        }
248    }
249}
250
251/// Meant to be used with Attribute::parse_args()
252impl Parse for DiplomatBackendAttr {
253    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
254        let cfg = input.parse()?;
255        let _comma: Token![,] = input.parse()?;
256        let meta = input.parse()?;
257        Ok(Self { cfg, meta })
258    }
259}
260
261#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
262pub(crate) enum AttrInheritContext {
263    Variant,
264    Type,
265    /// When a method or an impl is inheriting from a module
266    MethodOrImplFromModule,
267    /// When a method is inheriting from an impl
268    ///
269    /// This distinction is made because HIR attributes are pre-inherited from the impl to the
270    /// method, so the boundary of "method inheriting from module" is different
271    MethodFromImpl,
272    // Currently there's no way to feed an attribute to a Module, but such inheritance will
273    // likely apply during lowering for config defaults.
274    #[allow(unused)]
275    Module,
276}
277
278/// A pattern for use in rename attributes, like `#[diplomat::abi_rename]`
279///
280/// This can be parsed from a string, typically something like `icu4x_{0}`.
281/// It can have up to one {0} for replacement.
282///
283/// In the future this may support transformations like to_camel_case, etc,
284/// probably specified as a list like `#[diplomat::abi_rename("foo{0}", to_camel_case)]`
285#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize)]
286pub struct RenameAttr {
287    pattern: Option<RenamePattern>,
288}
289
290impl RenameAttr {
291    /// Apply all renames to a given string
292    pub fn apply<'a>(&'a self, name: Cow<'a, str>) -> Cow<'a, str> {
293        if let Some(ref pattern) = self.pattern {
294            let replacement = &pattern.replacement;
295            if let Some(index) = pattern.insertion_index {
296                format!("{}{name}{}", &replacement[..index], &replacement[index..]).into()
297            } else {
298                replacement.into()
299            }
300        } else {
301            name
302        }
303    }
304
305    /// Whether this rename is empty and will perform no changes
306    fn is_empty(&self) -> bool {
307        self.pattern.is_none()
308    }
309
310    pub(crate) fn extend(&mut self, other: &Self) {
311        if other.pattern.is_some() {
312            self.pattern.clone_from(&other.pattern);
313        }
314
315        // In the future if we support things like to_lower_case they may inherit separately
316        // from patterns.
317    }
318
319    /// Get a copy of these attributes for use in inheritance, masking out things
320    /// that should not be inherited
321    pub(crate) fn attrs_for_inheritance(
322        &self,
323        context: AttrInheritContext,
324        is_abi_rename: bool,
325    ) -> Self {
326        let pattern = match context {
327            // No inheritance from modules to method-likes for the rename attribute
328            AttrInheritContext::MethodOrImplFromModule if !is_abi_rename => Default::default(),
329            // No effect on variants
330            AttrInheritContext::Variant => Default::default(),
331            _ => self.pattern.clone(),
332        };
333        // In the future if we support things like to_lower_case they may inherit separately
334        // from patterns.
335        Self { pattern }
336    }
337
338    /// From a replacement pattern, like "icu4x_{0}". Can have up to one {0} in it for substitution.
339    fn from_pattern(s: &str) -> Self {
340        Self {
341            pattern: Some(s.parse().unwrap()),
342        }
343    }
344
345    pub(crate) fn from_meta(meta: &Meta) -> Result<Self, &'static str> {
346        let attr = StandardAttribute::from_meta(meta)
347            .map_err(|_| "#[diplomat::abi_rename] must be given a string value")?;
348
349        match attr {
350            StandardAttribute::String(s) => Ok(RenameAttr::from_pattern(&s)),
351            StandardAttribute::List(_) => {
352                Err("Failed to parse malformed #[diplomat::abi_rename(...)]: found list")
353            }
354            StandardAttribute::Empty => {
355                Err("Failed to parse malformed #[diplomat::abi_rename(...)]: found no parameters")
356            }
357        }
358    }
359}
360
361impl FromStr for RenamePattern {
362    type Err = Infallible;
363    fn from_str(s: &str) -> Result<Self, Infallible> {
364        if let Some(index) = s.find("{0}") {
365            let replacement = format!("{}{}", &s[..index], &s[index + 3..]);
366            Ok(Self {
367                replacement,
368                insertion_index: Some(index),
369            })
370        } else {
371            Ok(Self {
372                replacement: s.into(),
373                insertion_index: None,
374            })
375        }
376    }
377}
378
379#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize)]
380struct RenamePattern {
381    /// The string to replace with
382    replacement: String,
383    /// The index in `replacement` in which to insert the original string. If None,
384    /// this is a pure rename
385    insertion_index: Option<usize>,
386}
387
388/// Helper type for parsing standard attributes. A standard attribute typically will accept the forms:
389///
390/// - `#[attr = "foo"]` and `#[attr("foo")]` for a simple string
391/// - `#[attr(....)]` for a more complicated context
392/// - `#[attr]` for a "defaulting" context
393///
394/// This allows attributes to parse simple string values without caring too much about the NameValue vs List representation
395/// and then attributes can choose to handle more complicated lists if they so desire.
396pub(crate) enum StandardAttribute<'a> {
397    String(String),
398    List(#[allow(dead_code)] &'a MetaList),
399    Empty,
400}
401
402impl<'a> StandardAttribute<'a> {
403    /// Parse from a Meta. Returns an error when no string value is specified in the path/namevalue forms.
404    pub(crate) fn from_meta(meta: &'a Meta) -> Result<Self, ()> {
405        match meta {
406            Meta::Path(..) => Ok(Self::Empty),
407            Meta::NameValue(ref nv) => {
408                // Support a shortcut `abi_rename = "..."`
409                let Expr::Lit(ref lit) = nv.value else {
410                    return Err(());
411                };
412                let Lit::Str(ref lit) = lit.lit else {
413                    return Err(());
414                };
415                Ok(Self::String(lit.value()))
416            }
417            // The full syntax to which we'll add more things in the future, `abi_rename("")`
418            Meta::List(list) => {
419                if let Ok(lit) = list.parse_args::<LitStr>() {
420                    Ok(Self::String(lit.value()))
421                } else {
422                    Ok(Self::List(list))
423                }
424            }
425        }
426    }
427}
428
429#[cfg(test)]
430mod tests {
431    use insta;
432
433    use syn;
434
435    use super::{DiplomatBackendAttr, DiplomatBackendAttrCfg, RenameAttr};
436
437    #[test]
438    fn test_cfgs() {
439        let attr_cfg: DiplomatBackendAttrCfg = syn::parse_quote!(*);
440        insta::assert_yaml_snapshot!(attr_cfg);
441        let attr_cfg: DiplomatBackendAttrCfg = syn::parse_quote!(cpp);
442        insta::assert_yaml_snapshot!(attr_cfg);
443        let attr_cfg: DiplomatBackendAttrCfg = syn::parse_quote!(has = overloading);
444        insta::assert_yaml_snapshot!(attr_cfg);
445        let attr_cfg: DiplomatBackendAttrCfg = syn::parse_quote!(has = "overloading");
446        insta::assert_yaml_snapshot!(attr_cfg);
447        let attr_cfg: DiplomatBackendAttrCfg =
448            syn::parse_quote!(any(all(*, cpp, has="overloading"), not(js)));
449        insta::assert_yaml_snapshot!(attr_cfg);
450    }
451
452    #[test]
453    fn test_attr() {
454        let attr: syn::Attribute =
455            syn::parse_quote!(#[diplomat::attr(any(cpp, has = "overloading"), namespacing)]);
456        let attr: DiplomatBackendAttr = attr.parse_args().unwrap();
457        insta::assert_yaml_snapshot!(attr);
458    }
459
460    #[test]
461    fn test_rename() {
462        let attr: syn::Attribute = syn::parse_quote!(#[diplomat::abi_rename = "foobar_{0}"]);
463        let attr = RenameAttr::from_meta(&attr.meta).unwrap();
464        insta::assert_yaml_snapshot!(attr);
465        let attr: syn::Attribute = syn::parse_quote!(#[diplomat::abi_rename("foobar_{0}")]);
466        let attr = RenameAttr::from_meta(&attr.meta).unwrap();
467        insta::assert_yaml_snapshot!(attr);
468    }
469}