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

Generics #12

Closed
wants to merge 85 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
4ebc22b
Fields builder misc
ascjones Apr 3, 2020
32b218c
WIP: generics spike
ascjones Apr 4, 2020
4974967
Experiment extend TypeInfo trait with path, id and type params
ascjones Apr 7, 2020
1688db7
Add generics json test
ascjones Apr 15, 2020
12ca8cf
WIP generics impl
ascjones Apr 21, 2020
90a925b
Fix some errors, add some impls
ascjones Apr 21, 2020
65885ae
Add nested generic example
ascjones Apr 21, 2020
2e224d7
More spiking
ascjones Apr 23, 2020
3377a3d
Adding param stack
ascjones Apr 27, 2020
8343a92
More pre scale-info-generics spike WIP
ascjones May 5, 2020
ab02924
Copy proof of concept impl from scale-info-generics
ascjones May 5, 2020
e4d4b8b
Fmt
ascjones May 5, 2020
570fbc2
WIP fix tests
ascjones May 5, 2020
9b2d561
Continued WIP fixing tests
ascjones May 6, 2020
ebf67e7
Compiles...yes. Works...no.
ascjones May 6, 2020
889785c
Fix path test
ascjones May 6, 2020
56a0324
Update derive macro WIP
ascjones May 7, 2020
d2b3560
Basic generic struct derive working
ascjones May 7, 2020
cb2fe68
Fix SelfTyped struct derive test
ascjones May 7, 2020
f868f95
Fix variant derive test
ascjones May 7, 2020
63dac99
Fix array primitives impls
ascjones May 7, 2020
b242ea3
Fix prelude items tests
ascjones May 7, 2020
09acadf
Fix tuple impl tests
ascjones May 7, 2020
506f01f
Add pretty_assert, fix a couple of json tests
ascjones May 8, 2020
c1c6fcb
Fix derive for concrete fields, more json tests passing
ascjones May 8, 2020
0c40ccf
Fix tuple struct test
ascjones May 8, 2020
3aaa97e
Fix concrete generic field derive
ascjones May 8, 2020
d297109
WIP json generics test
ascjones May 8, 2020
b65bcda
json generics test fixed and annotated
ascjones May 8, 2020
534e878
Fmt
ascjones May 8, 2020
7496195
Existing json tests pass
ascjones May 8, 2020
4fe206f
Implement Tuple as a generic type, remove tuple_meta_type macro
ascjones May 11, 2020
1b9e310
Make generic tuple derive work
ascjones May 14, 2020
8b60859
Fmt
ascjones May 14, 2020
51aa4f7
Satisy clippy
ascjones May 14, 2020
79db360
More clippy fixes
ascjones May 14, 2020
9619c1d
Restore tm_std shim
ascjones May 14, 2020
450f9b4
Support generic arrays
ascjones May 15, 2020
ccaa650
Generic refs
ascjones May 21, 2020
b70f233
Fixing warnings
ascjones May 21, 2020
4267018
Fix "unnecessary lifetime parameter" warning
ascjones May 21, 2020
e65ea38
Multiple lifetimes for parameterized refs
ascjones May 21, 2020
425d4b3
Fmt
ascjones May 21, 2020
d3e4585
Fix no_std tests
ascjones May 21, 2020
3c339ce
Renamed RegistryType to InternedType
ascjones May 22, 2020
f36c3b4
Move registry mod and interner to it's own dir
ascjones May 22, 2020
56bcd09
WIP refactoring
ascjones May 22, 2020
dff74a5
InternedType constructors
ascjones May 26, 2020
8b5d851
Fmt and interned type constructors
ascjones May 26, 2020
7cda91f
More refactoring
ascjones May 26, 2020
7e95cf6
Allow unused registry methods for now
ascjones May 27, 2020
cda61c6
Use peekable api on param stack
ascjones May 27, 2020
1a643c2
Encapsulate MetatypeParameterized
ascjones May 27, 2020
0a00ac1
More encapsulating of InternedType fields
ascjones May 27, 2020
e2c06cc
Encapsulate MetaTypeParameter fields
ascjones May 27, 2020
4874c96
Encapsulate MetaTypeGeneric fields
ascjones May 27, 2020
5d9942c
Fmt
ascjones May 27, 2020
efc3df8
Extract register parameterized type
ascjones May 27, 2020
4d094ad
Fmt
ascjones May 27, 2020
a738660
More refactoring
ascjones May 27, 2020
b139c06
Remove MetaTypeGeneric: Step 1
ascjones May 28, 2020
8b82ea8
Extract MetaTypeDefinition
ascjones May 28, 2020
c8e7de5
More refactoring
ascjones May 29, 2020
3eb851f
Remove MetaTypeParameterized
ascjones May 29, 2020
628d4ea
Change concrete params to MetaTypeConcrete
ascjones May 29, 2020
1f7bb46
Simplify parameterized type registration logic
ascjones May 29, 2020
606c9c7
Refactor MetaType
ascjones May 31, 2020
325d8aa
Fix bug with MetaType parameter and add type name for debugging
ascjones May 31, 2020
cc735bc
Fmt
ascjones May 31, 2020
ce1ce44
Rename InternedTypeId::Any to Concrete
ascjones May 31, 2020
864fd87
Use MetaType Eq impl to compare concrete type ids
ascjones May 31, 2020
9a7b831
Remove some derived impls
ascjones May 31, 2020
8577ce8
Remove unnecessary Ord impls
ascjones May 31, 2020
7edba56
Remove redundant Ord constraints/impls
ascjones May 31, 2020
07879a7
Remove redundant clones/intos
ascjones Jun 1, 2020
a64f89d
Make concrete with params explicit
ascjones Jun 1, 2020
75654a7
Accessors for MetaTypeParameter
ascjones Jun 1, 2020
ee76503
Use default nightly profile which includes rustfmt and clippy
ascjones Jun 1, 2020
1afefdc
Fix no_std build
ascjones Jun 1, 2020
bdbd0cd
Rename InternedType to NormalizedType
ascjones Jun 1, 2020
7f1b1fd
Add test for out of order generic parameter usage
ascjones Jun 8, 2020
814a89a
Rename MetaTypeDefinition to MetaTypeInfo
ascjones Jun 8, 2020
fc36504
Register generic type parameters as part of definition
ascjones Jun 9, 2020
01bae9b
Add Form::GenericType for generic type definitions
ascjones Jun 10, 2020
e10b3c1
Fix up tests and derive
ascjones Jun 10, 2020
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
30 changes: 14 additions & 16 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,34 @@ jobs:

