Improve documentation of `hash_newtype!`

The macro is non-trivial, so documenting it well is very useful. This
change improves both user-facing and developer-facing code with
appropriate warnings about the limitations of the code and Rust macro
system.
This commit is contained in:
Martin Habovstiak 2023-02-24 21:39:29 +01:00
parent 58876e2be9
commit 8ccfb412c1
1 changed files with 64 additions and 4 deletions

View File

@ -121,11 +121,11 @@ macro_rules! engine_input_impl(
/// } /// }
/// ``` /// ```
/// ///
/// You can use any valid visibility specifier in place of `pub` or you can leave it out if you /// You can use any valid visibility specifier in place of `pub` or you can omit either or both, if
/// don't want the type or its field to be private. /// you want the type or its field to be private.
/// ///
/// Whether the hash is reversed or not depends on the inner type. However you can override it like /// Whether the hash is reversed or not when displaying depends on the inner type. However you can
/// this: /// override it like this:
/// ///
/// ``` /// ```
/// # use bitcoin_hashes::{hash_newtype, sha256}; /// # use bitcoin_hashes::{hash_newtype, sha256};
@ -156,6 +156,40 @@ macro_rules! engine_input_impl(
/// struct Newtype2(hash160::Hash); /// struct Newtype2(hash160::Hash);
/// } /// }
/// ``` /// ```
///
/// Note: the macro is internally recursive. If you use too many attributes (> 256 tokens) you may
/// hit recursion limit. If you have so many attributes for a good reason, just raising the limit
/// should be OK. Note however that attribute-processing part has to use [TT muncher] which has
/// quadratic complexity, so having many attributes may blow up compile time. This should be rare.
///
/// [TT muncher]: https://danielkeep.github.io/tlborm/book/pat-incremental-tt-munchers.html
///
// Ever heard of legendary comments warning developers to not touch the code? Yep, here's another
// one. The following code is written the way it is for some specific reasons. If you think you can
// simplify it, I suggest spending your time elsewhere.
//
// If you looks at the code carefully you might ask these questions:
//
// * Why are attributes using `tt` and not `meta`?!
// * Why are the macros split like that?!
// * Why use recursion instead of `$()*`?
//
// None of these are here by accident. For some reason unknown to me, if you accept an argument to
// macro with any fragment specifier other than `tt` it will **not** match any of the rules
// requiring a specific token. Yep, I tried it, I literally got error that `hash_newtype` doesn't
// match `hash_newtype`. So all input attributes must be `tt`.
//
// Originally I wanted to define a bunch of macros that would filter-out hash_type attributes. Then
// I remembered (by seeing compiler error) that calling macros is not allowed inside attributes.
// And no, you can't bypass it by calling a helper macro and passing "output of another macro" into
// it. The whole macro gets passed, not the resulting value. So we have to generate the entire
// attributes. And you can't just place an attribute-producing macro above struct - they are
// considered separate items. This is not C.
//
// Thus struct is generated in a separate macro together with attributes. And since the macro needs
// attributes as the input and I didn't want to create confusion by using `#[]` syntax *after*
// struct, I opted to use `{}` as a separator. Yes, a separator is required because an attribute
// may be composed of multiple token trees - that's the point of "double repetition".
#[macro_export] #[macro_export]
macro_rules! hash_newtype { macro_rules! hash_newtype {
($($(#[$($type_attrs:tt)*])* $type_vis:vis struct $newtype:ident($(#[$field_attrs:tt])* $field_vis:vis $hash:path);)+) => { ($($(#[$($type_attrs:tt)*])* $type_vis:vis struct $newtype:ident($(#[$field_attrs:tt])* $field_vis:vis $hash:path);)+) => {
@ -275,6 +309,16 @@ macro_rules! hash_newtype {
}; };
} }
// Generates the struct only (no impls)
//
// This is a separate macro to make it more readable and have a separate interface that allows for
// two groups of type attributes: processed and not-yet-processed ones (think about it like
// computation via recursion). The macro recursively matches unprocessed attributes, popping them
// one at a time and either ignoring them (`hash_newtype`) or appending them to the list of
// processed attributes to be added to the struct.
//
// Once the list of not-yet-processed attributes is empty the struct is generated with processed
// attributes added.
#[doc(hidden)] #[doc(hidden)]
#[macro_export] #[macro_export]
macro_rules! hash_newtype_struct { macro_rules! hash_newtype_struct {
@ -302,6 +346,14 @@ macro_rules! hash_newtype_struct {
}; };
} }
// Extracts `hash_newtype(forward)` and `hash_newtype(backward)` attributes if any and turns them
// into bool, defaulting to `DISPLAY_BACKWARD` of the wrapped type if the attribute is omitted.
//
// Once an appropriate attribute is found we pass the remaining ones into another macro to detect
// duplicates/conflicts and report an error.
//
// FYI, no, we can't use a helper macro to first filter all `hash_newtype` attributes. We would be
// attempting to match on macros instead. So we must write `hashe_newtype` in each branch.
#[doc(hidden)] #[doc(hidden)]
#[macro_export] #[macro_export]
macro_rules! hash_newtype_get_direction { macro_rules! hash_newtype_get_direction {
@ -311,6 +363,9 @@ macro_rules! hash_newtype_get_direction {
($hash:ty, #[$($ignore:tt)*] $($others:tt)*) => { $crate::hash_newtype_get_direction!($hash, $($others)*) }; ($hash:ty, #[$($ignore:tt)*] $($others:tt)*) => { $crate::hash_newtype_get_direction!($hash, $($others)*) };
} }
// Reports an error if any of the attributes is `hash_newtype($direction)`.
//
// This is used for detection of duplicates/conflicts, see the macro above.
#[doc(hidden)] #[doc(hidden)]
#[macro_export] #[macro_export]
macro_rules! hash_newtype_forbid_direction { macro_rules! hash_newtype_forbid_direction {
@ -326,6 +381,11 @@ macro_rules! hash_newtype_forbid_direction {
}; };
} }
// Checks (at compile time) that all `hash_newtype` attributes are known.
//
// An unknown attribute could be a typo that could cause problems - e.g. wrong display direction if
// it's missing. To prevent this, we call this macro above. The macro produces nothing unless an
// unknown attribute is found in which case it produces `compile_error!`.
#[doc(hidden)] #[doc(hidden)]
#[macro_export] #[macro_export]
macro_rules! hash_newtype_known_attrs { macro_rules! hash_newtype_known_attrs {