hugr_core/
builder.rs

1//! Utilities for building valid HUGRs.
2//!
3//! This module includes various tools for building HUGRs.
4//!
5//! Depending on the type of HUGR you want to build, you may want to use one of
6//! the following builders:
7//!
8//! - [`ModuleBuilder`]: For building a module with function declarations and
9//!   definitions.
10//! - [`DFGBuilder`]: For building a dataflow graph.
11//! - [`FunctionBuilder`]: A `DFGBuilder` specialised in defining functions with a
12//!   dataflow graph.
13//! - [`CFGBuilder`]: For building a control flow graph.
14//! - [`ConditionalBuilder`]: For building a conditional node.
15//! - [`TailLoopBuilder`]: For building a tail-loop node.
16//!
17//! Additionally, the [`CircuitBuilder`] provides an alternative to the
18//! [`DFGBuilder`] when working with circuits, where some inputs of operations directly
19//! correspond to some outputs and operations can be directly appended using
20//! unit indices.
21//!
22//! # Example
23//!
24//! The following example shows how to build a simple HUGR module with two
25//! dataflow functions, one built using the `DFGBuilder` and the other using the
26//! `CircuitBuilder`.
27//!
28//! ```rust
29//! # use hugr::{Hugr, HugrView};
30//! # use hugr::builder::{BuildError, BuildHandle, Container, DFGBuilder, Dataflow, DataflowHugr, ModuleBuilder, DataflowSubContainer, HugrBuilder};
31//! use hugr::extension::prelude::bool_t;
32//! use hugr::std_extensions::logic::{self, LogicOp};
33//! use hugr::types::Signature;
34//!
35//! # fn doctest() -> Result<(), BuildError> {
36//! let hugr = {
37//!     let mut module_builder = ModuleBuilder::new();
38//!
39//!     // Add a `main` function with signature `bool -> bool`.
40//!     //
41//!     // This block returns a handle to the built function.
42//!     let _dfg_handle = {
43//!         let mut dfg = module_builder.define_function(
44//!             "main",
45//!             Signature::new_endo(bool_t()),
46//!         )?;
47//!
48//!         // Get the wires from the function inputs.
49//!         let [w] = dfg.input_wires_arr();
50//!
51//!         // Add an operation connected to the input wire, and get the new dangling wires.
52//!         let [w] = dfg.add_dataflow_op(LogicOp::Not, [w])?.outputs_arr();
53//!
54//!         // Finish the function, connecting some wires to the output.
55//!         dfg.finish_with_outputs([w])
56//!     }?;
57//!
58//!     // Add a similar function, using the circuit builder interface.
59//!     let _circuit_handle = {
60//!         let mut dfg = module_builder.define_function(
61//!             "circuit",
62//!             Signature::new_endo(vec![bool_t(), bool_t()]),
63//!         )?;
64//!         let mut circuit = dfg.as_circuit(dfg.input_wires());
65//!
66//!         // Add multiple operations, indicating only the wire index.
67//!         circuit.append(LogicOp::Not, [0])?.append(LogicOp::Not, [1])?;
68//!
69//!         // Finish the circuit, and return the dataflow graph after connecting its outputs.
70//!         let outputs = circuit.finish();
71//!         dfg.finish_with_outputs(outputs)
72//!     }?;
73//!
74//!     // Finish building the HUGR, consuming the builder.
75//!     //
76//!     // Requires a registry with all the extensions used in the module.
77//!     module_builder.finish_hugr()
78//! }?;
79//!
80//! // The built HUGR is always valid.
81//! hugr.validate().unwrap_or_else(|e| {
82//!     panic!("HUGR validation failed: {e}");
83//! });
84//! # Ok(())
85//! # }
86//! # doctest().unwrap();
87//! ```
88use thiserror::Error;
89
90use crate::extension::SignatureError;
91use crate::extension::simple_op::OpLoadError;
92use crate::hugr::ValidationError;
93use crate::ops::handle::{BasicBlockID, CfgID, ConditionalID, DfgID, FuncID, TailLoopID};
94use crate::ops::{NamedOp, OpType};
95use crate::types::Type;
96use crate::types::{ConstTypeError, Signature, TypeRow};
97use crate::{Node, Port, Wire};
98
99pub mod handle;
100pub use handle::BuildHandle;
101
102mod build_traits;
103pub use build_traits::{
104    Container, Dataflow, DataflowHugr, DataflowSubContainer, HugrBuilder, SubContainer,
105};
106
107mod dataflow;
108pub use dataflow::{DFGBuilder, DFGWrapper, FunctionBuilder};
109
110mod module;
111pub use module::ModuleBuilder;
112
113mod cfg;
114pub use cfg::{BlockBuilder, CFGBuilder};
115
116mod tail_loop;
117pub use tail_loop::TailLoopBuilder;
118
119mod conditional;
120pub use conditional::{CaseBuilder, ConditionalBuilder};
121
122mod circuit;
123pub use circuit::{CircuitBuildError, CircuitBuilder};
124
125/// Return a `FunctionType` with the same input and output types (specified).
126pub fn endo_sig(types: impl Into<TypeRow>) -> Signature {
127    Signature::new_endo(types)
128}
129
130/// Return a `FunctionType` with the specified input and output types.
131pub fn inout_sig(inputs: impl Into<TypeRow>, outputs: impl Into<TypeRow>) -> Signature {
132    Signature::new(inputs, outputs)
133}
134
135#[derive(Debug, Clone, PartialEq, Error)]
136#[non_exhaustive]
137/// Error while building the HUGR.
138pub enum BuildError {
139    /// The constructed HUGR is invalid.
140    #[error("The constructed HUGR is invalid: {0}.")]
141    InvalidHUGR(#[from] ValidationError<Node>),
142    /// `SignatureError` in trying to construct a node (differs from
143    /// [`ValidationError::SignatureError`] in that we could not construct a node to report about)
144    #[error(transparent)]
145    SignatureError(#[from] SignatureError),
146    /// Tried to add a malformed [Const]
147    ///
148    /// [Const]: crate::ops::constant::Const
149    #[error("Constant failed typechecking: {0}")]
150    BadConstant(#[from] ConstTypeError),
151    /// CFG can only have one entry.
152    #[error("CFG entry node already built for CFG node: {0}.")]
153    EntryBuiltError(Node),
154    /// We don't allow creating `BasicBlockBuilder<Hugr>`s when the sum-rows
155    /// are not homogeneous. Use a `CFGBuilder` and create a valid graph instead.
156    #[error(
157        "Cannot initialize hugr for a BasicBlockBuilder with complex sum-rows. Use a CFGBuilder instead."
158    )]
159    BasicBlockTooComplex,
160    /// Node was expected to have a certain type but was found to not.
161    #[error("Node with index {node} does not have type {op_desc} as expected.")]
162    #[allow(missing_docs)]
163    UnexpectedType {
164        /// Index of node where error occurred.
165        node: Node,
166        /// Description of expected node.
167        op_desc: &'static str,
168    },
169    /// Error building Conditional node
170    #[error("Error building Conditional node: {0}.")]
171    ConditionalError(#[from] conditional::ConditionalBuildError),
172
173    /// Node not found in Hugr
174    #[error("{node} not found in the Hugr")]
175    NodeNotFound {
176        /// Missing node
177        node: Node,
178    },
179
180    /// Wire not found in Hugr
181    #[error("Wire not found in Hugr: {0}.")]
182    WireNotFound(Wire),
183
184    /// Error in `CircuitBuilder`
185    #[error("Error in CircuitBuilder: {0}.")]
186    CircuitError(#[from] circuit::CircuitBuildError),
187
188    /// Invalid wires when setting outputs
189    #[error("Found an error while setting the outputs of a {} container, {container_node}. {error}", .container_op.name())]
190    #[allow(missing_docs)]
191    OutputWiring {
192        container_op: Box<OpType>,
193        container_node: Node,
194        #[source]
195        error: BuilderWiringError,
196    },
197
198    /// Invalid input wires to a new operation
199    ///
200    /// The internal error message already contains the node index.
201    #[error("Got an input wire while adding a {} to the circuit. {error}", .op.name())]
202    #[allow(missing_docs)]
203    OperationWiring {
204        op: Box<OpType>,
205        #[source]
206        error: BuilderWiringError,
207    },
208
209    #[error("Failed to load an extension op: {0}")]
210    #[allow(missing_docs)]
211    ExtensionOp(#[from] OpLoadError),
212}
213
214#[derive(Debug, Clone, PartialEq, Error)]
215#[non_exhaustive]
216/// Error raised when wiring up a node during the build process.
217pub enum BuilderWiringError {
218    /// Tried to copy a linear type.
219    #[error("Cannot copy linear type {typ} from output {src_offset} of node {src}")]
220    #[allow(missing_docs)]
221    NoCopyLinear {
222        typ: Box<Type>,
223        src: Node,
224        src_offset: Port,
225    },
226    /// The ancestors of an inter-graph edge are not related.
227    #[error(
228        "Cannot connect an inter-graph edge between unrelated nodes. Tried connecting {src} ({src_offset}) with {dst} ({dst_offset})."
229    )]
230    #[allow(missing_docs)]
231    NoRelationIntergraph {
232        src: Node,
233        src_offset: Port,
234        dst: Node,
235        dst_offset: Port,
236    },
237    /// Inter-Graph edges can only carry copyable data.
238    #[error(
239        "Inter-graph edges cannot carry non-copyable data {typ}. Tried connecting {src} ({src_offset}) with {dst} ({dst_offset})."
240    )]
241    #[allow(missing_docs)]
242    NonCopyableIntergraph {
243        src: Node,
244        src_offset: Port,
245        dst: Node,
246        dst_offset: Port,
247        typ: Box<Type>,
248    },
249}
250
251#[cfg(test)]
252pub(crate) mod test {
253    use rstest::fixture;
254
255    use crate::Hugr;
256    use crate::extension::prelude::{bool_t, usize_t};
257    use crate::hugr::{HugrMut, views::HugrView};
258    use crate::ops;
259    use crate::package::Package;
260    use crate::types::{PolyFuncType, Signature};
261
262    use super::handle::BuildHandle;
263    use super::{
264        BuildError, CFGBuilder, DFGBuilder, Dataflow, DataflowHugr, FuncID, FunctionBuilder,
265        ModuleBuilder,
266    };
267    use super::{DataflowSubContainer, HugrBuilder};
268
269    /// Wire up inputs of a Dataflow container to the outputs.
270    pub(crate) fn n_identity<T: DataflowSubContainer>(
271        dataflow_builder: T,
272    ) -> Result<T::ContainerHandle, BuildError> {
273        let w = dataflow_builder.input_wires();
274        dataflow_builder.finish_with_outputs(w)
275    }
276
277    pub(crate) fn build_main(
278        signature: PolyFuncType,
279        f: impl FnOnce(FunctionBuilder<&mut Hugr>) -> Result<BuildHandle<FuncID<true>>, BuildError>,
280    ) -> Result<Hugr, BuildError> {
281        let mut module_builder = ModuleBuilder::new();
282        let f_builder = module_builder.define_function("main", signature)?;
283
284        f(f_builder)?;
285
286        Ok(module_builder.finish_hugr()?)
287    }
288
289    #[fixture]
290    pub(crate) fn simple_dfg_hugr() -> Hugr {
291        let dfg_builder = DFGBuilder::new(Signature::new(vec![bool_t()], vec![bool_t()])).unwrap();
292        let [i1] = dfg_builder.input_wires_arr();
293        dfg_builder.finish_hugr_with_outputs([i1]).unwrap()
294    }
295
296    #[fixture]
297    pub(crate) fn simple_funcdef_hugr() -> Hugr {
298        let fn_builder =
299            FunctionBuilder::new("test", Signature::new(vec![bool_t()], vec![bool_t()])).unwrap();
300        let [i1] = fn_builder.input_wires_arr();
301        fn_builder.finish_hugr_with_outputs([i1]).unwrap()
302    }
303
304    #[fixture]
305    pub(crate) fn simple_module_hugr() -> Hugr {
306        let mut builder = ModuleBuilder::new();
307        let sig = Signature::new(vec![bool_t()], vec![bool_t()]);
308        builder.declare("test", sig.into()).unwrap();
309        builder.finish_hugr().unwrap()
310    }
311
312    #[fixture]
313    pub(crate) fn simple_cfg_hugr() -> Hugr {
314        let mut cfg_builder =
315            CFGBuilder::new(Signature::new(vec![usize_t()], vec![usize_t()])).unwrap();
316        super::cfg::test::build_basic_cfg(&mut cfg_builder).unwrap();
317        cfg_builder.finish_hugr().unwrap()
318    }
319
320    #[fixture]
321    pub(crate) fn simple_package() -> Package {
322        let hugr = simple_module_hugr();
323        Package::new([hugr])
324    }
325
326    #[fixture]
327    pub(crate) fn multi_module_package() -> Package {
328        let hugr0 = simple_module_hugr();
329        let hugr1 = simple_module_hugr();
330        Package::new([hugr0, hugr1])
331    }
332
333    /// A helper method which creates a DFG rooted hugr with Input and Output node
334    /// only (no wires), given a function type with extension delta.
335    // TODO consider taking two type rows and using TO_BE_INFERRED
336    pub(crate) fn closed_dfg_root_hugr(signature: Signature) -> Hugr {
337        let mut hugr = Hugr::new_with_entrypoint(ops::DFG {
338            signature: signature.clone(),
339        })
340        .unwrap();
341        hugr.add_node_with_parent(
342            hugr.entrypoint(),
343            ops::Input {
344                types: signature.input,
345            },
346        );
347        hugr.add_node_with_parent(
348            hugr.entrypoint(),
349            ops::Output {
350                types: signature.output,
351            },
352        );
353        hugr
354    }
355}