geolog-zeta-fork/tests/unit_elaborate.rs
2026-02-26 11:50:51 +01:00

838 lines
27 KiB
Rust

//! Unit tests for theory and instance elaboration
use geolog::ast;
use geolog::core::DerivedSort;
use geolog::elaborate::{ElabError, ElaborationContext, Env, elaborate_instance_ctx, elaborate_theory};
use geolog::id::{NumericId, Slid};
use geolog::parse;
use geolog::repl::InstanceEntry;
use geolog::universe::Universe;
use std::collections::HashMap;
use std::rc::Rc;
#[test]
fn test_elaborate_simple_theory() {
let input = r#"
theory PetriNet {
P : Sort;
T : Sort;
src : P -> T;
}
"#;
let file = parse(input).expect("parse failed");
let mut env = Env::new();
if let ast::Declaration::Theory(t) = &file.declarations[0].node {
let elab = elaborate_theory(&mut env, t).expect("elaboration failed");
assert_eq!(elab.theory.name, "PetriNet");
assert_eq!(elab.theory.signature.sorts.len(), 2);
assert_eq!(elab.theory.signature.functions.len(), 1);
} else {
panic!("expected theory");
}
}
#[test]
fn test_elaborate_parameterized_theory() {
let input = r#"
theory PetriNet {
P : Sort;
T : Sort;
}
theory (N : PetriNet instance) Marking {
token : Sort;
token/of : token -> N/P;
}
"#;
let file = parse(input).expect("parse failed");
let mut env = Env::new();
// First elaborate PetriNet
if let ast::Declaration::Theory(t) = &file.declarations[0].node {
let elab = elaborate_theory(&mut env, t).expect("elaboration failed");
env.theories.insert(elab.theory.name.clone(), Rc::new(elab));
}
// Then elaborate Marking (which depends on PetriNet)
if let ast::Declaration::Theory(t) = &file.declarations[1].node {
let elab = elaborate_theory(&mut env, t).expect("elaboration failed");
assert_eq!(elab.theory.name, "Marking");
assert_eq!(elab.params.len(), 1);
assert_eq!(elab.params[0].name, "N");
assert_eq!(elab.params[0].theory_name, "PetriNet");
// Signature now includes param sorts: N/P, N/T (from PetriNet) + token (local)
assert_eq!(elab.theory.signature.sorts.len(), 3);
assert!(elab.theory.signature.lookup_sort("N/P").is_some());
assert!(elab.theory.signature.lookup_sort("N/T").is_some());
assert!(elab.theory.signature.lookup_sort("token").is_some());
// Functions: just token/of (PetriNet had no functions in this test)
assert_eq!(elab.theory.signature.functions.len(), 1);
assert!(elab.theory.signature.lookup_func("token/of").is_some());
} else {
panic!("expected theory");
}
}
#[test]
fn test_elaborate_theory_with_axiom() {
let input = r#"
theory Iso {
X : Sort;
Y : Sort;
fwd : X -> Y;
bwd : Y -> X;
fb : forall x : X. |- x fwd bwd = x;
}
"#;
let file = parse(input).expect("parse failed");
let mut env = Env::new();
if let ast::Declaration::Theory(t) = &file.declarations[0].node {
let elab = elaborate_theory(&mut env, t).expect("elaboration failed");
assert_eq!(elab.theory.name, "Iso");
assert_eq!(elab.theory.signature.sorts.len(), 2);
assert_eq!(elab.theory.signature.functions.len(), 2);
assert_eq!(elab.theory.axioms.len(), 1);
// Check the axiom structure
let ax = &elab.theory.axioms[0];
assert_eq!(ax.context.vars.len(), 1);
assert_eq!(ax.context.vars[0].0, "x");
} else {
panic!("expected theory");
}
}
#[test]
fn test_axiom_function_type_error() {
// x is of sort X, but bwd expects Y
let input = r#"
theory BadIso {
X : Sort;
Y : Sort;
fwd : X -> Y;
bwd : Y -> X;
bad : forall x : X. |- x bwd = x;
}
"#;
let file = parse(input).expect("parse failed");
let mut env = Env::new();
if let ast::Declaration::Theory(t) = &file.declarations[0].node {
let result = elaborate_theory(&mut env, t);
assert!(result.is_err(), "expected type error in axiom");
let err = result.unwrap_err();
match err {
ElabError::TypeMismatch { expected, got } => {
// expected Y (bwd's domain), got X
assert_eq!(expected, DerivedSort::Base(1)); // Y
assert_eq!(got, DerivedSort::Base(0)); // X
}
other => panic!("expected TypeMismatch error, got: {}", other),
}
} else {
panic!("expected theory");
}
}
#[test]
fn test_axiom_equality_type_error() {
// LHS is X, RHS is Y — can't compare different sorts
let input = r#"
theory BadEq {
X : Sort;
Y : Sort;
fwd : X -> Y;
bad : forall x : X. |- x = x fwd;
}
"#;
let file = parse(input).expect("parse failed");
let mut env = Env::new();
if let ast::Declaration::Theory(t) = &file.declarations[0].node {
let result = elaborate_theory(&mut env, t);
assert!(result.is_err(), "expected type error in equality");
let err = result.unwrap_err();
match err {
ElabError::TypeMismatch { expected, got } => {
// LHS is X, RHS is Y
assert_eq!(expected, DerivedSort::Base(0)); // X
assert_eq!(got, DerivedSort::Base(1)); // Y
}
other => panic!("expected TypeMismatch error, got: {}", other),
}
} else {
panic!("expected theory");
}
}
#[test]
fn test_elaborate_instance() {
let input = r#"
theory PetriNet {
P : Sort;
T : Sort;
in : Sort;
out : Sort;
in/src : in -> P;
in/tgt : in -> T;
out/src : out -> T;
out/tgt : out -> P;
}
instance ExampleNet : PetriNet = {
A : P;
B : P;
C : P;
ab : T;
ab_in : in;
ab_in in/src = A;
ab_in in/tgt = ab;
ab_out : out;
ab_out out/src = ab;
ab_out out/tgt = B;
}
"#;
let file = parse(input).expect("parse failed");
let mut env = Env::new();
let mut universe = Universe::new();
// First elaborate PetriNet theory
if let ast::Declaration::Theory(t) = &file.declarations[0].node {
let elab = elaborate_theory(&mut env, t).expect("theory elaboration failed");
env.theories.insert(elab.theory.name.clone(), Rc::new(elab));
}
// Then elaborate ExampleNet instance
if let ast::Declaration::Instance(inst) = &file.declarations[1].node {
let instances: HashMap<String, InstanceEntry> = HashMap::new();
let mut ctx = ElaborationContext {
theories: &env.theories,
instances: &instances,
universe: &mut universe,
siblings: HashMap::new(),
};
let result =
elaborate_instance_ctx(&mut ctx, inst).expect("instance elaboration failed");
let structure = result.structure;
// Elements are created in order: A(0), B(1), C(2), ab(3), ab_in(4), ab_out(5)
assert_eq!(structure.len(), 6); // A, B, C, ab, ab_in, ab_out
// Check carriers
assert_eq!(structure.carrier_size(0), 3); // P: A, B, C
assert_eq!(structure.carrier_size(1), 1); // T: ab
assert_eq!(structure.carrier_size(2), 1); // in: ab_in
assert_eq!(structure.carrier_size(3), 1); // out: ab_out
// Check function definitions using the new columnar API
// Elements by slid: A=0, B=1, C=2, ab=3, ab_in=4, ab_out=5
let a_slid = Slid::from_usize(0);
let ab_slid = Slid::from_usize(3);
let ab_in_slid = Slid::from_usize(4);
// Get the sort-local ID for ab_in
let ab_in_sort_slid = structure.sort_local_id(ab_in_slid);
// in/src is function 0, ab_in maps to A
assert_eq!(structure.get_function(0, ab_in_sort_slid), Some(a_slid));
// in/tgt is function 1, ab_in maps to ab
assert_eq!(structure.get_function(1, ab_in_sort_slid), Some(ab_slid));
} else {
panic!("expected instance");
}
}
#[test]
fn test_partial_function_error() {
// This instance is missing the definition for ab_in in/tgt
// (ab_in is in the domain of in/tgt but has no value defined)
let input = r#"
theory PetriNet {
P : Sort;
T : Sort;
in : Sort;
in/src : in -> P;
in/tgt : in -> T;
}
instance PartialNet : PetriNet = {
A : P;
ab : T;
ab_in : in;
ab_in in/src = A;
// Missing: ab_in in/tgt = ab;
}
"#;
let file = parse(input).expect("parse failed");
let mut env = Env::new();
let mut universe = Universe::new();
// First elaborate PetriNet theory
if let ast::Declaration::Theory(t) = &file.declarations[0].node {
let elab = elaborate_theory(&mut env, t).expect("theory elaboration failed");
env.theories.insert(elab.theory.name.clone(), Rc::new(elab));
}
// Then try to elaborate the partial instance — should fail
if let ast::Declaration::Instance(i) = &file.declarations[1].node {
let instances: HashMap<String, InstanceEntry> = HashMap::new();
let mut ctx = ElaborationContext {
theories: &env.theories,
instances: &instances,
universe: &mut universe,
siblings: HashMap::new(),
};
let result = elaborate_instance_ctx(&mut ctx, i);
assert!(result.is_err(), "expected error for partial function");
let err = result.unwrap_err();
match err {
ElabError::PartialFunction {
func_name,
missing_elements,
} => {
assert_eq!(func_name, "in/tgt");
assert_eq!(missing_elements, vec!["ab_in"]);
}
other => panic!("expected PartialFunction error, got: {}", other),
}
} else {
panic!("expected instance");
}
}
#[test]
fn test_domain_type_error() {
// ab is of sort T, but in/src expects domain sort `in`
let input = r#"
theory PetriNet {
P : Sort;
T : Sort;
in : Sort;
in/src : in -> P;
}
instance BadNet : PetriNet = {
A : P;
ab : T;
ab in/src = A;
}
"#;
let file = parse(input).expect("parse failed");
let mut env = Env::new();
let mut universe = Universe::new();
if let ast::Declaration::Theory(t) = &file.declarations[0].node {
let elab = elaborate_theory(&mut env, t).expect("theory elaboration failed");
env.theories.insert(elab.theory.name.clone(), Rc::new(elab));
}
if let ast::Declaration::Instance(i) = &file.declarations[1].node {
let instances: HashMap<String, InstanceEntry> = HashMap::new();
let mut ctx = ElaborationContext {
theories: &env.theories,
instances: &instances,
universe: &mut universe,
siblings: HashMap::new(),
};
let result = elaborate_instance_ctx(&mut ctx, i);
assert!(result.is_err(), "expected domain type error");
let err = result.unwrap_err();
match err {
ElabError::DomainMismatch {
func_name,
element_name,
expected_sort,
actual_sort,
} => {
assert_eq!(func_name, "in/src");
assert_eq!(element_name, "ab");
assert_eq!(expected_sort, "in");
assert_eq!(actual_sort, "T");
}
other => panic!("expected DomainMismatch error, got: {}", other),
}
} else {
panic!("expected instance");
}
}
#[test]
fn test_codomain_type_error() {
// ab is of sort T, but in/src has codomain P
let input = r#"
theory PetriNet {
P : Sort;
T : Sort;
in : Sort;
in/src : in -> P;
}
instance BadNet : PetriNet = {
A : P;
ab : T;
ab_in : in;
ab_in in/src = ab;
}
"#;
let file = parse(input).expect("parse failed");
let mut env = Env::new();
let mut universe = Universe::new();
if let ast::Declaration::Theory(t) = &file.declarations[0].node {
let elab = elaborate_theory(&mut env, t).expect("theory elaboration failed");
env.theories.insert(elab.theory.name.clone(), Rc::new(elab));
}
if let ast::Declaration::Instance(i) = &file.declarations[1].node {
let instances: HashMap<String, InstanceEntry> = HashMap::new();
let mut ctx = ElaborationContext {
theories: &env.theories,
instances: &instances,
universe: &mut universe,
siblings: HashMap::new(),
};
let result = elaborate_instance_ctx(&mut ctx, i);
assert!(result.is_err(), "expected codomain type error");
let err = result.unwrap_err();
match err {
ElabError::CodomainMismatch {
func_name,
element_name,
expected_sort,
actual_sort,
} => {
assert_eq!(func_name, "in/src");
assert_eq!(element_name, "ab");
assert_eq!(expected_sort, "P");
assert_eq!(actual_sort, "T");
}
other => panic!("expected CodomainMismatch error, got: {}", other),
}
} else {
panic!("expected instance");
}
}
#[test]
fn test_elaborate_theory_extends() {
// Simple single-level extends
let input = r#"
theory Base {
X : Sort;
f : X -> X;
}
theory Child extends Base {
Y : Sort;
g : Y -> Base/X;
}
"#;
let file = parse(input).expect("parse failed");
let mut env = Env::new();
// Elaborate Base
if let ast::Declaration::Theory(t) = &file.declarations[0].node {
let elab = elaborate_theory(&mut env, t).expect("Base elaboration failed");
env.theories.insert(elab.theory.name.clone(), Rc::new(elab));
}
// Elaborate Child (extends Base)
if let ast::Declaration::Theory(t) = &file.declarations[1].node {
let elab = elaborate_theory(&mut env, t).expect("Child elaboration failed");
assert_eq!(elab.theory.name, "Child");
// Child should have: Base/X (inherited), Y (own)
assert_eq!(elab.theory.signature.sorts.len(), 2);
assert!(
elab.theory.signature.lookup_sort("Base/X").is_some(),
"should have Base/X"
);
assert!(
elab.theory.signature.lookup_sort("Y").is_some(),
"should have Y"
);
// Functions: Base/f (inherited), g (own)
assert_eq!(elab.theory.signature.functions.len(), 2);
assert!(
elab.theory.signature.lookup_func("Base/f").is_some(),
"should have Base/f"
);
assert!(
elab.theory.signature.lookup_func("g").is_some(),
"should have g"
);
// Check g's domain/codomain are correct
let g_id = elab.theory.signature.lookup_func("g").unwrap();
let g_sym = &elab.theory.signature.functions[g_id];
let y_id = elab.theory.signature.lookup_sort("Y").unwrap();
let base_x_id = elab.theory.signature.lookup_sort("Base/X").unwrap();
assert_eq!(g_sym.domain, DerivedSort::Base(y_id));
assert_eq!(g_sym.codomain, DerivedSort::Base(base_x_id));
} else {
panic!("expected theory");
}
}
#[test]
fn test_elaborate_transitive_extends() {
// Transitive extends with requalification:
// Grandchild extends Child extends Base
// Grandchild should have: Base/X (from grandparent, NOT Child/Base/X), Child/Y, Z
let input = r#"
theory Base {
X : Sort;
f : X -> X;
}
theory Child extends Base {
Y : Sort;
}
theory Grandchild extends Child {
Z : Sort;
h : Z -> Base/X;
}
"#;
let file = parse(input).expect("parse failed");
let mut env = Env::new();
// Elaborate Base
if let ast::Declaration::Theory(t) = &file.declarations[0].node {
let elab = elaborate_theory(&mut env, t).expect("Base elaboration failed");
env.theories.insert(elab.theory.name.clone(), Rc::new(elab));
}
// Elaborate Child
if let ast::Declaration::Theory(t) = &file.declarations[1].node {
let elab = elaborate_theory(&mut env, t).expect("Child elaboration failed");
env.theories.insert(elab.theory.name.clone(), Rc::new(elab));
}
// Elaborate Grandchild
if let ast::Declaration::Theory(t) = &file.declarations[2].node {
let elab = elaborate_theory(&mut env, t).expect("Grandchild elaboration failed");
assert_eq!(elab.theory.name, "Grandchild");
// Grandchild should have: Base/X, Child/Y, Z
// NOT: Child/Base/X (that would be wrong requalification)
assert_eq!(elab.theory.signature.sorts.len(), 3);
assert!(
elab.theory.signature.lookup_sort("Base/X").is_some(),
"should have Base/X (preserved from grandparent)"
);
assert!(
elab.theory.signature.lookup_sort("Child/Y").is_some(),
"should have Child/Y"
);
assert!(
elab.theory.signature.lookup_sort("Z").is_some(),
"should have Z"
);
// Should NOT have these wrong names
assert!(
elab.theory.signature.lookup_sort("Child/Base/X").is_none(),
"should NOT have Child/Base/X"
);
// Functions: Base/f (preserved), h (own)
assert_eq!(elab.theory.signature.functions.len(), 2);
assert!(
elab.theory.signature.lookup_func("Base/f").is_some(),
"should have Base/f (preserved)"
);
assert!(
elab.theory.signature.lookup_func("h").is_some(),
"should have h"
);
// Check h's domain/codomain
let h_id = elab.theory.signature.lookup_func("h").unwrap();
let h_sym = &elab.theory.signature.functions[h_id];
let z_id = elab.theory.signature.lookup_sort("Z").unwrap();
let base_x_id = elab.theory.signature.lookup_sort("Base/X").unwrap();
assert_eq!(h_sym.domain, DerivedSort::Base(z_id));
assert_eq!(h_sym.codomain, DerivedSort::Base(base_x_id));
} else {
panic!("expected theory");
}
}
#[test]
fn test_instance_of_extended_theory() {
// Test that instances of extended theories work correctly
let input = r#"
theory Base {
X : Sort;
}
theory Child extends Base {
Y : Sort;
f : Y -> Base/X;
}
instance C : Child = {
a : Base/X;
b : Y;
b f = a;
}
"#;
let file = parse(input).expect("parse failed");
let mut env = Env::new();
let mut universe = Universe::new();
// Elaborate theories
for decl in &file.declarations[0..2] {
if let ast::Declaration::Theory(t) = &decl.node {
let elab = elaborate_theory(&mut env, t).expect("theory elaboration failed");
env.theories.insert(elab.theory.name.clone(), Rc::new(elab));
}
}
// Elaborate instance
if let ast::Declaration::Instance(inst) = &file.declarations[2].node {
let instances: HashMap<String, InstanceEntry> = HashMap::new();
let mut ctx = ElaborationContext {
theories: &env.theories,
instances: &instances,
universe: &mut universe,
siblings: HashMap::new(),
};
let result =
elaborate_instance_ctx(&mut ctx, inst).expect("instance elaboration failed");
let structure = result.structure;
// Should have 2 elements: a and b
assert_eq!(structure.len(), 2);
assert_eq!(structure.carrier_size(0), 1); // Base/X: a
assert_eq!(structure.carrier_size(1), 1); // Y: b
// Check name mappings
assert!(
result.name_to_slid.contains_key("a"),
"should have element 'a'"
);
assert!(
result.name_to_slid.contains_key("b"),
"should have element 'b'"
);
} else {
panic!("expected instance");
}
}
#[test]
fn test_nested_parameterized_theories() {
// Test deep nesting: C depends on B which depends on A
// theory A { X : Sort; }
// theory (N : A instance) B { Y : Sort; f : Y -> N/X; }
// theory (M : B instance) C { Z : Sort; g : Z -> M/Y; h : Z -> M/N/X; }
//
// C should have sorts: M/N/X (from A via B), M/Y (from B), Z (own)
let input = r#"
theory A { X : Sort; }
theory (N : A instance) B {
Y : Sort;
f : Y -> N/X;
}
theory (M : B instance) C {
Z : Sort;
g : Z -> M/Y;
h : Z -> M/N/X;
}
"#;
let file = parse(input).expect("parse failed");
let mut env = Env::new();
// Elaborate A
if let ast::Declaration::Theory(t) = &file.declarations[0].node {
let elab = elaborate_theory(&mut env, t).expect("A elaboration failed");
env.theories.insert(elab.theory.name.clone(), Rc::new(elab));
}
// Elaborate B
if let ast::Declaration::Theory(t) = &file.declarations[1].node {
let elab = elaborate_theory(&mut env, t).expect("B elaboration failed");
assert_eq!(elab.theory.signature.sorts.len(), 2);
assert!(elab.theory.signature.lookup_sort("N/X").is_some());
assert!(elab.theory.signature.lookup_sort("Y").is_some());
env.theories.insert(elab.theory.name.clone(), Rc::new(elab));
}
// Elaborate C
if let ast::Declaration::Theory(t) = &file.declarations[2].node {
let elab = elaborate_theory(&mut env, t).expect("C elaboration failed");
assert_eq!(elab.theory.name, "C");
// C should have: M/N/X, M/Y, Z
assert_eq!(elab.theory.signature.sorts.len(), 3);
assert!(
elab.theory.signature.lookup_sort("M/N/X").is_some(),
"should have M/N/X (from A via B)"
);
assert!(
elab.theory.signature.lookup_sort("M/Y").is_some(),
"should have M/Y (from B)"
);
assert!(
elab.theory.signature.lookup_sort("Z").is_some(),
"should have Z (own sort)"
);
// Functions: M/f (from B), g, h (own)
assert_eq!(elab.theory.signature.functions.len(), 3);
assert!(
elab.theory.signature.lookup_func("M/f").is_some(),
"should have M/f"
);
assert!(
elab.theory.signature.lookup_func("g").is_some(),
"should have g"
);
assert!(
elab.theory.signature.lookup_func("h").is_some(),
"should have h"
);
// Check h's domain/codomain are correct
let h_id = elab.theory.signature.lookup_func("h").unwrap();
let h_sym = &elab.theory.signature.functions[h_id];
let z_id = elab.theory.signature.lookup_sort("Z").unwrap();
let mnx_id = elab.theory.signature.lookup_sort("M/N/X").unwrap();
assert_eq!(h_sym.domain, DerivedSort::Base(z_id));
assert_eq!(h_sym.codomain, DerivedSort::Base(mnx_id));
} else {
panic!("expected theory");
}
}
#[test]
fn test_extends_with_naming_convention_slashes() {
// This test verifies the fix for the naming convention bug where
// function names like "Func/dom" (using "/" as DomainSort/descriptor)
// were incorrectly treated as grandparent-qualified names.
//
// The fix checks if the prefix before "/" is a sort in the parent theory.
// If so, it's a naming convention, not a grandparent qualifier.
let input = r#"
theory Base {
Func : Sort;
Rel : Sort;
Func/dom : Func -> Rel;
Func/cod : Func -> Rel;
Rel/type : Rel -> Func;
}
theory Child extends Base {
Op : Sort;
Op/func : Op -> Base/Func;
}
"#;
let file = parse(input).expect("parse failed");
let mut env = Env::new();
// Elaborate Base
if let ast::Declaration::Theory(t) = &file.declarations[0].node {
let elab = elaborate_theory(&mut env, t).expect("Base elaboration failed");
// Base has 2 sorts (Func, Rel) and 3 functions (Func/dom, Func/cod, Rel/type)
assert_eq!(elab.theory.signature.sorts.len(), 2);
assert_eq!(elab.theory.signature.functions.len(), 3);
env.theories.insert(elab.theory.name.clone(), Rc::new(elab));
}
// Elaborate Child
if let ast::Declaration::Theory(t) = &file.declarations[1].node {
let elab = elaborate_theory(&mut env, t).expect("Child elaboration failed");
assert_eq!(elab.theory.name, "Child");
// Child should have: Base/Func, Base/Rel, Op
assert_eq!(elab.theory.signature.sorts.len(), 3);
assert!(
elab.theory.signature.lookup_sort("Base/Func").is_some(),
"should have Base/Func"
);
assert!(
elab.theory.signature.lookup_sort("Base/Rel").is_some(),
"should have Base/Rel"
);
assert!(
elab.theory.signature.lookup_sort("Op").is_some(),
"should have Op"
);
// Functions should be: Base/Func/dom, Base/Func/cod, Base/Rel/type, Op/func
// NOT: Func/dom (which would be wrong - missing Base/ prefix)
assert_eq!(elab.theory.signature.functions.len(), 4);
assert!(
elab.theory.signature.lookup_func("Base/Func/dom").is_some(),
"should have Base/Func/dom (naming convention slash preserved)"
);
assert!(
elab.theory.signature.lookup_func("Base/Func/cod").is_some(),
"should have Base/Func/cod"
);
assert!(
elab.theory.signature.lookup_func("Base/Rel/type").is_some(),
"should have Base/Rel/type"
);
assert!(
elab.theory.signature.lookup_func("Op/func").is_some(),
"should have Op/func"
);
// Should NOT have these wrong names (without Base/ prefix)
assert!(
elab.theory.signature.lookup_func("Func/dom").is_none(),
"should NOT have Func/dom (missing prefix)"
);
assert!(
elab.theory.signature.lookup_func("Rel/type").is_none(),
"should NOT have Rel/type (missing prefix)"
);
// Verify Base/Func/dom has correct domain/codomain
let func_dom_id = elab.theory.signature.lookup_func("Base/Func/dom").unwrap();
let func_dom_sym = &elab.theory.signature.functions[func_dom_id];
let base_func_id = elab.theory.signature.lookup_sort("Base/Func").unwrap();
let base_rel_id = elab.theory.signature.lookup_sort("Base/Rel").unwrap();
assert_eq!(
func_dom_sym.domain,
DerivedSort::Base(base_func_id),
"Base/Func/dom domain should be Base/Func"
);
assert_eq!(
func_dom_sym.codomain,
DerivedSort::Base(base_rel_id),
"Base/Func/dom codomain should be Base/Rel"
);
// Verify Op/func has correct domain/codomain
let op_func_id = elab.theory.signature.lookup_func("Op/func").unwrap();
let op_func_sym = &elab.theory.signature.functions[op_func_id];
let op_id = elab.theory.signature.lookup_sort("Op").unwrap();
assert_eq!(
op_func_sym.domain,
DerivedSort::Base(op_id),
"Op/func domain should be Op"
);
assert_eq!(
op_func_sym.codomain,
DerivedSort::Base(base_func_id),
"Op/func codomain should be Base/Func"
);
} else {
panic!("expected theory");
}
}