Skip to content

Commit

Permalink
Merge pull request #61 from mkroening/general-derive
Browse files Browse the repository at this point in the history
feat: implement derive macro for all access types
  • Loading branch information
phil-opp authored Apr 26, 2024
2 parents 53c30e7 + 904021d commit 6616c04
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 82 deletions.
70 changes: 0 additions & 70 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,76 +47,6 @@
#![doc(test(attr(allow(dead_code))))]
#![doc(test(attr(allow(unused_variables))))]

/// A derive macro for method-based accesses to volatile structures.
///
/// This macro allows you to access the fields of a volatile structure via methods that enforce access limitations.
/// It is also more easily chainable than [`map_field`].
///
/// <div class="warning">
///
/// This macro generates and implements a new `{T}VolatileFieldAccess` trait, that you have to import if used from other modules.
/// Currently, the trait is only implemented for `VolatilePtr<'_, _, ReadWrite>`.
///
/// </div>
///
/// # Examples
///
/// ```
/// use volatile::access::ReadOnly;
/// use volatile::{VolatileFieldAccess, VolatileRef};
///
/// #[repr(C)]
/// #[derive(VolatileFieldAccess, Default)]
/// pub struct DeviceConfig {
/// feature_select: u32,
/// #[access(ReadOnly)]
/// feature: u32,
/// }
///
/// let mut device_config = DeviceConfig::default();
/// let mut volatile_ref = VolatileRef::from_mut_ref(&mut device_config);
/// let volatile_ptr = volatile_ref.as_mut_ptr();
///
/// volatile_ptr.feature_select().write(42);
/// assert_eq!(volatile_ptr.feature_select().read(), 42);
///
/// // This does not compile, because we specified `#[access(ReadOnly)]` for this field.
/// // volatile_ptr.feature().write(42);
///
/// // A real device might have changed the value, though.
/// assert_eq!(volatile_ptr.feature().read(), 0);
/// ```
///
/// # Details
///
/// This macro generates a new trait (`{T}VolatileFieldAccess`) and implements it for `VolatilePtr<'a, T, ReadWrite>`.
/// The example above results in (roughly) the following code:
///
/// ```
/// # #[repr(C)]
/// # pub struct DeviceConfig {
/// # feature_select: u32,
/// # feature: u32,
/// # }
/// use volatile::access::{ReadOnly, ReadWrite};
/// use volatile::{map_field, VolatilePtr};
///
/// pub trait DeviceConfigVolatileFieldAccess<'a> {
/// fn feature_select(self) -> VolatilePtr<'a, u32, ReadWrite>;
///
/// fn feature(self) -> VolatilePtr<'a, u32, ReadOnly>;
/// }
///
/// impl<'a> DeviceConfigVolatileFieldAccess<'a> for VolatilePtr<'a, DeviceConfig, ReadWrite> {
/// fn feature_select(self) -> VolatilePtr<'a, u32, ReadWrite> {
/// map_field!(self.feature_select).restrict()
/// }
///
/// fn feature(self) -> VolatilePtr<'a, u32, ReadOnly> {
/// map_field!(self.feature).restrict()
/// }
/// }
/// ```
#[cfg(feature = "derive")]
pub use volatile_macro::VolatileFieldAccess;

Expand Down
3 changes: 3 additions & 0 deletions volatile-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ proc-macro = true
proc-macro2 = "1"
quote = "1"
syn = { version = "2", features = ["full"] }

