Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rlp-derive extracted #343

Merged
merged 16 commits into from
Feb 12, 2020
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions rlp-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "rlp_derive"
ordian marked this conversation as resolved.
Show resolved Hide resolved
version = "0.1.0"
authors = ["Parity Technologies <[email protected]>"]
edition = "2018"

[lib]
name = "rlp_derive"
ordian marked this conversation as resolved.
Show resolved Hide resolved
proc-macro = true

[dependencies]
syn = "1.0.14"
quote = "1.0.2"
proc-macro2 = "1.0.8"

[dev-dependencies]
rlp = "0.4.0"
193 changes: 193 additions & 0 deletions rlp-derive/src/de.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
// This file is part of Parity Ethereum.
ordian marked this conversation as resolved.
Show resolved Hide resolved

// Parity Ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity Ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.

use proc_macro2::TokenStream;
use quote::quote;

struct ParseQuotes {
single: TokenStream,
list: TokenStream,
takes_index: bool,
}

fn decodable_parse_quotes() -> ParseQuotes {
ParseQuotes {
single: quote! { rlp.val_at },
list: quote! { rlp.list_at },
takes_index: true,
}
}

fn decodable_wrapper_parse_quotes() -> ParseQuotes {
ParseQuotes {
single: quote! { rlp.as_val },
list: quote! { rlp.as_list },
takes_index: false,
}
}

pub fn impl_decodable(ast: &syn::DeriveInput) -> TokenStream {
let body = match ast.data {
syn::Data::Struct(ref s) => s,
_ => panic!("#[derive(RlpDecodable)] is only defined for structs."),
};

let mut default_attribute_encountered = false;
let stmts: Vec<_> = body
.fields
.iter()
.enumerate()
.map(|(i, field)| decodable_field(
i,
field,
decodable_parse_quotes(),
&mut default_attribute_encountered,
)).collect();
let name = &ast.ident;

let impl_block = quote! {
impl rlp::Decodable for #name {
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
let result = #name {
#(#stmts)*
};

Ok(result)
}
}
};

quote! {
const _: () = {
extern crate rlp;
#impl_block
};
}
}

pub fn impl_decodable_wrapper(ast: &syn::DeriveInput) -> TokenStream {
let body = match ast.data {
syn::Data::Struct(ref s) => s,
_ => panic!("#[derive(RlpDecodableWrapper)] is only defined for structs."),
};

let stmt = {
let fields: Vec<_> = body.fields.iter().collect();
if fields.len() == 1 {
let field = fields.first().expect("fields.len() == 1; qed");
let mut default_attribute_encountered = false;
decodable_field(
0,
field,
decodable_wrapper_parse_quotes(),
&mut default_attribute_encountered,
)
} else {
panic!("#[derive(RlpEncodableWrapper)] is only defined for structs with one field.")
}
};

let name = &ast.ident;

let impl_block = quote! {
impl rlp::Decodable for #name {
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
let result = #name {
#stmt
};

Ok(result)
}
}
};

quote! {
const _: () = {
extern crate rlp;
#impl_block
};
}
}

