safety-guard 0.1.8

#[safety] attribute to generate a corresponding doc entry and a debug assertion if a constraint is specified.
Documentation
//! Provides a `#[safety]` attribute for generating a corresponding section in the documentation
//! and, if provided, checks for a constraint in debug builds.

extern crate proc_macro;

mod safety;

use proc_macro::TokenStream;
use quote::quote;
use syn::{fold::Fold, parse_macro_input};

/// Adds a `# Safety` section to the documentation of the function and tests the given constraints
/// in debug builds.
///
/// The attribute has four different forms:
/// - `#[safety("Description")]`: Only the description is added to the documentation
/// - `#[safety(assert(constraint), "Description")]`: `constraint` must evaluate to `true`.
/// - `#[safety(eq(lhs, rhs), "Description")]`: `lhs` and `rhs` needs to be equal
/// - `#[safety(ne(lhs, rhs), "Description")]`: `lhs` and `rhs` must not be equal
///
/// A function with a `#[safety]` attribute must be marked as `unsafe`. Otherwise a compile error
/// is generated.
///
/// If `# Safety` already exists in the documentation, the heading is not added.
///
/// # Examples
///
/// ```
/// use safety_guard::safety;
///
/// #[safety(assert(lhs.checked_add(rhs).is_some()), "`lhs` + `rhs` must not overflow")]
/// unsafe fn add_unchecked(lhs: usize, rhs: usize) -> usize {
/// 	lhs + rhs
/// }
/// ```
///
/// generates
///
/// ```
/// /// # Safety
/// /// - `lhs` + `rhs` must not overflow
/// unsafe fn add_unchecked(lhs: usize, rhs: usize) -> usize {
/// 	debug_assert!(lhs.checked_add(rhs).is_some(), "`lhs` + `rhs` must not overflow");
/// 	lhs + rhs
/// }
/// ```
///
/// Without a constraint, only the documentation is added:
///
/// ```
/// use safety_guard::safety;
///
/// #[safety("`hash` must correspond to the `string`s hash value")]
/// unsafe fn add_string_with_hash(string: &str, hash: u64) -> u64 {
/// 	# unimplemented!()
/// 	// ...
/// }
/// ```
///
/// generates
///
/// ```
/// /// # Safety
/// /// - `hash` must correspond to the `string`s hash value
/// unsafe fn add_string_with_hash(string: &str, hash: u64) -> u64 {
/// 	# unimplemented!()
/// 	// ...
/// }
/// ```
///
/// It is also possible to use multiple `#[safety]` attributes:
///
/// ```
/// # use core::alloc::Layout;
/// use safety_guard::safety;
///
///	#[safety(eq(ptr as usize % layout.align(), 0), "`layout` must *fit* the `ptr`")]
///	#[safety(assert(new_size > 0), "`new_size` must be greater than zero")]
///	#[safety(
///		"`new_size`, when rounded up to the nearest multiple of `layout.align()`, must not \
///		 overflow (i.e., the rounded value must be less than `usize::MAX`)."
///	)]
///	unsafe fn realloc(
///		ptr: *mut u8,
///		layout: Layout,
///		new_size: usize,
///	) -> *mut u8 {
/// 	# unimplemented!()
/// 	// ...
/// }
/// ```
///
/// However, the documentation is generated in reversed order:
///
///
/// ```
/// # use core::alloc::Layout;
/// /// # Safety
/// /// - `new_size`, when rounded up to the nearest multiple of `layout.align()`, must not
/// ///   overflow (i.e., the rounded value must be less than `usize::MAX`).
/// /// - `new_size` must be greater than zero
/// /// - `layout` must *fit* the `ptr`
///	unsafe fn realloc(
///		ptr: *mut u8,
///		layout: Layout,
///		new_size: usize,
///	) -> *mut u8 {
/// 	debug_assert!(new_size > 0, "`new_size` must be greater than zero");
/// 	debug_assert_eq!(ptr as usize % layout.align(), 0, "`layout` must *fit* the `ptr`");
/// 	# unimplemented!()
/// 	// ...
/// }
/// ```
#[proc_macro_attribute]
pub fn safety(args: TokenStream, input: TokenStream) -> TokenStream {
	let input = parse_macro_input!(input);
	let mut args = parse_macro_input!(args as safety::Args);
	let output = args.fold_item_fn(input);
	TokenStream::from(quote!(#output))
}