mago-codex 1.40.2

PHP type system representation, comparison logic, and codebase metadata for static analysis.
Documentation
mod comparator_common;

use comparator_common::*;

use std::collections::BTreeMap;

#[test]
fn list_of_lists_reflexive() {
    let inner = u(t_list(u(t_int()), false));
    let outer = t_list(inner, false);
    assert_atomic_subtype(&outer.clone(), &outer);
}

#[test]
fn deeply_nested_list_lit_in_general() {
    let lit3 = t_list(u(t_list(u(t_list(u(t_lit_int(1)), false)), false)), false);
    let int3 = t_list(u(t_list(u(t_list(u(t_int()), false)), false)), false);
    assert_atomic_subtype(&lit3, &int3);
    assert_atomic_not_subtype(&int3, &lit3);
}

#[test]
fn list_of_keyed_arrays() {
    let inner = u(t_keyed_unsealed(u(t_string()), u(t_int()), false));
    let outer = t_list(inner, false);
    assert_atomic_subtype(&outer.clone(), &outer);
}

#[test]
fn keyed_of_lists() {
    let inner = u(t_list(u(t_int()), false));
    let outer = t_keyed_unsealed(u(t_string()), inner, false);
    assert_atomic_subtype(&outer.clone(), &outer);
}

#[test]
fn shaped_array_simple() {
    let s = t_keyed_sealed(
        BTreeMap::from([(ak_str("name"), (false, u(t_string()))), (ak_str("age"), (false, u(t_int())))]),
        false,
    );
    assert_atomic_subtype(&s.clone(), &s);
}

#[test]
fn shaped_array_with_lit_values_in_general_shape() {
    let lit = t_keyed_sealed(
        BTreeMap::from([(ak_str("name"), (false, us("Alice"))), (ak_str("age"), (false, ui(30)))]),
        false,
    );
    let general = t_keyed_sealed(
        BTreeMap::from([(ak_str("name"), (false, u(t_string()))), (ak_str("age"), (false, u(t_int())))]),
        false,
    );
    assert_atomic_subtype(&lit, &general);
    assert_atomic_not_subtype(&general, &lit);
}

#[test]
fn shaped_array_required_in_optional() {
    let req = t_keyed_sealed(
        BTreeMap::from([(ak_str("name"), (false, u(t_string()))), (ak_str("age"), (false, u(t_int())))]),
        false,
    );
    let opt_age = t_keyed_sealed(
        BTreeMap::from([(ak_str("name"), (false, u(t_string()))), (ak_str("age"), (true, u(t_int())))]),
        false,
    );
    assert_atomic_subtype(&req, &opt_age);
    assert_atomic_not_subtype(&opt_age, &req);
}

#[test]
fn shaped_array_subset_with_optional_extra() {
    let small = t_keyed_sealed(BTreeMap::from([(ak_str("a"), (false, u(t_int())))]), false);
    let big_opt = t_keyed_sealed(
        BTreeMap::from([(ak_str("a"), (false, u(t_int()))), (ak_str("b"), (true, u(t_string())))]),
        false,
    );
    assert_atomic_subtype(&small, &big_opt);
}

#[test]
fn shaped_array_extra_required_not_subtype() {
    let small = t_keyed_sealed(BTreeMap::from([(ak_str("a"), (false, u(t_int())))]), false);
    let big = t_keyed_sealed(
        BTreeMap::from([(ak_str("a"), (false, u(t_int()))), (ak_str("b"), (false, u(t_string())))]),
        false,
    );
    assert_atomic_not_subtype(&small, &big);
}

#[test]
fn nested_shape_with_list_value() {
    let lit = t_keyed_sealed(BTreeMap::from([(ak_str("items"), (false, u(t_list(u(t_lit_int(1)), false))))]), false);
    let general = t_keyed_sealed(BTreeMap::from([(ak_str("items"), (false, u(t_list(u(t_int()), false))))]), false);
    assert_atomic_subtype(&lit, &general);
}

#[test]
fn nested_shape_with_keyed_value() {
    let lit = t_keyed_sealed(
        BTreeMap::from([(
            ak_str("user"),
            (false, u(t_keyed_sealed(BTreeMap::from([(ak_str("name"), (false, us("Alice")))]), false))),
        )]),
        false,
    );
    let general = t_keyed_sealed(
        BTreeMap::from([(
            ak_str("user"),
            (false, u(t_keyed_sealed(BTreeMap::from([(ak_str("name"), (false, u(t_string())))]), false))),
        )]),
        false,
    );
    assert_atomic_subtype(&lit, &general);
    assert_atomic_not_subtype(&general, &lit);
}

#[test]
fn deeply_nested_keyed_shape_three_levels() {
    let make = |inner: mago_codex::ttype::union::TUnion| {
        t_keyed_sealed(
            BTreeMap::from([(
                ak_str("level1"),
                (
                    false,
                    u(t_keyed_sealed(
                        BTreeMap::from([(
                            ak_str("level2"),
                            (false, u(t_keyed_sealed(BTreeMap::from([(ak_str("level3"), (false, inner))]), false))),
                        )]),
                        false,
                    )),
                ),
            )]),
            false,
        )
    };
    let lit = make(ui(42));
    let general = make(u(t_int()));
    assert_atomic_subtype(&lit, &general);
    assert_atomic_not_subtype(&general, &lit);
}

#[test]
fn list_with_known_elements_lit_in_general() {
    let lit = t_sealed_list(BTreeMap::from([(0usize, (false, ui(1))), (1, (false, ui(2))), (2, (false, ui(3)))]));
    let general = t_sealed_list(BTreeMap::from([
        (0usize, (false, u(t_int()))),
        (1, (false, u(t_int()))),
        (2, (false, u(t_int()))),
    ]));
    assert_atomic_subtype(&lit, &general);
}

#[test]
fn list_with_known_elements_in_unsealed_list() {
    let known = t_sealed_list(BTreeMap::from([(0usize, (false, ui(1))), (1, (false, ui(2)))]));
    assert_atomic_subtype(&known, &t_list(u(t_int()), false));
}

#[test]
fn shape_in_unsealed_keyed_array() {
    let s = t_keyed_sealed(
        BTreeMap::from([(ak_str("a"), (false, u(t_int()))), (ak_str("b"), (false, u(t_string())))]),
        false,
    );
    assert_atomic_subtype(&s, &t_keyed_unsealed(u(t_string()), u(t_array_key()), false));
}

#[test]
fn deep_object_with_generic_param() {
    let cb = codebase_from_php("<?php /** @template-covariant T */ class Box {}");
    let lit = t_generic_named("Box", vec![ui(42)]);
    let general = t_generic_named("Box", vec![u(t_int())]);
    assert!(atomic_is_contained(&lit, &general, &cb));
}

#[test]
fn deep_nested_object_in_box() {
    let cb = codebase_from_php("<?php /** @template-covariant T */ class Box {}");
    let inner = t_generic_named("Box", vec![ui(1)]);
    let outer = t_generic_named("Box", vec![u(inner)]);
    let inner_general = t_generic_named("Box", vec![u(t_int())]);
    let outer_general = t_generic_named("Box", vec![u(inner_general)]);
    assert!(atomic_is_contained(&outer, &outer_general, &cb));
}

#[test]
fn list_in_iterable_with_lit_values() {
    let list_lit = t_list(u(t_lit_int(1)), false);
    let iter_int = t_iterable(u(t_int()), u(t_int()));
    assert_atomic_subtype(&list_lit, &iter_int);
}

#[test]
fn array_of_arrays_chain() {
    let inner = u(t_keyed_unsealed(u(t_string()), u(t_int()), false));
    let outer = t_keyed_unsealed(u(t_string()), inner, false);
    assert_atomic_subtype(&outer.clone(), &outer);
}

#[test]
fn many_shape_widths() {
    for n_keys in 1..=5usize {
        let mut lit_map = BTreeMap::new();
        let mut general_map = BTreeMap::new();
        for i in 0..n_keys {
            let key = ak_str(&format!("k{i}"));
            lit_map.insert(key, (false, ui(i as i64)));
            general_map.insert(key, (false, u(t_int())));
        }
        let lit = t_keyed_sealed(lit_map, false);
        let general = t_keyed_sealed(general_map, false);
        assert_atomic_subtype(&lit, &general);
    }
}

#[test]
fn shape_with_string_in_string_lit() {
    for s in ["hello", "world", "foo", "bar"] {
        let lit = t_keyed_sealed(BTreeMap::from([(ak_str("k"), (false, us(s)))]), false);
        let general = t_keyed_sealed(BTreeMap::from([(ak_str("k"), (false, u(t_string())))]), false);
        assert_atomic_subtype(&lit, &general);
    }
}

#[test]
fn list_of_generic_objects() {
    let cb = codebase_from_php("<?php /** @template-covariant T */ class Box {}");
    let inner_lit = t_generic_named("Box", vec![ui(1)]);
    let inner_int = t_generic_named("Box", vec![u(t_int())]);
    let outer_lit = t_list(u(inner_lit), false);
    let outer_int = t_list(u(inner_int), false);
    assert!(atomic_is_contained(&outer_lit, &outer_int, &cb));
}

#[test]
fn keyed_with_object_values() {
    let cb = codebase_from_php("<?php class User {} class Admin extends User {}");
    let admin_keyed = t_keyed_unsealed(u(t_string()), u(t_named("Admin")), false);
    let user_keyed = t_keyed_unsealed(u(t_string()), u(t_named("User")), false);
    assert!(atomic_is_contained(&admin_keyed, &user_keyed, &cb));
    assert!(!atomic_is_contained(&user_keyed, &admin_keyed, &cb));
}

#[test]
fn list_with_class_hierarchy() {
    let cb = codebase_from_php("<?php class A {} class B extends A {}");
    let list_b = t_list(u(t_named("B")), false);
    let list_a = t_list(u(t_named("A")), false);
    assert!(atomic_is_contained(&list_b, &list_a, &cb));
    assert!(!atomic_is_contained(&list_a, &list_b, &cb));
}