fn decodable_field(
index: usize,
field: &syn::Field,
quotes: ParseQuotes,
default_attribute_encountered: &mut bool,
) -> TokenStream {
let id = match field.ident {
Some(ref ident) => quote! { #ident },
None => {
let index: syn::Index = index.into();
quote! { #index }
}
};

let index = index - *default_attribute_encountered as usize;
let index = quote! { #index };

let single = quotes.single;
let list = quotes.list;

let attributes = &field.attrs;
let default = if let Some(attr) = attributes.iter().find(|attr| attr.path.is_ident("rlp")) {
if *default_attribute_encountered {
panic!("only 1 #[rlp(default)] attribute is allowed in a struct")
}
match attr.parse_args() {
Ok(proc_macro2::TokenTree::Ident(ident)) if ident.to_string() == "default" => {},
_ => panic!("only #[rlp(default)] attribute is supported"),
}
*default_attribute_encountered = true;
true
} else {
false
};

match field.ty {
syn::Type::Path(ref path) => {
let ident = &path
.path
.segments
.first()
.expect("there must be at least 1 segment")
.ident;
let ident_type = ident.to_string();
if &ident_type == "Vec" {
if quotes.takes_index {
if default {
quote! { #id: #list(#index).unwrap_or_default(), }
} else {
quote! { #id: #list(#index)?, }
}
} else {
quote! { #id: #list()?, }
}
} else {
if quotes.takes_index {
if default {
quote! { #id: #single(#index).unwrap_or_default(), }
} else {
quote! { #id: #single(#index)?, }
}
} else {
quote! { #id: #single()?, }
}
}
}
_ => panic!("rlp_derive not supported"),
}
}
134 changes: 134 additions & 0 deletions rlp-derive/src/en.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
// This file is part of Parity Ethereum.

// Parity Ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity Ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.

use proc_macro2::TokenStream;
use quote::quote;

pub fn impl_encodable(ast: &syn::DeriveInput) -> TokenStream {
let body = match ast.data {
syn::Data::Struct(ref s) => s,
_ => panic!("#[derive(RlpEncodable)] is only defined for structs."),
};

let stmts: Vec<_> = body
.fields
.iter()
.enumerate()
.map(|(i, field)| encodable_field(i, field))
.collect();
let name = &ast.ident;

let stmts_len = stmts.len();
let stmts_len = quote! { #stmts_len };
let impl_block = quote! {
impl rlp::Encodable for #name {
fn rlp_append(&self, stream: &mut rlp::RlpStream) {
stream.begin_list(#stmts_len);
#(#stmts)*
}
}
};

quote! {
const _: () = {
extern crate rlp;
#impl_block
};
}
}

pub fn impl_encodable_wrapper(ast: &syn::DeriveInput) -> TokenStream {
let body = match ast.data {
syn::Data::Struct(ref s) => s,
_ => panic!("#[derive(RlpEncodableWrapper)] is only defined for structs."),
};

let stmt = {
let fields: Vec<_> = body.fields.iter().collect();
if fields.len() == 1 {
let field = fields.first().expect("fields.len() == 1; qed");
encodable_field(0, field)
} else {
panic!("#[derive(RlpEncodableWrapper)] is only defined for structs with one field.")
}
};

let name = &ast.ident;

let impl_block = quote! {
impl rlp::Encodable for #name {
fn rlp_append(&self, stream: &mut rlp::RlpStream) {
#stmt
}
}
};

quote! {
const _: () = {
extern crate rlp;
#impl_block
};
}
}

fn encodable_field(index: usize, field: &syn::Field) -> TokenStream {
let ident = match field.ident {
Some(ref ident) => quote! { #ident },
None => {
let index: syn::Index = index.into();
quote! { #index }
}
};

let id = quote! { self.#ident };

match field.ty {
syn::Type::Path(ref path) => {
let top_segment = path
.path
.segments
.first()
.expect("there must be at least 1 segment");
let ident = &top_segment.ident;
if &ident.to_string() == "Vec" {
let inner_ident = match top_segment.arguments {
syn::PathArguments::AngleBracketed(ref angle) => {
let ty = angle
.args
.first()
.expect("Vec has only one angle bracketed type; qed");
match *ty {
syn::GenericArgument::Type(syn::Type::Path(ref path)) => {
&path
.path
.segments
.first()
.expect("there must be at least 1 segment")
.ident
}
_ => panic!("rlp_derive not supported"),
}
}
_ => unreachable!("Vec has only one angle bracketed type; qed"),
};
quote! { stream.append_list::<#inner_ident, _>(&#id); }
} else {
quote! { stream.append(&#id); }
}
}
_ => panic!("rlp_derive not supported"),
}
}
52 changes: 52 additions & 0 deletions rlp-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
// This file is part of Parity Ethereum.

// Parity Ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity Ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.

extern crate proc_macro;

mod de;
mod en;

use de::{impl_decodable, impl_decodable_wrapper};
use en::{impl_encodable, impl_encodable_wrapper};
use proc_macro::TokenStream;

#[proc_macro_derive(RlpEncodable, attributes(rlp))]
pub fn encodable(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
let gen = impl_encodable(&ast);
gen.into()
}

#[proc_macro_derive(RlpEncodableWrapper)]
pub fn encodable_wrapper(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
let gen = impl_encodable_wrapper(&ast);
gen.into()
}

#[proc_macro_derive(RlpDecodable, attributes(rlp))]
pub fn decodable(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
let gen = impl_decodable(&ast);
gen.into()
}

#[proc_macro_derive(RlpDecodableWrapper)]
pub fn decodable_wrapper(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
let gen = impl_decodable_wrapper(&ast);
gen.into()
}
Loading