blob: f3079b820776bc8eabcead37279bd627ae1f4ad7 [file]
// Copyright 2026 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
use proc_macro::TokenStream;
use quote::quote;
use syn::ItemFn;
#[proc_macro_attribute]
pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
match entry_impl(args.into(), input.into()) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
fn entry_impl(
args: proc_macro2::TokenStream,
input: proc_macro2::TokenStream,
) -> syn::Result<proc_macro2::TokenStream> {
if !args.is_empty() {
return Err(syn::Error::new(
proc_macro2::Span::call_site(),
"#[entry] macro does not take any arguments",
));
}
let entry_fn = syn::parse2::<ItemFn>(input)?;
validate_entry_signature(&entry_fn.sig)?;
let entry_fn_ident = &entry_fn.sig.ident;
let expanded = quote! {
#entry_fn
#[no_mangle]
pub extern "C" fn pw_boot_rust_entry(_arg: *mut ::core::ffi::c_void) {
#entry_fn_ident();
}
};
Ok(expanded)
}
fn validate_entry_signature(sig: &syn::Signature) -> syn::Result<()> {
let is_valid = sig.constness.is_none()
&& sig.asyncness.is_none()
&& sig.unsafety.is_none()
&& sig.abi.is_none()
&& sig.generics.params.is_empty()
&& sig.generics.where_clause.is_none()
&& sig.inputs.is_empty()
&& sig.variadic.is_none()
&& matches!(sig.output, syn::ReturnType::Type(_, ref ty) if matches!(ty.as_ref(), syn::Type::Never(_)));
if !is_valid {
return Err(syn::Error::new_spanned(
sig,
"#[entry] function must have the signature `fn f() -> !`",
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use quote::quote;
use super::*;
#[test]
fn test_valid_entry() {
let args = quote! {};
let input = quote! {
fn main() -> ! {
loop {}
}
};
entry_impl(args, input).unwrap();
}
#[test]
fn test_args_not_empty() {
let args = quote! { foo };
let input = quote! {
fn main() -> ! {
loop {}
}
};
let err = entry_impl(args, input).unwrap_err();
assert_eq!(
err.to_string(),
"#[entry] macro does not take any arguments"
);
}
#[test]
fn test_invalid_signature() {
let test_cases = vec![
quote! { const fn main() -> ! { loop {} } },
quote! { async fn main() -> ! { loop {} } },
quote! { unsafe fn main() -> ! { loop {} } },
quote! { extern "C" fn main() -> ! { loop {} } },
quote! { fn main<T>() -> ! { loop {} } },
quote! { fn main(x: i32) -> ! { loop {} } },
quote! { fn main() {} },
quote! { fn main() -> () {} },
quote! { fn main() -> i32 { 0 } },
];
for input in test_cases {
let args = quote! {};
let err = entry_impl(args, input).unwrap_err();
assert_eq!(
err.to_string(),
"#[entry] function must have the signature `fn f() -> !`"
);
}
}
}