1
1
//! Token parsing
2
2
3
3
use {
4
- proc_macro2:: Ident ,
4
+ proc_macro2:: { Ident , Span , TokenStream } ,
5
+ quote:: quote,
5
6
syn:: {
6
7
parse:: { Parse , ParseStream } ,
7
8
token:: Comma ,
8
- LitInt , Token ,
9
+ LitInt , LitStr , Token ,
9
10
} ,
10
11
} ;
11
12
@@ -14,51 +15,131 @@ pub struct SplProgramErrorArgs {
14
15
/// Whether to hash the error codes using `solana_program::hash`
15
16
/// or to use the default error code assigned by `num_traits`.
16
17
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
+ }
17
49
}
18
50
19
51
impl Parse for SplProgramErrorArgs {
20
52
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
+ }
30
67
}
68
+ Ok ( Self {
69
+ hash_error_code_start,
70
+ import : import. unwrap_or ( SolanaProgram :: default ( ) ) ,
71
+ } )
31
72
}
32
73
}
33
74
34
75
/// Parser for args to the `#[spl_program_error]` attribute
35
76
/// ie. `#[spl_program_error(hash_error_code_start = 1275525928)]`
36
77
enum SplProgramErrorArgParser {
37
78
HashErrorCodes {
38
- _ident : Ident ,
39
79
_equals_sign : Token ! [ =] ,
40
80
value : LitInt ,
41
81
_comma : Option < Comma > ,
42
82
} ,
83
+ SolanaProgramCrate {
84
+ _equals_sign : Token ! [ =] ,
85
+ value : LitStr ,
86
+ _comma : Option < Comma > ,
87
+ } ,
43
88
}
44
89
45
90
impl Parse for SplProgramErrorArgParser {
46
91
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
+ } )
51
113
}
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
53
143
} ;
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
- } )
63
144
}
64
145
}
0 commit comments