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}