Skip to content

Commit 40eb039

Browse files
author
toasteater
authored
Merge pull request #192 from toasteater/feature/default-args
Support optional arguments
2 parents f92d12f + f9888d6 commit 40eb039

File tree

5 files changed

+256
-23
lines changed

5 files changed

+256
-23
lines changed

gdnative-core/src/macros.rs

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,7 @@ macro_rules! godot_wrap_method_inner {
513513
$self:ident,
514514
$owner:ident : $owner_ty:ty
515515
$(,$pname:ident : $pty:ty)*
516+
$(, #[opt] $opt_pname:ident : $opt_pty:ty)*
516517
) -> $retty:ty
517518
) => {
518519
{
@@ -530,9 +531,18 @@ macro_rules! godot_wrap_method_inner {
530531

531532
let __instance: Instance<$type_name> = Instance::from_raw(this, user_data);
532533

533-
let num_params = godot_wrap_method_parameter_count!($($pname,)*);
534-
if num_args != num_params {
535-
godot_error!("Incorrect number of parameters: expected {} but got {}", num_params, num_args);
534+
let num_args = num_args as isize;
535+
536+
let num_required_params = godot_wrap_method_parameter_count!($($pname,)*);
537+
if num_args < num_required_params {
538+
godot_error!("Incorrect number of parameters: required {} but got {}", num_required_params, num_args);
539+
return $crate::Variant::new().to_sys();
540+
}
541+
542+
let num_optional_params = godot_wrap_method_parameter_count!($($opt_pname,)*);
543+
let num_max_params = num_required_params + num_optional_params;
544+
if num_args > num_max_params {
545+
godot_error!("Incorrect number of parameters: expected at most {} but got {}", num_max_params, num_args);
536546
return $crate::Variant::new().to_sys();
537547
}
538548

@@ -556,9 +566,36 @@ macro_rules! godot_wrap_method_inner {
556566
offset += 1;
557567
)*
558568

569+
$(
570+
let $opt_pname = if offset < num_args {
571+
let _variant: &$crate::Variant = ::std::mem::transmute(&mut **(args.offset(offset)));
572+
573+
let $opt_pname = match <$opt_pty as $crate::FromVariant>::from_variant(_variant) {
574+
Ok(val) => val,
575+
Err(err) => {
576+
godot_error!(
577+
"Cannot convert argument #{idx} ({name}) to {ty}: {err} (non-primitive types may impose structural checks)",
578+
idx = offset + 1,
579+
name = stringify!($opt_pname),
580+
ty = stringify!($opt_pty),
581+
err = err,
582+
);
583+
return $crate::Variant::new().to_sys();
584+
},
585+
};
586+
587+
offset += 1;
588+
589+
$opt_pname
590+
}
591+
else {
592+
<$opt_pty as ::std::default::Default>::default()
593+
};
594+
)*
595+
559596
let rust_ret = match panic::catch_unwind(AssertUnwindSafe(move || {
560597
let ret = __instance.$map_method(|__rust_val, $owner| {
561-
let ret = __rust_val.$method_name($owner, $($pname,)*);
598+
let ret = __rust_val.$method_name($owner, $($pname,)* $($opt_pname,)*);
562599
<$retty as $crate::ToVariant>::to_variant(&ret)
563600
});
564601
std::mem::drop(__instance);
@@ -596,6 +633,7 @@ macro_rules! godot_wrap_method {
596633
&mut $self:ident,
597634
$owner:ident : $owner_ty:ty
598635
$(,$pname:ident : $pty:ty)*
636+
$(,#[opt] $opt_pname:ident : $opt_pty:ty)*
599637
$(,)?
600638
) -> $retty:ty
601639
) => {
@@ -606,6 +644,7 @@ macro_rules! godot_wrap_method {
606644
$self,
607645
$owner: $owner_ty
608646
$(,$pname : $pty)*
647+
$(,#[opt] $opt_pname : $opt_pty)*
609648
) -> $retty
610649
)
611650
};
@@ -616,6 +655,7 @@ macro_rules! godot_wrap_method {
616655
& $self:ident,
617656
$owner:ident : $owner_ty:ty
618657
$(,$pname:ident : $pty:ty)*
658+
$(,#[opt] $opt_pname:ident : $opt_pty:ty)*
619659
$(,)?
620660
) -> $retty:ty
621661
) => {
@@ -626,6 +666,7 @@ macro_rules! godot_wrap_method {
626666
$self,
627667
$owner: $owner_ty
628668
$(,$pname : $pty)*
669+
$(,#[opt] $opt_pname : $opt_pty)*
629670
) -> $retty
630671
)
631672
};
@@ -636,6 +677,7 @@ macro_rules! godot_wrap_method {
636677
&mut $self:ident,
637678
$owner:ident : $owner_ty:ty
638679
$(,$pname:ident : $pty:ty)*
680+
$(,#[opt] $opt_pname:ident : $opt_pty:ty)*
639681
$(,)?
640682
)
641683
) => {
@@ -645,6 +687,7 @@ macro_rules! godot_wrap_method {
645687
&mut $self,
646688
$owner: $owner_ty
647689
$(,$pname : $pty)*
690+
$(,#[opt] $opt_pname : $opt_pty)*
648691
) -> ()
649692
)
650693
};
@@ -655,6 +698,7 @@ macro_rules! godot_wrap_method {
655698
& $self:ident,
656699
$owner:ident : $owner_ty:ty
657700
$(,$pname:ident : $pty:ty)*
701+
$(,#[opt] $opt_pname:ident : $opt_pty:ty)*
658702
$(,)?
659703
)
660704
) => {
@@ -664,6 +708,7 @@ macro_rules! godot_wrap_method {
664708
& $self,
665709
$owner: $owner_ty
666710
$(,$pname : $pty)*
711+
$(,#[opt] $opt_pname : $opt_pty)*
667712
) -> ()
668713
)
669714
};

gdnative-derive/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub fn methods(meta: TokenStream, input: TokenStream) -> TokenStream {
1818

1919
#[proc_macro_derive(
2020
NativeClass,
21-
attributes(inherit, export, user_data, property, register_with)
21+
attributes(inherit, export, opt, user_data, property, register_with)
2222
)]
2323
pub fn derive_native_class(input: TokenStream) -> TokenStream {
2424
native_script::derive_native_class(input)

gdnative-derive/src/methods.rs

Lines changed: 143 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,18 @@ use syn::export::Span;
66

77
pub(crate) struct ClassMethodExport {
88
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>,
1021
}
1122

1223
pub(crate) fn derive_methods(meta: TokenStream, input: TokenStream) -> TokenStream {
@@ -18,17 +29,50 @@ pub(crate) fn derive_methods(meta: TokenStream, input: TokenStream) -> TokenStre
1829
let methods = export
1930
.methods
2031
.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+
});
2367

2468
quote!(
2569
{
2670
let method = gdnative::godot_wrap_method!(
2771
#class_name,
28-
#m
72+
fn #name ( #( #args )* ) -> #ret_ty
2973
);
3074

31-
builder.add_method(#name, method);
75+
builder.add_method(#name_string, method);
3276
}
3377
)
3478
})
@@ -85,34 +129,115 @@ fn impl_gdnative_expose(ast: ItemImpl) -> (ItemImpl, ClassMethodExport) {
85129
methods: vec![],
86130
};
87131

88-
let mut methods_to_export = Vec::<Signature>::new();
132+
let mut methods_to_export: Vec<ExportMethod> = Vec::new();
89133

90134
// extract all methods that have the #[export] attribute.
91135
// add all items back to the impl block again.
92136
for func in ast.items {
93137
let item = match func {
94138
ImplItem::Method(mut method) => {
139+
let mut export_args = None;
140+
95141
// only allow the "outer" style, aka #[thing] item.
96-
let attribute_pos = method.attrs.iter().position(|attr| {
142+
method.attrs.retain(|attr| {
97143
let correct_style = match attr.style {
98144
syn::AttrStyle::Outer => true,
99145
_ => false,
100146
};
101147

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;
105196
}
106197
}
107198

108-
false
199+
true
109200
});
110201

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;
114236

115-
methods_to_export.push(method.sig.clone());
237+
methods_to_export.push(ExportMethod {
238+
sig: method.sig.clone(),
239+
args: export_args,
240+
});
116241
}
117242

118243
ImplItem::Method(method)
@@ -127,7 +252,7 @@ fn impl_gdnative_expose(ast: ItemImpl) -> (ItemImpl, ClassMethodExport) {
127252
// into the list of things to export.
128253
{
129254
for mut method in methods_to_export {
130-
let generics = &method.generics;
255+
let generics = &method.sig.generics;
131256

132257
if generics.type_params().count() > 0 {
133258
eprintln!("type parameters not allowed in exported functions");
@@ -145,6 +270,7 @@ fn impl_gdnative_expose(ast: ItemImpl) -> (ItemImpl, ClassMethodExport) {
145270
// remove "mut" from arguments.
146271
// give every wildcard a (hopefully) unique name.
147272
method
273+
.sig
148274
.inputs
149275
.iter_mut()
150276
.enumerate()
@@ -172,7 +298,7 @@ fn impl_gdnative_expose(ast: ItemImpl) -> (ItemImpl, ClassMethodExport) {
172298

173299
// The calling site is already in an unsafe block, so removing it from just the
174300
// exported binding is fine.
175-
method.unsafety = None;
301+
method.sig.unsafety = None;
176302

177303
export.methods.push(method);
178304
}

0 commit comments

Comments
 (0)