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

Qiskit / qiskit / 23473015753

24 Mar 2026 04:24AM UTC coverage: 87.194% (-0.07%) from 87.268%
23473015753

Pull #14618

github

web-flow
Merge c9f55f2a6 into 572045f51
Pull Request #14618: New classical expr.Range specification

176 of 325 new or added lines in 15 files covered. (54.15%)

10 existing lines in 4 files now uncovered.

103677 of 118904 relevant lines covered (87.19%)

996124.05 hits per line

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

89.57
/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, Range, 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
    Range(Box<Range>),
34
}
35

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

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

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

66
impl Expr {
67
    /// Converts from `&Expr` to `ExprRef`.
68
    pub fn as_ref(&self) -> ExprRef<'_> {
23,914✔
69
        match self {
23,914✔
70
            Expr::Unary(u) => ExprRef::Unary(u.as_ref()),
554✔
71
            Expr::Binary(b) => ExprRef::Binary(b.as_ref()),
7,126✔
72
            Expr::Cast(c) => ExprRef::Cast(c.as_ref()),
148✔
73
            Expr::Value(v) => ExprRef::Value(v),
9,295✔
74
            Expr::Var(v) => ExprRef::Var(v),
6,727✔
75
            Expr::Stretch(s) => ExprRef::Stretch(s),
38✔
76
            Expr::Index(i) => ExprRef::Index(i.as_ref()),
26✔
NEW
77
            Expr::Range(r) => ExprRef::Range(r.as_ref()),
×
78
        }
79
    }
23,914✔
80

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

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

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

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

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

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

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

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

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

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

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

281
impl<'a> Iterator for ExprIterator<'a> {
282
    type Item = ExprRef<'a>;
283

284
    fn next(&mut self) -> Option<Self::Item> {
32,821✔
285
        let expr = self.stack.pop()?;
32,821✔
286
        match expr {
23,914✔
287
            Expr::Unary(u) => {
554✔
288
                self.stack.push(&u.operand);
554✔
289
            }
554✔
290
            Expr::Binary(b) => {
7,126✔
291
                self.stack.push(&b.left);
7,126✔
292
                self.stack.push(&b.right);
7,126✔
293
            }
7,126✔
294
            Expr::Cast(c) => self.stack.push(&c.operand),
148✔
295
            Expr::Value(_) => {}
9,295✔
296
            Expr::Var(_) => {}
6,727✔
297
            Expr::Stretch(_) => {}
38✔
298
            Expr::Index(i) => {
26✔
299
                self.stack.push(&i.index);
26✔
300
                self.stack.push(&i.target);
26✔
301
            }
26✔
NEW
302
            Expr::Range(r) => {
×
NEW
303
                self.stack.push(&r.stop);
×
NEW
304
                self.stack.push(&r.start);
×
NEW
305
                self.stack.push(&r.step);
×
NEW
306
            }
×
307
        }
308
        Some(expr.as_ref())
23,914✔
309
    }
32,821✔
310
}
311

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

315
impl<'a> Iterator for VarIterator<'a> {
316
    type Item = &'a Var;
317

318
    fn next(&mut self) -> Option<Self::Item> {
14,372✔
319
        for expr in self.0.by_ref() {
21,410✔
320
            if let ExprRef::Var(v) = expr {
21,410✔
321
                return Some(v);
6,085✔
322
            }
15,325✔
323
        }
324
        None
8,287✔
325
    }
14,372✔
326
}
327

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

