Skip to main content

conjure_cp_core/ast/domains/
unresolved.rs

1use std::fmt::{Display, Formatter};
2use std::iter::zip;
3
4use crate::ast::domains::attrs::MSetAttr;
5use crate::ast::domains::attrs::PartitionAttr;
6use crate::ast::domains::attrs::PermutationAttr;
7use crate::ast::domains::attrs::SetAttr;
8use crate::ast::domains::ground::FieldGround;
9use crate::ast::records::Field;
10use crate::ast::{
11    DomainOpError, Expression, FuncAttr, Moo, Reference, RelAttr, ReturnType, SequenceAttr,
12    Typeable,
13    domains::{DomainPtr, GroundDomain, int_val::IntVal, range::Range},
14    pretty::pretty_vec,
15};
16use crate::bug;
17
18use funcmap::{FuncMap, TryFuncMap};
19use itertools::Itertools;
20use polyquine::Quine;
21use serde::{Deserialize, Serialize};
22use uniplate::Uniplate;
23
24pub(super) type FieldUnresolved = Field<DomainPtr>;
25
26impl From<FieldGround> for FieldUnresolved {
27    fn from(v: FieldGround) -> Self {
28        v.func_map(DomainPtr::from)
29    }
30}
31
32impl TryFrom<FieldUnresolved> for FieldGround {
33    type Error = DomainOpError;
34    fn try_from(v: FieldUnresolved) -> Result<Self, Self::Error> {
35        v.try_func_map(DomainPtr::try_into)
36    }
37}
38
39#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Quine, Uniplate)]
40#[path_prefix(conjure_cp::ast)]
41#[biplate(to=Expression)]
42#[biplate(to=Reference)]
43#[biplate(to=IntVal)]
44#[biplate(to=DomainPtr)]
45/// Variants use the project-wide type/domain ordering; keep broad matches in the same order.
46pub enum UnresolvedDomain {
47    Int(Vec<Range<IntVal>>),
48    /// An integer domain given by the values of a collection, as in `int([i | i <- nums])`.
49    ///
50    /// The collection may be built from `given` declarations, so it stays an expression until
51    /// those are instantiated and it can be evaluated.
52    IntFromValues(Moo<Expression>),
53    /// A tuple of N elements, each with its own domain
54    Tuple(Vec<DomainPtr>),
55    /// A record
56    Record(Vec<FieldUnresolved>),
57    /// A variant domain with its domain options (reusing field entries)
58    Variant(Vec<FieldUnresolved>),
59    /// A n-dimensional matrix with a value domain and n-index domains
60    Matrix(DomainPtr, Vec<DomainPtr>),
61    Sequence(SequenceAttr<IntVal>, DomainPtr),
62    /// A set of elements drawn from the inner domain
63    Set(SetAttr<IntVal>, DomainPtr),
64    MSet(MSetAttr<IntVal>, DomainPtr),
65    /// A function with attributes, domain, and range
66    Function(FuncAttr<IntVal>, DomainPtr, DomainPtr),
67    /// A relation as a set of tuples
68    Relation(RelAttr<IntVal>, Vec<DomainPtr>),
69    Partition(PartitionAttr<IntVal>, DomainPtr),
70    Permutation(PermutationAttr<IntVal>, DomainPtr),
71    /// A reference to a domain letting
72    #[polyquine_skip]
73    Reference(Reference),
74}
75
76impl UnresolvedDomain {
77    pub(super) fn from_ground(domain: &GroundDomain) -> Option<UnresolvedDomain> {
78        let unresolved = match domain {
79            GroundDomain::Empty(_) | GroundDomain::Bool => return None,
80            GroundDomain::Int(ranges) => {
81                UnresolvedDomain::Int(ranges.iter().cloned().map(Into::into).collect())
82            }
83            GroundDomain::Tuple(inners) => {
84                UnresolvedDomain::Tuple(inners.iter().map(DomainPtr::from).collect())
85            }
86            GroundDomain::Record(fields) => {
87                UnresolvedDomain::Record(fields.iter().cloned().map(Into::into).collect())
88            }
89            GroundDomain::Variant(fields) => {
90                UnresolvedDomain::Variant(fields.iter().cloned().map(Into::into).collect())
91            }
92            GroundDomain::Matrix(inner, indices) => UnresolvedDomain::Matrix(
93                DomainPtr::from(inner),
94                indices.iter().map(DomainPtr::from).collect(),
95            ),
96            GroundDomain::Sequence(attributes, inner) => {
97                UnresolvedDomain::Sequence(attributes.clone().into(), DomainPtr::from(inner))
98            }
99            GroundDomain::Set(attributes, inner) => {
100                UnresolvedDomain::Set(attributes.clone().into(), DomainPtr::from(inner))
101            }
102            GroundDomain::MSet(attributes, inner) => {
103                UnresolvedDomain::MSet(attributes.clone().into(), DomainPtr::from(inner))
104            }
105            GroundDomain::Function(attributes, domain, codomain) => UnresolvedDomain::Function(
106                attributes.clone().into(),
107                DomainPtr::from(domain),
108                DomainPtr::from(codomain),
109            ),
110            GroundDomain::Relation(attributes, inners) => UnresolvedDomain::Relation(
111                attributes.clone().into(),
112                inners.iter().map(DomainPtr::from).collect(),
113            ),
114            GroundDomain::Partition(attributes, inner) => {
115                UnresolvedDomain::Partition(attributes.clone().into(), DomainPtr::from(inner))
116            }
117            GroundDomain::Permutation(attributes, inner) => {
118                UnresolvedDomain::Permutation(attributes.clone().into(), DomainPtr::from(inner))
119            }
120        };
121
122        Some(unresolved)
123    }
124
125    /// Whether this domain takes its values from a collection expression anywhere inside it.
126    ///
127    /// Such a domain is expensive to resolve -- the collection is evaluated afresh each time -- so
128    /// callers ground it once rather than leaving it to be re-resolved on every query.
129    pub fn has_int_from_values(&self) -> bool {
130        match self {
131            UnresolvedDomain::IntFromValues(_) => true,
132            UnresolvedDomain::Int(_) => false,
133            UnresolvedDomain::Tuple(inners) | UnresolvedDomain::Relation(_, inners) => {
134                inners.iter().any(domain_has_int_from_values)
135            }
136            UnresolvedDomain::Record(entries) | UnresolvedDomain::Variant(entries) => entries
137                .iter()
138                .any(|entry| domain_has_int_from_values(&entry.value)),
139            UnresolvedDomain::Matrix(value, indices) => {
140                domain_has_int_from_values(value) || indices.iter().any(domain_has_int_from_values)
141            }
142            UnresolvedDomain::Sequence(_, inner)
143            | UnresolvedDomain::Set(_, inner)
144            | UnresolvedDomain::MSet(_, inner)
145            | UnresolvedDomain::Partition(_, inner)
146            | UnresolvedDomain::Permutation(_, inner) => domain_has_int_from_values(inner),
147            UnresolvedDomain::Function(_, from, to) => {
148                domain_has_int_from_values(from) || domain_has_int_from_values(to)
149            }
150            UnresolvedDomain::Reference(_) => false,
151        }
152    }
153
154    pub fn resolve(&self) -> Result<GroundDomain, DomainOpError> {
155        match self {
156            UnresolvedDomain::IntFromValues(expr) => {
157                let values = crate::ast::eval::generator_values_from_expr(expr)
158                    .ok_or(DomainOpError::NotGround)?;
159                let mut ranges = Vec::with_capacity(values.len());
160                for value in values {
161                    let crate::ast::Literal::Int(value) = value else {
162                        return Err(DomainOpError::WrongType);
163                    };
164                    ranges.push(Range::Single(value));
165                }
166                Ok(GroundDomain::Int(Range::squeeze(&ranges)))
167            }
168            UnresolvedDomain::Int(rngs) => rngs
169                .iter()
170                .map(Range::<IntVal>::resolve)
171                .collect::<Result<Vec<_>, _>>()
172                .map(|ranges| {
173                    let ranges = ranges
174                        .into_iter()
175                        .filter(
176                            |range| !matches!(range, Range::Bounded(lower, upper) if lower > upper),
177                        )
178                        .collect::<Vec<_>>();
179                    GroundDomain::Int(Range::squeeze(&ranges))
180                }),
181            UnresolvedDomain::Tuple(inners) => inners
182                .iter()
183                .map(DomainPtr::resolve)
184                .collect::<Result<_, _>>()
185                .map(GroundDomain::Tuple),
186            UnresolvedDomain::Record(entries) => entries
187                .iter()
188                .map(|f| {
189                    f.value.resolve().map(|gd| FieldGround {
190                        name: f.name.clone(),
191                        value: gd,
192                    })
193                })
194                .collect::<Result<_, _>>()
195                .map(GroundDomain::Record),
196            UnresolvedDomain::Variant(entries) => entries
197                .iter()
198                .map(|f| {
199                    f.value.resolve().map(|gd| FieldGround {
200                        name: f.name.clone(),
201                        value: gd,
202                    })
203                })
204                .collect::<Result<_, _>>()
205                .map(GroundDomain::Variant),
206            UnresolvedDomain::Matrix(inner, idx_doms) => {
207                let inner_gd = inner.resolve()?;
208                idx_doms
209                    .iter()
210                    .map(DomainPtr::resolve)
211                    .collect::<Result<_, _>>()
212                    .map(|idx| GroundDomain::Matrix(inner_gd, idx))
213            }
214            UnresolvedDomain::Sequence(attr, inner) => {
215                Ok(GroundDomain::Sequence(attr.resolve()?, inner.resolve()?))
216            }
217            UnresolvedDomain::Set(attr, inner) => {
218                Ok(GroundDomain::Set(attr.resolve()?, inner.resolve()?))
219            }
220            UnresolvedDomain::MSet(attr, inner) => {
221                Ok(GroundDomain::MSet(attr.resolve()?, inner.resolve()?))
222            }
223            UnresolvedDomain::Function(attr, dom, cdom) => Ok(GroundDomain::Function(
224                attr.resolve()?,
225                dom.resolve()?,
226                cdom.resolve()?,
227            )),
228            UnresolvedDomain::Relation(attr, inners) => {
229                let resolved_attr = attr.resolve()?;
230                inners
231                    .iter()
232                    .map(DomainPtr::resolve)
233                    .collect::<Result<_, _>>()
234                    .map(|items| GroundDomain::Relation(resolved_attr, items))
235            }
236            UnresolvedDomain::Partition(attr, inner) => {
237                Ok(GroundDomain::Partition(attr.resolve()?, inner.resolve()?))
238            }
239            UnresolvedDomain::Permutation(attr, inner) => {
240                Ok(GroundDomain::Permutation(attr.resolve()?, inner.resolve()?))
241            }
242            UnresolvedDomain::Reference(re) => re
243                .ptr
244                .as_domain_letting()
245                .unwrap_or_else(|| {
246                    bug!("Reference domain should point to domain letting, but got {re}")
247                })
248                .resolve()
249                .map(Moo::unwrap_or_clone),
250        }
251    }
252
253    pub(super) fn union_unresolved(
254        &self,
255        other: &UnresolvedDomain,
256    ) -> Result<UnresolvedDomain, DomainOpError> {
257        // Keep implemented variants before unsupported variants so mixed-domain unions report the
258        // established error. Each group uses declaration order.
259        match (self, other) {
260            (UnresolvedDomain::Int(lhs), UnresolvedDomain::Int(rhs)) => {
261                let merged = lhs.iter().chain(rhs.iter()).cloned().collect_vec();
262                Ok(UnresolvedDomain::Int(merged))
263            }
264            (UnresolvedDomain::IntFromValues(_), _) | (_, UnresolvedDomain::IntFromValues(_)) => {
265                Err(DomainOpError::NotGround)
266            }
267            (UnresolvedDomain::Int(_), _) | (_, UnresolvedDomain::Int(_)) => {
268                Err(DomainOpError::WrongType)
269            }
270            (UnresolvedDomain::Tuple(lhs), UnresolvedDomain::Tuple(rhs))
271                if lhs.len() == rhs.len() =>
272            {
273                let mut merged = Vec::new();
274                for (l, r) in zip(lhs, rhs) {
275                    merged.push(l.union(r)?)
276                }
277                Ok(UnresolvedDomain::Tuple(merged))
278            }
279            (UnresolvedDomain::Tuple(_), _) | (_, UnresolvedDomain::Tuple(_)) => {
280                Err(DomainOpError::WrongType)
281            }
282            (UnresolvedDomain::Matrix(in1, idx1), UnresolvedDomain::Matrix(in2, idx2))
283                if idx1 == idx2 =>
284            {
285                Ok(UnresolvedDomain::Matrix(in1.union(in2)?, idx1.clone()))
286            }
287            (UnresolvedDomain::Matrix(_, _), _) | (_, UnresolvedDomain::Matrix(_, _)) => {
288                Err(DomainOpError::WrongType)
289            }
290            (UnresolvedDomain::Set(_, in1), UnresolvedDomain::Set(_, in2)) => {
291                Ok(UnresolvedDomain::Set(SetAttr::default(), in1.union(in2)?))
292            }
293            (UnresolvedDomain::Set(_, _), _) | (_, UnresolvedDomain::Set(_, _)) => {
294                Err(DomainOpError::WrongType)
295            }
296            (UnresolvedDomain::MSet(_, in1), UnresolvedDomain::MSet(_, in2)) => {
297                Ok(UnresolvedDomain::MSet(MSetAttr::default(), in1.union(in2)?))
298            }
299            (UnresolvedDomain::MSet(_, _), _) | (_, UnresolvedDomain::MSet(_, _)) => {
300                Err(DomainOpError::WrongType)
301            }
302            (UnresolvedDomain::Relation(_, in1s), UnresolvedDomain::Relation(_, in2s)) => {
303                let mut inners = Vec::new();
304                for (in1, in2) in in1s.iter().zip(in2s.iter()) {
305                    inners.push(in1.union(in2)?)
306                }
307                Ok(UnresolvedDomain::Relation(RelAttr::default(), inners))
308            }
309            (UnresolvedDomain::Relation(_, _), _) | (_, UnresolvedDomain::Relation(_, _)) => {
310                Err(DomainOpError::WrongType)
311            }
312            // TODO: Could we define semantics for merging record domains?
313            #[allow(unreachable_patterns)]
314            (UnresolvedDomain::Record(_), _) | (_, UnresolvedDomain::Record(_)) => {
315                Err(DomainOpError::WrongType)
316            }
317            #[allow(unreachable_patterns)]
318            (UnresolvedDomain::Variant(_), _) | (_, UnresolvedDomain::Variant(_)) => {
319                Err(DomainOpError::WrongType)
320            }
321            #[allow(unreachable_patterns)]
322            (UnresolvedDomain::Sequence(_, _), _) | (_, UnresolvedDomain::Sequence(_, _)) => {
323                Err(DomainOpError::WrongType)
324            }
325            #[allow(unreachable_patterns)]
326            (UnresolvedDomain::Function(_, _, _), _) | (_, UnresolvedDomain::Function(_, _, _)) => {
327                Err(DomainOpError::WrongType)
328            }
329            #[allow(unreachable_patterns)]
330            (UnresolvedDomain::Partition(_, _), _) | (_, UnresolvedDomain::Partition(_, _)) => {
331                Err(DomainOpError::WrongType)
332            }
333            #[allow(unreachable_patterns)]
334            (UnresolvedDomain::Permutation(_, _), _) | (_, UnresolvedDomain::Permutation(_, _)) => {
335                Err(DomainOpError::WrongType)
336            }
337            // TODO: Could we support unions of reference domains symbolically?
338            #[allow(unreachable_patterns)]
339            (UnresolvedDomain::Reference(_), _) | (_, UnresolvedDomain::Reference(_)) => {
340                Err(DomainOpError::NotGround)
341            }
342        }
343    }
344
345    pub fn element_domain(&self) -> Option<DomainPtr> {
346        match self {
347            UnresolvedDomain::Matrix(inner, _) => Some(inner.clone()),
348            // A sequence is a function from int(1..|s|), and iterating a function yields its
349            // pairs, so iterating a sequence yields (position, value). Mirrors
350            // `GroundDomain::element_domain`.
351            UnresolvedDomain::Sequence(attr, inner_dom) => {
352                let max = match &attr.size {
353                    Range::Single(max) | Range::UnboundedL(max) | Range::Bounded(_, max) => {
354                        max.clone()
355                    }
356                    Range::UnboundedR(_) | Range::Unbounded => return None,
357                };
358                let positions = Moo::new(crate::ast::Domain::Unresolved(Moo::new(
359                    UnresolvedDomain::Int(vec![Range::Bounded(IntVal::new_const(1), max)]),
360                )));
361                Some(Moo::new(crate::ast::Domain::Unresolved(Moo::new(
362                    UnresolvedDomain::Tuple(vec![positions, inner_dom.clone()]),
363                ))))
364            }
365            UnresolvedDomain::Set(_, inner_dom) => Some(inner_dom.clone()),
366            _ => None,
367        }
368    }
369
370    /// True if any domain in this tree has a representation preference.
371    pub fn has_representation_preference(&self) -> bool {
372        match self {
373            UnresolvedDomain::Int(_) | UnresolvedDomain::IntFromValues(_) => false,
374            UnresolvedDomain::Tuple(inners) => {
375                inners.iter().any(|d| d.has_representation_preference())
376            }
377            UnresolvedDomain::Record(entries) => entries
378                .iter()
379                .any(|f| f.value.has_representation_preference()),
380            UnresolvedDomain::Variant(entries) => entries
381                .iter()
382                .any(|f| f.value.has_representation_preference()),
383            UnresolvedDomain::Matrix(inner, idxs) => {
384                inner.has_representation_preference()
385                    || idxs.iter().any(|d| d.has_representation_preference())
386            }
387            UnresolvedDomain::Sequence(attr, inner) => {
388                attr.representation.is_some() || inner.has_representation_preference()
389            }
390            UnresolvedDomain::Set(attr, inner) => {
391                attr.representation.is_some() || inner.has_representation_preference()
392            }
393            UnresolvedDomain::MSet(attr, inner) => {
394                attr.representation.is_some() || inner.has_representation_preference()
395            }
396            UnresolvedDomain::Function(_, dom, cdom) => {
397                dom.has_representation_preference() || cdom.has_representation_preference()
398            }
399            UnresolvedDomain::Relation(_, inners) => {
400                inners.iter().any(|d| d.has_representation_preference())
401            }
402            UnresolvedDomain::Partition(_, inner) => inner.has_representation_preference(),
403            UnresolvedDomain::Permutation(_, inner) => inner.has_representation_preference(),
404            UnresolvedDomain::Reference(re) => re
405                .domain()
406                .is_some_and(|d| d.has_representation_preference()),
407        }
408    }
409
410    /// Format this domain in Essence type style, omitting size attributes and integer ranges.
411    pub fn as_type_string(&self) -> String {
412        match self {
413            UnresolvedDomain::Int(_) | UnresolvedDomain::IntFromValues(_) => "int".to_string(),
414            UnresolvedDomain::Tuple(inners) => {
415                format!(
416                    "tuple ({})",
417                    inners.iter().map(|d| d.as_type_string()).join(", ")
418                )
419            }
420            UnresolvedDomain::Record(entries) => {
421                let inners = entries
422                    .iter()
423                    .map(|f| format!("{}: {}", f.name, f.value.as_type_string()))
424                    .join(", ");
425                format!("record {{{inners}}}")
426            }
427            UnresolvedDomain::Variant(entries) => {
428                let inners = entries
429                    .iter()
430                    .map(|f| format!("{}: {}", f.name, f.value.as_type_string()))
431                    .join(", ");
432                format!("variant {{{inners}}}")
433            }
434            UnresolvedDomain::Matrix(inner, idxs) => {
435                let idxs = idxs.iter().map(|d| d.as_type_string()).join(", ");
436                format!("matrix indexed by [{idxs}] of {}", inner.as_type_string())
437            }
438            UnresolvedDomain::Sequence(_, inner) => {
439                format!("sequence of {}", inner.as_type_string())
440            }
441            UnresolvedDomain::Set(attrs, inner) => {
442                let mut out = String::from("set");
443                if let Some(repr) = &attrs.representation {
444                    out.push_str(" (representation ");
445                    out.push_str(repr);
446                    out.push(')');
447                }
448                out.push_str(" of ");
449                out.push_str(&inner.as_type_string());
450                out
451            }
452            UnresolvedDomain::MSet(attrs, inner) => {
453                let mut out = String::from("mset");
454                if let Some(repr) = &attrs.representation {
455                    out.push_str(" (representation ");
456                    out.push_str(repr);
457                    out.push(')');
458                }
459                out.push_str(" of ");
460                out.push_str(&inner.as_type_string());
461                out
462            }
463            UnresolvedDomain::Function(_, dom, cdom) => {
464                format!(
465                    "function {} --> {}",
466                    dom.as_type_string(),
467                    cdom.as_type_string()
468                )
469            }
470            UnresolvedDomain::Relation(_, inners) => {
471                format!(
472                    "relation of ({})",
473                    inners.iter().map(|d| d.as_type_string()).join(" * ")
474                )
475            }
476            UnresolvedDomain::Partition(_, inner) => {
477                format!("partition from {}", inner.as_type_string())
478            }
479            UnresolvedDomain::Permutation(_, inner) => {
480                format!("permutation of {}", inner.as_type_string())
481            }
482            UnresolvedDomain::Reference(re) => re.to_string(),
483        }
484    }
485}
486
487impl Typeable for UnresolvedDomain {
488    fn return_type(&self) -> ReturnType {
489        match self {
490            UnresolvedDomain::Int(_) | UnresolvedDomain::IntFromValues(_) => ReturnType::Int,
491            UnresolvedDomain::Tuple(inners) => {
492                let mut inner_types = Vec::new();
493                for inner in inners {
494                    inner_types.push(inner.return_type());
495                }
496                ReturnType::Tuple(inner_types)
497            }
498            UnresolvedDomain::Record(entries) => {
499                let mut entry_types = Vec::new();
500                for entry in entries {
501                    entry_types.push(entry.clone().func_map(|x| x.return_type()));
502                }
503                ReturnType::Record(entry_types)
504            }
505            UnresolvedDomain::Variant(entries) => {
506                let mut entry_types = Vec::new();
507                for entry in entries {
508                    entry_types.push(entry.clone().func_map(|x| x.return_type()));
509                }
510                ReturnType::Variant(entry_types)
511            }
512            UnresolvedDomain::Matrix(inner, _idx) => {
513                ReturnType::Matrix(Box::new(inner.return_type()))
514            }
515            UnresolvedDomain::Sequence(_attr, inner) => {
516                ReturnType::Sequence(Box::new(inner.return_type()))
517            }
518            UnresolvedDomain::Set(_attr, inner) => ReturnType::Set(Box::new(inner.return_type())),
519            UnresolvedDomain::MSet(_attr, inner) => ReturnType::MSet(Box::new(inner.return_type())),
520            UnresolvedDomain::Function(_, dom, cdom) => {
521                ReturnType::Function(Box::new(dom.return_type()), Box::new(cdom.return_type()))
522            }
523            UnresolvedDomain::Relation(_, inners) => {
524                let mut inner_types = Vec::new();
525                for inner in inners {
526                    inner_types.push(inner.return_type());
527                }
528                ReturnType::Relation(inner_types)
529            }
530            UnresolvedDomain::Partition(_, inner) => {
531                ReturnType::Partition(Box::new(inner.return_type()))
532            }
533            UnresolvedDomain::Permutation(_, inner) => {
534                ReturnType::Permutation(Box::new(inner.return_type()))
535            }
536            UnresolvedDomain::Reference(re) => re.return_type(),
537        }
538    }
539}
540
541impl Display for FieldUnresolved {
542    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
543        write!(f, "{}: {}", self.name, self.value)
544    }
545}
546
547impl Display for UnresolvedDomain {
548    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
549        match &self {
550            UnresolvedDomain::Int(ranges) => {
551                if ranges.iter().all(Range::is_lower_or_upper_bounded) {
552                    let rngs: String = ranges.iter().map(|r| format!("{r}")).join(", ");
553                    write!(f, "int({})", rngs)
554                } else {
555                    write!(f, "int")
556                }
557            }
558            UnresolvedDomain::IntFromValues(expr) => write!(f, "int({expr})"),
559            UnresolvedDomain::Tuple(domains) => {
560                write!(f, "tuple ({})", domains.iter().join(","))
561            }
562            UnresolvedDomain::Record(entries) => {
563                let inners = entries.iter().map(|t| format!("{}", t)).join(", ");
564                write!(f, "record {{{inners}}}",)
565            }
566            UnresolvedDomain::Variant(entries) => {
567                let inners = entries.iter().map(|t| format!("{}", t)).join(", ");
568                write!(f, "variant {{{inners}}}",)
569            }
570            UnresolvedDomain::Matrix(value_domain, index_domains) => {
571                write!(
572                    f,
573                    "matrix indexed by {} of {value_domain}",
574                    pretty_vec(&index_domains.iter().collect_vec())
575                )
576            }
577            UnresolvedDomain::Sequence(attrs, inner_dom) => {
578                write!(f, "sequence {attrs} of {inner_dom}")
579            }
580            UnresolvedDomain::Set(attrs, inner_dom) => {
581                write!(f, "set")?;
582                let attrs = attrs.to_string();
583                if attrs.is_empty() {
584                    write!(f, " of {inner_dom}")
585                } else {
586                    write!(f, " {attrs} of {inner_dom}")
587                }
588            }
589            UnresolvedDomain::MSet(attrs, inner_dom) => {
590                write!(f, "mset")?;
591                let attrs = attrs.to_string();
592                if attrs.is_empty() {
593                    write!(f, " of {inner_dom}")
594                } else {
595                    write!(f, " {attrs} of {inner_dom}")
596                }
597            }
598            UnresolvedDomain::Function(attribute, domain, codomain) => {
599                write!(f, "function {} {} --> {} ", attribute, domain, codomain)
600            }
601            UnresolvedDomain::Relation(attrs, domains) => {
602                write!(f, "relation {} of ({})", attrs, domains.iter().join(" * "))
603            }
604            UnresolvedDomain::Partition(attrs, inner_dom) => {
605                write!(f, "partition {attrs} from {inner_dom}")
606            }
607            UnresolvedDomain::Permutation(attrs, inner_dom) => {
608                write!(f, "permutation {attrs} of {inner_dom}")
609            }
610            UnresolvedDomain::Reference(re) => write!(f, "{re}"),
611        }
612    }
613}
614
615/// Whether `domain` takes its values from a collection expression anywhere inside it.
616pub fn domain_has_int_from_values(domain: &DomainPtr) -> bool {
617    match &**domain {
618        crate::ast::Domain::Ground(_) => false,
619        crate::ast::Domain::Unresolved(unresolved) => unresolved.has_int_from_values(),
620    }
621}