1
1
extern crate proc_macro;
2
2
3
3
use proc_macro:: TokenStream ;
4
- use proc_macro2:: TokenStream as TokenStream2 ;
4
+ use proc_macro2:: { Span , TokenStream as TokenStream2 } ;
5
5
use quote:: quote;
6
- use syn:: { Data , DeriveInput , Fields } ;
6
+ use syn:: {
7
+ parse_macro_input, AttributeArgs , Data , DeriveInput , Fields , Ident , ImplItem , Item , Lit , Meta ,
8
+ NestedMeta ,
9
+ } ;
7
10
8
- #[ proc_macro_derive( FromArgs ) ]
11
+ fn rustpython_path ( inside_vm : bool ) -> syn:: Path {
12
+ let path = if inside_vm {
13
+ quote ! ( crate )
14
+ } else {
15
+ quote ! ( :: rustpython_vm)
16
+ } ;
17
+ syn:: parse2 ( path) . unwrap ( )
18
+ }
19
+
20
+ /// Does the item have the #[__inside_vm] attribute on it, signifying that the derive target is
21
+ /// being derived from inside the `rustpython_vm` crate.
22
+ fn rustpython_path_derive ( input : & DeriveInput ) -> syn:: Path {
23
+ rustpython_path (
24
+ input
25
+ . attrs
26
+ . iter ( )
27
+ . any ( |attr| attr. path . is_ident ( "__inside_vm" ) ) ,
28
+ )
29
+ }
30
+
31
+ fn rustpython_path_attr ( attr : & AttributeArgs ) -> syn:: Path {
32
+ rustpython_path ( attr. iter ( ) . any ( |meta| {
33
+ if let syn:: NestedMeta :: Meta ( meta) = meta {
34
+ if let syn:: Meta :: Word ( ident) = meta {
35
+ ident == "__inside_vm"
36
+ } else {
37
+ false
38
+ }
39
+ } else {
40
+ false
41
+ }
42
+ } ) )
43
+ }
44
+
45
+ #[ proc_macro_derive( FromArgs , attributes( __inside_vm) ) ]
9
46
pub fn derive_from_args ( input : TokenStream ) -> TokenStream {
10
47
let ast: DeriveInput = syn:: parse ( input) . unwrap ( ) ;
11
48
12
- let gen = impl_from_args ( & ast) ;
49
+ let gen = impl_from_args ( ast) ;
13
50
gen. to_string ( ) . parse ( ) . unwrap ( )
14
51
}
15
52
16
- fn impl_from_args ( input : & DeriveInput ) -> TokenStream2 {
17
- // FIXME: This references types using `crate` instead of `rustpython_vm`
18
- // so that it can be used in the latter. How can we support both?
53
+ fn impl_from_args ( input : DeriveInput ) -> TokenStream2 {
54
+ let rp_path = rustpython_path_derive ( & input) ;
19
55
let fields = match input. data {
20
56
Data :: Struct ( ref data) => {
21
57
match data. fields {
@@ -36,7 +72,7 @@ fn impl_from_args(input: &DeriveInput) -> TokenStream2 {
36
72
37
73
let name = & input. ident ;
38
74
quote ! {
39
- impl crate :: function:: FromArgs for #name {
75
+ impl #rp_path :: function:: FromArgs for #name {
40
76
fn from_args(
41
77
vm: & crate :: vm:: VirtualMachine ,
42
78
args: & mut crate :: function:: PyFuncArgs
@@ -46,3 +82,149 @@ fn impl_from_args(input: &DeriveInput) -> TokenStream2 {
46
82
}
47
83
}
48
84
}
85
+
86
+ #[ proc_macro_attribute]
87
+ pub fn py_class ( attr : TokenStream , item : TokenStream ) -> TokenStream {
88
+ let attr = parse_macro_input ! ( attr as AttributeArgs ) ;
89
+ let item = parse_macro_input ! ( item as Item ) ;
90
+ impl_py_class ( attr, item) . into ( )
91
+ }
92
+
93
+ enum MethodKind {
94
+ Method ,
95
+ Property ,
96
+ }
97
+
98
+ impl MethodKind {
99
+ fn to_ctx_constructor_fn ( & self ) -> Ident {
100
+ let f = match self {
101
+ MethodKind :: Method => "new_rustfunc" ,
102
+ MethodKind :: Property => "new_property" ,
103
+ } ;
104
+ Ident :: new ( f, Span :: call_site ( ) )
105
+ }
106
+ }
107
+
108
+ struct Method {
109
+ fn_name : Ident ,
110
+ py_name : String ,
111
+ kind : MethodKind ,
112
+ }
113
+
114
+ fn item_impl_to_methods < ' a > ( imp : & ' a syn:: ItemImpl ) -> impl Iterator < Item = Method > + ' a {
115
+ imp. items . iter ( ) . filter_map ( |item| {
116
+ if let ImplItem :: Method ( meth) = item {
117
+ let mut py_name = None ;
118
+ let mut kind = MethodKind :: Method ;
119
+ let metas_iter = meth
120
+ . attrs
121
+ . iter ( )
122
+ . filter_map ( |attr| {
123
+ if attr. path . is_ident ( "py_class" ) {
124
+ let meta = attr. parse_meta ( ) . expect ( "Invalid attribute" ) ;
125
+ if let Meta :: List ( list) = meta {
126
+ Some ( list)
127
+ } else {
128
+ panic ! (
129
+ "#[py_class] attribute on a method should be a list, like \
130
+ #[py_class(...)]"
131
+ )
132
+ }
133
+ } else {
134
+ None
135
+ }
136
+ } )
137
+ . flat_map ( |attr| attr. nested ) ;
138
+ for meta in metas_iter {
139
+ if let NestedMeta :: Meta ( meta) = meta {
140
+ match meta {
141
+ Meta :: NameValue ( name_value) => {
142
+ if name_value. ident == "name" {
143
+ if let Lit :: Str ( s) = & name_value. lit {
144
+ py_name = Some ( s. value ( ) ) ;
145
+ } else {
146
+ panic ! ( "#[py_class(name = ...)] must be a string" ) ;
147
+ }
148
+ }
149
+ }
150
+ Meta :: Word ( ident) => match ident. to_string ( ) . as_str ( ) {
151
+ "property" => kind = MethodKind :: Property ,
152
+ _ => { }
153
+ } ,
154
+ _ => { }
155
+ }
156
+ }
157
+ }
158
+ let py_name = py_name. unwrap_or_else ( || meth. sig . ident . to_string ( ) ) ;
159
+ Some ( Method {
160
+ fn_name : meth. sig . ident . clone ( ) ,
161
+ py_name,
162
+ kind,
163
+ } )
164
+ } else {
165
+ None
166
+ }
167
+ } )
168
+ }
169
+
170
+ fn impl_py_class ( attr : AttributeArgs , item : Item ) -> TokenStream2 {
171
+ let imp = if let Item :: Impl ( imp) = item {
172
+ imp
173
+ } else {
174
+ return quote ! ( #item) ;
175
+ } ;
176
+ let rp_path = rustpython_path_attr ( & attr) ;
177
+ let mut class_name = None ;
178
+ let mut doc = None ;
179
+ for attr in attr {
180
+ if let NestedMeta :: Meta ( meta) = attr {
181
+ if let Meta :: NameValue ( name_value) = meta {
182
+ if name_value. ident == "name" {
183
+ if let Lit :: Str ( s) = name_value. lit {
184
+ class_name = Some ( s. value ( ) ) ;
185
+ } else {
186
+ panic ! ( "#[py_class(name = ...)] must be a string" ) ;
187
+ }
188
+ } else if name_value. ident == "doc" {
189
+ if let Lit :: Str ( s) = name_value. lit {
190
+ doc = Some ( s. value ( ) ) ;
191
+ } else {
192
+ panic ! ( "#[py_class(name = ...)] must be a string" ) ;
193
+ }
194
+ }
195
+ }
196
+ }
197
+ }
198
+ let class_name = class_name. expect ( "#[py_class] must have a name" ) ;
199
+ let doc = match doc {
200
+ Some ( doc) => quote ! ( Some ( #doc) ) ,
201
+ None => quote ! ( None ) ,
202
+ } ;
203
+ let ty = & imp. self_ty ;
204
+ let methods = item_impl_to_methods ( & imp) . map (
205
+ |Method {
206
+ py_name,
207
+ fn_name,
208
+ kind,
209
+ } | {
210
+ let constructor_fn = kind. to_ctx_constructor_fn ( ) ;
211
+ quote ! {
212
+ ctx. set_attr( class, #py_name, ctx. #constructor_fn( #ty:: #fn_name) ) ;
213
+ }
214
+ } ,
215
+ ) ;
216
+
217
+ quote ! {
218
+ #imp
219
+ impl #rp_path:: pyobject:: IntoPyClass for #ty {
220
+ const NAME : & ' static str = #class_name;
221
+ const DOC : Option <& ' static str > = #doc;
222
+ fn _extend_class(
223
+ ctx: & #rp_path:: pyobject:: PyContext ,
224
+ class: & #rp_path:: obj:: objtype:: PyClassRef ,
225
+ ) {
226
+ #( #methods) *
227
+ }
228
+ }
229
+ }
230
+ }
0 commit comments