hugr_core/ops/
constant.rs

1//! Constant value definitions.
2
3mod custom;
4
5use std::borrow::Cow;
6use std::collections::hash_map::DefaultHasher; // Moves into std::hash in Rust 1.76.
7use std::hash::{Hash, Hasher};
8
9use super::{NamedOp, OpName, OpTrait, StaticTag};
10use super::{OpTag, OpType};
11use crate::envelope::serde_with::AsStringEnvelope;
12use crate::types::{CustomType, EdgeKind, Signature, SumType, SumTypeError, Type, TypeRow};
13use crate::{Hugr, HugrView};
14
15use delegate::delegate;
16use itertools::Itertools;
17use serde::{Deserialize, Serialize};
18use serde_with::serde_as;
19use smol_str::SmolStr;
20use thiserror::Error;
21
22pub use custom::{
23    CustomConst, CustomSerialized, TryHash, downcast_equal_consts, get_pair_of_input_values,
24    get_single_input_value,
25};
26
27#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
28/// An operation returning a constant value.
29///
30/// Represents core types and extension types.
31#[non_exhaustive]
32#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
33pub struct Const {
34    /// The [Value] of the constant.
35    #[serde(rename = "v")]
36    pub value: Value,
37}
38
39impl Const {
40    /// Create a new [`Const`] operation.
41    #[must_use]
42    pub fn new(value: Value) -> Self {
43        Self { value }
44    }
45
46    /// The inner value of the [`Const`]
47    #[must_use]
48    pub fn value(&self) -> &Value {
49        &self.value
50    }
51
52    delegate! {
53        to self.value {
54            /// Returns the type of this constant.
55            #[must_use] pub fn get_type(&self) -> Type;
56            /// For a Const holding a CustomConst, extract the CustomConst by
57            /// downcasting.
58            #[must_use] pub fn get_custom_value<T: CustomConst>(&self) -> Option<&T>;
59
60            /// Check the value.
61            pub fn validate(&self) -> Result<(), ConstTypeError>;
62        }
63    }
64}
65
66impl From<Value> for Const {
67    fn from(value: Value) -> Self {
68        Self::new(value)
69    }
70}
71
72impl NamedOp for Const {
73    fn name(&self) -> OpName {
74        self.value().name()
75    }
76}
77
78impl StaticTag for Const {
79    const TAG: OpTag = OpTag::Const;
80}
81
82impl OpTrait for Const {
83    fn description(&self) -> &'static str {
84        "Constant value"
85    }
86
87    fn tag(&self) -> OpTag {
88        <Self as StaticTag>::TAG
89    }
90
91    fn static_output(&self) -> Option<EdgeKind> {
92        Some(EdgeKind::Const(self.get_type()))
93    }
94
95    // Constants cannot refer to TypeArgs of the enclosing Hugr, so no substitute().
96}
97
98impl From<Const> for Value {
99    fn from(konst: Const) -> Self {
100        konst.value
101    }
102}
103
104impl AsRef<Value> for Const {
105    fn as_ref(&self) -> &Value {
106        self.value()
107    }
108}
109
110#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
111struct SerialSum {
112    #[serde(default)]
113    tag: usize,
114    #[serde(rename = "vs")]
115    values: Vec<Value>,
116    #[serde(default, rename = "typ")]
117    sum_type: Option<SumType>,
118}
119
120#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
121#[serde(try_from = "SerialSum")]
122#[serde(into = "SerialSum")]
123/// A Sum variant, with a tag indicating the index of the variant and its
124/// value.
125pub struct Sum {
126    /// The tag index of the variant.
127    pub tag: usize,
128    /// The value of the variant.
129    ///
130    /// Sum variants are always a row of values, hence the Vec.
131    pub values: Vec<Value>,
132    /// The full type of the Sum, including the other variants.
133    pub sum_type: SumType,
134}
135
136impl Sum {
137    /// If value is a sum with a single row variant, return the row.
138    #[must_use]
139    pub fn as_tuple(&self) -> Option<&[Value]> {
140        // For valid instances, the type row will not have any row variables.
141        self.sum_type.as_tuple().map(|_| self.values.as_ref())
142    }
143
144    fn try_hash<H: Hasher>(&self, st: &mut H) -> bool {
145        maybe_hash_values(&self.values, st) && {
146            st.write_usize(self.tag);
147            self.sum_type.hash(st);
148            true
149        }
150    }
151}
152
153pub(crate) fn maybe_hash_values<H: Hasher>(vals: &[Value], st: &mut H) -> bool {
154    // We can't mutate the Hasher with the first element
155    // if any element, even the last, fails.
156    let mut hasher = DefaultHasher::new();
157    vals.iter().all(|e| e.try_hash(&mut hasher)) && {
158        st.write_u64(hasher.finish());
159        true
160    }
161}
162
163impl TryFrom<SerialSum> for Sum {
164    type Error = &'static str;
165
166    fn try_from(value: SerialSum) -> Result<Self, Self::Error> {
167        let SerialSum {
168            tag,
169            values,
170            sum_type,
171        } = value;
172
173        let sum_type = if let Some(sum_type) = sum_type {
174            sum_type
175        } else {
176            if tag != 0 {
177                return Err("Sum type must be provided if tag is not 0");
178            }
179            SumType::new_tuple(values.iter().map(Value::get_type).collect_vec())
180        };
181
182        Ok(Self {
183            tag,
184            values,
185            sum_type,
186        })
187    }
188}
189
190impl From<Sum> for SerialSum {
191    fn from(value: Sum) -> Self {
192        Self {
193            tag: value.tag,
194            values: value.values,
195            sum_type: Some(value.sum_type),
196        }
197    }
198}
199
200#[serde_as]
201#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
202#[serde(tag = "v")]
203/// A value that can be stored as a static constant. Representing core types and
204/// extension types.
205pub enum Value {
206    /// An extension constant value, that can check it is of a given [CustomType].
207    Extension {
208        #[serde(flatten)]
209        /// The custom constant value.
210        e: OpaqueValue,
211    },
212    /// A higher-order function value.
213    Function {
214        /// A Hugr defining the function.
215        #[serde_as(as = "Box<AsStringEnvelope>")]
216        hugr: Box<Hugr>,
217    },
218    /// A Sum variant, with a tag indicating the index of the variant and its
219    /// value.
220    #[serde(alias = "Tuple")]
221    Sum(Sum),
222}
223
224/// An opaque newtype around a [`Box<dyn CustomConst>`](CustomConst).
225///
226/// This type has special serialization behaviour in order to support
227/// serialization and deserialization of unknown impls of [CustomConst].
228///
229/// During serialization we first serialize the internal [`dyn` CustomConst](CustomConst)
230/// into a [serde_json::Value]. We then create a [CustomSerialized] wrapping
231/// that value.  That [CustomSerialized] is then serialized in place of the
232/// [OpaqueValue].
233///
234/// During deserialization, first we deserialize a [CustomSerialized]. We
235/// attempt to deserialize the internal [serde_json::Value] using the [`Box<dyn
236/// CustomConst>`](CustomConst) impl. This will fail if the appropriate `impl CustomConst`
237/// is not linked into the running program, in which case we coerce the
238/// [CustomSerialized] into a [`Box<dyn CustomConst>`](CustomConst). The [OpaqueValue] is
239/// then produced from the [`Box<dyn [CustomConst]>`](CustomConst).
240///
241/// In the case where the internal serialized value of a `CustomSerialized`
242/// is another `CustomSerialized` we do not attempt to recurse. This behaviour
243/// may change in future.
244///
245#[cfg_attr(not(miri), doc = "```")] // this doctest depends on typetag, so fails with miri
246#[cfg_attr(miri, doc = "```ignore")]
247/// use serde::{Serialize,Deserialize};
248/// use hugr::{
249///   types::Type,ops::constant::{OpaqueValue, ValueName, CustomConst, CustomSerialized},
250///   extension::{ExtensionSet, prelude::{usize_t, ConstUsize}},
251///   std_extensions::arithmetic::int_types};
252/// use serde_json::json;
253///
254/// let expected_json = json!({
255///     "typ": usize_t(),
256///     "value": {'c': "ConstUsize", 'v': 1}
257/// });
258/// let ev = OpaqueValue::new(ConstUsize::new(1));
259/// assert_eq!(&serde_json::to_value(&ev).unwrap(), &expected_json);
260/// assert_eq!(ev, serde_json::from_value(expected_json).unwrap());
261///
262/// let ev = OpaqueValue::new(CustomSerialized::new(usize_t().clone(), serde_json::Value::Null));
263/// let expected_json = json!({
264///     "typ": usize_t(),
265///     "value": null
266/// });
267///
268/// assert_eq!(&serde_json::to_value(ev.clone()).unwrap(), &expected_json);
269/// assert_eq!(ev, serde_json::from_value(expected_json).unwrap());
270/// ```
271#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct OpaqueValue {
273    #[serde(flatten, with = "self::custom::serde_extension_value")]
274    v: Box<dyn CustomConst>,
275}
276
277impl OpaqueValue {
278    /// Create a new [`OpaqueValue`] from any [`CustomConst`].
279    pub fn new(cc: impl CustomConst) -> Self {
280        Self { v: Box::new(cc) }
281    }
282
283    /// Returns a reference to the internal [`CustomConst`].
284    #[must_use]
285    pub fn value(&self) -> &dyn CustomConst {
286        self.v.as_ref()
287    }
288
289    /// Returns a reference to the internal [`CustomConst`].
290    pub(crate) fn value_mut(&mut self) -> &mut dyn CustomConst {
291        self.v.as_mut()
292    }
293
294    delegate! {
295        to self.value() {
296            /// Returns the type of the internal [`CustomConst`].
297            #[must_use] pub fn get_type(&self) -> Type;
298            /// An identifier of the internal [`CustomConst`].
299            #[must_use] pub fn name(&self) -> ValueName;
300        }
301    }
302}
303
304impl<CC: CustomConst> From<CC> for OpaqueValue {
305    fn from(x: CC) -> Self {
306        Self::new(x)
307    }
308}
309
310impl From<Box<dyn CustomConst>> for OpaqueValue {
311    fn from(value: Box<dyn CustomConst>) -> Self {
312        Self { v: value }
313    }
314}
315
316impl PartialEq for OpaqueValue {
317    fn eq(&self, other: &Self) -> bool {
318        self.value().equal_consts(other.value())
319    }
320}
321
322/// Struct for custom type check fails.
323#[derive(Clone, Debug, PartialEq, Eq, Error)]
324#[non_exhaustive]
325pub enum CustomCheckFailure {
326    /// The value had a specific type that was not what was expected
327    #[error("Expected type: {expected} but value was of type: {found}")]
328    TypeMismatch {
329        /// The expected custom type.
330        expected: Box<CustomType>,
331        /// The custom type found when checking.
332        found: Box<Type>,
333    },
334    /// Any other message
335    #[error("{0}")]
336    Message(String),
337}
338
339/// Errors that arise from typechecking constants
340#[derive(Clone, Debug, PartialEq, Error)]
341#[non_exhaustive]
342pub enum ConstTypeError {
343    /// Invalid sum type definition.
344    #[error("{0}")]
345    SumType(#[from] SumTypeError),
346    /// Function constant missing a function type.
347    #[error(
348        "A function constant cannot be defined using a Hugr with root of type {hugr_root_type}. Must be a monomorphic function."
349    )]
350    NotMonomorphicFunction {
351        /// The root node type of the Hugr that (claims to) define the function constant.
352        hugr_root_type: Box<OpType>,
353    },
354    /// A mismatch between the type expected and the value.
355    #[error("Value {1:?} does not match expected type {0}")]
356    ConstCheckFail(Box<Type>, Value),
357    /// Error when checking a custom value.
358    #[error("Error when checking custom type: {0}")]
359    CustomCheckFail(#[from] CustomCheckFailure),
360}
361
362/// Hugrs (even functions) inside Consts must be monomorphic
363fn mono_fn_type(h: &Hugr) -> Result<Cow<'_, Signature>, ConstTypeError> {
364    let err = || ConstTypeError::NotMonomorphicFunction {
365        hugr_root_type: Box::new(h.entrypoint_optype().clone()),
366    };
367    if let Some(pf) = h.poly_func_type() {
368        match pf.try_into() {
369            Ok(sig) => return Ok(Cow::Owned(sig)),
370            Err(_) => return Err(err()),
371        };
372    }
373
374    h.inner_function_type().ok_or_else(err)
375}
376
377impl Value {
378    /// Returns the type of this [`Value`].
379    #[must_use]
380    pub fn get_type(&self) -> Type {
381        match self {
382            Self::Extension { e } => e.get_type(),
383            Self::Sum(Sum { sum_type, .. }) => sum_type.clone().into(),
384            Self::Function { hugr } => {
385                let func_type = mono_fn_type(hugr).unwrap_or_else(|e| panic!("{}", e));
386                Type::new_function(func_type.into_owned())
387            }
388        }
389    }
390
391    /// Returns a Sum constant. The value is determined by `items` and is
392    /// type-checked `typ`. The `tag`th variant of `typ` should match the types
393    /// of `items`.
394    pub fn sum(
395        tag: usize,
396        items: impl IntoIterator<Item = Value>,
397        typ: SumType,
398    ) -> Result<Self, ConstTypeError> {
399        let values: Vec<Value> = items.into_iter().collect();
400        typ.check_type(tag, &values)?;
401        Ok(Self::Sum(Sum {
402            tag,
403            values,
404            sum_type: typ,
405        }))
406    }
407
408    /// Returns a tuple constant of constant values.
409    pub fn tuple(items: impl IntoIterator<Item = Value>) -> Self {
410        let vs = items.into_iter().collect_vec();
411        let tys = vs.iter().map(Self::get_type).collect_vec();
412
413        Self::sum(0, vs, SumType::new_tuple(tys)).expect("Tuple type is valid")
414    }
415
416    /// Returns a constant function defined by a Hugr.
417    ///
418    /// # Errors
419    ///
420    /// Returns an error if the Hugr root node does not define a function.
421    pub fn function(hugr: impl Into<Hugr>) -> Result<Self, ConstTypeError> {
422        let hugr = hugr.into();
423        mono_fn_type(&hugr)?;
424        Ok(Self::Function {
425            hugr: Box::new(hugr),
426        })
427    }
428
429    /// Returns a constant unit type (empty Tuple).
430    #[must_use]
431    pub const fn unit() -> Self {
432        Self::Sum(Sum {
433            tag: 0,
434            values: vec![],
435            sum_type: SumType::Unit { size: 1 },
436        })
437    }
438
439    /// Returns a constant Sum over units. Used as branching values.
440    pub fn unit_sum(tag: usize, size: u8) -> Result<Self, ConstTypeError> {
441        Self::sum(tag, [], SumType::Unit { size })
442    }
443
444    /// Returns a constant Sum over units, with only one variant.
445    #[must_use]
446    pub fn unary_unit_sum() -> Self {
447        Self::unit_sum(0, 1).expect("0 < 1")
448    }
449
450    /// Returns a constant "true" value, i.e. the second variant of Sum((), ()).
451    #[must_use]
452    pub fn true_val() -> Self {
453        Self::unit_sum(1, 2).expect("1 < 2")
454    }
455
456    /// Returns a constant "false" value, i.e. the first variant of Sum((), ()).
457    #[must_use]
458    pub fn false_val() -> Self {
459        Self::unit_sum(0, 2).expect("0 < 2")
460    }
461
462    /// Returns an optional with some values. This is a Sum with two variants, the
463    /// first being empty and the second being the values.
464    pub fn some<V: Into<Value>>(values: impl IntoIterator<Item = V>) -> Self {
465        let values: Vec<Value> = values.into_iter().map(Into::into).collect_vec();
466        let value_types: Vec<Type> = values.iter().map(Value::get_type).collect_vec();
467        let sum_type = SumType::new_option(value_types);
468        Self::sum(1, values, sum_type).unwrap()
469    }
470
471    /// Returns an optional with no value. This is a Sum with two variants, the
472    /// first being empty and the second being the value.
473    pub fn none(value_types: impl Into<TypeRow>) -> Self {
474        Self::sum(0, [], SumType::new_option(value_types)).unwrap()
475    }
476
477    /// Returns a constant `bool` value.
478    ///
479    /// see [`Value::true_val`] and [`Value::false_val`].
480    #[must_use]
481    pub fn from_bool(b: bool) -> Self {
482        if b {
483            Self::true_val()
484        } else {
485            Self::false_val()
486        }
487    }
488
489    /// Returns a [`Value::Extension`] holding `custom_const`.
490    pub fn extension(custom_const: impl CustomConst) -> Self {
491        Self::Extension {
492            e: OpaqueValue::new(custom_const),
493        }
494    }
495
496    /// For a [Value] holding a [`CustomConst`], extract the `CustomConst` by downcasting.
497    #[must_use]
498    pub fn get_custom_value<T: CustomConst>(&self) -> Option<&T> {
499        if let Self::Extension { e } = self {
500            e.v.downcast_ref()
501        } else {
502            None
503        }
504    }
505
506    fn name(&self) -> OpName {
507        match self {
508            Self::Extension { e } => format!("const:custom:{}", e.name()),
509            Self::Function { hugr: h } => {
510                let Ok(t) = mono_fn_type(h) else {
511                    panic!("HUGR root node isn't a valid function parent.");
512                };
513                format!("const:function:[{t}]")
514            }
515            Self::Sum(Sum {
516                tag,
517                values,
518                sum_type,
519            }) => {
520                if sum_type.as_tuple().is_some() {
521                    let names: Vec<_> = values.iter().map(Value::name).collect();
522                    format!("const:seq:{{{}}}", names.iter().join(", "))
523                } else {
524                    format!("const:sum:{{tag:{tag}, vals:{values:?}}}")
525                }
526            }
527        }
528        .into()
529    }
530
531    /// Check the value.
532    pub fn validate(&self) -> Result<(), ConstTypeError> {
533        match self {
534            Self::Extension { e } => Ok(e.value().validate()?),
535            Self::Function { hugr } => {
536                mono_fn_type(hugr)?;
537                Ok(())
538            }
539            Self::Sum(Sum {
540                tag,
541                values,
542                sum_type,
543            }) => {
544                sum_type.check_type(*tag, values)?;
545                Ok(())
546            }
547        }
548    }
549
550    /// If value is a sum with a single row variant, return the row.
551    #[must_use]
552    pub fn as_tuple(&self) -> Option<&[Value]> {
553        if let Self::Sum(sum) = self {
554            sum.as_tuple()
555        } else {
556            None
557        }
558    }
559
560    /// Hashes this value, if possible. [`Value::Extension`]s are hashable according
561    /// to their implementation of [`TryHash`]; [`Value::Function`]s never are;
562    /// [`Value::Sum`]s are if their contents are.
563    pub fn try_hash<H: Hasher>(&self, st: &mut H) -> bool {
564        match self {
565            Value::Extension { e } => e.value().try_hash(&mut *st),
566            Value::Function { .. } => false,
567            Value::Sum(s) => s.try_hash(st),
568        }
569    }
570}
571
572impl<T> From<T> for Value
573where
574    T: CustomConst,
575{
576    fn from(value: T) -> Self {
577        Self::extension(value)
578    }
579}
580
581/// A unique identifier for a constant value.
582pub type ValueName = SmolStr;
583
584/// Slice of a [`ValueName`] constant value identifier.
585pub type ValueNameRef = str;
586
587#[cfg(test)]
588pub(crate) mod test {
589    use std::collections::HashSet;
590    use std::sync::{Arc, Weak};
591
592    use super::Value;
593    use crate::builder::inout_sig;
594    use crate::builder::test::simple_dfg_hugr;
595    use crate::extension::PRELUDE;
596    use crate::extension::prelude::{bool_t, usize_custom_t};
597    use crate::extension::resolution::{
598        ExtensionResolutionError, WeakExtensionRegistry, resolve_custom_type_extensions,
599        resolve_typearg_extensions,
600    };
601    use crate::std_extensions::arithmetic::int_types::ConstInt;
602    use crate::std_extensions::collections::array::{ArrayValue, array_type};
603    use crate::std_extensions::collections::value_array::{VArrayValue, value_array_type};
604    use crate::{
605        builder::{BuildError, DFGBuilder, Dataflow, DataflowHugr},
606        extension::{
607            ExtensionId,
608            prelude::{ConstUsize, usize_t},
609        },
610        std_extensions::arithmetic::float_types::{ConstF64, float64_type},
611        type_row,
612        types::type_param::TypeArg,
613        types::{Type, TypeBound, TypeRow},
614    };
615    use cool_asserts::assert_matches;
616    use rstest::{fixture, rstest};
617
618    use super::*;
619
620    #[derive(Debug, Clone, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
621    /// A custom constant value used in testing
622    pub(crate) struct CustomTestValue(pub CustomType);
623
624    #[typetag::serde]
625    impl CustomConst for CustomTestValue {
626        fn name(&self) -> ValueName {
627            format!("CustomTestValue({:?})", self.0).into()
628        }
629
630        fn update_extensions(
631            &mut self,
632            extensions: &WeakExtensionRegistry,
633        ) -> Result<(), ExtensionResolutionError> {
634            resolve_custom_type_extensions(&mut self.0, extensions)?;
635            // This loop is redundant, but we use it to test the public
636            // function.
637            for arg in self.0.args_mut() {
638                resolve_typearg_extensions(arg, extensions)?;
639            }
640            Ok(())
641        }
642
643        fn get_type(&self) -> Type {
644            self.0.clone().into()
645        }
646
647        fn equal_consts(&self, other: &dyn CustomConst) -> bool {
648            crate::ops::constant::downcast_equal_consts(self, other)
649        }
650    }
651
652    /// A [`CustomSerialized`] encoding a [`float64_type()`] float constant used in testing.
653    pub(crate) fn serialized_float(f: f64) -> Value {
654        CustomSerialized::try_from_custom_const(ConstF64::new(f))
655            .unwrap()
656            .into()
657    }
658
659    /// Constructs a DFG hugr defining a sum constant, and returning the loaded value.
660    #[test]
661    fn test_sum() -> Result<(), BuildError> {
662        use crate::builder::Container;
663        let pred_rows = vec![vec![usize_t(), float64_type()].into(), Type::EMPTY_TYPEROW];
664        let pred_ty = SumType::new(pred_rows.clone());
665
666        let mut b = DFGBuilder::new(inout_sig(
667            type_row![],
668            TypeRow::from(vec![pred_ty.clone().into()]),
669        ))?;
670        let usize_custom_t = usize_custom_t(&Arc::downgrade(&PRELUDE));
671        let c = b.add_constant(Value::sum(
672            0,
673            [
674                CustomTestValue(usize_custom_t.clone()).into(),
675                ConstF64::new(5.1).into(),
676            ],
677            pred_ty.clone(),
678        )?);
679        let w = b.load_const(&c);
680        b.finish_hugr_with_outputs([w]).unwrap();
681
682        let mut b = DFGBuilder::new(Signature::new(
683            type_row![],
684            TypeRow::from(vec![pred_ty.clone().into()]),
685        ))?;
686        let c = b.add_constant(Value::sum(1, [], pred_ty.clone())?);
687        let w = b.load_const(&c);
688        b.finish_hugr_with_outputs([w]).unwrap();
689
690        Ok(())
691    }
692
693    #[test]
694    fn test_bad_sum() {
695        let pred_ty = SumType::new([vec![usize_t(), float64_type()].into(), type_row![]]);
696
697        let good_sum = const_usize();
698        println!("{}", serde_json::to_string_pretty(&good_sum).unwrap());
699
700        let good_sum =
701            Value::sum(0, [const_usize(), serialized_float(5.1)], pred_ty.clone()).unwrap();
702        println!("{}", serde_json::to_string_pretty(&good_sum).unwrap());
703
704        let res = Value::sum(0, [], pred_ty.clone());
705        assert_matches!(
706            res,
707            Err(ConstTypeError::SumType(SumTypeError::WrongVariantLength {
708                tag: 0,
709                expected: 2,
710                found: 0
711            }))
712        );
713
714        let res = Value::sum(4, [], pred_ty.clone());
715        assert_matches!(
716            res,
717            Err(ConstTypeError::SumType(SumTypeError::InvalidTag {
718                tag: 4,
719                num_variants: 2
720            }))
721        );
722
723        let res = Value::sum(0, [const_usize(), const_usize()], pred_ty);
724        assert_matches!(
725            res,
726            Err(ConstTypeError::SumType(SumTypeError::InvalidValueType {
727                tag: 0,
728                index: 1,
729                expected,
730                found,
731            })) if *expected == float64_type() && *found == const_usize()
732        );
733    }
734
735    #[rstest]
736    fn function_value(simple_dfg_hugr: Hugr) {
737        let v = Value::function(simple_dfg_hugr).unwrap();
738
739        let correct_type = Type::new_function(Signature::new_endo(vec![bool_t()]));
740
741        assert_eq!(v.get_type(), correct_type);
742        assert!(v.name().starts_with("const:function:"));
743    }
744
745    #[fixture]
746    fn const_usize() -> Value {
747        ConstUsize::new(257).into()
748    }
749
750    #[fixture]
751    fn const_serialized_usize() -> Value {
752        CustomSerialized::try_from_custom_const(ConstUsize::new(257))
753            .unwrap()
754            .into()
755    }
756
757    #[fixture]
758    fn const_tuple() -> Value {
759        Value::tuple([const_usize(), Value::true_val()])
760    }
761
762    /// Equivalent to [`const_tuple`], but uses a non-resolved opaque op for the usize element.
763    #[fixture]
764    fn const_tuple_serialized() -> Value {
765        Value::tuple([const_serialized_usize(), Value::true_val()])
766    }
767
768    #[fixture]
769    fn const_array_bool() -> Value {
770        ArrayValue::new(bool_t(), [Value::true_val(), Value::false_val()]).into()
771    }
772
773    #[fixture]
774    fn const_value_array_bool() -> Value {
775        VArrayValue::new(bool_t(), [Value::true_val(), Value::false_val()]).into()
776    }
777
778    #[fixture]
779    fn const_array_options() -> Value {
780        let some_true = Value::some([Value::true_val()]);
781        let none = Value::none(vec![bool_t()]);
782        let elem_ty = SumType::new_option(vec![bool_t()]);
783        ArrayValue::new(elem_ty.into(), [some_true, none]).into()
784    }
785
786    #[fixture]
787    fn const_value_array_options() -> Value {
788        let some_true = Value::some([Value::true_val()]);
789        let none = Value::none(vec![bool_t()]);
790        let elem_ty = SumType::new_option(vec![bool_t()]);
791        VArrayValue::new(elem_ty.into(), [some_true, none]).into()
792    }
793
794    #[rstest]
795    #[case(Value::unit(), Type::UNIT, "const:seq:{}")]
796    #[case(const_usize(), usize_t(), "const:custom:ConstUsize(")]
797    #[case(serialized_float(17.4), float64_type(), "const:custom:json:Object")]
798    #[case(const_tuple(), Type::new_tuple(vec![usize_t(), bool_t()]), "const:seq:{")]
799    #[case(const_array_bool(), array_type(2, bool_t()), "const:custom:array")]
800    #[case(
801        const_value_array_bool(),
802        value_array_type(2, bool_t()),
803        "const:custom:value_array"
804    )]
805    #[case(
806        const_array_options(),
807        array_type(2, SumType::new_option(vec![bool_t()]).into()),
808        "const:custom:array"
809    )]
810    #[case(
811        const_value_array_options(),
812        value_array_type(2, SumType::new_option(vec![bool_t()]).into()),
813        "const:custom:value_array"
814    )]
815    fn const_type(
816        #[case] const_value: Value,
817        #[case] expected_type: Type,
818        #[case] name_prefix: &str,
819    ) {
820        assert_eq!(const_value.get_type(), expected_type);
821        let name = const_value.name();
822        assert!(
823            name.starts_with(name_prefix),
824            "{name} does not start with {name_prefix}"
825        );
826    }
827
828    #[rstest]
829    #[case(Value::unit(), Value::unit())]
830    #[case(const_usize(), const_usize())]
831    #[case(const_serialized_usize(), const_usize())]
832    #[case(const_tuple_serialized(), const_tuple())]
833    #[case(const_array_bool(), const_array_bool())]
834    #[case(const_value_array_bool(), const_value_array_bool())]
835    #[case(const_array_options(), const_array_options())]
836    #[case(const_value_array_options(), const_value_array_options())]
837    // Opaque constants don't get resolved into concrete types when running miri,
838    // as the `typetag` machinery is not available.
839    #[cfg_attr(miri, ignore)]
840    fn const_serde_roundtrip(#[case] const_value: Value, #[case] expected_value: Value) {
841        let serialized = serde_json::to_string(&const_value).unwrap();
842        let deserialized: Value = serde_json::from_str(&serialized).unwrap();
843
844        assert_eq!(deserialized, expected_value);
845    }
846
847    #[rstest]
848    fn const_custom_value(const_usize: Value, const_tuple: Value) {
849        assert_eq!(
850            const_usize.get_custom_value::<ConstUsize>(),
851            Some(&ConstUsize::new(257))
852        );
853        assert_eq!(const_usize.get_custom_value::<ConstInt>(), None);
854        assert_eq!(const_tuple.get_custom_value::<ConstUsize>(), None);
855        assert_eq!(const_tuple.get_custom_value::<ConstInt>(), None);
856    }
857
858    #[test]
859    fn test_json_const() {
860        let ex_id: ExtensionId = "my_extension".try_into().unwrap();
861        let typ_int = CustomType::new(
862            "my_type",
863            vec![TypeArg::BoundedNat(8)],
864            ex_id.clone(),
865            TypeBound::Copyable,
866            // Dummy extension reference.
867            &Weak::default(),
868        );
869        let json_const: Value = CustomSerialized::new(typ_int.clone(), 6.into()).into();
870        let classic_t = Type::new_extension(typ_int.clone());
871        assert_matches!(classic_t.least_upper_bound(), TypeBound::Copyable);
872        assert_eq!(json_const.get_type(), classic_t);
873
874        let typ_qb = CustomType::new(
875            "my_type",
876            vec![],
877            ex_id,
878            TypeBound::Copyable,
879            &Weak::default(),
880        );
881        let t = Type::new_extension(typ_qb.clone());
882        assert_ne!(json_const.get_type(), t);
883    }
884
885    #[rstest]
886    fn hash_tuple(const_tuple: Value) {
887        let vals = [
888            Value::unit(),
889            Value::true_val(),
890            Value::false_val(),
891            ConstUsize::new(13).into(),
892            Value::tuple([ConstUsize::new(13).into()]),
893            Value::tuple([ConstUsize::new(13).into(), ConstUsize::new(14).into()]),
894            Value::tuple([ConstUsize::new(13).into(), ConstUsize::new(15).into()]),
895            const_tuple,
896        ];
897
898        let num_vals = vals.len();
899        let hashes = vals.map(|v| {
900            let mut h = DefaultHasher::new();
901            v.try_hash(&mut h).then_some(()).unwrap();
902            h.finish()
903        });
904        assert_eq!(HashSet::from(hashes).len(), num_vals); // all distinct
905    }
906
907    #[test]
908    fn unhashable_tuple() {
909        let tup = Value::tuple([ConstUsize::new(5).into(), ConstF64::new(4.97).into()]);
910        let mut h1 = DefaultHasher::new();
911        let r = tup.try_hash(&mut h1);
912        assert!(!r);
913
914        // Check that didn't do anything, by checking the hasher behaves
915        // just like one which never saw the tuple
916        h1.write_usize(5);
917        let mut h2 = DefaultHasher::new();
918        h2.write_usize(5);
919        assert_eq!(h1.finish(), h2.finish());
920    }
921
922    mod proptest {
923        use super::super::{OpaqueValue, Sum};
924        use crate::{
925            ops::{Value, constant::CustomSerialized},
926            std_extensions::arithmetic::int_types::ConstInt,
927            std_extensions::collections::list::ListValue,
928            types::{SumType, Type},
929        };
930        use ::proptest::{collection::vec, prelude::*};
931        impl Arbitrary for OpaqueValue {
932            type Parameters = ();
933            type Strategy = BoxedStrategy<Self>;
934            fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
935                // We intentionally do not include `ConstF64` because it does not
936                // roundtrip serialize
937                prop_oneof![
938                    any::<ConstInt>().prop_map_into(),
939                    any::<CustomSerialized>().prop_map_into()
940                ]
941                .prop_recursive(
942                    3,  // No more than 3 branch levels deep
943                    32, // Target around 32 total elements
944                    3,  // Each collection is up to 3 elements long
945                    |child_strat| {
946                        (any::<Type>(), vec(child_strat, 0..3)).prop_map(|(typ, children)| {
947                            Self::new(ListValue::new(
948                                typ,
949                                children.into_iter().map(|e| Value::Extension { e }),
950                            ))
951                        })
952                    },
953                )
954                .boxed()
955            }
956        }
957
958        impl Arbitrary for Value {
959            type Parameters = ();
960            type Strategy = BoxedStrategy<Self>;
961            fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
962                use ::proptest::collection::vec;
963                let leaf_strat = prop_oneof![
964                    any::<OpaqueValue>().prop_map(|e| Self::Extension { e }),
965                    crate::proptest::any_hugr().prop_map(|x| Value::function(x).unwrap())
966                ];
967                leaf_strat
968                    .prop_recursive(
969                        3,  // No more than 3 branch levels deep
970                        32, // Target around 32 total elements
971                        3,  // Each collection is up to 3 elements long
972                        |element| {
973                            prop_oneof![
974                                vec(element.clone(), 0..3).prop_map(Self::tuple),
975                                (
976                                    any::<usize>(),
977                                    vec(element.clone(), 0..3),
978                                    any_with::<SumType>(1.into()) // for speed: don't generate large sum types for now
979                                )
980                                    .prop_map(
981                                        |(tag, values, sum_type)| {
982                                            Self::Sum(Sum {
983                                                tag,
984                                                values,
985                                                sum_type,
986                                            })
987                                        }
988                                    ),
989                            ]
990                        },
991                    )
992                    .boxed()
993            }
994        }
995    }
996
997    #[test]
998    fn test_tuple_deserialize() {
999        let json = r#"
1000        {
1001    "v": "Tuple",
1002    "vs": [
1003        {
1004            "v": "Sum",
1005            "tag": 0,
1006            "typ": {
1007                "t": "Sum",
1008                "s": "Unit",
1009                "size": 1
1010            },
1011            "vs": []
1012        },
1013        {
1014            "v": "Sum",
1015            "tag": 1,
1016            "typ": {
1017                "t": "Sum",
1018                "s": "General",
1019                "rows": [
1020                    [
1021                        {
1022                            "t": "Sum",
1023                            "s": "Unit",
1024                            "size": 1
1025                        }
1026                    ],
1027                    [
1028                        {
1029                            "t": "Sum",
1030                            "s": "Unit",
1031                            "size": 2
1032                        }
1033                    ]
1034                ]
1035            },
1036            "vs": [
1037                {
1038                    "v": "Sum",
1039                    "tag": 1,
1040                    "typ": {
1041                        "t": "Sum",
1042                        "s": "Unit",
1043                        "size": 2
1044                    },
1045                    "vs": []
1046                }
1047            ]
1048        }
1049    ]
1050}
1051        "#;
1052
1053        let v: Value = serde_json::from_str(json).unwrap();
1054        assert_eq!(
1055            v,
1056            Value::tuple([
1057                Value::unit(),
1058                Value::sum(
1059                    1,
1060                    [Value::true_val()],
1061                    SumType::new([
1062                        type_row![Type::UNIT],
1063                        vec![Value::true_val().get_type()].into()
1064                    ]),
1065                )
1066                .unwrap()
1067            ])
1068        );
1069    }
1070}