use crate::engine::grammar::SPG;
use crate::typing::pattern::Pattern;
use crate::typing::term::{Subst, Term, apply, unify};
use crate::typing::{Context, TypingSynth};
fn arrow(a: Term, b: Term) -> Term {
Term::con("->", vec![a, b])
}
fn solve(a: &Term, b: &Term) -> Option<Subst> {
let mut s = Subst::new();
unify(a, b, &mut s, true).then_some(s)
}
#[test]
fn no_leftmost_ambiguity() {
let pat = arrow(Term::var("A"), Term::var("B"));
let right = arrow(Term::leaf("a"), arrow(Term::leaf("b"), Term::leaf("c")));
let s = solve(&pat, &right).expect("unifies");
assert_eq!(apply(&Term::var("A"), &s), Term::leaf("a"));
assert_eq!(
apply(&Term::var("B"), &s),
arrow(Term::leaf("b"), Term::leaf("c"))
);
let left = arrow(arrow(Term::leaf("a"), Term::leaf("b")), Term::leaf("c"));
let s = solve(&pat, &left).expect("unifies");
assert_eq!(
apply(&Term::var("A"), &s),
arrow(Term::leaf("a"), Term::leaf("b"))
);
assert_eq!(apply(&Term::var("B"), &s), Term::leaf("c"));
}
#[test]
fn two_open_patterns_align() {
let p = arrow(Term::var("A"), Term::var("B"));
let q = arrow(Term::var("X"), Term::var("Y"));
let s = solve(&p, &q).expect("unifies");
assert_eq!(apply(&Term::var("A"), &s), Term::var("X"));
assert_eq!(apply(&Term::var("B"), &s), Term::var("Y"));
}
#[test]
fn set_valued_leaves_meet() {
let ab = Term::Leaf(Pattern::closed(crate::regex::Regex::union_many(
["Int", "Bool"]
.into_iter()
.map(crate::regex::Regex::literal),
)));
let bs = Term::Leaf(Pattern::closed(crate::regex::Regex::union_many(
["Bool", "Str"]
.into_iter()
.map(crate::regex::Regex::literal),
)));
assert!(solve(&ab, &bs).is_some());
let cd = Term::leaf("Char");
assert!(solve(&ab, &cd).is_none());
let s = solve(
&arrow(ab.clone(), Term::leaf("c")),
&arrow(Term::var("A"), Term::leaf("c")),
)
.expect("unifies");
assert_eq!(apply(&Term::var("A"), &s), ab);
}
#[test]
fn constructor_clash_and_arity() {
assert!(
solve(
&arrow(Term::leaf("a"), Term::leaf("b")),
&Term::con(",", vec![Term::leaf("a"), Term::leaf("b")])
)
.is_none()
);
assert!(
solve(
&Term::con("f", vec![Term::leaf("a")]),
&Term::con("f", vec![Term::leaf("a"), Term::leaf("b")])
)
.is_none()
);
assert!(solve(&arrow(Term::leaf("a"), Term::leaf("b")), &Term::leaf("a")).is_none());
}
#[test]
fn shared_variable_propagates() {
let p = arrow(Term::var("A"), Term::var("A"));
let q = arrow(Term::leaf("Int"), Term::var("B"));
let s = solve(&p, &q).expect("unifies");
assert_eq!(apply(&Term::var("A"), &s), Term::leaf("Int"));
assert_eq!(apply(&Term::var("B"), &s), Term::leaf("Int"));
}
#[test]
fn shared_variable_can_clash() {
let p = arrow(Term::var("A"), Term::var("A"));
let q = arrow(Term::leaf("Int"), Term::leaf("Bool"));
assert!(solve(&p, &q).is_none());
}
#[test]
fn occurs_check_toggles_recursive_types() {
let recursive = arrow(Term::var("A"), Term::leaf("B"));
let mut s = Subst::new();
assert!(!unify(&Term::var("A"), &recursive, &mut s, true));
let mut s = Subst::new();
assert!(unify(&Term::var("A"), &recursive, &mut s, false));
assert_eq!(s.get("A"), Some(&recursive));
}
#[test]
fn from_engine_parsed_type() {
let grammar = SPG::load(
r#"
Atom ::= 'Int' | 'Bool'
Fun ::= Atom '->' Type
Type ::= Fun | Atom
Start ::= Type
"#,
)
.unwrap();
let mut synth = TypingSynth::new(grammar, "Int->Bool");
let ast = synth.parse_with(&Context::new()).unwrap();
let root = ast
.roots()
.find(|r| r.is_complete())
.expect("complete root");
let term = Term::from_node(&root);
let expected = Term::con("Fun", vec![Term::leaf("Int"), Term::leaf("Bool")]);
assert_eq!(term, expected);
let pat = Term::con("Fun", vec![Term::var("A"), Term::var("B")]);
let s = solve(&pat, &term).expect("unifies");
assert_eq!(apply(&Term::var("A"), &s), Term::leaf("Int"));
assert_eq!(apply(&Term::var("B"), &s), Term::leaf("Bool"));
}
#[test]
fn top_unifies_with_everything_and_binds_nothing() {
let cases = [
Term::var("A"),
Term::leaf("Int"),
arrow(Term::leaf("Int"), Term::leaf("Bool")),
Term::bottom(),
Term::top(),
];
for t in &cases {
let s = solve(&Term::top(), t).expect("⊤ unifies");
assert!(s.is_empty(), "⊤ must not bind, got {s:?} against {t}");
assert!(solve(t, &Term::top()).is_some());
}
}
#[test]
fn two_tops_do_not_constrain_a_shared_hole() {
let mut s = Subst::new();
assert!(unify(&Term::var("T"), &Term::top(), &mut s, true));
assert!(unify(&Term::var("T"), &Term::top(), &mut s, true));
assert!(unify(&Term::var("T"), &Term::leaf("Int"), &mut s, true));
assert_eq!(apply(&Term::var("T"), &s), Term::leaf("Int"));
}
#[test]
fn bottom_clashes_except_against_a_variable() {
assert!(solve(&Term::bottom(), &Term::leaf("Int")).is_none());
assert!(solve(&Term::bottom(), &arrow(Term::leaf("a"), Term::leaf("b"))).is_none());
assert!(solve(&Term::bottom(), &Term::bottom()).is_none());
let s = solve(&Term::var("A"), &Term::bottom()).expect("⊥ instantiates a var");
assert_eq!(apply(&Term::var("A"), &s), Term::bottom());
}
#[test]
fn free_theory_failure_is_stable() {
use crate::typing::normalize::{Normalizer, failure_is_stable};
let free = Normalizer::new();
let open = Term::con("fst", vec![Term::var("X")]);
assert!(failure_is_stable(&free, &open, &Term::leaf("Int")));
assert!(failure_is_stable(&free, &Term::leaf("A"), &Term::leaf("B")));
}
#[test]
fn rewrite_theory_failure_on_open_term_is_not_stable() {
use crate::typing::normalize::{Normalizer, RewriteRule, failure_is_stable, unify_modulo};
let proj = RewriteRule {
lhs: Term::con("fst", vec![Term::con("Pair", vec![Term::var("A"), Term::var("B")])]),
rhs: Term::var("A"),
};
let norm = Normalizer::from_rules(vec![proj]);
let open = Term::con("fst", vec![Term::var("X")]);
let int = Term::leaf("Int");
let mut s = Subst::new();
assert!(!unify_modulo(&norm, &open, &int, &mut s, true));
assert!(!failure_is_stable(&norm, &open, &int));
let repaired = Term::con("fst", vec![Term::con("Pair", vec![int.clone(), Term::leaf("Y")])]);
let mut s2 = Subst::new();
assert!(unify_modulo(&norm, &repaired, &int, &mut s2, true));
assert!(failure_is_stable(&norm, &Term::leaf("A"), &Term::leaf("B")));
}