Skip to content

Commit

Permalink
Custom fragment generator for clone impls
Browse files Browse the repository at this point in the history
  • Loading branch information
arnodb committed Nov 23, 2024
1 parent 532d5b2 commit 6e1fab8
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 2 deletions.
1 change: 1 addition & 0 deletions scripts/msrv_pin_dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ set -x
cargo update -p ppv-lite86 --precise 0.2.17
cargo update -p derive_more --precise 0.99.17
cargo update -p pretty_assertions --precise 1.3.0
cargo update -p libc --precise 0.2.163

8 changes: 8 additions & 0 deletions scripts/switch_rust_toolchain.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
set -eu

RUST_TOOLCHAIN=
PIN_FOR_MSRV=

SCRIPTS_DIR=$(dirname "$0")
BASENAME=$(basename "$0")
Expand Down Expand Up @@ -34,6 +35,7 @@ while [ $# -gt 0 ]; do
;;
-m|--msrv)
RUST_TOOLCHAIN="$MSRV"
PIN_FOR_MSRV=1
shift
;;
-s|--stable)
Expand Down Expand Up @@ -79,4 +81,10 @@ then
else
echo "Switching $RUST_TOOLCHAIN_FILE to \"$RUST_TOOLCHAIN\""
echo "$RUST_TOOLCHAIN" >| "$RUST_TOOLCHAIN_FILE"

if [ x"$PIN_FOR_MSRV" = x1 ]
then
echo "Pinning dependencies to make build successful..."
"$SCRIPTS_DIR/msrv_pin_dependencies.sh"
fi
fi
238 changes: 238 additions & 0 deletions truc/src/generator/fragment/clone.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
//! Clone support.
use codegen::Scope;

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

/// Use this generator in [GeneratorConfig](crate::generator::config::GeneratorConfig) in order to
/// generate `Clone` implementations.
pub struct CloneImplGenerator;

impl CloneImplGenerator {
fn generate_clone_impl(record_spec: &RecordSpec, scope: &mut Scope) {
let clone_impl = scope
.new_impl(&record_spec.capped_record_name)
.generic(CAP_GENERIC)
.target_generic(CAP)
.impl_trait("Clone");

{
let clone_fn = clone_impl.new_fn("clone").arg_ref_self().ret("Self");
for datum in &record_spec.data {
clone_fn.line(format!(
"let {} = self.{}().clone();",
datum.name(),
datum.name()
));
}
clone_fn.line(format!(
"Self::from({} {{",
record_spec.unpacked_record_name
));
for datum in &record_spec.data {
clone_fn.line(format!(" {},", datum.name()));
}
clone_fn.line("})");
}

{
let clone_from_fn = clone_impl
.new_fn("clone_from")
.arg_mut_self()
.arg("source", "&Self");
for datum in &record_spec.data {
clone_from_fn.line(format!(
"self.{}_mut().clone_from(source.{}());",
datum.name(),
datum.name()
));
}
}
}
}

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

Self::generate_clone_impl(record_spec, 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::RecordDefinitionBuilder, type_resolver::HostTypeResolver},
};

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

let config =
GeneratorConfig::new([Box::new(CloneImplGenerator) 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> Clone for CappedRecord0<CAP> {
fn clone(&self) -> Self {
Self::from(UnpackedRecord0 {
})
}
fn clone_from(&mut self, source: &Self) {
}
}
"#,
&scope.to_string(),
);

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

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

let config =
GeneratorConfig::new([Box::new(CloneImplGenerator) 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> Clone for CappedRecord0<CAP> {
fn clone(&self) -> Self {
let integer = self.integer().clone();
let not_copy_integer = self.not_copy_integer().clone();
Self::from(UnpackedRecord0 {
integer,
not_copy_integer,
})
}
fn clone_from(&mut self, source: &Self) {
self.integer_mut().clone_from(source.integer());
self.not_copy_integer_mut().clone_from(source.not_copy_integer());
}
}
"#,
&scope.to_string(),
);

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

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

let config =
GeneratorConfig::new([Box::new(CloneImplGenerator) 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> Clone for CappedRecord1<CAP> {
fn clone(&self) -> Self {
let boolean1 = self.boolean1().clone();
let integer1 = self.integer1().clone();
let not_copy_integer1 = self.not_copy_integer1().clone();
Self::from(UnpackedRecord1 {
boolean1,
integer1,
not_copy_integer1,
})
}
fn clone_from(&mut self, source: &Self) {
self.boolean1_mut().clone_from(source.boolean1());
self.integer1_mut().clone_from(source.integer1());
self.not_copy_integer1_mut().clone_from(source.not_copy_integer1());
}
}
"#,
&scope.to_string(),
);

assert_eq!(
btreeset![("u32", std::mem::size_of::<u32>())],
type_size_assertions
);
}
}
1 change: 1 addition & 0 deletions truc/src/generator/fragment/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use codegen::Scope;

use crate::record::definition::{DatumDefinition, RecordVariant};

pub mod clone;
pub(crate) mod data_records;
pub(crate) mod drop_impl;
pub(crate) mod from_previous_record_data_records;
Expand Down
5 changes: 3 additions & 2 deletions truc/src/generator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ mod tests {

use super::*;
use crate::{
generator::fragment::serde::SerdeImplGenerator,
generator::fragment::{clone::CloneImplGenerator, serde::SerdeImplGenerator},
record::{
definition::{DatumDefinitionOverride, RecordDefinitionBuilder},
type_resolver::{StaticTypeResolver, TypeResolver},
Expand Down Expand Up @@ -400,7 +400,8 @@ mod tests {
generate(
&def,
&GeneratorConfig::default_with_custom_generators([
Box::new(SerdeImplGenerator) as Box<dyn FragmentGenerator>
Box::new(CloneImplGenerator) as Box<dyn FragmentGenerator>,
Box::new(SerdeImplGenerator) as Box<dyn FragmentGenerator>,
]),
);
}
Expand Down

0 comments on commit 6e1fab8

Please sign in to comment.