1use bynk_syntax::ast::{BinOp, Expr, ExprKind, UnaryOp};
31
32pub fn matched_is_tests(expr: &Expr, when_true: bool) -> Vec<&Expr> {
35 let mut out = Vec::new();
36 collect(expr, when_true, &mut out);
37 out
38}
39
40fn collect<'e>(expr: &'e Expr, when_true: bool, out: &mut Vec<&'e Expr>) {
41 match (&expr.kind, when_true) {
42 (ExprKind::Is { .. }, true) => out.push(expr),
43 (ExprKind::Paren(inner), _) => collect(inner, when_true, out),
44 (ExprKind::UnaryOp(UnaryOp::Not, inner), _) => collect(inner, !when_true, out),
45 (ExprKind::BinOp(BinOp::And, a, b), true) | (ExprKind::BinOp(BinOp::Or, a, b), false) => {
46 collect(a, when_true, out);
47 collect(b, when_true, out);
48 }
49 (ExprKind::BinOp(BinOp::Implies, a, b), false) => {
50 collect(a, true, out);
51 collect(b, false, out);
52 }
53 _ => {}
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::matched_is_tests;
60 use bynk_syntax::{lexer, parser};
61
62 fn proved(cond: &str, when_true: bool) -> Vec<String> {
64 let src = format!(
65 "commons m\n\nfn f(a: Option[Int], b: Option[Int], c: Bool) -> Bool {{ {cond} }}\n"
66 );
67 let tokens = lexer::tokenize(&src).expect("lex");
68 let unit = parser::parse_unit(&tokens, &src).expect("parse");
69 let bynk_syntax::ast::SourceUnit::Commons(commons) = unit else {
70 panic!("commons")
71 };
72 let bynk_syntax::ast::CommonsItem::Fn(f) = &commons.items[0] else {
73 panic!("fn")
74 };
75 matched_is_tests(&f.body.tail, when_true)
76 .into_iter()
77 .map(|e| match &e.kind {
78 bynk_syntax::ast::ExprKind::Is { value, .. } => match &value.kind {
79 bynk_syntax::ast::ExprKind::Ident(id) => id.name.clone(),
80 _ => "?".to_string(),
81 },
82 _ => unreachable!(),
83 })
84 .collect()
85 }
86
87 #[test]
88 fn a_test_proves_itself_only_when_true() {
89 assert_eq!(proved("a is Some(x)", true), ["a"]);
90 assert!(proved("a is Some(x)", false).is_empty());
91 assert_eq!(proved("(a is Some(x))", true), ["a"]);
92 }
93
94 #[test]
95 fn and_proves_both_when_true_and_nothing_when_false() {
96 assert_eq!(proved("a is Some(x) && b is Some(y)", true), ["a", "b"]);
97 assert!(proved("a is Some(x) && b is Some(y)", false).is_empty());
98 }
99
100 #[test]
101 fn or_proves_nothing_when_true_and_both_negations_when_false() {
102 assert!(proved("a is Some(x) || b is Some(y)", true).is_empty());
103 assert_eq!(
104 proved("!(a is Some(x)) || !(b is Some(y))", false),
105 ["a", "b"]
106 );
107 }
108
109 #[test]
110 fn not_flips_the_polarity() {
111 assert!(proved("!(a is Some(x))", true).is_empty());
112 assert_eq!(proved("!(a is Some(x))", false), ["a"]);
113 assert_eq!(proved("!!(a is Some(x))", true), ["a"]);
114 assert_eq!(proved("!(a is Some(x) && b is Some(y))", false), ["a", "b"]);
116 assert!(proved("!(a is Some(x) && b is Some(y))", true).is_empty());
118 }
119
120 #[test]
121 fn implies_proves_its_antecedent_and_negated_consequent_when_false() {
122 assert!(proved("a is Some(x) implies c", true).is_empty());
123 assert_eq!(
124 proved("a is Some(x) implies !(b is Some(y))", false),
125 ["a", "b"]
126 );
127 }
128}