Skip to content

Commit 2ac0c2a

Browse files
committed
Updating macros with tests
* Adds helpful error messages to failures * Added with_hint property
1 parent 1866572 commit 2ac0c2a

File tree

62 files changed

+1122
-298
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1122
-298
lines changed

gdnative-derive/src/lib.rs

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ extern crate syn;
66
extern crate quote;
77

88
use proc_macro::TokenStream;
9+
use proc_macro2::TokenStream as TokenStream2;
10+
use syn::{AttributeArgs, DeriveInput, ItemFn, ItemImpl};
911

1012
mod methods;
1113
mod native_script;
@@ -14,7 +16,26 @@ mod variant;
1416

1517
#[proc_macro_attribute]
1618
pub fn methods(meta: TokenStream, input: TokenStream) -> TokenStream {
17-
methods::derive_methods(meta, input)
19+
if syn::parse::<syn::parse::Nothing>(meta.clone()).is_err() {
20+
let err = syn::Error::new_spanned(
21+
TokenStream2::from(meta),
22+
"#[methods] does not take parameters.",
23+
);
24+
return error_with_input(input, err);
25+
}
26+
27+
let impl_block = match syn::parse::<ItemImpl>(input.clone()) {
28+
Ok(impl_block) => impl_block,
29+
Err(err) => return error_with_input(input, err),
30+
};
31+
32+
fn error_with_input(input: TokenStream, err: syn::Error) -> TokenStream {
33+
let mut err = TokenStream::from(err.to_compile_error());
34+
err.extend(std::iter::once(input));
35+
err
36+
}
37+
38+
TokenStream::from(methods::derive_methods(impl_block))
1839
}
1940

2041
/// Makes a function profiled in Godot's built-in profiler. This macro automatically
@@ -49,7 +70,13 @@ pub fn methods(meta: TokenStream, input: TokenStream) -> TokenStream {
4970
/// ```
5071
#[proc_macro_attribute]
5172
pub fn profiled(meta: TokenStream, input: TokenStream) -> TokenStream {
52-
profiled::derive_profiled(meta, input)
73+
let args = parse_macro_input!(meta as AttributeArgs);
74+
let item_fn = parse_macro_input!(input as ItemFn);
75+
76+
match profiled::derive_profiled(args, item_fn) {
77+
Ok(tokens) => tokens.into(),
78+
Err(err) => err.to_compile_error().into(),
79+
}
5380
}
5481

5582
/// Makes it possible to use a type as a NativeScript.
@@ -139,20 +166,47 @@ pub fn profiled(meta: TokenStream, input: TokenStream) -> TokenStream {
139166
)
140167
)]
141168
pub fn derive_native_class(input: TokenStream) -> TokenStream {
142-
native_script::derive_native_class(input)
169+
// Converting the proc_macro::TokenStream into non proc_macro types so that tests
170+
// can be written against the inner functions.
171+
let derive_input = syn::parse_macro_input!(input as DeriveInput);
172+
173+
// Implement NativeClass for the input
174+
native_script::derive_native_class(&derive_input).map_or_else(
175+
|err| {
176+
// Silence the other errors that happen because NativeClass is not implemented
177+
let empty_nativeclass = native_script::impl_empty_nativeclass(&derive_input);
178+
let err = err.to_compile_error();
179+
180+
TokenStream::from(quote! {
181+
#empty_nativeclass
182+
#err
183+
})
184+
},
185+
std::convert::identity,
186+
)
143187
}
144188

145189
#[proc_macro_derive(ToVariant, attributes(variant))]
146190
pub fn derive_to_variant(input: TokenStream) -> TokenStream {
147-
variant::derive_to_variant(variant::ToVariantTrait::ToVariant, input)
191+
match variant::derive_to_variant(variant::ToVariantTrait::ToVariant, input) {
192+
Ok(stream) => stream.into(),
193+
Err(err) => err.to_compile_error().into(),
194+
}
148195
}
149196

150197
#[proc_macro_derive(OwnedToVariant, attributes(variant))]
151198
pub fn derive_owned_to_variant(input: TokenStream) -> TokenStream {
152-
variant::derive_to_variant(variant::ToVariantTrait::OwnedToVariant, input)
199+
match variant::derive_to_variant(variant::ToVariantTrait::OwnedToVariant, input) {
200+
Ok(stream) => stream.into(),
201+
Err(err) => err.to_compile_error().into(),
202+
}
153203
}
154204

155205
#[proc_macro_derive(FromVariant, attributes(variant))]
156206
pub fn derive_from_variant(input: TokenStream) -> TokenStream {
157-
variant::derive_from_variant(input)
207+
let derive_input = syn::parse_macro_input!(input as syn::DeriveInput);
208+
match variant::derive_from_variant(derive_input) {
209+
Ok(stream) => stream.into(),
210+
Err(err) => err.to_compile_error().into(),
211+
}
158212
}

gdnative-derive/src/methods.rs

Lines changed: 65 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use syn::{spanned::Spanned, FnArg, ImplItem, ItemImpl, Pat, PatIdent, Signature, Type};
22

3-
use proc_macro::TokenStream;
3+
use proc_macro2::TokenStream as TokenStream2;
44
use quote::{quote, ToTokens};
55
use std::boxed::Box;
66

@@ -67,115 +67,89 @@ pub(crate) struct ExportArgs {
6767
pub(crate) rpc_mode: RpcMode,
6868
}
6969

