Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 4c84992

Browse files
authored
program-error: Add option to specify solana_program crate (#7112)
* program-error: Add option to specify solana_program crate Problem There are going to be a lot of issues with people seeing errors on spl_program_error derivation. When a build pulls in two version of solana_program, the macro uses `::solana_program`, which now becomes ambiguous. Summary of changes I'm not sure if this is a good idea, but this PR gives the ability to specify which version of `solana_program` to use when deriving the various traits on your error type. Borsh has this same functionality, and it's saved us when pulling in multiple versions of borsh in the SDK. Note: this PR defaults to `solana_program` instead of `::solana_program`, which might cause downstream issues. * Address feedback * Rename solana_program_crate -> solana_program * Oops, change the name everywhere
1 parent 5c5fefd commit 4c84992

File tree

4 files changed

+157
-49
lines changed

4 files changed

+157
-49
lines changed

libraries/program-error/derive/src/macro_impl.rs

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! The actual token generator for the macro
22
33
use {
4-
crate::parser::SplProgramErrorArgs,
4+
crate::parser::{SolanaProgram, SplProgramErrorArgs},
55
proc_macro2::Span,
66
quote::quote,
77
sha2::{Digest, Sha256},
@@ -36,10 +36,13 @@ pub enum MacroType {
3636
impl MacroType {
3737
/// Generates the corresponding tokens based on variant selection
3838
pub fn generate_tokens(&mut self) -> proc_macro2::TokenStream {
39+
let default_solana_program = SolanaProgram::default();
3940
match self {
40-
Self::IntoProgramError { ident } => into_program_error(ident),
41-
Self::DecodeError { ident } => decode_error(ident),
42-
Self::PrintProgramError { ident, variants } => print_program_error(ident, variants),
41+
Self::IntoProgramError { ident } => into_program_error(ident, &default_solana_program),
42+
Self::DecodeError { ident } => decode_error(ident, &default_solana_program),
43+
Self::PrintProgramError { ident, variants } => {
44+
print_program_error(ident, variants, &default_solana_program)
45+
}
4346
Self::SplProgramError { args, item_enum } => spl_program_error(args, item_enum),
4447
}
4548
}
@@ -48,59 +51,63 @@ impl MacroType {
4851
/// Builds the implementation of
4952
/// `Into<solana_program::program_error::ProgramError>` More specifically,
5053
/// implements `From<Self> for solana_program::program_error::ProgramError`
51-
pub fn into_program_error(ident: &Ident) -> proc_macro2::TokenStream {
52-
quote! {
53-
impl From<#ident> for ::solana_program::program_error::ProgramError {
54+
pub fn into_program_error(ident: &Ident, import: &SolanaProgram) -> proc_macro2::TokenStream {
55+
let this_impl = quote! {
56+
impl From<#ident> for #import::program_error::ProgramError {
5457
fn from(e: #ident) -> Self {
55-
::solana_program::program_error::ProgramError::Custom(e as u32)
58+
#import::program_error::ProgramError::Custom(e as u32)
5659
}
5760
}
58-
}
61+
};
62+
import.wrap(this_impl)
5963
}
6064

6165
/// Builds the implementation of `solana_program::decode_error::DecodeError<T>`
62-
pub fn decode_error(ident: &Ident) -> proc_macro2::TokenStream {
63-
quote! {
64-
impl<T> ::solana_program::decode_error::DecodeError<T> for #ident {
66+
pub fn decode_error(ident: &Ident, import: &SolanaProgram) -> proc_macro2::TokenStream {
67+
let this_impl = quote! {
68+
impl<T> #import::decode_error::DecodeError<T> for #ident {
6569
fn type_of() -> &'static str {
6670
stringify!(#ident)
6771
}
6872
}
69-
}
73+
};
74+
import.wrap(this_impl)
7075
}
7176

7277
/// Builds the implementation of
7378
/// `solana_program::program_error::PrintProgramError`
7479
pub fn print_program_error(
7580
ident: &Ident,
7681
variants: &Punctuated<Variant, Comma>,
82+
import: &SolanaProgram,
7783
) -> proc_macro2::TokenStream {
7884
let ppe_match_arms = variants.iter().map(|variant| {
7985
let variant_ident = &variant.ident;
8086
let error_msg = get_error_message(variant)
8187
.unwrap_or_else(|| String::from("Unknown custom program error"));
8288
quote! {
8389
#ident::#variant_ident => {
84-
::solana_program::msg!(#error_msg)
90+
#import::msg!(#error_msg)
8591
}
8692
}
8793
});
88-
quote! {
89-
impl ::solana_program::program_error::PrintProgramError for #ident {
94+
let this_impl = quote! {
95+
impl #import::program_error::PrintProgramError for #ident {
9096
fn print<E>(&self)
9197
where
9298
E: 'static
9399
+ std::error::Error
94-
+ ::solana_program::decode_error::DecodeError<E>
95-
+ ::solana_program::program_error::PrintProgramError
100+
+ #import::decode_error::DecodeError<E>
101+
+ #import::program_error::PrintProgramError
96102
+ num_traits::FromPrimitive,
97103
{
98104
match self {
99105
#(#ppe_match_arms),*
100106
}
101107
}
102108
}
103-
}
109+
};
110+
import.wrap(this_impl)
104111
}
105112

106113
/// Helper to parse out the string literal from the `#[error(..)]` attribute
@@ -128,9 +135,9 @@ pub fn spl_program_error(
128135

129136
let ident = &item_enum.ident;
130137
let variants = &item_enum.variants;
131-
let into_program_error = into_program_error(ident);
132-
let decode_error = decode_error(ident);
133-
let print_program_error = print_program_error(ident, variants);
138+
let into_program_error = into_program_error(ident, &args.import);
139+
let decode_error = decode_error(ident, &args.import);
140+
let print_program_error = print_program_error(ident, variants, &args.import);
134141

135142
quote! {
136143
#[repr(u32)]
Lines changed: 107 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
//! Token parsing
22
33
use {
4-
proc_macro2::Ident,
4+
proc_macro2::{Ident, Span, TokenStream},
5+
quote::quote,
56
syn::{
67
parse::{Parse, ParseStream},
78
token::Comma,
8-
LitInt, Token,
9+
LitInt, LitStr, Token,
910
},
1011
};
1112

@@ -14,51 +15,131 @@ pub struct SplProgramErrorArgs {
1415
/// Whether to hash the error codes using `solana_program::hash`
1516
/// or to use the default error code assigned by `num_traits`.
1617
pub hash_error_code_start: Option<u32>,
18+
/// Crate to use for solana_program
19+
pub import: SolanaProgram,
20+
}
21+
22+
/// Struct representing the path to a `solana_program` crate, which may be
23+
/// renamed or otherwise.
24+
pub struct SolanaProgram {
25+
import: Ident,
26+
explicit: bool,
27+
}
28+
impl quote::ToTokens for SolanaProgram {
29+
fn to_tokens(&self, tokens: &mut TokenStream) {
30+
self.import.to_tokens(tokens);
31+
}
32+
}
33+
impl SolanaProgram {
34+
pub fn wrap(&self, output: TokenStream) -> TokenStream {
35+
if self.explicit {
36+
output
37+
} else {
38+
anon_const_trick(output)
39+
}
40+
}
41+
}
42+
impl Default for SolanaProgram {
43+
fn default() -> Self {
44+
Self {
45+
import: Ident::new("_solana_program", Span::call_site()),
46+
explicit: false,
47+
}
48+
}
1749
}
1850

1951
impl Parse for SplProgramErrorArgs {
2052
fn parse(input: ParseStream) -> syn::Result<Self> {
21-
if input.is_empty() {
22-
return Ok(Self {
23-
hash_error_code_start: None,
24-
});
25-
}
26-
match SplProgramErrorArgParser::parse(input)? {
27-
SplProgramErrorArgParser::HashErrorCodes { value, .. } => Ok(Self {
28-
hash_error_code_start: Some(value.base10_parse::<u32>()?),
29-
}),
53+
let mut hash_error_code_start = None;
54+
let mut import = None;
55+
while !input.is_empty() {
56+
match SplProgramErrorArgParser::parse(input)? {
57+
SplProgramErrorArgParser::HashErrorCodes { value, .. } => {
58+
hash_error_code_start = Some(value.base10_parse::<u32>()?);
59+
}
60+
SplProgramErrorArgParser::SolanaProgramCrate { value, .. } => {
61+
import = Some(SolanaProgram {
62+
import: value.parse()?,
63+
explicit: true,
64+
});
65+
}
66+
}
3067
}
68+
Ok(Self {
69+
hash_error_code_start,
70+
import: import.unwrap_or(SolanaProgram::default()),
71+
})
3172
}
3273
}
3374

3475
/// Parser for args to the `#[spl_program_error]` attribute
3576
/// ie. `#[spl_program_error(hash_error_code_start = 1275525928)]`
3677
enum SplProgramErrorArgParser {
3778
HashErrorCodes {
38-
_ident: Ident,
3979
_equals_sign: Token![=],
4080
value: LitInt,
4181
_comma: Option<Comma>,
4282
},
83+
SolanaProgramCrate {
84+
_equals_sign: Token![=],
85+
value: LitStr,
86+
_comma: Option<Comma>,
87+
},
4388
}
4489

4590
impl Parse for SplProgramErrorArgParser {
4691
fn parse(input: ParseStream) -> syn::Result<Self> {
47-
let _ident = {
48-
let ident = input.parse::<Ident>()?;
49-
if ident != "hash_error_code_start" {
50-
return Err(input.error("Expected argument 'hash_error_code_start'"));
92+
let ident = input.parse::<Ident>()?;
93+
match ident.to_string().as_str() {
94+
"hash_error_code_start" => {
95+
let _equals_sign = input.parse::<Token![=]>()?;
96+
let value = input.parse::<LitInt>()?;
97+
let _comma: Option<Comma> = input.parse().unwrap_or(None);
98+
Ok(Self::HashErrorCodes {
99+
_equals_sign,
100+
value,
101+
_comma,
102+
})
103+
}
104+
"solana_program" => {
105+
let _equals_sign = input.parse::<Token![=]>()?;
106+
let value = input.parse::<LitStr>()?;
107+
let _comma: Option<Comma> = input.parse().unwrap_or(None);
108+
Ok(Self::SolanaProgramCrate {
109+
_equals_sign,
110+
value,
111+
_comma,
112+
})
51113
}
52-
ident
114+
_ => Err(input.error("Expected argument 'hash_error_code_start' or 'solana_program'")),
115+
}
116+
}
117+
}
118+
119+
// Within `exp`, you can bring things into scope with `extern crate`.
120+
//
121+
// We don't want to assume that `solana_program::` is in scope - the user may
122+
// have imported it under a different name, or may have imported it in a
123+
// non-toplevel module (common when putting impls behind a feature gate).
124+
//
125+
// Solution: let's just generate `extern crate solana_program as
126+
// _solana_program` and then refer to `_solana_program` in the derived code.
127+
// However, macros are not allowed to produce `extern crate` statements at the
128+
// toplevel.
129+
//
130+
// Solution: let's generate `mod _impl_foo` and import solana_program within
131+
// that. However, now we lose access to private members of the surrounding
132+
// module. This is a problem if, for example, we're deriving for a newtype,
133+
// where the inner type is defined in the same module, but not exported.
134+
//
135+
// Solution: use the anonymous const trick. For some reason, `extern crate`
136+
// statements are allowed here, but everything from the surrounding module is in
137+
// scope. This trick is taken from serde and num_traits.
138+
fn anon_const_trick(exp: TokenStream) -> TokenStream {
139+
quote! {
140+
const _: () = {
141+
extern crate solana_program as _solana_program;
142+
#exp
53143
};
54-
let _equals_sign = input.parse::<Token![=]>()?;
55-
let value = input.parse::<LitInt>()?;
56-
let _comma: Option<Comma> = input.parse().unwrap_or(None);
57-
Ok(Self::HashErrorCodes {
58-
_ident,
59-
_equals_sign,
60-
value,
61-
_comma,
62-
})
63144
}
64145
}

libraries/program-error/tests/spl.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,20 @@ fn test_library_error_codes() {
7272
first_error_as_u32 + 3,
7373
);
7474
}
75+
76+
/// Example error with solana_program crate set
77+
#[spl_program_error(solana_program = "solana_program")]
78+
enum ExampleSolanaProgramCrateError {
79+
/// This is a very informative error
80+
#[error("This is a very informative error")]
81+
VeryInformativeError,
82+
/// This is a super important error
83+
#[error("This is a super important error")]
84+
SuperImportantError,
85+
}
86+
87+
/// Tests that all macros compile
88+
#[test]
89+
fn test_macros_compile_with_solana_program_crate() {
90+
let _ = ExampleSolanaProgramCrateError::VeryInformativeError;
91+
}

libraries/type-length-value/src/error.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
use spl_program_error::*;
44

55
/// Errors that may be returned by the Token program.
6-
#[spl_program_error(hash_error_code_start = 1_202_666_432)]
6+
#[spl_program_error(
7+
hash_error_code_start = 1_202_666_432,
8+
solana_program = "solana_program"
9+
)]
710
pub enum TlvError {
811
/// Type not found in TLV data
912
#[error("Type not found in TLV data")]

0 commit comments

Comments
 (0)