Skip to content

Commit

Permalink
doc: add description of derive macros' attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
dj8yf0μl committed Jul 17, 2023
1 parent c1d6383 commit d90216f
Show file tree
Hide file tree
Showing 4 changed files with 343 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ fn parse_lit_into<T: syn::parse::Parse>(
}
}

/// struct describes entries like `K => <K as TraitName>::Associated`
/**
Struct describes an entry like `order_param => override_type`, e.g. `K => <K as TraitName>::Associated`
*/
#[derive(Clone, syn_derive::Parse, syn_derive::ToTokens)]
pub(crate) struct SchemaParamsOverride {
pub struct SchemaParamsOverride {
pub order_param: Ident,
arrow_token: Token![=>],
pub override_type: Type,
Expand Down
3 changes: 3 additions & 0 deletions borsh-derive-internal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,8 @@ pub use struct_ser::struct_ser;
pub use union_de::union_de;
pub use union_ser::union_ser;

// TODO: similarly reexport this struct for documentation in `borsh-derive` when unsplit is done
pub use attribute_helpers::parsing_helpers::SchemaParamsOverride;

#[cfg(test)]
pub mod test_helpers;
332 changes: 332 additions & 0 deletions borsh-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,104 @@ use borsh_derive_internal::*;
#[cfg(feature = "schema")]
use borsh_schema_derive_internal::*;

/**
# derive proc-macro for `borsh::ser::BorshSerialize` trait
## Bounds
Generally, `BorshSerialize` adds `borsh::ser::BorshSerialize` bound to any type parameter
found in item's fields.
```ignore
/// impl<U, V> borsh::ser::BorshSerialize for A<U, V>
/// where
/// U: borsh::ser::BorshSerialize,
/// V: borsh::ser::BorshSerialize,
#[derive(BorshSerialize)]
struct A<U, V> {
x: U,
y: V,
}
```
```ignore
/// impl<U, V> borsh::ser::BorshSerialize for A<U, V>
/// where
/// U: borsh::ser::BorshSerialize,
#[derive(BorshSerialize)]
struct A<U, V> {
x: U,
#[borsh_skip]
y: V,
}
```
## Attributes
### `borsh_skip` (field level attribute)
`borsh_skip` makes derive skip serializing annotated field.
`borsh_skip` makes derive skip adding any type parameters, present in the field, to parameters bound by `borsh::ser::BorshSerialize`.
```ignore
#[derive(BorshSerialize)]
struct A {
x: u64,
#[borsh_skip]
y: f32,
}
```
### `#[borsh(bound(serialize = ...))]` (field level attribute)
#### syntax
Attribute takes literal string value, which is a comma-separated list of [WherePredicate](syn::WherePredicate)-s, which may be empty.
#### usage
Attribute adds possibility to override bounds for `BorshSerialize` in order to enable removal
of bounds on type parameters from struct/enum definition itself and fixing complex cases,
when derive hasn't figured out the right bounds on type parameters automatically.
```ignore
/// additional bound `T: PartialOrd` (required by `HashMap`) is injected into
/// derived trait implementation via attribute to avoid adding the bounds on the struct itself
#[derive(BorshSerialize)]
struct A<T, U> {
a: String,
#[borsh(bound(serialize =
"T: borsh::ser::BorshSerialize + PartialOrd,
U: borsh::ser::BorshSerialize"))]
b: HashMap<T, U>,
}
```
```ignore
/// derive here figures the bound erroneously as `T: borsh::ser::BorshSerialize`
#[derive(BorshSerialize)]
struct A<T, V>
where
T: TraitName,
{
#[borsh(bound(serialize = "<T as TraitName>::Associated: borsh::ser::BorshSerialize"))]
field: <T as TraitName>::Associated,
another: V,
}
```
#### interaction with `#[borsh_skip]`
`#[borsh(bound(serialize = ...))]` replaces bounds, which are derived automatically,
irrelevant of whether `#[borsh_skip]` attribute is present.
#### interaction with `#[borsh(bound(deserialize = ...))]`
Both attributes may be used simultaneously, separated by a comma: `#[borsh(bound(serialize = ..., deserialize = ...))]`
*/
#[proc_macro_derive(BorshSerialize, attributes(borsh_skip, borsh))]
pub fn borsh_serialize(input: TokenStream) -> TokenStream {
let name = &crate_name("borsh").unwrap();
Expand All @@ -34,6 +132,150 @@ pub fn borsh_serialize(input: TokenStream) -> TokenStream {
})
}

/**
# derive proc-macro for `borsh::de::BorshDeserialize` trait
## Bounds
Generally, `BorshDeserialize` adds `borsh::de::BorshDeserialize` bound to any type parameter
found in item's fields and `core::default::Default` bound to any type parameter found
in item's skipped fields.
```ignore
/// impl<U, V> borsh::de::BorshDeserialize for A<U, V>
/// where
/// U: borsh::de::BorshDeserialize,
/// V: borsh::de::BorshDeserialize,
#[derive(BorshDeserialize)]
struct A<U, V> {
x: U,
y: V,
}
```
```ignore
/// impl<U, V> borsh::de::BorshDeserialize for A<U, V>
/// where
/// U: borsh::de::BorshDeserialize,
/// V: core::default::Default,
#[derive(BorshDeserialize)]
struct A<U, V> {
x: U,
#[borsh_skip]
y: V,
}
```
## Attributes
### `borsh_init` (item level attribute)
`borsh_init` allows to automatically run an initialization function right after deserialization.
This adds a lot of convenience for objects that are architectured to be used as strictly immutable.
```ignore
#[derive(BorshDeserialize)]
#[borsh_init(init)]
struct Message {
message: String,
timestamp: u64,
public_key: CryptoKey,
signature: CryptoSignature,
hash: CryptoHash,
}
impl Message {
pub fn init(&mut self) {
self.hash = CryptoHash::new().write_string(self.message).write_u64(self.timestamp);
self.signature.verify(self.hash, self.public_key);
}
}
```
### `borsh_skip` (field level attribute)
`borsh_skip` makes derive skip deserializing annotated field.
`borsh_skip` makes derive skip adding any type parameters, present in the field, to parameters bound by `borsh::de::BorshDeserialize`.
It adds `core::default::Default` bound to any
parameters encountered in annotated field.
```ignore
#[derive(BorshDeserialize)]
struct A {
x: u64,
#[borsh_skip]
y: f32,
}
```
### `#[borsh(bound(deserialize = ...))]` (field level attribute)
#### syntax
Attribute takes literal string value, which is a comma-separated list of [WherePredicate](syn::WherePredicate)-s, which may be empty.
#### usage
Attribute adds possibility to override bounds for `BorshDeserialize` in order to enable removal
of bounds on type parameters from struct/enum definition itself and fixing complex cases,
when derive hasn't figured out the right bounds on type parameters automatically.
```ignore
/// additional bounds `T: PartialOrd + Hash + Eq` (required by `HashMap`) are injected into
/// derived trait implementation via attribute to avoid adding the bounds on the struct itself
#[derive(BorshDeserialize)]
struct A<T, U> {
a: String,
#[borsh(bound(
deserialize =
"T: PartialOrd + Hash + Eq + borsh::de::BorshDeserialize,
U: borsh::de::BorshDeserialize"
))]
b: HashMap<T, U>,
}
```
```ignore
// derive here figures the bound erroneously as `T: borsh::de::BorshDeserialize,`
#[derive(BorshDeserialize)]
struct A<T, V>
where
T: TraitName,
{
#[borsh(bound(deserialize = "<T as TraitName>::Associated: borsh::de::BorshDeserialize"))]
field: <T as TraitName>::Associated,
another: V,
}
```
#### interaction with `#[borsh_skip]`
`#[borsh(bound(deserialize = ...))]` replaces bounds, which are derived automatically,
irrelevant of whether `#[borsh_skip]` attribute is present.
```ignore
/// implicit derived `core::default::Default` bounds on `K` and `V` type parameters are removed by
/// empty bound specified, as `HashMap` has its own `Default` implementation
#[derive(BorshDeserialize)]
struct A<K, V, U>(
#[borsh_skip]
#[borsh(bound(deserialize = ""))]
HashMap<K, V>,
U,
);
```
#### interaction with `#[borsh(bound(serialize = ...))]`
Both attributes may be used simultaneously, separated by a comma: `#[borsh(bound(serialize = ..., deserialize = ...))]`
*/
#[proc_macro_derive(BorshDeserialize, attributes(borsh_skip, borsh_init, borsh))]
pub fn borsh_deserialize(input: TokenStream) -> TokenStream {
let name = &crate_name("borsh").unwrap();
Expand All @@ -59,6 +301,96 @@ pub fn borsh_deserialize(input: TokenStream) -> TokenStream {
})
}

