1use std::{collections::HashMap, sync::Arc};
2
3use hugr_core::builder::{
4 BuildError, ConditionalBuilder, DFGBuilder, Dataflow, DataflowHugr, DataflowSubContainer,
5 HugrBuilder, inout_sig,
6};
7use hugr_core::extension::{SignatureError, TypeDef};
8use hugr_core::std_extensions::collections::array::array_type_def;
9use hugr_core::std_extensions::collections::borrow_array::borrow_array_type_def;
10use hugr_core::types::{CustomType, Signature, Type, TypeArg, TypeEnum, TypeRow};
11use hugr_core::{HugrView, IncomingPort, Node, Wire, hugr::hugrmut::HugrMut, ops::Tag};
12use itertools::Itertools;
13
14use super::handlers::{copy_discard_array, copy_discard_borrow_array};
15use super::{NodeTemplate, ParametricType};
16
17#[deprecated(
34 note = "`hugr-passes` is deprecated. Use tket::passes instead",
35 since = "0.26.2"
36)]
37pub trait Linearizer {
38 fn insert_copy_discard(
58 &self,
59 hugr: &mut impl HugrMut<Node = Node>,
60 src: Wire,
61 targets: &[(Node, IncomingPort)],
62 ) -> Result<(), LinearizeError> {
63 let (tgt_node, tgt_inport) = if targets.len() == 1 {
64 *targets.first().unwrap()
65 } else {
66 let src_parent = hugr
68 .get_parent(src.node())
69 .expect("Root node cannot have out edges");
70 if let Some((tgt, tgt_parent)) = targets.iter().find_map(|(tgt, _)| {
71 let tgt_parent = hugr
72 .get_parent(*tgt)
73 .expect("Root node cannot have incoming edges");
74 (tgt_parent != src_parent).then_some((*tgt, tgt_parent))
75 }) {
76 return Err(LinearizeError::NoLinearNonLocalEdges {
77 src: src.node(),
78 src_parent,
79 tgt,
80 tgt_parent,
81 });
82 }
83 let sig = hugr.signature(src.node()).unwrap();
84 let typ = sig.port_type(src.source()).unwrap().clone();
85 let copy_discard_op = self
86 .copy_discard_op(&typ, targets.len())?
87 .add_hugr(hugr, src_parent)
88 .map_err(|e| LinearizeError::NestedTemplateError(Box::new(typ), Box::new(e)))?;
89 for (n, (tgt_node, tgt_port)) in targets.iter().enumerate() {
90 hugr.connect(copy_discard_op, n, *tgt_node, *tgt_port);
91 }
92 (copy_discard_op, 0.into())
93 };
94 hugr.connect(src.node(), src.source(), tgt_node, tgt_inport);
95 Ok(())
96 }
97
98 fn copy_discard_op(
105 &self,
106 typ: &Type,
107 num_outports: usize,
108 ) -> Result<NodeTemplate, LinearizeError>;
109}
110
111#[derive(Clone)]
115#[deprecated(
116 note = "`hugr-passes` is deprecated. Use tket::passes instead",
117 since = "0.26.2"
118)]
119pub struct DelegatingLinearizer {
120 copy_discard: HashMap<CustomType, (NodeTemplate, NodeTemplate)>,
122 copy_discard_parametric: HashMap<
128 ParametricType,
129 Arc<
130 dyn Fn(&[TypeArg], usize, &CallbackHandler<'_>) -> Result<NodeTemplate, LinearizeError>,
131 >,
132 >,
133}
134
135impl Default for DelegatingLinearizer {
136 fn default() -> Self {
137 let mut res = Self::new_empty();
138 res.register_callback(array_type_def(), copy_discard_array);
139 res.register_callback(borrow_array_type_def(), copy_discard_borrow_array);
140 res
141 }
142}
143
144#[deprecated(
149 note = "`hugr-passes` is deprecated. Use tket::passes instead",
150 since = "0.26.2"
151)]
152pub struct CallbackHandler<'a>(&'a DelegatingLinearizer);
153
154#[derive(Clone, Debug, thiserror::Error, PartialEq)]
155#[expect(missing_docs)]
156#[non_exhaustive]
157#[deprecated(
158 note = "`hugr-passes` is deprecated. Use tket::passes instead",
159 since = "0.26.2"
160)]
161pub enum LinearizeError {
162 #[error("Need copy/discard op for {_0}")]
163 NeedCopyDiscard(Box<Type>),
164 #[error("Copy/discard op for {typ} with {num_outports} outputs had wrong signature {sig:?}")]
165 WrongSignature {
166 typ: Box<Type>,
167 num_outports: usize,
168 sig: Option<Box<Signature>>,
169 },
170 #[error(
171 "Cannot add nonlocal edge for linear type from {src} (with parent {src_parent}) to {tgt} (with parent {tgt_parent}).
172 Try using LocalizeEdges pass first."
173 )]
174 NoLinearNonLocalEdges {
175 src: Node,
176 src_parent: Node,
177 tgt: Node,
178 tgt_parent: Node,
179 },
180 #[error(transparent)]
182 SignatureError(#[from] SignatureError),
183 #[error("Cannot linearize type {_0}")]
187 UnsupportedType(Box<Type>),
188 #[error("Type {_0} is copyable")]
190 CopyableType(Box<Type>),
191 #[error("Could not generate NodeTemplate for contained type {0} because {1}")]
194 NestedTemplateError(Box<Type>, Box<BuildError>),
195}
196
197impl DelegatingLinearizer {
198 #[must_use]
201 pub fn new_empty() -> Self {
202 Self {
203 copy_discard: Default::default(),
204 copy_discard_parametric: Default::default(),
205 }
206 }
207
208 pub fn register_simple(
223 &mut self,
224 cty: CustomType,
225 copy: NodeTemplate,
226 discard: NodeTemplate,
227 ) -> Result<(), LinearizeError> {
228 let typ = Type::new_extension(cty.clone());
229 if typ.copyable() {
230 return Err(LinearizeError::CopyableType(Box::new(typ)));
231 }
232 check_sig(©, &typ, 2)?;
233 check_sig(&discard, &typ, 0)?;
234 self.copy_discard.insert(cty, (copy, discard));
235 Ok(())
236 }
237
238 pub fn register_callback(
251 &mut self,
252 src: &TypeDef,
253 copy_discard_fn: impl Fn(
254 &[TypeArg],
255 usize,
256 &CallbackHandler<'_>,
257 ) -> Result<NodeTemplate, LinearizeError>
258 + 'static,
259 ) {
260 self.copy_discard_parametric
264 .insert(src.into(), Arc::new(copy_discard_fn));
265 }
266}
267
268fn check_sig(tmpl: &NodeTemplate, typ: &Type, num_outports: usize) -> Result<(), LinearizeError> {
269 tmpl.check_signature(
270 &[typ.clone()].into(),
271 &vec![typ.clone(); num_outports].into(),
272 )
273 .map_err(|sig| LinearizeError::WrongSignature {
274 typ: Box::new(typ.clone()),
275 num_outports,
276 sig: sig.map(Box::new),
277 })
278}
279
280impl Linearizer for DelegatingLinearizer {
281 fn copy_discard_op(
282 &self,
283 typ: &Type,
284 num_outports: usize,
285 ) -> Result<NodeTemplate, LinearizeError> {
286 if typ.copyable() {
287 return Err(LinearizeError::CopyableType(Box::new(typ.clone())));
288 }
289 assert!(num_outports != 1);
290
291 match typ.as_type_enum() {
292 TypeEnum::Sum(sum_type) => {
293 let variants = sum_type
294 .variants()
295 .map(|trv| trv.clone().try_into())
296 .collect::<Result<Vec<TypeRow>, _>>()?;
297 let mut cb = ConditionalBuilder::new(
298 variants.clone(),
299 vec![],
300 vec![sum_type.clone().into(); num_outports],
301 )
302 .unwrap();
303 for (tag, variant) in variants.iter().enumerate() {
304 let mut case_b = cb.case_builder(tag).unwrap();
305 let mut elems_for_copy = vec![vec![]; num_outports];
306 for (inp, ty) in case_b.input_wires().zip_eq(variant.iter()) {
307 let inp_copies = if ty.copyable() {
308 std::iter::repeat_n(inp, num_outports).collect::<Vec<_>>()
309 } else {
310 self.copy_discard_op(ty, num_outports)?
311 .add(&mut case_b, [inp])
312 .unwrap()
313 .outputs()
314 .collect()
315 };
316 for (src, elems) in inp_copies.into_iter().zip_eq(elems_for_copy.iter_mut())
317 {
318 elems.push(src);
319 }
320 }
321 let t = Tag::new(tag, variants.clone());
322 let outputs = elems_for_copy
323 .into_iter()
324 .map(|elems| {
325 let [copy] = case_b
326 .add_dataflow_op(t.clone(), elems)
327 .unwrap()
328 .outputs_arr();
329 copy
330 })
331 .collect::<Vec<_>>(); case_b.finish_with_outputs(outputs).unwrap();
333 }
334 Ok(NodeTemplate::CompoundOp(Box::new(
335 cb.finish_hugr().unwrap(),
336 )))
337 }
338 TypeEnum::Extension(cty) => {
339 if let Some((copy, discard)) = self.copy_discard.get(cty) {
340 Ok(if num_outports == 0 {
341 discard.clone()
342 } else {
343 let mut dfb = DFGBuilder::new(inout_sig(
344 [typ.clone()],
345 vec![typ.clone(); num_outports],
346 ))
347 .unwrap();
348 let [mut src] = dfb.input_wires_arr();
349 let mut outputs = vec![];
350 for _ in 0..num_outports - 1 {
351 let [out0, out1] =
352 copy.clone().add(&mut dfb, [src]).unwrap().outputs_arr();
353 outputs.push(out0);
354 src = out1;
355 }
356 outputs.push(src);
357 NodeTemplate::CompoundOp(Box::new(
358 dfb.finish_hugr_with_outputs(outputs).unwrap(),
359 ))
360 })
361 } else {
362 let copy_discard_fn = self
363 .copy_discard_parametric
364 .get(&cty.into())
365 .ok_or_else(|| LinearizeError::NeedCopyDiscard(Box::new(typ.clone())))?;
366 let tmpl = copy_discard_fn(cty.args(), num_outports, &CallbackHandler(self))?;
367 check_sig(&tmpl, typ, num_outports)?;
368 Ok(tmpl)
369 }
370 }
371 TypeEnum::Function(_) => panic!("Ruled out above as copyable"),
372 _ => Err(LinearizeError::UnsupportedType(Box::new(typ.clone()))),
373 }
374 }
375}
376
377impl Linearizer for CallbackHandler<'_> {
378 fn copy_discard_op(
379 &self,
380 typ: &Type,
381 num_outports: usize,
382 ) -> Result<NodeTemplate, LinearizeError> {
383 self.0.copy_discard_op(typ, num_outports)
384 }
385}
386
387#[cfg(test)]
388mod test {
389 use std::collections::HashMap;
390 use std::sync::Arc;
391
392 use hugr_core::builder::{
393 Container, DFGBuilder, Dataflow, DataflowHugr, DataflowSubContainer, HugrBuilder, inout_sig,
394 };
395
396 use hugr_core::Visibility;
397 use hugr_core::extension::prelude::{option_type, qb_t, usize_t};
398 use hugr_core::extension::{
399 CustomSignatureFunc, OpDef, SignatureError, SignatureFunc, TypeDefBound, Version,
400 };
401 use hugr_core::hugr::ValidationError;
402 use hugr_core::hugr::hugrmut::HugrMut;
403 use hugr_core::ops::handle::NodeHandle;
404 use hugr_core::ops::{DataflowOpTrait, ExtensionOp, OpName, OpType};
405 use hugr_core::std_extensions::arithmetic::int_types::INT_TYPES;
406 use hugr_core::std_extensions::collections::array::array_type;
407 use hugr_core::std_extensions::collections::borrow_array::{BArrayOpDef, borrow_array_type};
408 use hugr_core::types::type_param::TypeParam;
409 use hugr_core::types::{
410 FuncValueType, PolyFuncTypeRV, Signature, Type, TypeArg, TypeBound, TypeRow,
411 };
412 use hugr_core::{Extension, Hugr, HugrView, Node, hugr::IdentList};
413 use itertools::Itertools;
414 use rstest::rstest;
415
416 use crate::replace_types::handlers::{DISCARD_TO_UNIT_PREFIX, MAKE_NONE_PREFIX, UNWRAP_PREFIX};
417 use crate::replace_types::{LinearizeError, Linearizer, NodeTemplate, ReplaceTypesError};
418 use crate::{ComposablePass, ReplaceTypes};
419
420 const LIN_T: &str = "Lin";
421 const COPY_T: &str = "Copy";
422
423 struct NWayCopySigFn(Type);
424 impl CustomSignatureFunc for NWayCopySigFn {
425 fn compute_signature<'o, 'a: 'o>(
426 &'a self,
427 arg_values: &[TypeArg],
428 _def: &'o OpDef,
429 ) -> Result<PolyFuncTypeRV, SignatureError> {
430 let [TypeArg::BoundedNat(n)] = arg_values else {
431 panic!()
432 };
433 let outs = vec![self.0.clone(); *n as usize];
434 Ok(FuncValueType::new([self.0.clone()], outs).into())
435 }
436
437 fn static_params(&self) -> &[TypeParam] {
438 const JUST_NAT: &[TypeParam] = &[TypeParam::max_nat_type()];
439 JUST_NAT
440 }
441 }
442
443 fn ext_lowerer() -> (Arc<Extension>, ReplaceTypes) {
444 let e = Extension::new_arc(
446 IdentList::new_unchecked("TestExt"),
447 Version::new(0, 0, 0),
448 |e, w| {
449 let lin = Type::new_extension(
450 e.add_type(LIN_T.into(), vec![], String::new(), TypeDefBound::any(), w)
451 .unwrap()
452 .instantiate([])
453 .unwrap(),
454 );
455 e.add_type(
456 COPY_T.into(),
457 vec![],
458 String::new(),
459 TypeDefBound::copyable(),
460 w,
461 )
462 .unwrap()
463 .instantiate([])
464 .unwrap();
465 e.add_op(
466 "discard".into(),
467 String::new(),
468 Signature::new([lin.clone()], []),
469 w,
470 )
471 .unwrap();
472 e.add_op(
473 "copy".into(),
474 String::new(),
475 SignatureFunc::CustomFunc(Box::new(NWayCopySigFn(lin))),
476 w,
477 )
478 .unwrap();
479 },
480 );
481
482 let lin_custom_t = e.get_type(LIN_T).unwrap().instantiate([]).unwrap();
483
484 let copy_op = ExtensionOp::new(e.get_op("copy").unwrap().clone(), [2.into()]).unwrap();
486 let discard_op = ExtensionOp::new(e.get_op("discard").unwrap().clone(), []).unwrap();
487 let mut lowerer = ReplaceTypes::default();
488 let usize_custom_t = usize_t().as_extension().unwrap().clone();
489 lowerer.set_replace_type(usize_custom_t, Type::new_extension(lin_custom_t.clone()));
490 lowerer
491 .linearizer_mut()
492 .register_simple(
493 lin_custom_t,
494 NodeTemplate::SingleOp(copy_op.into()),
495 NodeTemplate::SingleOp(discard_op.into()),
496 )
497 .unwrap();
498 (e, lowerer)
499 }
500
501 #[test]
502 fn single_values() {
503 let (_e, lowerer) = ext_lowerer();
504 let mut outer = DFGBuilder::new(inout_sig(
506 vec![usize_t(); 2],
507 vec![usize_t(), borrow_array_type(2, usize_t())],
508 ))
509 .unwrap();
510 let [inp, _] = outer.input_wires_arr();
511 let new_array = outer
512 .add_dataflow_op(BArrayOpDef::new_array.to_concrete(usize_t(), 2), [inp, inp])
513 .unwrap();
514 let [arr] = new_array.outputs_arr();
515 let mut h = outer.finish_hugr_with_outputs([inp, arr]).unwrap();
516
517 assert!(lowerer.run(&mut h).unwrap());
518
519 let ext_ops = h
520 .entry_descendants()
521 .filter_map(|n| h.get_optype(n).as_extension_op());
522 let mut counts = HashMap::<OpName, u32>::new();
523 for e in ext_ops {
524 *counts.entry(e.qualified_id()).or_default() += 1;
525 }
526 assert_eq!(
527 counts,
528 HashMap::from([
529 ("TestExt.copy".into(), 2),
530 ("TestExt.discard".into(), 1),
531 ("collections.borrow_arr.new_array".into(), 1)
532 ])
533 );
534 }
535
536 fn copy_n_discard_one(ty: Type, n: usize) -> (Hugr, Node) {
537 let mut outer = DFGBuilder::new(inout_sig([ty.clone()], vec![ty.clone(); n - 1])).unwrap();
538 let [inp] = outer.input_wires_arr();
539 let inner = outer
540 .dfg_builder(inout_sig([ty], []), [inp])
541 .unwrap()
542 .finish_with_outputs([])
543 .unwrap();
544 let h = outer.finish_hugr_with_outputs(vec![inp; n - 1]).unwrap();
545 (h, inner.node())
546 }
547
548 #[rstest]
549 fn sums_2way_copy(#[values(2, 3, 4)] num_copies: usize) {
550 let (mut h, inner) = copy_n_discard_one(option_type([usize_t()]).into(), num_copies);
551
552 let (e, lowerer) = ext_lowerer();
553 assert!(lowerer.run(&mut h).unwrap());
554
555 let lin_t = Type::from(e.get_type(LIN_T).unwrap().instantiate([]).unwrap());
556 let sum_ty: Type = option_type([lin_t.clone()]).into();
557 let count_tags = |n| h.children(n).filter(|n| h.get_optype(*n).is_tag()).count();
558
559 for (dfg, num_tags, expected_ext_ops) in [
561 (inner.node(), 0, vec!["TestExt.discard"]),
562 (
563 h.entrypoint(),
564 num_copies,
565 vec!["TestExt.copy"; num_copies - 1],
566 ), ] {
568 let [(cond_node, cond)] = h
569 .children(dfg)
570 .filter_map(|n| h.get_optype(n).as_conditional().map(|c| (n, c)))
571 .collect_array()
572 .unwrap();
573 assert_eq!(
574 cond.signature().output(),
575 &TypeRow::from(vec![sum_ty.clone(); num_tags])
576 );
577 let [case0, case1] = h.children(cond_node).collect_array().unwrap();
578 assert_eq!(h.children(case0).count(), 2 + num_tags); assert_eq!(count_tags(case0), num_tags);
581
582 assert_eq!(h.children(case1).count(), 3 + num_tags); assert_eq!(count_tags(case1), num_tags);
585 let ext_ops = h
586 .descendants(case1)
587 .filter_map(|n| {
588 h.get_optype(n)
589 .as_extension_op()
590 .map(ExtensionOp::qualified_id)
591 })
592 .collect_vec();
593 assert_eq!(ext_ops, expected_ext_ops);
594 }
595 }
596
597 #[rstest]
598 fn sum_nway_copy(#[values(2, 5, 9)] num_copies: usize) {
599 let i8_t = || INT_TYPES[3].clone();
600 let sum_ty = Type::new_sum([vec![i8_t()], vec![usize_t(); 2]]);
601
602 let (mut h, inner) = copy_n_discard_one(sum_ty, num_copies);
603 let (e, _) = ext_lowerer();
604 let mut lowerer = ReplaceTypes::default();
605 let lin_t_def = e.get_type(LIN_T).unwrap();
606 lowerer.set_replace_type(
607 usize_t().as_extension().unwrap().clone(),
608 lin_t_def.instantiate([]).unwrap().into(),
609 );
610 let opdef = e.get_op("copy").unwrap();
611 let opdef2 = opdef.clone();
612 lowerer
613 .linearizer_mut()
614 .register_callback(lin_t_def, move |args, num_outs, _| {
615 assert!(args.is_empty());
616 Ok(NodeTemplate::SingleOp(
617 ExtensionOp::new(opdef2.clone(), [(num_outs as u64).into()])
618 .unwrap()
619 .into(),
620 ))
621 });
622 assert!(lowerer.run(&mut h).unwrap());
623
624 let lin_t = Type::from(e.get_type(LIN_T).unwrap().instantiate([]).unwrap());
625 let sum_ty = Type::new_sum([vec![i8_t()], vec![lin_t.clone(); 2]]);
626 let count_tags = |n| h.children(n).filter(|n| h.get_optype(*n).is_tag()).count();
627
628 for (dfg, num_tags) in [(inner.node(), 0), (h.entrypoint(), num_copies)] {
630 let [cond] = h
631 .children(dfg)
632 .filter(|n| h.get_optype(*n).is_conditional())
633 .collect_array()
634 .unwrap();
635 let [case0, case1] = h.children(cond).collect_array().unwrap();
636 let out_row = vec![sum_ty.clone(); num_tags].into();
637 assert_eq!(h.children(case0).count(), 2 + num_tags); assert_eq!(count_tags(case0), num_tags);
640 let case0 = h.get_optype(case0).as_case().unwrap();
641 assert_eq!(case0.signature.io(), (&vec![i8_t()].into(), &out_row));
642
643 assert_eq!(h.children(case1).count(), 4 + num_tags); assert_eq!(count_tags(case1), num_tags);
646 let ext_ops = h
647 .children(case1)
648 .filter_map(|n| h.get_optype(n).as_extension_op())
649 .collect_vec();
650 let expected_op = ExtensionOp::new(opdef.clone(), [(num_tags as u64).into()]).unwrap();
651 assert_eq!(ext_ops, vec![&expected_op; 2]);
652
653 let case1 = h.get_optype(case1).as_case().unwrap();
654 assert_eq!(
655 case1.signature.io(),
656 (&vec![lin_t.clone(); 2].into(), &out_row)
657 );
658 }
659 }
660
661 #[test]
662 fn bad_sig() {
663 let (ext, _) = ext_lowerer();
665 let lin_ct = ext.get_type(LIN_T).unwrap().instantiate([]).unwrap();
666 let lin_t = Type::from(lin_ct.clone());
667 let copy3 = OpType::from(
668 ExtensionOp::new(ext.get_op("copy").unwrap().clone(), [3.into()]).unwrap(),
669 );
670 let copy2 = ExtensionOp::new(ext.get_op("copy").unwrap().clone(), [2.into()]).unwrap();
671 let discard = ExtensionOp::new(ext.get_op("discard").unwrap().clone(), []).unwrap();
672 let mut replacer = ReplaceTypes::default();
673 replacer.set_replace_type(usize_t().as_extension().unwrap().clone(), lin_t.clone());
674
675 let bad_copy = replacer.linearizer_mut().register_simple(
676 lin_ct.clone(),
677 NodeTemplate::SingleOp(copy3.clone()),
678 NodeTemplate::SingleOp(discard.clone().into()),
679 );
680 let sig3 = Some(Signature::new([lin_t.clone()], vec![lin_t.clone(); 3]));
681 assert_eq!(
682 bad_copy,
683 Err(LinearizeError::WrongSignature {
684 typ: Box::new(lin_t.clone()),
685 num_outports: 2,
686 sig: sig3.clone().map(Box::new)
687 })
688 );
689
690 let bad_discard = replacer.linearizer_mut().register_simple(
691 lin_ct.clone(),
692 NodeTemplate::SingleOp(copy2.into()),
693 NodeTemplate::SingleOp(copy3.clone()),
694 );
695
696 assert_eq!(
697 bad_discard,
698 Err(LinearizeError::WrongSignature {
699 typ: Box::new(lin_t.clone()),
700 num_outports: 0,
701 sig: sig3.clone().map(Box::new)
702 })
703 );
704
705 replacer
707 .linearizer_mut()
708 .register_callback(ext.get_type(LIN_T).unwrap(), move |_args, _, _| {
709 Ok(NodeTemplate::SingleOp(copy3.clone()))
710 });
711
712 let dfb = DFGBuilder::new(inout_sig([usize_t()], vec![usize_t(); 2])).unwrap();
714 let [inp] = dfb.input_wires_arr();
715 let mut h = dfb.finish_hugr_with_outputs([inp, inp]).unwrap();
716
717 assert_eq!(
718 replacer.run(&mut h),
719 Err(ReplaceTypesError::LinearizeError(
720 LinearizeError::WrongSignature {
721 typ: Box::new(lin_t.clone()),
722 num_outports: 2,
723 sig: sig3.clone().map(Box::new)
724 }
725 ))
726 );
727 }
728
729 #[rstest]
730 fn call_in_array(#[values(true, false)] use_linking: bool) {
731 let (e, _) = ext_lowerer();
732 let lin_ct = e.get_type(LIN_T).unwrap().instantiate([]).unwrap();
733 let lin_t: Type = lin_ct.clone().into();
734
735 let mut dfb = DFGBuilder::new(inout_sig([usize_t()], [])).unwrap();
737 let discard_fn = {
738 let mut mb = dfb.module_root_builder();
739 let mut fb = mb
740 .define_function_vis(
741 "drop",
742 Signature::new([lin_t.clone()], []),
743 Visibility::Public,
744 )
745 .unwrap();
746 let ins = fb.input_wires();
747 fb.add_dataflow_op(
748 ExtensionOp::new(e.get_op("discard").unwrap().clone(), []).unwrap(),
749 ins,
750 )
751 .unwrap();
752 fb.finish_with_outputs([]).unwrap()
753 }
754 .node();
755 let backup = dfb.finish_hugr().unwrap();
756
757 let mut lower_discard_to_call = ReplaceTypes::default();
758 if use_linking {
759 lower_discard_to_call
760 .linearizer_mut()
761 .register_simple(
762 lin_ct.clone(),
763 NodeTemplate::CompoundOp(Box::new({
764 std::mem::take(
766 DFGBuilder::new(inout_sig([lin_t.clone()], vec![lin_t.clone(); 2]))
767 .unwrap()
768 .hugr_mut(),
769 )
770 })),
771 NodeTemplate::linked_hugr({
772 let mut dfb = DFGBuilder::new(inout_sig([lin_t.clone()], [])).unwrap();
773 let drop_fn = dfb
774 .module_root_builder()
775 .declare("drop", inout_sig([lin_t.clone()], []).into())
776 .unwrap();
777 let ins = dfb.input_wires();
778 let call = dfb.call(&drop_fn, &[], ins).unwrap();
779 dfb.finish_hugr_with_outputs(call.outputs()).unwrap()
780 }),
781 )
782 .unwrap();
783 } else {
784 #[expect(deprecated)] lower_discard_to_call
786 .linearizer_mut()
787 .register_simple(
788 lin_ct.clone(),
789 NodeTemplate::Call(backup.entrypoint(), vec![]), NodeTemplate::Call(discard_fn, vec![]),
791 )
792 .unwrap();
793 };
794 {
796 let mut lowerer = lower_discard_to_call.clone();
797 lowerer.set_replace_type(usize_t().as_extension().unwrap().clone(), lin_t.clone());
798 let mut h = backup.clone();
799 lowerer.run(&mut h).unwrap();
800 assert_eq!(h.output_neighbours(discard_fn).count(), 1);
801 }
802
803 lower_discard_to_call.set_replace_type(
805 usize_t().as_extension().unwrap().clone(),
806 array_type(4, lin_ct.into()),
807 );
808 let mut h = backup.clone();
809 let r = lower_discard_to_call.run(&mut h);
810 if use_linking {
811 r.unwrap();
812 h.validate().unwrap();
813 } else {
814 r.unwrap();
823 h.validate().unwrap();
824 let disc = h
825 .children(h.module_root())
826 .find(|n| {
827 h.get_optype(*n)
828 .as_func_defn()
829 .is_some_and(|fd| fd.func_name().contains(DISCARD_TO_UNIT_PREFIX))
830 })
831 .unwrap();
832 let call = h
833 .descendants(disc)
834 .filter(|n| h.get_optype(*n).is_call())
835 .exactly_one()
836 .ok()
837 .unwrap();
838 assert_eq!(h.static_source(call), Some(disc)); }
840 }
841
842 #[test]
843 fn use_in_op_callback() {
844 let (e, mut lowerer) = ext_lowerer();
845 let drop_ext = Extension::new_arc(
846 IdentList::new_unchecked("DropExt"),
847 Version::new(0, 0, 0),
848 |e, w| {
849 e.add_op(
850 "drop".into(),
851 String::new(),
852 PolyFuncTypeRV::new(
853 [TypeBound::Linear.into()], Signature::new([Type::new_var_use(0, TypeBound::Linear)], vec![]),
855 ),
856 w,
857 )
858 .unwrap();
859 },
860 );
861 let drop_op = drop_ext.get_op("drop").unwrap();
862 lowerer.set_replace_parametrized_op(drop_op, |args, rt| {
863 let [TypeArg::Runtime(ty)] = args else {
864 panic!("Expected just one type")
865 };
866 Ok(Some(rt.get_linearizer().copy_discard_op(ty, 0)?))
867 });
868
869 let build_hugr = |ty: Type| {
870 let mut dfb = DFGBuilder::new(Signature::new([ty.clone()], [])).unwrap();
871 let [inp] = dfb.input_wires_arr();
872 let drop_op = drop_ext
873 .instantiate_extension_op("drop", [ty.into()])
874 .unwrap();
875 dfb.add_dataflow_op(drop_op, [inp]).unwrap();
876 dfb.finish_hugr().unwrap()
877 };
878 let lin_t = Type::from(e.get_type(LIN_T).unwrap().instantiate([]).unwrap());
880 let mut h = build_hugr(Type::new_tuple(vec![lin_t.clone(); 2]));
881 lowerer.run(&mut h).unwrap();
882 h.validate().unwrap();
883 let mut exts = h.nodes().filter_map(|n| h.get_optype(n).as_extension_op());
884 assert_eq!(exts.clone().count(), 2);
885 assert!(exts.all(|eo| eo.qualified_id() == "TestExt.discard"));
886
887 let mut h = build_hugr(borrow_array_type(4, lin_t));
889 lowerer.run(&mut h).unwrap();
890 h.validate().unwrap();
891 let mut exts = h.nodes().filter_map(|n| h.get_optype(n).as_extension_op());
892 assert!(exts.any(|eo| eo.qualified_id() == "collections.borrow_arr.discard_all_borrowed"));
893
894 let mut h = build_hugr(borrow_array_type(4, usize_t()));
896 lowerer.run(&mut h).unwrap();
897 h.validate().unwrap();
898 let mut exts = h.nodes().filter_map(|n| h.get_optype(n).as_extension_op());
899 assert!(exts.any(|eo| eo.qualified_id() == "collections.borrow_arr.discard_all_borrowed"));
900
901 let mut h = build_hugr(qb_t());
903 assert_eq!(
904 lowerer.run(&mut h).unwrap_err(),
905 ReplaceTypesError::LinearizeError(LinearizeError::NeedCopyDiscard(Box::new(qb_t())))
906 );
907
908 let mut h = build_hugr(borrow_array_type(4, qb_t()));
910 assert_eq!(
911 lowerer.run(&mut h).unwrap_err(),
912 ReplaceTypesError::LinearizeError(LinearizeError::NeedCopyDiscard(Box::new(qb_t())))
913 );
914 }
915
916 #[rstest]
917 #[case([borrow_array_type(2, usize_t())])]
918 #[case([borrow_array_type(2, usize_t()), borrow_array_type(4, usize_t())])]
919 fn test_copy_borrow_array<const N: usize>(#[case] tys: [Type; N]) {
920 let (inp, out, mut h) = {
922 let mut dfb = DFGBuilder::new(Signature::new(
923 Vec::from_iter(tys.clone()),
924 tys.clone().into_iter().chain(tys.clone()).collect_vec(),
925 ))
926 .unwrap();
927 (dfb.input(), dfb.output(), std::mem::take(dfb.hugr_mut()))
928 };
929 for (n, _) in tys.iter().enumerate() {
930 h.connect(inp.node(), n, out.node(), n);
931 h.connect(inp.node(), n, out.node(), n + tys.len());
932 }
933 assert!(matches!(
934 h.validate(),
935 Err(ValidationError::TooManyConnections { .. })
936 ));
937 let (_e, lowerer) = ext_lowerer();
938 lowerer.run(&mut h).unwrap();
939 h.validate().unwrap();
940 for prefix in [UNWRAP_PREFIX, MAKE_NONE_PREFIX] {
941 assert_eq!(
942 h.children(h.module_root())
943 .filter(|n| match h.get_optype(*n) {
944 OpType::FuncDecl(_) => panic!("Unexpected FuncDecl"),
945 OpType::FuncDefn(fd) => fd.func_name().contains(prefix),
946 _ => false,
947 })
948 .count(),
949 1,
950 "Found multiple {prefix} funcs"
951 );
952 }
953 }
954}