Skip to content

Commit 99b9e44

Browse files
committed
Non-panicking Hugr::new and Hugr::new_with_entrypoint
1 parent c2cbaeb commit 99b9e44

File tree

14 files changed

+96
-59
lines changed

14 files changed

+96
-59
lines changed

hugr-core/src/builder.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,9 +328,10 @@ pub(crate) mod test {
328328
/// only (no wires), given a function type with extension delta.
329329
// TODO consider taking two type rows and using TO_BE_INFERRED
330330
pub(crate) fn closed_dfg_root_hugr(signature: Signature) -> Hugr {
331-
let mut hugr = Hugr::new(ops::DFG {
331+
let mut hugr = Hugr::new_with_entrypoint(ops::DFG {
332332
signature: signature.clone(),
333-
});
333+
})
334+
.unwrap();
334335
hugr.add_node_with_parent(
335336
hugr.entrypoint(),
336337
ops::Input {

hugr-core/src/builder/cfg.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ impl CFGBuilder<Hugr> {
148148
signature: signature.clone(),
149149
};
150150

151-
let base = Hugr::new(cfg_op);
151+
let base = Hugr::new_with_entrypoint(cfg_op).expect("CFG entrypoints be valid");
152152
let cfg_node = base.entrypoint();
153153
CFGBuilder::create(base, cfg_node, signature.input, signature.output)
154154
}

hugr-core/src/builder/conditional.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::hugr::views::HugrView;
22
use crate::types::{Signature, TypeRow};
33

44
use crate::ops;
5-
use crate::ops::handle::CaseID;
5+
use crate::ops::handle::{CaseID, NodeHandle};
66

77
use super::build_traits::SubContainer;
88
use super::handle::BuildHandle;
@@ -153,7 +153,7 @@ impl ConditionalBuilder<Hugr> {
153153
) -> Result<Self, BuildError> {
154154
let sum_rows: Vec<_> = sum_rows.into_iter().collect();
155155
let other_inputs = other_inputs.into();
156-
let outputs = outputs.into();
156+
let outputs: TypeRow = outputs.into();
157157

158158
let n_out_wires = outputs.len();
159159
let n_cases = sum_rows.len();
@@ -163,7 +163,7 @@ impl ConditionalBuilder<Hugr> {
163163
other_inputs,
164164
outputs,
165165
};
166-
let base = Hugr::new(op);
166+
let base = Hugr::new_with_entrypoint(op).expect("Conditional entrypoint should be valid");
167167
let conditional_node = base.entrypoint();
168168

169169
Ok(ConditionalBuilder {
@@ -178,13 +178,15 @@ impl ConditionalBuilder<Hugr> {
178178
impl CaseBuilder<Hugr> {
179179
/// Initialize a Case rooted HUGR
180180
pub fn new(signature: Signature) -> Result<Self, BuildError> {
181-
let op = ops::Case {
182-
signature: signature.clone(),
183-
};
184-
let base = Hugr::new(op);
185-
let root = base.entrypoint();
186-
let dfg_builder = DFGBuilder::create_with_io(base, root, signature)?;
187-
181+
// Start by building a conditional with a single case
182+
let mut conditional =
183+
ConditionalBuilder::new([signature.input.clone()], vec![], signature.output.clone())?;
184+
let case = conditional.case_builder(0)?.finish_sub_container()?.node();
185+
186+
// Extract the half-finished hugr, and wrap it in an owned case builder
187+
let mut base = std::mem::take(conditional.hugr_mut());
188+
base.set_entrypoint(case);
189+
let dfg_builder = DFGBuilder::create(base, case)?;
188190
Ok(CaseBuilder::from_dfg_builder(dfg_builder))
189191
}
190192
}

hugr-core/src/builder/dataflow.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ impl DFGBuilder<Hugr> {
9191
let dfg_op = ops::DFG {
9292
signature: signature.clone(),
9393
};
94-
let base = Hugr::new(dfg_op);
94+
let base = Hugr::new_with_entrypoint(dfg_op).expect("DFG entrypoint should be valid");
9595
let root = base.entrypoint();
9696
DFGBuilder::create_with_io(base, root, signature)
9797
}
@@ -166,7 +166,7 @@ impl FunctionBuilder<Hugr> {
166166
name: name.into(),
167167
};
168168

169-
let base = Hugr::new(op);
169+
let base = Hugr::new_with_entrypoint(op).expect("FuncDefn entrypoint should be valid");
170170
let root = base.entrypoint();
171171

172172
let db = DFGBuilder::create_with_io(base, root, body)?;

hugr-core/src/builder/tail_loop.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ impl TailLoopBuilder<Hugr> {
8181
just_outputs: just_outputs.into(),
8282
rest: inputs_outputs.into(),
8383
};
84-
let base = Hugr::new(tail_loop.clone());
84+
let base = Hugr::new_with_entrypoint(tail_loop.clone())
85+
.expect("tail_loop entrypoint should be valid");
8586
let root = base.entrypoint();
8687
Self::create_with_io(base, root, &tail_loop)
8788
}

hugr-core/src/hugr.rs

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use crate::extension::resolution::{
3030
WeakExtensionRegistry,
3131
};
3232
use crate::extension::{ExtensionRegistry, ExtensionSet};
33-
use crate::ops::{self, NamedOp, OpTag, OpTrait};
33+
use crate::ops::{self, Module, NamedOp, OpName, OpTag, OpTrait};
3434
pub use crate::ops::{OpType, DEFAULT_OPTYPE};
3535
use crate::{Direction, Node};
3636

@@ -63,7 +63,7 @@ pub struct Hugr {
6363

6464
impl Default for Hugr {
6565
fn default() -> Self {
66-
Self::new(crate::ops::Module::new())
66+
Self::new()
6767
}
6868
}
6969

@@ -89,19 +89,50 @@ pub type NodeMetadataMap = serde_json::Map<String, NodeMetadata>;
8989

9090
/// Public API for HUGRs.
9191
impl Hugr {
92-
/// Create a new Hugr, with a single root node.
93-
pub fn new(root: impl Into<OpType>) -> Self {
94-
make_module_hugr(root.into(), 0, 0)
92+
/// Create a new Hugr, with a single [`Module`] operation as the root node.
93+
pub fn new() -> Self {
94+
make_module_hugr(Module::new().into(), 0, 0).unwrap()
9595
}
9696

97-
/// Create a new Hugr, with a given root node and preallocated capacity.
97+
/// Create a new Hugr, with a given entrypoint operation.
9898
///
99-
/// If the root optype is [`OpType::Module`], the HUGR module root will
100-
/// match the root node. Otherwise, the root node will be a child of the a
101-
/// module created at the node hierarchy root. The specific HUGR created depends
102-
/// on the operation type, and whether it can be contained
103-
pub fn with_capacity(root: impl Into<OpType>, nodes: usize, ports: usize) -> Self {
104-
make_module_hugr(root.into(), nodes, ports)
99+
/// If the optype is [`OpType::Module`], the HUGR module root will match the
100+
/// entrypoint node. Otherwise, the entrypoint will be a child of the a
101+
/// module initialized at the node hierarchy root. The specific HUGR created
102+
/// depends on the operation type.
103+
///
104+
/// # Error
105+
///
106+
/// Returns [`HugrError::UnsupportedEntrypoint`] if the entrypoint operation
107+
/// requires additional context to be defined. This is the case for
108+
/// [`OpType::Case`], [`OpType::DataflowBlock`], and [`OpType::ExitBlock`]
109+
/// since they are context-specific definitions.
110+
pub fn new_with_entrypoint(entrypoint_op: impl Into<OpType>) -> Result<Self, HugrError> {
111+
Self::with_capacity(entrypoint_op, 0, 0)
112+
}
113+
114+
/// Create a new Hugr, with a given entrypoint operation and preallocated capacity.
115+
///
116+
/// If the optype is [`OpType::Module`], the HUGR module root will match the
117+
/// entrypoint node. Otherwise, the entrypoint will be a child of the a
118+
/// module initialized at the node hierarchy root. The specific HUGR created
119+
/// depends on the operation type.
120+
///
121+
/// # Error
122+
///
123+
/// Returns [`HugrError::UnsupportedEntrypoint`] if the entrypoint operation
124+
/// requires additional context to be defined. This is the case for
125+
/// [`OpType::Case`], [`OpType::DataflowBlock`], and [`OpType::ExitBlock`]
126+
/// since they are context-specific definitions.
127+
pub fn with_capacity(
128+
entrypoint_op: impl Into<OpType>,
129+
nodes: usize,
130+
ports: usize,
131+
) -> Result<Self, HugrError> {
132+
let entrypoint_op: OpType = entrypoint_op.into();
133+
let op_name = entrypoint_op.name();
134+
make_module_hugr(entrypoint_op, nodes, ports)
135+
.ok_or(HugrError::UnsupportedEntrypoint { op: op_name })
105136
}
106137

107138
/// Load a Hugr from a json reader.
@@ -289,6 +320,12 @@ pub enum HugrError {
289320
/// An invalid port was specified.
290321
#[error("Invalid port direction {0:?}.")]
291322
InvalidPortDirection(Direction),
323+
/// Cannot initialize a HUGR with the given entrypoint operation type.
324+
#[error("Cannot initialize a HUGR with entrypoint type {op}")]
325+
UnsupportedEntrypoint {
326+
/// The name of the unsupported operation.
327+
op: OpName,
328+
},
292329
}
293330

294331
/// Errors that can occur while loading and validating a Hugr json.
@@ -321,7 +358,7 @@ pub enum LoadHugrError {
321358
/// Some operation types are not allowed and will result in a panic. This is the
322359
/// case for [`OpType::Case`], [`OpType::DataflowBlock`], and [`OpType::ExitBlock`]
323360
/// since they are context-specific operation.
324-
fn make_module_hugr(root_op: OpType, nodes: usize, ports: usize) -> Hugr {
361+
fn make_module_hugr(root_op: OpType, nodes: usize, ports: usize) -> Option<Hugr> {
325362
let mut graph = MultiPortGraph::with_capacity(nodes, ports);
326363
let hierarchy = Hierarchy::new();
327364
let mut op_types = UnmanagedDenseMap::with_capacity(nodes);
@@ -395,28 +432,18 @@ fn make_module_hugr(root_op: OpType, nodes: usize, ports: usize) -> Hugr {
395432
}
396433
// Other more exotic ops are unsupported, and will cause a panic.
397434
else {
398-
let is_expected = matches!(
435+
debug_assert!(matches!(
399436
root_op,
400437
OpType::Input(_)
401438
| OpType::Output(_)
402439
| OpType::DataflowBlock(_)
403440
| OpType::ExitBlock(_)
404441
| OpType::Case(_)
405-
);
406-
if is_expected {
407-
panic!(
408-
"Operation type {} is not allowed as a root type of a Hugr being initialized",
409-
root_op.name()
410-
)
411-
} else {
412-
panic!(
413-
"Operation type {} should have been allowed in `make_module_hugr`",
414-
root_op.name()
415-
)
416-
}
442+
));
443+
return None;
417444
}
418445

419-
hugr
446+
Some(hugr)
420447
}
421448

422449
#[cfg(test)]

hugr-core/src/hugr/patch/replace.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -603,9 +603,10 @@ mod test {
603603
// Replacement: one BB with two DFGs inside.
604604
// Use Hugr rather than Builder because it must be empty (not even
605605
// Input/Output).
606-
let mut replacement = Hugr::new(ops::CFG {
606+
let mut replacement = Hugr::new_with_entrypoint(ops::CFG {
607607
signature: Signature::new_endo(just_list.clone()),
608-
});
608+
})
609+
.expect("CFG is a valid entrypoint");
609610
let r_bb = replacement.add_node_with_parent(
610611
replacement.entrypoint(),
611612
DataflowBlock {
@@ -761,7 +762,7 @@ mod test {
761762
let cond = cond.finish_sub_container().unwrap();
762763
let h = h.finish_hugr_with_outputs(cond.outputs()).unwrap();
763764

764-
let mut r_hugr = Hugr::new(h.get_optype(cond.node()).clone());
765+
let mut r_hugr = Hugr::new_with_entrypoint(h.get_optype(cond.node()).clone()).unwrap();
765766
let r1 = r_hugr.add_node_with_parent(
766767
r_hugr.entrypoint(),
767768
Case {

hugr-core/src/hugr/serialize.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use serde::{Deserialize, Deserializer, Serialize};
1616

1717
use self::upgrade::UpgradeError;
1818

19-
use super::{HugrMut, HugrView, NodeMetadataMap};
19+
use super::{HugrError, HugrMut, HugrView, NodeMetadataMap};
2020

2121
mod upgrade;
2222

@@ -133,6 +133,9 @@ pub enum HUGRSerializationError {
133133
/// First node in node list must be the HUGR root.
134134
#[error("The first node in the node list has parent {0}, should be itself (index 0)")]
135135
FirstNodeNotRoot(Node),
136+
/// Failed to deserialize the HUGR.
137+
#[error(transparent)]
138+
HugrError(#[from] HugrError),
136139
}
137140

138141
impl Serialize for Hugr {
@@ -243,7 +246,7 @@ impl TryFrom<SerHugrLatest> for Hugr {
243246
}
244247
// if there are any unconnected ports or copy nodes the capacity will be
245248
// an underestimate
246-
let mut hugr = Hugr::with_capacity(root_type, nodes.len(), edges.len() * 2);
249+
let mut hugr = Hugr::with_capacity(root_type, nodes.len(), edges.len() * 2)?;
247250

248251
// Since the new Hugr may add some nodes to contain the root (if the
249252
// encoded file did not have a module at the root), we need a function

hugr-core/src/hugr/validate/test.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ fn add_df_children(b: &mut Hugr, parent: Node, copies: usize) -> (Node, Node, No
6464

6565
#[test]
6666
fn invalid_root() {
67-
let mut b = Hugr::new(LogicOp::Not);
67+
let mut b = Hugr::new_with_entrypoint(LogicOp::Not).unwrap();
6868
let root = b.module_root();
6969
assert_eq!(b.validate(), Ok(()));
7070

@@ -99,7 +99,7 @@ fn invalid_root() {
9999
fn leaf_root() {
100100
let leaf_op: OpType = Noop(usize_t()).into();
101101

102-
let b = Hugr::new(leaf_op);
102+
let b = Hugr::new_with_entrypoint(leaf_op).unwrap();
103103
assert_eq!(b.validate(), Ok(()));
104104
}
105105

@@ -110,7 +110,7 @@ fn dfg_root() {
110110
}
111111
.into();
112112

113-
let mut b = Hugr::new(dfg_op);
113+
let mut b = Hugr::new_with_entrypoint(dfg_op).unwrap();
114114
let root = b.entrypoint();
115115
add_df_children(&mut b, root, 1);
116116
assert_eq!(b.validate(), Ok(()));

hugr-core/src/hugr/views.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,7 @@ impl HugrView for Hugr {
681681
}
682682

683683
let new_entrypoint_op = self.entrypoint_optype().clone();
684-
let mut extracted = Hugr::new(new_entrypoint_op);
684+
let mut extracted = Hugr::new_with_entrypoint(new_entrypoint_op).unwrap(); // TODO: Handle error
685685

686686
let old_entrypoint = extracted.entrypoint();
687687
let old_parent = extracted.get_parent(old_entrypoint);

0 commit comments

Comments
 (0)