1
//! Value-level collection bounds; domain unions remain common type envelopes.
2

            
3
use super::{
4
    Domain, DomainPtr, Expression, GroundDomain, IntVal, JectivityAttr, MSetAttr, Range,
5
    ReturnType, SetAttr, Typeable,
6
};
7

            
8
/// Cardinalities use wider arithmetic so an overflowing upper bound can be omitted safely.
9
#[derive(Clone, Copy, Debug)]
10
pub(super) struct SizeBounds {
11
    pub low: i64,
12
    pub high: Option<i64>,
13
}
14

            
15
impl SizeBounds {
16
437820
    pub fn new(low: i64, high: Option<i64>) -> Self {
17
437820
        Self {
18
437820
            low: low.max(0),
19
437820
            high,
20
437820
        }
21
437820
    }
22

            
23
    /// Resolve endpoints independently: a symbolic endpoint must not hide a known one.
24
69567
    pub fn from_size(size: &Range<IntVal>) -> Self {
25
69567
        let (low, high) = match size {
26
18271
            Range::Single(n) => (Some(n), Some(n)),
27
28181
            Range::Bounded(low, high) => (Some(low), Some(high)),
28
1544
            Range::UnboundedL(high) => (None, Some(high)),
29
1357
            Range::UnboundedR(low) => (Some(low), None),
30
20214
            Range::Unbounded => (None, None),
31
        };
32
69567
        Self::new(
33
69567
            low.and_then(|x| x.resolve().ok())
34
69567
                .map(i64::from)
35
69567
                .unwrap_or(0),
36
69567
            high.and_then(|x| x.resolve().ok()).map(i64::from),
37
        )
38
69567
    }
39

            
40
448154
    pub fn cap(mut self, capacity: Option<i64>) -> Self {
41
448154
        self.high = min_known(self.high, capacity);
42
448154
        self
43
448154
    }
44

            
45
439843
    pub fn range(self) -> Option<Range<i32>> {
46
439843
        if self.high.is_some_and(|high| high < self.low) {
47
4507
            return None;
48
435336
        }
49
435336
        Some(Range::new(
50
435336
            Some(self.low.min(i64::from(i32::MAX)) as i32),
51
435336
            self.high.and_then(|high| i32::try_from(high).ok()),
52
        ))
53
439843
    }
54
}
55

            
56
461136
pub(super) fn min_known(a: Option<i64>, b: Option<i64>) -> Option<i64> {
57
461136
    match (a, b) {
58
384072
        (Some(a), Some(b)) => Some(a.min(b)),
59
77064
        (a, b) => a.or(b),
60
    }
61
461136
}
62

            
63
4
pub(super) fn product_capacity(domains: &[DomainPtr]) -> Option<i64> {
64
    // A known empty factor makes the product zero even with unresolved other factors.
65
4
    let lengths = domains.iter().map(capacity).collect::<Vec<_>>();
66
4
    if lengths.contains(&Some(0)) {
67
        return Some(0);
68
4
    }
69
4
    lengths
70
4
        .into_iter()
71
12
        .try_fold(1i64, |n, length| n.checked_mul(length?))
72
4
}
73

            
74
480
pub(super) fn mset_size(attrs: &MSetAttr<IntVal>, inner: &DomainPtr) -> SizeBounds {
75
480
    let size = SizeBounds::from_size(&attrs.size);
76
480
    let occurrence = SizeBounds::from_size(&attrs.occurrence);
77
480
    let capacity = match (capacity(inner), occurrence.high) {
78
2
        (Some(0), _) | (_, Some(0)) => Some(0),
79
403
        (Some(n), Some(occ)) => n.checked_mul(occ),
80
75
        _ => None,
81
    };
82
480
    size.cap(capacity)
83
480
}
84

            
85
25478
pub(super) fn collection_size(domain: &DomainPtr) -> Option<SizeBounds> {
86
25478
    if let Some((attrs, inner)) = domain.as_set() {
87
19932
        Some(SizeBounds::from_size(&attrs.size).cap(capacity(&inner)))
88
5546
    } else if let Some((attrs, inner)) = domain.as_mset() {
89
170
        Some(mset_size(&attrs, &inner))
90
5376
    } else if let Some((_, dimensions)) = domain.as_matrix() {
91
        // Matrix cardinality counts top-level entries, including rows of a higher-rank matrix.
92
10
        Some(match dimensions.first().and_then(capacity) {
93
10
            Some(n) => SizeBounds::new(n, Some(n)),
94
            None => SizeBounds::new(0, None),
95
        })
96
5366
    } else if let Some((attrs, domains)) = domain.as_relation() {
97
4
        Some(SizeBounds::from_size(&attrs.size).cap(product_capacity(&domains)))
98
5362
    } else if let Some((attrs, inner)) = domain.as_sequence() {
99
4586
        let mut bounds = SizeBounds::from_size(&attrs.size);
100
4586
        let n = capacity(&inner);
101
4586
        match attrs.jectivity {
102
1
            JectivityAttr::Injective => bounds = bounds.cap(n),
103
1
            JectivityAttr::Surjective => bounds.low = bounds.low.max(n.unwrap_or(0)),
104
            JectivityAttr::Bijective => {
105
1
                bounds.low = bounds.low.max(n.unwrap_or(0));
106
1
                bounds = bounds.cap(n);
107
            }
108
4583
            JectivityAttr::None => {}
109
        }
110
4586
        if n == Some(0) {
111
4379
            bounds = bounds.cap(Some(0));
112
207
        }
113
4586
        Some(bounds)
114
    } else {
115
776
        None
116
    }
117
25478
}
118

            
119
/// Duplicates can shrink a collection; each present value occurs at least once.
120
174
pub(super) fn distinct_size(size: SizeBounds, occurrence: SizeBounds) -> SizeBounds {
121
174
    let low = if size.low == 0 {
122
42
        0
123
    } else {
124
132
        occurrence
125
132
            .high
126
123
            .filter(|n| *n > 0)
127
123
            .map(|n| 1 + (size.low - 1) / n)
128
132
            .unwrap_or(1)
129
    };
130
174
    let high = size.high.map(|n| n / occurrence.low.max(1));
131
174
    SizeBounds::new(low, high)
132
174
}
133

            
134
/// Count integer values using interval arithmetic, including ranges wider than i32.
135
485717
pub(super) fn capacity(domain: &DomainPtr) -> Option<i64> {
136
485717
    ground_capacity(domain.resolve().ok()?.as_ref())
137
485717
}
138

            
139
654463
fn ground_capacity(ground: &GroundDomain) -> Option<i64> {
140
654463
    if let GroundDomain::Int(ranges, _) = ground {
141
474093
        let mut intervals = ranges
142
474093
            .iter()
143
600754
            .map(|range| Some((i64::from(*range.low()?), i64::from(*range.high()?))))
144
            .collect::<Option<Vec<_>>>()?;
145
474093
        intervals.sort_unstable();
146
474093
        let mut total = 0i64;
147
474093
        let mut previous: Option<(i64, i64)> = None;
148
474093
        for (low, high) in intervals {
149
600754
            if low > high {
150
                continue;
151
600754
            }
152
600754
            match previous {
153
137040
                Some((start, end)) if low <= end + 1 => previous = Some((start, end.max(high))),
154
                Some((start, end)) => {
155
137040
                    total = total.checked_add(end - start + 1)?;
156
137040
                    previous = Some((low, high));
157
                }
158
463714
                None => previous = Some((low, high)),
159
            }
160
        }
161
474093
        if let Some((start, end)) = previous {
162
463714
            total = total.checked_add(end - start + 1)?;
163
10379
        }
164
474093
        Some(total)
165
    } else {
166
180370
        match ground {
167
33585
            GroundDomain::Empty(_) => Some(0),
168
6819
            GroundDomain::Bool => Some(2),
169
73817
            GroundDomain::Tuple(domains, _) => domains
170
73817
                .iter()
171
147634
                .try_fold(1i64, |n, d| n.checked_mul(ground_capacity(d)?)),
172
2117
            GroundDomain::Record(fields, _) => fields
173
2117
                .iter()
174
4234
                .try_fold(1i64, |n, f| n.checked_mul(ground_capacity(&f.value)?)),
175
8468
            GroundDomain::Variant(fields, _) => fields
176
8468
                .iter()
177
16936
                .try_fold(0i64, |n, f| n.checked_add(ground_capacity(&f.value)?)),
178
            // Avoid potentially expensive combinatorial counting for nested collections.
179
55564
            _ => None,
180
        }
181
    }
182
654463
}
183

            
184
/// Return an overlap envelope without enumerating integer values.
185
16622
pub(super) fn overlap_domain(a: &DomainPtr, b: &DomainPtr) -> DomainPtr {
186
16622
    if a == b {
187
4021
        return a.clone();
188
12601
    }
189
12601
    if let (Ok(a_ground), Ok(b_ground)) = (a.resolve(), b.resolve()) {
190
12600
        match (a_ground.as_ref(), b_ground.as_ref()) {
191
            (GroundDomain::Empty(_), _) => return DomainPtr::from(a_ground),
192
            (_, GroundDomain::Empty(_)) => return DomainPtr::from(b_ground),
193
            (GroundDomain::Int(a_ranges, _), GroundDomain::Int(b_ranges, _)) => {
194
12600
                let ranges = a_ranges
195
12600
                    .iter()
196
13632
                    .flat_map(|a| {
197
13632
                        b_ranges
198
13632
                            .iter()
199
14014
                            .filter_map(move |b| Range::minimal(&[a.clone(), b.clone()]).ok())
200
13632
                    })
201
                    .collect();
202
12600
                return Domain::int(ranges);
203
            }
204
            _ => {}
205
        }
206
1
    }
207
    // The left domain always contains the overlap, even for unsupported/symbolic types.
208
1
    a.clone()
209
16622
}
210

            
211
372553
pub(super) fn bounded_set(bounds: SizeBounds, inner: DomainPtr) -> DomainPtr {
212
372553
    match bounds.cap(capacity(&inner)).range() {
213
372093
        Some(size) => Domain::set(SetAttr::new(size), inner),
214
460
        None => Domain::empty(ReturnType::Set(Box::new(inner.return_type()))),
215
    }
216
372553
}
217

            
218
15132
fn represented_set(
219
15132
    bounds: SizeBounds,
220
15132
    inner: DomainPtr,
221
15132
    representation: Option<String>,
222
15132
) -> DomainPtr {
223
15132
    let mut result = bounded_set(bounds, inner);
224
15132
    if let Some(representation) = representation {
225
1075
        if let Some((attrs, _)) = result.as_set_ground_mut() {
226
1075
            attrs.representation = Some(representation);
227
        } else if let Some((attrs, _)) = result.as_set_mut() {
228
            attrs.representation = Some(representation);
229
        }
230
14057
    }
231
15132
    result
232
15132
}
233

            
234
#[derive(Clone, Copy)]
235
pub(super) enum SetOperation {
236
    Union,
237
    Intersect,
238
    Difference,
239
}
240

            
241
15203
pub(super) fn set_operation_domain(
242
15203
    a: &Expression,
243
15203
    b: &Expression,
244
15203
    op: SetOperation,
245
15203
) -> Option<DomainPtr> {
246
15203
    let (a_attr, a_inner) = a.domain_of()?.as_set()?;
247
15133
    let (b_attr, b_inner) = b.domain_of()?.as_set()?;
248
15133
    let a_bounds = SizeBounds::from_size(&a_attr.size).cap(capacity(&a_inner));
249
15133
    let b_bounds = SizeBounds::from_size(&b_attr.size).cap(capacity(&b_inner));
250
15133
    if a_bounds.range().is_none() || b_bounds.range().is_none() {
251
1
        return Some(Domain::empty(ReturnType::Set(Box::new(
252
1
            a_inner.return_type(),
253
1
        ))));
254
15132
    }
255
15132
    let a_rep = super::expression_domains::selected_representation(a).or(a_attr.representation);
256
15132
    let b_rep = super::expression_domains::selected_representation(b).or(b_attr.representation);
257
15132
    let representation = match op {
258
4300
        SetOperation::Difference => a_rep,
259
10832
        _ => match (a_rep, b_rep) {
260
464
            (Some(a), Some(b)) if a == b => Some(a),
261
553
            (Some(a), None) | (None, Some(a)) => Some(a),
262
9815
            _ => None,
263
        },
264
    };
265
15132
    if a_bounds.high == Some(0) || b_bounds.high == Some(0) {
266
3731
        let (bounds, inner) = match op {
267
1245
            SetOperation::Union if a_bounds.high == Some(0) => (b_bounds, b_inner),
268
1805
            SetOperation::Union | SetOperation::Difference => (a_bounds, a_inner),
269
            SetOperation::Intersect => (
270
1243
                SizeBounds::new(0, Some(0)),
271
1243
                if a_bounds.high == Some(0) {
272
682
                    b_inner
273
                } else {
274
561
                    a_inner
275
                },
276
            ),
277
        };
278
3731
        return Some(represented_set(bounds, inner, representation));
279
11401
    }
280
11401
    if a == b {
281
9
        return Some(match op {
282
            SetOperation::Union | SetOperation::Intersect => {
283
                represented_set(a_bounds, a_inner, representation)
284
            }
285
            SetOperation::Difference => {
286
9
                represented_set(SizeBounds::new(0, Some(0)), a_inner, representation)
287
            }
288
        });
289
11392
    }
290
11392
    let overlap = overlap_domain(&a_inner, &b_inner);
291
11392
    let overlap_capacity = capacity(&overlap);
292
11392
    let (bounds, inner) = match op {
293
        SetOperation::Union => {
294
3645
            let low = overlap_capacity
295
3645
                .map(|n| (a_bounds.low + b_bounds.low - n).max(0))
296
3645
                .unwrap_or(0)
297
3645
                .max(a_bounds.low)
298
3645
                .max(b_bounds.low);
299
3645
            let high = a_bounds
300
3645
                .high
301
3645
                .zip(b_bounds.high)
302
3645
                .and_then(|(a, b)| a.checked_add(b));
303
3645
            (SizeBounds::new(low, high), a_inner.union(&b_inner).ok()?)
304
        }
305
        SetOperation::Intersect => {
306
4699
            let universe = a_inner.union(&b_inner).ok()?;
307
4699
            let low = capacity(&universe)
308
4699
                .map(|n| (a_bounds.low + b_bounds.low - n).max(0))
309
4699
                .unwrap_or(0);
310
4699
            let high = min_known(min_known(a_bounds.high, b_bounds.high), overlap_capacity);
311
4699
            (SizeBounds::new(low, high), overlap)
312
        }
313
        SetOperation::Difference => {
314
3048
            let removable = min_known(b_bounds.high, overlap_capacity);
315
3048
            let low = removable.map(|n| (a_bounds.low - n).max(0)).unwrap_or(0);
316
3048
            (SizeBounds::new(low, a_bounds.high), a_inner)
317
        }
318
    };
319
11392
    Some(represented_set(bounds, inner, representation))
320
15203
}
321

            
322
#[cfg(test)]
323
mod tests {
324
    use super::*;
325
    use crate::ast::{AbstractLiteral, DeclarationPtr, Literal, Metadata, Moo, Name, Reference};
326
    use std::collections::BTreeSet;
327

            
328
7868
    fn variable(name: &str, domain: DomainPtr) -> Expression {
329
7868
        Reference::new(DeclarationPtr::new_find(Name::user(name), domain)).into()
330
7868
    }
331

            
332
11546
    fn operation(a: &Expression, b: &Expression, op: SetOperation) -> Expression {
333
11546
        let a = Moo::new(a.clone());
334
11546
        let b = Moo::new(b.clone());
335
11546
        match op {
336
3853
            SetOperation::Union => Expression::Union(Metadata::new(), a, b),
337
3847
            SetOperation::Intersect => Expression::Intersect(Metadata::new(), a, b),
338
3846
            SetOperation::Difference => Expression::Difference(Metadata::new(), a, b),
339
        }
340
11546
    }
341

            
342
    #[test]
343
1
    fn set_operations_cover_every_small_operand_value() {
344
1
        let mut cases = Vec::new();
345
1
        for support in 0..8 {
346
8
            let inner = Domain::int(
347
8
                (0..3)
348
24
                    .filter(|i| support & (1 << i) != 0)
349
8
                    .map(Range::Single)
350
                    .collect(),
351
            );
352
8
            for low in 0..=3 {
353
32
                for high in low..=3 {
354
80
                    let domain = Domain::set(SetAttr::new_min_max_size(low, high), inner.clone());
355
80
                    let values = domain
356
80
                        .resolve()
357
80
                        .unwrap()
358
80
                        .values()
359
80
                        .unwrap()
360
80
                        .collect::<Vec<_>>();
361
80
                    if !values.is_empty() {
362
62
                        cases.push((domain, values));
363
18
                    }
364
                }
365
            }
366
        }
367
1
        for (a_domain, a_values) in &cases {
368
62
            for (b_domain, b_values) in &cases {
369
3844
                let a = variable("a", a_domain.clone());
370
3844
                let b = variable("b", b_domain.clone());
371
3844
                for op in [
372
3844
                    SetOperation::Union,
373
3844
                    SetOperation::Intersect,
374
3844
                    SetOperation::Difference,
375
3844
                ] {
376
11532
                    let result = operation(&a, &b, op)
377
11532
                        .domain_of()
378
11532
                        .unwrap()
379
11532
                        .resolve()
380
11532
                        .unwrap();
381
11532
                    for a in a_values {
382
26784
                        for b in b_values {
383
62208
                            let Literal::AbstractLiteral(AbstractLiteral::Set(a)) = a else {
384
                                unreachable!()
385
                            };
386
62208
                            let Literal::AbstractLiteral(AbstractLiteral::Set(b)) = b else {
387
                                unreachable!()
388
                            };
389
62208
                            let a = a
390
62208
                                .iter()
391
67392
                                .map(|x| match x {
392
67392
                                    Literal::Int(n) => *n,
393
                                    _ => unreachable!(),
394
67392
                                })
395
                                .collect::<BTreeSet<_>>();
396
62208
                            let b = b
397
62208
                                .iter()
398
67392
                                .map(|x| match x {
399
67392
                                    Literal::Int(n) => *n,
400
                                    _ => unreachable!(),
401
67392
                                })
402
                                .collect::<BTreeSet<_>>();
403
62208
                            let elems = match op {
404
                                SetOperation::Union => {
405
20736
                                    a.union(&b).copied().map(Literal::Int).collect()
406
                                }
407
                                SetOperation::Intersect => {
408
20736
                                    a.intersection(&b).copied().map(Literal::Int).collect()
409
                                }
410
                                SetOperation::Difference => {
411
20736
                                    a.difference(&b).copied().map(Literal::Int).collect()
412
                                }
413
                            };
414
62208
                            let value = Literal::AbstractLiteral(AbstractLiteral::Set(elems));
415
62208
                            assert!(
416
62208
                                result.contains(&value).unwrap(),
417
                                "{a_domain} / {b_domain}: {value} outside {result}"
418
                            );
419
                        }
420
                    }
421
                }
422
            }
423
        }
424
1
    }
425

            
426
    #[test]
427
1
    fn set_bounds_use_capacity_overlap_and_identity() {
428
1
        let inner = Domain::int(vec![Range::Bounded(1, 3)]);
429
1
        let a = variable("a", Domain::set(SetAttr::new_size(2), inner.clone()));
430
1
        let b = variable("b", Domain::set(SetAttr::new_size(2), inner));
431
1
        let intersection = operation(&a, &b, SetOperation::Intersect)
432
1
            .domain_of()
433
1
            .unwrap();
434
1
        assert_eq!(
435
1
            intersection.as_set_ground().unwrap().0.size,
436
1
            Range::Bounded(1, 2)
437
        );
438
1
        let union = operation(&a, &b, SetOperation::Union).domain_of().unwrap();
439
1
        assert_eq!(union.as_set_ground().unwrap().0.size, Range::Bounded(2, 3));
440
1
        let difference = operation(&a, &b, SetOperation::Difference)
441
1
            .domain_of()
442
1
            .unwrap();
443
1
        assert_eq!(
444
1
            difference.as_set_ground().unwrap().0.size,
445
1
            Range::Bounded(0, 2)
446
        );
447
1
        let same = operation(&a, &a, SetOperation::Difference)
448
1
            .domain_of()
449
1
            .unwrap();
450
1
        assert_eq!(same.as_set_ground().unwrap().0.size, Range::Single(0));
451
1
    }
452

            
453
    #[test]
454
1
    fn large_set_bounds_do_not_overflow_or_enumerate_support() {
455
1
        let inner = Domain::int(vec![Range::Bounded(0, i32::MAX)]);
456
1
        let a = variable(
457
1
            "a",
458
1
            Domain::set(SetAttr::new_max_size(i32::MAX), inner.clone()),
459
1
        );
460
1
        let b = variable("b", Domain::set(SetAttr::new_max_size(i32::MAX), inner));
461
1
        let union = operation(&a, &b, SetOperation::Union).domain_of().unwrap();
462
1
        assert_eq!(union.as_set_ground().unwrap().0.size, Range::UnboundedR(0));
463
1
        let intersection = operation(&a, &b, SetOperation::Intersect)
464
1
            .domain_of()
465
1
            .unwrap();
466
1
        assert_eq!(
467
1
            intersection.as_set_ground().unwrap().0.size,
468
1
            Range::Bounded(0, i32::MAX)
469
        );
470
1
    }
471

            
472
    #[test]
473
1
    fn symbolic_endpoints_keep_known_bounds_and_representation() {
474
1
        let n = IntVal::Reference(Reference::new(DeclarationPtr::new_given(
475
1
            Name::user("n"),
476
1
            Domain::int(vec![Range::Bounded(1, 4)]),
477
1
        )));
478
1
        let a = variable(
479
1
            "a",
480
1
            Domain::set(
481
1
                SetAttr::new(Range::Bounded(IntVal::from(1), n)).with_representation("packed"),
482
1
                Domain::int(vec![Range::Bounded(1, 4)]),
483
1
            ),
484
1
        );
485
1
        let b = variable(
486
1
            "b",
487
1
            Domain::set(
488
1
                SetAttr::new_max_size(1),
489
1
                Domain::int(vec![Range::Single(1)]),
490
1
            ),
491
1
        );
492
1
        let result = operation(&a, &b, SetOperation::Union).domain_of().unwrap();
493
1
        let (attrs, _) = result.as_set_ground().unwrap();
494
1
        assert_eq!(attrs.size, Range::Bounded(1, 4));
495
1
        assert_eq!(attrs.representation.as_deref(), Some("packed"));
496
1
        let result = operation(&a, &b, SetOperation::Intersect)
497
1
            .domain_of()
498
1
            .unwrap();
499
1
        assert_eq!(result.as_set_ground().unwrap().0.size, Range::Bounded(0, 1));
500
1
    }
501

            
502
    #[test]
503
1
    fn impossible_operand_is_distinct_from_an_empty_set() {
504
1
        let inner = Domain::int(vec![Range::Single(1)]);
505
1
        let impossible = variable("a", Domain::set(SetAttr::new_size(2), inner.clone()));
506
1
        let empty = variable("b", Domain::set(SetAttr::new_size(0), inner));
507
1
        let result = operation(&impossible, &empty, SetOperation::Union)
508
1
            .domain_of()
509
1
            .unwrap();
510
1
        assert!(matches!(
511
1
            result.as_ground(),
512
            Some(GroundDomain::Empty(ReturnType::Set(_)))
513
        ));
514
1
        let result = operation(&empty, &empty, SetOperation::Union)
515
1
            .domain_of()
516
1
            .unwrap();
517
1
        assert_eq!(result.as_set_ground().unwrap().0.size, Range::Single(0));
518
1
    }
519
    #[test]
520
1
    fn multiset_cardinality_and_distinct_bounds_cover_small_values() {
521
1
        for min_size in 0..=5 {
522
6
            for max_size in min_size..=6 {
523
27
                for min_occurrence in 1..=3 {
524
81
                    for max_occurrence in min_occurrence..=3 {
525
162
                        let attrs = MSetAttr::new(
526
162
                            Range::Bounded(min_size, max_size),
527
162
                            Range::Bounded(min_occurrence, max_occurrence),
528
162
                        );
529
162
                        let domain = Domain::mset(attrs, Domain::int(vec![Range::Bounded(0, 2)]));
530
162
                        let source = variable("m", domain);
531
162
                        let card = Expression::Card(Metadata::new(), Moo::new(source.clone()))
532
162
                            .domain_of()
533
162
                            .unwrap()
534
162
                            .resolve()
535
162
                            .unwrap();
536
162
                        let set = Expression::ToSet(Metadata::new(), Moo::new(source))
537
162
                            .domain_of()
538
162
                            .unwrap()
539
162
                            .resolve()
540
162
                            .unwrap();
541
162
                        for a in 0..=3 {
542
648
                            for b in 0..=3 {
543
2592
                                for c in 0..=3 {
544
10368
                                    let counts = [a, b, c];
545
10368
                                    let n = a + b + c;
546
10368
                                    if !(min_size..=max_size).contains(&n)
547
8960
                                        || counts.iter().any(|x| {
548
8960
                                            *x != 0
549
6222
                                                && !(min_occurrence..=max_occurrence).contains(x)
550
8960
                                        })
551
                                    {
552
8793
                                        continue;
553
1575
                                    }
554
1575
                                    let elems = counts
555
1575
                                        .iter()
556
1575
                                        .enumerate()
557
4725
                                        .filter(|(_, n)| **n > 0)
558
2940
                                        .map(|(i, _)| Literal::Int(i as i32))
559
                                        .collect();
560
1575
                                    assert!(card.contains(&Literal::Int(n)).unwrap());
561
1575
                                    assert!(
562
1575
                                        set.contains(&Literal::AbstractLiteral(
563
1575
                                            AbstractLiteral::Set(elems)
564
1575
                                        ))
565
1575
                                        .unwrap()
566
                                    );
567
                                }
568
                            }
569
                        }
570
                    }
571
                }
572
            }
573
        }
574
1
    }
575

            
576
    #[test]
577
1
    fn multiset_union_retains_one_sided_bounds() {
578
1
        let a = variable(
579
1
            "a",
580
1
            Domain::mset(
581
1
                MSetAttr::new_min_size(2),
582
1
                Domain::int(vec![Range::Bounded(0, 2)]),
583
1
            ),
584
1
        );
585
1
        let b = variable(
586
1
            "b",
587
1
            Domain::mset(MSetAttr::new_size(1), Domain::int(vec![Range::Single(3)])),
588
1
        );
589
1
        let result = operation(&a, &b, SetOperation::Union).domain_of().unwrap();
590
1
        let (attrs, _) = result.as_mset_ground().unwrap();
591
1
        assert_eq!(attrs.size, Range::UnboundedR(3));
592
1
        assert_eq!(attrs.occurrence, Range::UnboundedR(1));
593
1
    }
594

            
595
    #[test]
596
1
    fn multiset_union_accepts_an_untyped_empty_literal() {
597
1
        let a = variable(
598
1
            "a",
599
1
            Domain::mset(MSetAttr::new_size(2), Domain::int(vec![Range::Single(1)])),
600
1
        );
601
1
        let empty = Expression::AbstractLiteral(Metadata::new(), AbstractLiteral::MSet(vec![]));
602
1
        for (a, b) in [(&a, &empty), (&empty, &a)] {
603
2
            let result = operation(a, b, SetOperation::Union).domain_of().unwrap();
604
2
            assert_eq!(result.as_mset_ground().unwrap().0.size, Range::Single(2));
605
        }
606
1
    }
607

            
608
    #[test]
609
1
    fn bounds_wider_than_i32_weaken_without_becoming_empty() {
610
1
        let max = i64::from(i32::MAX);
611
1
        assert_eq!(
612
1
            SizeBounds::new(max + 1, Some(max * 2)).range(),
613
1
            Some(Range::UnboundedR(i32::MAX))
614
        );
615
1
        let low = Domain::int(vec![
616
1
            Range::Bounded(i32::MIN, 0),
617
1
            Range::Bounded(i32::MIN, -1),
618
1
        ]);
619
1
        assert_eq!(capacity(&low), Some(max + 2));
620
1
    }
621

            
622
    #[test]
623
1
    fn constant_literal_sets_count_distinct_entries_and_preserve_empty_values() {
624
        use crate::ast::HasDomain;
625
        let duplicate =
626
1
            Literal::AbstractLiteral(AbstractLiteral::Set(vec![Literal::Int(1), Literal::Int(1)]));
627
1
        let domain = duplicate.domain_of();
628
1
        assert_eq!(domain.as_set_ground().unwrap().0.size, Range::Single(1));
629
1
        assert!(domain.resolve().unwrap().contains(&duplicate).unwrap());
630
1
        let normalised = crate::ast::eval_constant(&Expression::from(duplicate)).unwrap();
631
1
        assert!(domain.resolve().unwrap().contains(&normalised).unwrap());
632
        let value =
633
1
            Literal::AbstractLiteral(AbstractLiteral::Set(vec![Literal::Int(1), Literal::Int(2)]));
634
1
        let domain = value.domain_of();
635
1
        assert_eq!(domain.as_set_ground().unwrap().0.size, Range::Single(2));
636
1
        assert!(domain.resolve().unwrap().contains(&value).unwrap());
637
1
        let empty = Expression::AbstractLiteral(Metadata::new(), AbstractLiteral::Set(vec![]));
638
1
        let a = variable(
639
1
            "a",
640
1
            Domain::set(SetAttr::new_size(1), Domain::int(vec![Range::Single(1)])),
641
1
        );
642
1
        let result = operation(&a, &empty, SetOperation::Union)
643
1
            .domain_of()
644
1
            .unwrap();
645
1
        assert_eq!(result.as_set_ground().unwrap().0.size, Range::Single(1));
646
1
    }
647

            
648
    #[test]
649
1
    fn sequence_attributes_bound_cardinality_but_repetition_has_no_support_cap() {
650
        use crate::ast::SequenceAttr;
651
1
        for (jectivity, expected) in [
652
1
            (JectivityAttr::None, Range::UnboundedR(0)),
653
1
            (JectivityAttr::Injective, Range::Bounded(0, 3)),
654
1
            (JectivityAttr::Surjective, Range::UnboundedR(3)),
655
1
            (JectivityAttr::Bijective, Range::Single(3)),
656
1
        ] {
657
4
            let source = variable(
658
4
                "s",
659
4
                Domain::sequence(
660
4
                    SequenceAttr::<i32> {
661
4
                        jectivity,
662
4
                        ..Default::default()
663
4
                    },
664
4
                    Domain::int(vec![Range::Bounded(0, 2)]),
665
4
                ),
666
4
            );
667
4
            assert_eq!(
668
4
                Expression::Card(Metadata::new(), Moo::new(source)).domain_of(),
669
4
                Some(Domain::int(vec![expected]))
670
            );
671
        }
672
1
    }
673

            
674
    #[test]
675
1
    fn matrix_to_set_uses_element_type_and_length_bounds() {
676
1
        let source = variable(
677
1
            "xs",
678
1
            Domain::matrix(
679
1
                Domain::int(vec![Range::Bounded(0, 1)]),
680
1
                vec![Domain::int(vec![Range::Bounded(1, 3)])],
681
1
            ),
682
1
        );
683
1
        let result = Expression::ToSet(Metadata::new(), Moo::new(source))
684
1
            .domain_of()
685
1
            .unwrap();
686
1
        let (attrs, inner) = result.as_set_ground().unwrap();
687
1
        assert_eq!(attrs.size, Range::Bounded(1, 2));
688
1
        assert_eq!(inner.return_type(), ReturnType::Int);
689
1
    }
690
    #[test]
691
1
    fn matrix_cardinality_domain_matches_every_two_dimensional_value() {
692
1
        let domain = Domain::matrix(
693
1
            Domain::bool(),
694
1
            vec![
695
1
                Domain::int(vec![Range::Bounded(1, 2)]),
696
1
                Domain::int(vec![Range::Bounded(1, 3)]),
697
1
            ],
698
1
        );
699
1
        let source = variable("matrix", domain.clone());
700
1
        let inferred = Expression::Card(Metadata::new(), Moo::new(source))
701
1
            .domain_of()
702
1
            .unwrap();
703
1
        assert_eq!(inferred, Domain::int(vec![Range::Single(2)]));
704
1
        for value in domain.resolve().unwrap().values().unwrap() {
705
64
            let actual = crate::ast::eval_constant(&Expression::Card(
706
64
                Metadata::new(),
707
64
                Moo::new(value.into()),
708
64
            ))
709
64
            .unwrap();
710
64
            assert!(inferred.resolve().unwrap().contains(&actual).unwrap());
711
        }
712
1
    }
713
}