diff --git a/hugr-passes/src/composable.rs b/hugr-passes/src/composable.rs new file mode 100644 index 000000000..0d5e6eb6c --- /dev/null +++ b/hugr-passes/src/composable.rs @@ -0,0 +1,133 @@ +//! Compiler passes and utilities for composing them + +use std::{error::Error, marker::PhantomData}; + +use hugr_core::hugr::{hugrmut::HugrMut, ValidationError}; +use hugr_core::HugrView; +use itertools::Either; + +pub trait ComposablePass: Sized { + type Err: Error; + fn run(&self, hugr: &mut impl HugrMut) -> Result<(), Self::Err>; + fn map_err(self, f: impl Fn(Self::Err) -> E2) -> impl ComposablePass { + ErrMapper::new(self, f) + } + fn sequence( + self, + other: impl ComposablePass, + ) -> impl ComposablePass { + (self, other) // SequencePass::new(self, other) ? + } + fn sequence_either( + self, + other: P, + ) -> impl ComposablePass> { + self.map_err(Either::Left) + .sequence(other.map_err(Either::Right)) + } +} + +struct ErrMapper(P, F, PhantomData); + +impl E> ErrMapper { + fn new(pass: P, err_fn: F) -> Self { + Self(pass, err_fn, PhantomData) + } +} + +impl E> ComposablePass for ErrMapper { + type Err = E; + + fn run(&self, hugr: &mut impl HugrMut) -> Result<(), Self::Err> { + self.0.run(hugr).map_err(&self.1) + } +} + +impl, P2: ComposablePass> ComposablePass + for (P1, P2) +{ + type Err = E; + + fn run(&self, hugr: &mut impl HugrMut) -> Result<(), Self::Err> { + self.0.run(hugr)?; + self.1.run(hugr) + } +} + +#[derive(thiserror::Error, Debug)] +#[allow(missing_docs)] +pub enum ValidatePassError { + #[error("Failed to validate input HUGR: {err}\n{pretty_hugr}")] + Input { + #[source] + err: ValidationError, + pretty_hugr: String, + }, + #[error("Failed to validate output HUGR: {err}\n{pretty_hugr}")] + Output { + #[source] + err: ValidationError, + pretty_hugr: String, + }, + #[error(transparent)] + Underlying(E), +} + +/// Runs another, underlying, pass, with validation of the Hugr +/// both before and afterwards. +pub struct ValidatingPass

(P, bool); + +impl ValidatingPass

