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 all 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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ members = [
"parity-path",
"plain_hasher",
"rlp",
"rlp-derive",
"runtime",
"transaction-pool",
"trace-time",
Expand Down
10 changes: 10 additions & 0 deletions rlp-derive/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Changelog

The format is based on [Keep a Changelog].

[Keep a Changelog]: http://keepachangelog.com/en/1.0.0/

## [Unreleased]

## [0.1.0] - 2020-02-12
- Extracted from parity-ethereum repo. [#343](https://github.com/paritytech/parity-common/pull/343)
19 changes: 19 additions & 0 deletions rlp-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "rlp-derive"
version = "0.1.0"
authors = ["Parity Technologies <[email protected]>"]
license = "MIT/Apache-2.0"
description = "Derive macro for #[derive(RlpEncodable, RlpDecodable)]"
homepage = "http://parity.io"
edition = "2018"

[lib]
proc-macro = true

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

[dev-dependencies]
rlp = "0.4.0"
163 changes: 163 additions & 0 deletions rlp-derive/src/de.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

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"),
}
}
109 changes: 109 additions & 0 deletions rlp-derive/src/en.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

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"),
}
}
54 changes: 54 additions & 0 deletions rlp-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Derive macro for `#[derive(RlpEncodable, RlpDecodable)]`.
//!
//! For example of usage see `./tests/rlp.rs`.
//!
//! This library also supports up to 1 `#[rlp(default)]` in a struct,
//! which is similar to [`#[serde(default)]`](https://serde.rs/field-attrs.html#default)
//! with the caveat that we use the `Default` value if
//! the field deserialization fails, as we don't serialize field
//! names and there is no way to tell if it is present or not.

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