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_satellite::*,
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 const DISCRIMINATOR: &'static [u8] = #discriminator;
140
141 fn deserialize(data: &[u8]) -> Option<Self> {
142 if data.len() < Self::DISCRIMINATOR.len() {
143 return None;
144 }
145
146
147 let (disc, mut rest) = data.split_at(Self::DISCRIMINATOR.len());
148 if disc != Self::DISCRIMINATOR {
149 return None;
150 }
151
152 match carbon_core::borsh::BorshDeserialize::deserialize(&mut rest) {
153 Ok(res) => {
154 if !rest.is_empty() {
155 carbon_core::log::debug!(
156 "Not all bytes were read when deserializing {}: {} bytes remaining",
157 stringify!(#name),
158 rest.len(),
159 );
160 }
161 Some(res)
162 }
163 Err(_) => None,
164 }
165 }
166 }
167 };
168
169 TokenStream::from(expanded)
170}
171
172/// Generates an implementation of the `CarbonDeserialize` trait for a given
173/// type.
174///
175/// This procedural macro automatically derives the `CarbonDeserialize` trait
176/// for structs, enums, or unions, enabling them to be deserialized using Borsh
177/// serialization format. The generated implementation includes type checking
178/// and allows for customized deserialization using the `#[carbon]` attribute to
179/// specify a unique discriminator for the type.
180///
181/// # Syntax
182///
183/// To use this macro, annotate the target type with
184/// `#[derive(CarbonDeserialize)]`. Optionally, you can specify a
185/// `#[carbon(discriminator = "...")]` attribute to define a custom
186/// discriminator, which will be checked during deserialization.
187///
188/// # Example
189///
190/// ```ignore
191/// use carbon_proc_macros::CarbonDeserialize;
192///
193/// #[derive(CarbonDeserialize)]
194/// #[carbon(discriminator = "0x1234")]
195/// struct MyStruct {
196/// id: u32,
197/// name: String,
198/// }
199///
200/// let bytes = ...; // serialized bytes
201/// let my_struct = MyStruct::deserialize(&bytes)
202/// .expect("Failed to deserialize `MyStruct`");
203/// ```
204///
205/// # Parameters
206///
207/// - `input_token_stream`: A `TokenStream` containing the parsed syntax tree of
208/// the target type definition. This input is processed to generate the
209/// appropriate `CarbonDeserialize` implementation.
210///
211/// # Return
212///
213/// Returns a `TokenStream` containing the implementation of the
214/// `CarbonDeserialize` trait for the given type. If successful, this enables
215/// Borsh deserialization with the custom discriminator check.
216///
217/// # Errors
218///
219/// This macro will panic if the target type is not a struct, enum, or union, as
220/// these are the only supported forms for `CarbonDeserialize` derivation.
221/// Additionally, an invalid or missing `#[carbon]` attribute may result in a
222/// deserialization failure due to discriminator mismatch.
223///
224/// # Notes
225///
226/// - Ensure the discriminator length matches the expected format in serialized
227/// data; otherwise, deserialization will return `None`.
228/// - This macro leverages the Borsh serialization framework and assumes that
229/// the type implements `BorshDeserialize` for successful deserialization.
230fn gen_borsh_deserialize(input: TokenStream) -> TokenStream2 {
231 let cratename = Ident::new("borsh", Span::call_site());
232
233 let item: Item = syn::parse(input).expect("Failed to parse input");
234 let res = match item {
235 Item::Struct(item) => struct_de(&item, cratename),
236 Item::Enum(item) => enum_de(&item, cratename),
237 Item::Union(item) => union_de(&item, cratename),
238 // Derive macros can only be defined on structs, enums, and unions.
239 _ => unreachable!(),
240 };
241
242 match res {
243 Ok(res) => res,
244 Err(err) => err.to_compile_error(),
245 }
246}
247
248/// Extracts the discriminator value from a set of attributes.
249///
250/// This function searches through a list of attributes for a `carbon` attribute
251/// containing a `discriminator` key in the format `carbon(discriminator =
252/// "0x...")`. If found, it parses the discriminator as a hexadecimal string and
253/// returns it as a byte slice within a `TokenStream`. If the
254/// `carbon(discriminator = "...")` attribute is not present, the function
255/// returns `None`.
256///
257/// # Syntax
258///
259/// The attribute should be specified in the format:
260///
261/// ```ignore
262/// #[carbon(discriminator = "0x...")]
263/// ```
264///
265/// # Example
266///
267/// ```ignore
268/// use syn::Attribute;
269///
270/// // Example attribute with a discriminator
271/// let attrs: Vec<Attribute> = vec![parse_quote!(#[carbon(discriminator = "0x1234")])];
272/// let discriminator = get_discriminator(&attrs);
273///
274/// assert!(discriminator.is_some());
275/// ```
276///
277/// # Parameters
278///
279/// - `attrs`: A reference to a slice of `syn::Attribute` items. These represent
280/// the attributes attached to a Rust item, from which the function will
281/// attempt to extract the discriminator.
282///
283/// # Return
284///
285/// Returns an `Option<TokenStream>` containing the parsed byte slice if a
286/// valid `carbon(discriminator = "...")` attribute is found. If the attribute
287/// is not present, or if the value is not a valid hexadecimal string, the
288/// function returns `None`.
289///
290/// # Errors
291///
292/// If the `carbon(discriminator = "...")` attribute contains an invalid hex
293/// string, this function will panic with an error message indicating
294/// "Invalid hex string". To avoid runtime panics, ensure that the hex string
295/// provided is correctly formatted.
296///
297/// # Notes
298///
299/// - The `discriminator` value must be a hexadecimal string prefixed with "0x".
300/// - If the hex string is invalid, an error will be raised; consider adding
301/// further error handling if required for your application.
302fn get_discriminator(attrs: &[syn::Attribute]) -> Option<quote::__private::TokenStream> {
303 attrs.iter().find_map(|attr| {
304 if attr.path.is_ident("carbon") {
305 attr.parse_meta().ok().and_then(|meta| {
306 if let Meta::List(list) = meta {
307 list.nested.iter().find_map(|nested| {
308 if let NestedMeta::Meta(Meta::NameValue(nv)) = nested {
309 if nv.path.is_ident("discriminator") {
310 if let Lit::Str(lit_str) = &nv.lit {
311 let disc_str = lit_str.value();
312 let disc_bytes = hex::decode(disc_str.trim_start_matches("0x"))
313 .expect("Invalid hex string");
314 let disc_array = disc_bytes.as_slice();
315 return Some(quote! { &[#(#disc_array),*] });
316 }
317 }
318 }
319 None
320 })
321 } else {
322 None
323 }
324 })
325 } else {
326 None
327 }
328 })
329}
330
331/// Represents the parsed input for instruction decoder collection macros.
332///
333/// The `InstructionMacroInput` struct holds the essential elements required to
334/// generate instruction decoding logic within
335/// `instruction_decoder_collection!` and `instruction_decoder_collection_fast!`.
336/// It includes the names of the enums for instructions, instruction types, and
337/// programs, along with a collection of `InstructionEntry` mappings that define
338/// the relationships between program variants, decoder expressions, and
339/// instruction types.
340///
341/// # Fields
342///
343/// - `instructions_enum_name`: The identifier for the enum representing the
344/// instructions. This enum contains the primary instruction variants to be
345/// used within the macro.
346/// - `instruction_types_enum_name`: The identifier for the enum representing
347/// the various types of instructions. This enum categorizes instructions by
348/// their specific types.
349/// - `programs_enum_name`: The identifier for the enum representing the
350/// programs. This enum is used to identify different programs and their
351/// corresponding variants in the macro.
352/// - `entries`: A vector of `InstructionEntry` items, each of which maps a
353/// program variant to a decoder expression and an instruction type, defining
354/// how each instruction should be processed.
355///
356/// # Example
357///
358/// ```ignore
359/// use syn::Ident;
360/// use syn::parse_quote;
361///
362/// let instructions_enum_name: Ident = parse_quote!(InstructionsEnum);
363/// let instruction_types_enum_name: Ident = parse_quote!(InstructionTypesEnum);
364/// let programs_enum_name: Ident = parse_quote!(ProgramsEnum);
365/// let entries = vec![
366/// InstructionEntry {
367/// program_variant: parse_quote!(MyProgram),
368/// decoder_expr: parse_quote!(my_decoder),
369/// instruction_type: parse_quote!(MyInstructionType),
370/// },
371/// ];
372///
373/// let input = InstructionMacroInput {
374/// instructions_enum_name,
375/// instruction_types_enum_name,
376/// programs_enum_name,
377/// entries,
378/// };
379/// ```
380///
381/// # Usage
382///
383/// The `InstructionMacroInput` struct is primarily used within procedural
384/// macros for parsing and storing elements required for generating complex
385/// decoding logic. Each field serves a specific role in defining how
386/// instructions are categorized, decoded, and mapped to programs.
387///
388/// # Notes
389///
390/// - Ensure that all identifiers correspond to valid enums in your macro
391/// context, as these will be referenced directly when generating code.
392/// - The `entries` vector should contain an `InstructionEntry` for each mapping
393/// you wish to include. Each entry specifies a program variant and the logic
394/// to decode its instructions.
395struct InstructionMacroInput {
396 instructions_enum_name: Ident,
397 instruction_types_enum_name: Ident,
398 programs_enum_name: Ident,
399 entries: Vec<InstructionEntry>,
400}
401
402/// Represents a mapping between a program variant, its decoder expression, and
403/// an instruction type.
404///
405/// The `InstructionEntry` struct is used to define individual mappings within
406/// the `instruction_decoder_collection!` macro. Each entry specifies a unique
407/// program variant, decoder for its instructions, and the
408/// resulting instruction type. This structure enables the macro to understand
409/// and process different program instructions efficiently.
410///
411/// # Fields
412///
413/// - `program_variant`: An `Ident` representing the variant of the program
414/// enum. This is used to match against specific programs within the macro.
415/// - `decoder_expr`: An expression (`syn::Expr`) that defines the decoding
416/// logic for this program variant.
417/// - `instruction_type`: A `TypePath` that specifies the type of instruction
418/// resulting from the decoding process. This type should correspond to one of
419/// the variants in the instruction types enum.
420///
421/// # Example
422///
423/// ```ignore
424///
425/// let program_variant: Ident = parse_quote!(MyProgram);
426/// let decoder_expr: Expr = parse_quote!(MyDecoder);
427/// let instruction_type: TypePath = parse_quote!(MyInstructionType);
428///
429/// let entry = InstructionEntry {
430/// program_variant,
431/// decoder_expr,
432/// instruction_type,
433/// };
434/// ```
435///
436/// # Usage
437///
438/// The `InstructionEntry` struct is used as part of a vector within the
439/// `InstructionMacroInput` struct. Each entry allows the macro to handle
440/// multiple programs and their associated instruction types in a modular
441/// and scalable manner. By specifying each program's decoding logic and
442/// instruction type, the macro can dynamically adapt to different program
443/// requirements.
444///
445/// # Notes
446///
447/// - Ensure that `decoder_expr` correctly implements the decoding functionality
448/// expected by the associated `instruction_type`. Misalignment between the
449/// decoder expression and the expected instruction type can lead to runtime
450/// errors.
451/// - This struct is typically not used standalone but as part of a collection
452/// that defines multiple program-instruction mappings for procedural macros.
453struct InstructionEntry {
454 program_variant: Ident,
455 program_id_path: Option<syn::Path>,
456 decoder_expr: syn::Expr,
457 instruction_type: TypePath,
458}
459
460/// Parses input for the instruction decoder collection macros.
461///
462/// This implementation of the `Parse` trait is responsible for parsing the
463/// input provided to `instruction_decoder_collection!` and
464/// `instruction_decoder_collection_fast!`. It accepts either the legacy
465/// 3-part entry form or the new 4-part entry form, and produces a unified
466/// `InstructionMacroInput` structure used to generate decoding logic.
467///
468/// # Syntax
469///
470/// The input format for the macro should follow this structure:
471///
472/// ```ignore
473/// instruction_decoder_collection_fast!(
474/// InstructionsEnum, InstructionTypesEnum, ProgramsEnum,
475/// // 4-part: ProgramVariant => ProgramIdPath => DecoderExpr => InstructionType
476/// ProgramVariant => my_decoder_crate::PROGRAM_ID => decoder_expr => InstructionType,
477/// // 3-part (legacy): ProgramVariant => DecoderExpr => InstructionType
478/// AnotherVariant => decoder_expr => AnotherInstructionType,
479/// );
480/// ```
481///
482/// - `InstructionsEnum`: Identifier for the enum representing instruction names
483/// with data.
484/// - `InstructionTypesEnum`: Identifier for the enum representing types of
485/// instructions.
486/// - `ProgramsEnum`: Identifier for the enum representing program types.
487/// - Each `InstructionEntry` is either:
488/// - 4-part: program variant, program id path, decoder expression, and
489/// instruction type; or
490/// - 3-part: program variant, decoder expression, and instruction type.
491///
492/// # Example
493///
494/// ```ignore
495///
496/// let input = parse_quote! {
497/// MyInstructionsEnum, MyInstructionTypesEnum, MyProgramsEnum,
498/// MyProgram => my_decoder => MyInstruction,
499/// AnotherProgram => another_decoder => AnotherInstruction,
500/// };
501///
502/// let parsed_input: InstructionMacroInput = syn::parse2(input)
503/// .expect("Failed to parse macro input");
504/// ```
505///
506/// # Parameters
507///
508/// - `input`: A `ParseStream` representing the macro input, expected to
509/// contain:
510/// - An enum name for instructions
511/// - An enum name for instruction types
512/// - An enum name for program types
513/// - A series of `InstructionEntry` mappings for program variants and
514/// instructions.
515///
516/// # Return
517///
518/// Returns a `syn::Result<Self>`, which will be an `InstructionMacroInput`
519/// containing the parsed components if successful. On failure, returns a
520/// `syn::Error` indicating the specific parsing issue.
521///
522/// # Notes
523///
524/// - The macro requires the input format to be strictly adhered to, with commas
525/// separating the enum identifiers and each `InstructionEntry` mapping.
526/// Ensure that all mappings include `=>` separators between program variants,
527/// decoder expressions, and instruction types.
528/// - This parsing process is typically used within a procedural macro and may
529/// be subject to Rust's macro hygiene and parsing rules.
530///
531/// # Errors
532///
533/// An error will be returned if:
534/// - An identifier or component is missing or improperly formatted
535/// - The input stream does not conform to the expected comma-separated format
536impl Parse for InstructionMacroInput {
537 fn parse(input: ParseStream) -> syn::Result<Self> {
538 let instructions_enum_name: Ident = input.parse()?;
539 input.parse::<Token![,]>()?;
540 let instruction_types_enum_name: Ident = input.parse()?;
541 input.parse::<Token![,]>()?;
542 let programs_enum_name: Ident = input.parse()?;
543 input.parse::<Token![,]>()?;
544
545 let mut entries = Vec::new();
546
547 while !input.is_empty() {
548 let program_variant: Ident = input.parse()?;
549 input.parse::<Token![=>]>()?;
550
551 // Attempt to parse 4-part syntax: variant => PROGRAM_ID_PATH => DECODER => INSTRUCTION
552 // Use a forked parser to decide without consuming on failure.
553 let mut use_four_part = false;
554 let fork = input.fork();
555 let program_id_path_candidate: syn::Path = match fork.parse() {
556 Ok(p) => p,
557 Err(_) => {
558 // Cannot parse path; must be legacy 3-part
559 syn::Path {
560 leading_colon: None,
561 segments: syn::punctuated::Punctuated::new(),
562 }
563 }
564 };
565
566 if !program_id_path_candidate.segments.is_empty()
567 && fork.parse::<Token![=>]>().is_ok()
568 && fork.parse::<syn::Expr>().is_ok()
569 && fork.parse::<Token![=>]>().is_ok()
570 && fork.parse::<TypePath>().is_ok()
571 {
572 use_four_part = true;
573 }
574
575 if use_four_part {
576 let program_id_path: syn::Path = input.parse()?;
577 input.parse::<Token![=>]>()?;
578 let decoder_expr: syn::Expr = input.parse()?;
579 input.parse::<Token![=>]>()?;
580 let instruction_type: TypePath = input.parse()?;
581
582 entries.push(InstructionEntry {
583 program_variant,
584 program_id_path: Some(program_id_path),
585 decoder_expr,
586 instruction_type,
587 });
588 } else {
589 // Legacy 3-part syntax: variant => DECODER => INSTRUCTION
590 let decoder_expr: syn::Expr = input.parse()?;
591 input.parse::<Token![=>]>()?;
592 let instruction_type: TypePath = input.parse()?;
593
594 entries.push(InstructionEntry {
595 program_variant,
596 program_id_path: None,
597 decoder_expr,
598 instruction_type,
599 });
600 }
601
602 if input.peek(Token![,]) {
603 input.parse::<Token![,]>()?;
604 }
605 }
606
607 Ok(InstructionMacroInput {
608 instructions_enum_name,
609 instruction_types_enum_name,
610 programs_enum_name,
611 entries,
612 })
613 }
614}
615
616/// Generates a collection of instruction decoders and associated enums.
617///
618/// Deprecated: Prefer `instruction_decoder_collection_fast!`, which dispatches
619/// by `program_id` using a `match` and is more efficient.
620///
621/// This macro creates a set of enums and implementations to handle decoding
622/// of instructions for multiple Solana programs. It generates:
623/// 1. An enum for all instructions
624/// 2. An enum for all instruction types
625/// 3. An enum for all programs
626/// 4. An implementation of InstructionDecoderCollection trait
627///
628/// # Syntax
629///
630/// The macro takes the following arguments:
631/// 1. Name for the all-encompassing instructions enum
632/// 2. Name for the all-encompassing instruction types enum
633/// 3. Name for the programs enum
634/// 4. One or more entries, each consisting of:
635/// - Program variant name
636/// - Decoder expression
637/// - Instruction enum for the program
638///
639/// # Example
640///
641/// ```ignore
642/// instruction_decoder_collection!(
643/// AllInstructions, AllInstructionTypes, AllPrograms,
644/// JupSwap => JupiterDecoder => JupiterInstruction,
645/// MeteoraSwap => MeteoraDecoder => MeteoraInstruction
646/// );
647/// ```
648///
649///
650/// This example will generate:
651/// - AllInstructions enum with variants JupSwap(JupiterInstruction) and
652/// MeteoraSwap(MeteoraInstruction)
653/// - AllInstructionTypes enum with variants JupSwap(JupiterInstructionType) and
654/// MeteoraSwap(MeteoraInstructionType)
655/// - AllPrograms enum with variants JupSwap and MeteoraSwap
656/// - An implementation of InstructionDecoderCollection for AllInstructions
657///
658/// # Generated Code
659///
660/// The macro generates the following:
661/// 1. An enum AllInstructions containing variants for each program's
662/// instructions
663/// 2. An enum AllInstructionTypes containing variants for each program's
664/// instruction types
665/// 3. An enum AllPrograms listing all programs
666/// 4. An implementation of InstructionDecoderCollection for AllInstructions,
667/// including:
668/// - parse_instruction method to decode instructions
669/// - get_type method to retrieve the instruction type
670///
671/// # Note
672///
673/// Ensure that all necessary types (e.g., DecodedInstruction,
674/// InstructionDecoderCollection) are in scope where this macro is used.
675///
676/// Deprecated: Prefer `instruction_decoder_collection_fast!`, which dispatches
677/// by `program_id` using a `match` and is more efficient.
678#[deprecated(
679 note = "Use `instruction_decoder_collection_fast!` for faster dispatch by program_id."
680)]
681#[proc_macro]
682pub fn instruction_decoder_collection(input: TokenStream) -> TokenStream {
683 let input = parse_macro_input!(input as InstructionMacroInput);
684
685 let instructions_enum_name = input.instructions_enum_name;
686 let instruction_types_enum_name = input.instruction_types_enum_name;
687 let programs_enum_name = input.programs_enum_name;
688 let entries = input.entries;
689
690 let mut instruction_variants = Vec::new();
691 let mut instruction_type_variants = Vec::new();
692 let mut program_variants = Vec::new();
693 let mut parse_instruction_arms = Vec::new();
694 let mut get_type_arms = Vec::new();
695
696 for entry in entries {
697 let program_variant = entry.program_variant;
698 let decoder_expr = entry.decoder_expr;
699 let instruction_type = entry.instruction_type;
700
701 let instruction_enum_ident = &instruction_type
702 .path
703 .segments
704 .last()
705 .expect("segment")
706 .ident;
707 let instruction_type_ident = format_ident!("{}Type", instruction_enum_ident);
708
709 instruction_variants.push(quote! {
710 #program_variant(#instruction_enum_ident)
711 });
712 instruction_type_variants.push(quote! {
713 #program_variant(#instruction_type_ident)
714 });
715 program_variants.push(quote! {
716 #program_variant
717 });
718
719 parse_instruction_arms.push(quote! {
720 if let Some(decoded_instruction) = #decoder_expr.decode_instruction(&instruction) {
721 return Some(carbon_core::instruction::DecodedInstruction {
722 program_id: instruction.program_id,
723 accounts: instruction.accounts.clone(),
724 data: #instructions_enum_name::#program_variant(decoded_instruction.data),
725 });
726 }
727 });
728
729 get_type_arms.push(quote! {
730 #instructions_enum_name::#program_variant(instruction) => {
731 #instruction_types_enum_name::#program_variant(instruction.get_instruction_type())
732 }
733 });
734 }
735
736 let expanded = quote! {
737 #[derive(Debug, Clone, std::hash::Hash, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
738 pub enum #instructions_enum_name {
739 #(#instruction_variants),*
740 }
741
742 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
743 pub enum #instruction_types_enum_name {
744 #(#instruction_type_variants),*
745 }
746
747 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
748 pub enum #programs_enum_name {
749 #(#program_variants),*
750 }
751
752 impl carbon_core::collection::InstructionDecoderCollection for #instructions_enum_name {
753 type InstructionType = #instruction_types_enum_name;
754
755 fn parse_instruction(
756 instruction: &solana_instruction::Instruction
757 ) -> Option<carbon_core::instruction::DecodedInstruction<Self>> {
758 #(#parse_instruction_arms)*
759 None
760 }
761
762 fn get_type(&self) -> Self::InstructionType {
763 match self {
764 #(#get_type_arms),*
765 }
766 }
767 }
768 };
769
770 TokenStream::from(expanded)
771}
772
773/// Similar to `instruction_decoder_collection!` but dispatches by
774/// `program_id` via `match` for faster decoding.
775///
776/// Syntax
777///
778/// ```ignore
779/// instruction_decoder_collection_fast!(
780/// AllInstructionsEnum,
781/// AllInstructionTypesEnum,
782/// AllProgramsEnum,
783/// // 4-part (preferred): Variant => ProgramIdPath => DecoderExpr => InstructionTypePath
784/// Pumpfun => carbon_pumpfun_decoder::PROGRAM_ID => carbon_pumpfun_decoder::PumpfunDecoder => carbon_pumpfun_decoder::instructions::PumpfunInstruction,
785/// // 3-part (legacy): falls back to slow sequential decode
786/// PumpSwap => carbon_pump_swap_decoder::PumpSwapDecoder => carbon_pump_swap_decoder::instructions::PumpSwapInstruction,
787/// );
788/// ```
789#[proc_macro]
790pub fn instruction_decoder_collection_fast(input: TokenStream) -> TokenStream {
791 let input = parse_macro_input!(input as InstructionMacroInput);
792
793 let instructions_enum_name = input.instructions_enum_name;
794 let instruction_types_enum_name = input.instruction_types_enum_name;
795 let programs_enum_name = input.programs_enum_name;
796 let entries = input.entries;
797
798 let mut instruction_variants = Vec::new();
799 let mut instruction_type_variants = Vec::new();
800 let mut program_variants = Vec::new();
801 let mut parse_instruction_match_arms = Vec::new();
802 let mut fallback_decode_blocks = Vec::new();
803 let mut get_type_arms = Vec::new();
804
805 for entry in entries {
806 let program_variant = entry.program_variant;
807 let decoder_expr = entry.decoder_expr;
808 let instruction_type = entry.instruction_type;
809
810 let instruction_enum_ident = &instruction_type
811 .path
812 .segments
813 .last()
814 .expect("segment")
815 .ident;
816 let instruction_type_ident = format_ident!("{}Type", instruction_enum_ident);
817
818 // Resolve the program id path for dispatch. Prefer explicitly provided
819 // path if available; otherwise, fall back to inferring `<crate>::PROGRAM_ID`
820 // from the first segment of the instruction type path for backward
821 // compatibility with older 3-part syntax.
822 let explicit_program_id_path = entry.program_id_path;
823
824 instruction_variants.push(quote! {
825 #program_variant(#instruction_enum_ident)
826 });
827 instruction_type_variants.push(quote! {
828 #program_variant(#instruction_type_ident)
829 });
830 program_variants.push(quote! {
831 #program_variant
832 });
833
834 if let Some(program_id_path) = explicit_program_id_path {
835 parse_instruction_match_arms.push(quote! {
836 #program_id_path => {
837 if let Some(decoded_instruction) = #decoder_expr.decode_instruction(&instruction) {
838 Some(carbon_core::instruction::DecodedInstruction {
839 program_id: instruction.program_id,
840 accounts: instruction.accounts.clone(),
841 data: #instructions_enum_name::#program_variant(decoded_instruction.data),
842 })
843 } else {
844 None
845 }
846 }
847 });
848 } else {
849 // No program id path: include in slow-path fallback.
850 fallback_decode_blocks.push(quote! {
851 if let Some(decoded_instruction) = #decoder_expr.decode_instruction(&instruction) {
852 return Some(carbon_core::instruction::DecodedInstruction {
853 program_id: instruction.program_id,
854 accounts: instruction.accounts.clone(),
855 data: #instructions_enum_name::#program_variant(decoded_instruction.data),
856 });
857 }
858 });
859 }
860
861 get_type_arms.push(quote! {
862 #instructions_enum_name::#program_variant(instruction) => {
863 #instruction_types_enum_name::#program_variant(instruction.get_instruction_type())
864 }
865 });
866 }
867
868 let expanded = quote! {
869 #[derive(Debug, Clone, std::hash::Hash, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
870 pub enum #instructions_enum_name {
871 #(#instruction_variants),*
872 }
873
874 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
875 pub enum #instruction_types_enum_name {
876 #(#instruction_type_variants),*
877 }
878
879 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
880 pub enum #programs_enum_name {
881 #(#program_variants),*
882 }
883
884 impl carbon_core::collection::InstructionDecoderCollection for #instructions_enum_name {
885 type InstructionType = #instruction_types_enum_name;
886
887 fn parse_instruction(
888 instruction: &solana_instruction::Instruction
889 ) -> Option<carbon_core::instruction::DecodedInstruction<Self>> {
890 match instruction.program_id {
891 #(#parse_instruction_match_arms),*
892 _ => {
893 #(#fallback_decode_blocks)*
894 None
895 }
896 }
897 }
898
899 fn get_type(&self) -> Self::InstructionType {
900 match self {
901 #(#get_type_arms),*
902 }
903 }
904 }
905 };
906
907 TokenStream::from(expanded)
908}
909
910/// Derives a corresponding `InstructionType` enum for a given enum.
911///
912/// This procedural macro generates an `InstructionType` enum that mirrors the
913/// variants of the specified input enum. The `InstructionType` enum contains
914/// only the variant names, without any associated data. This is particularly
915/// useful for implementations that require a simplified representation of
916/// instruction types, such as in `InstructionDecoderCollection`.
917///
918/// # Syntax
919///
920/// To use this macro, annotate your enum with `#[derive(InstructionType)]`.
921/// This will automatically create an `InstructionType` enum with the same
922/// variant names as your original enum, suffixed with `Type`. Additionally,
923/// a `get_instruction_type` method will be implemented on the original enum,
924/// returning the corresponding `InstructionType` variant for each instance.
925///
926/// ```ignore
927/// #[derive(InstructionType)]
928/// enum MyEnum {
929/// VariantOne,
930/// VariantTwo(u32),
931/// VariantThree { data: String },
932/// }
933/// ```
934///
935/// The derived `InstructionType` enum will look like:
936///
937/// ```rust
938/// #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
939/// pub enum MyEnumType {
940/// VariantOne,
941/// VariantTwo,
942/// VariantThree,
943/// }
944/// ```
945///
946/// # Example
947///
948/// ```rust
949/// use carbon_proc_macros::InstructionType;
950///
951/// #[derive(InstructionType)]
952/// enum Instructions {
953/// NoData,
954/// WithData(u64),
955/// NamedData { field: String },
956/// }
957///
958/// let inst = Instructions::WithData(42);
959/// let inst_type = inst.get_instruction_type();
960///
961/// assert_eq!(inst_type, InstructionsType::WithData);
962/// ```
963///
964/// # Parameters
965///
966/// - `input`: A `TokenStream` representing the input enum, which is parsed to
967/// extract variant names and generate the `InstructionType` enum. Each
968/// variant is processed without any associated data.
969///
970/// # Return
971///
972/// Returns a `TokenStream` containing the expanded code for the generated
973/// `InstructionType` enum and the implementation of the `get_instruction_type`
974/// method on the original enum.
975///
976/// # Notes
977///
978/// - This macro will only derive an `InstructionType` enum for the input enum.
979/// It does not modify or remove any data associated with the original enum
980/// variants.
981/// - The generated `InstructionType` enum derives `Debug`, `Clone`,
982/// `PartialEq`, `Eq`, and `serde::Serialize`, making it suitable for use in
983/// serialization contexts as well as comparison and debugging.
984#[proc_macro_derive(InstructionType)]
985pub fn instruction_type_derive(input: TokenStream) -> TokenStream {
986 let input = parse_macro_input!(input as ItemEnum);
987
988 let enum_name = &input.ident;
989 let instruction_type_name = format_ident!("{}Type", enum_name);
990
991 let variants = input.variants.iter().map(|v| {
992 let variant_ident = &v.ident;
993 quote! {
994 #variant_ident
995 }
996 });
997
998 let instruction_type_enum = quote! {
999 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
1000 pub enum #instruction_type_name {
1001 #(#variants),*
1002 }
1003 };
1004
1005 let get_instruction_type_arms = input.variants.iter().map(|v| {
1006 let variant_ident = &v.ident;
1007 if let syn::Fields::Unit = v.fields {
1008 quote! {
1009 Self::#variant_ident => #instruction_type_name::#variant_ident,
1010 }
1011 } else if let syn::Fields::Unnamed(_) = v.fields {
1012 quote! {
1013 Self::#variant_ident(..) => #instruction_type_name::#variant_ident,
1014 }
1015 } else if let syn::Fields::Named(_) = v.fields {
1016 quote! {
1017 Self::#variant_ident { .. } => #instruction_type_name::#variant_ident,
1018 }
1019 } else {
1020 quote! {}
1021 }
1022 });
1023
1024 let impl_get_instruction_type = quote! {
1025 impl #enum_name {
1026 pub fn get_instruction_type(&self) -> #instruction_type_name {
1027 match self {
1028 #(#get_instruction_type_arms)*
1029 }
1030 }
1031 }
1032 };
1033
1034 let expanded = quote! {
1035 #instruction_type_enum
1036
1037 #impl_get_instruction_type
1038 };
1039
1040 TokenStream::from(expanded)
1041}