conjure_cp_core/ast/
assertions.rs1use super::Model;
9
10#[cfg(debug_assertions)]
11use std::collections::{BTreeSet, HashSet};
12
13#[cfg(debug_assertions)]
14use super::{Expression, Name, Reference, SymbolTablePtr, serde::HasId};
15#[cfg(debug_assertions)]
16use uniplate::Biplate;
17
18#[cfg(debug_assertions)]
20pub fn debug_assert_model_well_formed(model: &Model, origin: &str) {
21 debug_assert_root_at_top_level_only(model, origin);
22 debug_assert_all_names_resolved(model, origin);
23}
24
25#[cfg(not(debug_assertions))]
27pub fn debug_assert_model_well_formed(_model: &Model, _origin: &str) {}
28
29#[cfg(debug_assertions)]
31pub fn debug_assert_all_names_resolved(model: &Model, origin: &str) {
32 let mut declared_names: BTreeSet<Name> = BTreeSet::new();
33 let mut referenced_names: BTreeSet<Name> = BTreeSet::new();
34
35 for table_ptr in collect_reachable_symbol_tables(model) {
36 let table = table_ptr.read();
37
38 for (name, decl) in table.iter_local() {
39 declared_names.insert(name.clone());
40
41 if let Some(expr) = decl.as_value_letting() {
42 referenced_names.extend(Biplate::<Reference>::universe_bi(&*expr).into_iter().map(
43 |reference| {
44 let name = reference.name();
45 canonical_resolution_name(&name).clone()
46 },
47 ));
48 }
49
50 if let Some(domain) = decl.domain() {
51 referenced_names.extend(
52 Biplate::<Reference>::universe_bi(domain.as_ref())
53 .into_iter()
54 .map(|reference| {
55 let name = reference.name();
56 canonical_resolution_name(&name).clone()
57 }),
58 );
59 }
60 }
61 }
62
63 referenced_names.extend(
64 Biplate::<Reference>::universe_bi(model.root())
65 .into_iter()
66 .map(|reference| {
67 let name = reference.name();
68 canonical_resolution_name(&name).clone()
69 }),
70 );
71
72 if let Some(dominance) = &model.dominance {
73 referenced_names.extend(
74 Biplate::<Reference>::universe_bi(dominance)
75 .into_iter()
76 .map(|reference| {
77 let name = reference.name();
78 canonical_resolution_name(&name).clone()
79 }),
80 );
81 }
82
83 for clause in model.clauses() {
84 for literal in clause.iter() {
85 referenced_names.extend(Biplate::<Reference>::universe_bi(literal).into_iter().map(
86 |reference| {
87 let name = reference.name();
88 canonical_resolution_name(&name).clone()
89 },
90 ));
91 }
92 }
93
94 let unresolved: Vec<Name> = referenced_names
95 .difference(&declared_names)
96 .cloned()
97 .collect();
98
99 debug_assert!(
100 unresolved.is_empty(),
101 "Model from '{origin}' contains unresolved names: {unresolved:?}"
102 );
103}
104
105#[cfg(not(debug_assertions))]
107pub fn debug_assert_all_names_resolved(_model: &Model, _origin: &str) {}
108
109#[cfg(debug_assertions)]
110fn canonical_resolution_name(name: &Name) -> &Name {
111 match name {
112 Name::WithRepresentation(inner, _) => canonical_resolution_name(inner),
114 _ => name,
115 }
116}
117
118#[cfg(debug_assertions)]
120pub fn debug_assert_root_at_top_level_only(model: &Model, origin: &str) {
121 debug_assert!(
122 matches!(model.root(), Expression::Root(_, _)),
123 "Model from '{origin}' does not have Root at top-level"
124 );
125
126 let root_count_in_main_tree = Biplate::<Expression>::universe_bi(model)
127 .iter()
128 .filter(|expr| matches!(expr, Expression::Root(_, _)))
129 .count();
130
131 let root_count_in_clauses = model
132 .clauses()
133 .iter()
134 .flat_map(|clause| clause.iter())
135 .map(|expr| {
136 Biplate::<Expression>::universe_bi(expr)
137 .iter()
138 .filter(|inner| matches!(inner, Expression::Root(_, _)))
139 .count()
140 })
141 .sum::<usize>();
142
143 let total_root_count = root_count_in_main_tree + root_count_in_clauses;
144 debug_assert_eq!(
145 total_root_count, 1,
146 "Model from '{origin}' should contain exactly one Root expression at top-level, found {total_root_count}"
147 );
148}
149
150#[cfg(not(debug_assertions))]
152pub fn debug_assert_root_at_top_level_only(_model: &Model, _origin: &str) {}
153
154#[cfg(debug_assertions)]
155fn collect_reachable_symbol_tables(model: &Model) -> Vec<SymbolTablePtr> {
156 let mut pending_tables: Vec<SymbolTablePtr> = vec![model.symbols_ptr_unchecked().clone()];
157 pending_tables.extend(Biplate::<SymbolTablePtr>::universe_bi(model.root()));
158
159 if let Some(dominance) = &model.dominance {
160 pending_tables.extend(Biplate::<SymbolTablePtr>::universe_bi(dominance));
161 }
162
163 for clause in model.clauses() {
164 for literal in clause.iter() {
165 pending_tables.extend(Biplate::<SymbolTablePtr>::universe_bi(literal));
166 }
167 }
168
169 let mut seen_tables = HashSet::new();
170 let mut out = Vec::new();
171
172 while let Some(table_ptr) = pending_tables.pop() {
173 if !seen_tables.insert(table_ptr.id()) {
174 continue;
175 }
176
177 let parent = table_ptr.read().parent().clone();
178 if let Some(parent) = parent {
179 pending_tables.push(parent);
180 }
181
182 out.push(table_ptr);
183 }
184
185 out
186}