Skip to main content

cqlib_core/compile/transform/
transformer.rs

1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2025-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 crate::circuit::Circuit;
14use crate::compile::CompilerError;
15use crate::compile::transform::analysis::{CircuitAnalysis, WorkflowCircuitAnalysis};
16
17/// Conservative workflow scheduling decision for one compiler pass.
18///
19/// `ProvenNoOp` is stronger than ordinary inapplicability: executing the pass
20/// on the exact analyzed circuit revision must return `TransformOutcome::Unchanged`
21/// and must not suppress an error that the pass would otherwise report.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub(crate) enum PassApplicability {
24    Run,
25    ProvenNoOp(&'static str),
26}
27
28/// Workflow-only precondition declaration.
29///
30/// This remains separate from the public [`Transformer`] contract so adding
31/// scheduling facts does not expand the Rust or Python API surface.
32pub(crate) trait WorkflowPass {
33    fn applicability(&self, analysis: &WorkflowCircuitAnalysis) -> PassApplicability;
34}
35
36/// Outcome of applying a compiler transform to a circuit.
37///
38/// `Unchanged` means the input compiler IR can be retained exactly as-is.
39/// `Changed` carries the replacement circuit that callers must adopt.
40#[derive(Debug, Clone, PartialEq)]
41#[allow(clippy::large_enum_variant)] // Boxing would allocate on every changed pass.
42pub enum TransformOutcome {
43    /// The input circuit representation can be retained without rebuilding it.
44    Unchanged,
45    /// The transform produced a replacement circuit representation.
46    Changed(Circuit),
47}
48
49impl TransformOutcome {
50    /// Whether the transform produced a replacement circuit.
51    pub const fn changed(&self) -> bool {
52        matches!(self, Self::Changed(_))
53    }
54
55    /// Resolves the outcome into an owned circuit.
56    ///
57    /// This helper is intended for APIs that promise an owned circuit. Core
58    /// workflows should match on the outcome directly so `Unchanged` remains
59    /// zero-copy.
60    pub fn into_circuit(self, original: &Circuit) -> Circuit {
61        match self {
62            Self::Unchanged => original.clone(),
63            Self::Changed(circuit) => circuit,
64        }
65    }
66}
67
68/// Common interface for compiler transforms over an immutable circuit.
69///
70/// # Implementing
71///
72/// - [`name`](Transformer::name) returns a static human-readable label for logging.
73/// - [`transform`](Transformer::transform) applies the pass to a circuit.
74///
75/// Parameters that differ between pass instances (e.g. config, device) are bound at
76/// construction time so `transform` keeps a uniform signature across all passes.
77pub trait Transformer {
78    /// Human-readable pass name for logging and debugging.
79    fn name(&self) -> &'static str;
80
81    /// Applies the transform to `circuit`.
82    ///
83    /// Callers may provide precomputed [`CircuitAnalysis`] to avoid repeated
84    /// structural scans across workflow stages. When `analysis` is `None`, the
85    /// transform must derive any required facts itself.
86    fn transform(
87        &self,
88        circuit: &Circuit,
89        analysis: Option<&CircuitAnalysis>,
90    ) -> Result<TransformOutcome, CompilerError>;
91}
92
93#[cfg(test)]
94#[derive(Debug)]
95pub(crate) struct ResolvedTransform {
96    pub(crate) circuit: Circuit,
97    pub(crate) changed: bool,
98}
99
100#[cfg(test)]
101pub(crate) fn resolve_transform_for_test(
102    outcome: TransformOutcome,
103    original: &Circuit,
104) -> ResolvedTransform {
105    let changed = outcome.changed();
106    ResolvedTransform {
107        circuit: outcome.into_circuit(original),
108        changed,
109    }
110}
111
112#[cfg(test)]
113pub(crate) trait TransformerTestExt: Transformer {
114    fn transform_resolved(
115        &self,
116        circuit: &Circuit,
117        analysis: Option<&CircuitAnalysis>,
118    ) -> Result<ResolvedTransform, CompilerError> {
119        self.transform(circuit, analysis)
120            .map(|outcome| resolve_transform_for_test(outcome, circuit))
121    }
122}
123
124#[cfg(test)]
125impl<T: Transformer + ?Sized> TransformerTestExt for T {}
126
127#[cfg(test)]
128#[path = "./transformer_test.rs"]
129mod transformer_test;