• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Qiskit / qiskit / 26277326689

22 May 2026 08:32AM UTC coverage: 87.467% (-0.03%) from 87.494%
26277326689

Pull #14618

github

web-flow
Merge be0e5057c into 376781740
Pull Request #14618: New classical expr.Range specification

373 of 500 new or added lines in 16 files covered. (74.6%)

3 existing lines in 3 files now uncovered.

108776 of 124362 relevant lines covered (87.47%)

953953.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.17
/crates/circuit/src/classical/expr/expr.rs
1
// This code is part of Qiskit.
2
//
3
// (C) Copyright IBM 2025
4
//
5
// This code is licensed under the Apache License, Version 2.0. You may
6
// obtain a copy of this license in the LICENSE.txt file in the root directory
7
// of this source tree or at https://www.apache.org/licenses/LICENSE-2.0.
8
//
9
// Any modifications or derivative works of this code must retain this
10
// copyright notice, and modified files need to carry a notice indicating
11
// that they have been altered from the originals.
12

13
use std::error::Error;
14

15
use crate::classical::expr::{Binary, Cast, Index, Range, Stretch, Unary, Value, Var};
16
use crate::classical::types::Type;
17
use pyo3::prelude::*;
18
use pyo3::{IntoPyObjectExt, intern};
19

20
/// A classical expression.
21
///
22
/// Variants that themselves contain [Expr]s are boxed. This is done instead
23
/// of boxing the contained [Expr]s within the specific type to reduce the
24
/// number of boxes we need (e.g. Binary would otherwise contain two boxed
25
/// expressions).
26
#[derive(Clone, Debug, PartialEq)]
27
pub enum Expr {
28
    Unary(Box<Unary>),
29
    Binary(Box<Binary>),
30
    Cast(Box<Cast>),
31
    Value(Value),
32
    Var(Var),
33
    Stretch(Stretch),
34
    Index(Box<Index>),
35
    Range(Box<Range>),
36
}
37

