diff --git a/module/core/derive_tools/tests/inc/index/compiletime/struct.rs b/module/core/derive_tools/tests/inc/index/compiletime/struct.rs index 1b9d3785a8..df10669a10 100644 --- a/module/core/derive_tools/tests/inc/index/compiletime/struct.rs +++ b/module/core/derive_tools/tests/inc/index/compiletime/struct.rs @@ -4,7 +4,7 @@ use derive_tools::Index; struct StructMultipleNamed< T > { #[ index ] - a: Vec< T >, + a : Vec< T >, #[ index ] b : Vec< T >, } diff --git a/module/core/derive_tools/tests/inc/index/compiletime/struct.stderr b/module/core/derive_tools/tests/inc/index/compiletime/struct.stderr index e150d4f21d..2410de4629 100644 --- a/module/core/derive_tools/tests/inc/index/compiletime/struct.stderr +++ b/module/core/derive_tools/tests/inc/index/compiletime/struct.stderr @@ -1,8 +1,8 @@ -error: Only one field can include #[index] derive macro +error: Only one field can include #[ index ] derive macro --> tests/inc/index/compiletime/struct.rs:6:3 | 6 | / #[ index ] -7 | | a: Vec< T >, +7 | | a : Vec< T >, 8 | | #[ index ] 9 | | b : Vec< T >, | |_______________^ diff --git a/module/core/derive_tools/tests/inc/index/only_test/struct_multiple_named.rs b/module/core/derive_tools/tests/inc/index/only_test/struct_multiple_named.rs index 6153f8751e..50ea4f671a 100644 --- a/module/core/derive_tools/tests/inc/index/only_test/struct_multiple_named.rs +++ b/module/core/derive_tools/tests/inc/index/only_test/struct_multiple_named.rs @@ -3,8 +3,8 @@ fn index() { let x = StructMultipleNamed { - a: vec![ 12, 22 ], - b: vec![ 33, 55 ] + a : vec![ 12, 22 ], + b : vec![ 33, 55 ] }; let v = vec![ 33, 55 ]; let exp = ( v[ 0 ], v[ 1 ] ); diff --git a/module/core/derive_tools/tests/inc/index/only_test/struct_named.rs b/module/core/derive_tools/tests/inc/index/only_test/struct_named.rs index 53b2ab3f79..2d17264939 100644 --- a/module/core/derive_tools/tests/inc/index/only_test/struct_named.rs +++ b/module/core/derive_tools/tests/inc/index/only_test/struct_named.rs @@ -3,7 +3,7 @@ fn index() { let x = StructNamed { - a: vec![ false, true ] + a : vec![ false, true ] }; let v = vec![ false, true ]; let exp = ( v[ 0 ], v[ 1 ] ); diff --git a/module/core/derive_tools/tests/inc/index/struct_collisions.rs b/module/core/derive_tools/tests/inc/index/struct_collisions.rs new file mode 100644 index 0000000000..5d000f096c --- /dev/null +++ b/module/core/derive_tools/tests/inc/index/struct_collisions.rs @@ -0,0 +1,23 @@ +#![ allow( non_snake_case ) ] +#![ allow( unused_imports ) ] +use super::*; + +pub mod core {} +pub mod std {} +pub mod marker {} + +pub mod a {} +pub mod b {} + +#[ derive( the_module::Index, the_module::From ) ] +#[ allow( dead_code ) ] +struct StructMultipleNamed< T > +{ + #[ from ( on ) ] + a : Vec< T >, + #[ index ] + b : Vec< T >, +} + +include!( "./only_test/struct_multiple_named.rs" ); + diff --git a/module/core/derive_tools/tests/inc/index/struct_multiple_named.rs b/module/core/derive_tools/tests/inc/index/struct_multiple_named_field.rs similarity index 99% rename from module/core/derive_tools/tests/inc/index/struct_multiple_named.rs rename to module/core/derive_tools/tests/inc/index/struct_multiple_named_field.rs index 3dea73695e..a99e72a7b5 100644 --- a/module/core/derive_tools/tests/inc/index/struct_multiple_named.rs +++ b/module/core/derive_tools/tests/inc/index/struct_multiple_named_field.rs @@ -11,3 +11,4 @@ struct StructMultipleNamed< T > } include!( "./only_test/struct_multiple_named.rs" ); + diff --git a/module/core/derive_tools/tests/inc/index/struct_multiple_named_item.rs b/module/core/derive_tools/tests/inc/index/struct_multiple_named_item.rs new file mode 100644 index 0000000000..e2751673f8 --- /dev/null +++ b/module/core/derive_tools/tests/inc/index/struct_multiple_named_item.rs @@ -0,0 +1,13 @@ +#![ allow( dead_code ) ] +#[ allow( unused_imports ) ] +use super::*; + +#[ derive( the_module::Index ) ] +#[ index ( name = b ) ] +struct StructMultipleNamed< T > +{ + a : Vec< T >, + b : Vec< T >, +} + +include!( "./only_test/struct_multiple_named.rs" ); diff --git a/module/core/derive_tools/tests/inc/index/struct_multiple_named_manual.rs b/module/core/derive_tools/tests/inc/index/struct_multiple_named_manual.rs index 6e5bc2cba2..ff3d26f7e2 100644 --- a/module/core/derive_tools/tests/inc/index/struct_multiple_named_manual.rs +++ b/module/core/derive_tools/tests/inc/index/struct_multiple_named_manual.rs @@ -3,8 +3,8 @@ use core::ops::Index; #[ allow( dead_code ) ] struct StructMultipleNamed< T > { - a: Vec< T >, - b: Vec< T >, + a : Vec< T >, + b : Vec< T >, } impl< T > Index< usize > for StructMultipleNamed< T > diff --git a/module/core/derive_tools/tests/inc/index/struct_named_manual.rs b/module/core/derive_tools/tests/inc/index/struct_named_manual.rs index f09d4bbeb1..e66ce4131d 100644 --- a/module/core/derive_tools/tests/inc/index/struct_named_manual.rs +++ b/module/core/derive_tools/tests/inc/index/struct_named_manual.rs @@ -3,7 +3,7 @@ use core::ops::Index; #[ allow( dead_code ) ] struct StructNamed< T > { - a: Vec< T > + a : Vec< T > } impl< T > Index< usize > for StructNamed< T > diff --git a/module/core/derive_tools/tests/inc/mod.rs b/module/core/derive_tools/tests/inc/mod.rs index 6f46e324f5..5613c24bfd 100644 --- a/module/core/derive_tools/tests/inc/mod.rs +++ b/module/core/derive_tools/tests/inc/mod.rs @@ -305,15 +305,17 @@ mod index_tests #[ allow( unused_imports ) ] use super::*; - mod struct_named; - mod struct_multiple_named; + mod struct_named; + mod struct_multiple_named_field; + mod struct_multiple_named_item; mod struct_named_manual; mod struct_multiple_named_manual; mod struct_tuple; mod struct_multiple_tuple; mod struct_tuple_manual; mod struct_multiple_tuple_manual; - + mod struct_collisions; + only_for_terminal_module! { #[ test_tools::nightly ] diff --git a/module/core/derive_tools_meta/src/derive/index.rs b/module/core/derive_tools_meta/src/derive/index.rs index eb0f2fde9c..f9841e0d6a 100644 --- a/module/core/derive_tools_meta/src/derive/index.rs +++ b/module/core/derive_tools_meta/src/derive/index.rs @@ -8,12 +8,22 @@ use macro_tools:: Result }; -pub fn index( input : proc_macro::TokenStream ) -> Result< proc_macro2::TokenStream > +#[ path = "index/item_attributes.rs" ] +mod item_attributes; +use item_attributes::*; +#[ path = "index/field_attributes.rs" ] +mod field_attributes; +use field_attributes::*; + + +pub fn index( input : proc_macro::TokenStream ) -> Result< proc_macro2::TokenStream > { let original_input = input.clone(); let parsed = syn::parse::< StructLike >( input )?; let has_debug = attr::has_debug( parsed.attrs().iter() )?; let item_name = &parsed.ident(); + + let item_attrs = ItemAttributes::from_attrs( parsed.attrs().iter() )?; let ( _generics_with_defaults, generics_impl, generics_ty, generics_where ) = generic_params::decompose( &parsed.generics() ); @@ -24,6 +34,7 @@ pub fn index( input : proc_macro::TokenStream ) -> Result< proc_macro2::TokenStr generate_struct ( item_name, + &item_attrs, &generics_impl, &generics_ty, &generics_where, @@ -49,6 +60,7 @@ pub fn index( input : proc_macro::TokenStream ) -> Result< proc_macro2::TokenStr fn generate_struct ( item_name : &syn::Ident, + item_attrs : &ItemAttributes, generics_impl : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, generics_ty : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, generics_where : &syn::punctuated::Punctuated< syn::WherePredicate, syn::token::Comma >, @@ -62,10 +74,11 @@ fn generate_struct syn::Fields::Named( fields ) => generate_struct_named_fields ( - item_name, - generics_impl, - generics_ty, - generics_where, + item_name, + &item_attrs, + generics_impl, + generics_ty, + generics_where, fields ), @@ -84,10 +97,41 @@ fn generate_struct } } - +/// Generates `Index` implementation for named structs +/// +/// # Example +/// +/// ## Input +/// # use derive_tools_meta::Index; +/// #[ derive( Index ) ] +/// pub struct IsTransparent +/// { +/// #[ index ] +/// value : Vec< u8 >, +/// } +/// +/// ## Output +/// ```rust +/// pub struct IsTransparent +/// { +/// value : Vec< u8 >, +/// } +/// #[ automatically_derived ] +/// impl ::core::ops::Index< usize > for IsTransparent +/// { +/// type Output = u8; +/// #[ inline( always ) ] +/// fn index( &self, index : usize ) -> &Self::Output +/// { +/// &self.value[ index ] +/// } +/// } +/// ``` +/// fn generate_struct_named_fields ( item_name : &syn::Ident, + item_attrs : &ItemAttributes, generics_impl : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, generics_ty : &syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >, generics_where : &syn::punctuated::Punctuated< syn::WherePredicate, syn::token::Comma >, @@ -95,38 +139,76 @@ fn generate_struct_named_fields ) -> Result< proc_macro2::TokenStream > { + let fields = fields.named.clone(); - let non_empty_attrs : Vec< &syn::Field > = fields.iter().filter(| field | - !field.attrs.is_empty() - ).collect(); + let attr_name = &item_attrs.index.name.clone().internal(); - if non_empty_attrs.len() != 1 - { - return Err( - syn::Error::new_spanned - ( - &fields, - "Only one field can include #[index] derive macro" - ) - ); - } + let field_attrs: Vec< &syn::Field > = fields + .iter() + .filter + ( + | field | + { + FieldAttributes::from_attrs( field.attrs.iter() ).map_or + ( + false, + | attrs | attrs.index.value( false ) + ) + } + ) + .collect(); - let generated = fields.iter().map(| field | - { - let field_name = &field.ident; - if !field.attrs.is_empty() - { - qt! + let generated = if let Some( attr_name ) = attr_name + { + Ok + ( + qt! { - &self.#field_name[ index ] + &self.#attr_name[ index ] } - } - else + ) + } + else + { + match field_attrs.len() { - qt!{ } + 0 | 1 => + { + let field_name = + match field_attrs + .first() + .copied() + .or_else + ( + || fields.first() + ) + { + Some( field ) => + field.ident.as_ref().unwrap(), + None => + unimplemented!( "IndexMut not implemented for Unit" ), + }; + + Ok + ( + qt! + { + &self.#field_name[ index ] + } + ) + } + _ => + Err + ( + syn::Error::new_spanned + ( + &fields, + "Only one field can include #[ index ] derive macro" + ) + ), } - }); + }?; Ok ( @@ -141,13 +223,44 @@ fn generate_struct_named_fields #[ inline( always ) ] fn index( &self, index : usize ) -> &Self::Output { - #( #generated )* + #generated } } } ) } +/// Generates `Index` implementation for tuple structs +/// +/// # Example +/// +/// ## Input +/// # use derive_tools_meta::Index; +/// #[ derive( Index ) ] +/// pub struct IsTransparent +/// ( +/// #[ index ] +/// Vec< u8 > +/// ); +/// +/// ## Output +/// ```rust +/// pub struct IsTransparent +/// ( +/// Vec< u8 > +/// ); +/// #[ automatically_derived ] +/// impl ::core::ops::Index< usize > for IsTransparent +/// { +/// type Output = u8; +/// #[ inline( always ) ] +/// fn index( &self, index : usize ) -> &Self::Output +/// { +/// &self.0[ index ] +/// } +/// } +/// ``` +/// fn generate_struct_tuple_fields ( item_name : &syn::Ident, @@ -159,36 +272,62 @@ fn generate_struct_tuple_fields -> Result< proc_macro2::TokenStream > { let fields = fields.unnamed.clone(); - let non_empty_attrs : Vec< &syn::Field > = fields.iter().filter(| field | - !field.attrs.is_empty() - ).collect(); - - if non_empty_attrs.len() != 1 + let non_empty_attrs : Vec< &syn::Field > = fields + .iter() + .filter( | field | !field.attrs.is_empty() ) + .collect(); + + let generated = match non_empty_attrs.len() { - return Err( - syn::Error::new_spanned + 0 => + { + Ok ( - &fields, - "Only one field can include #[index] derive macro" + qt! + { + &self.0[ index ] + } ) - ); - } - - let generated = fields.iter().enumerate().map(|( i, field )| - { - let i = syn::Index::from( i ); - if !field.attrs.is_empty() { - qt! - { - &self.#i[ index ] + }, + 1 => + fields + .iter() + .enumerate() + .map + ( + | ( i, field ) | + { + let i = syn::Index::from( i ); + if !field.attrs.is_empty() + { + Ok + ( + qt! + { + &self.#i[ index ] + } + ) + } + else + { + Ok + ( + qt!{ } + ) + } } - } - else - { - qt!{ } - } - }); - + ).collect(), + _ => + Err + ( + syn::Error::new_spanned + ( + &fields, + "Only one field can include #[ index ] derive macro" + ) + ), + }?; + Ok ( qt! @@ -202,7 +341,7 @@ fn generate_struct_tuple_fields #[ inline( always ) ] fn index( &self, index : usize ) -> &Self::Output { - #( #generated )* + #generated } } } diff --git a/module/core/derive_tools_meta/src/derive/index/item_attributes.rs b/module/core/derive_tools_meta/src/derive/index/item_attributes.rs index 0e223789e0..33a056e248 100644 --- a/module/core/derive_tools_meta/src/derive/index/item_attributes.rs +++ b/module/core/derive_tools_meta/src/derive/index/item_attributes.rs @@ -1,11 +1,11 @@ +use super::*; use macro_tools:: { ct, - syn_err, - syn, - qt, Result, + AttributeComponent, AttributePropertyComponent, + AttributePropertyOptionalSyn, AttributePropertyOptionalSingletone, }; @@ -13,9 +13,20 @@ use macro_tools:: #[ derive( Debug, Default ) ] pub struct ItemAttributes { + /// Attribute for customizing generated code. + pub index : ItemAttributeIndex, + /// Specifies whether to provide a generated code as a hint. + /// Defaults to `false`, which means no code is printed unless explicitly requested. pub debug : AttributePropertyDebug, } +#[ derive( Debug, Default ) ] +pub struct ItemAttributeIndex +{ + /// Specifies what specific named field must implement Index. + pub name : AttributePropertyName, +} + impl ItemAttributes { /// Constructs a `ItemAttributes` instance from an iterator of attributes. @@ -24,7 +35,7 @@ impl ItemAttributes /// appropriate fields in the `ItemAttributes` struct. pub fn from_attrs< 'a >( attrs : impl Iterator< Item = & 'a syn::Attribute > ) -> Result< Self > { - let result = Self::default(); + let mut result = Self::default(); // Closure to generate an error message for unknown attributes. let error = | attr : & syn::Attribute | -> syn::Error @@ -33,6 +44,7 @@ impl ItemAttributes ( "Known attributes are: ", "debug", + ", ", ItemAttributeIndex::KEYWORD, "." ); syn_err! @@ -49,6 +61,7 @@ impl ItemAttributes let key_str = format!( "{}", key_ident ); match key_str.as_ref() { + ItemAttributeIndex::KEYWORD => result.assign( ItemAttributeIndex::from_meta( attr )? ), "debug" => {}, _ => {}, // _ => return Err( error( attr ) ), @@ -59,22 +72,162 @@ impl ItemAttributes } } +impl AttributeComponent for ItemAttributeIndex +{ + const KEYWORD : &'static str = "index"; + + fn from_meta( attr : &syn::Attribute ) -> Result< Self > + { + match attr.meta + { + syn::Meta::List( ref meta_list ) => + { + return syn::parse2::< ItemAttributeIndex >( meta_list.tokens.clone() ); + }, + syn::Meta::Path( ref _path ) => + { + return Ok( Default::default() ) + }, + _ => return_syn_err!( attr, "Expects an attribute of format `#[ from( on ) ]`. \nGot: {}", qt!{ #attr } ), + } + } + +} + + +impl< IntoT > Assign< ItemAttributeIndex, IntoT > for ItemAttributes +where + IntoT : Into< ItemAttributeIndex >, +{ + #[ inline( always ) ] + fn assign( &mut self, component : IntoT ) + { + self.index.assign( component.into() ); + } +} + + + +impl< IntoT > Assign< AttributePropertyDebug, IntoT > for ItemAttributes +where + IntoT : Into< AttributePropertyDebug >, +{ + #[ inline( always ) ] + fn assign( &mut self, component : IntoT ) + { + self.debug = component.into(); + } +} + + +impl< IntoT > Assign< ItemAttributeIndex, IntoT > for ItemAttributeIndex +where + IntoT : Into< ItemAttributeIndex >, +{ + #[ inline( always ) ] + fn assign( &mut self, component : IntoT ) + { + let component = component.into(); + self.name.assign( component.name ); + } +} + +impl< IntoT > Assign< AttributePropertyName, IntoT > for ItemAttributeIndex +where + IntoT : Into< AttributePropertyName >, +{ + #[ inline( always ) ] + fn assign( &mut self, component : IntoT ) + { + self.name = component.into(); + } +} + + +impl syn::parse::Parse for ItemAttributeIndex +{ + fn parse( input : syn::parse::ParseStream< '_ > ) -> syn::Result< Self > + { + let mut result = Self::default(); + + let error = | ident : &syn::Ident | -> syn::Error + { + let known = ct::concatcp! + ( + "Known entries of attribute ", ItemAttributeIndex::KEYWORD, " are : ", + AttributePropertyName::KEYWORD, + ".", + ); + syn_err! + ( + ident, + r#"Expects an attribute of format '#[ from( off ) ]' + {known} + But got: '{}' +"#, + qt!{ #ident } + ) + }; + + while !input.is_empty() + { + let lookahead = input.lookahead1(); + if lookahead.peek( syn::Ident ) + { + let ident : syn::Ident = input.parse()?; + match ident.to_string().as_str() + { + AttributePropertyName::KEYWORD => result.assign( AttributePropertyName::parse( input )? ), + _ => return Err( error( &ident ) ), + } + } + else + { + return Err( lookahead.error() ); + } + + // Optional comma handling + if input.peek( syn::Token![ , ] ) + { + input.parse::< syn::Token![ , ] >()?; + } + } + + Ok( result ) + } +} + + // == Attribute properties -/// Marker type for attribute property to specify whether to provide a sketch as a hint. -/// Defaults to `false`, which means no hint is provided unless explicitly requested. +/// Marker type for attribute property of optional identifier that names the setter. It is parsed from inputs +/// like `name = field_name`. +#[ derive( Debug, Default, Clone, Copy ) ] +pub struct NameMarker; + +impl AttributePropertyComponent for NameMarker +{ + const KEYWORD : &'static str = "name"; +} + +/// An optional identifier that names the setter. It is parsed from inputs +/// like `name = field_name`. +pub type AttributePropertyName = AttributePropertyOptionalSyn< syn::Ident, NameMarker >; + +// = + +/// Marker type for attribute property to specify whether to provide a generated code as a hint. +/// Defaults to `false`, which means no debug is provided unless explicitly requested. #[ derive( Debug, Default, Clone, Copy ) ] pub struct AttributePropertyDebugMarker; impl AttributePropertyComponent for AttributePropertyDebugMarker { - const KEYWORD : & 'static str = "debug"; + const KEYWORD : &'static str = "debug"; } -/// Specifies whether to provide a sketch as a hint. -/// Defaults to `false`, which means no hint is provided unless explicitly requested. +/// Specifies whether to provide a generated code as a hint. +/// Defaults to `false`, which means no debug is provided unless explicitly requested. pub type AttributePropertyDebug = AttributePropertyOptionalSingletone< AttributePropertyDebugMarker >; -// == - - +// == diff --git a/module/core/derive_tools_meta/src/lib.rs b/module/core/derive_tools_meta/src/lib.rs index 0bf7a79669..798ee1c2bd 100644 --- a/module/core/derive_tools_meta/src/lib.rs +++ b/module/core/derive_tools_meta/src/lib.rs @@ -584,6 +584,7 @@ pub fn phantom( _attr: proc_macro::TokenStream, input : proc_macro::TokenStream /// /// ```rust /// use core::ops::Index; +/// /// pub struct IsTransparent< T > /// { /// a : Vec< T >, @@ -604,9 +605,12 @@ pub fn phantom( _attr: proc_macro::TokenStream, input : proc_macro::TokenStream /// Use `#[ index ]` to automatically generate the implementation: /// /// ```rust -/// # use derive_tools_meta::*; +/// use derive_tools_meta::*; +/// +/// #[ derive( Index ) ] /// pub struct IsTransparent< T > /// { +/// #[ index ] /// a : Vec< T > /// }; /// ```