sbor_derive/
lib.rs

1use proc_macro::TokenStream;
2use std::str::FromStr;
3mod eager;
4
5/// Derive code that returns the value kind.
6#[proc_macro_derive(Categorize, attributes(sbor))]
7pub fn categorize(input: TokenStream) -> TokenStream {
8    sbor_derive_common::categorize::handle_categorize(proc_macro2::TokenStream::from(input), None)
9        .unwrap_or_else(|err| err.to_compile_error())
10        .into()
11}
12
13/// Derive code that encodes this data structure
14#[proc_macro_derive(Encode, attributes(sbor))]
15pub fn encode(input: TokenStream) -> TokenStream {
16    sbor_derive_common::encode::handle_encode(proc_macro2::TokenStream::from(input), None)
17        .unwrap_or_else(|err| err.to_compile_error())
18        .into()
19}
20
21/// Derive code that decodes this data structure from a byte array.
22#[proc_macro_derive(Decode, attributes(sbor))]
23pub fn decode(input: TokenStream) -> TokenStream {
24    sbor_derive_common::decode::handle_decode(proc_macro2::TokenStream::from(input), None)
25        .unwrap_or_else(|err| err.to_compile_error())
26        .into()
27}
28
29/// Derive code that describes this type.
30#[proc_macro_derive(Describe, attributes(sbor))]
31pub fn describe(input: TokenStream) -> TokenStream {
32    sbor_derive_common::describe::handle_describe(proc_macro2::TokenStream::from(input), None)
33        .unwrap_or_else(|err| err.to_compile_error())
34        .into()
35}
36
37/// A shortcut for [`Categorize`], [`Encode`], [`Decode`], and [`Describe`] derives.
38///
39#[proc_macro_derive(Sbor, attributes(sbor))]
40pub fn sbor(input: TokenStream) -> TokenStream {
41    sbor_derive_common::sbor::handle_sbor(proc_macro2::TokenStream::from(input), None, None)
42        .unwrap_or_else(|err| err.to_compile_error())
43        .into()
44}
45
46/// An empty derive which exists solely to allow the helper "sbor" attribute
47/// to be used without generating a compile error.
48///
49/// The intended use-case is as a utility for building other macros,
50/// which wish to add sbor attribute annotations to types for when they do
51/// use an Sbor derive - but wish to avoid the following error when they don't:
52/// ```text
53/// error: cannot find attribute `sbor` in this scope
54/// ```
55///
56/// Ideally this would output an empty token stream, but instead we
57/// return a simply comment, to avoid the proc macro system thinking
58/// the macro build has broken and returning this error:
59/// ```text
60/// proc macro `PermitSborAttributes` not expanded: internal error
61/// ```
62#[proc_macro_derive(PermitSborAttributes, attributes(sbor))]
63pub fn permit_sbor_attributes(_: TokenStream) -> TokenStream {
64    TokenStream::from_str(&"// Empty PermitSborAttributes expansion").unwrap()
65}
66
67/// NOTE: This should probably be moved out of sbor to its own crate.
68///
69/// This macro is a powerful but simple general-purpose tool to ease building declarative macros which create
70/// new types.
71///
72/// # Motivation and Examples
73///
74/// Effectively it functions as a more powerful version of [paste!](https://github.com/dtolnay/paste),
75/// whilst bringing the power of [quote!](https://docs.rs/quote/latest/quote/)'s variable
76/// substitution to declarative macros.
77///
78/// This approach neatly solves the following cases:
79/// 1. Wanting `paste!` to output strings or work with [attributes other than doc](https://github.com/dtolnay/paste/issues/40#issuecomment-2062953012).
80/// 2. Improves readability of long procedural macros through substitution of repeated segments.
81/// 3. Avoiding defining internal `macro_rules!` to handle instances where you need to do a procedural macro repeat across two conflicting expansions .
82/// 4. Alternatives to [meta-variables](https://github.com/rust-lang/rust/issues/83527) such as `$count`, `$index` before
83///    they are stabilized, and alternatives to some forms of append-only recursive declarative macros.
84///
85/// An example of case 1:
86/// ```rust
87/// # extern crate sbor_derive;
88/// # use sbor_derive::*;
89/// #
90/// macro_rules! impl_new_type {
91///     {
92///         $vis:vis $my_type:ident($my_inner_type:ty)
93///     } => {eager_replace!{
94///         #[sbor(as_type = [!stringify! $my_inner_type])]
95///         $vis struct $my_type($my_inner_type)
96///
97///         // ...
98///     }}
99/// }
100/// ```
101///
102/// The following is an example of case 2 and case 3, which creates a much more readable macro.
103/// This example is hard to do with a normal macro, because the iteration of the generics in `#ImplGenerics` and `#MyType` wouldn't be compatible with the iteration over `$trait`.
104/// Instead, you have to work around it, for example with internal `macro_rules!` definitions [as per this stack overflow post](https://stackoverflow.com/a/73543948).
105///
106/// Using the `!SET!` functionality, we can define these token streams earlier and output them in each loop iteration.
107/// This also makes the intention of the macro writer much clearer, similar to [quote!](https://docs.rs/quote/latest/quote/)
108/// in procedural macros:
109/// ```rust
110/// # extern crate sbor_derive;
111/// # use sbor_derive::*;
112/// #
113/// macro_rules! impl_marker_traits {
114///     {
115///         $vis:vis $type_name:ident
116///         // Arbitrary generics
117///         $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? $( = $deflt:tt)? ),+ >)?
118///         [
119///             $($trait:ident),*
120///             $(,)? // Optional trailing comma
121///         ]
122///     } => {eager_replace!{
123///         [!SET! #ImplGenerics = $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?]
124///         [!SET! #TypeGenerics = $(< $( $lt ),+ >)?]
125///         [!SET! #MyType = $type_name #TypeGenerics]
126///
127///         // Output for each marker trait
128///         $(
129///             impl #ImplGenerics $trait for #MyType {}
130///         )*
131///     }}
132/// }
133/// ```
134///
135/// An example of case 4 - a simple count function, without needing recursive macros:
136/// ```rust
137/// # extern crate sbor_derive;
138/// # use sbor_derive::*;
139/// #
140/// macro_rules! count_idents {
141///     {
142///         $($value: ident),*
143///     } => {eager_replace!{
144///         [!SET! #index = 0]
145///         $(
146///             [!SET! #ignored = $value]
147///             [!SET! #index = #index + 1]
148///         )*
149///         #index
150///     }}
151/// }
152/// ```
153/// To quickly work this through, take `count_idents!(a, b, c)`. As a first pass, the declarative macro expands, giving:
154/// ```text
155/// eager_replace!{
156///   [!SET! #index = 0]
157///   [!SET! ignored = a]
158///   [!SET! #index = #index + 1]
159///   [!SET! ignored = b]
160///   [!SET! #index = #index + 1]
161///   [!SET! ignored = c]
162///   [!SET! #index = #index + 1]
163///   #index
164/// }
165/// ```
166/// Which then evaluates by setting `#index` to the token stream `0 + 1 + 1 + 1`, and then outputting that sum.
167///
168/// # Details
169/// ## Specific functions
170///
171/// * `[!concat! X Y " " Z (Hello World)]` gives `"XY Z(HelloWorld)"` by concatenating each argument without spaces, and recursing inside groups. String and char literals are first unquoted. Spaces can be added with " ".
172/// * `[!ident! X Y "Z"]` gives an ident `XYZ`, using the same algorithm as `concat`.
173/// * `[!literal! 31 u 32]` gives `31u32`, using the same algorithm as `concat`.
174/// * `[!raw! abc #abc [!ident! test]]` outputs its contents without any nested expansion, giving `abc #abc [!ident! test]`.
175/// * `[!stringify! X Y " " Z]` gives `"X Y \" \" Z"` - IMPORTANT: This uses `token_stream.into_string()` which is compiler-version dependent. Do not use if that is important. Instead, the output from `concat` should be independent of compiler version.
176///
177/// Note that all functions except `raw` resolve in a nested manner as you would expected, e.g.
178/// ```rust,ignore
179/// [!ident! X Y [!ident! Hello World] Z] // "XYHelloWorldZ"
180/// ```
181///
182/// ## Variables
183///
184/// You can define variables starting with `#` which can be used outside the set call.
185/// All of the following calls don't return anything, but create a variable, which can be embedded later in the macro.
186///
187/// * `[!SET! #MyVar = ..]` sets `#MyVar` to the given token stream.
188/// * `[!SET:concat! #MyVar = ..]` sets `#MyVar` to the result of applying the `concat` function to the token stream.
189/// * `[!SET:ident! #MyVar = ..]` sets `#MyVar` to the result of applying the `ident` function to the token stream.
190/// * `[!SET:literal! #MyVar = ..]` sets `#MyVar` to the result of applying the `literal` function to the token stream.
191/// * `[!SET:raw! #MyVar = ..]` sets `#MyVar` to the result of applying the `raw` function to the token stream.
192/// * `[!SET:stringify! #MyVar = ..]` sets `#MyVar` to the result of applying the `stringify` function to the token stream.
193///
194/// # Future extensions
195/// ## String case conversion
196///
197/// This could in future support case conversion like [paste](https://docs.rs/paste/1.0.15/paste/index.html).
198/// e.g. `[!snakecase! ..]`, `[!camelcase! ..]`, `[!uppercase! ..]`, `[!lowercase! ..]`, `[!capitalize! ..]`, `[!decapitalize! ..]`.
199/// Which all use the `concat` algorithm to combine inputs, and then apply a string function.
200///
201/// These can be composed to achieve things like `UPPER_SNAKE_CASE` or `lowerCamelCase`,
202///
203/// # Hypothetical extensions
204/// None of these are likely additions, but in theory, this system could be made turing complete to decrease the amount
205/// you have to reach for writing your own procedural macros.
206///
207/// ## Functions returning literals
208/// * Integer functions like `[!sum! a b]`, `[!mod! a b]` which work on integer literal tokens.
209/// * Boolean conditionals like `[!eq! a b]`, `[!lt! a b]`, `[!lte! a b]` operating on literals `[!contains! needle (haystack)]`
210///
211/// ## Eager expansion of macros
212/// When eager expansion of macros returning literals from https://github.com/rust-lang/rust/issues/90765 is stabilized,
213/// things like `[!expand_literal_macros! include!("my-poem.txt")]` will be possible.
214///
215/// ## Conditions and if statements
216/// `[!IF! cond { .. } !ELSE! { .. }]`, for example `[!IF! [!eq! [!mod! $length 2] 0] { "even length" } !ELSE! { "odd length" }]`.
217///
218/// ## Labels and gotos
219/// `[!LABEL:loop!]` and `[!GOBACKTO:loop!]` would bring turing completeness - although it would need a re-architecture
220/// of the token streaming logic to support jumping backwards in the stream.
221#[proc_macro]
222pub fn eager_replace(token_stream: TokenStream) -> TokenStream {
223    eager::replace(proc_macro2::TokenStream::from(token_stream))
224        .unwrap_or_else(|err| err.to_compile_error())
225        .into()
226}
227
228const BASIC_CUSTOM_VALUE_KIND: &str = "sbor::NoCustomValueKind";
229const BASIC_CUSTOM_TYPE_KIND: &str = "sbor::NoCustomTypeKind";
230const BASIC_CUSTOM_SCHEMA: &str = "sbor::NoCustomSchema";
231
232/// Derive code that returns the value kind - specifically for Basic SBOR.
233#[proc_macro_derive(BasicCategorize, attributes(sbor))]
234pub fn basic_categorize(input: TokenStream) -> TokenStream {
235    sbor_derive_common::categorize::handle_categorize(
236        proc_macro2::TokenStream::from(input),
237        Some(BASIC_CUSTOM_VALUE_KIND),
238    )
239    .unwrap_or_else(|err| err.to_compile_error())
240    .into()
241}
242
243/// Derive code that encodes this data structure - specifically for Basic SBOR.
244#[proc_macro_derive(BasicEncode, attributes(sbor))]
245pub fn basic_encode(input: TokenStream) -> TokenStream {
246    sbor_derive_common::encode::handle_encode(
247        proc_macro2::TokenStream::from(input),
248        Some(BASIC_CUSTOM_VALUE_KIND),
249    )
250    .unwrap_or_else(|err| err.to_compile_error())
251    .into()
252}
253
254/// Derive code that decodes this data structure from a byte array - specifically for Basic SBOR.
255#[proc_macro_derive(BasicDecode, attributes(sbor))]
256pub fn basic_decode(input: TokenStream) -> TokenStream {
257    sbor_derive_common::decode::handle_decode(
258        proc_macro2::TokenStream::from(input),
259        Some(BASIC_CUSTOM_VALUE_KIND),
260    )
261    .unwrap_or_else(|err| err.to_compile_error())
262    .into()
263}
264
265/// Derive code that describes the type - specifically for Basic SBOR.
266#[proc_macro_derive(BasicDescribe, attributes(sbor))]
267pub fn basic_describe(input: TokenStream) -> TokenStream {
268    sbor_derive_common::describe::handle_describe(
269        proc_macro2::TokenStream::from(input),
270        Some(BASIC_CUSTOM_TYPE_KIND),
271    )
272    .unwrap_or_else(|err| err.to_compile_error())
273    .into()
274}
275
276/// A shortcut for [`BasicCategorize`], [`BasicEncode`], [`BasicDecode`], and [`BasicDescribe`] derives.
277///
278#[proc_macro_derive(BasicSbor, attributes(sbor))]
279pub fn basic_sbor(input: TokenStream) -> TokenStream {
280    sbor_derive_common::sbor::handle_sbor(
281        proc_macro2::TokenStream::from(input),
282        Some(BASIC_CUSTOM_VALUE_KIND),
283        Some(BASIC_CUSTOM_TYPE_KIND),
284    )
285    .unwrap_or_else(|err| err.to_compile_error())
286    .into()
287}
288
289/// A macro for outputting tests and marker traits to assert that a type has maintained its shape over time.
290///
291/// There are two types of assertion modes:
292/// * `fixed` mode is used to ensure that a type is unchanged.
293/// * `backwards_compatible` mode is used when multiple versions of the type are permissible, but
294///   newer versions of the type must be compatible with the older version where they align.
295///   This mode (A) ensures that the type's current schema is equivalent to the latest version, and
296///   (B) ensures that each of the schemas is a strict extension of the previous mode.
297///
298/// ## Initial schema generation and regeneration
299///
300/// To output a generated schema, temporarily add a `generate` parameter or a `regenerate` parameter,
301/// after the `fixed` or `backwards_compatible` parameter, and then run the created test.
302/// If using Rust Analyzer this can be run from the IDE, or it can be run via `cargo test`.
303///
304/// To protect against accidentally doing the wrong thing, `generate` can only be used for initial generation,
305/// whereas `regenerate` can only be used for replacing an existing generation.
306///
307/// If a "FILE:.." path is specified, it will (re)generate that file, else it will output to the console:
308/// * In `fixed` mode, this will (re)generate against the given schema location.
309/// * In `backwards_compatible` mode, this will (re)generate against the latest schema location (the last in the list).
310///
311/// The test will then panic to ensure it fails, and can't be left accidentally in (re)generate state.
312///
313/// ```ignore
314/// # // Ignored because the generated code references sbor which can't be imported
315/// # // by the doctest framework, because it doesn't know what those crates are
316/// # extern crate sbor_derive;
317/// # use sbor_derive::*;
318/// #
319/// #[derive(BasicSbor, BasicSborAssertion)]
320/// #[sbor_assert(fixed("FILE:MyType-schema-v1.txt"), generate)]
321/// struct MyType {
322///     // ...
323/// }
324/// ```
325///
326/// ## Fixed schema verification
327///
328/// To verify the type's schema is unchanged, do:
329/// ```ignore
330/// # // Ignored because the generated code references sbor which can't be imported
331/// # // by the doctest framework, because it doesn't know what those crates are
332/// # extern crate sbor_derive;
333/// # use sbor_derive::*;
334/// #
335/// #[derive(BasicSbor, BasicSborAssertion)]
336/// #[sbor_assert(fixed("FILE:MyType-schema-v1.txt"))]
337/// struct MyType {
338///     // ...
339/// }
340/// ```
341///
342/// Instead of `"FILE:X"`, you can also use `"INLINE:<hex>"`, `"CONST:<Constant>"` or `"EXPR:<Expression>"`
343/// where the expression (such as `generate_schema()`) has to generate a `SingleTypeSchema<NoCustomSchema>`.
344///
345/// ## Backwards compatibility verification
346///
347/// To allow multiple backwards-compatible versions, you can do this:
348/// ```ignore
349/// # // Ignored because the generated code references sbor which can't be imported
350/// # // by the doctest framework, because it doesn't know what those crates are
351/// # extern crate sbor_derive;
352/// # use sbor_derive::*;
353/// #
354/// #[derive(BasicSbor, BasicSborAssertion)]
355/// #[sbor_assert(backwards_compatible(
356///     version1 = "FILE:MyType-schema-v1.txt",
357///     version2 = "FILE:MyType-schema-v2.txt",
358/// ))]
359/// struct MyType {
360///     // ...
361/// }
362/// ```
363///
364/// Instead of `"FILE:X"`, you can also use `"INLINE:<hex>"`, `"CONST:<Constant>"` or `"EXPR:<Expression>"`
365/// where the expression (such as `generate_schema()`) has to generate a `SingleTypeSchema<NoCustomSchema>`.
366///
367/// If you wish to configure exactly which schemas are used for comparison of the current schema with
368/// the latest named schema; and each named schema with its predecessor, you can use:
369///
370/// ```ignore
371/// # // Ignored because the generated code references sbor which can't be imported
372/// # // by the doctest framework, because it doesn't know what those crates are
373/// # extern crate sbor_derive;
374/// # use sbor_derive::*;
375/// #
376/// #[sbor_assert(backwards_compatible("EXPR:<Expression>"))
377/// ```
378/// Where the expression (such as `params_builder()`) has to generate a `SingleTypeSchemaCompatibilityParameters<NoCustomSchema>`.
379///
380/// ## Custom settings
381/// By default, the `fixed` mode will use `SchemaComparisonSettings::require_equality()` and
382/// the `backwards_compatible` mode will use `SchemaComparisonSettings::require_equality()` for the check
383/// of `current` aginst the latest version, and `SchemaComparisonSettings::allow_extension()` for the
384/// checks between consecutive versions.
385///
386/// You may wish to change these:
387/// * If you just wish to ignore the equality of metadata such as names, you can use the
388///   `allow_name_changes` flag.
389/// * If you wish to override all settings, you can provide a constant containing your
390///   own SchemaComparisonSettings.
391/// * If you wish to specify a builder for settings, you can provide `"EXPR:|builder| builder.<stuff>"`
392/// * If for `backwards_compatible`, you wish to provide a separate configuration for the "latest" and
393///   "named versions" checks, you can use `settings(comparison_between_versions = \"EXPR:F1\", comparison_between_current_and_latest = \"EXPR:F2\") `
394///    
395///
396/// For example:
397/// ```ignore
398/// # // Ignored because the generated code references sbor which can't be imported
399/// # // by the doctest framework, because it doesn't know what those crates are
400/// # extern crate sbor_derive;
401/// # use sbor_derive::*;
402/// #
403/// #[derive(BasicSbor, BasicSborAssertion)]
404/// #[sbor_assert(
405///     fixed("FILE:MyType-schema-v1.txt"),
406///     settings(allow_name_changes),
407/// )]
408/// struct MyType {
409///     // ...
410/// }
411///
412/// #[derive(BasicSbor, BasicSborAssertion)]
413/// #[sbor_assert(
414///     backwards_compatible(
415///         v1 = "FILE:MyType-schema-v1.txt",
416///         v2 = "FILE:MyType-schema-v2.txt",
417///     ),
418///     settings(
419///         // We allow name changes between versions, but require the current schema to exactly match
420///         // the latest version (v2 in this case).
421///         // This could be useful to e.g. ensure that we have a fixed schema with the latest naming available.
422///         comparison_between_versions = "EXPR: |s| s.allow_all_name_changes()",
423///         comparison_between_current_and_latest = "EXPR: |s| s",
424///     ),
425/// )]
426/// struct MyType {
427///     // ...
428/// }
429/// ```
430#[proc_macro_derive(BasicSborAssertion, attributes(sbor_assert))]
431pub fn basic_sbor_assertion(input: TokenStream) -> TokenStream {
432    sbor_derive_common::sbor_assert::handle_sbor_assert_derive(
433        proc_macro2::TokenStream::from(input),
434        BASIC_CUSTOM_SCHEMA,
435    )
436    .unwrap_or_else(|err| err.to_compile_error())
437    .into()
438}