Skip to content

Commit

Permalink
Add diplomat::c_rename (#416)
Browse files Browse the repository at this point in the history
  • Loading branch information
Manishearth authored Feb 1, 2024
1 parent 2653a8a commit d5c6b32
Show file tree
Hide file tree
Showing 46 changed files with 564 additions and 149 deletions.
161 changes: 152 additions & 9 deletions core/src/ast/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
use serde::ser::{SerializeStruct, Serializer};
use serde::Serialize;
use std::borrow::Cow;
use std::convert::Infallible;
use std::str::FromStr;
use syn::parse::{Error as ParseError, Parse, ParseStream};
use syn::{Attribute, Ident, LitStr, Meta, Token};
use syn::{Attribute, Expr, Ident, Lit, LitStr, Meta, Token};

/// The list of attributes on a type
#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)]
Expand All @@ -16,6 +19,11 @@ pub struct Attrs {
/// This isn't a regular attribute since AST backends do not handle regular attributes. Do not use
/// in HIR backends,
pub skip_if_unsupported: bool,

/// Renames to apply to the underlying C function. Can be found on methods, impls, and bridge modules, and is inherited.
///
/// Has no effect on types.
pub c_rename: RenameAttr,
}

impl Attrs {
Expand All @@ -24,12 +32,14 @@ impl Attrs {
Attr::Cfg(attr) => self.cfg.push(attr),
Attr::DiplomatBackend(attr) => self.attrs.push(attr),
Attr::SkipIfUnsupported => self.skip_if_unsupported = true,
Attr::CRename(rename) => self.c_rename.extend(&rename),
}
}

/// Merge attributes that should be inherited from the parent
pub(crate) fn merge_parent_attrs(&mut self, other: &Attrs) {
self.cfg.extend(other.cfg.iter().cloned())
self.cfg.extend(other.cfg.iter().cloned());
self.c_rename.extend(&other.c_rename);
}
pub(crate) fn add_attrs(&mut self, attrs: &[Attribute]) {
for attr in syn_attr_to_ast_attr(attrs) {
Expand All @@ -53,12 +63,14 @@ enum Attr {
Cfg(Attribute),
DiplomatBackend(DiplomatBackendAttr),
SkipIfUnsupported,
CRename(RenameAttr),
// More goes here
}

fn syn_attr_to_ast_attr(attrs: &[Attribute]) -> impl Iterator<Item = Attr> + '_ {
let cfg_path: syn::Path = syn::parse_str("cfg").unwrap();
let dattr_path: syn::Path = syn::parse_str("diplomat::attr").unwrap();
let crename_attr: syn::Path = syn::parse_str("diplomat::c_rename").unwrap();
let skipast: syn::Path = syn::parse_str("diplomat::skip_if_unsupported").unwrap();
attrs.iter().filter_map(move |a| {
if a.path() == &cfg_path {
Expand All @@ -68,6 +80,8 @@ fn syn_attr_to_ast_attr(attrs: &[Attribute]) -> impl Iterator<Item = Attr> + '_
a.parse_args()
.expect("Failed to parse malformed diplomat::attr"),
))
} else if a.path() == &crename_attr {
Some(Attr::CRename(RenameAttr::from_syn(a).unwrap()))
} else if a.path() == &skipast {
Some(Attr::SkipIfUnsupported)
} else {
Expand All @@ -83,12 +97,23 @@ impl Serialize for Attrs {
{
// 1 is the number of fields in the struct.
let mut state = serializer.serialize_struct("Attrs", 1)?;
let cfg: Vec<_> = self
.cfg
.iter()
.map(|a| quote::quote!(#a).to_string())
.collect();
state.serialize_field("cfg", &cfg)?;
if !self.cfg.is_empty() {
let cfg: Vec<_> = self
.cfg
.iter()
.map(|a| quote::quote!(#a).to_string())
.collect();
state.serialize_field("cfg", &cfg)?;
}
if !self.attrs.is_empty() {
state.serialize_field("attrs", &self.attrs)?;
}
if self.skip_if_unsupported {
state.serialize_field("skip_if_unsupported", &self.skip_if_unsupported)?;
}
if !self.c_rename.is_empty() {
state.serialize_field("c_rename", &self.c_rename)?;
}
state.end()
}
}
Expand Down Expand Up @@ -184,13 +209,121 @@ impl Parse for DiplomatBackendAttr {
}
}

/// A pattern for use in rename attributes, like `#[diplomat::c_rename]`
///
/// This can be parsed from a string, typically something like `icu4x_{0}`.
/// It can have up to one {0} for replacement.
///
/// In the future this may support transformations like to_camel_case, etc,
/// probably specified as a list like `#[diplomat::c_rename("foo{0}", to_camel_case)]`
#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize)]
pub struct RenameAttr {
pattern: Option<RenamePattern>,
}

impl RenameAttr {
/// Apply all renames to a given string
pub fn apply<'a>(&'a self, name: &'a str) -> Cow<'a, str> {
if let Some(ref pattern) = self.pattern {
let replacement = &pattern.replacement;
if let Some(index) = pattern.insertion_index {
format!("{}{name}{}", &replacement[..index], &replacement[index..]).into()
} else {
replacement.into()
}
} else {
name.into()
}
}

/// Whether this rename is empty and will perform no changes
fn is_empty(&self) -> bool {
self.pattern.is_none()
}

fn extend(&mut self, parent: &Self) {
// Patterns override each other on inheritance
if self.pattern.is_none() {
self.pattern = parent.pattern.clone();
}

// In the future if we support things like to_lower_case they may inherit separately
// from patterns.
}

/// From a replacement pattern, like "icu4x_{0}". Can have up to one {0} in it for substitution.
fn from_pattern(s: &str) -> Self {
Self {
pattern: Some(s.parse().unwrap()),
}
}

fn from_syn(a: &Attribute) -> Result<Self, Cow<'static, str>> {
static C_RENAME_ERROR: &str = "#[diplomat::c_rename] must be given a string value";

match a.meta {
Meta::Path(..) => Err(C_RENAME_ERROR.into()),
Meta::NameValue(ref nv) => {
// Support a shortcut `c_rename = "..."`
let Expr::Lit(ref lit) = nv.value else {
return Err(C_RENAME_ERROR.into());
};
let Lit::Str(ref lit) = lit.lit else {
return Err(C_RENAME_ERROR.into());
};
Ok(RenameAttr::from_pattern(&lit.value()))
}
// The full syntax to which we'll add more things in the future, `c_rename("")`
Meta::List(..) => a.parse_args().map_err(|e| {
format!("Failed to parse malformed #[diplomat::c_rename(...)]: {e}").into()
}),
}
}
}

impl FromStr for RenamePattern {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Infallible> {
if let Some(index) = s.find("{0}") {
let replacement = format!("{}{}", &s[..index], &s[index + 3..]);
Ok(Self {
replacement,
insertion_index: Some(index),
})
} else {
Ok(Self {
replacement: s.into(),
insertion_index: None,
})
}
}
}

/// Meant to be used with Attribute::parse_args()
impl Parse for RenameAttr {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let value: LitStr = input.parse()?;
let attr = RenameAttr::from_pattern(&value.value());
Ok(attr)
}
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize)]
struct RenamePattern {
/// The string to replace with
replacement: String,
/// The index in `replacement` in which to insert the original string. If None,
/// this is a pure rename
insertion_index: Option<usize>,
}

#[cfg(test)]
mod tests {
use insta;

use syn;

use super::{DiplomatBackendAttr, DiplomatBackendAttrCfg};
use super::{DiplomatBackendAttr, DiplomatBackendAttrCfg, RenameAttr};

#[test]
fn test_cfgs() {
Expand All @@ -214,4 +347,14 @@ mod tests {
let attr: DiplomatBackendAttr = attr.parse_args().unwrap();
insta::assert_yaml_snapshot!(attr);
}

#[test]
fn test_rename() {
let attr: syn::Attribute = syn::parse_quote!(#[diplomat::c_rename = "foobar_{0}"]);
let attr = RenameAttr::from_syn(&attr).unwrap();
insta::assert_yaml_snapshot!(attr);
let attr: syn::Attribute = syn::parse_quote!(#[diplomat::c_rename("foobar_{0}")]);
let attr = RenameAttr::from_syn(&attr).unwrap();
insta::assert_yaml_snapshot!(attr);
}
}
53 changes: 31 additions & 22 deletions core/src/ast/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ pub struct Enum {
pub attrs: Attrs,
}

impl From<&syn::ItemEnum> for Enum {
impl Enum {
/// Extract an [`Enum`] metadata value from an AST node.
fn from(enm: &syn::ItemEnum) -> Enum {
pub fn new(enm: &syn::ItemEnum, parent_attrs: &Attrs) -> Enum {
let mut last_discriminant = -1;
if !enm.generics.params.is_empty() {
// Generic types are not allowed.
Expand All @@ -28,6 +28,9 @@ impl From<&syn::ItemEnum> for Enum {
panic!("Enums cannot have generic parameters");
}

let mut attrs: Attrs = (&*enm.attrs).into();
attrs.merge_parent_attrs(parent_attrs);

Enum {
name: (&enm.ident).into(),
docs: Docs::from_attrs(&enm.attrs),
Expand Down Expand Up @@ -61,7 +64,7 @@ impl From<&syn::ItemEnum> for Enum {
})
.collect(),
methods: vec![],
attrs: (&*enm.attrs).into(),
attrs,
}
}
}
Expand All @@ -80,15 +83,18 @@ mod tests {
settings.set_sort_maps(true);

settings.bind(|| {
insta::assert_yaml_snapshot!(Enum::from(&syn::parse_quote! {
/// Some docs.
#[diplomat::rust_link(foo::Bar, Enum)]
enum MyLocalEnum {
Abc,
/// Some more docs.
Def
}
}));
insta::assert_yaml_snapshot!(Enum::new(
&syn::parse_quote! {
/// Some docs.
#[diplomat::rust_link(foo::Bar, Enum)]
enum MyLocalEnum {
Abc,
/// Some more docs.
Def
}
},
&Default::default()
));
});
}

Expand All @@ -98,16 +104,19 @@ mod tests {
settings.set_sort_maps(true);

settings.bind(|| {
insta::assert_yaml_snapshot!(Enum::from(&syn::parse_quote! {
/// Some docs.
#[diplomat::rust_link(foo::Bar, Enum)]
enum DiscriminantedEnum {
Abc = -1,
Def = 0,
Ghi = 1,
Jkl = 2,
}
}));
insta::assert_yaml_snapshot!(Enum::new(
&syn::parse_quote! {
/// Some docs.
#[diplomat::rust_link(foo::Bar, Enum)]
enum DiscriminantedEnum {
Abc = -1,
Def = 0,
Ghi = 1,
Jkl = 2,
}
},
&Default::default()
));
});
}
}
9 changes: 5 additions & 4 deletions core/src/ast/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,14 @@ impl Method {
impl_generics: Option<&syn::Generics>,
impl_attrs: &Attrs,
) -> Method {
let mut attrs: Attrs = (&*m.attrs).into();
attrs.merge_parent_attrs(impl_attrs);

let self_ident = self_path_type.path.elements.last().unwrap();
let method_ident = &m.sig.ident;
let concat_method_ident = format!("{self_ident}_{method_ident}");
let extern_ident = syn::Ident::new(
format!("{self_ident}_{method_ident}").as_str(),
&attrs.c_rename.apply(&concat_method_ident),
m.sig.ident.span(),
);

Expand Down Expand Up @@ -91,9 +95,6 @@ impl Method {
return_ty.as_ref(),
);

let mut attrs: Attrs = (&*m.attrs).into();
attrs.merge_parent_attrs(impl_attrs);

Method {
name: Ident::from(method_ident),
docs: Docs::from_attrs(&m.attrs),
Expand Down
Loading

0 comments on commit d5c6b32

Please sign in to comment.