Skip to content

Commit

Permalink
Make #[derive(Copy, Clone)] optional. (#59)
Browse files Browse the repository at this point in the history
This is useful for bit-packed pointer types to owned data. During a
clone, pointers to owned data need to be unpacked and their targets
cloned as well.
  • Loading branch information
kevinhartman authored Dec 18, 2024
1 parent c37d566 commit 6a0c625
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 6 deletions.
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,31 @@ assert_eq!(my_be_bitfield.into_bits().to_be_bytes(), [0x23, 0x41]);

## Automatic Trait Implementations

Besides implementing `Clone` and `Copy`, this macro automatically creates a suitable `fmt::Debug` and `Default` implementations similar to the ones created for normal structs by `#[derive(Debug, Default)]`.
### `Clone`, `Copy`
By default, this macro derives `Clone` and `Copy`.
You can disable this with the extra `clone` argument if the semantics of cloning your type require it (e.g. the type holds a pointer to owned data that must also be cloned).
In this case, you can provide your own implementations for `Clone` and `Copy`.

```rust
use bitfield_struct::bitfield;

#[bitfield(u64, clone = false)]
struct CustomClone {
data: u64
}

impl Clone for CustomClone {
fn clone(&self) -> Self {
Self::new().with_data(self.data())
}
}

// optionally:
impl Copy for CustomClone {}
```

### `fmt::Debug`, `Default`
By default, it also generates suitable `fmt::Debug` and `Default` implementations similar to the ones created for normal structs by `#[derive(Debug, Default)]`.
You can disable this with the extra `debug` and `default` arguments.

```rust
Expand Down Expand Up @@ -411,9 +435,9 @@ struct DefmtExample {
}
```

### Conditionally Enable `new`/`Debug`/`Default`/`defmt::Format`
### Conditionally Enable `new`/`Clone`/`Debug`/`Default`/`defmt::Format`

Instead of booleans, you can specify `cfg(...)` attributes for `new`, `debug`, `default` and `defmt`:
Instead of booleans, you can specify `cfg(...)` attributes for `new`, `clone`, `debug`, `default` and `defmt`:

```rust
use bitfield_struct::bitfield;
Expand Down
18 changes: 15 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ fn s_err(span: proc_macro2::Span, msg: impl fmt::Display) -> syn::Error {
/// - `from` to specify a conversion function from repr to the bitfield's integer type
/// - `into` to specify a conversion function from the bitfield's integer type to repr
/// - `new` to disable the `new` function generation
/// - `clone` to disable the `Clone` trait generation
/// - `debug` to disable the `Debug` trait generation
/// - `defmt` to enable the `defmt::Format` trait generation.
/// - `defmt` to enable the `defmt::Format` trait generation
/// - `default` to disable the `Default` trait generation
/// - `order` to specify the bit order (Lsb, Msb)
/// - `conversion` to disable the generation of `into_bits` and `from_bits`
///
/// > For `new`, `debug`, `defmt` or `default`, you can either use booleans
/// > For `new`, `clone`, `debug`, `defmt` or `default`, you can either use booleans
/// > (`#[bitfield(u8, debug = false)]`) or cfg attributes
/// > (`#[bitfield(u8, debug = cfg(test))]`) to enable/disable them.
///
Expand All @@ -61,6 +62,7 @@ fn bitfield_inner(args: TokenStream, input: TokenStream) -> syn::Result<TokenStr
from,
bits,
new,
clone,
debug,
defmt,
default,
Expand All @@ -72,6 +74,11 @@ fn bitfield_inner(args: TokenStream, input: TokenStream) -> syn::Result<TokenStr
let name = input.ident;
let vis = input.vis;
let attrs: TokenStream = input.attrs.iter().map(ToTokens::to_token_stream).collect();
let derive = match clone {
Enable::No => None,
Enable::Yes => Some(quote! { #[derive(Copy, Clone)] }),
Enable::Cfg(cfg) => Some(quote! { #[cfg_attr(#cfg, derive(Copy, Clone))] }),
};

let syn::Fields::Named(fields) = input.fields else {
return Err(s_err(span, "only named fields are supported"));
Expand Down Expand Up @@ -166,7 +173,7 @@ fn bitfield_inner(args: TokenStream, input: TokenStream) -> syn::Result<TokenStr

Ok(quote! {
#attrs
#[derive(Copy, Clone)]
#derive
#[repr(transparent)]
#vis struct #name(#repr);

Expand Down Expand Up @@ -843,6 +850,7 @@ struct Params {
from: Option<syn::Path>,
bits: usize,
new: Enable,
clone: Enable,
debug: Enable,
defmt: Enable,
default: Enable,
Expand All @@ -867,6 +875,7 @@ impl Parse for Params {
from: None,
bits,
new: Enable::Yes,
clone: Enable::Yes,
debug: Enable::Yes,
defmt: Enable::No,
default: Enable::Yes,
Expand Down Expand Up @@ -897,6 +906,9 @@ impl Parse for Params {
"new" => {
ret.new = input.parse()?;
}
"clone" => {
ret.clone = input.parse()?;
}
"default" => {
ret.default = input.parse()?;
}
Expand Down
36 changes: 36 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,42 @@ fn attrs() {
assert_eq!(full, Full::new().with_data(u64::MAX));
}

#[test]
fn clone() {
/// We have a custom clone implementation -> opt out
#[bitfield(u64, clone = false)]
struct Full {
data: u64,
}

impl Clone for Full {
fn clone(&self) -> Self {
Self::new().with_data(self.data())
}
}

impl Copy for Full {}

let full = Full::new().with_data(123);
let full_copy = full;
assert_eq!(full.data(), full_copy.data());
assert_eq!(full.data(), full.clone().data());
}

#[test]
fn clone_cfg() {
/// We opt in for clone/copy implementation, via a cfg.
#[bitfield(u64, clone = cfg(test))]
struct Full {
data: u64,
}

let full = Full::new().with_data(123);
let full_copy = full;
assert_eq!(full.data(), full_copy.data());
assert_eq!(full.data(), full.clone().data());
}

#[test]
fn debug() {
/// We have a custom debug implementation -> opt out
Expand Down

0 comments on commit 6a0c625

Please sign in to comment.