steps:
- uses: actions/checkout@v2

- name: setup
run: |
rustup install nightly-2020-03-11
rustup component add rustfmt --toolchain nightly-2020-03-11
rustup component add clippy --toolchain nightly-2020-03-11

rustup install nightly --profile default

- name: fmt
run: |
cargo +nightly-2020-03-11 fmt --version
cargo +nightly-2020-03-11 fmt --all -- --check
cargo +nightly fmt --version
cargo +nightly fmt --all -- --check

- name: clippy
run: |
cargo +nightly-2020-03-11 clippy --version
cargo +nightly-2020-03-11 clippy --all -- -D warnings
cargo +nightly clippy --version
cargo +nightly clippy --all -- -D warnings

- name: build
run: |
cargo --version --verbose
cargo build --all
cargo build --all --no-default-features

- name: test
run: |
cargo test --all

- name: test no-std
run: |
cd ./test_suite/derive_tests_no_std
cargo +nightly-2020-03-11 build --no-default-features
cargo +nightly build --no-default-features


2 changes: 1 addition & 1 deletion derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ proc-macro = true

[dependencies]
quote = "1.0"
syn = { version = "1.0", features = ["derive"] }
syn = { version = "1.0", features = ["derive", "visit-mut"] }
proc-macro2 = "1.0"
210 changes: 156 additions & 54 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ extern crate alloc;

