truc 0.4.0

Rust code generator for safe, fixed size, evolving records.
Documentation
use codegen::Scope;

use super::{FragmentGenerator, FragmentGeneratorSpecs};
use crate::generator::{fragment::RecordSpec, CAP, CAP_GENERIC};

pub struct FromUnnamedFieldsImplsGenerator;

impl FromUnnamedFieldsImplsGenerator {
    fn generate_from_unnamed_fields_impl(
        record_spec: &RecordSpec,
        uninit: bool,
        scope: &mut Scope,
    ) {
        if uninit
            && !record_spec
                .data
                .iter()
                .any(|datum| datum.details().allow_uninit())
        {
            return;
        }

        let (from_args, from_types) = if !uninit {
            let iter = record_spec.data.iter();
            (
                iter.clone()
                    .map(|datum| datum.name())
                    .collect::<Vec<&str>>(),
                iter.map(|datum| datum.details().type_name())
                    .collect::<Vec<&str>>(),
            )
        } else {
            let iter = record_spec
                .data
                .iter()
                .filter(|datum| !datum.details().allow_uninit());

            (
                iter.clone()
                    .map(|datum| datum.name())
                    .collect::<Vec<&str>>(),
                iter.map(|datum| datum.details().type_name())
                    .collect::<Vec<&str>>(),
            )
        };

        let ty = match from_types.len() {
            0 => "()".to_owned(),
            1 => from_types[0].to_owned(),
            _ => format!("({})", from_types.join(", ")),
        };

        let from_impl = scope
            .new_impl(&record_spec.capped_record_name)
            .generic(CAP_GENERIC)
            .target_generic(CAP)
            .impl_trait(format!("From<{}>", ty));

        let from_fn = from_impl
            .new_fn("from")
            .arg(
                &match from_args.len() {
                    0 => "()".to_owned(),
                    1 => from_args[0].to_owned(),
                    _ => format!("({})", from_args.join(", ")),
                },
                ty,
            )
            .ret("Self");
        from_fn.line(format!(
            "Self::{}({})",
            if !uninit {
                "new_unnamed"
            } else {
                "new_uninit_unnamed"
            },
            from_args.join(", "),
        ));
    }
}

impl FragmentGenerator for FromUnnamedFieldsImplsGenerator {
    fn generate(&self, specs: &FragmentGeneratorSpecs, scope: &mut Scope) {
        let record_spec = &specs.record;

        Self::generate_from_unnamed_fields_impl(record_spec, false, scope);

        Self::generate_from_unnamed_fields_impl(record_spec, true, scope);
    }
}

#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
    use std::collections::BTreeSet;

    use maplit::btreeset;
    use pretty_assertions::assert_eq;

    use super::*;
    use crate::{
        generator::{config::GeneratorConfig, generate_variant, tests::assert_fragment_eq},
        record::{
            definition::builder::native::NativeRecordDefinitionBuilder,
            type_resolver::HostTypeResolver,
        },
    };

    #[test]
    fn should_generate_empty_impls() {
        let mut builder = NativeRecordDefinitionBuilder::new(HostTypeResolver);
        builder.close_record_variant();
        let definition = builder.build();

        let config = GeneratorConfig::new([
            Box::new(FromUnnamedFieldsImplsGenerator) as Box<dyn FragmentGenerator>
        ]);

        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<const CAP: usize> From<()> for CappedRecord0<CAP> {
    fn from((): ()) -> Self {
        Self::new_unnamed()
    }
}
"#,
            &scope.to_string(),
        );

        assert_eq!(btreeset![], type_size_assertions);
    }

    #[test]
    fn should_generate_impls_with_data() {
        let mut builder = NativeRecordDefinitionBuilder::new(HostTypeResolver);
        builder.add_datum_allow_uninit::<u32, _>("integer").unwrap();
        builder.add_datum::<u32, _>("not_copy_integer").unwrap();
        builder.close_record_variant();
        let definition = builder.build();

        let config = GeneratorConfig::new([
            Box::new(FromUnnamedFieldsImplsGenerator) as Box<dyn FragmentGenerator>
        ]);

        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<const CAP: usize> From<(u32, u32)> for CappedRecord0<CAP> {
    fn from((integer, not_copy_integer): (u32, u32)) -> Self {
        Self::new_unnamed(integer, not_copy_integer)
    }
}

impl<const CAP: usize> From<u32> for CappedRecord0<CAP> {
    fn from(not_copy_integer: u32) -> Self {
        Self::new_uninit_unnamed(not_copy_integer)
    }
}
"#,
            &scope.to_string(),
        );

        assert_eq!(
            btreeset![("u32", std::mem::size_of::<u32>())],
            type_size_assertions
        );
    }

    #[test]
    fn should_generate_next_impls_with_data() {
        let mut builder = NativeRecordDefinitionBuilder::new(HostTypeResolver);
        let i0 = builder
            .add_datum_allow_uninit::<u32, _>("integer0")
            .unwrap();
        let nci0 = builder.add_datum::<u32, _>("not_copy_integer0").unwrap();
        builder
            .add_datum_allow_uninit::<bool, _>("boolean1")
            .unwrap();
        builder.close_record_variant();
        builder.remove_datum(i0).unwrap();
        builder.remove_datum(nci0).unwrap();
        builder
            .add_datum_allow_uninit::<u32, _>("integer1")
            .unwrap();
        builder.add_datum::<u32, _>("not_copy_integer1").unwrap();
        builder.close_record_variant();
        let definition = builder.build();

        let config = GeneratorConfig::new([
            Box::new(FromUnnamedFieldsImplsGenerator) as Box<dyn FragmentGenerator>
        ]);

        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<const CAP: usize> From<(bool, u32, u32)> for CappedRecord1<CAP> {
    fn from((boolean1, integer1, not_copy_integer1): (bool, u32, u32)) -> Self {
        Self::new_unnamed(boolean1, integer1, not_copy_integer1)
    }
}

impl<const CAP: usize> From<u32> for CappedRecord1<CAP> {
    fn from(not_copy_integer1: u32) -> Self {
        Self::new_uninit_unnamed(not_copy_integer1)
    }
}
"#,
            &scope.to_string(),
        );

        assert_eq!(
            btreeset![("u32", std::mem::size_of::<u32>())],
            type_size_assertions
        );
    }
}