vrl/compiler/expression/
op.rs

1use std::fmt;
2
3use crate::compiler::state::{TypeInfo, TypeState};
4use crate::compiler::{
5    Context, Expression, TypeDef,
6    expression::{self, Expr, Resolved},
7    parser::{Node, ast},
8    value::VrlValueArithmetic,
9};
10use crate::diagnostic::{DiagnosticMessage, Label, Note, Span, Urls};
11use crate::value::Value;
12
13#[derive(Clone, PartialEq)]
14pub struct Op {
15    pub(crate) lhs: Box<Expr>,
16    pub(crate) rhs: Box<Expr>,
17    pub(crate) opcode: ast::Opcode,
18}
19
20impl Op {
21    /// Creates a new `Op` expression.
22    ///
23    /// # Arguments
24    /// * `lhs` - The left-hand side expression.
25    /// * `opcode` - The operator to apply.
26    /// * `rhs` - The right-hand side expression.
27    /// * `state` - The current type state.
28    ///
29    /// # Returns
30    /// A `Result` containing the new `Op` expression or an error.
31    ///
32    /// # Errors
33    /// - `ChainedComparison`: If the left-hand side expression is already a comparison operation.
34    /// - `UnnecessaryCoalesce`: If the left-hand side expression is infallible and the operator is `Err`.
35    /// - `MergeNonObjects`: If the operator is `Merge` and either operand is not an object.
36    pub fn new(
37        lhs: Node<Expr>,
38        opcode: Node<ast::Opcode>,
39        rhs: Node<Expr>,
40        state: &TypeState,
41    ) -> Result<Self, Error> {
42        use ast::Opcode::{Eq, Ge, Gt, Le, Lt, Ne};
43
44        let mut state = state.clone();
45
46        let (op_span, opcode) = opcode.take();
47
48        let (lhs_span, lhs) = lhs.take();
49        let lhs_type_def = lhs.apply_type_info(&mut state);
50        let rhs_type_def = rhs.apply_type_info(&mut state);
51
52        let (rhs_span, rhs) = rhs.take();
53
54        if matches!(opcode, Eq | Ne | Lt | Le | Gt | Ge)
55            && let Expr::Op(op) = &lhs
56            && matches!(op.opcode, Eq | Ne | Lt | Le | Gt | Ge)
57        {
58            return Err(Error::ChainedComparison { span: op_span });
59        }
60
61        if let ast::Opcode::Err = opcode
62            && lhs_type_def.is_infallible()
63        {
64            return Err(Error::UnnecessaryCoalesce {
65                lhs_span,
66                rhs_span,
67                op_span,
68            });
69        }
70
71        if let ast::Opcode::Merge = opcode
72            && !(lhs_type_def.is_object() && rhs_type_def.is_object())
73        {
74            return Err(Error::MergeNonObjects {
75                lhs_span: if lhs_type_def.is_object() {
76                    None
77                } else {
78                    Some(lhs_span)
79                },
80                rhs_span: if rhs_type_def.is_object() {
81                    None
82                } else {
83                    Some(rhs_span)
84                },
85            });
86        }
87
88        Ok(Op {
89            lhs: Box::new(lhs),
90            rhs: Box::new(rhs),
91            opcode,
92        })
93    }
94}
95
96impl Expression for Op {
97    fn resolve(&self, ctx: &mut Context) -> Resolved {
98        use crate::value::Value::{Boolean, Null};
99        use ast::Opcode::{Add, And, Div, Eq, Err, Ge, Gt, Le, Lt, Merge, Mul, Ne, Or, Sub};
100
101        match self.opcode {
102            Err => return self.lhs.resolve(ctx).or_else(|_| self.rhs.resolve(ctx)),
103            Or => {
104                return self
105                    .lhs
106                    .resolve(ctx)?
107                    .try_or(|| self.rhs.resolve(ctx))
108                    .map_err(Into::into);
109            }
110            And => {
111                return match self.lhs.resolve(ctx)? {
112                    Null | Boolean(false) => Ok(false.into()),
113                    v => v.try_and(self.rhs.resolve(ctx)?).map_err(Into::into),
114                };
115            }
116            _ => (),
117        }
118
119        let lhs = self.lhs.resolve(ctx)?;
120        let rhs = self.rhs.resolve(ctx)?;
121
122        // Arithmetic that can overflow should wrap
123        match self.opcode {
124            Mul => lhs.try_mul(rhs),
125            Div => lhs.try_div(rhs),
126            Add => lhs.try_add(rhs),
127            Sub => lhs.try_sub(rhs),
128            Eq => Ok(lhs.eq_lossy(&rhs).into()),
129            Ne => Ok((!lhs.eq_lossy(&rhs)).into()),
130            Gt => lhs.try_gt(rhs),
131            Ge => lhs.try_ge(rhs),
132            Lt => lhs.try_lt(rhs),
133            Le => lhs.try_le(rhs),
134            Merge => lhs.try_merge(rhs),
135            And | Or | Err => unreachable!(),
136        }
137        .map_err(Into::into)
138    }
139
140    #[allow(clippy::too_many_lines)]
141    fn type_info(&self, state: &TypeState) -> TypeInfo {
142        use crate::value::Kind as K;
143        use ast::Opcode::{Add, And, Div, Eq, Err, Ge, Gt, Le, Lt, Merge, Mul, Ne, Or, Sub};
144        let original_state = state.clone();
145
146        let mut state = state.clone();
147        let mut lhs_def = self.lhs.apply_type_info(&mut state);
148        let lhs_value = self.lhs.resolve_constant(&original_state);
149
150        // TODO: this is incorrect, but matches the existing behavior of the compiler
151        // see: https://github.com/vectordotdev/vector/issues/13789
152        // and: https://github.com/vectordotdev/vector/issues/13791
153
154        // calculates state for an RHS that may or may not be resolved at runtime
155        // This merges the type definitions for state. If it is known at compile-time
156        // if the RHS will be resolved, this should not be used
157        let maybe_rhs = |state: &mut TypeState| {
158            let rhs_info = self.rhs.type_info(state);
159            *state = state.clone().merge(rhs_info.state);
160            rhs_info.result
161        };
162
163        let result = match self.opcode {
164            Err => {
165                let rhs_def = maybe_rhs(&mut state);
166
167                // entire expression is only fallible if both lhs and rhs were fallible
168                let fallible = lhs_def.is_fallible() && rhs_def.is_fallible();
169
170                lhs_def.union(rhs_def).maybe_fallible(fallible)
171            }
172
173            Or => {
174                if lhs_def.is_null() || lhs_value == Some(Value::Boolean(false)) {
175                    // lhs is always "false"
176                    self.rhs.apply_type_info(&mut state)
177                } else if !(lhs_def.contains_null() || lhs_def.contains_boolean())
178                    || lhs_value == Some(Value::Boolean(true))
179                {
180                    // lhs is always "true"
181                    lhs_def
182                } else {
183                    // not sure if lhs is true/false, merge both
184
185                    // We can remove Null from the lhs since we know that if the lhs is Null
186                    // we will be returning the rhs and only the rhs type_def will then be relevant.
187                    lhs_def.remove_null();
188
189                    lhs_def.union(maybe_rhs(&mut state))
190                }
191            }
192
193            // ... | ...
194            Merge => lhs_def.merge_overwrite(self.rhs.apply_type_info(&mut state)),
195
196            And => {
197                if lhs_def.is_null() || lhs_value == Some(Value::Boolean(false)) {
198                    // lhs is always "false"
199                    TypeDef::boolean()
200                } else if lhs_value == Some(Value::Boolean(true)) {
201                    // lhs is always "true"
202                    // keep the fallibility of RHS, but change it to a boolean
203                    self.rhs.apply_type_info(&mut state).with_kind(K::boolean())
204                } else {
205                    // unknown if lhs is true or false
206                    lhs_def
207                        .fallible_unless(K::null().or_boolean())
208                        .union(maybe_rhs(&mut state).fallible_unless(K::null().or_boolean()))
209                        .with_kind(K::boolean())
210                }
211            }
212
213            // ... == ...
214            // ... != ...
215            Eq | Ne => lhs_def
216                .union(self.rhs.apply_type_info(&mut state))
217                .with_kind(K::boolean()),
218
219            Gt | Ge | Lt | Le => {
220                let rhs_def = self.rhs.apply_type_info(&mut state);
221
222                // "b" >  "a"
223                // "a" >= "a"
224                // "a" <  "b"
225                // "b" <= "b"
226                // t'2023-05-04T22:22:22.234142Z' >  t'2023-04-04T22:22:22.234142Z'
227                // t'2023-05-04T22:22:22.234142Z' >= t'2023-04-04T22:22:22.234142Z'
228                // t'2023-05-04T22:22:22.234142Z' <  t'2023-04-04T22:22:22.234142Z'
229                // t'2023-05-04T22:22:22.234142Z' <= t'2023-04-04T22:22:22.234142Z'
230                if (lhs_def.is_bytes() && rhs_def.is_bytes())
231                    || (lhs_def.is_timestamp() && rhs_def.is_timestamp())
232                {
233                    lhs_def.union(rhs_def).with_kind(K::boolean())
234                }
235                // ... >  ...
236                // ... >= ...
237                // ... <  ...
238                // ... <= ...
239                else {
240                    lhs_def
241                        .fallible_unless(K::integer().or_float())
242                        .union(rhs_def.fallible_unless(K::integer().or_float()))
243                        .with_kind(K::boolean())
244                }
245            }
246
247            // ... / ...
248            Div => {
249                let td = TypeDef::float();
250
251                // Division is infallible if the rhs is a literal normal float or integer.
252                match self.rhs.resolve_constant(&state) {
253                    Some(value) if lhs_def.is_float() || lhs_def.is_integer() => match value {
254                        Value::Float(v) if v.is_normal() => td.infallible(),
255                        Value::Integer(v) if v != 0 => td.infallible(),
256                        _ => td.fallible(),
257                    },
258                    _ => td.fallible(),
259                }
260            }
261
262            Add | Sub | Mul => {
263                // none of these operations short-circuit, so the type of RHS can be applied
264                let rhs_def = self.rhs.apply_type_info(&mut state);
265
266                match self.opcode {
267                    // "bar" + ...
268                    // ... + "bar"
269                    Add if lhs_def.is_bytes() || rhs_def.is_bytes() => lhs_def
270                        .fallible_unless(K::bytes().or_null())
271                        .union(rhs_def.fallible_unless(K::bytes().or_null()))
272                        .with_kind(K::bytes()),
273
274                    // ... + 1.0
275                    // ... - 1.0
276                    // ... * 1.0
277                    // ... % 1.0
278                    // 1.0 + ...
279                    // 1.0 - ...
280                    // 1.0 * ...
281                    // 1.0 % ...
282                    Add | Sub | Mul if lhs_def.is_float() || rhs_def.is_float() => lhs_def
283                        .fallible_unless(K::integer().or_float())
284                        .union(rhs_def.fallible_unless(K::integer().or_float()))
285                        .with_kind(K::float()),
286
287                    // 1 + 1
288                    // 1 - 1
289                    // 1 * 1
290                    // 1 % 1
291                    Add | Sub | Mul if lhs_def.is_integer() && rhs_def.is_integer() => {
292                        lhs_def.union(rhs_def).with_kind(K::integer())
293                    }
294
295                    // "bar" * 1
296                    Mul if lhs_def.is_bytes() && rhs_def.is_integer() => {
297                        lhs_def.union(rhs_def).with_kind(K::bytes())
298                    }
299
300                    // 1 * "bar"
301                    Mul if lhs_def.is_integer() && rhs_def.is_bytes() => {
302                        lhs_def.union(rhs_def).with_kind(K::bytes())
303                    }
304
305                    // ... + ...
306                    // ... * ...
307                    Add | Mul => lhs_def
308                        .union(rhs_def)
309                        .fallible()
310                        .with_kind(K::bytes().or_integer().or_float()),
311
312                    // ... - ...
313                    Sub => lhs_def
314                        .union(rhs_def)
315                        .fallible()
316                        .with_kind(K::integer().or_float()),
317                    _ => unreachable!("Add, Sub, or Mul operation not handled"),
318                }
319            }
320        };
321        TypeInfo::new(state, result)
322    }
323}
324
325impl fmt::Display for Op {
326    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327        write!(f, "{} {} {}", self.lhs, self.opcode, self.rhs)
328    }
329}
330
331impl fmt::Debug for Op {
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333        write!(f, "Op({} {} {})", self.lhs, self.opcode, self.rhs)
334    }
335}
336
337// -----------------------------------------------------------------------------
338
339#[derive(thiserror::Error, Debug)]
340pub enum Error {
341    #[error("comparison operators can't be chained together")]
342    ChainedComparison { span: Span },
343
344    #[error("unnecessary error coalescing operation")]
345    UnnecessaryCoalesce {
346        lhs_span: Span,
347        rhs_span: Span,
348        op_span: Span,
349    },
350
351    #[error("only objects can be merged")]
352    MergeNonObjects {
353        lhs_span: Option<Span>,
354        rhs_span: Option<Span>,
355    },
356
357    #[error("fallible operation")]
358    Expr(#[from] expression::ExpressionError),
359}
360
361impl DiagnosticMessage for Error {
362    fn code(&self) -> usize {
363        use Error::{ChainedComparison, Expr, MergeNonObjects, UnnecessaryCoalesce};
364
365        match self {
366            ChainedComparison { .. } => 650,
367            UnnecessaryCoalesce { .. } => 651,
368            MergeNonObjects { .. } => 652,
369            Expr(err) => err.code(),
370        }
371    }
372
373    fn message(&self) -> String {
374        use Error::Expr;
375
376        match self {
377            Expr(err) => err.message(),
378            err => err.to_string(),
379        }
380    }
381
382    fn labels(&self) -> Vec<Label> {
383        use Error::{ChainedComparison, Expr, MergeNonObjects, UnnecessaryCoalesce};
384
385        match self {
386            ChainedComparison { span } => vec![Label::primary("", span)],
387            UnnecessaryCoalesce {
388                lhs_span,
389                rhs_span,
390                op_span,
391            } => vec![
392                Label::primary("this expression can't fail", lhs_span),
393                Label::context("this expression never resolves", rhs_span),
394                Label::context("remove this error coalescing operation", op_span),
395            ],
396            MergeNonObjects { lhs_span, rhs_span } => {
397                let mut labels = Vec::new();
398                if let Some(lhs_span) = lhs_span {
399                    labels.push(Label::primary(
400                        "this expression must resolve to an object",
401                        lhs_span,
402                    ));
403                }
404                if let Some(rhs_span) = rhs_span {
405                    labels.push(Label::primary(
406                        "this expression must resolve to an object",
407                        rhs_span,
408                    ));
409                }
410
411                labels
412            }
413            Expr(err) => err.labels(),
414        }
415    }
416
417    fn notes(&self) -> Vec<Note> {
418        use Error::{ChainedComparison, Expr};
419
420        match self {
421            ChainedComparison { .. } => vec![Note::SeeDocs(
422                "comparisons".to_owned(),
423                Urls::expression_docs_url("#comparison"),
424            )],
425            Expr(err) => err.notes(),
426            _ => vec![],
427        }
428    }
429}
430
431// -----------------------------------------------------------------------------
432
433#[cfg(test)]
434mod tests {
435    use std::convert::TryInto;
436
437    use chrono::Utc;
438    use ordered_float::NotNan;
439
440    use ast::{
441        Ident,
442        Opcode::{Add, And, Div, Eq, Err, Ge, Gt, Le, Lt, Mul, Ne, Or, Sub},
443    };
444
445    use crate::compiler::expression::{Block, IfStatement, Literal, Predicate, Variable};
446    use crate::test_type_def;
447
448    use super::*;
449
450    #[allow(clippy::needless_pass_by_value)]
451    fn op(
452        opcode: ast::Opcode,
453        lhs: impl TryInto<Literal> + fmt::Debug + Clone,
454        rhs: impl TryInto<Literal> + fmt::Debug + Clone,
455    ) -> Op {
456        let Ok(lhs) = lhs.clone().try_into() else {
457            panic!("not a valid lhs expression: {lhs:?}")
458        };
459
460        let Ok(rhs) = rhs.clone().try_into() else {
461            panic!("not a valid rhs expression: {rhs:?}")
462        };
463
464        Op {
465            lhs: Box::new(lhs.into()),
466            rhs: Box::new(rhs.into()),
467            opcode,
468        }
469    }
470
471    fn f(f: f64) -> NotNan<f64> {
472        NotNan::new(f).unwrap()
473    }
474
475    test_type_def![
476        or_exact {
477            expr: |_| op(Or, "foo", true),
478            want: TypeDef::bytes(),
479        }
480
481        or_null {
482            expr: |_| op(Or, (), true),
483            want: TypeDef::boolean(),
484        }
485
486        multiply_string_integer {
487            expr: |_| op(Mul, "foo", 1),
488            want: TypeDef::bytes(),
489        }
490
491        multiply_integer_string {
492            expr: |_| op(Mul, 1, "foo"),
493            want: TypeDef::bytes(),
494        }
495
496        multiply_float_integer {
497            expr: |_| op(Mul, f(1.0), 1),
498            want: TypeDef::float(),
499        }
500
501        multiply_integer_float {
502            expr: |_| op(Mul, 1, f(1.0)),
503            want: TypeDef::float(),
504        }
505
506        multiply_integer_integer {
507            expr: |_| op(Mul, 1, 1),
508            want: TypeDef::integer(),
509        }
510
511        multiply_other {
512            expr: |_| op(Mul, (), ()),
513            want: TypeDef::bytes().fallible().or_integer().or_float(),
514        }
515
516        add_string_string {
517            expr: |_| op(Add, "foo", "bar"),
518            want: TypeDef::bytes(),
519        }
520
521        add_string_null {
522            expr: |_| op(Add, "foo", ()),
523            want: TypeDef::bytes(),
524        }
525
526        add_null_string {
527            expr: |_| op(Add, (), "foo"),
528            want: TypeDef::bytes(),
529        }
530
531        add_string_bool {
532            expr: |_| op(Add, "foo", true),
533            want: TypeDef::bytes().fallible(),
534        }
535
536        add_float_integer {
537            expr: |_| op(Add, f(1.0), 1),
538            want: TypeDef::float(),
539        }
540
541        add_integer_float {
542            expr: |_| op(Add, 1, f(1.0)),
543            want: TypeDef::float(),
544        }
545
546        add_float_other {
547            expr: |_| op(Add, f(1.0), ()),
548            want: TypeDef::float().fallible(),
549        }
550
551        add_other_float {
552            expr: |_| op(Add, (), f(1.0)),
553            want: TypeDef::float().fallible(),
554        }
555
556        add_integer_integer {
557            expr: |_| op(Add, 1, 1),
558            want: TypeDef::integer(),
559        }
560
561        add_other {
562            expr: |_| op(Add, (), ()),
563            want: TypeDef::bytes().or_integer().or_float().fallible(),
564        }
565
566        subtract_integer {
567            expr: |_| op(Sub, 1, 1),
568            want: TypeDef::integer().infallible(),
569        }
570
571        subtract_float {
572            expr: |_| op(Sub, 1.0, 1.0),
573            want: TypeDef::float().infallible(),
574        }
575
576        subtract_mixed {
577            expr: |_| op(Sub, 1, 1.0),
578            want: TypeDef::float().infallible(),
579        }
580
581        subtract_other {
582            expr: |_| op(Sub, 1, ()),
583            want: TypeDef::integer().fallible().or_float(),
584        }
585
586        divide_integer_literal {
587            expr: |_| op(Div, 1, 1),
588            want: TypeDef::float().infallible(),
589        }
590
591        divide_float_literal {
592            expr: |_| op(Div, 1.0, 1.0),
593            want: TypeDef::float().infallible(),
594        }
595
596        divide_mixed_literal {
597            expr: |_| op(Div, 1, 1.0),
598            want: TypeDef::float().infallible(),
599        }
600
601        divide_float_zero_literal {
602            expr: |_| op(Div, 1, 0.0),
603            want: TypeDef::float().fallible(),
604        }
605
606        divide_integer_zero_literal {
607            expr: |_| op(Div, 1, 0),
608            want: TypeDef::float().fallible(),
609        }
610
611        divide_lhs_literal_wrong_rhs {
612            expr: |_| Op {
613                lhs: Box::new(Literal::from(true).into()),
614                rhs: Box::new(Literal::from(NotNan::new(1.0).unwrap()).into()),
615                opcode: Div,
616            },
617            want: TypeDef::float().fallible(),
618        }
619
620        divide_dynamic_rhs {
621            expr: |state: &mut TypeState| {
622                state.local.insert_variable(Ident::new("foo"), crate::compiler::type_def::Details {
623                    type_def: TypeDef::null(),
624                    value: None,
625                });
626
627                Op {
628                    lhs: Box::new(Literal::from(1).into()),
629                    rhs: Box::new(Variable::new(Span::default(), Ident::new("foo"), &state.local).unwrap().into()),
630                    opcode: Div,
631                }
632            },
633            want: TypeDef::float().fallible(),
634        }
635
636        divide_other {
637            expr: |_| op(Div, 1.0, ()),
638            want: TypeDef::float().fallible(),
639        }
640
641        and_null {
642            expr: |_| op(And, (), ()),
643            want: TypeDef::boolean().infallible(),
644        }
645
646        and_boolean {
647            expr: |_| op(And, true, true),
648            want: TypeDef::boolean().infallible(),
649        }
650
651        and_mixed {
652            expr: |_| op(And, (), true),
653            want: TypeDef::boolean().infallible(),
654        }
655
656        and_other {
657            expr: |_| op(And, (), "bar"),
658            want: TypeDef::boolean().infallible(),
659        }
660
661        equal {
662            expr: |_| op(Eq, (), ()),
663            want: TypeDef::boolean().infallible(),
664        }
665
666        not_equal {
667            expr: |_| op(Ne, (), "foo"),
668            want: TypeDef::boolean().infallible(),
669        }
670
671        greater_integer {
672            expr: |_| op(Gt, 1, 1),
673            want: TypeDef::boolean().infallible(),
674        }
675
676        greater_float {
677            expr: |_| op(Gt, 1.0, 1.0),
678            want: TypeDef::boolean().infallible(),
679        }
680
681        greater_mixed {
682            expr: |_| op(Gt, 1, 1.0),
683            want: TypeDef::boolean().infallible(),
684        }
685
686        greater_bytes {
687            expr: |_| op(Gt, "c", "b"),
688            want: TypeDef::boolean().infallible(),
689        }
690
691        greater_timestamps {
692            expr: |_| op(Gt, Utc::now(), Utc::now()),
693            want: TypeDef::boolean().infallible(),
694        }
695
696        greater_other {
697            expr: |_| op(Gt, 1, "foo"),
698            want: TypeDef::boolean().fallible(),
699        }
700
701        greater_or_equal_integer {
702            expr: |_| op(Ge, 1, 1),
703            want: TypeDef::boolean().infallible(),
704        }
705
706        greater_or_equal_float {
707            expr: |_| op(Ge, 1.0, 1.0),
708            want: TypeDef::boolean().infallible(),
709        }
710
711        greater_or_equal_mixed {
712            expr: |_| op(Ge, 1, 1.0),
713            want: TypeDef::boolean().infallible(),
714        }
715
716        greater_or_equal_bytes {
717            expr: |_| op(Ge, "foo", "foo"),
718            want: TypeDef::boolean().infallible(),
719        }
720
721        greater_or_equal_timestamps {
722            expr: |_| op(Ge, Utc::now(), Utc::now()),
723            want: TypeDef::boolean().infallible(),
724        }
725
726        greater_or_equal_other {
727            expr: |_| op(Ge, 1, "foo"),
728            want: TypeDef::boolean().fallible(),
729        }
730
731        less_integer {
732            expr: |_| op(Lt, 1, 1),
733            want: TypeDef::boolean().infallible(),
734        }
735
736        less_float {
737            expr: |_| op(Lt, 1.0, 1.0),
738            want: TypeDef::boolean().infallible(),
739        }
740
741        less_mixed {
742            expr: |_| op(Lt, 1, 1.0),
743            want: TypeDef::boolean().infallible(),
744        }
745
746        less_bytes {
747            expr: |_| op(Lt, "bar", "foo"),
748            want: TypeDef::boolean().infallible(),
749        }
750
751        less_timestamps {
752            expr: |_| op(Lt, Utc::now(), Utc::now()),
753            want: TypeDef::boolean().infallible(),
754        }
755
756        less_other {
757            expr: |_| op(Lt, 1, "foo"),
758            want: TypeDef::boolean().fallible(),
759        }
760
761        less_or_equal_integer {
762            expr: |_| op(Le, 1, 1),
763            want: TypeDef::boolean().infallible(),
764        }
765
766        less_or_equal_float {
767            expr: |_| op(Le, 1.0, 1.0),
768            want: TypeDef::boolean().infallible(),
769        }
770
771        less_or_equal_mixed {
772            expr: |_| op(Le, 1, 1.0),
773            want: TypeDef::boolean().infallible(),
774        }
775
776        less_or_equal_bytes {
777            expr: |_| op(Le, "bar", "bar"),
778            want: TypeDef::boolean().infallible(),
779        }
780
781        less_or_equal_timestamps {
782            expr: |_| op(Le, Utc::now(), Utc::now()),
783            want: TypeDef::boolean().infallible(),
784        }
785
786        less_or_equal_other {
787            expr: |_| op(Le, 1, "baz"),
788            want: TypeDef::boolean().fallible(),
789        }
790
791        error_or_rhs_infallible {
792            expr: |_| Op {
793                lhs: Box::new(Op {
794                    lhs: Box::new(Literal::from("foo").into()),
795                    rhs: Box::new(Literal::from(1).into()),
796                    opcode: Div,
797                }.into()),
798                rhs: Box::new(Literal::from(true).into()),
799                opcode: Err,
800            },
801            want: TypeDef::float().or_boolean(),
802        }
803
804        error_or_fallible {
805            expr: |_| Op {
806                lhs: Box::new(Op {
807                    lhs: Box::new(Literal::from("foo").into()),
808                    rhs: Box::new(Literal::from(NotNan::new(0.0).unwrap()).into()),
809                    opcode: Div,
810                }.into()),
811                rhs: Box::new(Op {
812                    lhs: Box::new(Literal::from(true).into()),
813                    rhs: Box::new(Literal::from(NotNan::new(0.0).unwrap()).into()),
814                    opcode: Div,
815                }.into()),
816                opcode: Err,
817            },
818            want: TypeDef::float().fallible(),
819        }
820
821        error_or_nested_infallible {
822            expr: |_| Op {
823                lhs: Box::new(Op {
824                    lhs: Box::new(Literal::from("foo").into()),
825                    rhs: Box::new(Literal::from(1).into()),
826                    opcode: Div,
827                }.into()),
828                rhs: Box::new(Op {
829                    lhs: Box::new(Op {
830                        lhs: Box::new(Literal::from(true).into()),
831                        rhs: Box::new(Literal::from(1).into()),
832                        opcode: Div,
833                    }.into()),
834                    rhs: Box::new(Literal::from("foo").into()),
835                    opcode: Err,
836                }.into()),
837                opcode: Err,
838            },
839            want: TypeDef::float().or_bytes(),
840        }
841
842        or_nullable {
843            expr: |_| Op {
844                lhs: Box::new(
845                    IfStatement {
846                        predicate: Predicate::new_unchecked(vec![Literal::from(true).into()]),
847                        if_block: Block::new_scoped(vec![Literal::from("string").into()]),
848                        else_block: None,
849                    }.into()),
850                rhs: Box::new(Literal::from("another string").into()),
851                opcode: Or,
852            },
853            want: TypeDef::bytes(),
854        }
855
856        or_not_nullable {
857            expr: |_| Op {
858                lhs: Box::new(
859                    IfStatement {
860                        predicate: Predicate::new_unchecked(vec![Literal::from(true).into()]),
861                        if_block: Block::new_scoped(vec![Literal::from("string").into()]),
862                        else_block:  Some(Block::new_scoped(vec![Literal::from(42).into()]))
863                }.into()),
864                rhs: Box::new(Literal::from("another string").into()),
865                opcode: Or,
866            },
867            want: TypeDef::bytes().or_integer(),
868        }
869    ];
870}