ot-tools-io-derive 0.4.2

Derive proc macros used in the `ot-tools-io` library crate.
Documentation
/*
SPDX-License-Identifier: GPL-3.0-or-later
Copyright © 2024 Mike Robeson [dijksterhuis]
*/

//! Derive macros for boilerplate trait implementation in the `ot_tools_io`
//! library crate

#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;

extern crate proc_macro;

use proc_macro::TokenStream;
use std::str::FromStr;
use syn::DeriveInput;

/// Adds `std::error::Error` to a type
#[proc_macro_derive(WithStdError)]
pub fn error_with_std_error(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // get the name of the type we want to implement the trait for
    let name = &input.ident;

    let expanded = quote! {
        impl std::error::Error for #name {}
    };
    TokenStream::from(expanded)
}

/// Adds the default implementation of `ot_tools_io::IsDefault` to a type
#[proc_macro_derive(IsDefaultCheck)]
pub fn check_is_default(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // get the name of the type we want to implement the trait for
    let name = &input.ident;

    let expanded = quote! {
        impl crate::IsDefault for #name {}
    };
    TokenStream::from(expanded)
}

/// Adds the `ot_tools_io::CheckIntegrity` trait to a type -- adding the `check_integrity()` method
/// for running header, file version and checksum validation
#[proc_macro_derive(IntegrityChecks)]
pub fn check_integrity_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // get the name of the type we want to implement the trait for
    let name = &input.ident;

    let expanded = quote! {
        impl crate::CheckFileIntegrity for #name {}
    };
    TokenStream::from(expanded)
}

/// Macro to create a standard `[Self; N]` implementation of `ot_tools_io::DefaultsArray` on a type,
/// i.e. a standard array filled with an inferred volume of defaults instances based on type hints
#[proc_macro_derive(ArrayDefaults)]
pub fn defaults_as_array_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // get the name of the type we want to implement the trait for
    let name = &input.ident;

    let expanded = quote! {
        impl<const N: usize> Defaults<[Self; N]> for #name {
            fn defaults() -> [Self; N] where Self: Default {
                from_fn(|_| Self::default())
            }
        }
    };
    TokenStream::from(expanded)
}

/// Macro to create a standard `Box<serde_big_array::Array<Self, N>>` implementation of
/// `ot_tools_io::DefaultsArray` on a type,
/// i.e. a non-standard array filled with an inferred volume of defaults instances based on type
/// hints
/// Requires an existing implementation of `Defaults<[Self; N]>` on the type.
#[proc_macro_derive(BoxedBigArrayDefaults)]
pub fn defaults_as_array_boxed_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // get the name of the type we want to implement the trait for
    let name = &input.ident;

    let expanded = quote! {
        impl<const N: usize> Defaults<Box<Array<Self, N>>> for #name {
            fn defaults() -> Box<Array<Self, N>> where Self: Defaults<[Self; N]> {
                Box::new(Array(
                    <Self as Defaults<[Self; N]>>::defaults()
                ))
            }
        }
    };
    TokenStream::from(expanded)
}

/// Macro that expands an existing `impl TryFrom<u32> for SettingEnum` to account for all lower
/// resolution unsigned type variants (includes borrow variants too).
///
/// Note this implementation is quite simple and hacky, doing string replacement on the original
/// `TryFrom<u32>` to generate additional trait implementations
///
/// ```rust
/// use ot_tools_io_derive::enum_try_from_u32_to_unsigned_types;
///
/// #[derive(Debug, PartialEq)]
/// enum Something { A = 0, B = 1, C = 2 }
///
/// #[derive(Debug)]
/// struct SomethingError;
/// // .. implement std::fmt::Display and stf::error::Error
/// # use std::fmt;
/// # impl fmt::Display for SomethingError {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// #         f.write_str("something error")
/// #     }
/// # }
/// # impl std::error::Error for SomethingError {}
///
/// #[enum_try_from_u32_to_unsigned_types]
/// impl TryFrom<u32> for Something {
///     type Error = SomethingError;
///     fn try_from(value: u32) -> Result<Self, Self::Error> {
///         match value {
///             0 => Ok(Self::A),
///             1 => Ok(Self::B),
///             2 => Ok(Self::C),
///             _ => Err(SomethingError),
///         }
///     }
/// }
///
/// assert_eq!(Something::try_from(0_u8).unwrap(), Something::A);
/// assert_eq!(Something::try_from(1_u8).unwrap(), Something::B);
/// assert_eq!(Something::try_from(2_u8).unwrap(), Something::C);
/// assert_eq!(Something::try_from(3_u8).unwrap_err().to_string(), "something error".to_string());
/// ```
#[proc_macro_attribute]
pub fn enum_try_from_u32_to_unsigned_types(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut s = item.to_string();
    for t in ["&u32", "u16", "&u16", "u8", "&u8"] {
        s += &*item.to_string().replace("u32", t);
        s += "\n";
    }

    TokenStream::from_str(&s).unwrap()
}

