solverforge-macros 0.12.1

Derive macros for SolverForge constraint solver
Documentation
use crate::planning_entity::expand_derive;
use syn::parse_quote;

#[test]
fn golden_entity_expansion_includes_descriptor_and_planning_id() {
    let input = parse_quote! {
        struct Task {
            #[planning_id]
            id: String,
            #[planning_variable(allows_unassigned = true, value_range_provider = "workers")]
            worker_idx: Option<usize>,
            #[planning_list_variable(element_collection = "all_tasks")]
            chain: Vec<usize>,
        }
    };

    let expanded = expand_derive(input)
        .expect("entity expansion should succeed")
        .to_string();

    assert!(expanded.contains("impl :: solverforge :: __internal :: PlanningEntity for Task"));
    assert!(expanded.contains("impl :: solverforge :: __internal :: PlanningId for Task"));
    assert!(expanded.contains("with_allows_unassigned (true)"));
    assert!(expanded.contains("with_value_range (\"workers\")"));
    assert!(expanded.contains("with_id_field (stringify ! (id))"));
    assert!(expanded.contains("pub fn entity_descriptor"));
    assert!(expanded.contains("pub const __SOLVERFORGE_LIST_VARIABLE_COUNT : usize = 1"));
    assert!(expanded
        .contains("const __SOLVERFORGE_LIST_ELEMENT_COLLECTION : & 'static str = \"all_tasks\""));
    assert!(expanded.contains("pub (crate) fn __solverforge_get_worker_idx_scalar"));
    assert!(expanded.contains("pub (crate) fn __solverforge_set_worker_idx_scalar"));
    assert!(expanded.contains("const HAS_LIST_VARIABLE : bool = true"));
    assert!(expanded.contains("LIST_ELEMENT_SOURCE"));
    assert!(expanded.contains("fn __solverforge_list_metadata < Solution >"));
    assert!(expanded.contains("UnassignedEntity < __SolverForgeSolution > for Task"));
    assert!(expanded.contains("fn is_unassigned"));
}

#[test]
fn entity_descriptor_preserves_mixed_variable_declaration_order() {
    let input = parse_quote! {
        struct Route {
            #[planning_id]
            id: String,
            #[planning_list_variable(element_collection = "all_tasks")]
            chain: Vec<usize>,
            #[planning_variable(allows_unassigned = true, value_range_provider = "workers")]
            worker_idx: Option<usize>,
            #[planning_variable(allows_unassigned = true, value_range_provider = "workers")]
            backup_idx: Option<usize>,
        }
    };

    let expanded = expand_derive(input)
        .expect("entity expansion should succeed")
        .to_string();

    let chain_pos = expanded
        .find("VariableDescriptor :: list (\"chain\")")
        .expect("list variable descriptor should exist");
    let worker_pos = expanded
        .find("VariableDescriptor :: genuine (\"worker_idx\")")
        .expect("worker variable descriptor should exist");
    let backup_pos = expanded
        .find("VariableDescriptor :: genuine (\"backup_idx\")")
        .expect("backup variable descriptor should exist");

    assert!(chain_pos < worker_pos);
    assert!(worker_pos < backup_pos);

    assert!(expanded.contains("fn __solverforge_scalar_variable_count"));
    assert!(expanded.contains("fn __solverforge_scalar_get_by_index"));
    assert!(expanded.contains("fn __solverforge_scalar_set_by_index"));

    let name_helper_pos = expanded
        .find("fn __solverforge_scalar_variable_name_by_index")
        .expect("indexed scalar name helper should exist");
    let name_helper_end = expanded[name_helper_pos..]
        .find("fn __solverforge_scalar_allows_unassigned_by_index")
        .map(|offset| name_helper_pos + offset)
        .expect("next scalar helper should exist");
    let name_helper = &expanded[name_helper_pos..name_helper_end];
    let worker_name_pos = name_helper
        .find("\"worker_idx\"")
        .expect("worker scalar index arm should exist");
    let backup_name_pos = name_helper
        .find("\"backup_idx\"")
        .expect("backup scalar index arm should exist");
    assert!(worker_name_pos < backup_name_pos);
}

#[test]
fn chained_option_usize_does_not_enter_scalar_helper_indexing() {
    let input = parse_quote! {
        struct Task {
            #[planning_id]
            id: String,
            #[planning_variable(chained = true, value_range_provider = "tasks")]
            previous: Option<usize>,
            #[planning_variable(allows_unassigned = true, value_range_provider = "workers")]
            worker: Option<usize>,
        }
    };

    let expanded = expand_derive(input)
        .expect("entity expansion should succeed")
        .to_string();

    assert!(expanded.contains("VariableDescriptor :: chained (\"previous\")"));
    assert!(expanded.contains("VariableDescriptor :: genuine (\"worker\")"));
    assert!(!expanded.contains("__solverforge_get_previous_scalar"));
    assert!(!expanded.contains("__solverforge_set_previous_scalar"));
    assert!(expanded.contains("__solverforge_get_worker_scalar"));
    assert!(expanded.contains("__solverforge_set_worker_scalar"));
    assert!(expanded.contains(
        "pub (crate) const fn __solverforge_scalar_variable_count () -> usize { 1usize }"
    ));

    let name_helper_pos = expanded
        .find("fn __solverforge_scalar_variable_name_by_index")
        .expect("indexed scalar name helper should exist");
    let name_helper_end = expanded[name_helper_pos..]
        .find("fn __solverforge_scalar_allows_unassigned_by_index")
        .map(|offset| name_helper_pos + offset)
        .expect("next scalar helper should exist");
    let name_helper = &expanded[name_helper_pos..name_helper_end];
    assert!(name_helper.contains("0usize => :: core :: option :: Option :: Some (\"worker\")"));
    assert!(!name_helper.contains("\"previous\""));
}