//! Handling of boolean evaluation (conditions) use super::framework::*; /// Implementor of [`SubstParseContext`] for booleans /// /// No values of this type are ever created - /// it's just a generic parameter, used to select the associated /// marker types (and their constructor methods( in SubstParseContext. /// /// So it can be uninhabited. #[derive(Debug)] pub struct BooleanContext(Void); struct Found; fn is_found(r: Result<(), Found>) -> bool { r.is_err() } impl SubstParseContext for BooleanContext { type NotInPaste = (); type NotInBool = Void; type BoolOnly = (); fn not_in_paste(_: &impl Spanned) -> syn::Result<()> { Ok(()) } fn bool_only(_: &impl Spanned) -> syn::Result<()> { Ok(()) } fn not_in_bool(span: &impl Spanned) -> syn::Result { Err(span.error( "derive-adhoc construct is an expansion - not valid in a condition", )) } type SpecialParseContext = (); } impl Subst { pub fn eval_bool(&self, ctx: &Context) -> syn::Result { // eprintln!("@@@@@@@@@@@@@@@@@@@@ EVAL {:?}", self); let v_fields = || ctx.variant(&self.kw_span).map(|v| &v.fields); use syn::Fields as SF; let r = match &self.sd { SD::is_enum(..) => ctx.is_enum(), SD::is_struct(..) => matches!(ctx.top.data, syn::Data::Struct(_)), SD::is_union(..) => matches!(ctx.top.data, syn::Data::Union(_)), SD::v_is_unit(..) => matches!(v_fields()?, SF::Unit), SD::v_is_tuple(..) => matches!(v_fields()?, SF::Unnamed(..)), SD::v_is_named(..) => matches!(v_fields()?, SF::Named(..)), SD::xmeta(sm) => { let SubstMeta { path, as_, level: _, } = sm; use SubstMetaAs as SMA; if let Some(as_) = as_ { match as_ { SMA::str(nb) | SMA::tokens(nb, ..) | SMA::ty(nb) => { void::unreachable(*nb) } } }; is_found(path.search_eval_bool(sm.pmetas(ctx)?)) } SD::approx_equal(_, [a, b]) => { let s = |x: &Template<_>| { let mut out = TokenAccumulator::new(); x.expand(ctx, &mut out); let out = out.tokens()?; Ok::(out) }; tokens_cmp(s(a)?, s(b)?) == Ordering::Equal } SD::UserDefined(name) => name.lookup_eval_bool(ctx)?, SD::False(..) => false, SD::True(..) => true, SD::not(v, _) => !v.eval_bool(ctx)?, SD::any(vs, _) => vs .iter() .find_map(|v| match v.eval_bool(ctx) { Ok(true) => Some(Ok(true)), Err(e) => Some(Err(e)), Ok(false) => None, }) .unwrap_or(Ok(false))?, SD::all(vs, _) => vs .iter() .find_map(|v| match v.eval_bool(ctx) { Ok(true) => None, Err(e) => Some(Err(e)), Ok(false) => Some(Ok(false)), }) .unwrap_or(Ok(true))?, SD::Vis(vis, _) => match vis.syn_vis(ctx, self.kw_span)? { syn::Visibility::Public(_) => true, _ => false, }, // ## maint/check-keywords-documented NotInBool ## SD::tname(not_in_bool) | SD::ttype(not_in_bool) | SD::tdeftype(not_in_bool) | SD::vname(not_in_bool) | SD::fname(not_in_bool) | SD::ftype(not_in_bool) | SD::vtype(_, _, not_in_bool) | SD::tdefkwd(not_in_bool) | SD::tattrs(_, _, not_in_bool) | SD::vattrs(_, _, not_in_bool) | SD::fattrs(_, _, not_in_bool) | SD::tgens(_, not_in_bool) | SD::tdefgens(_, not_in_bool) | SD::tgnames(_, not_in_bool) | SD::twheres(_, not_in_bool) | SD::vpat(_, _, not_in_bool) | SD::fpatname(not_in_bool) | SD::tdefvariants(_, _, not_in_bool) | SD::fdefine(_, _, not_in_bool) | SD::vdefbody(_, _, _, not_in_bool) | SD::paste(_, not_in_bool) | SD::ChangeCase(_, _, not_in_bool) | SD::when(_, not_in_bool) | SD::define(_, not_in_bool) | SD::defcond(_, not_in_bool) | SD::For(_, not_in_bool) | SD::If(_, not_in_bool) | SD::select1(_, not_in_bool) | SD::dbg_all_keywords(not_in_bool) | SD::Crate(_, not_in_bool) => void::unreachable(*not_in_bool), }; Ok(r) } } impl DefinitionName { fn lookup_eval_bool(&self, ctx: &Context<'_>) -> syn::Result { let (def, ctx) = ctx.find_definition::(self)?.ok_or_else(|| { let mut error = self.error("user-defined condition not fund"); if let Some(def) = ctx.definitions.find_raw::(self) { // Condition syntax looks like fine tokens, // so the ${define } wouldn't spot this mistake. error.combine( def.name.error( "this user-defined expansion used as a condition (perhaps you meant ${defcond ?}" ) ); } error })?; def.body.eval_bool(&ctx) } } impl SubstMetaPath { fn search_eval_bool( &self, pmetas: &PreprocessedMetas, ) -> Result<(), Found> { self.search( pmetas, &mut |_av| /* got it! */ Err(Found), &mut |nearby| match nearby.kind { FNMNK::List => Err(Found), // there's more, that'll do FNMNK::Unit => Ok(()), FNMNK::Lit => Ok(()), }, ) } } /// Compares two `TokenStream`s for "equivalence" /// /// We intend that two `TokenStream`s count as "equivalent" /// if they mean the same thing to the compiler, /// modulo any differences in spans. /// /// We also disregard spacing. This is not 100% justifiable but /// I think there are no token sequences differing only in spacing /// which are *both* valid and which differ in meaning. /// /// ### Why ?! /// /// `< <` and `<<` demonstrate that it is not possible to provide /// a fully correct and coherent equality function on Rust tokens, /// without knowing the parsing context: /// /// In places where `<<` is a shift operator, `< <` is not legal. /// But in places where `<<` introduces two lots of generics, /// `<<` means the same. /// /// I think a function which treats `< <` and `<<` as equal is more useful /// than one that doesn't, because it will DTRT for types. // // Comparing for equality has to be done by steam. // And a lot of stringification. pub fn tokens_cmp(a: TokenStream, b: TokenStream) -> cmp::Ordering { use proc_macro2::Group; fn tt_cmp(a: TokenTree, b: TokenTree) -> Ordering { let discrim = |tt: &_| match tt { TT::Punct(_) => 0, TT::Literal(_) => 1, TT::Ident(_) => 2, TT::Group(_) => 3, }; discrim(&a).cmp(&discrim(&b)).then_with(|| match (a, b) { (TT::Group(a), TT::Group(b)) => group_cmp(a, b), (l, r) => l.to_string().cmp(&r.to_string()), }) } fn group_cmp(a: Group, b: Group) -> Ordering { let delim = |g: &Group| { proc_macro2::Group::new(g.delimiter(), TokenStream::new()) .to_string() }; delim(&a) .cmp(&delim(&b)) .then_with(|| tokens_cmp(a.stream(), b.stream())) } for (a, b) in izip!(a, b) { match tt_cmp(a, b) { Ordering::Equal => {} neq => return neq, } } return Ordering::Equal; }