mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 16:21:33 +11:00
Better error message for #[agb::entry] errors
This commit is contained in:
parent
3793034ec3
commit
52e9e3eb32
|
@ -8,13 +8,20 @@ use syn::{FnArg, Ident, ItemFn, Pat, ReturnType, Token, Type, Visibility};
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
|
fn token_stream_with_string_error(mut tokens: TokenStream, error_str: &str) -> TokenStream {
|
||||||
|
tokens.extend(TokenStream::from(quote! { compile_error!(#error_str); }));
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
let f: ItemFn = syn::parse(input).expect("#[agb::entry] must be applied to a function");
|
let f: ItemFn = match syn::parse(input.clone()) {
|
||||||
|
Ok(it) => it,
|
||||||
|
Err(_) => return input,
|
||||||
|
};
|
||||||
|
|
||||||
// Check that the function signature is correct
|
// Check that the function signature is correct
|
||||||
assert!(
|
if !(f.sig.constness.is_none()
|
||||||
f.sig.constness.is_none()
|
|
||||||
&& f.vis == Visibility::Inherited
|
&& f.vis == Visibility::Inherited
|
||||||
&& f.sig.abi.is_none()
|
&& f.sig.abi.is_none()
|
||||||
&& f.sig.generics.params.is_empty()
|
&& f.sig.generics.params.is_empty()
|
||||||
|
@ -22,42 +29,61 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
&& match f.sig.output {
|
&& match f.sig.output {
|
||||||
ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
|
ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
})
|
||||||
"#[agb::entry] must have signature [unsafe] fn (mut agb::Gba) -> !"
|
{
|
||||||
|
return token_stream_with_string_error(
|
||||||
|
input,
|
||||||
|
"#[agb::entry] must have signature [unsafe] fn (mut agb::Gba) -> !",
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Check that the function signature takes 1 argument, agb::Gba
|
// Check that the function signature takes 1 argument, agb::Gba
|
||||||
let arguments: Vec<_> = f.sig.inputs.iter().collect();
|
let arguments: Vec<_> = f.sig.inputs.iter().collect();
|
||||||
|
|
||||||
assert_eq!(
|
if arguments.len() != 1 {
|
||||||
arguments.len(),
|
return token_stream_with_string_error(
|
||||||
1,
|
input,
|
||||||
|
&format!(
|
||||||
"#[agb::entry] must have signature [unsafe] fn (mut agb::Gba) -> !, but got {} arguments",
|
"#[agb::entry] must have signature [unsafe] fn (mut agb::Gba) -> !, but got {} arguments",
|
||||||
arguments.len(),
|
arguments.len()
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let (argument_type, (argument_name, is_mutable)) = match arguments[0] {
|
let (argument_type, (argument_name, is_mutable)) = match arguments[0] {
|
||||||
FnArg::Typed(pat_type) => (
|
FnArg::Typed(pat_type) => (
|
||||||
pat_type.ty.to_token_stream(),
|
pat_type.ty.to_token_stream(),
|
||||||
match &*pat_type.pat {
|
match &*pat_type.pat {
|
||||||
Pat::Ident(ident) => {
|
Pat::Ident(ident) => {
|
||||||
assert!(
|
if !(ident.attrs.is_empty() && ident.by_ref.is_none() && ident.subpat.is_none())
|
||||||
ident.attrs.is_empty() && ident.by_ref.is_none() && ident.subpat.is_none(),
|
{
|
||||||
"#[agb::entry] must have signature [unsafe] fn (mut agb::Gba) -> !"
|
return token_stream_with_string_error(
|
||||||
|
input,
|
||||||
|
"#[agb::entry] must have signature [unsafe] fn (mut agb::Gba) -> !",
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
(ident.ident.clone(), ident.mutability.is_some())
|
(ident.ident.clone(), ident.mutability.is_some())
|
||||||
}
|
}
|
||||||
_ => panic!("Expected first argument to #[agb::entry] to be a basic identifier"),
|
_ => {
|
||||||
|
return token_stream_with_string_error(
|
||||||
|
input,
|
||||||
|
"Expected first argument to #[agb::entry] to be a basic identifier",
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_ => panic!("Expected first argument to #[agb::entry] to not be self"),
|
_ => {
|
||||||
|
return token_stream_with_string_error(
|
||||||
|
input,
|
||||||
|
"Expected first argument to #[agb::entry] to not be self",
|
||||||
|
)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(
|
if args.to_string() != "" {
|
||||||
args.to_string() == "",
|
return token_stream_with_string_error(input, "Must pass no args to #[agb::entry] macro");
|
||||||
"Must pass no args to #[agb::entry] macro"
|
}
|
||||||
);
|
|
||||||
|
|
||||||
let fn_name = hashed_ident(&f);
|
let fn_name = hashed_ident(&f);
|
||||||
|
|
||||||
|
@ -70,10 +96,9 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(
|
if !argument_type.to_string().ends_with("Gba") {
|
||||||
argument_type.to_string().ends_with("Gba"),
|
return token_stream_with_string_error(input, "Expected first argument to have type 'Gba'");
|
||||||
"Expected first argument to have type 'Gba'"
|
}
|
||||||
);
|
|
||||||
|
|
||||||
quote!(
|
quote!(
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
|
|
Loading…
Reference in a new issue