use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, spanned::Spanned, }; pub(crate) enum MemberOrString { Member(syn::Member), String(syn::LitStr), } impl ToTokens for MemberOrString { fn to_tokens(&self, tokens: &mut TokenStream) { use MemberOrString::*; match self { Member(member) => member.to_tokens(tokens), String(string) => string.to_tokens(tokens), } } } impl Parse for MemberOrString { fn parse(input: ParseStream) -> syn::Result { let lookahead = input.lookahead1(); if lookahead.peek(syn::Ident) || lookahead.peek(syn::LitInt) { Ok(MemberOrString::Member(input.parse()?)) } else if lookahead.peek(syn::LitStr) { Ok(MemberOrString::String(input.parse()?)) } else { Err(syn::Error::new( input.span(), "Expected a string or a field reference.", )) } } } use crate::{ diagnostic::{DiagnosticConcreteArgs, DiagnosticDef}, forward::WhichFn, }; pub(crate) fn gen_all_variants_with( variants: &[DiagnosticDef], which_fn: WhichFn, mut f: impl FnMut(&syn::Ident, &syn::Fields, &DiagnosticConcreteArgs) -> Option, ) -> Option { let pairs = variants .iter() .filter_map(|def| { def.args .forward_or_override_enum(&def.ident, which_fn, |concrete| { f(&def.ident, &def.fields, concrete) }) }) .collect::>(); if pairs.is_empty() { return None; } let signature = which_fn.signature(); let catchall = which_fn.catchall_arm(); Some(quote! { #signature { #[allow(unused_variables, deprecated)] match self { #(#pairs)* #catchall } } }) } use crate::fmt::Display; use std::collections::HashSet; pub(crate) fn gen_unused_pat(fields: &syn::Fields) -> TokenStream { match fields { syn::Fields::Named(_) => quote! { { .. } }, syn::Fields::Unnamed(_) => quote! { ( .. ) }, syn::Fields::Unit => quote! {}, } } /// Goes in the slot `let Self #pat = self;` or `match self { Self #pat => ... /// }`. fn gen_fields_pat(fields: &syn::Fields) -> TokenStream { let member_idents = fields.iter().enumerate().map(|(i, field)| { field .ident .as_ref() .cloned() .unwrap_or_else(|| format_ident!("_{}", i)) }); match fields { syn::Fields::Named(_) => quote! { { #(#member_idents),* } }, syn::Fields::Unnamed(_) => quote! { ( #(#member_idents),* ) }, syn::Fields::Unit => quote! {}, } } /// The returned tokens go in the slot `let Self #pat = self;` or `match self { /// Self #pat => ... }`. The members can be passed to /// `Display::expand_shorthand[_cloned]`. pub(crate) fn display_pat_members(fields: &syn::Fields) -> (TokenStream, HashSet) { let pat = gen_fields_pat(fields); 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(); (pat, members) } impl Display { /// Returns `(fmt, args)` which must be passed to some kind of format macro /// without tokens in between, i.e. `format!(#fmt #args)`. pub(crate) fn expand_shorthand_cloned( &self, members: &HashSet, ) -> (syn::LitStr, TokenStream) { let mut display = self.clone(); display.expand_shorthand(members); let Display { fmt, args, .. } = display; (fmt, args) } }