use std::collections::HashSet; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::{ parenthesized, parse::{Parse, ParseStream}, spanned::Spanned, Fields, Token, }; use crate::diagnostic::DiagnosticVariant; use crate::fmt::{self, Display}; pub struct Help { pub display: Display, } impl Parse for Help { fn parse(input: ParseStream) -> syn::Result { let ident = input.parse::()?; if ident == "help" { let la = input.lookahead1(); if la.peek(syn::token::Paren) { let content; parenthesized!(content in input); let fmt = content.parse()?; let args = if content.is_empty() { TokenStream::new() } else { content.parse::()?; fmt::parse_token_expr(&content, false)? }; let display = Display { fmt, args, has_bonus_display: false, }; Ok(Help { display }) } else { input.parse::()?; Ok(Help { display: Display { fmt: input.parse()?, args: TokenStream::new(), has_bonus_display: false, }, }) } } else { Err(syn::Error::new(ident.span(), "not a help")) } } } impl Help { pub(crate) fn gen_enum(variants: &[DiagnosticVariant]) -> Option { let help_pairs = variants .iter() .filter(|v| v.help.is_some()) .map( |DiagnosticVariant { ref ident, ref help, ref fields, .. }| { let mut display = help.as_ref().expect("already checked for Some").display.clone(); let member_idents = fields.iter().enumerate().map(|(i, field)| { field .ident .as_ref() .cloned() .unwrap_or_else(|| format_ident!("_{}", i)) }); let members: HashSet = fields.iter().enumerate().map(|(i, field)| { if let Some(ident) = field.ident.as_ref().cloned() { syn::Member::Named(ident) } else { syn::Member::Unnamed(syn::Index { index: i as u32, span: field.span() }) } }).collect(); display.expand_shorthand(&members); let Display { fmt, args, .. } = display; match fields { syn::Fields::Named(_) => { quote! { Self::#ident{ #(#member_idents),* } => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args))), } } syn::Fields::Unnamed(_) => { quote! { Self::#ident( #(#member_idents),* ) => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args))), } } syn::Fields::Unit => quote! { Self::#ident => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args))), }, } }, ) .collect::>(); if help_pairs.is_empty() { None } else { Some(quote! { fn help<'a>(&'a self) -> std::option::Option> { #[allow(unused_variables, deprecated)] match self { #(#help_pairs)* _ => None, } } }) } } pub(crate) fn gen_struct(&self, fields: &Fields) -> Option { let mut display = self.display.clone(); let members: HashSet = fields .iter() .enumerate() .map(|(i, field)| { if let Some(ident) = field.ident.as_ref().cloned() { syn::Member::Named(ident) } else { syn::Member::Unnamed(syn::Index { index: i as u32, span: field.span(), }) } }) .collect(); display.expand_shorthand(&members); let members = members.iter(); let Display { fmt, args, .. } = display; let fields_pat = match fields { Fields::Named(_) => quote! { let Self { #(#members),* } = self; }, Fields::Unnamed(_) => { let vars = members.map(|member| { if let syn::Member::Unnamed(member) = member { format_ident!("_{}", member) } else { unreachable!() } }); quote! { let Self(#(#vars),*) = self; } } Fields::Unit => quote! {}, }; Some(quote! { fn help<'a>(&'a self) -> std::option::Option> { #[allow(unused_variables, deprecated)] #fields_pat std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args))) } }) } }