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