1
//! Infer tighter declaration domains from the constraint list, before rewriting.
2
//!
3
//! The first component looks at cardinality equalities `|s| = k` (and `|s| = |t|` when `|t|` is
4
//! constant) and, when `s` is a sequence find, intersects that size into its domain. Indexed
5
//! equalities `forAll i : D . |m[i]| = k_i` over a matrix of sequences are included so that each
6
//! cell can later be represented as a fixed-length sequence.
7

            
8
use std::collections::HashMap;
9

            
10
use crate::ast::{
11
    Atom, DeclarationKind, DeclarationPtr, DomainPtr, Expression as Expr, GroundDomain, Literal,
12
    Metadata, Model, Moo, Name, Range,
13
    comprehension::{Comprehension, ComprehensionQualifier},
14
    eval_constant,
15
    matrix::shape_of_dom,
16
};
17

            
18
/// Walk the model's constraints and tighten declaration domains where a fact can be proved.
19
1351571
pub fn tighten_domains_from_constraints(model: &mut Model) {
20
1351571
    let facts = collect_sequence_size_facts(model);
21
1351571
    apply_sequence_size_facts(facts);
22
1351571
}
23

            
24
struct SequenceSizeFact {
25
    decl: DeclarationPtr,
26
    /// `None` for a top-level sequence; `Some(indices)` for a matrix element.
27
    indices: Option<Vec<Literal>>,
28
    size: i32,
29
}
30

            
31
1351571
fn collect_sequence_size_facts(model: &Model) -> Vec<SequenceSizeFact> {
32
1351571
    let mut facts = Vec::new();
33
1351571
    for constraint in model.constraints() {
34
2548318
        collect_from_expr(constraint, &mut facts);
35
    }
36
1351571
    facts
37
1351571
}
38

            
39
2805095
fn collect_from_expr(expr: &Expr, facts: &mut Vec<SequenceSizeFact>) {
40
2805095
    match expr {
41
        Expr::And(_, inner) => {
42
152924
            if let Some(children) = inner.unwrap_list_ref() {
43
16508
                for child in children {
44
33015
                    collect_from_expr(child, facts);
45
                }
46
            } else {
47
                // `forAll` parses as `And(Comprehension)`, not `And` of a list.
48
136416
                collect_from_expr(inner.as_ref(), facts);
49
            }
50
        }
51
        Expr::Comprehension(_, comprehension) => {
52
129898
            collect_from_comprehension(comprehension, facts);
53
        }
54
943534
        Expr::Eq(_, lhs, rhs) => collect_from_card_eq(lhs, rhs, facts),
55
1578739
        _ => {}
56
    }
57
2805095
}
58

            
59
129898
fn collect_from_comprehension(comprehension: &Comprehension, facts: &mut Vec<SequenceSizeFact>) {
60
129898
    let generators: Vec<&DeclarationPtr> = comprehension
61
129898
        .qualifiers
62
129898
        .iter()
63
165208
        .filter_map(|qualifier| match qualifier {
64
155649
            ComprehensionQualifier::Generator { ptr } => Some(ptr),
65
            ComprehensionQualifier::ExpressionGenerator { .. }
66
9559
            | ComprehensionQualifier::Condition(_) => None,
67
165208
        })
68
        .collect();
69

            
70
    // First component: a single generator over a finite domain, body an equality of cards.
71
129898
    if generators.len() != 1 {
72
19928
        collect_from_expr(&comprehension.return_expression, facts);
73
19928
        return;
74
109970
    }
75

            
76
109970
    let generator = generators[0];
77
109970
    let Some(domain) = generator.domain().and_then(|domain| domain.resolve().ok()) else {
78
5133
        collect_from_expr(&comprehension.return_expression, facts);
79
5133
        return;
80
    };
81
104837
    let Ok(values) = domain.values() else {
82
        collect_from_expr(&comprehension.return_expression, facts);
83
        return;
84
    };
85
104837
    let values: Vec<Literal> = values.collect();
86
104837
    if values.is_empty() {
87
2063
        return;
88
102774
    }
89

            
90
102774
    let Expr::Eq(_, lhs, rhs) = &comprehension.return_expression else {
91
62285
        collect_from_expr(&comprehension.return_expression, facts);
92
62285
        return;
93
    };
94

            
95
40489
    for value in values {
96
199501
        let Some(size) = card_eq_size_at_generator(lhs, rhs, generator, &value) else {
97
199209
            continue;
98
        };
99
292
        let Some(subject) = card_eq_subject_at_generator(lhs, rhs, generator, &value) else {
100
            continue;
101
        };
102
292
        facts.push(subject.with_size(size));
103
    }
104
129898
}
105

            
106
943534
fn collect_from_card_eq(lhs: &Expr, rhs: &Expr, facts: &mut Vec<SequenceSizeFact>) {
107
943534
    let Some(size) = card_eq_constant_size(lhs, rhs) else {
108
942111
        return;
109
    };
110
1423
    if let Some(subject) = sequence_subject(lhs).or_else(|| sequence_subject(rhs)) {
111
437
        facts.push(subject.with_size(size));
112
986
    }
113
943534
}
114

            
115
943534
fn card_eq_constant_size(lhs: &Expr, rhs: &Expr) -> Option<i32> {
116
943534
    match (as_card(lhs), as_card(rhs)) {
117
        (Some(_), Some(_)) => eval_card(lhs).or_else(|| eval_card(rhs)),
118
1423
        (Some(_), None) => eval_int(rhs),
119
17313
        (None, Some(_)) => eval_int(lhs),
120
924798
        (None, None) => None,
121
    }
122
943534
}
123

            
124
199501
fn card_eq_size_at_generator(
125
199501
    lhs: &Expr,
126
199501
    rhs: &Expr,
127
199501
    generator: &DeclarationPtr,
128
199501
    value: &Literal,
129
199501
) -> Option<i32> {
130
199501
    let lhs_size = eval_card_with_index(lhs, generator, value);
131
199501
    let rhs_size = eval_card_with_index(rhs, generator, value);
132
199501
    match (as_card(lhs), as_card(rhs), lhs_size, rhs_size) {
133
292
        (Some(_), Some(_), Some(size), _) | (Some(_), Some(_), None, Some(size)) => Some(size),
134
        (Some(_), None, _, _) => eval_int(rhs),
135
        (None, Some(_), _, _) => eval_int(lhs),
136
199209
        _ => None,
137
    }
138
199501
}
139

            
140
292
fn card_eq_subject_at_generator(
141
292
    lhs: &Expr,
142
292
    rhs: &Expr,
143
292
    generator: &DeclarationPtr,
144
292
    value: &Literal,
145
292
) -> Option<SequenceSubject> {
146
292
    let lhs_eval = eval_card_with_index(lhs, generator, value).is_some();
147
292
    let rhs_eval = eval_card_with_index(rhs, generator, value).is_some();
148
292
    match (as_card(lhs), as_card(rhs), lhs_eval, rhs_eval) {
149
        // Prefer the side that is *not* a known constant, so `|locs[i]| = |clues[i]|` tightens
150
        // the find, not the given.
151
292
        (Some(_), Some(_), false, true) => sequence_subject_at(lhs, generator, value),
152
        (Some(_), Some(_), true, false) => sequence_subject_at(rhs, generator, value),
153
        (Some(_), None, _, _) => sequence_subject_at(lhs, generator, value),
154
        (None, Some(_), _, _) => sequence_subject_at(rhs, generator, value),
155
        _ => None,
156
    }
157
292
}
158

            
159
struct SequenceSubject {
160
    decl: DeclarationPtr,
161
    indices: Option<Vec<Literal>>,
162
}
163

            
164
impl SequenceSubject {
165
729
    fn with_size(self, size: i32) -> SequenceSizeFact {
166
729
        SequenceSizeFact {
167
729
            decl: self.decl,
168
729
            indices: self.indices,
169
729
            size,
170
729
        }
171
729
    }
172
}
173

            
174
2409
fn sequence_subject(expr: &Expr) -> Option<SequenceSubject> {
175
2409
    let collection = as_card(expr)?;
176
1423
    match collection {
177
437
        Expr::Atomic(_, Atom::Reference(reference)) => Some(SequenceSubject {
178
437
            decl: reference.ptr().clone(),
179
437
            indices: None,
180
437
        }),
181
        Expr::UnsafeIndex(_, subject, indices) | Expr::SafeIndex(_, subject, indices) => {
182
            let Expr::Atomic(_, Atom::Reference(reference)) = subject.as_ref() else {
183
                return None;
184
            };
185
            let indices: Vec<Literal> = indices.iter().map(eval_constant).collect::<Option<_>>()?;
186
            Some(SequenceSubject {
187
                decl: reference.ptr().clone(),
188
                indices: Some(indices),
189
            })
190
        }
191
986
        _ => None,
192
    }
193
2409
}
194

            
195
292
fn sequence_subject_at(
196
292
    expr: &Expr,
197
292
    generator: &DeclarationPtr,
198
292
    value: &Literal,
199
292
) -> Option<SequenceSubject> {
200
292
    let collection = as_card(expr)?;
201
292
    match collection {
202
        Expr::Atomic(_, Atom::Reference(reference)) => Some(SequenceSubject {
203
            decl: reference.ptr().clone(),
204
            indices: None,
205
        }),
206
        Expr::UnsafeIndex(_, subject, indices) | Expr::SafeIndex(_, subject, indices) => {
207
292
            let Expr::Atomic(_, Atom::Reference(reference)) = subject.as_ref() else {
208
                return None;
209
            };
210
292
            let indices: Vec<Literal> = indices
211
292
                .iter()
212
292
                .map(|index| eval_index_at(index, generator, value))
213
                .collect::<Option<_>>()?;
214
292
            Some(SequenceSubject {
215
292
                decl: reference.ptr().clone(),
216
292
                indices: Some(indices),
217
292
            })
218
        }
219
        _ => None,
220
    }
221
292
}
222

            
223
2688941
fn as_card(expr: &Expr) -> Option<&Expr> {
224
2688941
    match expr {
225
22787
        Expr::Card(_, collection) => Some(collection.as_ref()),
226
2666154
        _ => None,
227
    }
228
2688941
}
229

            
230
1168
fn eval_card(expr: &Expr) -> Option<i32> {
231
1168
    match eval_constant(expr)? {
232
584
        Literal::Int(size) if size >= 0 => Some(size),
233
        _ => None,
234
    }
235
1168
}
236

            
237
18736
fn eval_int(expr: &Expr) -> Option<i32> {
238
18736
    match eval_constant(expr)? {
239
1423
        Literal::Int(size) if size >= 0 => Some(size),
240
        _ => None,
241
    }
242
18736
}
243

            
244
399586
fn eval_card_with_index(expr: &Expr, generator: &DeclarationPtr, value: &Literal) -> Option<i32> {
245
399586
    let collection = as_card(expr)?;
246
1168
    let instantiated = instantiate_index_expr(collection, generator, value)?;
247
1168
    eval_card(&Expr::Card(Metadata::new(), Moo::new(instantiated)))
248
399586
}
249

            
250
1168
fn instantiate_index_expr(
251
1168
    expr: &Expr,
252
1168
    generator: &DeclarationPtr,
253
1168
    value: &Literal,
254
1168
) -> Option<Expr> {
255
1168
    match expr {
256
        Expr::UnsafeIndex(meta, subject, indices) => {
257
1168
            let indices = instantiate_indices(indices, generator, value)?;
258
1168
            Some(Expr::UnsafeIndex(meta.clone(), subject.clone(), indices))
259
        }
260
        Expr::SafeIndex(meta, subject, indices) => {
261
            let indices = instantiate_indices(indices, generator, value)?;
262
            Some(Expr::SafeIndex(meta.clone(), subject.clone(), indices))
263
        }
264
        _ => None,
265
    }
266
1168
}
267

            
268
1168
fn instantiate_indices(
269
1168
    indices: &[Expr],
270
1168
    generator: &DeclarationPtr,
271
1168
    value: &Literal,
272
1168
) -> Option<Vec<Expr>> {
273
1168
    indices
274
1168
        .iter()
275
1168
        .map(|index| {
276
1168
            eval_index_at(index, generator, value)
277
1168
                .map(|lit| Expr::Atomic(Metadata::new(), Atom::Literal(lit)))
278
1168
        })
279
        .collect()
280
1168
}
281

            
282
1460
fn eval_index_at(index: &Expr, generator: &DeclarationPtr, value: &Literal) -> Option<Literal> {
283
1460
    if let Expr::Atomic(_, Atom::Reference(reference)) = index
284
1460
        && reference.ptr() == generator
285
    {
286
1460
        return Some(value.clone());
287
    }
288
    eval_constant(index)
289
1460
}
290

            
291
1351571
fn apply_sequence_size_facts(facts: Vec<SequenceSizeFact>) {
292
1351571
    let mut by_decl: HashMap<Name, (DeclarationPtr, Vec<SequenceSizeFact>)> = HashMap::new();
293
1351571
    for fact in facts {
294
729
        if fact.size < 0 {
295
            continue;
296
729
        }
297
729
        let name = fact.decl.name().clone();
298
729
        by_decl
299
729
            .entry(name)
300
496
            .or_insert_with(|| (fact.decl.clone(), Vec::new()))
301
            .1
302
729
            .push(fact);
303
    }
304

            
305
1351571
    for (decl, decl_facts) in by_decl.into_values() {
306
496
        apply_facts_to_declaration(decl, decl_facts);
307
    }
308
1351571
}
309

            
310
496
fn apply_facts_to_declaration(mut decl: DeclarationPtr, facts: Vec<SequenceSizeFact>) {
311
496
    if !matches!(
312
496
        &*decl.kind(),
313
496
        DeclarationKind::Find(_) | DeclarationKind::FindAuxiliary(_)
314
496
    ) {
315
319
        return;
316
177
    }
317

            
318
177
    let Some(domain) = decl.domain() else {
319
        return;
320
    };
321
177
    let Ok(ground) = domain.resolve() else {
322
        return;
323
    };
324

            
325
177
    let top_level: Vec<i32> = facts
326
177
        .iter()
327
410
        .filter(|fact| fact.indices.is_none())
328
118
        .map(|fact| fact.size)
329
        .collect();
330
177
    let indexed: Vec<(Vec<Literal>, i32)> = facts
331
177
        .iter()
332
410
        .filter_map(|fact| {
333
410
            fact.indices
334
410
                .as_ref()
335
292
                .map(|indices| (indices.clone(), fact.size))
336
410
        })
337
        .collect();
338

            
339
177
    if let GroundDomain::Sequence(attr, _) = ground.as_ref()
340
2
        && let Some(&size) = top_level.first()
341
2
        && top_level.iter().all(|&other| other == size)
342
2
        && let Some(new_size) = merge_to_size(&attr.size, size)
343
1
        && let Some(new_domain) = with_sequence_size(ground.as_ref(), new_size)
344
1
        && let Some(mut var) = decl.as_find_mut()
345
    {
346
1
        var.domain = new_domain.into();
347
1
        return;
348
176
    }
349

            
350
176
    let GroundDomain::Matrix(inner, _, _) = ground.as_ref() else {
351
117
        return;
352
    };
353
59
    let GroundDomain::Sequence(_, _) = inner.as_ref() else {
354
        return;
355
    };
356
59
    if indexed.is_empty() {
357
        return;
358
59
    }
359

            
360
59
    let Ok(shape) = shape_of_dom(ground.as_ref()) else {
361
        return;
362
    };
363
59
    let Some(index_tuples) = index_tuples(&shape.idx_doms) else {
364
        return;
365
    };
366
59
    if index_tuples.len() != shape.size {
367
        return;
368
59
    }
369

            
370
59
    let mut size_by_index: HashMap<Vec<Literal>, i32> = HashMap::new();
371
59
    for (indices, size) in indexed {
372
292
        if let Some(existing) = size_by_index.get(&indices)
373
            && *existing != size
374
        {
375
            return;
376
292
        }
377
292
        size_by_index.insert(indices, size);
378
    }
379
59
    if size_by_index.len() != index_tuples.len() {
380
        return;
381
59
    }
382

            
383
59
    let sizes: Vec<i32> = index_tuples
384
59
        .iter()
385
292
        .map(|indices| size_by_index[indices])
386
        .collect();
387
59
    let min_size = *sizes.iter().min().unwrap_or(&0);
388
59
    let max_size = *sizes.iter().max().unwrap_or(&0);
389
59
    let Some(new_inner_size) = merge_to_bounds(
390
59
        match inner.as_ref() {
391
59
            GroundDomain::Sequence(attr, _) => &attr.size,
392
            _ => return,
393
        },
394
59
        min_size,
395
59
        max_size,
396
    ) else {
397
        return;
398
    };
399

            
400
59
    let element_domains: Option<Vec<DomainPtr>> = sizes
401
59
        .iter()
402
59
        .copied()
403
292
        .map(|size| {
404
292
            let size_range = merge_to_size(
405
292
                match inner.as_ref() {
406
292
                    GroundDomain::Sequence(attr, _) => &attr.size,
407
                    _ => return None,
408
                },
409
292
                size,
410
            )?;
411
292
            Some(with_sequence_size(inner.as_ref(), size_range)?.into())
412
292
        })
413
        .collect();
414
59
    let Some(element_domains) = element_domains else {
415
        return;
416
    };
417

            
418
59
    let Some(new_inner) = with_sequence_size(inner.as_ref(), new_inner_size) else {
419
        return;
420
    };
421
59
    let GroundDomain::Matrix(_, idx_doms, _) = ground.as_ref() else {
422
        return;
423
    };
424
59
    let new_domain = GroundDomain::Matrix(Moo::new(new_inner), idx_doms.clone(), None);
425
59
    if let Some(mut var) = decl.as_find_mut() {
426
59
        var.domain = new_domain.into();
427
59
        var.element_domains = Some(element_domains);
428
    }
429
496
}
430

            
431
352
fn with_sequence_size(domain: &GroundDomain, size: Range<i32>) -> Option<GroundDomain> {
432
352
    let GroundDomain::Sequence(attr, inner) = domain else {
433
        return None;
434
    };
435
352
    let mut attr = attr.clone();
436
352
    attr.size = size;
437
352
    Some(GroundDomain::Sequence(attr, inner.clone()))
438
352
}
439

            
440
294
fn merge_to_size(current: &Range<i32>, size: i32) -> Option<Range<i32>> {
441
294
    current.contains(&size).then_some(Range::Single(size))
442
294
}
443

            
444
59
fn merge_to_bounds(current: &Range<i32>, min: i32, max: i32) -> Option<Range<i32>> {
445
59
    let lo = current.low().copied().unwrap_or(min).max(min);
446
59
    let hi = current.high().copied().unwrap_or(max).min(max);
447
59
    if lo > hi {
448
        return None;
449
59
    }
450
59
    Some(Range::new(Some(lo), Some(hi)))
451
59
}
452

            
453
59
fn index_tuples(idx_doms: &[Moo<GroundDomain>]) -> Option<Vec<Vec<Literal>>> {
454
59
    let lists: Vec<Vec<Literal>> = idx_doms
455
59
        .iter()
456
59
        .map(|domain| domain.values().ok().map(Iterator::collect))
457
        .collect::<Option<_>>()?;
458
59
    Some(lists.into_iter().fold(vec![vec![]], |prefixes, list| {
459
59
        prefixes
460
59
            .into_iter()
461
59
            .flat_map(|prefix| {
462
292
                list.iter().map(move |item| {
463
292
                    let mut next = prefix.clone();
464
292
                    next.push(item.clone());
465
292
                    next
466
292
                })
467
59
            })
468
            .collect()
469
59
    }))
470
59
}
471

            
472
#[cfg(test)]
473
mod tests {
474
    use super::*;
475
    use crate::ast::{
476
        AbstractLiteral, DeclarationPtr, Domain, JectivityAttr, Name, Reference, SequenceAttr,
477
        SymbolTablePtr, comprehension::ComprehensionBuilder,
478
    };
479
    use crate::{domain_int, matrix_expr, range};
480

            
481
3
    fn sequence_attr(size: Range<i32>) -> SequenceAttr {
482
3
        SequenceAttr {
483
3
            size,
484
3
            jectivity: JectivityAttr::None,
485
3
            representation: None,
486
3
        }
487
3
    }
488

            
489
2
    fn int_lit(value: i32) -> Expr {
490
2
        Expr::Atomic(Metadata::new(), Atom::Literal(Literal::Int(value)))
491
2
    }
492

            
493
6
    fn ref_expr(decl: &DeclarationPtr) -> Expr {
494
6
        Expr::Atomic(
495
6
            Metadata::new(),
496
6
            Atom::Reference(Reference::new(decl.clone())),
497
6
        )
498
6
    }
499

            
500
4
    fn card(expr: Expr) -> Expr {
501
4
        Expr::Card(Metadata::new(), Moo::new(expr))
502
4
    }
503

            
504
3
    fn eq(lhs: Expr, rhs: Expr) -> Expr {
505
3
        Expr::Eq(Metadata::new(), Moo::new(lhs), Moo::new(rhs))
506
3
    }
507

            
508
2
    fn sequence_size(decl: &DeclarationPtr) -> Range<i32> {
509
2
        let domain = decl.domain().unwrap();
510
2
        let GroundDomain::Sequence(attr, _) = domain.as_ground().unwrap() else {
511
            panic!("expected a sequence domain");
512
        };
513
2
        attr.size.clone()
514
2
    }
515

            
516
    #[test]
517
1
    fn tightens_a_top_level_sequence_from_a_cardinality_equality() {
518
1
        let mut model = Model::new(Default::default());
519
1
        let s = DeclarationPtr::new_find(
520
1
            Name::user("s"),
521
1
            Domain::sequence(sequence_attr(range!(0..10)), domain_int!(1..3)),
522
1
        );
523
1
        model.symbols_mut().insert(s.clone()).unwrap();
524
1
        model.add_constraint(eq(card(ref_expr(&s)), int_lit(4)));
525

            
526
1
        tighten_domains_from_constraints(&mut model);
527

            
528
1
        let s = model.symbols().lookup_local(&Name::user("s")).unwrap();
529
1
        assert_eq!(sequence_size(&s), Range::Single(4));
530
1
    }
531

            
532
    #[test]
533
1
    fn leaves_the_domain_alone_when_the_size_is_outside_the_declared_range() {
534
1
        let mut model = Model::new(Default::default());
535
1
        let s = DeclarationPtr::new_find(
536
1
            Name::user("s"),
537
1
            Domain::sequence(sequence_attr(range!(0..3)), domain_int!(1..3)),
538
1
        );
539
1
        model.symbols_mut().insert(s.clone()).unwrap();
540
1
        model.add_constraint(eq(card(ref_expr(&s)), int_lit(4)));
541

            
542
1
        tighten_domains_from_constraints(&mut model);
543

            
544
1
        let s = model.symbols().lookup_local(&Name::user("s")).unwrap();
545
1
        assert_eq!(sequence_size(&s), range!(0..3));
546
1
    }
547

            
548
    #[test]
549
1
    fn tightens_matrix_of_sequence_cells_from_a_forall_cardinality_equality() {
550
1
        let mut model = Model::new(Default::default());
551
1
        let clues_lit = Literal::AbstractLiteral(AbstractLiteral::Matrix(
552
1
            vec![
553
1
                Literal::AbstractLiteral(AbstractLiteral::Sequence(vec![
554
1
                    Literal::Int(1),
555
1
                    Literal::Int(2),
556
1
                    Literal::Int(3),
557
1
                ])),
558
1
                Literal::AbstractLiteral(AbstractLiteral::Sequence(vec![
559
1
                    Literal::Int(1),
560
1
                    Literal::Int(1),
561
1
                    Literal::Int(1),
562
1
                    Literal::Int(1),
563
1
                    Literal::Int(1),
564
1
                ])),
565
1
            ],
566
1
            Moo::new(GroundDomain::Int(vec![range!(1..2)], None)),
567
1
        ));
568
1
        let clues = DeclarationPtr::new_value_letting(Name::user("clues"), Expr::from(clues_lit));
569
1
        let locs = DeclarationPtr::new_find(
570
1
            Name::user("locs"),
571
1
            Domain::matrix(
572
1
                Domain::sequence(sequence_attr(range!(0..10)), domain_int!(1..9)),
573
1
                vec![domain_int!(1..2)],
574
1
            ),
575
1
        );
576
1
        model.symbols_mut().insert(clues.clone()).unwrap();
577
1
        model.symbols_mut().insert(locs.clone()).unwrap();
578

            
579
1
        let builder = ComprehensionBuilder::new(SymbolTablePtr::new());
580
1
        let i_template = DeclarationPtr::new_find(Name::user("i"), domain_int!(1..2));
581
1
        let mut builder = builder.generator(i_template);
582
1
        let i = builder
583
1
            .generator_symboltable()
584
1
            .read()
585
1
            .lookup_local(&Name::user("i"))
586
1
            .expect("generator i");
587
1
        let body = eq(
588
1
            card(Expr::UnsafeIndex(
589
1
                Metadata::new(),
590
1
                Moo::new(ref_expr(&locs)),
591
1
                vec![ref_expr(&i)],
592
1
            )),
593
1
            card(Expr::UnsafeIndex(
594
1
                Metadata::new(),
595
1
                Moo::new(ref_expr(&clues)),
596
1
                vec![ref_expr(&i)],
597
1
            )),
598
1
        );
599
1
        let comprehension = builder.with_return_value(body);
600
1
        model.add_constraint(Expr::And(
601
1
            Metadata::new(),
602
1
            Moo::new(matrix_expr![Expr::Comprehension(
603
1
                Metadata::new(),
604
1
                Moo::new(comprehension)
605
1
            )]),
606
1
        ));
607

            
608
1
        tighten_domains_from_constraints(&mut model);
609

            
610
1
        let locs = model.symbols().lookup_local(&Name::user("locs")).unwrap();
611
1
        let var = locs.as_find().unwrap();
612
1
        let domains = var.element_domains.as_ref().expect("per-cell domains");
613
1
        assert_eq!(domains.len(), 2);
614
1
        let sizes: Vec<Range<i32>> = domains
615
1
            .iter()
616
2
            .map(|domain| {
617
2
                let GroundDomain::Sequence(attr, _) = domain.as_ground().unwrap() else {
618
                    panic!("expected a sequence element domain");
619
                };
620
2
                attr.size.clone()
621
2
            })
622
            .collect();
623
1
        assert_eq!(sizes, vec![Range::Single(3), Range::Single(5)]);
624
1
    }
625
}