70-
pub(crate) fn derive_methods(meta: TokenStream, input: TokenStream) -> TokenStream {
71-
let (impl_block, export) = match parse_method_export(meta, input) {
72-
Ok(val) => val,
73-
Err(toks) => return toks,
74-
};
70+
pub(crate) fn derive_methods(item_impl: ItemImpl) -> TokenStream2 {
71+
let (impl_block, export) = impl_gdnative_expose(item_impl);
7572

76-
let output = {
77-
let class_name = export.class_ty;
78-
79-
let builder = syn::Ident::new("builder", proc_macro2::Span::call_site());
80-
81-
let methods = export
82-
.methods
83-
.into_iter()
84-
.map(|ExportMethod { sig, args }| {
85-
let sig_span = sig.ident.span();
86-
87-
let name = sig.ident;
88-
let name_string = name.to_string();
89-
let ret_span = sig.output.span();
90-
let ret_ty = match sig.output {
91-
syn::ReturnType::Default => quote_spanned!(ret_span => ()),
92-
syn::ReturnType::Type(_, ty) => quote_spanned!( ret_span => #ty ),
93-
};
94-
95-
let arg_count = sig.inputs.len();
96-
97-
if arg_count < 2 {
98-
return syn::Error::new(
99-
sig_span,
100-
"exported methods must take self and owner as arguments",
101-
)
102-
.to_compile_error();
103-
}
73+
let class_name = export.class_ty;
10474

105-
let optional_args = match args.optional_args {
106-
Some(count) => {
107-
let max_optional = arg_count - 2; // self and owner
108-
if count > max_optional {
109-
let message = format!(
110-
"there can be at most {} optional arguments, got {}",
111-
max_optional, count,
112-
);
113-
return syn::Error::new(sig_span, message).to_compile_error();
114-
}
115-
count
116-
}
117-
None => 0,
118-
};
75+
let builder = syn::Ident::new("builder", proc_macro2::Span::call_site());
11976

120-
let rpc = args.rpc_mode;
77+
let methods = export
78+
.methods
79+
.into_iter()
80+
.map(|ExportMethod { sig, args }| {
81+
let sig_span = sig.ident.span();
12182

122-
let args = sig.inputs.iter().enumerate().map(|(n, arg)| {
123-
let span = arg.span();
124-
if n < arg_count - optional_args {
125-
quote_spanned!(span => #arg ,)
126-
} else {
127-
quote_spanned!(span => #[opt] #arg ,)
128-
}
129-
});
83+
let name = sig.ident;
84+
let name_string = name.to_string();
85+
let ret_span = sig.output.span();
86+
let ret_ty = match sig.output {
87+
syn::ReturnType::Default => quote_spanned!(ret_span => ()),
88+
syn::ReturnType::Type(_, ty) => quote_spanned!( ret_span => #ty ),
89+
};
13090

131-
quote_spanned!( sig_span=>
132-
{
133-
let method = ::gdnative::godot_wrap_method!(
134-
#class_name,
135-
fn #name ( #( #args )* ) -> #ret_ty
136-
);
91+
let arg_count = sig.inputs.len();
13792

138-
#builder.add_method_with_rpc_mode(#name_string, method, #rpc);
139-
}
93+
if arg_count < 2 {
94+
return syn::Error::new(
95+
sig_span,
96+
"exported methods must take self and owner as arguments",
14097
)
141-
})
142-
.collect::<Vec<_>>();
98+
.to_compile_error();
99+
}
143100

144-
quote::quote!(
101+
let optional_args = match args.optional_args {
102+
Some(count) => {
103+
let max_optional = arg_count - 2; // self and owner
104+
if count > max_optional {
105+
let message = format!(
106+
"there can be at most {} optional arguments, got {}",
107+
max_optional, count,
108+
);
109+
return syn::Error::new(sig_span, message).to_compile_error();
110+
}
111+
count
112+
}
113+
None => 0,
114+
};
145115

146-
#impl_block
116+
let rpc = args.rpc_mode;
147117

148-
impl gdnative::nativescript::NativeClassMethods for #class_name {
118+
let args = sig.inputs.iter().enumerate().map(|(n, arg)| {
119+
let span = arg.span();
120+
if n < arg_count - optional_args {
121+
quote_spanned!(span => #arg ,)
122+
} else {
123+
quote_spanned!(span => #[opt] #arg ,)
124+
}
125+
});
149126

150-
fn register(#builder: &::gdnative::nativescript::init::ClassBuilder<Self>) {
151-
use gdnative::nativescript::init::*;
127+
quote_spanned!( sig_span=>
128+
{
129+
let method = ::gdnative::godot_wrap_method!(
130+
#class_name,
131+
fn #name ( #( #args )* ) -> #ret_ty
132+
);
152133

153-
#(#methods)*
134+
#builder.add_method_with_rpc_mode(#name_string, method, #rpc);
154135
}
136+
)
137+
})
138+
.collect::<Vec<_>>();
155139

156-
}
140+
quote::quote!(
157141

158-
)
159-
};
142+
#impl_block
160143

161-
TokenStream::from(output)
162-
}
144+
impl gdnative::nativescript::NativeClassMethods for #class_name {
145+
fn register(#builder: &::gdnative::nativescript::init::ClassBuilder<Self>) {
146+
use gdnative::nativescript::init::*;
163147

164-
/// Parse the input.
165-
///
166-
/// Returns the TokenStream of the impl block together with a description of methods to export.
167-
fn parse_method_export(
168-
_meta: TokenStream,
169-
input: TokenStream,
170-
) -> Result<(ItemImpl, ClassMethodExport), TokenStream> {
171-
let ast = match syn::parse_macro_input::parse::<ItemImpl>(input) {
172-
Ok(impl_block) => impl_block,
173-
Err(err) => {
174-
return Err(err.to_compile_error().into());
148+
#(#methods)*
149+
}
175150
}
176-
};
177151

178-
Ok(impl_gdnative_expose(ast))
152+
)
179153
}
180154

181155
/// Extract the data to export from the impl block.

0 commit comments

Comments
 (0)