mod impl_wrapper;

use alloc::vec;
use alloc::vec::Vec;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
Expand All @@ -29,7 +30,9 @@ use syn::{
parse_quote,
punctuated::Punctuated,
token::Comma,
Data, DataEnum, DataStruct, DeriveInput, Expr, ExprLit, Field, Fields, Lit, Variant,
visit_mut::VisitMut,
Data, DataEnum, DataStruct, DeriveInput, Expr, ExprLit, Field, Fields, GenericArgument, Lifetime, Lit,
PathArguments, Type, TypeParam, Variant,
};

#[proc_macro_derive(Metadata)]
Expand All @@ -54,30 +57,41 @@ fn generate_type(input: TokenStream2) -> Result<TokenStream2> {
p.bounds.push(parse_quote!('static));
});

let mut static_lifetime_generics = ast.generics.clone();
static_lifetime_generics
.lifetimes_mut()
.for_each(|l| *l = parse_quote!('static));

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! {
<#ty_ident as _scale_info::Metadata>::meta_type()
}
});
let impl_generics_no_lifetimes = ast.generics.type_params();
let (_, _, where_clause) = ast.generics.split_for_impl();
let (_, ty_generics, _) = static_lifetime_generics.split_for_impl();
let generic_type_ids = ast.generics.type_params().map(|ty| &ty.ident);

let ast: DeriveInput = syn::parse2(input.clone())?;
let type_params = ast.generics.type_params().collect::<Vec<_>>();
let (type_kind, build_type) = match &ast.data {
Data::Struct(ref s) => (quote!(TypeComposite), generate_composite_type(s)),
Data::Enum(ref e) => (quote!(TypeVariant), generate_variant_type(e)),
Data::Struct(ref s) => (quote!(TypeComposite), generate_composite_type(s, &type_params)),
Data::Enum(ref e) => (quote!(TypeVariant), generate_variant_type(e, &type_params)),
Data::Union(_) => return Err(Error::new_spanned(input, "Unions not supported")),
};

let meta_type_params =
generic_type_ids.map(|tp| quote! { _scale_info::MetaTypeParameter::new::<Self, #tp>(stringify!(#tp)) });

let type_info_impl = quote! {
impl #impl_generics _scale_info::TypeInfo for #ident #ty_generics #where_clause {
impl <#( #impl_generics_no_lifetimes ),*> _scale_info::TypeInfo for #ident #ty_generics #where_clause {
fn path() -> _scale_info::Path {
_scale_info::Path::new(stringify!(#ident), module_path!())
}

fn params() -> __core::Vec<_scale_info::MetaTypeParameter> {
__core::vec![
#( #meta_type_params ),*
]
}

fn type_info() -> _scale_info::Type {
_scale_info::#type_kind::new()
.path(_scale_info::Path::new(stringify!(#ident), module_path!()))
.type_params(__core::vec![ #( #generic_type_ids ),* ])
.#build_type
.into()
_scale_info::#type_kind::new(#build_type).into()
}
}
};
Expand All @@ -87,46 +101,138 @@ fn generate_type(input: TokenStream2) -> Result<TokenStream2> {

type FieldsList = Punctuated<Field, Comma>;

fn generate_fields(fields: &FieldsList) -> Vec<TokenStream2> {
fields
.iter()
.map(|f| {
let (ty, ident) = (&f.ty, &f.ident);
if let Some(i) = ident {
quote! {
.field_of::<#ty>(stringify!(#i))
}
fn generate_fields(fields: &FieldsList, type_params: &[&TypeParam]) -> Vec<TokenStream2> {
fields.iter().map(|f| generate_field(f, type_params)).collect()
}

fn generate_field(field: &Field, type_params: &[&TypeParam]) -> TokenStream2 {
let (ty, ident) = (&field.ty, &field.ident);

// Replace any field lifetime params with `static to prevent "unnecessary lifetime parameter"
// warning. Any lifetime parameters are specified as 'static in the type of the impl.
struct StaticLifetimesReplace;
impl VisitMut for StaticLifetimesReplace {
fn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) {
*lifetime = parse_quote!('static)
}
}
let mut ty = ty.clone();
StaticLifetimesReplace.visit_type_mut(&mut ty);

let (field_method, field_args) = if let Some(ty) = get_type_parameter(&ty, type_params) {
// it's a field of a parameter e.g. `a: T`
(quote!( .parameter_field::<Self, #ty> ), quote!(stringify!(#ty)))
} else {
let type_params = generate_parameterized_field_parameters(&ty, type_params, true);
if type_params.is_empty() {
// it's a concrete non-generic type
(quote!( .field_of::<#ty> ), quote!())
} else {
let parameters = quote! {
__core::vec![
#( #type_params ),*
]
};
(quote!( .parameterized_field::<#ty> ), quote!( #parameters ))
}
};

if let Some(i) = ident {
// it's a named field, assumes the field name is the first argument to the field method
quote! {
#field_method(stringify!(#i), #field_args)
}
} else {
// it's an unnamed field
quote! {
#field_method(#field_args)
}
}
}

fn get_type_parameter(ty: &Type, type_params: &[&TypeParam]) -> Option<Type> {
match ty {
Type::Path(path) => {
if type_params.iter().any(|tp| Some(&tp.ident) == path.path.get_ident()) {
Some(ty.clone())
} else {
quote! {
.field_of::<#ty>()
None
}
}
// references to type params are just the plain type param (`&'a T` and `&'a mut T`) -> T
Type::Reference(reference) => Some(*reference.elem.clone()),
_ => None,
}
}

fn generate_parameterized_field_parameters(ty: &Type, type_params: &[&TypeParam], is_root: bool) -> Vec<TokenStream2> {
if let Some(ty) = get_type_parameter(ty, type_params) {
return vec![quote! {
_scale_info::MetaTypeParameterValue::type_param::<Self, #ty>(stringify!(#ty))
}];
}

match ty {
Type::Path(path) => {
if let Some(segment) = path.path.segments.last() {
match &segment.arguments {
PathArguments::None => {
if is_root {
Vec::new()
} else {
vec![quote! {
_scale_info::MetaTypeParameterValue::concrete::<#ty>()
}]
}
}
PathArguments::AngleBracketed(args) => args
.args
.iter()
.flat_map(|arg| match arg {
GenericArgument::Type(ty) => {
generate_parameterized_field_parameters(ty, type_params, false)
}
_ => Vec::new(),
})
.collect(),
PathArguments::Parenthesized(args) => args
.inputs
.iter()
.flat_map(|arg_ty| generate_parameterized_field_parameters(arg_ty, type_params, false))
.collect(),
}
} else {
Vec::new()
}
})
.collect()
}
Type::Tuple(tuple) => tuple
.elems
.iter()
.flat_map(|ty| generate_parameterized_field_parameters(ty, type_params, false))
.collect(),
Type::Array(array) => generate_parameterized_field_parameters(&array.elem, type_params, false),
_ => Vec::new(), // todo: handle references, slices and any other parameterized types
}
}

fn generate_composite_type(data_struct: &DataStruct) -> TokenStream2 {
fn generate_composite_type(data_struct: &DataStruct, type_params: &[&TypeParam]) -> TokenStream2 {
match data_struct.fields {
Fields::Named(ref fs) => {
let fields = generate_fields(&fs.named);
let fields = generate_fields(&fs.named, type_params);
quote! {
fields(
_scale_info::Fields::named()
#( #fields )*
)
_scale_info::Fields::named()
#( #fields )*
}
}
Fields::Unnamed(ref fs) => {
let fields = generate_fields(&fs.unnamed);
let fields = generate_fields(&fs.unnamed, type_params);
quote! {
fields(
_scale_info::Fields::unnamed()
#( #fields )*
)
_scale_info::Fields::unnamed()
#( #fields )*
}
}
Fields::Unit => quote! {
unit()
_scale_info::Fields::unit()
},
}
}
Expand Down Expand Up @@ -155,10 +261,8 @@ fn generate_c_like_enum_def(variants: &VariantList) -> TokenStream2 {
}
});
quote! {
variants(
_scale_info::Variants::with_discriminants()
#( #variants )*
)
_scale_info::Variants::with_discriminants()
#( #variants )*
}
}

Expand All @@ -172,7 +276,7 @@ fn is_c_like_enum(variants: &VariantList) -> bool {
})
}

fn generate_variant_type(data_enum: &DataEnum) -> TokenStream2 {
fn generate_variant_type(data_enum: &DataEnum, type_params: &[&TypeParam]) -> TokenStream2 {
let variants = &data_enum.variants;

if is_c_like_enum(&variants) {
Expand All @@ -181,10 +285,10 @@ fn generate_variant_type(data_enum: &DataEnum) -> TokenStream2 {

let variants = variants.into_iter().map(|v| {
let ident = &v.ident;
let v_name = quote! {stringify!(#ident) };
let v_name = quote! { stringify!(#ident) };
match v.fields {
Fields::Named(ref fs) => {
let fields = generate_fields(&fs.named);
let fields = generate_fields(&fs.named, type_params);
quote! {
.variant(
#v_name,
Expand All @@ -194,7 +298,7 @@ fn generate_variant_type(data_enum: &DataEnum) -> TokenStream2 {
}
}
Fields::Unnamed(ref fs) => {
let fields = generate_fields(&fs.unnamed);
let fields = generate_fields(&fs.unnamed, type_params);
quote! {
.variant(
#v_name,
Expand All @@ -209,9 +313,7 @@ fn generate_variant_type(data_enum: &DataEnum) -> TokenStream2 {
}
});
quote! {
variants(
_scale_info::Variants::with_fields()
#( #variants)*
)
_scale_info::Variants::with_fields()
#( #variants)*
}
}
17 changes: 10 additions & 7 deletions src/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,21 @@
//! (also via lifetime tracking) are possible but current not needed.

use crate::tm_std::*;
use crate::{interner::UntrackedSymbol, meta_type::MetaType};
use crate::{meta_type::{MetaType, MetaTypeInfo}, registry::NormalizedTypeId, UntrackedSymbol};
use serde::Serialize;

/// Trait to control the internal structures of type identifiers and
/// definitions.
/// Trait to control the internal structures of type representations.
///
/// This allows for type-level separation between free forms that can be
/// instantiated out of the flux and compact forms that require some sort of
/// interning data structures.
pub trait Form {
/// The string type.
type String: Serialize + PartialEq + Eq + PartialOrd + Ord + Clone + core::fmt::Debug;
/// The type identifier type.
type TypeId: PartialEq + Eq + PartialOrd + Ord + Clone + core::fmt::Debug;
/// The type type.
type Type: PartialEq + Eq + Clone + core::fmt::Debug;
/// todo: is this the correct name for this?
type GenericType: PartialEq + Eq + Clone + core::fmt::Debug;
}

/// A meta meta-type.
Expand All @@ -58,7 +59,8 @@ pub enum MetaForm {}

impl Form for MetaForm {
type String = &'static str;
type TypeId = MetaType;
type Type = MetaType;
type GenericType = MetaTypeInfo;
}

/// Compact form that has its lifetime untracked in association to its interner.
Expand All @@ -73,5 +75,6 @@ pub enum CompactForm {}

impl Form for CompactForm {
type String = UntrackedSymbol<&'static str>;
type TypeId = UntrackedSymbol<TypeId>;
type Type = UntrackedSymbol<NormalizedTypeId>;
type GenericType = UntrackedSymbol<NormalizedTypeId>;
}
Loading