use serde::ser::{SerializeStruct, Serializer};
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::convert::Infallible;
use std::str::FromStr;
use syn::parse::{Error as ParseError, Parse, ParseStream};
use syn::{Attribute, Expr, Ident, Lit, LitStr, Meta, MetaList, MetaNameValue, Token};
#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)]
#[non_exhaustive]
pub struct Attrs {
pub cfg: Vec<Attribute>,
pub deprecated: Option<String>,
pub attrs: Vec<DiplomatBackendAttr>,
pub abi_rename: RenameAttr,
pub demo_attrs: Vec<DemoBackendAttr>,
}
impl Attrs {
fn add_attr(&mut self, attr: Attr) {
match attr {
Attr::Cfg(attr) => self.cfg.push(attr),
Attr::DiplomatBackend(attr) => self.attrs.push(attr),
Attr::DiplomatCFGBackend(attr) => self.attrs.push(DiplomatBackendAttr {
cfg: DiplomatBackendAttrCfg::Not(Box::new(attr)),
meta: Meta::Path(syn::parse_quote!(disable)),
}),
Attr::CRename(rename) => self.abi_rename.extend(&rename),
Attr::DemoBackend(attr) => self.demo_attrs.push(attr),
Attr::Deprecated(msg) => self.deprecated = Some(msg),
}
}
pub(crate) fn attrs_for_inheritance(&self, context: AttrInheritContext) -> Self {
let attrs = if context == AttrInheritContext::MethodFromImpl {
self.attrs.clone()
} else {
Vec::new()
};
let demo_attrs = if context == AttrInheritContext::MethodFromImpl {
self.demo_attrs.clone()
} else {
Vec::new()
};
let abi_rename = self.abi_rename.attrs_for_inheritance(context, true);
Self {
cfg: self.cfg.clone(),
deprecated: None,
attrs,
abi_rename,
demo_attrs,
}
}
pub(crate) fn add_attrs(&mut self, attrs: &[Attribute]) {
for attr in syn_attr_to_ast_attr(attrs) {
self.add_attr(attr)
}
}
pub(crate) fn from_attrs(attrs: &[Attribute]) -> Self {
let mut this = Self::default();
this.add_attrs(attrs);
this
}
}
impl From<&[Attribute]> for Attrs {
fn from(other: &[Attribute]) -> Self {
Self::from_attrs(other)
}
}
enum Attr {
Cfg(Attribute),
DiplomatBackend(DiplomatBackendAttr),
DiplomatCFGBackend(DiplomatBackendAttrCfg),
CRename(RenameAttr),
DemoBackend(DemoBackendAttr),
Deprecated(String),
}
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 diplomat_cfg_path: syn::Path = syn::parse_str("diplomat::cfg").unwrap();
let crename_attr: syn::Path = syn::parse_str("diplomat::abi_rename").unwrap();
let demo_path: syn::Path = syn::parse_str("diplomat::demo").unwrap();
let deprecated: syn::Path = syn::parse_str("deprecated").unwrap();
attrs.iter().filter_map(move |a| {
if a.path() == &cfg_path {
Some(Attr::Cfg(a.clone()))
} else if a.path() == &dattr_path {
Some(Attr::DiplomatBackend(
a.parse_args()
.expect("Failed to parse malformed diplomat::attr"),
))
} else if a.path() == &diplomat_cfg_path {
Some(Attr::DiplomatCFGBackend(
a.parse_args()
.expect("Failed to parse malformed diplomat::cfg"),
))
} else if a.path() == &crename_attr {
Some(Attr::CRename(RenameAttr::from_meta(&a.meta).unwrap()))
} else if a.path() == &demo_path {
Some(Attr::DemoBackend(
a.parse_args()
.expect("Failed to parse malformed diplomat::demo"),
))
} else if a.path() == &deprecated {
if let Some(Meta::NameValue(MetaNameValue {
value:
syn::Expr::Lit(syn::ExprLit {
lit: Lit::Str(s), ..
}),
..
})) = a
.meta
.require_list()
.ok()
.and_then(|m| syn::parse2::<Meta>(m.tokens.clone()).ok())
{
Some(Attr::Deprecated(s.value()))
} else {
Some(Attr::Deprecated("deprecated".into()))
}
} else {
None
}
})
}
impl Serialize for Attrs {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("Attrs", 1)?;
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.abi_rename.is_empty() {
state.serialize_field("abi_rename", &self.abi_rename)?;
}
state.end()
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize)]
#[non_exhaustive]
pub struct DiplomatBackendAttr {
pub cfg: DiplomatBackendAttrCfg,
#[serde(serialize_with = "serialize_meta")]
pub meta: Meta,
}
fn serialize_meta<S>(m: &Meta, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
quote::quote!(#m).to_string().serialize(s)
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub enum DiplomatBackendAttrCfg {
Not(Box<DiplomatBackendAttrCfg>),
Any(Vec<DiplomatBackendAttrCfg>),
All(Vec<DiplomatBackendAttrCfg>),
Star,
Auto,
BackendName(String),
NameValue(String, String),
}
impl Parse for DiplomatBackendAttrCfg {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(Ident) {
let name: Ident = input.parse()?;
if name == "auto" {
Ok(DiplomatBackendAttrCfg::Auto)
} else if name == "not" {
let content;
let _paren = syn::parenthesized!(content in input);
Ok(DiplomatBackendAttrCfg::Not(Box::new(content.parse()?)))
} else if name == "any" || name == "all" {
let content;
let _paren = syn::parenthesized!(content in input);
let list = content.parse_terminated(Self::parse, Token![,])?;
let vec = list.into_iter().collect();
if name == "any" {
Ok(DiplomatBackendAttrCfg::Any(vec))
} else {
Ok(DiplomatBackendAttrCfg::All(vec))
}
} else if input.peek(Token![=]) {
let _t: Token![=] = input.parse()?;
if input.peek(Ident) {
let value: Ident = input.parse()?;
Ok(DiplomatBackendAttrCfg::NameValue(
name.to_string(),
value.to_string(),
))
} else {
let value: LitStr = input.parse()?;
Ok(DiplomatBackendAttrCfg::NameValue(
name.to_string(),
value.value(),
))
}
} else {
Ok(DiplomatBackendAttrCfg::BackendName(name.to_string()))
}
} else if lookahead.peek(Token![*]) {
let _t: Token![*] = input.parse()?;
Ok(DiplomatBackendAttrCfg::Star)
} else {
Err(ParseError::new(
input.span(),
"CFG portion of #[diplomat::attr] fails to parse",
))
}
}
}
impl Parse for DiplomatBackendAttr {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let cfg = input.parse()?;
let _comma: Token![,] = input.parse()?;
let meta = input.parse()?;
Ok(Self { cfg, meta })
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize)]
#[non_exhaustive]
pub struct DemoBackendAttr {
#[serde(serialize_with = "serialize_meta")]
pub meta: Meta,
}
impl Parse for DemoBackendAttr {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let meta = input.parse()?;
Ok(Self { meta })
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) enum AttrInheritContext {
Variant,
Type,
MethodOrImplFromModule,
MethodFromImpl,
#[allow(unused)]
Module,
}
#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize)]
pub struct RenameAttr {
pattern: Option<RenamePattern>,
}
impl RenameAttr {
pub fn apply<'a>(&'a self, name: Cow<'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
}
}
pub(crate) fn is_empty(&self) -> bool {
self.pattern.is_none()
}
pub(crate) fn extend(&mut self, other: &Self) {
if other.pattern.is_some() {
self.pattern.clone_from(&other.pattern);
}
}
pub(crate) fn attrs_for_inheritance(
&self,
context: AttrInheritContext,
is_abi_rename: bool,
) -> Self {
let pattern = match context {
AttrInheritContext::MethodOrImplFromModule if !is_abi_rename => Default::default(),
AttrInheritContext::Variant => Default::default(),
_ => self.pattern.clone(),
};
Self { pattern }
}
fn from_pattern(s: &str) -> Self {
Self {
pattern: Some(s.parse().unwrap()),
}
}
pub(crate) fn from_meta(meta: &Meta) -> Result<Self, &'static str> {
let attr = StandardAttribute::from_meta(meta)
.map_err(|_| "#[diplomat::abi_rename] must be given a string value")?;
match attr {
StandardAttribute::String(s) => Ok(RenameAttr::from_pattern(&s)),
StandardAttribute::List(_) => {
Err("Failed to parse malformed #[diplomat::abi_rename(...)]: found list")
}
StandardAttribute::Empty => {
Err("Failed to parse malformed #[diplomat::abi_rename(...)]: found no parameters")
}
}
}
}
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,
})
}
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize)]
struct RenamePattern {
replacement: String,
insertion_index: Option<usize>,
}
pub(crate) enum StandardAttribute<'a> {
String(String),
List(#[allow(dead_code)] &'a MetaList),
Empty,
}
impl<'a> StandardAttribute<'a> {
pub(crate) fn from_meta(meta: &'a Meta) -> Result<Self, ()> {
match meta {
Meta::Path(..) => Ok(Self::Empty),
Meta::NameValue(ref nv) => {
let Expr::Lit(ref lit) = nv.value else {
return Err(());
};
let Lit::Str(ref lit) = lit.lit else {
return Err(());
};
Ok(Self::String(lit.value()))
}
Meta::List(list) => {
if let Ok(lit) = list.parse_args::<LitStr>() {
Ok(Self::String(lit.value()))
} else {
Ok(Self::List(list))
}
}
}
}
}
#[cfg(test)]
mod tests {
use insta;
use syn;
use super::{DiplomatBackendAttr, DiplomatBackendAttrCfg, RenameAttr};
#[test]
fn test_cfgs() {
let attr_cfg: DiplomatBackendAttrCfg = syn::parse_quote!(*);
insta::assert_yaml_snapshot!(attr_cfg);
let attr_cfg: DiplomatBackendAttrCfg = syn::parse_quote!(cpp);
insta::assert_yaml_snapshot!(attr_cfg);
let attr_cfg: DiplomatBackendAttrCfg = syn::parse_quote!(has = overloading);
insta::assert_yaml_snapshot!(attr_cfg);
let attr_cfg: DiplomatBackendAttrCfg = syn::parse_quote!(has = "overloading");
insta::assert_yaml_snapshot!(attr_cfg);
let attr_cfg: DiplomatBackendAttrCfg =
syn::parse_quote!(any(all(*, cpp, has="overloading"), not(js)));
insta::assert_yaml_snapshot!(attr_cfg);
}
#[test]
fn test_attr() {
let attr: syn::Attribute =
syn::parse_quote!(#[diplomat::attr(any(cpp, has = "overloading"), namespacing)]);
let attr: DiplomatBackendAttr = attr.parse_args().unwrap();
insta::assert_yaml_snapshot!(attr);
}
#[test]
fn test_rename() {
let attr: syn::Attribute = syn::parse_quote!(#[diplomat::abi_rename = "foobar_{0}"]);
let attr = RenameAttr::from_meta(&attr.meta).unwrap();
insta::assert_yaml_snapshot!(attr);
let attr: syn::Attribute = syn::parse_quote!(#[diplomat::abi_rename("foobar_{0}")]);
let attr = RenameAttr::from_meta(&attr.meta).unwrap();
insta::assert_yaml_snapshot!(attr);
}
}