diff --git a/examples/machin/machin/build.rs b/examples/machin/machin/build.rs index 67e68fe..8fafd11 100644 --- a/examples/machin/machin/build.rs +++ b/examples/machin/machin/build.rs @@ -2,7 +2,11 @@ use std::{collections::BTreeMap, env, fs::File, io::Write, path::PathBuf}; use machin_data::MachinEnum; use truc::{ - generator::{config::GeneratorConfig, fragment::serde::SerdeImplGenerator, generate}, + generator::{ + config::GeneratorConfig, + fragment::{serde::SerdeImplGenerator, FragmentGenerator}, + generate, + }, record::{ definition::{DatumDefinitionOverride, RecordDefinitionBuilder}, type_resolver::{DynamicTypeInfo, StaticTypeResolver}, @@ -216,9 +220,9 @@ fn serialize_deserialize() { "{}", generate( &definition, - &GeneratorConfig { - custom_fragment_generators: vec![Box::new(SerdeImplGenerator)] - } + &GeneratorConfig::default_with_custom_generators([ + Box::new(SerdeImplGenerator) as Box + ]) ) ) .unwrap(); diff --git a/truc/src/generator/config.rs b/truc/src/generator/config.rs index 397f369..b03786b 100644 --- a/truc/src/generator/config.rs +++ b/truc/src/generator/config.rs @@ -1,6 +1,47 @@ -use super::fragment::FragmentGenerator; +use super::fragment::{ + data_records::DataRecordsGenerator, drop_impl::DropImplGenerator, + from_previous_record_data_records::FromPreviousRecordDataRecordsGenerator, + from_previous_record_impls::FromPreviousRecordImplsGenerator, + from_unpacked_record_impls::FromUnpackedRecordImplsGenerator, record::RecordGenerator, + record_impl::RecordImplGenerator, FragmentGenerator, +}; -#[derive(Default)] pub struct GeneratorConfig { - pub custom_fragment_generators: Vec>, + pub(crate) fragment_generators: Vec>, +} + +impl GeneratorConfig { + fn common_fragment_generators() -> [Box; 7] { + [ + Box::new(DataRecordsGenerator), + Box::new(RecordGenerator), + Box::new(RecordImplGenerator), + Box::new(DropImplGenerator), + Box::new(FromUnpackedRecordImplsGenerator), + Box::new(FromPreviousRecordDataRecordsGenerator), + Box::new(FromPreviousRecordImplsGenerator), + ] + } + + pub fn new(fragment_generators: impl IntoIterator>) -> Self { + Self { + fragment_generators: fragment_generators.into_iter().collect(), + } + } + + pub fn default_with_custom_generators( + custom_generators: impl IntoIterator>, + ) -> Self { + Self::new( + Self::common_fragment_generators() + .into_iter() + .chain(custom_generators.into_iter()), + ) + } +} + +impl Default for GeneratorConfig { + fn default() -> Self { + Self::new(Self::common_fragment_generators()) + } } diff --git a/truc/src/generator/fragment/data_records.rs b/truc/src/generator/fragment/data_records.rs index 690c186..60c0ff0 100644 --- a/truc/src/generator/fragment/data_records.rs +++ b/truc/src/generator/fragment/data_records.rs @@ -54,3 +54,221 @@ using it."#, } } } + +#[cfg(test)] +#[cfg_attr(coverage_nightly, coverage(off))] +mod tests { + use std::collections::BTreeSet; + + use maplit::btreeset; + use pretty_assertions::assert_eq; + use syn::File; + + use super::*; + use crate::{ + generator::{config::GeneratorConfig, generate_variant, tests::assert_fragment_eq}, + record::{definition::RecordDefinitionBuilder, type_resolver::HostTypeResolver}, + }; + + #[test] + fn should_generate_empty_data_record() { + let mut builder = RecordDefinitionBuilder::new(HostTypeResolver); + builder.close_record_variant(); + let definition = builder.build(); + + let config = + GeneratorConfig::new([Box::new(DataRecordsGenerator) as Box]); + + let mut scope = Scope::new(); + let mut type_size_assertions = BTreeSet::new(); + + generate_variant( + &definition, + definition.max_type_align(), + definition.variants().next().expect("variant"), + None, + &config, + &mut scope, + &mut type_size_assertions, + ); + + let actual = syn::parse_str::(&scope.to_string()).expect("actual"); + let expected = syn::parse_str::( + r#" +/// Data container for packing/unpacking records. +/// +/// All the fields are named for the safe interoperability between the generated code and the code +/// using it. +pub struct UnpackedRecord0; + +/// Data container for packing/unpacking records without the data to be left uninitialized. +/// +/// All the fields are named for the safe interoperability between the generated code and the code +/// using it. +pub struct UnpackedUninitRecord0; + +/// It only exists to check that the uninitialized data is actually [`Copy`] at run time. +struct UnpackedUninitSafeRecord0; + +impl From for UnpackedUninitSafeRecord0 { + fn from(_from: UnpackedUninitRecord0) -> Self { + Self { } + } +} +"#, + ) + .expect("expected"); + + if actual != expected { + assert_eq!(quote!(#expected).to_string(), quote!(#actual).to_string()); + } + assert_eq!(btreeset![], type_size_assertions); + } + + #[test] + fn should_generate_data_record_with_data() { + let mut builder = RecordDefinitionBuilder::new(HostTypeResolver); + builder.add_datum_allow_uninit::("integer"); + builder.add_datum::("not_copy_integer"); + builder.close_record_variant(); + let definition = builder.build(); + + let config = + GeneratorConfig::new([Box::new(DataRecordsGenerator) as Box]); + + let mut scope = Scope::new(); + let mut type_size_assertions = BTreeSet::new(); + + generate_variant( + &definition, + definition.max_type_align(), + definition.variants().next().expect("variant"), + None, + &config, + &mut scope, + &mut type_size_assertions, + ); + + assert_fragment_eq( + r#" +/// Data container for packing/unpacking records. +/// +/// All the fields are named for the safe interoperability between the generated code and the code +/// using it. +pub struct UnpackedRecord0 { + pub integer: u32, + pub not_copy_integer: u32, +} + +/// Data container for packing/unpacking records without the data to be left uninitialized. +/// +/// All the fields are named for the safe interoperability between the generated code and the code +/// using it. +pub struct UnpackedUninitRecord0 { + pub not_copy_integer: u32, +} + +/// It only exists to check that the uninitialized data is actually [`Copy`] at run time. +struct UnpackedUninitSafeRecord0 { + pub integer: std::marker::PhantomData, + pub not_copy_integer: u32, +} + +impl From for UnpackedUninitSafeRecord0 { + fn from(from: UnpackedUninitRecord0) -> Self { + Self { integer: std::marker::PhantomData, not_copy_integer: from.not_copy_integer } + } +} +"#, + &scope.to_string(), + ); + + assert_eq!( + btreeset![("u32", std::mem::size_of::()),], + type_size_assertions + ); + } + + #[test] + fn should_generate_next_data_record_with_data() { + let mut builder = RecordDefinitionBuilder::new(HostTypeResolver); + let i0 = builder.add_datum_allow_uninit::("integer0"); + let nci0 = builder.add_datum::("not_copy_integer0"); + builder.add_datum_allow_uninit::("boolean1"); + builder.close_record_variant(); + builder.remove_datum(i0); + builder.remove_datum(nci0); + builder.add_datum_allow_uninit::("integer1"); + builder.add_datum::("not_copy_integer1"); + builder.close_record_variant(); + let definition = builder.build(); + + let config = + GeneratorConfig::new([Box::new(DataRecordsGenerator) as Box]); + + let mut scope = Scope::new(); + let mut type_size_assertions = BTreeSet::new(); + + let record0_spec = generate_variant( + &definition, + definition.max_type_align(), + definition.variants().next().expect("variant"), + None, + &config, + &mut scope, + &mut type_size_assertions, + ); + let mut scope = Scope::new(); + type_size_assertions.clear(); + generate_variant( + &definition, + definition.max_type_align(), + definition.variants().nth(1).expect("variant"), + Some(&record0_spec), + &config, + &mut scope, + &mut type_size_assertions, + ); + + assert_fragment_eq( + r#" +/// Data container for packing/unpacking records. +/// +/// All the fields are named for the safe interoperability between the generated code and the code +/// using it. +pub struct UnpackedRecord1 { + pub boolean1: bool, + pub integer1: u32, + pub not_copy_integer1: u32, +} + +/// Data container for packing/unpacking records without the data to be left uninitialized. +/// +/// All the fields are named for the safe interoperability between the generated code and the code +/// using it. +pub struct UnpackedUninitRecord1 { + pub not_copy_integer1: u32, +} + +/// It only exists to check that the uninitialized data is actually [`Copy`] at run time. +struct UnpackedUninitSafeRecord1 { + pub boolean1: std::marker::PhantomData, + pub integer1: std::marker::PhantomData, + pub not_copy_integer1: u32, +} + +impl From for UnpackedUninitSafeRecord1 { + fn from(from: UnpackedUninitRecord1) -> Self { + Self { boolean1: std::marker::PhantomData, integer1: std::marker::PhantomData, not_copy_integer1: from.not_copy_integer1 } + } +} +"#, + &scope.to_string(), + ); + + assert_eq!( + btreeset![("u32", std::mem::size_of::()),], + type_size_assertions + ); + } +} diff --git a/truc/src/generator/fragment/drop_impl.rs b/truc/src/generator/fragment/drop_impl.rs index 41f2838..348bdd5 100644 --- a/truc/src/generator/fragment/drop_impl.rs +++ b/truc/src/generator/fragment/drop_impl.rs @@ -27,3 +27,157 @@ impl FragmentGenerator for DropImplGenerator { } } } + +#[cfg(test)] +#[cfg_attr(coverage_nightly, coverage(off))] +mod tests { + use std::collections::BTreeSet; + + use maplit::btreeset; + use pretty_assertions::assert_eq; + use syn::File; + + use super::*; + use crate::{ + generator::{config::GeneratorConfig, generate_variant, tests::assert_fragment_eq}, + record::{definition::RecordDefinitionBuilder, type_resolver::HostTypeResolver}, + }; + + #[test] + fn should_generate_empty_drop_impl() { + let mut builder = RecordDefinitionBuilder::new(HostTypeResolver); + builder.close_record_variant(); + let definition = builder.build(); + + let config = + GeneratorConfig::new([Box::new(DropImplGenerator) as Box]); + + let mut scope = Scope::new(); + let mut type_size_assertions = BTreeSet::new(); + + generate_variant( + &definition, + definition.max_type_align(), + definition.variants().next().expect("variant"), + None, + &config, + &mut scope, + &mut type_size_assertions, + ); + + let actual = syn::parse_str::(&scope.to_string()).expect("actual"); + let expected = syn::parse_str::( + r#" +impl < const CAP : usize > Drop for CappedRecord0 < CAP > { fn drop (& mut self) { } } +"#, + ) + .expect("expected"); + + if actual != expected { + assert_eq!(quote!(#expected).to_string(), quote!(#actual).to_string()); + } + assert_eq!(btreeset![], type_size_assertions); + } + + #[test] + fn should_generate_drop_impl_with_data() { + let mut builder = RecordDefinitionBuilder::new(HostTypeResolver); + builder.add_datum_allow_uninit::("integer"); + builder.add_datum::("not_copy_integer"); + builder.close_record_variant(); + let definition = builder.build(); + + let config = + GeneratorConfig::new([Box::new(DropImplGenerator) as Box]); + + let mut scope = Scope::new(); + let mut type_size_assertions = BTreeSet::new(); + + generate_variant( + &definition, + definition.max_type_align(), + definition.variants().next().expect("variant"), + None, + &config, + &mut scope, + &mut type_size_assertions, + ); + + assert_fragment_eq( + r#" +impl Drop for CappedRecord0 { + fn drop(&mut self) { + let _integer: u32 = unsafe { self.data.read(0) }; + let _not_copy_integer: u32 = unsafe { self.data.read(4) }; + } +} +"#, + &scope.to_string(), + ); + + assert_eq!( + btreeset![("u32", std::mem::size_of::()),], + type_size_assertions + ); + } + + #[test] + fn should_generate_next_drop_impl_with_data() { + let mut builder = RecordDefinitionBuilder::new(HostTypeResolver); + let i0 = builder.add_datum_allow_uninit::("integer0"); + let nci0 = builder.add_datum::("not_copy_integer0"); + builder.add_datum_allow_uninit::("boolean1"); + builder.close_record_variant(); + builder.remove_datum(i0); + builder.remove_datum(nci0); + builder.add_datum_allow_uninit::("integer1"); + builder.add_datum::("not_copy_integer1"); + builder.close_record_variant(); + let definition = builder.build(); + + let config = + GeneratorConfig::new([Box::new(DropImplGenerator) as Box]); + + let mut scope = Scope::new(); + let mut type_size_assertions = BTreeSet::new(); + + let record0_spec = generate_variant( + &definition, + definition.max_type_align(), + definition.variants().next().expect("variant"), + None, + &config, + &mut scope, + &mut type_size_assertions, + ); + let mut scope = Scope::new(); + type_size_assertions.clear(); + generate_variant( + &definition, + definition.max_type_align(), + definition.variants().nth(1).expect("variant"), + Some(&record0_spec), + &config, + &mut scope, + &mut type_size_assertions, + ); + + assert_fragment_eq( + r#" +impl Drop for CappedRecord1 { + fn drop(&mut self) { + let _boolean1: bool = unsafe { self.data.read(8) }; + let _integer1: u32 = unsafe { self.data.read(0) }; + let _not_copy_integer1: u32 = unsafe { self.data.read(4) }; + } +} +"#, + &scope.to_string(), + ); + + assert_eq!( + btreeset![("u32", std::mem::size_of::()),], + type_size_assertions + ); + } +} diff --git a/truc/src/generator/fragment/mod.rs b/truc/src/generator/fragment/mod.rs index 43b2399..1fb9136 100644 --- a/truc/src/generator/fragment/mod.rs +++ b/truc/src/generator/fragment/mod.rs @@ -23,7 +23,7 @@ pub struct FragmentGeneratorSpecs<'a> { pub prev_record: Option<&'a RecordSpec<'a>>, } -#[derive(Debug)] +#[derive(PartialEq, Eq, Debug)] pub struct RecordSpec<'a> { pub max_type_align: usize, pub variant: &'a RecordVariant, diff --git a/truc/src/generator/mod.rs b/truc/src/generator/mod.rs index 5de9db0..ba0f3b1 100644 --- a/truc/src/generator/mod.rs +++ b/truc/src/generator/mod.rs @@ -5,16 +5,9 @@ use itertools::{Either, EitherOrBoth, Itertools}; use self::{ config::GeneratorConfig, - fragment::{ - data_records::DataRecordsGenerator, drop_impl::DropImplGenerator, - from_previous_record_data_records::FromPreviousRecordDataRecordsGenerator, - from_previous_record_impls::FromPreviousRecordImplsGenerator, - from_unpacked_record_impls::FromUnpackedRecordImplsGenerator, record::RecordGenerator, - record_impl::RecordImplGenerator, FragmentGenerator, FragmentGeneratorSpecs, RecordGeneric, - RecordSpec, - }, + fragment::{FragmentGeneratorSpecs, RecordGeneric, RecordSpec}, }; -use crate::record::definition::{DatumDefinition, RecordDefinition}; +use crate::record::definition::{DatumDefinition, RecordDefinition, RecordVariant}; pub mod config; pub mod fragment; @@ -27,7 +20,7 @@ pub fn generate(definition: &RecordDefinition, config: &GeneratorConfig) -> Stri scope.import("truc_runtime::data", "RecordMaybeUninit"); - for customizer in &config.custom_fragment_generators { + for customizer in &config.fragment_generators { customizer.imports(&mut scope); } @@ -66,75 +59,15 @@ This is to be used in custom allocators."#, let mut type_size_assertions = BTreeSet::new(); for variant in definition.variants() { - let data = variant - .data() - .sorted() - .map(|d| &definition[d]) - .collect::>(); - let (minus_data, plus_data) = if let Some(prev_record_spec) = &prev_record_spec { - prev_record_spec - .variant - .data() - .sorted() - .merge_join_by(&data, |left_id, right| left_id.cmp(&right.id())) - .filter_map(|either| match either { - EitherOrBoth::Left(left_id) => Some(Either::Left(&definition[left_id])), - EitherOrBoth::Right(right) => Some(Either::Right(right)), - EitherOrBoth::Both(_, _) => None, - }) - .partition_map::, Vec<_>, _, _, _>(|e| e) - } else { - (Vec::new(), data.clone()) - }; - let unpacked_uninit_safe_generic = safe_record_generic(&data); - let plus_uninit_safe_generic = safe_record_generic(&plus_data); - let record_spec = RecordSpec { + let record_spec = generate_variant( + definition, max_type_align, variant, - capped_record_name: format!("CappedRecord{}", variant.id()), - record_name: format!("Record{}", variant.id()), - unpacked_record_name: format!("UnpackedRecord{}", variant.id()), - unpacked_uninit_record_name: format!("UnpackedUninitRecord{}", variant.id()), - unpacked_uninit_safe_record_name: format!("UnpackedUninitSafeRecord{}", variant.id()), - unpacked_record_in_name: format!("UnpackedRecordIn{}", variant.id()), - unpacked_uninit_record_in_name: format!("UnpackedUninitRecordIn{}", variant.id()), - unpacked_uninit_safe_record_in_name: format!( - "UnpackedUninitSafeRecordIn{}", - variant.id() - ), - record_and_unpacked_out_name: format!("Record{}AndUnpackedOut", variant.id()), - data, - minus_data, - plus_data, - unpacked_uninit_safe_generic, - plus_uninit_safe_generic, - }; - - for datum in &record_spec.plus_data { - type_size_assertions.insert((datum.type_name(), datum.size())); - } - - let specs = FragmentGeneratorSpecs { - record: &record_spec, - prev_record: prev_record_spec.as_ref(), - }; - - let common_fragment_generators: [Box; 7] = [ - Box::new(DataRecordsGenerator), - Box::new(RecordGenerator), - Box::new(RecordImplGenerator), - Box::new(DropImplGenerator), - Box::new(FromUnpackedRecordImplsGenerator), - Box::new(FromPreviousRecordDataRecordsGenerator), - Box::new(FromPreviousRecordImplsGenerator), - ]; - let fragment_generators = common_fragment_generators - .iter() - .chain(config.custom_fragment_generators.iter()); - - for generator in fragment_generators { - generator.generate(&specs, &mut scope); - } + prev_record_spec.as_ref(), + config, + &mut scope, + &mut type_size_assertions, + ); prev_record_spec = Some(record_spec); } @@ -151,6 +84,74 @@ This is to be used in custom allocators."#, scope.to_string() } +pub(crate) fn generate_variant<'a>( + definition: &'a RecordDefinition, + max_type_align: usize, + variant: &'a RecordVariant, + prev_record_spec: Option<&RecordSpec>, + config: &GeneratorConfig, + scope: &mut Scope, + type_size_assertions: &mut BTreeSet<(&'a str, usize)>, +) -> RecordSpec<'a> { + let data = variant + .data() + .sorted() + .map(|d| &definition[d]) + .collect::>(); + let (minus_data, plus_data) = if let Some(prev_record_spec) = &prev_record_spec { + prev_record_spec + .variant + .data() + .sorted() + .merge_join_by(&data, |left_id, right| left_id.cmp(&right.id())) + .filter_map(|either| match either { + EitherOrBoth::Left(left_id) => Some(Either::Left(&definition[left_id])), + EitherOrBoth::Right(right) => Some(Either::Right(right)), + EitherOrBoth::Both(_, _) => None, + }) + .partition_map::, Vec<_>, _, _, _>(|e| e) + } else { + (Vec::new(), data.clone()) + }; + let unpacked_uninit_safe_generic = safe_record_generic(&data); + let plus_uninit_safe_generic = safe_record_generic(&plus_data); + let record_spec = RecordSpec { + max_type_align, + variant, + capped_record_name: format!("CappedRecord{}", variant.id()), + record_name: format!("Record{}", variant.id()), + unpacked_record_name: format!("UnpackedRecord{}", variant.id()), + unpacked_uninit_record_name: format!("UnpackedUninitRecord{}", variant.id()), + unpacked_uninit_safe_record_name: format!("UnpackedUninitSafeRecord{}", variant.id()), + unpacked_record_in_name: format!("UnpackedRecordIn{}", variant.id()), + unpacked_uninit_record_in_name: format!("UnpackedUninitRecordIn{}", variant.id()), + unpacked_uninit_safe_record_in_name: format!("UnpackedUninitSafeRecordIn{}", variant.id()), + record_and_unpacked_out_name: format!("Record{}AndUnpackedOut", variant.id()), + data, + minus_data, + plus_data, + unpacked_uninit_safe_generic, + plus_uninit_safe_generic, + }; + + for datum in &record_spec.plus_data { + type_size_assertions.insert((datum.type_name(), datum.size())); + } + + let specs = FragmentGeneratorSpecs { + record: &record_spec, + prev_record: prev_record_spec, + }; + + let fragment_generators = config.fragment_generators.iter(); + + for generator in fragment_generators { + generator.generate(&specs, scope); + } + + record_spec +} + struct RecordImplRecordNames<'a> { name: &'a str, unpacked: &'a str, @@ -300,8 +301,11 @@ fn generate_data_out_record( #[cfg(test)] #[cfg_attr(coverage_nightly, coverage(off))] mod tests { + use fragment::FragmentGenerator; + use pretty_assertions::assert_eq; use rand::Rng; use rand_chacha::rand_core::SeedableRng; + use syn::File; use super::*; use crate::{ @@ -312,6 +316,14 @@ mod tests { }, }; + pub(crate) fn assert_fragment_eq(left: &str, right: &str) { + let parsed_left = syn::parse_str::(left).expect("left"); + let parsed_right = syn::parse_str::(right).expect("right"); + if parsed_left != parsed_right { + assert_eq!(left, right.to_string()); + } + } + fn add_one( definition: &mut RecordDefinitionBuilder, rng: &mut rand_chacha::ChaCha8Rng, @@ -384,9 +396,9 @@ mod tests { let def = definition.build(); generate( &def, - &GeneratorConfig { - custom_fragment_generators: vec![Box::new(SerdeImplGenerator)], - }, + &GeneratorConfig::default_with_custom_generators([ + Box::new(SerdeImplGenerator) as Box + ]), ); } } diff --git a/truc/src/record/definition.rs b/truc/src/record/definition.rs index 08f2ed1..c42b038 100644 --- a/truc/src/record/definition.rs +++ b/truc/src/record/definition.rs @@ -14,7 +14,7 @@ impl Debug for DatumId { } } -#[derive(Debug, new)] +#[derive(PartialEq, Eq, Debug, new)] pub struct DatumDefinition { id: DatumId, name: String, @@ -108,7 +108,7 @@ impl DatumDefinitionCollection { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Display, From)] pub struct RecordVariantId(usize); -#[derive(Debug)] +#[derive(PartialEq, Eq, Debug, new)] pub struct RecordVariant { id: RecordVariantId, data: Vec, diff --git a/truc/src/record/type_resolver.rs b/truc/src/record/type_resolver.rs index 116ca0a..78f05ef 100644 --- a/truc/src/record/type_resolver.rs +++ b/truc/src/record/type_resolver.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::record::type_name::{truc_dynamic_type_name, truc_type_name}; -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct TypeInfo { pub name: String, pub size: usize,