From f0e647f2cc122730bf41b92c3edd052af8783573 Mon Sep 17 00:00:00 2001 From: Craig Roy Date: Fri, 5 Jan 2024 18:24:19 +0000 Subject: [PATCH 1/7] refactor: Put extension inference behind a feature gate --- .github/workflows/ci.yml | 4 ++-- Cargo.toml | 3 +++ src/extension.rs | 5 ++++- src/extension/infer/test.rs | 14 ++++++++++++-- src/hugr.rs | 23 ++++++++++++++++++++--- src/hugr/validate.rs | 12 ++++++++++-- src/hugr/validate/test.rs | 13 ++++++++++++- 7 files changed, 63 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5514dedee..8ed7a545f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: Continuous integration on: push: branches: - - main + - main pull_request: branches: - main @@ -33,7 +33,7 @@ jobs: - name: Check formatting run: cargo fmt -- --check - name: Run clippy - run: cargo clippy --all-targets -- -D warnings + run: cargo clippy --all-targets --all-features -- -D warnings - name: Build docs run: cargo doc --no-deps --all-features env: diff --git a/Cargo.toml b/Cargo.toml index c633a1222..70c5dab08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,9 @@ name = "hugr" bench = false path = "src/lib.rs" +[features] +extension_inference = [] + [dependencies] thiserror = "1.0.28" portgraph = { version = "0.11.0", features = ["serde", "petgraph"] } diff --git a/src/extension.rs b/src/extension.rs index 5519e456b..237dc3f4f 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -18,8 +18,11 @@ use crate::types::type_param::{check_type_args, TypeArgError}; use crate::types::type_param::{TypeArg, TypeParam}; use crate::types::{check_typevar_decl, CustomType, PolyFuncType, Substitution, TypeBound}; +#[allow(dead_code)] mod infer; -pub use infer::{infer_extensions, ExtensionSolution, InferExtensionError}; +#[cfg(feature = "extension_inference")] +pub use infer::infer_extensions; +pub use infer::{ExtensionSolution, InferExtensionError}; mod op_def; pub use op_def::{ diff --git a/src/extension/infer/test.rs b/src/extension/infer/test.rs index 1ac40455a..e89de2dea 100644 --- a/src/extension/infer/test.rs +++ b/src/extension/infer/test.rs @@ -1,6 +1,7 @@ use std::error::Error; use super::*; +#[cfg(feature = "extension_inference")] use crate::builder::test::closed_dfg_root_hugr; use crate::builder::{ Container, DFGBuilder, Dataflow, DataflowHugr, DataflowSubContainer, HugrBuilder, ModuleBuilder, @@ -8,10 +9,14 @@ use crate::builder::{ use crate::extension::prelude::QB_T; use crate::extension::ExtensionId; use crate::extension::{prelude::PRELUDE_REGISTRY, ExtensionSet}; -use crate::hugr::{validate::ValidationError, Hugr, HugrMut, HugrView, NodeType}; +#[cfg(feature = "extension_inference")] +use crate::hugr::validate::ValidationError; +use crate::hugr::{Hugr, HugrMut, HugrView, NodeType}; use crate::macros::const_extension_ids; use crate::ops::custom::{ExternalOp, OpaqueOp}; -use crate::ops::{self, dataflow::IOTrait, handle::NodeHandle}; +#[cfg(feature = "extension_inference")] +use crate::ops::handle::NodeHandle; +use crate::ops::{self, dataflow::IOTrait}; use crate::ops::{LeafOp, OpType}; use crate::type_row; @@ -153,6 +158,7 @@ fn plus() -> Result<(), InferExtensionError> { Ok(()) } +#[cfg(feature = "extension_inference")] #[test] // This generates a solution that causes validation to fail // because of a missing lift node @@ -214,6 +220,7 @@ fn open_variables() -> Result<(), InferExtensionError> { Ok(()) } +#[cfg(feature = "extension_inference")] #[test] // Infer the extensions on a child node with no inputs fn dangling_src() -> Result<(), Box> { @@ -305,6 +312,7 @@ fn create_with_io( Ok([node, input, output]) } +#[cfg(feature = "extension_inference")] #[test] fn test_conditional_inference() -> Result<(), Box> { fn build_case( @@ -967,6 +975,7 @@ fn simple_funcdefn() -> Result<(), Box> { Ok(()) } +#[cfg(feature = "extension_inference")] #[test] fn funcdefn_signature_mismatch() -> Result<(), Box> { let mut builder = ModuleBuilder::new(); @@ -997,6 +1006,7 @@ fn funcdefn_signature_mismatch() -> Result<(), Box> { Ok(()) } +#[cfg(feature = "extension_inference")] #[test] // Test that the difference between a FuncDefn's input and output nodes is being // constrained to be the same as the extension delta in the FuncDefn signature. diff --git a/src/hugr.rs b/src/hugr.rs index 9672f3dbb..a13d1f601 100644 --- a/src/hugr.rs +++ b/src/hugr.rs @@ -8,6 +8,8 @@ pub mod serialize; pub mod validate; pub mod views; +#[cfg(not(feature = "extension_inference"))] +use std::collections::HashMap; use std::collections::VecDeque; use std::iter; @@ -23,9 +25,9 @@ use thiserror::Error; pub use self::views::{HugrView, RootTagged}; use crate::core::NodeIndex; -use crate::extension::{ - infer_extensions, ExtensionRegistry, ExtensionSet, ExtensionSolution, InferExtensionError, -}; +#[cfg(feature = "extension_inference")] +use crate::extension::infer_extensions; +use crate::extension::{ExtensionRegistry, ExtensionSet, ExtensionSolution, InferExtensionError}; use crate::ops::custom::resolve_extension_ops; use crate::ops::{OpTag, OpTrait, OpType, DEFAULT_OPTYPE}; use crate::types::FunctionType; @@ -197,12 +199,19 @@ impl Hugr { /// Infer extension requirements and add new information to `op_types` field /// /// See [`infer_extensions`] for details on the "closure" value + #[cfg(feature = "extension_inference")] pub fn infer_extensions(&mut self) -> Result { let (solution, extension_closure) = infer_extensions(self)?; self.instantiate_extensions(solution); Ok(extension_closure) } + /// Do nothing - this functionality is gated by the feature "extension_inference" + #[cfg(not(feature = "extension_inference"))] + pub fn infer_extensions(&mut self) -> Result { + Ok(HashMap::new()) + } + #[allow(dead_code)] /// Add extension requirement information to the hugr in place. fn instantiate_extensions(&mut self, solution: ExtensionSolution) { // We only care about inferred _input_ extensions, because `NodeType` @@ -345,13 +354,20 @@ pub enum HugrError { #[cfg(test)] mod test { use super::{Hugr, HugrView}; + #[cfg(feature = "extension_inference")] use crate::builder::test::closed_dfg_root_hugr; + #[cfg(feature = "extension_inference")] use crate::extension::ExtensionSet; + #[cfg(feature = "extension_inference")] use crate::hugr::HugrMut; + #[cfg(feature = "extension_inference")] use crate::ops; + #[cfg(feature = "extension_inference")] use crate::type_row; + #[cfg(feature = "extension_inference")] use crate::types::{FunctionType, Type}; + #[cfg(feature = "extension_inference")] use std::error::Error; #[test] @@ -371,6 +387,7 @@ mod test { assert_matches!(hugr.get_io(hugr.root()), Some(_)); } + #[cfg(feature = "extension_inference")] #[test] fn extension_instantiation() -> Result<(), Box> { const BIT: Type = crate::extension::prelude::USIZE_T; diff --git a/src/hugr/validate.rs b/src/hugr/validate.rs index 295ef76ce..df982816c 100644 --- a/src/hugr/validate.rs +++ b/src/hugr/validate.rs @@ -9,10 +9,11 @@ use petgraph::visit::{Topo, Walker}; use portgraph::{LinkView, PortView}; use thiserror::Error; +#[cfg(feature = "extension_inference")] +use crate::extension::validate::ExtensionValidator; use crate::extension::SignatureError; use crate::extension::{ - validate::{ExtensionError, ExtensionValidator}, - ExtensionRegistry, ExtensionSolution, InferExtensionError, + validate::ExtensionError, ExtensionRegistry, ExtensionSolution, InferExtensionError, }; use crate::ops::custom::CustomOpError; @@ -36,6 +37,7 @@ struct ValidationContext<'a, 'b> { /// Dominator tree for each CFG region, using the container node as index. dominators: HashMap>, /// Context for the extension validation. + #[cfg(feature = "extension_inference")] extension_validator: ExtensionValidator, /// Registry of available Extensions extension_registry: &'b ExtensionRegistry, @@ -64,6 +66,9 @@ impl Hugr { impl<'a, 'b> ValidationContext<'a, 'b> { /// Create a new validation context. + // Allow unused "extension_closure" variable for when + // the "extension_inference" feature is disabled. + #[allow(unused_variables)] pub fn new( hugr: &'a Hugr, extension_closure: ExtensionSolution, @@ -72,6 +77,7 @@ impl<'a, 'b> ValidationContext<'a, 'b> { Self { hugr, dominators: HashMap::new(), + #[cfg(feature = "extension_inference")] extension_validator: ExtensionValidator::new(hugr, extension_closure), extension_registry, } @@ -163,6 +169,7 @@ impl<'a, 'b> ValidationContext<'a, 'b> { // FuncDefns have no resources since they're static nodes, but the // functions they define can have any extension delta. + #[cfg(feature = "extension_inference")] if node_type.tag() != OpTag::FuncDefn { // If this is a container with I/O nodes, check that the extension they // define match the extensions of the container. @@ -240,6 +247,7 @@ impl<'a, 'b> ValidationContext<'a, 'b> { let other_node: Node = self.hugr.graph.port_node(link).unwrap().into(); let other_offset = self.hugr.graph.port_offset(link).unwrap().into(); + #[cfg(feature = "extension_inference")] self.extension_validator .check_extensions_compatible(&(node, port), &(other_node, other_offset))?; diff --git a/src/hugr/validate/test.rs b/src/hugr/validate/test.rs index 520359a3a..4d9cb4937 100644 --- a/src/hugr/validate/test.rs +++ b/src/hugr/validate/test.rs @@ -2,9 +2,10 @@ use cool_asserts::assert_matches; use super::*; use crate::builder::test::closed_dfg_root_hugr; +#[cfg(feature = "extension_inference")] +use crate::builder::ModuleBuilder; use crate::builder::{ BuildError, Container, Dataflow, DataflowHugr, DataflowSubContainer, FunctionBuilder, - ModuleBuilder, }; use crate::extension::prelude::{BOOL_T, PRELUDE, USIZE_T}; use crate::extension::{ @@ -12,6 +13,7 @@ use crate::extension::{ }; use crate::hugr::hugrmut::sealed::HugrMutInternals; use crate::hugr::{HugrError, HugrMut, NodeType}; +#[cfg(feature = "extension_inference")] use crate::macros::const_extension_ids; use crate::ops::dataflow::IOTrait; use crate::ops::{self, Const, LeafOp, OpType}; @@ -23,6 +25,7 @@ use crate::values::Value; use crate::{type_row, Direction, IncomingPort, Node}; const NAT: Type = crate::extension::prelude::USIZE_T; +#[cfg(feature = "extension_inference")] const Q: Type = crate::extension::prelude::QB_T; /// Creates a hugr with a single function definition that copies a bit `copies` times. @@ -71,6 +74,7 @@ fn add_df_children(b: &mut Hugr, parent: Node, copies: usize) -> (Node, Node, No /// Intended to be used to populate a BasicBlock node in a CFG. /// /// Returns the node indices of each of the operations. +#[cfg(feature = "extension_inference")] fn add_block_children( b: &mut Hugr, parent: Node, @@ -257,6 +261,7 @@ fn df_children_restrictions() { ); } +#[cfg(feature = "extension_inference")] #[test] /// Validation errors in a dataflow subgraph. fn cfg_children_restrictions() { @@ -404,6 +409,7 @@ fn test_ext_edge() -> Result<(), HugrError> { Ok(()) } +#[cfg(feature = "extension_inference")] const_extension_ids! { const XA: ExtensionId = "A"; const XB: ExtensionId = "BOOL_EXT"; @@ -441,6 +447,7 @@ fn test_local_const() -> Result<(), HugrError> { Ok(()) } +#[cfg(feature = "extension_inference")] #[test] /// A wire with no extension requirements is wired into a node which has /// [A,BOOL_T] extensions required on its inputs and outputs. This could be fixed @@ -474,6 +481,7 @@ fn missing_lift_node() -> Result<(), BuildError> { Ok(()) } +#[cfg(feature = "extension_inference")] #[test] /// A wire with extension requirement `[A]` is wired into a an output with no /// extension req. In the validation extension typechecking, we don't do any @@ -505,6 +513,7 @@ fn too_many_extension() -> Result<(), BuildError> { Ok(()) } +#[cfg(feature = "extension_inference")] #[test] /// A wire with extension requirements `[A]` and another with requirements /// `[BOOL_T]` are both wired into a node which requires its inputs to have @@ -558,6 +567,7 @@ fn extensions_mismatch() -> Result<(), BuildError> { Ok(()) } +#[cfg(feature = "extension_inference")] #[test] fn parent_signature_mismatch() -> Result<(), BuildError> { let rs = ExtensionSet::singleton(&XA); @@ -740,6 +750,7 @@ fn invalid_types() { ); } +#[cfg(feature = "extension_inference")] #[test] fn parent_io_mismatch() { // The DFG node declares that it has an empty extension delta, From 9e3be391e29e94cc7103cc5e9afeb20236855f32 Mon Sep 17 00:00:00 2001 From: Craig Roy Date: Mon, 8 Jan 2024 13:26:57 +0000 Subject: [PATCH 2/7] refactor: Bunch feature gated imports together --- src/extension/infer/test.rs | 10 ++++------ src/hugr/validate/test.rs | 7 +++---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/extension/infer/test.rs b/src/extension/infer/test.rs index e89de2dea..ab071005b 100644 --- a/src/extension/infer/test.rs +++ b/src/extension/infer/test.rs @@ -1,23 +1,21 @@ use std::error::Error; use super::*; -#[cfg(feature = "extension_inference")] -use crate::builder::test::closed_dfg_root_hugr; use crate::builder::{ Container, DFGBuilder, Dataflow, DataflowHugr, DataflowSubContainer, HugrBuilder, ModuleBuilder, }; use crate::extension::prelude::QB_T; use crate::extension::ExtensionId; use crate::extension::{prelude::PRELUDE_REGISTRY, ExtensionSet}; -#[cfg(feature = "extension_inference")] -use crate::hugr::validate::ValidationError; use crate::hugr::{Hugr, HugrMut, HugrView, NodeType}; use crate::macros::const_extension_ids; use crate::ops::custom::{ExternalOp, OpaqueOp}; -#[cfg(feature = "extension_inference")] -use crate::ops::handle::NodeHandle; use crate::ops::{self, dataflow::IOTrait}; use crate::ops::{LeafOp, OpType}; +#[cfg(feature = "extension_inference")] +use crate::{ + builder::test::closed_dfg_root_hugr, hugr::validate::ValidationError, ops::handle::NodeHandle, +}; use crate::type_row; use crate::types::{FunctionType, Type, TypeRow}; diff --git a/src/hugr/validate/test.rs b/src/hugr/validate/test.rs index 4d9cb4937..da5dc6507 100644 --- a/src/hugr/validate/test.rs +++ b/src/hugr/validate/test.rs @@ -1,9 +1,10 @@ use cool_asserts::assert_matches; +#[cfg(feature = "extension_inference")] +use crate::{builder::ModuleBuilder, macros::const_extension_ids}; + use super::*; use crate::builder::test::closed_dfg_root_hugr; -#[cfg(feature = "extension_inference")] -use crate::builder::ModuleBuilder; use crate::builder::{ BuildError, Container, Dataflow, DataflowHugr, DataflowSubContainer, FunctionBuilder, }; @@ -13,8 +14,6 @@ use crate::extension::{ }; use crate::hugr::hugrmut::sealed::HugrMutInternals; use crate::hugr::{HugrError, HugrMut, NodeType}; -#[cfg(feature = "extension_inference")] -use crate::macros::const_extension_ids; use crate::ops::dataflow::IOTrait; use crate::ops::{self, Const, LeafOp, OpType}; use crate::std_extensions::logic::test::{and_op, or_op}; From 7918df34578d40addeafa33eacb8e053be08d335 Mon Sep 17 00:00:00 2001 From: Craig Roy Date: Mon, 8 Jan 2024 13:29:47 +0000 Subject: [PATCH 3/7] refactor: Move feature-gated imports into tests that use them --- src/hugr.rs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/hugr.rs b/src/hugr.rs index a13d1f601..83a919abd 100644 --- a/src/hugr.rs +++ b/src/hugr.rs @@ -354,19 +354,6 @@ pub enum HugrError { #[cfg(test)] mod test { use super::{Hugr, HugrView}; - #[cfg(feature = "extension_inference")] - use crate::builder::test::closed_dfg_root_hugr; - #[cfg(feature = "extension_inference")] - use crate::extension::ExtensionSet; - #[cfg(feature = "extension_inference")] - use crate::hugr::HugrMut; - #[cfg(feature = "extension_inference")] - use crate::ops; - #[cfg(feature = "extension_inference")] - use crate::type_row; - #[cfg(feature = "extension_inference")] - use crate::types::{FunctionType, Type}; - #[cfg(feature = "extension_inference")] use std::error::Error; @@ -390,6 +377,13 @@ mod test { #[cfg(feature = "extension_inference")] #[test] fn extension_instantiation() -> Result<(), Box> { + use crate::builder::test::closed_dfg_root_hugr; + use crate::extension::ExtensionSet; + use crate::hugr::HugrMut; + use crate::ops::LeafOp; + use crate::type_row; + use crate::types::{FunctionType, Type}; + const BIT: Type = crate::extension::prelude::USIZE_T; let r = ExtensionSet::singleton(&"R".try_into().unwrap()); @@ -399,7 +393,7 @@ mod test { let [input, output] = hugr.get_io(hugr.root()).unwrap(); let lift = hugr.add_node_with_parent( hugr.root(), - ops::LeafOp::Lift { + LeafOp::Lift { type_row: type_row![BIT], new_extension: "R".try_into().unwrap(), }, From b533ee83fe0f0abf9a66e6a8764433a7973bce67 Mon Sep 17 00:00:00 2001 From: Craig Roy Date: Mon, 8 Jan 2024 15:32:31 +0000 Subject: [PATCH 4/7] refactor: Feature-gated extension validation --- src/extension/validate.rs | 3 +++ src/hugr/validate.rs | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/extension/validate.rs b/src/extension/validate.rs index c66f777fa..e8b90aeb3 100644 --- a/src/extension/validate.rs +++ b/src/extension/validate.rs @@ -120,12 +120,14 @@ impl ExtensionValidator { /// Check that a pair of input and output nodes declare the same extensions /// as in the signature of their parents. + #[allow(unused_variables)] pub fn validate_io_extensions( &self, parent: Node, input: Node, output: Node, ) -> Result<(), ExtensionError> { + #[cfg(feature = "extension_inference")] { let parent_input_extensions = self.query_extensions(parent, Direction::Incoming)?; let parent_output_extensions = self.query_extensions(parent, Direction::Outgoing)?; for dir in Direction::BOTH { @@ -148,6 +150,7 @@ impl ExtensionValidator { }); }; } + } Ok(()) } } diff --git a/src/hugr/validate.rs b/src/hugr/validate.rs index df982816c..2466059b0 100644 --- a/src/hugr/validate.rs +++ b/src/hugr/validate.rs @@ -9,7 +9,6 @@ use petgraph::visit::{Topo, Walker}; use portgraph::{LinkView, PortView}; use thiserror::Error; -#[cfg(feature = "extension_inference")] use crate::extension::validate::ExtensionValidator; use crate::extension::SignatureError; use crate::extension::{ @@ -37,7 +36,7 @@ struct ValidationContext<'a, 'b> { /// Dominator tree for each CFG region, using the container node as index. dominators: HashMap>, /// Context for the extension validation. - #[cfg(feature = "extension_inference")] + #[allow(dead_code)] extension_validator: ExtensionValidator, /// Registry of available Extensions extension_registry: &'b ExtensionRegistry, @@ -77,7 +76,6 @@ impl<'a, 'b> ValidationContext<'a, 'b> { Self { hugr, dominators: HashMap::new(), - #[cfg(feature = "extension_inference")] extension_validator: ExtensionValidator::new(hugr, extension_closure), extension_registry, } From cb7044eb87800121599e5daecf06275804062290 Mon Sep 17 00:00:00 2001 From: Craig Roy Date: Mon, 8 Jan 2024 15:55:37 +0000 Subject: [PATCH 5/7] refactor: Collocate functionality for feature-gated inference tests --- src/extension/validate.rs | 47 +-- src/hugr/validate/test.rs | 704 +++++++++++++++++++------------------- 2 files changed, 374 insertions(+), 377 deletions(-) diff --git a/src/extension/validate.rs b/src/extension/validate.rs index e8b90aeb3..7d81a88f1 100644 --- a/src/extension/validate.rs +++ b/src/extension/validate.rs @@ -127,29 +127,30 @@ impl ExtensionValidator { input: Node, output: Node, ) -> Result<(), ExtensionError> { - #[cfg(feature = "extension_inference")] { - let parent_input_extensions = self.query_extensions(parent, Direction::Incoming)?; - let parent_output_extensions = self.query_extensions(parent, Direction::Outgoing)?; - for dir in Direction::BOTH { - let input_extensions = self.query_extensions(input, dir)?; - let output_extensions = self.query_extensions(output, dir)?; - if parent_input_extensions != input_extensions { - return Err(ExtensionError::ParentIOExtensionMismatch { - parent, - parent_extensions: parent_input_extensions.clone(), - child: input, - child_extensions: input_extensions.clone(), - }); - }; - if parent_output_extensions != output_extensions { - return Err(ExtensionError::ParentIOExtensionMismatch { - parent, - parent_extensions: parent_output_extensions.clone(), - child: output, - child_extensions: output_extensions.clone(), - }); - }; - } + #[cfg(feature = "extension_inference")] + { + let parent_input_extensions = self.query_extensions(parent, Direction::Incoming)?; + let parent_output_extensions = self.query_extensions(parent, Direction::Outgoing)?; + for dir in Direction::BOTH { + let input_extensions = self.query_extensions(input, dir)?; + let output_extensions = self.query_extensions(output, dir)?; + if parent_input_extensions != input_extensions { + return Err(ExtensionError::ParentIOExtensionMismatch { + parent, + parent_extensions: parent_input_extensions.clone(), + child: input, + child_extensions: input_extensions.clone(), + }); + }; + if parent_output_extensions != output_extensions { + return Err(ExtensionError::ParentIOExtensionMismatch { + parent, + parent_extensions: parent_output_extensions.clone(), + child: output, + child_extensions: output_extensions.clone(), + }); + }; + } } Ok(()) } diff --git a/src/hugr/validate/test.rs b/src/hugr/validate/test.rs index da5dc6507..d974339e7 100644 --- a/src/hugr/validate/test.rs +++ b/src/hugr/validate/test.rs @@ -1,8 +1,5 @@ use cool_asserts::assert_matches; -#[cfg(feature = "extension_inference")] -use crate::{builder::ModuleBuilder, macros::const_extension_ids}; - use super::*; use crate::builder::test::closed_dfg_root_hugr; use crate::builder::{ @@ -24,8 +21,6 @@ use crate::values::Value; use crate::{type_row, Direction, IncomingPort, Node}; const NAT: Type = crate::extension::prelude::USIZE_T; -#[cfg(feature = "extension_inference")] -const Q: Type = crate::extension::prelude::QB_T; /// Creates a hugr with a single function definition that copies a bit `copies` times. /// @@ -68,39 +63,6 @@ fn add_df_children(b: &mut Hugr, parent: Node, copies: usize) -> (Node, Node, No (input, copy, output) } -/// Adds an input{BOOL_T}, tag_constant(0, BOOL_T^tuple_sum_size), tag(BOOL_T^tuple_sum_size), and -/// output{Sum{unit^tuple_sum_size}, BOOL_T} operation to a dataflow container. -/// Intended to be used to populate a BasicBlock node in a CFG. -/// -/// Returns the node indices of each of the operations. -#[cfg(feature = "extension_inference")] -fn add_block_children( - b: &mut Hugr, - parent: Node, - tuple_sum_size: usize, -) -> (Node, Node, Node, Node) { - let const_op = ops::Const::unit_sum(0, tuple_sum_size as u8); - let tag_type = Type::new_unit_sum(tuple_sum_size as u8); - - let input = b - .add_node_with_parent(parent, ops::Input::new(type_row![BOOL_T])) - .unwrap(); - let output = b - .add_node_with_parent(parent, ops::Output::new(vec![tag_type.clone(), BOOL_T])) - .unwrap(); - let tag_def = b.add_node_with_parent(b.root(), const_op).unwrap(); - let tag = b - .add_node_with_parent(parent, ops::LoadConstant { datatype: tag_type }) - .unwrap(); - - b.connect(tag_def, 0, tag, 0).unwrap(); - b.add_other_edge(input, tag).unwrap(); - b.connect(tag, 0, output, 0).unwrap(); - b.connect(input, 0, output, 1).unwrap(); - - (input, tag_def, tag, output) -} - #[test] fn invalid_root() { let declare_op: OpType = ops::FuncDecl { @@ -260,108 +222,6 @@ fn df_children_restrictions() { ); } -#[cfg(feature = "extension_inference")] -#[test] -/// Validation errors in a dataflow subgraph. -fn cfg_children_restrictions() { - let (mut b, def) = make_simple_hugr(1); - let (_input, _output, copy) = b - .hierarchy - .children(def.pg_index()) - .map_into() - .collect_tuple() - .unwrap(); - // Write Extension annotations into the Hugr while it's still well-formed - // enough for us to compute them - let closure = b.infer_extensions().unwrap(); - b.instantiate_extensions(closure); - b.validate(&EMPTY_REG).unwrap(); - b.replace_op( - copy, - NodeType::new_pure(ops::CFG { - signature: FunctionType::new(type_row![BOOL_T], type_row![BOOL_T]), - }), - ) - .unwrap(); - assert_matches!( - b.validate(&EMPTY_REG), - Err(ValidationError::ContainerWithoutChildren { .. }) - ); - let cfg = copy; - - // Construct a valid CFG, with one BasicBlock node and one exit node - let block = b - .add_node_with_parent( - cfg, - ops::DataflowBlock { - inputs: type_row![BOOL_T], - tuple_sum_rows: vec![type_row![]], - other_outputs: type_row![BOOL_T], - extension_delta: ExtensionSet::new(), - }, - ) - .unwrap(); - add_block_children(&mut b, block, 1); - let exit = b - .add_node_with_parent( - cfg, - ops::ExitBlock { - cfg_outputs: type_row![BOOL_T], - }, - ) - .unwrap(); - b.add_other_edge(block, exit).unwrap(); - assert_eq!(b.update_validate(&EMPTY_REG), Ok(())); - - // Test malformed errors - - // Add an internal exit node - let exit2 = b - .add_node_after( - exit, - ops::ExitBlock { - cfg_outputs: type_row![BOOL_T], - }, - ) - .unwrap(); - assert_matches!( - b.validate(&EMPTY_REG), - Err(ValidationError::InvalidChildren { parent, source: ChildrenValidationError::InternalExitChildren { child, .. }, .. }) - => {assert_eq!(parent, cfg); assert_eq!(child, exit2.pg_index())} - ); - b.remove_node(exit2).unwrap(); - - // Change the types in the BasicBlock node to work on qubits instead of bits - b.replace_op( - block, - NodeType::new_pure(ops::DataflowBlock { - inputs: type_row![Q], - tuple_sum_rows: vec![type_row![]], - other_outputs: type_row![Q], - extension_delta: ExtensionSet::new(), - }), - ) - .unwrap(); - let mut block_children = b.hierarchy.children(block.pg_index()); - let block_input = block_children.next().unwrap().into(); - let block_output = block_children.next_back().unwrap().into(); - b.replace_op( - block_input, - NodeType::new_pure(ops::Input::new(type_row![Q])), - ) - .unwrap(); - b.replace_op( - block_output, - NodeType::new_pure(ops::Output::new(type_row![Type::new_unit_sum(1), Q])), - ) - .unwrap(); - assert_matches!( - b.validate(&EMPTY_REG), - Err(ValidationError::InvalidEdges { parent, source: EdgeValidationError::CFGEdgeSignatureMismatch { .. }, .. }) - => assert_eq!(parent, cfg) - ); -} - #[test] fn test_ext_edge() -> Result<(), HugrError> { let mut h = closed_dfg_root_hugr(FunctionType::new( @@ -408,12 +268,6 @@ fn test_ext_edge() -> Result<(), HugrError> { Ok(()) } -#[cfg(feature = "extension_inference")] -const_extension_ids! { - const XA: ExtensionId = "A"; - const XB: ExtensionId = "BOOL_EXT"; -} - #[test] fn test_local_const() -> Result<(), HugrError> { let mut h = closed_dfg_root_hugr(FunctionType::new(type_row![BOOL_T], type_row![BOOL_T])); @@ -446,163 +300,6 @@ fn test_local_const() -> Result<(), HugrError> { Ok(()) } -#[cfg(feature = "extension_inference")] -#[test] -/// A wire with no extension requirements is wired into a node which has -/// [A,BOOL_T] extensions required on its inputs and outputs. This could be fixed -/// by adding a lift node, but for validation this is an error. -fn missing_lift_node() -> Result<(), BuildError> { - let mut module_builder = ModuleBuilder::new(); - let mut main = module_builder.define_function( - "main", - FunctionType::new(type_row![NAT], type_row![NAT]).into(), - )?; - let [main_input] = main.input_wires_arr(); - - let f_builder = main.dfg_builder( - FunctionType::new(type_row![NAT], type_row![NAT]), - // Inner DFG has extension requirements that the wire wont satisfy - Some(ExtensionSet::from_iter([XA, XB])), - [main_input], - )?; - let f_inputs = f_builder.input_wires(); - let f_handle = f_builder.finish_with_outputs(f_inputs)?; - let [f_output] = f_handle.outputs_arr(); - main.finish_with_outputs([f_output])?; - let handle = module_builder.hugr().validate(&PRELUDE_REGISTRY); - - assert_matches!( - handle, - Err(ValidationError::ExtensionError( - ExtensionError::TgtExceedsSrcExtensionsAtPort { .. } - )) - ); - Ok(()) -} - -#[cfg(feature = "extension_inference")] -#[test] -/// A wire with extension requirement `[A]` is wired into a an output with no -/// extension req. In the validation extension typechecking, we don't do any -/// unification, so don't allow open extension variables on the function -/// signature, so this fails. -fn too_many_extension() -> Result<(), BuildError> { - let mut module_builder = ModuleBuilder::new(); - - let main_sig = FunctionType::new(type_row![NAT], type_row![NAT]).into(); - - let mut main = module_builder.define_function("main", main_sig)?; - let [main_input] = main.input_wires_arr(); - - let inner_sig = FunctionType::new(type_row![NAT], type_row![NAT]) - .with_extension_delta(&ExtensionSet::singleton(&XA)); - - let f_builder = main.dfg_builder(inner_sig, Some(ExtensionSet::new()), [main_input])?; - let f_inputs = f_builder.input_wires(); - let f_handle = f_builder.finish_with_outputs(f_inputs)?; - let [f_output] = f_handle.outputs_arr(); - main.finish_with_outputs([f_output])?; - let handle = module_builder.hugr().validate(&PRELUDE_REGISTRY); - assert_matches!( - handle, - Err(ValidationError::ExtensionError( - ExtensionError::SrcExceedsTgtExtensionsAtPort { .. } - )) - ); - Ok(()) -} - -#[cfg(feature = "extension_inference")] -#[test] -/// A wire with extension requirements `[A]` and another with requirements -/// `[BOOL_T]` are both wired into a node which requires its inputs to have -/// requirements `[A,BOOL_T]`. A slightly more complex test of the error from -/// `missing_lift_node`. -fn extensions_mismatch() -> Result<(), BuildError> { - let mut module_builder = ModuleBuilder::new(); - - let all_rs = ExtensionSet::from_iter([XA, XB]); - - let main_sig = FunctionType::new(type_row![], type_row![NAT]) - .with_extension_delta(&all_rs) - .into(); - - let mut main = module_builder.define_function("main", main_sig)?; - - let [left_wire] = main - .dfg_builder( - FunctionType::new(type_row![], type_row![NAT]), - Some(ExtensionSet::singleton(&XA)), - [], - )? - .finish_with_outputs([])? - .outputs_arr(); - - let [right_wire] = main - .dfg_builder( - FunctionType::new(type_row![], type_row![NAT]), - Some(ExtensionSet::singleton(&XB)), - [], - )? - .finish_with_outputs([])? - .outputs_arr(); - - let builder = main.dfg_builder( - FunctionType::new(type_row![NAT, NAT], type_row![NAT]), - Some(all_rs), - [left_wire, right_wire], - )?; - let [_left, _right] = builder.input_wires_arr(); - let [output] = builder.finish_with_outputs([])?.outputs_arr(); - - main.finish_with_outputs([output])?; - let handle = module_builder.hugr().validate(&PRELUDE_REGISTRY); - assert_matches!( - handle, - Err(ValidationError::ExtensionError( - ExtensionError::TgtExceedsSrcExtensionsAtPort { .. } - )) - ); - Ok(()) -} - -#[cfg(feature = "extension_inference")] -#[test] -fn parent_signature_mismatch() -> Result<(), BuildError> { - let rs = ExtensionSet::singleton(&XA); - - let main_signature = - FunctionType::new(type_row![NAT], type_row![NAT]).with_extension_delta(&rs); - - let mut hugr = Hugr::new(NodeType::new_pure(ops::DFG { - signature: main_signature, - })); - let input = hugr.add_node_with_parent( - hugr.root(), - NodeType::new_pure(ops::Input { - types: type_row![NAT], - }), - )?; - let output = hugr.add_node_with_parent( - hugr.root(), - NodeType::new( - ops::Output { - types: type_row![NAT], - }, - rs, - ), - )?; - hugr.connect(input, 0, output, 0)?; - - assert_matches!( - hugr.validate(&PRELUDE_REGISTRY), - Err(ValidationError::ExtensionError( - ExtensionError::TgtExceedsSrcExtensionsAtPort { .. } - )) - ); - Ok(()) -} - #[test] fn dfg_with_cycles() -> Result<(), HugrError> { let mut h = closed_dfg_root_hugr(FunctionType::new( @@ -749,57 +446,6 @@ fn invalid_types() { ); } -#[cfg(feature = "extension_inference")] -#[test] -fn parent_io_mismatch() { - // The DFG node declares that it has an empty extension delta, - // but it's child graph adds extension "XB", causing a mismatch. - let mut hugr = Hugr::new(NodeType::new_pure(ops::DFG { - signature: FunctionType::new(type_row![USIZE_T], type_row![USIZE_T]), - })); - - let input = hugr - .add_node_with_parent( - hugr.root(), - NodeType::new_pure(ops::Input { - types: type_row![USIZE_T], - }), - ) - .unwrap(); - let output = hugr - .add_node_with_parent( - hugr.root(), - NodeType::new( - ops::Output { - types: type_row![USIZE_T], - }, - ExtensionSet::singleton(&XB), - ), - ) - .unwrap(); - - let lift = hugr - .add_node_with_parent( - hugr.root(), - NodeType::new_pure(ops::LeafOp::Lift { - type_row: type_row![USIZE_T], - new_extension: XB, - }), - ) - .unwrap(); - - hugr.connect(input, 0, lift, 0).unwrap(); - hugr.connect(lift, 0, output, 0).unwrap(); - - let result = hugr.validate(&PRELUDE_REGISTRY); - assert_matches!( - result, - Err(ValidationError::ExtensionError( - ExtensionError::ParentIOExtensionMismatch { .. } - )) - ); -} - #[test] fn typevars_declared() -> Result<(), Box> { // Base case @@ -912,3 +558,353 @@ fn no_polymorphic_consts() -> Result<(), Box> { ); Ok(()) } + +#[cfg(feature = "extension_inference")] +mod extension_tests { + use super::*; + use crate::builder::ModuleBuilder; + use crate::macros::const_extension_ids; + + const_extension_ids! { + const XA: ExtensionId = "A"; + const XB: ExtensionId = "BOOL_EXT"; + } + + const Q: Type = crate::extension::prelude::QB_T; + + /// Adds an input{BOOL_T}, tag_constant(0, BOOL_T^tuple_sum_size), tag(BOOL_T^tuple_sum_size), and + /// output{Sum{unit^tuple_sum_size}, BOOL_T} operation to a dataflow container. + /// Intended to be used to populate a BasicBlock node in a CFG. + /// + /// Returns the node indices of each of the operations. + fn add_block_children( + b: &mut Hugr, + parent: Node, + tuple_sum_size: usize, + ) -> (Node, Node, Node, Node) { + let const_op = ops::Const::unit_sum(0, tuple_sum_size as u8); + let tag_type = Type::new_unit_sum(tuple_sum_size as u8); + + let input = b + .add_node_with_parent(parent, ops::Input::new(type_row![BOOL_T])) + .unwrap(); + let output = b + .add_node_with_parent(parent, ops::Output::new(vec![tag_type.clone(), BOOL_T])) + .unwrap(); + let tag_def = b.add_node_with_parent(b.root(), const_op).unwrap(); + let tag = b + .add_node_with_parent(parent, ops::LoadConstant { datatype: tag_type }) + .unwrap(); + + b.connect(tag_def, 0, tag, 0).unwrap(); + b.add_other_edge(input, tag).unwrap(); + b.connect(tag, 0, output, 0).unwrap(); + b.connect(input, 0, output, 1).unwrap(); + + (input, tag_def, tag, output) + } + + #[test] + /// Validation errors in a dataflow subgraph. + fn cfg_children_restrictions() { + let (mut b, def) = make_simple_hugr(1); + let (_input, _output, copy) = b + .hierarchy + .children(def.pg_index()) + .map_into() + .collect_tuple() + .unwrap(); + // Write Extension annotations into the Hugr while it's still well-formed + // enough for us to compute them + let closure = b.infer_extensions().unwrap(); + b.instantiate_extensions(closure); + b.validate(&EMPTY_REG).unwrap(); + b.replace_op( + copy, + NodeType::new_pure(ops::CFG { + signature: FunctionType::new(type_row![BOOL_T], type_row![BOOL_T]), + }), + ) + .unwrap(); + assert_matches!( + b.validate(&EMPTY_REG), + Err(ValidationError::ContainerWithoutChildren { .. }) + ); + let cfg = copy; + + // Construct a valid CFG, with one BasicBlock node and one exit node + let block = b + .add_node_with_parent( + cfg, + ops::DataflowBlock { + inputs: type_row![BOOL_T], + tuple_sum_rows: vec![type_row![]], + other_outputs: type_row![BOOL_T], + extension_delta: ExtensionSet::new(), + }, + ) + .unwrap(); + add_block_children(&mut b, block, 1); + let exit = b + .add_node_with_parent( + cfg, + ops::ExitBlock { + cfg_outputs: type_row![BOOL_T], + }, + ) + .unwrap(); + b.add_other_edge(block, exit).unwrap(); + assert_eq!(b.update_validate(&EMPTY_REG), Ok(())); + + // Test malformed errors + + // Add an internal exit node + let exit2 = b + .add_node_after( + exit, + ops::ExitBlock { + cfg_outputs: type_row![BOOL_T], + }, + ) + .unwrap(); + assert_matches!( + b.validate(&EMPTY_REG), + Err(ValidationError::InvalidChildren { parent, source: ChildrenValidationError::InternalExitChildren { child, .. }, .. }) + => {assert_eq!(parent, cfg); assert_eq!(child, exit2.pg_index())} + ); + b.remove_node(exit2).unwrap(); + + // Change the types in the BasicBlock node to work on qubits instead of bits + b.replace_op( + block, + NodeType::new_pure(ops::DataflowBlock { + inputs: type_row![Q], + tuple_sum_rows: vec![type_row![]], + other_outputs: type_row![Q], + extension_delta: ExtensionSet::new(), + }), + ) + .unwrap(); + let mut block_children = b.hierarchy.children(block.pg_index()); + let block_input = block_children.next().unwrap().into(); + let block_output = block_children.next_back().unwrap().into(); + b.replace_op( + block_input, + NodeType::new_pure(ops::Input::new(type_row![Q])), + ) + .unwrap(); + b.replace_op( + block_output, + NodeType::new_pure(ops::Output::new(type_row![Type::new_unit_sum(1), Q])), + ) + .unwrap(); + assert_matches!( + b.validate(&EMPTY_REG), + Err(ValidationError::InvalidEdges { parent, source: EdgeValidationError::CFGEdgeSignatureMismatch { .. }, .. }) + => assert_eq!(parent, cfg) + ); + } + + #[test] + fn parent_io_mismatch() { + // The DFG node declares that it has an empty extension delta, + // but it's child graph adds extension "XB", causing a mismatch. + let mut hugr = Hugr::new(NodeType::new_pure(ops::DFG { + signature: FunctionType::new(type_row![USIZE_T], type_row![USIZE_T]), + })); + + let input = hugr + .add_node_with_parent( + hugr.root(), + NodeType::new_pure(ops::Input { + types: type_row![USIZE_T], + }), + ) + .unwrap(); + let output = hugr + .add_node_with_parent( + hugr.root(), + NodeType::new( + ops::Output { + types: type_row![USIZE_T], + }, + ExtensionSet::singleton(&XB), + ), + ) + .unwrap(); + + let lift = hugr + .add_node_with_parent( + hugr.root(), + NodeType::new_pure(ops::LeafOp::Lift { + type_row: type_row![USIZE_T], + new_extension: XB, + }), + ) + .unwrap(); + + hugr.connect(input, 0, lift, 0).unwrap(); + hugr.connect(lift, 0, output, 0).unwrap(); + + let result = hugr.validate(&PRELUDE_REGISTRY); + assert_matches!( + result, + Err(ValidationError::ExtensionError( + ExtensionError::ParentIOExtensionMismatch { .. } + )) + ); + } + + #[test] + /// A wire with no extension requirements is wired into a node which has + /// [A,BOOL_T] extensions required on its inputs and outputs. This could be fixed + /// by adding a lift node, but for validation this is an error. + fn missing_lift_node() -> Result<(), BuildError> { + let mut module_builder = ModuleBuilder::new(); + let mut main = module_builder.define_function( + "main", + FunctionType::new(type_row![NAT], type_row![NAT]).into(), + )?; + let [main_input] = main.input_wires_arr(); + + let f_builder = main.dfg_builder( + FunctionType::new(type_row![NAT], type_row![NAT]), + // Inner DFG has extension requirements that the wire wont satisfy + Some(ExtensionSet::from_iter([XA, XB])), + [main_input], + )?; + let f_inputs = f_builder.input_wires(); + let f_handle = f_builder.finish_with_outputs(f_inputs)?; + let [f_output] = f_handle.outputs_arr(); + main.finish_with_outputs([f_output])?; + let handle = module_builder.hugr().validate(&PRELUDE_REGISTRY); + + assert_matches!( + handle, + Err(ValidationError::ExtensionError( + ExtensionError::TgtExceedsSrcExtensionsAtPort { .. } + )) + ); + Ok(()) + } + + #[test] + /// A wire with extension requirement `[A]` is wired into a an output with no + /// extension req. In the validation extension typechecking, we don't do any + /// unification, so don't allow open extension variables on the function + /// signature, so this fails. + fn too_many_extension() -> Result<(), BuildError> { + let mut module_builder = ModuleBuilder::new(); + + let main_sig = FunctionType::new(type_row![NAT], type_row![NAT]).into(); + + let mut main = module_builder.define_function("main", main_sig)?; + let [main_input] = main.input_wires_arr(); + + let inner_sig = FunctionType::new(type_row![NAT], type_row![NAT]) + .with_extension_delta(&ExtensionSet::singleton(&XA)); + + let f_builder = main.dfg_builder(inner_sig, Some(ExtensionSet::new()), [main_input])?; + let f_inputs = f_builder.input_wires(); + let f_handle = f_builder.finish_with_outputs(f_inputs)?; + let [f_output] = f_handle.outputs_arr(); + main.finish_with_outputs([f_output])?; + let handle = module_builder.hugr().validate(&PRELUDE_REGISTRY); + assert_matches!( + handle, + Err(ValidationError::ExtensionError( + ExtensionError::SrcExceedsTgtExtensionsAtPort { .. } + )) + ); + Ok(()) + } + + #[test] + /// A wire with extension requirements `[A]` and another with requirements + /// `[BOOL_T]` are both wired into a node which requires its inputs to have + /// requirements `[A,BOOL_T]`. A slightly more complex test of the error from + /// `missing_lift_node`. + fn extensions_mismatch() -> Result<(), BuildError> { + let mut module_builder = ModuleBuilder::new(); + + let all_rs = ExtensionSet::from_iter([XA, XB]); + + let main_sig = FunctionType::new(type_row![], type_row![NAT]) + .with_extension_delta(&all_rs) + .into(); + + let mut main = module_builder.define_function("main", main_sig)?; + + let [left_wire] = main + .dfg_builder( + FunctionType::new(type_row![], type_row![NAT]), + Some(ExtensionSet::singleton(&XA)), + [], + )? + .finish_with_outputs([])? + .outputs_arr(); + + let [right_wire] = main + .dfg_builder( + FunctionType::new(type_row![], type_row![NAT]), + Some(ExtensionSet::singleton(&XB)), + [], + )? + .finish_with_outputs([])? + .outputs_arr(); + + let builder = main.dfg_builder( + FunctionType::new(type_row![NAT, NAT], type_row![NAT]), + Some(all_rs), + [left_wire, right_wire], + )?; + let [_left, _right] = builder.input_wires_arr(); + let [output] = builder.finish_with_outputs([])?.outputs_arr(); + + main.finish_with_outputs([output])?; + let handle = module_builder.hugr().validate(&PRELUDE_REGISTRY); + assert_matches!( + handle, + Err(ValidationError::ExtensionError( + ExtensionError::TgtExceedsSrcExtensionsAtPort { .. } + )) + ); + Ok(()) + } + + #[test] + fn parent_signature_mismatch() -> Result<(), BuildError> { + let rs = ExtensionSet::singleton(&XA); + + let main_signature = + FunctionType::new(type_row![NAT], type_row![NAT]).with_extension_delta(&rs); + + let mut hugr = Hugr::new(NodeType::new_pure(ops::DFG { + signature: main_signature, + })); + let input = hugr.add_node_with_parent( + hugr.root(), + NodeType::new_pure(ops::Input { + types: type_row![NAT], + }), + )?; + let output = hugr.add_node_with_parent( + hugr.root(), + NodeType::new( + ops::Output { + types: type_row![NAT], + }, + rs, + ), + )?; + hugr.connect(input, 0, output, 0)?; + + assert_matches!( + hugr.validate(&PRELUDE_REGISTRY), + Err(ValidationError::ExtensionError( + ExtensionError::TgtExceedsSrcExtensionsAtPort { .. } + )) + ); + Ok(()) + } +} From 8eafea53cf8355c6ae1b3624b006be79e71217ed Mon Sep 17 00:00:00 2001 From: Craig Roy Date: Mon, 8 Jan 2024 16:11:55 +0000 Subject: [PATCH 6/7] chore: Run coverage with all features --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ed7a545f..a916d9be8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,7 +104,7 @@ jobs: cargo llvm-cov clean --workspace cargo llvm-cov --doctests - name: Generate coverage report - run: cargo llvm-cov report --codecov --output-path coverage.json + run: cargo llvm-cov --all-features report --codecov --output-path coverage.json - name: Upload coverage to codecov.io uses: codecov/codecov-action@v3 with: From d1e8fbf476f0fe2314e2b12c70b8aefe7bf63638 Mon Sep 17 00:00:00 2001 From: Craig Roy Date: Mon, 8 Jan 2024 16:20:47 +0000 Subject: [PATCH 7/7] fix: Run coverage with all features --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a916d9be8..cf5c86c6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -102,7 +102,7 @@ jobs: - name: Run tests with coverage instrumentation run: | cargo llvm-cov clean --workspace - cargo llvm-cov --doctests + cargo llvm-cov --all-features --doctests - name: Generate coverage report run: cargo llvm-cov --all-features report --codecov --output-path coverage.json - name: Upload coverage to codecov.io