#[cfg(test)]
mod tests {
use crate::ast::{ResolvedCommand, RuleEvalMode, sanitize_internal_names};
use crate::{
CommandOutput, EGraph, Error, ProofEncodingUnsupportedReason, TermDag, TermId,
add_primitive_with_validator,
};
fn term_encode(source: &str) -> Vec<ResolvedCommand> {
let mut egraph = crate::EGraph::new_with_term_encoding();
egraph.resolve_program(None, source).unwrap()
}
#[test]
fn unsafe_seminaive_matches_naive() {
let files = [
"tests/calc.egg",
"tests/integer_math.egg",
"tests/fibonacci-demand.egg",
"tests/until.egg",
];
for file in files {
let source = std::fs::read_to_string(file)
.unwrap_or_else(|e| panic!("couldn't read {file}: {e}"));
let encode = |naive: bool| -> String {
let mut egraph = crate::EGraph::new_with_proofs();
egraph.proof_state.force_proof_naive = naive;
egraph
.resolve_program(Some(file.to_string()), &source)
.unwrap_or_else(|e| panic!("{file} resolve (naive={naive}) failed: {e}"))
.iter()
.map(|cmd| cmd.to_string())
.collect::<Vec<_>>()
.join("\n")
};
assert!(
encode(false).contains(":unsafe-seminaive") && encode(false) != encode(true),
"expected {file} to exercise the `:unsafe-seminaive` encoding path"
);
let program = format!("{source}\n(print-size)");
let run = |naive: bool| -> Vec<CommandOutput> {
let mut egraph = crate::EGraph::new_with_proofs();
egraph.proof_state.force_proof_naive = naive;
egraph
.parse_and_run_program(Some(file.to_string()), &program)
.unwrap_or_else(|e| panic!("{file} (naive={naive}) failed: {e}"))
};
let unsafe_seminaive = CommandOutput::snapshot_stable_under_proof_encoding(&run(false));
let naive = CommandOutput::snapshot_stable_under_proof_encoding(&run(true));
assert_eq!(
unsafe_seminaive, naive,
":unsafe-seminaive and :naive proof encodings disagree for {file}"
);
}
}
#[test]
fn proof_encoding_preserves_naive() {
let cases = [
r#"(relation r (i64))
(relation s (i64))
(rule ((r x)) ((s x)) :naive :name "keep")"#,
r#"(sort Math)
(constructor Num (i64) Math)
(constructor Neg (Math) Math)
(relation seen (Math))
(rule ((Neg m)) ((seen m)) :naive :name "keep")"#,
];
for source in cases {
let mut egraph = crate::EGraph::new_with_proofs();
let resolved = egraph.resolve_program(None, source).unwrap();
let rule = resolved
.iter()
.find_map(|c| match c {
ResolvedCommand::Rule { rule } if rule.name == "keep" => Some(rule),
_ => None,
})
.expect("instrumented rule not found");
assert_eq!(
rule.eval_mode,
RuleEvalMode::Naive,
"proof encoding did not preserve :naive for:\n{source}"
);
}
}
#[test]
fn proof_mode_allows_eq_sort_primitive_results_in_facts() {
let mut egraph = EGraph::default();
let validator =
|_: &mut TermDag, args: &[TermId]| -> Option<TermId> { args.first().copied() };
add_primitive_with_validator!(
&mut egraph,
"proof-id" = |x: #| -> # { x },
validator
);
let mut egraph = egraph.with_proofs_enabled();
egraph
.parse_and_run_program(
None,
r#"
(datatype Math
(Done)
(Num i64))
(relation Seed (Math))
(Seed (Num 1))
(rule ((Seed y)
(= x (proof-id y)))
((Done))
:name "use-proof-id")
(run 1)
(prove (Done))
"#,
)
.unwrap();
}
#[test]
fn proof_support_rejects_naive_eq_sort_primitive_results_in_facts() {
let mut egraph = EGraph::default();
let validator =
|_: &mut TermDag, args: &[TermId]| -> Option<TermId> { args.first().copied() };
add_primitive_with_validator!(
&mut egraph,
"proof-id" = |x: #| -> # { x },
validator
);
let mut egraph = egraph.with_proofs_enabled();
let err = egraph
.parse_and_run_program(
None,
r#"
(datatype Math
(Done)
(Num i64))
(relation Seed (Math))
(rule ((Seed y)
(= x (proof-id y)))
((Done))
:naive
:name "naive-use-proof-id")
"#,
)
.unwrap_err();
assert!(
matches!(
err,
Error::UnsupportedProofCommand {
reason: ProofEncodingUnsupportedReason::NaiveEqSortPrimitiveFact,
..
}
),
"expected NaiveEqSortPrimitiveFact, got {err:?}"
);
}
#[test]
fn proof_mode_allows_eq_container_primitive_results_in_facts() {
let mut egraph = EGraph::new_with_proofs();
egraph
.parse_and_run_program(
None,
r#"
(datatype E (Mk))
(sort EqContainer (Vec E))
"#,
)
.unwrap();
let eq_container_sort = egraph
.type_info
.get_sort_by_name("EqContainer")
.expect("EqContainer sort")
.clone();
let validator =
|_: &mut TermDag, args: &[TermId]| -> Option<TermId> { args.first().copied() };
add_primitive_with_validator!(
&mut egraph,
"proof-container-id" = |x: # (eq_container_sort)| -> # (eq_container_sort) { x },
validator
);
let validator =
|_: &mut TermDag, args: &[TermId]| -> Option<TermId> { args.first().copied() };
let original_typechecking = egraph.proof_state.original_typechecking.as_mut().unwrap();
add_primitive_with_validator!(
&mut **original_typechecking,
"proof-container-id" = |x: # (eq_container_sort)| -> # (eq_container_sort) { x },
validator
);
egraph
.parse_and_run_program(
None,
r#"
(relation SeedContainer (EqContainer))
(relation Done ())
(SeedContainer (vec-of (Mk)))
(rule ((SeedContainer ys)
(= xs (proof-container-id ys)))
((Done))
:name "use-proof-container-id")
(run 1)
(prove (Done))
"#,
)
.unwrap();
}
#[test]
fn proof_mode_query_constructed_container_not_used_in_action() {
let mut egraph = EGraph::new_with_proofs();
egraph
.parse_and_run_program(
None,
r#"
(datatype E (Mk))
(sort EqContainer (Vec E))
(relation SeedElem (E))
(relation Done ())
(SeedElem (Mk))
(rule ((SeedElem e)
(= xs (vec-of e)))
((Done))
:name "new-container-in-body")
(run 1)
(prove (Done))
"#,
)
.unwrap();
}
#[test]
fn proof_support_rejects_query_constructed_container_used_in_action() {
let mut egraph = EGraph::new_with_proofs();
let err = egraph
.parse_and_run_program(
None,
r#"
(datatype E (Mk))
(sort EqContainer (Vec E))
(relation SeedElem (E))
(relation Out (EqContainer))
(rule ((SeedElem e)
(= xs (vec-of e)))
((Out xs))
:name "new-container-in-action")
"#,
)
.unwrap_err();
assert!(
matches!(
err,
Error::UnsupportedProofCommand {
reason: ProofEncodingUnsupportedReason::ContainerCreatedInQueryUsedInAction,
..
}
),
"expected ContainerCreatedInQueryUsedInAction, got {err:?}"
);
}
#[test]
fn doc_example_add_function2() {
let commands = term_encode(
r#"
(function add (i64 i64) i64 :merge old)
(check (= (add 0 0) 0))
"#,
);
let snapshot = sanitize_internal_names(&commands)
.iter()
.map(|cmd| cmd.to_string())
.collect::<Vec<_>>()
.join("\n");
insta::assert_snapshot!("doc_example_add_function2", snapshot);
}
#[test]
fn doc_example_add_function1() {
let commands = term_encode(
r#"
(sort Math)
(constructor Add (i64 i64) Math)
(Add 1 2)
(rule ((Add a b))
((union (Add a b) (Add b a)))
:name "commutativity")
(check (= (Add 1 2) (Add 2 1)))
"#,
);
let snapshot = sanitize_internal_names(&commands)
.iter()
.map(|cmd| cmd.to_string())
.collect::<Vec<_>>()
.join("\n");
insta::assert_snapshot!("doc_example_add_function1", snapshot);
}
}