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