diff --git a/src/lib.rs b/src/lib.rs
index 4cc9d61..c419c8b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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;
 
diff --git a/volatile-macro/Cargo.toml b/volatile-macro/Cargo.toml
index a592811..8f17c47 100644
--- a/volatile-macro/Cargo.toml
+++ b/volatile-macro/Cargo.toml
@@ -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"] }
diff --git a/volatile-macro/src/lib.rs b/volatile-macro/src/lib.rs
index 8e2e175..2fe9cad 100644
--- a/volatile-macro/src/lib.rs
+++ b/volatile-macro/src/lib.rs
@@ -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;
@@ -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)) {
diff --git a/volatile-macro/src/volatile.rs b/volatile-macro/src/volatile.rs
index ecff9f6..a80f635 100644
--- a/volatile-macro/src/volatile.rs
+++ b/volatile-macro/src/volatile.rs
@@ -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);
     }
@@ -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;
@@ -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()
                 }
             )*
@@ -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()
                 }
             }
@@ -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!(