@@ -3,31 +3,198 @@ extern crate proc_macro;
3
3
use proc_macro:: TokenStream ;
4
4
use proc_macro2:: TokenStream as TokenStream2 ;
5
5
use quote:: quote;
6
- use syn:: { Data , DeriveInput , Fields } ;
6
+ use syn:: { Attribute , Data , DeriveInput , Expr , Field , Fields , Ident , Lit , Meta , NestedMeta } ;
7
7
8
- #[ proc_macro_derive( FromArgs ) ]
8
+ #[ proc_macro_derive( FromArgs , attributes ( pyarg ) ) ]
9
9
pub fn derive_from_args ( input : TokenStream ) -> TokenStream {
10
10
let ast: DeriveInput = syn:: parse ( input) . unwrap ( ) ;
11
11
12
12
let gen = impl_from_args ( & ast) ;
13
13
gen. to_string ( ) . parse ( ) . unwrap ( )
14
14
}
15
15
16
+ /// The kind of the python parameter, this corresponds to the value of Parameter.kind
17
+ /// (https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind)
18
+ enum ParameterKind {
19
+ PositionalOnly ,
20
+ PositionalOrKeyword ,
21
+ KeywordOnly ,
22
+ }
23
+
24
+ impl ParameterKind {
25
+ fn from_ident ( ident : & Ident ) -> ParameterKind {
26
+ if ident == "positional_only" {
27
+ ParameterKind :: PositionalOnly
28
+ } else if ident == "positional_or_keyword" {
29
+ ParameterKind :: PositionalOrKeyword
30
+ } else if ident == "keyword_only" {
31
+ ParameterKind :: KeywordOnly
32
+ } else {
33
+ panic ! ( "Unrecognised attribute" )
34
+ }
35
+ }
36
+ }
37
+
38
+ struct ArgAttribute {
39
+ kind : ParameterKind ,
40
+ default : Option < Expr > ,
41
+ optional : bool ,
42
+ }
43
+
44
+ impl ArgAttribute {
45
+ fn from_attribute ( attr : & Attribute ) -> Option < ArgAttribute > {
46
+ if !attr. path . is_ident ( "pyarg" ) {
47
+ return None ;
48
+ }
49
+
50
+ match attr. parse_meta ( ) . unwrap ( ) {
51
+ Meta :: List ( list) => {
52
+ let mut iter = list. nested . iter ( ) ;
53
+ let first_arg = iter. next ( ) . expect ( "at least one argument in pyarg list" ) ;
54
+ let kind = match first_arg {
55
+ NestedMeta :: Meta ( Meta :: Word ( ident) ) => ParameterKind :: from_ident ( ident) ,
56
+ _ => panic ! ( "Bad syntax for first pyarg attribute argument" ) ,
57
+ } ;
58
+
59
+ let mut attribute = ArgAttribute {
60
+ kind,
61
+ default : None ,
62
+ optional : false ,
63
+ } ;
64
+
65
+ while let Some ( arg) = iter. next ( ) {
66
+ attribute. parse_argument ( arg) ;
67
+ }
68
+
69
+ assert ! (
70
+ attribute. default . is_none( ) || !attribute. optional,
71
+ "Can't set both a default value and optional"
72
+ ) ;
73
+
74
+ Some ( attribute)
75
+ }
76
+ _ => panic ! ( "Bad syntax for pyarg attribute" ) ,
77
+ }
78
+ }
79
+
80
+ fn parse_argument ( & mut self , arg : & NestedMeta ) {
81
+ match arg {
82
+ NestedMeta :: Meta ( Meta :: Word ( ident) ) => {
83
+ if ident == "default" {
84
+ assert ! ( self . default . is_none( ) , "Default already set" ) ;
85
+ let expr = syn:: parse_str :: < Expr > ( "Default::default()" ) . unwrap ( ) ;
86
+ self . default = Some ( expr) ;
87
+ } else if ident == "optional" {
88
+ self . optional = true ;
89
+ } else {
90
+ panic ! ( "Unrecognised pyarg attribute '{}'" , ident) ;
91
+ }
92
+ }
93
+ NestedMeta :: Meta ( Meta :: NameValue ( name_value) ) => {
94
+ if name_value. ident == "default" {
95
+ assert ! ( self . default . is_none( ) , "Default already set" ) ;
96
+
97
+ match name_value. lit {
98
+ Lit :: Str ( ref val) => {
99
+ let expr = val
100
+ . parse :: < Expr > ( )
101
+ . expect ( "a valid expression for default argument" ) ;
102
+ self . default = Some ( expr) ;
103
+ }
104
+ _ => panic ! ( "Expected string value for default argument" ) ,
105
+ }
106
+ } else if name_value. ident == "optional" {
107
+ match name_value. lit {
108
+ Lit :: Bool ( ref val) => {
109
+ self . optional = val. value ;
110
+ }
111
+ _ => panic ! ( "Expected boolean value for optional argument" ) ,
112
+ }
113
+ } else {
114
+ panic ! ( "Unrecognised pyarg attribute '{}'" , name_value. ident) ;
115
+ }
116
+ }
117
+ _ => panic ! ( "Bad syntax for first pyarg attribute argument" ) ,
118
+ } ;
119
+ }
120
+ }
121
+
122
+ fn generate_field ( field : & Field ) -> TokenStream2 {
123
+ let mut pyarg_attrs = field
124
+ . attrs
125
+ . iter ( )
126
+ . filter_map ( ArgAttribute :: from_attribute)
127
+ . collect :: < Vec < _ > > ( ) ;
128
+ let attr = if pyarg_attrs. is_empty ( ) {
129
+ ArgAttribute {
130
+ kind : ParameterKind :: PositionalOrKeyword ,
131
+ default : None ,
132
+ optional : false ,
133
+ }
134
+ } else if pyarg_attrs. len ( ) == 1 {
135
+ pyarg_attrs. remove ( 0 )
136
+ } else {
137
+ panic ! (
138
+ "Multiple pyarg attributes on field '{}'" ,
139
+ field. ident. as_ref( ) . unwrap( )
140
+ ) ;
141
+ } ;
142
+
143
+ let name = & field. ident ;
144
+ let middle = quote ! {
145
+ . map( |x| crate :: pyobject:: TryFromObject :: try_from_object( vm, x) ) . transpose( ) ?
146
+ } ;
147
+ let ending = if let Some ( default) = attr. default {
148
+ quote ! {
149
+ . unwrap_or_else( || #default )
150
+ }
151
+ } else if attr. optional {
152
+ quote ! {
153
+ . map( crate :: function:: OptionalArg :: Present )
154
+ . unwrap_or( crate :: function:: OptionalArg :: Missing )
155
+ }
156
+ } else {
157
+ let err = match attr. kind {
158
+ ParameterKind :: PositionalOnly | ParameterKind :: PositionalOrKeyword => quote ! {
159
+ crate :: function:: ArgumentError :: TooFewArgs
160
+ } ,
161
+ ParameterKind :: KeywordOnly => quote ! {
162
+ crate :: function:: ArgumentError :: RequiredKeywordArgument ( tringify!( #name) )
163
+ } ,
164
+ } ;
165
+ quote ! {
166
+ . ok_or_else( || #err) ?
167
+ }
168
+ } ;
169
+
170
+ match attr. kind {
171
+ ParameterKind :: PositionalOnly => {
172
+ quote ! {
173
+ #name: args. take_positional( ) #middle#ending,
174
+ }
175
+ }
176
+ ParameterKind :: PositionalOrKeyword => {
177
+ quote ! {
178
+ #name: args. take_positional_keyword( stringify!( #name) ) #middle#ending,
179
+ }
180
+ }
181
+ ParameterKind :: KeywordOnly => {
182
+ quote ! {
183
+ #name: args. take_keyword( stringify!( #name) ) #middle#ending,
184
+ }
185
+ }
186
+ }
187
+ }
188
+
16
189
fn impl_from_args ( input : & DeriveInput ) -> TokenStream2 {
17
190
// FIXME: This references types using `crate` instead of `rustpython_vm`
18
191
// so that it can be used in the latter. How can we support both?
192
+ // Can use extern crate self as rustpython_vm; once in stable.
193
+ // https://github.com/rust-lang/rust/issues/56409
19
194
let fields = match input. data {
20
195
Data :: Struct ( ref data) => {
21
196
match data. fields {
22
- Fields :: Named ( ref fields) => fields. named . iter ( ) . map ( |field| {
23
- let name = & field. ident ;
24
- quote ! {
25
- #name: crate :: pyobject:: TryFromObject :: try_from_object(
26
- vm,
27
- args. take_keyword( stringify!( #name) ) . unwrap_or_else( || vm. ctx. none( ) )
28
- ) ?,
29
- }
30
- } ) ,
197
+ Fields :: Named ( ref fields) => fields. named . iter ( ) . map ( generate_field) ,
31
198
Fields :: Unnamed ( _) | Fields :: Unit => unimplemented ! ( ) , // TODO: better error message
32
199
}
33
200
}
0 commit comments