Lines
40.25 %
Functions
17.14 %
use crate::ast::enumerated::{EnumVariant, EnumeratedType};
use crate::ast::{DeclarationKind, Literal, eval_constant};
use std::fmt::{Display, Formatter};
use std::iter::zip;
use std::ops::Deref;
use crate::ast::domains::attrs::MSetAttr;
use crate::ast::domains::attrs::PartitionAttr;
use crate::ast::domains::attrs::PermutationAttr;
use crate::ast::domains::attrs::SetAttr;
use crate::ast::domains::ground::{
FieldGround, int_type_string, representation_attribute, write_int_domain,
};
use crate::ast::records::Field;
use crate::ast::{
DomainOpError, Expression, FuncAttr, Moo, Reference, RelAttr, ReturnType, SequenceAttr,
Typeable,
domains::{DomainPtr, GroundDomain, int_val::IntVal, range::Range},
pretty::pretty_vec,
use crate::bug;
use funcmap::{FuncMap, TryFuncMap};
use itertools::Itertools;
use polyquine::Quine;
use serde::{Deserialize, Serialize};
use uniplate::Uniplate;
fn eval_expr_to_enum_variant(expr: &Expression, expected: &Moo<EnumeratedType>) -> Option<u32> {
let Literal::EnumVariant(EnumVariant { ty, variant }) = eval_constant(expr)? else {
bug!("Expected enum variant expression, got: {expr}");
if ty != *expected {
bug!("Expected variant of enum {expected}, got {ty}:");
}
Some(variant)
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Uniplate)]
#[biplate(to=Expression)]
#[biplate(to=Reference)]
pub struct EnumVariantVal(pub Reference);
impl EnumVariantVal {
fn resolve(&self, expected: &Moo<EnumeratedType>) -> Option<u32> {
let re = &self.0;
match re.ptr.kind().deref() {
DeclarationKind::ValueLetting(expr, _)
| DeclarationKind::TemporaryValueLetting(expr)
| DeclarationKind::QuantifiedExpr(expr) => eval_expr_to_enum_variant(expr, expected),
// If this is an int given we will be able to resolve it eventually, but not yet
DeclarationKind::Given(_) => None,
DeclarationKind::Quantified(inner) => {
if let Some(generator) = inner.generator()
&& let Some(expr) = generator.as_value_letting()
{
eval_expr_to_enum_variant(&expr, expected)
} else {
None
// Decision variables inside domains are unresolved until solving.
DeclarationKind::Find(_) | DeclarationKind::FindAuxiliary(_) => None,
DeclarationKind::DomainLetting(_)
| DeclarationKind::EnumeratedType(_)
| DeclarationKind::UnnamedType(_) => {
bug!("Expected integer expression, given, or letting inside enum domain; Got: {re}")
impl Range<EnumVariantVal> {
pub fn resolve(&self, expected: &Moo<EnumeratedType>) -> Option<Range<u32>> {
match self {
Range::Single(x) => Some(Range::Single(x.resolve(expected)?)),
Range::Bounded(l, r) => {
Some(Range::Bounded(l.resolve(expected)?, r.resolve(expected)?))
Range::UnboundedL(r) => Some(Range::UnboundedL(r.resolve(expected)?)),
Range::UnboundedR(l) => Some(Range::UnboundedR(l.resolve(expected)?)),
Range::Unbounded => Some(Range::Unbounded),
impl Display for EnumVariantVal {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
// TODO: Better
// write!(f, "{}", resolve_constant(self.0))
pub(super) type FieldUnresolved = Field<DomainPtr>;
impl From<FieldGround> for FieldUnresolved {
fn from(v: FieldGround) -> Self {
v.func_map(DomainPtr::from)
impl TryFrom<FieldUnresolved> for FieldGround {
type Error = DomainOpError;
fn try_from(v: FieldUnresolved) -> Result<Self, Self::Error> {
v.try_func_map(DomainPtr::try_into)
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Quine, Uniplate)]
#[path_prefix(conjure_cp::ast)]
#[biplate(to=IntVal)]
#[biplate(to=DomainPtr)]
/// Variants use the project-wide type/domain ordering; keep broad matches in the same order.
pub enum UnresolvedDomain {
/// An integer domain with an optional representation preference
Int(Vec<Range<IntVal>>, Option<String>),
/// An integer domain given by the values of a collection, as in `int([i | i <- nums])`.
///
/// The collection may be built from `given` declarations, so it stays an expression until
/// those are instantiated and it can be evaluated.
IntFromValues(Moo<Expression>),
/// A tuple of N elements, each with its own domain, and an optional representation preference
Tuple(Vec<DomainPtr>, Option<String>),
/// A record, with an optional representation preference
Record(Vec<FieldUnresolved>, Option<String>),
/// A variant domain with its domain options (reusing field entries), and an optional
/// representation preference
Variant(Vec<FieldUnresolved>, Option<String>),
/// A n-dimensional matrix with a value domain and n-index domains, and an optional
Matrix(DomainPtr, Vec<DomainPtr>, Option<String>),
Sequence(SequenceAttr<IntVal>, DomainPtr),
/// A set of elements drawn from the inner domain
Set(SetAttr<IntVal>, DomainPtr),
MSet(MSetAttr<IntVal>, DomainPtr),
/// A function with attributes, domain, and range
Function(FuncAttr<IntVal>, DomainPtr, DomainPtr),
/// A relation as a set of tuples
Relation(RelAttr<IntVal>, Vec<DomainPtr>),
Partition(PartitionAttr<IntVal>, DomainPtr),
/// An enumerated type
#[polyquine_skip]
EnumeratedType(Moo<EnumeratedType>, Vec<Range<EnumVariantVal>>),
/// An unnamed type
UnnamedType(Reference),
Permutation(PermutationAttr<IntVal>, DomainPtr),
/// A reference to a domain letting
Reference(Reference),
impl UnresolvedDomain {
pub(super) fn from_ground(domain: &GroundDomain) -> Option<UnresolvedDomain> {
let unresolved = match domain {
GroundDomain::Empty(_)
| GroundDomain::Bool
| GroundDomain::EnumeratedType(_, _)
| GroundDomain::UnnamedType(_) => return None,
GroundDomain::Int(ranges, representation) => UnresolvedDomain::Int(
ranges.iter().cloned().map(Into::into).collect(),
representation.clone(),
),
GroundDomain::Tuple(inners, representation) => UnresolvedDomain::Tuple(
inners.iter().map(DomainPtr::from).collect(),
GroundDomain::Record(fields, representation) => UnresolvedDomain::Record(
fields.iter().cloned().map(Into::into).collect(),
GroundDomain::Variant(fields, representation) => UnresolvedDomain::Variant(
GroundDomain::Matrix(inner, indices, representation) => UnresolvedDomain::Matrix(
DomainPtr::from(inner),
indices.iter().map(DomainPtr::from).collect(),
GroundDomain::Sequence(attributes, inner) => {
UnresolvedDomain::Sequence(attributes.clone().into(), DomainPtr::from(inner))
GroundDomain::Set(attributes, inner) => {
UnresolvedDomain::Set(attributes.clone().into(), DomainPtr::from(inner))
GroundDomain::MSet(attributes, inner) => {
UnresolvedDomain::MSet(attributes.clone().into(), DomainPtr::from(inner))
GroundDomain::Function(attributes, domain, codomain) => UnresolvedDomain::Function(
attributes.clone().into(),
DomainPtr::from(domain),
DomainPtr::from(codomain),
GroundDomain::Relation(attributes, inners) => UnresolvedDomain::Relation(
GroundDomain::Partition(attributes, inner) => {
UnresolvedDomain::Partition(attributes.clone().into(), DomainPtr::from(inner))
GroundDomain::Permutation(attributes, inner) => {
UnresolvedDomain::Permutation(attributes.clone().into(), DomainPtr::from(inner))
Some(unresolved)
/// Whether this domain takes its values from a collection expression anywhere inside it.
/// Such a domain is expensive to resolve -- the collection is evaluated afresh each time -- so
/// callers ground it once rather than leaving it to be re-resolved on every query.
pub fn has_int_from_values(&self) -> bool {
UnresolvedDomain::IntFromValues(_) => true,
UnresolvedDomain::Int(_, _) => false,
UnresolvedDomain::Tuple(inners, _) | UnresolvedDomain::Relation(_, inners) => {
inners.iter().any(domain_has_int_from_values)
UnresolvedDomain::Record(entries, _) | UnresolvedDomain::Variant(entries, _) => entries
.iter()
.any(|entry| domain_has_int_from_values(&entry.value)),
UnresolvedDomain::Matrix(value, indices, _) => {
domain_has_int_from_values(value) || indices.iter().any(domain_has_int_from_values)
UnresolvedDomain::Sequence(_, inner)
| UnresolvedDomain::Set(_, inner)
| UnresolvedDomain::MSet(_, inner)
| UnresolvedDomain::Partition(_, inner)
| UnresolvedDomain::Permutation(_, inner) => domain_has_int_from_values(inner),
UnresolvedDomain::Function(_, from, to) => {
domain_has_int_from_values(from) || domain_has_int_from_values(to)
UnresolvedDomain::Reference(_)
| UnresolvedDomain::EnumeratedType(_, _)
| UnresolvedDomain::UnnamedType(_) => false,
pub fn resolve(&self) -> Result<GroundDomain, DomainOpError> {
UnresolvedDomain::IntFromValues(expr) => {
let values = crate::ast::eval::generator_values_from_expr(expr)
.ok_or(DomainOpError::NotGround)?;
let mut ranges = Vec::with_capacity(values.len());
for value in values {
let crate::ast::Literal::Int(value) = value else {
return Err(DomainOpError::WrongType);
ranges.push(Range::Single(value));
Ok(GroundDomain::Int(Range::squeeze(&ranges), None))
UnresolvedDomain::Int(rngs, representation) => rngs
.map(Range::<IntVal>::resolve)
.collect::<Result<Vec<_>, _>>()
.map(|ranges| {
let ranges = ranges
.into_iter()
.filter(
|range| !matches!(range, Range::Bounded(lower, upper) if lower > upper),
)
.collect::<Vec<_>>();
GroundDomain::Int(Range::squeeze(&ranges), representation.clone())
}),
UnresolvedDomain::Tuple(inners, representation) => inners
.map(DomainPtr::resolve)
.collect::<Result<_, _>>()
.map(|inners| GroundDomain::Tuple(inners, representation.clone())),
UnresolvedDomain::Record(entries, representation) => entries
.map(|f| {
f.value.resolve().map(|gd| FieldGround {
name: f.name.clone(),
value: gd,
})
.map(|entries| GroundDomain::Record(entries, representation.clone())),
UnresolvedDomain::Variant(entries, representation) => entries
.map(|entries| GroundDomain::Variant(entries, representation.clone())),
UnresolvedDomain::Matrix(inner, idx_doms, representation) => {
let inner_gd = inner.resolve()?;
idx_doms
.map(|idx| GroundDomain::Matrix(inner_gd, idx, representation.clone()))
UnresolvedDomain::Sequence(attr, inner) => {
Ok(GroundDomain::Sequence(attr.resolve()?, inner.resolve()?))
UnresolvedDomain::Set(attr, inner) => {
Ok(GroundDomain::Set(attr.resolve()?, inner.resolve()?))
UnresolvedDomain::MSet(attr, inner) => {
Ok(GroundDomain::MSet(attr.resolve()?, inner.resolve()?))
UnresolvedDomain::Function(attr, dom, cdom) => Ok(GroundDomain::Function(
attr.resolve()?,
dom.resolve()?,
cdom.resolve()?,
)),
UnresolvedDomain::Relation(attr, inners) => {
let resolved_attr = attr.resolve()?;
inners
.map(|items| GroundDomain::Relation(resolved_attr, items))
UnresolvedDomain::Partition(attr, inner) => {
Ok(GroundDomain::Partition(attr.resolve()?, inner.resolve()?))
UnresolvedDomain::Permutation(attr, inner) => {
Ok(GroundDomain::Permutation(attr.resolve()?, inner.resolve()?))
UnresolvedDomain::Reference(re) => re
.ptr
.as_domain_letting()
.unwrap_or_else(|| {
bug!("Reference domain should point to domain letting, but got {re}")
.resolve()
.map(Moo::unwrap_or_clone),
UnresolvedDomain::EnumeratedType(et, ranges) => {
let ranges: Vec<_> = ranges
.clone()
.map(|r| r.resolve(et))
.collect::<Option<Vec<_>>>()
Ok(GroundDomain::EnumeratedType(et.clone(), ranges))
UnresolvedDomain::UnnamedType(decl) => {
let ut = *decl
.as_unnamed_type()
bug!("Unnamed type domain should point to an unnamed type declaration, but got {decl}")
});
Ok(GroundDomain::UnnamedType(Moo::new(
crate::ast::unnamed::UnnamedType {
name: decl.ptr.name().clone(),
size: ut,
},
)))
pub(super) fn union_unresolved(
&self,
other: &UnresolvedDomain,
) -> Result<UnresolvedDomain, DomainOpError> {
// Keep implemented variants before unsupported variants so mixed-domain unions report the
// established error. Each group uses declaration order.
match (self, other) {
(UnresolvedDomain::Int(lhs, _), UnresolvedDomain::Int(rhs, _)) => {
let merged = lhs.iter().chain(rhs.iter()).cloned().collect_vec();
Ok(UnresolvedDomain::Int(merged, None))
(UnresolvedDomain::IntFromValues(_), _) | (_, UnresolvedDomain::IntFromValues(_)) => {
Err(DomainOpError::NotGround)
(UnresolvedDomain::Int(_, _), _) | (_, UnresolvedDomain::Int(_, _)) => {
Err(DomainOpError::WrongType)
(UnresolvedDomain::Tuple(lhs, _), UnresolvedDomain::Tuple(rhs, _))
if lhs.len() == rhs.len() =>
let mut merged = Vec::new();
for (l, r) in zip(lhs, rhs) {
merged.push(l.union(r)?)
Ok(UnresolvedDomain::Tuple(merged, None))
(UnresolvedDomain::Tuple(_, _), _) | (_, UnresolvedDomain::Tuple(_, _)) => {
(UnresolvedDomain::Matrix(in1, idx1, _), UnresolvedDomain::Matrix(in2, idx2, _))
if idx1 == idx2 =>
Ok(UnresolvedDomain::Matrix(
in1.union(in2)?,
idx1.clone(),
None,
))
(UnresolvedDomain::Matrix(_, _, _), _) | (_, UnresolvedDomain::Matrix(_, _, _)) => {
(UnresolvedDomain::Set(_, in1), UnresolvedDomain::Set(_, in2)) => {
Ok(UnresolvedDomain::Set(SetAttr::default(), in1.union(in2)?))
(UnresolvedDomain::Set(_, _), _) | (_, UnresolvedDomain::Set(_, _)) => {
(UnresolvedDomain::MSet(_, in1), UnresolvedDomain::MSet(_, in2)) => {
Ok(UnresolvedDomain::MSet(MSetAttr::default(), in1.union(in2)?))
(UnresolvedDomain::MSet(_, _), _) | (_, UnresolvedDomain::MSet(_, _)) => {
(UnresolvedDomain::Relation(_, in1s), UnresolvedDomain::Relation(_, in2s)) => {
let mut inners = Vec::new();
for (in1, in2) in in1s.iter().zip(in2s.iter()) {
inners.push(in1.union(in2)?)
Ok(UnresolvedDomain::Relation(RelAttr::default(), inners))
(UnresolvedDomain::Relation(_, _), _) | (_, UnresolvedDomain::Relation(_, _)) => {
// TODO: Could we define semantics for merging record domains?
#[allow(unreachable_patterns)]
(UnresolvedDomain::Record(_, _), _) | (_, UnresolvedDomain::Record(_, _)) => {
(UnresolvedDomain::Variant(_, _), _) | (_, UnresolvedDomain::Variant(_, _)) => {
(UnresolvedDomain::Sequence(_, _), _) | (_, UnresolvedDomain::Sequence(_, _)) => {
(UnresolvedDomain::EnumeratedType(_, _), _)
| (_, UnresolvedDomain::EnumeratedType(_, _)) => Err(DomainOpError::WrongType),
(UnresolvedDomain::UnnamedType(_), _) | (_, UnresolvedDomain::UnnamedType(_)) => {
(UnresolvedDomain::Function(_, _, _), _) | (_, UnresolvedDomain::Function(_, _, _)) => {
(UnresolvedDomain::Partition(_, _), _) | (_, UnresolvedDomain::Partition(_, _)) => {
(UnresolvedDomain::Permutation(_, _), _) | (_, UnresolvedDomain::Permutation(_, _)) => {
// TODO: Could we support unions of reference domains symbolically?
(UnresolvedDomain::Reference(_), _) | (_, UnresolvedDomain::Reference(_)) => {
pub fn element_domain(&self) -> Option<DomainPtr> {
UnresolvedDomain::Matrix(inner, _, _) => Some(inner.clone()),
// A sequence is a function from int(1..|s|), and iterating a function yields its
// pairs, so iterating a sequence yields (position, value). Mirrors
// `GroundDomain::element_domain`.
UnresolvedDomain::Sequence(attr, inner_dom) => {
let max = match &attr.size {
Range::Single(max) | Range::UnboundedL(max) | Range::Bounded(_, max) => {
max.clone()
Range::UnboundedR(_) | Range::Unbounded => return None,
let positions = Moo::new(crate::ast::Domain::Unresolved(Moo::new(
UnresolvedDomain::Int(vec![Range::Bounded(IntVal::new_const(1), max)], None),
)));
Some(Moo::new(crate::ast::Domain::Unresolved(Moo::new(
UnresolvedDomain::Tuple(vec![positions, inner_dom.clone()], None),
))))
UnresolvedDomain::Set(_, inner_dom) => Some(inner_dom.clone()),
_ => None,
/// True if any domain in this tree has a representation preference.
pub fn has_representation_preference(&self) -> bool {
UnresolvedDomain::Int(_, representation) => representation.is_some(),
UnresolvedDomain::IntFromValues(_)
UnresolvedDomain::Tuple(inners, representation) => {
representation.is_some() || inners.iter().any(|d| d.has_representation_preference())
UnresolvedDomain::Record(entries, representation) => {
representation.is_some()
|| entries
.any(|f| f.value.has_representation_preference())
UnresolvedDomain::Variant(entries, representation) => {
UnresolvedDomain::Matrix(inner, idxs, representation) => {
|| inner.has_representation_preference()
|| idxs.iter().any(|d| d.has_representation_preference())
attr.representation.is_some() || inner.has_representation_preference()
UnresolvedDomain::Function(attr, dom, cdom) => {
attr.representation.is_some()
|| dom.has_representation_preference()
|| cdom.has_representation_preference()
|| inners.iter().any(|d| d.has_representation_preference())
.domain()
.is_some_and(|d| d.has_representation_preference()),
/// Format this domain in Essence type style, omitting size attributes and integer ranges.
pub fn as_type_string(&self) -> String {
UnresolvedDomain::Int(_, representation) => int_type_string(representation.as_deref()),
UnresolvedDomain::IntFromValues(_) => "int".to_string(),
UnresolvedDomain::EnumeratedType(ty, _) => ty.to_string(),
UnresolvedDomain::UnnamedType(_) => "unnamed type".to_string(),
let args = representation
.map(|r| format!("representation {r}"))
.chain(inners.iter().map(|d| d.as_type_string()))
.join(", ");
format!("tuple ({args})")
let inners = entries
.map(|f| format!("{}: {}", f.name, f.value.as_type_string()))
format!(
"record{} {{{inners}}}",
representation_attribute(representation)
"variant{} {{{inners}}}",
let idxs = idxs.iter().map(|d| d.as_type_string()).join(", ");
"matrix{} indexed by [{idxs}] of {}",
representation_attribute(representation),
inner.as_type_string()
UnresolvedDomain::Sequence(attrs, inner) => format!(
"sequence{} of {}",
representation_attribute(&attrs.representation),
UnresolvedDomain::Set(attrs, inner) => {
let mut out = String::from("set");
if let Some(repr) = &attrs.representation {
out.push_str(" (representation ");
out.push_str(repr);
out.push(')');
out.push_str(" of ");
out.push_str(&inner.as_type_string());
out
UnresolvedDomain::MSet(attrs, inner) => {
let mut out = String::from("mset");
"function{} {} --> {}",
representation_attribute(&attr.representation),
dom.as_type_string(),
cdom.as_type_string()
"relation{} of ({})",
inners.iter().map(|d| d.as_type_string()).join(" * ")
"partition{} from {}",
"permutation{} of {}",
UnresolvedDomain::Reference(re) => re.to_string(),
impl Typeable for UnresolvedDomain {
fn return_type(&self) -> ReturnType {
UnresolvedDomain::Int(_, _) | UnresolvedDomain::IntFromValues(_) => ReturnType::Int,
UnresolvedDomain::Tuple(inners, _) => {
let mut inner_types = Vec::new();
for inner in inners {
inner_types.push(inner.return_type());
ReturnType::Tuple(inner_types)
UnresolvedDomain::Record(entries, _) => {
let mut entry_types = Vec::new();
for entry in entries {
entry_types.push(entry.clone().func_map(|x| x.return_type()));
entry_types.sort();
ReturnType::Record(entry_types)
UnresolvedDomain::Variant(entries, _) => {
ReturnType::Variant(entry_types)
UnresolvedDomain::Matrix(inner, _idx, _) => {
ReturnType::Matrix(Box::new(inner.return_type()))
UnresolvedDomain::Sequence(_attr, inner) => {
ReturnType::Sequence(Box::new(inner.return_type()))
UnresolvedDomain::Set(_attr, inner) => ReturnType::Set(Box::new(inner.return_type())),
UnresolvedDomain::MSet(_attr, inner) => ReturnType::MSet(Box::new(inner.return_type())),
UnresolvedDomain::Function(_, dom, cdom) => {
ReturnType::Function(Box::new(dom.return_type()), Box::new(cdom.return_type()))
UnresolvedDomain::Relation(_, inners) => {
ReturnType::Relation(inner_types)
UnresolvedDomain::EnumeratedType(et, _) => ReturnType::EnumeratedType(et.clone()),
UnresolvedDomain::UnnamedType(reference) => reference.ptr.return_type(),
UnresolvedDomain::Partition(_, inner) => {
ReturnType::Partition(Box::new(inner.return_type()))
UnresolvedDomain::Permutation(_, inner) => {
ReturnType::Permutation(Box::new(inner.return_type()))
UnresolvedDomain::Reference(re) => re.return_type(),
impl Display for FieldUnresolved {
write!(f, "{}: {}", self.name, self.value)
impl Display for UnresolvedDomain {
match &self {
UnresolvedDomain::Int(ranges, representation) => {
write_int_domain(f, ranges, representation.as_deref())
UnresolvedDomain::IntFromValues(expr) => write!(f, "int({expr})"),
UnresolvedDomain::Tuple(domains, representation) => {
// Members have always been printed without a space after the comma here.
let members = domains.iter().join(",");
match representation {
Some(representation) if members.is_empty() => {
write!(f, "tuple (representation {representation})")
Some(representation) => {
write!(f, "tuple (representation {representation}, {members})")
None => write!(f, "tuple ({members})"),
let inners = entries.iter().map(|t| format!("{}", t)).join(", ");
let attrs = representation_attribute(representation);
write!(f, "record{attrs} {{{inners}}}",)
write!(f, "variant{attrs} {{{inners}}}",)
UnresolvedDomain::Matrix(value_domain, index_domains, representation) => {
write!(
f,
"matrix{} indexed by {} of {value_domain}",
pretty_vec(&index_domains.iter().collect_vec())
UnresolvedDomain::Sequence(attrs, inner_dom) => {
write!(f, "sequence {attrs} of {inner_dom}")
UnresolvedDomain::Set(attrs, inner_dom) => {
write!(f, "set")?;
let attrs = attrs.to_string();
if attrs.is_empty() {
write!(f, " of {inner_dom}")
write!(f, " {attrs} of {inner_dom}")
UnresolvedDomain::MSet(attrs, inner_dom) => {
write!(f, "mset")?;
UnresolvedDomain::Function(attribute, domain, codomain) => {
write!(f, "function {} {} --> {} ", attribute, domain, codomain)
UnresolvedDomain::Relation(attrs, domains) => {
write!(f, "relation {} of ({})", attrs, domains.iter().join(" * "))
UnresolvedDomain::EnumeratedType(ty, ranges) => {
if !ranges.is_empty() && ranges.iter().all(Range::is_lower_or_upper_bounded) {
let rngs: String = ranges.iter().map(|r| format!("{r}")).join(", ");
write!(f, "{}({})", ty.name, rngs)
write!(f, "{}", ty.name)
UnresolvedDomain::UnnamedType(reference) => {
write!(f, "{reference}")
UnresolvedDomain::Partition(attrs, inner_dom) => {
write!(f, "partition {attrs} from {inner_dom}")
UnresolvedDomain::Permutation(attrs, inner_dom) => {
write!(f, "permutation {attrs} of {inner_dom}")
UnresolvedDomain::Reference(re) => write!(f, "{re}"),
/// Whether `domain` takes its values from a collection expression anywhere inside it.
pub fn domain_has_int_from_values(domain: &DomainPtr) -> bool {
match &**domain {
crate::ast::Domain::Ground(_) => false,
crate::ast::Domain::Unresolved(unresolved) => unresolved.has_int_from_values(),