diff --git a/multi_index_map/Cargo.toml b/multi_index_map/Cargo.toml index ec4ea69..bdfb076 100644 --- a/multi_index_map/Cargo.toml +++ b/multi_index_map/Cargo.toml @@ -29,3 +29,6 @@ criterion = "0.5.0" [[bench]] name = "performance" harness = false + +[features] +trivial_bounds = ["multi_index_map_derive/trivial_bounds"] diff --git a/multi_index_map/tests/debug.rs b/multi_index_map/tests/debug.rs new file mode 100644 index 0000000..2aeaee2 --- /dev/null +++ b/multi_index_map/tests/debug.rs @@ -0,0 +1,33 @@ +#![cfg_attr(feature = "trivial_bounds", feature(trivial_bounds))] +#![cfg(feature = "trivial_bounds")] +use multi_index_map::MultiIndexMap; + +#[derive(Hash, PartialEq, Eq, Clone, Debug)] +struct TestNonPrimitiveType(u64); + +#[derive(MultiIndexMap, Clone, Debug)] +struct TestElement { + #[multi_index(hashed_unique)] + field1: TestNonPrimitiveType, + field2: String, +} + +#[test] +fn should_compile() { + let mut map = MultiIndexTestElementMap::default(); + + // check that formatting produces non empty strings + assert!(!format!("{:?}", map._field1_index).is_empty()); + assert!(!format!("{:?}", map._store).is_empty()); + assert!(!format!("{:?}", map).is_empty()); + + let elem1 = TestElement { + field1: TestNonPrimitiveType(42), + field2: "ElementOne".to_string(), + }; + map.insert(elem1); + + let msg = format!("{:?}", map); + // check if stored field 1 is present in debug output + assert!(msg.contains("42")); +} diff --git a/multi_index_map/tests/get_and_modify_mut.rs b/multi_index_map/tests/get_and_modify_mut.rs index a960ba7..7101c17 100644 --- a/multi_index_map/tests/get_and_modify_mut.rs +++ b/multi_index_map/tests/get_and_modify_mut.rs @@ -1,3 +1,5 @@ +#![cfg_attr(feature = "trivial_bounds", feature(trivial_bounds))] + use multi_index_map::MultiIndexMap; #[derive(Hash, PartialEq, Eq, Clone)] diff --git a/multi_index_map/tests/hashed_unique.rs b/multi_index_map/tests/hashed_unique.rs index 6ef2f41..244bdca 100644 --- a/multi_index_map/tests/hashed_unique.rs +++ b/multi_index_map/tests/hashed_unique.rs @@ -1,3 +1,5 @@ +#![cfg_attr(feature = "trivial_bounds", feature(trivial_bounds))] + use multi_index_map::MultiIndexMap; #[derive(Hash, PartialEq, Eq, Clone)] diff --git a/multi_index_map/tests/mixed_non_unique.rs b/multi_index_map/tests/mixed_non_unique.rs index 3949c74..ad381f4 100644 --- a/multi_index_map/tests/mixed_non_unique.rs +++ b/multi_index_map/tests/mixed_non_unique.rs @@ -1,3 +1,5 @@ +#![cfg_attr(feature = "trivial_bounds", feature(trivial_bounds))] + use multi_index_map::MultiIndexMap; #[derive(MultiIndexMap)] diff --git a/multi_index_map/tests/ordered_unique.rs b/multi_index_map/tests/ordered_unique.rs index d9ed5db..c243a0c 100644 --- a/multi_index_map/tests/ordered_unique.rs +++ b/multi_index_map/tests/ordered_unique.rs @@ -1,3 +1,5 @@ +#![cfg_attr(feature = "trivial_bounds", feature(trivial_bounds))] + use multi_index_map::MultiIndexMap; #[derive(Hash, PartialEq, Eq, Clone, PartialOrd, Ord)] diff --git a/multi_index_map_derive/Cargo.toml b/multi_index_map_derive/Cargo.toml index cfd93b0..7eeeee1 100644 --- a/multi_index_map_derive/Cargo.toml +++ b/multi_index_map_derive/Cargo.toml @@ -31,3 +31,6 @@ convert_case = { version = "0.6" } [lib] proc-macro = true + +[features] +trivial_bounds = [] diff --git a/multi_index_map_derive/src/generators.rs b/multi_index_map_derive/src/generators.rs index 2bedfbf..cc20284 100644 --- a/multi_index_map_derive/src/generators.rs +++ b/multi_index_map_derive/src/generators.rs @@ -36,27 +36,63 @@ pub(crate) fn generate_lookup_tables( let ty = &f.ty; let index_name = &idents.index_name; - match uniqueness { - Uniqueness::Unique => match ordering { - Ordering::Hashed => quote! { - #index_name: ::multi_index_map::rustc_hash::FxHashMap<#ty, usize>, - }, - Ordering::Ordered => quote! { - #index_name: ::std::collections::BTreeMap<#ty, usize>, - }, - }, - Uniqueness::NonUnique => match ordering { - Ordering::Hashed => quote! { - #index_name: ::multi_index_map::rustc_hash::FxHashMap<#ty, ::std::collections::BTreeSet>, - }, - Ordering::Ordered => quote! { - #index_name: ::std::collections::BTreeMap<#ty, ::std::collections::BTreeSet>, - }, - }, + let field_type = index_field_type(ty, ordering, uniqueness); + + quote! { + #index_name: #field_type, } }) } +fn index_field_type( + ty: &Type, + ordering: &Ordering, + uniqueness: &Uniqueness, +) -> ::proc_macro2::TokenStream { + match uniqueness { + Uniqueness::Unique => match ordering { + Ordering::Hashed => quote! { + ::multi_index_map::rustc_hash::FxHashMap<#ty, usize> + }, + Ordering::Ordered => quote! { + ::std::collections::BTreeMap<#ty, usize> + }, + }, + Uniqueness::NonUnique => match ordering { + Ordering::Hashed => quote! { + ::multi_index_map::rustc_hash::FxHashMap<#ty, ::std::collections::BTreeSet> + }, + Ordering::Ordered => quote! { + ::std::collections::BTreeMap<#ty, ::std::collections::BTreeSet> + }, + }, + } +} + +// For each indexed field generate a TokenStream of the Debug bound for the field type and the multi_index_map specific type +#[cfg(feature = "trivial_bounds")] +pub(crate) fn generate_lookup_table_field_types( + fields: &[(Field, FieldIdents, Ordering, Uniqueness)], +) -> impl Iterator + '_ { + fields + .iter() + .flat_map(|(f, _idents, ordering, uniqueness)| { + let ty = &f.ty; + + let type_debug = quote! { + #ty: ::core::fmt::Debug, + }; + + let field_type = index_field_type(ty, ordering, uniqueness); + + let field_debug = quote! { + #field_type: ::core::fmt::Debug, + }; + + [type_debug, field_debug] + }) +} + // For each indexed field generate a TokenStream representing initializing the lookup table. // Used in `with_capacity` initialization // If lookup table data structures support `with_capacity`, change `default()` and `new()` calls to @@ -116,6 +152,20 @@ pub(crate) fn generate_lookup_table_shrink( }) } +// For each indexed field generate a TokenStream representing a debug struct field +#[cfg(feature = "trivial_bounds")] +pub(crate) fn generate_lookup_table_debug( + fields: &[(Field, FieldIdents, Ordering, Uniqueness)], +) -> impl Iterator + '_ { + fields.iter().map(|(_f, idents, _ordering, _uniqueness)| { + let index_name = &idents.index_name; + + quote! { + .field(stringify!(#index_name), &self.#index_name) + } + }) +} + // For each indexed field generate a TokenStream representing inserting the position // in the backing storage to that field's lookup table // Unique indexed fields just require a simple insert to the map, @@ -811,7 +861,30 @@ pub(crate) fn generate_expanded( lookup_table_fields_init: impl Iterator, lookup_table_fields_shrink: impl Iterator, lookup_table_fields_reserve: impl Iterator, + #[cfg(feature = "trivial_bounds")] lookup_table_fields_debug: impl Iterator< + Item = proc_macro2::TokenStream, + >, + #[cfg(feature = "trivial_bounds")] lookup_table_field_types: impl Iterator< + Item = proc_macro2::TokenStream, + >, ) -> proc_macro2::TokenStream { + #[cfg(not(feature = "trivial_bounds"))] + let debug_impl = quote! {}; + + #[cfg(feature = "trivial_bounds")] + let debug_impl = quote! { + impl ::core::fmt::Debug for #map_name where #element_name: ::core::fmt::Debug, + #(#lookup_table_field_types)* + { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct(stringify!(#map_name)) + .field("_store", &self._store) + #(#lookup_table_fields_debug)* + .finish() + } + } + }; + quote! { #[derive(Default)] #element_vis struct #map_name { @@ -819,6 +892,8 @@ pub(crate) fn generate_expanded( #(#lookup_table_fields)* } + #debug_impl + impl #map_name { #element_vis fn with_capacity(n: usize) -> #map_name { #map_name { diff --git a/multi_index_map_derive/src/lib.rs b/multi_index_map_derive/src/lib.rs index f8d555b..9df00ac 100644 --- a/multi_index_map_derive/src/lib.rs +++ b/multi_index_map_derive/src/lib.rs @@ -81,8 +81,14 @@ pub fn multi_index_map(input: proc_macro::TokenStream) -> proc_macro::TokenStrea let lookup_table_fields_reserve = generators::generate_lookup_table_reserve(&indexed_fields); + #[cfg(feature = "trivial_bounds")] + let lookup_table_fields_debug = generators::generate_lookup_table_debug(&indexed_fields); + let lookup_table_fields_shrink = generators::generate_lookup_table_shrink(&indexed_fields); + #[cfg(feature = "trivial_bounds")] + let lookup_table_field_types = generators::generate_lookup_table_field_types(&indexed_fields); + let inserts = generators::generate_inserts(&indexed_fields); let removes = generators::generate_removes(&indexed_fields); @@ -118,6 +124,10 @@ pub fn multi_index_map(input: proc_macro::TokenStream) -> proc_macro::TokenStrea lookup_table_fields_init, lookup_table_fields_shrink, lookup_table_fields_reserve, + #[cfg(feature = "trivial_bounds")] + lookup_table_fields_debug, + #[cfg(feature = "trivial_bounds")] + lookup_table_field_types, ); // Hand the output tokens back to the compiler.