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