1#![allow(clippy::type_complexity)]
2use std::borrow::Cow;
5use std::collections::HashMap;
6use std::sync::Arc;
7
8use handlers::list_const;
9use hugr_core::hugr::linking::{HugrLinking, NameLinkingPolicy, OnMultiDefn};
10use hugr_core::std_extensions::collections::array::array_type_def;
11use hugr_core::std_extensions::collections::list::list_type_def;
12use itertools::Either;
13use thiserror::Error;
14
15use hugr_core::builder::{
16 BuildError, BuildHandle, Container, Dataflow, DataflowHugr, FunctionBuilder, HugrBuilder,
17};
18use hugr_core::extension::{ExtensionId, OpDef, SignatureError, TypeDef};
19use hugr_core::hugr::hugrmut::HugrMut;
20use hugr_core::ops::constant::{OpaqueValue, Sum};
21use hugr_core::ops::handle::{DataflowOpID, FuncID, NodeHandle};
22use hugr_core::ops::{
23 AliasDefn, CFG, Call, CallIndirect, Case, Conditional, Const, DFG, DataflowBlock, ExitBlock,
24 ExtensionOp, Input, LoadConstant, LoadFunction, OpTrait, OpType, Output, Tag, TailLoop, Value,
25};
26use hugr_core::types::{
27 ConstTypeError, CustomType, Signature, Transformable, Type, TypeArg, TypeEnum, TypeRow,
28 TypeTransformer,
29};
30use hugr_core::{Direction, Hugr, HugrView, Node, PortIndex, Visibility, Wire};
31
32use crate::composable::WithScope;
33use crate::{ComposablePass, PassScope};
34
35mod linearize;
36pub use linearize::{CallbackHandler, DelegatingLinearizer, LinearizeError, Linearizer};
37
38#[derive(Clone, Debug, PartialEq)]
43#[non_exhaustive]
44#[deprecated(
45 note = "`hugr-passes` is deprecated. Use tket::passes instead",
46 since = "0.26.2"
47)]
48pub enum NodeTemplate {
49 SingleOp(OpType),
53 CompoundOp(Box<Hugr>),
66 LinkedHugr(Box<Hugr>, NameLinkingPolicy),
70 #[deprecated(
72 note = "Use LinkedHugr with Call entrypoint and FuncDecl",
73 since = "0.24.4"
74 )]
75 Call(Node, Vec<TypeArg>),
76}
77
78impl NodeTemplate {
79 pub fn linked_hugr(h: impl Into<Hugr>) -> Self {
81 NodeTemplate::LinkedHugr(Box::new(h.into()), NameLinkingPolicy::default())
82 }
83
84 pub fn call_to_function(
90 func_def: Hugr,
91 type_args: &[TypeArg],
92 ) -> Result<NodeTemplate, BuildError> {
93 let func_op = func_def.entrypoint_optype();
95 let func_signature = match func_op {
96 OpType::FuncDecl(decl) => decl.signature().clone(),
97 OpType::FuncDefn(defn) => defn.signature().clone(),
98 _ => {
99 return Err(BuildError::UnexpectedType {
100 node: func_def.entrypoint(),
101 op_desc: "function definition or declaration",
102 });
103 }
104 }
105 .instantiate(type_args)?;
106
107 let mut b = FunctionBuilder::new_vis("", func_signature, Visibility::Private).unwrap();
109 let func_id = FuncID::<true>::from(
110 b.module_root_builder()
111 .add_hugr(func_def)
112 .inserted_entrypoint,
113 );
114
115 let call = b.call(&func_id, type_args, b.input_wires()).unwrap();
117 let mut call_hugr = b.finish_hugr_with_outputs(call.outputs()).unwrap();
118 call_hugr.set_entrypoint(call.node());
119
120 Ok(NodeTemplate::LinkedHugr(
121 Box::new(call_hugr),
122 NameLinkingPolicy::default().on_multiple_defn(OnMultiDefn::UseTarget),
123 ))
124 }
125
126 pub fn add_hugr(
143 self,
144 hugr: &mut impl HugrMut<Node = Node>,
145 parent: Node,
146 ) -> Result<Node, BuildError> {
147 match self {
148 NodeTemplate::SingleOp(op_type) => Ok(hugr.add_node_with_parent(parent, op_type)),
149 NodeTemplate::CompoundOp(new_h) => {
150 Ok(hugr.insert_hugr(parent, *new_h).inserted_entrypoint)
151 }
152 NodeTemplate::LinkedHugr(h, pol) => {
153 Ok(hugr.insert_link_hugr(parent, *h, &pol)?.inserted_entrypoint)
154 }
155 #[expect(deprecated)] NodeTemplate::Call(target, type_args) => {
157 let c = call(hugr, target, type_args)?;
158 let tgt_port = c.called_function_port();
159 let n = hugr.add_node_with_parent(parent, c);
160 hugr.connect(target, 0, n, tgt_port);
161 Ok(n)
162 }
163 }
164 }
165
166 pub fn add(
168 self,
169 dfb: &mut impl Dataflow,
170 inputs: impl IntoIterator<Item = Wire>,
171 ) -> Result<BuildHandle<DataflowOpID>, BuildError> {
172 match self {
173 NodeTemplate::SingleOp(opty) => dfb.add_dataflow_op(opty, inputs),
174 NodeTemplate::CompoundOp(h) => dfb.add_hugr_with_wires(*h, inputs),
175 NodeTemplate::LinkedHugr(h, pol) => dfb.add_link_hugr_with_wires(*h, &pol, inputs),
176 #[expect(deprecated)] NodeTemplate::Call(func, type_args) => {
180 if !dfb.hugr().contains_node(func) {
181 return Err(BuildError::NodeNotFound { node: func });
182 }
183 dfb.call(&FuncID::<true>::from(func), &type_args, inputs)
184 }
185 }
186 }
187
188 fn replace<H: HugrMut<Node = Node>>(
189 self,
190 hugr: &mut H,
191 n: Node,
192 rt: &ReplaceTypes,
193 opts: &ReplacementOptions,
194 ) -> Result<(), ReplaceTypesError> {
195 let ef = |e| ReplaceTypesError::AddTemplateError(n, Box::new(e));
196 assert_eq!(hugr.children(n).count(), 0);
197 let (new_optype, static_source, static_inport) = match self {
198 NodeTemplate::SingleOp(op_type) => {
199 if op_type.static_input_port().is_some() {
200 return Err(ef(BuildError::UnexpectedType {
201 node: n,
202 op_desc: "Replacement SingleOp without static input",
203 }));
204 }
205 (op_type, None, None)
206 }
207 NodeTemplate::CompoundOp(new_h) => {
208 let root = new_h.entrypoint_optype();
209 if !matches!(
210 root,
211 OpType::CFG(_) | OpType::DFG(_) | OpType::Conditional(_) | OpType::TailLoop(_)
212 )
213 {
215 return Err(ef(BuildError::UnexpectedType {
216 node: n,
217 op_desc: "Replacement CompoundOp not a container/dataflow node",
218 }));
219 }
220 assert!(root.static_input_port().is_none());
221 let new_entrypoint = hugr.insert_hugr(n, *new_h).inserted_entrypoint;
222 let children = hugr.children(new_entrypoint).collect::<Vec<_>>();
223 let root_opty = hugr.remove_node(new_entrypoint);
224 for ch in children {
225 hugr.set_parent(ch, n);
226 }
227 (root_opty, None, None)
228 }
229 NodeTemplate::LinkedHugr(mut h, pol) => {
230 let mut containing_func = h.entrypoint();
234 while let Some(parent) = h.get_parent(containing_func)
235 && !h.get_optype(parent).is_module()
236 {
237 containing_func = parent;
238 }
239
240 for ch in h.children(h.module_root()).collect::<Vec<_>>() {
241 if ch != containing_func {
242 rt.process_subtree_opts(&mut h, ch, opts)?;
243 }
244 }
245 let new_entrypoint = hugr
246 .insert_link_hugr(n, *h, &pol)
247 .map_err(|e| ef(BuildError::from(e)))?
248 .inserted_entrypoint;
249 let children = hugr.children(new_entrypoint).collect::<Vec<_>>();
250 let static_source = hugr.static_source(new_entrypoint);
251 let root_opty = hugr.remove_node(new_entrypoint);
252 let static_inport = root_opty.static_input_port();
253 for ch in children {
254 hugr.set_parent(ch, n);
255 }
256 (root_opty, static_source, static_inport)
257 }
258 #[expect(deprecated)] NodeTemplate::Call(func, type_args) => {
260 let c = call(hugr, func, type_args).map_err(ef)?;
261 let called_func_port = c.called_function_port();
262 (c.into(), Some(func), Some(called_func_port))
263 }
264 };
265 *hugr.optype_mut(n) = new_optype;
266 if let Some(static_inport) = static_inport {
267 hugr.insert_ports(n, Direction::Incoming, static_inport.index(), 1);
268 if let Some(static_source) = static_source {
269 hugr.connect(static_source, 0, n, static_inport);
270 }
271 }
272 rt.process_subtree_opts(hugr, n, opts)?;
273 Ok(())
274 }
275
276 fn check_signature(
277 &self,
278 inputs: &TypeRow,
279 outputs: &TypeRow,
280 ) -> Result<(), Option<Signature>> {
281 let sig = match self {
282 NodeTemplate::SingleOp(op_type) => op_type,
283 NodeTemplate::CompoundOp(hugr) => hugr.entrypoint_optype(),
284 NodeTemplate::LinkedHugr(hugr, _) => hugr.entrypoint_optype(),
285 #[expect(deprecated)] NodeTemplate::Call(_, _) => return Ok(()), }
288 .dataflow_signature();
289 if sig.as_deref().map(Signature::io) == Some((inputs, outputs)) {
290 Ok(())
291 } else {
292 Err(sig.map(Cow::into_owned))
293 }
294 }
295}
296
297fn call<H: HugrView<Node = Node>>(
298 h: &H,
299 func: Node,
300 type_args: Vec<TypeArg>,
301) -> Result<Call, BuildError> {
302 let func_sig = match h.get_optype(func) {
303 OpType::FuncDecl(fd) => fd.signature().clone(),
304 OpType::FuncDefn(fd) => fd.signature().clone(),
305 _ => {
306 return Err(BuildError::UnexpectedType {
307 node: func,
308 op_desc: "func defn/decl",
309 });
310 }
311 };
312 Ok(Call::try_new(func_sig, type_args)?)
313}
314
315#[derive(Clone, Default, PartialEq, Eq)] #[deprecated(
323 note = "`hugr-passes` is deprecated. Use tket::passes instead",
324 since = "0.26.2"
325)]
326pub struct ReplacementOptions {
327 process_recursive: bool,
328 linearize_unchanged: bool,
329}
330
331impl ReplacementOptions {
332 fn recursive() -> Self {
333 Self {
334 process_recursive: true,
335 linearize_unchanged: false,
336 }
337 }
338
339 pub fn with_linearization(mut self, lin: bool) -> Self {
342 self.linearize_unchanged = lin;
343 self
344 }
345}
346
347#[derive(Clone)]
378#[deprecated(
379 note = "`hugr-passes` is deprecated. Use tket::passes instead",
380 since = "0.26.2"
381)]
382pub struct ReplaceTypes {
383 type_map: HashMap<CustomType, (Type, ReplacementOptions)>,
384 param_types:
385 HashMap<ParametricType, (Arc<dyn Fn(&[TypeArg]) -> Option<Type>>, ReplacementOptions)>,
386 linearize: DelegatingLinearizer,
387 op_map: HashMap<OpHashWrapper, (NodeTemplate, ReplacementOptions)>,
388 param_ops: HashMap<
389 ParametricOp,
390 (
391 Arc<
392 dyn Fn(
393 &[TypeArg],
394 &ReplaceTypes,
395 ) -> Result<Option<NodeTemplate>, ReplaceTypesError>,
396 >,
397 ReplacementOptions,
398 ),
399 >,
400 consts: HashMap<
401 CustomType,
402 Arc<dyn Fn(&OpaqueValue, &ReplaceTypes) -> Result<Value, ReplaceTypesError>>,
403 >,
404 param_consts: HashMap<
405 ParametricType,
406 Arc<dyn Fn(&OpaqueValue, &ReplaceTypes) -> Result<Option<Value>, ReplaceTypesError>>,
407 >,
408 scope: Either<PassScope, Vec<Node>>,
409}
410
411impl Default for ReplaceTypes {
412 fn default() -> Self {
413 let mut res = Self::new_empty();
414 res.linearize = DelegatingLinearizer::default();
415 res.replace_consts_parametrized(array_type_def(), handlers::array_const);
416 res.replace_consts_parametrized(list_type_def(), list_const);
417 res
418 }
419}
420
421impl TypeTransformer for ReplaceTypes {
422 type Err = ReplaceTypesError;
423
424 fn apply_custom(&self, ct: &CustomType) -> Result<Option<Type>, Self::Err> {
425 let mut ty_and_opts = None;
426 if let Some(res) = self.type_map.get(ct) {
427 ty_and_opts = Some(res.clone())
428 } else if let Some((dest_fn, opts)) = self.param_types.get(&ct.into()) {
429 let mut nargs = ct.args().to_vec();
431 nargs
433 .iter_mut()
434 .try_for_each(|ta| ta.transform(self).map(|_ch| ()))?;
435 ty_and_opts = dest_fn(&nargs).map(|ty| (ty, opts.clone()))
436 };
437 let Some((mut ty, opts)) = ty_and_opts else {
438 return Ok(None);
439 };
440 if opts.process_recursive {
441 ty.transform(self)?;
442 }
443 Ok(Some(ty))
444 }
445}
446
447#[derive(Debug, Error, PartialEq)]
449#[non_exhaustive]
450#[expect(missing_docs)]
451#[deprecated(
452 note = "`hugr-passes` is deprecated. Use tket::passes instead",
453 since = "0.26.2"
454)]
455pub enum ReplaceTypesError {
456 #[error(transparent)]
457 SignatureError(#[from] SignatureError),
458 #[error(transparent)]
459 ConstError(#[from] ConstTypeError),
460 #[error(transparent)]
461 LinearizeError(#[from] LinearizeError),
462 #[error("Replacement op for {0} could not be added because {1}")]
463 AddTemplateError(Node, Box<BuildError>),
464}
465
466impl ReplaceTypes {
467 #[must_use]
470 pub fn new_empty() -> Self {
471 Self {
472 type_map: Default::default(),
473 param_types: Default::default(),
474 linearize: DelegatingLinearizer::new_empty(),
475 op_map: Default::default(),
476 param_ops: Default::default(),
477 consts: Default::default(),
478 param_consts: Default::default(),
479 scope: Either::Left(PassScope::default()),
482 }
483 }
484
485 #[deprecated(note = "Use set_replace_type", since = "0.25.0")]
487 pub fn replace_type(&mut self, src: CustomType, dest: Type) {
488 #[expect(deprecated)] self.replace_type_opts(src, dest, ReplacementOptions::default())
490 }
491
492 pub fn set_replace_type(&mut self, src: CustomType, dest: Type) {
512 self.type_map
516 .insert(src, (dest, ReplacementOptions::recursive()));
517 }
518
519 #[deprecated(note = "Use set_replace_type", since = "0.25.0")]
522 pub fn replace_type_opts(&mut self, src: CustomType, dest: Type, opts: ReplacementOptions) {
523 self.type_map.insert(src, (dest, opts));
524 }
525
526 #[deprecated(note = "Use set_replace_parametrized_type", since = "0.25.0")]
529 pub fn replace_parametrized_type(
530 &mut self,
531 src: &TypeDef,
532 dest_fn: impl Fn(&[TypeArg]) -> Option<Type> + 'static,
533 ) {
534 #[expect(deprecated)] self.replace_parametrized_type_opts(src, dest_fn, ReplacementOptions::default())
536 }
537
538 pub fn set_replace_parametrized_type(
555 &mut self,
556 src: &TypeDef,
557 dest_fn: impl Fn(&[TypeArg]) -> Option<Type> + 'static,
558 ) {
559 self.param_types.insert(
569 src.into(),
570 (Arc::new(dest_fn), ReplacementOptions::recursive()),
571 );
572 }
573
574 #[deprecated(note = "Use set_replace_parametrized_type", since = "0.25.0")]
578 pub fn replace_parametrized_type_opts(
579 &mut self,
580 src: &TypeDef,
581 dest_fn: impl Fn(&[TypeArg]) -> Option<Type> + 'static,
582 opts: ReplacementOptions,
583 ) {
584 self.param_types
585 .insert(src.into(), (Arc::new(dest_fn), opts));
586 }
587
588 #[deprecated(note = "Use get_linearizer or linearizer_mut", since = "0.25.0")]
591 pub fn linearizer(&mut self) -> &mut DelegatingLinearizer {
592 &mut self.linearize
593 }
594
595 pub fn linearizer_mut(&mut self) -> &mut DelegatingLinearizer {
605 &mut self.linearize
606 }
607
608 pub fn get_linearizer(&self) -> &impl Linearizer {
611 &self.linearize
612 }
613
614 #[deprecated(note = "Use set_replace_op", since = "0.25.0")]
616 pub fn replace_op(&mut self, src: &ExtensionOp, dest: NodeTemplate) {
617 #[expect(deprecated)] self.replace_op_with(src, dest, ReplacementOptions::default())
619 }
620
621 pub fn set_replace_op(&mut self, src: &ExtensionOp, dest: NodeTemplate) {
634 self.op_map.insert(
635 OpHashWrapper::from(src),
636 (dest, ReplacementOptions::recursive()),
637 );
638 }
639
640 #[deprecated(note = "Use set_replace_op", since = "0.25.0")]
642 pub fn replace_op_with(
643 &mut self,
644 src: &ExtensionOp,
645 dest: NodeTemplate,
646 opts: ReplacementOptions,
647 ) {
648 self.op_map.insert(OpHashWrapper::from(src), (dest, opts));
649 }
650
651 #[deprecated(note = "Use set_replace_parametrized_op", since = "0.25.0")]
654 pub fn replace_parametrized_op(
655 &mut self,
656 src: &OpDef,
657 dest_fn: impl Fn(&[TypeArg]) -> Option<NodeTemplate> + 'static,
658 ) {
659 #[expect(deprecated)] self.replace_parametrized_op_with(src, dest_fn, ReplacementOptions::default())
661 }
662
663 pub fn set_replace_parametrized_op(
673 &mut self,
674 src: &OpDef,
675 dest_fn: impl Fn(&[TypeArg], &ReplaceTypes) -> Result<Option<NodeTemplate>, ReplaceTypesError>
676 + 'static,
677 ) {
678 self.param_ops.insert(
679 src.into(),
680 (Arc::new(dest_fn), ReplacementOptions::recursive()),
681 );
682 }
683
684 #[deprecated(note = "Use set_replace_parametrized_op", since = "0.25.0")]
687 pub fn replace_parametrized_op_with(
688 &mut self,
689 src: &OpDef,
690 dest_fn: impl Fn(&[TypeArg]) -> Option<NodeTemplate> + 'static,
691 opts: ReplacementOptions,
692 ) {
693 self.param_ops.insert(
694 src.into(),
695 (Arc::new(move |args, _| Ok(dest_fn(args))), opts),
696 );
697 }
698
699 pub fn replace_consts(
706 &mut self,
707 src_ty: CustomType,
708 const_fn: impl Fn(&OpaqueValue, &ReplaceTypes) -> Result<Value, ReplaceTypesError> + 'static,
709 ) {
710 self.consts.insert(src_ty, Arc::new(const_fn));
711 }
712
713 pub fn replace_consts_parametrized(
718 &mut self,
719 src_ty: &TypeDef,
720 const_fn: impl Fn(&OpaqueValue, &ReplaceTypes) -> Result<Option<Value>, ReplaceTypesError>
721 + 'static,
722 ) {
723 self.param_consts.insert(src_ty.into(), Arc::new(const_fn));
724 }
725
726 pub fn set_regions(&mut self, regions: impl IntoIterator<Item = Node>) {
731 self.scope = Either::Right(regions.into_iter().collect());
732 }
733
734 fn process_subtree_opts(
735 &self,
736 hugr: &mut impl HugrMut<Node = Node>,
737 root: Node,
738 opts: &ReplacementOptions,
739 ) -> Result<(), ReplaceTypesError> {
740 if opts.process_recursive {
741 self.change_subtree(hugr, root, opts.linearize_unchanged)?;
742 } else if opts.linearize_unchanged {
745 let mut descs = hugr.descendants(root);
746 assert_eq!(descs.next(), Some(root));
747 for n in descs.collect::<Vec<_>>() {
748 self.linearize_outputs(hugr, n)?;
749 }
750 }
751 Ok(())
752 }
753
754 fn change_subtree(
755 &self,
756 hugr: &mut impl HugrMut<Node = Node>,
757 root: Node,
758 linearize_unchanged_ops: bool,
759 ) -> Result<bool, ReplaceTypesError> {
760 let mut descs = hugr.descendants(root).collect::<Vec<_>>().into_iter();
761 assert_eq!(descs.next(), Some(root));
762 let mut changed = self.change_node(hugr, root)?;
763 for n in descs {
766 if self.change_node(hugr, n)? {
767 changed = true;
768 } else if !linearize_unchanged_ops {
769 continue;
770 }
771 self.linearize_outputs(hugr, n)?;
772 }
773 Ok(changed)
774 }
775
776 fn change_node(
777 &self,
778 hugr: &mut impl HugrMut<Node = Node>,
779 n: Node,
780 ) -> Result<bool, ReplaceTypesError> {
781 match hugr.optype_mut(n) {
782 OpType::FuncDefn(fd) => fd.signature_mut().body_mut().transform(self),
783 OpType::FuncDecl(fd) => fd.signature_mut().body_mut().transform(self),
784 OpType::LoadConstant(LoadConstant { datatype: ty })
785 | OpType::AliasDefn(AliasDefn { definition: ty, .. }) => ty.transform(self),
786
787 OpType::ExitBlock(ExitBlock { cfg_outputs: types })
788 | OpType::Input(Input { types })
789 | OpType::Output(Output { types }) => types.transform(self),
790 OpType::LoadFunction(LoadFunction {
791 func_sig,
792 type_args,
793 instantiation,
794 })
795 | OpType::Call(Call {
796 func_sig,
797 type_args,
798 instantiation,
799 }) => {
800 let change = func_sig.body_mut().transform(self)? | type_args.transform(self)?;
801 if change {
802 let new_inst = func_sig
803 .instantiate(type_args)
804 .map_err(ReplaceTypesError::SignatureError)?;
805 *instantiation = new_inst;
806 }
807 Ok(change)
808 }
809 OpType::Case(Case { signature })
810 | OpType::CFG(CFG { signature })
811 | OpType::DFG(DFG { signature })
812 | OpType::CallIndirect(CallIndirect { signature }) => signature.transform(self),
813 OpType::Tag(Tag { variants, .. }) => variants.transform(self),
814 OpType::Conditional(Conditional {
815 other_inputs: row1,
816 outputs: row2,
817 sum_rows,
818 ..
819 })
820 | OpType::DataflowBlock(DataflowBlock {
821 inputs: row1,
822 other_outputs: row2,
823 sum_rows,
824 ..
825 }) => Ok(row1.transform(self)? | row2.transform(self)? | sum_rows.transform(self)?),
826 OpType::TailLoop(TailLoop {
827 just_inputs,
828 just_outputs,
829 rest,
830 ..
831 }) => Ok(just_inputs.transform(self)?
832 | just_outputs.transform(self)?
833 | rest.transform(self)?),
834
835 OpType::Const(Const { value, .. }) => self.change_value(value),
836 OpType::ExtensionOp(ext_op) => Ok({
837 let def = ext_op.def_arc();
838 let mut changed = false;
839 let replacement = match self.op_map.get(&OpHashWrapper::from(&*ext_op)) {
840 r @ Some(_) => r.cloned(),
841 None => {
842 let mut args = ext_op.args().to_vec();
843 changed = args.transform(self)?;
844 let r2 = match self.param_ops.get(&def.as_ref().into()) {
845 None => None,
846 Some((rep_fn, opts)) => {
847 rep_fn(&args, self)?.map(|nt| (nt, opts.clone()))
848 }
849 };
850 if r2.is_none() && changed {
851 *ext_op = ExtensionOp::new(def.clone(), args)?;
852 }
853 r2
854 }
855 };
856 if let Some((replacement, opts)) = replacement {
857 replacement.replace(hugr, n, self, &opts)?;
858 true
859 } else {
860 changed
861 }
862 }),
863
864 OpType::OpaqueOp(_) => panic!("OpaqueOp should not be in a Hugr"),
865
866 OpType::AliasDecl(_) | OpType::Module(_) => Ok(false),
867 _ => todo!(),
868 }
869 }
870
871 pub fn change_value(&self, value: &mut Value) -> Result<bool, ReplaceTypesError> {
874 match value {
875 Value::Sum(Sum {
876 values, sum_type, ..
877 }) => {
878 let mut any_change = false;
879 for value in values {
880 any_change |= self.change_value(value)?;
881 }
882 any_change |= sum_type.transform(self)?;
883 Ok(any_change)
884 }
885 Value::Extension { e } => Ok({
886 let new_const = match e.get_type().as_type_enum() {
887 TypeEnum::Extension(exty) => match self.consts.get(exty) {
888 Some(const_fn) => Some(const_fn(e, self)),
889 None => self
890 .param_consts
891 .get(&exty.into())
892 .and_then(|const_fn| const_fn(e, self).transpose()),
893 },
894 _ => None,
895 };
896 if let Some(new_const) = new_const {
897 *value = new_const?;
898 true
899 } else {
900 false
901 }
902 }),
903 }
904 }
905
906 fn linearize_outputs<H: HugrMut<Node = Node>>(
907 &self,
908 hugr: &mut H,
909 n: H::Node,
910 ) -> Result<(), LinearizeError> {
911 if let Some(new_sig) = hugr.get_optype(n).dataflow_signature() {
912 let new_sig = new_sig.into_owned();
913 for outp in new_sig.output_ports() {
914 if !new_sig.out_port_type(outp).unwrap().copyable() {
915 let targets = hugr.linked_inputs(n, outp).collect::<Vec<_>>();
916 if targets.len() != 1 {
917 hugr.disconnect(n, outp);
918 let src = Wire::new(n, outp);
919 self.linearize.insert_copy_discard(hugr, src, &targets)?;
920 }
921 }
922 }
923 }
924 Ok(())
925 }
926}
927
928impl<H: HugrMut<Node = Node>> ComposablePass<H> for ReplaceTypes {
929 type Error = ReplaceTypesError;
930 type Result = bool;
931
932 fn run(&self, hugr: &mut H) -> Result<bool, ReplaceTypesError> {
933 let temp: Vec<Node>; let regions = match &self.scope {
935 Either::Left(scope) => {
936 temp = Vec::from_iter(scope.root(hugr));
937 &temp
938 }
939 Either::Right(regs) => regs,
940 };
941 let mut changed = false;
942 for region_root in regions {
943 changed |= self.change_subtree(hugr, *region_root, false)?;
944 }
945 Ok(changed)
946 }
947}
948
949impl WithScope for ReplaceTypes {
950 fn with_scope(mut self, scope: impl Into<PassScope>) -> Self {
957 self.scope = Either::Left(scope.into());
958 self
959 }
960}
961
962#[deprecated(
963 note = "`hugr-passes` is deprecated. Use tket::passes instead",
964 since = "0.26.2"
965)]
966pub mod handlers;
967
968#[derive(Clone, Hash, PartialEq, Eq)]
969struct OpHashWrapper {
970 op_name: String, args: Vec<TypeArg>,
972}
973
974impl From<&ExtensionOp> for OpHashWrapper {
975 fn from(op: &ExtensionOp) -> Self {
976 Self {
977 op_name: op.qualified_id().to_string(),
978 args: op.args().to_vec(),
979 }
980 }
981}
982
983#[derive(Clone, Debug, PartialEq, Eq, Hash)]
984struct ParametricType(ExtensionId, String);
985
986impl From<&TypeDef> for ParametricType {
987 fn from(value: &TypeDef) -> Self {
988 Self(value.extension_id().clone(), value.name().to_string())
989 }
990}
991
992impl From<&CustomType> for ParametricType {
993 fn from(value: &CustomType) -> Self {
994 Self(value.extension().clone(), value.name().to_string())
995 }
996}
997
998#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1000struct ParametricOp(ExtensionId, String);
1001
1002impl From<&OpDef> for ParametricOp {
1003 fn from(value: &OpDef) -> Self {
1004 Self(value.extension_id().clone(), value.name().to_string())
1005 }
1006}
1007
1008#[cfg(test)]
1009mod test {
1010 use std::sync::Arc;
1011
1012 use crate::replace_types::handlers::generic_array_const;
1013 use hugr_core::builder::{
1014 BuildError, Container, DFGBuilder, Dataflow, DataflowHugr, DataflowSubContainer,
1015 FunctionBuilder, HugrBuilder, ModuleBuilder, SubContainer, TailLoopBuilder, endo_sig,
1016 inout_sig,
1017 };
1018 use hugr_core::extension::SignatureError;
1019 use hugr_core::extension::prelude::{
1020 ConstUsize, Noop, UnwrapBuilder, bool_t, option_type, qb_t, usize_t,
1021 };
1022 use hugr_core::extension::simple_op::{MakeOpDef, MakeRegisteredOp};
1023 use hugr_core::extension::{TypeDefBound, Version, simple_op::MakeExtensionOp};
1024 use hugr_core::hugr::{IdentList, ValidationError, hugrmut::HugrMut};
1025 use hugr_core::ops::constant::{CustomConst, OpaqueValue};
1026 use hugr_core::ops::{self, ExtensionOp, OpTrait, OpType, Tag, Value, handle::NodeHandle};
1027 use hugr_core::std_extensions::arithmetic::conversions::ConvertOpDef;
1028 use hugr_core::std_extensions::arithmetic::int_types::{ConstInt, INT_TYPES};
1029 use hugr_core::std_extensions::collections::array::{
1030 self, Array, ArrayKind, ArrayOpDef, GenericArrayValue, array_type, array_type_def,
1031 };
1032 use hugr_core::std_extensions::collections::borrow_array::{
1033 BArrayValue, BorrowArray, borrow_array_type,
1034 };
1035 use hugr_core::std_extensions::collections::list::{
1036 ListOp, ListOpInst, ListValue, list_type, list_type_def,
1037 };
1038 use hugr_core::types::{
1039 EdgeKind, PolyFuncType, Signature, SumType, Term, Type, TypeArg, TypeBound, TypeRow,
1040 };
1041 use hugr_core::{Direction, Extension, HugrView, Port, Visibility, type_row};
1042 use itertools::Itertools;
1043 use rstest::rstest;
1044
1045 use crate::{ComposablePass, mangle_name};
1046
1047 use super::{NodeTemplate, ReplaceTypes, handlers::list_const};
1048
1049 const PACKED_VEC: &str = "PackedVec";
1050 const READ: &str = "read";
1051
1052 fn i64_t() -> Type {
1053 INT_TYPES[6].clone()
1054 }
1055
1056 fn read_op(ext: &Arc<Extension>, t: Type) -> ExtensionOp {
1057 ExtensionOp::new(ext.get_op(READ).unwrap().clone(), [t.into()]).unwrap()
1058 }
1059
1060 fn just_elem_type(args: &[TypeArg]) -> &Type {
1061 let [TypeArg::Runtime(ty)] = args else {
1062 panic!("Expected just elem type")
1063 };
1064 ty
1065 }
1066
1067 fn ext() -> Arc<Extension> {
1068 Extension::new_arc(
1069 IdentList::new("TestExt").unwrap(),
1070 Version::new(0, 0, 1),
1071 |ext, w| {
1072 let pv_of_var = ext
1073 .add_type(
1074 PACKED_VEC.into(),
1075 vec![TypeBound::Linear.into()],
1076 String::new(),
1077 TypeDefBound::from_params(vec![0]),
1078 w,
1079 )
1080 .unwrap()
1081 .instantiate(vec![Type::new_var_use(0, TypeBound::Copyable).into()])
1082 .unwrap();
1083 ext.add_op(
1084 READ.into(),
1085 String::new(),
1086 PolyFuncType::new(
1087 vec![TypeBound::Copyable.into()],
1088 Signature::new(
1089 vec![pv_of_var.into(), i64_t()],
1090 [Type::new_var_use(0, TypeBound::Linear)],
1091 ),
1092 ),
1093 w,
1094 )
1095 .unwrap();
1096 ext.add_op(
1097 "lowered_read_bool".into(),
1098 String::new(),
1099 Signature::new(vec![i64_t(); 2], [bool_t()]),
1100 w,
1101 )
1102 .unwrap();
1103 },
1104 )
1105 }
1106
1107 fn lowered_read<T: Container + Dataflow>(
1108 elem_ty: Type,
1109 new: impl Fn(Signature) -> Result<T, BuildError>,
1110 ) -> T {
1111 let mut dfb = new(Signature::new(
1112 [list_type(elem_ty.clone()), i64_t()],
1113 [elem_ty.clone()],
1114 ))
1115 .unwrap();
1116 let [val, idx] = dfb.input_wires_arr();
1117 let [idx] = dfb
1118 .add_dataflow_op(ConvertOpDef::itousize.without_log_width(), [idx])
1119 .unwrap()
1120 .outputs_arr();
1121 let [opt] = dfb
1122 .add_dataflow_op(
1123 ListOp::get
1124 .with_type(elem_ty.clone())
1125 .to_extension_op()
1126 .unwrap(),
1127 [val, idx],
1128 )
1129 .unwrap()
1130 .outputs_arr();
1131 let [res] = dfb
1132 .build_unwrap_sum(1, option_type([Type::from(elem_ty)]), opt)
1133 .unwrap();
1134 dfb.set_outputs([res]).unwrap();
1135 dfb
1136 }
1137
1138 fn lowerer(ext: &Arc<Extension>) -> ReplaceTypes {
1139 let pv = ext.get_type(PACKED_VEC).unwrap();
1140 let mut lw = ReplaceTypes::default();
1141 lw.set_replace_type(pv.instantiate([bool_t().into()]).unwrap(), i64_t());
1142 lw.set_replace_parametrized_type(
1143 pv,
1144 Box::new(|args: &[TypeArg]| Some(list_type(just_elem_type(args).clone()))),
1145 );
1146 lw.set_replace_op(
1147 &read_op(ext, bool_t()),
1148 NodeTemplate::SingleOp(
1149 ExtensionOp::new(ext.get_op("lowered_read_bool").unwrap().clone(), [])
1150 .unwrap()
1151 .into(),
1152 ),
1153 );
1154 lw.set_replace_parametrized_op(ext.get_op(READ).unwrap().as_ref(), |type_args, _| {
1155 Ok(Some(NodeTemplate::CompoundOp(Box::new(
1156 lowered_read(just_elem_type(type_args).clone(), DFGBuilder::new)
1157 .finish_hugr()
1158 .unwrap(),
1159 ))))
1160 });
1161 lw
1162 }
1163
1164 #[test]
1165 fn module_func_cfg_call() {
1166 let ext = ext();
1167 let coln = ext.get_type(PACKED_VEC).unwrap();
1168 let c_int = Type::from(coln.instantiate([i64_t().into()]).unwrap());
1169 let c_bool = Type::from(coln.instantiate([bool_t().into()]).unwrap());
1170 let mut mb = ModuleBuilder::new();
1171 let sig = Signature::new_endo([Type::new_var_use(0, TypeBound::Linear)]);
1172 let fb = mb
1173 .define_function("id", PolyFuncType::new([TypeBound::Linear.into()], sig))
1174 .unwrap();
1175 let inps = fb.input_wires();
1176 let id = fb.finish_with_outputs(inps).unwrap();
1177
1178 let sig = Signature::new([i64_t(), c_int.clone(), c_bool.clone()], [bool_t()]);
1179 let mut fb = mb.define_function("main", sig).unwrap();
1180 let [idx, indices, bools] = fb.input_wires_arr();
1181 let [indices] = fb
1182 .call(id.handle(), &[c_int.into()], [indices])
1183 .unwrap()
1184 .outputs_arr();
1185 let [idx2] = fb
1186 .add_dataflow_op(read_op(&ext, i64_t()), [indices, idx])
1187 .unwrap()
1188 .outputs_arr();
1189 let mut cfg = fb
1190 .cfg_builder(
1191 [(i64_t(), idx2), (c_bool.clone(), bools)],
1192 [bool_t()].into(),
1193 )
1194 .unwrap();
1195 let mut entry = cfg.entry_builder([[bool_t()].into()], type_row![]).unwrap();
1196 let [idx2, bools] = entry.input_wires_arr();
1197 let [bools] = entry
1198 .call(id.handle(), &[c_bool.into()], [bools])
1199 .unwrap()
1200 .outputs_arr();
1201 let bool_read_op = entry
1202 .add_dataflow_op(read_op(&ext, bool_t()), [bools, idx2])
1203 .unwrap();
1204 let [tagged] = entry
1205 .add_dataflow_op(
1206 OpType::Tag(Tag::new(0, vec![[bool_t()].into()])),
1207 bool_read_op.outputs(),
1208 )
1209 .unwrap()
1210 .outputs_arr();
1211 let entry = entry.finish_with_outputs(tagged, []).unwrap();
1212 cfg.branch(&entry, 0, &cfg.exit_block()).unwrap();
1213 let cfg = cfg.finish_sub_container().unwrap();
1214 fb.finish_with_outputs(cfg.outputs()).unwrap();
1215 let mut h = mb.finish_hugr().unwrap();
1216
1217 assert!(lowerer(&ext).run(&mut h).unwrap());
1218
1219 let ext_ops = h
1220 .entry_descendants()
1221 .filter_map(|n| h.get_optype(n).as_extension_op());
1222 assert_eq!(
1223 ext_ops
1224 .map(hugr_core::ops::ExtensionOp::unqualified_id)
1225 .sorted()
1226 .collect_vec(),
1227 ["get", "itousize", "lowered_read_bool", "panic",]
1228 );
1229 }
1230
1231 #[test]
1232 fn dfg_conditional_case() {
1233 let ext = ext();
1234 let coln = ext.get_type(PACKED_VEC).unwrap();
1235 let pv = |t: Type| Type::new_extension(coln.instantiate([t.into()]).unwrap());
1236 let sum_rows = [[pv(pv(bool_t())), i64_t()].into(), [pv(i64_t())].into()];
1237 let mut dfb = DFGBuilder::new(inout_sig(
1238 vec![Type::new_sum(sum_rows.clone()), pv(bool_t()), pv(i64_t())],
1239 vec![pv(bool_t()), pv(i64_t())],
1240 ))
1241 .unwrap();
1242 let [sum, vb, vi] = dfb.input_wires_arr();
1243 let mut cb = dfb
1244 .conditional_builder(
1245 (sum_rows, sum),
1246 [(pv(bool_t()), vb), (pv(i64_t()), vi)],
1247 vec![pv(bool_t()), pv(i64_t())].into(),
1248 )
1249 .unwrap();
1250 let mut case0 = cb.case_builder(0).unwrap();
1251 let [vvb, i, _, vi0] = case0.input_wires_arr();
1252 let [vb0] = case0
1253 .add_dataflow_op(read_op(&ext, pv(bool_t())), [vvb, i])
1254 .unwrap()
1255 .outputs_arr();
1256 case0.finish_with_outputs([vb0, vi0]).unwrap();
1257
1258 let case1 = cb.case_builder(1).unwrap();
1259 let [vi, vb1, _vi1] = case1.input_wires_arr();
1260 case1.finish_with_outputs([vb1, vi]).unwrap();
1261 let cond = cb.finish_sub_container().unwrap();
1262 let mut h = dfb.finish_hugr_with_outputs(cond.outputs()).unwrap();
1263
1264 lowerer(&ext).run(&mut h).unwrap();
1265
1266 let ext_ops = h
1267 .entry_descendants()
1268 .filter_map(|n| h.get_optype(n).as_extension_op())
1269 .collect_vec();
1270 assert_eq!(
1271 ext_ops
1272 .iter()
1273 .map(|x| x.unqualified_id())
1274 .sorted()
1275 .collect_vec(),
1276 ["get", "itousize", "panic"]
1277 );
1278 let array_gets = ext_ops
1280 .into_iter()
1281 .filter_map(|e| ListOpInst::from_extension_op(e).ok())
1282 .collect_vec();
1283 assert_eq!(array_gets, [ListOp::get.with_type(i64_t())]);
1284 }
1285
1286 #[test]
1287 fn loop_const() {
1288 let cu = |u| ConstUsize::new(u).into();
1289 let mut tl = TailLoopBuilder::new(
1290 [list_type(usize_t())],
1291 [list_type(bool_t())],
1292 [list_type(usize_t())],
1293 )
1294 .unwrap();
1295 let [_, bools] = tl.input_wires_arr();
1296 let st = SumType::new(vec![[list_type(usize_t())]; 2]);
1297 let pred = tl.add_load_value(
1298 Value::sum(
1299 0,
1300 [ListValue::new(usize_t(), [cu(1), cu(3), cu(3), cu(7)]).into()],
1301 st,
1302 )
1303 .unwrap(),
1304 );
1305 tl.set_outputs(pred, [bools]).unwrap();
1306 let backup = tl.finish_hugr().unwrap();
1307
1308 let mut lowerer = ReplaceTypes::default();
1309
1310 lowerer.set_replace_parametrized_type(list_type_def(), |args| {
1312 let ty = just_elem_type(args);
1313 (![usize_t(), i64_t()].contains(ty)).then_some(borrow_array_type(10, ty.clone()))
1314 });
1315 {
1316 let mut h = backup.clone();
1317 assert_eq!(lowerer.run(&mut h), Ok(true));
1318 let sig = h.signature(h.entrypoint()).unwrap();
1319 assert_eq!(
1320 sig.input(),
1321 &TypeRow::from(vec![list_type(usize_t()), borrow_array_type(10, bool_t())])
1322 );
1323 assert_eq!(sig.input(), sig.output());
1324 }
1325
1326 let usize_custom_t = usize_t().as_extension().unwrap().clone();
1328 lowerer.set_replace_type(usize_custom_t.clone(), i64_t());
1329 lowerer.replace_consts(usize_custom_t, |opaq, _| {
1330 Ok(ConstInt::new_u(
1331 6,
1332 opaq.value().downcast_ref::<ConstUsize>().unwrap().value(),
1333 )
1334 .unwrap()
1335 .into())
1336 });
1337 {
1338 let mut h = backup.clone();
1339 assert_eq!(lowerer.run(&mut h), Ok(true));
1340 let sig = h.signature(h.entrypoint()).unwrap();
1341 assert_eq!(
1342 sig.input(),
1343 &TypeRow::from(vec![list_type(i64_t()), borrow_array_type(10, bool_t())])
1344 );
1345 assert_eq!(sig.input(), sig.output());
1346 let cst = h
1348 .entry_descendants()
1349 .filter_map(|n| h.get_optype(n).as_const())
1350 .exactly_one()
1351 .ok()
1352 .unwrap();
1353 assert_eq!(cst.get_type(), Type::new_sum(vec![[list_type(i64_t())]; 2]));
1354 }
1355
1356 let mut h = backup;
1358 lowerer.set_replace_parametrized_type(
1359 list_type_def(),
1360 Box::new(|args: &[TypeArg]| Some(borrow_array_type(4, just_elem_type(args).clone()))),
1361 );
1362 lowerer.replace_consts_parametrized(list_type_def(), |opaq, repl| {
1363 let Some(Value::Extension { e: opaq }) = list_const(opaq, repl)? else {
1365 panic!("Expected list value to stay a list value");
1366 };
1367 let lv = opaq.value().downcast_ref::<ListValue>().unwrap();
1368
1369 Ok(Some(
1370 BArrayValue::new(lv.get_element_type().clone(), lv.get_contents().to_vec()).into(),
1371 ))
1372 });
1373 lowerer.run(&mut h).unwrap();
1374
1375 assert_eq!(
1376 h.get_optype(pred.node())
1377 .as_load_constant()
1378 .map(hugr_core::ops::LoadConstant::constant_type),
1379 Some(&Type::new_sum(vec![
1380 [Type::from(borrow_array_type(
1381 4,
1382 i64_t()
1383 ))];
1384 2
1385 ]))
1386 );
1387 }
1388
1389 #[test]
1390 fn partial_replace() {
1391 let e = Extension::new_arc(
1392 IdentList::new_unchecked("NoBoundsCheck"),
1393 Version::new(0, 0, 0),
1394 |e, w| {
1395 let params = vec![TypeBound::Linear.into()];
1396 let tv = Type::new_var_use(0, TypeBound::Linear);
1397 let list_of_var = list_type(tv.clone());
1398 e.add_op(
1399 READ.into(),
1400 "Like List::get but without the option".to_string(),
1401 PolyFuncType::new(params, Signature::new([list_of_var, usize_t()], [tv])),
1402 w,
1403 )
1404 .unwrap();
1405 },
1406 );
1407 fn option_contents(ty: &Type) -> Option<Type> {
1408 let row = ty.as_sum()?.get_variant(1).unwrap().clone();
1409 let elem = row.into_owned().into_iter().exactly_one().unwrap();
1410 Some(elem.try_into_type().unwrap())
1411 }
1412 let i32_t = || INT_TYPES[5].clone();
1413 let opt_i32 = Type::from(option_type([i32_t()]));
1414 let i32_custom_t = i32_t().as_extension().unwrap().clone();
1415 let mut dfb = DFGBuilder::new(inout_sig(
1416 vec![list_type(i32_t()), list_type(opt_i32.clone())],
1417 vec![i32_t(), opt_i32.clone()],
1418 ))
1419 .unwrap();
1420 let [l_i, l_oi] = dfb.input_wires_arr();
1421 let idx = dfb.add_load_value(ConstUsize::new(2));
1422 let [i] = dfb
1423 .add_dataflow_op(read_op(&e, i32_t()), [l_i, idx])
1424 .unwrap()
1425 .outputs_arr();
1426 let [oi] = dfb
1427 .add_dataflow_op(read_op(&e, opt_i32.clone()), [l_oi, idx])
1428 .unwrap()
1429 .outputs_arr();
1430 let mut h = dfb.finish_hugr_with_outputs([i, oi]).unwrap();
1431
1432 let mut lowerer = ReplaceTypes::default();
1433 lowerer.set_replace_type(i32_custom_t, qb_t());
1434 lowerer.set_replace_parametrized_type(list_type_def(), |args| {
1436 option_contents(just_elem_type(args)).map(list_type)
1437 });
1438 lowerer.set_replace_parametrized_op(e.get_op(READ).unwrap().as_ref(), |args, _| {
1440 Ok(option_contents(just_elem_type(args)).map(|elem| {
1441 NodeTemplate::SingleOp(
1442 ListOp::get
1443 .with_type(elem)
1444 .to_extension_op()
1445 .unwrap()
1446 .into(),
1447 )
1448 }))
1449 });
1450 assert!(lowerer.run(&mut h).unwrap());
1451 assert_eq!(
1454 h.entrypoint_optype().dataflow_signature().unwrap().io(),
1455 (
1456 &vec![list_type(qb_t()); 2].into(),
1457 &vec![qb_t(), option_type([qb_t()]).into()].into()
1458 )
1459 );
1460 assert_eq!(
1461 h.entry_descendants()
1462 .filter_map(|n| h.get_optype(n).as_extension_op())
1463 .map(hugr_core::ops::ExtensionOp::qualified_id)
1464 .sorted()
1465 .collect_vec(),
1466 ["NoBoundsCheck.read", "collections.list.get"]
1467 );
1468 }
1469
1470 #[rstest]
1471 #[case(&[], Array)]
1472 #[case(&[3], Array)]
1473 #[case(&[5,7,11,13,17,19], BorrowArray)]
1474 fn array_const<AK: ArrayKind>(#[case] vals: &[u64], #[case] _kind: AK)
1475 where
1476 GenericArrayValue<AK>: CustomConst,
1477 {
1478 let mut dfb =
1479 DFGBuilder::new(inout_sig(type_row![], [AK::ty(vals.len() as _, usize_t())])).unwrap();
1480 let c = dfb.add_load_value(GenericArrayValue::<AK>::new(
1481 usize_t(),
1482 vals.iter().map(|u| ConstUsize::new(*u).into()),
1483 ));
1484 let backup = dfb.finish_hugr_with_outputs([c]).unwrap();
1485
1486 let mut repl = ReplaceTypes::new_empty();
1487 let usize_custom_t = usize_t().as_extension().unwrap().clone();
1488 repl.set_replace_type(usize_custom_t.clone(), INT_TYPES[6].clone());
1489 repl.replace_consts(usize_custom_t, |cst: &OpaqueValue, _| {
1490 let cu = cst.value().downcast_ref::<ConstUsize>().unwrap();
1491 Ok(ConstInt::new_u(6, cu.value())?.into())
1492 });
1493
1494 let mut h = backup.clone();
1495 repl.run(&mut h).unwrap(); assert!(
1497 matches!(h.validate(), Err(ValidationError::IncompatiblePorts {from, to, ..})
1498 if backup.get_optype(from).is_const() && to == c.node())
1499 );
1500 repl.replace_consts_parametrized(AK::type_def(), generic_array_const::<AK>);
1501 let mut h = backup;
1502 repl.run(&mut h).unwrap();
1503 h.validate().unwrap();
1504 }
1505
1506 #[rstest]
1507 fn op_to_call_polymorphic(#[values(true, false)] use_linking: bool) {
1508 let e = ext();
1512 let pv = e.get_type(PACKED_VEC).unwrap();
1513 let inner = pv.instantiate([usize_t().into()]).unwrap();
1514 let outer = pv
1515 .instantiate([Type::new_extension(inner.clone()).into()])
1516 .unwrap();
1517 let mut dfb = DFGBuilder::new(inout_sig([outer.into(), i64_t()], [usize_t()])).unwrap();
1518 let read_func = dfb
1519 .module_root_builder()
1520 .add_hugr(
1521 lowered_read(Type::new_var_use(0, TypeBound::Copyable), |sig| {
1522 FunctionBuilder::new_vis(
1523 "lowered_read",
1524 PolyFuncType::new([TypeBound::Copyable.into()], sig),
1525 Visibility::Public,
1526 )
1527 })
1528 .finish_hugr()
1529 .unwrap(),
1530 )
1531 .inserted_entrypoint;
1532 let [outer, idx] = dfb.input_wires_arr();
1533 let [inner] = dfb
1534 .add_dataflow_op(read_op(&e, inner.clone().into()), [outer, idx])
1535 .unwrap()
1536 .outputs_arr();
1537 let res = dfb
1538 .add_dataflow_op(read_op(&e, usize_t()), [inner, idx])
1539 .unwrap();
1540 let mut h = dfb.finish_hugr_with_outputs(res.outputs()).unwrap();
1541 let read_poly = h
1542 .get_optype(read_func)
1543 .as_func_defn()
1544 .unwrap()
1545 .signature()
1546 .clone();
1547
1548 let mut lw = lowerer(&e);
1549 lw.set_replace_parametrized_op(e.get_op(READ).unwrap().as_ref(), move |args, _| {
1550 Ok(Some(if use_linking {
1551 let mut decl_b = ModuleBuilder::new();
1552 let decl_node = decl_b.declare("lowered_read", read_poly.clone()).unwrap();
1553 let mut decl_hugr = decl_b.finish_hugr().unwrap();
1554 decl_hugr.set_entrypoint(decl_node.node());
1555
1556 NodeTemplate::call_to_function(decl_hugr, args).unwrap()
1557 } else {
1558 #[expect(deprecated)] NodeTemplate::Call(read_func, args.to_owned())
1560 }))
1561 });
1562 lw.run(&mut h).unwrap();
1563 h.validate().unwrap();
1564
1565 assert_eq!(h.output_neighbours(read_func).count(), 2);
1566 assert_eq!(
1567 h.entry_descendants()
1568 .find(|n| h.get_optype(*n).is_extension_op()),
1569 None
1570 );
1571 assert_eq!(h.children(h.module_root()).count(), 2); }
1573
1574 #[rstest]
1575 fn op_to_call_monomorphic(#[values(false, true)] i64_to_usize: bool) {
1576 let e = ext();
1577 let pv = e.get_type(PACKED_VEC).unwrap();
1578 let inner = pv.instantiate([usize_t().into()]).unwrap();
1579 let outer = pv
1580 .instantiate([Type::new_extension(inner.clone()).into()])
1581 .unwrap();
1582 let read_outer = read_op(&e, inner.clone().into());
1583 let mut dfb = DFGBuilder::new(inout_sig(
1584 vec![outer.into(), inner.clone().into(), i64_t()],
1585 vec![usize_t(); 2],
1586 ))
1587 .unwrap();
1588
1589 let [outer, inner, idx] = dfb.input_wires_arr();
1590 let res1 = dfb
1591 .add_dataflow_op(read_op(&e, usize_t()), [inner, idx])
1592 .unwrap();
1593 let [inner] = dfb
1594 .add_dataflow_op(read_outer, [outer, idx])
1595 .unwrap()
1596 .outputs_arr();
1597 let res2 = dfb
1598 .add_dataflow_op(read_op(&e, usize_t()), [inner, idx])
1599 .unwrap();
1600 let mut h = dfb
1601 .finish_hugr_with_outputs(res1.outputs().chain(res2.outputs()))
1602 .unwrap();
1603
1604 let mut lw = lowerer(&e);
1605 lw.set_replace_parametrized_op(e.get_op(READ).unwrap().as_ref(), move |args, _| {
1606 Ok(Some({
1607 let [Term::Runtime(ty)] = args else {
1608 return Err(SignatureError::InvalidTypeArgs.into());
1609 };
1610
1611 let defn_hugr = lowered_read(ty.clone(), |sig| {
1612 FunctionBuilder::new_vis(
1613 mangle_name("lowered_read", args),
1614 sig,
1615 Visibility::Public,
1616 )
1617 })
1618 .finish_hugr()
1619 .unwrap();
1620
1621 NodeTemplate::call_to_function(defn_hugr, &[]).unwrap()
1622 }))
1623 });
1624 if i64_to_usize {
1625 lw.set_replace_type(i64_t().as_extension().unwrap().clone(), usize_t());
1626 lw.set_replace_op(
1627 &ConvertOpDef::itousize
1628 .without_log_width()
1629 .to_extension_op()
1630 .unwrap(),
1631 NodeTemplate::SingleOp(Noop::new(usize_t()).into()),
1632 );
1633 }
1634 lw.run(&mut h).unwrap();
1635 h.validate().unwrap();
1636
1637 assert_eq!(
1638 h.entry_descendants()
1639 .find(|n| h.get_optype(*n).is_extension_op()),
1640 None
1641 );
1642 assert_eq!(h.children(h.module_root()).count(), 3); for n in h.children(h.module_root()) {
1644 let fd = h.get_optype(n).as_func_defn().unwrap();
1645 let expected_uses_and_vis = if fd.func_name() == "main" {
1646 (0, Visibility::Private)
1647 } else {
1648 let is_array = !fd.signature().body().output[0]
1649 .as_extension()
1650 .unwrap()
1651 .args()
1652 .is_empty();
1653 (2 - (is_array as usize), Visibility::Public)
1654 };
1655 assert_eq!(h.output_neighbours(n).count(), expected_uses_and_vis.0);
1656 assert_eq!(fd.visibility(), &expected_uses_and_vis.1);
1657 }
1658 }
1659
1660 #[test]
1661 fn regions() {
1662 let ext = ext();
1663 let coln = ext.get_type(PACKED_VEC).unwrap();
1664 let c_u = Type::new_extension(coln.instantiate(&[usize_t().into()]).unwrap());
1665 let mut h = {
1666 let db = DFGBuilder::new(endo_sig([c_u.clone()])).unwrap();
1667 let inps = db.input_wires();
1668 db.finish_hugr_with_outputs(inps)
1669 }
1670 .unwrap();
1671 let mut lowerer = lowerer(&ext);
1672
1673 {
1674 let backup = h.clone();
1675 lowerer.set_regions(vec![]);
1676 assert!(!lowerer.run(&mut h).unwrap());
1677 assert_eq!(h, backup);
1678 }
1679
1680 let ep = h.entrypoint();
1681 lowerer.set_regions(vec![h.entrypoint()]);
1682 assert!(lowerer.run(&mut h).unwrap());
1683 let v_u = list_type(usize_t());
1684 assert_eq!(h.signature(ep).unwrap().as_ref(), &endo_sig([v_u.clone()]));
1685 assert_eq!(h.num_nodes(), h.num_nodes());
1686 let [f_in, _] = h.get_io(h.get_parent(ep).unwrap()).unwrap();
1687 assert_eq!(
1688 h.validate(),
1689 Err(ValidationError::IncompatiblePorts {
1690 from: f_in,
1691 from_port: Port::new(Direction::Outgoing, 0),
1692 to: ep,
1693 to_port: Port::new(Direction::Incoming, 0),
1694 from_kind: Box::new(EdgeKind::Value(c_u)),
1695 to_kind: Box::new(EdgeKind::Value(v_u))
1696 })
1697 );
1698 }
1699
1700 #[test]
1701 fn compositionality() {
1702 let ext = ext();
1703 let mut lowerer = lowerer(&ext);
1704 let ext2 = ext.clone();
1706 lowerer.set_replace_parametrized_type(array_type_def(), move |args| {
1707 let [sz, ty] = args else {
1708 panic!("Expected two args to array")
1709 };
1710 (sz == &Term::BoundedNat(64)).then_some(
1711 ext2.get_type(PACKED_VEC)
1712 .unwrap()
1713 .instantiate([ty.clone()])
1714 .unwrap()
1715 .into(),
1716 )
1717 });
1718
1719 let ext = ext.clone();
1721 lowerer.set_replace_parametrized_op(
1722 array::EXTENSION
1723 .get_op(ArrayOpDef::get.opdef_id().as_str())
1724 .unwrap()
1725 .as_ref(),
1726 move |args, _| {
1727 let [sz, Term::Runtime(ty)] = args else {
1728 panic!("Expected two args to array-get")
1729 };
1730 if sz != &Term::BoundedNat(64) {
1731 return Ok(None);
1732 }
1733 let pv = ext
1734 .get_type(PACKED_VEC)
1735 .unwrap()
1736 .instantiate([ty.clone().into()])
1737 .unwrap();
1738
1739 let mut dfb = DFGBuilder::new(Signature::new(
1740 vec![pv.clone().into(), usize_t()],
1741 vec![option_type([ty.clone()]).into(), pv.into()],
1742 ))
1743 .unwrap();
1744 let [pvec, idx] = dfb.input_wires_arr();
1745 let [idx] = dfb
1746 .add_dataflow_op(ConvertOpDef::ifromusize.without_log_width(), [idx])
1747 .unwrap()
1748 .outputs_arr();
1749 let [elem] = dfb
1750 .add_dataflow_op(read_op(&ext, ty.clone()), [pvec, idx])
1751 .unwrap()
1752 .outputs_arr();
1753 let [wrapped_elem] = dfb
1754 .add_dataflow_op(
1755 ops::Tag::new(1, vec![type_row![], [ty.clone()].into()]),
1756 [elem],
1757 )
1758 .unwrap()
1759 .outputs_arr();
1760 Ok(Some(NodeTemplate::CompoundOp(Box::new(
1761 dfb.finish_hugr_with_outputs([wrapped_elem, pvec]).unwrap(),
1762 ))))
1763 },
1764 );
1765
1766 let a64 = |t| array_type(64, t);
1769 let opt = |t| Type::from(option_type([t]));
1770 let mut dfb = DFGBuilder::new(Signature::new(
1771 vec![a64(bool_t()), a64(usize_t())],
1772 vec![opt(bool_t()), a64(bool_t()), opt(usize_t()), a64(usize_t())],
1773 ))
1774 .unwrap();
1775 let [bools, usizes] = dfb.input_wires_arr();
1776 let idx = dfb.add_load_value(ConstUsize::new(5));
1777 let [b, bools] = dfb
1778 .add_dataflow_op(ArrayOpDef::get.to_concrete(bool_t(), 64), [bools, idx])
1779 .unwrap()
1780 .outputs_arr();
1781 let [u, usizes] = dfb
1782 .add_dataflow_op(ArrayOpDef::get.to_concrete(usize_t(), 64), [usizes, idx])
1783 .unwrap()
1784 .outputs_arr();
1785 let mut h = dfb.finish_hugr_with_outputs([b, bools, u, usizes]).unwrap();
1786
1787 lowerer.run(&mut h).unwrap();
1788
1789 h.validate().unwrap();
1790 }
1791}