Skip to content

Commit

Permalink
[refactor] hyperledger-iroha#3830: iroha_ffi_derive: use darling to p…
Browse files Browse the repository at this point in the history
…arse attributes and use syn 2.0

Signed-off-by: Nikita Strygin <DCNick3@users.noreply.github.com>
  • Loading branch information
DCNick3 committed Aug 23, 2023
1 parent a0a9460 commit 73bbc38
Show file tree
Hide file tree
Showing 21 changed files with 2,675 additions and 822 deletions.
27 changes: 8 additions & 19 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions ffi/derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ categories = ["development-tools::ffi"]
proc-macro = true

[dependencies]
syn = { workspace = true, features = ["full", "visit", "visit-mut", "extra-traits"] }
syn2 = { workspace = true, features = ["full", "visit", "visit-mut", "extra-traits"] }
quote = { workspace = true }
proc-macro2 = { workspace = true }
manyhow = { workspace = true, features = ["syn1"] }
derive_more = { workspace = true, default-features = true }
manyhow = { workspace = true }
darling = { workspace = true }
rustc-hash = { workspace = true }

drop_bomb = "0.1.5"

[dev-dependencies]
iroha_ffi = { workspace = true }

Expand Down
187 changes: 187 additions & 0 deletions ffi/derive/src/attr_parse/derive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
//! This module provides parsing of `#[derive(...)]` attributes

use darling::FromAttributes;
use quote::ToTokens;
use syn2::{punctuated::Punctuated, Attribute, Token};

use super::getset::GetSetDerive;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RustcDerive {
Eq,
PartialEq,
Ord,
PartialOrd,
Clone,
Copy,
Hash,
Default,
Debug,
}

impl RustcDerive {
fn try_from_path(path: &syn2::Path) -> Option<Self> {
let Some(ident) = path.get_ident() else {
return None;
};

match ident.to_string().as_str() {
"Eq" => Some(Self::Eq),
"PartialEq" => Some(Self::PartialEq),
"Ord" => Some(Self::Ord),
"PartialOrd" => Some(Self::PartialOrd),
"Clone" => Some(Self::Clone),
"Copy" => Some(Self::Copy),
"Hash" => Some(Self::Hash),
"Default" => Some(Self::Default),
"Debug" => Some(Self::Debug),
_ => None,
}
}
}

#[allow(variant_size_differences)] // it's not like it's possible to change that..
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Derive {
Rustc(RustcDerive),
GetSet(GetSetDerive),
Other(String),
}

/// Represents a collection of all `#[derive(...)]` attributes placed on the item
///
/// NOTE: strictly speaking, correctly parsing this is impossible, since it requires
/// us to resolve the paths in the attributes, which is not possible in a proc-macro context.
///
/// We just __hope__ that the user refers to the derives by their canonical names (no aliases).
///
/// This, however, will mistakingly thing that `derive_more` derives are actually rustc's built-in ones.
///
/// Care should be taken, and it should be documented in the macro APIs that use this.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DeriveAttr {
pub derives: Vec<Derive>,
}

impl FromAttributes for DeriveAttr {
fn from_attributes(attrs: &[Attribute]) -> darling::Result<Self> {
let mut derives = Vec::new();
let mut accumulator = darling::error::Accumulator::default();

for attr in attrs {
if attr.path().is_ident("derive") {
let Some(list) = accumulator.handle(attr.meta.require_list().map_err(Into::into)) else {
continue
};
let Some(paths) = accumulator.handle(
list.parse_args_with(Punctuated::<syn2::Path, Token![,]>::parse_terminated).map_err(Into::into)
) else {
continue
};

for path in paths {
// what clippy suggests here is much harder to read
#[allow(clippy::option_if_let_else)]
let derive = if let Some(derive) = RustcDerive::try_from_path(&path) {
Derive::Rustc(derive)
} else if let Some(derive) = GetSetDerive::try_from_path(&path) {
Derive::GetSet(derive)
} else {
Derive::Other(path.to_token_stream().to_string())
};

// I __think__ it's an error to use the same derive twice
// I don't think we care in this case though
derives.push(derive);
}
}
}

accumulator.finish_with(Self { derives })
}
}

#[cfg(test)]
mod test {
use darling::FromAttributes;
use proc_macro2::TokenStream;
use quote::quote;
use syn2::parse::ParseStream;

use super::{Derive, DeriveAttr, GetSetDerive, RustcDerive};

fn parse_derives(attrs: TokenStream) -> darling::Result<DeriveAttr> {
struct Attributes(Vec<syn2::Attribute>);

impl syn2::parse::Parse for Attributes {
fn parse(input: ParseStream) -> syn2::Result<Self> {
Ok(Self(input.call(syn2::Attribute::parse_outer)?))
}
}

let attrs = syn2::parse2::<Attributes>(attrs)
.expect("Failed to parse tokens as outer attributes")
.0;
DeriveAttr::from_attributes(&attrs)
}

macro_rules! assert_derive_ok {
($( #[$meta:meta] )*,
$expected:expr
) => {
assert_eq!(parse_derives(quote!(
$( #[$meta] )*
)).unwrap(),
$expected
)
};
}

#[test]
fn derive_rustc() {
assert_derive_ok!(
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash, Default, Debug)],
DeriveAttr {
derives: vec![
RustcDerive::Eq,
RustcDerive::PartialEq,
RustcDerive::Ord,
RustcDerive::PartialOrd,
RustcDerive::Clone,
RustcDerive::Copy,
RustcDerive::Hash,
RustcDerive::Default,
RustcDerive::Debug,
].into_iter().map(Derive::Rustc).collect(),
}
)
}

#[test]
fn derive_getset() {
assert_derive_ok!(
#[derive(Getters, Setters, MutGetters, CopyGetters)],
DeriveAttr {
derives: vec![
GetSetDerive::Getters,
GetSetDerive::Setters,
GetSetDerive::MutGetters,
GetSetDerive::CopyGetters,
].into_iter().map(Derive::GetSet).collect(),
}
)
}

#[test]
fn derive_unknown() {
assert_derive_ok!(
#[derive(Aboba, Kek)],
DeriveAttr {
derives: vec![
"Aboba".to_string(),
"Kek".to_string(),
].into_iter().map(Derive::Other).collect(),
}
)
}
}
18 changes: 18 additions & 0 deletions ffi/derive/src/attr_parse/doc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use darling::FromAttributes;
use syn2::Attribute;

pub struct DocAttrs {
pub attrs: Vec<Attribute>,
}

impl FromAttributes for DocAttrs {
fn from_attributes(attrs: &[Attribute]) -> darling::Result<Self> {
let mut docs = Vec::new();
for attr in attrs {
if attr.path().is_ident("doc") {
docs.push(attr.clone());
}
}
Ok(DocAttrs { attrs: docs })
}
}
Loading

0 comments on commit 73bbc38

Please sign in to comment.