[dev-dependencies]
volatile = { version = "=0.5.4", path = "..", features = ["derive"] }
89 changes: 89 additions & 0 deletions volatile-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#![doc(test(attr(deny(warnings))))]
#![doc(test(attr(allow(dead_code))))]

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::ToTokens;
Expand All @@ -11,6 +14,92 @@ macro_rules! bail {

mod volatile;

/// A derive macro for method-based accesses to volatile structures.
///
/// This macro allows you to access the fields of a volatile structure via methods that enforce access limitations.
/// It is also more easily chainable than `map_field`.
///
/// <div class="warning">
///
/// This macro generates and implements a new `{T}VolatileFieldAccess` trait, that you have to import if used from other modules.
/// Currently, the trait is only implemented for `VolatilePtr<'_, _, ReadWrite>`.
///
/// </div>
///
/// # Examples
///
/// ```
/// use volatile::access::ReadOnly;
/// use volatile::{VolatileFieldAccess, VolatileRef};
///
/// #[repr(C)]
/// #[derive(VolatileFieldAccess, Default)]
/// pub struct DeviceConfig {
/// feature_select: u32,
/// #[access(ReadOnly)]
/// feature: u32,
/// }
///
/// let mut device_config = DeviceConfig::default();
/// let mut volatile_ref = VolatileRef::from_mut_ref(&mut device_config);
/// let volatile_ptr = volatile_ref.as_mut_ptr();
///
/// volatile_ptr.feature_select().write(42);
/// assert_eq!(volatile_ptr.feature_select().read(), 42);
///
/// // This does not compile, because we specified `#[access(ReadOnly)]` for this field.
/// // volatile_ptr.feature().write(42);
///
/// // A real device might have changed the value, though.
/// assert_eq!(volatile_ptr.feature().read(), 0);
///
/// // You can also use shared references.
/// let volatile_ptr = volatile_ref.as_ptr();
/// assert_eq!(volatile_ptr.feature_select().read(), 42);
/// // This does not compile, because `volatile_ptr` is `ReadOnly`.
/// // volatile_ptr.feature_select().write(42);
/// ```
///
/// # Details
///
/// This macro generates a new trait (`{T}VolatileFieldAccess`) and implements it for `VolatilePtr<'a, T, ReadWrite>`.
/// The example above results in (roughly) the following code:
///
/// ```
/// # #[repr(C)]
/// # pub struct DeviceConfig {
/// # feature_select: u32,
/// # feature: u32,
/// # }
/// use volatile::access::{ReadOnly, ReadWrite, RestrictAccess};
/// use volatile::{map_field, VolatilePtr};
///
/// pub trait DeviceConfigVolatileFieldAccess<'a, A> {
/// fn feature_select(self) -> VolatilePtr<'a, u32, A::Restricted>
/// where
/// A: RestrictAccess<ReadWrite>;
///
/// fn feature(self) -> VolatilePtr<'a, u32, A::Restricted>
/// where
/// A: RestrictAccess<ReadOnly>;
/// }
///
/// impl<'a, A> DeviceConfigVolatileFieldAccess<'a, A> for VolatilePtr<'a, DeviceConfig, A> {
/// fn feature_select(self) -> VolatilePtr<'a, u32, A::Restricted>
/// where
/// A: RestrictAccess<ReadWrite>
/// {
/// map_field!(self.feature_select).restrict()
/// }
///
/// fn feature(self) -> VolatilePtr<'a, u32, A::Restricted>
/// where
/// A: RestrictAccess<ReadOnly>
/// {
/// map_field!(self.feature).restrict()
/// }
/// }
/// ```
#[proc_macro_derive(VolatileFieldAccess, attributes(access))]
pub fn derive_volatile(item: TokenStream) -> TokenStream {
match volatile::derive_volatile(parse_macro_input!(item)) {
Expand Down
37 changes: 25 additions & 12 deletions volatile-macro/src/volatile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ fn parse_input(input: &ItemStruct) -> Result<ParsedInput> {
}

let sig = parse_quote! {
fn #ident(self) -> ::volatile::VolatilePtr<'a, #ty, #access>
fn #ident(self) -> ::volatile::VolatilePtr<'a, #ty, A::Restricted>
where
A: ::volatile::access::RestrictAccess<#access>
};
sigs.push(sig);
}
Expand Down Expand Up @@ -112,7 +114,7 @@ fn emit_trait(
parse_quote! {
#(#attrs)*
#[allow(non_camel_case_types)]
#vis trait #trait_ident <'a> {
#vis trait #trait_ident <'a, A> {
#(
#(#method_attrs)*
#sigs;
Expand All @@ -133,9 +135,10 @@ fn emit_impl(

parse_quote! {
#[automatically_derived]
impl<'a> #trait_ident<'a> for ::volatile::VolatilePtr<'a, #struct_ident, ::volatile::access::ReadWrite> {
impl<'a, A> #trait_ident<'a, A> for ::volatile::VolatilePtr<'a, #struct_ident, A> {
#(
#sigs {
#sigs,
{
::volatile::map_field!(self.#fields).restrict()
}
)*
Expand Down Expand Up @@ -183,24 +186,34 @@ mod tests {
///
/// This is a wonderful struct.
#[allow(non_camel_case_types)]
pub trait DeviceConfigVolatileFieldAccess<'a> {
fn feature_select(self) -> ::volatile::VolatilePtr<'a, u32, ::volatile::access::ReadWrite>;
pub trait DeviceConfigVolatileFieldAccess<'a, A> {
fn feature_select(self) -> ::volatile::VolatilePtr<'a, u32, A::Restricted>
where
A: ::volatile::access::RestrictAccess<::volatile::access::ReadWrite>;

/// Feature.
///
/// This is a good field.
fn feature(self) -> ::volatile::VolatilePtr<'a, u32, ReadOnly>;
fn feature(self) -> ::volatile::VolatilePtr<'a, u32, A::Restricted>
where
A: ::volatile::access::RestrictAccess<ReadOnly>;
}
};

let expected_impl = quote! {
#[automatically_derived]
impl<'a> DeviceConfigVolatileFieldAccess<'a> for ::volatile::VolatilePtr<'a, DeviceConfig, ::volatile::access::ReadWrite> {
fn feature_select(self) -> ::volatile::VolatilePtr<'a, u32, ::volatile::access::ReadWrite> {
impl<'a, A> DeviceConfigVolatileFieldAccess<'a, A> for ::volatile::VolatilePtr<'a, DeviceConfig, A> {
fn feature_select(self) -> ::volatile::VolatilePtr<'a, u32, A::Restricted>
where
A: ::volatile::access::RestrictAccess<::volatile::access::ReadWrite>,
{
::volatile::map_field!(self.feature_select).restrict()
}

fn feature(self) -> ::volatile::VolatilePtr<'a, u32, ReadOnly> {
fn feature(self) -> ::volatile::VolatilePtr<'a, u32, A::Restricted>
where
A: ::volatile::access::RestrictAccess<ReadOnly>,
{
::volatile::map_field!(self.feature).restrict()
}
}
Expand Down Expand Up @@ -230,12 +243,12 @@ mod tests {

let expected_trait = quote! {
#[allow(non_camel_case_types)]
pub trait DeviceConfigVolatileFieldAccess<'a> {}
pub trait DeviceConfigVolatileFieldAccess<'a, A> {}
};

let expected_impl = quote! {
#[automatically_derived]
impl<'a> DeviceConfigVolatileFieldAccess<'a> for ::volatile::VolatilePtr<'a, DeviceConfig, ::volatile::access::ReadWrite> {}
impl<'a, A> DeviceConfigVolatileFieldAccess<'a, A> for ::volatile::VolatilePtr<'a, DeviceConfig, A> {}
};

assert_eq!(
Expand Down

0 comments on commit 6616c04

Please sign in to comment.