fn generate_fmt_impl_for_enum()

in pyo3_special_method_derive_macro/src/str_repr.rs [249:434]


fn generate_fmt_impl_for_enum(
    data_enum: &syn::DataEnum,
    name: &Ident,
    is_repr: bool,
    string_formatter: Option<&Vec<Attribute>>,
    macro_name: &str,
) -> syn::Result<proc_macro2::TokenStream> {
    let variants = data_enum.variants.iter().collect::<Vec<_>>();
    let mut ident_formatter = quote! { #DEFAULT_ENUM_IDENT_FORMATTER };
    if let Some(attrs) = string_formatter {
        for attr in attrs {
            if attr.path().is_ident(ATTR_NAMESPACE_FORMATTER) {
                if let Some(formatter) = find_display_attribute(attr) {
                    ident_formatter = formatter;
                    break;
                }
                break;
            }
        }
    }

    let arms = variants.iter().map(|variant| {
        let ident = &variant.ident;
        let (to_skip, display_attr) = {
            let mut to_skip = false;
            let mut display_attr = None;

            for attr in &variant.attrs {
                let path = attr.path();
                if path.is_ident(ATTR_SKIP_NAMESPACE)  {
                    let _ = attr.parse_nested_meta(|meta| {
                        to_skip |= meta.path.is_ident(macro_name) || meta.path.is_ident(SKIP_ALL);
                        Ok(())
                    });
                    if path.is_ident(ATTR_NAMESPACE_FORMATTER) {
                        display_attr = Some(attr);
                    }
                }
            }

            (to_skip, display_attr)
        };

        let mut variant_fmt = quote! { #DEFAULT_ELEMENT_FORMATTER };
        if let Some(display_attr) = display_attr {
            if let Some(formatter) = find_display_attribute(display_attr) {
                variant_fmt = formatter;
            }
        }

        // If {} is not in ident_fmt, we must not format ident.
        // If {} is not in variant_fmt, we don't use stringify! either
        match &variant.fields {
            Fields::Unit => {
                let formatters = variant_fmt.to_string().matches("{}").count()
                    - variant_fmt.to_string().matches("{{}}").count();
                let variant_formatter = if formatters == 1 {
                    quote! { &format!(#variant_fmt, stringify!(#ident)) }
                } else if formatters == 0 {
                    quote! { &format!(#variant_fmt) }
                } else {
                    return Err(syn::Error::new(data_enum.enum_token.span(), "Specify 1 (variant), or 0 formatters in the format string."));
                };

                Ok(if !to_skip {
                    quote! {
                        Self::#ident => repr += #variant_formatter,
                    }
                } else {
                    quote! {
                        Self::#ident => repr += "<variant skipped>",
                    }
                })
            }
            syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
                // Tuple variant with one field
                // TODO now that we have AutoDisplay we want this
                Ok(if !to_skip {
                    quote! { #name::#ident(ref single) => {#ident_formatter;} }
                } else {
                    quote! {
                        #ident => repr += "<variant skipped>",
                    }
                })
            }
            Fields::Named(fields) => {
                let mut field_names: Vec<(&Option<Ident>, String, usize)> = Vec::new();
                for field in &fields.named {
                    let display_attr = {
                        let mut display_attr = None;

                        for attr in &field.attrs {
                            let path = attr.path();
                            if path.is_ident(ATTR_NAMESPACE_FORMATTER) {
                                display_attr = Some(attr);
                            }
                        }

                        display_attr
                    };

                    let mut variant_fmt = quote! { #DEFAULT_ELEMENT_FORMATTER };
                    if let Some(display_attr) = display_attr {
                        if let Some(formatter) = find_display_attribute(display_attr) {
                            variant_fmt = formatter;
                        }
                    }

                    let formatters = variant_fmt.to_string().matches("{}").count()
                        - variant_fmt.to_string().matches("{{}}").count();
                    if formatters > 1 {
                        return Err(syn::Error::new(data_enum.enum_token.span(), "Specify 1 (variant), or 0 formatters in the format string."));
                    };
                    let formatter_str = variant_fmt.to_string();

                    field_names.push((&field.ident, formatter_str[1..formatter_str.len()-1].to_string(), formatters));
                }

                let mut format_string = "{}(".to_string();
                let formatter = if is_repr { quote! { fmt_debug } } else { quote! { fmt_display } };
                for (i, (name, formatter, _n_formatters)) in field_names.iter().enumerate() {
                    if i == 0 {
                        format_string = format!("{format_string}{}={}", name.as_ref().unwrap(), formatter);
                    } else {
                        format_string = format!("{format_string}, {}={}", name.as_ref().unwrap(), formatter);
                    }
                }
                format_string = format!("{format_string})");
                Ok(if !to_skip {
                    let mut names = Vec::new();
                    for (name, _, n_formatters) in field_names.clone() {
                        if n_formatters > 0 {
                            names.push(quote! { #name.#formatter() });
                        }
                    }
                    let mut new_field_names = Vec::new();
                    for (name, _, _) in field_names.clone() {
                        new_field_names.push(name);
                    }
                    quote! {
                        Self::#ident { #(#new_field_names),* } => repr += &format!(#format_string, stringify!(#ident), #(#names),*),
                    }
                } else {
                    let mut names = Vec::new();
                    for (name, _, _) in field_names.clone() {
                        names.push(quote! { #name });
                    }
                    quote! {
                        Self::#ident { #(#names),* } => {
                            let _ = (#(#names),*);
                            repr += "<variant skipped>";
                        }
                    }
                })
            }
            _ => {
                // Default case: stringify the variant name
                Ok(quote! {  &format!("{}", stringify!(#ident)); })
            }
        }
    }).collect::<syn::Result<Vec<_>>>()?;

    // Handle any escaped {}
    let formatters = ident_formatter.to_string().matches("{}").count()
        - ident_formatter.to_string().matches("{{}}").count();
    let ident_formatter = if formatters == 2 {
        quote! { format!(#ident_formatter, stringify!(#name), repr) }
    } else if formatters == 1 {
        quote! { format!(#ident_formatter, stringify!(#name)) }
    } else if formatters == 0 {
        quote! { format!(#ident_formatter) }
    } else {
        return Err(syn::Error::new(
            data_enum.enum_token.span(),
            "Specify 2 (name, repr), 1 (name), or 0 formatters in the format string.",
        ));
    };

    Ok(quote! {
        let mut repr = "".to_string();
        match self {
            #(#arms)*
        }
        let repr = #ident_formatter;
    })
}