├── .github └── workflows │ ├── rust-clippy.yml │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── attr.rs ├── lib.rs └── traits.rs └── tests └── test.rs /.github/workflows/rust-clippy.yml: -------------------------------------------------------------------------------- 1 | name: Rust Clippy 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ "main" ] 9 | schedule: 10 | - cron: '31 2 * * 3' 11 | 12 | jobs: 13 | rust-clippy-analyze: 14 | name: Run rust-clippy analyzing 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: read 18 | security-events: write 19 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v2 23 | 24 | - name: Install Rust toolchain 25 | uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af #@v1 26 | with: 27 | profile: minimal 28 | toolchain: stable 29 | components: clippy 30 | override: true 31 | 32 | - name: Install required cargo 33 | run: cargo install clippy-sarif sarif-fmt 34 | 35 | - name: Run rust-clippy 36 | run: 37 | cargo clippy 38 | --all-features 39 | --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt 40 | continue-on-error: true 41 | 42 | - name: Upload analysis results to GitHub 43 | uses: github/codeql-action/upload-sarif@v1 44 | with: 45 | sarif_file: rust-clippy-results.sarif 46 | wait-for-processing: true 47 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust Tests 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "bitfield-struct" 7 | version = "0.11.0" 8 | dependencies = [ 9 | "defmt", 10 | "endian-num", 11 | "proc-macro2", 12 | "quote", 13 | "syn", 14 | ] 15 | 16 | [[package]] 17 | name = "bitflags" 18 | version = "1.3.2" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 21 | 22 | [[package]] 23 | name = "defmt" 24 | version = "1.0.1" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" 27 | dependencies = [ 28 | "bitflags", 29 | "defmt-macros", 30 | ] 31 | 32 | [[package]] 33 | name = "defmt-macros" 34 | version = "1.0.1" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" 37 | dependencies = [ 38 | "defmt-parser", 39 | "proc-macro-error2", 40 | "proc-macro2", 41 | "quote", 42 | "syn", 43 | ] 44 | 45 | [[package]] 46 | name = "defmt-parser" 47 | version = "1.0.0" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" 50 | dependencies = [ 51 | "thiserror", 52 | ] 53 | 54 | [[package]] 55 | name = "endian-num" 56 | version = "0.2.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "2b4a54dfedecb1dc004776a597db0c858cf6c31579296734511309291b8399ce" 59 | 60 | [[package]] 61 | name = "proc-macro-error-attr2" 62 | version = "2.0.0" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" 65 | dependencies = [ 66 | "proc-macro2", 67 | "quote", 68 | ] 69 | 70 | [[package]] 71 | name = "proc-macro-error2" 72 | version = "2.0.1" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" 75 | dependencies = [ 76 | "proc-macro-error-attr2", 77 | "proc-macro2", 78 | "quote", 79 | "syn", 80 | ] 81 | 82 | [[package]] 83 | name = "proc-macro2" 84 | version = "1.0.95" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 87 | dependencies = [ 88 | "unicode-ident", 89 | ] 90 | 91 | [[package]] 92 | name = "quote" 93 | version = "1.0.40" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 96 | dependencies = [ 97 | "proc-macro2", 98 | ] 99 | 100 | [[package]] 101 | name = "syn" 102 | version = "2.0.101" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 105 | dependencies = [ 106 | "proc-macro2", 107 | "quote", 108 | "unicode-ident", 109 | ] 110 | 111 | [[package]] 112 | name = "thiserror" 113 | version = "2.0.12" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 116 | dependencies = [ 117 | "thiserror-impl", 118 | ] 119 | 120 | [[package]] 121 | name = "thiserror-impl" 122 | version = "2.0.12" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 125 | dependencies = [ 126 | "proc-macro2", 127 | "quote", 128 | "syn", 129 | ] 130 | 131 | [[package]] 132 | name = "unicode-ident" 133 | version = "1.0.18" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 136 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitfield-struct" 3 | version = "0.11.0" 4 | edition = "2021" 5 | authors = ["Lars Wrenger "] 6 | description = "Struct-like procedural macro for bitfields." 7 | keywords = ["bitfields", "bits", "proc-macro"] 8 | categories = ["data-structures", "no-std"] 9 | repository = "https://github.com/wrenger/bitfield-struct-rs.git" 10 | documentation = "https://docs.rs/bitfield-struct" 11 | readme = "README.md" 12 | license = "MIT" 13 | rust-version = "1.83.0" 14 | 15 | [lib] 16 | proc-macro = true 17 | 18 | [dependencies] 19 | quote = "1.0" 20 | syn = { version = "2.0", features = ["full", "extra-traits"] } 21 | proc-macro2 = "1.0" 22 | 23 | [dev-dependencies] 24 | defmt = "1.0" 25 | endian-num = { version = "0.2", features = ["linux-types"] } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Lars Wrenger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bitfield Struct 2 | 3 | [![Crate](https://img.shields.io/crates/v/bitfield-struct.svg)](https://crates.io/crates/bitfield-struct) 4 | [![API](https://docs.rs/bitfield-struct/badge.svg)](https://docs.rs/bitfield-struct) 5 | 6 | Procedural macro for bitfields that allows specifying bitfields as structs. 7 | As this library provides a procedural macro, it has no runtime dependencies and works for `no-std` environments. 8 | 9 | - Ideal for driver/OS/embedded development (defining HW registers/structures) 10 | - Supports bool flags, integers, and custom types convertible into integers (structs/enums) 11 | - Generates minimalistic, pure, safe rust functions 12 | - Compile-time checks for type and field sizes 13 | - Rust-analyzer/docrs friendly (carries over docs to accessor functions) 14 | - Exports field offsets and sizes as constants (useful for const asserts) 15 | - Optional generation of `Default`, `Clone`, `Debug`, `Hash`, or `defmt::Format` traits 16 | - Custom internal representation (endianness) 17 | 18 | ## Usage 19 | 20 | Add this to your `Cargo.toml`: 21 | 22 | ```toml 23 | [dependencies] 24 | bitfield-struct = "0.11" 25 | ``` 26 | 27 | ## Basics 28 | 29 | Let's begin with a simple example. 30 | Suppose we want to store multiple data inside a single Byte, as shown below: 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
76543210
PLevelSKind
50 | 51 | This crate generates a nice wrapper type that makes it easy to do this: 52 | 53 | ```rust 54 | use bitfield_struct::bitfield; 55 | 56 | /// Define your type like this with the bitfield attribute 57 | #[bitfield(u8)] 58 | struct MyByte { 59 | /// The first field occupies the least significant bits 60 | #[bits(4)] 61 | kind: usize, 62 | /// Booleans are 1 bit large 63 | system: bool, 64 | /// The bits attribute specifies the bit size of this field 65 | #[bits(2)] 66 | level: usize, 67 | /// The last field spans over the most significant bits 68 | present: bool 69 | } 70 | // The macro creates three accessor functions for each field: 71 | // , with_ and set_ 72 | let my_byte = MyByte::new() 73 | .with_kind(15) 74 | .with_system(false) 75 | .with_level(3) 76 | .with_present(true); 77 | 78 | assert!(my_byte.present()); 79 | ``` 80 | 81 | ## Features 82 | 83 | Additionally, this crate has a few useful features, which are shown here in more detail. 84 | 85 | The example below shows how attributes are carried over and how signed integers, padding, and custom types are handled. 86 | 87 | ```rust 88 | use bitfield_struct::bitfield; 89 | 90 | /// A test bitfield with documentation 91 | #[bitfield(u64)] 92 | #[derive(PartialEq, Eq)] // <- Attributes after `bitfield` are carried over 93 | struct MyBitfield { 94 | /// Defaults to 16 bits for u16 95 | int: u16, 96 | /// Interpreted as 1 bit flag, with a custom default value 97 | #[bits(default = true)] 98 | flag: bool, 99 | /// Custom bit size 100 | #[bits(1)] 101 | tiny: u8, 102 | /// Sign extend for signed integers 103 | #[bits(13)] 104 | negative: i16, 105 | /// Supports any type with `into_bits`/`from_bits` functions 106 | #[bits(16)] 107 | custom: CustomEnum, 108 | /// Public field -> public accessor functions 109 | #[bits(10)] 110 | pub public: usize, 111 | /// Also supports read-only fields 112 | #[bits(1, access = RO)] 113 | read_only: bool, 114 | /// And write-only fields 115 | #[bits(1, access = WO)] 116 | write_only: bool, 117 | /// Padding 118 | #[bits(5)] 119 | __: u8, 120 | } 121 | 122 | /// A custom enum 123 | #[derive(Debug, PartialEq, Eq)] 124 | #[repr(u16)] 125 | enum CustomEnum { 126 | A = 0, 127 | B = 1, 128 | C = 2, 129 | } 130 | impl CustomEnum { 131 | // This has to be a const fn 132 | const fn into_bits(self) -> u16 { 133 | self as _ 134 | } 135 | const fn from_bits(value: u16) -> Self { 136 | match value { 137 | 0 => Self::A, 138 | 1 => Self::B, 139 | _ => Self::C, 140 | } 141 | } 142 | } 143 | 144 | // Usage: 145 | let mut val = MyBitfield::new() 146 | .with_int(3 << 15) 147 | .with_tiny(1) 148 | .with_negative(-3) 149 | .with_custom(CustomEnum::B) 150 | .with_public(2) 151 | // .with_read_only(true) <- Would not compile 152 | .with_write_only(false); 153 | 154 | println!("{val:?}"); 155 | let raw: u64 = val.into(); 156 | println!("{raw:b}"); 157 | 158 | assert_eq!(val.int(), 3 << 15); 159 | assert_eq!(val.flag(), true); 160 | assert_eq!(val.negative(), -3); 161 | assert_eq!(val.tiny(), 1); 162 | assert_eq!(val.custom(), CustomEnum::B); 163 | assert_eq!(val.public(), 2); 164 | assert_eq!(val.read_only(), false); 165 | 166 | // const members 167 | assert_eq!(MyBitfield::FLAG_BITS, 1); 168 | assert_eq!(MyBitfield::FLAG_OFFSET, 16); 169 | 170 | val.set_negative(1); 171 | assert_eq!(val.negative(), 1); 172 | ``` 173 | 174 | The macro generates three accessor functions for each field. 175 | Each accessor also inherits the documentation of its field. 176 | 177 | The signatures for `int` are: 178 | 179 | ```rust 180 | // generated struct 181 | struct MyBitfield(u64); 182 | impl MyBitfield { 183 | const fn new() -> Self { Self(0) } 184 | 185 | const INT_BITS: usize = 16; 186 | const INT_OFFSET: usize = 0; 187 | 188 | const fn int(&self) -> u16 { todo!() } 189 | 190 | const fn with_int(self, value: u16) -> Self { todo!() } 191 | const fn with_int_checked(self, value: u16) -> Result { todo!() } 192 | 193 | const fn set_int(&mut self, value: u16) { todo!() } 194 | const fn set_int_checked(&mut self, value: u16) -> Result<(), ()> { todo!() } 195 | 196 | // other field ... 197 | } 198 | // Also generates From, Into, Default, and Debug implementations... 199 | ``` 200 | 201 | > Hint: You can use the rust-analyzer "Expand macro recursively" action to view the generated code. 202 | 203 | ## Custom Types 204 | 205 | The macro supports any types that are convertible into the underlying bitfield type. 206 | This can be enums like in the following example or any other struct. 207 | 208 | The conversion and default values can be specified with the following `#[bits]` parameters: 209 | - `from`: Function converting from raw bits into the custom type, defaults to `::from_bits` 210 | - `into`: Function converting from the custom type into raw bits, defaults to `::into_bits` 211 | - `default`: Custom expression, defaults to calling `::from_bits(0)` 212 | 213 | 214 | ```rust 215 | use bitfield_struct::bitfield; 216 | 217 | #[bitfield(u16)] 218 | #[derive(PartialEq, Eq)] 219 | struct Bits { 220 | /// Supports any convertible type 221 | #[bits(8, default = CustomEnum::B, from = CustomEnum::my_from_bits)] 222 | custom: CustomEnum, 223 | /// And nested bitfields 224 | #[bits(8)] 225 | nested: Nested, 226 | } 227 | 228 | #[derive(Debug, PartialEq, Eq)] 229 | #[repr(u8)] 230 | enum CustomEnum { 231 | A = 0, 232 | B = 1, 233 | C = 2, 234 | } 235 | impl CustomEnum { 236 | // This has to be a const fn 237 | const fn into_bits(self) -> u8 { 238 | self as _ 239 | } 240 | const fn my_from_bits(value: u8) -> Self { 241 | match value { 242 | 0 => Self::A, 243 | 1 => Self::B, 244 | _ => Self::C, 245 | } 246 | } 247 | } 248 | 249 | /// Bitfields implement the conversion functions automatically 250 | #[bitfield(u8)] 251 | struct Nested { 252 | #[bits(4)] 253 | lo: u8, 254 | #[bits(4)] 255 | hi: u8, 256 | } 257 | ``` 258 | 259 | ## Field Order 260 | 261 | The optional `order` macro argument determines the layout of the bits, with the default being 262 | Lsb (least significant bit) first: 263 | 264 | ```rust 265 | use bitfield_struct::bitfield; 266 | 267 | #[bitfield(u8, order = Lsb)] 268 | struct MyLsbByte { 269 | /// The first field occupies the *least* significant bits 270 | #[bits(4)] 271 | kind: usize, 272 | system: bool, 273 | #[bits(2)] 274 | level: usize, 275 | present: bool 276 | } 277 | let my_byte_lsb = MyLsbByte::new() 278 | .with_kind(10) 279 | .with_system(false) 280 | .with_level(2) 281 | .with_present(true); 282 | 283 | // .- present 284 | // | .- level 285 | // | | .- system 286 | // | | | .- kind 287 | assert_eq!(my_byte_lsb.0, 0b1_10_0_1010); 288 | ``` 289 | 290 | The macro generates the reverse order when Msb (most significant bit) is specified: 291 | 292 | ```rust 293 | use bitfield_struct::bitfield; 294 | 295 | #[bitfield(u8, order = Msb)] 296 | struct MyMsbByte { 297 | /// The first field occupies the *most* significant bits 298 | #[bits(4)] 299 | kind: usize, 300 | system: bool, 301 | #[bits(2)] 302 | level: usize, 303 | present: bool 304 | } 305 | let my_byte_msb = MyMsbByte::new() 306 | .with_kind(10) 307 | .with_system(false) 308 | .with_level(2) 309 | .with_present(true); 310 | 311 | // .- kind 312 | // | .- system 313 | // | | .- level 314 | // | | | .- present 315 | assert_eq!(my_byte_msb.0, 0b1010_0_10_1); 316 | ``` 317 | 318 | ## Custom Representation and Endianness 319 | 320 | The macro supports custom types for the representation of the bitfield struct. 321 | This can be an endian-defining type like in the following examples (from [`endian-num`]) or any other struct that can be converted to and from the main bitfield type. 322 | 323 | The representation and its conversion functions can be specified with the following `#[bitfield]` parameters: 324 | - `repr` specifies the bitfield's representation in memory 325 | - `from` to specify a conversion function from repr to the bitfield's integer type 326 | - `into` to specify a conversion function from the bitfield's integer type to repr 327 | 328 | [`endian-num`]: https://docs.rs/endian-num 329 | 330 | This example has a little-endian byte order even on big-endian machines: 331 | 332 | ```rust 333 | use bitfield_struct::bitfield; 334 | use endian_num::le16; 335 | 336 | #[bitfield(u16, repr = le16, from = le16::from_ne, into = le16::to_ne)] 337 | struct MyLeBitfield { 338 | #[bits(4)] 339 | first_nibble: u8, 340 | #[bits(12)] 341 | other: u16, 342 | } 343 | 344 | let my_be_bitfield = MyLeBitfield::new() 345 | .with_first_nibble(0x1) 346 | .with_other(0x234); 347 | 348 | assert_eq!(my_be_bitfield.into_bits().to_le_bytes(), [0x41, 0x23]); 349 | ``` 350 | 351 | This example has a big-endian byte order even on little-endian machines: 352 | 353 | ```rust 354 | use bitfield_struct::bitfield; 355 | use endian_num::be16; 356 | 357 | #[bitfield(u16, repr = be16, from = be16::from_ne, into = be16::to_ne)] 358 | struct MyBeBitfield { 359 | #[bits(4)] 360 | first_nibble: u8, 361 | #[bits(12)] 362 | other: u16, 363 | } 364 | 365 | let my_be_bitfield = MyBeBitfield::new() 366 | .with_first_nibble(0x1) 367 | .with_other(0x234); 368 | 369 | assert_eq!(my_be_bitfield.into_bits().to_be_bytes(), [0x23, 0x41]); 370 | ``` 371 | 372 | ## Automatic Trait Implementations 373 | 374 | ### `Clone`, `Copy` 375 | By default, this macro derives `Clone` and `Copy`. 376 | 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). 377 | In this case, you can provide your own implementations for `Clone` and `Copy`. 378 | 379 | ```rust 380 | use bitfield_struct::bitfield; 381 | 382 | #[bitfield(u64, clone = false)] 383 | struct CustomClone { 384 | data: u64 385 | } 386 | 387 | impl Clone for CustomClone { 388 | fn clone(&self) -> Self { 389 | Self::new().with_data(self.data()) 390 | } 391 | } 392 | 393 | // optionally: 394 | impl Copy for CustomClone {} 395 | ``` 396 | 397 | ### `fmt::Debug`, `Default` 398 | By default, it also generates suitable `fmt::Debug` and `Default` implementations similar to the ones created for normal structs by `#[derive(Debug, Default)]`. 399 | You can disable this with the extra `debug` and `default` arguments. 400 | 401 | ```rust 402 | use std::fmt::{Debug, Formatter, Result}; 403 | use bitfield_struct::bitfield; 404 | 405 | #[bitfield(u64, debug = false, default = false)] 406 | struct CustomDebug { 407 | data: u64 408 | } 409 | impl Debug for CustomDebug { 410 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 411 | write!(f, "0x{:x}", self.data()) 412 | } 413 | } 414 | impl Default for CustomDebug { 415 | fn default() -> Self { 416 | Self(123) 417 | } 418 | } 419 | 420 | let val = CustomDebug::default(); 421 | println!("{val:?}") 422 | ``` 423 | 424 | ### Support for `defmt::Format` 425 | 426 | This macro can automatically implement a `defmt::Format` that mirrors the default `fmt::Debug` implementation by passing the extra `defmt` argument. 427 | This implementation requires the defmt crate to be available as `defmt`, and has the same rules and caveats as `#[derive(defmt::Format)]`. 428 | 429 | ```rust 430 | use bitfield_struct::bitfield; 431 | 432 | #[bitfield(u64, defmt = true)] 433 | struct DefmtExample { 434 | data: u64 435 | } 436 | ``` 437 | 438 | ### Support for `std::hash::Hash` 439 | 440 | This macro can also implement `Hash`, which ignores any padding when hashing. 441 | 442 | ```rust 443 | use bitfield_struct::bitfield; 444 | 445 | #[bitfield(u64, hash = true)] 446 | struct HashExample { 447 | __ignored: u32, 448 | data: u32, 449 | } 450 | ``` 451 | 452 | ### Conditionally Enable `new`/`Clone`/`Debug`/`Default`/`defmt::Format`/`Hash` 453 | 454 | Instead of booleans, you can specify `cfg(...)` attributes for `new`, `clone`, `debug`, `default`, `defmt` and `hash`: 455 | 456 | ```rust 457 | use bitfield_struct::bitfield; 458 | 459 | #[bitfield(u64, debug = cfg(test), default = cfg(feature = "foo"))] 460 | struct CustomDebug { 461 | data: u64 462 | } 463 | ``` 464 | -------------------------------------------------------------------------------- /src/attr.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, TokenStream}; 2 | use syn::parse::{Parse, ParseStream}; 3 | use syn::spanned::Spanned; 4 | use syn::Token; 5 | 6 | use crate::{type_info, TypeClass}; 7 | 8 | use super::s_err; 9 | 10 | 11 | /// The bitfield macro parameters 12 | pub struct Params { 13 | pub ty: syn::Type, 14 | pub repr: syn::Type, 15 | pub into: Option, 16 | pub from: Option, 17 | pub bits: usize, 18 | pub new: Enable, 19 | pub clone: Enable, 20 | pub debug: Enable, 21 | pub defmt: Enable, 22 | pub default: Enable, 23 | pub hash: Enable, 24 | pub order: Order, 25 | pub conversion: bool, 26 | } 27 | 28 | impl Parse for Params { 29 | fn parse(input: ParseStream) -> syn::Result { 30 | let Ok(ty) = syn::Type::parse(input) else { 31 | return Err(s_err(input.span(), "unknown type")); 32 | }; 33 | let (class, bits) = type_info(&ty); 34 | if class != TypeClass::UInt { 35 | return Err(s_err(input.span(), "unsupported type")); 36 | } 37 | 38 | let mut ret = Params { 39 | repr: ty.clone(), 40 | ty, 41 | into: None, 42 | from: None, 43 | bits, 44 | new: Enable::Yes, 45 | clone: Enable::Yes, 46 | debug: Enable::Yes, 47 | defmt: Enable::No, 48 | default: Enable::Yes, 49 | hash: Enable::No, 50 | order: Order::Lsb, 51 | conversion: true, 52 | }; 53 | 54 | // try parse additional args 55 | while ::parse(input).is_ok() { 56 | let ident = Ident::parse(input)?; 57 | ::parse(input)?; 58 | match ident.to_string().as_str() { 59 | "repr" => { 60 | ret.repr = input.parse()?; 61 | } 62 | "from" => { 63 | ret.from = Some(input.parse()?); 64 | } 65 | "into" => { 66 | ret.into = Some(input.parse()?); 67 | } 68 | "debug" => { 69 | ret.debug = input.parse()?; 70 | } 71 | "defmt" => { 72 | ret.defmt = input.parse()?; 73 | } 74 | "new" => { 75 | ret.new = input.parse()?; 76 | } 77 | "clone" => { 78 | ret.clone = input.parse()?; 79 | } 80 | "default" => { 81 | ret.default = input.parse()?; 82 | } 83 | "hash" => { 84 | ret.hash = input.parse()?; 85 | } 86 | "order" => { 87 | ret.order = match syn::Ident::parse(input)?.to_string().as_str() { 88 | "Msb" | "msb" => Order::Msb, 89 | "Lsb" | "lsb" => Order::Lsb, 90 | _ => return Err(s_err(ident.span(), "unknown value for order")), 91 | }; 92 | } 93 | "conversion" => { 94 | ret.conversion = syn::LitBool::parse(input)?.value; 95 | } 96 | _ => return Err(s_err(ident.span(), "unknown argument")), 97 | }; 98 | } 99 | 100 | if ret.repr != ret.ty && (ret.from.is_none() || ret.into.is_none()) { 101 | return Err(s_err( 102 | input.span(), 103 | "`repr` requires both `from` and `into`", 104 | )); 105 | } 106 | 107 | Ok(ret) 108 | } 109 | } 110 | 111 | 112 | /// The bits attribute of the fields of a bitfield struct 113 | pub struct BitsAttr { 114 | pub bits: Option, 115 | pub default: Option, 116 | pub into: Option, 117 | pub from: Option, 118 | pub access: Option, 119 | } 120 | 121 | impl Parse for BitsAttr { 122 | fn parse(input: ParseStream) -> syn::Result { 123 | let mut attr = Self { 124 | bits: None, 125 | default: None, 126 | into: None, 127 | from: None, 128 | access: None, 129 | }; 130 | if let Ok(bits) = syn::LitInt::parse(input) { 131 | attr.bits = Some(bits.base10_parse()?); 132 | if !input.is_empty() { 133 | ::parse(input)?; 134 | } 135 | } 136 | // parse remainder 137 | if !input.is_empty() { 138 | loop { 139 | let ident = syn::Ident::parse(input)?; 140 | 141 | ::parse(input)?; 142 | 143 | if ident == "default" { 144 | attr.default = Some(input.parse()?); 145 | } else if ident == "into" { 146 | attr.into = Some(input.parse()?); 147 | } else if ident == "from" { 148 | attr.from = Some(input.parse()?); 149 | } else if ident == "access" { 150 | attr.access = Some(input.parse()?); 151 | } 152 | 153 | if input.is_empty() { 154 | break; 155 | } 156 | 157 | ::parse(input)?; 158 | } 159 | } 160 | Ok(attr) 161 | } 162 | } 163 | 164 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 165 | pub enum Access { 166 | ReadWrite, 167 | ReadOnly, 168 | WriteOnly, 169 | None, 170 | } 171 | 172 | impl Parse for Access { 173 | fn parse(input: ParseStream) -> syn::Result { 174 | let mode = input.parse::()?; 175 | 176 | if mode == "RW" { 177 | Ok(Self::ReadWrite) 178 | } else if mode == "RO" { 179 | Ok(Self::ReadOnly) 180 | } else if mode == "WO" { 181 | Ok(Self::WriteOnly) 182 | } else if mode == "None" { 183 | Ok(Self::None) 184 | } else { 185 | Err(s_err( 186 | mode.span(), 187 | "Invalid access mode, only RW, RO, WO, or None are allowed", 188 | )) 189 | } 190 | } 191 | } 192 | 193 | #[derive(Clone, Copy, PartialEq)] 194 | pub enum Order { 195 | Lsb, 196 | Msb, 197 | } 198 | 199 | #[derive(Debug, Clone)] 200 | pub enum Enable { 201 | No, 202 | Yes, 203 | Cfg(TokenStream), 204 | } 205 | impl Enable { 206 | pub fn cfg(self) -> Option> { 207 | match self { 208 | Enable::No => None, 209 | Enable::Yes => Some(None), 210 | Enable::Cfg(c) => Some(Some(c)), 211 | } 212 | } 213 | } 214 | impl Parse for Enable { 215 | fn parse(input: ParseStream) -> syn::Result { 216 | Ok(if let Ok(lit_bool) = syn::LitBool::parse(input) { 217 | if lit_bool.value { 218 | Enable::Yes 219 | } else { 220 | Enable::No 221 | } 222 | } else { 223 | let meta = syn::MetaList::parse(input)?; 224 | if matches!(meta.delimiter, syn::MacroDelimiter::Paren(_)) && meta.path.is_ident("cfg") 225 | { 226 | Enable::Cfg(meta.tokens) 227 | } else { 228 | return Err(s_err(meta.span(), "Only `cfg` attributes are allowed")); 229 | } 230 | }) 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Generate docs from readme 2 | #![doc = include_str!("../README.md")] 3 | #![warn(clippy::unwrap_used)] 4 | 5 | use proc_macro as pc; 6 | use proc_macro2::{Ident, TokenStream}; 7 | use quote::{format_ident, quote, ToTokens}; 8 | use std::{fmt, stringify}; 9 | use syn::spanned::Spanned; 10 | 11 | mod attr; 12 | use attr::*; 13 | mod traits; 14 | 15 | fn s_err(span: proc_macro2::Span, msg: impl fmt::Display) -> syn::Error { 16 | syn::Error::new(span, msg) 17 | } 18 | 19 | /// Creates a bitfield for this struct. 20 | /// 21 | /// The arguments first, have to begin with the integer type of the bitfield: 22 | /// For example: `#[bitfield(u64)]`. 23 | /// 24 | /// It can contain the following additional parameters, like the `debug` argument 25 | /// for disabling the `Debug` trait generation (`#[bitfield(u64, debug = false)]`). 26 | /// 27 | /// Parameters of the `bitfield` attribute: 28 | /// - the bitfield integer type (required) 29 | /// - `repr` specifies the bitfield's representation in memory 30 | /// - `from` to specify a conversion function from repr to the bitfield's integer type 31 | /// - `into` to specify a conversion function from the bitfield's integer type to repr 32 | /// - `new` to disable the `new` function generation 33 | /// - `clone` to disable the `Clone` trait generation 34 | /// - `debug` to disable the `Debug` trait generation 35 | /// - `defmt` to enable the `defmt::Format` trait generation 36 | /// - `default` to disable the `Default` trait generation 37 | /// - `hash` to generate the `Hash` trait 38 | /// - `order` to specify the bit order (Lsb, Msb) 39 | /// - `conversion` to disable the generation of `into_bits` and `from_bits` 40 | /// 41 | /// > For `new`, `clone`, `debug`, `defmt` or `default`, you can either use booleans 42 | /// > (`#[bitfield(u8, debug = false)]`) or cfg attributes 43 | /// > (`#[bitfield(u8, debug = cfg(test))]`) to enable/disable them. 44 | /// 45 | /// Parameters of the `bits` attribute (for fields): 46 | /// - the number of bits 47 | /// - `access` to specify the access mode (RW, RO, WO, None) 48 | /// - `default` to set a default value 49 | /// - `into` to specify a conversion function from the field type to the bitfield type 50 | /// - `from` to specify a conversion function from the bitfield type to the field type 51 | #[proc_macro_attribute] 52 | pub fn bitfield(args: pc::TokenStream, input: pc::TokenStream) -> pc::TokenStream { 53 | match bitfield_inner(args.into(), input.into()) { 54 | Ok(result) => result.into(), 55 | Err(e) => e.into_compile_error().into(), 56 | } 57 | } 58 | 59 | fn bitfield_inner(args: TokenStream, input: TokenStream) -> syn::Result { 60 | let input = syn::parse2::(input)?; 61 | let Params { 62 | ty, 63 | repr, 64 | into, 65 | from, 66 | bits, 67 | new, 68 | clone, 69 | debug, 70 | defmt, 71 | default, 72 | hash, 73 | order, 74 | conversion, 75 | } = syn::parse2(args)?; 76 | 77 | let span = input.fields.span(); 78 | let name = input.ident; 79 | let vis = input.vis; 80 | let attrs: TokenStream = input.attrs.iter().map(ToTokens::to_token_stream).collect(); 81 | let derive = match clone { 82 | Enable::No => None, 83 | Enable::Yes => Some(quote! { #[derive(Copy, Clone)] }), 84 | Enable::Cfg(cfg) => Some(quote! { #[cfg_attr(#cfg, derive(Copy, Clone))] }), 85 | }; 86 | 87 | let syn::Fields::Named(fields) = input.fields else { 88 | return Err(s_err(span, "only named fields are supported")); 89 | }; 90 | 91 | let mut offset = 0; 92 | let mut members = Vec::with_capacity(fields.named.len()); 93 | for field in fields.named { 94 | let f = Member::new( 95 | ty.clone(), 96 | bits, 97 | into.clone(), 98 | from.clone(), 99 | field, 100 | offset, 101 | order, 102 | )?; 103 | offset += f.bits; 104 | members.push(f); 105 | } 106 | 107 | if offset < bits { 108 | return Err(s_err( 109 | span, 110 | format!( 111 | "The bitfield size ({bits} bits) has to be equal to the sum of its fields ({offset} bits). \ 112 | You might have to add padding (a {} bits large field prefixed with \"_\").", 113 | bits - offset 114 | ), 115 | )); 116 | } 117 | if offset > bits { 118 | return Err(s_err( 119 | span, 120 | format!( 121 | "The size of the fields ({offset} bits) is larger than the type ({bits} bits)." 122 | ), 123 | )); 124 | } 125 | 126 | let mut impl_debug = TokenStream::new(); 127 | if let Some(cfg) = debug.cfg() { 128 | impl_debug.extend(traits::debug(&name, &members, cfg)); 129 | } 130 | if let Some(cfg) = defmt.cfg() { 131 | impl_debug.extend(traits::defmt(&name, &members, cfg)); 132 | } 133 | if let Some(cfg) = hash.cfg() { 134 | impl_debug.extend(traits::hash(&name, &members, cfg)); 135 | } 136 | 137 | let defaults = members.iter().map(Member::default).collect::>(); 138 | 139 | let impl_new = new.cfg().map(|cfg| { 140 | let attr = cfg.map(|cfg| quote!(#[cfg(#cfg)])); 141 | quote! { 142 | /// Creates a new default initialized bitfield. 143 | #attr 144 | #vis const fn new() -> Self { 145 | let mut this = Self(#from(0)); 146 | #( #defaults )* 147 | this 148 | } 149 | } 150 | }); 151 | 152 | let impl_default = default.cfg().map(|cfg| { 153 | let attr = cfg.map(|cfg| quote!(#[cfg(#cfg)])); 154 | quote! { 155 | #attr 156 | impl Default for #name { 157 | fn default() -> Self { 158 | let mut this = Self(#from(0)); 159 | #( #defaults )* 160 | this 161 | } 162 | } 163 | } 164 | }); 165 | 166 | let conversion = conversion.then(|| { 167 | quote! { 168 | /// Convert from bits. 169 | #vis const fn from_bits(bits: #repr) -> Self { 170 | Self(bits) 171 | } 172 | /// Convert into bits. 173 | #vis const fn into_bits(self) -> #repr { 174 | self.0 175 | } 176 | } 177 | }); 178 | 179 | Ok(quote! { 180 | #attrs 181 | #derive 182 | #[repr(transparent)] 183 | #vis struct #name(#repr); 184 | 185 | #[allow(unused_comparisons)] 186 | #[allow(clippy::unnecessary_cast)] 187 | #[allow(clippy::assign_op_pattern)] 188 | impl #name { 189 | #impl_new 190 | 191 | #conversion 192 | 193 | #( #members )* 194 | } 195 | 196 | #[allow(unused_comparisons)] 197 | #[allow(clippy::unnecessary_cast)] 198 | #[allow(clippy::assign_op_pattern)] 199 | #impl_default 200 | 201 | impl From<#repr> for #name { 202 | fn from(v: #repr) -> Self { 203 | Self(v) 204 | } 205 | } 206 | impl From<#name> for #repr { 207 | fn from(v: #name) -> #repr { 208 | v.0 209 | } 210 | } 211 | 212 | #impl_debug 213 | }) 214 | } 215 | 216 | /// Represents a member where accessor functions should be generated for. 217 | struct Member { 218 | offset: usize, 219 | bits: usize, 220 | base_ty: syn::Type, 221 | repr_into: Option, 222 | repr_from: Option, 223 | default: TokenStream, 224 | inner: Option, 225 | } 226 | 227 | struct MemberInner { 228 | ident: syn::Ident, 229 | ty: syn::Type, 230 | attrs: Vec, 231 | vis: syn::Visibility, 232 | into: TokenStream, 233 | from: TokenStream, 234 | } 235 | 236 | impl Member { 237 | fn new( 238 | base_ty: syn::Type, 239 | base_bits: usize, 240 | repr_into: Option, 241 | repr_from: Option, 242 | field: syn::Field, 243 | offset: usize, 244 | order: Order, 245 | ) -> syn::Result { 246 | let span = field.span(); 247 | 248 | let syn::Field { 249 | mut attrs, 250 | vis, 251 | ident, 252 | ty, 253 | .. 254 | } = field; 255 | 256 | let ident = ident.ok_or_else(|| s_err(span, "Not supported"))?; 257 | let ignore = ident.to_string().starts_with('_'); 258 | 259 | let Field { 260 | bits, 261 | ty, 262 | mut default, 263 | into, 264 | from, 265 | access, 266 | } = parse_field(&base_ty, &attrs, &ty, ignore)?; 267 | 268 | let ignore = ignore || access == Access::None; 269 | 270 | // compute the offset 271 | let offset = if order == Order::Lsb { 272 | offset 273 | } else { 274 | base_bits - offset - bits 275 | }; 276 | 277 | if bits > 0 && !ignore { 278 | // overflow check 279 | if offset + bits > base_bits { 280 | return Err(s_err( 281 | ty.span(), 282 | "The sum of the members overflows the type size", 283 | )); 284 | }; 285 | 286 | // clear conversion expr if not needed 287 | let (from, into) = match access { 288 | Access::ReadWrite => (from, into), 289 | Access::ReadOnly => (from, quote!()), 290 | Access::WriteOnly => (quote!(), into), 291 | Access::None => (quote!(), quote!()), 292 | }; 293 | 294 | // auto-conversion from zero 295 | if default.is_empty() { 296 | if !from.is_empty() { 297 | default = quote!({ let this = 0; #from }); 298 | } else { 299 | default = quote!(0); 300 | } 301 | } 302 | 303 | // remove our attribute 304 | attrs.retain(|a| !a.path().is_ident("bits")); 305 | 306 | Ok(Self { 307 | offset, 308 | bits, 309 | base_ty, 310 | repr_into, 311 | repr_from, 312 | default, 313 | inner: Some(MemberInner { 314 | ident, 315 | ty, 316 | attrs, 317 | vis, 318 | into, 319 | from, 320 | }), 321 | }) 322 | } else { 323 | if default.is_empty() { 324 | default = quote!(0); 325 | } 326 | 327 | Ok(Self { 328 | offset, 329 | bits, 330 | base_ty, 331 | repr_into, 332 | repr_from, 333 | default, 334 | inner: None, 335 | }) 336 | } 337 | } 338 | 339 | fn default(&self) -> TokenStream { 340 | let default = &self.default; 341 | 342 | if let Some(inner) = &self.inner { 343 | if !inner.into.is_empty() { 344 | let ident = &inner.ident; 345 | let with_ident = format_ident!("with_{}", ident); 346 | return quote!(this = this.#with_ident(#default);); 347 | } 348 | } 349 | 350 | // fallback when there is no setter 351 | let offset = self.offset; 352 | let base_ty = &self.base_ty; 353 | let repr_into = &self.repr_into; 354 | let repr_from = &self.repr_from; 355 | let bits = self.bits as u32; 356 | 357 | quote! { 358 | let mask = #base_ty::MAX >> (#base_ty::BITS - #bits); 359 | this.0 = #repr_from(#repr_into(this.0) | (((#default as #base_ty) & mask) << #offset)); 360 | } 361 | } 362 | } 363 | 364 | impl ToTokens for Member { 365 | fn to_tokens(&self, tokens: &mut TokenStream) { 366 | let Self { 367 | offset, 368 | bits, 369 | base_ty, 370 | repr_into, 371 | repr_from, 372 | default: _, 373 | inner: 374 | Some(MemberInner { 375 | ident, 376 | ty, 377 | attrs, 378 | vis, 379 | into, 380 | from, 381 | }), 382 | } = self 383 | else { 384 | return Default::default(); 385 | }; 386 | 387 | let ident_str = ident.to_string().to_uppercase(); 388 | let ident_upper = Ident::new( 389 | ident_str.strip_prefix("R#").unwrap_or(&ident_str), 390 | ident.span(), 391 | ); 392 | 393 | let with_ident = format_ident!("with_{}", ident); 394 | let with_ident_checked = format_ident!("with_{}_checked", ident); 395 | let set_ident = format_ident!("set_{}", ident); 396 | let set_ident_checked = format_ident!("set_{}_checked", ident); 397 | let bits_ident = format_ident!("{}_BITS", ident_upper); 398 | let offset_ident = format_ident!("{}_OFFSET", ident_upper); 399 | 400 | let location = format!("\n\nBits: {offset}..{}", offset + bits); 401 | 402 | let doc: TokenStream = attrs 403 | .iter() 404 | .filter(|a| !a.path().is_ident("bits")) 405 | .map(ToTokens::to_token_stream) 406 | .collect(); 407 | 408 | tokens.extend(quote! { 409 | const #bits_ident: usize = #bits; 410 | const #offset_ident: usize = #offset; 411 | }); 412 | 413 | if !from.is_empty() { 414 | tokens.extend(quote! { 415 | #doc 416 | #[doc = #location] 417 | #vis const fn #ident(&self) -> #ty { 418 | let mask = #base_ty::MAX >> (#base_ty::BITS - Self::#bits_ident as u32); 419 | let this = (#repr_into(self.0) >> Self::#offset_ident) & mask; 420 | #from 421 | } 422 | }); 423 | } 424 | 425 | if !into.is_empty() { 426 | let (class, _) = type_info(ty); 427 | // generate static strings for the error messages (due to const) 428 | let bounds = if class == TypeClass::SInt { 429 | let min = -((u128::MAX >> (128 - (bits - 1))) as i128) - 1; 430 | let max = u128::MAX >> (128 - (bits - 1)); 431 | format!("[{}, {}]", min, max) 432 | } else { 433 | format!("[0, {}]", u128::MAX >> (128 - bits)) 434 | }; 435 | let bounds_error = format!("value out of bounds {bounds}"); 436 | 437 | tokens.extend(quote! { 438 | #doc 439 | #[doc = #location] 440 | #vis const fn #with_ident_checked(mut self, value: #ty) -> core::result::Result { 441 | match self.#set_ident_checked(value) { 442 | Ok(_) => Ok(self), 443 | Err(_) => Err(()), 444 | } 445 | } 446 | #doc 447 | #[doc = #location] 448 | #[cfg_attr(debug_assertions, track_caller)] 449 | #vis const fn #with_ident(mut self, value: #ty) -> Self { 450 | self.#set_ident(value); 451 | self 452 | } 453 | 454 | #doc 455 | #[doc = #location] 456 | #vis const fn #set_ident(&mut self, value: #ty) { 457 | if let Err(_) = self.#set_ident_checked(value) { 458 | panic!(#bounds_error) 459 | } 460 | } 461 | #doc 462 | #[doc = #location] 463 | #vis const fn #set_ident_checked(&mut self, value: #ty) -> core::result::Result<(), ()> { 464 | let this = value; 465 | let value: #base_ty = #into; 466 | let mask = #base_ty::MAX >> (#base_ty::BITS - Self::#bits_ident as u32); 467 | if value > mask { 468 | return Err(()); 469 | } 470 | let bits = #repr_into(self.0) & !(mask << Self::#offset_ident) | (value & mask) << Self::#offset_ident; 471 | self.0 = #repr_from(bits); 472 | Ok(()) 473 | } 474 | }); 475 | } 476 | } 477 | } 478 | 479 | /// Distinguish between different types for code generation. 480 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 481 | enum TypeClass { 482 | /// Booleans with 1 bit size 483 | Bool, 484 | /// Unsigned ints with fixes sizes: u8, u64, ... 485 | UInt, 486 | /// Signed ints with fixes sizes: i8, i64, ... 487 | SInt, 488 | /// Custom types 489 | Other, 490 | } 491 | 492 | /// Field information, including the `bits` attribute 493 | struct Field { 494 | bits: usize, 495 | ty: syn::Type, 496 | 497 | default: TokenStream, 498 | into: TokenStream, 499 | from: TokenStream, 500 | 501 | access: Access, 502 | } 503 | 504 | /// Parses the `bits` attribute that allows specifying a custom number of bits. 505 | fn parse_field( 506 | base_ty: &syn::Type, 507 | attrs: &[syn::Attribute], 508 | ty: &syn::Type, 509 | ignore: bool, 510 | ) -> syn::Result { 511 | fn malformed(mut e: syn::Error, attr: &syn::Attribute) -> syn::Error { 512 | e.combine(s_err(attr.span(), "malformed #[bits] attribute")); 513 | e 514 | } 515 | 516 | let access = if ignore { 517 | Access::None 518 | } else { 519 | Access::ReadWrite 520 | }; 521 | 522 | // Defaults for the different types 523 | let (class, ty_bits) = type_info(ty); 524 | let mut ret = match class { 525 | TypeClass::Bool => Field { 526 | bits: ty_bits, 527 | ty: ty.clone(), 528 | default: quote!(false), 529 | into: quote!(this as _), 530 | from: quote!(this != 0), 531 | access, 532 | }, 533 | TypeClass::SInt => Field { 534 | bits: ty_bits, 535 | ty: ty.clone(), 536 | default: quote!(0), 537 | into: quote!(), 538 | from: quote!(), 539 | access, 540 | }, 541 | TypeClass::UInt => Field { 542 | bits: ty_bits, 543 | ty: ty.clone(), 544 | default: quote!(0), 545 | into: quote!(this as _), 546 | from: quote!(this as _), 547 | access, 548 | }, 549 | TypeClass::Other => Field { 550 | bits: ty_bits, 551 | ty: ty.clone(), 552 | default: quote!(), 553 | into: quote!(<#ty>::into_bits(this) as _), 554 | from: quote!(<#ty>::from_bits(this as _)), 555 | access, 556 | }, 557 | }; 558 | 559 | // Find and parse the bits attribute 560 | for attr in attrs { 561 | let syn::Attribute { 562 | style: syn::AttrStyle::Outer, 563 | meta: syn::Meta::List(syn::MetaList { path, tokens, .. }), 564 | .. 565 | } = attr 566 | else { 567 | continue; 568 | }; 569 | if !path.is_ident("bits") { 570 | continue; 571 | } 572 | 573 | let span = tokens.span(); 574 | let BitsAttr { 575 | bits, 576 | default, 577 | into, 578 | from, 579 | access, 580 | } = syn::parse2(tokens.clone()).map_err(|e| malformed(e, attr))?; 581 | 582 | // bit size 583 | if let Some(bits) = bits { 584 | if bits == 0 { 585 | return Err(s_err(span, "bits cannot bit 0")); 586 | } 587 | if ty_bits != 0 && bits > ty_bits { 588 | return Err(s_err(span, "overflowing field type")); 589 | } 590 | ret.bits = bits; 591 | } 592 | 593 | // read/write access 594 | if let Some(access) = access { 595 | if ignore { 596 | return Err(s_err( 597 | tokens.span(), 598 | "'access' is not supported for padding", 599 | )); 600 | } 601 | ret.access = access; 602 | } 603 | 604 | // conversion 605 | if let Some(into) = into { 606 | if ret.access == Access::None { 607 | return Err(s_err(into.span(), "'into' is not supported on padding")); 608 | } 609 | ret.into = quote!(#into(this) as _); 610 | } 611 | if let Some(from) = from { 612 | if ret.access == Access::None { 613 | return Err(s_err(from.span(), "'from' is not supported on padding")); 614 | } 615 | ret.from = quote!(#from(this as _)); 616 | } 617 | if let Some(default) = default { 618 | ret.default = default.into_token_stream(); 619 | } 620 | } 621 | 622 | if ret.bits == 0 { 623 | return Err(s_err( 624 | ty.span(), 625 | "Custom types and isize/usize require an explicit bit size", 626 | )); 627 | } 628 | 629 | // Signed integers need some special handling... 630 | if !ignore && ret.access != Access::None && class == TypeClass::SInt { 631 | let bits = ret.bits as u32; 632 | if ret.into.is_empty() { 633 | // Bounds check and remove leading ones from negative values 634 | ret.into = quote! {{ 635 | let m = #ty::MIN >> (#ty::BITS - #bits); 636 | if !(m <= this && this <= -(m + 1)) { 637 | return Err(()) 638 | } 639 | let mask = #base_ty::MAX >> (#base_ty::BITS - #bits); 640 | (this as #base_ty & mask) 641 | }}; 642 | } 643 | if ret.from.is_empty() { 644 | // Sign extend negative values 645 | ret.from = quote! {{ 646 | let shift = #ty::BITS - #bits; 647 | ((this as #ty) << shift) >> shift 648 | }}; 649 | } 650 | } 651 | 652 | Ok(ret) 653 | } 654 | 655 | 656 | /// Returns the number of bits for a given type 657 | fn type_info(ty: &syn::Type) -> (TypeClass, usize) { 658 | let syn::Type::Path(syn::TypePath { path, .. }) = ty else { 659 | return (TypeClass::Other, 0); 660 | }; 661 | let Some(ident) = path.get_ident() else { 662 | return (TypeClass::Other, 0); 663 | }; 664 | if ident == "bool" { 665 | return (TypeClass::Bool, 1); 666 | } 667 | if ident == "isize" || ident == "usize" { 668 | return (TypeClass::UInt, 0); // they have architecture dependend sizes 669 | } 670 | macro_rules! integer { 671 | ($ident:ident => $($uint:ident),* ; $($sint:ident),*) => { 672 | match ident { 673 | $(_ if ident == stringify!($uint) => (TypeClass::UInt, $uint::BITS as _),)* 674 | $(_ if ident == stringify!($sint) => (TypeClass::SInt, $sint::BITS as _),)* 675 | _ => (TypeClass::Other, 0) 676 | } 677 | }; 678 | } 679 | integer!(ident => u8, u16, u32, u64, u128 ; i8, i16, i32, i64, i128) 680 | } 681 | 682 | #[cfg(test)] 683 | mod test { 684 | #![allow(clippy::unwrap_used)] 685 | use quote::quote; 686 | 687 | use crate::{Access, BitsAttr, Enable, Order, Params}; 688 | 689 | #[test] 690 | fn parse_args() { 691 | let args = quote!(u64); 692 | let params = syn::parse2::(args).unwrap(); 693 | assert_eq!(params.bits, u64::BITS as usize); 694 | assert!(matches!(params.debug, Enable::Yes)); 695 | assert!(matches!(params.defmt, Enable::No)); 696 | 697 | let args = quote!(u32, debug = false); 698 | let params = syn::parse2::(args).unwrap(); 699 | assert_eq!(params.bits, u32::BITS as usize); 700 | assert!(matches!(params.debug, Enable::No)); 701 | assert!(matches!(params.defmt, Enable::No)); 702 | 703 | let args = quote!(u32, defmt = true); 704 | let params = syn::parse2::(args).unwrap(); 705 | assert_eq!(params.bits, u32::BITS as usize); 706 | assert!(matches!(params.debug, Enable::Yes)); 707 | assert!(matches!(params.defmt, Enable::Yes)); 708 | 709 | let args = quote!(u32, defmt = cfg(test), debug = cfg(feature = "foo")); 710 | let params = syn::parse2::(args).unwrap(); 711 | assert_eq!(params.bits, u32::BITS as usize); 712 | assert!(matches!(params.debug, Enable::Cfg(_))); 713 | assert!(matches!(params.defmt, Enable::Cfg(_))); 714 | 715 | let args = quote!(u32, order = Msb); 716 | let params = syn::parse2::(args).unwrap(); 717 | assert!(params.bits == u32::BITS as usize && params.order == Order::Msb); 718 | } 719 | 720 | #[test] 721 | fn parse_bits() { 722 | let args = quote!(8); 723 | let attr = syn::parse2::(args).unwrap(); 724 | assert_eq!(attr.bits, Some(8)); 725 | assert!(attr.default.is_none()); 726 | assert!(attr.into.is_none()); 727 | assert!(attr.from.is_none()); 728 | assert!(attr.access.is_none()); 729 | 730 | let args = quote!(8, default = 8, access = RW); 731 | let attr = syn::parse2::(args).unwrap(); 732 | assert_eq!(attr.bits, Some(8)); 733 | assert!(attr.default.is_some()); 734 | assert!(attr.into.is_none()); 735 | assert!(attr.from.is_none()); 736 | assert_eq!(attr.access, Some(Access::ReadWrite)); 737 | 738 | let args = quote!(access = RO); 739 | let attr = syn::parse2::(args).unwrap(); 740 | assert_eq!(attr.bits, None); 741 | assert!(attr.default.is_none()); 742 | assert!(attr.into.is_none()); 743 | assert!(attr.from.is_none()); 744 | assert_eq!(attr.access, Some(Access::ReadOnly)); 745 | 746 | let args = quote!(default = 8, access = WO); 747 | let attr = syn::parse2::(args).unwrap(); 748 | assert_eq!(attr.bits, None); 749 | assert!(attr.default.is_some()); 750 | assert!(attr.into.is_none()); 751 | assert!(attr.from.is_none()); 752 | assert_eq!(attr.access, Some(Access::WriteOnly)); 753 | 754 | let args = quote!( 755 | 3, 756 | into = into_something, 757 | default = 1, 758 | from = from_something, 759 | access = None 760 | ); 761 | let attr = syn::parse2::(args).unwrap(); 762 | assert_eq!(attr.bits, Some(3)); 763 | assert!(attr.default.is_some()); 764 | assert!(attr.into.is_some()); 765 | assert!(attr.from.is_some()); 766 | assert_eq!(attr.access, Some(Access::None)); 767 | } 768 | 769 | #[test] 770 | fn parse_access_mode() { 771 | let args = quote!(RW); 772 | let mode = syn::parse2::(args).unwrap(); 773 | assert_eq!(mode, Access::ReadWrite); 774 | 775 | let args = quote!(RO); 776 | let mode = syn::parse2::(args).unwrap(); 777 | assert_eq!(mode, Access::ReadOnly); 778 | 779 | let args = quote!(WO); 780 | let mode = syn::parse2::(args).unwrap(); 781 | assert_eq!(mode, Access::WriteOnly); 782 | 783 | let args = quote!(None); 784 | let mode = syn::parse2::(args).unwrap(); 785 | assert_eq!(mode, Access::None); 786 | 787 | let args = quote!(garbage); 788 | let mode = syn::parse2::(args); 789 | assert!(mode.is_err()); 790 | } 791 | } 792 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | use super::Member; 2 | use proc_macro2::TokenStream; 3 | use quote::quote; 4 | 5 | /// Implements the `core::clone::Clone` trait for the given bitfield struct. 6 | pub fn debug( 7 | name: &syn::Ident, 8 | members: &[Member], 9 | cfg: Option, 10 | ) -> TokenStream { 11 | let fields = members.iter().filter_map(|m| { 12 | let inner = m.inner.as_ref()?; 13 | if inner.from.is_empty() { 14 | return None; 15 | } 16 | 17 | let ident = &inner.ident; 18 | Some(quote!(.field(stringify!(#ident), &self.#ident()))) 19 | }); 20 | 21 | let attr = cfg.map(|cfg| quote!(#[cfg(#cfg)])); 22 | 23 | quote! { 24 | #attr 25 | impl core::fmt::Debug for #name { 26 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 27 | f.debug_struct(stringify!(#name)) 28 | #( #fields )* 29 | .finish() 30 | } 31 | } 32 | } 33 | } 34 | 35 | /// Implements the `core::fmt::Display` trait for the given bitfield struct. 36 | pub fn defmt( 37 | name: &syn::Ident, 38 | members: &[Member], 39 | cfg: Option, 40 | ) -> TokenStream { 41 | // build a part of the format string for each field 42 | let formats = members.iter().filter_map(|m| { 43 | let inner = m.inner.as_ref()?; 44 | if inner.from.is_empty() { 45 | return None; 46 | } 47 | 48 | // primitives supported by defmt 49 | const PRIMITIVES: &[&str] = &[ 50 | "bool", "usize", "isize", // 51 | "u8", "u16", "u32", "u64", "u128", // 52 | "i8", "i16", "i32", "i64", "i128", // 53 | "f32", "f64", // 54 | ]; 55 | 56 | // get the type name so we can use more efficient defmt formats 57 | // if it's a primitive 58 | if let syn::Type::Path(syn::TypePath { path, .. }) = &inner.ty { 59 | if let Some(ident) = path.get_ident() { 60 | if PRIMITIVES.iter().any(|s| ident == s) { 61 | // defmt supports this primitive, use special spec 62 | return Some(format!("{}: {{={ident}}}", inner.ident)); 63 | } 64 | } 65 | } 66 | 67 | Some(format!("{}: {{:?}}", inner.ident)) 68 | }); 69 | 70 | // find the corresponding format argument for each field 71 | let args = members.iter().filter_map(|m| { 72 | let inner = m.inner.as_ref()?; 73 | if inner.from.is_empty() { 74 | return None; 75 | } 76 | 77 | let ident = &inner.ident; 78 | Some(quote!(self.#ident())) 79 | }); 80 | 81 | // build a string like "Foo { field_name: {:?}, ... }" 82 | // four braces, two to escape *this* format, times two to escape 83 | // the defmt::write! call below. 84 | let format_string = format!( 85 | "{name} {{{{ {} }}}} ", 86 | formats.collect::>().join(", ") 87 | ); 88 | 89 | let attr = cfg.map(|cfg| quote!(#[cfg(#cfg)])); 90 | 91 | // note: we use defmt paths here, not ::defmt, because many crates 92 | // in the embedded space will rename defmt (e.g. to defmt_03) in 93 | // order to support multiple incompatible defmt versions. 94 | // 95 | // defmt itself avoids ::defmt for this reason. For more info, see: 96 | // https://github.com/knurling-rs/defmt/pull/835 97 | quote! { 98 | #attr 99 | impl defmt::Format for #name { 100 | fn format(&self, f: defmt::Formatter) { 101 | defmt::write!(f, #format_string, #( #args, )*) 102 | } 103 | } 104 | } 105 | } 106 | 107 | /// Implements the `core::hash::Hash` trait for the given bitfield struct. 108 | pub fn hash( 109 | name: &syn::Ident, 110 | members: &[Member], 111 | cfg: Option, 112 | ) -> TokenStream { 113 | let fields = members.iter().filter_map(|m| { 114 | let inner = m.inner.as_ref()?; 115 | if inner.from.is_empty() { 116 | return None; 117 | } 118 | 119 | let ident = &inner.ident; 120 | Some(quote!(self.#ident())) 121 | }); 122 | 123 | let attr = cfg.map(|cfg| quote!(#[cfg(#cfg)])); 124 | 125 | quote! { 126 | #attr 127 | impl core::hash::Hash for #name { 128 | fn hash(&self, state: &mut H) { 129 | #( #fields.hash(state); )* 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use bitfield_struct::bitfield; 4 | 5 | #[test] 6 | fn members() { 7 | /// A test bitfield with documentation 8 | #[bitfield(u64, hash = true)] 9 | #[derive(PartialEq, Eq)] // <- Attributes after `bitfield` are carried over 10 | struct MyBitfield { 11 | /// Defaults to 16 bits for u16 12 | int: u16, 13 | /// Interpreted as 1 bit flag, with a custom default value 14 | #[bits(default = true)] 15 | flag: bool, 16 | /// Custom bit size 17 | #[bits(1)] 18 | tiny: u8, 19 | /// Sign extend for signed integers 20 | #[bits(13)] 21 | negative: i16, 22 | /// Supports any type with `into_bits`/`from_bits` functions 23 | #[bits(16)] 24 | custom: CustomEnum, 25 | /// Public field -> public accessor functions 26 | #[bits(10)] 27 | pub public: usize, 28 | /// Also supports read-only fields 29 | #[bits(1, access = RO)] 30 | read_only: bool, 31 | /// And write-only fields 32 | #[bits(1, access = WO)] 33 | write_only: bool, 34 | /// Padding 35 | #[bits(5)] 36 | __: u8, 37 | } 38 | 39 | /// A custom enum 40 | #[derive(Debug, PartialEq, Eq, Hash)] 41 | #[repr(u64)] 42 | enum CustomEnum { 43 | A = 0, 44 | B = 1, 45 | C = 2, 46 | } 47 | impl CustomEnum { 48 | const fn into_bits(self) -> u64 { 49 | self as _ 50 | } 51 | const fn from_bits(value: u64) -> Self { 52 | match value { 53 | 0 => Self::A, 54 | 1 => Self::B, 55 | _ => Self::C, 56 | } 57 | } 58 | } 59 | 60 | let mut val = MyBitfield::new() 61 | .with_int(3 << 15) 62 | .with_tiny(1) 63 | .with_negative(-3) 64 | .with_public(2) 65 | // Would not compile 66 | // .with_read_only(true) 67 | .with_write_only(false); 68 | 69 | println!("{val:?}"); 70 | 71 | let raw: u64 = val.into(); 72 | println!("{raw:b}"); 73 | 74 | assert_eq!(val.custom(), CustomEnum::A); 75 | val.set_custom(CustomEnum::B); 76 | 77 | assert_eq!(val.int(), 3 << 15); 78 | assert_eq!(val.flag(), true); // from default 79 | assert_eq!(val.negative(), -3); 80 | assert_eq!(val.tiny(), 1); 81 | assert_eq!(val.custom(), CustomEnum::B); 82 | assert_eq!(val.public(), 2); 83 | assert_eq!(val.read_only(), false); 84 | 85 | // const members 86 | assert_eq!(MyBitfield::FLAG_BITS, 1); 87 | assert_eq!(MyBitfield::FLAG_OFFSET, 16); 88 | 89 | val.set_negative(1); 90 | assert_eq!(val.negative(), 1); 91 | 92 | let pte = val.with_flag(false); 93 | assert_eq!(pte.flag(), false); 94 | } 95 | 96 | #[test] 97 | fn attrs() { 98 | /// We have a custom default 99 | #[bitfield(u64, default = false)] 100 | #[derive(PartialEq, Eq)] 101 | struct Full { 102 | data: u64, 103 | } 104 | impl Default for Full { 105 | fn default() -> Self { 106 | Self(0) 107 | } 108 | } 109 | 110 | let full = Full::default(); 111 | assert_eq!(u64::from(full), u64::from(Full::new())); 112 | 113 | let full = Full::new().with_data(u64::MAX); 114 | assert_eq!(full.data(), u64::MAX); 115 | assert_eq!(full, Full::new().with_data(u64::MAX)); 116 | } 117 | 118 | #[test] 119 | fn clone() { 120 | /// We have a custom clone implementation -> opt out 121 | #[bitfield(u64, clone = false)] 122 | struct Full { 123 | data: u64, 124 | } 125 | 126 | impl Clone for Full { 127 | fn clone(&self) -> Self { 128 | Self::new().with_data(self.data()) 129 | } 130 | } 131 | 132 | impl Copy for Full {} 133 | 134 | let full = Full::new().with_data(123); 135 | let full_copy = full; 136 | assert_eq!(full.data(), full_copy.data()); 137 | assert_eq!(full.data(), full.clone().data()); 138 | } 139 | 140 | #[test] 141 | fn clone_cfg() { 142 | /// We opt in for clone/copy implementation, via a cfg. 143 | #[bitfield(u64, clone = cfg(test))] 144 | struct Full { 145 | data: u64, 146 | } 147 | 148 | let full = Full::new().with_data(123); 149 | let full_copy = full; 150 | assert_eq!(full.data(), full_copy.data()); 151 | assert_eq!(full.data(), full.clone().data()); 152 | } 153 | 154 | #[test] 155 | fn debug() { 156 | /// We have a custom debug implementation -> opt out 157 | #[bitfield(u64, debug = false)] 158 | struct Full { 159 | data: u64, 160 | } 161 | 162 | impl fmt::Debug for Full { 163 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 164 | write!(f, "0x{:x}", self.data()) 165 | } 166 | } 167 | 168 | let full = Full::new().with_data(123); 169 | println!("{full:?}"); 170 | } 171 | 172 | // a dummy defmt logger and timestamp implementation, for testing 173 | mod defmt_logger { 174 | #[defmt::global_logger] 175 | struct Logger; 176 | 177 | unsafe impl defmt::Logger for Logger { 178 | fn acquire() {} 179 | unsafe fn flush() {} 180 | unsafe fn release() {} 181 | unsafe fn write(_bytes: &[u8]) {} 182 | } 183 | 184 | defmt::timestamp!(""); 185 | } 186 | 187 | #[test] 188 | fn defmt() { 189 | #[bitfield(u64, defmt = true)] 190 | struct Full { 191 | data: u64, 192 | } 193 | 194 | let full = Full::new().with_data(123); 195 | defmt::println!("{:?}", full); 196 | } 197 | 198 | #[test] 199 | fn defmt_primitives() { 200 | #[bitfield(u128, defmt = true)] 201 | struct Unsigned { 202 | a: bool, 203 | #[bits(7)] 204 | __: u8, 205 | b: u8, 206 | c: u16, 207 | d: u32, 208 | e: u64, 209 | } 210 | 211 | defmt::println!("{}", Unsigned::new()); 212 | 213 | #[bitfield(u128, defmt = true)] 214 | struct FullUnsigned { 215 | data: u128, 216 | } 217 | 218 | defmt::println!("{}", FullUnsigned::new()); 219 | 220 | #[bitfield(u128, defmt = true)] 221 | struct Signed { 222 | a: bool, 223 | #[bits(7)] 224 | __: u8, 225 | b: i8, 226 | c: i16, 227 | d: i32, 228 | e: i64, 229 | } 230 | 231 | defmt::println!("{}", Signed::new()); 232 | 233 | #[bitfield(u128, defmt = true)] 234 | struct FullSigned { 235 | data: i128, 236 | } 237 | 238 | defmt::println!("{}", FullSigned::new()); 239 | 240 | #[bitfield(u128, defmt = true)] 241 | struct Size { 242 | #[bits(64)] 243 | a: usize, 244 | #[bits(64)] 245 | b: usize, 246 | } 247 | 248 | defmt::println!("{}", Size::new()); 249 | 250 | const fn f32_from_bits(_bits: u32) -> f32 { 251 | // just for testing 252 | 0.0 253 | } 254 | 255 | const fn f64_from_bits(_bits: u64) -> f64 { 256 | // just for testing 257 | 0.0 258 | } 259 | 260 | #[bitfield(u128, defmt = true)] 261 | struct Float { 262 | __: u32, 263 | #[bits(32, from = f32_from_bits, access = RO)] 264 | a: f32, 265 | #[bits(64, from = f64_from_bits, access = RO)] 266 | b: f64, 267 | } 268 | 269 | defmt::println!("{}", Float::new()); 270 | } 271 | 272 | #[test] 273 | fn debug_cfg() { 274 | /// We have a custom debug implementation -> opt out 275 | #[bitfield(u64, debug = cfg(test), defmt = cfg(target_has_atomic = "64"))] 276 | struct Full { 277 | data: u64, 278 | } 279 | 280 | let full = Full::new().with_data(123); 281 | println!("{full:?}"); 282 | } 283 | 284 | #[test] 285 | fn positive() { 286 | #[bitfield(u32)] 287 | struct MyBitfield { 288 | #[bits(3)] 289 | positive: u32, 290 | #[bits(29)] 291 | __: (), 292 | } 293 | 294 | let v = MyBitfield::new().with_positive(0); 295 | assert_eq!(v.positive(), 0); 296 | let v = MyBitfield::new().with_positive(1); 297 | assert_eq!(v.positive(), 1); 298 | let v = MyBitfield::new().with_positive(7); 299 | assert_eq!(v.positive(), 7); 300 | } 301 | 302 | #[test] 303 | fn negative() { 304 | #[bitfield(u32)] 305 | struct MyBitfield { 306 | #[bits(3)] 307 | negative: i32, 308 | #[bits(29)] 309 | __: (), 310 | } 311 | 312 | let v = MyBitfield::new().with_negative(-3); 313 | assert_eq!(v.negative(), -3); 314 | let v = MyBitfield::new().with_negative(0); 315 | assert_eq!(v.negative(), 0); 316 | let v = MyBitfield::new().with_negative(3); 317 | assert_eq!(v.negative(), 3); 318 | let v = MyBitfield::new().with_negative(-4); 319 | assert_eq!(v.negative(), -4); 320 | } 321 | 322 | #[test] 323 | #[should_panic] 324 | #[cfg(debug_assertions)] 325 | fn negative_pos_overflow() { 326 | #[bitfield(u32)] 327 | struct MyBitfield { 328 | #[bits(3)] 329 | negative: i32, 330 | #[bits(29)] 331 | __: (), 332 | } 333 | 334 | MyBitfield::new().with_negative(4); 335 | } 336 | 337 | #[test] 338 | #[should_panic] 339 | #[cfg(debug_assertions)] 340 | fn negative_neg_overflow() { 341 | #[bitfield(u32)] 342 | struct MyBitfield { 343 | #[bits(3)] 344 | negative: i32, 345 | #[bits(29)] 346 | __: (), 347 | } 348 | 349 | MyBitfield::new().with_negative(-5); 350 | } 351 | 352 | #[test] 353 | fn checked_overflow() { 354 | #[bitfield(u32)] 355 | struct MyBitfield { 356 | #[bits(3)] 357 | negative: i32, 358 | #[bits(29)] 359 | __: (), 360 | } 361 | 362 | assert!(MyBitfield::new().with_negative_checked(4).is_err()); 363 | assert!(MyBitfield::new().with_negative_checked(-5).is_err()); 364 | } 365 | 366 | #[test] 367 | fn negative_signed() { 368 | #[bitfield(u64)] 369 | struct MyBitfield { 370 | negative: i32, 371 | #[bits(10)] 372 | positive: u16, 373 | #[bits(22)] 374 | __: u32, 375 | } 376 | 377 | let val = MyBitfield::new() 378 | .with_negative(-1) 379 | .with_positive(0b11_1111_1111); 380 | assert_eq!(val.negative(), -1); 381 | assert_eq!(val.positive(), 0b11_1111_1111); 382 | } 383 | 384 | #[test] 385 | fn entirely_negative() { 386 | #[bitfield(u32)] 387 | struct MyBitfield { 388 | negative: i32, 389 | } 390 | 391 | let v = MyBitfield::new().with_negative(-3); 392 | assert_eq!(v.negative(), -3); 393 | let v = MyBitfield::new().with_negative(0); 394 | assert_eq!(v.negative(), 0); 395 | let v = MyBitfield::new().with_negative(3); 396 | assert_eq!(v.negative(), 3); 397 | let v = MyBitfield::new().with_negative(i32::MIN); 398 | assert_eq!(v.negative(), i32::MIN); 399 | let v = MyBitfield::new().with_negative(i32::MAX); 400 | assert_eq!(v.negative(), i32::MAX); 401 | } 402 | 403 | #[test] 404 | fn custom() { 405 | #[bitfield(u16)] 406 | #[derive(PartialEq, Eq)] 407 | struct Bits { 408 | /// Supports any type, with default/to/from expressions 409 | /// - into/from call Bits::into_bits/Bits::from_bits if nothing else is specified 410 | /// - default falls back to calling Bits::from_bits with 0 411 | #[bits(13, default = CustomEnum::B, from = CustomEnum::my_from_bits)] 412 | custom: CustomEnum, 413 | // Padding with default 414 | #[bits(3)] 415 | __: (), 416 | } 417 | 418 | #[derive(Debug, PartialEq, Eq)] 419 | #[repr(u16)] 420 | enum CustomEnum { 421 | A = 0, 422 | B = 1, 423 | C = 2, 424 | } 425 | impl CustomEnum { 426 | // This has to be a const fn 427 | const fn into_bits(self) -> u16 { 428 | self as _ 429 | } 430 | const fn my_from_bits(value: u16) -> Self { 431 | match value { 432 | 0 => Self::A, 433 | 1 => Self::B, 434 | _ => Self::C, 435 | } 436 | } 437 | } 438 | } 439 | 440 | #[test] 441 | fn defaults() { 442 | #[bitfield(u16)] 443 | #[derive(PartialEq, Eq)] 444 | struct MyBitfield { 445 | /// Interpreted as 1-bit flag, with custom default 446 | #[bits(default = true)] 447 | flag: bool, 448 | /// Supports any type, with default/to/from expressions (that are const eval) 449 | /// - into/from call #ty::into_bits/#ty::from_bits if nothing else is specified 450 | #[bits(13, default = CustomEnum::B, from = CustomEnum::my_from_bits)] 451 | custom: CustomEnum, 452 | // Padding with default 453 | #[bits(2, default = 0b10)] 454 | __: (), 455 | } 456 | 457 | /// A custom enum 458 | #[derive(Debug, PartialEq, Eq)] 459 | #[repr(u8)] 460 | enum CustomEnum { 461 | A = 0, 462 | B = 1, 463 | C = 2, 464 | } 465 | impl CustomEnum { 466 | // This has to be const eval 467 | const fn into_bits(self) -> u8 { 468 | self as _ 469 | } 470 | const fn my_from_bits(value: u8) -> Self { 471 | match value { 472 | 0 => Self::A, 473 | 1 => Self::B, 474 | _ => Self::C, 475 | } 476 | } 477 | } 478 | 479 | // Uses defaults 480 | let val = MyBitfield::new(); 481 | 482 | assert_eq!(val.flag(), true); 483 | assert_eq!(val.custom(), CustomEnum::B); 484 | assert_eq!(val.0 >> 14, 0b10); // padding 485 | } 486 | 487 | #[test] 488 | fn default_padding() { 489 | #[bitfield(u32)] 490 | struct MyBitfield { 491 | value: u16, 492 | #[bits(15, default = 0xfff)] 493 | __: (), 494 | #[bits(default = true)] 495 | __: bool, 496 | } 497 | let v = MyBitfield::new().with_value(0xff); 498 | 499 | assert_eq!(v.0, 0x8fff_00ff); 500 | } 501 | 502 | #[test] 503 | fn lsb_order() { 504 | #[bitfield(u32, order=lsb)] 505 | struct MyBitfield { 506 | short: u16, 507 | #[bits(8)] 508 | __: (), 509 | byte: u8, 510 | } 511 | 512 | let v = MyBitfield::new().with_short(0xe11e).with_byte(0xf0); 513 | 514 | assert_eq!(v.0, 0xf0_00_e11e); 515 | } 516 | 517 | #[test] 518 | fn msb_order() { 519 | #[bitfield(u32, order=msb)] 520 | struct MyBitfield { 521 | short: u16, 522 | #[bits(8)] 523 | __: (), 524 | byte: u8, 525 | } 526 | 527 | let v = MyBitfield::new().with_short(0xe11e).with_byte(0xf0); 528 | 529 | assert_eq!(v.0, 0xe11e_00_f0); 530 | } 531 | 532 | #[test] 533 | fn nested() { 534 | #[bitfield(u8)] 535 | #[derive(PartialEq)] 536 | struct Child { 537 | contents: u8, 538 | } 539 | #[bitfield(u16)] 540 | #[derive(PartialEq)] 541 | struct Parent { 542 | #[bits(8)] 543 | child: Child, 544 | other: u8, 545 | } 546 | let child = Child::new().with_contents(0xff); 547 | let parent = Parent::new().with_child(child); 548 | assert_eq!(child.into_bits(), 0xff); 549 | assert_eq!(parent.into_bits(), 0xff); 550 | } 551 | 552 | #[test] 553 | fn raw() { 554 | #[bitfield(u8)] 555 | #[derive(PartialEq)] 556 | struct Raw { 557 | r#type: u8, 558 | } 559 | let raw = Raw::new().with_type(0xff); 560 | assert_eq!(raw.r#type(), 0xff); 561 | assert_eq!(raw.into_bits(), 0xff); 562 | } 563 | 564 | #[test] 565 | fn custom_inner() { 566 | #[bitfield(u32, repr = CustomInner, from = CustomInner::from_inner, into = CustomInner::to_inner)] 567 | #[derive(PartialEq, Eq)] 568 | struct MyBitfield { 569 | data: u32, 570 | } 571 | 572 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] 573 | #[repr(transparent)] 574 | struct CustomInner(u32); 575 | 576 | impl CustomInner { 577 | const fn to_inner(self) -> u32 { 578 | self.0 579 | } 580 | 581 | const fn from_inner(inner: u32) -> Self { 582 | Self(inner) 583 | } 584 | } 585 | 586 | let my_bitfield = MyBitfield::new(); 587 | assert_eq!(my_bitfield, MyBitfield::from_bits(CustomInner(0))); 588 | assert_eq!(my_bitfield.into_bits(), CustomInner(0)); 589 | } 590 | 591 | #[test] 592 | fn default_without_setter() { 593 | use endian_num::le16; 594 | 595 | #[bitfield(u16, repr = le16, from = le16::from_ne, into = le16::to_ne)] 596 | struct Test { 597 | #[bits(14)] 598 | f3: u16, 599 | _reserved: bool, // no setter 600 | #[bits(1, access = RO)] // no setter 601 | reserved: bool, 602 | } 603 | } 604 | 605 | #[test] 606 | fn default_msb_padding_default_value() { 607 | #[bitfield(u8, order = Msb)] 608 | struct MyMsbByte { 609 | #[bits(4, default = 0b1111)] 610 | __padding: usize, 611 | #[bits(4, default = 0b1010)] 612 | kind: usize, 613 | } 614 | let my_byte_msb = MyMsbByte::new(); 615 | let val: u8 = my_byte_msb.into(); 616 | assert_eq!(val, 0b1111_1010); 617 | } 618 | 619 | #[test] 620 | fn default_lsb_padding_default_value() { 621 | #[bitfield(u8, order = Lsb)] 622 | struct MyMsbByte { 623 | #[bits(4, default = 0b1111)] 624 | __padding: usize, 625 | #[bits(4, default = 0b1010)] 626 | kind: usize, 627 | } 628 | let my_byte_msb = MyMsbByte::new(); 629 | let val: u8 = my_byte_msb.into(); 630 | assert_eq!(val, 0b1010_1111); 631 | } 632 | 633 | #[test] 634 | fn hash() { 635 | use std::collections::hash_map::DefaultHasher; 636 | use std::hash::{Hash, Hasher}; 637 | 638 | #[bitfield(u32, hash = true)] 639 | struct MyBitfield { 640 | data: u16, 641 | __: u8, 642 | #[bits(8)] 643 | extra: u8, 644 | } 645 | 646 | let mut hasher = DefaultHasher::new(); 647 | MyBitfield::new() 648 | .with_data(0x1234) 649 | .with_extra(0x56) 650 | .hash(&mut hasher); 651 | let hash = hasher.finish(); 652 | 653 | let mut hasher = DefaultHasher::new(); 654 | MyBitfield::from_bits(0x56ee_1234).hash(&mut hasher); 655 | let other_hash = hasher.finish(); 656 | 657 | assert_eq!(hash, other_hash, "Padding should not affect hash"); 658 | 659 | let mut hasher = DefaultHasher::new(); 660 | MyBitfield::new() 661 | .with_data(0x1234) 662 | .with_extra(0x57) 663 | .hash(&mut hasher); 664 | let other_hash = hasher.finish(); 665 | assert_ne!(hash, other_hash, "Hash should change when data changes"); 666 | } 667 | --------------------------------------------------------------------------------