carbon_proc_macros/lib.rs
1//! # Carbon Proc Macros
2//!
3//! `carbon-proc-macros` is a collection of procedural macros designed to
4//! simplify and enhance Rust-based development for Solana programs using the
5//! Carbon framework. This crate provides macros for generating
6//! deserialization implementations, instruction decoders, and type conversions.
7//!
8//! ## Overview
9//!
10//! The macros in this crate are intended to streamline common patterns
11//! encountered when working with Carbon, particularly around deserialization,
12//! instruction decoding, and structuring custom types. By leveraging
13//! `carbon-proc-macros`, you can reduce the amount of manual coding and ensure
14//! consistent, performant handling of Solana-specific data structures.
15//!
16//! ## Key Features
17//!
18//! - **`CarbonDeserialize`**: Automatically implement the `CarbonDeserialize`
19//! trait for structs and enums, enabling Borsh-based deserialization with
20//! optional discriminators for type validation.
21//! - **`Instruction Decoder Collection`**: Create and manage complex
22//! instruction decoders for multiple Solana programs, simplifying how
23//! instructions are parsed and categorized.
24//! - **`InstructionType` Derivation**: Derive `InstructionType` enums that
25//! mirror existing enum structures, providing a simplified, data-free version
26//! of each variant.
27//!
28//! ## Usage
29//!
30//! To use any of the provided macros, simply import the desired macro into your
31//! Rust program and apply it to the relevant struct or enum.
32//!
33//! ## Notes
34//!
35//! - This crate relies on the `borsh` library for serialization and
36//! deserialization, so ensure the relevant dependencies are included in your
37//! project.
38//! - The macros provided are optimized for use within the Carbon framework.
39//!
40//! ## Contribution
41//!
42//! Contributions are welcome! If you have ideas for improving or expanding the
43//! functionality of `carbon_macros`, please consider submitting a pull request
44//! or opening an issue on the project’s GitHub repository.
45use {
46 borsh_derive_internal::*,
47 proc_macro::TokenStream,
48 proc_macro2::{Span, TokenStream as TokenStream2},
49 quote::{format_ident, quote},
50 syn::{
51 parse::{Parse, ParseStream},
52 parse_macro_input, DeriveInput, Ident, Item, ItemEnum, Lit, Meta, NestedMeta, Token,
53 TypePath,
54 },
55};
56
57/// Automatically generates an implementation of the `CarbonDeserialize` trait.
58///
59/// This derive macro creates the `CarbonDeserialize` implementation for a given
60/// struct or enum, enabling deserialization from a byte slice using the `borsh`
61/// serialization format. If a field in the struct or enum is marked with the
62/// `#[carbon(discriminator)]` attribute, the macro uses this field's value as a
63/// discriminator to match and validate data during deserialization.
64///
65/// # Syntax
66///
67/// To use this macro, annotate your struct or enum with
68/// `#[derive(CarbonDeserialize)]`. Optionally, use the `#[carbon(discriminator
69/// = "0x...")]` attribute to specify a unique discriminator for this type. This
70/// discriminator is validated at the start of the byte slice before proceeding
71/// with full deserialization.
72///
73/// ```ignore
74/// #[derive(CarbonDeserialize)]
75/// #[carbon(discriminator = "0x1234")]
76/// struct MyStruct {
77/// id: u32,
78/// data: String,
79/// }
80/// ```
81///
82/// # Example
83///
84/// ```ignore
85/// use carbon_proc_macros::CarbonDeserialize;
86///
87/// #[derive(CarbonDeserialize)]
88/// #[carbon(discriminator = "0x01")]
89/// struct Message {
90/// header: u16,
91/// body: Vec<u8>,
92/// }
93///
94/// let bytes = vec![0x01, 0x00, 0x10, 0x20, 0x30]; // Serialized data
95/// let message = Message::deserialize(&bytes)
96/// .expect("Failed to deserialize `Message`");
97/// ```
98///
99/// # Parameters
100///
101/// - `input_token_stream`: A `TokenStream` containing the syntax tree of the
102/// input type (struct or enum). The macro parses this to generate the
103/// corresponding `CarbonDeserialize` implementation.
104///
105/// # Return
106///
107/// Returns a `TokenStream` representing the generated `CarbonDeserialize`
108/// implementation. The function expects the target type to implement the
109/// `borsh::BorshDeserialize` trait to support deserialization.
110///
111/// # Notes
112///
113/// - The `#[carbon(discriminator = "0x...")]` attribute is optional. If not
114/// provided, the deserialization proceeds without a discriminator check.
115/// - Ensure the discriminator matches the data's format exactly, as the
116/// deserialization will return `None` if there is a mismatch.
117/// - The macro will panic if the discriminator is invalid or not provided
118/// correctly as a hex string when expected.
119///
120/// # Errors
121///
122/// - The macro will return `None` during deserialization if the data is shorter
123/// than the discriminator or if there is a mismatch between the provided and
124/// expected discriminators.
125#[proc_macro_derive(CarbonDeserialize, attributes(carbon))]
126pub fn carbon_deserialize_derive(input_token_stream: TokenStream) -> TokenStream {
127 let derive_input = input_token_stream.clone();
128 let input = parse_macro_input!(derive_input as DeriveInput);
129 let name = &input.ident;
130
131 let discriminator = get_discriminator(&input.attrs).unwrap_or(quote! { &[] });
132 let deser = gen_borsh_deserialize(input_token_stream);
133
134 let expanded = quote! {
135 #deser
136
137 #[automatically_derived]
138 impl carbon_core::deserialize::CarbonDeserialize for #name {
139 fn deserialize(data: &[u8]) -> Option<Self> {
140 let discriminator: &[u8] = #discriminator;
141 if data.len() < discriminator.len() {
142 return None;
143 }
144
145
146 let (disc, mut rest) = data.split_at(discriminator.len());
147 if disc != discriminator {
148 return None;
149 }
150
151 match carbon_core::borsh::BorshDeserialize::deserialize(&mut rest) {
152 Ok(res) => {
153 if !rest.is_empty() {
154 carbon_core::log::warn!(
155 "Not all bytes were read when deserializing {}: {} bytes remaining",
156 stringify!(#name),
157 rest.len(),
158 );
159 }
160 Some(res)
161 }
162 Err(_) => None,
163 }
164 }
165 }
166 };
167
168 TokenStream::from(expanded)
169}
170
171/// Generates an implementation of the `CarbonDeserialize` trait for a given
172/// type.
173///
174/// This procedural macro automatically derives the `CarbonDeserialize` trait
175/// for structs, enums, or unions, enabling them to be deserialized using Borsh
176/// serialization format. The generated implementation includes type checking
177/// and allows for customized deserialization using the `#[carbon]` attribute to
178/// specify a unique discriminator for the type.
179///
180/// # Syntax
181///
182/// To use this macro, annotate the target type with
183/// `#[derive(CarbonDeserialize)]`. Optionally, you can specify a
184/// `#[carbon(discriminator = "...")]` attribute to define a custom
185/// discriminator, which will be checked during deserialization.
186///
187/// # Example
188///
189/// ```ignore
190/// use carbon_proc_macros::CarbonDeserialize;
191///
192/// #[derive(CarbonDeserialize)]
193/// #[carbon(discriminator = "0x1234")]
194/// struct MyStruct {
195/// id: u32,
196/// name: String,
197/// }
198///
199/// let bytes = ...; // serialized bytes
200/// let my_struct = MyStruct::deserialize(&bytes)
201/// .expect("Failed to deserialize `MyStruct`");
202/// ```
203///
204/// # Parameters
205///
206/// - `input_token_stream`: A `TokenStream` containing the parsed syntax tree of
207/// the target type definition. This input is processed to generate the
208/// appropriate `CarbonDeserialize` implementation.
209///
210/// # Return
211///
212/// Returns a `TokenStream` containing the implementation of the
213/// `CarbonDeserialize` trait for the given type. If successful, this enables
214/// Borsh deserialization with the custom discriminator check.
215///
216/// # Errors
217///
218/// This macro will panic if the target type is not a struct, enum, or union, as
219/// these are the only supported forms for `CarbonDeserialize` derivation.
220/// Additionally, an invalid or missing `#[carbon]` attribute may result in a
221/// deserialization failure due to discriminator mismatch.
222///
223/// # Notes
224///
225/// - Ensure the discriminator length matches the expected format in serialized
226/// data; otherwise, deserialization will return `None`.
227/// - This macro leverages the Borsh serialization framework and assumes that
228/// the type implements `BorshDeserialize` for successful deserialization.
229fn gen_borsh_deserialize(input: TokenStream) -> TokenStream2 {
230 let cratename = Ident::new("borsh", Span::call_site());
231
232 let item: Item = syn::parse(input).unwrap();
233 let res = match item {
234 Item::Struct(item) => struct_de(&item, cratename),
235 Item::Enum(item) => enum_de(&item, cratename),
236 Item::Union(item) => union_de(&item, cratename),
237 // Derive macros can only be defined on structs, enums, and unions.
238 _ => unreachable!(),
239 };
240
241 match res {
242 Ok(res) => res,
243 Err(err) => err.to_compile_error(),
244 }
245}
246
247/// Extracts the discriminator value from a set of attributes.
248///
249/// This function searches through a list of attributes for a `carbon` attribute
250/// containing a `discriminator` key in the format `carbon(discriminator =
251/// "0x...")`. If found, it parses the discriminator as a hexadecimal string and
252/// returns it as a byte slice within a `TokenStream`. If the
253/// `carbon(discriminator = "...")` attribute is not present, the function
254/// returns `None`.
255///
256/// # Syntax
257///
258/// The attribute should be specified in the format:
259///
260/// ```ignore
261/// #[carbon(discriminator = "0x...")]
262/// ```
263///
264/// # Example
265///
266/// ```ignore
267/// use syn::Attribute;
268///
269/// // Example attribute with a discriminator
270/// let attrs: Vec<Attribute> = vec![parse_quote!(#[carbon(discriminator = "0x1234")])];
271/// let discriminator = get_discriminator(&attrs);
272///
273/// assert!(discriminator.is_some());
274/// ```
275///
276/// # Parameters
277///
278/// - `attrs`: A reference to a slice of `syn::Attribute` items. These represent
279/// the attributes attached to a Rust item, from which the function will
280/// attempt to extract the discriminator.
281///
282/// # Return
283///
284/// Returns an `Option<TokenStream>` containing the parsed byte slice if a
285/// valid `carbon(discriminator = "...")` attribute is found. If the attribute
286/// is not present, or if the value is not a valid hexadecimal string, the
287/// function returns `None`.
288///
289/// # Errors
290///
291/// If the `carbon(discriminator = "...")` attribute contains an invalid hex
292/// string, this function will panic with an error message indicating
293/// "Invalid hex string". To avoid runtime panics, ensure that the hex string
294/// provided is correctly formatted.
295///
296/// # Notes
297///
298/// - The `discriminator` value must be a hexadecimal string prefixed with "0x".
299/// - If the hex string is invalid, an error will be raised; consider adding
300/// further error handling if required for your application.
301fn get_discriminator(attrs: &[syn::Attribute]) -> Option<quote::__private::TokenStream> {
302 attrs.iter().find_map(|attr| {
303 if attr.path.is_ident("carbon") {
304 attr.parse_meta().ok().and_then(|meta| {
305 if let Meta::List(list) = meta {
306 list.nested.iter().find_map(|nested| {
307 if let NestedMeta::Meta(Meta::NameValue(nv)) = nested {
308 if nv.path.is_ident("discriminator") {
309 if let Lit::Str(lit_str) = &nv.lit {
310 let disc_str = lit_str.value();
311 let disc_bytes = hex::decode(disc_str.trim_start_matches("0x"))
312 .expect("Invalid hex string");
313 let disc_array = disc_bytes.as_slice();
314 return Some(quote! { &[#(#disc_array),*] });
315 }
316 }
317 }
318 None
319 })
320 } else {
321 None
322 }
323 })
324 } else {
325 None
326 }
327 })
328}
329
330/// Represents the parsed input for the `instruction_decoder_collection!` macro.
331///
332/// The `InstructionMacroInput` struct holds the essential elements required
333/// to generate instruction decoding logic within the
334/// `instruction_decoder_collection!` macro. It includes the names of the enums
335/// for instructions, instruction types, and programs, along with a collection
336/// of `InstructionEntry` mappings that define the relationships between program
337/// variants, decoder expressions, and instruction types.
338///
339/// # Fields
340///
341/// - `instructions_enum_name`: The identifier for the enum representing the
342/// instructions. This enum contains the primary instruction variants to be
343/// used within the macro.
344/// - `instruction_types_enum_name`: The identifier for the enum representing
345/// the various types of instructions. This enum categorizes instructions by
346/// their specific types.
347/// - `programs_enum_name`: The identifier for the enum representing the
348/// programs. This enum is used to identify different programs and their
349/// corresponding variants in the macro.
350/// - `entries`: A vector of `InstructionEntry` items, each of which maps a
351/// program variant to a decoder expression and an instruction type, defining
352/// how each instruction should be processed.
353///
354/// # Example
355///
356/// ```ignore
357/// use syn::Ident;
358/// use syn::parse_quote;
359///
360/// let instructions_enum_name: Ident = parse_quote!(InstructionsEnum);
361/// let instruction_types_enum_name: Ident = parse_quote!(InstructionTypesEnum);
362/// let programs_enum_name: Ident = parse_quote!(ProgramsEnum);
363/// let entries = vec![
364/// InstructionEntry {
365/// program_variant: parse_quote!(MyProgram),
366/// decoder_expr: parse_quote!(my_decoder),
367/// instruction_type: parse_quote!(MyInstructionType),
368/// },
369/// ];
370///
371/// let input = InstructionMacroInput {
372/// instructions_enum_name,
373/// instruction_types_enum_name,
374/// programs_enum_name,
375/// entries,
376/// };
377/// ```
378///
379/// # Usage
380///
381/// The `InstructionMacroInput` struct is primarily used within procedural
382/// macros for parsing and storing elements required for generating complex
383/// decoding logic. Each field serves a specific role in defining how
384/// instructions are categorized, decoded, and mapped to programs.
385///
386/// # Notes
387///
388/// - Ensure that all identifiers correspond to valid enums in your macro
389/// context, as these will be referenced directly when generating code.
390/// - The `entries` vector should contain an `InstructionEntry` for each mapping
391/// you wish to include. Each entry specifies a program variant and the logic
392/// to decode its instructions.
393struct InstructionMacroInput {
394 instructions_enum_name: Ident,
395 instruction_types_enum_name: Ident,
396 programs_enum_name: Ident,
397 entries: Vec<InstructionEntry>,
398}
399
400/// Represents a mapping between a program variant, its decoder expression, and
401/// an instruction type.
402///
403/// The `InstructionEntry` struct is used to define individual mappings within
404/// the `instruction_decoder_collection!` macro. Each entry specifies a unique
405/// program variant, decoder for its instructions, and the
406/// resulting instruction type. This structure enables the macro to understand
407/// and process different program instructions efficiently.
408///
409/// # Fields
410///
411/// - `program_variant`: An `Ident` representing the variant of the program
412/// enum. This is used to match against specific programs within the macro.
413/// - `decoder_expr`: An expression (`syn::Expr`) that defines the decoding
414/// logic for this program variant.
415/// - `instruction_type`: A `TypePath` that specifies the type of instruction
416/// resulting from the decoding process. This type should correspond to one of
417/// the variants in the instruction types enum.
418///
419/// # Example
420///
421/// ```ignore
422///
423/// let program_variant: Ident = parse_quote!(MyProgram);
424/// let decoder_expr: Expr = parse_quote!(MyDecoder);
425/// let instruction_type: TypePath = parse_quote!(MyInstructionType);
426///
427/// let entry = InstructionEntry {
428/// program_variant,
429/// decoder_expr,
430/// instruction_type,
431/// };
432/// ```
433///
434/// # Usage
435///
436/// The `InstructionEntry` struct is used as part of a vector within the
437/// `InstructionMacroInput` struct. Each entry allows the macro to handle
438/// multiple programs and their associated instruction types in a modular
439/// and scalable manner. By specifying each program's decoding logic and
440/// instruction type, the macro can dynamically adapt to different program
441/// requirements.
442///
443/// # Notes
444///
445/// - Ensure that `decoder_expr` correctly implements the decoding functionality
446/// expected by the associated `instruction_type`. Misalignment between the
447/// decoder expression and the expected instruction type can lead to runtime
448/// errors.
449/// - This struct is typically not used standalone but as part of a collection
450/// that defines multiple program-instruction mappings for procedural macros.
451struct InstructionEntry {
452 program_variant: Ident,
453 decoder_expr: syn::Expr,
454 instruction_type: TypePath,
455}
456
457/// Parses input for the `instruction_decoder_collection!` macro.
458///
459/// This implementation of the `Parse` trait is responsible for parsing the
460/// input provided to the `instruction_decoder_collection!` macro. It expects a
461/// comma-separated sequence of identifiers followed by a series of
462/// `InstructionEntry` items, which define mappings between program variants,
463/// decoder expressions, and instruction types. These entries are collected into
464/// an `InstructionMacroInput` struct, which can then be used to generate
465/// instruction decoding logic.
466///
467/// # Syntax
468///
469/// The input format for the macro should follow this structure:
470///
471/// ```ignore
472/// instruction_decoder_collection!(
473/// InstructionsEnum, InstructionTypesEnum, ProgramsEnum,
474/// ProgramVariant => decoder_expr => InstructionType,
475/// ProgramVariant => decoder_expr => InstructionType,
476/// ...
477/// );
478/// ```
479///
480/// - `InstructionsEnum`: Identifier for the enum representing instruction names
481/// with data.
482/// - `InstructionTypesEnum`: Identifier for the enum representing types of
483/// instructions.
484/// - `ProgramsEnum`: Identifier for the enum representing program types.
485/// - Each `InstructionEntry` consists of a program variant, a decoder
486/// expression, and an instruction type, separated by `=>` and followed by a
487/// comma.
488///
489/// # Example
490///
491/// ```ignore
492///
493/// let input = parse_quote! {
494/// MyInstructionsEnum, MyInstructionTypesEnum, MyProgramsEnum,
495/// MyProgram => my_decoder => MyInstruction,
496/// AnotherProgram => another_decoder => AnotherInstruction,
497/// };
498///
499/// let parsed_input: InstructionMacroInput = syn::parse2(input)
500/// .expect("Failed to parse macro input");
501/// ```
502///
503/// # Parameters
504///
505/// - `input`: A `ParseStream` representing the macro input, expected to
506/// contain:
507/// - An enum name for instructions
508/// - An enum name for instruction types
509/// - An enum name for program types
510/// - A series of `InstructionEntry` mappings for program variants and
511/// instructions.
512///
513/// # Return
514///
515/// Returns a `syn::Result<Self>`, which will be an `InstructionMacroInput`
516/// containing the parsed components if successful. On failure, returns a
517/// `syn::Error` indicating the specific parsing issue.
518///
519/// # Notes
520///
521/// - The macro requires the input format to be strictly adhered to, with commas
522/// separating the enum identifiers and each `InstructionEntry` mapping.
523/// Ensure that all mappings include `=>` separators between program variants,
524/// decoder expressions, and instruction types.
525/// - This parsing process is typically used within a procedural macro and may
526/// be subject to Rust's macro hygiene and parsing rules.
527///
528/// # Errors
529///
530/// An error will be returned if:
531/// - An identifier or component is missing or improperly formatted
532/// - The input stream does not conform to the expected comma-separated format
533impl Parse for InstructionMacroInput {
534 fn parse(input: ParseStream) -> syn::Result<Self> {
535 let instructions_enum_name: Ident = input.parse()?;
536 input.parse::<Token![,]>()?;
537 let instruction_types_enum_name: Ident = input.parse()?;
538 input.parse::<Token![,]>()?;
539 let programs_enum_name: Ident = input.parse()?;
540 input.parse::<Token![,]>()?;
541
542 let mut entries = Vec::new();
543
544 while !input.is_empty() {
545 let program_variant: Ident = input.parse()?;
546 input.parse::<Token![=>]>()?;
547 let decoder_expr: syn::Expr = input.parse()?;
548 input.parse::<Token![=>]>()?;
549 let instruction_type: TypePath = input.parse()?;
550
551 entries.push(InstructionEntry {
552 program_variant,
553 decoder_expr,
554 instruction_type,
555 });
556
557 if input.peek(Token![,]) {
558 input.parse::<Token![,]>()?;
559 }
560 }
561
562 Ok(InstructionMacroInput {
563 instructions_enum_name,
564 instruction_types_enum_name,
565 programs_enum_name,
566 entries,
567 })
568 }
569}
570
571/// Generates a collection of instruction decoders and associated enums.
572///
573/// This macro creates a set of enums and implementations to handle decoding
574/// of instructions for multiple Solana programs. It generates:
575/// 1. An enum for all instructions
576/// 2. An enum for all instruction types
577/// 3. An enum for all programs
578/// 4. An implementation of InstructionDecoderCollection trait
579///
580/// # Syntax
581///
582/// The macro takes the following arguments:
583/// 1. Name for the all-encompassing instructions enum
584/// 2. Name for the all-encompassing instruction types enum
585/// 3. Name for the programs enum
586/// 4. One or more entries, each consisting of:
587/// - Program variant name
588/// - Decoder expression
589/// - Instruction enum for the program
590///
591/// # Example
592///
593/// ```ignore
594/// instruction_decoder_collection!(
595/// AllInstructions, AllInstructionTypes, AllPrograms,
596/// JupSwap => JupiterDecoder => JupiterInstruction,
597/// MeteoraSwap => MeteoraDecoder => MeteoraInstruction
598/// );
599/// ```
600///
601///
602/// This example will generate:
603/// - AllInstructions enum with variants JupSwap(JupiterInstruction) and
604/// MeteoraSwap(MeteoraInstruction)
605/// - AllInstructionTypes enum with variants JupSwap(JupiterInstructionType) and
606/// MeteoraSwap(MeteoraInstructionType)
607/// - AllPrograms enum with variants JupSwap and MeteoraSwap
608/// - An implementation of InstructionDecoderCollection for AllInstructions
609///
610/// # Generated Code
611///
612/// The macro generates the following:
613/// 1. An enum AllInstructions containing variants for each program's
614/// instructions
615/// 2. An enum AllInstructionTypes containing variants for each program's
616/// instruction types
617/// 3. An enum AllPrograms listing all programs
618/// 4. An implementation of InstructionDecoderCollection for AllInstructions,
619/// including:
620/// - parse_instruction method to decode instructions
621/// - get_type method to retrieve the instruction type
622///
623/// # Note
624///
625/// Ensure that all necessary types (e.g., DecodedInstruction,
626/// InstructionDecoderCollection) are in scope where this macro is used.
627#[proc_macro]
628pub fn instruction_decoder_collection(input: TokenStream) -> TokenStream {
629 let input = parse_macro_input!(input as InstructionMacroInput);
630
631 let instructions_enum_name = input.instructions_enum_name;
632 let instruction_types_enum_name = input.instruction_types_enum_name;
633 let programs_enum_name = input.programs_enum_name;
634 let entries = input.entries;
635
636 let mut instruction_variants = Vec::new();
637 let mut instruction_type_variants = Vec::new();
638 let mut program_variants = Vec::new();
639 let mut parse_instruction_arms = Vec::new();
640 let mut get_type_arms = Vec::new();
641
642 for entry in entries {
643 let program_variant = entry.program_variant;
644 let decoder_expr = entry.decoder_expr;
645 let instruction_type = entry.instruction_type;
646
647 let instruction_enum_ident = &instruction_type.path.segments.last().unwrap().ident;
648 let instruction_type_ident = format_ident!("{}Type", instruction_enum_ident);
649
650 instruction_variants.push(quote! {
651 #program_variant(#instruction_enum_ident)
652 });
653 instruction_type_variants.push(quote! {
654 #program_variant(#instruction_type_ident)
655 });
656 program_variants.push(quote! {
657 #program_variant
658 });
659
660 parse_instruction_arms.push(quote! {
661 if let Some(decoded_instruction) = #decoder_expr.decode_instruction(&instruction) {
662 return Some(carbon_core::instruction::DecodedInstruction {
663 program_id: instruction.program_id,
664 accounts: instruction.accounts.clone(),
665 data: #instructions_enum_name::#program_variant(decoded_instruction.data),
666 });
667 }
668 });
669
670 get_type_arms.push(quote! {
671 #instructions_enum_name::#program_variant(instruction) => {
672 #instruction_types_enum_name::#program_variant(instruction.get_instruction_type())
673 }
674 });
675 }
676
677 let expanded = quote! {
678 #[derive(Debug, Clone, std::hash::Hash, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
679 pub enum #instructions_enum_name {
680 #(#instruction_variants),*
681 }
682
683 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
684 pub enum #instruction_types_enum_name {
685 #(#instruction_type_variants),*
686 }
687
688 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
689 pub enum #programs_enum_name {
690 #(#program_variants),*
691 }
692
693 impl carbon_core::collection::InstructionDecoderCollection for #instructions_enum_name {
694 type InstructionType = #instruction_types_enum_name;
695
696 fn parse_instruction(
697 instruction: &solana_instruction::Instruction
698 ) -> Option<carbon_core::instruction::DecodedInstruction<Self>> {
699 #(#parse_instruction_arms)*
700 None
701 }
702
703 fn get_type(&self) -> Self::InstructionType {
704 match self {
705 #(#get_type_arms),*
706 }
707 }
708 }
709 };
710
711 TokenStream::from(expanded)
712}
713
714/// Derives a corresponding `InstructionType` enum for a given enum.
715///
716/// This procedural macro generates an `InstructionType` enum that mirrors the
717/// variants of the specified input enum. The `InstructionType` enum contains
718/// only the variant names, without any associated data. This is particularly
719/// useful for implementations that require a simplified representation of
720/// instruction types, such as in `InstructionDecoderCollection`.
721///
722/// # Syntax
723///
724/// To use this macro, annotate your enum with `#[derive(InstructionType)]`.
725/// This will automatically create an `InstructionType` enum with the same
726/// variant names as your original enum, suffixed with `Type`. Additionally,
727/// a `get_instruction_type` method will be implemented on the original enum,
728/// returning the corresponding `InstructionType` variant for each instance.
729///
730/// ```ignore
731/// #[derive(InstructionType)]
732/// enum MyEnum {
733/// VariantOne,
734/// VariantTwo(u32),
735/// VariantThree { data: String },
736/// }
737/// ```
738///
739/// The derived `InstructionType` enum will look like:
740///
741/// ```rust
742/// #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
743/// pub enum MyEnumType {
744/// VariantOne,
745/// VariantTwo,
746/// VariantThree,
747/// }
748/// ```
749///
750/// # Example
751///
752/// ```rust
753/// use carbon_proc_macros::InstructionType;
754///
755/// #[derive(InstructionType)]
756/// enum Instructions {
757/// NoData,
758/// WithData(u64),
759/// NamedData { field: String },
760/// }
761///
762/// let inst = Instructions::WithData(42);
763/// let inst_type = inst.get_instruction_type();
764///
765/// assert_eq!(inst_type, InstructionsType::WithData);
766/// ```
767///
768/// # Parameters
769///
770/// - `input`: A `TokenStream` representing the input enum, which is parsed to
771/// extract variant names and generate the `InstructionType` enum. Each
772/// variant is processed without any associated data.
773///
774/// # Return
775///
776/// Returns a `TokenStream` containing the expanded code for the generated
777/// `InstructionType` enum and the implementation of the `get_instruction_type`
778/// method on the original enum.
779///
780/// # Notes
781///
782/// - This macro will only derive an `InstructionType` enum for the input enum.
783/// It does not modify or remove any data associated with the original enum
784/// variants.
785/// - The generated `InstructionType` enum derives `Debug`, `Clone`,
786/// `PartialEq`, `Eq`, and `serde::Serialize`, making it suitable for use in
787/// serialization contexts as well as comparison and debugging.
788#[proc_macro_derive(InstructionType)]
789pub fn instruction_type_derive(input: TokenStream) -> TokenStream {
790 let input = parse_macro_input!(input as ItemEnum);
791
792 let enum_name = &input.ident;
793 let instruction_type_name = format_ident!("{}Type", enum_name);
794
795 let variants = input.variants.iter().map(|v| {
796 let variant_ident = &v.ident;
797 quote! {
798 #variant_ident
799 }
800 });
801
802 let instruction_type_enum = quote! {
803 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
804 pub enum #instruction_type_name {
805 #(#variants),*
806 }
807 };
808
809 let get_instruction_type_arms = input.variants.iter().map(|v| {
810 let variant_ident = &v.ident;
811 if let syn::Fields::Unit = v.fields {
812 quote! {
813 Self::#variant_ident => #instruction_type_name::#variant_ident,
814 }
815 } else if let syn::Fields::Unnamed(_) = v.fields {
816 quote! {
817 Self::#variant_ident(..) => #instruction_type_name::#variant_ident,
818 }
819 } else if let syn::Fields::Named(_) = v.fields {
820 quote! {
821 Self::#variant_ident { .. } => #instruction_type_name::#variant_ident,
822 }
823 } else {
824 quote! {}
825 }
826 });
827
828 let impl_get_instruction_type = quote! {
829 impl #enum_name {
830 pub fn get_instruction_type(&self) -> #instruction_type_name {
831 match self {
832 #(#get_instruction_type_arms)*
833 }
834 }
835 }
836 };
837
838 let expanded = quote! {
839 #instruction_type_enum
840
841 #impl_get_instruction_type
842 };
843
844 TokenStream::from(expanded)
845}