From a69ac5516ab292176a51860ab8c87f89b49ff588 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 7 Oct 2022 12:06:53 +0100 Subject: [PATCH] Construct `PortableRegistry` dynamically at runtime (#164) * WIP: allow constructing MetaTypes at runtime * Remove fmt config options * Add some docs * std feature String * fully qualified String * Fix features build * Generic string parameter for MetaType * Remove license_template_path * Fmt * Fix fmt * Replace MetaFormString trait with cfg based string * Remove unused into * Fix more errors * Now string types are the same across Meta/Portable, no longer necessary to convert * Remove custom MetaType and TypeId * EXPERIMENT: make all type def fields public to allow construting PortableRegistry dynamically * Rename test * Fix up type ids in test * Fix ui test * *TEMPORARILY* pub registry for PortableType * add constructor for portableregistry * constructor for PortableType * implement remaining constructors for generic Form * make Type::new constructor public * make remaining constructor public * add a new custom constructor for path * use new_custom in a test * Remove not required builder fn for MetaType * WIP propogate Form through builders * Fix up path builders * More fixes * Fix up generated From impls * Fix up field builders * Fix up field builder methods for MetaForm * Rename constructor * Hide pub fields and add portable builder helper methods * Fmt * Explicity export PortableType * Clippy * Move PortableRegistry to own file, introduce PortableRegistryBuilder * Remove some stray `Str` type params * Implement PortableRegistryBuilder to manage ids * Default impl for PortableRegistryBuilder * implement getter for registered types in PortableRegistryBuilder Signed-off-by: Cyrill Leutwiler * Rename new path constructor * Rename new_custom methods to new_portable * Revert reordering of MetaForm * Move path construction back to MetaForm for non-breaking changes * Update path tests * TypeParameter::new_portable for non breaking change Signed-off-by: Cyrill Leutwiler Co-authored-by: Cyrill Leutwiler --- .rustfmt.toml | 3 - derive/src/lib.rs | 10 +- src/build.rs | 277 +++++++++++++++++++++++++------------ src/impls.rs | 2 +- src/interner.rs | 16 ++- src/lib.rs | 6 +- src/portable.rs | 221 +++++++++++++++++++++++++++++ src/registry.rs | 96 +------------ src/tests.rs | 2 +- src/ty/composite.rs | 9 +- src/ty/fields.rs | 24 ++-- src/ty/mod.rs | 153 ++++++++++++++------ src/ty/path.rs | 64 +++++---- src/ty/variant.rs | 32 +++-- test_suite/tests/derive.rs | 53 ++++--- 15 files changed, 661 insertions(+), 307 deletions(-) create mode 100644 src/portable.rs diff --git a/.rustfmt.toml b/.rustfmt.toml index 82af1506..536703e6 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -9,7 +9,6 @@ format_code_in_doc_comments = false comment_width = 80 normalize_comments = true # changed normalize_doc_attributes = false -license_template_path = "FILE_TEMPLATE" # changed format_strings = false format_macro_matchers = false format_macro_bodies = true @@ -57,8 +56,6 @@ skip_children = false hide_parse_errors = false error_on_line_overflow = false error_on_unformatted = false -report_todo = "Always" -report_fixme = "Always" ignore = [] # Below are `rustfmt` internal settings diff --git a/derive/src/lib.rs b/derive/src/lib.rs index e5c9007f..08a74f0c 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -288,9 +288,13 @@ impl TypeInfoImpl { }) .collect::>(); - Some(quote! { - .#docs_builder_fn(&[ #( #docs ),* ]) - }) + if !docs.is_empty() { + Some(quote! { + .#docs_builder_fn([ #( #docs ),* ]) + }) + } else { + None + } } } diff --git a/src/build.rs b/src/build.rs index 46141092..e25d4b00 100644 --- a/src/build.rs +++ b/src/build.rs @@ -128,7 +128,11 @@ use crate::prelude::{ }; use crate::{ - form::MetaForm, + form::{ + Form, + MetaForm, + PortableForm, + }, Field, MetaType, Path, @@ -151,14 +155,14 @@ pub mod state { /// Builds a [`Type`](`crate::Type`) #[must_use] -pub struct TypeBuilder { - path: Option, - type_params: Vec, - docs: Vec<&'static str>, - marker: PhantomData S>, +pub struct TypeBuilder { + path: Option>, + type_params: Vec>, + docs: Vec, + marker: PhantomData (F, S)>, } -impl Default for TypeBuilder { +impl Default for TypeBuilder { fn default() -> Self { TypeBuilder { path: Default::default(), @@ -169,9 +173,9 @@ impl Default for TypeBuilder { } } -impl TypeBuilder { +impl TypeBuilder { /// Set the Path for the type - pub fn path(self, path: Path) -> TypeBuilder { + pub fn path(self, path: Path) -> TypeBuilder { TypeBuilder { path: Some(path), type_params: self.type_params, @@ -181,31 +185,31 @@ impl TypeBuilder { } } -impl TypeBuilder { - fn build(self, type_def: D) -> Type +impl TypeBuilder { + fn build(self, type_def: D) -> Type where - D: Into, + D: Into>, { let path = self.path.expect("Path not assigned"); Type::new(path, self.type_params, type_def, self.docs) } /// Construct a "variant" type i.e an `enum` - pub fn variant(self, builder: Variants) -> Type { + pub fn variant(self, builder: Variants) -> Type { self.build(builder.finalize()) } /// Construct a "composite" type i.e. a `struct` - pub fn composite(self, fields: FieldsBuilder) -> Type { + pub fn composite(self, fields: FieldsBuilder) -> Type { self.build(TypeDefComposite::new(fields.finalize())) } } -impl TypeBuilder { +impl TypeBuilder { /// Set the type parameters if it's a generic type pub fn type_params(mut self, type_params: I) -> Self where - I: IntoIterator, + I: IntoIterator>, { self.type_params = type_params.into_iter().collect(); self @@ -213,21 +217,30 @@ impl TypeBuilder { #[cfg(feature = "docs")] /// Set the type documentation - pub fn docs(mut self, docs: &[&'static str]) -> Self { - self.docs = docs.to_vec(); + pub fn docs(mut self, docs: I) -> Self + where + I: IntoIterator, + { + self.docs = docs.into_iter().collect(); self } #[cfg(not(feature = "docs"))] #[inline] /// Doc capture is not enabled via the "docs" feature so this is a no-op. - pub fn docs(self, _docs: &'static [&'static str]) -> Self { + pub fn docs(self, _docs: I) -> Self + where + I: IntoIterator, + { self } /// Set the type documentation, always captured even if the "docs" feature is not enabled. - pub fn docs_always(mut self, docs: &[&'static str]) -> Self { - self.docs = docs.to_vec(); + pub fn docs_always(mut self, docs: I) -> Self + where + I: IntoIterator, + { + self.docs = docs.into_iter().collect(); self } } @@ -240,33 +253,33 @@ pub enum NamedFields {} pub enum UnnamedFields {} /// Provides FieldsBuilder constructors -pub enum Fields {} +pub struct Fields(PhantomData F>); -impl Fields { +impl Fields { /// The type construct has no fields - pub fn unit() -> FieldsBuilder { - FieldsBuilder::::default() + pub fn unit() -> FieldsBuilder { + FieldsBuilder::::default() } /// Fields for a type construct with named fields - pub fn named() -> FieldsBuilder { + pub fn named() -> FieldsBuilder { FieldsBuilder::default() } /// Fields for a type construct with unnamed fields - pub fn unnamed() -> FieldsBuilder { + pub fn unnamed() -> FieldsBuilder { FieldsBuilder::default() } } /// Build a set of either all named (e.g. for a struct) or all unnamed (e.g. for a tuple struct) #[must_use] -pub struct FieldsBuilder { - fields: Vec, +pub struct FieldsBuilder { + fields: Vec>, marker: PhantomData T>, } -impl Default for FieldsBuilder { +impl Default for FieldsBuilder { fn default() -> Self { Self { fields: Vec::new(), @@ -275,12 +288,14 @@ impl Default for FieldsBuilder { } } -impl FieldsBuilder { +impl FieldsBuilder { /// Complete building and return the set of fields - pub fn finalize(self) -> Vec> { + pub fn finalize(self) -> Vec> { self.fields } +} +impl FieldsBuilder { fn push_field(mut self, field: Field) -> Self { // filter out fields of PhantomData if !field.ty().is_phantom() { @@ -290,28 +305,83 @@ impl FieldsBuilder { } } -impl FieldsBuilder { +impl FieldsBuilder { /// Add a named field constructed using the builder. - pub fn field(self, builder: F) -> Self + pub fn field(self, builder: B) -> Self where - F: Fn( + B: Fn( FieldBuilder, - ) - -> FieldBuilder, + ) -> FieldBuilder< + MetaForm, + field_state::NameAssigned, + field_state::TypeAssigned, + >, { let builder = builder(FieldBuilder::new()); self.push_field(builder.finalize()) } } -impl FieldsBuilder { +impl FieldsBuilder { /// Add an unnamed field constructed using the builder. - pub fn field(self, builder: F) -> Self + pub fn field(self, builder: B) -> Self where - F: Fn( + B: Fn( FieldBuilder, - ) - -> FieldBuilder, + ) -> FieldBuilder< + MetaForm, + field_state::NameNotAssigned, + field_state::TypeAssigned, + >, + { + let builder = builder(FieldBuilder::new()); + self.push_field(builder.finalize()) + } +} + +impl FieldsBuilder { + fn push_field(mut self, field: Field) -> Self { + self.fields.push(field); + self + } +} + +impl FieldsBuilder { + /// Add a named field constructed using the builder. + pub fn field_portable(self, builder: B) -> Self + where + B: Fn( + FieldBuilder< + PortableForm, + field_state::NameNotAssigned, + field_state::TypeNotAssigned, + >, + ) -> FieldBuilder< + PortableForm, + field_state::NameAssigned, + field_state::TypeAssigned, + >, + { + let builder = builder(FieldBuilder::new()); + self.push_field(builder.finalize()) + } +} + +impl FieldsBuilder { + /// Add an unnamed field constructed using the builder. + pub fn field_portable(self, builder: B) -> Self + where + B: Fn( + FieldBuilder< + PortableForm, + field_state::NameNotAssigned, + field_state::TypeNotAssigned, + >, + ) -> FieldBuilder< + PortableForm, + field_state::NameNotAssigned, + field_state::TypeAssigned, + >, { let builder = builder(FieldBuilder::new()); self.push_field(builder.finalize()) @@ -333,17 +403,18 @@ pub mod field_state { /// Construct a valid [`Field`]. #[must_use] pub struct FieldBuilder< + F: Form = MetaForm, N = field_state::NameNotAssigned, T = field_state::TypeNotAssigned, > { - name: Option<&'static str>, - ty: Option, - type_name: Option<&'static str>, - docs: &'static [&'static str], + name: Option, + ty: Option, + type_name: Option, + docs: Vec, marker: PhantomData (N, T)>, } -impl Default for FieldBuilder { +impl Default for FieldBuilder { fn default() -> Self { FieldBuilder { name: Default::default(), @@ -355,16 +426,16 @@ impl Default for FieldBuilder { } } -impl FieldBuilder { +impl FieldBuilder { /// Create a new FieldBuilder. pub fn new() -> Self { Default::default() } } -impl FieldBuilder { +impl FieldBuilder { /// Initialize the field name. - pub fn name(self, name: &'static str) -> FieldBuilder { + pub fn name(self, name: F::String) -> FieldBuilder { FieldBuilder { name: Some(name), ty: self.ty, @@ -375,9 +446,9 @@ impl FieldBuilder { } } -impl FieldBuilder { +impl FieldBuilder { /// Initialize the type of the field. - pub fn ty(self) -> FieldBuilder + pub fn ty(self) -> FieldBuilder where TY: TypeInfo + 'static + ?Sized, { @@ -391,7 +462,7 @@ impl FieldBuilder { } /// Initializes the type of the field as a compact type. - pub fn compact(self) -> FieldBuilder + pub fn compact(self) -> FieldBuilder where TY: scale::HasCompact + TypeInfo + 'static, { @@ -405,9 +476,25 @@ impl FieldBuilder { } } -impl FieldBuilder { +impl FieldBuilder { + /// Initialize the type of the field. + pub fn ty(self, ty: T) -> FieldBuilder + where + T: Into<::Type>, + { + FieldBuilder { + name: self.name, + ty: Some(ty.into()), + type_name: self.type_name, + docs: self.docs, + marker: PhantomData, + } + } +} + +impl FieldBuilder { /// Initialize the type name of a field (optional). - pub fn type_name(self, type_name: &'static str) -> FieldBuilder { + pub fn type_name(self, type_name: F::String) -> FieldBuilder { FieldBuilder { name: self.name, ty: self.ty, @@ -419,12 +506,15 @@ impl FieldBuilder { #[cfg(feature = "docs")] /// Initialize the documentation of a field (optional). - pub fn docs(self, docs: &'static [&'static str]) -> FieldBuilder { + pub fn docs(self, docs: I) -> FieldBuilder + where + I: IntoIterator, + { FieldBuilder { name: self.name, ty: self.ty, type_name: self.type_name, - docs, + docs: docs.into_iter().collect(), marker: PhantomData, } } @@ -432,26 +522,32 @@ impl FieldBuilder { #[cfg(not(feature = "docs"))] #[inline] /// Doc capture is not enabled via the "docs" feature so this is a no-op. - pub fn docs(self, _docs: &'static [&'static str]) -> FieldBuilder { + pub fn docs(self, _docs: I) -> FieldBuilder + where + I: IntoIterator, + { self } /// Initialize the documentation of a field, always captured even if the "docs" feature is not /// enabled. - pub fn docs_always(self, docs: &'static [&'static str]) -> Self { + pub fn docs_always(self, docs: I) -> Self + where + I: IntoIterator, + { FieldBuilder { name: self.name, ty: self.ty, type_name: self.type_name, - docs, + docs: docs.into_iter().collect(), marker: PhantomData, } } } -impl FieldBuilder { +impl FieldBuilder { /// Complete building and return a new [`Field`]. - pub fn finalize(self) -> Field { + pub fn finalize(self) -> Field { Field::new( self.name, self.ty.expect("Type should be set by builder"), @@ -464,22 +560,22 @@ impl FieldBuilder { /// Builds a definition of a variant type i.e an `enum` #[derive(Default)] #[must_use] -pub struct Variants { - variants: Vec, +pub struct Variants { + variants: Vec>, } -impl Variants { +impl Variants { /// Create a new [`VariantsBuilder`]. pub fn new() -> Self { - Variants { + Self { variants: Vec::new(), } } /// Add a variant - pub fn variant(mut self, name: &'static str, builder: F) -> Self + pub fn variant(mut self, name: F::String, builder: B) -> Self where - F: Fn(VariantBuilder) -> VariantBuilder, + B: Fn(VariantBuilder) -> VariantBuilder, { let builder = builder(VariantBuilder::new(name)); self.variants.push(builder.finalize()); @@ -487,14 +583,14 @@ impl Variants { } /// Add a unit variant (without fields). - pub fn variant_unit(mut self, name: &'static str, index: u8) -> Self { + pub fn variant_unit(mut self, name: F::String, index: u8) -> Self { let builder = VariantBuilder::new(name).index(index); self.variants.push(builder.finalize()); self } /// Construct a new [`TypeDefVariant`] from the initialized builder variants. - pub fn finalize(self) -> TypeDefVariant { + pub fn finalize(self) -> TypeDefVariant { TypeDefVariant::new(self.variants) } } @@ -509,18 +605,18 @@ pub mod variant_state { /// Build a [`Variant`]. #[must_use] -pub struct VariantBuilder { - name: &'static str, +pub struct VariantBuilder { + name: F::String, index: Option, - fields: Vec>, + fields: Vec>, discriminant: Option, - docs: Vec<&'static str>, + docs: Vec, marker: PhantomData, } -impl VariantBuilder { +impl VariantBuilder { /// Create a new [`VariantBuilder`]. - pub fn new(name: &'static str) -> Self { + pub fn new(name: F::String) -> Self { Self { name, fields: Vec::new(), @@ -532,7 +628,7 @@ impl VariantBuilder { } /// Set the variant's codec index. - pub fn index(self, index: u8) -> VariantBuilder { + pub fn index(self, index: u8) -> VariantBuilder { VariantBuilder { name: self.name, index: Some(index), @@ -544,7 +640,7 @@ impl VariantBuilder { } } -impl VariantBuilder { +impl VariantBuilder { /// Set the variant's discriminant. pub fn discriminant(mut self, discriminant: u64) -> Self { self.discriminant = Some(discriminant); @@ -552,36 +648,45 @@ impl VariantBuilder { } /// Initialize the variant's fields. - pub fn fields(mut self, fields_builder: FieldsBuilder) -> Self { + pub fn fields(mut self, fields_builder: FieldsBuilder) -> Self { self.fields = fields_builder.finalize(); self } #[cfg(feature = "docs")] /// Initialize the variant's documentation. - pub fn docs(mut self, docs: &[&'static str]) -> Self { - self.docs = docs.to_vec(); + pub fn docs(mut self, docs: I) -> Self + where + I: IntoIterator, + { + self.docs = docs.into_iter().map(Into::into).collect(); self } #[cfg(not(feature = "docs"))] #[inline] /// Doc capture is not enabled via the "docs" feature so this is a no-op. - pub fn docs(self, _docs: &[&'static str]) -> Self { + pub fn docs(self, _docs: I) -> Self + where + I: IntoIterator, + { self } /// Initialize the variant's documentation, always captured even if the "docs" feature is not /// enabled. - pub fn docs_always(mut self, docs: &[&'static str]) -> Self { - self.docs = docs.to_vec(); + pub fn docs_always(mut self, docs: I) -> Self + where + I: IntoIterator, + { + self.docs = docs.into_iter().collect(); self } } -impl VariantBuilder { +impl VariantBuilder { /// Complete building and create final [`Variant`] instance. - pub fn finalize(self) -> Variant { + pub fn finalize(self) -> Variant { Variant::new( self.name, self.fields, diff --git a/src/impls.rs b/src/impls.rs index 27efbb20..b4263f87 100644 --- a/src/impls.rs +++ b/src/impls.rs @@ -337,7 +337,7 @@ impl TypeInfo for PhantomData { // Fields of this type should be filtered out and never appear in the type graph. Type::builder() .path(Path::prelude("PhantomData")) - .docs(&["PhantomData placeholder, this type should be filtered out"]) + .docs(["PhantomData placeholder, this type should be filtered out"]) .composite(Fields::unit()) } } diff --git a/src/interner.rs b/src/interner.rs index 112575ec..65ae7d26 100644 --- a/src/interner.rs +++ b/src/interner.rs @@ -61,6 +61,15 @@ impl UntrackedSymbol { } } +impl From for UntrackedSymbol { + fn from(id: u32) -> Self { + Self { + id, + marker: Default::default(), + } + } +} + /// A symbol from an interner. /// /// Can be used to resolve to the associated instance. @@ -121,7 +130,7 @@ pub struct Interner { /// /// This is used to efficiently provide access to the cached elements and /// to establish a strict ordering upon them since each is uniquely - /// idenfitied later by its position in the vector. + /// identified later by its position in the vector. vec: Vec, } @@ -189,6 +198,11 @@ where } self.vec.get(idx) } + + /// Returns the ordered sequence of interned elements. + pub fn elements(&self) -> &[T] { + &self.vec + } } #[cfg(test)] diff --git a/src/lib.rs b/src/lib.rs index 8b8131ce..4fa4dbae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -335,6 +335,7 @@ pub mod form; mod impls; pub mod interner; mod meta_type; +mod portable; mod registry; mod ty; mod utils; @@ -347,9 +348,12 @@ pub use scale; pub use self::{ meta_type::MetaType, + portable::{ + PortableRegistry, + PortableRegistryBuilder, + }, registry::{ IntoPortable, - PortableRegistry, Registry, }, ty::*, diff --git a/src/portable.rs b/src/portable.rs new file mode 100644 index 00000000..96978bf2 --- /dev/null +++ b/src/portable.rs @@ -0,0 +1,221 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The registry stores type definitions in a space-efficient manner. +//! +//! This is done by deduplicating common types in order to reuse their +//! definitions which otherwise can grow arbitrarily large. A type is uniquely +//! identified by its type identifier that is therefore used to refer to types +//! and their definitions. +//! +//! Types with the same name are uniquely identifiable by introducing +//! namespaces. The normal Rust namespace of a type is used, except for the Rust +//! prelude types that live in the so-called root namespace which is empty. + +use crate::{ + form::PortableForm, + interner::Interner, + prelude::{ + fmt::Debug, + vec::Vec, + }, + Registry, + Type, +}; +use scale::Encode; + +/// A read-only registry containing types in their portable form for serialization. +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(all(feature = "serde", feature = "decode"), derive(serde::Deserialize))] +#[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] +#[derive(Clone, Debug, PartialEq, Eq, Encode)] +pub struct PortableRegistry { + types: Vec, +} + +impl From for PortableRegistry { + fn from(registry: Registry) -> Self { + PortableRegistry { + types: registry + .types() + .map(|(k, v)| { + PortableType { + id: k.id(), + ty: v.clone(), + } + }) + .collect::>(), + } + } +} + +impl PortableRegistry { + /// Returns the type definition for the given identifier, `None` if no type found for that ID. + pub fn resolve(&self, id: u32) -> Option<&Type> { + self.types.get(id as usize).map(|ty| ty.ty()) + } + + /// Returns all types with their associated identifiers. + pub fn types(&self) -> &[PortableType] { + &self.types + } +} + +/// Represent a type in it's portable form. +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(all(feature = "serde", feature = "decode"), derive(serde::Deserialize))] +#[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] +#[derive(Clone, Debug, PartialEq, Eq, Encode)] +pub struct PortableType { + #[codec(compact)] + id: u32, + #[cfg_attr(feature = "serde", serde(rename = "type"))] + ty: Type, +} + +impl PortableType { + /// Construct a custom `PortableType`. + pub fn new(id: u32, ty: Type) -> Self { + Self { id, ty } + } + + /// Returns the index of the [`PortableType`]. + pub fn id(&self) -> u32 { + self.id + } + + /// Returns the type of the [`PortableType`]. + pub fn ty(&self) -> &Type { + &self.ty + } +} + +/// Construct a [`PortableRegistry`]. +/// +/// Guarantees that the resulting [`PortableRegistry`] has the list of types in the correct order, +/// since downstream libs assume that a `u32` type id corresponds to the index of the type +/// definition type table. +#[derive(Debug, Default)] +pub struct PortableRegistryBuilder { + types: Interner>, +} + +impl PortableRegistryBuilder { + /// Create a new [`PortableRegistryBuilder`]. + pub fn new() -> Self { + Default::default() + } + + /// Register a type, returning the assigned ID. + /// + /// If the type is already registered it will return the existing ID. + pub fn register_type(&mut self, ty: Type) -> u32 { + self.types.intern_or_get(ty).1.into_untracked().id() + } + + /// Returns the type id that would be assigned to a newly registered type. + pub fn next_type_id(&self) -> u32 { + self.types.elements().len() as u32 + } + + /// Returns a reference to the type registered at the given ID (if any). + pub fn get(&self, id: u32) -> Option<&Type> { + self.types.elements().get(id as usize) + } + + /// Finalize and return a valid [`PortableRegistry`] instance. + pub fn finish(&self) -> PortableRegistry { + let types = self + .types + .elements() + .iter() + .enumerate() + .map(|(i, ty)| { + PortableType { + id: i as u32, + ty: ty.clone(), + } + }) + .collect(); + PortableRegistry { types } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + build::*, + prelude::vec, + *, + }; + + #[test] + fn type_ids_are_sequential() { + let mut registry = Registry::new(); + registry.register_type(&MetaType::new::()); + registry.register_type(&MetaType::new::()); + registry.register_type(&MetaType::new::>()); + + let readonly: PortableRegistry = registry.into(); + + assert_eq!(4, readonly.types().len()); + + for (expected, ty) in readonly.types().iter().enumerate() { + assert_eq!(expected as u32, ty.id()); + } + } + + #[test] + fn construct_portable_registry() { + let mut builder = PortableRegistryBuilder::new(); + let u32_type = Type::new(Path::default(), vec![], TypeDefPrimitive::U32, vec![]); + let u32_type_id = builder.register_type(u32_type.clone()); + + let vec_u32_type = Type::new( + Path::default(), + vec![], + TypeDefSequence::new(u32_type_id.into()), + vec![], + ); + let vec_u32_type_id = builder.register_type(vec_u32_type.clone()); + + let self_referential_type_id = builder.next_type_id(); + + let composite_type = Type::builder_portable() + .path(Path::from_segments_unchecked(["MyStruct".into()])) + .composite( + Fields::named() + .field_portable(|f| f.name("primitive".into()).ty(u32_type_id)) + .field_portable(|f| f.name("vec_of_u32".into()).ty(vec_u32_type_id)) + .field_portable(|f| { + f.name("self_referential".into()) + .ty(self_referential_type_id) + }), + ); + let composite_type_id = builder.register_type(composite_type.clone()); + + assert_eq!(self_referential_type_id, composite_type_id); + + assert_eq!(builder.get(u32_type_id).unwrap(), &u32_type); + assert_eq!(builder.get(vec_u32_type_id).unwrap(), &vec_u32_type); + assert_eq!(builder.get(composite_type_id).unwrap(), &composite_type); + + let registry = builder.finish(); + + assert_eq!(Some(&u32_type), registry.resolve(u32_type_id)); + assert_eq!(Some(&vec_u32_type), registry.resolve(vec_u32_type_id)); + assert_eq!(Some(&composite_type), registry.resolve(composite_type_id)); + } +} diff --git a/src/registry.rs b/src/registry.rs index 1e225f14..4c6b6c83 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -31,10 +31,7 @@ use crate::prelude::{ }; use crate::{ - form::{ - Form, - PortableForm, - }, + form::PortableForm, interner::{ Interner, UntrackedSymbol, @@ -42,7 +39,6 @@ use crate::{ meta_type::MetaType, Type, }; -use scale::Encode; /// Convert the type definition into the portable form using a registry. pub trait IntoPortable { @@ -53,14 +49,6 @@ pub trait IntoPortable { fn into_portable(self, registry: &mut Registry) -> Self::Output; } -impl IntoPortable for &'static str { - type Output = ::String; - - fn into_portable(self, _registry: &mut Registry) -> Self::Output { - self.into() - } -} - /// The registry for space-efficient storage of type identifiers and /// definitions. /// @@ -84,7 +72,7 @@ pub struct Registry { /// The database where registered types reside. /// /// The contents herein is used for serlialization. - types: BTreeMap, Type>, + types: BTreeMap, Type>, } impl Default for Registry { @@ -156,66 +144,12 @@ impl Registry { .map(|i| i.into_portable(self)) .collect::>() } -} - -/// A read-only registry containing types in their portable form for serialization. -#[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[cfg_attr(all(feature = "serde", feature = "decode"), derive(serde::Deserialize))] -#[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] -#[derive(Clone, Debug, PartialEq, Eq, Encode)] -pub struct PortableRegistry { - types: Vec, -} -impl From for PortableRegistry { - fn from(registry: Registry) -> Self { - PortableRegistry { - types: registry - .types - .iter() - .map(|(k, v)| { - PortableType { - id: k.id(), - ty: v.clone(), - } - }) - .collect::>(), - } - } -} - -impl PortableRegistry { - /// Returns the type definition for the given identifier, `None` if no type found for that ID. - pub fn resolve(&self, id: u32) -> Option<&Type> { - self.types.get(id as usize).map(|ty| ty.ty()) - } - - /// Returns all types with their associated identifiers. - pub fn types(&self) -> &[PortableType] { - &self.types - } -} - -#[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[cfg_attr(all(feature = "serde", feature = "decode"), derive(serde::Deserialize))] -#[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] -#[derive(Clone, Debug, PartialEq, Eq, Encode)] -pub struct PortableType { - #[codec(compact)] - id: u32, - #[cfg_attr(feature = "serde", serde(rename = "type"))] - ty: Type, -} - -impl PortableType { - /// Returns the index of the [`PortableType`]. - pub fn id(&self) -> u32 { - self.id - } - - /// Returns the type of the [`PortableType`]. - pub fn ty(&self) -> &Type { - &self.ty + /// Returns an iterator over the types with their keys + pub fn types( + &self, + ) -> impl Iterator, &Type)> { + self.types.iter() } } @@ -230,22 +164,6 @@ mod tests { TypeInfo, }; - #[test] - fn readonly_type_ids() { - let mut registry = Registry::new(); - registry.register_type(&MetaType::new::()); - registry.register_type(&MetaType::new::()); - registry.register_type(&MetaType::new::>()); - - let readonly: PortableRegistry = registry.into(); - - assert_eq!(4, readonly.types().len()); - - for (expected, ty) in readonly.types().iter().enumerate() { - assert_eq!(expected as u32, ty.id()); - } - } - #[test] fn recursive_struct_with_references() { #[allow(unused)] diff --git a/src/tests.rs b/src/tests.rs index 753ada1f..7a21fbb6 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -111,7 +111,7 @@ fn phantom_data() { PhantomData, Type::builder() .path(Path::prelude("PhantomData")) - .docs(&["PhantomData placeholder, this type should be filtered out"]) + .docs(["PhantomData placeholder, this type should be filtered out"]) .composite(Fields::unit()) ) } diff --git a/src/ty/composite.rs b/src/ty/composite.rs index c14d7860..faeaef68 100644 --- a/src/ty/composite.rs +++ b/src/ty/composite.rs @@ -89,11 +89,14 @@ impl IntoPortable for TypeDefComposite { } } -impl TypeDefComposite { +impl TypeDefComposite +where + T: Form, +{ /// Creates a new struct definition with named fields. - pub(crate) fn new(fields: I) -> Self + pub fn new(fields: I) -> Self where - I: IntoIterator, + I: IntoIterator>, { Self { fields: fields.into_iter().collect(), diff --git a/src/ty/fields.rs b/src/ty/fields.rs index 7b4a0c91..e024a421 100644 --- a/src/ty/fields.rs +++ b/src/ty/fields.rs @@ -21,7 +21,6 @@ use crate::{ }, prelude::vec::Vec, IntoPortable, - MetaType, Registry, }; use scale::Encode; @@ -102,17 +101,20 @@ impl IntoPortable for Field { fn into_portable(self, registry: &mut Registry) -> Self::Output { Field { - name: self.name.map(|name| name.into_portable(registry)), + name: self.name.map(Into::into), ty: registry.register_type(&self.ty), - type_name: self.type_name.map(|tn| tn.into_portable(registry)), - docs: registry.map_into_portable(self.docs), + type_name: self.type_name.map(Into::into), + docs: self.docs.into_iter().map(Into::into).collect(), } } } -impl Field { +impl Field +where + T: Form, +{ /// Returns a new [`FieldBuilder`] for constructing a field. - pub fn builder() -> FieldBuilder { + pub fn builder() -> FieldBuilder { FieldBuilder::new() } @@ -120,16 +122,16 @@ impl 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: Option<&'static str>, - docs: &[&'static str], + name: Option, + ty: T::Type, + type_name: Option, + docs: Vec, ) -> Self { Self { name, ty, type_name, - docs: docs.to_vec(), + docs, } } } diff --git a/src/ty/mod.rs b/src/ty/mod.rs index 02c25137..20215a22 100644 --- a/src/ty/mod.rs +++ b/src/ty/mod.rs @@ -94,14 +94,14 @@ impl IntoPortable for Type { path: self.path.into_portable(registry), type_params: registry.map_into_portable(self.type_params), type_def: self.type_def.into_portable(registry), - docs: registry.map_into_portable(self.docs), + docs: self.docs.into_iter().map(Into::into).collect(), } } } macro_rules! impl_from_type_def_for_type { ( $( $t:ty ), + $(,)?) => { $( - impl From<$t> for Type { + impl From<$t> for Type { fn from(item: $t) -> Self { Self::new(Path::voldemort(), Vec::new(), item, Vec::new()) } @@ -111,28 +111,41 @@ macro_rules! impl_from_type_def_for_type { impl_from_type_def_for_type!( TypeDefPrimitive, - TypeDefArray, - TypeDefSequence, - TypeDefTuple, - TypeDefCompact, - TypeDefBitSequence, + TypeDefArray, + TypeDefSequence, + TypeDefTuple, + TypeDefCompact, + TypeDefBitSequence, ); impl Type { - /// Create a [`TypeBuilder`](`crate::build::TypeBuilder`) the public API for constructing a [`Type`] + /// Create a [`TypeBuilder`](`crate::build::TypeBuilder`) the public API for constructing a + /// [`Type`] of [`MetaForm`]. pub fn builder() -> TypeBuilder { TypeBuilder::default() } - pub(crate) fn new( - path: Path, + /// Create a [`TypeBuilder`](`crate::build::TypeBuilder`) the public API for constructing a + /// [`Type`] of [`PortableForm`] for use at runtime. + pub fn builder_portable() -> TypeBuilder { + TypeBuilder::default() + } +} + +impl Type +where + F: Form, +{ + /// Create a [`Type`]. + pub fn new( + path: Path, type_params: I, type_def: D, - docs: Vec<&'static str>, - ) -> Self + docs: Vec, + ) -> Type where - I: IntoIterator, - D: Into, + I: IntoIterator>, + D: Into>, { Self { path, @@ -194,21 +207,36 @@ impl IntoPortable for TypeParameter { fn into_portable(self, registry: &mut Registry) -> Self::Output { TypeParameter { - name: self.name.into_portable(registry), + name: self.name.into(), ty: self.ty.map(|ty| registry.register_type(&ty)), } } } -impl TypeParameter -where - T: Form, -{ +impl TypeParameter { /// Create a new [`TypeParameter`]. - pub fn new(name: T::String, ty: Option) -> Self { + pub fn new( + name: ::String, + ty: Option<::Type>, + ) -> Self { Self { name, ty } } +} +impl TypeParameter { + /// Create a new [`TypeParameter`] in [`PortableForm`]. + pub fn new_portable( + name: ::String, + ty: Option<::Type>, + ) -> Self { + Self { name, ty } + } +} + +impl TypeParameter +where + T: Form, +{ /// Get the type of the parameter. /// /// `None` if the parameter is skipped. @@ -243,7 +271,7 @@ where )] #[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] #[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))] -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, From, Debug, Encode)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Encode)] pub enum TypeDef { /// A composite type (e.g. a struct or a tuple) #[codec(index = 0)] @@ -271,6 +299,27 @@ pub enum TypeDef { BitSequence(TypeDefBitSequence), } +macro_rules! impl_from_type_defs { + ( $($from:ty => $variant:ident, )* ) => { $( + impl From<$from> for TypeDef { + fn from(x: $from) -> Self { + Self::$variant(x) + } + } + )* } +} + +impl_from_type_defs!( + TypeDefComposite => Composite, + TypeDefVariant => Variant, + TypeDefSequence => Sequence, + TypeDefArray => Array, + TypeDefTuple => Tuple, + TypeDefPrimitive => Primitive, + TypeDefCompact => Compact, + TypeDefBitSequence => BitSequence, +); + impl IntoPortable for TypeDef { type Output = TypeDef; @@ -368,18 +417,16 @@ impl IntoPortable for TypeDefArray { } } -impl TypeDefArray { - /// Creates a new array type. - pub fn new(len: u32, type_param: MetaType) -> Self { - Self { len, type_param } - } -} - #[allow(clippy::len_without_is_empty)] impl TypeDefArray where T: Form, { + /// Creates a new array type. + pub fn new(len: u32, type_param: ::Type) -> Self { + Self { len, type_param } + } + /// Returns the length of the array type. pub fn len(&self) -> u32 { self.len @@ -438,6 +485,18 @@ impl TypeDefTuple { } } +impl TypeDefTuple { + /// Creates a new custom type definition from the given types. + pub fn new_portable(type_params: I) -> Self + where + I: IntoIterator::Type>, + { + Self { + fields: type_params.into_iter().collect(), + } + } +} + impl TypeDefTuple where T: Form, @@ -469,13 +528,6 @@ impl IntoPortable for TypeDefSequence { } impl TypeDefSequence { - /// Creates a new sequence type. - /// - /// Use this constructor if you want to instantiate from a given meta type. - pub fn new(type_param: MetaType) -> Self { - Self { type_param } - } - /// Creates a new sequence type. /// /// Use this constructor if you want to instantiate from a given @@ -492,6 +544,13 @@ impl TypeDefSequence where T: Form, { + /// Creates a new sequence type. + /// + /// Use this constructor if you want to instantiate from a given meta type. + pub fn new(type_param: ::Type) -> Self { + Self { type_param } + } + /// Returns the element type of the sequence type. pub fn type_param(&self) -> &T::Type { &self.type_param @@ -518,16 +577,15 @@ impl IntoPortable for TypeDefCompact { } } -impl TypeDefCompact { - /// Creates a new type wrapped in [`Compact`]. - pub fn new(type_param: MetaType) -> Self { - Self { type_param } - } -} impl TypeDefCompact where T: Form, { + /// Creates a new type wrapped in [`Compact`]. + pub fn new(type_param: ::Type) -> Self { + Self { type_param } + } + /// Returns the [`Compact`] wrapped type, i.e. the `T` in `Compact`. pub fn type_param(&self) -> &T::Type { &self.type_param @@ -594,3 +652,16 @@ impl TypeDefBitSequence { } } } + +impl TypeDefBitSequence { + /// Creates a new [`TypeDefBitSequence`] for the supplied bit order and bit store types. + pub fn new_portable( + bit_store_type: ::Type, + bit_order_type: ::Type, + ) -> Self { + Self { + bit_store_type, + bit_order_type, + } + } +} diff --git a/src/ty/path.rs b/src/ty/path.rs index 1d31ac71..1153074b 100644 --- a/src/ty/path.rs +++ b/src/ty/path.rs @@ -18,7 +18,6 @@ use crate::prelude::{ Error as FmtError, Formatter, }, - vec, vec::Vec, }; @@ -77,9 +76,9 @@ where impl IntoPortable for Path { type Output = Path; - fn into_portable(self, registry: &mut Registry) -> Self::Output { + fn into_portable(self, _registry: &mut Registry) -> Self::Output { Path { - segments: registry.map_into_portable(self.segments), + segments: self.segments.into_iter().map(Into::into).collect(), } } } @@ -90,7 +89,7 @@ impl Display for Path { } } -impl Path { +impl Path { /// Create a new Path /// /// # Panics @@ -103,33 +102,15 @@ impl Path { .expect("All path segments should be valid Rust identifiers") } - /// Create an empty path for types which shall not be named - #[allow(unused)] - pub(crate) fn voldemort() -> Path { - Path { - segments: Vec::new(), - } - } - - /// Crate a Path for types in the Prelude namespace - /// - /// # Panics - /// - /// - If the supplied ident is not a valid Rust identifier - pub(crate) fn prelude(ident: &'static str) -> Path { - Self::from_segments(vec![ident]) - .unwrap_or_else(|_| panic!("{} is not a valid Rust identifier", ident)) - } - /// Create a Path from the given segments /// /// # Errors /// /// - If no segments are supplied /// - If any of the segments are invalid Rust identifiers - pub fn from_segments(segments: I) -> Result + pub fn from_segments(segments: I) -> Result where - I: IntoIterator, + I: IntoIterator::String>, { let segments = segments.into_iter().collect::>(); if segments.is_empty() { @@ -140,12 +121,42 @@ impl Path { } Ok(Path { segments }) } + + /// Crate a Path for types in the Prelude namespace + /// + /// # Panics + /// + /// - If the supplied ident is not a valid Rust identifier + pub(crate) fn prelude(ident: ::String) -> Self { + Self::from_segments([ident]) + .unwrap_or_else(|_| panic!("{:?} is not a valid Rust identifier", ident)) + } } impl Path where T: Form, { + /// Create an empty path for types which shall not be named + #[allow(unused)] + pub(crate) fn voldemort() -> Self { + Self { + segments: Vec::new(), + } + } + + /// Create a Path from the given segments. + /// + /// Does *not* check that the segments are valid Rust identifiers. + pub fn from_segments_unchecked(segments: I) -> Path + where + I: IntoIterator, + { + Self { + segments: segments.into_iter().collect(), + } + } + /// Returns the segments of the Path pub fn segments(&self) -> &[T::String] { &self.segments @@ -217,7 +228,10 @@ mod tests { #[test] fn path_err() { - assert_eq!(Path::from_segments(vec![]), Err(PathError::MissingSegments)); + assert_eq!( + Path::from_segments(Vec::new()), + Err(PathError::MissingSegments) + ); assert_eq!( Path::from_segments(vec![""]), Err(PathError::InvalidIdentifier { segment: 0 }) diff --git a/src/ty/variant.rs b/src/ty/variant.rs index cac1ee3f..eb791b24 100644 --- a/src/ty/variant.rs +++ b/src/ty/variant.rs @@ -101,11 +101,14 @@ impl IntoPortable for TypeDefVariant { } } -impl TypeDefVariant { +impl TypeDefVariant +where + T: Form, +{ /// Create a new `TypeDefVariant` with the given variants pub fn new(variants: I) -> Self where - I: IntoIterator, + I: IntoIterator>, { Self { variants: variants.into_iter().collect(), @@ -150,25 +153,25 @@ where #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Encode)] pub struct Variant { /// The name of the variant. - name: T::String, + pub name: T::String, /// The fields of the variant. #[cfg_attr( feature = "serde", serde(skip_serializing_if = "Vec::is_empty", default) )] - fields: Vec>, + pub fields: Vec>, /// Index of the variant, used in `parity-scale-codec`. /// /// The value of this will be, in order of precedence: /// 1. The explicit index defined by a `#[codec(index = N)]` attribute. /// 2. The implicit index from the position of the variant in the `enum` definition. - index: u8, + pub index: u8, /// Documentation #[cfg_attr( feature = "serde", serde(skip_serializing_if = "Vec::is_empty", default) )] - docs: Vec, + pub docs: Vec, } impl IntoPortable for Variant { @@ -176,21 +179,24 @@ impl IntoPortable for Variant { fn into_portable(self, registry: &mut Registry) -> Self::Output { Variant { - name: self.name.into_portable(registry), + name: self.name.into(), fields: registry.map_into_portable(self.fields), index: self.index, - docs: registry.map_into_portable(self.docs), + docs: self.docs.into_iter().map(Into::into).collect(), } } } -impl Variant { +impl Variant +where + T: Form, +{ /// Creates a new variant. - pub(crate) fn new( - name: &'static str, - fields: Vec>, + pub fn new( + name: T::String, + fields: Vec>, index: u8, - docs: Vec<&'static str>, + docs: Vec, ) -> Self { Self { name, diff --git a/test_suite/tests/derive.rs b/test_suite/tests/derive.rs index 21d24a8a..33c3f617 100644 --- a/test_suite/tests/derive.rs +++ b/test_suite/tests/derive.rs @@ -67,14 +67,14 @@ fn struct_derive() { let struct_type = Type::builder() .path(Path::new("S", "derive")) .type_params(named_type_params![(T, bool), (U, u8)]) - .docs(&["Type docs.", "Multiline."]) + .docs(["Type docs.", "Multiline."]) .composite( Fields::named() .field(|f| { f.ty::() .name("t") .type_name("T") - .docs(&["Field docs."]) + .docs(["Field docs."]) }) .field(|f| f.ty::().name("u").type_name("U")), ); @@ -88,14 +88,14 @@ fn struct_derive() { let self_typed_type = Type::builder() .path(Path::new("S", "derive")) .type_params(named_type_params!((T, Box>), (U, bool))) - .docs(&["Type docs.", "Multiline."]) + .docs(["Type docs.", "Multiline."]) .composite( Fields::named() .field(|f| { f.ty::>>() .name("t") .type_name("T") - .docs(&["Field docs."]) + .docs(["Field docs."]) }) .field(|f| f.ty::().name("u").type_name("U")), ); @@ -146,10 +146,10 @@ fn tuple_struct_derive() { let ty = Type::builder() .path(Path::new("S", "derive")) .type_params(named_type_params!((T, bool))) - .docs(&["Type docs."]) + .docs(["Type docs."]) .composite( Fields::unnamed() - .field(|f| f.ty::().type_name("T").docs(&["Unnamed field docs."])), + .field(|f| f.ty::().type_name("T").docs(["Unnamed field docs."])), ); assert_type!(S, ty); @@ -182,11 +182,11 @@ fn c_like_enum_derive() { let ty = Type::builder() .path(Path::new("E", "derive")) - .docs(&["Enum docs."]) + .docs(["Enum docs."]) .variant( Variants::new() - .variant("A", |v| v.index(0).docs(&["Unit variant."])) - .variant("B", |v| v.index(10).docs(&["Variant with discriminant."])), + .variant("A", |v| v.index(0).docs(["Unit variant."])) + .variant("B", |v| v.index(10).docs(["Variant with discriminant."])), ); assert_type!(E, ty); @@ -241,15 +241,15 @@ fn enum_derive() { let ty = Type::builder() .path(Path::new("E", "derive")) .type_params(named_type_params!((T, bool))) - .docs(&["Enum docs."]) + .docs(["Enum docs."]) .variant( Variants::new() .variant("A", |v| { v.index(0) .fields(Fields::unnamed().field(|f| { - f.ty::().type_name("T").docs(&["Unnamed field."]) + f.ty::().type_name("T").docs(["Unnamed field."]) })) - .docs(&["Unnamed fields variant."]) + .docs(["Unnamed fields variant."]) }) .variant("B", |v| { v.index(1) @@ -257,11 +257,11 @@ fn enum_derive() { f.ty::() .name("b") .type_name("T") - .docs(&["Named field."]) + .docs(["Named field."]) })) - .docs(&["Named fields variant."]) + .docs(["Named fields variant."]) }) - .variant("C", |v| v.index(2).docs(&["Unit variant."])), + .variant("C", |v| v.index(2).docs(["Unit variant."])), ); assert_type!(E, ty); @@ -606,18 +606,13 @@ fn doc_capture_works() { 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("a").type_name("bool").docs(["Field a"])) + .field(|f| f.ty::().name("b").type_name("u8")) .field(|f| { f.ty::() .name("c") .type_name("u16") - .docs(&[" Indented"]) + .docs([" Indented"]) }), ); @@ -689,26 +684,26 @@ fn always_capture_docs() { let enum_ty = Type::builder() .path(Path::new("E", "derive")) - .docs_always(&["Type docs"]) + .docs_always(["Type docs"]) .variant(Variants::new().variant("A", |v| { v.index(0) .fields(Fields::named().field(|f| { f.ty::() .name("a") .type_name("u32") - .docs_always(&["field docs"]) + .docs_always(["field docs"]) })) - .docs_always(&["Variant docs"]) + .docs_always(["Variant docs"]) })); let struct_ty = Type::builder() .path(Path::new("S", "derive")) - .docs_always(&["Type docs"]) + .docs_always(["Type docs"]) .composite(Fields::named().field(|f| { f.ty::() .name("a") .type_name("bool") - .docs_always(&["field docs"]) + .docs_always(["field docs"]) })); assert_type!(E, enum_ty); @@ -838,7 +833,7 @@ fn docs_attr() { let ty = Type::builder() .path(Path::new("S", "derive")) - .docs(&["Docs attr"]) + .docs(["Docs attr"]) .composite(Fields::unit()); assert_type!(S, ty);