1
//! Debug-only structural assertions for AST/model integrity.
2
//!
3
//! The assertions in this module validate a few key invariants:
4
//! - `Root` exists exactly once, at the top-level model root, and nowhere else.
5
//! - all referenced names resolve to declarations present in reachable symbol tables.
6
//! - a combined model well-formedness check that applies all assertions.
7

            
8
use super::Model;
9

            
10
#[cfg(debug_assertions)]
11
use std::collections::{BTreeSet, HashSet};
12

            
13
#[cfg(debug_assertions)]
14
use super::{Expression, Name, Reference, SymbolTablePtr, serde::HasId};
15
#[cfg(debug_assertions)]
16
use uniplate::Biplate;
17

            
18
/// Debug-assert that a model is well-formed by applying all AST assertions in this module.
19
#[cfg(debug_assertions)]
20
2731683
pub fn debug_assert_model_well_formed(model: &Model, origin: &str) {
21
2731683
    debug_assert_root_at_top_level_only(model, origin);
22
2731683
    debug_assert_all_names_resolved(model, origin);
23
2731683
}
24

            
25
/// Debug-assert that a model is well-formed by applying all AST assertions in this module.
26
#[cfg(not(debug_assertions))]
27
pub fn debug_assert_model_well_formed(_model: &Model, _origin: &str) {}
28

            
29
/// Debug-assert that all names referenced by expressions/domains resolve to declared symbols.
30
#[cfg(debug_assertions)]
31
2731683
pub fn debug_assert_all_names_resolved(model: &Model, origin: &str) {
32
2731683
    let mut declared_names: BTreeSet<Name> = BTreeSet::new();
33
2731683
    let mut referenced_names: BTreeSet<Name> = BTreeSet::new();
34

            
35
2731683
    for table_ptr in collect_reachable_symbol_tables(model) {
36
3037691
        let table = table_ptr.read();
37

            
38
3037691
        for (name, decl) in table.iter_local() {
39
650446053
            declared_names.insert(name.clone());
40

            
41
650446053
            if let Some(expr) = decl.as_value_letting() {
42
1716788
                referenced_names.extend(Biplate::<Reference>::universe_bi(&*expr).into_iter().map(
43
322439
                    |reference| {
44
322439
                        let name = reference.name();
45
322439
                        canonical_resolution_name(&name).clone()
46
322439
                    },
47
                ));
48
648729265
            }
49

            
50
650446053
            if let Some(domain) = decl.domain() {
51
650434652
                referenced_names.extend(
52
650434652
                    Biplate::<Reference>::universe_bi(domain.as_ref())
53
650434652
                        .into_iter()
54
376535
                        .map(|reference| {
55
376535
                            let name = reference.name();
56
376535
                            canonical_resolution_name(&name).clone()
57
376535
                        }),
58
                );
59
11401
            }
60
        }
61
    }
62

            
63
2731683
    referenced_names.extend(
64
2731683
        Biplate::<Reference>::universe_bi(model.root())
65
2731683
            .into_iter()
66
8984053
            .map(|reference| {
67
8984053
                let name = reference.name();
68
8984053
                canonical_resolution_name(&name).clone()
69
8984053
            }),
70
    );
71

            
72
2731683
    if let Some(dominance) = &model.dominance {
73
35931
        referenced_names.extend(
74
35931
            Biplate::<Reference>::universe_bi(dominance)
75
35931
                .into_iter()
76
441264
                .map(|reference| {
77
441264
                    let name = reference.name();
78
441264
                    canonical_resolution_name(&name).clone()
79
441264
                }),
80
        );
81
2695752
    }
82

            
83
2731683
    if let Some(objective) = &model.objective {
84
42610
        referenced_names.extend(
85
42610
            Biplate::<Reference>::universe_bi(&objective.expression)
86
42610
                .into_iter()
87
51165
                .map(|reference| {
88
51165
                    let name = reference.name();
89
51165
                    canonical_resolution_name(&name).clone()
90
51165
                }),
91
        );
92
2689073
    }
93

            
94
2731683
    for clause in model.sat_decisions() {
95
613138722
        for literal in clause.expressions().into_iter() {
96
1289595063
            referenced_names.extend(Biplate::<Reference>::universe_bi(literal).into_iter().map(
97
1929104098
                |reference| {
98
1929104098
                    let name = reference.name();
99
1929104098
                    canonical_resolution_name(&name).clone()
100
1929104098
                },
101
            ));
102
        }
103
    }
104

            
105
2731683
    let unresolved: Vec<Name> = referenced_names
106
2731683
        .difference(&declared_names)
107
2731683
        .cloned()
108
2731683
        .collect();
109

            
110
    debug_assert!(
111
2731683
        unresolved.is_empty(),
112
        "Model from '{origin}' contains unresolved names: {unresolved:?}"
113
    );
114
2731683
}
115

            
116
/// Debug-assert that all names referenced by expressions/domains resolve to declared symbols.
117
#[cfg(not(debug_assertions))]
118
pub fn debug_assert_all_names_resolved(_model: &Model, _origin: &str) {}
119

            
120
#[cfg(debug_assertions)]
121
1939279554
fn canonical_resolution_name(name: &Name) -> &Name {
122
1939279554
    match name {
123
        // Names wrapped in a selected representation still resolve through the source declaration.
124
        Name::WithRepresentation(inner, _) => canonical_resolution_name(inner),
125
1939279554
        _ => name,
126
    }
127
1939279554
}
128

            
129
/// Debug-assert that there is exactly one `Root` expression, and it is the model's top-level root.
130
#[cfg(debug_assertions)]
131
2731683
pub fn debug_assert_root_at_top_level_only(model: &Model, origin: &str) {
132
    debug_assert!(
133
2731683
        matches!(model.root(), Expression::Root(_, _)),
134
        "Model from '{origin}' does not have Root at top-level"
135
    );
136

            
137
2731683
    let root_count_in_main_tree = Biplate::<Expression>::universe_bi(model)
138
2731683
        .iter()
139
35027775
        .filter(|expr| matches!(expr, Expression::Root(_, _)))
140
        .count();
141

            
142
2731683
    let root_count_in_clauses = model
143
2731683
        .sat_decisions()
144
2731683
        .iter()
145
613138722
        .flat_map(|clause| clause.expressions().into_iter())
146
1289595063
        .map(|expr| {
147
1289595063
            Biplate::<Expression>::universe_bi(expr)
148
1289595063
                .iter()
149
3543259496
                .filter(|inner| matches!(inner, Expression::Root(_, _)))
150
                .count()
151
1289595063
        })
152
        .sum::<usize>();
153

            
154
2731683
    let total_root_count = root_count_in_main_tree + root_count_in_clauses;
155
2731683
    debug_assert_eq!(
156
2731683
        total_root_count, 1,
157
        "Model from '{origin}' should contain exactly one Root expression at top-level, found {total_root_count}"
158
    );
159
2731683
}
160

            
161
/// Debug-assert that there is exactly one `Root` expression, and it is the model's top-level root.
162
#[cfg(not(debug_assertions))]
163
pub fn debug_assert_root_at_top_level_only(_model: &Model, _origin: &str) {}
164

            
165
#[cfg(debug_assertions)]
166
2731683
fn collect_reachable_symbol_tables(model: &Model) -> Vec<SymbolTablePtr> {
167
2731683
    let mut pending_tables: Vec<SymbolTablePtr> = vec![model.symbols_ptr_unchecked().clone()];
168
2731683
    pending_tables.extend(Biplate::<SymbolTablePtr>::universe_bi(model.root()));
169

            
170
2731683
    if let Some(dominance) = &model.dominance {
171
35931
        pending_tables.extend(Biplate::<SymbolTablePtr>::universe_bi(dominance));
172
2695752
    }
173

            
174
2731683
    if let Some(objective) = &model.objective {
175
42610
        pending_tables.extend(Biplate::<SymbolTablePtr>::universe_bi(
176
42610
            &objective.expression,
177
42610
        ));
178
2689073
    }
179

            
180
2731683
    for clause in model.sat_decisions() {
181
613138722
        for literal in clause.expressions().into_iter() {
182
1289595063
            pending_tables.extend(Biplate::<SymbolTablePtr>::universe_bi(literal));
183
        }
184
    }
185

            
186
2731683
    let mut seen_tables = HashSet::new();
187
2731683
    let mut out = Vec::new();
188

            
189
6060582
    while let Some(table_ptr) = pending_tables.pop() {
190
3328899
        if !seen_tables.insert(table_ptr.id()) {
191
291208
            continue;
192
3037691
        }
193

            
194
3037691
        let parent = table_ptr.read().parent().clone();
195
3037691
        if let Some(parent) = parent {
196
306008
            pending_tables.push(parent);
197
2731683
        }
198

            
199
3037691
        out.push(table_ptr);
200
    }
201

            
202
2731683
    out
203
2731683
}