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)]
45pub enum UnresolvedDomain {
47 Int(Vec<Range<IntVal>>),
48 IntFromValues(Moo<Expression>),
53 Tuple(Vec<DomainPtr>),
55 Record(Vec<FieldUnresolved>),
57 Variant(Vec<FieldUnresolved>),
59 Matrix(DomainPtr, Vec<DomainPtr>),
61 Sequence(SequenceAttr<IntVal>, DomainPtr),
62 Set(SetAttr<IntVal>, DomainPtr),
64 MSet(MSetAttr<IntVal>, DomainPtr),
65 Function(FuncAttr<IntVal>, DomainPtr, DomainPtr),
67 Relation(RelAttr<IntVal>, Vec<DomainPtr>),
69 Partition(PartitionAttr<IntVal>, DomainPtr),
70 Permutation(PermutationAttr<IntVal>, DomainPtr),
71 #[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 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 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 #[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 #[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 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 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 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
615pub 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}