derive_deftly_macros/
boolean.rs

1//! Handling of boolean evaluation (conditions)
2
3use super::framework::*;
4
5/// Implementor of [`SubstParseContext`] for booleans
6///
7/// No values of this type are ever created -
8/// it's just a generic parameter, used to select the associated
9/// marker types (and their constructor methods( in SubstParseContext.
10///
11/// So it can be uninhabited.
12#[derive(Debug)]
13pub struct BooleanContext(Void);
14
15pub struct Found;
16
17fn is_found(r: Result<(), Found>) -> bool {
18    r.is_err()
19}
20
21impl SubstParseContext for BooleanContext {
22    type NotInPaste = ();
23    type NotInConcat = ();
24    type NotInBool = Void;
25    type ConcatOnly = Void;
26    type BoolOnly = ();
27    const IS_BOOL: bool = true;
28    type DbgContent = Subst<BooleanContext>;
29
30    fn not_in_paste(_: &impl Spanned) -> syn::Result<()> {
31        Ok(())
32    }
33    fn not_in_concat(_: &impl Spanned) -> syn::Result<()> {
34        Ok(())
35    }
36    fn bool_only(_: &impl Spanned) -> syn::Result<()> {
37        Ok(())
38    }
39    fn meta_recog_usage(_: &meta::SubstMeta<Self>) -> meta::Usage {
40        meta::Usage::BOOL_ONLY
41    }
42
43    fn not_in_bool(span: &impl Spanned) -> syn::Result<Void> {
44        Err(span.error(
45            "derive-deftly construct is an expansion - not valid in a condition",
46        ))
47    }
48
49    type SpecialParseContext = ();
50
51    fn missing_keyword_arguments(kw_span: Span) -> syn::Result<Void> {
52        Err(kw_span.error("missing parameters to condition"))
53    }
54}
55
56impl Subst<BooleanContext> {
57    pub fn eval_bool<'c>(
58        &self,
59        gctx: GeneralContext<'c>,
60    ) -> syn::Result<bool> {
61        // eprintln!("@@@@@@@@@@@@@@@@@@@@ EVAL {:?}", self);
62        let kw_span = self.kw_span;
63
64        let ctx = gctx.full_ctx(kw_span);
65
66        let v_fields = || ctx?.variant(&kw_span).map(|v| &v.fields);
67        use syn::Fields as SF;
68
69        let expand = |x: &Template<_>| {
70            let mut out = TokenAccumulator::new();
71            x.expand(gctx, &mut out);
72            let out = out.tokens()?;
73            Ok::<TokenStream, syn::Error>(out)
74        };
75
76        let r = match &self.sd {
77            SD::is_enum(..) => ctx?.is_enum(),
78            SD::is_struct(..) => matches!(ctx?.top.data, syn::Data::Struct(_)),
79            SD::is_union(..) => matches!(ctx?.top.data, syn::Data::Union(_)),
80            SD::v_is_unit(..) => matches!(v_fields()?, SF::Unit),
81            SD::v_is_tuple(..) => matches!(v_fields()?, SF::Unnamed(..)),
82            SD::v_is_named(..) => matches!(v_fields()?, SF::Named(..)),
83            SD::tgens(_) => !ctx?.top.generics.params.is_empty(),
84
85            SD::Xmeta(sm) => {
86                let meta::SubstMeta {
87                    desig: meta::Desig { label, scope: _ },
88                    as_,
89                    default,
90                } = sm;
91                match default {
92                    None => {}
93                    Some((_, nb, _)) => void::unreachable(*nb),
94                };
95                use meta::SubstAs as mSA;
96                if let Some(as_) = as_ {
97                    match as_ {
98                        mSA::expr(nb, ..)
99                        | mSA::ident(nb, ..)
100                        | mSA::items(nb, ..)
101                        | mSA::path(nb)
102                        | mSA::str(nb)
103                        | mSA::token_stream(nb, ..)
104                        | mSA::ty(nb) => void::unreachable(*nb),
105                    }
106                };
107                is_found(label.search_eval_bool(sm.pmetas(ctx?, kw_span)?))
108            }
109
110            SD::is_empty(_, content) => expand(content)?.is_empty(),
111            SD::approx_equal(_, [a, b]) => {
112                tokens_cmpeq(expand(a)?, expand(b)?, kw_span)?
113                    == Equality::Equal
114            }
115
116            SD::UserDefined(name) => name.lookup_eval_bool(gctx)?,
117
118            SD::False(..) => false,
119            SD::True(..) => true,
120
121            SD::not(v, _) => !v.eval_bool(gctx)?,
122            SD::any(vs, _) => vs
123                .iter()
124                .find_map(|v| match v.eval_bool(gctx) {
125                    Ok(true) => Some(Ok(true)),
126                    Err(e) => Some(Err(e)),
127                    Ok(false) => None,
128                })
129                .unwrap_or(Ok(false))?,
130            SD::all(vs, _) => vs
131                .iter()
132                .find_map(|v| match v.eval_bool(gctx) {
133                    Ok(true) => None,
134                    Err(e) => Some(Err(e)),
135                    Ok(false) => Some(Ok(false)),
136                })
137                .unwrap_or(Ok(true))?,
138
139            SD::Vis(vis, _) => match vis.syn_vis(ctx?, kw_span)? {
140                syn::Visibility::Public(_) => true,
141                _ => false,
142            },
143
144            SD::dbg(ddr) => {
145                let r = ddr.content_parsed.eval_bool(gctx);
146                let () = &ddr.content_string;
147                let w = |s: fmt::Arguments| {
148                    eprintln!(
149                        "derive-deftly dbg condition {} evaluated to {}",
150                        ddr.display_heading(gctx),
151                        s,
152                    )
153                };
154                match &r {
155                    Ok(y) => w(format_args!("{:?}", y)),
156                    Err(e) => w(format_args!("error: {}", e)),
157                };
158                r?
159            }
160
161            // ## maint/check-keywords-documented NotInBool ##
162            SD::tname(not_in_bool)
163            | SD::ttype(not_in_bool)
164            | SD::tdeftype(not_in_bool)
165            | SD::vname(not_in_bool)
166            | SD::fname(not_in_bool)
167            | SD::ftype(not_in_bool)
168            | SD::vtype(_, _, not_in_bool)
169            | SD::tdefkwd(not_in_bool)
170            | SD::vindex(not_in_bool, ..)
171            | SD::findex(not_in_bool, ..)
172            | SD::tattrs(_, _, not_in_bool)
173            | SD::vattrs(_, _, not_in_bool)
174            | SD::fattrs(_, _, not_in_bool)
175            | SD::tdefgens(_, not_in_bool)
176            | SD::tgnames(_, not_in_bool)
177            | SD::twheres(_, not_in_bool)
178            | SD::vpat(_, _, not_in_bool)
179            | SD::fpatname(not_in_bool)
180            | SD::tdefvariants(_, _, not_in_bool)
181            | SD::fdefine(_, _, not_in_bool)
182            | SD::vdefbody(_, _, _, not_in_bool)
183            | SD::paste(_, not_in_bool)
184            | SD::paste_spanned(_, _, not_in_bool, ..)
185            | SD::ChangeCase(.., paste::ChangeCaseOkIn { not_in_bool, .. })
186            | SD::concat(_, not_in_bool, ..)
187            | SD::when(_, not_in_bool)
188            | SD::define(_, not_in_bool)
189            | SD::defcond(_, not_in_bool)
190            | SD::For(_, not_in_bool)
191            | SD::If(_, not_in_bool)
192            | SD::select1(_, not_in_bool)
193            | SD::ignore(_, not_in_bool)
194            | SD::error(_, not_in_bool)
195            | SD::require_beta(_, not_in_bool)
196            | SD::dbg_all_keywords(not_in_bool)
197            | SD::Crate(_, not_in_bool) => void::unreachable(*not_in_bool),
198        };
199        Ok(r)
200    }
201}
202
203impl DefinitionName {
204    fn lookup_eval_bool<'c>(
205        &self,
206        ctx: GeneralContext<'c>,
207    ) -> syn::Result<bool> {
208        let (def, ctx) = ctx.find_definition::<DefCondBody>(self)?
209            .ok_or_else(|| {
210                let mut error = self.error(format!(
211                    "user-defined condition `{}` not found",
212                    self,
213                ));
214                if let Some(def) = ctx.defs().defs
215                    .find_raw::<DefinitionBody>(self)
216                {
217                    // Condition syntax looks like fine tokens,
218                    // so the ${define } wouldn't spot this mistake.
219                    error.combine(
220                        def.name.error(
221"this user-defined expansion used as a condition (perhaps you meant ${defcond ?}"
222                        )
223                    );
224                }
225                error
226            })?;
227
228        def.body.eval_bool(ctx.as_ref())
229    }
230}