/**
# derive proc-macro for `borsh::BorshSchema` trait
## Bounds
Generally, `BorshSchema` adds `borsh::BorshSchema` bound to any type parameter
found in item's fields.
```ignore
/// impl<U, V> borsh::BorshSchema for A<U, V>
/// where
/// U: borsh::BorshSchema,
/// V: borsh::BorshSchema,
#[derive(BorshSchema)]
struct A<U, V> {
x: U,
y: V,
}
```
```ignore
/// impl<U, V> borsh::BorshSchema for A<U, V>
/// where
/// U: borsh::BorshSchema,
#[derive(BorshSchema)]
struct A<U, V> {
x: U,
#[borsh_skip]
y: V,
}
```
## Attributes
### `borsh_skip` (field level attribute)
`borsh_skip` makes derive skip including schema from annotated field into schema's implementation.
`borsh_skip` makes derive skip adding any type parameters, present in the field, to parameters bound by `borsh::BorshSchema`.
```ignore
#[derive(BorshSchema)]
struct A {
x: u64,
#[borsh_skip]
y: f32,
}
```
### `#[borsh(schema(params = ...))]` (field level attribute)
#### syntax
Attribute takes literal string value, which is a comma-separated list of [SchemaParamsOverride]-s, which may be empty.
#### usage
It may be used to fix complex cases, when derive hasn't figured out the right bounds on type parameters and declaration
parameters automatically.
[SchemaParamsOverride] describes an entry like `order_param => override_type`,
e.g. `K => <K as TraitName>::Associated`.
Such an entry instructs `BorshSchema` derive to:
1. add `override_type` to types, bounded by `borsh::BorshSchema` in implementation block.
2. add `<override_type>::declaration()` to parameters vector in `fn declaration()` method of `BorshSchema` trait that is being derived.
3. the `order_param` is required to establish the same order in parameters vector (2.) as that of type parameters in generics of type, that `BorshSchema` is derived for.
4. entries, specified for a field, together replace whatever would've been derived automatically for 1. and 2. .
```ignore
// derive here figures the bound erroneously as `T: borsh::BorshSchema` .
// attribute replaces it with <T as TraitName>::Associated: borsh::BorshSchema`
#[derive(BorshSchema)]
struct A<V, T>
where
T: TraitName,
{
#[borsh(schema(params = "T => <T as TraitName>::Associated"))]
field: <T as TraitName>::Associated,
another: V,
}
```
#### interaction with `#[borsh_skip]`
`#[borsh(schema(params = ...))]` is ignored if field is also annotated with `#[borsh_skip]`.
*/
#[cfg(feature = "schema")]
#[proc_macro_derive(BorshSchema, attributes(borsh_skip, borsh))]
pub fn borsh_schema(input: TokenStream) -> TokenStream {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ fn parse_lit_into<T: syn::parse::Parse>(
}
}

/// struct describes entries like `K => <K as TraitName>::Associated`
/**
Struct describes an entry like `order_param => override_type`, e.g. `K => <K as TraitName>::Associated`
*/
#[derive(Clone, syn_derive::Parse, syn_derive::ToTokens)]
pub(crate) struct SchemaParamsOverride {
pub struct SchemaParamsOverride {
pub order_param: Ident,
arrow_token: Token![=>],
pub override_type: Type,
Expand Down

0 comments on commit d90216f

Please sign in to comment.