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