diff --git a/.github/workflows/no-std.yml b/.github/workflows/no-std.yml index a86bc9f..ad9b6cf 100644 --- a/.github/workflows/no-std.yml +++ b/.github/workflows/no-std.yml @@ -25,4 +25,55 @@ jobs: - run: | cd nostd-app cargo add bitfields --path ../bitfields --package nostd-app - - run: cargo build --target thumbv7em-none-eabihf \ No newline at end of file + cargo add buddy_system_allocator --package nostd-app + - run: | + cat > nostd-app/src/main.rs << 'EOF' + #![no_std] + #![no_main] + + use bitfields::{bitfield, bitflag}; + + // pick a panicking behavior + use panic_halt as _; // you can put a breakpoint on `rust_begin_unwind` to catch panics + // use panic_abort as _; // requires nightly + // use panic_itm as _; // logs messages over ITM; requires ITM support + // use panic_semihosting as _; // logs messages to the host stderr; requires a debugger + + use cortex_m_rt::entry; + use buddy_system_allocator::LockedHeap; + extern crate alloc; + + #[global_allocator] + static HEAP_ALLOCATOR: LockedHeap<32> = LockedHeap::empty(); + + #[entry] + fn main() -> ! { + loop { + #[bitfield(u32)] + pub struct Bitfield { + a: u8, + b: u8, + c: u8, + d: u8, + } + + #[bitfield([u8; 4], array_heap_no_std = true)] + pub struct ArrayHeapBitfield { + a: u8, + b: u8, + c: u8, + d: u8, + } + + #[bitflag(u8)] + #[derive(Debug, PartialEq)] + enum Flags { + #[base] + Unknown = 0, + A = 1, + B = 2, + } + } + } + EOF + - run: cargo build --target thumbv7em-none-eabihf diff --git a/Cargo.lock b/Cargo.lock index 1cb8220..89a4290 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,6 +7,7 @@ name = "bitfields" version = "2.0.8" dependencies = [ "bitfields-impl", + "dummy-alloc", "trybuild", ] @@ -57,6 +58,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "dummy-alloc" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadb95bab5cf4a3b4d608255efdb2493a7755ba6e602bd88c95a6912609d2550" + [[package]] name = "equivalent" version = "1.0.1" diff --git a/README.md b/README.md index 298e430..7dd13e1 100644 --- a/README.md +++ b/README.md @@ -23,30 +23,42 @@ systems (e.g. embedded development or emulators). ### 🛠️ Core Features -- **Custom & Nested Bitfield Types**: Supports user-defined types, nested bitfields, and enum-based bitflags. +- **Custom & Nested Bitfield Types**: Supports user-defined types, nested + bitfields, and enum-based bitflags. - **Bitflags**: Easily define bitflags using enums. -- **Array Support**: Supports array-backed bitfields and array fields, ideal for representing data exceeding 128 bits. -- **Endianness & Signed Data**: Built-in support for little/big endian conversions and 2's complement signed fields. +- **Array Support**: Supports array-backed bitfields and array fields, ideal for + representing data exceeding 128 bits. +- **Endianness & Signed Data**: Built-in support for little/big endian + conversions and 2's complement signed fields. - **Flexible Fields**: Field defaults and ignored fields. ### ⚡ Performance -- **Safe & Lightweight**: No `unsafe`, zero allocations, no runtime dependencies, and constant memory usage for primitive bitfields. -- **`no_std` Compatible**: Works in embedded and standard library-free environments. -- **Efficient**: Generates efficient, `const`-friendly code comparable to handwritten implementations. +- **Safe & Lightweight**: No `unsafe`, zero allocations, no runtime + dependencies, and constant memory usage for primitive bitfields. +- **`no_std` Compatible**: Works in embedded and standard library-free + environments. +- **Efficient**: Generates efficient, `const`-friendly code comparable to + handwritten implementations. ### 🛡️ Safety & Reliability -- **Compile-Time Validation**: Type, size, and bounds checks are validated at compile time. -- **Checked Accessors**: Optional checked accesses help catch out-of-bounds errors. +- **Compile-Time Validation**: Type, size, and bounds checks are validated at + compile time. +- **Checked Accessors**: Optional checked accesses help catch out-of-bounds + errors. - **Well Tested**: Comprehensive testing with over 90% code coverage. ### 💻 Developer Experience -- **Generated Bit Operations**: Automatically generates operations such as `get`, `set`, and `invert`. -- **Flexible Code Generation**: Configurable generation for different schema layouts and use cases. -- **Editor-Friendly**: Preserves syntax highlighting on annotated structs and enums. -- **Documentation & Examples**: Comprehensive documentation that compiles and examples. +- **Generated Bit Operations**: Automatically generates operations such as + `get`, `set`, and `invert`. +- **Flexible Code Generation**: Configurable generation for different schema + layouts and use cases. +- **Editor-Friendly**: Preserves syntax highlighting on annotated structs and + enums. +- **Documentation & Examples**: Comprehensive documentation that compiles and + examples. ## 📖 Table of Contents @@ -254,10 +266,11 @@ bitfields larger than `u128`. Just like primitive bitfields, the bitfield field bits must add up to the exact number of bits of the bitfield type. If you have an array backed bitfield that may overflow the stack, you can pass -the optional argument `#[bitfield(array_heap = true)]` to the bitfield, which -will box the array on the heap instead of the stack. Keep in mind that you -**lose constant memory, zero-allocation, and no_std guarantees when using heap -array bitfields**. +the optional argument `#[bitfield(array_heap_std = true)]` or +`#[bitfield(array_heap_no_std = true)]` (depending on your environment) to the +bitfield, which will box the array on the heap instead of the stack. Keep in +mind that you **lose constant memory and zero-allocation when using heap array +bitfields**. ```rust use bitfields::bitfield; @@ -268,8 +281,9 @@ struct ArrayBitfield { b: u8, } -#[bitfield([u8; 96], array_heap = true -)] /// Allocated on the heap, 768 bits (96 bytes). +#[bitfield( + [u8; 96], array_heap_std = true) +] /// Allocated on the heap, 768 bits (96 bytes). struct HeapArrayBitfield { a: u128, b: u128, @@ -374,7 +388,7 @@ a `u8` field will occupy 8 bits. Fields can have a default value, which must fit in the field type or the specified bits of the field. A default value must be a const variable or -a const function. Just be aware that const function and variable +a const function. Just be aware that const function and variable defaults lose their compile-time field size checks, so it's up to you to make sure they fit. @@ -420,7 +434,8 @@ fn main() { A bitfield can have signed (`i8`, `i16`, `i32`, `i64`, `i128`) types. Signed types are treated as 2's complement data types, where the most significant bit -representing the sign bit. For example, if you had a field with 5 bits, the value +representing the sign bit. For example, if you had a field with 5 bits, the +value range would be `-16` to `15`. The more bits you include, the larger the range! ```rust @@ -525,7 +540,7 @@ field. There are four access levels: fields. But use a **reserved field** when you want to pad/fill bits that have no user-facing identity (hardware reserved bits, alignment gaps). Use **`access = na`** when the field is a real, named field that you want to - completely lock down from the API while still keeping it distinguishable + completely lock down from the API while still keeping it distinguishable by name. Later on, if you want to expose the field, you can just change the access level without having to change the field name. @@ -783,7 +798,7 @@ struct ReservedBitfield { /// Fills the middle bits of the u16. #[bits(4, default = 0xF)] __: u8, - + /// Fils the end bits of the u16. #[bits(4, default = 0xF)] __: u8, @@ -796,7 +811,7 @@ fn main() { // bitfield.set__reserved(0xFF); // Compile error, reserved fields are inaccessible. assert_eq!(bitfield.into_bits(), 0xFF00); // All fields exposed when converted // to bits. - + let reserved_bitfield = ReservedBitfield::new(); assert_eq!(reserved_bitfield.a(), 0); // assert_eq!(reserved_bitfield.__(), 0xFF0); // Compile error, reserved inaccessible. @@ -816,8 +831,10 @@ the `#[bitflag]` attribute which generates `from_bits` and `into_bits` for enums automatically. Bitflags only supports unsigned types (`u8`, `u16`, `u32`, `u64`, `u128`) and -the one of the variants must be annotated with `#[base]` or `#[default]` which represents the -base value of the bitflag. If `#[base]` and `#[default]` are both present, `#[base]` takes precedence. +the one of the variants must be annotated with `#[base]` or `#[default]` which +represents the +base value of the bitflag. If `#[base]` and `#[default]` are both present, +`#[base]` takes precedence. ```rust use bitfields::bitfield; @@ -895,7 +912,8 @@ fn main() { #### Bitflag Configuration -Bitflags can be configured with arguments passed to the `#[bitflag(...)]` attribute (the first argument is always the backing unsigned integer type): +Bitflags can be configured with arguments passed to the `#[bitflag(...)]` +attribute (the first argument is always the backing unsigned integer type): | Argument | Values | Default | Description | |------------------|-----------------------------------|----------|------------------------------------------------------------------------------------------------------| @@ -1289,7 +1307,8 @@ fn main() { Sometimes the outside world is outside our control, like the endianness of how systems export data. Luckily, the endianness of the bitfield conversions can be controlled by specifying the `#[bitfield(from_endian = N, into_endian = N)]` -args. The possible endianness options are `little` or `big`. By default, the endianness +args. The possible endianness options are `little` or `big`. By default, the +endianness of both is `big`. This arg controls the endianness of the `from`, `into`, and `From` trait @@ -2166,34 +2185,35 @@ use bitfields::bitfield; set_get_bit_ops = true, invert_bit_ops = true, toggle_bit_ops = true, - array_heap = false, + array_heap_std = false, )] struct Example { a: u32, } ``` -| Argument | Values | Default | Description | -|-------------------|-------------------------------------------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `` | `u8`, `u16`, `u32`, `u64`, `u128`, or `[u8; N]` | Required | The storage used by the generated bitfield. Primitive backing types support bitfields up to 128 bits. `[u8; N]` creates an array-backed bitfield for larger layouts. Field bit widths, excluding ignored fields, must add up exactly to the backing storage size. | -| `order` | `lsb`, `msb` | `lsb` | Controls how struct fields are assigned to bit offsets. `lsb` assigns the first non-ignored field to the least-significant bits. `msb` assigns the first non-ignored field to the most-significant bits. | -| `from_endian` | `big`, `little` | `big` | Default endian used by `from_bits`, `from_bytes`, `from_slice`, and `From for Bitfield`. Explicit helpers such as `from_le_bits` and `from_be_bytes` ignore this setting. | -| `into_endian` | `big`, `little` | `big` | Default endian used by `into_bits`, `into_bytes`, `into_slice`, and `From for Backing`. Explicit helpers such as `into_le_bits` and `into_be_bytes` ignore this setting. | -| `write_endian` | `big`, `little` | `big` | Default endian used by whole-bitfield write helpers such as `write_bits` and `write_bytes`. Explicit helpers such as `write_le_bits` and `write_be_bytes` ignore this setting. | -| `new` | `true`, `false` | `true` | Generates `new()` and `new_without_defaults()` constructors. Other generated features that need construction logic, such as `Default` and the builder, still inline equivalent initialization logic when this is disabled. | -| `from_into_bits` | `true`, `false` | `true` | Generates backing-data conversion functions. Primitive bitfields get `from_bits`, `from_bits_with_defaults`, endian-specific `from_*_bits` helpers, `into_bits`, and endian-specific `into_*_bits` helpers. Array-backed bitfields get the corresponding `bytes` and `slice` APIs. | -| `from_traits` | `true`, `false` | `true` | Generates `From for Bitfield` and `From for Backing`. These conversions use `from_endian` and `into_endian`. | -| `default` | `true`, `false` | `true` | Generates `Default` for the bitfield. The default value is equivalent to `new()`: zero-initialized storage with field defaults applied. | -| `debug` | `true`, `false` | `true` | Generates `core::fmt::Debug` for the bitfield. The implementation prints readable fields and their values. | -| `copy` | `true`, `false` | `true` | Derives `Copy` and `Clone` for primitive and stack array-backed bitfields. Heap array-backed bitfields derive `Clone` only because `Box<[u8; N]>` is not `Copy`. | -| `builder` | `true`, `false` | `true` | Generates the `Builder` type, `new`, `new_without_defaults`, `with_`, `checked_with_`, and `build`. Reserved fields do not get builder setters. | -| `bit_ops` | `true`, `false` | `true` | Master switch for bit operation groups. When `false`, all bit operation groups are disabled unless a specific bit operation group is explicitly set to `true`. | -| `write_bit_ops` | `true`, `false` | `true` | Generates whole-bitfield write helpers such as `write_bits`, `write_bits_with_defaults`, `write_le_bits`, `write_be_bits`, and `write_defaults` for primitive bitfields, or the corresponding `bytes` helpers for array-backed bitfields. | -| `clear_bit_ops` | `true`, `false` | `true` | Generates whole-bitfield clear helpers such as `clear_bits` / `clear_bytes`, `clear_bits_with_defaults` / `clear_bytes_with_defaults`, plus per-field helpers like `clear_()` and `clear__to_default()`. | -| `set_get_bit_ops` | `true`, `false` | `true` | Generates individual bit helpers (`get_bit`, `checked_get_bit`, `set_bit`, `checked_set_bit`) and range helpers (`get_bits_range` / `set_bits_range` for primitive bitfields, `get_bytes_range` / `set_bytes_range` for array-backed bitfields, plus checked variants). | -| `invert_bit_ops` | `true`, `false` | `true` | Generates inversion helpers such as `invert_bits` / `invert_bytes`, per-field `invert_()`, and readable-field `_inverted()` getters. | -| `toggle_bit_ops` | `true`, `false` | `true` | Accepted as a bit-operation group flag for configuration compatibility. In this version, there are no separate `toggle_*` APIs; use the generated invert helpers to toggle bits. | -| `array_heap` | `true`, `false` | `false` | For array-backed bitfields only, stores the backing `[u8; N]` in a `Box` instead of inline in the struct. This helps avoid large stack values but requires heap allocation and therefore gives up the zero-allocation and `no_std` guarantees for that bitfield. It has no effect on primitive-backed bitfields. | +| Argument | Values | Default | Description | +|---------------------|-------------------------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `` | `u8`, `u16`, `u32`, `u64`, `u128`, or `[u8; N]` | Required | The storage used by the generated bitfield. Primitive backing types support bitfields up to 128 bits. `[u8; N]` creates an array-backed bitfield for larger layouts. Field bit widths, excluding ignored fields, must add up exactly to the backing storage size. | +| `order` | `lsb`, `msb` | `lsb` | Controls how struct fields are assigned to bit offsets. `lsb` assigns the first non-ignored field to the least-significant bits. `msb` assigns the first non-ignored field to the most-significant bits. | +| `from_endian` | `big`, `little` | `big` | Default endian used by `from_bits`, `from_bytes`, `from_slice`, and `From for Bitfield`. Explicit helpers such as `from_le_bits` and `from_be_bytes` ignore this setting. | +| `into_endian` | `big`, `little` | `big` | Default endian used by `into_bits`, `into_bytes`, `into_slice`, and `From for Backing`. Explicit helpers such as `into_le_bits` and `into_be_bytes` ignore this setting. | +| `write_endian` | `big`, `little` | `big` | Default endian used by whole-bitfield write helpers such as `write_bits` and `write_bytes`. Explicit helpers such as `write_le_bits` and `write_be_bytes` ignore this setting. | +| `new` | `true`, `false` | `true` | Generates `new()` and `new_without_defaults()` constructors. Other generated features that need construction logic, such as `Default` and the builder, still inline equivalent initialization logic when this is disabled. | +| `from_into_bits` | `true`, `false` | `true` | Generates backing-data conversion functions. Primitive bitfields get `from_bits`, `from_bits_with_defaults`, endian-specific `from_*_bits` helpers, `into_bits`, and endian-specific `into_*_bits` helpers. Array-backed bitfields get the corresponding `bytes` and `slice` APIs. | +| `from_traits` | `true`, `false` | `true` | Generates `From for Bitfield` and `From for Backing`. These conversions use `from_endian` and `into_endian`. | +| `default` | `true`, `false` | `true` | Generates `Default` for the bitfield. The default value is equivalent to `new()`: zero-initialized storage with field defaults applied. | +| `debug` | `true`, `false` | `true` | Generates `core::fmt::Debug` for the bitfield. The implementation prints readable fields and their values. | +| `copy` | `true`, `false` | `true` | Derives `Copy` and `Clone` for primitive and stack array-backed bitfields. Heap array-backed bitfields derive `Clone` only because `Box<[u8; N]>` is not `Copy`. | +| `builder` | `true`, `false` | `true` | Generates the `Builder` type, `new`, `new_without_defaults`, `with_`, `checked_with_`, and `build`. Reserved fields do not get builder setters. | +| `bit_ops` | `true`, `false` | `true` | Master switch for bit operation groups. When `false`, all bit operation groups are disabled unless a specific bit operation group is explicitly set to `true`. | +| `write_bit_ops` | `true`, `false` | `true` | Generates whole-bitfield write helpers such as `write_bits`, `write_bits_with_defaults`, `write_le_bits`, `write_be_bits`, and `write_defaults` for primitive bitfields, or the corresponding `bytes` helpers for array-backed bitfields. | +| `clear_bit_ops` | `true`, `false` | `true` | Generates whole-bitfield clear helpers such as `clear_bits` / `clear_bytes`, `clear_bits_with_defaults` / `clear_bytes_with_defaults`, plus per-field helpers like `clear_()` and `clear__to_default()`. | +| `set_get_bit_ops` | `true`, `false` | `true` | Generates individual bit helpers (`get_bit`, `checked_get_bit`, `set_bit`, `checked_set_bit`) and range helpers (`get_bits_range` / `set_bits_range` for primitive bitfields, `get_bytes_range` / `set_bytes_range` for array-backed bitfields, plus checked variants). | +| `invert_bit_ops` | `true`, `false` | `true` | Generates inversion helpers such as `invert_bits` / `invert_bytes`, per-field `invert_()`, and readable-field `_inverted()` getters. | +| `toggle_bit_ops` | `true`, `false` | `true` | Accepted as a bit-operation group flag for configuration compatibility. In this version, there are no separate `toggle_*` APIs; use the generated invert helpers to toggle bits. | +| `array_heap_std` | `true`, `false` | `false` | For array-backed bitfields only, stores the backing `[u8; N]` in a `Box` instead of inline in the struct. This helps avoid large stack values but requires heap allocation and therefore gives up the zero-allocation and `no_std` guarantees for that bitfield. It has no effect on primitive-backed bitfields. | +| `array_heap_no_std` | `true`, `false` | `false` | For array-backed bitfields only, stores the backing `[u8; N]` in a `Box` instead of inline in the struct. This helps avoid large stack values but requires heap allocation and therefore gives up the zero-allocation but **keeps** `no_std` guarantees for that bitfield. It has no effect on primitive-backed bitfields. | ```rust use bitfields::bitfield; @@ -2252,7 +2272,7 @@ This lets you generate only the bit-operation APIs you need. #### Global Cargo Feature Flags If you find yourself applying the same configuration arguments to many bitfields -in your codebase, you can set those defaults globally by **disabling default +in your codebase, you can set those defaults globally by **disabling default features** and enabling the corresponding Cargo features: - Constructors: `generate_new` / `disable_new` @@ -2274,7 +2294,9 @@ features** and enabling the corresponding Cargo features: `disable_invert_bit_ops` - Toggle bit operations: `generate_toggle_bit_ops` / `disable_toggle_bit_ops` -- Array heap storage: `enable_array_heap` / `disable_array_heap` +- Array heap std storage: `enable_array_heap_std` / `disable_array_heap_std` +- Array heap no_std storage: `enable_array_heap_no_std` / + `disable_array_heap_no_std` Endian and order defaults have dedicated feature names: diff --git a/bitfields/Cargo.toml b/bitfields/Cargo.toml index d26f2b6..8a97b06 100644 --- a/bitfields/Cargo.toml +++ b/bitfields/Cargo.toml @@ -16,6 +16,7 @@ bitfields-impl = { path = "../bitfields_impl", version = "2.0.8" } [dev-dependencies] trybuild = "1.0.116" +dummy-alloc = "1.0.0" # Represents the bitfield arguments the user can enable or disable by default for # all bitfields instead of specifying them for each bitfield. @@ -44,7 +45,8 @@ default = [ "bitflag_from_endian_big", "bitflag_into_endian_big", "bitflag_derive_copy", - "disable_array_heap", + "disable_array_heap_no_std", + "disable_array_heap_std", ] order_lsb = [] @@ -98,8 +100,11 @@ disable_toggle_bit_ops = [] generate_builder = [] disable_builder = [] -enable_array_heap = [] -disable_array_heap = [] +enable_array_heap_std = [] +disable_array_heap_std = [] + +enable_array_heap_no_std = [] +disable_array_heap_no_std = [] bitflag_from_endian_big = [] bitflag_from_endian_little = [] diff --git a/bitfields/tests/compile_error_cases/errors/arrays/array_heap_no_std_in_std.rs b/bitfields/tests/compile_error_cases/errors/arrays/array_heap_no_std_in_std.rs new file mode 100644 index 0000000..fda354d --- /dev/null +++ b/bitfields/tests/compile_error_cases/errors/arrays/array_heap_no_std_in_std.rs @@ -0,0 +1,30 @@ +#![no_std] + +use bitfields::bitfield; +use core::panic::PanicInfo; +extern crate alloc; +use dummy_alloc::DummyAllocator; + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} + +#[global_allocator] +static ALLOCATOR: DummyAllocator = DummyAllocator; + +#[bitfield([u8; 4], array_heap_no_std = true)] +struct ArrayHeapBitfield { + a: u8, + b: u8, + c: u8, + d: u8, +} + +fn main() { + let bitfield = ArrayHeapBitfield::from_slice(&[0x11, 0x22, 0x33, 0x44]); + assert_eq!(bitfield.a(), 0x44); + assert_eq!(bitfield.b(), 0x33); + assert_eq!(bitfield.c(), 0x22); + assert_eq!(bitfield.d(), 0x11); +} diff --git a/bitfields/tests/compile_error_cases/errors/arrays/array_heap_no_std_in_std.stderr b/bitfields/tests/compile_error_cases/errors/arrays/array_heap_no_std_in_std.stderr new file mode 100644 index 0000000..8ace0cc --- /dev/null +++ b/bitfields/tests/compile_error_cases/errors/arrays/array_heap_no_std_in_std.stderr @@ -0,0 +1,4 @@ +error: unwinding panics are not supported without std + | + = help: using nightly cargo, use -Zbuild-std with panic="abort" to avoid unwinding + = note: since the core library is usually precompiled with panic="unwind", rebuilding your crate with panic="abort" may not be enough to fix the problem diff --git a/bitfields/tests/compile_error_cases/errors/bitfield_invalid_bitfield_enable_disable_bit_ops_value.stderr b/bitfields/tests/compile_error_cases/errors/bitfield_invalid_bitfield_enable_disable_bit_ops_value.stderr index e100ebc..1c79925 100644 --- a/bitfields/tests/compile_error_cases/errors/bitfield_invalid_bitfield_enable_disable_bit_ops_value.stderr +++ b/bitfields/tests/compile_error_cases/errors/bitfield_invalid_bitfield_enable_disable_bit_ops_value.stderr @@ -1,10 +1,10 @@ -error: Unknown argument 'enable_disable_bit_ops'. Valid arguments are: 'array_heap', 'bit_ops', 'builder', 'clear_bit_ops', 'copy', 'debug', 'default', 'from_endian', 'from_into_bits', 'from_traits', 'into_endian', 'invert_bit_ops', 'new', 'order', 'set_get_bit_ops', 'toggle_bit_ops', 'write_bit_ops', 'write_endian'. +error: Unknown argument 'enable_disable_bit_ops'. Valid arguments are: 'array_heap_no_std', 'array_heap_std', 'bit_ops', 'builder', 'clear_bit_ops', 'copy', 'debug', 'default', 'from_endian', 'from_into_bits', 'from_traits', 'into_endian', 'invert_bit_ops', 'new', 'order', 'set_get_bit_ops', 'toggle_bit_ops', 'write_bit_ops', 'write_endian'. --> tests/compile_error_cases/errors/bitfield_invalid_bitfield_enable_disable_bit_ops_value.rs:3:17 | 3 | #[bitfield(u32, enable_disable_bit_ops = 123)] | ^^^^^^^^^^^^^^^^^^^^^^ -error: Unknown argument 'enable_disable_bit_ops'. Valid arguments are: 'array_heap', 'bit_ops', 'builder', 'clear_bit_ops', 'copy', 'debug', 'default', 'from_endian', 'from_into_bits', 'from_traits', 'into_endian', 'invert_bit_ops', 'new', 'order', 'set_get_bit_ops', 'toggle_bit_ops', 'write_bit_ops', 'write_endian'. +error: Unknown argument 'enable_disable_bit_ops'. Valid arguments are: 'array_heap_no_std', 'array_heap_std', 'bit_ops', 'builder', 'clear_bit_ops', 'copy', 'debug', 'default', 'from_endian', 'from_into_bits', 'from_traits', 'into_endian', 'invert_bit_ops', 'new', 'order', 'set_get_bit_ops', 'toggle_bit_ops', 'write_bit_ops', 'write_endian'. --> tests/compile_error_cases/errors/bitfield_invalid_bitfield_enable_disable_bit_ops_value.rs:9:17 | 9 | #[bitfield(u32, enable_disable_bit_ops = invalid)] diff --git a/bitfields/tests/compile_error_cases/errors/bitfield_unknown_arg.stderr b/bitfields/tests/compile_error_cases/errors/bitfield_unknown_arg.stderr index b32b758..77e79f6 100644 --- a/bitfields/tests/compile_error_cases/errors/bitfield_unknown_arg.stderr +++ b/bitfields/tests/compile_error_cases/errors/bitfield_unknown_arg.stderr @@ -1,4 +1,4 @@ -error: Unknown argument 'deez'. Valid arguments are: 'array_heap', 'bit_ops', 'builder', 'clear_bit_ops', 'copy', 'debug', 'default', 'from_endian', 'from_into_bits', 'from_traits', 'into_endian', 'invert_bit_ops', 'new', 'order', 'set_get_bit_ops', 'toggle_bit_ops', 'write_bit_ops', 'write_endian'. +error: Unknown argument 'deez'. Valid arguments are: 'array_heap_no_std', 'array_heap_std', 'bit_ops', 'builder', 'clear_bit_ops', 'copy', 'debug', 'default', 'from_endian', 'from_into_bits', 'from_traits', 'into_endian', 'invert_bit_ops', 'new', 'order', 'set_get_bit_ops', 'toggle_bit_ops', 'write_bit_ops', 'write_endian'. --> tests/compile_error_cases/errors/bitfield_unknown_arg.rs:3:17 | 3 | #[bitfield(u32, deez = what)] diff --git a/bitfields/tests/lib_array_backed_tests.rs b/bitfields/tests/lib_array_backed_tests.rs index 8e01798..5613e3d 100644 --- a/bitfields/tests/lib_array_backed_tests.rs +++ b/bitfields/tests/lib_array_backed_tests.rs @@ -4977,4 +4977,38 @@ mod tests { "clear_bytes: _reserved must retain its default 0x78" ); } + + #[test] + fn bitfield_array_heap_std_true() { + #[bitfield([u8; 4], array_heap_std = true)] + struct Bitfield { + a: u8, + b: u8, + c: u8, + d: u8, + } + + let bitfield = Bitfield::from_slice(&[0x11, 0x22, 0x33, 0x44]); + assert_eq!(bitfield.a(), 0x44); + assert_eq!(bitfield.b(), 0x33); + assert_eq!(bitfield.c(), 0x22); + assert_eq!(bitfield.d(), 0x11); + } + + #[test] + fn bitfield_array_heap_std_false() { + #[bitfield([u8; 4], array_heap_std = false)] + struct Bitfield { + a: u8, + b: u8, + c: u8, + d: u8, + } + + let bitfield = Bitfield::from_slice(&[0x11, 0x22, 0x33, 0x44]); + assert_eq!(bitfield.a(), 0x44); + assert_eq!(bitfield.b(), 0x33); + assert_eq!(bitfield.c(), 0x22); + assert_eq!(bitfield.d(), 0x11); + } } diff --git a/bitfields_impl/Cargo.toml b/bitfields_impl/Cargo.toml index 8bda548..efa349e 100644 --- a/bitfields_impl/Cargo.toml +++ b/bitfields_impl/Cargo.toml @@ -49,7 +49,8 @@ default = [ "bitflag_from_endian_big", "bitflag_into_endian_big", "bitflag_derive_copy", - "disable_array_heap", + "disable_array_heap_no_std", + "disable_array_heap_std", ] order_lsb = [] @@ -103,8 +104,11 @@ disable_toggle_bit_ops = [] generate_builder = [] disable_builder = [] -enable_array_heap = [] -disable_array_heap = [] +enable_array_heap_std = [] +disable_array_heap_std = [] + +enable_array_heap_no_std = [] +disable_array_heap_no_std = [] bitflag_from_endian_big = [] bitflag_from_endian_little = [] diff --git a/bitfields_impl/src/generating/bitfield/features/bitfield_struct_feature.rs b/bitfields_impl/src/generating/bitfield/features/bitfield_struct_feature.rs index f88075e..0d9e926 100644 --- a/bitfields_impl/src/generating/bitfield/features/bitfield_struct_feature.rs +++ b/bitfields_impl/src/generating/bitfield/features/bitfield_struct_feature.rs @@ -80,8 +80,7 @@ impl BitfieldStructFeatureGenerator { } if bitfield.arguments().derive_copy() { - let is_heap_array = bitfield.arguments().array_heap() && !bitfield.is_integer_backed(); - if is_heap_array { + if bitfield.is_array_heap() { attributes_tokens.push(quote! { #[derive(core::clone::Clone)] }); @@ -105,10 +104,14 @@ impl BitfieldStructFeatureGenerator { /// Returns the backing storage type tokens for the bitfield struct field. fn get_backing_field_type_tokens(bitfield: &Bitfield) -> TokenStream { let inner = bitfield.spanned_data_type_token().to_tokens(); - if bitfield.arguments().array_heap() && !bitfield.is_integer_backed() { + if !bitfield.is_array_heap() { + inner + } else if bitfield.arguments().array_heap_std() { quote! { ::std::boxed::Box<#inner> } + } else if bitfield.arguments().array_heap_no_std() { + quote! { ::alloc::boxed::Box<#inner> } } else { - inner + unreachable!() } } diff --git a/bitfields_impl/src/generating/bitfield/features/common/generator_helper.rs b/bitfields_impl/src/generating/bitfield/features/common/generator_helper.rs index 18456b3..a7b4feb 100644 --- a/bitfields_impl/src/generating/bitfield/features/common/generator_helper.rs +++ b/bitfields_impl/src/generating/bitfield/features/common/generator_helper.rs @@ -17,8 +17,7 @@ pub enum BitsSource { /// Returns the function modifiers for generated functions (e.g., `const`). pub fn get_function_modifier_tokens(bitfield: &Bitfield) -> Option { - let is_heap_array = bitfield.arguments().array_heap() && !bitfield.is_integer_backed(); - (!bitfield.has_ignored_fields() && !is_heap_array && supports_const_mut_refs()) + (!bitfield.has_ignored_fields() && !bitfield.is_array_heap() && supports_const_mut_refs()) .then(|| quote::quote! { const }) } @@ -55,14 +54,20 @@ pub fn generate_bitfield_struct_initialization_tokens( length, } => { let length = length as usize; - if bitfield.arguments().array_heap() { + if !bitfield.is_array_heap() { + quote! { + [0u8; #length] + } + } else if bitfield.arguments().array_heap_std() { quote! { ::std::boxed::Box::new([0u8; #length]) } - } else { + } else if bitfield.arguments().array_heap_no_std() { quote! { - [0u8; #length] + ::alloc::boxed::Box::new([0u8; #length]) } + } else { + unreachable!() } }, DataType::Custom => { @@ -1004,6 +1009,6 @@ pub fn generate_backing_data_param_ident(bitfield: &Bitfield) -> TokenStream { } /// Returns the term used for the backing data of a bitfield. -pub const fn get_bits_or_bytes_term(bitfield: &Bitfield) -> &'static str { +pub fn get_bits_or_bytes_term(bitfield: &Bitfield) -> &'static str { if bitfield.is_integer_backed() { "bits" } else { "bytes" } } diff --git a/bitfields_impl/src/generating/bitfield/features/from_into_bits_feature.rs b/bitfields_impl/src/generating/bitfield/features/from_into_bits_feature.rs index a62d506..bf4adf3 100644 --- a/bitfields_impl/src/generating/bitfield/features/from_into_bits_feature.rs +++ b/bitfields_impl/src/generating/bitfield/features/from_into_bits_feature.rs @@ -517,7 +517,7 @@ impl FromIntoBitsFeature { let half_len = array_len / 2; let last_idx = array_len - 1; - let is_heap = bitfield.arguments().array_heap(); + let is_heap = bitfield.is_array_heap(); let into_bits_convert_tokens = |endian| match endian { ConversionEndian::Little => { @@ -601,7 +601,7 @@ impl FromIntoBitsFeature { let half_len = array_len / 2; let last_idx = array_len - 1; - let bits_access_tokens = if bitfield.arguments().array_heap() { + let bits_access_tokens = if bitfield.is_array_heap() { quote! { *#bitfield_internal_value_ident_tokens } } else { quote! { #bitfield_internal_value_ident_tokens } diff --git a/bitfields_impl/src/generating/bitfield/features/from_traits_feature.rs b/bitfields_impl/src/generating/bitfield/features/from_traits_feature.rs index e1616cf..7ed4485 100644 --- a/bitfields_impl/src/generating/bitfield/features/from_traits_feature.rs +++ b/bitfields_impl/src/generating/bitfield/features/from_traits_feature.rs @@ -116,8 +116,7 @@ impl FromTraitsFeature { bitfield.arguments().from_endian() }; - let is_heap_array = - into_bits && bitfield.arguments().array_heap() && !bitfield.is_integer_backed(); + let is_heap_array = into_bits && bitfield.is_array_heap() && !bitfield.is_integer_backed(); match conversion_endian { ConversionEndian::Little => { diff --git a/bitfields_impl/src/generating/bitfield/features/invert_bit_ops_feature.rs b/bitfields_impl/src/generating/bitfield/features/invert_bit_ops_feature.rs index 1d087d4..82cb39f 100644 --- a/bitfields_impl/src/generating/bitfield/features/invert_bit_ops_feature.rs +++ b/bitfields_impl/src/generating/bitfield/features/invert_bit_ops_feature.rs @@ -84,7 +84,7 @@ impl InvertBitOpsFeature { .array_length() .expect("array-backed bitfield must have a known length"); - let bits_init_tokens = if bitfield.arguments().array_heap() { + let bits_init_tokens = if bitfield.is_array_heap() { quote! { let mut bits = *#bitfield_internal_value_ident_tokens; } } else { quote! { let mut bits = #bitfield_internal_value_ident_tokens; } diff --git a/bitfields_impl/src/lib.rs b/bitfields_impl/src/lib.rs index 1803831..33319d0 100644 --- a/bitfields_impl/src/lib.rs +++ b/bitfields_impl/src/lib.rs @@ -44,10 +44,11 @@ const INTERNAL_ERROR_MESSAGE: &str = "A major unexpected error has occurred. If /// field bits must add up to the exact number of bits of the bitfield type. /// /// If you have an array backed bitfield that may overflow the stack, you can pass -/// the optional argument `#[bitfield(array_heap = true)]` to the bitfield, which -/// will box the array on the heap instead of the stack. Keep in mind that you -/// **lose constant memory, zero-allocation, and no_std guarantees when using heap -/// array bitfields**. +/// the optional argument `#[bitfield(array_heap_std = true)]` or +/// `#[bitfield(array_heap_no_std = true)]` (depending on your environment) to the +/// bitfield, which will box the array on the heap instead of the stack. Keep in +/// mind that you **lose constant memory and zero-allocation when using heap array +/// bitfields**. /// /// ```rust /// # use bitfields_impl as bitfields; @@ -59,8 +60,9 @@ const INTERNAL_ERROR_MESSAGE: &str = "A major unexpected error has occurred. If /// b: u8, /// } /// -/// #[bitfield([u8; 96], array_heap = true -/// )] /// Allocated on the heap, 768 bits (96 bytes). +/// #[bitfield( +/// [u8; 96], array_heap_std = true) +/// ] /// Allocated on the heap, 768 bits (96 bytes). /// struct HeapArrayBitfield { /// a: u128, /// b: u128, @@ -167,7 +169,7 @@ const INTERNAL_ERROR_MESSAGE: &str = "A major unexpected error has occurred. If /// /// Fields can have a default value, which must fit in the field type or the /// specified bits of the field. A default value must be a const variable or -/// a const function. Just be aware that const function and variable +/// a const function. Just be aware that const function and variable /// defaults lose their compile-time field size checks, so it's /// up to you to make sure they fit. /// @@ -214,7 +216,8 @@ const INTERNAL_ERROR_MESSAGE: &str = "A major unexpected error has occurred. If /// /// A bitfield can have signed (`i8`, `i16`, `i32`, `i64`, `i128`) types. Signed /// types are treated as 2's complement data types, where the most significant bit -/// representing the sign bit. For example, if you had a field with 5 bits, the value +/// representing the sign bit. For example, if you had a field with 5 bits, the +/// value /// range would be `-16` to `15`. The more bits you include, the larger the range! /// /// ```rust @@ -322,7 +325,7 @@ const INTERNAL_ERROR_MESSAGE: &str = "A major unexpected error has occurred. If /// fields. But use a **reserved field** when you want to pad/fill bits that have /// no user-facing identity (hardware reserved bits, alignment gaps). Use /// **`access = na`** when the field is a real, named field that you want to -/// completely lock down from the API while still keeping it distinguishable +/// completely lock down from the API while still keeping it distinguishable /// by name. Later on, if you want to expose the field, you can just change /// the access level without having to change the field name. /// @@ -584,7 +587,7 @@ const INTERNAL_ERROR_MESSAGE: &str = "A major unexpected error has occurred. If /// /// Fills the middle bits of the u16. /// #[bits(4, default = 0xF)] /// __: u8, -/// +/// /// /// Fils the end bits of the u16. /// #[bits(4, default = 0xF)] /// __: u8, @@ -597,7 +600,7 @@ const INTERNAL_ERROR_MESSAGE: &str = "A major unexpected error has occurred. If /// // bitfield.set__reserved(0xFF); // Compile error, reserved fields are inaccessible. /// assert_eq!(bitfield.into_bits(), 0xFF00); // All fields exposed when converted /// // to bits. -/// +/// /// let reserved_bitfield = ReservedBitfield::new(); /// assert_eq!(reserved_bitfield.a(), 0); /// // assert_eq!(reserved_bitfield.__(), 0xFF0); // Compile error, reserved inaccessible. @@ -617,8 +620,10 @@ const INTERNAL_ERROR_MESSAGE: &str = "A major unexpected error has occurred. If /// automatically. /// /// Bitflags only supports unsigned types (`u8`, `u16`, `u32`, `u64`, `u128`) and -/// the one of the variants must be annotated with `#[base]` or `#[default]` which represents the -/// base value of the bitflag. If `#[base]` and `#[default]` are both present, `#[base]` takes precedence. +/// the one of the variants must be annotated with `#[base]` or `#[default]` which +/// represents the +/// base value of the bitflag. If `#[base]` and `#[default]` are both present, +/// `#[base]` takes precedence. /// /// ```rust /// # use bitfields_impl as bitfields; @@ -697,7 +702,8 @@ const INTERNAL_ERROR_MESSAGE: &str = "A major unexpected error has occurred. If /// /// #### Bitflag Configuration /// -/// Bitflags can be configured with arguments passed to the `#[bitflag(...)]` attribute (the first argument is always the backing unsigned integer type): +/// Bitflags can be configured with arguments passed to the `#[bitflag(...)]` +/// attribute (the first argument is always the backing unsigned integer type): /// /// | Argument | Values | Default | Description | /// |------------------|-----------------------------------|----------|------------------------------------------------------------------------------------------------------| @@ -1097,7 +1103,8 @@ const INTERNAL_ERROR_MESSAGE: &str = "A major unexpected error has occurred. If /// Sometimes the outside world is outside our control, like the endianness of how /// systems export data. Luckily, the endianness of the bitfield conversions can /// be controlled by specifying the `#[bitfield(from_endian = N, into_endian = N)]` -/// args. The possible endianness options are `little` or `big`. By default, the endianness +/// args. The possible endianness options are `little` or `big`. By default, the +/// endianness /// of both is `big`. /// /// This arg controls the endianness of the `from`, `into`, and `From` trait @@ -1996,34 +2003,35 @@ const INTERNAL_ERROR_MESSAGE: &str = "A major unexpected error has occurred. If /// set_get_bit_ops = true, /// invert_bit_ops = true, /// toggle_bit_ops = true, -/// array_heap = false, +/// array_heap_std = false, /// )] /// struct Example { /// a: u32, /// } /// ``` /// -/// | Argument | Values | Default | Description | -/// |-------------------|-------------------------------------------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -/// | `` | `u8`, `u16`, `u32`, `u64`, `u128`, or `[u8; N]` | Required | The storage used by the generated bitfield. Primitive backing types support bitfields up to 128 bits. `[u8; N]` creates an array-backed bitfield for larger layouts. Field bit widths, excluding ignored fields, must add up exactly to the backing storage size. | -/// | `order` | `lsb`, `msb` | `lsb` | Controls how struct fields are assigned to bit offsets. `lsb` assigns the first non-ignored field to the least-significant bits. `msb` assigns the first non-ignored field to the most-significant bits. | -/// | `from_endian` | `big`, `little` | `big` | Default endian used by `from_bits`, `from_bytes`, `from_slice`, and `From for Bitfield`. Explicit helpers such as `from_le_bits` and `from_be_bytes` ignore this setting. | -/// | `into_endian` | `big`, `little` | `big` | Default endian used by `into_bits`, `into_bytes`, `into_slice`, and `From for Backing`. Explicit helpers such as `into_le_bits` and `into_be_bytes` ignore this setting. | -/// | `write_endian` | `big`, `little` | `big` | Default endian used by whole-bitfield write helpers such as `write_bits` and `write_bytes`. Explicit helpers such as `write_le_bits` and `write_be_bytes` ignore this setting. | -/// | `new` | `true`, `false` | `true` | Generates `new()` and `new_without_defaults()` constructors. Other generated features that need construction logic, such as `Default` and the builder, still inline equivalent initialization logic when this is disabled. | -/// | `from_into_bits` | `true`, `false` | `true` | Generates backing-data conversion functions. Primitive bitfields get `from_bits`, `from_bits_with_defaults`, endian-specific `from_*_bits` helpers, `into_bits`, and endian-specific `into_*_bits` helpers. Array-backed bitfields get the corresponding `bytes` and `slice` APIs. | -/// | `from_traits` | `true`, `false` | `true` | Generates `From for Bitfield` and `From for Backing`. These conversions use `from_endian` and `into_endian`. | -/// | `default` | `true`, `false` | `true` | Generates `Default` for the bitfield. The default value is equivalent to `new()`: zero-initialized storage with field defaults applied. | -/// | `debug` | `true`, `false` | `true` | Generates `core::fmt::Debug` for the bitfield. The implementation prints readable fields and their values. | -/// | `copy` | `true`, `false` | `true` | Derives `Copy` and `Clone` for primitive and stack array-backed bitfields. Heap array-backed bitfields derive `Clone` only because `Box<[u8; N]>` is not `Copy`. | -/// | `builder` | `true`, `false` | `true` | Generates the `Builder` type, `new`, `new_without_defaults`, `with_`, `checked_with_`, and `build`. Reserved fields do not get builder setters. | -/// | `bit_ops` | `true`, `false` | `true` | Master switch for bit operation groups. When `false`, all bit operation groups are disabled unless a specific bit operation group is explicitly set to `true`. | -/// | `write_bit_ops` | `true`, `false` | `true` | Generates whole-bitfield write helpers such as `write_bits`, `write_bits_with_defaults`, `write_le_bits`, `write_be_bits`, and `write_defaults` for primitive bitfields, or the corresponding `bytes` helpers for array-backed bitfields. | -/// | `clear_bit_ops` | `true`, `false` | `true` | Generates whole-bitfield clear helpers such as `clear_bits` / `clear_bytes`, `clear_bits_with_defaults` / `clear_bytes_with_defaults`, plus per-field helpers like `clear_()` and `clear__to_default()`. | -/// | `set_get_bit_ops` | `true`, `false` | `true` | Generates individual bit helpers (`get_bit`, `checked_get_bit`, `set_bit`, `checked_set_bit`) and range helpers (`get_bits_range` / `set_bits_range` for primitive bitfields, `get_bytes_range` / `set_bytes_range` for array-backed bitfields, plus checked variants). | -/// | `invert_bit_ops` | `true`, `false` | `true` | Generates inversion helpers such as `invert_bits` / `invert_bytes`, per-field `invert_()`, and readable-field `_inverted()` getters. | -/// | `toggle_bit_ops` | `true`, `false` | `true` | Accepted as a bit-operation group flag for configuration compatibility. In this version, there are no separate `toggle_*` APIs; use the generated invert helpers to toggle bits. | -/// | `array_heap` | `true`, `false` | `false` | For array-backed bitfields only, stores the backing `[u8; N]` in a `Box` instead of inline in the struct. This helps avoid large stack values but requires heap allocation and therefore gives up the zero-allocation and `no_std` guarantees for that bitfield. It has no effect on primitive-backed bitfields. | +/// | Argument | Values | Default | Description | +/// |---------------------|-------------------------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +/// | `` | `u8`, `u16`, `u32`, `u64`, `u128`, or `[u8; N]` | Required | The storage used by the generated bitfield. Primitive backing types support bitfields up to 128 bits. `[u8; N]` creates an array-backed bitfield for larger layouts. Field bit widths, excluding ignored fields, must add up exactly to the backing storage size. | +/// | `order` | `lsb`, `msb` | `lsb` | Controls how struct fields are assigned to bit offsets. `lsb` assigns the first non-ignored field to the least-significant bits. `msb` assigns the first non-ignored field to the most-significant bits. | +/// | `from_endian` | `big`, `little` | `big` | Default endian used by `from_bits`, `from_bytes`, `from_slice`, and `From for Bitfield`. Explicit helpers such as `from_le_bits` and `from_be_bytes` ignore this setting. | +/// | `into_endian` | `big`, `little` | `big` | Default endian used by `into_bits`, `into_bytes`, `into_slice`, and `From for Backing`. Explicit helpers such as `into_le_bits` and `into_be_bytes` ignore this setting. | +/// | `write_endian` | `big`, `little` | `big` | Default endian used by whole-bitfield write helpers such as `write_bits` and `write_bytes`. Explicit helpers such as `write_le_bits` and `write_be_bytes` ignore this setting. | +/// | `new` | `true`, `false` | `true` | Generates `new()` and `new_without_defaults()` constructors. Other generated features that need construction logic, such as `Default` and the builder, still inline equivalent initialization logic when this is disabled. | +/// | `from_into_bits` | `true`, `false` | `true` | Generates backing-data conversion functions. Primitive bitfields get `from_bits`, `from_bits_with_defaults`, endian-specific `from_*_bits` helpers, `into_bits`, and endian-specific `into_*_bits` helpers. Array-backed bitfields get the corresponding `bytes` and `slice` APIs. | +/// | `from_traits` | `true`, `false` | `true` | Generates `From for Bitfield` and `From for Backing`. These conversions use `from_endian` and `into_endian`. | +/// | `default` | `true`, `false` | `true` | Generates `Default` for the bitfield. The default value is equivalent to `new()`: zero-initialized storage with field defaults applied. | +/// | `debug` | `true`, `false` | `true` | Generates `core::fmt::Debug` for the bitfield. The implementation prints readable fields and their values. | +/// | `copy` | `true`, `false` | `true` | Derives `Copy` and `Clone` for primitive and stack array-backed bitfields. Heap array-backed bitfields derive `Clone` only because `Box<[u8; N]>` is not `Copy`. | +/// | `builder` | `true`, `false` | `true` | Generates the `Builder` type, `new`, `new_without_defaults`, `with_`, `checked_with_`, and `build`. Reserved fields do not get builder setters. | +/// | `bit_ops` | `true`, `false` | `true` | Master switch for bit operation groups. When `false`, all bit operation groups are disabled unless a specific bit operation group is explicitly set to `true`. | +/// | `write_bit_ops` | `true`, `false` | `true` | Generates whole-bitfield write helpers such as `write_bits`, `write_bits_with_defaults`, `write_le_bits`, `write_be_bits`, and `write_defaults` for primitive bitfields, or the corresponding `bytes` helpers for array-backed bitfields. | +/// | `clear_bit_ops` | `true`, `false` | `true` | Generates whole-bitfield clear helpers such as `clear_bits` / `clear_bytes`, `clear_bits_with_defaults` / `clear_bytes_with_defaults`, plus per-field helpers like `clear_()` and `clear__to_default()`. | +/// | `set_get_bit_ops` | `true`, `false` | `true` | Generates individual bit helpers (`get_bit`, `checked_get_bit`, `set_bit`, `checked_set_bit`) and range helpers (`get_bits_range` / `set_bits_range` for primitive bitfields, `get_bytes_range` / `set_bytes_range` for array-backed bitfields, plus checked variants). | +/// | `invert_bit_ops` | `true`, `false` | `true` | Generates inversion helpers such as `invert_bits` / `invert_bytes`, per-field `invert_()`, and readable-field `_inverted()` getters. | +/// | `toggle_bit_ops` | `true`, `false` | `true` | Accepted as a bit-operation group flag for configuration compatibility. In this version, there are no separate `toggle_*` APIs; use the generated invert helpers to toggle bits. | +/// | `array_heap_std` | `true`, `false` | `false` | For array-backed bitfields only, stores the backing `[u8; N]` in a `Box` instead of inline in the struct. This helps avoid large stack values but requires heap allocation and therefore gives up the zero-allocation and `no_std` guarantees for that bitfield. It has no effect on primitive-backed bitfields. | +/// | `array_heap_no_std` | `true`, `false` | `false` | For array-backed bitfields only, stores the backing `[u8; N]` in a `Box` instead of inline in the struct. This helps avoid large stack values but requires heap allocation and therefore gives up the zero-allocation but **keeps** `no_std` guarantees for that bitfield. It has no effect on primitive-backed bitfields. | /// /// ```rust /// # use bitfields_impl as bitfields; @@ -2083,7 +2091,7 @@ const INTERNAL_ERROR_MESSAGE: &str = "A major unexpected error has occurred. If /// #### Global Cargo Feature Flags /// /// If you find yourself applying the same configuration arguments to many bitfields -/// in your codebase, you can set those defaults globally by **disabling default +/// in your codebase, you can set those defaults globally by **disabling default /// features** and enabling the corresponding Cargo features: /// /// - Constructors: `generate_new` / `disable_new` @@ -2105,7 +2113,9 @@ const INTERNAL_ERROR_MESSAGE: &str = "A major unexpected error has occurred. If /// `disable_invert_bit_ops` /// - Toggle bit operations: `generate_toggle_bit_ops` / /// `disable_toggle_bit_ops` -/// - Array heap storage: `enable_array_heap` / `disable_array_heap` +/// - Array heap std storage: `enable_array_heap_std` / `disable_array_heap_std` +/// - Array heap no_std storage: `enable_array_heap_no_std` / +/// `disable_array_heap_no_std` /// /// Endian and order defaults have dedicated feature names: /// @@ -2172,8 +2182,10 @@ fn check_force_panic(bitfield: &Bitfield) { /// automatically. /// /// Bitflags only supports unsigned types (`u8`, `u16`, `u32`, `u64`, `u128`) and -/// the one of the variants must be annotated with `#[base]` or `#[default]` which represents the -/// base value of the bitflag. If `#[base]` and `#[default]` are both present, `#[base]` takes precedence. +/// the one of the variants must be annotated with `#[base]` or `#[default]` which +/// represents the +/// base value of the bitflag. If `#[base]` and `#[default]` are both present, +/// `#[base]` takes precedence. /// /// ```rust /// # use bitfields_impl as bitfields; @@ -2252,7 +2264,8 @@ fn check_force_panic(bitfield: &Bitfield) { /// /// #### Bitflag Configuration /// -/// Bitflags can be configured with arguments passed to the `#[bitflag(...)]` attribute (the first argument is always the backing unsigned integer type): +/// Bitflags can be configured with arguments passed to the `#[bitflag(...)]` +/// attribute (the first argument is always the backing unsigned integer type): /// /// | Argument | Values | Default | Description | /// |------------------|-----------------------------------|----------|------------------------------------------------------------------------------------------------------| diff --git a/bitfields_impl/src/parsing/bitfields/bitfield.rs b/bitfields_impl/src/parsing/bitfields/bitfield.rs index cabf078..4844438 100644 --- a/bitfields_impl/src/parsing/bitfields/bitfield.rs +++ b/bitfields_impl/src/parsing/bitfields/bitfield.rs @@ -71,9 +71,16 @@ impl Bitfield { self.name_ident.to_token_stream() } - pub const fn is_integer_backed(&self) -> bool { + /// Returns if the bitfield is array backed. + pub fn is_integer_backed(&self) -> bool { matches!(self.spanned_data_type_token.data_type(), DataType::Integer(_)) } + + /// Returns if the bitfield is heap array backed. + pub fn is_array_heap(&self) -> bool { + (self.arguments().array_heap_std() || self.arguments().array_heap_no_std()) + && !self.is_integer_backed() + } } /// Represents a bitfield field. diff --git a/bitfields_impl/src/parsing/bitfields/bitfield_attribute/bitfield_arguments.rs b/bitfields_impl/src/parsing/bitfields/bitfield_attribute/bitfield_arguments.rs index 4812f39..41cc761 100644 --- a/bitfields_impl/src/parsing/bitfields/bitfield_attribute/bitfield_arguments.rs +++ b/bitfields_impl/src/parsing/bitfields/bitfield_attribute/bitfield_arguments.rs @@ -65,8 +65,11 @@ const DISABLE_TOGGLE_BIT_OPS_FEATURE_ENABLED: bool = cfg!(feature = "disable_tog const GENERATE_BUILDER_FEATURE_ENABLED: bool = cfg!(feature = "generate_builder"); const DISABLE_BUILDER_FEATURE_ENABLED: bool = cfg!(feature = "disable_builder"); -const ENABLE_ARRAY_HEAP_FEATURE_ENABLED: bool = cfg!(feature = "enable_array_heap"); -const DISABLE_ARRAY_HEAP_FEATURE_ENABLED: bool = cfg!(feature = "disable_array_heap"); +const ENABLE_ARRAY_HEAP_STD_FEATURE_ENABLED: bool = cfg!(feature = "enable_array_heap_std"); +const DISABLE_ARRAY_HEAP_STD_FEATURE_ENABLED: bool = cfg!(feature = "disable_array_heap_std"); + +const ENABLE_ARRAY_HEAP_NO_STD_FEATURE_ENABLED: bool = cfg!(feature = "enable_array_heap_no_std"); +const DISABLE_ARRAY_HEAP_NO_STD_FEATURE_ENABLED: bool = cfg!(feature = "disable_array_heap_no_std"); /// The order of the bits in the bitfield. /// @@ -165,11 +168,18 @@ pub struct BitfieldArguments { /// Whether a bitfield builder should be generated. generate_builder: bool, - /// Whether to allocate array-backed bitfield storage on the heap. + /// Whether to allocate array-backed bitfield storage on the heap using std. + /// + /// Useful when the array would be too large to live on the stack and has no + /// effect on integer-backed bitfields. + array_heap_std: bool, + + /// Whether to allocate array-backed bitfield storage on the heap using + /// alloc. /// /// Useful when the array would be too large to live on the stack and has no /// effect on integer-backed bitfields. - array_heap: bool, + array_heap_no_std: bool, /// Whether to force a panic during macro generation. force_panic: bool, @@ -251,7 +261,10 @@ impl Default for BitfieldArguments { GENERATE_BUILDER_FEATURE_ENABLED, DISABLE_BUILDER_FEATURE_ENABLED, ), - array_heap: ENABLE_ARRAY_HEAP_FEATURE_ENABLED && !DISABLE_ARRAY_HEAP_FEATURE_ENABLED, + array_heap_std: ENABLE_ARRAY_HEAP_STD_FEATURE_ENABLED + && !DISABLE_ARRAY_HEAP_STD_FEATURE_ENABLED, + array_heap_no_std: ENABLE_ARRAY_HEAP_NO_STD_FEATURE_ENABLED + && !DISABLE_ARRAY_HEAP_NO_STD_FEATURE_ENABLED, user_set_generate_write_bit_ops: false, force_panic: false, } @@ -333,8 +346,11 @@ enum BitfieldArgumentKey { #[strum(serialize = "builder")] Builder, - #[strum(serialize = "array_heap")] - ArrayHeap, + #[strum(serialize = "array_heap_std")] + ArrayHeapStd, + + #[strum(serialize = "array_heap_no_std")] + ArrayHeapNoStd, #[strum(serialize = "force_panic")] ForcePanic, @@ -430,8 +446,12 @@ impl Parse for BitfieldArguments { bitfield_arguments.generate_builder = parse_boolean_attribute_argument(argument)?; }, - BitfieldArgumentKey::ArrayHeap => { - bitfield_arguments.array_heap = parse_boolean_attribute_argument(argument)?; + BitfieldArgumentKey::ArrayHeapStd => { + bitfield_arguments.array_heap_std = parse_boolean_attribute_argument(argument)?; + }, + BitfieldArgumentKey::ArrayHeapNoStd => { + bitfield_arguments.array_heap_no_std = + parse_boolean_attribute_argument(argument)?; }, BitfieldArgumentKey::Copy => { bitfield_arguments.derive_copy = parse_boolean_attribute_argument(argument)?; diff --git a/bitfields_impl/src/parsing/bitfields/bitfield_attribute/bitfield_attribute_parser.rs b/bitfields_impl/src/parsing/bitfields/bitfield_attribute/bitfield_attribute_parser.rs index 4250bdb..9f8bb1e 100644 --- a/bitfields_impl/src/parsing/bitfields/bitfield_attribute/bitfield_attribute_parser.rs +++ b/bitfields_impl/src/parsing/bitfields/bitfield_attribute/bitfield_attribute_parser.rs @@ -53,7 +53,9 @@ impl BitfieldAttribute { | TypeParsingError::SizeTypeNotSupported => { Err(create_user_parsing_compiler_error( input.span(), - format!("{BITFIELD_ATTRIBUTE_NON_UNSIGNED_INTEGER_FIRST_ARGUMENT_ERROR_MESSAGE}."), + format!( + "{BITFIELD_ATTRIBUTE_NON_UNSIGNED_INTEGER_FIRST_ARGUMENT_ERROR_MESSAGE}." + ), )) }, TypeParsingError::UnexpectedFloat => Err(create_user_parsing_compiler_error(