Commit 85e538fa authored by thiolliere's avatar thiolliere Committed by Bastian Köcher
Browse files

impl skip on master (#62)



* impl skip fields and variants (#58)

* impl skip variants

* impl skip on fields

* reorganize code

* in code rename

Co-Authored-By: thiolliere's avatarthiolliere <gui.thiolliere@gmail.com>

* in code rename

* code refactor

* refactor: variant filter

* impl skip variant and fields
parent 35aec447
......@@ -34,14 +34,16 @@ pub fn quote(data: &Data, type_name: &Ident, input: &TokenStream) -> TokenStream
},
},
Data::Enum(ref data) => {
if data.variants.len() > 256 {
let data_variants = || data.variants.iter().filter(|variant| crate::utils::get_skip(&variant.attrs).is_none());
if data_variants().count() > 256 {
return Error::new(
Span::call_site(),
"Currently only enums with at most 256 variants are encodable."
).to_compile_error();
}
let recurse = data.variants.iter().enumerate().map(|(i, v)| {
let recurse = data_variants().enumerate().map(|(i, v)| {
let name = &v.ident;
let index = utils::index(v, i);
......@@ -75,11 +77,12 @@ pub fn quote(data: &Data, type_name: &Ident, input: &TokenStream) -> TokenStream
fn create_decode_expr(field: &Field, name: &String, input: &TokenStream) -> TokenStream {
let encoded_as = utils::get_encoded_as_type(field);
let compact = utils::get_enable_compact(field);
let skip = utils::get_skip(&field.attrs).is_some();
if encoded_as.is_some() && compact {
if encoded_as.is_some() as u8 + compact as u8 + skip as u8 > 1 {
return Error::new(
Span::call_site(),
"`encoded_as` and `compact` can not be used at the same time!"
"`encoded_as`, `compact` and `skip` can only be used one at a time!"
).to_compile_error();
}
......@@ -106,6 +109,8 @@ fn create_decode_expr(field: &Field, name: &String, input: &TokenStream) -> Toke
}
}
}
} else if skip {
quote_spanned! { field.span() => Default::default() }
} else {
quote_spanned! { field.span() =>
{
......
......@@ -16,7 +16,7 @@ use std::str::from_utf8;
use proc_macro2::{Span, TokenStream};
use syn::{
Data, Field, Fields, Ident, Index,
Data, Field, Fields, Ident,
punctuated::Punctuated,
spanned::Spanned,
token::Comma,
......@@ -26,6 +26,7 @@ use crate::utils;
type FieldsList = Punctuated<Field, Comma>;
// Encode a signle field by using using_encoded, must not have skip attribute
fn encode_single_field(
closure: &TokenStream,
field: &Field,
......@@ -34,6 +35,13 @@ fn encode_single_field(
let encoded_as = utils::get_encoded_as_type(field);
let compact = utils::get_enable_compact(field);
if utils::get_skip(&field.attrs).is_some() {
return Error::new(
Span::call_site(),
"Internal error: cannot encode single field optimisation if skipped"
).to_compile_error();
}
if encoded_as.is_some() && compact {
return Error::new(
Span::call_site(),
......@@ -77,11 +85,12 @@ fn encode_fields<F>(
let field = field_name(i, &f.ident);
let encoded_as = utils::get_encoded_as_type(f);
let compact = utils::get_enable_compact(f);
let skip = utils::get_skip(&f.attrs).is_some();
if encoded_as.is_some() && compact {
if encoded_as.is_some() as u8 + compact as u8 + skip as u8 > 1 {
return Error::new(
Span::call_site(),
"`encoded_as` and `compact` can not be used at the same time!"
"`encoded_as`, `compact` and `skip` can only be used one at a time!"
).to_compile_error();
}
......@@ -107,6 +116,8 @@ fn encode_fields<F>(
);
}
}
} else if skip {
quote! {}
} else {
quote_spanned! { f.span() =>
#dest.push(#field);
......@@ -121,23 +132,36 @@ fn encode_fields<F>(
fn try_impl_encode_single_field_optimisation(data: &Data) -> Option<TokenStream> {
let ref closure = quote!(f);
fn filter_skip_named<'a>(fields: &'a syn::FieldsNamed) -> impl Iterator<Item=&Field> + 'a {
fields.named.iter()
.filter(|f| utils::get_skip(&f.attrs).is_none())
}
fn filter_skip_unnamed<'a>(fields: &'a syn::FieldsUnnamed) -> impl Iterator<Item=(usize, &Field)> + 'a {
fields.unnamed.iter()
.enumerate()
.filter(|(_, f)| utils::get_skip(&f.attrs).is_none())
}
let optimisation = match *data {
Data::Struct(ref data) => {
match data.fields {
Fields::Named(ref fields) if fields.named.len() == 1 => {
let field = fields.named.first().unwrap();
let ref name = field.value().ident;
Fields::Named(ref fields) if filter_skip_named(fields).count() == 1 => {
let field = filter_skip_named(fields).next().unwrap();
let ref name = field.ident;
Some(encode_single_field(
closure,
field.value(),
field,
quote!(&self.#name)
))
},
Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => {
Fields::Unnamed(ref fields) if filter_skip_unnamed(fields).count() == 1 => {
let (ids, field) = filter_skip_unnamed(fields).next().unwrap();
Some(encode_single_field(
closure,
fields.unnamed.first().unwrap().value(),
quote!(&self.0)
field,
quote!(&self.#ids)
))
},
_ => None,
......@@ -175,14 +199,16 @@ fn impl_encode(data: &Data, type_name: &Ident) -> TokenStream {
}
},
Data::Enum(ref data) => {
if data.variants.len() > 256 {
let data_variants = || data.variants.iter().filter(|variant| crate::utils::get_skip(&variant.attrs).is_none());
if data_variants().count() > 256 {
return Error::new(
Span::call_site(),
"Currently only enums with at most 256 variants are encodable."
).to_compile_error();
}
let recurse = data.variants.iter().enumerate().map(|(i, f)| {
let recurse = data_variants().enumerate().map(|(i, f)| {
let name = &f.ident;
let index = utils::index(f, i);
......@@ -245,6 +271,7 @@ fn impl_encode(data: &Data, type_name: &Ident) -> TokenStream {
quote! {
match *#self_ {
#( #recurse )*,
_ => (),
}
}
},
......
......@@ -58,12 +58,17 @@ pub fn encode_derive(input: TokenStream) -> TokenStream {
Ok(input) => input,
Err(e) => return e.to_compile_error().into(),
};
if let Some(span) = utils::get_skip(&input.attrs) {
return Error::new(span, "invalid attribute `skip` on root input")
.to_compile_error().into();
}
if let Err(e) = trait_bounds::add(
&input.ident,
&mut input.generics,
&input.data,
parse_quote!(_parity_codec::Encode)
parse_quote!(_parity_codec::Encode),
None,
) {
return e.to_compile_error().into();
}
......@@ -104,12 +109,17 @@ pub fn decode_derive(input: TokenStream) -> TokenStream {
Ok(input) => input,
Err(e) => return e.to_compile_error().into(),
};
if let Some(span) = utils::get_skip(&input.attrs) {
return Error::new(span, "invalid attribute `skip` on root input")
.to_compile_error().into();
}
if let Err(e) = trait_bounds::add(
&input.ident,
&mut input.generics,
&input.data,
parse_quote!(_parity_codec::Decode),
Some(parse_quote!(Default))
) {
return e.to_compile_error().into();
}
......
......@@ -103,13 +103,14 @@ pub fn add(
generics: &mut Generics,
data: &syn::Data,
codec_bound: syn::Path,
codec_skip_bound: Option<syn::Path>,
) -> syn::Result<()> {
let ty_params = generics.type_params().map(|p| p.ident.clone()).collect::<Vec<_>>();
if ty_params.is_empty() {
return Ok(());
}
let codec_types = collect_types(&data, needs_codec_bound)?
let codec_types = collect_types(&data, needs_codec_bound, variant_not_skipped)?
.into_iter()
// Only add a bound if the type uses a generic
.filter(|ty| type_contain_idents(ty, &ty_params))
......@@ -128,13 +129,23 @@ pub fn add(
.filter(|ty| !type_or_sub_type_path_starts_with_ident(ty, input_ident))
.collect::<Vec<_>>();
let compact_types = collect_types(&data, needs_has_compact_bound)?
let compact_types = collect_types(&data, needs_has_compact_bound, variant_not_skipped)?
.into_iter()
// Only add a bound if the type uses a generic
.filter(|ty| type_contain_idents(ty, &ty_params))
.collect::<Vec<_>>();
if !codec_types.is_empty() || !compact_types.is_empty() {
let skip_types = if codec_skip_bound.is_some() {
collect_types(&data, needs_default_bound, variant_not_skipped)?
.into_iter()
// Only add a bound if the type uses a generic
.filter(|ty| type_contain_idents(ty, &ty_params))
.collect::<Vec<_>>()
} else {
Vec::new()
};
if !codec_types.is_empty() || !compact_types.is_empty() || !skip_types.is_empty() {
let where_clause = generics.make_where_clause();
codec_types
......@@ -149,6 +160,13 @@ pub fn add(
.for_each(|ty| {
where_clause.predicates.push(parse_quote!(#ty : #has_compact_bound))
});
skip_types
.into_iter()
.for_each(|ty| {
let codec_skip_bound = codec_skip_bound.as_ref().unwrap();
where_clause.predicates.push(parse_quote!(#ty : #codec_skip_bound))
});
}
Ok(())
......@@ -157,13 +175,26 @@ pub fn add(
fn needs_codec_bound(field: &syn::Field) -> bool {
!crate::utils::get_enable_compact(field)
&& crate::utils::get_encoded_as_type(field).is_none()
&& crate::utils::get_skip(&field.attrs).is_none()
}
fn needs_has_compact_bound(field: &syn::Field) -> bool {
crate::utils::get_enable_compact(field)
}
fn collect_types(data: &syn::Data, type_filter: fn(&syn::Field) -> bool) -> syn::Result<Vec<syn::Type>> {
fn needs_default_bound(field: &syn::Field) -> bool {
crate::utils::get_skip(&field.attrs).is_some()
}
fn variant_not_skipped(variant: &syn::Variant) -> bool {
crate::utils::get_skip(&variant.attrs).is_none()
}
fn collect_types(
data: &syn::Data,
type_filter: fn(&syn::Field) -> bool,
variant_filter: fn(&syn::Variant) -> bool,
) -> syn::Result<Vec<syn::Type>> {
use syn::*;
let types = match *data {
......@@ -179,19 +210,21 @@ fn collect_types(data: &syn::Data, type_filter: fn(&syn::Field) -> bool) -> syn:
Fields::Unit => { Vec::new() },
},
Data::Enum(ref data) => data.variants.iter().flat_map(|variant| {
match &variant.fields {
| Fields::Named(FieldsNamed { named: fields , .. })
| Fields::Unnamed(FieldsUnnamed { unnamed: fields, .. }) => {
fields.iter()
.filter(|f| type_filter(f))
.map(|f| f.ty.clone())
.collect()
},
Fields::Unit => { Vec::new() },
}
}).collect(),
Data::Enum(ref data) => data.variants.iter()
.filter(|variant| variant_filter(variant))
.flat_map(|variant| {
match &variant.fields {
| Fields::Named(FieldsNamed { named: fields , .. })
| Fields::Unnamed(FieldsUnnamed { unnamed: fields, .. }) => {
fields.iter()
.filter(|f| type_filter(f))
.map(|f| f.ty.clone())
.collect()
},
Fields::Unit => { Vec::new() },
}
}).collect(),
Data::Union(_) => return Err(Error::new(Span::call_site(), "Union types are not supported.")),
};
......
......@@ -13,7 +13,7 @@
// limitations under the License.
use syn::{Meta, NestedMeta, Lit, Attribute, Variant, Field};
use proc_macro2::TokenStream;
use proc_macro2::{TokenStream, Span};
use std::str::FromStr;
fn find_meta_item<'a, F, R, I>(itr: I, pred: F) -> Option<R> where
......@@ -88,3 +88,17 @@ pub fn get_enable_compact(field_entry: &Field) -> bool {
None
}).is_some()
}
// return span of skip if found
pub fn get_skip(attrs: &Vec<Attribute>) -> Option<Span> {
// look for `skip` in the attributes
find_meta_item(attrs.iter(), |meta| {
if let NestedMeta::Meta(Meta::Word(ref word)) = meta {
if word == "skip" {
return Some(word.span());
}
}
None
})
}
#[macro_use]
extern crate parity_codec_derive;
use parity_codec::{Encode, Decode, HasCompact, Compact, EncodeAsRef, CompactAs};
use parity_codec::{Encode, Decode, HasCompact};
#[derive(Debug, PartialEq, Encode, Decode)]
struct S {
x: u32,
}
#[derive(Debug, PartialEq, Encode, Decode)]
struct SSkip {
#[codec(skip)]
s1: u32,
x: u32,
#[codec(skip)]
s2: u32,
}
#[derive(Debug, PartialEq, Encode, Decode)]
struct Sc {
#[codec(compact)]
......@@ -23,6 +32,9 @@ struct Sh<T: HasCompact> {
#[derive(Debug, PartialEq, Encode, Decode)]
struct U(u32);
#[derive(Debug, PartialEq, Encode, Decode)]
struct USkip(#[codec(skip)] u32, u32, #[codec(skip)] u32);
#[derive(Debug, PartialEq, Encode, Decode)]
struct Uc(#[codec(compact)] u32);
......@@ -33,30 +45,38 @@ struct Uh<T: HasCompact>(#[codec(encoded_as = "<T as HasCompact>::Type")] T);
fn test_encoding() {
let x = 3u32;
let s = S { x };
let s_skip = SSkip { x, s1: Default::default(), s2: Default::default() };
let sc = Sc { x };
let sh = Sh { x };
let u = U(x);
let u_skip = USkip(Default::default(), x, Default::default());
let uc = Uc(x);
let uh = Uh(x);
let mut s_encoded: &[u8] = &[3, 0, 0, 0];
let mut s_skip_encoded: &[u8] = &[3, 0, 0, 0];
let mut sc_encoded: &[u8] = &[12];
let mut sh_encoded: &[u8] = &[12];
let mut u_encoded: &[u8] = &[3, 0, 0, 0];
let mut u_skip_encoded: &[u8] = &[3, 0, 0, 0];
let mut uc_encoded: &[u8] = &[12];
let mut uh_encoded: &[u8] = &[12];
assert_eq!(s.encode(), s_encoded);
assert_eq!(s_skip.encode(), s_skip_encoded);
assert_eq!(sc.encode(), sc_encoded);
assert_eq!(sh.encode(), sh_encoded);
assert_eq!(u.encode(), u_encoded);
assert_eq!(u_skip.encode(), u_skip_encoded);
assert_eq!(uc.encode(), uc_encoded);
assert_eq!(uh.encode(), uh_encoded);
assert_eq!(s, S::decode(&mut s_encoded).unwrap());
assert_eq!(s_skip, SSkip::decode(&mut s_skip_encoded).unwrap());
assert_eq!(sc, Sc::decode(&mut sc_encoded).unwrap());
assert_eq!(sh, Sh::decode(&mut sh_encoded).unwrap());
assert_eq!(u, U::decode(&mut u_encoded).unwrap());
assert_eq!(u_skip, USkip::decode(&mut u_skip_encoded).unwrap());
assert_eq!(uc, Uc::decode(&mut uc_encoded).unwrap());
assert_eq!(uh, Uh::decode(&mut uh_encoded).unwrap());
}
#[macro_use]
extern crate parity_codec_derive;
use parity_codec::{Encode, Decode};
#[test]
fn enum_struct_test() {
#[derive(PartialEq, Debug, Default)]
struct UncodecType;
#[derive(PartialEq, Debug)]
struct UncodecUndefaultType;
#[derive(PartialEq, Debug, Encode, Decode)]
enum Enum<T=UncodecType, S=UncodecUndefaultType> {
#[codec(skip)]
A(S),
B {
#[codec(skip)]
_b1: T,
b2: u32,
},
C(
#[codec(skip)]
T,
u32,
),
}
#[derive(PartialEq, Debug, Encode, Decode)]
struct StructNamed<T=UncodecType> {
#[codec(skip)]
a: T,
b: u32,
}
#[derive(PartialEq, Debug, Encode, Decode)]
struct StructUnnamed<T=UncodecType>(
#[codec(skip)]
T,
u32,
);
let ea: Enum = Enum::A(UncodecUndefaultType);
let eb: Enum = Enum::B { _b1: UncodecType, b2: 1 };
let ec: Enum = Enum::C(UncodecType, 1);
let sn = StructNamed { a: UncodecType, b: 1 };
let su = StructUnnamed(UncodecType, 1);
assert_eq!(ea.encode(), Vec::new());
let mut eb_encoded: &[u8] = &eb.encode();
let mut ec_encoded: &[u8] = &ec.encode();
let mut sn_encoded: &[u8] = &sn.encode();
let mut su_encoded: &[u8] = &su.encode();
assert_eq!(Enum::decode(&mut eb_encoded).unwrap(), eb);
assert_eq!(Enum::decode(&mut ec_encoded).unwrap(), ec);
assert_eq!(StructNamed::decode(&mut sn_encoded).unwrap(), sn);
assert_eq!(StructUnnamed::decode(&mut su_encoded).unwrap(), su);
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment