sim-lib-class 0.1.0

Checked class descriptors and inheritance semantics for SIM.
Documentation
use std::collections::BTreeSet;

use super::*;

use sim_kernel::{
    Cx, DefaultFactory, Expr, MatchScore, NoopEvalPolicy, Shape, ShapeDoc, ShapeMatch, Symbol,
    Value,
};
use std::sync::Arc;

struct AnyShape;
impl Shape for AnyShape {
    fn check_value(&self, _cx: &mut Cx, _value: Value) -> sim_kernel::Result<ShapeMatch> {
        Ok(ShapeMatch::accept(MatchScore::exact(1)))
    }
    fn check_expr(&self, _cx: &mut Cx, _expr: &Expr) -> sim_kernel::Result<ShapeMatch> {
        Ok(ShapeMatch::accept(MatchScore::exact(1)))
    }
    fn describe(&self, _cx: &mut Cx) -> sim_kernel::Result<ShapeDoc> {
        Ok(ShapeDoc::new("any"))
    }
}

fn descriptor_class(cx: &mut Cx, id: u32, name: &str, parents: Vec<DeclaredParent>) -> Value {
    let shape = cx.factory().opaque(Arc::new(AnyShape)).unwrap();
    let descriptor = ClassDescriptor::new(ClassDescriptorInput {
        identity: ClassIdentity::checked(sim_kernel::ClassId(id), Symbol::qualified("test", name))
            .unwrap(),
        parents,
        constructor_shape: shape.clone(),
        instance_shape: shape,
        members: Vec::new(),
        read_construction: None,
        metadata: Vec::new(),
    })
    .unwrap();
    cx.factory()
        .opaque(Arc::new(DescriptorClass::new(
            descriptor,
            |cx: &mut Cx, _| cx.factory().string("constructed".into()),
            32,
            32,
        )))
        .unwrap()
}

#[test]
fn descriptor_projects_kernel_class_and_bounded_subclass_evidence() {
    let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
    let root = descriptor_class(&mut cx, 7000, "Root", Vec::new());
    let root_identity =
        ClassIdentity::checked(sim_kernel::ClassId(7000), Symbol::qualified("test", "Root"))
            .unwrap();
    let leaf = descriptor_class(
        &mut cx,
        7001,
        "Leaf",
        vec![DeclaredParent::resolved(root_identity, root.clone())],
    );
    let leaf_class = leaf.object().as_class().unwrap();
    assert_eq!(leaf_class.symbol(), Symbol::qualified("test", "Leaf"));
    assert_eq!(
        leaf.object()
            .as_callable()
            .unwrap()
            .call(&mut cx, sim_kernel::Args::default())
            .unwrap()
            .object()
            .as_expr(&mut cx)
            .unwrap(),
        Expr::String("constructed".into())
    );

    let descriptor = leaf.object().downcast_ref::<DescriptorClass>().unwrap();
    assert!(matches!(
        descriptor
            .query_subclass(&mut cx, root.clone(), 2, 1)
            .unwrap(),
        SubclassQuery::Answer {
            is_subclass: true,
            evidence: SubclassEvidence {
                visited_nodes: 2,
                performed_work: 1
            }
        }
    ));
    assert!(matches!(
        descriptor.query_subclass(&mut cx, root, 1, 1).unwrap(),
        SubclassQuery::NodeBudgetExhausted {
            limit: 1,
            required: 2,
            evidence: SubclassEvidence {
                visited_nodes: 1,
                performed_work: 1
            }
        }
    ));
}

#[test]
fn every_excluded_candidate_has_an_exact_machine_reason() {
    let excluded = candidate_inventory()
        .iter()
        .filter(|row| row.disposition == CandidateDisposition::Exclude)
        .map(|row| row.model)
        .collect::<BTreeSet<_>>();
    let reasons = exclusion_ledger()
        .iter()
        .map(|row| {
            assert_eq!(row.required, ParentMeaning::DeclaredSuperclass);
            assert_ne!(row.actual, row.required);
            assert!(!row.mismatch_code.is_empty());
            row.model
        })
        .collect::<BTreeSet<_>>();
    assert_eq!(excluded, reasons);
}

#[test]
fn every_exclusion_has_a_precise_non_goal() {
    let exclusions = exclusion_ledger()
        .iter()
        .map(|row| row.model)
        .collect::<BTreeSet<_>>();
    let goals = non_goals()
        .iter()
        .map(|row| {
            assert!(!row.excluded_semantics.is_empty());
            row.model
        })
        .collect::<BTreeSet<_>>();
    assert_eq!(exclusions, goals);
}

#[test]
fn characterize_1_covers_success_and_every_failure_mode() {
    let scenarios = characterization_scenarios();
    for required in [
        ScenarioKind::C3Resolution,
        ScenarioKind::DeclaredParentTraversal,
        ScenarioKind::SubclassTest,
        ScenarioKind::ReadConstruction,
    ] {
        assert!(scenarios.iter().any(|scenario| scenario.kind == required));
    }
    for failure in [
        FailureMode::UnknownParent,
        FailureMode::DuplicateParent,
        FailureMode::InconsistentC3,
        FailureMode::ParentCycle,
        FailureMode::ReadConstructorMissing,
        FailureMode::ReadShapeMismatch,
    ] {
        assert!(
            scenarios
                .iter()
                .any(|scenario| scenario.kind == ScenarioKind::Failure(failure))
        );
    }
}

#[test]
fn characterize_1_replays_to_identical_content_ids() {
    let first = characterization_scenarios()
        .iter()
        .map(scenario_content_id)
        .collect::<Vec<_>>();
    let replay = characterization_scenarios()
        .iter()
        .map(scenario_content_id)
        .collect::<Vec<_>>();
    assert_eq!(first, replay);
    assert_eq!(first.iter().collect::<BTreeSet<_>>().len(), first.len());
}