Better error message for #[agb::entry] errors (#400)

Generates compile error calls when something goes wrong.

- [x] Changelog updated / no changelog update needed
This commit is contained in:
Gwilym Inzani 2023-03-23 23:24:06 +00:00 committed by GitHub
commit ee823418a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 59 additions and 33 deletions

View file

@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Alpha channel is now considered by `include_gfx!()` even when `transparent_colour` is absent. - Alpha channel is now considered by `include_gfx!()` even when `transparent_colour` is absent.
- 256 colour backgrounds are now correctly rendered (breaking change). - 256 colour backgrounds are now correctly rendered (breaking change).
- The `#[agb::entry]` macro now reports errors better.
## [0.13.0] - 2023/01/19 ## [0.13.0] - 2023/01/19

View file

@ -8,56 +8,82 @@ 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() && f.sig.generics.where_clause.is_none()
&& f.sig.generics.where_clause.is_none() && 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,
"#[agb::entry] must have signature [unsafe] fn (mut agb::Gba) -> !, but got {} arguments", &format!(
arguments.len(), "#[agb::entry] must have signature [unsafe] fn (mut agb::Gba) -> !, but got {} arguments",
); 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))]