@@ -6,7 +6,18 @@ use syn::export::Span;
6
6
7
7
pub ( crate ) struct ClassMethodExport {
8
8
pub ( crate ) class_ty : Box < Type > ,
9
- pub ( crate ) methods : Vec < Signature > ,
9
+ pub ( crate ) methods : Vec < ExportMethod > ,
10
+ }
11
+
12
+ #[ derive( Clone , Eq , PartialEq , Hash , Debug ) ]
13
+ pub ( crate ) struct ExportMethod {
14
+ pub ( crate ) sig : Signature ,
15
+ pub ( crate ) args : ExportArgs ,
16
+ }
17
+
18
+ #[ derive( Clone , Eq , PartialEq , Ord , PartialOrd , Hash , Debug , Default ) ]
19
+ pub ( crate ) struct ExportArgs {
20
+ pub ( crate ) optional_args : Option < usize > ,
10
21
}
11
22
12
23
pub ( crate ) fn derive_methods ( meta : TokenStream , input : TokenStream ) -> TokenStream {
@@ -18,17 +29,50 @@ pub(crate) fn derive_methods(meta: TokenStream, input: TokenStream) -> TokenStre
18
29
let methods = export
19
30
. methods
20
31
. into_iter ( )
21
- . map ( |m| {
22
- let name = m. ident . clone ( ) . to_string ( ) ;
32
+ . map ( |ExportMethod { sig, args } | {
33
+ let name = sig. ident ;
34
+ let name_string = name. to_string ( ) ;
35
+ let ret_ty = match sig. output {
36
+ syn:: ReturnType :: Default => quote ! ( ( ) ) ,
37
+ syn:: ReturnType :: Type ( _, ty) => quote ! ( #ty ) ,
38
+ } ;
39
+
40
+ let arg_count = sig. inputs . len ( ) ;
41
+
42
+ if arg_count < 2 {
43
+ panic ! ( "exported methods must take self and owner as arguments." ) ;
44
+ }
45
+
46
+ let optional_args = match args. optional_args {
47
+ Some ( count) => {
48
+ let max_optional = arg_count - 2 ; // self and owner
49
+ if count > max_optional {
50
+ panic ! (
51
+ "there can be at most {} optional arguments, got {}" ,
52
+ max_optional, count
53
+ ) ;
54
+ }
55
+ count
56
+ }
57
+ None => 0 ,
58
+ } ;
59
+
60
+ let args = sig. inputs . iter ( ) . enumerate ( ) . map ( |( n, arg) | {
61
+ if n < arg_count - optional_args {
62
+ quote ! ( #arg , )
63
+ } else {
64
+ quote ! ( #[ opt] #arg , )
65
+ }
66
+ } ) ;
23
67
24
68
quote ! (
25
69
{
26
70
let method = gdnative:: godot_wrap_method!(
27
71
#class_name,
28
- #m
72
+ fn #name ( # ( #args ) * ) -> #ret_ty
29
73
) ;
30
74
31
- builder. add_method( #name , method) ;
75
+ builder. add_method( #name_string , method) ;
32
76
}
33
77
)
34
78
} )
@@ -85,34 +129,115 @@ fn impl_gdnative_expose(ast: ItemImpl) -> (ItemImpl, ClassMethodExport) {
85
129
methods : vec ! [ ] ,
86
130
} ;
87
131
88
- let mut methods_to_export = Vec :: < Signature > :: new ( ) ;
132
+ let mut methods_to_export: Vec < ExportMethod > = Vec :: new ( ) ;
89
133
90
134
// extract all methods that have the #[export] attribute.
91
135
// add all items back to the impl block again.
92
136
for func in ast. items {
93
137
let item = match func {
94
138
ImplItem :: Method ( mut method) => {
139
+ let mut export_args = None ;
140
+
95
141
// only allow the "outer" style, aka #[thing] item.
96
- let attribute_pos = method. attrs . iter ( ) . position ( |attr| {
142
+ method. attrs . retain ( |attr| {
97
143
let correct_style = match attr. style {
98
144
syn:: AttrStyle :: Outer => true ,
99
145
_ => false ,
100
146
} ;
101
147
102
- for path in attr. path . segments . iter ( ) {
103
- if path. ident . to_string ( ) == "export" {
104
- return correct_style;
148
+ if correct_style {
149
+ let last_seg = attr
150
+ . path
151
+ . segments
152
+ . iter ( )
153
+ . last ( )
154
+ . map ( |i| i. ident . to_string ( ) ) ;
155
+
156
+ if let Some ( "export" ) = last_seg. as_ref ( ) . map ( String :: as_str) {
157
+ let _export_args = export_args. get_or_insert_with ( ExportArgs :: default) ;
158
+ if !attr. tokens . is_empty ( ) {
159
+ use quote:: ToTokens ;
160
+ use syn:: { Meta , MetaNameValue , NestedMeta } ;
161
+
162
+ let meta =
163
+ attr. parse_meta ( ) . expect ( "cannot parse attribute arguments" ) ;
164
+
165
+ let pairs: Vec < _ > = match meta {
166
+ Meta :: List ( list) => list
167
+ . nested
168
+ . into_pairs ( )
169
+ . map ( |p| match p. into_value ( ) {
170
+ NestedMeta :: Meta ( Meta :: NameValue ( pair) ) => pair,
171
+ unexpected => panic ! (
172
+ "unexpected argument in list: {}" ,
173
+ unexpected. into_token_stream( )
174
+ ) ,
175
+ } )
176
+ . collect ( ) ,
177
+ Meta :: NameValue ( pair) => vec ! [ pair] ,
178
+ meta => panic ! (
179
+ "unexpected attribute argument: {}" ,
180
+ meta. into_token_stream( )
181
+ ) ,
182
+ } ;
183
+
184
+ for MetaNameValue { path, .. } in pairs. into_iter ( ) {
185
+ let last =
186
+ path. segments . last ( ) . expect ( "the path should not be empty" ) ;
187
+ match last. ident . to_string ( ) . as_str ( ) {
188
+ unexpected => {
189
+ panic ! ( "unknown option for export: `{}`" , unexpected)
190
+ }
191
+ }
192
+ }
193
+ }
194
+
195
+ return false ;
105
196
}
106
197
}
107
198
108
- false
199
+ true
109
200
} ) ;
110
201
111
- if let Some ( idx) = attribute_pos {
112
- // TODO renaming? rpc modes?
113
- let _attr = method. attrs . remove ( idx) ;
202
+ if let Some ( mut export_args) = export_args. take ( ) {
203
+ let mut optional_args = None ;
204
+
205
+ for ( n, arg) in method. sig . inputs . iter_mut ( ) . enumerate ( ) {
206
+ let attrs = match arg {
207
+ FnArg :: Receiver ( a) => & mut a. attrs ,
208
+ FnArg :: Typed ( a) => & mut a. attrs ,
209
+ } ;
210
+
211
+ let mut is_optional = false ;
212
+
213
+ attrs. retain ( |attr| {
214
+ if attr. path . is_ident ( "opt" ) {
215
+ is_optional = true ;
216
+ false
217
+ } else {
218
+ true
219
+ }
220
+ } ) ;
221
+
222
+ if is_optional {
223
+ if n < 2 {
224
+ panic ! ( "self and owner cannot be optional" ) ;
225
+ }
226
+
227
+ * optional_args. get_or_insert ( 0 ) += 1 ;
228
+ } else {
229
+ if optional_args. is_some ( ) {
230
+ panic ! ( "cannot add required parameters after optional ones" ) ;
231
+ }
232
+ }
233
+ }
234
+
235
+ export_args. optional_args = optional_args;
114
236
115
- methods_to_export. push ( method. sig . clone ( ) ) ;
237
+ methods_to_export. push ( ExportMethod {
238
+ sig : method. sig . clone ( ) ,
239
+ args : export_args,
240
+ } ) ;
116
241
}
117
242
118
243
ImplItem :: Method ( method)
@@ -127,7 +252,7 @@ fn impl_gdnative_expose(ast: ItemImpl) -> (ItemImpl, ClassMethodExport) {
127
252
// into the list of things to export.
128
253
{
129
254
for mut method in methods_to_export {
130
- let generics = & method. generics ;
255
+ let generics = & method. sig . generics ;
131
256
132
257
if generics. type_params ( ) . count ( ) > 0 {
133
258
eprintln ! ( "type parameters not allowed in exported functions" ) ;
@@ -145,6 +270,7 @@ fn impl_gdnative_expose(ast: ItemImpl) -> (ItemImpl, ClassMethodExport) {
145
270
// remove "mut" from arguments.
146
271
// give every wildcard a (hopefully) unique name.
147
272
method
273
+ . sig
148
274
. inputs
149
275
. iter_mut ( )
150
276
. enumerate ( )
@@ -172,7 +298,7 @@ fn impl_gdnative_expose(ast: ItemImpl) -> (ItemImpl, ClassMethodExport) {
172
298
173
299
// The calling site is already in an unsafe block, so removing it from just the
174
300
// exported binding is fine.
175
- method. unsafety = None ;
301
+ method. sig . unsafety = None ;
176
302
177
303
export. methods . push ( method) ;
178
304
}
0 commit comments