To distinguish Nones, we first need to distinguish Options. And this is little hard.
There are two ways to do that: syntactically or via the type system. Syntactically means that the macro will identify fields that have the type Option<T> (i.e. Option < type >). However, this requires a TT munching, will force you to parse the ty fragement type yourself (you cannot rely on it because you'll not be able to inspect into its composed tokens later), and as a bonus won't work if users will type-alias Option.
The type system approach is more robust. The idea is to create some trait, implemented for any type, with a method that for Option<T> will return 1 iff it is Some and for other types will always return 1. This sounds like a perfect use case for specialization (playground):
#![feature(min_specialization)]
pub trait OneIfSome {
fn one_if_some(&self) -> usize;
}
impl<T> OneIfSome for T {
default fn one_if_some(&self) -> usize { 1 }
}
impl<T> OneIfSome for Option<T> {
fn one_if_some(&self) -> usize { self.is_some() as usize }
}
macro_rules! generate {
($name:ident {$($field:ident : $t:ty),+}) => {
struct $name { $($field: $t),+ }
impl $name {
fn field_count(&self) -> usize {
generate!(@count self $($field,)*)
}
}
};
(@count $self:ident $first_field:ident, $($rest:ident,)*) => {
$crate::OneIfSome::one_if_some(&$self.$first_field)
+ generate!(@count $self $($rest,)*)
};
(@count $self:ident) => { 0 };
}
Unfortunately, specialization is unstable, and doesn't even have a clear path towards stabilization. Can we replace it with something stable?
And it turns out, yes! @dtolnay invented a technique called "autoref specialization" that is intended exactly for cases like that (specialization inside a macro). I won't explain how it works here (the linked article is a great read), but here is how you can apply it to your case (playground):
pub trait OneIfSomeGeneral {
fn one_if_some(&self) -> usize;
}
impl<T> OneIfSomeGeneral for &'_ T {
fn one_if_some(&self) -> usize { 1 }
}
pub trait OneIfSomeOption {
fn one_if_some(&self) -> usize;
}
impl<T> OneIfSomeOption for Option<T> {
fn one_if_some(&self) -> usize { self.is_some() as usize }
}
macro_rules! generate {
($name:ident {$($field:ident : $t:ty),+}) => {
struct $name { $($field: $t),+ }
impl $name {
fn field_count(&self) -> usize {
#[allow(unused_imports)]
use $crate::{OneIfSomeGeneral, OneIfSomeOption};
generate!(@count self $($field,)*)
}
}
};
(@count $self:ident $first_field:ident, $($rest:ident,)*) => {
(&$self.$first_field).one_if_some()
+ generate!(@count $self $($rest,)*)
};
(@count $self:ident) => { 0 };
}
(Note: There is a problem with the above code: if the field type has a method named one_if_some(), it will take priority. So choose an unambiguous name (__my_generate_macro__one_if_some(), perhaps?)).