Skip to main content

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([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::{NameLinkingError, 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    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    /// From [Dataflow::add_link_hugr_by_node_with_wires]
181    #[error("In inserting Hugr: {0}")]
182    HugrNodeLinkingError(#[from] NodeLinkingError<Node, Node>),
183
184    /// From [Dataflow::add_link_hugr_with_wires].
185    #[error{"In linking Hugr: {0}"}]
186    HugrLinkingError(#[from] NameLinkingError<Node, Node>),
187
188    /// From [Dataflow::add_link_view_by_node_with_wires] or
189    /// [Dataflow::add_link_view_with_wires].
190    /// Note that because the type of node in the [NodeLinkingError] or [NameLinkingError]
191    /// depends upon the view being inserted, we convert the error to a string here.
192    #[error("In inserting HugrView: {0}")]
193    HugrViewInsertionError(String),
194
195    /// Wire not found in Hugr
196    #[error("Wire not found in Hugr: {0}.")]
197    WireNotFound(Wire),
198
199    /// Error in `CircuitBuilder`
200    #[error("Error in CircuitBuilder: {0}.")]
201    CircuitError(#[from] circuit::CircuitBuildError),
202
203    /// Invalid wires when setting outputs
204    #[error("Found an error while setting the outputs of a {} container, {container_node}. {error}", .container_op.name())]
205    #[allow(missing_docs)]
206    OutputWiring {
207        container_op: Box<OpType>,
208        container_node: Node,
209        #[source]
210        error: BuilderWiringError,
211    },
212
213    /// Invalid input wires to a new operation
214    ///
215    /// The internal error message already contains the node index.
216    #[error("Got an input wire while adding a {} to the circuit. {error}", .op.name())]
217    #[allow(missing_docs)]
218    OperationWiring {
219        op: Box<OpType>,
220        #[source]
221        error: BuilderWiringError,
222    },
223
224    #[error("Failed to load an extension op: {0}")]
225    #[allow(missing_docs)]
226    ExtensionOp(#[from] OpLoadError),
227}
228
229#[derive(Debug, Clone, PartialEq, Error)]
230#[non_exhaustive]
231/// Error raised when wiring up a node during the build process.
232pub enum BuilderWiringError {
233    /// Tried to copy a linear type.
234    #[error("Cannot copy linear type {typ} from output {src_offset} of node {src}")]
235    #[allow(missing_docs)]
236    NoCopyLinear {
237        typ: Box<Type>,
238        src: Node,
239        src_offset: Port,
240    },
241    /// The ancestors of an inter-graph edge are not related.
242    #[error(
243        "Cannot connect an inter-graph edge between unrelated nodes. Tried connecting {src} ({src_offset}) with {dst} ({dst_offset})."
244    )]
245    #[allow(missing_docs)]
246    NoRelationIntergraph {
247        src: Node,
248        src_offset: Port,
249        dst: Node,
250        dst_offset: Port,
251    },
252    /// Inter-Graph edges can only carry copyable data.
253    #[error(
254        "Inter-graph edges cannot carry non-copyable data {typ}. Tried connecting {src} ({src_offset}) with {dst} ({dst_offset})."
255    )]
256    #[allow(missing_docs)]
257    NonCopyableIntergraph {
258        src: Node,
259        src_offset: Port,
260        dst: Node,
261        dst_offset: Port,
262        typ: Box<Type>,
263    },
264}
265
266#[cfg(test)]
267pub(crate) mod test {
268    use rstest::fixture;
269
270    use crate::Hugr;
271    use crate::extension::prelude::{bool_t, usize_t};
272    use crate::hugr::{HugrMut, views::HugrView};
273    use crate::ops;
274    use crate::package::Package;
275    use crate::types::{PolyFuncType, Signature};
276
277    use super::handle::BuildHandle;
278    use super::{
279        BuildError, CFGBuilder, DFGBuilder, Dataflow, DataflowHugr, FuncID, FunctionBuilder,
280        ModuleBuilder,
281    };
282    use super::{DataflowSubContainer, HugrBuilder};
283
284    /// Wire up inputs of a Dataflow container to the outputs.
285    pub(crate) fn n_identity<T: DataflowSubContainer>(
286        dataflow_builder: T,
287    ) -> Result<T::ContainerHandle, BuildError> {
288        let w = dataflow_builder.input_wires();
289        dataflow_builder.finish_with_outputs(w)
290    }
291
292    pub(crate) fn build_main(
293        signature: PolyFuncType,
294        f: impl FnOnce(FunctionBuilder<&mut Hugr>) -> Result<BuildHandle<FuncID<true>>, BuildError>,
295    ) -> Result<Hugr, BuildError> {
296        let mut module_builder = ModuleBuilder::new();
297        let f_builder = module_builder.define_function("main", signature)?;
298
299        f(f_builder)?;
300
301        Ok(module_builder.finish_hugr()?)
302    }
303
304    #[fixture]
305    pub(crate) fn simple_dfg_hugr() -> Hugr {
306        let dfg_builder = DFGBuilder::new(Signature::new(vec![bool_t()], vec![bool_t()])).unwrap();
307        let [i1] = dfg_builder.input_wires_arr();
308        dfg_builder.finish_hugr_with_outputs([i1]).unwrap()
309    }
310
311    #[fixture]
312    pub(crate) fn simple_funcdef_hugr() -> Hugr {
313        let fn_builder =
314            FunctionBuilder::new("test", Signature::new(vec![bool_t()], vec![bool_t()])).unwrap();
315        let [i1] = fn_builder.input_wires_arr();
316        fn_builder.finish_hugr_with_outputs([i1]).unwrap()
317    }
318
319    #[fixture]
320    pub(crate) fn simple_module_hugr() -> Hugr {
321        let mut builder = ModuleBuilder::new();
322        let sig = Signature::new(vec![bool_t()], vec![bool_t()]);
323        builder.declare("test", sig.into()).unwrap();
324        builder.finish_hugr().unwrap()
325    }
326
327    #[fixture]
328    pub(crate) fn simple_cfg_hugr() -> Hugr {
329        let mut cfg_builder =
330            CFGBuilder::new(Signature::new(vec![usize_t()], vec![usize_t()])).unwrap();
331        super::cfg::test::build_basic_cfg(&mut cfg_builder).unwrap();
332        cfg_builder.finish_hugr().unwrap()
333    }
334
335    #[fixture]
336    pub(crate) fn simple_package() -> Package {
337        let hugr = simple_module_hugr();
338        Package::new([hugr])
339    }
340
341    #[fixture]
342    pub(crate) fn multi_module_package() -> Package {
343        let hugr0 = simple_module_hugr();
344        let hugr1 = simple_module_hugr();
345        Package::new([hugr0, hugr1])
346    }
347
348    /// A helper method which creates a DFG rooted hugr with Input and Output node
349    /// only (no wires), given a function type with extension delta.
350    // TODO consider taking two type rows and using TO_BE_INFERRED
351    pub(crate) fn closed_dfg_root_hugr(signature: Signature) -> Hugr {
352        let mut hugr = Hugr::new_with_entrypoint(ops::DFG {
353            signature: signature.clone(),
354        })
355        .unwrap();
356        hugr.add_node_with_parent(
357            hugr.entrypoint(),
358            ops::Input {
359                types: signature.input,
360            },
361        );
362        hugr.add_node_with_parent(
363            hugr.entrypoint(),
364            ops::Output {
365                types: signature.output,
366            },
367        );
368        hugr
369    }
370
371    /// Builds a DFG-entrypoint Hugr (no inputs, one bool_t output) containing two calls,
372    /// to a FuncDefn and a FuncDecl each bool_t->bool_t.
373    /// Returns the Hugr and both function handles.
374    #[fixture]
375    pub(crate) fn dfg_calling_defn_decl() -> (Hugr, FuncID<true>, FuncID<false>) {
376        let mut dfb = DFGBuilder::new(Signature::new(vec![], [bool_t()])).unwrap();
377        let new_defn = {
378            let mut mb = dfb.module_root_builder();
379            let fb = mb
380                .define_function("helper_id", Signature::new_endo([bool_t()]))
381                .unwrap();
382            let [f_inp] = fb.input_wires_arr();
383            fb.finish_with_outputs([f_inp]).unwrap()
384        };
385        let new_decl = dfb
386            .module_root_builder()
387            .declare("helper2", Signature::new_endo([bool_t()]).into())
388            .unwrap();
389        let cst = dfb.add_load_value(ops::Value::true_val());
390        let [c1] = dfb
391            .call(new_defn.handle(), &[], [cst])
392            .unwrap()
393            .outputs_arr();
394        let [c2] = dfb.call(&new_decl, &[], [c1]).unwrap().outputs_arr();
395        (
396            dfb.finish_hugr_with_outputs([c2]).unwrap(),
397            *new_defn.handle(),
398            new_decl,
399        )
400    }
401}