Skip to main content

cqlib_core/circuit/control_flow/
if_op.rs

1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2026
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 http://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
13use super::ControlBody;
14use crate::circuit::classical_expr::ClassicalExpr;
15use crate::circuit::{CircuitError, ClassicalType, ClassicalValue, ClassicalVar, Qubit};
16use std::collections::BTreeSet;
17
18/// Conditional execution controlled by a boolean classical expression.
19#[derive(Debug, Clone, PartialEq)]
20pub struct IfOp {
21    condition: ClassicalExpr,
22    then_body: ControlBody,
23    else_body: Option<ControlBody>,
24}
25
26impl IfOp {
27    /// Creates an expression-based `if` operation.
28    pub fn new(
29        condition: ClassicalExpr,
30        then_body: ControlBody,
31        else_body: Option<ControlBody>,
32    ) -> Result<Self, CircuitError> {
33        if condition.ty() != ClassicalType::Bool {
34            return Err(CircuitError::InvalidOperation(format!(
35                "if condition must be Bool, got {:?}",
36                condition.ty()
37            )));
38        }
39        Ok(Self {
40            condition,
41            then_body,
42            else_body,
43        })
44    }
45
46    /// Returns the boolean branch condition.
47    pub fn condition(&self) -> &ClassicalExpr {
48        &self.condition
49    }
50
51    /// Returns the body executed when the condition is true.
52    pub fn then_body(&self) -> &ControlBody {
53        &self.then_body
54    }
55
56    /// Returns the optional body executed when the condition is false.
57    pub fn else_body(&self) -> Option<&ControlBody> {
58        self.else_body.as_ref()
59    }
60
61    /// Returns mutable variables read by the condition.
62    pub fn classical_var_reads(&self) -> BTreeSet<ClassicalVar> {
63        self.condition.vars()
64    }
65
66    /// Returns immutable values read by the condition.
67    pub fn classical_value_reads(&self) -> BTreeSet<ClassicalValue> {
68        self.condition.values()
69    }
70
71    /// Returns qubits used by either branch body.
72    pub fn used_qubits(&self) -> BTreeSet<Qubit> {
73        let mut qubits = self.then_body.used_qubits();
74        if let Some(else_body) = &self.else_body {
75            qubits.extend(else_body.used_qubits());
76        }
77        qubits
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::IfOp;
84    use crate::circuit::{
85        CircuitId, ClassicalExpr, ClassicalType, ClassicalValue, ClassicalVar, ControlBody,
86    };
87
88    #[test]
89    fn if_requires_bool_condition() {
90        let condition = ClassicalExpr::bool_literal(true);
91        let op = IfOp::new(condition, ControlBody::new(vec![]), None).unwrap();
92
93        assert!(op.else_body().is_none());
94        assert!(
95            IfOp::new(
96                ClassicalExpr::bit_literal(true),
97                ControlBody::new(vec![]),
98                None
99            )
100            .is_err()
101        );
102    }
103
104    #[test]
105    fn if_reports_condition_var_reads() {
106        let circuit_id = CircuitId::new();
107        let bit = ClassicalExpr::var(ClassicalVar::new(circuit_id, 3, ClassicalType::Bit));
108        let condition = ClassicalExpr::bit_to_bool(bit).unwrap();
109        let op = IfOp::new(condition, ControlBody::new(vec![]), None).unwrap();
110
111        assert!(op.classical_var_reads().contains(&ClassicalVar::new(
112            circuit_id,
113            3,
114            ClassicalType::Bit
115        )));
116    }
117
118    #[test]
119    fn if_reports_condition_value_reads() {
120        let value = ClassicalValue::new(CircuitId::new(), 4, ClassicalType::Bit);
121        let condition = ClassicalExpr::bit_to_bool(value.expr()).unwrap();
122        let op = IfOp::new(condition, ControlBody::new(vec![]), None).unwrap();
123
124        assert!(op.classical_var_reads().is_empty());
125        assert!(op.classical_value_reads().contains(&value));
126    }
127}