use super::test_schema;
use crate::engine::simplify::Simplify;
use toasty_core::stmt::{BinaryOp, Expr, ExprAnd, ExprOr};
fn nested_and(a: Expr, b: Expr, c: Expr) -> ExprAnd {
ExprAnd {
operands: vec![
a,
Expr::And(ExprAnd {
operands: vec![b, c],
}),
],
}
}
#[test]
fn flatten_all_symbolic() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = nested_and(Expr::arg(0), Expr::arg(1), Expr::arg(2));
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none()); assert_eq!(expr.operands.len(), 3);
assert_eq!(expr.operands[0], Expr::arg(0));
assert_eq!(expr.operands[1], Expr::arg(1));
assert_eq!(expr.operands[2], Expr::arg(2));
}
#[test]
fn flatten_with_true_in_outer() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = nested_and(true.into(), Expr::arg(1), Expr::arg(2));
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none());
assert_eq!(expr.operands.len(), 2);
assert_eq!(expr.operands[0], Expr::arg(1));
assert_eq!(expr.operands[1], Expr::arg(2));
}
#[test]
fn flatten_with_true_in_nested_first() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = nested_and(Expr::arg(0), true.into(), Expr::arg(2));
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none());
assert_eq!(expr.operands.len(), 2);
assert_eq!(expr.operands[0], Expr::arg(0));
assert_eq!(expr.operands[1], Expr::arg(2));
}
#[test]
fn flatten_with_true_in_nested_second() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = nested_and(Expr::arg(0), Expr::arg(1), true.into());
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none());
assert_eq!(expr.operands.len(), 2);
assert_eq!(expr.operands[0], Expr::arg(0));
assert_eq!(expr.operands[1], Expr::arg(1));
}
#[test]
fn flatten_outer_true_nested_one_true() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = nested_and(true.into(), true.into(), Expr::arg(2));
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert_eq!(result.unwrap(), Expr::arg(2));
}
#[test]
fn flatten_outer_symbolic_nested_all_true() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = nested_and(Expr::arg(0), true.into(), true.into());
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert_eq!(result.unwrap(), Expr::arg(0));
}
#[test]
fn flatten_all_true() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = nested_and(true.into(), true.into(), true.into());
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(result.unwrap().is_true());
}
#[test]
fn flatten_with_false_in_outer() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = nested_and(false.into(), Expr::arg(1), Expr::arg(2));
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(result.unwrap().is_false());
}
#[test]
fn flatten_with_false_in_nested() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = nested_and(Expr::arg(0), false.into(), Expr::arg(2));
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(result.unwrap().is_false());
}
#[test]
fn flatten_true_and_false_mixed() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = nested_and(true.into(), false.into(), true.into());
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(result.unwrap().is_false());
}
#[test]
fn single_operand_unwrapped() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![Expr::arg(0)],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert_eq!(result.unwrap(), Expr::arg(0));
}
#[test]
fn empty_after_removing_true() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![true.into(), true.into()],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(result.unwrap().is_true());
}
#[test]
fn idempotent_two_identical() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![Expr::arg(0), Expr::arg(0)],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert_eq!(result.unwrap(), Expr::arg(0));
}
#[test]
fn idempotent_three_identical() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![Expr::arg(0), Expr::arg(0), Expr::arg(0)],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert_eq!(result.unwrap(), Expr::arg(0));
}
#[test]
fn idempotent_with_different() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![Expr::arg(0), Expr::arg(1), Expr::arg(0)],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none());
assert_eq!(expr.operands.len(), 2);
assert_eq!(expr.operands[0], Expr::arg(0));
assert_eq!(expr.operands[1], Expr::arg(1));
}
#[test]
fn absorption_and_or() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![
Expr::arg(0),
Expr::Or(ExprOr {
operands: vec![Expr::arg(0), Expr::arg(1)],
}),
],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert_eq!(result.unwrap(), Expr::arg(0));
}
#[test]
fn absorption_with_multiple_operands() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![
Expr::arg(0),
Expr::arg(1),
Expr::Or(ExprOr {
operands: vec![Expr::arg(0), Expr::arg(2)],
}),
],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none());
assert_eq!(expr.operands.len(), 2);
assert_eq!(expr.operands[0], Expr::arg(0));
assert_eq!(expr.operands[1], Expr::arg(1));
}
#[test]
fn absorption_two_and_three_or() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![
Expr::arg(0),
Expr::arg(1),
Expr::Or(ExprOr {
operands: vec![Expr::arg(0), Expr::arg(2), Expr::arg(3)],
}),
],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none());
assert_eq!(expr.operands.len(), 2);
assert_eq!(expr.operands[0], Expr::arg(0));
assert_eq!(expr.operands[1], Expr::arg(1));
}
#[test]
fn complement_basic() {
use toasty_core::stmt::ExprNot;
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let a = Expr::eq(Expr::arg(0), Expr::arg(1));
let mut expr = ExprAnd {
operands: vec![a.clone(), Expr::Not(ExprNot { expr: Box::new(a) })],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(result.unwrap().is_false());
}
#[test]
fn complement_with_other_operands() {
use toasty_core::stmt::ExprNot;
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let a = Expr::eq(Expr::arg(0), Expr::arg(1));
let mut expr = ExprAnd {
operands: vec![
a.clone(),
Expr::arg(2),
Expr::Not(ExprNot { expr: Box::new(a) }),
],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(result.unwrap().is_false());
}
#[test]
fn complement_nullable_not_simplified() {
use toasty_core::stmt::ExprNot;
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let a = Expr::arg(0);
let mut expr = ExprAnd {
operands: vec![a.clone(), Expr::Not(ExprNot { expr: Box::new(a) })],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none());
}
#[test]
fn complement_multiple_repetitions() {
use toasty_core::stmt::ExprNot;
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let a = Expr::eq(Expr::arg(0), Expr::arg(1));
let mut expr = ExprAnd {
operands: vec![
a.clone(),
a.clone(),
Expr::Not(ExprNot {
expr: Box::new(a.clone()),
}),
Expr::Not(ExprNot { expr: Box::new(a) }),
],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(result.unwrap().is_false());
}
#[test]
fn range_to_equality_ge_le() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![
Expr::binary_op(Expr::arg(0), BinaryOp::Ge, 5i64),
Expr::binary_op(Expr::arg(0), BinaryOp::Le, 5i64),
],
};
let result = simplify.simplify_expr_and(&mut expr);
let Some(Expr::BinaryOp(bin_op)) = result else {
panic!("expected binary op");
};
assert!(bin_op.op.is_eq());
}
#[test]
fn range_to_equality_le_ge() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![
Expr::binary_op(Expr::arg(0), BinaryOp::Le, 5i64),
Expr::binary_op(Expr::arg(0), BinaryOp::Ge, 5i64),
],
};
let result = simplify.simplify_expr_and(&mut expr);
let Some(Expr::BinaryOp(bin_op)) = result else {
panic!("expected binary op");
};
assert!(bin_op.op.is_eq());
}
#[test]
fn range_to_equality_different_bounds_not_simplified() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![
Expr::binary_op(Expr::arg(0), BinaryOp::Ge, 5i64),
Expr::binary_op(Expr::arg(0), BinaryOp::Le, 10i64),
],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none());
assert_eq!(expr.operands.len(), 2);
}
#[test]
fn range_to_equality_different_exprs_not_simplified() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![
Expr::binary_op(Expr::arg(0), BinaryOp::Ge, 5i64),
Expr::binary_op(Expr::arg(1), BinaryOp::Le, 5i64),
],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none());
assert_eq!(expr.operands.len(), 2);
}
#[test]
fn range_to_equality_with_other_operands() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![
Expr::arg(0),
Expr::binary_op(Expr::arg(1), BinaryOp::Ge, 5i64),
Expr::binary_op(Expr::arg(1), BinaryOp::Le, 5i64),
],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none()); assert_eq!(expr.operands.len(), 2);
let has_equality = expr
.operands
.iter()
.any(|e| matches!(e, Expr::BinaryOp(op) if op.op.is_eq()));
assert!(has_equality);
}
#[test]
fn range_to_equality_uneven_repetitions() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![
Expr::binary_op(Expr::arg(0), BinaryOp::Ge, 5i64),
Expr::binary_op(Expr::arg(0), BinaryOp::Ge, 5i64),
Expr::binary_op(Expr::arg(0), BinaryOp::Le, 5i64),
],
};
let result = simplify.simplify_expr_and(&mut expr);
let Some(Expr::BinaryOp(bin_op)) = result else {
panic!("expected binary op");
};
assert!(bin_op.op.is_eq());
}
#[test]
fn null_and_null_becomes_null() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![Expr::null(), Expr::null()],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(result.unwrap().is_value_null());
}
#[test]
fn null_and_true_becomes_null() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![Expr::null(), true.into()],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(result.unwrap().is_value_null());
}
#[test]
fn true_and_null_becomes_null() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![true.into(), Expr::null()],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(result.unwrap().is_value_null());
}
#[test]
fn null_and_false_becomes_false() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![Expr::null(), false.into()],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(result.unwrap().is_false());
}
#[test]
fn false_and_null_becomes_false() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![false.into(), Expr::null()],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(result.unwrap().is_false());
}
#[test]
fn null_and_symbolic_not_simplified() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![Expr::null(), Expr::arg(0)],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none());
assert_eq!(expr.operands.len(), 2);
}
#[test]
fn multiple_nulls_become_null() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![Expr::null(), Expr::null(), Expr::null()],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(result.unwrap().is_value_null());
}
#[test]
fn multiple_nulls_and_true_becomes_null() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![Expr::null(), true.into(), Expr::null(), true.into()],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(result.unwrap().is_value_null());
}
#[test]
fn error_operand_not_simplified_in_and() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![Expr::error("boom"), Expr::arg(0)],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none());
assert_eq!(expr.operands.len(), 2);
}
#[test]
fn error_and_true_keeps_error() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![Expr::error("boom"), true.into()],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(matches!(&result.unwrap(), Expr::Error(e) if e.message == "boom"));
}
#[test]
fn error_and_false_becomes_false() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![Expr::error("boom"), false.into()],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(result.unwrap().is_false());
}
#[test]
fn prune_or_branch_contradicting_outer_eq() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let disc_eq_1 = Expr::eq(Expr::arg(0), Expr::from(1i64));
let addr_eq_alice = Expr::eq(Expr::arg(1), Expr::from("alice"));
let disc_ne_1 = Expr::ne(Expr::arg(0), Expr::from(1i64));
let error_eq_alice = Expr::eq(Expr::error("unreachable"), Expr::from("alice"));
let mut expr = ExprAnd {
operands: vec![
disc_eq_1.clone(),
Expr::Or(ExprOr {
operands: vec![
Expr::and(disc_eq_1.clone(), addr_eq_alice.clone()),
Expr::and(disc_ne_1, error_eq_alice),
],
}),
],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none()); assert_eq!(expr.operands.len(), 2);
assert!(expr.operands.contains(&disc_eq_1));
assert!(expr.operands.contains(&addr_eq_alice));
}
#[test]
fn prune_or_multiple_contradicting_branches() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let x_eq_1 = Expr::eq(Expr::arg(0), Expr::from(1i64));
let x_eq_2 = Expr::eq(Expr::arg(0), Expr::from(2i64));
let x_eq_3 = Expr::eq(Expr::arg(0), Expr::from(3i64));
let mut expr = ExprAnd {
operands: vec![
x_eq_1.clone(),
Expr::Or(ExprOr {
operands: vec![
Expr::and(x_eq_1.clone(), Expr::arg(1)),
Expr::and(x_eq_2, Expr::arg(2)),
Expr::and(x_eq_3, Expr::arg(3)),
],
}),
],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none());
assert_eq!(expr.operands.len(), 2);
assert!(expr.operands.contains(&x_eq_1));
assert!(expr.operands.contains(&Expr::arg(1)));
}
#[test]
fn prune_or_no_contradiction_preserved() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![
Expr::eq(Expr::arg(0), Expr::from(1i64)),
Expr::Or(ExprOr {
operands: vec![
Expr::and(Expr::eq(Expr::arg(1), Expr::from(2i64)), Expr::arg(2)),
Expr::and(Expr::eq(Expr::arg(1), Expr::from(3i64)), Expr::arg(3)),
],
}),
],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none());
assert_eq!(expr.operands.len(), 2);
assert!(matches!(&expr.operands[1], Expr::Or(or) if or.operands.len() == 2));
}
#[test]
fn prune_or_all_branches_contradicted() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![
Expr::eq(Expr::arg(0), Expr::from(1i64)),
Expr::Or(ExprOr {
operands: vec![
Expr::and(Expr::eq(Expr::arg(0), Expr::from(2i64)), Expr::arg(1)),
Expr::and(Expr::eq(Expr::arg(0), Expr::from(3i64)), Expr::arg(2)),
],
}),
],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_some());
assert!(result.unwrap().is_false());
}
#[test]
fn prune_or_non_and_branch() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let x_eq_1 = Expr::eq(Expr::arg(0), Expr::from(1i64));
let x_eq_2 = Expr::eq(Expr::arg(0), Expr::from(2i64));
let mut expr = ExprAnd {
operands: vec![
x_eq_1.clone(),
Expr::Or(ExprOr {
operands: vec![
x_eq_2, Expr::and(x_eq_1.clone(), Expr::arg(1)),
],
}),
],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none());
assert_eq!(expr.operands.len(), 2);
assert!(expr.operands.contains(&x_eq_1));
assert!(expr.operands.contains(&Expr::arg(1)));
}
#[test]
fn prune_or_multiple_constraints() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let x_eq_1 = Expr::eq(Expr::arg(0), Expr::from(1i64));
let y_eq_2 = Expr::eq(Expr::arg(1), Expr::from(2i64));
let y_eq_3 = Expr::eq(Expr::arg(1), Expr::from(3i64));
let mut expr = ExprAnd {
operands: vec![
x_eq_1.clone(),
y_eq_2.clone(),
Expr::Or(ExprOr {
operands: vec![
Expr::and_from_vec(vec![x_eq_1.clone(), y_eq_3, Expr::arg(2)]),
Expr::and_from_vec(vec![x_eq_1.clone(), y_eq_2.clone(), Expr::arg(3)]),
],
}),
],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none());
assert_eq!(expr.operands.len(), 3);
assert!(expr.operands.contains(&x_eq_1));
assert!(expr.operands.contains(&y_eq_2));
assert!(expr.operands.contains(&Expr::arg(3)));
}
#[test]
fn prune_or_no_constraints_no_change() {
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let mut expr = ExprAnd {
operands: vec![
Expr::Or(ExprOr {
operands: vec![Expr::arg(0), Expr::arg(1)],
}),
Expr::Or(ExprOr {
operands: vec![Expr::arg(2), Expr::arg(3)],
}),
],
};
let result = simplify.simplify_expr_and(&mut expr);
assert!(result.is_none());
assert_eq!(expr.operands.len(), 2);
}
#[test]
fn prune_or_end_to_end_via_visit() {
use toasty_core::stmt::{ExprMatch, MatchArm, Value, VisitMut};
let schema = test_schema();
let mut simplify = Simplify::new(&schema);
let disc = Expr::arg(0);
let addr = Expr::arg(1);
let mut expr = Expr::and(
Expr::eq(disc.clone(), Expr::from(1i64)),
Expr::eq(
Expr::Match(ExprMatch {
subject: Box::new(disc.clone()),
arms: vec![
MatchArm {
pattern: Value::from(1i64),
expr: addr.clone(),
},
MatchArm {
pattern: Value::from(2i64),
expr: Expr::arg(2),
},
],
else_expr: Box::new(Expr::error("unreachable")),
}),
Expr::from("alice"),
),
);
simplify.visit_expr_mut(&mut expr);
let Expr::And(and) = &expr else {
panic!("expected AND, got: {expr:?}");
};
assert_eq!(and.operands.len(), 2);
assert!(and.operands.contains(&Expr::eq(disc, Expr::from(1i64))));
assert!(and.operands.contains(&Expr::eq(addr, Expr::from("alice"))));
}