From 700f810d885970a595c126347b150c97549fa99f Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Sun, 6 Jun 2021 16:19:47 +0100 Subject: [PATCH] Capture doc comments, add variant and field builders (#87) * Capture docs for types, fields, and enum variants * Add docs to derive tests * Fmt * Fmt * Make clippy happy * Fix struct docs tests * Fix struct docs tests * Add missing Vec import * Fix test * Clear docs * Promote build to it's own mod, split fields and variant builders * Refactor variant construction with builder * Fix up derive and unit tests with Variant builder * Fmt * Condense build module back to single file * Fix up doc tests * Fix up README * Define initial field builder * Fix up field building * Fix tests * Fully qualify calls to stringify * Default impl for FieldBuilder * Fix up default impl * Fix spelling errors * Remove clear_docs * Fully qualify module_path * Update derive/src/lib.rs Co-authored-by: Robin Freyler * Use callback for variant builder * Update README * Remove leading space in doc comments * Satisfy clippy * Add test for doc capture * Rename index back to discriminant, changes for this will be in https://github.com/paritytech/scale-info/pull/80 * Rename index back to discriminant, changes for this will be in https://github.com/paritytech/scale-info/pull/80 * Update src/build.rs Co-authored-by: David * Update src/build.rs Co-authored-by: David Co-authored-by: Robin Freyler Co-authored-by: David --- README.md | 24 +-- derive/src/lib.rs | 71 +++++---- derive/src/utils.rs | 27 ++++ src/build.rs | 291 +++++++++++++++++++++++++++---------- src/impls.rs | 22 +-- src/registry.rs | 27 ++-- src/tests.rs | 43 ++++-- src/ty/fields.rs | 80 +++++----- src/ty/mod.rs | 22 ++- src/ty/variant.rs | 38 +++-- test_suite/tests/derive.rs | 250 +++++++++++++++++++++++-------- test_suite/tests/json.rs | 14 +- 12 files changed, 628 insertions(+), 281 deletions(-) diff --git a/README.md b/README.md index 86a24411..bc2a0f46 100644 --- a/README.md +++ b/README.md @@ -106,8 +106,8 @@ where .path(Path::new("Foo", module_path!())) .type_params(vec![MetaType::new::()]) .composite(Fields::named() - .field_of::("bar", "T") - .field_of::("data", "u64") + .field(|f| f.ty::().name("bar").type_name("T")) + .field(|f| f.ty::().name("data").type_name("u64")) ) } } @@ -125,8 +125,8 @@ impl TypeInfo for Foo { Type::builder() .path(Path::new("Foo", module_path!())) .composite(Fields::unnamed() - .field_of::("u32") - .field_of::("bool") + .field(|f| f.ty::().type_name("u32")) + .field(|f| f.ty::().type_name("bool")) ) } } @@ -155,10 +155,10 @@ where .path(Path::new("Foo", module_path!())) .type_params(vec![MetaType::new::()]) .variant( - Variants::with_fields() - .variant("A", Fields::unnamed().field_of::("T")) - .variant("B", Fields::named().field_of::("f", "u32")) - .variant("C", Fields::unit()) + Variants::new() + .variant("A", |v| v.fields(Fields::unnamed().field(|f| f.ty::()))) + .variant("B", |v| v.fields(Fields::named().field(|f| f.ty::().name("f").type_name("u32")))) + .variant_unit("C") ) } } @@ -181,10 +181,10 @@ impl TypeInfo for Foo { Type::builder() .path(Path::new("Foo", module_path!())) .variant( - Variants::fieldless() - .variant("A", 1) - .variant("B", 2) - .variant("C", 33) + Variants::new() + .variant("A", |v| v.index(1)) + .variant("B", |v| v.index(2)) + .variant("C", |v| v.index(33)) ) } } diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 91a6e8d3..71ab562f 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![cfg_attr(not(feature = "std"), no_std)] - extern crate alloc; extern crate proc_macro; @@ -96,13 +94,16 @@ fn generate_type(input: TokenStream2) -> Result { Data::Enum(ref e) => generate_variant_type(e, &scale_info), Data::Union(_) => return Err(Error::new_spanned(input, "Unions not supported")), }; + let docs = utils::get_doc_literals(&ast.attrs); + let type_info_impl = quote! { impl #impl_generics :: #scale_info ::TypeInfo for #ident #ty_generics #where_clause { type Identity = Self; fn type_info() -> :: #scale_info ::Type { :: #scale_info ::Type::builder() - .path(:: #scale_info ::Path::new(stringify!(#ident), module_path!())) + .path(:: #scale_info ::Path::new(::core::stringify!(#ident), ::core::module_path!())) .type_params(:: #scale_info ::prelude::vec![ #( #generic_type_ids ),* ]) + .docs(&[ #( #docs ),* ]) .#build_type } } @@ -149,16 +150,25 @@ fn generate_fields(fields: &FieldsList) -> Vec { StaticLifetimesReplace.visit_type_mut(&mut ty); let type_name = clean_type_string("e!(#ty).to_string()); - let method_call = if utils::is_compact(f) { - quote!(.compact_of::<#ty>) + let docs = utils::get_doc_literals(&f.attrs); + let type_of_method = if utils::is_compact(f) { + quote!(compact) } else { - quote!(.field_of::<#ty>) + quote!(ty) }; - if let Some(ident) = ident { - quote!(#method_call(stringify!(#ident), #type_name)) + let name = if let Some(ident) = ident { + quote!(.name(::core::stringify!(#ident))) } else { - quote!(#method_call(#type_name)) - } + quote!() + }; + quote!( + .field(|f| f + .#type_of_method::<#ty>() + #name + .type_name(#type_name) + .docs(&[ #( #docs ),* ]) + ) + ) }) .collect() } @@ -214,13 +224,18 @@ fn generate_c_like_enum_def(variants: &VariantList, scale_info: &Ident) -> Token .map(|(i, v)| { let name = &v.ident; let discriminant = utils::variant_index(v, i); + let docs = utils::get_doc_literals(&v.attrs); quote! { - .variant(stringify!(#name), #discriminant as u64) + .variant(::core::stringify!(#name), |v| + v + .discriminant(#discriminant as ::core::primitive::u64) + .docs(&[ #( #docs ),* ]) + ) } }); quote! { variant( - :: #scale_info ::build::Variants::fieldless() + :: #scale_info ::build::Variants::new() #( #variants )* ) } @@ -245,39 +260,41 @@ fn generate_variant_type(data_enum: &DataEnum, scale_info: &Ident) -> TokenStrea .filter(|v| !utils::should_skip(&v.attrs)) .map(|v| { let ident = &v.ident; - let v_name = quote! {stringify!(#ident) }; - match v.fields { + let v_name = quote! {::core::stringify!(#ident) }; + let docs = utils::get_doc_literals(&v.attrs); + + let fields = match v.fields { Fields::Named(ref fs) => { let fields = generate_fields(&fs.named); quote! { - .variant( - #v_name, - :: #scale_info::build::Fields::named() - #( #fields)* - ) + :: #scale_info::build::Fields::named() + #( #fields )* } } Fields::Unnamed(ref fs) => { let fields = generate_fields(&fs.unnamed); quote! { - .variant( - #v_name, - :: #scale_info::build::Fields::unnamed() - #( #fields)* - ) + :: #scale_info::build::Fields::unnamed() + #( #fields )* } } Fields::Unit => { quote! { - .variant_unit(#v_name) + :: #scale_info::build::Fields::unit() } } + }; + + quote! { + .variant(#v_name, |v| + v.fields(#fields).docs(&[ #( #docs ),* ]) + ) } }); quote! { variant( - :: #scale_info ::build::Variants::with_fields() - #( #variants)* + :: #scale_info ::build::Variants::new() + #( #variants )* ) } } diff --git a/derive/src/utils.rs b/derive/src/utils.rs index f3911c98..47d3d404 100644 --- a/derive/src/utils.rs +++ b/derive/src/utils.rs @@ -16,9 +16,14 @@ //! //! NOTE: The code here is copied verbatim from `parity-scale-codec-derive`. +use alloc::{ + string::ToString, + vec::Vec, +}; use proc_macro2::TokenStream; use quote::quote; use syn::{ + parse_quote, spanned::Spanned, AttrStyle, Attribute, @@ -28,6 +33,28 @@ use syn::{ Variant, }; +/// Return all doc attributes literals found. +pub fn get_doc_literals(attrs: &[syn::Attribute]) -> Vec { + attrs + .iter() + .filter_map(|attr| { + if let Ok(syn::Meta::NameValue(meta)) = attr.parse_meta() { + if meta.path.get_ident().map_or(false, |ident| ident == "doc") { + let lit = &meta.lit; + let doc_lit = quote!(#lit).to_string(); + let trimmed_doc_lit = + doc_lit.trim_start_matches(r#"" "#).trim_end_matches('"'); + Some(parse_quote!(#trimmed_doc_lit)) + } else { + None + } + } else { + None + } + }) + .collect() +} + /// Look for a `#[codec(index = $int)]` attribute on a variant. If no attribute /// is found, fall back to the discriminant or just the variant index. pub fn variant_index(v: &Variant, i: usize) -> TokenStream { diff --git a/src/build.rs b/src/build.rs index 3bad9718..663c66d4 100644 --- a/src/build.rs +++ b/src/build.rs @@ -39,8 +39,8 @@ //! .path(Path::new("Foo", module_path!())) //! .type_params(vec![MetaType::new::()]) //! .composite(Fields::named() -//! .field_of::("bar", "T") -//! .field_of::("data", "u64") +//! .field(|f| f.ty::().name("bar").type_name("T")) +//! .field(|f| f.ty::().name("data").type_name("u64")) //! ) //! } //! } @@ -57,15 +57,15 @@ //! Type::builder() //! .path(Path::new("Foo", module_path!())) //! .composite(Fields::unnamed() -//! .field_of::("u32") -//! .field_of::("bool") +//! .field(|f| f.ty::().type_name("u32")) +//! .field(|f| f.ty::().type_name("bool")) //! ) //! } //! } //! ``` //! ## Enum with fields //! ``` -//! # use scale_info::{build::{Fields, Variants}, MetaType, Path, Type, TypeInfo}; +//! # use scale_info::{build::{Fields, Variants}, MetaType, Path, Type, TypeInfo, Variant}; //! enum Foo{ //! A(T), //! B { f: u32 }, @@ -83,17 +83,17 @@ //! .path(Path::new("Foo", module_path!())) //! .type_params(vec![MetaType::new::()]) //! .variant( -//! Variants::with_fields() -//! .variant("A", Fields::unnamed().field_of::("T")) -//! .variant("B", Fields::named().field_of::("f", "u32")) -//! .variant("C", Fields::unit()) +//! Variants::new() +//! .variant("A", |v| v.fields(Fields::unnamed().field(|f| f.ty::().type_name("T")))) +//! .variant("B", |v| v.fields(Fields::named().field(|f| f.ty::().name("f").type_name("u32")))) +//! .variant_unit("A") //! ) //! } //! } //! ``` //! ## Enum without fields //! ``` -//! # use scale_info::{build::{Fields, Variants}, MetaType, Path, Type, TypeInfo}; +//! # use scale_info::{build::{Fields, Variants}, MetaType, Path, Type, TypeInfo, Variant}; //! enum Foo { //! A, //! B, @@ -107,10 +107,10 @@ //! Type::builder() //! .path(Path::new("Foo", module_path!())) //! .variant( -//! Variants::fieldless() -//! .variant("A", 1) -//! .variant("B", 2) -//! .variant("C", 33) +//! Variants::new() +//! .variant("A", |v| v.discriminant(1)) +//! .variant("B", |v| v.discriminant(2)) +//! .variant("C", |v| v.discriminant(33)) //! ) //! } //! } @@ -146,6 +146,7 @@ pub mod state { pub struct TypeBuilder { path: Option, type_params: Vec, + docs: Vec<&'static str>, marker: PhantomData S>, } @@ -154,6 +155,7 @@ impl Default for TypeBuilder { TypeBuilder { path: Default::default(), type_params: Default::default(), + docs: Default::default(), marker: Default::default(), } } @@ -165,6 +167,7 @@ impl TypeBuilder { TypeBuilder { path: Some(path), type_params: self.type_params, + docs: self.docs, marker: Default::default(), } } @@ -176,11 +179,11 @@ impl TypeBuilder { D: Into, { let path = self.path.expect("Path not assigned"); - Type::new(path, self.type_params, type_def) + Type::new(path, self.type_params, type_def, self.docs) } /// Construct a "variant" type i.e an `enum` - pub fn variant(self, builder: VariantsBuilder) -> Type { + pub fn variant(self, builder: Variants) -> Type { self.build(builder.finalize()) } @@ -199,6 +202,12 @@ impl TypeBuilder { self.type_params = type_params.into_iter().collect(); self } + + /// Set the type documentation + pub fn docs(mut self, docs: &[&'static str]) -> Self { + self.docs = docs.to_vec(); + self + } } /// A fields builder has no fields (e.g. a unit struct) @@ -251,110 +260,234 @@ impl FieldsBuilder { } impl FieldsBuilder { - /// Add a named field with the type of the type parameter `T` - pub fn field_of(mut self, name: &'static str, type_name: &'static str) -> Self + /// Add a named field constructed using the builder. + pub fn field(mut self, builder: F) -> Self where - T: TypeInfo + ?Sized + 'static, + F: Fn( + FieldBuilder, + ) + -> FieldBuilder, { - self.fields.push(Field::named_of::(name, type_name)); + let builder = builder(FieldBuilder::new()); + self.fields.push(builder.finalize()); self } +} - /// Add a named, [`Compact`] field of type `T`. - pub fn compact_of(mut self, name: &'static str, type_name: &'static str) -> Self +impl FieldsBuilder { + /// Add an unnamed field constructed using the builder. + pub fn field(mut self, builder: F) -> Self where - T: scale::HasCompact, - ::Type: TypeInfo + 'static, + F: Fn( + FieldBuilder, + ) + -> FieldBuilder, { - self.fields - .push(Field::compact_of::(Some(name), type_name)); + let builder = builder(FieldBuilder::new()); + self.fields.push(builder.finalize()); self } } -impl FieldsBuilder { - /// Add an unnamed field with the type of the type parameter `T` - pub fn field_of(mut self, type_name: &'static str) -> Self +/// Type states for building a field. +pub mod field_state { + /// A name has not been assigned to the field. + pub enum NameNotAssigned {} + /// A name has been assigned to the field. + pub enum NameAssigned {} + /// A type has not been assigned to the field. + pub enum TypeNotAssigned {} + /// A type has been assigned to the field. + pub enum TypeAssigned {} +} + +/// Construct a valid [`Field`]. +pub struct FieldBuilder< + N = field_state::NameNotAssigned, + T = field_state::TypeNotAssigned, +> { + name: Option<&'static str>, + ty: Option, + type_name: Option<&'static str>, + docs: &'static [&'static str], + marker: PhantomData (N, T)>, +} + +impl Default for FieldBuilder { + fn default() -> Self { + FieldBuilder { + name: Default::default(), + ty: Default::default(), + type_name: Default::default(), + docs: Default::default(), + marker: Default::default(), + } + } +} + +impl FieldBuilder { + /// Create a new FieldBuilder. + pub fn new() -> Self { + Default::default() + } +} + +impl FieldBuilder { + /// Initialize the field name. + pub fn name(self, name: &'static str) -> FieldBuilder { + FieldBuilder { + name: Some(name), + ty: self.ty, + type_name: self.type_name, + docs: self.docs, + marker: PhantomData, + } + } +} + +impl FieldBuilder { + /// Initialize the type of the field. + pub fn ty(self) -> FieldBuilder where - T: TypeInfo + ?Sized + 'static, + TY: TypeInfo + 'static + ?Sized, { - self.fields.push(Field::unnamed_of::(type_name)); - self + FieldBuilder { + name: self.name, + ty: Some(MetaType::new::()), + type_name: self.type_name, + docs: self.docs, + marker: PhantomData, + } } - /// Add an unnamed, [`Compact`] field of type `T`. - pub fn compact_of(mut self, type_name: &'static str) -> Self + /// Initializes the type of the field as a compact type. + pub fn compact(self) -> FieldBuilder where - T: scale::HasCompact, - ::Type: TypeInfo + 'static, + TY: scale::HasCompact, + ::Type: TypeInfo + 'static, { - self.fields.push(Field::compact_of::(None, type_name)); - self + FieldBuilder { + name: self.name, + ty: Some(MetaType::new::<::Type>()), + type_name: self.type_name, + docs: self.docs, + marker: PhantomData, + } } } -/// Build a type with no variants. -pub enum NoVariants {} -/// Build a type where at least one variant has fields. -pub enum VariantFields {} -/// Build a type where *all* variants have no fields and the discriminant can -/// be directly chosen or accessed -pub enum Fieldless {} - -/// Empty enum for VariantsBuilder constructors for the type builder DSL. -pub enum Variants {} +impl FieldBuilder { + /// Initialize the type name of a field (optional). + pub fn type_name(self, type_name: &'static str) -> FieldBuilder { + FieldBuilder { + name: self.name, + ty: self.ty, + type_name: Some(type_name), + docs: self.docs, + marker: PhantomData, + } + } -impl Variants { - /// Build a set of variants, at least one of which will have fields. - pub fn with_fields() -> VariantsBuilder { - VariantsBuilder::new() + /// Initialize the documentation of a field (optional). + pub fn docs(self, docs: &'static [&'static str]) -> FieldBuilder { + FieldBuilder { + name: self.name, + ty: self.ty, + type_name: self.type_name, + docs, + marker: PhantomData, + } } +} - /// Build a set of variants, none of which will have fields, and the discriminant can - /// be directly chosen or accessed - pub fn fieldless() -> VariantsBuilder { - VariantsBuilder::new() +impl FieldBuilder { + /// Complete building and return a new [`Field`]. + pub fn finalize(self) -> Field { + Field::new( + self.name, + self.ty.expect("Type should be set by builder"), + self.type_name, + &self.docs, + ) } } /// Builds a definition of a variant type i.e an `enum` #[derive(Default)] -pub struct VariantsBuilder { +pub struct Variants { variants: Vec, - marker: PhantomData T>, } -impl VariantsBuilder { - /// Add a variant with fields constructed by the supplied [`FieldsBuilder`](`crate::build::FieldsBuilder`) - pub fn variant(mut self, name: &'static str, fields: FieldsBuilder) -> Self { - self.variants.push(Variant::with_fields(name, fields)); - self +impl Variants { + /// Create a new [`VariantsBuilder`]. + pub fn new() -> Self { + Variants { + variants: Vec::new(), + } } - /// Add a variant with no fields i.e. a unit variant - pub fn variant_unit(self, name: &'static str) -> Self { - self.variant::(name, Fields::unit()) + /// Add a variant + pub fn variant(mut self, name: &'static str, builder: F) -> Self + where + F: Fn(VariantBuilder) -> VariantBuilder, + { + let builder = builder(VariantBuilder::new(name)); + self.variants.push(builder.finalize()); + self } -} -impl VariantsBuilder { - /// Add a fieldless variant, explicitly setting the discriminant - pub fn variant(mut self, name: &'static str, discriminant: u64) -> Self { - self.variants - .push(Variant::with_discriminant(name, discriminant)); + /// Add a unit variant (without fields). + pub fn variant_unit(mut self, name: &'static str) -> Self { + let builder = VariantBuilder::new(name); + self.variants.push(builder.finalize()); self } + + /// Construct a new [`TypeDefVariant`] from the initialized builder variants. + pub fn finalize(self) -> TypeDefVariant { + TypeDefVariant::new(self.variants) + } } -impl VariantsBuilder { - fn new() -> Self { - VariantsBuilder { - variants: Vec::new(), - marker: Default::default(), +/// Build a [`Variant`]. +pub struct VariantBuilder { + name: &'static str, + fields: Vec>, + discriminant: Option, + docs: Vec<&'static str>, +} + +impl VariantBuilder { + /// Create a new [`VariantBuilder`]. + pub fn new(name: &'static str) -> Self { + Self { + name, + fields: Vec::new(), + discriminant: None, + docs: Vec::new(), } } - fn finalize(self) -> TypeDefVariant { - TypeDefVariant::new(self.variants) + /// Set the variant's discriminant. + pub fn discriminant(mut self, discriminant: u64) -> Self { + self.discriminant = Some(discriminant); + self + } + + /// Initialize the variant's fields. + pub fn fields(mut self, fields_builder: FieldsBuilder) -> Self { + self.fields = fields_builder.finalize(); + self + } + + /// Initialize the variant's documentation. + pub fn docs(mut self, docs: &[&'static str]) -> Self { + self.docs = docs.to_vec(); + self + } + + /// Complete building and create final [`Variant`] instance. + pub fn finalize(self) -> Variant { + Variant::new(self.name, self.fields, self.discriminant, self.docs) } } diff --git a/src/impls.rs b/src/impls.rs index 1be2f8d5..51cfa714 100644 --- a/src/impls.rs +++ b/src/impls.rs @@ -136,11 +136,9 @@ where Type::builder() .path(Path::prelude("Option")) .type_params(tuple_meta_type![T]) - .variant( - Variants::with_fields() - .variant_unit("None") - .variant("Some", Fields::unnamed().field_of::("T")), - ) + .variant(Variants::new().variant("None", |v| v).variant("Some", |v| { + v.fields(Fields::unnamed().field(|f| f.ty::())) + })) } } @@ -156,9 +154,11 @@ where .path(Path::prelude("Result")) .type_params(tuple_meta_type!(T, E)) .variant( - Variants::with_fields() - .variant("Ok", Fields::unnamed().field_of::("T")) - .variant("Err", Fields::unnamed().field_of::("E")), + Variants::new() + .variant("Ok", |v| v.fields(Fields::unnamed().field(|f| f.ty::()))) + .variant("Err", |v| { + v.fields(Fields::unnamed().field(|f| f.ty::())) + }), ) } } @@ -173,7 +173,7 @@ where Type::builder() .path(Path::prelude("Cow")) .type_params(tuple_meta_type!(T)) - .composite(Fields::unnamed().field_of::("T")) + .composite(Fields::unnamed().field(|f| f.ty::())) } } @@ -188,7 +188,7 @@ where Type::builder() .path(Path::prelude("BTreeMap")) .type_params(tuple_meta_type![(K, V)]) - .composite(Fields::unnamed().field_of::<[(K, V)]>("[(K, V)]")) + .composite(Fields::unnamed().field(|f| f.ty::<[(K, V)]>())) } } @@ -202,7 +202,7 @@ where Type::builder() .path(Path::prelude("BTreeSet")) .type_params(tuple_meta_type![T]) - .composite(Fields::unnamed().field_of::<[T]>("[T]")) + .composite(Fields::unnamed().field(|f| f.ty::<[T]>())) } } diff --git a/src/registry.rs b/src/registry.rs index 7655a865..a461caff 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -236,18 +236,21 @@ mod tests { .path(Path::new("RecursiveRefs", module_path!())) .composite( Fields::named() - .field_of::>( - "boxed", - "Box < RecursiveRefs >", - ) - .field_of::<&'static RecursiveRefs<'static>>( - "reference", - "&RecursiveRefs", - ) - .field_of::<&'static mut RecursiveRefs<'static>>( - "mutable_reference", - "&mut RecursiveRefs", - ), + .field(|f| { + f.ty::>() + .name("boxed") + .type_name("Box < RecursiveRefs >") + }) + .field(|f| { + f.ty::<&'static RecursiveRefs<'static>>() + .name("reference") + .type_name("&RecursiveRefs") + }) + .field(|f| { + f.ty::<&'static mut RecursiveRefs<'static>>() + .name("mutable_reference") + .type_name("&mut RecursiveRefs") + }), ) } } diff --git a/src/tests.rs b/src/tests.rs index fea9a82e..653d66c4 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -61,11 +61,9 @@ fn prelude_items() { Type::builder() .path(Path::prelude("Option")) .type_params(tuple_meta_type!(u128)) - .variant( - Variants::with_fields() - .variant_unit("None") - .variant("Some", Fields::unnamed().field_of::("T")) - ) + .variant(Variants::new().variant("None", |v| v).variant("Some", |v| { + v.fields(Fields::unnamed().field(|f| f.ty::())) + })) ); assert_type!( Result, @@ -73,9 +71,15 @@ fn prelude_items() { .path(Path::prelude("Result")) .type_params(tuple_meta_type!(bool, String)) .variant( - Variants::with_fields() - .variant("Ok", Fields::unnamed().field_of::("T")) - .variant("Err", Fields::unnamed().field_of::("E")) + Variants::new() + .variant( + "Ok", |v| v + .fields(Fields::unnamed().field(|f| f.ty::())) + ) + .variant( + "Err", |v| v + .fields(Fields::unnamed().field(|f| f.ty::())) + ) ) ); assert_type!(PhantomData, TypeDefPhantom::new(meta_type::())); @@ -84,7 +88,7 @@ fn prelude_items() { Type::builder() .path(Path::prelude("Cow")) .type_params(tuple_meta_type!(u128)) - .composite(Fields::unnamed().field_of::("T")) + .composite(Fields::unnamed().field(|f| f.ty::())) ); } @@ -95,7 +99,7 @@ fn collections() { Type::builder() .path(Path::prelude("BTreeMap")) .type_params(tuple_meta_type![(String, u32)]) - .composite(Fields::unnamed().field_of::<[(String, u32)]>("[(K, V)]")) + .composite(Fields::unnamed().field(|f| f.ty::<[(String, u32)]>())) ); assert_type!( @@ -103,7 +107,7 @@ fn collections() { Type::builder() .path(Path::prelude("BTreeSet")) .type_params(tuple_meta_type![String]) - .composite(Fields::unnamed().field_of::<[String]>("[T]")) + .composite(Fields::unnamed().field(|f| f.ty::<[String]>())) ); } @@ -162,7 +166,9 @@ fn struct_with_generics() { Type::builder() .path(Path::new("MyStruct", module_path!())) .type_params(tuple_meta_type!(T)) - .composite(Fields::named().field_of::("data", "T")) + .composite( + Fields::named().field(|f| f.ty::().name("data").type_name("T")), + ) } } @@ -170,7 +176,7 @@ fn struct_with_generics() { let struct_bool_type_info = Type::builder() .path(Path::from_segments(vec!["scale_info", "tests", "MyStruct"]).unwrap()) .type_params(tuple_meta_type!(bool)) - .composite(Fields::named().field_of::("data", "T")); + .composite(Fields::named().field(|f| f.ty::().name("data").type_name("T"))); assert_type!(MyStruct, struct_bool_type_info); @@ -179,7 +185,10 @@ fn struct_with_generics() { let expected_type = Type::builder() .path(Path::new("MyStruct", "scale_info::tests")) .type_params(tuple_meta_type!(Box>)) - .composite(Fields::named().field_of::>>("data", "T")); + .composite( + Fields::named() + .field(|f| f.ty::>>().name("data").type_name("T")), + ); assert_type!(SelfTyped, expected_type); } @@ -201,14 +210,16 @@ fn basic_struct_with_phantoms() { Type::builder() .path(Path::new("SomeStruct", module_path!())) .type_params(tuple_meta_type!(T)) - .composite(Fields::named().field_of::("a", "u8")) + .composite( + Fields::named().field(|f| f.ty::().name("a").type_name("u8")), + ) } } 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::("a", "u8")); + .composite(Fields::named().field(|f| f.ty::().name("a").type_name("u8"))); assert_type!(SomeStruct, struct_bool_type_info); } diff --git a/src/ty/fields.rs b/src/ty/fields.rs index 1ef06ce9..97e77a8a 100644 --- a/src/ty/fields.rs +++ b/src/ty/fields.rs @@ -13,22 +13,21 @@ // limitations under the License. use crate::{ + build::FieldBuilder, form::{ Form, MetaForm, PortableForm, }, + prelude::vec::Vec, IntoPortable, MetaType, Registry, - TypeInfo, -}; -use scale::{ - Encode, - HasCompact, }; +use scale::Encode; #[cfg(feature = "serde")] use serde::{ + de::DeserializeOwned, Deserialize, Serialize, }; @@ -64,6 +63,13 @@ use serde::{ /// alias, there are no guarantees provided, and the type name representation /// may change. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "serde", + serde(bound( + serialize = "T::Type: Serialize, T::String: Serialize", + deserialize = "T::Type: DeserializeOwned, T::String: DeserializeOwned", + )) +)] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Encode)] @@ -78,7 +84,17 @@ pub struct Field { #[cfg_attr(feature = "serde", serde(rename = "type"))] ty: T::Type, /// The name of the type of the field as it appears in the source code. - type_name: T::String, + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Option::is_none", default) + )] + type_name: Option, + /// Documentation + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Vec::is_empty", default) + )] + docs: Vec, } impl IntoPortable for Field { @@ -88,57 +104,34 @@ impl IntoPortable for Field { Field { name: self.name.map(|name| name.into_portable(registry)), ty: registry.register_type(&self.ty), - type_name: self.type_name.into_portable(registry), + type_name: self.type_name.map(|tn| tn.into_portable(registry)), + docs: registry.map_into_portable(self.docs), } } } impl Field { + /// Returns a new [`FieldBuilder`] for constructing a field. + pub fn builder() -> FieldBuilder { + FieldBuilder::new() + } + /// Creates a new field. /// /// Use this constructor if you want to instantiate from a given meta type. pub fn new( name: Option<&'static str>, ty: MetaType, - type_name: &'static str, + type_name: Option<&'static str>, + docs: &[&'static str], ) -> Self { Self { name, ty, type_name, + docs: docs.to_vec(), } } - - /// Creates a new named field. - /// - /// Use this constructor if you want to instantiate from a given - /// compile-time type. - pub fn named_of(name: &'static str, type_name: &'static str) -> Field - where - T: TypeInfo + ?Sized + 'static, - { - Self::new(Some(name), MetaType::new::(), type_name) - } - - /// Creates a new unnamed field. - /// - /// Use this constructor if you want to instantiate an unnamed field from a - /// given compile-time type. - pub fn unnamed_of(type_name: &'static str) -> Field - where - T: TypeInfo + ?Sized + 'static, - { - Self::new(None, MetaType::new::(), type_name) - } - - /// Creates a new [`Compact`] field. - pub fn compact_of(name: Option<&'static str>, type_name: &'static str) -> Field - where - T: HasCompact, - ::Type: TypeInfo + 'static, - { - Self::new(name, MetaType::new::<::Type>(), type_name) - } } impl Field @@ -160,7 +153,12 @@ where /// name are not specified, but in practice will be the name of any valid /// type for a field. This is intended for informational and diagnostic /// purposes only. - pub fn type_name(&self) -> &T::String { - &self.type_name + pub fn type_name(&self) -> Option<&T::String> { + self.type_name.as_ref() + } + + /// Returns the documentation of the field. + pub fn docs(&self) -> &[T::String] { + &self.docs } } diff --git a/src/ty/mod.rs b/src/ty/mod.rs index e3dd5cf4..cf9cc8ba 100644 --- a/src/ty/mod.rs +++ b/src/ty/mod.rs @@ -78,6 +78,12 @@ pub struct Type { /// The actual type definition #[cfg_attr(feature = "serde", serde(rename = "def"))] type_def: TypeDef, + /// Documentation + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Vec::is_empty", default) + )] + docs: Vec, } impl IntoPortable for Type { @@ -88,6 +94,7 @@ impl IntoPortable for Type { path: self.path.into_portable(registry), type_params: registry.register_types(self.type_params), type_def: self.type_def.into_portable(registry), + docs: registry.map_into_portable(self.docs), } } } @@ -96,7 +103,7 @@ macro_rules! impl_from_type_def_for_type { ( $( $t:ty ), + $(,)?) => { $( impl From<$t> for Type { fn from(item: $t) -> Self { - Self::new(Path::voldemort(), Vec::new(), item) + Self::new(Path::voldemort(), Vec::new(), item, Vec::new()) } } )* } @@ -117,7 +124,12 @@ impl Type { TypeBuilder::default() } - pub(crate) fn new(path: Path, type_params: I, type_def: D) -> Self + pub(crate) fn new( + path: Path, + type_params: I, + type_def: D, + docs: Vec<&'static str>, + ) -> Self where I: IntoIterator, D: Into, @@ -126,6 +138,7 @@ impl Type { path, type_params: type_params.into_iter().collect(), type_def: type_def.into(), + docs, } } } @@ -148,6 +161,11 @@ where pub fn type_def(&self) -> &TypeDef { &self.type_def } + + /// Returns the documentation of the type + pub fn docs(&self) -> &[T::String] { + &self.docs + } } /// The possible types a SCALE encodable Rust value could have. diff --git a/src/ty/variant.rs b/src/ty/variant.rs index dcf2db09..99763eaa 100644 --- a/src/ty/variant.rs +++ b/src/ty/variant.rs @@ -15,7 +15,6 @@ use crate::prelude::vec::Vec; use crate::{ - build::FieldsBuilder, form::{ Form, MetaForm, @@ -170,6 +169,12 @@ pub struct Variant { serde(skip_serializing_if = "Option::is_none", default) )] discriminant: Option, + /// Documentation + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Vec::is_empty", default) + )] + docs: Vec, } impl IntoPortable for Variant { @@ -180,26 +185,24 @@ impl IntoPortable for Variant { name: self.name.into_portable(registry), fields: registry.map_into_portable(self.fields), discriminant: self.discriminant, + docs: registry.map_into_portable(self.docs), } } } impl Variant { - /// Creates a new variant with the given fields. - pub fn with_fields(name: &'static str, fields: FieldsBuilder) -> Self { + /// Creates a new variant. + pub(crate) fn new( + name: &'static str, + fields: Vec>, + index: Option, + docs: Vec<&'static str>, + ) -> Self { Self { name, - fields: fields.finalize(), - discriminant: None, - } - } - - /// Creates a new variant with the given discriminant. - pub fn with_discriminant(name: &'static str, discriminant: u64) -> Self { - Self { - name, - fields: Vec::new(), - discriminant: Some(discriminant), + fields, + discriminant: index, + docs, } } } @@ -208,7 +211,7 @@ impl Variant where T: Form, { - /// Returns the name of the variant + /// Returns the name of the variant. pub fn name(&self) -> &T::String { &self.name } @@ -222,4 +225,9 @@ where pub fn discriminant(&self) -> Option { self.discriminant } + + /// Returns the documentation of the variant. + pub fn docs(&self) -> &[T::String] { + &self.docs + } } diff --git a/test_suite/tests/derive.rs b/test_suite/tests/derive.rs index febae6f3..dd3c763c 100644 --- a/test_suite/tests/derive.rs +++ b/test_suite/tests/derive.rs @@ -47,7 +47,10 @@ macro_rules! assert_type { fn struct_derive() { #[allow(unused)] #[derive(TypeInfo)] + /// Type docs. + /// Multiline. struct S { + /// Field docs. pub t: T, pub u: U, } @@ -55,10 +58,16 @@ fn struct_derive() { let struct_type = Type::builder() .path(Path::new("S", "derive")) .type_params(tuple_meta_type!(bool, u8)) + .docs(&["Type docs.", "Multiline."]) .composite( Fields::named() - .field_of::("t", "T") - .field_of::("u", "U"), + .field(|f| { + f.ty::() + .name("t") + .type_name("T") + .docs(&["Field docs."]) + }) + .field(|f| f.ty::().name("u").type_name("U")), ); assert_type!(S, struct_type); @@ -70,10 +79,16 @@ fn struct_derive() { let self_typed_type = Type::builder() .path(Path::new("S", "derive")) .type_params(tuple_meta_type!(Box>, bool)) + .docs(&["Type docs.", "Multiline."]) .composite( Fields::named() - .field_of::>>("t", "T") - .field_of::("u", "U"), + .field(|f| { + f.ty::>>() + .name("t") + .type_name("T") + .docs(&["Field docs."]) + }) + .field(|f| f.ty::().name("u").type_name("U")), ); assert_type!(SelfTyped, self_typed_type); } @@ -92,8 +107,12 @@ fn phantom_data_is_part_of_the_type_info() { .type_params(tuple_meta_type!(bool)) .composite( Fields::named() - .field_of::("a", "u8") - .field_of::>("m", "PhantomData"), + .field(|f| f.ty::().name("a").type_name("u8")) + .field(|f| { + f.ty::>() + .name("m") + .type_name("PhantomData") + }), ); assert_type!(P, ty); @@ -103,12 +122,20 @@ fn phantom_data_is_part_of_the_type_info() { fn tuple_struct_derive() { #[allow(unused)] #[derive(TypeInfo)] - struct S(T); + /// Type docs. + struct S( + /// Unnamed field docs. + T, + ); let ty = Type::builder() .path(Path::new("S", "derive")) .type_params(tuple_meta_type!(bool)) - .composite(Fields::unnamed().field_of::("T")); + .docs(&["Type docs."]) + .composite( + Fields::unnamed() + .field(|f| f.ty::().type_name("T").docs(&["Unnamed field docs."])), + ); assert_type!(S, ty); } @@ -130,14 +157,24 @@ fn unit_struct_derive() { fn c_like_enum_derive() { #[allow(unused)] #[derive(TypeInfo)] + /// Enum docs. enum E { + /// Unit variant. A, + /// Variant with discriminant. B = 10, } let ty = Type::builder() .path(Path::new("E", "derive")) - .variant(Variants::fieldless().variant("A", 0u64).variant("B", 10u64)); + .docs(&["Enum docs."]) + .variant( + Variants::new() + .variant("A", |v| v.discriminant(0).docs(&["Unit variant."])) + .variant("B", |v| { + v.discriminant(10).docs(&["Variant with discriminant."]) + }), + ); assert_type!(E, ty); } @@ -157,12 +194,12 @@ fn c_like_enum_derive_with_scale_index_set() { } let ty = Type::builder().path(Path::new("E", "derive")).variant( - Variants::fieldless() - .variant("A", 0) - .variant("B", 10) - .variant("C", 13) - .variant("D", 3) - .variant("E", 14), + Variants::new() + .variant("A", |v| v.discriminant(0)) + .variant("B", |v| v.discriminant(10)) + .variant("C", |v| v.discriminant(13)) + .variant("D", |v| v.discriminant(3)) + .variant("E", |v| v.discriminant(14)), ); assert_type!(E, ty); @@ -172,20 +209,44 @@ fn c_like_enum_derive_with_scale_index_set() { fn enum_derive() { #[allow(unused)] #[derive(TypeInfo)] + /// Enum docs. enum E { - A(T), - B { b: T }, + /// Unnamed fields variant. + A( + /// Unnamed field. + T, + ), + /// Named fields variant. + B { + /// Named field. + b: T, + }, + /// Unit variant. C, } let ty = Type::builder() .path(Path::new("E", "derive")) .type_params(tuple_meta_type!(bool)) + .docs(&["Enum docs."]) .variant( - Variants::with_fields() - .variant("A", Fields::unnamed().field_of::("T")) - .variant("B", Fields::named().field_of::("b", "T")) - .variant_unit("C"), + Variants::new() + .variant("A", |v| { + v.fields(Fields::unnamed().field(|f| { + f.ty::().type_name("T").docs(&["Unnamed field."]) + })) + .docs(&["Unnamed fields variant."]) + }) + .variant("B", |v| { + v.fields(Fields::named().field(|f| { + f.ty::() + .name("b") + .type_name("T") + .docs(&["Named field."]) + })) + .docs(&["Named fields variant."]) + }) + .variant("C", |v| v.docs(&["Unit variant."])), ); assert_type!(E, ty); @@ -201,14 +262,24 @@ fn recursive_type_derive() { } let ty = Type::builder().path(Path::new("Tree", "derive")).variant( - Variants::with_fields() - .variant("Leaf", Fields::named().field_of::("value", "i32")) - .variant( - "Node", - Fields::named() - .field_of::>("right", "Box") - .field_of::>("left", "Box"), - ), + Variants::new() + .variant("Leaf", |v| { + v.fields( + Fields::named() + .field(|f| f.ty::().name("value").type_name("i32")), + ) + }) + .variant("Node", |v| { + v.fields( + Fields::named() + .field(|f| { + f.ty::>().name("right").type_name("Box") + }) + .field(|f| { + f.ty::>().name("left").type_name("Box") + }), + ) + }), ); assert_type!(Tree, ty); @@ -224,9 +295,9 @@ fn fields_with_type_alias() { a: BoolAlias, } - let ty = Type::builder() - .path(Path::new("S", "derive")) - .composite(Fields::named().field_of::("a", "BoolAlias")); + let ty = Type::builder().path(Path::new("S", "derive")).composite( + Fields::named().field(|f| f.ty::().name("a").type_name("BoolAlias")), + ); assert_type!(S, ty); } @@ -254,8 +325,8 @@ fn associated_types_derive_without_bounds() { .type_params(tuple_meta_type!(ConcreteTypes)) .composite( Fields::named() - .field_of::("a", "T::A") - .field_of::("b", "&'static u64"), + .field(|f| f.ty::().name("a").type_name("T::A")) + .field(|f| f.ty::().name("b").type_name("&'static u64")), ); assert_type!(Assoc, struct_type); @@ -286,10 +357,10 @@ fn associated_types_named_like_the_derived_type_works() { .type_params(tuple_meta_type!(ConcreteTypes)) .composite( Fields::named() - .field_of::>("a", "Vec") - .field_of::>("b", "Vec<::Assoc>") - .field_of::("c", "T::Assoc") - .field_of::("d", "::Assoc"), + .field(|f| f.ty::>().name("a").type_name("Vec")) + .field(|f| f.ty::>().name("b").type_name("Vec<::Assoc>")) + .field(|f| f.ty::().name("c").type_name("T::Assoc")) + .field(|f| f.ty::().name("d").type_name("::Assoc")), ); assert_type!(Assoc, struct_type); @@ -309,8 +380,8 @@ fn scale_compact_types_work_in_structs() { .path(Path::new("Dense", "derive")) .composite( Fields::named() - .field_of::("a", "u8") - .compact_of::("b", "u16"), + .field(|f| f.ty::().name("a").type_name("u8")) + .field(|f| f.compact::().name("b").type_name("u16")), ); assert_type!(Dense, ty_alt); } @@ -329,13 +400,24 @@ fn scale_compact_types_work_in_enums() { .path(Path::new("MutilatedMultiAddress", "derive")) .type_params(tuple_meta_type!(u8, u16)) .variant( - Variants::with_fields() - .variant("Id", Fields::unnamed().field_of::("AccountId")) - .variant("Index", Fields::unnamed().compact_of::("AccountIndex")) - .variant( - "Address32", - Fields::unnamed().field_of::<[u8; 32]>("[u8; 32]"), - ), + Variants::new() + .variant("Id", |v| { + v.fields( + Fields::unnamed().field(|f| f.ty::().type_name("AccountId")), + ) + }) + .variant("Index", |v| { + v.fields( + Fields::unnamed() + .field(|f| f.compact::().type_name("AccountIndex")), + ) + }) + .variant("Address32", |v| { + v.fields( + Fields::unnamed() + .field(|f| f.ty::<[u8; 32]>().type_name("[u8; 32]")), + ) + }), ); assert_type!(MutilatedMultiAddress, ty); @@ -356,8 +438,8 @@ fn struct_fields_marked_scale_skip_are_skipped() { .path(Path::new("Skippy", "derive")) .composite( Fields::named() - .field_of::("a", "u8") - .field_of::("c", "u32"), + .field(|f| f.ty::().name("a").type_name("u8")) + .field(|f| f.ty::().name("c").type_name("u32")), ); assert_type!(Skippy, ty); } @@ -373,9 +455,11 @@ fn enum_variants_marked_scale_skip_are_skipped() { C, } - let ty = Type::builder() - .path(Path::new("Skippy", "derive")) - .variant(Variants::fieldless().variant("A", 0).variant("C", 2)); + let ty = Type::builder().path(Path::new("Skippy", "derive")).variant( + Variants::new() + .variant("A", |v| v.discriminant(0)) + .variant("C", |v| v.discriminant(2)), + ); assert_type!(Skippy, ty); } @@ -395,9 +479,15 @@ fn enum_variants_with_fields_marked_scale_skip_are_skipped() { } let ty = Type::builder().path(Path::new("Skippy", "derive")).variant( - Variants::with_fields() - .variant("Bajs", Fields::named().field_of::("b", "bool")) - .variant("Coo", Fields::unnamed().field_of::("bool")), + Variants::new() + .variant("Bajs", |v| { + v.fields( + Fields::named().field(|f| f.ty::().name("b").type_name("bool")), + ) + }) + .variant("Coo", |v| { + v.fields(Fields::unnamed().field(|f| f.ty::().type_name("bool"))) + }), ); assert_type!(Skippy, ty); } @@ -422,7 +512,9 @@ fn type_parameters_with_default_bound_works() { let ty = Type::builder() .path(Path::new("Bat", "derive")) .type_params(tuple_meta_type!(MetaFormy)) - .composite(Fields::named().field_of::("one", "TTT")); + .composite( + Fields::named().field(|f| f.ty::().name("one").type_name("TTT")), + ); assert_type!(Bat, ty); } @@ -435,13 +527,53 @@ fn whitespace_scrubbing_works() { a: (u8, (bool, u8)), } - let ty = Type::builder() - .path(Path::new("A", "derive")) - .composite(Fields::named().field_of::<(u8, (bool, u8))>("a", "(u8, (bool, u8))")); + let ty = + Type::builder() + .path(Path::new("A", "derive")) + .composite(Fields::named().field(|f| { + f.ty::<(u8, (bool, u8))>() + .name("a") + .type_name("(u8, (bool, u8))") + })); assert_type!(A, ty); } +#[test] +fn doc_capture_works() { + //! Que pasa + #[allow(unused)] + #[derive(TypeInfo)] + #[doc(hidden)] + struct S { + #[doc = " Field a"] + a: bool, + #[doc(primitive)] + b: u8, + /// Indented + c: u16, + } + + let ty = Type::builder().path(Path::new("S", "derive")).composite( + Fields::named() + .field(|f| { + f.ty::() + .name("a") + .type_name("bool") + .docs(&["Field a"]) + }) + .field(|f| f.ty::().name("b").type_name("u8").docs(&[])) + .field(|f| { + f.ty::() + .name("c") + .type_name("u16") + .docs(&[" Indented"]) + }), + ); + + assert_type!(S, ty); +} + #[rustversion::nightly] #[test] fn ui_tests() { diff --git a/test_suite/tests/json.rs b/test_suite/tests/json.rs index a1951540..89ef93a8 100644 --- a/test_suite/tests/json.rs +++ b/test_suite/tests/json.rs @@ -103,7 +103,7 @@ fn test_builtins() { }, { "name": "Some", - "fields": [ { "type": 0, "typeName": "T" } ] + "fields": [ { "type": 0 } ] }, ] } @@ -117,11 +117,11 @@ fn test_builtins() { "variants": [ { "name": "Ok", - "fields": [ { "type": 0, "typeName": "T" } ] + "fields": [ { "type": 0 } ] }, { "name": "Err", - "fields": [ { "type": 1, "typeName": "E" } ] + "fields": [ { "type": 1 } ] } ] } @@ -221,10 +221,10 @@ fn test_struct_with_some_fields_marked_as_compact() { .path(Path::new("Dense", module_path!())) .composite( Fields::named() - .compact_of::("a", "u128") - .field_of::("a_not_compact", "u128") - .field_of::<[u8; 32]>("b", "[u8; 32]") - .compact_of::("c", "u64"), + .field(|f| f.compact::().name("a").type_name("u128")) + .field(|f| f.ty::().name("a_not_compact").type_name("u128")) + .field(|f| f.ty::<[u8; 32]>().name("b").type_name("[u8; 32]")) + .field(|f| f.compact::().name("c").type_name("u64")), ) } }