chore: update ethers-contract-derive to use syn 2

This commit is contained in:
DaniPopes 2023-03-18 19:44:43 +01:00
parent ef5a37b2e5
commit 85f6710471
No known key found for this signature in database
GPG Key ID: 0F09640DDB7AC692
6 changed files with 113 additions and 279 deletions

View File

@ -40,8 +40,7 @@ impl Contracts {
impl Parse for Contracts {
fn parse(input: ParseStream) -> Result<Self> {
let inner =
input.parse_terminated::<_, Token![;]>(ContractArgs::parse)?.into_iter().collect();
let inner = input.parse_terminated(ContractArgs::parse, Token![;])?.into_iter().collect();
Ok(Self { inner })
}
}
@ -122,7 +121,7 @@ impl Parse for Parameter {
"methods" => {
let content;
braced!(content in input);
let parsed = content.parse_terminated::<_, Token![;]>(Spanned::<Method>::parse)?;
let parsed = content.parse_terminated(Spanned::<Method>::parse, Token![;])?;
let mut methods = Vec::with_capacity(parsed.len());
let mut signatures = HashSet::new();
@ -141,7 +140,7 @@ impl Parse for Parameter {
"derives" | "event_derives" => {
let content;
parenthesized!(content in input);
let derives = content.parse_terminated::<_, Token![,]>(Path::parse)?;
let derives = content.parse_terminated(Path::parse, Token![,])?;
Ok(Parameter::Derives(derives))
}
_ => Err(Error::new(name.span(), "unexpected named parameter")),
@ -168,7 +167,7 @@ impl Parse for Method {
// function params
let content;
parenthesized!(content in input);
let params = content.parse_terminated::<_, Token![,]>(Ident::parse)?;
let params = content.parse_terminated(Ident::parse, Token![,])?;
let last_i = params.len().saturating_sub(1);
signature.push('(');

View File

@ -11,12 +11,10 @@ use syn::{parse::Error, DeriveInput};
/// Generates the `ethcall` trait support
pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> Result<TokenStream, Error> {
let attributes = parse_calllike_attributes(&input, "ethcall")?;
let attributes = parse_calllike_attributes!(input, "ethcall");
let function_call_name =
attributes.name.map(|(s, _)| s).unwrap_or_else(|| input.ident.to_string());
let mut function = if let Some((abi, span)) = attributes.abi {
let function_call_name = attributes.name(&input.ident);
let mut function = if let Some((abi, span)) = attributes.abi() {
let sig = abi.trim_start_matches("function ").trim_start();
// try to parse as solidity function
match HumanReadableParser::parse_function(&abi) {

View File

@ -7,97 +7,50 @@ use ethers_core::{
};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{parse::Error, spanned::Spanned, AttrStyle, DeriveInput, Lit, Meta, NestedMeta};
use syn::{DeriveInput, LitStr, Result};
/// All the attributes the `EthCall`/`EthError` macro supports
#[derive(Default)]
pub struct EthCalllikeAttributes {
pub name: Option<(String, Span)>,
pub abi: Option<(String, Span)>,
pub name: Option<LitStr>,
pub abi: Option<LitStr>,
}
/// extracts the attributes from the struct annotated with the given attribute
pub fn parse_calllike_attributes(
input: &DeriveInput,
attr_name: &str,
) -> Result<EthCalllikeAttributes, Error> {
let mut result = EthCalllikeAttributes::default();
for a in input.attrs.iter() {
if let AttrStyle::Outer = a.style {
if let Ok(Meta::List(meta)) = a.parse_meta() {
if meta.path.is_ident(attr_name) {
for n in meta.nested.iter() {
if let NestedMeta::Meta(meta) = n {
match meta {
Meta::Path(path) => {
return Err(Error::new(
path.span(),
format!("unrecognized {attr_name} parameter"),
))
}
Meta::List(meta) => {
return Err(Error::new(
meta.path.span(),
format!("unrecognized {attr_name} parameter"),
))
}
Meta::NameValue(meta) => {
if meta.path.is_ident("name") {
if let Lit::Str(ref lit_str) = meta.lit {
if result.name.is_none() {
result.name =
Some((lit_str.value(), lit_str.span()));
} else {
return Err(Error::new(
meta.span(),
"name already specified",
))
}
} else {
return Err(Error::new(
meta.span(),
"name must be a string",
))
}
} else if meta.path.is_ident("abi") {
if let Lit::Str(ref lit_str) = meta.lit {
if result.abi.is_none() {
result.abi =
Some((lit_str.value(), lit_str.span()));
} else {
return Err(Error::new(
meta.span(),
"abi already specified",
))
}
} else {
return Err(Error::new(
meta.span(),
"abi must be a string",
))
}
} else {
return Err(Error::new(
meta.span(),
format!("unrecognized {attr_name} parameter"),
))
}
}
}
}
}
}
}
}
impl EthCalllikeAttributes {
pub fn name(&self, fallback: &Ident) -> String {
self.name.as_ref().map(|s| s.value()).unwrap_or_else(|| fallback.to_string())
}
pub fn abi(&self) -> Option<(String, Span)> {
self.abi.as_ref().map(|s| (s.value(), s.span()))
}
Ok(result)
}
macro_rules! parse_calllike_attributes {
($input:ident, $attr_ident:literal) => {{
let mut result = EthCalllikeAttributes::default();
$crate::utils::parse_attributes!($input.attrs.iter(), $attr_ident, meta,
"name", result.name => {
meta.input.parse::<::syn::Token![=]>()?;
let litstr: ::syn::LitStr = meta.input.parse()?;
result.name = Some(litstr);
}
"abi", result.abi => {
meta.input.parse::<::syn::Token![=]>()?;
let litstr: ::syn::LitStr = meta.input.parse()?;
result.abi = Some(litstr);
}
);
result
}};
}
pub(crate) use parse_calllike_attributes;
/// Generates the decode implementation based on the type's runtime `AbiType` impl
pub fn derive_decode_impl_with_abi_type(
input: &DeriveInput,
trait_ident: Ident,
) -> Result<TokenStream, Error> {
) -> Result<TokenStream> {
let datatypes_array = utils::abi_parameters_array(input, &trait_ident.to_string())?;
Ok(derive_decode_impl(datatypes_array, trait_ident))
}
@ -130,7 +83,7 @@ pub fn derive_codec_impls(
input: &DeriveInput,
decode_impl: TokenStream,
trait_ident: Ident,
) -> Result<TokenStream, Error> {
) -> Result<TokenStream> {
// the ethers crates to use
let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate();

View File

@ -11,11 +11,10 @@ use syn::{parse::Error, DeriveInput};
/// Generates the `EthError` trait support
pub(crate) fn derive_eth_error_impl(input: DeriveInput) -> Result<TokenStream, Error> {
let attributes = parse_calllike_attributes(&input, "etherror")?;
let attributes = parse_calllike_attributes!(input, "etherror");
let error_name = attributes.name.map(|(s, _)| s).unwrap_or_else(|| input.ident.to_string());
let mut error = if let Some((src, span)) = attributes.abi {
let error_name = attributes.name(&input.ident);
let mut error = if let Some((src, span)) = attributes.abi() {
let raw_function_sig = src.trim_start_matches("error ").trim_start();
// try to parse as solidity error
match HumanReadableParser::parse_error(&src) {

View File

@ -6,16 +6,12 @@ use ethers_core::{
abi::{Event, EventExt, EventParam, HumanReadableParser},
macros::{ethers_contract_crate, ethers_core_crate},
};
use hex::FromHex;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
parse::Error, spanned::Spanned, AttrStyle, Data, DeriveInput, Field, Fields, Lit, Meta,
NestedMeta,
};
use syn::{spanned::Spanned, Data, DeriveInput, Error, Field, Fields, LitStr, Result, Token};
/// Generates the `EthEvent` trait support
pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result<TokenStream, Error> {
pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result<TokenStream> {
let name = &input.ident;
let attributes = parse_event_attributes(&input)?;
@ -64,7 +60,7 @@ pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result<TokenStream, E
let (abi, event_sig) = (event.abi_signature(), event.signature());
let signature = if let Some((hash, _)) = attributes.signature_hash {
let signature = if let Some((hash, _)) = attributes.signature {
utils::signature(&hash)
} else {
utils::signature(event_sig.as_bytes())
@ -122,7 +118,7 @@ impl EventField {
}
}
fn derive_decode_from_log_impl(input: &DeriveInput, event: &Event) -> Result<TokenStream, Error> {
fn derive_decode_from_log_impl(input: &DeriveInput, event: &Event) -> Result<TokenStream> {
let ethers_core = ethers_core_crate();
let fields: Vec<_> = match input.data {
@ -291,7 +287,7 @@ fn derive_decode_from_log_impl(input: &DeriveInput, event: &Event) -> Result<Tok
}
/// Determine the event's ABI by parsing the AST
fn derive_abi_event_from_fields(input: &DeriveInput) -> Result<Event, Error> {
fn derive_abi_event_from_fields(input: &DeriveInput) -> Result<Event> {
let event = Event {
name: input.ident.to_string(),
inputs: utils::derive_abi_inputs_from_fields(input, "EthEvent")?
@ -303,53 +299,18 @@ fn derive_abi_event_from_fields(input: &DeriveInput) -> Result<Event, Error> {
Ok(event)
}
fn parse_field_attributes(field: &Field) -> Result<(Option<String>, bool), Error> {
let mut indexed = false;
let mut topic_name = None;
for a in field.attrs.iter() {
if let AttrStyle::Outer = a.style {
if let Ok(Meta::List(meta)) = a.parse_meta() {
if meta.path.is_ident("ethevent") {
for n in meta.nested.iter() {
if let NestedMeta::Meta(meta) = n {
match meta {
Meta::Path(path) => {
if path.is_ident("indexed") {
indexed = true;
} else {
return Err(Error::new(
path.span(),
"unrecognized ethevent parameter",
))
}
}
Meta::List(meta) => {
return Err(Error::new(
meta.path.span(),
"unrecognized ethevent parameter",
))
}
Meta::NameValue(meta) => {
if meta.path.is_ident("name") {
if let Lit::Str(ref lit_str) = meta.lit {
topic_name = Some(lit_str.value());
} else {
return Err(Error::new(
meta.span(),
"name attribute must be a string",
))
}
}
}
}
}
}
}
}
fn parse_field_attributes(field: &Field) -> Result<(Option<String>, bool)> {
let mut indexed = None::<bool>;
let mut topic_name = None::<String>;
utils::parse_attributes!(field.attrs.iter(), "ethevent", meta,
"indexed", indexed => { indexed = Some(true) }
"name", topic_name => {
meta.input.parse::<Token![=]>()?;
let litstr: LitStr = meta.input.parse()?;
topic_name = Some(litstr.value());
}
}
Ok((topic_name, indexed))
);
Ok((topic_name, indexed.unwrap_or_default()))
}
/// All the attributes the `EthEvent` macro supports
@ -357,139 +318,32 @@ fn parse_field_attributes(field: &Field) -> Result<(Option<String>, bool), Error
struct EthEventAttributes {
name: Option<(String, Span)>,
abi: Option<(String, Span)>,
signature_hash: Option<(Vec<u8>, Span)>,
signature: Option<(Vec<u8>, Span)>,
anonymous: Option<(bool, Span)>,
}
/// extracts the attributes from the struct annotated with `EthEvent`
fn parse_event_attributes(input: &DeriveInput) -> Result<EthEventAttributes, Error> {
fn parse_event_attributes(input: &DeriveInput) -> Result<EthEventAttributes> {
let mut result = EthEventAttributes::default();
for a in input.attrs.iter() {
if let AttrStyle::Outer = a.style {
if let Ok(Meta::List(meta)) = a.parse_meta() {
if meta.path.is_ident("ethevent") {
for n in meta.nested.iter() {
if let NestedMeta::Meta(meta) = n {
match meta {
Meta::Path(path) => {
if let Some(name) = path.get_ident() {
if &*name.to_string() == "anonymous" {
if result.anonymous.is_none() {
result.anonymous = Some((true, name.span()));
continue
} else {
return Err(Error::new(
name.span(),
"anonymous already specified",
))
}
}
}
return Err(Error::new(
path.span(),
"unrecognized ethevent parameter",
))
}
Meta::List(meta) => {
return Err(Error::new(
meta.path.span(),
"unrecognized ethevent parameter",
))
}
Meta::NameValue(meta) => {
if meta.path.is_ident("anonymous") {
if let Lit::Bool(ref bool_lit) = meta.lit {
if result.anonymous.is_none() {
result.anonymous =
Some((bool_lit.value, bool_lit.span()));
} else {
return Err(Error::new(
meta.span(),
"anonymous already specified",
))
}
} else {
return Err(Error::new(
meta.span(),
"name must be a string",
))
}
} else if meta.path.is_ident("name") {
if let Lit::Str(ref lit_str) = meta.lit {
if result.name.is_none() {
result.name =
Some((lit_str.value(), lit_str.span()));
} else {
return Err(Error::new(
meta.span(),
"name already specified",
))
}
} else {
return Err(Error::new(
meta.span(),
"name must be a string",
))
}
} else if meta.path.is_ident("abi") {
if let Lit::Str(ref lit_str) = meta.lit {
if result.abi.is_none() {
result.abi =
Some((lit_str.value(), lit_str.span()));
} else {
return Err(Error::new(
meta.span(),
"abi already specified",
))
}
} else {
return Err(Error::new(
meta.span(),
"abi must be a string",
))
}
} else if meta.path.is_ident("signature") {
if let Lit::Str(ref lit_str) = meta.lit {
if result.signature_hash.is_none() {
match Vec::from_hex(lit_str.value()) {
Ok(sig) => {
result.signature_hash =
Some((sig, lit_str.span()))
}
Err(err) => {
return Err(Error::new(
meta.span(),
format!(
"Expected hex signature: {err:?}"
),
))
}
}
} else {
return Err(Error::new(
meta.span(),
"signature already specified",
))
}
} else {
return Err(Error::new(
meta.span(),
"signature must be a hex string",
))
}
} else {
return Err(Error::new(
meta.span(),
"unrecognized ethevent parameter",
))
}
}
}
}
}
}
}
utils::parse_attributes!(input.attrs.iter(), "ethevent", meta,
"name", result.name => {
meta.input.parse::<Token![=]>()?;
let litstr: LitStr = meta.input.parse()?;
result.name = Some((litstr.value(), litstr.span()));
}
}
"abi", result.abi => {
meta.input.parse::<Token![=]>()?;
let litstr: LitStr = meta.input.parse()?;
result.abi = Some((litstr.value(), litstr.span()));
}
"signature", result.signature => {
meta.input.parse::<Token![=]>()?;
let litstr: LitStr = meta.input.parse()?;
let s = litstr.value();
let b = hex::decode(s.strip_prefix("0x").unwrap_or(&s)).map_err(|e| meta.error(e))?;
result.signature = Some((b, litstr.span()));
}
"anonymous", result.anonymous => { result.anonymous = Some((true, meta.path.span())); }
);
Ok(result)
}

View File

@ -6,6 +6,35 @@ use syn::{
PathArguments, Type,
};
/// Parses the specified attributes from a `syn::Attribute` iterator.
macro_rules! parse_attributes {
($attrs:expr, $attr_ident:literal, $meta:ident, $($field:pat, $opt:expr => $block:block)*) => {
const ERROR: &str = concat!("unrecognized ", $attr_ident, " attribute");
const ALREADY_SPECIFIED: &str = concat!($attr_ident, " attribute already specified");
for attr in $attrs {
if !attr.path().is_ident($attr_ident) {
continue;
}
attr.parse_nested_meta(|$meta| {
let ident = $meta.path.get_ident().ok_or_else(|| $meta.error(ERROR))?.to_string();
match ident.as_str() {
$(
$field if $opt.is_none() => $block,
$field => return Err($meta.error(ALREADY_SPECIFIED)),
)*
_ => return Err($meta.error(ERROR)),
}
Ok(())
})?;
}
};
}
pub(crate) use parse_attributes;
pub fn ident(name: &str) -> Ident {
Ident::new(name, Span::call_site())
}
@ -104,6 +133,8 @@ pub fn param_type_quote(kind: &ParamType) -> TokenStream {
/// Tries to find the corresponding `ParamType` used for tokenization for the
/// given type
pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
const ERROR: &str = "Failed to derive proper ABI from array field";
match ty {
Type::Array(arr) => {
let ty = find_parameter_type(&arr.elem)?;
@ -114,7 +145,7 @@ pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
}
}
}
Err(Error::new(arr.span(), "Failed to derive proper ABI from array field"))
Err(Error::new(arr.span(), ERROR))
}
Type::Path(ty) => {
@ -151,7 +182,7 @@ pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
s => parse_param_type(s),
}
})
.ok_or_else(|| Error::new(ty.span(), "Failed to derive proper ABI from fields"))
.ok_or_else(|| Error::new(ty.span(), ERROR))
}
Type::Tuple(ty) => ty
@ -161,7 +192,7 @@ pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
.collect::<Result<Vec<_>, _>>()
.map(ParamType::Tuple),
_ => Err(Error::new(ty.span(), "Failed to derive proper ABI from fields")),
_ => Err(Error::new(ty.span(), ERROR)),
}
}