331
impl<'a> Iterator for IdentIterator<'a> {
332
    type Item = IdentifierRef<'a>;
333

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

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

353
impl From<Box<Unary>> for Expr {
354
    fn from(value: Box<Unary>) -> Self {
×
355
        Expr::Unary(value)
×
356
    }
×
357
}
358

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

365
impl From<Box<Binary>> for Expr {
366
    fn from(value: Box<Binary>) -> Self {
×
367
        Expr::Binary(value)
×
368
    }
×
369
}
370

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

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

383
impl From<Value> for Expr {
384
    fn from(value: Value) -> Self {
16✔
385
        Expr::Value(value)
16✔
386
    }
16✔
387
}
388

389
impl From<Var> for Expr {
390
    fn from(value: Var) -> Self {
6✔
391
        Expr::Var(value)
6✔
392
    }
6✔
393
}
394

395
impl From<Stretch> for Expr {
396
    fn from(value: Stretch) -> Self {
6✔
397
        Expr::Stretch(value)
6✔
398
    }
6✔
399
}
400

401
impl From<Index> for Expr {
402
    fn from(value: Index) -> Self {
×
403
        Expr::Index(Box::new(value))
×
404
    }
×
405
}
406

407
impl From<Box<Index>> for Expr {
408
    fn from(value: Box<Index>) -> Self {
×
409
        Expr::Index(value)
×
410
    }
×
411
}
412

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

419
impl From<Box<Range>> for Expr {
NEW
420
    fn from(value: Box<Range>) -> Self {
×
NEW
421
        Expr::Range(value)
×
NEW
422
    }
×
423
}
424

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

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

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

481
impl<'py> IntoPyObject<'py> for Expr {
482
    type Target = PyAny;
483
    type Output = Bound<'py, PyAny>;
484
    type Error = PyErr;
485

486
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
8,188✔
487
        match self {
8,188✔
488
            Expr::Unary(u) => u.into_bound_py_any(py),
202✔
489
            Expr::Binary(b) => b.into_bound_py_any(py),
2,240✔
490
            Expr::Cast(c) => c.into_bound_py_any(py),
98✔
491
            Expr::Value(v) => v.into_bound_py_any(py),
2,730✔
492
            Expr::Var(v) => v.into_bound_py_any(py),
2,748✔
493
            Expr::Stretch(s) => s.into_bound_py_any(py),
150✔
494
            Expr::Index(i) => i.into_bound_py_any(py),
20✔
NEW
495
            Expr::Range(r) => r.into_bound_py_any(py),
×
496
        }
497
    }
8,188✔
498
}
499

500
impl<'a, 'py> FromPyObject<'a, 'py> for Expr {
501
    type Error = PyErr;
502

503
    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
14,398✔
504
        let expr: PyRef<'_, PyExpr> = ob.cast()?.borrow();
14,398✔
505
        match expr.0 {
14,192✔
506
            ExprKind::Unary => Ok(Expr::Unary(Box::new(ob.extract()?))),
376✔
507
            ExprKind::Binary => Ok(Expr::Binary(Box::new(ob.extract()?))),
2,580✔
508
            ExprKind::Value => Ok(Expr::Value(ob.extract()?)),
5,148✔
509
            ExprKind::Var => Ok(Expr::Var(ob.extract()?)),
5,752✔
510
            ExprKind::Cast => Ok(Expr::Cast(Box::new(ob.extract()?))),
152✔
511
            ExprKind::Stretch => Ok(Expr::Stretch(ob.extract()?)),
156✔
512
            ExprKind::Index => Ok(Expr::Index(Box::new(ob.extract()?))),
28✔
NEW
513
            ExprKind::Range => Ok(Expr::Range(Box::new(ob.extract()?))),
×
514
        }
515
    }
14,398✔
516
}
517

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

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

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

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

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

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

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

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

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

749
        expr.visit_mut(|x| {
7✔
750
            assert!(order.next().unwrap()(&x));
7✔
751
            Ok(())
7✔
752
        })?;
7✔
753

754
        assert!(order.next().is_none());
1✔
755
        Ok(())
1✔
756
    }
1✔
757

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

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

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

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

938
        for expr in exprs.iter() {
10✔
939
            assert!(expr.structurally_equivalent(expr));
10✔
940
        }
941

942
        Ok(())
1✔
943
    }
1✔
944

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

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

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

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

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

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

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

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

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

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

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

1128
        Ok(())
1✔
1129
    }
1✔
1130
}
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