/// Same as [`macro@enum_try_from_u32_to_unsigned_types`], but for signed types (32-bit and below).
/// ```rust
/// use ot_tools_io_derive::enum_try_from_i32_to_signed_types;
///
/// #[derive(Debug, PartialEq)]
/// enum Something { A = 0, B = 1, C = 2 }
///
/// #[derive(Debug)]
/// struct SomethingError;
/// // .. implement std::fmt::Display and stf::error::Error
/// # use std::fmt;
/// # impl fmt::Display for SomethingError {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// #         f.write_str("something error")
/// #     }
/// # }
/// # impl std::error::Error for SomethingError {}
///
/// #[enum_try_from_i32_to_signed_types]
/// impl TryFrom<i32> for Something {
///     type Error = SomethingError;
///     fn try_from(value: i32) -> Result<Self, Self::Error> {
///         match value {
///             0 => Ok(Self::A),
///             1 => Ok(Self::B),
///             2 => Ok(Self::C),
///             _ => Err(SomethingError),
///         }
///     }
/// }
///
/// assert_eq!(Something::try_from(0_i8).unwrap(), Something::A);
/// assert_eq!(Something::try_from(1_i8).unwrap(), Something::B);
/// assert_eq!(Something::try_from(2_i8).unwrap(), Something::C);
/// assert_eq!(Something::try_from(3_i8).unwrap_err().to_string(), "something error".to_string());
/// ```
#[proc_macro_attribute]
pub fn enum_try_from_i32_to_signed_types(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut s = item.to_string();
    for t in ["&i32", "i16", "&i16", "i8", "&i8"] {
        s += &*item.to_string().replace("i32", t);
        s += "\n";
    }
    TokenStream::from_str(&s).unwrap()
}

/// Adds implementations for `Into<u8>`, `Into<u16>` and `Into<u32>`.
/// ```rust
/// use ot_tools_io_derive::IntoUnsigneds;
///
/// #[derive(IntoUnsigneds)]
/// enum Something { A = 0, B = 1, C = 2 }
///
/// let u_8: u8 = Something::A.into();
/// let u_16: u16 = Something::A.into();
/// let u_32: u32 = Something::A.into();
/// assert_eq!(u_8, 0_u8);
/// assert_eq!(u_16, 0_u16);
/// assert_eq!(u_32, 0_u32);
/// ```
#[proc_macro_derive(IntoUnsigneds)]
pub fn into_enum_unsigned_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // get the name of the type we want to implement the trait for
    let name = &input.ident;

    let expanded = quote! {
        impl Into<u8> for #name { fn into(self) -> u8 { self as u8 } }
        impl Into<u16> for #name { fn into(self) -> u16 { self as u16 } }
        impl Into<u32> for #name { fn into(self) -> u32 { self as u32 } }
    };
    TokenStream::from(expanded)
}

/// Adds implementations for `Into<i8>`, `Into<i16>` and `Into<i32>`.
/// ```rust
/// use ot_tools_io_derive::IntoSigneds;
///
/// #[derive(IntoSigneds)]
/// enum Something { A = 0, B = 1, C = 2 }
///
/// let i_8: i8 = Something::A.into();
/// let i_16: i16 = Something::A.into();
/// let i_32: i32 = Something::A.into();
/// assert_eq!(i_8, 0_i8);
/// assert_eq!(i_16, 0_i16);
/// assert_eq!(i_32, 0_i32);
/// ```
#[proc_macro_derive(IntoSigneds)]
pub fn into_enum_signed_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // get the name of the type we want to implement the trait for
    let name = &input.ident;

    let expanded = quote! {
        impl Into<i8> for #name { fn into(self) -> i8 { self as i8 } }
        impl Into<i16> for #name { fn into(self) -> i16 { self as i16 } }
        impl Into<i32> for #name { fn into(self) -> i32 { self as i32 } }
    };
    TokenStream::from(expanded)
}