nan-default 0.0.2

Derive macro for defaulting structs with floats to NAN
Documentation
//! # NaN Default Derive Macro
//!
//! A macro which lets you create structs whose floating-point members
//! (f32, f64) are initialized as `NAN` rather than `0.0` by default.
//! Struct members which are not floats get `Default::default()`
//! assignments, and partial defaulting via `..Default::default()` is
//! also supported.
//!
//! This can be convenient when using the special NAN state of a
//! floating point number to designate it as invalid or uninitialized,
//! rather than wrapping it in an `Option` which takes some extra
//! space — especially if you have many of them.  Of course, any
//! operations on such variables need to check for this state just as
//! they would need to check for the `Some` variant of an `Option`
//! (albeit without idiomatic if-let statements, mapping, and so on).
//! Depending on the application, `is_nan()` or `!is_finite()` may be
//! appropriate functions to perform the check.
//!
//! # Example
//! ```
//! use nan_default::NanDefault;
//!
//! #[derive(NanDefault)]
//! struct Data {
//!     a: f32,
//!     b: [f64; 10],
//!     c: i32,
//! }
//!
//! fn basic() {
//!     let data = Data::default();
//!     assert!(data.a.is_nan());
//!     assert!(data.b[9].is_nan());
//!     assert_eq!(data.c, 0);
//! }
//! ```
//!

use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse::Error, parse_macro_input, DeriveInput, Type};

fn get_defaults(body: &syn::Fields) -> Result<TokenStream, Error> {
    match body {
        syn::Fields::Named(fields) => {
            let defaults = fields
                .named
                .iter()
                .map(|field| {
                    let (ty, len) = match &field.ty {
                        Type::Path(v) => (v, None),
                        Type::Array(a) => match *a.elem {
                            Type::Path(ref v) => {
                                (v, Some(a.len.to_token_stream()))
                            }
                            _ => unimplemented!(),
                        },
                        _ => unimplemented!(),
                    };

                    let name = field.ident.as_ref().unwrap().to_string();
                    let ty = &ty.path.segments[0].ident.to_string();
                    match ty.as_str() {
                        "f32" | "f64" => Ok(match len {
                            None => format!("{name} : {ty}::NAN"),
                            Some(len) => {
                                format!("{name} : [{ty}::NAN; {}]", len)
                            }
                        }
                        .parse::<TokenStream>()?),

                        _ => Ok(format!("{name} : Default::default()")
                            .parse::<TokenStream>()?),
                    }
                })
                .collect::<Result<Vec<_>, Error>>()?;

            Ok(quote! {
                #( #defaults ),*
            })
        }
        syn::Fields::Unnamed(ref _fields) => Ok(quote! {}),
        &syn::Fields::Unit => Ok(quote! {}),
    }
}

fn impl_nan_derive(input: &DeriveInput) -> Result<TokenStream, Error> {
    let name = &input.ident;

    let (impl_generics, ty_generics, where_clause) =
        input.generics.split_for_impl();

    match input.data {
        syn::Data::Struct(ref body) => {
            let defaults = get_defaults(&body.fields)?;

            let output = quote! {
                #[automatically_derived]
                impl #impl_generics Default for #name #ty_generics #where_clause {
                    fn default() -> Self {
                        Self {
                            #defaults
                        }
                    }
                }
            };
            Ok(output)
        }
        _ => Err(Error::new(name.span(), "Unsupported type")),
    }
}

#[proc_macro_derive(NanDefault)]
pub fn derive_nan_default(
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    match impl_nan_derive(&input) {
        Ok(output) => output.into(),
        Err(error) => error.to_compile_error().into(),
    }
}