diff --git a/bitbybit/src/bitfield/codegen.rs b/bitbybit/src/bitfield/codegen.rs index 6abbd91..07274b5 100644 --- a/bitbybit/src/bitfield/codegen.rs +++ b/bitbybit/src/bitfield/codegen.rs @@ -2,8 +2,8 @@ use crate::bitfield::{ const_name, mask_for_width_and_offset, mask_name, setter_name, with_name, ArrayInfo, BaseDataSize, BitfieldAttributes, CustomType, DefmtVariant, FieldDefinition, BITCOUNT_BOOL, }; -use proc_macro2::{Ident, TokenStream}; -use quote::{quote, TokenStreamExt as _}; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, quote_spanned, TokenStreamExt as _}; use std::str::FromStr; use std::{collections::HashSet, ops::Range}; use syn::{LitInt, Type, Visibility}; @@ -677,6 +677,25 @@ pub fn generate_debug_trait_impl( debug_trait } +pub fn generate_from_trait_impl( + struct_name: &Ident, + base_data_type: &Ident, + span: Span, +) -> TokenStream { + quote_spanned! {span=> + impl ::core::convert::From<#base_data_type> for #struct_name { + fn from(item: #base_data_type) -> #struct_name { + #struct_name::new_with_raw_value(item) + } + } + impl ::core::convert::From<#struct_name> for #base_data_type { + fn from(item: #struct_name) -> #base_data_type { + item.raw_value() + } + } + } +} + pub fn generate_defmt_trait_impl( struct_name: &Ident, bitfield_attrs: &BitfieldAttributes, diff --git a/bitbybit/src/bitfield/mod.rs b/bitbybit/src/bitfield/mod.rs index 34fd54c..e59e877 100644 --- a/bitbybit/src/bitfield/mod.rs +++ b/bitbybit/src/bitfield/mod.rs @@ -9,6 +9,7 @@ use quote::{quote, quote_spanned, ToTokens}; use std::ops::Range; use std::str::FromStr; use syn::meta::ParseNestedMeta; +use syn::spanned::Spanned; use syn::LitStr; use syn::{parse_macro_input, Attribute, Data, DeriveInput, LitInt, Token, Type}; @@ -148,6 +149,7 @@ struct BitfieldAttributes { pub default_val: Option, pub forbid_overlaps: bool, pub debug_trait: bool, + pub from_trait: Option, pub introspect: bool, pub defmt_trait: Option, } @@ -225,6 +227,23 @@ impl BitfieldAttributes { }); return Ok(()); } + if meta.path.is_ident("derive") { + meta.parse_nested_meta(|meta| { + if meta.path.is_ident("Default") { + if self.default_val.is_none() { + let zero = DefaultVal::Lit(LitInt::new("0", meta.path.span())); + self.default_val = Some(zero); + } + } else if meta.path.is_ident("Debug") { + self.debug_trait = true; + } else if meta.path.is_ident("From") { + self.from_trait = Some(meta.path.require_ident()?.clone()); + } else { + return Err(syn::Error::new(meta.path.span(), "unsupported derive")); + } + Ok(()) + })?; + } Ok(()) } } @@ -349,6 +368,11 @@ pub fn bitfield(args: TokenStream, input: TokenStream) -> TokenStream { debug_trait = codegen::generate_debug_trait_impl(struct_name, &field_definitions); } + let mut from_trait = TokenStream2::new(); + if let Some(ref ident) = bitfield_attrs.from_trait { + from_trait = codegen::generate_from_trait_impl(struct_name, base_data_type, ident.span()); + } + let mut defmt_trait = TokenStream2::new(); if bitfield_attrs.defmt_trait.is_some() { defmt_trait = codegen::generate_defmt_trait_impl( @@ -439,6 +463,8 @@ pub fn bitfield(args: TokenStream, input: TokenStream) -> TokenStream { #debug_trait + #from_trait + #defmt_trait #( #new_with_builder_chain )* diff --git a/bitbybit/tests/derive.rs b/bitbybit/tests/derive.rs new file mode 100644 index 0000000..9c097fd --- /dev/null +++ b/bitbybit/tests/derive.rs @@ -0,0 +1,113 @@ +use arbitrary_int::u31; +use bitbybit::bitfield; +use core::ptr::NonNull; + +// native inner type + +#[bitfield(u32, default = 42, derive(Default, Debug, From))] +#[derive(PartialEq)] // NOTE works because `raw_value` exists after #[bitfield(...)] +struct TestNative {} + +fn test_native_int() { + let t: TestNative = Default::default(); + assert_eq!(42, t.raw_value); + + let debug = format!("{t:?}"); + assert!(debug.starts_with("TestNative")); + + let t = TestNative::from(42); + assert_eq!(42, t.raw_value); + + let value = u32::from(t); + assert_eq!(42, value); + + let t: TestNative = value.into(); + assert_eq!(42, t.raw_value); + + let value: u32 = t.into(); + assert_eq!(42, value); +} + +// arbitrary_int inner type + +#[bitfield(u31, default = 42, derive(Default, Debug, From))] +struct TestArbitrary {} + +fn test_arbitrary_int() { + const _42: u31 = u31::from_u32(42); + + let t: TestArbitrary = Default::default(); + assert_eq!(_42, t.raw_value()); + + let debug = format!("{t:?}"); + assert!(debug.starts_with("TestArbitrary")); + + let t = TestArbitrary::from(_42); + assert_eq!(_42, t.raw_value()); + + let value = u31::from(t); + assert_eq!(_42, value); + + let t: TestArbitrary = value.into(); + assert_eq!(_42, t.raw_value()); + + let value: u31 = t.into(); + assert_eq!(_42, value); +} + +// generic conversion to/from raw types + +trait NonNullExt { + unsafe fn read_volatile_le(self) -> T; + unsafe fn read_volatile_be(self) -> T; + unsafe fn write_volatile_le(self, value: T); + unsafe fn write_volatile_be(self, value: T); +} + +impl NonNullExt for NonNull +where + T: From + Into, // NOTE works on any #[bitfield(u32, derive(From))] +{ + unsafe fn read_volatile_le(self) -> T { + assert_eq!(size_of::(), size_of::()); + unsafe { u32::from_le(self.cast::().read_volatile()).into() } + } + unsafe fn read_volatile_be(self) -> T { + assert_eq!(size_of::(), size_of::()); + unsafe { u32::from_be(self.cast::().read_volatile()).into() } + } + unsafe fn write_volatile_le(self, val: T) { + assert_eq!(size_of::(), size_of::()); + unsafe { self.cast::().write_volatile(val.into().to_le()) }; + } + unsafe fn write_volatile_be(self, val: T) { + assert_eq!(size_of::(), size_of::()); + unsafe { self.cast::().write_volatile(val.into().to_be()) }; + } +} + +fn test_generic_conversion() { + let mut le = TestNative::from(1u32.to_le()); + let mut be = TestNative::from(1u32.to_be()); + assert_ne!(le, be); + + let ptr_le = NonNull::from_mut(&mut le); + assert_eq!(1u32.to_le(), le.raw_value()); + assert_eq!(1u32, unsafe { ptr_le.read_volatile_le().raw_value() }); + + let ptr_be = NonNull::from_mut(&mut be); + assert_eq!(1u32.to_be(), be.raw_value()); + assert_eq!(1u32, unsafe { ptr_be.read_volatile_be().raw_value() }); + + unsafe { ptr_le.write_volatile_le(Default::default()) }; + assert_eq!(42u32.to_le(), le.raw_value()); + + unsafe { ptr_be.write_volatile_be(Default::default()) }; + assert_eq!(42u32.to_be(), be.raw_value()); +} + +fn main() { + test_native_int(); + test_arbitrary_int(); + test_generic_conversion(); +} diff --git a/bitbybit/tests/no_compile/invalid_field_range.stderr b/bitbybit/tests/no_compile/invalid_field_range.stderr index 3cddb8c..ef0a365 100644 --- a/bitbybit/tests/no_compile/invalid_field_range.stderr +++ b/bitbybit/tests/no_compile/invalid_field_range.stderr @@ -13,8 +13,7 @@ error[E0599]: no method named `with_c` found for struct `PartialTest` | - = note: the method was found for - - `PartialTest` + = note: the method was found for `PartialTest` help: one of the expressions' fields has a method of the same name | 30 | Test::builder().with_a(u4::new(1)).with_b(u4::new(1)).0.with_c(true).build(); @@ -29,17 +28,16 @@ error[E0599]: no method named `build` found for struct `PartialTest` | - = note: the method was found for - - `PartialTest` + = note: the method was found for `PartialTest` -error[E0599]: no function or associated item named `builder` found for struct `Test2` in the current scope +error[E0599]: no associated function or constant named `builder` found for struct `Test2` in the current scope --> tests/no_compile/invalid_field_range.rs:32:12 | 20 | #[bitfield(u8)] - | --------------- function or associated item `builder` not found for this struct + | --------------- associated function or constant `builder` not found for this struct ... 32 | Test2::builder().with_a(u4::new(1)).with_b(u4::new(1)).build(); - | ^^^^^^^ function or associated item not found in `Test2` + | ^^^^^^^ associated function or constant not found in `Test2` | note: if you're trying to build a new `Test2`, consider using `Test2::new_with_raw_value` which returns `Test2` --> tests/no_compile/invalid_field_range.rs:21:8 diff --git a/bitbybit/tests/no_compile/missing_fields_in_builder.stderr b/bitbybit/tests/no_compile/missing_fields_in_builder.stderr index 8a51c6f..17a6eb4 100644 --- a/bitbybit/tests/no_compile/missing_fields_in_builder.stderr +++ b/bitbybit/tests/no_compile/missing_fields_in_builder.stderr @@ -7,8 +7,7 @@ error[E0599]: no method named `build` found for struct `PartialTest 12 | Test::builder().with_foo(1).build(); | ^^^^^ method not found in `PartialTest` | - = note: the method was found for - - `PartialTest` + = note: the method was found for `PartialTest` error[E0599]: no method named `build` found for struct `PartialTest` in the current scope --> tests/no_compile/missing_fields_in_builder.rs:13:33 @@ -19,8 +18,7 @@ error[E0599]: no method named `build` found for struct `PartialTest 13 | Test::builder().with_bar(1).build(); | ^^^^^ method not found in `PartialTest` | - = note: the method was found for - - `PartialTest` + = note: the method was found for `PartialTest` error[E0599]: no method named `with_bar` found for struct `PartialTest` in the current scope --> tests/no_compile/missing_fields_in_builder.rs:15:45 @@ -31,8 +29,7 @@ error[E0599]: no method named `with_bar` found for struct `PartialTest` | - = note: the method was found for - - `PartialTest` + = note: the method was found for `PartialTest` help: one of the expressions' fields has a method of the same name | 15 | Test::builder().with_bar(1).with_foo(1).0.with_bar(2).build(); diff --git a/bitbybit/tests/no_compile/overlapping_bitfield_bits.stderr b/bitbybit/tests/no_compile/overlapping_bitfield_bits.stderr index d3a1118..faf482b 100644 --- a/bitbybit/tests/no_compile/overlapping_bitfield_bits.stderr +++ b/bitbybit/tests/no_compile/overlapping_bitfield_bits.stderr @@ -84,8 +84,7 @@ error[E0599]: no method named `with_b` found for struct `PartialTestExhaustiveOv | |_________| | | - = note: the method was found for - - `PartialTestExhaustiveOverlap` + = note: the method was found for `PartialTestExhaustiveOverlap` help: one of the expressions' fields has a method of the same name | 48 | .0.with_b(0) diff --git a/bitbybit/tests/no_compile/overlapping_bitfield_u8_fields.stderr b/bitbybit/tests/no_compile/overlapping_bitfield_u8_fields.stderr index 2c3209f..6a4142e 100644 --- a/bitbybit/tests/no_compile/overlapping_bitfield_u8_fields.stderr +++ b/bitbybit/tests/no_compile/overlapping_bitfield_u8_fields.stderr @@ -9,14 +9,14 @@ error: bitfield!: Detected overlap of field `lower_bits` with other field 10 | | } | |_^ -error[E0599]: no function or associated item named `builder` found for struct `TestSometimesUnconstructable` in the current scope +error[E0599]: no associated function or constant named `builder` found for struct `TestSometimesUnconstructable` in the current scope --> tests/no_compile/overlapping_bitfield_u8_fields.rs:46:35 | 12 | #[bitfield(u16)] - | ---------------- function or associated item `builder` not found for this struct + | ---------------- associated function or constant `builder` not found for this struct ... 46 | TestSometimesUnconstructable::builder().with_lower_bits(0).with_bit_upper(u4::new(0)).build(); - | ^^^^^^^ function or associated item not found in `TestSometimesUnconstructable` + | ^^^^^^^ associated function or constant not found in `TestSometimesUnconstructable` | note: if you're trying to build a new `TestSometimesUnconstructable`, consider using `TestSometimesUnconstructable::new_with_raw_value` which returns `TestSometimesUnconstructable` --> tests/no_compile/overlapping_bitfield_u8_fields.rs:13:8 @@ -33,21 +33,20 @@ error[E0599]: no method named `with_a` found for struct `PartialTestAllowed` | - = note: the method was found for - - `PartialTestAllowed` + = note: the method was found for `PartialTestAllowed` help: one of the expressions' fields has a method of the same name | 48 | TestAllowed::builder().with_upper(u4::new(0)).with_lower(u12::new(0)).0.with_a(u2::new(0)).build(); | ++ -error[E0599]: no function or associated item named `builder` found for struct `TestSometimesUnconstructable` in the current scope +error[E0599]: no associated function or constant named `builder` found for struct `TestSometimesUnconstructable` in the current scope --> tests/no_compile/overlapping_bitfield_u8_fields.rs:49:35 | 12 | #[bitfield(u16)] - | ---------------- function or associated item `builder` not found for this struct + | ---------------- associated function or constant `builder` not found for this struct ... 49 | TestSometimesUnconstructable::builder().with_lower_bits(0).with_bit_upper(u4::new(0)).with_middle_bits(0).build(); - | ^^^^^^^ function or associated item not found in `TestSometimesUnconstructable` + | ^^^^^^^ associated function or constant not found in `TestSometimesUnconstructable` | note: if you're trying to build a new `TestSometimesUnconstructable`, consider using `TestSometimesUnconstructable::new_with_raw_value` which returns `TestSometimesUnconstructable` --> tests/no_compile/overlapping_bitfield_u8_fields.rs:13:8 @@ -85,14 +84,14 @@ error[E0599]: no method named `build` found for struct `PartialTestAllowed` - `PartialTestAllowed` -error[E0599]: no function or associated item named `builder` found for struct `TestSometimesUnconstructable` in the current scope +error[E0599]: no associated function or constant named `builder` found for struct `TestSometimesUnconstructable` in the current scope --> tests/no_compile/overlapping_bitfield_u8_fields.rs:54:35 | 12 | #[bitfield(u16)] - | ---------------- function or associated item `builder` not found for this struct + | ---------------- associated function or constant `builder` not found for this struct ... 54 | TestSometimesUnconstructable::builder().with_lower_bits(0).build(); - | ^^^^^^^ function or associated item not found in `TestSometimesUnconstructable` + | ^^^^^^^ associated function or constant not found in `TestSometimesUnconstructable` | note: if you're trying to build a new `TestSometimesUnconstructable`, consider using `TestSometimesUnconstructable::new_with_raw_value` which returns `TestSometimesUnconstructable` --> tests/no_compile/overlapping_bitfield_u8_fields.rs:13:8 @@ -100,14 +99,14 @@ note: if you're trying to build a new `TestSometimesUnconstructable`, consider u 13 | struct TestSometimesUnconstructable { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error[E0599]: no function or associated item named `builder` found for struct `TestSometimesUnconstructable` in the current scope +error[E0599]: no associated function or constant named `builder` found for struct `TestSometimesUnconstructable` in the current scope --> tests/no_compile/overlapping_bitfield_u8_fields.rs:55:35 | 12 | #[bitfield(u16)] - | ---------------- function or associated item `builder` not found for this struct + | ---------------- associated function or constant `builder` not found for this struct ... 55 | TestSometimesUnconstructable::builder().with_middle_bits(0).build(); - | ^^^^^^^ function or associated item not found in `TestSometimesUnconstructable` + | ^^^^^^^ associated function or constant not found in `TestSometimesUnconstructable` | note: if you're trying to build a new `TestSometimesUnconstructable`, consider using `TestSometimesUnconstructable::new_with_raw_value` which returns `TestSometimesUnconstructable` --> tests/no_compile/overlapping_bitfield_u8_fields.rs:13:8 @@ -115,14 +114,14 @@ note: if you're trying to build a new `TestSometimesUnconstructable`, consider u 13 | struct TestSometimesUnconstructable { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error[E0599]: no function or associated item named `builder` found for struct `TestUnconstructable` in the current scope +error[E0599]: no associated function or constant named `builder` found for struct `TestUnconstructable` in the current scope --> tests/no_compile/overlapping_bitfield_u8_fields.rs:56:26 | 21 | #[bitfield(u16)] - | ---------------- function or associated item `builder` not found for this struct + | ---------------- associated function or constant `builder` not found for this struct ... 56 | TestUnconstructable::builder().with_bits(0).build(); - | ^^^^^^^ function or associated item not found in `TestUnconstructable` + | ^^^^^^^ associated function or constant not found in `TestUnconstructable` | note: if you're trying to build a new `TestUnconstructable`, consider using `TestUnconstructable::new_with_raw_value` which returns `TestUnconstructable` --> tests/no_compile/overlapping_bitfield_u8_fields.rs:22:8 diff --git a/bitbybit/tests/tests.rs b/bitbybit/tests/tests.rs index 1c413cc..eff1155 100644 --- a/bitbybit/tests/tests.rs +++ b/bitbybit/tests/tests.rs @@ -7,6 +7,7 @@ fn all_tests() { t.pass("tests/with_fields.rs"); t.pass("tests/correct.rs"); t.pass("tests/builder_value_field.rs"); + t.pass("tests/derive.rs"); t.compile_fail("tests/no_compile/exhaustive_bitenum.rs"); t.compile_fail("tests/no_compile/non_exhaustive_bitenum.rs");