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::hugr::linking::NodeLinkingError;
94use crate::ops::handle::{BasicBlockID, CfgID, ConditionalID, DfgID, FuncID, TailLoopID};
95use crate::ops::{NamedOp, OpType};
96use crate::types::Type;
97use crate::types::{ConstTypeError, Signature, TypeRow};
98use crate::{Node, Port, Wire};
99
100pub mod handle;
101pub use handle::BuildHandle;
102
103mod build_traits;
104pub use build_traits::{
105    Container, Dataflow, DataflowHugr, DataflowSubContainer, HugrBuilder, SubContainer,
106};
107
108pub mod dataflow;
109pub use dataflow::{DFGBuilder, DFGWrapper, FunctionBuilder};
110
111mod module;
112pub use module::ModuleBuilder;
113
114mod cfg;
115pub use cfg::{BlockBuilder, CFGBuilder};
116
117mod tail_loop;
118pub use tail_loop::TailLoopBuilder;
119
120mod conditional;
121pub use conditional::{CaseBuilder, ConditionalBuilder};
122
123mod circuit;
124pub use circuit::{CircuitBuildError, CircuitBuilder};
125
126/// Return a `FunctionType` with the same input and output types (specified).
127pub fn endo_sig(types: impl Into<TypeRow>) -> Signature {
128    Signature::new_endo(types)
129}
130
131/// Return a `FunctionType` with the specified input and output types.
132pub fn inout_sig(inputs: impl Into<TypeRow>, outputs: impl Into<TypeRow>) -> Signature {
133    Signature::new(inputs, outputs)
134}
135
136#[derive(Debug, Clone, PartialEq, Error)]
137#[non_exhaustive]
138/// Error while building the HUGR.
139pub enum BuildError {
140    /// The constructed HUGR is invalid.
141    #[error("The constructed HUGR is invalid: {0}.")]
142    InvalidHUGR(#[from] ValidationError<Node>),
143    /// `SignatureError` in trying to construct a node (differs from
144    /// [`ValidationError::SignatureError`] in that we could not construct a node to report about)
145    #[error(transparent)]
146    SignatureError(#[from] SignatureError),
147    /// Tried to add a malformed [Const]
148    ///
149    /// [Const]: crate::ops::constant::Const
150    #[error("Constant failed typechecking: {0}")]
151    BadConstant(#[from] ConstTypeError),
152    /// CFG can only have one entry.
153    #[error("CFG entry node already built for CFG node: {0}.")]
154    EntryBuiltError(Node),
155    /// We don't allow creating `BasicBlockBuilder<Hugr>`s when the sum-rows
156    /// are not homogeneous. Use a `CFGBuilder` and create a valid graph instead.
157    #[error(
158        "Cannot initialize hugr for a BasicBlockBuilder with complex sum-rows. Use a CFGBuilder instead."
159    )]
160    BasicBlockTooComplex,
161    /// Node was expected to have a certain type but was found to not.
162    #[error("Node with index {node} does not have type {op_desc} as expected.")]
163    #[allow(missing_docs)]
164    UnexpectedType {
165        /// Index of node where error occurred.
166        node: Node,
167        /// Description of expected node.
168        op_desc: &'static str,
169    },
170    /// Error building Conditional node
171    #[error("Error building Conditional node: {0}.")]
172    ConditionalError(#[from] conditional::ConditionalBuildError),
173
174    /// Node not found in Hugr
175    #[error("{node} not found in the Hugr")]
176    NodeNotFound {
177        /// Missing node
178        node: Node,
179    },
180
181    /// From [Dataflow::add_link_hugr_by_node_with_wires]
182    #[error{"In inserting Hugr: {0}"}]
183    HugrInsertionError(#[from] NodeLinkingError<Node, Node>),
184
185    /// From [Dataflow::add_link_view_by_node_with_wires].
186    /// Note that because the type of node in the [NodeLinkingError] depends
187    /// upon the view being inserted, we convert the error to a string here.
188    #[error("In inserting HugrView: {0}")]
189    HugrViewInsertionError(String),
190
191    /// Wire not found in Hugr
192    #[error("Wire not found in Hugr: {0}.")]
193    WireNotFound(Wire),
194
195    /// Error in `CircuitBuilder`
196    #[error("Error in CircuitBuilder: {0}.")]
197    CircuitError(#[from] circuit::CircuitBuildError),
198
199    /// Invalid wires when setting outputs
200    #[error("Found an error while setting the outputs of a {} container, {container_node}. {error}", .container_op.name())]
201    #[allow(missing_docs)]
202    OutputWiring {
203        container_op: Box<OpType>,
204        container_node: Node,
205        #[source]
206        error: BuilderWiringError,
207    },
208
209    /// Invalid input wires to a new operation
210    ///
211    /// The internal error message already contains the node index.
212    #[error("Got an input wire while adding a {} to the circuit. {error}", .op.name())]
213    #[allow(missing_docs)]
214    OperationWiring {
215        op: Box<OpType>,
216        #[source]
217        error: BuilderWiringError,
218    },
219
220    #[error("Failed to load an extension op: {0}")]
221    #[allow(missing_docs)]
222    ExtensionOp(#[from] OpLoadError),
223}
224
225#[derive(Debug, Clone, PartialEq, Error)]
226#[non_exhaustive]
227/// Error raised when wiring up a node during the build process.
228pub enum BuilderWiringError {
229    /// Tried to copy a linear type.
230    #[error("Cannot copy linear type {typ} from output {src_offset} of node {src}")]
231    #[allow(missing_docs)]
232    NoCopyLinear {
233        typ: Box<Type>,
234        src: Node,
235        src_offset: Port,
236    },
237    /// The ancestors of an inter-graph edge are not related.
238    #[error(
239        "Cannot connect an inter-graph edge between unrelated nodes. Tried connecting {src} ({src_offset}) with {dst} ({dst_offset})."
240    )]
241    #[allow(missing_docs)]
242    NoRelationIntergraph {
243        src: Node,
244        src_offset: Port,
245        dst: Node,
246        dst_offset: Port,
247    },
248    /// Inter-Graph edges can only carry copyable data.
249    #[error(
250        "Inter-graph edges cannot carry non-copyable data {typ}. Tried connecting {src} ({src_offset}) with {dst} ({dst_offset})."
251    )]
252    #[allow(missing_docs)]
253    NonCopyableIntergraph {
254        src: Node,
255        src_offset: Port,
256        dst: Node,
257        dst_offset: Port,
258        typ: Box<Type>,
259    },
260}
261
262#[cfg(test)]
263pub(crate) mod test {
264    use rstest::fixture;
265
266    use crate::Hugr;
267    use crate::extension::prelude::{bool_t, usize_t};
268    use crate::hugr::{HugrMut, views::HugrView};
269    use crate::ops;
270    use crate::package::Package;
271    use crate::types::{PolyFuncType, Signature};
272
273    use super::handle::BuildHandle;
274    use super::{
275        BuildError, CFGBuilder, DFGBuilder, Dataflow, DataflowHugr, FuncID, FunctionBuilder,
276        ModuleBuilder,
277    };
278    use super::{DataflowSubContainer, HugrBuilder};
279
280    /// Wire up inputs of a Dataflow container to the outputs.
281    pub(crate) fn n_identity<T: DataflowSubContainer>(
282        dataflow_builder: T,
283    ) -> Result<T::ContainerHandle, BuildError> {
284        let w = dataflow_builder.input_wires();
285        dataflow_builder.finish_with_outputs(w)
286    }
287
288    pub(crate) fn build_main(
289        signature: PolyFuncType,
290        f: impl FnOnce(FunctionBuilder<&mut Hugr>) -> Result<BuildHandle<FuncID<true>>, BuildError>,
291    ) -> Result<Hugr, BuildError> {
292        let mut module_builder = ModuleBuilder::new();
293        let f_builder = module_builder.define_function("main", signature)?;
294
295        f(f_builder)?;
296
297        Ok(module_builder.finish_hugr()?)
298    }
299
300    #[fixture]
301    pub(crate) fn simple_dfg_hugr() -> Hugr {
302        let dfg_builder = DFGBuilder::new(Signature::new(vec![bool_t()], vec![bool_t()])).unwrap();
303        let [i1] = dfg_builder.input_wires_arr();
304        dfg_builder.finish_hugr_with_outputs([i1]).unwrap()
305    }
306
307    #[fixture]
308    pub(crate) fn simple_funcdef_hugr() -> Hugr {
309        let fn_builder =
310            FunctionBuilder::new("test", Signature::new(vec![bool_t()], vec![bool_t()])).unwrap();
311        let [i1] = fn_builder.input_wires_arr();
312        fn_builder.finish_hugr_with_outputs([i1]).unwrap()
313    }
314
315    #[fixture]
316    pub(crate) fn simple_module_hugr() -> Hugr {
317        let mut builder = ModuleBuilder::new();
318        let sig = Signature::new(vec![bool_t()], vec![bool_t()]);
319        builder.declare("test", sig.into()).unwrap();
320        builder.finish_hugr().unwrap()
321    }
322
323    #[fixture]
324    pub(crate) fn simple_cfg_hugr() -> Hugr {
325        let mut cfg_builder =
326            CFGBuilder::new(Signature::new(vec![usize_t()], vec![usize_t()])).unwrap();
327        super::cfg::test::build_basic_cfg(&mut cfg_builder).unwrap();
328        cfg_builder.finish_hugr().unwrap()
329    }
330
331    #[fixture]
332    pub(crate) fn simple_package() -> Package {
333        let hugr = simple_module_hugr();
334        Package::new([hugr])
335    }
336
337    #[fixture]
338    pub(crate) fn multi_module_package() -> Package {
339        let hugr0 = simple_module_hugr();
340        let hugr1 = simple_module_hugr();
341        Package::new([hugr0, hugr1])
342    }
343
344    /// A helper method which creates a DFG rooted hugr with Input and Output node
345    /// only (no wires), given a function type with extension delta.
346    // TODO consider taking two type rows and using TO_BE_INFERRED
347    pub(crate) fn closed_dfg_root_hugr(signature: Signature) -> Hugr {
348        let mut hugr = Hugr::new_with_entrypoint(ops::DFG {
349            signature: signature.clone(),
350        })
351        .unwrap();
352        hugr.add_node_with_parent(
353            hugr.entrypoint(),
354            ops::Input {
355                types: signature.input,
356            },
357        );
358        hugr.add_node_with_parent(
359            hugr.entrypoint(),
360            ops::Output {
361                types: signature.output,
362            },
363        );
364        hugr
365    }
366
367    /// Builds a DFG-entrypoint Hugr (no inputs, one bool_t output) containing two calls,
368    /// to a FuncDefn and a FuncDecl each bool_t->bool_t.
369    /// Returns the Hugr and both function handles.
370    #[fixture]
371    pub(crate) fn dfg_calling_defn_decl() -> (Hugr, FuncID<true>, FuncID<false>) {
372        let mut dfb = DFGBuilder::new(Signature::new(vec![], bool_t())).unwrap();
373        let new_defn = {
374            let mut mb = dfb.module_root_builder();
375            let fb = mb
376                .define_function("helper_id", Signature::new_endo(bool_t()))
377                .unwrap();
378            let [f_inp] = fb.input_wires_arr();
379            fb.finish_with_outputs([f_inp]).unwrap()
380        };
381        let new_decl = dfb
382            .module_root_builder()
383            .declare("helper2", Signature::new_endo(bool_t()).into())
384            .unwrap();
385        let cst = dfb.add_load_value(ops::Value::true_val());
386        let [c1] = dfb
387            .call(new_defn.handle(), &[], [cst])
388            .unwrap()
389            .outputs_arr();
390        let [c2] = dfb.call(&new_decl, &[], [c1]).unwrap().outputs_arr();
391        (
392            dfb.finish_hugr_with_outputs([c2]).unwrap(),
393            *new_defn.handle(),
394            new_decl,
395        )
396    }
397}