use std::hash::Hash; use proc_macro2::Span; use syn::parse::{Parse, ParseStream}; use syn::{Ident, Lit, LitBool, Token}; use crate::util::error::diagnostic_error_enum; #[derive(Debug, Clone)] pub struct MacroFlag { pub name: Ident, pub value: MacroFlagValue, } impl MacroFlag { pub fn new_off(flag: &str) -> Self { Self { name: Ident::new(flag, Span::call_site()), value: MacroFlagValue::Literal(Lit::Bool(LitBool::new( false, Span::call_site(), ))), } } pub fn name(&self) -> &Ident { &self.name } pub fn get_bool(&self) -> Result { if let MacroFlagValue::Literal(Lit::Bool(lit_bool)) = &self.value { return Ok(lit_bool.value); } Err(MacroFlagError::UnexpectedValueKind { expected: "boolean literal", value_span: self.value.span(), }) } pub fn get_ident(&self) -> Result { if let MacroFlagValue::Identifier(ident) = &self.value { return Ok(ident.clone()); } Err(MacroFlagError::UnexpectedValueKind { expected: "identifier", value_span: self.value.span(), }) } } impl Parse for MacroFlag { fn parse(input: ParseStream) -> syn::Result { let name = input.parse::()?; input.parse::()?; let value: MacroFlagValue = input.parse()?; Ok(Self { name, value }) } } impl PartialEq for MacroFlag { fn eq(&self, other: &Self) -> bool { self.name == other.name } } impl Eq for MacroFlag {} impl Hash for MacroFlag { fn hash(&self, state: &mut H) { self.name.hash(state); } } diagnostic_error_enum! { pub enum MacroFlagError { #[error("Expected a {expected}"), span = value_span] UnexpectedValueKind { expected: &'static str, value_span: Span }, } } #[derive(Debug, Clone)] pub enum MacroFlagValue { Literal(Lit), Identifier(Ident), } impl MacroFlagValue { fn span(&self) -> Span { match self { Self::Literal(lit) => lit.span(), Self::Identifier(ident) => ident.span(), } } } impl Parse for MacroFlagValue { fn parse(input: ParseStream) -> syn::Result { if let Ok(lit) = input.parse::() { return Ok(Self::Literal(lit)); }; input.parse::().map(Self::Identifier).map_err(|err| { syn::Error::new(err.span(), "Expected a literal or a identifier") }) } } #[cfg(test)] mod tests { use proc_macro2::Span; use quote::{format_ident, quote}; use syn::{parse2, LitInt, LitStr}; use super::*; #[test] fn parse_macro_flag_literal_works() { assert_eq!( parse2::(quote! { more = true }) .expect("Expected Ok"), MacroFlag { name: format_ident!("more"), value: MacroFlagValue::Literal(Lit::Bool(LitBool::new( true, Span::call_site() ))) } ); assert_eq!( parse2::(quote! { guitarist = "John Norum" }) .expect("Expected Ok"), MacroFlag { name: format_ident!("guitarist"), value: MacroFlagValue::Literal(Lit::Str(LitStr::new( "John Norum", Span::call_site() ))) } ); } #[test] fn parse_macro_flag_identifier_works() { assert_eq!( parse2::(quote! { error = FooError }) .expect("Expected Ok"), MacroFlag { name: format_ident!("error"), value: MacroFlagValue::Identifier(Ident::new( "FooError", Span::call_site() )) } ); } #[test] fn get_bool_works() { assert!( // Formatting is weird without this comment MacroFlag { name: format_ident!("rocked_the_night"), value: MacroFlagValue::Literal(Lit::Bool(LitBool { value: true, span: Span::call_site() })) } .get_bool() .expect("Expected Ok") ); assert!( // Formatting is weird without this comment MacroFlag { name: format_ident!("vocalist"), value: MacroFlagValue::Literal(Lit::Str(LitStr::new( "Joey Tempest", Span::call_site() ))) } .get_bool() .is_err() ); } #[test] fn get_ident_works() { assert_eq!( MacroFlag { name: format_ident!("formed_in"), value: MacroFlagValue::Identifier(format_ident!("upplands_vasby")) } .get_ident() .expect("Expected Ok"), "upplands_vasby" ); assert!( // Formatting is weird without this comment MacroFlag { name: format_ident!("formed"), value: MacroFlagValue::Literal(Lit::Int(LitInt::new( "1979", Span::call_site() ))) } .get_ident() .is_err() ); } #[test] fn parse_with_invalid_name_fails() { assert!( // Formatting is weird without this comment parse2::(quote! { 10 = false }) .is_err() ); } #[test] fn parse_with_invalid_value_fails() { assert!( // Formatting is weird without this comment parse2::(quote! { years_active = ["1979-1992", "1999", "2003–present"] }) .is_err() ); } }