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;