38
#[derive(Debug, PartialEq)]
39
pub enum ExprRef<'a> {
40
    Unary(&'a Unary),
41
    Binary(&'a Binary),
42
    Cast(&'a Cast),
43
    Value(&'a Value),
44
    Var(&'a Var),
45
    Stretch(&'a Stretch),
46
    Index(&'a Index),
47
    Range(&'a Range),
48
}
49

50
#[derive(Debug, PartialEq)]
51
pub enum ExprRefMut<'a> {
52
    Unary(&'a mut Unary),
53
    Binary(&'a mut Binary),
54
    Cast(&'a mut Cast),
55
    Value(&'a mut Value),
56
    Var(&'a mut Var),
57
    Stretch(&'a mut Stretch),
58
    Index(&'a mut Index),
59
    Range(&'a mut Range),
60
}
61

62
#[derive(Clone, Debug, PartialEq)]
63
pub enum IdentifierRef<'a> {
64
    Var(&'a Var),
65
    Stretch(&'a Stretch),
66
}
67

68
impl Expr {
69
    /// Converts from `&Expr` to `ExprRef`.
70
    pub fn as_ref(&self) -> ExprRef<'_> {
24,834✔
71
        match self {
24,834✔
72
            Expr::Unary(u) => ExprRef::Unary(u.as_ref()),
610✔
73
            Expr::Binary(b) => ExprRef::Binary(b.as_ref()),
7,398✔
74
            Expr::Cast(c) => ExprRef::Cast(c.as_ref()),
148✔
75
            Expr::Value(v) => ExprRef::Value(v),
9,583✔
76
            Expr::Var(v) => ExprRef::Var(v),
6,951✔
77
            Expr::Stretch(s) => ExprRef::Stretch(s),
118✔
78
            Expr::Index(i) => ExprRef::Index(i.as_ref()),
26✔
NEW
79
            Expr::Range(r) => ExprRef::Range(r.as_ref()),
×
80
        }
81
    }
24,834✔
82

83
    /// Converts from `&mut Expr` to `ExprRefMut`.
84
    pub fn as_mut(&mut self) -> ExprRefMut<'_> {
69✔
85
        match self {
69✔
86
            Expr::Unary(u) => ExprRefMut::Unary(u.as_mut()),
7✔
87
            Expr::Binary(b) => ExprRefMut::Binary(b.as_mut()),
16✔
88
            Expr::Cast(c) => ExprRefMut::Cast(c.as_mut()),
4✔
89
            Expr::Value(v) => ExprRefMut::Value(v),
10✔
90
            Expr::Var(v) => ExprRefMut::Var(v),
30✔
91
            Expr::Stretch(s) => ExprRefMut::Stretch(s),
2✔
92
            Expr::Index(i) => ExprRefMut::Index(i.as_mut()),
×
NEW
93
            Expr::Range(r) => ExprRefMut::Range(r.as_mut()),
×
94
        }
95
    }
69✔
96

97
    /// The const-ness of the expression.
98
    pub fn is_const(&self) -> bool {
6,798✔
99
        match self {
6,798✔
100
            Expr::Unary(u) => u.constant,
48✔
101
            Expr::Binary(b) => b.constant,
1,074✔
102
            Expr::Cast(c) => c.constant,
116✔
103
            Expr::Value(_) => true,
3,046✔
104
            Expr::Var(_) => false,
2,244✔
105
            Expr::Stretch(_) => true,
264✔
106
            Expr::Index(i) => i.constant,
6✔
NEW
107
            Expr::Range(r) => r.constant,
×
108
        }
109
    }
6,798✔
110

111
    /// The expression's [Type].
112
    pub fn ty(&self) -> Type {
864✔
113
        match self {
864✔
114
            Expr::Unary(u) => u.ty,
×
115
            Expr::Binary(b) => b.ty,
×
116
            Expr::Cast(c) => c.ty,
×
117
            Expr::Value(v) => match v {
722✔
118
                Value::Duration(_) => Type::Duration,
×
119
                Value::Float { ty, .. } => *ty,
8✔
120
                Value::Uint { ty, .. } => *ty,
714✔
121
            },
122
            Expr::Var(v) => match v {
142✔
123
                Var::Standalone { ty, .. } => *ty,
116✔
124
                Var::Bit { .. } => Type::Bool,
×
125
                Var::Register { ty, .. } => *ty,
26✔
126
            },
127
            Expr::Stretch(_) => Type::Duration,
×
128
            Expr::Index(i) => i.ty,
×
NEW
129
            Expr::Range(r) => r.ty,
×
130
        }
131
    }
864✔
132

133
    /// Returns an iterator over the identifier nodes in this expression in some
134
    /// deterministic order.
135
    pub fn identifiers(&self) -> impl Iterator<Item = IdentifierRef<'_>> {
1✔
136
        IdentIterator(ExprIterator { stack: vec![self] })
1✔
137
    }
1✔
138

139
    /// Returns an iterator over the [Var] nodes in this expression in some
140
    /// deterministic order.
141
    pub fn vars(&self) -> impl Iterator<Item = &Var> {
8,424✔
142
        VarIterator(ExprIterator { stack: vec![self] })
8,424✔
143
    }
8,424✔
144

145
    /// Returns an iterator over all nodes in this expression in some deterministic
146
    /// order.
147
    pub fn iter(&self) -> impl Iterator<Item = ExprRef<'_>> {
873✔
148
        ExprIterator { stack: vec![self] }
873✔
149
    }
873✔
150

151
    /// Visits all nodes by mutable reference, in a post-order traversal.
152
    pub fn visit_mut<F, E>(&mut self, mut visitor: F) -> Result<(), E>
26✔
153
    where
26✔
154
        F: FnMut(ExprRefMut) -> Result<(), E>,
26✔
155
        E: Error,
26✔
156
    {
157
        self.visit_mut_impl(&mut visitor)
26✔
158
    }
26✔
159

160
    fn visit_mut_impl<F, E>(&mut self, visitor: &mut F) -> Result<(), E>
69✔
161
    where
69✔
162
        F: FnMut(ExprRefMut) -> Result<(), E>,
69✔
163
        E: Error,
69✔
164
    {
165
        match self {
69✔
166
            Expr::Unary(u) => u.operand.visit_mut_impl(visitor)?,
7✔
167
            Expr::Binary(b) => {
16✔
168
                b.left.visit_mut_impl(visitor)?;
16✔
169
                b.right.visit_mut_impl(visitor)?;
16✔
170
            }
171
            Expr::Cast(c) => c.operand.visit_mut_impl(visitor)?,
4✔
172
            Expr::Value(_) => {}
10✔
173
            Expr::Var(_) => {}
30✔
174
            Expr::Stretch(_) => {}
2✔
175
            Expr::Index(i) => {
×
176
                i.target.visit_mut_impl(visitor)?;
×
177
                i.index.visit_mut_impl(visitor)?;
×
178
            }
NEW
179
            Expr::Range(r) => {
×
NEW
180
                r.start.visit_mut_impl(visitor)?;
×
NEW
181
                r.stop.visit_mut_impl(visitor)?;
×
NEW
182
                r.step.visit_mut_impl(visitor)?;
×
183
            }
184
        }
185
        visitor(self.as_mut())
69✔
186
    }
69✔
187

188
    /// Do these two expressions have exactly the same tree structure?
189
    pub fn structurally_equivalent(&self, other: &Expr) -> bool {
45✔
190
        let identity_key = |v: &Var| v.clone();
45✔
191
        self.structurally_equivalent_by_key(identity_key, other, identity_key)
45✔
192
    }
45✔
193

194
    /// Do these two expressions have exactly the same tree structure, up to some key function for
195
    /// [Var] nodes?
196
    ///
197
    /// In other words, are these two expressions the exact same trees, except we compare the
198
    /// [Var] nodes by calling the appropriate `*_var_key` function on them, and comparing
199
    /// that output for equality.  This function does not allow any semantic "equivalences" such as
200
    /// asserting that `a == b` is equivalent to `b == a`; the evaluation order of the operands
201
    /// could, in general, cause such a statement to be false.
202
    pub fn structurally_equivalent_by_key<F1, F2, K>(
436✔
203
        &self,
436✔
204
        mut self_var_key: F1,
436✔
205
        other: &Expr,
436✔
206
        mut other_var_key: F2,
436✔
207
    ) -> bool
436✔
208
    where
436✔
209
        F1: FnMut(&Var) -> K,
436✔
210
        F2: FnMut(&Var) -> K,
436✔
211
        K: PartialEq,
436✔
212
    {
213
        let mut self_nodes = self.iter();
436✔
214
        let mut other_nodes = other.iter();
436✔
215
        loop {
216
            match (self_nodes.next(), other_nodes.next()) {
1,948✔
217
                (Some(a), Some(b)) => match (a, b) {
1,547✔
218
                    (ExprRef::Unary(a), ExprRef::Unary(b)) => {
81✔
219
                        if a.op != b.op || a.ty != b.ty || a.constant != b.constant {
81✔
220
                            return false;
×
221
                        }
81✔
222
                    }
223
                    (ExprRef::Binary(a), ExprRef::Binary(b)) => {
513✔
224
                        if a.op != b.op || a.ty != b.ty || a.constant != b.constant {
513✔
225
                            return false;
×
226
                        }
513✔
227
                    }
228
                    (ExprRef::Cast(a), ExprRef::Cast(b)) => {
29✔
229
                        if a.ty != b.ty || a.constant != b.constant {
29✔
230
                            return false;
×
231
                        }
29✔
232
                    }
233
                    (ExprRef::Value(a), ExprRef::Value(b)) => {
473✔
234
                        if a != b {
473✔
235
                            return false;
×
236
                        }
473✔
237
                    }
238
                    (ExprRef::Var(a), ExprRef::Var(b)) => {
355✔
239
                        if a.ty() != b.ty() {
355✔
240
                            return false;
×
241
                        }
355✔
242
                        if self_var_key(a) != other_var_key(b) {
355✔
243
                            return false;
1✔
244
                        }
354✔
245
                    }
246
                    (ExprRef::Stretch(a), ExprRef::Stretch(b)) => {
57✔
247
                        if a.uuid != b.uuid {
57✔
248
                            return false;
×
249
                        }
57✔
250
                    }
251
                    (ExprRef::Index(a), ExprRef::Index(b)) => {
5✔
252
                        if a.ty != b.ty || a.constant != b.constant {
5✔
253
                            return false;
×
254
                        }
5✔
255
                    }
NEW
256
                    (ExprRef::Range(a), ExprRef::Range(b)) => {
×
NEW
257
                        if a.ty != b.ty || a.constant != b.constant {
×
NEW
258
                            return false;
×
NEW
259
                        }
×
260
                    }
261
                    _ => return false,
34✔
262
                },
263
                (None, None) => return true,
401✔
264
                _ => return false,
×
265
            }
266
        }
267
    }
436✔
268
}
269

270
/// A private iterator over the [Expr] nodes of an expression
271
/// by reference.
272
///
273
/// The first node reference returned is the [Expr] itself.
274
struct ExprIterator<'a> {
275
    stack: Vec<&'a Expr>,
276
}
277

278
impl<'a> Iterator for ExprIterator<'a> {
279
    type Item = ExprRef<'a>;
280

281
    fn next(&mut self) -> Option<Self::Item> {
34,061✔
282
        let expr = self.stack.pop()?;
34,061✔
283
        match expr {
24,834✔
284
            Expr::Unary(u) => {
610✔
285
                self.stack.push(&u.operand);
610✔
286
            }
610✔
287
            Expr::Binary(b) => {
7,398✔
288
                self.stack.push(&b.left);
7,398✔
289
                self.stack.push(&b.right);
7,398✔
290
            }
7,398✔
291
            Expr::Cast(c) => self.stack.push(&c.operand),
148✔
292
            Expr::Value(_) => {}
9,583✔
293
            Expr::Var(_) => {}
6,951✔
294
            Expr::Stretch(_) => {}
118✔
295
            Expr::Index(i) => {
26✔
296
                self.stack.push(&i.index);
26✔
297
                self.stack.push(&i.target);
26✔
298
            }
26✔
NEW
299
            Expr::Range(r) => {
×
NEW
300
                self.stack.push(&r.stop);
×
NEW
301
                self.stack.push(&r.start);
×
NEW
302
                self.stack.push(&r.step);
×
NEW
303
            }
×
304
        }
305
        Some(expr.as_ref())
24,834✔
306
    }
34,061✔
307
}
308

309
/// A private iterator over the [Var] nodes contained within an [Expr].
310
struct VarIterator<'a>(ExprIterator<'a>);
311

312
impl<'a> Iterator for VarIterator<'a> {
313
    type Item = &'a Var;
314

315
    fn next(&mut self) -> Option<Self::Item> {
14,628✔
316
        for expr in self.0.by_ref() {
21,726✔
317
            if let ExprRef::Var(v) = expr {
21,726✔
318
                return Some(v);
6,205✔
319
            }
15,521✔
320
        }
321
        None
8,423✔
322
    }
14,628✔
323
}
324

325
/// A private iterator over the [Var] and [Stretch] nodes contained within an [Expr].
326
struct IdentIterator<'a>(ExprIterator<'a>);
327

328
impl<'a> Iterator for IdentIterator<'a> {
329
    type Item = IdentifierRef<'a>;
330

331
    fn next(&mut self) -> Option<Self::Item> {
4✔
332
        for expr in self.0.by_ref() {
7✔
333
            if let ExprRef::Var(v) = expr {
7✔
334
                return Some(IdentifierRef::Var(v));
1✔
335
            }
6✔
336
            if let ExprRef::Stretch(s) = expr {
6✔
337
                return Some(IdentifierRef::Stretch(s));
2✔
338
            }
4✔
339
        }
340
        None
1✔
341
    }
4✔
342
}
343

344
impl From<Unary> for Expr {
345
    fn from(value: Unary) -> Self {
2✔
346
        Expr::Unary(Box::new(value))
2✔
347
    }
2✔
348
}
349

350
impl From<Box<Unary>> for Expr {
351
    fn from(value: Box<Unary>) -> Self {
×
352
        Expr::Unary(value)
×
353
    }
×
354
}
355

356
impl From<Binary> for Expr {
357
    fn from(value: Binary) -> Self {
11✔
358
        Expr::Binary(Box::new(value))
11✔
359
    }
11✔
360
}
361

362
impl From<Box<Binary>> for Expr {
363
    fn from(value: Box<Binary>) -> Self {
×
364
        Expr::Binary(value)
×
365
    }
×
366
}
367

368
impl From<Cast> for Expr {
369
    fn from(value: Cast) -> Self {
×
370
        Expr::Cast(Box::new(value))
×
371
    }
×
372
}
373

374
impl From<Box<Cast>> for Expr {
375
    fn from(value: Box<Cast>) -> Self {
×
376
        Expr::Cast(value)
×
377
    }
×
378
}
379

380
impl From<Value> for Expr {
381
    fn from(value: Value) -> Self {
52✔
382
        Expr::Value(value)
52✔
383
    }
52✔
384
}
385

386
impl From<Var> for Expr {
387
    fn from(value: Var) -> Self {
7✔
388
        Expr::Var(value)
7✔
389
    }
7✔
390
}
391

392
impl From<Stretch> for Expr {
393
    fn from(value: Stretch) -> Self {
6✔
394
        Expr::Stretch(value)
6✔
395
    }
6✔
396
}
397

398
impl From<Index> for Expr {
399
    fn from(value: Index) -> Self {
×
400
        Expr::Index(Box::new(value))
×
401
    }
×
402
}
403

404
impl From<Box<Index>> for Expr {
405
    fn from(value: Box<Index>) -> Self {
×
406
        Expr::Index(value)
×
407
    }
×
408
}
409

410
impl From<Range> for Expr {
NEW
411
    fn from(value: Range) -> Self {
×
NEW
412
        Expr::Range(Box::new(value))
×
NEW
413
    }
×
414
}
415

416
impl From<Box<Range>> for Expr {
NEW
417
    fn from(value: Box<Range>) -> Self {
×
NEW
418
        Expr::Range(value)
×
NEW
419
    }
×
420
}
421

422
/// Root base class of all nodes in the expression tree.  The base case should never be
423
/// instantiated directly.
424
///
425
/// This must not be subclassed by users; subclasses form the internal data of the representation of
426
/// expressions, and it does not make sense to add more outside of Qiskit library code.
427
///
428
/// All subclasses are responsible for setting their ``type`` attribute in their ``__init__``, and
429
/// should not call the parent initializer."""
430
#[pyclass(
×
431
    eq,
×
432
    hash,
×
433
    subclass,
434
    frozen,
435
    name = "Expr",
436
    module = "qiskit._accelerate.circuit.classical.expr",
437
    skip_from_py_object
438
)]
×
439
#[derive(PartialEq, Clone, Copy, Debug, Hash)]
440
pub struct PyExpr(pub ExprKind); // ExprKind is used for fast extraction from Python
441

442
#[pymethods]
443
impl PyExpr {
444
    /// Call the relevant ``visit_*`` method on the given :class:`ExprVisitor`.  The usual entry
445
    /// point for a simple visitor is to construct it, and then call :meth:`accept` on the root
446
    /// object to be visited.  For example::
447
    ///
448
    ///     expr = ...
449
    ///     visitor = MyVisitor()
450
    ///     visitor.accept(expr)
451
    ///
452
    /// Subclasses of :class:`Expr` should override this to call the correct virtual method on the
453
    /// visitor.  This implements double dispatch with the visitor."""
454
    /// return visitor.visit_generic(self)
455
    fn accept<'py>(
×
456
        slf: PyRef<'py, Self>,
×
457
        visitor: &Bound<'py, PyAny>,
×
458
    ) -> PyResult<Bound<'py, PyAny>> {
×
459
        visitor.call_method1(intern!(visitor.py(), "visit_generic"), (slf,))
×
460
    }
×
461
}
462

463
/// The expression's kind, used internally during Python instance extraction to avoid
464
/// `isinstance` checks.
465
#[repr(u8)]
466
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
467
pub enum ExprKind {
468
    Unary,
469
    Binary,
470
    Value,
471
    Var,
472
    Cast,
473
    Stretch,
474
    Index,
475
    Range,
476
}
477

478
impl<'py> IntoPyObject<'py> for Expr {
479
    type Target = PyAny;
480
    type Output = Bound<'py, PyAny>;
481
    type Error = PyErr;
482

483
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
9,036✔
484
        match self {
9,036✔
485
            Expr::Unary(u) => u.into_bound_py_any(py),
192✔
486
            Expr::Binary(b) => b.into_bound_py_any(py),
2,308✔
487
            Expr::Cast(c) => c.into_bound_py_any(py),
106✔
488
            Expr::Value(v) => v.into_bound_py_any(py),
3,220✔
489
            Expr::Var(v) => v.into_bound_py_any(py),
2,880✔
490
            Expr::Stretch(s) => s.into_bound_py_any(py),
310✔
491
            Expr::Index(i) => i.into_bound_py_any(py),
20✔
NEW
492
            Expr::Range(r) => r.into_bound_py_any(py),
×
493
        }
494
    }
9,036✔
495
}
496

497
impl<'a, 'py> FromPyObject<'a, 'py> for Expr {
498
    type Error = PyErr;
499

500
    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
15,314✔
501
        let expr: PyRef<'_, PyExpr> = ob.cast()?.borrow();
15,314✔
502
        match expr.0 {
15,094✔
503
            ExprKind::Unary => Ok(Expr::Unary(Box::new(ob.extract()?))),
392✔
504
            ExprKind::Binary => Ok(Expr::Binary(Box::new(ob.extract()?))),
2,788✔
505
            ExprKind::Value => Ok(Expr::Value(ob.extract()?)),
5,558✔
506
            ExprKind::Var => Ok(Expr::Var(ob.extract()?)),
5,886✔
507
            ExprKind::Cast => Ok(Expr::Cast(Box::new(ob.extract()?))),
152✔
508
            ExprKind::Stretch => Ok(Expr::Stretch(ob.extract()?)),
290✔
509
            ExprKind::Index => Ok(Expr::Index(Box::new(ob.extract()?))),
28✔
NEW
510
            ExprKind::Range => Ok(Expr::Range(Box::new(ob.extract()?))),
×
511
        }
512
    }
15,314✔
513
}
514

515
#[cfg(test)]
516
mod tests {
517
    use crate::bit::{ClassicalRegister, ShareableClbit};
518
    use crate::classical::expr::{
519
        Binary, BinaryOp, Cast, Expr, ExprRef, ExprRefMut, IdentifierRef, Index, Stretch, Unary,
520
        UnaryOp, Value, Var,
521
    };
522
    use crate::classical::types::Type;
523
    use crate::duration::Duration;
524
    use num_bigint::BigUint;
525
    use pyo3::{PyErr, PyResult};
526
    use uuid::Uuid;
527

528
    #[test]
529
    fn test_vars() {
1✔
530
        let expr: Expr = Binary {
1✔
531
            op: BinaryOp::BitAnd,
1✔
532
            left: Unary {
1✔
533
                op: UnaryOp::BitNot,
1✔
534
                operand: Var::Standalone {
1✔
535
                    uuid: Uuid::new_v4().as_u128(),
1✔
536
                    name: "test".to_string(),
1✔
537
                    ty: Type::Bool,
1✔
538
                }
1✔
539
                .into(),
1✔
540
                ty: Type::Bool,
1✔
541
                constant: false,
1✔
542
            }
1✔
543
            .into(),
1✔
544
            right: Var::Bit {
1✔
545
                bit: ShareableClbit::new_anonymous(),
1✔
546
            }
1✔
547
            .into(),
1✔
548
            ty: Type::Bool,
1✔
549
            constant: false,
1✔
550
        }
1✔
551
        .into();
1✔
552

553
        let vars: Vec<&Var> = expr.vars().collect();
1✔
554
        assert!(matches!(
1✔
555
            vars.as_slice(),
1✔
556
            [Var::Bit { .. }, Var::Standalone { .. }]
1✔
557
        ));
558
    }
1✔
559

560
    #[test]
561
    fn test_identifiers() {
1✔
562
        let expr: Expr = Binary {
1✔
563
            op: BinaryOp::Mul,
1✔
564
            left: Binary {
1✔
565
                op: BinaryOp::Add,
1✔
566
                left: Var::Standalone {
1✔
567
                    uuid: Uuid::new_v4().as_u128(),
1✔
568
                    name: "test".to_string(),
1✔
569
                    ty: Type::Duration,
1✔
570
                }
1✔
571
                .into(),
1✔
572
                right: Stretch {
1✔
573
                    uuid: Uuid::new_v4().as_u128(),
1✔
574
                    name: "test".to_string(),
1✔
575
                }
1✔
576
                .into(),
1✔
577
                ty: Type::Duration,
1✔
578
                constant: false,
1✔
579
            }
1✔
580
            .into(),
1✔
581
            right: Binary {
1✔
582
                op: BinaryOp::Div,
1✔
583
                left: Stretch {
1✔
584
                    uuid: Uuid::new_v4().as_u128(),
1✔
585
                    name: "test".to_string(),
1✔
586
                }
1✔
587
                .into(),
1✔
588
                right: Value::Duration(Duration::dt(1000)).into(),
1✔
589
                ty: Type::Float,
1✔
590
                constant: true,
1✔
591
            }
1✔
592
            .into(),
1✔
593
            ty: Type::Bool,
1✔
594
            constant: false,
1✔
595
        }
1✔
596
        .into();
1✔
597

598
        let identifiers: Vec<IdentifierRef> = expr.identifiers().collect();
1✔
599
        assert!(matches!(
1✔
600
            identifiers.as_slice(),
1✔
601
            [
1✔
602
                IdentifierRef::Stretch(Stretch { .. }),
1✔
603
                IdentifierRef::Stretch(Stretch { .. }),
1✔
604
                IdentifierRef::Var(Var::Standalone { .. }),
1✔
605
            ]
1✔
606
        ));
607
    }
1✔
608

609
    #[test]
610
    fn test_iter() {
1✔
611
        let expr: Expr = Binary {
1✔
612
            op: BinaryOp::Mul,
1✔
613
            left: Binary {
1✔
614
                op: BinaryOp::Add,
1✔
615
                left: Var::Standalone {
1✔
616
                    uuid: Uuid::new_v4().as_u128(),
1✔
617
                    name: "test".to_string(),
1✔
618
                    ty: Type::Duration,
1✔
619
                }
1✔
620
                .into(),
1✔
621
                right: Stretch {
1✔
622
                    uuid: Uuid::new_v4().as_u128(),
1✔
623
                    name: "test".to_string(),
1✔
624
                }
1✔
625
                .into(),
1✔
626
                ty: Type::Duration,
1✔
627
                constant: false,
1✔
628
            }
1✔
629
            .into(),
1✔
630
            right: Binary {
1✔
631
                op: BinaryOp::Div,
1✔
632
                left: Stretch {
1✔
633
                    uuid: Uuid::new_v4().as_u128(),
1✔
634
                    name: "test".to_string(),
1✔
635
                }
1✔
636
                .into(),
1✔
637
                right: Value::Duration(Duration::dt(1000)).into(),
1✔
638
                ty: Type::Float,
1✔
639
                constant: true,
1✔
640
            }
1✔
641
            .into(),
1✔
642
            ty: Type::Bool,
1✔
643
            constant: false,
1✔
644
        }
1✔
645
        .into();
1✔
646

647
        let exprs: Vec<ExprRef> = expr.iter().collect();
1✔
648
        assert!(matches!(
1✔
649
            exprs.as_slice(),
1✔
650
            [
1✔
651
                ExprRef::Binary(Binary {
1✔
652
                    op: BinaryOp::Mul,
1✔
653
                    ..
1✔
654
                }),
1✔
655
                ExprRef::Binary(Binary {
1✔
656
                    op: BinaryOp::Div,
1✔
657
                    ..
1✔
658
                }),
1✔
659
                ExprRef::Value(Value::Duration(..)),
1✔
660
                ExprRef::Stretch(Stretch { .. }),
1✔
661
                ExprRef::Binary(Binary {
1✔
662
                    op: BinaryOp::Add,
1✔
663
                    ..
1✔
664
                }),
1✔
665
                ExprRef::Stretch(Stretch { .. }),
1✔
666
                ExprRef::Var(Var::Standalone { .. }),
1✔
667
            ]
1✔
668
        ));
669
    }
1✔
670

671
    #[test]
672
    fn test_visit_mut_ordering() -> PyResult<()> {
1✔
673
        let mut expr: Expr = Binary {
1✔
674
            op: BinaryOp::Mul,
1✔
675
            left: Binary {
1✔
676
                op: BinaryOp::Add,
1✔
677
                left: Var::Standalone {
1✔
678
                    uuid: Uuid::new_v4().as_u128(),
1✔
679
                    name: "test".to_string(),
1✔
680
                    ty: Type::Duration,
1✔
681
                }
1✔
682
                .into(),
1✔
683
                right: Stretch {
1✔
684
                    uuid: Uuid::new_v4().as_u128(),
1✔
685
                    name: "test".to_string(),
1✔
686
                }
1✔
687
                .into(),
1✔
688
                ty: Type::Duration,
1✔
689
                constant: false,
1✔
690
            }
1✔
691
            .into(),
1✔
692
            right: Binary {
1✔
693
                op: BinaryOp::Div,
1✔
694
                left: Stretch {
1✔
695
                    uuid: Uuid::new_v4().as_u128(),
1✔
696
                    name: "test".to_string(),
1✔
697
                }
1✔
698
                .into(),
1✔
699
                right: Value::Duration(Duration::dt(1000)).into(),
1✔
700
                ty: Type::Float,
1✔
701
                constant: true,
1✔
702
            }
1✔
703
            .into(),
1✔
704
            ty: Type::Bool,
1✔
705
            constant: false,
1✔
706
        }
1✔
707
        .into();
1✔
708

709
        // These get *consumed* by every visit, so by the end we expect this
710
        // iterator to be empty. The ordering here is post-order, LRN.
711
        let mut order = [
1✔
712
            |x: &ExprRefMut| matches!(x, ExprRefMut::Var(Var::Standalone { .. })),
1✔
713
            |x: &ExprRefMut| matches!(x, ExprRefMut::Stretch(Stretch { .. })),
1✔
714
            |x: &ExprRefMut| {
1✔
715
                matches!(
×
716
                    x,
1✔
717
                    ExprRefMut::Binary(Binary {
718
                        op: BinaryOp::Add,
719
                        ..
720
                    })
721
                )
722
            },
1✔
723
            |x: &ExprRefMut| matches!(x, ExprRefMut::Stretch(Stretch { .. })),
1✔
724
            |x: &ExprRefMut| matches!(x, ExprRefMut::Value(Value::Duration(..))),
1✔
725
            |x: &ExprRefMut| {
1✔
726
                matches!(
×
727
                    x,
1✔
728
                    ExprRefMut::Binary(Binary {
729
                        op: BinaryOp::Div,
730
                        ..
731
                    })
732
                )
733
            },
1✔
734
            |x: &ExprRefMut| {
1✔
735
                matches!(
×
736
                    x,
1✔
737
                    ExprRefMut::Binary(Binary {
738
                        op: BinaryOp::Mul,
739
                        ..
740
                    })
741
                )
742
            },
1✔
743
        ]
744
        .into_iter();
1✔
745

746
        expr.visit_mut(|x| {
7✔
747
            assert!(order.next().unwrap()(&x));
7✔
748
            Ok::<_, PyErr>(())
7✔
749
        })?;
7✔
750

751
        assert!(order.next().is_none());
1✔
752
        Ok(())
1✔
753
    }
1✔
754

755
    #[test]
756
    fn test_visit_mut() -> PyResult<()> {
1✔
757
        let mut expr: Expr = Binary {
1✔
758
            op: BinaryOp::BitAnd,
1✔
759
            left: Unary {
1✔
760
                op: UnaryOp::BitNot,
1✔
761
                operand: Var::Standalone {
1✔
762
                    uuid: Uuid::new_v4().as_u128(),
1✔
763
                    name: "test".to_string(),
1✔
764
                    ty: Type::Bool,
1✔
765
                }
1✔
766
                .into(),
1✔
767
                ty: Type::Bool,
1✔
768
                constant: false,
1✔
769
            }
1✔
770
            .into(),
1✔
771
            right: Value::Uint {
1✔
772
                raw: BigUint::from(1u8),
1✔
773
                ty: Type::Bool,
1✔
774
            }
1✔
775
            .into(),
1✔
776
            ty: Type::Bool,
1✔
777
            constant: false,
1✔
778
        }
1✔
779
        .into();
1✔
780

781
        expr.visit_mut(|x| match x {
1✔
782
            ExprRefMut::Var(Var::Standalone { name, .. }) => {
1✔
783
                *name = "updated".to_string();
1✔
784
                Ok::<_, PyErr>(())
1✔
785
            }
786
            _ => Ok(()),
3✔
787
        })?;
4✔
788

789
        let Var::Standalone { name, .. } = expr.vars().next().unwrap() else {
1✔
790
            panic!("wrong var type")
×
791
        };
792
        assert_eq!(name.as_str(), "updated");
1✔
793
        Ok(())
1✔
794
    }
1✔
795

796
    #[test]
797
    fn test_structurally_eq_to_self() -> PyResult<()> {
1✔
798
        let exprs = [
1✔
799
            Expr::Var(Var::Bit {
1✔
800
                bit: ShareableClbit::new_anonymous(),
1✔
801
            }),
1✔
802
            Expr::Var(Var::Register {
1✔
803
                register: ClassicalRegister::new_owning("a", 3),
1✔
804
                ty: Type::Uint(3),
1✔
805
            }),
1✔
806
            Expr::Value(Value::Uint {
1✔
807
                raw: BigUint::from(3u8),
1✔
808
                ty: Type::Uint(2),
1✔
809
            }),
1✔
810
            Expr::Cast(
1✔
811
                Cast {
1✔
812
                    operand: Expr::Var(Var::Register {
1✔
813
                        register: ClassicalRegister::new_owning("a", 3),
1✔
814
                        ty: Type::Uint(3),
1✔
815
                    }),
1✔
816
                    ty: Type::Bool,
1✔
817
                    constant: false,
1✔
818
                    implicit: false,
1✔
819
                }
1✔
820
                .into(),
1✔
821
            ),
1✔
822
            Expr::Unary(
1✔
823
                Unary {
1✔
824
                    op: UnaryOp::LogicNot,
1✔
825
                    operand: Expr::Var(Var::Bit {
1✔
826
                        bit: ShareableClbit::new_anonymous(),
1✔
827
                    }),
1✔
828
                    ty: Type::Bool,
1✔
829
                    constant: false,
1✔
830
                }
1✔
831
                .into(),
1✔
832
            ),
1✔
833
            Expr::Binary(
1✔
834
                Binary {
1✔
835
                    op: BinaryOp::BitAnd,
1✔
836
                    left: Expr::Value(Value::Uint {
1✔
837
                        raw: BigUint::from(5u8),
1✔
838
                        ty: Type::Uint(3),
1✔
839
                    }),
1✔
840
                    right: Expr::Var(Var::Register {
1✔
841
                        register: ClassicalRegister::new_owning("a", 3),
1✔
842
                        ty: Type::Uint(3),
1✔
843
                    }),
1✔
844
                    ty: Type::Uint(3),
1✔
845
                    constant: false,
1✔
846
                }
1✔
847
                .into(),
1✔
848
            ),
1✔
849
            Expr::Binary(
1✔
850
                Binary {
1✔
851
                    op: BinaryOp::LogicAnd,
1✔
852
                    left: Expr::Binary(
1✔
853
                        Binary {
1✔
854
                            op: BinaryOp::Less,
1✔
855
                            left: Expr::Value(Value::Uint {
1✔
856
                                raw: BigUint::from(2u8),
1✔
857
                                ty: Type::Uint(3),
1✔
858
                            }),
1✔
859
                            right: Expr::Var(Var::Register {
1✔
860
                                register: ClassicalRegister::new_owning("a", 3),
1✔
861
                                ty: Type::Uint(3),
1✔
862
                            }),
1✔
863
                            ty: Type::Bool,
1✔
864
                            constant: false,
1✔
865
                        }
1✔
866
                        .into(),
1✔
867
                    ),
1✔
868
                    right: Expr::Var(Var::Bit {
1✔
869
                        bit: ShareableClbit::new_anonymous(),
1✔
870
                    }),
1✔
871
                    ty: Type::Bool,
1✔
872
                    constant: false,
1✔
873
                }
1✔
874
                .into(),
1✔
875
            ),
1✔
876
            Expr::Binary(
1✔
877
                Binary {
1✔
878
                    op: BinaryOp::ShiftLeft,
1✔
879
                    left: Expr::Binary(
1✔
880
                        Binary {
1✔
881
                            op: BinaryOp::ShiftRight,
1✔
882
                            left: Expr::Value(Value::Uint {
1✔
883
                                raw: BigUint::from(255u8),
1✔
884
                                ty: Type::Uint(8),
1✔
885
                            }),
1✔
886
                            right: Expr::Value(Value::Uint {
1✔
887
                                raw: BigUint::from(3u8),
1✔
888
                                ty: Type::Uint(8),
1✔
889
                            }),
1✔
890
                            ty: Type::Uint(8),
1✔
891
                            constant: true,
1✔
892
                        }
1✔
893
                        .into(),
1✔
894
                    ),
1✔
895
                    right: Expr::Value(Value::Uint {
1✔
896
                        raw: BigUint::from(3u8),
1✔
897
                        ty: Type::Uint(8),
1✔
898
                    }),
1✔
899
                    ty: Type::Uint(8),
1✔
900
                    constant: true,
1✔
901
                }
1✔
902
                .into(),
1✔
903
            ),
1✔
904
            Expr::Index(
1✔
905
                Index {
1✔
906
                    target: Expr::Var(Var::Standalone {
1✔
907
                        uuid: Uuid::new_v4().as_u128(),
1✔
908
                        name: "a".to_string(),
1✔
909
                        ty: Type::Uint(8),
1✔
910
                    }),
1✔
911
                    index: Expr::Value(Value::Uint {
1✔
912
                        raw: BigUint::from(0u8),
1✔
913
                        ty: Type::Uint(8),
1✔
914
                    }),
1✔
915
                    ty: Type::Uint(1),
1✔
916
                    constant: false,
1✔
917
                }
1✔
918
                .into(),
1✔
919
            ),
1✔
920
            Expr::Binary(
1✔
921
                Binary {
1✔
922
                    op: BinaryOp::Greater,
1✔
923
                    left: Expr::Stretch(Stretch {
1✔
924
                        uuid: Uuid::new_v4().as_u128(),
1✔
925
                        name: "a".to_string(),
1✔
926
                    }),
1✔
927
                    right: Expr::Value(Value::Duration(Duration::dt(100))),
1✔
928
                    ty: Type::Bool,
1✔
929
                    constant: false,
1✔
930
                }
1✔
931
                .into(),
1✔
932
            ),
1✔
933
        ];
1✔
934

935
        for expr in exprs.iter() {
10✔
936
            assert!(expr.structurally_equivalent(expr));
10✔
937
        }
938

939
        Ok(())
1✔
940
    }
1✔
941

942
    #[test]
943
    fn test_structurally_eq_does_not_compare_symmetrically() {
1✔
944
        let all_ops = [
1✔
945
            BinaryOp::BitAnd,
1✔
946
            BinaryOp::BitOr,
1✔
947
            BinaryOp::BitXor,
1✔
948
            BinaryOp::LogicAnd,
1✔
949
            BinaryOp::LogicOr,
1✔
950
            BinaryOp::Equal,
1✔
951
            BinaryOp::NotEqual,
1✔
952
            BinaryOp::Less,
1✔
953
            BinaryOp::LessEqual,
1✔
954
            BinaryOp::Greater,
1✔
955
            BinaryOp::GreaterEqual,
1✔
956
            BinaryOp::ShiftLeft,
1✔
957
            BinaryOp::ShiftRight,
1✔
958
            BinaryOp::Add,
1✔
959
            BinaryOp::Sub,
1✔
960
            BinaryOp::Mul,
1✔
961
            BinaryOp::Div,
1✔
962
        ];
1✔
963

964
        for op in all_ops.iter().copied() {
17✔
965
            let (left, right, out_ty) = match op {
17✔
966
                BinaryOp::LogicAnd | BinaryOp::LogicOr => (
2✔
967
                    Expr::Value(Value::Uint {
2✔
968
                        raw: BigUint::from(1u8),
2✔
969
                        ty: Type::Bool,
2✔
970
                    }),
2✔
971
                    Expr::Var(Var::Bit {
2✔
972
                        bit: ShareableClbit::new_anonymous(),
2✔
973
                    }),
2✔
974
                    Type::Bool,
2✔
975
                ),
2✔
976
                _ => (
977
                    Expr::Value(Value::Uint {
15✔
978
                        raw: BigUint::from(5u8),
15✔
979
                        ty: Type::Uint(3),
15✔
980
                    }),
15✔
981
                    Expr::Var(Var::Register {
15✔
982
                        register: ClassicalRegister::new_owning("a", 3),
15✔
983
                        ty: Type::Uint(3),
15✔
984
                    }),
15✔
985
                    match op {
15✔
986
                        BinaryOp::BitAnd | BinaryOp::BitOr | BinaryOp::BitXor => Type::Uint(3),
3✔
987
                        _ => Type::Bool,
12✔
988
                    },
989
                ),
990
            };
991

992
            let cis = Expr::Binary(
17✔
993
                Binary {
17✔
994
                    op,
17✔
995
                    left: left.clone(),
17✔
996
                    right: right.clone(),
17✔
997
                    ty: out_ty,
17✔
998
                    constant: false,
17✔
999
                }
17✔
1000
                .into(),
17✔
1001
            );
17✔
1002

1003
            let trans = Expr::Binary(
17✔
1004
                Binary {
17✔
1005
                    op,
17✔
1006
                    left: right,
17✔
1007
                    right: left,
17✔
1008
                    ty: out_ty,
17✔
1009
                    constant: false,
17✔
1010
                }
17✔
1011
                .into(),
17✔
1012
            );
17✔
1013

1014
            assert!(
17✔
1015
                !cis.structurally_equivalent(&trans),
17✔
1016
                "Expected {op:?} to not be structurally equivalent to its flipped form"
1017
            );
1018
            assert!(
17✔
1019
                !trans.structurally_equivalent(&cis),
17✔
1020
                "Expected flipped {op:?} to not be structurally equivalent to original form"
1021
            );
1022
        }
1023
    }
1✔
1024

1025
    #[test]
1026
    fn test_structurally_eq_key_function_both() -> PyResult<()> {
1✔
1027
        let left_clbit = ShareableClbit::new_anonymous();
1✔
1028
        let left_cr = ClassicalRegister::new_owning("a", 3);
1✔
1029
        let right_clbit = ShareableClbit::new_anonymous();
1✔
1030
        let right_cr = ClassicalRegister::new_owning("b", 3);
1✔
1031
        assert_ne!(left_clbit, right_clbit);
1✔
1032
        assert_ne!(left_cr, right_cr);
1✔
1033

1034
        let left = Expr::Unary(
1✔
1035
            Unary {
1✔
1036
                op: UnaryOp::LogicNot,
1✔
1037
                operand: Expr::Binary(
1✔
1038
                    Binary {
1✔
1039
                        op: BinaryOp::LogicAnd,
1✔
1040
                        left: Expr::Binary(
1✔
1041
                            Binary {
1✔
1042
                                op: BinaryOp::Less,
1✔
1043
                                left: Expr::Value(Value::Uint {
1✔
1044
                                    raw: BigUint::from(5u8),
1✔
1045
                                    ty: Type::Uint(3),
1✔
1046
                                }),
1✔
1047
                                right: Expr::Var(Var::Register {
1✔
1048
                                    register: left_cr.clone(),
1✔
1049
                                    ty: Type::Uint(3),
1✔
1050
                                }),
1✔
1051
                                ty: Type::Bool,
1✔
1052
                                constant: false,
1✔
1053
                            }
1✔
1054
                            .into(),
1✔
1055
                        ),
1✔
1056
                        right: Expr::Var(Var::Bit {
1✔
1057
                            bit: left_clbit.clone(),
1✔
1058
                        }),
1✔
1059
                        ty: Type::Bool,
1✔
1060
                        constant: false,
1✔
1061
                    }
1✔
1062
                    .into(),
1✔
1063
                ),
1✔
1064
                ty: Type::Bool,
1✔
1065
                constant: false,
1✔
1066
            }
1✔
1067
            .into(),
1✔
1068
        );
1✔
1069

1070
        let right = Expr::Unary(
1✔
1071
            Unary {
1✔
1072
                op: UnaryOp::LogicNot,
1✔
1073
                operand: Expr::Binary(
1✔
1074
                    Binary {
1✔
1075
                        op: BinaryOp::LogicAnd,
1✔
1076
                        left: Expr::Binary(
1✔
1077
                            Binary {
1✔
1078
                                op: BinaryOp::Less,
1✔
1079
                                left: Expr::Value(Value::Uint {
1✔
1080
                                    raw: BigUint::from(5u8),
1✔
1081
                                    ty: Type::Uint(3),
1✔
1082
                                }),
1✔
1083
                                right: Expr::Var(Var::Register {
1✔
1084
                                    register: right_cr.clone(),
1✔
1085
                                    ty: Type::Uint(3),
1✔
1086
                                }),
1✔
1087
                                ty: Type::Bool,
1✔
1088
                                constant: false,
1✔
1089
                            }
1✔
1090
                            .into(),
1✔
1091
                        ),
1✔
1092
                        right: Expr::Var(Var::Bit {
1✔
1093
                            bit: right_clbit.clone(),
1✔
1094
                        }),
1✔
1095
                        ty: Type::Bool,
1✔
1096
                        constant: false,
1✔
1097
                    }
1✔
1098
                    .into(),
1✔
1099
                ),
1✔
1100
                ty: Type::Bool,
1✔
1101
                constant: false,
1✔
1102
            }
1✔
1103
            .into(),
1✔
1104
        );
1✔
1105

1106
        assert!(
1✔
1107
            !left.structurally_equivalent(&right),
1✔
1108
            "Expressions using different registers/clbits should not be structurally equivalent"
1109
        );
1110

1111
        // We're happy as long as the variables are of the same kind.
1112
        let key_func = |v: &Var| -> &str {
4✔
1113
            match v {
4✔
1114
                Var::Standalone { .. } => "standalone",
×
1115
                Var::Bit { .. } => "bit",
2✔
1116
                Var::Register { .. } => "register",
2✔
1117
            }
1118
        };
4✔
1119

1120
        assert!(
1✔
1121
            left.structurally_equivalent_by_key(key_func, &right, key_func),
1✔
1122
            "Expressions that only care that their vars are of the same kind should be equivalent"
1123
        );
1124

1125
        Ok(())
1✔
1126
    }
1✔
1127
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc