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

Qiskit / qiskit / 22353739383

24 Feb 2026 01:50PM UTC coverage: 87.831% (-0.04%) from 87.867%
22353739383

push

github

web-flow
Upgrade to PyO3 0.28 (#15670)

The major change for us is that `pyclass` on `Clone` types no longer
automatically derives a cloning implementation of `FromPyObject`.  We
now have to opt-in to having it with an extra derive.  Here I've tried
to avoid opting in, except in cases where the underlying `struct` is
small and `Copy`; this turned up a few places where we've been lazy with
/ missed `extract` calls that could just be `cast`s.  I left one place
(in `ConsolidateBlocks`) where we _shouldn't_ need to clone out of
Python space, but it would be rather involved to refactor the logic to
make this work better, since we're unfortunately using the
Python-exposed function as the main driver logic, rather than the
Rust-native help function that lives next to it.

On the `rust-numpy` side, a change intended to fix a bug in implicit
dimension handling in `PyArrayLike` had a knock-on effect of making
allocating type conversions fail.  Every current usage within Qiskit
_wants_ the automatic conversion by nature of using `PyArrayLike` as an
extraction point and not a cast, so we have to change our usage sites to
allow the conversion now.

117 of 188 new or added lines in 36 files covered. (62.23%)

26 existing lines in 9 files now uncovered.

100279 of 114173 relevant lines covered (87.83%)

1150510.32 hits per line

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

91.93
/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 crate::classical::expr::{Binary, Cast, Index, Stretch, Unary, Value, Var};
14
use crate::classical::types::Type;
15
use pyo3::prelude::*;
16
use pyo3::{IntoPyObjectExt, intern};
17

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

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

46
#[derive(Debug, PartialEq)]
47
pub enum ExprRefMut<'a> {
48
    Unary(&'a mut Unary),
49
    Binary(&'a mut Binary),
50
    Cast(&'a mut Cast),
51
    Value(&'a mut Value),
52
    Var(&'a mut Var),
53
    Stretch(&'a mut Stretch),
54
    Index(&'a mut Index),
55
}
56

57
#[derive(Clone, Debug, PartialEq)]
58
pub enum IdentifierRef<'a> {
59
    Var(&'a Var),
60
    Stretch(&'a Stretch),
61
}
62

63
impl Expr {
64
    /// Converts from `&Expr` to `ExprRef`.
65
    pub fn as_ref(&self) -> ExprRef<'_> {
23,706✔
66
        match self {
23,706✔
67
            Expr::Unary(u) => ExprRef::Unary(u.as_ref()),
522✔
68
            Expr::Binary(b) => ExprRef::Binary(b.as_ref()),
7,078✔
69
            Expr::Cast(c) => ExprRef::Cast(c.as_ref()),
132✔
70
            Expr::Value(v) => ExprRef::Value(v),
9,247✔
71
            Expr::Var(v) => ExprRef::Var(v),
6,663✔
72
            Expr::Stretch(s) => ExprRef::Stretch(s),
38✔
73
            Expr::Index(i) => ExprRef::Index(i.as_ref()),
26✔
74
        }
75
    }
23,706✔
76

77
    /// Converts from `&mut Expr` to `ExprRefMut`.
78
    pub fn as_mut(&mut self) -> ExprRefMut<'_> {
69✔
79
        match self {
69✔
80
            Expr::Unary(u) => ExprRefMut::Unary(u.as_mut()),
7✔
81
            Expr::Binary(b) => ExprRefMut::Binary(b.as_mut()),
16✔
82
            Expr::Cast(c) => ExprRefMut::Cast(c.as_mut()),
4✔
83
            Expr::Value(v) => ExprRefMut::Value(v),
10✔
84
            Expr::Var(v) => ExprRefMut::Var(v),
30✔
85
            Expr::Stretch(s) => ExprRefMut::Stretch(s),
2✔
86
            Expr::Index(i) => ExprRefMut::Index(i.as_mut()),
×
87
        }
88
    }
69✔
89

90
    /// The const-ness of the expression.
91
    pub fn is_const(&self) -> bool {
5,188✔
92
        match self {
5,188✔
93
            Expr::Unary(u) => u.constant,
38✔
94
            Expr::Binary(b) => b.constant,
866✔
95
            Expr::Cast(c) => c.constant,
104✔
96
            Expr::Value(_) => true,
2,106✔
97
            Expr::Var(_) => false,
1,968✔
98
            Expr::Stretch(_) => true,
100✔
99
            Expr::Index(i) => i.constant,
6✔
100
        }
101
    }
5,188✔
102

103
    /// The expression's [Type].
104
    pub fn ty(&self) -> Type {
×
105
        match self {
×
106
            Expr::Unary(u) => u.ty,
×
107
            Expr::Binary(b) => b.ty,
×
108
            Expr::Cast(c) => c.ty,
×
109
            Expr::Value(v) => match v {
×
110
                Value::Duration(_) => Type::Duration,
×
111
                Value::Float { ty, .. } => *ty,
×
112
                Value::Uint { ty, .. } => *ty,
×
113
            },
114
            Expr::Var(v) => match v {
×
115
                Var::Standalone { ty, .. } => *ty,
×
116
                Var::Bit { .. } => Type::Bool,
×
117
                Var::Register { ty, .. } => *ty,
×
118
            },
119
            Expr::Stretch(_) => Type::Duration,
×
120
            Expr::Index(i) => i.ty,
×
121
        }
122
    }
×
123

124
    /// Returns an iterator over the identifier nodes in this expression in some
125
    /// deterministic order.
126
    pub fn identifiers(&self) -> impl Iterator<Item = IdentifierRef<'_>> {
1✔
127
        IdentIterator(ExprIterator { stack: vec![self] })
1✔
128
    }
1✔
129

130
    /// Returns an iterator over the [Var] nodes in this expression in some
131
    /// deterministic order.
132
    pub fn vars(&self) -> impl Iterator<Item = &Var> {
8,256✔
133
        VarIterator(ExprIterator { stack: vec![self] })
8,256✔
134
    }
8,256✔
135

136
    /// Returns an iterator over all nodes in this expression in some deterministic
137
    /// order.
138
    pub fn iter(&self) -> impl Iterator<Item = ExprRef<'_>> {
657✔
139
        ExprIterator { stack: vec![self] }
657✔
140
    }
657✔
141

142
    /// Visits all nodes by mutable reference, in a post-order traversal.
143
    pub fn visit_mut<F>(&mut self, mut visitor: F) -> PyResult<()>
26✔
144
    where
26✔
145
        F: FnMut(ExprRefMut) -> PyResult<()>,
26✔
146
    {
147
        self.visit_mut_impl(&mut visitor)
26✔
148
    }
26✔
149

150
    fn visit_mut_impl<F>(&mut self, visitor: &mut F) -> PyResult<()>
69✔
151
    where
69✔
152
        F: FnMut(ExprRefMut) -> PyResult<()>,
69✔
153
    {
154
        match self {
69✔
155
            Expr::Unary(u) => u.operand.visit_mut_impl(visitor)?,
7✔
156
            Expr::Binary(b) => {
16✔
157
                b.left.visit_mut_impl(visitor)?;
16✔
158
                b.right.visit_mut_impl(visitor)?;
16✔
159
            }
160
            Expr::Cast(c) => c.operand.visit_mut_impl(visitor)?,
4✔
161
            Expr::Value(_) => {}
10✔
162
            Expr::Var(_) => {}
30✔
163
            Expr::Stretch(_) => {}
2✔
164
            Expr::Index(i) => {
×
165
                i.target.visit_mut_impl(visitor)?;
×
166
                i.index.visit_mut_impl(visitor)?;
×
167
            }
168
        }
169
        visitor(self.as_mut())
69✔
170
    }
69✔
171

172
    /// Do these two expressions have exactly the same tree structure?
173
    pub fn structurally_equivalent(&self, other: &Expr) -> bool {
45✔
174
        let identity_key = |v: &Var| v.clone();
45✔
175
        self.structurally_equivalent_by_key(identity_key, other, identity_key)
45✔
176
    }
45✔
177

178
    /// Do these two expressions have exactly the same tree structure, up to some key function for
179
    /// [Var] nodes?
180
    ///
181
    /// In other words, are these two expressions the exact same trees, except we compare the
182
    /// [Var] nodes by calling the appropriate `*_var_key` function on them, and comparing
183
    /// that output for equality.  This function does not allow any semantic "equivalences" such as
184
    /// asserting that `a == b` is equivalent to `b == a`; the evaluation order of the operands
185
    /// could, in general, cause such a statement to be false.
186
    pub fn structurally_equivalent_by_key<F1, F2, K>(
328✔
187
        &self,
328✔
188
        mut self_var_key: F1,
328✔
189
        other: &Expr,
328✔
190
        mut other_var_key: F2,
328✔
191
    ) -> bool
328✔
192
    where
328✔
193
        F1: FnMut(&Var) -> K,
328✔
194
        F2: FnMut(&Var) -> K,
328✔
195
        K: PartialEq,
328✔
196
    {
197
        let mut self_nodes = self.iter();
328✔
198
        let mut other_nodes = other.iter();
328✔
199
        loop {
200
            match (self_nodes.next(), other_nodes.next()) {
1,486✔
201
                (Some(a), Some(b)) => match (a, b) {
1,193✔
202
                    (ExprRef::Unary(a), ExprRef::Unary(b)) => {
59✔
203
                        if a.op != b.op || a.ty != b.ty || a.constant != b.constant {
59✔
204
                            return false;
×
205
                        }
59✔
206
                    }
207
                    (ExprRef::Binary(a), ExprRef::Binary(b)) => {
403✔
208
                        if a.op != b.op || a.ty != b.ty || a.constant != b.constant {
403✔
209
                            return false;
×
210
                        }
403✔
211
                    }
212
                    (ExprRef::Cast(a), ExprRef::Cast(b)) => {
25✔
213
                        if a.ty != b.ty || a.constant != b.constant {
25✔
214
                            return false;
×
215
                        }
25✔
216
                    }
217
                    (ExprRef::Value(a), ExprRef::Value(b)) => {
363✔
218
                        if a != b {
363✔
219
                            return false;
×
220
                        }
363✔
221
                    }
222
                    (ExprRef::Var(a), ExprRef::Var(b)) => {
287✔
223
                        if a.ty() != b.ty() {
287✔
224
                            return false;
×
225
                        }
287✔
226
                        if self_var_key(a) != other_var_key(b) {
287✔
227
                            return false;
1✔
228
                        }
286✔
229
                    }
230
                    (ExprRef::Stretch(a), ExprRef::Stretch(b)) => {
17✔
231
                        if a.uuid != b.uuid {
17✔
232
                            return false;
×
233
                        }
17✔
234
                    }
235
                    (ExprRef::Index(a), ExprRef::Index(b)) => {
5✔
236
                        if a.ty != b.ty || a.constant != b.constant {
5✔
237
                            return false;
×
238
                        }
5✔
239
                    }
240
                    _ => return false,
34✔
241
                },
242
                (None, None) => return true,
293✔
243
                _ => return false,
×
244
            }
245
        }
246
    }
328✔
247
}
248

249
/// A private iterator over the [Expr] nodes of an expression
250
/// by reference.
251
///
252
/// The first node reference returned is the [Expr] itself.
253
struct ExprIterator<'a> {
254
    stack: Vec<&'a Expr>,
255
}
256

257
impl<'a> Iterator for ExprIterator<'a> {
258
    type Item = ExprRef<'a>;
259

260
    fn next(&mut self) -> Option<Self::Item> {
32,549✔
261
        let expr = self.stack.pop()?;
32,549✔
262
        match expr {
23,706✔
263
            Expr::Unary(u) => {
522✔
264
                self.stack.push(&u.operand);
522✔
265
            }
522✔
266
            Expr::Binary(b) => {
7,078✔
267
                self.stack.push(&b.left);
7,078✔
268
                self.stack.push(&b.right);
7,078✔
269
            }
7,078✔
270
            Expr::Cast(c) => self.stack.push(&c.operand),
132✔
271
            Expr::Value(_) => {}
9,247✔
272
            Expr::Var(_) => {}
6,663✔
273
            Expr::Stretch(_) => {}
38✔
274
            Expr::Index(i) => {
26✔
275
                self.stack.push(&i.index);
26✔
276
                self.stack.push(&i.target);
26✔
277
            }
26✔
278
        }
279
        Some(expr.as_ref())
23,706✔
280
    }
32,549✔
281
}
282

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

286
impl<'a> Iterator for VarIterator<'a> {
287
    type Item = &'a Var;
288

289
    fn next(&mut self) -> Option<Self::Item> {
14,308✔
290
        for expr in self.0.by_ref() {
21,306✔
291
            if let ExprRef::Var(v) = expr {
21,306✔
292
                return Some(v);
6,053✔
293
            }
15,253✔
294
        }
295
        None
8,255✔
296
    }
14,308✔
297
}
298

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

302
impl<'a> Iterator for IdentIterator<'a> {
303
    type Item = IdentifierRef<'a>;
304

305
    fn next(&mut self) -> Option<Self::Item> {
4✔
306
        for expr in self.0.by_ref() {
7✔
307
            if let ExprRef::Var(v) = expr {
7✔
308
                return Some(IdentifierRef::Var(v));
1✔
309
            }
6✔
310
            if let ExprRef::Stretch(s) = expr {
6✔
311
                return Some(IdentifierRef::Stretch(s));
2✔
312
            }
4✔
313
        }
314
        None
1✔
315
    }
4✔
316
}
317

318
impl From<Unary> for Expr {
319
    fn from(value: Unary) -> Self {
2✔
320
        Expr::Unary(Box::new(value))
2✔
321
    }
2✔
322
}
323

324
impl From<Box<Unary>> for Expr {
325
    fn from(value: Box<Unary>) -> Self {
×
326
        Expr::Unary(value)
×
327
    }
×
328
}
329

330
impl From<Binary> for Expr {
331
    fn from(value: Binary) -> Self {
11✔
332
        Expr::Binary(Box::new(value))
11✔
333
    }
11✔
334
}
335

336
impl From<Box<Binary>> for Expr {
337
    fn from(value: Box<Binary>) -> Self {
×
338
        Expr::Binary(value)
×
339
    }
×
340
}
341

342
impl From<Cast> for Expr {
343
    fn from(value: Cast) -> Self {
×
344
        Expr::Cast(Box::new(value))
×
345
    }
×
346
}
347

348
impl From<Box<Cast>> for Expr {
349
    fn from(value: Box<Cast>) -> Self {
×
350
        Expr::Cast(value)
×
351
    }
×
352
}
353

354
impl From<Value> for Expr {
355
    fn from(value: Value) -> Self {
4✔
356
        Expr::Value(value)
4✔
357
    }
4✔
358
}
359

360
impl From<Var> for Expr {
361
    fn from(value: Var) -> Self {
6✔
362
        Expr::Var(value)
6✔
363
    }
6✔
364
}
365

366
impl From<Stretch> for Expr {
367
    fn from(value: Stretch) -> Self {
6✔
368
        Expr::Stretch(value)
6✔
369
    }
6✔
370
}
371

372
impl From<Index> for Expr {
373
    fn from(value: Index) -> Self {
×
374
        Expr::Index(Box::new(value))
×
375
    }
×
376
}
377

378
impl From<Box<Index>> for Expr {
379
    fn from(value: Box<Index>) -> Self {
×
380
        Expr::Index(value)
×
381
    }
×
382
}
383

384
/// Root base class of all nodes in the expression tree.  The base case should never be
385
/// instantiated directly.
386
///
387
/// This must not be subclassed by users; subclasses form the internal data of the representation of
388
/// expressions, and it does not make sense to add more outside of Qiskit library code.
389
///
390
/// All subclasses are responsible for setting their ``type`` attribute in their ``__init__``, and
391
/// should not call the parent initializer."""
392
#[pyclass(
×
393
    eq,
×
394
    hash,
×
395
    subclass,
396
    frozen,
397
    name = "Expr",
398
    module = "qiskit._accelerate.circuit.classical.expr",
399
    skip_from_py_object
UNCOV
400
)]
×
401
#[derive(PartialEq, Clone, Copy, Debug, Hash)]
402
pub struct PyExpr(pub ExprKind); // ExprKind is used for fast extraction from Python
403

404
#[pymethods]
405
impl PyExpr {
406
    /// Call the relevant ``visit_*`` method on the given :class:`ExprVisitor`.  The usual entry
407
    /// point for a simple visitor is to construct it, and then call :meth:`accept` on the root
408
    /// object to be visited.  For example::
409
    ///
410
    ///     expr = ...
411
    ///     visitor = MyVisitor()
412
    ///     visitor.accept(expr)
413
    ///
414
    /// Subclasses of :class:`Expr` should override this to call the correct virtual method on the
415
    /// visitor.  This implements double dispatch with the visitor."""
416
    /// return visitor.visit_generic(self)
417
    fn accept<'py>(
×
418
        slf: PyRef<'py, Self>,
×
419
        visitor: &Bound<'py, PyAny>,
×
420
    ) -> PyResult<Bound<'py, PyAny>> {
×
421
        visitor.call_method1(intern!(visitor.py(), "visit_generic"), (slf,))
×
422
    }
×
423
}
424

425
/// The expression's kind, used internally during Python instance extraction to avoid
426
/// `isinstance` checks.
427
#[repr(u8)]
428
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
429
pub enum ExprKind {
430
    Unary,
431
    Binary,
432
    Value,
433
    Var,
434
    Cast,
435
    Stretch,
436
    Index,
437
}
438

439
impl<'py> IntoPyObject<'py> for Expr {
440
    type Target = PyAny;
441
    type Output = Bound<'py, PyAny>;
442
    type Error = PyErr;
443

444
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
7,900✔
445
        match self {
7,900✔
446
            Expr::Unary(u) => u.into_bound_py_any(py),
194✔
447
            Expr::Binary(b) => b.into_bound_py_any(py),
2,228✔
448
            Expr::Cast(c) => c.into_bound_py_any(py),
18✔
449
            Expr::Value(v) => v.into_bound_py_any(py),
2,596✔
450
            Expr::Var(v) => v.into_bound_py_any(py),
2,694✔
451
            Expr::Stretch(s) => s.into_bound_py_any(py),
150✔
452
            Expr::Index(i) => i.into_bound_py_any(py),
20✔
453
        }
454
    }
7,900✔
455
}
456

457
impl<'a, 'py> FromPyObject<'a, 'py> for Expr {
458
    type Error = PyErr;
459

460
    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
14,170✔
461
        let expr: PyRef<'_, PyExpr> = ob.cast()?.borrow();
14,170✔
462
        match expr.0 {
13,964✔
463
            ExprKind::Unary => Ok(Expr::Unary(Box::new(ob.extract()?))),
360✔
464
            ExprKind::Binary => Ok(Expr::Binary(Box::new(ob.extract()?))),
2,560✔
465
            ExprKind::Value => Ok(Expr::Value(ob.extract()?)),
5,002✔
466
            ExprKind::Var => Ok(Expr::Var(ob.extract()?)),
5,710✔
467
            ExprKind::Cast => Ok(Expr::Cast(Box::new(ob.extract()?))),
148✔
468
            ExprKind::Stretch => Ok(Expr::Stretch(ob.extract()?)),
156✔
469
            ExprKind::Index => Ok(Expr::Index(Box::new(ob.extract()?))),
28✔
470
        }
471
    }
14,170✔
472
}
473

474
#[cfg(test)]
475
mod tests {
476
    use crate::bit::{ClassicalRegister, ShareableClbit};
477
    use crate::classical::expr::{
478
        Binary, BinaryOp, Cast, Expr, ExprRef, ExprRefMut, IdentifierRef, Index, Stretch, Unary,
479
        UnaryOp, Value, Var,
480
    };
481
    use crate::classical::types::Type;
482
    use crate::duration::Duration;
483
    use num_bigint::BigUint;
484
    use pyo3::PyResult;
485
    use uuid::Uuid;
486

487
    #[test]
488
    fn test_vars() {
1✔
489
        let expr: Expr = Binary {
1✔
490
            op: BinaryOp::BitAnd,
1✔
491
            left: Unary {
1✔
492
                op: UnaryOp::BitNot,
1✔
493
                operand: Var::Standalone {
1✔
494
                    uuid: Uuid::new_v4().as_u128(),
1✔
495
                    name: "test".to_string(),
1✔
496
                    ty: Type::Bool,
1✔
497
                }
1✔
498
                .into(),
1✔
499
                ty: Type::Bool,
1✔
500
                constant: false,
1✔
501
            }
1✔
502
            .into(),
1✔
503
            right: Var::Bit {
1✔
504
                bit: ShareableClbit::new_anonymous(),
1✔
505
            }
1✔
506
            .into(),
1✔
507
            ty: Type::Bool,
1✔
508
            constant: false,
1✔
509
        }
1✔
510
        .into();
1✔
511

512
        let vars: Vec<&Var> = expr.vars().collect();
1✔
513
        assert!(matches!(
1✔
514
            vars.as_slice(),
1✔
515
            [Var::Bit { .. }, Var::Standalone { .. }]
1✔
516
        ));
517
    }
1✔
518

519
    #[test]
520
    fn test_identifiers() {
1✔
521
        let expr: Expr = Binary {
1✔
522
            op: BinaryOp::Mul,
1✔
523
            left: Binary {
1✔
524
                op: BinaryOp::Add,
1✔
525
                left: Var::Standalone {
1✔
526
                    uuid: Uuid::new_v4().as_u128(),
1✔
527
                    name: "test".to_string(),
1✔
528
                    ty: Type::Duration,
1✔
529
                }
1✔
530
                .into(),
1✔
531
                right: Stretch {
1✔
532
                    uuid: Uuid::new_v4().as_u128(),
1✔
533
                    name: "test".to_string(),
1✔
534
                }
1✔
535
                .into(),
1✔
536
                ty: Type::Duration,
1✔
537
                constant: false,
1✔
538
            }
1✔
539
            .into(),
1✔
540
            right: Binary {
1✔
541
                op: BinaryOp::Div,
1✔
542
                left: Stretch {
1✔
543
                    uuid: Uuid::new_v4().as_u128(),
1✔
544
                    name: "test".to_string(),
1✔
545
                }
1✔
546
                .into(),
1✔
547
                right: Value::Duration(Duration::dt(1000)).into(),
1✔
548
                ty: Type::Float,
1✔
549
                constant: true,
1✔
550
            }
1✔
551
            .into(),
1✔
552
            ty: Type::Bool,
1✔
553
            constant: false,
1✔
554
        }
1✔
555
        .into();
1✔
556

557
        let identifiers: Vec<IdentifierRef> = expr.identifiers().collect();
1✔
558
        assert!(matches!(
1✔
559
            identifiers.as_slice(),
1✔
560
            [
1✔
561
                IdentifierRef::Stretch(Stretch { .. }),
1✔
562
                IdentifierRef::Stretch(Stretch { .. }),
1✔
563
                IdentifierRef::Var(Var::Standalone { .. }),
1✔
564
            ]
1✔
565
        ));
566
    }
1✔
567

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

606
        let exprs: Vec<ExprRef> = expr.iter().collect();
1✔
607
        assert!(matches!(
1✔
608
            exprs.as_slice(),
1✔
609
            [
1✔
610
                ExprRef::Binary(Binary {
1✔
611
                    op: BinaryOp::Mul,
1✔
612
                    ..
1✔
613
                }),
1✔
614
                ExprRef::Binary(Binary {
1✔
615
                    op: BinaryOp::Div,
1✔
616
                    ..
1✔
617
                }),
1✔
618
                ExprRef::Value(Value::Duration(..)),
1✔
619
                ExprRef::Stretch(Stretch { .. }),
1✔
620
                ExprRef::Binary(Binary {
1✔
621
                    op: BinaryOp::Add,
1✔
622
                    ..
1✔
623
                }),
1✔
624
                ExprRef::Stretch(Stretch { .. }),
1✔
625
                ExprRef::Var(Var::Standalone { .. }),
1✔
626
            ]
1✔
627
        ));
628
    }
1✔
629

630
    #[test]
631
    fn test_visit_mut_ordering() -> PyResult<()> {
1✔
632
        let mut expr: Expr = Binary {
1✔
633
            op: BinaryOp::Mul,
1✔
634
            left: Binary {
1✔
635
                op: BinaryOp::Add,
1✔
636
                left: Var::Standalone {
1✔
637
                    uuid: Uuid::new_v4().as_u128(),
1✔
638
                    name: "test".to_string(),
1✔
639
                    ty: Type::Duration,
1✔
640
                }
1✔
641
                .into(),
1✔
642
                right: Stretch {
1✔
643
                    uuid: Uuid::new_v4().as_u128(),
1✔
644
                    name: "test".to_string(),
1✔
645
                }
1✔
646
                .into(),
1✔
647
                ty: Type::Duration,
1✔
648
                constant: false,
1✔
649
            }
1✔
650
            .into(),
1✔
651
            right: Binary {
1✔
652
                op: BinaryOp::Div,
1✔
653
                left: Stretch {
1✔
654
                    uuid: Uuid::new_v4().as_u128(),
1✔
655
                    name: "test".to_string(),
1✔
656
                }
1✔
657
                .into(),
1✔
658
                right: Value::Duration(Duration::dt(1000)).into(),
1✔
659
                ty: Type::Float,
1✔
660
                constant: true,
1✔
661
            }
1✔
662
            .into(),
1✔
663
            ty: Type::Bool,
1✔
664
            constant: false,
1✔
665
        }
1✔
666
        .into();
1✔
667

668
        // These get *consumed* by every visit, so by the end we expect this
669
        // iterator to be empty. The ordering here is post-order, LRN.
670
        let mut order = [
1✔
671
            |x: &ExprRefMut| matches!(x, ExprRefMut::Var(Var::Standalone { .. })),
1✔
672
            |x: &ExprRefMut| matches!(x, ExprRefMut::Stretch(Stretch { .. })),
1✔
673
            |x: &ExprRefMut| {
1✔
674
                matches!(
×
675
                    x,
1✔
676
                    ExprRefMut::Binary(Binary {
677
                        op: BinaryOp::Add,
678
                        ..
679
                    })
680
                )
681
            },
1✔
682
            |x: &ExprRefMut| matches!(x, ExprRefMut::Stretch(Stretch { .. })),
1✔
683
            |x: &ExprRefMut| matches!(x, ExprRefMut::Value(Value::Duration(..))),
1✔
684
            |x: &ExprRefMut| {
1✔
685
                matches!(
×
686
                    x,
1✔
687
                    ExprRefMut::Binary(Binary {
688
                        op: BinaryOp::Div,
689
                        ..
690
                    })
691
                )
692
            },
1✔
693
            |x: &ExprRefMut| {
1✔
694
                matches!(
×
695
                    x,
1✔
696
                    ExprRefMut::Binary(Binary {
697
                        op: BinaryOp::Mul,
698
                        ..
699
                    })
700
                )
701
            },
1✔
702
        ]
703
        .into_iter();
1✔
704

705
        expr.visit_mut(|x| {
7✔
706
            assert!(order.next().unwrap()(&x));
7✔
707
            Ok(())
7✔
708
        })?;
7✔
709

710
        assert!(order.next().is_none());
1✔
711
        Ok(())
1✔
712
    }
1✔
713

714
    #[test]
715
    fn test_visit_mut() -> PyResult<()> {
1✔
716
        let mut expr: Expr = Binary {
1✔
717
            op: BinaryOp::BitAnd,
1✔
718
            left: Unary {
1✔
719
                op: UnaryOp::BitNot,
1✔
720
                operand: Var::Standalone {
1✔
721
                    uuid: Uuid::new_v4().as_u128(),
1✔
722
                    name: "test".to_string(),
1✔
723
                    ty: Type::Bool,
1✔
724
                }
1✔
725
                .into(),
1✔
726
                ty: Type::Bool,
1✔
727
                constant: false,
1✔
728
            }
1✔
729
            .into(),
1✔
730
            right: Value::Uint {
1✔
731
                raw: BigUint::from(1u8),
1✔
732
                ty: Type::Bool,
1✔
733
            }
1✔
734
            .into(),
1✔
735
            ty: Type::Bool,
1✔
736
            constant: false,
1✔
737
        }
1✔
738
        .into();
1✔
739

740
        expr.visit_mut(|x| match x {
1✔
741
            ExprRefMut::Var(Var::Standalone { name, .. }) => {
1✔
742
                *name = "updated".to_string();
1✔
743
                Ok(())
1✔
744
            }
745
            _ => Ok(()),
3✔
746
        })?;
4✔
747

748
        let Var::Standalone { name, .. } = expr.vars().next().unwrap() else {
1✔
749
            panic!("wrong var type")
×
750
        };
751
        assert_eq!(name.as_str(), "updated");
1✔
752
        Ok(())
1✔
753
    }
