hugr_core/hugr/
patch.rs

1//! Rewrite operations on the HUGR - replacement, outlining, etc.
2
3pub mod consts;
4pub mod inline_call;
5pub mod inline_dfg;
6pub mod insert_cut;
7pub mod insert_identity;
8pub mod outline_cfg;
9pub mod peel_loop;
10mod port_types;
11pub mod replace;
12pub mod simple_replace;
13
14use crate::HugrView;
15use crate::core::HugrNode;
16use itertools::Itertools;
17pub use port_types::{BoundaryPort, HostPort, ReplacementPort};
18pub use simple_replace::{SimpleReplacement, SimpleReplacementError};
19
20use super::HugrMut;
21use super::views::ExtractionResult;
22
23/// Verify that a patch application would succeed.
24pub trait PatchVerification {
25    /// The type of Error with which this Rewrite may fail
26    type Error: std::error::Error;
27
28    /// The node type of the `HugrView` that this patch applies to.
29    type Node: HugrNode;
30
31    /// Checks whether the rewrite would succeed on the specified Hugr.
32    /// If this call succeeds, [`Patch::apply`] should also succeed on the same
33    /// `h` If this calls fails, [`Patch::apply`] would fail with the same
34    /// error.
35    fn verify(&self, h: &impl HugrView<Node = Self::Node>) -> Result<(), Self::Error>;
36
37    /// The nodes invalidated by the rewrite. Deprecated: implement
38    /// [Self::invalidated_nodes] instead. The default returns the empty
39    /// iterator; this should be fine as there are no external calls.
40    #[deprecated(note = "Use/implement invalidated_nodes instead")]
41    fn invalidation_set(&self) -> impl Iterator<Item = Self::Node> {
42        std::iter::empty()
43    }
44
45    /// Returns the nodes removed or altered by the rewrite. Modifying any of these
46    /// nodes will invalidate the rewrite.
47    ///
48    /// Two `impl Rewrite`s can be composed if their `invalidated_nodes` are
49    /// disjoint.
50    fn invalidated_nodes(
51        &self,
52        h: &impl HugrView<Node = Self::Node>,
53    ) -> impl Iterator<Item = Self::Node> {
54        let _ = h;
55        #[expect(deprecated)]
56        self.invalidation_set()
57    }
58}
59
60/// A patch that can be applied to a mutable Hugr of type `H`.
61///
62/// ### When to use
63///
64/// Use this trait whenever possible in bounds for the most generality. Note
65/// that this will require specifying which type `H` the patch applies to.
66///
67/// ### When to implement
68///
69/// For patches that work on any `H: HugrMut`, prefer implementing [`PatchHugrMut`] instead. This
70/// will automatically implement this trait.
71pub trait Patch<H: HugrView>: PatchVerification<Node = H::Node> {
72    /// The type returned on successful application of the rewrite.
73    type Outcome;
74
75    /// If `true`, [`Patch::apply`]'s of this rewrite guarantee that they do not
76    /// mutate the Hugr when they return an Err. If `false`, there is no
77    /// guarantee; the Hugr should be assumed invalid when Err is returned.
78    const UNCHANGED_ON_FAILURE: bool;
79
80    /// Mutate the specified Hugr, or fail with an error.
81    ///
82    /// Returns [`Self::Outcome`] if successful. If
83    /// [`Patch::UNCHANGED_ON_FAILURE`] is true, then `h` must be unchanged if Err
84    /// is returned. See also [`PatchVerification::verify`]
85    ///
86    /// # Panics
87    ///
88    /// May panic if-and-only-if `h` would have failed
89    /// [`Hugr::validate`][crate::Hugr::validate]; that is, implementations may
90    /// begin with `assert!(h.validate())`, with `debug_assert!(h.validate())`
91    /// being preferred.
92    fn apply(self, h: &mut H) -> Result<Self::Outcome, Self::Error>;
93}
94
95/// A patch that can be applied to any [`HugrMut`].
96///
97/// This trait is a generalisation of [`Patch`] in that it guarantees that
98/// the patch can be applied to any type implementing [`HugrMut`].
99///
100/// ### When to use
101///
102/// Prefer using the more general [`Patch`] trait in bounds where the
103/// type `H` is known. Resort to this trait if patches must be applicable to
104/// any [`HugrMut`] instance.
105///
106/// ### When to implement
107///
108/// Always implement this trait when possible, to define how a patch is applied
109/// to any type implementing [`HugrMut`]. A blanket implementation ensures that
110/// any type implementing this trait also implements [`Patch`].
111pub trait PatchHugrMut: PatchVerification {
112    /// The type returned on successful application of the rewrite.
113    type Outcome;
114
115    /// If `true`, [self.apply]'s of this rewrite guarantee that they do not
116    /// mutate the Hugr when they return an Err. If `false`, there is no
117    /// guarantee; the Hugr should be assumed invalid when Err is returned.
118    const UNCHANGED_ON_FAILURE: bool;
119
120    /// Mutate the specified Hugr, or fail with an error.
121    ///
122    /// Returns [`Self::Outcome`] if successful. If [`self.unchanged_on_failure`]
123    /// is true, then `h` must be unchanged if Err is returned. See also
124    /// [self.verify]
125    ///
126    /// # Panics
127    ///
128    /// May panic if-and-only-if `h` would have failed
129    /// [`Hugr::validate`][crate::Hugr::validate]; that is, implementations may
130    /// begin with `assert!(h.validate())`, with `debug_assert!(h.validate())`
131    /// being preferred.
132    fn apply_hugr_mut(
133        self,
134        h: &mut impl HugrMut<Node = Self::Node>,
135    ) -> Result<Self::Outcome, Self::Error>;
136}
137
138impl<R: PatchHugrMut, H: HugrMut<Node = R::Node>> Patch<H> for R {
139    type Outcome = R::Outcome;
140    const UNCHANGED_ON_FAILURE: bool = R::UNCHANGED_ON_FAILURE;
141
142    fn apply(self, h: &mut H) -> Result<Self::Outcome, Self::Error> {
143        self.apply_hugr_mut(h)
144    }
145}
146
147/// Wraps any rewrite into a transaction (i.e. that has no effect upon failure)
148pub struct Transactional<R> {
149    underlying: R,
150}
151
152impl<R: PatchVerification> PatchVerification for Transactional<R> {
153    type Error = R::Error;
154    type Node = R::Node;
155
156    fn verify(&self, h: &impl HugrView<Node = Self::Node>) -> Result<(), Self::Error> {
157        self.underlying.verify(h)
158    }
159
160    #[inline]
161    fn invalidated_nodes(
162        &self,
163        h: &impl HugrView<Node = Self::Node>,
164    ) -> impl Iterator<Item = Self::Node> {
165        self.underlying.invalidated_nodes(h)
166    }
167}
168
169// Note we might like to constrain R to Rewrite<unchanged_on_failure=false> but
170// this is not yet supported, https://github.com/rust-lang/rust/issues/92827
171impl<R: PatchHugrMut> PatchHugrMut for Transactional<R> {
172    type Outcome = R::Outcome;
173    const UNCHANGED_ON_FAILURE: bool = true;
174
175    fn apply_hugr_mut(
176        self,
177        h: &mut impl HugrMut<Node = Self::Node>,
178    ) -> Result<Self::Outcome, Self::Error> {
179        if R::UNCHANGED_ON_FAILURE {
180            return self.underlying.apply_hugr_mut(h);
181        }
182        // Backup the whole Hugr.
183        // Temporarily move the entrypoint so every node gets copied.
184        //
185        // TODO: This requires a full graph copy on each application.
186        // Ideally we'd be able to just restore modified nodes, perhaps using a `HugrMut` wrapper
187        // that keeps track of them.
188        let (backup, backup_map) = h.extract_hugr(h.module_root());
189        let backup_root = backup_map.extracted_node(h.module_root());
190        let backup_entrypoint = backup_map.extracted_node(h.entrypoint());
191
192        let r = self.underlying.apply_hugr_mut(h);
193        if r.is_err() {
194            // Restore the backup.
195            let h_root = h.module_root();
196            for f in h.children(h_root).collect_vec() {
197                h.remove_subtree(f);
198            }
199            let insert_map = h.insert_hugr(h_root, backup).node_map;
200            let inserted_root = insert_map[&backup_root];
201            let inserted_entrypoint = insert_map[&backup_entrypoint];
202            h.remove_node(h_root);
203            h.set_module_root(inserted_root);
204            h.set_entrypoint(inserted_entrypoint);
205        }
206        r
207    }
208}