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

Qiskit / qiskit / 25853702590

14 May 2026 09:54AM UTC coverage: 87.425% (-0.2%) from 87.632%
25853702590

Pull #16145

github

web-flow
Merge b64e22949 into dcc06fb27
Pull Request #16145: Add generated `qiskit-pyo3-ffi` crate

0 of 317 new or added lines in 4 files covered. (0.0%)

247 existing lines in 12 files now uncovered.

107175 of 122591 relevant lines covered (87.42%)

956757.34 hits per line

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

91.95
/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, 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
}
36

37
#[derive(Debug, PartialEq)]
38
pub enum ExprRef<'a> {
39
    Unary(&'a Unary),
40
    Binary(&'a Binary),
41
    Cast(&'a Cast),
42
    Value(&'a Value),
43
    Var(&'a Var),
44
    Stretch(&'a Stretch),
45
    Index(&'a Index),
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
}
58

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

65
impl Expr {
66
    /// Converts from `&Expr` to `ExprRef`.
67
    pub fn as_ref(&self) -> ExprRef<'_> {
24,802✔
68
        match self {
24,802✔
69
            Expr::Unary(u) => ExprRef::Unary(u.as_ref()),
610✔
70
            Expr::Binary(b) => ExprRef::Binary(b.as_ref()),
7,398✔
71
            Expr::Cast(c) => ExprRef::Cast(c.as_ref()),
148✔
72
            Expr::Value(v) => ExprRef::Value(v),
9,567✔
73
            Expr::Var(v) => ExprRef::Var(v),
6,935✔
74
            Expr::Stretch(s) => ExprRef::Stretch(s),
118✔
75
            Expr::Index(i) => ExprRef::Index(i.as_ref()),
26✔
76
        }
77
    }
24,802✔
78

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

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

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

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

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

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

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

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

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

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

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

261
impl<'a> Iterator for ExprIterator<'a> {
262
    type Item = ExprRef<'a>;
263

264
    fn next(&mut self) -> Option<Self::Item> {
33,997✔
265
        let expr = self.stack.pop()?;
33,997✔
266
        match expr {
24,802✔
267
            Expr::Unary(u) => {
610✔
268
                self.stack.push(&u.operand);
610✔
269
            }
610✔
270
            Expr::Binary(b) => {
7,398✔
271
                self.stack.push(&b.left);
7,398✔
272
                self.stack.push(&b.right);
7,398✔
273
            }
7,398✔
274
            Expr::Cast(c) => self.stack.push(&c.operand),
148✔
275
            Expr::Value(_) => {}
9,567✔
276
            Expr::Var(_) => {}
6,935✔
277
            Expr::Stretch(_) => {}
118✔
278
            Expr::Index(i) => {
26✔
279
                self.stack.push(&i.index);
26✔
280
                self.stack.push(&i.target);
26✔
281
            }
26✔
282
        }
283
        Some(expr.as_ref())
24,802✔
284
    }
33,997✔
285
}
286

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

290
impl<'a> Iterator for VarIterator<'a> {
291
    type Item = &'a Var;
292

293
    fn next(&mut self) -> Option<Self::Item> {
14,580✔
294
        for expr in self.0.by_ref() {
21,694✔
295
            if let ExprRef::Var(v) = expr {
21,694✔
296
                return Some(v);
6,189✔
297
            }
15,505✔
298
        }
299
        None
8,391✔
300
    }
14,580✔
301
}
302

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

306
impl<'a> Iterator for IdentIterator<'a> {
307
    type Item = IdentifierRef<'a>;
308

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

322
impl From<Unary> for Expr {
323
    fn from(value: Unary) -> Self {
2✔
324
        Expr::Unary(Box::new(value))
2✔
325
    }
2✔
326
}
327

328
impl From<Box<Unary>> for Expr {
UNCOV
329
    fn from(value: Box<Unary>) -> Self {
×
UNCOV
330
        Expr::Unary(value)
×
UNCOV
331
    }
×
332
}
333

334
impl From<Binary> for Expr {
335
    fn from(value: Binary) -> Self {
11✔
336
        Expr::Binary(Box::new(value))
11✔
337
    }
11✔
338
}
339

340
impl From<Box<Binary>> for Expr {
UNCOV
341
    fn from(value: Box<Binary>) -> Self {
×
UNCOV
342
        Expr::Binary(value)
×
343
    }
×
344
}
345

346
impl From<Cast> for Expr {
UNCOV
347
    fn from(value: Cast) -> Self {
×
UNCOV
348
        Expr::Cast(Box::new(value))
×
349
    }
×
350
}
351

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

358
impl From<Value> for Expr {
359
    fn from(value: Value) -> Self {
4✔
360
        Expr::Value(value)
4✔
361
    }
4✔
362
}
363

364
impl From<Var> for Expr {
365
    fn from(value: Var) -> Self {
6✔
366
        Expr::Var(value)
6✔
367
    }
6✔
368
}
369

370
impl From<Stretch> for Expr {
371
    fn from(value: Stretch) -> Self {
6✔
372
        Expr::Stretch(value)
6✔
373
    }
6✔
374
}
375

376
impl From<Index> for Expr {
UNCOV
377
    fn from(value: Index) -> Self {
×
UNCOV
378
        Expr::Index(Box::new(value))
×
379
    }
×
380
}
381

382
impl From<Box<Index>> for Expr {
UNCOV
383
    fn from(value: Box<Index>) -> Self {
×
UNCOV
384
        Expr::Index(value)
×
UNCOV
385
    }
×
386
}
387

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

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

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

443
impl<'py> IntoPyObject<'py> for Expr {
444
    type Target = PyAny;
445
    type Output = Bound<'py, PyAny>;
446
    type Error = PyErr;
447

448
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
8,586✔
449
        match self {
8,586✔
450
            Expr::Unary(u) => u.into_bound_py_any(py),
192✔
451
            Expr::Binary(b) => b.into_bound_py_any(py),
2,308✔
452
            Expr::Cast(c) => c.into_bound_py_any(py),
22✔
453
            Expr::Value(v) => v.into_bound_py_any(py),
2,892✔
454
            Expr::Var(v) => v.into_bound_py_any(py),
2,842✔
455
            Expr::Stretch(s) => s.into_bound_py_any(py),
310✔
456
            Expr::Index(i) => i.into_bound_py_any(py),
20✔
457
        }
458
    }
8,586✔
459
}
460

461
impl<'a, 'py> FromPyObject<'a, 'py> for Expr {
462
    type Error = PyErr;
463

464
    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
14,930✔
465
        let expr: PyRef<'_, PyExpr> = ob.cast()?.borrow();
14,930✔
466
        match expr.0 {
14,710✔
467
            ExprKind::Unary => Ok(Expr::Unary(Box::new(ob.extract()?))),
392✔
468
            ExprKind::Binary => Ok(Expr::Binary(Box::new(ob.extract()?))),
2,788✔
469
            ExprKind::Value => Ok(Expr::Value(ob.extract()?)),
5,246✔
470
            ExprKind::Var => Ok(Expr::Var(ob.extract()?)),
5,814✔
471
            ExprKind::Cast => Ok(Expr::Cast(Box::new(ob.extract()?))),
152✔
472
            ExprKind::Stretch => Ok(Expr::Stretch(ob.extract()?)),
290✔
473
            ExprKind::Index => Ok(Expr::Index(Box::new(ob.extract()?))),
28✔
474
        }
475
    }
14,930✔
476
}
477

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

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

516
        let vars: Vec<&Var> = expr.vars().collect();
1✔
517
        assert!(matches!(
1✔
518
            vars.as_slice(),
1✔
519
            [Var::Bit { .. }, Var::Standalone { .. }]
1✔
520
        ));
521
    }
1✔
522

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

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

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

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

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

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

709
        expr.visit_mut(|x| {
7✔
710
            assert!(order.next().unwrap()(&x));
7✔
711
            Ok::<_, PyErr>(())
7✔
712
        })?;
7✔
713

714
        assert!(order.next().is_none());
1✔
715
        Ok(())
1✔
716
    }
1✔
717

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

744
        expr.visit_mut(|x| match x {
1✔
745
            ExprRefMut::Var(Var::Standalone { name, .. }) => {
1✔
746
                *name = "updated".to_string();
1✔
747
                Ok::<_, PyErr>(())
1✔
748
            }
749
            _ => Ok(()),
3✔
750
        })?;
4✔
751

752
        let Var::Standalone { name, .. } = expr.vars().next().unwrap() else {
1✔
UNCOV
753
            panic!("wrong var type")
×
754
        };
755
        assert_eq!(name.as_str(), "updated");
1✔
756
        Ok(())
1✔
757
    }
1✔
758

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

898
        for expr in exprs.iter() {
10✔
899
            assert!(expr.structurally_equivalent(expr));
10✔
900
        }
901

902
        Ok(())
1✔
903
    }
1✔
904

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

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

955
            let cis = Expr::Binary(
17✔
956
                Binary {
17✔
957
                    op,
17✔
958
                    left: left.clone(),
17✔
959
                    right: right.clone(),
17✔
960
                    ty: out_ty,
17✔
961
                    constant: false,
17✔
962
                }
17✔
963
                .into(),
17✔
964
            );
17✔
965

966
            let trans = Expr::Binary(
17✔
967
                Binary {
17✔
968
                    op,
17✔
969
                    left: right,
17✔
970
                    right: left,
17✔
971
                    ty: out_ty,
17✔
972
                    constant: false,
17✔
973
                }
17✔
974
                .into(),
17✔
975
            );
17✔
976

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

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

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

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

1069
        assert!(
1✔
1070
            !left.structurally_equivalent(&right),
1✔
1071
            "Expressions using different registers/clbits should not be structurally equivalent"
1072
        );
1073

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

1083
        assert!(
1✔
1084
            left.structurally_equivalent_by_key(key_func, &right, key_func),
1✔
1085
            "Expressions that only care that their vars are of the same kind should be equivalent"
1086
        );
1087

1088
        Ok(())
1✔
1089
    }
1✔
1090
}
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