1✔
754

755
    #[test]
756
    fn test_structurally_eq_to_self() -> PyResult<()> {
1✔
757
        let exprs = [
1✔
758
            Expr::Var(Var::Bit {
1✔
759
                bit: ShareableClbit::new_anonymous(),
1✔
760
            }),
1✔
761
            Expr::Var(Var::Register {
1✔
762
                register: ClassicalRegister::new_owning("a", 3),
1✔
763
                ty: Type::Uint(3),
1✔
764
            }),
1✔
765
            Expr::Value(Value::Uint {
1✔
766
                raw: BigUint::from(3u8),
1✔
767
                ty: Type::Uint(2),
1✔
768
            }),
1✔
769
            Expr::Cast(
1✔
770
                Cast {
1✔
771
                    operand: Expr::Var(Var::Register {
1✔
772
                        register: ClassicalRegister::new_owning("a", 3),
1✔
773
                        ty: Type::Uint(3),
1✔
774
                    }),
1✔
775
                    ty: Type::Bool,
1✔
776
                    constant: false,
1✔
777
                    implicit: false,
1✔
778
                }
1✔
779
                .into(),
1✔
780
            ),
1✔
781
            Expr::Unary(
1✔
782
                Unary {
1✔
783
                    op: UnaryOp::LogicNot,
1✔
784
                    operand: Expr::Var(Var::Bit {
1✔
785
                        bit: ShareableClbit::new_anonymous(),
1✔
786
                    }),
1✔
787
                    ty: Type::Bool,
1✔
788
                    constant: false,
1✔
789
                }
1✔
790
                .into(),
1✔
791
            ),
1✔
792
            Expr::Binary(
1✔
793
                Binary {
1✔
794
                    op: BinaryOp::BitAnd,
1✔
795
                    left: Expr::Value(Value::Uint {
1✔
796
                        raw: BigUint::from(5u8),
1✔
797
                        ty: Type::Uint(3),
1✔
798
                    }),
1✔
799
                    right: Expr::Var(Var::Register {
1✔
800
                        register: ClassicalRegister::new_owning("a", 3),
1✔
801
                        ty: Type::Uint(3),
1✔
802
                    }),
1✔
803
                    ty: Type::Uint(3),
1✔
804
                    constant: false,
1✔
805
                }
1✔
806
                .into(),
1✔
807
            ),
1✔
808
            Expr::Binary(
1✔
809
                Binary {
1✔
810
                    op: BinaryOp::LogicAnd,
1✔
811
                    left: Expr::Binary(
1✔
812
                        Binary {
1✔
813
                            op: BinaryOp::Less,
1✔
814
                            left: Expr::Value(Value::Uint {
1✔
815
                                raw: BigUint::from(2u8),
1✔
816
                                ty: Type::Uint(3),
1✔
817
                            }),
1✔
818
                            right: Expr::Var(Var::Register {
1✔
819
                                register: ClassicalRegister::new_owning("a", 3),
1✔
820
                                ty: Type::Uint(3),
1✔
821
                            }),
1✔
822
                            ty: Type::Bool,
1✔
823
                            constant: false,
1✔
824
                        }
1✔
825
                        .into(),
1✔
826
                    ),
1✔
827
                    right: Expr::Var(Var::Bit {
1✔
828
                        bit: ShareableClbit::new_anonymous(),
1✔
829
                    }),
1✔
830
                    ty: Type::Bool,
1✔
831
                    constant: false,
1✔
832
                }
1✔
833
                .into(),
1✔
834
            ),
1✔
835
            Expr::Binary(
1✔
836
                Binary {
1✔
837
                    op: BinaryOp::ShiftLeft,
1✔
838
                    left: Expr::Binary(
1✔
839
                        Binary {
1✔
840
                            op: BinaryOp::ShiftRight,
1✔
841
                            left: Expr::Value(Value::Uint {
1✔
842
                                raw: BigUint::from(255u8),
1✔
843
                                ty: Type::Uint(8),
1✔
844
                            }),
1✔
845
                            right: Expr::Value(Value::Uint {
1✔
846
                                raw: BigUint::from(3u8),
1✔
847
                                ty: Type::Uint(8),
1✔
848
                            }),
1✔
849
                            ty: Type::Uint(8),
1✔
850
                            constant: true,
1✔
851
                        }
1✔
852
                        .into(),
1✔
853
                    ),
1✔
854
                    right: Expr::Value(Value::Uint {
1✔
855
                        raw: BigUint::from(3u8),
1✔
856
                        ty: Type::Uint(8),
1✔
857
                    }),
1✔
858
                    ty: Type::Uint(8),
1✔
859
                    constant: true,
1✔
860
                }
1✔
861
                .into(),
1✔
862
            ),
1✔
863
            Expr::Index(
1✔
864
                Index {
1✔
865
                    target: Expr::Var(Var::Standalone {
1✔
866
                        uuid: Uuid::new_v4().as_u128(),
1✔
867
                        name: "a".to_string(),
1✔
868
                        ty: Type::Uint(8),
1✔
869
                    }),
1✔
870
                    index: Expr::Value(Value::Uint {
1✔
871
                        raw: BigUint::from(0u8),
1✔
872
                        ty: Type::Uint(8),
1✔
873
                    }),
1✔
874
                    ty: Type::Uint(1),
1✔
875
                    constant: false,
1✔
876
                }
1✔
877
                .into(),
1✔
878
            ),
1✔
879
            Expr::Binary(
1✔
880
                Binary {
1✔
881
                    op: BinaryOp::Greater,
1✔
882
                    left: Expr::Stretch(Stretch {
1✔
883
                        uuid: Uuid::new_v4().as_u128(),
1✔
884
                        name: "a".to_string(),
1✔
885
                    }),
1✔
886
                    right: Expr::Value(Value::Duration(Duration::dt(100))),
1✔
887
                    ty: Type::Bool,
1✔
888
                    constant: false,
1✔
889
                }
1✔
890
                .into(),
1✔
891
            ),
1✔
892
        ];
1✔
893

894
        for expr in exprs.iter() {
10✔
895
            assert!(expr.structurally_equivalent(expr));
10✔
896
        }
897

898
        Ok(())
1✔
899
    }
