hugr_model/v0/
mod.rs

1//! Version 0 (unstable).
2//!
3//! **Warning**: This module is still under development and is expected to change.
4//! It is included in the library to allow for early experimentation, and for
5//! the core and model to converge incrementally.
6//!
7//! This module defines representations of the hugr IR as plain data, designed
8//! to be as independent of implementation details as feasible. It can be used
9//! by the core compiler, alternative implementations or tooling that does not
10//! need the power/complexity of the full compiler. We provide the following
11//! in-memory representations:
12//!
13//! - [Table]: Efficient intermediate data structure to facilitate conversions.
14//! - [AST]: Abstract syntax tree that uses direct references rather than table indices.
15//!
16//! The table and AST format are interconvertible and can be serialised to
17//! a binary and text format, respectively:
18//!
19//! - [Binary]: Binary serialisation format optimised for performance and size.
20//! - [Text]: Human readable s-expression based text format.
21//!
22//! # Logical Format
23//!
24//! The hugr IR is a hierarchical graph data structure. __Nodes__ represent both
25//! __instructions__ that manipulate runtime values and __symbols__ which
26//! represent named language objects. Instructions have __input__ and __output__ ports
27//! and runtime values flow between ports when they are connected by a __link__.
28//!
29//! Nodes are organised into __regions__ and do not have any explicit ordering
30//! between them; any schedule that respects the data dependencies between nodes
31//! is valid. Regions come in three different kinds. __Module regions__ form the
32//! top level of a module and can only contain symbols. __Dataflow regions__
33//! describe how data flows from the region's __source__ ports to the region's
34//! __target__ ports. __Controlflow regions__ are control flow graphs containing
35//! dataflow __blocks__, with control flow originating from the region's source
36//! ports and ending in the region's target ports.
37//!
38//! __Terms__ form a meta language that is used to describe types, parameters and metadata that
39//! are known statically. To allow types to be parameterized by values, types and values
40//! are treated uniformly as terms, enabling a restricted form of dependent typing.
41//! Terms are extensible declaratively via __constructors__.
42//! __Constraints__ can be used to express more complex validation rules.
43//!
44//! # Remaining Mismatch with `hugr-core`
45//!
46//! This data model was designed to encode as much of `hugr-core` as possible while also
47//! filling in conceptual gaps and providing a forward-compatible foundation for future
48//! development. However, there are still some mismatches with `hugr-core` that are not
49//! addressed by conversions in import/export:
50//!
51//! - Some static types can not yet be represented in `hugr-core` although they should be.
52//! - The model does not have types with a copy bound as `hugr-core` does, and instead uses
53//!   a more general form of type constraints ([#1556]). Similarly, the model does not have
54//!   bounded naturals. We perform a conversion for compatibility where possible, but this does
55//!   not fully cover all potential cases of bounds.
56//! - `hugr-model` allows to declare term constructors that serve as blueprints for constructing
57//!   runtime values. This allows constants to have potentially multiple representations,
58//!   which can be essential in case of very large constants that require efficient encodings.
59//!   `hugr-core` is more restricted, requiring a canonical representation for constant values.
60//! - `hugr-model` has support for passing closed regions as static parameters to operations,
61//!   which allows for higher-order operations that require their function arguments to be
62//!   statically known. We currently do not yet support converting this to `hugr-core`.
63//! - In a model module, ports are connected when they share the same link. This differs from
64//!   the type of port connectivity in the graph data structure used by `hugr-core`. However,
65//!   `hugr-core` restricts connectivity so that in any group of connected ports there is at
66//!   most one output port (for dataflow) or at most one input port (for control flow). In
67//!   these cases, there is no mismatch.
68//! - `hugr-core` only allows to define type aliases, but not aliases for other terms.
69//! - `hugr-model` has no concept of order edges, encoding a strong preference that ordering
70//!   requirements be encoded within the dataflow paradigm.
71//! - Both `hugr-model` and `hugr-core` support metadata, but they use different encodings.
72//!   `hugr-core` encodes metadata as JSON objects, while `hugr-model` uses terms. Using
73//!   terms has the advantage that metadata can be validated with the same type checking
74//!   mechanism as the rest of the model ([#1553]).
75//! - `hugr-model` have a root region that corresponds to a root `Module` in `hugr-core`.
76//!   `hugr-core` however can have nodes with different operations as their root ([#1554]).
77//!
78//! [#1556]: https://github.com/CQCL/hugr/discussions/1556
79//! [#1553]: https://github.com/CQCL/hugr/issues/1553
80//! [#1554]: https://github.com/CQCL/hugr/issues/1554
81//! [Text]: crate::v0::ast
82//! [Binary]: crate::v0::binary
83//! [Table]: crate::v0::table
84//! [AST]: crate::v0::ast
85use ordered_float::OrderedFloat;
86#[cfg(feature = "pyo3")]
87use pyo3::PyTypeInfo as _;
88#[cfg(feature = "pyo3")]
89use pyo3::types::PyAnyMethods as _;
90use smol_str::SmolStr;
91use std::sync::Arc;
92use table::LinkIndex;
93
94/// Describes how a function or symbol should be acted upon by a linker
95#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
96pub enum Visibility {
97    /// The linker should ignore this function or symbol
98    Private,
99    /// The linker should act upon this function or symbol
100    Public,
101}
102
103/// Core function types.
104///
105/// - **Parameter:** `?inputs : (core.list core.type)`
106/// - **Parameter:** `?outputs : (core.list core.type)`
107/// - **Result:** `core.type`
108pub const CORE_FN: &str = "core.fn";
109
110/// The type of runtime types.
111///
112/// Runtime types are the types of values that can flow between nodes at runtime.
113///
114/// - **Result:** `?type : core.static`
115pub const CORE_TYPE: &str = "core.type";
116
117/// The type of static types.
118///
119/// Static types are the types of statically known parameters.
120///
121/// This is the only term that is its own type.
122///
123/// - **Result:** `?type : core.static`
124pub const CORE_STATIC: &str = "core.static";
125
126/// The type of constraints.
127///
128/// - **Result:** `?type : core.static`
129pub const CORE_CONSTRAINT: &str = "core.constraint";
130
131/// The constraint for non-linear runtime data.
132///
133/// Runtime values are copied implicitly by connecting an output port to more
134/// than one input port. Similarly runtime values can be deleted implicitly when
135/// an output port is not connected to any input port. In either of these cases
136/// the type of the runtime value must satisfy this constraint.
137///
138/// - **Parameter:** `?type : core.type`
139/// - **Result:** `core.constraint`
140pub const CORE_NON_LINEAR: &str = "core.nonlinear";
141
142/// The type of metadata.
143///
144/// - **Result:** `?type : core.static`
145pub const CORE_META: &str = "core.meta";
146
147/// Runtime algebraic data types.
148///
149/// Algebraic data types are sums of products of other runtime types.
150///
151/// - **Parameter:** `?variants : (core.list (core.list core.type))`
152/// - **Result:** `core.type`
153pub const CORE_ADT: &str = "core.adt";
154
155/// Type of string literals.
156///
157/// - **Result:** `core.static`
158pub const CORE_STR_TYPE: &str = "core.str";
159
160/// Type of natural number literals.
161///
162/// - **Result:** `core.static`
163pub const CORE_NAT_TYPE: &str = "core.nat";
164
165/// Type of bytes literals.
166///
167/// - **Result:** `core.static`
168pub const CORE_BYTES_TYPE: &str = "core.bytes";
169
170/// Type of float literals.
171///
172/// - **Result:** `core.static`
173pub const CORE_FLOAT_TYPE: &str = "core.float";
174
175/// Type of control flow regions.
176///
177/// - **Parameter:** `?inputs : (core.list (core.list core.type))`
178/// - **Parameter:** `?outputs : (core.list (core.list core.type))`
179/// - **Result:** `core.type`
180pub const CORE_CTRL: &str = "core.ctrl";
181
182/// The type for runtime constants.
183///
184/// - **Parameter:** `?type : core.type`
185/// - **Result:** `core.static`
186pub const CORE_CONST: &str = "core.const";
187
188/// Constants for runtime algebraic data types.
189///
190/// - **Parameter:** `?variants : (core.list core.type)`
191/// - **Parameter:** `?types : (core.list core.static)`
192/// - **Parameter:** `?tag : core.nat`
193/// - **Parameter:** `?values : (core.tuple ?types)`
194/// - **Result:** `(core.const (core.adt ?variants))`
195pub const CORE_CONST_ADT: &str = "core.const.adt";
196
197/// The type for lists of static data.
198///
199/// Lists are finite sequences such that all elements have the same type.
200/// For heterogeneous sequences, see [`CORE_TUPLE_TYPE`].
201///
202/// - **Parameter:** `?type : core.static`
203/// - **Result:** `core.static`
204pub const CORE_LIST_TYPE: &str = "core.list";
205
206/// The type for tuples of static data.
207///
208/// Tuples are finite sequences that allow elements to have different types.
209/// For homogeneous sequences, see [`CORE_LIST_TYPE`].
210///
211/// - **Parameter:** `?types : (core.list core.static)`
212/// - **Result:** `core.static`
213pub const CORE_TUPLE_TYPE: &str = "core.tuple";
214
215/// Operation to call a statically known function.
216///
217/// - **Parameter:** `?inputs : (core.list core.type)`
218/// - **Parameter:** `?outputs : (core.list core.type)`
219/// - **Parameter:** `?func : (core.const (core.fn ?inputs ?outputs))`
220/// - **Result:** `(core.fn ?inputs ?outputs ?ext)`
221pub const CORE_CALL: &str = "core.call";
222
223/// Operation to call a functiion known at runtime.
224///
225/// - **Parameter:** `?inputs : (core.list core.type)`
226/// - **Parameter:** `?outputs : (core.list core.type)`
227/// - **Result:** `(core.fn [(core.fn ?inputs ?outputs) ?inputs ...] ?outputs)`
228pub const CORE_CALL_INDIRECT: &str = "core.call_indirect";
229
230/// Operation to load a constant value.
231///
232/// - **Parameter:** `?type : core.type`
233/// - **Parameter:** `?value : (core.const ?type)`
234/// - **Result:** `(core.fn [] [?type])`
235pub const CORE_LOAD_CONST: &str = "core.load_const";
236
237/// Operation to create a value of an algebraic data type.
238///
239/// - **Parameter:** `?variants : (core.list (core.list core.type))`
240/// - **Parameter:** `?types : (core.list core.type)`
241/// - **Parameter:** `?tag : core.nat`
242/// - **Result:** `(core.fn ?types [(core.adt ?variants)])`
243pub const CORE_MAKE_ADT: &str = "core.make_adt";
244
245/// Constructor for documentation metadata.
246///
247/// - **Parameter:** `?description : core.str`
248/// - **Result:** `core.meta`
249pub const CORE_META_DESCRIPTION: &str = "core.meta.description";
250
251/// Metadata to tag a node or region as the entrypoint of a module.
252///
253/// - **Result:** `core.meta`
254pub const CORE_ENTRYPOINT: &str = "core.entrypoint";
255
256/// Constructor for JSON encoded metadata.
257///
258/// This is included in the model to allow for compatibility with `hugr-core`.
259/// The intention is to deprecate this in the future in favor of metadata
260/// expressed with custom constructors.
261///
262/// - **Parameter:** `?name : core.str`
263/// - **Parameter:** `?json : core.str`
264/// - **Result:** `core.meta`
265pub const COMPAT_META_JSON: &str = "compat.meta_json";
266
267/// Constructor for JSON encoded constants.
268///
269/// This is included in the model to allow for compatibility with `hugr-core`.
270/// The intention is to deprecate this in the future in favor of constants
271/// expressed with custom constructors.
272///
273/// - **Parameter:** `?type : core.type`
274/// - **Parameter:** `?json : core.str`
275/// - **Result:** `(core.const ?type)`
276pub const COMPAT_CONST_JSON: &str = "compat.const_json";
277
278/// Metadata constructor for order hint keys.
279///
280/// Nodes in a dataflow region can be annotated with a key. Each node may have
281/// at most one key and the key must be unique among all nodes in the same
282/// dataflow region. The parent dataflow graph can then use the
283/// `order_hint.order` metadata to imply a desired ordering relation, referring
284/// to the nodes by their key.
285///
286/// - **Parameter:** `?key : core.nat`
287/// - **Result:** `core.meta`
288pub const ORDER_HINT_KEY: &str = "core.order_hint.key";
289
290/// Metadata constructor for order hint keys on input nodes.
291///
292/// When the sources of a dataflow region are represented by an input operation
293/// within the region, this metadata can be attached the region to give the
294/// input node an order hint key.
295///
296/// - **Parameter:** `?key : core.nat`
297/// - **Result:** `core.meta`
298pub const ORDER_HINT_INPUT_KEY: &str = "core.order_hint.input_key";
299
300/// Metadata constructor for order hint keys on output nodes.
301///
302/// When the targets of a dataflow region are represented by an output operation
303/// within the region, this metadata can be attached the region to give the
304/// output node an order hint key.
305///
306/// - **Parameter:** `?key : core.nat`
307/// - **Result:** `core.meta`
308pub const ORDER_HINT_OUTPUT_KEY: &str = "core.order_hint.output_key";
309
310/// Metadata constructor for order hints.
311///
312/// When this metadata is attached to a dataflow region, it can indicate a
313/// preferred ordering relation between child nodes. Code generation must take
314/// this into account when deciding on an execution order. The child nodes are
315/// identified by a key, using the `order_hint.key` metadata.
316///
317/// The graph consisting of both value dependencies between nodes and order
318/// hints must be directed acyclic.
319///
320/// - **Parameter:** `?before : core.nat`
321/// - **Parameter:** `?after : core.nat`
322/// - **Result:** `core.meta`
323pub const ORDER_HINT_ORDER: &str = "core.order_hint.order";
324
325/// Metadata constructor for symbol titles.
326///
327/// The names of functions in `hugr-core` are currently not used for symbol
328/// resolution, but rather serve as a short description of the function.
329/// As such, there is no requirement for uniqueness or formatting.
330/// This metadata can be used to preserve that name when serializing through
331/// `hugr-model`.
332///
333/// - **Parameter:** `?title: core.str`
334/// - **Result:** `core.meta`
335pub const CORE_TITLE: &str = "core.title";
336
337pub mod ast;
338pub mod binary;
339pub mod scope;
340pub mod table;
341
342pub use bumpalo;
343
344/// Type to indicate whether scopes are open or closed.
345#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
346pub enum ScopeClosure {
347    /// A scope that is open and therefore not isolated from its parent scope.
348    #[default]
349    Open,
350    /// A scope that is closed and therefore isolated from its parent scope.
351    Closed,
352}
353
354#[cfg(feature = "pyo3")]
355impl<'py> pyo3::FromPyObject<'py> for ScopeClosure {
356    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
357        let value: usize = ob.getattr("value")?.extract()?;
358        match value {
359            0 => Ok(Self::Open),
360            1 => Ok(Self::Closed),
361            _ => Err(pyo3::exceptions::PyTypeError::new_err(
362                "Invalid ScopeClosure.",
363            )),
364        }
365    }
366}
367
368#[cfg(feature = "pyo3")]
369impl<'py> pyo3::IntoPyObject<'py> for ScopeClosure {
370    type Target = pyo3::PyAny;
371    type Output = pyo3::Bound<'py, Self::Target>;
372    type Error = pyo3::PyErr;
373
374    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
375        let py_module = py.import("hugr.model")?;
376        let py_class = py_module.getattr("ScopeClosure")?;
377
378        match self {
379            ScopeClosure::Open => py_class.getattr("OPEN"),
380            ScopeClosure::Closed => py_class.getattr("CLOSED"),
381        }
382    }
383}
384
385/// The kind of a region.
386#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
387pub enum RegionKind {
388    /// Data flow region.
389    #[default]
390    DataFlow = 0,
391    /// Control flow region.
392    ControlFlow = 1,
393    /// Module region.
394    Module = 2,
395}
396
397#[cfg(feature = "pyo3")]
398impl<'py> pyo3::FromPyObject<'py> for RegionKind {
399    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
400        let value: usize = ob.getattr("value")?.extract()?;
401        match value {
402            0 => Ok(Self::DataFlow),
403            1 => Ok(Self::ControlFlow),
404            2 => Ok(Self::Module),
405            _ => Err(pyo3::exceptions::PyTypeError::new_err(
406                "Invalid RegionKind.",
407            )),
408        }
409    }
410}
411
412#[cfg(feature = "pyo3")]
413impl<'py> pyo3::IntoPyObject<'py> for RegionKind {
414    type Target = pyo3::PyAny;
415    type Output = pyo3::Bound<'py, Self::Target>;
416    type Error = pyo3::PyErr;
417
418    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
419        let py_module = py.import("hugr.model")?;
420        let py_class = py_module.getattr("RegionKind")?;
421
422        match self {
423            RegionKind::DataFlow => py_class.getattr("DATA_FLOW"),
424            RegionKind::ControlFlow => py_class.getattr("CONTROL_FLOW"),
425            RegionKind::Module => py_class.getattr("MODULE"),
426        }
427    }
428}
429
430/// The name of a variable.
431#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
432pub struct VarName(SmolStr);
433
434impl VarName {
435    /// Create a new variable name.
436    pub fn new(name: impl Into<SmolStr>) -> Self {
437        Self(name.into())
438    }
439}
440
441impl AsRef<str> for VarName {
442    fn as_ref(&self) -> &str {
443        self.0.as_ref()
444    }
445}
446
447#[cfg(feature = "pyo3")]
448impl<'py> pyo3::FromPyObject<'py> for VarName {
449    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
450        let name: String = ob.extract()?;
451        Ok(Self::new(name))
452    }
453}
454
455#[cfg(feature = "pyo3")]
456impl<'py> pyo3::IntoPyObject<'py> for &VarName {
457    type Target = pyo3::types::PyString;
458    type Output = pyo3::Bound<'py, Self::Target>;
459    type Error = pyo3::PyErr;
460
461    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
462        Ok(self.as_ref().into_pyobject(py)?)
463    }
464}
465
466/// The name of a symbol.
467#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
468pub struct SymbolName(SmolStr);
469
470impl SymbolName {
471    /// Create a new symbol name.
472    pub fn new(name: impl Into<SmolStr>) -> Self {
473        Self(name.into())
474    }
475}
476
477impl AsRef<str> for SymbolName {
478    fn as_ref(&self) -> &str {
479        self.0.as_ref()
480    }
481}
482
483#[cfg(feature = "pyo3")]
484impl<'py> pyo3::FromPyObject<'py> for SymbolName {
485    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
486        let name: String = ob.extract()?;
487        Ok(Self::new(name))
488    }
489}
490
491/// The name of a link.
492#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
493pub struct LinkName(SmolStr);
494
495impl LinkName {
496    /// Create a new link name.
497    pub fn new(name: impl Into<SmolStr>) -> Self {
498        Self(name.into())
499    }
500
501    /// Create a new link name from a link index.
502    #[must_use]
503    pub fn new_index(index: LinkIndex) -> Self {
504        // TODO: Should named and numbered links have different namespaces?
505        Self(format!("{index}").into())
506    }
507}
508
509impl AsRef<str> for LinkName {
510    fn as_ref(&self) -> &str {
511        self.0.as_ref()
512    }
513}
514
515#[cfg(feature = "pyo3")]
516impl<'py> pyo3::FromPyObject<'py> for LinkName {
517    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
518        let name: String = ob.extract()?;
519        Ok(Self::new(name))
520    }
521}
522
523#[cfg(feature = "pyo3")]
524impl<'py> pyo3::IntoPyObject<'py> for &LinkName {
525    type Target = pyo3::types::PyString;
526    type Output = pyo3::Bound<'py, Self::Target>;
527    type Error = pyo3::PyErr;
528
529    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
530        Ok(self.as_ref().into_pyobject(py)?)
531    }
532}
533
534/// A static literal value.
535///
536/// Literal values may be large since they can include strings and byte
537/// sequences of arbitrary length. To enable cheap cloning and sharing,
538/// strings and byte sequences use reference counting.
539#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
540pub enum Literal {
541    /// String literal.
542    Str(SmolStr),
543    /// Natural number literal (unsigned 64 bit).
544    Nat(u64),
545    /// Byte sequence literal.
546    Bytes(Arc<[u8]>),
547    /// Floating point literal
548    Float(OrderedFloat<f64>),
549}
550
551#[cfg(feature = "pyo3")]
552impl<'py> pyo3::FromPyObject<'py> for Literal {
553    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
554        if pyo3::types::PyString::is_type_of(ob) {
555            let value: String = ob.extract()?;
556            Ok(Literal::Str(value.into()))
557        } else if pyo3::types::PyInt::is_type_of(ob) {
558            let value: u64 = ob.extract()?;
559            Ok(Literal::Nat(value))
560        } else if pyo3::types::PyFloat::is_type_of(ob) {
561            let value: f64 = ob.extract()?;
562            Ok(Literal::Float(value.into()))
563        } else if pyo3::types::PyBytes::is_type_of(ob) {
564            let value: Vec<u8> = ob.extract()?;
565            Ok(Literal::Bytes(value.into()))
566        } else {
567            Err(pyo3::exceptions::PyTypeError::new_err(
568                "Invalid literal value.",
569            ))
570        }
571    }
572}
573
574#[cfg(feature = "pyo3")]
575impl<'py> pyo3::IntoPyObject<'py> for &Literal {
576    type Target = pyo3::PyAny;
577    type Output = pyo3::Bound<'py, Self::Target>;
578    type Error = pyo3::PyErr;
579
580    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
581        Ok(match self {
582            Literal::Str(s) => s.as_str().into_pyobject(py)?.into_any(),
583            Literal::Nat(n) => n.into_pyobject(py)?.into_any(),
584            Literal::Bytes(b) => pyo3::types::PyBytes::new(py, b)
585                .into_pyobject(py)?
586                .into_any(),
587            Literal::Float(f) => f.0.into_pyobject(py)?.into_any(),
588        })
589    }
590}
591
592#[cfg(test)]
593mod test {
594    use super::*;
595    use proptest::{prelude::*, string::string_regex};
596
597    impl Arbitrary for Literal {
598        type Parameters = ();
599        type Strategy = BoxedStrategy<Self>;
600
601        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
602            prop_oneof![
603                any::<String>().prop_map(|s| Literal::Str(s.into())),
604                any::<u64>().prop_map(Literal::Nat),
605                prop::collection::vec(any::<u8>(), 0..100).prop_map(|v| Literal::Bytes(v.into())),
606                any::<f64>().prop_map(|f| Literal::Float(OrderedFloat(f)))
607            ]
608            .boxed()
609        }
610    }
611
612    impl Arbitrary for SymbolName {
613        type Parameters = ();
614        type Strategy = BoxedStrategy<Self>;
615
616        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
617            string_regex(r"[a-zA-Z\-_][0-9a-zA-Z\-_](\.[a-zA-Z\-_][0-9a-zA-Z\-_])*")
618                .unwrap()
619                .prop_map(Self::new)
620                .boxed()
621        }
622    }
623
624    impl Arbitrary for VarName {
625        type Parameters = ();
626        type Strategy = BoxedStrategy<Self>;
627
628        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
629            string_regex(r"[a-zA-Z\-_][0-9a-zA-Z\-_]")
630                .unwrap()
631                .prop_map(Self::new)
632                .boxed()
633        }
634    }
635
636    proptest! {
637        #[test]
638        fn test_literal_text(lit: Literal) {
639            let text = lit.to_string();
640            let parsed: Literal = text.parse().unwrap();
641            assert_eq!(lit, parsed);
642        }
643    }
644}