{ + pub fn new_default(underlying: P) -> Self { + // Self(underlying, cfg!(feature = "extension_inference")) + // Sadly, many tests fail with extension inference, hence: + Self(underlying, false) + } + + pub fn new_validating_extensions(underlying: P) -> Self { + Self(underlying, true) + } + + pub fn new(underlying: P, validate_extensions: bool) -> Self { + Self(underlying, validate_extensions) + } + + fn validation_impl( + &self, + hugr: &impl HugrView, + mk_err: impl FnOnce(ValidationError, String) -> ValidatePassError, + ) -> Result<(), ValidatePassError> { + match self.1 { + false => hugr.validate_no_extensions(), + true => hugr.validate(), + } + .map_err(|err| mk_err(err, hugr.mermaid_string())) + } +} + +impl ComposablePass for ValidatingPass

{ + type Err = ValidatePassError; + + fn run(&self, hugr: &mut impl HugrMut) -> Result<(), Self::Err> { + self.validation_impl(hugr, |err, pretty_hugr| ValidatePassError::Input { + err, + pretty_hugr, + })?; + self.0.run(hugr).map_err(ValidatePassError::Underlying)?; + self.validation_impl(hugr, |err, pretty_hugr| ValidatePassError::Output { + err, + pretty_hugr, + }) + } +} + +pub fn validate_if_test( + pass: P, + hugr: &mut impl HugrMut, +) -> Result<(), ValidatePassError> { + if cfg!(test) { + ValidatingPass::new_default(pass).run(hugr) + } else { + pass.run(hugr).map_err(ValidatePassError::Underlying) + } +} diff --git a/hugr-passes/src/const_fold.rs b/hugr-passes/src/const_fold.rs index ce0f82476..b2ef4102d 100644 --- a/hugr-passes/src/const_fold.rs +++ b/hugr-passes/src/const_fold.rs @@ -4,7 +4,7 @@ pub mod value_handle; use std::collections::{HashMap, HashSet, VecDeque}; -use thiserror::Error; +use std::convert::Infallible; use hugr_core::{ hugr::{ @@ -20,36 +20,21 @@ use hugr_core::{ }; use value_handle::ValueHandle; +use crate::composable::validate_if_test; use crate::dataflow::{ partial_from_const, AbstractValue, AnalysisResults, ConstLoader, ConstLocation, DFContext, Machine, PartialValue, TailLoopTermination, }; -use crate::validation::{ValidatePassError, ValidationLevel}; +use crate::ComposablePass; #[derive(Debug, Clone, Default)] /// A configuration for the Constant Folding pass. pub struct ConstantFoldPass { - validation: ValidationLevel, allow_increase_termination: bool, inputs: HashMap, } -#[derive(Debug, Error)] -#[non_exhaustive] -/// Errors produced by [ConstantFoldPass]. -pub enum ConstFoldError { - #[error(transparent)] - #[allow(missing_docs)] - ValidationError(#[from] ValidatePassError), -} - impl ConstantFoldPass { - /// Sets the validation level used before and after the pass is run - pub fn validation_level(mut self, level: ValidationLevel) -> Self { - self.validation = level; - self - } - /// Allows the pass to remove potentially-non-terminating [TailLoop]s and [CFG] if their /// result (if/when they do terminate) is either known or not needed. /// @@ -71,8 +56,63 @@ impl ConstantFoldPass { self } - /// Run the Constant Folding pass. - fn run_no_validate(&self, hugr: &mut impl HugrMut) -> Result<(), ConstFoldError> { + fn find_needed_nodes( + &self, + results: &AnalysisResults, + needed: &mut HashSet, + ) { + let mut q = VecDeque::new(); + let h = results.hugr(); + q.push_back(h.root()); + while let Some(n) = q.pop_front() { + if !needed.insert(n) { + continue; + }; + + if h.get_optype(n).is_cfg() { + for bb in h.children(n) { + //if results.bb_reachable(bb).unwrap() { // no, we'd need to patch up predicates + q.push_back(bb); + } + } else if let Some(inout) = h.get_io(n) { + // Dataflow. Find minimal nodes necessary to compute output, including StateOrder edges. + q.extend(inout); // Input also necessary for legality even if unreachable + + if !self.allow_increase_termination { + // Also add on anything that might not terminate (even if results not required - + // if its results are required we'll add it by following dataflow, below.) + for ch in h.children(n) { + if might_diverge(results, ch) { + q.push_back(ch); + } + } + } + } + // Also follow dataflow demand + for (src, op) in h.all_linked_outputs(n) { + let needs_predecessor = match h.get_optype(src).port_kind(op).unwrap() { + EdgeKind::Value(_) => { + h.get_optype(src).is_load_constant() + || results + .try_read_wire_concrete::(Wire::new(src, op)) + .is_err() + } + EdgeKind::StateOrder | EdgeKind::Const(_) | EdgeKind::Function(_) => true, + EdgeKind::ControlFlow => false, // we always include all children of a CFG above + _ => true, // needed as EdgeKind non-exhaustive; not knowing what it is, assume the worst + }; + if needs_predecessor { + q.push_back(src); + } + } + } + } +} + +impl ComposablePass for ConstantFoldPass { + type Err = Infallible; + + fn run(&self, hugr: &mut impl HugrMut) -> Result<(), Self::Err> { let fresh_node = Node::from(portgraph::NodeIndex::new( hugr.nodes().max().map_or(0, |n| n.index() + 1), )); @@ -135,64 +175,6 @@ impl ConstantFoldPass { } Ok(()) } - - /// Run the pass using this configuration - pub fn run(&self, hugr: &mut H) -> Result<(), ConstFoldError> { - self.validation - .run_validated_pass(hugr, |hugr: &mut H, _| self.run_no_validate(hugr)) - } - - fn find_needed_nodes( - &self, - results: &AnalysisResults, - needed: &mut HashSet, - ) { - let mut q = VecDeque::new(); - let h = results.hugr(); - q.push_back(h.root()); - while let Some(n) = q.pop_front() { - if !needed.insert(n) { - continue; - }; - - if h.get_optype(n).is_cfg() { - for bb in h.children(n) { - //if results.bb_reachable(bb).unwrap() { // no, we'd need to patch up predicates - q.push_back(bb); - } - } else if let Some(inout) = h.get_io(n) { - // Dataflow. Find minimal nodes necessary to compute output, including StateOrder edges. - q.extend(inout); // Input also necessary for legality even if unreachable - - if !self.allow_increase_termination { - // Also add on anything that might not terminate (even if results not required - - // if its results are required we'll add it by following dataflow, below.) - for ch in h.children(n) { - if might_diverge(results, ch) { - q.push_back(ch); - } - } - } - } - // Also follow dataflow demand - for (src, op) in h.all_linked_outputs(n) { - let needs_predecessor = match h.get_optype(src).port_kind(op).unwrap() { - EdgeKind::Value(_) => { - h.get_optype(src).is_load_constant() - || results - .try_read_wire_concrete::(Wire::new(src, op)) - .is_err() - } - EdgeKind::StateOrder | EdgeKind::Const(_) | EdgeKind::Function(_) => true, - EdgeKind::ControlFlow => false, // we always include all children of a CFG above - _ => true, // needed as EdgeKind non-exhaustive; not knowing what it is, assume the worst - }; - if needs_predecessor { - q.push_back(src); - } - } - } - } } // "Diverge" aka "never-terminate" @@ -219,7 +201,7 @@ fn might_diverge(results: &AnalysisResults, /// Exhaustively apply constant folding to a HUGR. pub fn constant_fold_pass(h: &mut H) { - ConstantFoldPass::default().run(h).unwrap() + validate_if_test(ConstantFoldPass::default(), h).unwrap() } struct ConstFoldContext<'a, H>(&'a H); diff --git a/hugr-passes/src/const_fold/test.rs b/hugr-passes/src/const_fold/test.rs index bc1a5e2d1..0f9e7760a 100644 --- a/hugr-passes/src/const_fold/test.rs +++ b/hugr-passes/src/const_fold/test.rs @@ -29,6 +29,7 @@ use hugr_core::types::{Signature, SumType, Type, TypeRow, TypeRowRV}; use hugr_core::{type_row, Hugr, HugrView, IncomingPort, Node}; use crate::dataflow::{partial_from_const, DFContext, PartialValue}; +use crate::ComposablePass as _; use super::{constant_fold_pass, ConstFoldContext, ConstantFoldPass, ValueHandle}; diff --git a/hugr-passes/src/dead_funcs.rs b/hugr-passes/src/dead_funcs.rs index fe1a7cb51..eb3b227c7 100644 --- a/hugr-passes/src/dead_funcs.rs +++ b/hugr-passes/src/dead_funcs.rs @@ -10,7 +10,10 @@ use hugr_core::{ }; use petgraph::visit::{Dfs, Walker}; -use crate::validation::{ValidatePassError, ValidationLevel}; +use crate::{ + composable::{validate_if_test, ValidatePassError}, + ComposablePass, +}; use super::call_graph::{CallGraph, CallGraphNode}; @@ -26,9 +29,6 @@ pub enum RemoveDeadFuncsError { /// The invalid node. node: Node, }, - #[error(transparent)] - #[allow(missing_docs)] - ValidationError(#[from] ValidatePassError), } fn reachable_funcs<'a>( @@ -64,17 +64,10 @@ fn reachable_funcs<'a>( #[derive(Debug, Clone, Default)] /// A configuration for the Dead Function Removal pass. pub struct RemoveDeadFuncsPass { - validation: ValidationLevel, entry_points: Vec, } impl RemoveDeadFuncsPass { - /// Sets the validation level used before and after the pass is run - pub fn validation_level(mut self, level: ValidationLevel) -> Self { - self.validation = level; - self - } - /// Adds new entry points - these must be [FuncDefn] nodes /// that are children of the [Module] at the root of the Hugr. /// @@ -87,16 +80,31 @@ impl RemoveDeadFuncsPass { self.entry_points.extend(entry_points); self } +} - /// Runs the pass (see [remove_dead_funcs]) with this configuration - pub fn run(&self, hugr: &mut H) -> Result<(), RemoveDeadFuncsError> { - self.validation.run_validated_pass(hugr, |hugr: &mut H, _| { - remove_dead_funcs(hugr, self.entry_points.iter().cloned()) - }) +impl ComposablePass for RemoveDeadFuncsPass { + type Err = RemoveDeadFuncsError; + fn run(&self, hugr: &mut impl HugrMut) -> Result<(), RemoveDeadFuncsError> { + let reachable = reachable_funcs( + &CallGraph::new(hugr), + hugr, + self.entry_points.iter().cloned(), + )? + .collect::>(); + let unreachable = hugr + .nodes() + .filter(|n| { + OpTag::Function.is_superset(hugr.get_optype(*n).tag()) && !reachable.contains(n) + }) + .collect::>(); + for n in unreachable { + hugr.remove_subtree(n); + } + Ok(()) } } -/// Delete from the Hugr any functions that are not used by either [Call] or +/// Deletes from the Hugr any functions that are not used by either [Call] or /// [LoadFunction] nodes in reachable parts. /// /// For [Module]-rooted Hugrs, `entry_points` may provide a list of entry points, @@ -118,16 +126,11 @@ impl RemoveDeadFuncsPass { pub fn remove_dead_funcs( h: &mut impl HugrMut, entry_points: impl IntoIterator, -) -> Result<(), RemoveDeadFuncsError> { - let reachable = reachable_funcs(&CallGraph::new(h), h, entry_points)?.collect::>(); - let unreachable = h - .nodes() - .filter(|n| OpTag::Function.is_superset(h.get_optype(*n).tag()) && !reachable.contains(n)) - .collect::>(); - for n in unreachable { - h.remove_subtree(n); - } - Ok(()) +) -> Result<(), ValidatePassError> { + validate_if_test( + RemoveDeadFuncsPass::default().with_module_entry_points(entry_points), + h, + ) } #[cfg(test)] @@ -142,7 +145,7 @@ mod test { }; use hugr_core::{extension::prelude::usize_t, types::Signature, HugrView}; - use super::RemoveDeadFuncsPass; + use super::remove_dead_funcs; #[rstest] #[case([], vec![])] // No entry_points removes everything! @@ -182,15 +185,14 @@ mod test { }) .collect::>(); - RemoveDeadFuncsPass::default() - .with_module_entry_points( - entry_points - .into_iter() - .map(|name| *avail_funcs.get(name).unwrap()) - .collect::>(), - ) - .run(&mut hugr) - .unwrap(); + remove_dead_funcs( + &mut hugr, + entry_points + .into_iter() + .map(|name| *avail_funcs.get(name).unwrap()) + .collect::>(), + ) + .unwrap(); let remaining_funcs = hugr .nodes() diff --git a/hugr-passes/src/lib.rs b/hugr-passes/src/lib.rs index 026e817c1..c922d9acf 100644 --- a/hugr-passes/src/lib.rs +++ b/hugr-passes/src/lib.rs @@ -1,6 +1,8 @@ //! Compilation passes acting on the HUGR program representation. pub mod call_graph; +pub mod composable; +pub use composable::ComposablePass; pub mod const_fold; pub mod dataflow; mod dead_funcs; @@ -17,17 +19,9 @@ mod monomorphize; )] #[allow(deprecated)] pub use monomorphize::remove_polyfuncs; -// TODO: Deprecated re-export. Remove on a breaking release. -#[deprecated( - since = "0.14.1", - note = "Use `hugr_passes::MonomorphizePass` instead." -)] -#[allow(deprecated)] -pub use monomorphize::monomorphize; -pub use monomorphize::{MonomorphizeError, MonomorphizePass}; +pub use monomorphize::{monomorphize, MonomorphizePass}; pub mod nest_cfgs; pub mod non_local; -pub mod validation; pub use force_order::{force_order, force_order_by_key}; pub use lower::{lower_ops, replace_many_ops}; pub use non_local::{ensure_no_nonlocal_edges, nonlocal_edges}; diff --git a/hugr-passes/src/monomorphize.rs b/hugr-passes/src/monomorphize.rs index 1e3b2d9a0..65fdf78ff 100644 --- a/hugr-passes/src/monomorphize.rs +++ b/hugr-passes/src/monomorphize.rs @@ -1,5 +1,6 @@ use std::{ collections::{hash_map::Entry, HashMap}, + convert::Infallible, fmt::Write, ops::Deref, }; @@ -12,7 +13,9 @@ use hugr_core::{ use hugr_core::hugr::{hugrmut::HugrMut, Hugr, HugrView, OpType}; use itertools::Itertools as _; -use thiserror::Error; + +use crate::composable::{validate_if_test, ValidatePassError}; +use crate::ComposablePass; /// Replaces calls to polymorphic functions with calls to new monomorphic /// instantiations of the polymorphic ones. @@ -30,26 +33,8 @@ use thiserror::Error; /// children of the root node. We make best effort to ensure that names (derived /// from parent function names and concrete type args) of new functions are unique /// whenever the names of their parents are unique, but this is not guaranteed. -#[deprecated( - since = "0.14.1", - note = "Use `hugr_passes::MonomorphizePass` instead." -)] -// TODO: Deprecated. Remove on a breaking release and rename private `monomorphize_ref` to `monomorphize`. -pub fn monomorphize(mut h: Hugr) -> Hugr { - monomorphize_ref(&mut h); - h -} - -fn monomorphize_ref(h: &mut impl HugrMut) { - let root = h.root(); - // If the root is a polymorphic function, then there are no external calls, so nothing to do - if !is_polymorphic_funcdefn(h.get_optype(root)) { - mono_scan(h, root, None, &mut HashMap::new()); - if !h.get_optype(root).is_module() { - #[allow(deprecated)] // TODO remove in next breaking release and update docs - remove_polyfuncs_ref(h); - } - } +pub fn monomorphize(hugr: &mut impl HugrMut) -> Result<(), ValidatePassError> { + validate_if_test(MonomorphizePass, hugr) } /// Removes any polymorphic [FuncDefn]s from the Hugr. Note that if these have @@ -254,8 +239,6 @@ fn instantiate( mono_tgt } -use crate::validation::{ValidatePassError, ValidationLevel}; - /// Replaces calls to polymorphic functions with calls to new monomorphic /// instantiations of the polymorphic ones. /// @@ -271,38 +254,24 @@ use crate::validation::{ValidatePassError, ValidationLevel}; /// children of the root node. We make best effort to ensure that names (derived /// from parent function names and concrete type args) of new functions are unique /// whenever the names of their parents are unique, but this is not guaranteed. -#[derive(Debug, Clone, Default)] -pub struct MonomorphizePass { - validation: ValidationLevel, -} - -#[derive(Debug, Error)] -#[non_exhaustive] -/// Errors produced by [MonomorphizePass]. -pub enum MonomorphizeError { - #[error(transparent)] - #[allow(missing_docs)] - ValidationError(#[from] ValidatePassError), -} - -impl MonomorphizePass { - /// Sets the validation level used before and after the pass is run. - pub fn validation_level(mut self, level: ValidationLevel) -> Self { - self.validation = level; - self - } - - /// Run the Monomorphization pass. - fn run_no_validate(&self, hugr: &mut impl HugrMut) -> Result<(), MonomorphizeError> { - monomorphize_ref(hugr); +#[derive(Debug, Clone)] +pub struct MonomorphizePass; + +impl ComposablePass for MonomorphizePass { + type Err = Infallible; + + fn run(&self, h: &mut impl HugrMut) -> Result<(), Self::Err> { + let root = h.root(); + // If the root is a polymorphic function, then there are no external calls, so nothing to do + if !is_polymorphic_funcdefn(h.get_optype(root)) { + mono_scan(h, root, None, &mut HashMap::new()); + if !h.get_optype(root).is_module() { + #[allow(deprecated)] // TODO remove in next breaking release and update docs + remove_polyfuncs_ref(h); + } + } Ok(()) } - - /// Run the pass using specified configuration. - pub fn run(&self, hugr: &mut H) -> Result<(), MonomorphizeError> { - self.validation - .run_validated_pass(hugr, |hugr: &mut H, _| self.run_no_validate(hugr)) - } } struct TypeArgsList<'a>(&'a [TypeArg]); @@ -387,9 +356,9 @@ mod test { use hugr_core::{Hugr, HugrView, Node}; use rstest::rstest; - use crate::remove_dead_funcs; + use crate::{monomorphize, remove_dead_funcs}; - use super::{is_polymorphic, mangle_inner_func, mangle_name, MonomorphizePass}; + use super::{is_polymorphic, mangle_inner_func, mangle_name}; fn pair_type(ty: Type) -> Type { Type::new_tuple(vec![ty.clone(), ty]) @@ -410,7 +379,7 @@ mod test { let [i1] = dfg_builder.input_wires_arr(); let hugr = dfg_builder.finish_hugr_with_outputs([i1]).unwrap(); let mut hugr2 = hugr.clone(); - MonomorphizePass::default().run(&mut hugr2).unwrap(); + monomorphize(&mut hugr2).unwrap(); assert_eq!(hugr, hugr2); } @@ -472,7 +441,7 @@ mod test { .count(), 3 ); - MonomorphizePass::default().run(&mut hugr)?; + monomorphize(&mut hugr)?; let mono = hugr; mono.validate()?; @@ -493,7 +462,7 @@ mod test { ["double", "main", "triple"] ); let mut mono2 = mono.clone(); - MonomorphizePass::default().run(&mut mono2)?; + monomorphize(&mut mono2)?; assert_eq!(mono2, mono); // Idempotent @@ -601,7 +570,7 @@ mod test { .outputs_arr(); let mut hugr = outer.finish_hugr_with_outputs([e1, e2]).unwrap(); - MonomorphizePass::default().run(&mut hugr).unwrap(); + monomorphize(&mut hugr).unwrap(); let mono_hugr = hugr; mono_hugr.validate().unwrap(); let funcs = list_funcs(&mono_hugr); @@ -662,7 +631,7 @@ mod test { let mono = mono.finish_with_outputs([a, b]).unwrap(); let c = dfg.call(mono.handle(), &[], dfg.input_wires()).unwrap(); let mut hugr = dfg.finish_hugr_with_outputs(c.outputs()).unwrap(); - MonomorphizePass::default().run(&mut hugr)?; + monomorphize(&mut hugr)?; let mono_hugr = hugr; let mut funcs = list_funcs(&mono_hugr); @@ -719,7 +688,7 @@ mod test { module_builder.finish_hugr().unwrap() }; - MonomorphizePass::default().run(&mut hugr).unwrap(); + monomorphize(&mut hugr).unwrap(); remove_dead_funcs(&mut hugr, []).unwrap(); let funcs = list_funcs(&hugr); diff --git a/hugr-passes/src/validation.rs b/hugr-passes/src/validation.rs deleted file mode 100644 index baf3b86d8..000000000 --- a/hugr-passes/src/validation.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! Provides [ValidationLevel] with tools to run passes with configurable -//! validation. - -use thiserror::Error; - -use hugr_core::hugr::{hugrmut::HugrMut, ValidationError}; -use hugr_core::HugrView; - -#[derive(Debug, Clone, Copy, Ord, Eq, PartialOrd, PartialEq)] -/// A type for running [HugrMut] algorithms with verification. -/// -/// Provides [ValidationLevel::run_validated_pass] to invoke a closure with pre and post -/// validation. -/// -/// The default level is `None` because validation can be expensive. -pub enum ValidationLevel { - /// Do no verification. - None, - /// Validate using [HugrView::validate_no_extensions]. This is useful when you - /// do not expect valid Extension annotations on Nodes. - WithoutExtensions, - /// Validate using [HugrView::validate]. - WithExtensions, -} - -#[derive(Error, Debug)] -#[allow(missing_docs)] -pub enum ValidatePassError { - #[error("Failed to validate input HUGR: {err}\n{pretty_hugr}")] - InputError { - #[source] - err: ValidationError, - pretty_hugr: String, - }, - #[error("Failed to validate output HUGR: {err}\n{pretty_hugr}")] - OutputError { - #[source] - err: ValidationError, - pretty_hugr: String, - }, -} - -impl Default for ValidationLevel { - fn default() -> Self { - if cfg!(test) { - // Many tests fail when run with Self::WithExtensions - Self::WithoutExtensions - } else { - Self::None - } - } -} - -impl ValidationLevel { - /// Run an operation on a [HugrMut]. `hugr` will be verified according to - /// [self](ValidationLevel), then `pass` will be invoked. If `pass` succeeds - /// then `hugr` will be verified again. - pub fn run_validated_pass( - &self, - hugr: &mut H, - pass: impl FnOnce(&mut H, &Self) -> Result, - ) -> Result - where - ValidatePassError: Into, - { - self.validation_impl(hugr, |err, pretty_hugr| ValidatePassError::InputError { - err, - pretty_hugr, - })?; - let result = pass(hugr, self)?; - self.validation_impl(hugr, |err, pretty_hugr| ValidatePassError::OutputError { - err, - pretty_hugr, - })?; - Ok(result) - } - - fn validation_impl( - &self, - hugr: &impl HugrView, - mk_err: impl FnOnce(ValidationError, String) -> ValidatePassError, - ) -> Result<(), E> - where - ValidatePassError: Into, - { - match self { - ValidationLevel::None => Ok(()), - ValidationLevel::WithoutExtensions => hugr.validate_no_extensions(), - ValidationLevel::WithExtensions => hugr.validate(), - } - .map_err(|err| mk_err(err, hugr.mermaid_string()).into()) - } -}