1✔
900

901
    #[test]
902
    fn test_structurally_eq_does_not_compare_symmetrically() {
1✔
903
        let all_ops = [
1✔
904
            BinaryOp::BitAnd,
1✔
905
            BinaryOp::BitOr,
1✔
906
            BinaryOp::BitXor,
1✔
907
            BinaryOp::LogicAnd,
1✔
908
            BinaryOp::LogicOr,
1✔
909
            BinaryOp::Equal,
1✔
910
            BinaryOp::NotEqual,
1✔
911
            BinaryOp::Less,
1✔
912
            BinaryOp::LessEqual,
1✔
913
            BinaryOp::Greater,
1✔
914
            BinaryOp::GreaterEqual,
1✔
915
            BinaryOp::ShiftLeft,
1✔
916
            BinaryOp::ShiftRight,
1✔
917
            BinaryOp::Add,
1✔
918
            BinaryOp::Sub,
1✔
919
            BinaryOp::Mul,
1✔
920
            BinaryOp::Div,
1✔
921
        ];
1✔
922

923
        for op in all_ops.iter().copied() {
17✔
924
            let (left, right, out_ty) = match op {
17✔
925
                BinaryOp::LogicAnd | BinaryOp::LogicOr => (
2✔
926
                    Expr::Value(Value::Uint {
2✔
927
                        raw: BigUint::from(1u8),
2✔
928
                        ty: Type::Bool,
2✔
929
                    }),
2✔
930
                    Expr::Var(Var::Bit {
2✔
931
                        bit: ShareableClbit::new_anonymous(),
2✔
932
                    }),
2✔
933
                    Type::Bool,
2✔
934
                ),
2✔
935
                _ => (
936
                    Expr::Value(Value::Uint {
15✔
937
                        raw: BigUint::from(5u8),
15✔
938
                        ty: Type::Uint(3),
15✔
939
                    }),
15✔
940
                    Expr::Var(Var::Register {
15✔
941
                        register: ClassicalRegister::new_owning("a", 3),
15✔
942
                        ty: Type::Uint(3),
15✔
943
                    }),
15✔
944
                    match op {
15✔
945
                        BinaryOp::BitAnd | BinaryOp::BitOr | BinaryOp::BitXor => Type::Uint(3),
3✔
946
                        _ => Type::Bool,
12✔
947
                    },
948
                ),
949
            };
950

951
            let cis = Expr::Binary(
17✔
952
                Binary {
17✔
953
                    op,
17✔
954
                    left: left.clone(),
17✔
955
                    right: right.clone(),
17✔
956
                    ty: out_ty,
17✔
957
                    constant: false,
17✔
958
                }
17✔
959
                .into(),
17✔
960
            );
17✔
961

962
            let trans = Expr::Binary(
17✔
963
                Binary {
17✔
964
                    op,
17✔
965
                    left: right,
17✔
966
                    right: left,
17✔
967
                    ty: out_ty,
17✔
968
                    constant: false,
17✔
969
                }
17✔
970
                .into(),
17✔
971
            );
17✔
972

973
            assert!(
17✔
974
                !cis.structurally_equivalent(&trans),
17✔
975
                "Expected {op:?} to not be structurally equivalent to its flipped form"
976
            );
977
            assert!(
17✔
978
                !trans.structurally_equivalent(&cis),
17✔
979
                "Expected flipped {op:?} to not be structurally equivalent to original form"
980
            );
981
        }
982
    }
