use std::borrow::Cow;
use std::fs::File;
use std::io::BufReader;
use std::sync::Arc;
use cool_asserts::assert_matches;
use rstest::rstest;
use super::*;
use crate::builder::test::closed_dfg_root_hugr;
use crate::builder::{
BuildError, Container, DFGBuilder, Dataflow, DataflowHugr, DataflowSubContainer,
FunctionBuilder, HugrBuilder, ModuleBuilder, inout_sig,
};
use crate::extension::prelude::Noop;
use crate::extension::prelude::{bool_t, qb_t, usize_t};
use crate::extension::{Extension, ExtensionRegistry, PRELUDE, TypeDefBound};
use crate::hugr::HugrMut;
use crate::hugr::internal::HugrMutInternals;
use crate::ops::dataflow::{DataflowParent, IOTrait};
use crate::ops::handle::NodeHandle;
use crate::ops::{self, FuncDecl, FuncDefn, OpType, Value};
use crate::std_extensions::logic::LogicOp;
use crate::std_extensions::logic::test::{and_op, or_op};
use crate::types::type_param::{TermTypeError, TypeArg};
use crate::types::{
CustomType, FuncValueType, PolyFuncType, PolyFuncTypeRV, Signature, Term, Type, TypeBound,
TypeRV, TypeRow,
};
use crate::{Direction, Hugr, IncomingPort, Node, const_extension_ids, test_file, type_row};
fn make_simple_hugr(copies: usize) -> (Hugr, Node) {
let def_op: OpType = FuncDefn::new_vis(
"main",
Signature::new([bool_t()], vec![bool_t(); copies]),
Visibility::Public,
)
.into();
let mut b = Hugr::default();
let root = b.entrypoint();
let def = b.add_node_with_parent(root, def_op);
let _ = add_df_children(&mut b, def, copies);
(b, def)
}
fn add_df_children(b: &mut Hugr, parent: Node, copies: usize) -> (Node, Node, Node) {
let input = b.add_node_with_parent(parent, ops::Input::new(vec![bool_t()]));
let output = b.add_node_with_parent(parent, ops::Output::new(vec![bool_t(); copies]));
let copy = b.add_node_with_parent(parent, Noop(bool_t()));
b.connect(input, 0, copy, 0);
for i in 0..copies {
b.connect(copy, 0, output, i);
}
(input, copy, output)
}
#[test]
fn invalid_root() {
let build = DFGBuilder::new(Signature::default()).unwrap();
let mut b = build.finish_hugr().unwrap();
let root = b.module_root();
assert_eq!(b.validate(), Ok(()));
b.set_num_ports(root, 1, 0);
assert_matches!(
b.validate(),
Err(ValidationError::WrongNumberOfPorts { node, .. }) => assert_eq!(node, root)
);
b.set_num_ports(root, 0, 0);
let module = b.add_node(ops::Module::new().into());
assert_matches!(
b.validate(),
Err(ValidationError::NoParent { node }) => assert_eq!(node, module)
);
b.set_parent(root, module);
assert_matches!(
b.validate(),
Err(ValidationError::NoParent { node }) => assert_eq!(node, module)
);
b.remove_node(module);
assert_eq!(b.validate(), Ok(()));
}
#[test]
fn dfg_root() {
let dfg_op: OpType = ops::DFG {
signature: Signature::new_endo([bool_t()]),
}
.into();
let mut b = Hugr::new_with_entrypoint(dfg_op).unwrap();
let root = b.entrypoint();
add_df_children(&mut b, root, 1);
assert_eq!(b.validate(), Ok(()));
}
#[test]
fn simple_hugr() {
let b = make_simple_hugr(2).0;
assert_eq!(b.validate(), Ok(()));
}
#[test]
fn children_restrictions() {
let (mut b, def) = make_simple_hugr(2);
let root = b.entrypoint();
let (_input, copy, _output) = b
.hierarchy
.children(def.into_portgraph())
.map_into()
.collect_tuple()
.unwrap();
let def_sig = Signature::new(vec![bool_t()], vec![bool_t(), bool_t()]);
let new_def = b.add_node_with_parent(root, FuncDefn::new("main", def_sig));
assert_matches!(
b.validate(),
Err(ValidationError::ContainerWithoutChildren { node, .. }) => assert_eq!(node, new_def)
);
add_df_children(&mut b, new_def, 2);
b.set_parent(new_def, copy);
assert_matches!(
b.validate(),
Err(ValidationError::NonContainerWithChildren { node, .. }) => assert_eq!(node, copy)
);
b.set_parent(new_def, root);
let new_input = b.add_node_with_parent(root, ops::Input::new(type_row![]));
assert_matches!(
b.validate(),
Err(ValidationError::InvalidParentOp { parent, child, .. }) => {assert_eq!(parent, root); assert_eq!(child, new_input)}
);
}
#[test]
fn df_children_restrictions() {
let (mut b, def) = make_simple_hugr(2);
let (_input, output, copy) = b
.hierarchy
.children(def.into_portgraph())
.map_into()
.collect_tuple()
.unwrap();
b.replace_op(output, Noop(usize_t()));
assert_matches!(
b.validate(),
Err(ValidationError::InvalidInitialChild { parent, .. }) => assert_eq!(parent, def)
);
b.replace_op(output, ops::Output::new(vec![bool_t()]));
assert_matches!(
b.validate(),
Err(ValidationError::InvalidChildren { parent, source: ChildrenValidationError::IOSignatureMismatch { child, .. }, .. })
=> {assert_eq!(parent, def); assert_eq!(child, output)}
);
b.replace_op(output, ops::Output::new(vec![bool_t(), bool_t()]));
b.replace_op(copy, ops::Output::new(vec![bool_t(), bool_t()]));
assert_matches!(
b.validate(),
Err(ValidationError::InvalidChildren { parent, source: ChildrenValidationError::InternalIOChildren { child, .. }, .. })
=> {assert_eq!(parent, def); assert_eq!(child, copy)}
);
}
#[test]
fn test_ext_edge() {
let mut h = closed_dfg_root_hugr(Signature::new(vec![bool_t(), bool_t()], vec![bool_t()]));
let [input, output] = h.get_io(h.entrypoint()).unwrap();
let sub_dfg = h.add_node_with_parent(
h.entrypoint(),
ops::DFG {
signature: Signature::new_endo([bool_t()]),
},
);
let sub_op = {
let sub_input = h.add_node_with_parent(sub_dfg, ops::Input::new(vec![bool_t()]));
let sub_output = h.add_node_with_parent(sub_dfg, ops::Output::new(vec![bool_t()]));
let sub_op = h.add_node_with_parent(sub_dfg, and_op());
h.connect(sub_input, 0, sub_op, 0);
h.connect(sub_op, 0, sub_output, 0);
sub_op
};
h.connect(input, 0, sub_dfg, 0);
h.connect(sub_dfg, 0, output, 0);
assert_matches!(h.validate(), Err(ValidationError::UnconnectedPort { .. }));
h.connect(input, 1, sub_op, 1);
assert_matches!(
h.validate(),
Err(ValidationError::InterGraphEdgeError(
InterGraphEdgeError::MissingOrderEdge { .. }
))
);
h.add_other_edge(input, sub_dfg);
h.validate().unwrap();
}
#[test]
fn test_local_const() {
let mut h = closed_dfg_root_hugr(Signature::new_endo([bool_t()]));
let [input, output] = h.get_io(h.entrypoint()).unwrap();
let and = h.add_node_with_parent(h.entrypoint(), and_op());
h.connect(input, 0, and, 0);
h.connect(and, 0, output, 0);
assert_eq!(
h.validate(),
Err(ValidationError::UnconnectedPort {
node: and,
port: IncomingPort::from(1).into(),
port_kind: Box::new(EdgeKind::Value(bool_t()))
})
);
let const_op: ops::Const = ops::Value::from_bool(true).into();
let cst = h.add_node_with_parent(h.entrypoint(), const_op);
let lcst = h.add_node_with_parent(h.entrypoint(), ops::LoadConstant { datatype: bool_t() });
h.connect(cst, 0, lcst, 0);
h.connect(lcst, 0, and, 1);
assert_eq!(h.static_source(lcst), Some(cst));
h.validate().unwrap();
}
#[test]
fn dfg_with_cycles() {
let mut h = closed_dfg_root_hugr(Signature::new(vec![bool_t(), bool_t()], vec![bool_t()]));
let [input, output] = h.get_io(h.entrypoint()).unwrap();
let or = h.add_node_with_parent(h.entrypoint(), or_op());
let not1 = h.add_node_with_parent(h.entrypoint(), LogicOp::Not);
let not2 = h.add_node_with_parent(h.entrypoint(), LogicOp::Not);
h.connect(input, 0, or, 0);
h.connect(or, 0, not1, 0);
h.connect(not1, 0, or, 1);
h.connect(input, 1, not2, 0);
h.connect(not2, 0, output, 0);
assert_matches!(h.validate(), Err(ValidationError::NotADag { .. }));
}
fn identity_hugr_with_type(t: Type) -> (Hugr, Node) {
let mut b = Hugr::default();
let row: TypeRow = vec![t].into();
let def = b.add_node_with_parent(
b.entrypoint(),
FuncDefn::new("main", Signature::new_endo(row.clone())),
);
let input = b.add_node_with_parent(def, ops::Input::new(row.clone()));
let output = b.add_node_with_parent(def, ops::Output::new(row));
b.connect(input, 0, output, 0);
(b, def)
}
const_extension_ids! {
const EXT_ID: ExtensionId = "MyExt";
}
#[test]
fn invalid_types() {
let ext = Extension::new_test_arc(EXT_ID, |ext, extension_ref| {
ext.add_type(
"MyContainer".into(),
vec![TypeBound::Copyable.into()],
String::new(),
TypeDefBound::any(),
extension_ref,
)
.unwrap();
});
let reg = ExtensionRegistry::new([ext.clone(), PRELUDE.to_owned()]);
reg.validate().unwrap();
let validate_to_sig_error = |t: CustomType| -> SignatureError {
let (mut h, def) = identity_hugr_with_type(Type::new_extension(t));
h.resolve_extension_defs(®).unwrap();
let e = h.validate().unwrap_err();
let (node, cause) = assert_matches!(
e,
ValidationError::SignatureError{ node, cause, .. } => (node, cause)
);
assert_eq!(node, def);
cause
};
let valid = Type::new_extension(CustomType::new(
"MyContainer",
vec![usize_t().into()],
EXT_ID,
TypeBound::Linear,
&Arc::downgrade(&ext),
));
let mut hugr = identity_hugr_with_type(valid.clone()).0;
hugr.resolve_extension_defs(®).unwrap();
assert_eq!(hugr.validate(), Ok(()));
let element_outside_bound = CustomType::new(
"MyContainer",
vec![valid.clone().into()],
EXT_ID,
TypeBound::Linear,
&Arc::downgrade(&ext),
);
assert_eq!(
validate_to_sig_error(element_outside_bound),
SignatureError::TypeArgMismatch(TermTypeError::TypeMismatch {
type_: Box::new(TypeBound::Copyable.into()),
term: Box::new(valid.into())
})
);
let bad_bound = CustomType::new(
"MyContainer",
vec![usize_t().into()],
EXT_ID,
TypeBound::Copyable,
&Arc::downgrade(&ext),
);
assert_eq!(
validate_to_sig_error(bad_bound.clone()),
SignatureError::WrongBound {
actual: TypeBound::Copyable,
expected: TypeBound::Linear
}
);
let nested = CustomType::new(
"MyContainer",
vec![Type::new_extension(bad_bound).into()],
EXT_ID,
TypeBound::Linear,
&Arc::downgrade(&ext),
);
assert_eq!(
validate_to_sig_error(nested),
SignatureError::WrongBound {
actual: TypeBound::Copyable,
expected: TypeBound::Linear
}
);
let too_many_type_args = CustomType::new(
"MyContainer",
vec![usize_t().into(), 3u64.into()],
EXT_ID,
TypeBound::Linear,
&Arc::downgrade(&ext),
);
assert_eq!(
validate_to_sig_error(too_many_type_args),
SignatureError::TypeArgMismatch(TermTypeError::WrongNumberArgs(2, 1))
);
}
#[test]
fn typevars_declared() -> Result<(), Box<dyn std::error::Error>> {
let f = FunctionBuilder::new(
"myfunc",
PolyFuncType::new(
[TypeBound::Linear.into()],
Signature::new_endo([Type::new_var_use(0, TypeBound::Linear)]),
),
)?;
let [w] = f.input_wires_arr();
f.finish_hugr_with_outputs([w])?;
let f = FunctionBuilder::new(
"myfunc",
PolyFuncType::new(
[TypeBound::Linear.into()],
Signature::new_endo([Type::new_var_use(1, TypeBound::Linear)]),
),
)?;
let [w] = f.input_wires_arr();
assert!(f.finish_hugr_with_outputs([w]).is_err());
let f = FunctionBuilder::new(
"myfunc",
PolyFuncType::new(
[TypeBound::Linear.into()],
Signature::new_endo([Type::new_var_use(1, TypeBound::Copyable)]),
),
)?;
let [w] = f.input_wires_arr();
assert!(f.finish_hugr_with_outputs([w]).is_err());
Ok(())
}
#[test]
fn no_nested_funcdefns() -> Result<(), Box<dyn std::error::Error>> {
let mut outer = FunctionBuilder::new("outer", Signature::new_endo([usize_t()]))?;
let inner = outer
.add_hugr({
let inner = FunctionBuilder::new("inner", Signature::new_endo([bool_t()]))?;
let [w] = inner.input_wires_arr();
inner.finish_hugr_with_outputs([w])?
})
.inserted_entrypoint;
let [w] = outer.input_wires_arr();
let outer_node = outer.container_node();
let hugr = outer.finish_hugr_with_outputs([w]);
assert_matches!(
hugr.unwrap_err(),
BuildError::InvalidHUGR(ValidationError::InvalidParentOp {
child_optype,
allowed_children: OpTag::DataflowChild,
parent_optype,
child, parent
}) if matches!(*child_optype, OpType::FuncDefn(_)) && matches!(*parent_optype, OpType::FuncDefn(_)) => {
assert_eq!(child, inner);
assert_eq!(parent, outer_node);
}
);
Ok(())
}
#[test]
fn no_polymorphic_consts() -> Result<(), Box<dyn std::error::Error>> {
use crate::std_extensions::collections::list;
const BOUND: TypeParam = TypeParam::RuntimeType(TypeBound::Copyable);
let list_of_var = Type::new_extension(
list::EXTENSION
.get_type(&list::LIST_TYPENAME)
.unwrap()
.instantiate(vec![TypeArg::new_var_use(0, BOUND)])?,
);
let reg = ExtensionRegistry::new([list::EXTENSION.to_owned()]);
reg.validate()?;
let mut def = FunctionBuilder::new(
"myfunc",
PolyFuncType::new([BOUND], Signature::new(vec![], vec![list_of_var.clone()])),
)?;
let empty_list = Value::extension(list::ListValue::new_empty(Type::new_var_use(
0,
TypeBound::Copyable,
)));
let cst = def.add_load_const(empty_list);
let res = def.finish_hugr_with_outputs([cst]);
assert_matches!(
res.unwrap_err(),
BuildError::InvalidHUGR(ValidationError::SignatureError {
cause: SignatureError::FreeTypeVar {
idx: 0,
num_decls: 0
},
..
})
);
Ok(())
}
pub(crate) fn extension_with_eval_parallel() -> Arc<Extension> {
let rowp = TypeParam::new_list_type(TypeBound::Linear);
Extension::new_test_arc(EXT_ID, |ext, extension_ref| {
let inputs = TypeRV::new_row_var_use(0, TypeBound::Linear);
let outputs = TypeRV::new_row_var_use(1, TypeBound::Linear);
let evaled_fn =
TypeRV::new_function(FuncValueType::new([inputs.clone()], [outputs.clone()]));
let pf = PolyFuncTypeRV::new(
[rowp.clone(), rowp.clone()],
FuncValueType::new([evaled_fn, inputs], [outputs]),
);
ext.add_op("eval".into(), String::new(), pf, extension_ref)
.unwrap();
let rv = |idx| TypeRV::new_row_var_use(idx, TypeBound::Linear);
let pf = PolyFuncTypeRV::new(
[rowp.clone(), rowp.clone(), rowp.clone(), rowp.clone()],
Signature::new(
vec![
Type::new_function(FuncValueType::new([rv(0)], [rv(2)])),
Type::new_function(FuncValueType::new([rv(1)], [rv(3)])),
],
[Type::new_function(FuncValueType::new(
[rv(0), rv(1)],
[rv(2), rv(3)],
))],
),
);
ext.add_op("parallel".into(), String::new(), pf, extension_ref)
.unwrap();
})
}
#[test]
fn instantiate_row_variables() -> Result<(), Box<dyn std::error::Error>> {
fn uint_seq(i: usize) -> Term {
vec![usize_t().into(); i].into()
}
let e = extension_with_eval_parallel();
let mut dfb = DFGBuilder::new(inout_sig(
vec![
Type::new_function(Signature::new([usize_t()], vec![usize_t(); 2])),
usize_t(),
], vec![usize_t(); 4], ))?;
let [func, int] = dfb.input_wires_arr();
let eval = e.instantiate_extension_op("eval", [uint_seq(1), uint_seq(2)])?;
let [a, b] = dfb.add_dataflow_op(eval, [func, int])?.outputs_arr();
let par = e.instantiate_extension_op(
"parallel",
[uint_seq(1), uint_seq(1), uint_seq(2), uint_seq(2)],
)?;
let [par_func] = dfb.add_dataflow_op(par, [func, func])?.outputs_arr();
let eval2 = e.instantiate_extension_op("eval", [uint_seq(2), uint_seq(4)])?;
let eval2 = dfb.add_dataflow_op(eval2, [par_func, a, b])?;
dfb.finish_hugr_with_outputs(eval2.outputs())?;
Ok(())
}
fn list1ty(t: TypeRV) -> Term {
Term::new_list([t.into()])
}
#[test]
fn row_variables() -> Result<(), Box<dyn std::error::Error>> {
let e = extension_with_eval_parallel();
let tv = TypeRV::new_row_var_use(0, TypeBound::Linear);
let inner_ft = Type::new_function(FuncValueType::new_endo([tv.clone()]));
let ft_usz = Type::new_function(FuncValueType::new_endo([tv.clone(), usize_t().into()]));
let mut fb = FunctionBuilder::new(
"id",
PolyFuncType::new(
[TypeParam::new_list_type(TypeBound::Linear)],
Signature::new([inner_ft.clone()], [ft_usz]),
),
)?;
let [func_arg] = fb.input_wires_arr();
let id_usz = {
let mut mb = fb.module_root_builder();
let bldr = mb.define_function("id_usz", Signature::new_endo([usize_t()]))?;
let vals = bldr.input_wires();
let helper_def = bldr.finish_with_outputs(vals)?;
fb.load_func(helper_def.handle(), &[])?
};
let par = e.instantiate_extension_op(
"parallel",
[tv.clone(), usize_t().into(), tv.clone(), usize_t().into()].map(list1ty),
)?;
let par_func = fb.add_dataflow_op(par, [func_arg, id_usz])?;
fb.finish_hugr_with_outputs(par_func.outputs())?;
Ok(())
}
#[test]
fn test_polymorphic_load() -> Result<(), Box<dyn std::error::Error>> {
let mut m = ModuleBuilder::new();
let id = m.declare(
"id",
PolyFuncType::new(
vec![TypeBound::Linear.into()],
Signature::new_endo([Type::new_var_use(0, TypeBound::Linear)]),
),
)?;
let sig = Signature::new(
vec![],
vec![Type::new_function(Signature::new_endo([usize_t()]))],
);
let mut f = m.define_function("main", sig)?;
let l = f.load_func(&id, &[usize_t().into()])?;
f.finish_with_outputs([l])?;
let _ = m.finish_hugr()?;
Ok(())
}
#[test]
fn cfg_children_restrictions() {
let (mut b, def) = make_simple_hugr(1);
let (_input, _output, copy) = b
.hierarchy
.children(def.into_portgraph())
.map_into()
.collect_tuple()
.unwrap();
b.validate().unwrap();
b.replace_op(
copy,
ops::CFG {
signature: Signature::new(vec![bool_t()], vec![bool_t()]),
},
);
assert_matches!(
b.validate(),
Err(ValidationError::ContainerWithoutChildren { .. })
);
let cfg = copy;
let block = b.add_node_with_parent(
cfg,
ops::DataflowBlock {
inputs: vec![bool_t()].into(),
sum_rows: vec![type_row![]],
other_outputs: vec![bool_t()].into(),
},
);
let const_op: ops::Const = ops::Value::unit_sum(0, 1).unwrap().into();
let tag_type = Type::new_unit_sum(1);
{
let input = b.add_node_with_parent(block, ops::Input::new(vec![bool_t()]));
let output =
b.add_node_with_parent(block, ops::Output::new(vec![tag_type.clone(), bool_t()]));
let tag_def = b.add_node_with_parent(b.entrypoint(), const_op);
let tag = b.add_node_with_parent(block, ops::LoadConstant { datatype: tag_type });
b.connect(tag_def, 0, tag, 0);
b.add_other_edge(input, tag);
b.connect(tag, 0, output, 0);
b.connect(input, 0, output, 1);
}
let exit = b.add_node_with_parent(
cfg,
ops::ExitBlock {
cfg_outputs: vec![bool_t()].into(),
},
);
b.add_other_edge(block, exit);
assert_eq!(b.validate(), Ok(()));
let exit2 = b.add_node_after(
exit,
ops::ExitBlock {
cfg_outputs: vec![bool_t()].into(),
},
);
assert_matches!(
b.validate(),
Err(ValidationError::InvalidChildren { parent, source: ChildrenValidationError::InternalExitChildren { child, .. }, .. })
=> {assert_eq!(parent, cfg); assert_eq!(child, exit2)}
);
b.remove_node(exit2);
b.replace_op(
cfg,
ops::CFG {
signature: Signature::new(vec![qb_t()], vec![bool_t()]),
},
);
b.replace_op(
block,
ops::DataflowBlock {
inputs: vec![qb_t()].into(),
sum_rows: vec![type_row![]],
other_outputs: vec![qb_t()].into(),
},
);
let mut block_children = b.hierarchy.children(block.into_portgraph());
let block_input = block_children.next().unwrap().into();
let block_output = block_children.next_back().unwrap().into();
b.replace_op(block_input, ops::Input::new(vec![qb_t()]));
b.replace_op(
block_output,
ops::Output::new(vec![Type::new_unit_sum(1), qb_t()]),
);
assert_matches!(
b.validate(),
Err(ValidationError::InvalidEdges { parent, source: EdgeValidationError::CFGEdgeSignatureMismatch { .. }, .. })
=> assert_eq!(parent, cfg)
);
}
#[test]
fn cfg_connections() -> Result<(), Box<dyn std::error::Error>> {
use crate::builder::CFGBuilder;
let mut hugr = CFGBuilder::new(Signature::new_endo([usize_t()]))?;
let unary_pred = hugr.add_constant(Value::unary_unit_sum());
let mut entry = hugr.simple_entry_builder(vec![usize_t()].into(), 1)?;
let p = entry.load_const(&unary_pred);
let ins = entry.input_wires();
let entry = entry.finish_with_outputs(p, ins)?;
let mut middle = hugr.simple_block_builder(Signature::new_endo([usize_t()]), 1)?;
let p = middle.load_const(&unary_pred);
let ins = middle.input_wires();
let middle = middle.finish_with_outputs(p, ins)?;
let exit = hugr.exit_block();
hugr.branch(&entry, 0, &middle)?;
hugr.branch(&middle, 0, &exit)?;
let mut h = hugr.finish_hugr()?;
h.connect(middle.node(), 0, middle.node(), 0);
assert_eq!(
h.validate(),
Err(ValidationError::TooManyConnections {
node: middle.node(),
port: Port::new(Direction::Outgoing, 0),
port_kind: Box::new(EdgeKind::ControlFlow)
})
);
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)] fn cfg_entry_io_bug() -> Result<(), Box<dyn std::error::Error>> {
let hugr: Hugr = Hugr::load(
BufReader::new(File::open(test_file!("issue-1189.hugr")).unwrap()),
None,
)
.unwrap();
assert_matches!(
hugr.validate(),
Err(ValidationError::InvalidChildren { parent, source: ChildrenValidationError::IOSignatureMismatch{..}, .. })
=> assert_eq!(parent, hugr.entrypoint())
);
Ok(())
}
fn sig1() -> Signature {
Signature::new_endo([bool_t()])
}
fn sig2() -> Signature {
Signature::new_endo([usize_t()])
}
#[rstest]
#[case(
FuncDefn::new_vis("foo", sig1(), Visibility::Public),
FuncDefn::new("foo", sig2()),
None
)]
#[case(FuncDefn::new("foo", sig1()), FuncDecl::new("foo", sig2()), None)]
#[case(
FuncDefn::new_vis("foo", sig1(), Visibility::Public),
FuncDefn::new_vis("foo", sig1(), Visibility::Public),
Some("foo")
)]
#[case(
FuncDefn::new_vis("foo", sig1(), Visibility::Public),
FuncDecl::new("foo", sig1()),
Some("foo")
)]
#[case(FuncDecl::new("foo", sig1()), FuncDecl::new("foo", sig1()), None)]
#[case(
FuncDecl::new("foo", sig1()),
FuncDecl::new("foo", sig2()),
Some("foo")
)]
fn validate_linkage(
#[case] f1: impl Into<OpType>,
#[case] f2: impl Into<OpType>,
#[case] err: Option<&str>,
) {
let mut h = Hugr::new();
let [n1, n2] = [f1.into(), f2.into()].map(|f| {
let def_sig = f
.as_func_defn()
.map(FuncDefn::inner_signature)
.map(Cow::into_owned);
let n = h.add_node_with_parent(h.module_root(), f);
if let Some(Signature { input, output }) = def_sig {
let i = h.add_node_with_parent(n, ops::Input::new(input));
let o = h.add_node_with_parent(n, ops::Output::new(output));
h.connect(i, 0, o, 0); }
n
});
let r = h.validate();
match err {
None => r.unwrap(),
Some(name) => {
let Err(ValidationError::DuplicateExport {
link_name,
children,
}) = r
else {
panic!("validate() should have produced DuplicateExport error not {r:?}")
};
assert_eq!(link_name, name);
assert!(children == [n1, n2] || children == [n2, n1]);
}
}
}