Skip to content
Closed
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
202 changes: 158 additions & 44 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use syn::{
parse_quote,
punctuated::Punctuated,
token::Comma,
AngleBracketedGenericArguments,
Data,
DataEnum,
DataStruct,
Expand All @@ -45,7 +46,11 @@ use syn::{
ExprLit,
Field,
Fields,
GenericArgument,
Ident,
Lit,
PathArguments,
TypePath,
Variant,
};

Expand Down Expand Up @@ -73,20 +78,24 @@ fn generate_type(input: TokenStream2) -> Result<TokenStream2> {

let ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let generic_type_ids = ast.generics.type_params().map(|ty| {
let ty_ident = &ty.ident;
quote! {
::scale_info::meta_type::<#ty_ident>()
}
});

let ast: DeriveInput = syn::parse2(input.clone())?;
let build_type = match &ast.data {
let (build_type, phantom_params) = match &ast.data {
Data::Struct(ref s) => generate_composite_type(s),
Data::Enum(ref e) => generate_variant_type(e),
Data::Union(_) => return Err(Error::new_spanned(input, "Unions not supported")),
};

let generic_type_ids = ast.generics.type_params().fold(Vec::new(), |mut acc, ty| {
let ty_ident = &ty.ident;
if !phantom_params.contains(ty_ident) {
acc.push(quote! {
::scale_info::meta_type::<#ty_ident>()
});
}
acc
});

let type_info_impl = quote! {
impl #impl_generics ::scale_info::TypeInfo for #ident #ty_generics #where_clause {
type Identity = Self;
Expand All @@ -105,24 +114,121 @@ fn generate_type(input: TokenStream2) -> Result<TokenStream2> {

type FieldsList = Punctuated<Field, Comma>;

fn generate_fields(fields: &FieldsList) -> Vec<TokenStream2> {
fields
/// Given a type `Path`, find all generics params that are used in a
/// `PhantomData` and return them as a `Vec` of `Ident`.
/// E.g. given `utils::stuff::Thing<PhantomData<P>, Q, PhantomData<R>>`, return
/// `vec!["P", "R"]`
fn find_phantoms_in_path(path: &syn::Path) -> Vec<Ident> {
path.segments
.iter()
.map(|f| {
let (ty, ident) = (&f.ty, &f.ident);
let type_name = clean_type_string(&quote!(#ty).to_string());
.filter(|seg| seg.ident == "PhantomData")
.fold(Vec::new(), |mut acc, seg| {
if let PathArguments::AngleBracketed(AngleBracketedGenericArguments {
args,
..
}) = &seg.arguments
{
for arg in args {
if let GenericArgument::Type(syn::Type::Path(TypePath {
path, ..
})) = arg
{
for segment in path.segments.iter() {
acc.push(segment.ident.clone())
}
}
}
}
acc
})
}

if let Some(i) = ident {
quote! {
.field_of::<#ty>(stringify!(#i), #type_name)
/// Given a list of tuple elements, removes all `PhantomData` and returns a new
/// tuple along with any type parameters used in `PhantomData`.
fn scrub_phantoms_from_tuple(
tuple_elements: &Punctuated<syn::Type, Comma>,
paren_token: syn::token::Paren,
) -> (syn::Type, Vec<Ident>) {
let mut phantom_params = Vec::new();
let mut punctuated: Punctuated<_, Comma> = Punctuated::new();
for tuple_element in tuple_elements {
match tuple_element {
syn::Type::Path(syn::TypePath { path, .. }) => {
let phantoms = find_phantoms_in_path(path);
if phantoms.is_empty() {
punctuated.push(tuple_element.clone());
} else {
phantom_params.extend(phantoms);
}
} else {
quote! {
.field_of::<#ty>(#type_name)
}
syn::Type::Tuple(syn::TypeTuple { elems, paren_token }) => {
let (sub_tuple, phantoms) =
scrub_phantoms_from_tuple(elems, *paren_token);
punctuated.push(sub_tuple);
phantom_params.extend(phantoms);
}
// REVIEW: (dp) can there be anything but types and tuples in a tuple?
_ => unreachable!("Only types and tuples can appear in tuples"),
}
}
let tuple = syn::Type::Tuple(syn::TypeTuple {
elems: punctuated,
paren_token,
});

(tuple, phantom_params)
}

/// Generate code for each field of a struct (named or unnamed).
/// Filters out `PhantomData` fields and returns the type parameters used as a
/// `Vec` of `Ident` so that other code can match up the type parameters in the
/// generics section to `PhantomData` (and omit them).
fn generate_fields(fields: &FieldsList) -> (Vec<TokenStream2>, Vec<Ident>) {
// Collect all type params used with `PhantomData` anywhere in the type.
let mut phantom_params = Vec::new();
let field_tokens = fields.iter().fold(Vec::new(), |mut acc, field| {
let (ty, ident) = (&field.ty, &field.ident);
let type_name = clean_type_string(&quote!(#ty).to_string());
match ty {
// Regular types, e.g. `struct A<T> { a: PhantomData<T>, b: u8 }`
// Check for phantom types and skip them; record any
// PhantomData type params.
syn::Type::Path(syn::TypePath { path, .. }) => {
let phantoms = find_phantoms_in_path(path);
if phantoms.is_empty() {
let tokens = if let Some(i) = ident {
quote! {.field_of::<#ty>(stringify!(#i), #type_name) }
} else {
quote! {.field_of::<#ty>(#type_name) }
};
acc.push(tokens);
} else {
phantom_params.extend(phantoms);
}
}
})
.collect()
// If the type is a tuple, check it for any `PhantomData` elements and rebuild a new tuple without them.
syn::Type::Tuple(syn::TypeTuple { elems, paren_token }) => {
let (tuple, phantoms) = scrub_phantoms_from_tuple(elems, *paren_token);
let tokens = if let Some(i) = ident {
quote! { .field_of::<#tuple>(stringify!(#i), #type_name) }
} else {
quote! { .field_of::<#tuple>(#type_name)}
};
phantom_params.extend(phantoms);
acc.push(tokens)
}
_ => {
let tokens = if let Some(i) = ident {
quote! { .field_of::<#ty>(stringify!(#i), #type_name) }
} else {
quote! { .field_of::<#ty>(#type_name)}
};
acc.push(tokens)
}
}
acc
});
(field_tokens, phantom_params)
}

fn clean_type_string(input: &str) -> String {
Expand All @@ -135,32 +241,33 @@ fn clean_type_string(input: &str) -> String {
.replace("[ ", "[")
.replace(" ]", "]")
.replace(" (", "(")
// put back a space so that `a: u8, (bool, u8)` isn't turned into `a: u8,(bool, u8)`
.replace(",(", ", (")
.replace("( ", "(")
.replace(" )", ")")
.replace(" <", "<")
.replace("< ", "<")
.replace(" >", ">")
}

fn generate_composite_type(data_struct: &DataStruct) -> TokenStream2 {
let fields = match data_struct.fields {
/// Generate code for a composite type. Returns the token stream and all the
/// generic types used in `PhantomData` members.
fn generate_composite_type(data_struct: &DataStruct) -> (TokenStream2, Vec<Ident>) {
let (fields, phantom_params) = match data_struct.fields {
Fields::Named(ref fs) => {
let fields = generate_fields(&fs.named);
quote! { named()#( #fields )* }
let (fields, phantoms) = generate_fields(&fs.named);
(quote! { named()#( #fields )* }, phantoms)
}
Fields::Unnamed(ref fs) => {
let fields = generate_fields(&fs.unnamed);
quote! { unnamed()#( #fields )* }
}
Fields::Unit => {
quote! {
unit()
}
let (fields, phantoms) = generate_fields(&fs.unnamed);
(quote! { unnamed()#( #fields )* }, phantoms)
}
Fields::Unit => (quote! { unit() }, Vec::new()),
};
quote! {
composite(::scale_info::build::Fields::#fields)
}
(
quote! { composite(::scale_info::build::Fields::#fields) },
phantom_params,
)
}

type VariantList = Punctuated<Variant, Comma>;
Expand Down Expand Up @@ -205,19 +312,22 @@ fn is_c_like_enum(variants: &VariantList) -> bool {
})
}

fn generate_variant_type(data_enum: &DataEnum) -> TokenStream2 {
/// Build enum variants while keeping track of any `PhantomData` members so we can skip them later.
fn generate_variant_type(data_enum: &DataEnum) -> (TokenStream2, Vec<Ident>) {
let variants = &data_enum.variants;

if is_c_like_enum(&variants) {
return generate_c_like_enum_def(variants)
return (generate_c_like_enum_def(variants), Vec::new())
}

let mut phantom_params = Vec::new();
let variants = variants.into_iter().map(|v| {
let ident = &v.ident;
let v_name = quote! {stringify!(#ident) };
match v.fields {
Fields::Named(ref fs) => {
let fields = generate_fields(&fs.named);
let (fields, phantoms) = generate_fields(&fs.named);
phantom_params.extend(phantoms);
quote! {
.variant(
#v_name,
Expand All @@ -227,7 +337,8 @@ fn generate_variant_type(data_enum: &DataEnum) -> TokenStream2 {
}
}
Fields::Unnamed(ref fs) => {
let fields = generate_fields(&fs.unnamed);
let (fields, phantoms) = generate_fields(&fs.unnamed);
phantom_params.extend(phantoms);
quote! {
.variant(
#v_name,
Expand All @@ -243,10 +354,13 @@ fn generate_variant_type(data_enum: &DataEnum) -> TokenStream2 {
}
}
});
quote! {
variant(
::scale_info::build::Variants::with_fields()
#( #variants)*
)
}
(
quote! {
variant(
::scale_info::build::Variants::with_fields()
#( #variants)*
)
},
phantom_params,
)
}
7 changes: 1 addition & 6 deletions src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ use crate::prelude::{
collections::BTreeMap,
marker::PhantomData,
string::String,
vec,
vec::Vec,
};

use crate::{
build::*,
meta_type,
MetaType,
Path,
Type,
Expand Down Expand Up @@ -243,9 +241,6 @@ where
type Identity = Self;

fn type_info() -> Type {
Type::builder()
.path(Path::prelude("PhantomData"))
.type_params(vec![meta_type::<T>()])
.composite(Fields::unit())
Type::new(Path::voldemort(), Vec::new(), TypeDefTuple::unit())
}
}
39 changes: 32 additions & 7 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,7 @@ fn prelude_items() {
.variant("Err", Fields::unnamed().field_of::<String>("E"))
)
);
assert_type!(
PhantomData<i32>,
Type::builder()
.path(Path::prelude("PhantomData"))
.type_params(tuple_meta_type!(i32))
.composite(Fields::unit())
);
assert_type!(PhantomData<i32>, TypeDefTuple::new(tuple_meta_type!()));
}

#[test]
Expand Down Expand Up @@ -154,3 +148,34 @@ fn struct_with_generics() {
.composite(Fields::named().field_of::<Box<MyStruct<bool>>>("data", "T"));
assert_type!(SelfTyped, expected_type);
}

#[test]
fn basic_struct_with_phantoms() {
#[allow(unused)]
struct SomeStruct<T> {
a: u8,
marker: PhantomData<T>,
}

impl<T> TypeInfo for SomeStruct<T>
where
T: TypeInfo + 'static,
{
type Identity = Self;

fn type_info() -> Type {
Type::builder()
.path(Path::new("SomeStruct", module_path!()))
.type_params(tuple_meta_type!(T))
.composite(Fields::named().field_of::<u8>("a", "T"))
}
}

// Normal struct
let struct_bool_type_info = Type::builder()
.path(Path::from_segments(vec!["scale_info", "tests", "SomeStruct"]).unwrap())
.type_params(tuple_meta_type!(bool))
.composite(Fields::named().field_of::<u8>("a", "T"));

assert_type!(SomeStruct<bool>, struct_bool_type_info);
}
Loading