1✔
983

984
    #[test]
985
    fn test_structurally_eq_key_function_both() -> PyResult<()> {
1✔
986
        let left_clbit = ShareableClbit::new_anonymous();
1✔
987
        let left_cr = ClassicalRegister::new_owning("a", 3);
1✔
988
        let right_clbit = ShareableClbit::new_anonymous();
1✔
989
        let right_cr = ClassicalRegister::new_owning("b", 3);
1✔
990
        assert_ne!(left_clbit, right_clbit);
1✔
991
        assert_ne!(left_cr, right_cr);
1✔
992

993
        let left = Expr::Unary(
1✔
994
            Unary {
1✔
995
                op: UnaryOp::LogicNot,
1✔
996
                operand: Expr::Binary(
1✔
997
                    Binary {
1✔
998
                        op: BinaryOp::LogicAnd,
1✔
999
                        left: Expr::Binary(
1✔
1000
                            Binary {
1✔
1001
                                op: BinaryOp::Less,
1✔
1002
                                left: Expr::Value(Value::Uint {
1✔
1003
                                    raw: BigUint::from(5u8),
1✔
1004
                                    ty: Type::Uint(3),
1✔
1005
                                }),
1✔
1006
                                right: Expr::Var(Var::Register {
1✔
1007
                                    register: left_cr.clone(),
1✔
1008
                                    ty: Type::Uint(3),
1✔
1009
                                }),
1✔
1010
                                ty: Type::Bool,
1✔
1011
                                constant: false,
1✔
1012
                            }
1✔
1013
                            .into(),
1✔
1014
                        ),
1✔
1015
                        right: Expr::Var(Var::Bit {
1✔
1016
                            bit: left_clbit.clone(),
1✔
1017
                        }),
1✔
1018
                        ty: Type::Bool,
1✔
1019
                        constant: false,
1✔
1020
                    }
1✔
1021
                    .into(),
1✔
1022
                ),
1✔
1023
                ty: Type::Bool,
1✔
1024
                constant: false,
1✔
1025
            }
1✔
1026
            .into(),
1✔
1027
        );
1✔
1028

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

1065
        assert!(
1✔
1066
            !left.structurally_equivalent(&right),
1✔
1067
            "Expressions using different registers/clbits should not be structurally equivalent"
1068
        );
1069

1070
        // We're happy as long as the variables are of the same kind.
1071
        let key_func = |v: &Var| -> &str {
4✔
1072
            match v {
4✔
1073
                Var::Standalone { .. } => "standalone",
×
1074
                Var::Bit { .. } => "bit",
2✔
1075
                Var::Register { .. } => "register",
2✔
1076
            }
1077
        };
4✔
1078

1079
        assert!(
1✔
1080
            left.structurally_equivalent_by_key(key_func, &right, key_func),
1✔
1081
            "Expressions that only care that their vars are of the same kind should be equivalent"
1082
        );
1083

1084
        Ok(())
1✔
1085
    }
1✔
1086
}
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