Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ethercrab-wire-derive/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Derives for `ethercrab`.

- **(breaking)** [#230](https://github.com/ethercrab-rs/ethercrab/pull/230) Increase MSRV from 1.77
to 1.79.
- Support generic parameters for structs.

## [0.2.0] - 2024-07-28

Expand Down
1 change: 1 addition & 0 deletions ethercrab-wire-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ syn = { version = "2.0.44", features = ["full"] }
trybuild = "1.0.86"
ethercrab-wire = { path = "../ethercrab-wire" }
syn = { version = "2.0.44", features = ["full", "extra-traits"] }
pretty_assertions = "1.4.1"

[[bench]]
name = "derive-struct"
Expand Down
34 changes: 34 additions & 0 deletions ethercrab-wire-derive/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,40 @@ struct Middle {
}
```

## Generic structs

Structs can take generic parameters so long as those parameters are `EtherCrabWireRead/Write` as relevant.
It's up to the user to ensure that the generic type can be constructed with the number of bits given.

```rust
/// Status word for Beckhoff EL31xx devices and others.
#[derive(ethercrab_wire::EtherCrabWireRead)]
#[wire(bytes = 4)]
pub struct AnalogInput<Value>
where
Value: ethercrab_wire::EtherCrabWireRead,
{
#[wire(bits = 1)]
underrange: bool,
#[wire(bits = 1)]
overrange: bool,
#[wire(bits = 2)]
limit1: u8,
#[wire(bits = 2)]
limit2: u8,
#[wire(bits = 1)]
error: bool,
#[wire(pre_skip = 6, bits = 1)]
sync_error: bool,
#[wire(bits = 1)]
tx_pdo_bad: bool,
#[wire(bits = 1)]
tx_pdo_toggle: bool,
#[wire(bits = 16)]
value: Value, // <-- generic field
}
```

[`ethercrab`]: https://docs.rs/ethercrab
[`ethercrab-wire`]: https://docs.rs/ethercrab-wire

Expand Down
66 changes: 62 additions & 4 deletions ethercrab-wire-derive/src/generate_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ pub fn generate_struct_write(parsed: &StructMeta, input: &DeriveInput) -> proc_m
}
});

let (impl_generics, type_generics, where_clause) = parsed.generics.split_for_impl();
quote! {
impl ::ethercrab_wire::EtherCrabWireWrite for #name {
impl #impl_generics ::ethercrab_wire::EtherCrabWireWrite for #name #type_generics
#where_clause {
fn pack_to_slice_unchecked<'buf>(&self, buf: &'buf mut [u8]) -> &'buf [u8] {
let buf = match buf.get_mut(0..#size_bytes) {
Some(buf) => buf,
Expand All @@ -75,7 +77,8 @@ pub fn generate_struct_write(parsed: &StructMeta, input: &DeriveInput) -> proc_m
}
}

impl ::ethercrab_wire::EtherCrabWireWriteSized for #name {
impl #impl_generics ::ethercrab_wire::EtherCrabWireWriteSized for #name #type_generics
#where_clause {
fn pack(&self) -> Self::Buffer {
let mut buf = [0u8; #size_bytes];

Expand Down Expand Up @@ -144,8 +147,10 @@ pub fn generate_struct_read(parsed: &StructMeta, input: &DeriveInput) -> proc_ma
}
});

let (impl_generics, type_generics, where_clause) = parsed.generics.split_for_impl();
quote! {
impl ::ethercrab_wire::EtherCrabWireRead for #name {
impl #impl_generics ::ethercrab_wire::EtherCrabWireRead for #name #type_generics
#where_clause {
fn unpack_from_slice(buf: &[u8]) -> Result<Self, ::ethercrab_wire::WireError> {
let buf = buf.get(0..#size_bytes).ok_or(::ethercrab_wire::WireError::ReadBufferTooShort)?;

Expand All @@ -161,8 +166,10 @@ pub fn generate_sized_impl(parsed: &StructMeta, input: &DeriveInput) -> proc_mac
let name = input.ident.clone();
let size_bytes = parsed.width_bits.div_ceil(8);

let (impl_generics, type_generics, where_clause) = parsed.generics.split_for_impl();
quote! {
impl ::ethercrab_wire::EtherCrabWireSized for #name {
impl #impl_generics ::ethercrab_wire::EtherCrabWireSized for #name #type_generics
#where_clause {
const PACKED_LEN: usize = #size_bytes;

type Buffer = [u8; #size_bytes];
Expand All @@ -173,3 +180,54 @@ pub fn generate_sized_impl(parsed: &StructMeta, input: &DeriveInput) -> proc_mac
}
}
}

#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;

use ethercrab_wire::{EtherCrabWireRead, EtherCrabWireReadWrite, EtherCrabWireWrite};

#[test]
fn generic_struct() {
#[derive(EtherCrabWireReadWrite, PartialEq, Debug)]
#[wire(bytes = 8)]
struct TestTypeGeneric<T: EtherCrabWireReadWrite> {
#[wire(bits = 32)]
a: i32,
#[wire(bits = 32)]
b: T,
}
let test_type_generic = TestTypeGeneric::<u32> {
a: -16,
b: u32::MAX,
};
let mut slice = [0u8; 8];
test_type_generic.pack_to_slice(&mut slice).unwrap();
assert_eq!(
Ok(test_type_generic),
TestTypeGeneric::<u32>::unpack_from_slice(&slice)
);

#[derive(EtherCrabWireReadWrite, PartialEq, Debug)]
#[wire(bytes = 8)]
struct TestWhereClause<T>
where
T: EtherCrabWireReadWrite,
{
#[wire(bits = 32)]
a: i32,
#[wire(bits = 32)]
b: T,
}
let test_where_clause = TestWhereClause::<u32> {
a: -16,
b: u32::MAX,
};
let mut slice = [0u8; 8];
test_where_clause.pack_to_slice(&mut slice).unwrap();
assert_eq!(
Ok(test_where_clause),
TestWhereClause::<u32>::unpack_from_slice(&slice)
);
}
}
34 changes: 34 additions & 0 deletions ethercrab-wire-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,40 @@
//! }
//! ```
//!
//! ## Generic structs
//!
//! Structs can take generic parameters so long as those parameters are `EtherCrabWireRead/Write` as relevant.
//! It's up to the user to ensure that the generic type can be constructed with the number of bits given.
//!
//! ```rust
//! /// Status word for Beckhoff EL31xx devices and others.
//! #[derive(ethercrab_wire::EtherCrabWireRead)]
//! #[wire(bytes = 4)]
//! pub struct AnalogInput<Value>
//! where
//! Value: ethercrab_wire::EtherCrabWireRead,
//! {
//! #[wire(bits = 1)]
//! underrange: bool,
//! #[wire(bits = 1)]
//! overrange: bool,
//! #[wire(bits = 2)]
//! limit1: u8,
//! #[wire(bits = 2)]
//! limit2: u8,
//! #[wire(bits = 1)]
//! error: bool,
//! #[wire(pre_skip = 6, bits = 1)]
//! sync_error: bool,
//! #[wire(bits = 1)]
//! tx_pdo_bad: bool,
//! #[wire(bits = 1)]
//! tx_pdo_toggle: bool,
//! #[wire(bits = 16)]
//! value: Value, // <-- generic field
//! }
//! ```
//!
//! [`ethercrab`]: https://docs.rs/ethercrab
//! [`ethercrab-wire`]: https://docs.rs/ethercrab-wire

Expand Down
11 changes: 9 additions & 2 deletions ethercrab-wire-derive/src/parse_struct.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use crate::help::{all_valid_attrs, attr_exists, bit_width_attr, usize_attr};
use std::ops::Range;
use syn::{DataStruct, DeriveInput, Fields, FieldsNamed, Ident, Type, Visibility};
use syn::{DataStruct, DeriveInput, Fields, FieldsNamed, Generics, Ident, Type, Visibility};

#[derive(Clone)]
pub struct StructMeta {
/// Width in bits on the wire.
pub width_bits: usize,

pub fields: Vec<FieldMeta>,
pub generics: Generics,
}

#[derive(Clone)]
Expand Down Expand Up @@ -42,7 +43,12 @@ pub struct FieldMeta {

pub fn parse_struct(
s: DataStruct,
DeriveInput { attrs, ident, .. }: DeriveInput,
DeriveInput {
attrs,
ident,
generics,
..
}: DeriveInput,
) -> syn::Result<StructMeta> {
// --- Struct attributes

Expand Down Expand Up @@ -184,5 +190,6 @@ pub fn parse_struct(
Ok(StructMeta {
width_bits: width,
fields: field_meta,
generics,
})
}