Improve #[entry] macro

This commit is contained in:
BlueTheDuck 2024-08-06 15:19:56 -03:00
parent dd5ae49909
commit 4b58ae07ed
No known key found for this signature in database
GPG Key ID: 256464D859A672F3
3 changed files with 115 additions and 35 deletions

View File

@ -1,21 +1,109 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{parse_macro_input, ItemFn, Meta, MetaNameValue, ReturnType, Type}; use syn::{
parse_macro_input, spanned::Spanned, Attribute, FnArg, ItemFn, Meta, MetaNameValue, PatType,
ReturnType, Type, Visibility,
};
/// Use it to mark the ROM entry point. /// Allows only `fn(Hw) -> !`
fn check_sig(signature: &syn::Signature) -> Result<(), syn::Error> {
let span = signature.span();
if let ReturnType::Type(_, ref t) = signature.output {
match **t {
Type::Never(_) => {}
Type::Infer(_) => {
return Err(syn::Error::new(
span,
"Add `-> !`. To avoid ambiguity, please specify the return type as `!`",
));
}
_ => {
return Err(syn::Error::new(
span,
"Change to `-> !`. The entry point must be diverging. E.g. `fn() -> !`",
));
}
}
} else {
return Err(syn::Error::new(
span,
"Add `-> !`. To avoid ambiguity, please specify the return type as `!`.",
));
}
if signature.inputs.len() != 1 {
return Err(syn::Error::new(
span,
"Incorrect arguments. The entry point must take exactly one argument of type `Hw`",
));
} else {
let arg = signature.inputs.first().unwrap();
if let FnArg::Typed(PatType { ty, .. }) = arg {
if let Type::Path(ref path) = **ty {
if path.path.segments.last().unwrap().ident != "Hw" {
return Err(syn::Error::new(
arg.span(),
"Change type. The entry point must take exactly one argument of type `Hw`",
));
}
} else {
return Err(syn::Error::new(
arg.span(),
"Change type. The entry point must take exactly one argument of type `Hw`",
));
}
} else {
return Err(syn::Error::new(
arg.span(),
"Change type. The entry point can not be a method.",
));
}
}
Ok(())
}
/// Allow only `pub` visibility
fn check_vis(vis: &Visibility) -> Result<(), syn::Error> {
if let Visibility::Public(..) = vis {
Ok(())
} else {
Err(syn::Error::new(
vis.span(),
"Add `pub`. The entry point must be public and without restriction.",
))
}
}
/// Forbids `#[no_mangle]` and `#[link_name = ...]`
fn check_attr(attrs: &Vec<Attribute>) -> Result<(), syn::Error> {
for attr in attrs {
match &attr.meta {
Meta::NameValue(MetaNameValue { path, .. }) if path.is_ident("export_name") => {
return Err(syn::Error::new(path.span(), "Remove this `link_name`. The entry point requires a specific symbol name that it's set by the `entry` attribute."))
}
Meta::Path(path) if path.is_ident("no_mangle") => {
return Err(syn::Error::new(path.span(), "Remove this `no_mangle`. The entry point requires a specific symbol name that it's set by the `entry` attribute."))
}
_ => {}
}
}
Ok(())
}
/// Use it to mark the entry point of your program.
/// ///
/// Example: /// # Example:
/// ```rust,no_run /// ```rust,no_run
/// #![no_main] /// #![no_main]
/// ///
/// #[macro_use] /// #[macro_use]
/// extern crate nds: /// extern crate nds_proc_macros:
/// ///
/// #[entry] /// #[entry]
/// fn main(mut hw: nds::Hw) -> ! { /// pub fn main(_: nds::Hw) -> ! {
/// loop { /// loop { }
/// nds::interrupts::swi_wait_for_v_blank();
/// }
/// } /// }
/// ``` /// ```
#[proc_macro_attribute] #[proc_macro_attribute]
@ -24,37 +112,24 @@ pub fn entry(_: TokenStream, input: TokenStream) -> TokenStream {
// Check the signature is correct: // Check the signature is correct:
// - Must be `fn() -> !` // - Must be `fn() -> !`
// - Must be `pub`
// - Must have 1 argument // - Must have 1 argument
// - Must not have `link_name` or `no_mangle` attributes // - Must be `pub`
// - Must not have `export_name` or `no_mangle` attributes
if let ReturnType::Type(_, t) = &input.sig.output { if let Err(e) = check_sig(&input.sig) {
assert!( return e.to_compile_error().into();
matches!(**t, Type::Never(_)),
"The function must return `!`"
);
} else {
panic!("The function must return `!`");
} }
assert!(matches!(input.vis, syn::Visibility::Public(_))); if let Err(e) = check_vis(&input.vis) {
return e.to_compile_error().into();
}
assert_eq!(input.sig.inputs.len(), 1); if let Err(e) = check_attr(&input.attrs) {
return e.to_compile_error().into();
for attr in &input.attrs {
match &attr.meta {
Meta::NameValue(MetaNameValue { path, .. }) if path.is_ident("link_name") => {
panic!("The `link_name` attribute is not allowed here");
}
Meta::Path(path) if path.is_ident("no_mangle") => {
panic!("The `no_mangle` attribute is not allowed here");
}
_ => {}
}
} }
quote! { quote! {
#[link_name = "rust_main"] #[export_name = "__rust_user_main"]
#input #input
} }
.into() .into()

View File

@ -2,14 +2,19 @@
use nds_rs::Hw; use nds_rs::Hw;
/// Entry point called from the C runtime
///
/// # Safety
///
/// Must never be called by user code.
#[no_mangle] #[no_mangle]
pub extern "C" fn main() -> ! { pub unsafe extern "C" fn main() -> ! {
extern "Rust" { extern "Rust" {
#[link_name = "rust_main"] #[link_name = "__rust_user_main"]
fn main(hw: Hw) -> !; fn main(hw: Hw) -> !;
} }
let peripherals = Hw::take().unwrap(); let peripherals = Hw::take().unwrap_unchecked();
unsafe { unsafe {
main(peripherals); main(peripherals);
} }