diff --git a/hugr-py/src/hugr/serialization/tys.py b/hugr-py/src/hugr/serialization/tys.py index 86078fc27..daee10b43 100644 --- a/hugr-py/src/hugr/serialization/tys.py +++ b/hugr-py/src/hugr/serialization/tys.py @@ -214,6 +214,15 @@ class Variable(ConfiguredBaseModel): b: "TypeBound" +class RowVar(ConfiguredBaseModel): + """A variable standing for a row of some (unknown) number of types. + May occur only within a row; not a node input/output.""" + + t: Literal["R"] = "R" + i: int + b: "TypeBound" + + class USize(ConfiguredBaseModel): """Unsigned integer size type.""" @@ -321,7 +330,15 @@ class Type(RootModel): """A HUGR type.""" root: Annotated[ - Qubit | Variable | USize | FunctionType | Array | SumType | Opaque | Alias, + Qubit + | Variable + | RowVar + | USize + | FunctionType + | Array + | SumType + | Opaque + | Alias, WrapValidator(_json_custom_error_validator), Field(discriminator="t"), ] diff --git a/hugr/src/builder/build_traits.rs b/hugr/src/builder/build_traits.rs index e3ef10de8..1ddd5a1d6 100644 --- a/hugr/src/builder/build_traits.rs +++ b/hugr/src/builder/build_traits.rs @@ -19,7 +19,7 @@ use crate::{ types::EdgeKind, }; -use crate::extension::{ExtensionRegistry, ExtensionSet, PRELUDE_REGISTRY}; +use crate::extension::{ExtensionRegistry, ExtensionSet, SignatureError, PRELUDE_REGISTRY}; use crate::types::{FunctionType, PolyFuncType, Type, TypeArg, TypeRow}; use itertools::Itertools; @@ -87,7 +87,7 @@ pub trait Container { name: impl Into, signature: PolyFuncType, ) -> Result, BuildError> { - let body = signature.body().clone(); + let body = signature.body_norowvars()?.clone(); let f_node = self.add_child_node(NodeType::new_pure(ops::FuncDefn { name: name.into(), signature, diff --git a/hugr/src/builder/dataflow.rs b/hugr/src/builder/dataflow.rs index 473e79877..a8ebad2cf 100644 --- a/hugr/src/builder/dataflow.rs +++ b/hugr/src/builder/dataflow.rs @@ -147,7 +147,7 @@ impl FunctionBuilder { /// /// Error in adding DFG child nodes. pub fn new(name: impl Into, signature: PolyFuncType) -> Result { - let body = signature.body().clone(); + let body = signature.body_norowvars()?.clone(); let op = ops::FuncDefn { signature, name: name.into(), diff --git a/hugr/src/builder/module.rs b/hugr/src/builder/module.rs index f6b4b7f20..4fada8957 100644 --- a/hugr/src/builder/module.rs +++ b/hugr/src/builder/module.rs @@ -84,7 +84,7 @@ impl + AsRef> ModuleBuilder { op_desc: "crate::ops::OpType::FuncDecl", })? .clone(); - let body = signature.body().clone(); + let body = signature.body_norowvars()?.clone(); self.hugr_mut() .replace_op( f_node, diff --git a/hugr/src/extension.rs b/hugr/src/extension.rs index 9b9c56b9d..b0f0cc620 100644 --- a/hugr/src/extension.rs +++ b/hugr/src/extension.rs @@ -161,6 +161,9 @@ pub enum SignatureError { /// A type variable that was used has not been declared #[error("Type variable {idx} was not declared ({num_decls} in scope)")] FreeTypeVar { idx: usize, num_decls: usize }, + /// A row variable was found outside of a variable-length row + #[error("Row variable {idx} was found outside of a type row")] + RowTypeVarOutsideRow { idx: usize }, /// The result of the type application stored in a [Call] /// is not what we get by applying the type-args to the polymorphic function /// diff --git a/hugr/src/extension/op_def.rs b/hugr/src/extension/op_def.rs index 91a49cfa5..b81db42c0 100644 --- a/hugr/src/extension/op_def.rs +++ b/hugr/src/extension/op_def.rs @@ -11,7 +11,7 @@ use super::{ use crate::ops::{OpName, OpNameRef}; use crate::types::type_param::{check_type_args, TypeArg, TypeParam}; -use crate::types::{FunctionType, PolyFuncType}; +use crate::types::{FunctionType, FunctionTypeVarArgs, PolyFuncType, Substitution}; use crate::Hugr; /// Trait necessary for binary computations of OpDef signature @@ -405,7 +405,10 @@ impl OpDef { // TODO https://github.com/CQCL/hugr/issues/624 validate declared TypeParams // for both type scheme and custom binary if let SignatureFunc::TypeScheme(ts) = &self.signature_func { - ts.poly_func.validate(exts)?; + // The type scheme may contain row variables so be of variable length; + // these will have to be substituted to fixed-length concrete types when + // the OpDef is instantiated into an actual OpType. + ts.poly_func.validate_varargs(exts)?; } Ok(()) } @@ -482,6 +485,7 @@ mod test { use crate::extension::{SignatureError, EMPTY_REG, PRELUDE_REGISTRY}; use crate::ops::{CustomOp, OpName}; use crate::std_extensions::collections::{EXTENSION, LIST_TYPENAME}; + use crate::types::type_param::TypeArgError; use crate::types::Type; use crate::types::{type_param::TypeParam, FunctionType, PolyFuncType, TypeArg, TypeBound}; use crate::{const_extension_ids, Extension}; @@ -639,6 +643,17 @@ mod test { def.compute_signature(&args, &EMPTY_REG), Ok(FunctionType::new_endo(vec![tv])) ); + // But not with an external row variable + let arg: TypeArg = Type::new_row_var_use(0, TypeBound::Eq).into(); + assert_eq!( + def.compute_signature(&[arg.clone()], &EMPTY_REG), + Err(SignatureError::TypeArgMismatch( + TypeArgError::TypeMismatch { + param: TypeBound::Any.into(), + arg + } + )) + ); Ok(()) } diff --git a/hugr/src/hugr/serialize/test.rs b/hugr/src/hugr/serialize/test.rs index c9ff8eae0..4f891bd86 100644 --- a/hugr/src/hugr/serialize/test.rs +++ b/hugr/src/hugr/serialize/test.rs @@ -392,13 +392,32 @@ fn polyfunctype1() -> PolyFuncType { PolyFuncType::new([TypeParam::max_nat(), TypeParam::Extensions], function_type) } +fn polyfunctype2() -> PolyFuncType { + let tv0 = Type::new_row_var_use(0, TypeBound::Any); + let tv1 = Type::new_row_var_use(1, TypeBound::Eq); + let params = [TypeBound::Any, TypeBound::Eq].map(TypeParam::new_list); + let inputs = vec![ + Type::new_function(FunctionType::new(tv0.clone(), tv1.clone())), + tv0, + ]; + let res = PolyFuncType::new(params, FunctionType::new(inputs, tv1)); + // Just check we've got the arguments the right way round + // (not that it really matters for the serialization schema we have) + res.validate_varargs(&EMPTY_REG).unwrap(); + res +} + #[rstest] #[case(FunctionType::new_endo(type_row![]).into())] #[case(polyfunctype1())] #[case(PolyFuncType::new([TypeParam::Opaque { ty: int_custom_type(TypeArg::BoundedNat { n: 1 }) }], FunctionType::new_endo(type_row![Type::new_var_use(0, TypeBound::Copyable)])))] #[case(PolyFuncType::new([TypeBound::Eq.into()], FunctionType::new_endo(type_row![Type::new_var_use(0, TypeBound::Eq)])))] -#[case(PolyFuncType::new([TypeParam::List { param: Box::new(TypeBound::Any.into()) }], FunctionType::new_endo(type_row![])))] +#[case(PolyFuncType::new([TypeParam::new_list(TypeBound::Any)], FunctionType::new_endo(type_row![])))] #[case(PolyFuncType::new([TypeParam::Tuple { params: [TypeBound::Any.into(), TypeParam::bounded_nat(2.try_into().unwrap())].into() }], FunctionType::new_endo(type_row![])))] +#[case(PolyFuncType::new( + [TypeParam::new_list(TypeBound::Any)], + FunctionType::new_endo(Type::new_tuple(Type::new_row_var_use(0, TypeBound::Any)))))] +#[case(polyfunctype2())] fn roundtrip_polyfunctype(#[case] poly_func_type: PolyFuncType) { check_testing_roundtrip(poly_func_type) } diff --git a/hugr/src/hugr/validate.rs b/hugr/src/hugr/validate.rs index 40de5d805..95ea36e4c 100644 --- a/hugr/src/hugr/validate.rs +++ b/hugr/src/hugr/validate.rs @@ -301,11 +301,14 @@ impl<'a, 'b> ValidationContext<'a, 'b> { var_decls: &[TypeParam], ) -> Result<(), SignatureError> { match &port_kind { - EdgeKind::Value(ty) => ty.validate(self.extension_registry, var_decls), + EdgeKind::Value(ty) => ty.validate_1type(self.extension_registry, var_decls), // Static edges must *not* refer to type variables declared by enclosing FuncDefns // as these are only types at runtime. - EdgeKind::Const(ty) => ty.validate(self.extension_registry, &[]), - EdgeKind::Function(pf) => pf.validate(self.extension_registry), + EdgeKind::Const(ty) => ty.validate_1type(self.extension_registry, &[]), + // Allow function "value" to have unknown arity. A Call node will have to provide + // TypeArgs that produce a known arity, but a LoadFunction might pass the function + // value ("function pointer") around without knowing how to call it. + EdgeKind::Function(pf) => pf.validate_varargs(self.extension_registry), _ => Ok(()), } } diff --git a/hugr/src/hugr/validate/test.rs b/hugr/src/hugr/validate/test.rs index a1518fcb2..120a23bf4 100644 --- a/hugr/src/hugr/validate/test.rs +++ b/hugr/src/hugr/validate/test.rs @@ -1,4 +1,5 @@ use cool_asserts::assert_matches; +use rstest::rstest; use super::*; use crate::builder::test::closed_dfg_root_hugr; @@ -10,7 +11,7 @@ use crate::extension::prelude::{BOOL_T, PRELUDE, PRELUDE_ID, USIZE_T}; use crate::extension::{Extension, ExtensionSet, TypeDefBound, EMPTY_REG, PRELUDE_REGISTRY}; use crate::hugr::hugrmut::sealed::HugrMutInternals; use crate::hugr::HugrMut; -use crate::ops::dataflow::IOTrait; +use crate::ops::dataflow::{IOTrait, LoadFunction}; use crate::ops::handle::NodeHandle; use crate::ops::leaf::MakeTuple; use crate::ops::{self, Noop, OpType, Value}; @@ -546,6 +547,147 @@ fn no_polymorphic_consts() -> Result<(), Box> { Ok(()) } +fn extension_with_eval_parallel() -> Extension { + let rowp = TypeParam::new_list(TypeBound::Any); + let mut e = Extension::new(EXT_ID); + + let inputs = Type::new_row_var_use(0, TypeBound::Any); + let outputs = Type::new_row_var_use(1, TypeBound::Any); + let evaled_fn = Type::new_function(FunctionType::new(inputs.clone(), outputs.clone())); + let pf = PolyFuncType::new( + [rowp.clone(), rowp.clone()], + FunctionType::new(vec![evaled_fn, inputs], outputs), + ); + e.add_op("eval".into(), "".into(), pf).unwrap(); + + let rv = |idx| Type::new_row_var_use(idx, TypeBound::Any); + let pf = PolyFuncType::new( + [rowp.clone(), rowp.clone(), rowp.clone(), rowp.clone()], + FunctionType::new( + vec![ + Type::new_function(FunctionType::new(rv(0), rv(2))), + Type::new_function(FunctionType::new(rv(1), rv(3))), + ], + Type::new_function(FunctionType::new(vec![rv(0), rv(1)], vec![rv(2), rv(3)])), + ), + ); + e.add_op("parallel".into(), "".into(), pf).unwrap(); + + e +} + +#[test] +fn instantiate_row_variables() -> Result<(), Box> { + fn uint_seq(i: usize) -> TypeArg { + vec![TypeArg::Type { ty: USIZE_T }; i].into() + } + let e = extension_with_eval_parallel(); + let mut dfb = DFGBuilder::new(FunctionType::new( + vec![ + Type::new_function(FunctionType::new(USIZE_T, vec![USIZE_T, USIZE_T])), + USIZE_T, + ], // inputs: function + its argument + vec![USIZE_T; 4], // outputs (*2^2, three calls) + ))?; + let [func, int] = dfb.input_wires_arr(); + let eval = e.instantiate_extension_op("eval", [uint_seq(1), uint_seq(2)], &PRELUDE_REGISTRY)?; + let [a, b] = dfb.add_dataflow_op(eval, [func, int])?.outputs_arr(); + let par = e.instantiate_extension_op( + "parallel", + [uint_seq(1), uint_seq(1), uint_seq(2), uint_seq(2)], + &PRELUDE_REGISTRY, + )?; + let [par_func] = dfb.add_dataflow_op(par, [func, func])?.outputs_arr(); + let eval2 = + e.instantiate_extension_op("eval", [uint_seq(2), uint_seq(4)], &PRELUDE_REGISTRY)?; + let eval2 = dfb.add_dataflow_op(eval2, [par_func, a, b])?; + dfb.finish_hugr_with_outputs( + eval2.outputs(), + &ExtensionRegistry::try_new([PRELUDE.to_owned(), e]).unwrap(), + )?; + Ok(()) +} + +fn seq1ty(t: Type) -> TypeArg { + TypeArg::Sequence { + elems: vec![t.into()], + } +} + +#[test] +fn inner_row_variables() -> Result<(), Box> { + let e = extension_with_eval_parallel(); + let tv = Type::new_row_var_use(0, TypeBound::Any); + let inner_ft = Type::new_function(FunctionType::new_endo(tv.clone())); + let ft_usz = Type::new_function(FunctionType::new_endo(vec![tv.clone(), USIZE_T])); + let mut fb = FunctionBuilder::new( + "id", + PolyFuncType::new( + [TypeParam::new_list(TypeBound::Any)], + FunctionType::new(inner_ft.clone(), ft_usz), + ), + )?; + // All the wires here are carrying higher-order Function values + let [func_arg] = fb.input_wires_arr(); + let [id_usz] = { + let bldr = fb.define_function("id_usz", FunctionType::new_endo(USIZE_T).into())?; + let vals = bldr.input_wires(); + let [inner_def] = bldr.finish_with_outputs(vals)?.outputs_arr(); + let loadf = LoadFunction::try_new( + FunctionType::new_endo(USIZE_T).into(), + [], + &PRELUDE_REGISTRY, + ) + .unwrap(); + fb.add_dataflow_op(loadf, [inner_def])?.outputs_arr() + }; + let par = e.instantiate_extension_op( + "parallel", + [tv.clone(), USIZE_T, tv.clone(), USIZE_T].map(seq1ty), + &PRELUDE_REGISTRY, + )?; + let par_func = fb.add_dataflow_op(par, [func_arg, id_usz])?; + fb.finish_hugr_with_outputs( + par_func.outputs(), + &ExtensionRegistry::try_new([PRELUDE.to_owned(), e]).unwrap(), + )?; + Ok(()) +} + +#[rstest] +#[case(false)] +#[case(true)] +fn no_outer_row_variables(#[case] connect: bool) { + let e = extension_with_eval_parallel(); + let tv = Type::new_row_var_use(0, TypeBound::Copyable); + let mut fb = FunctionBuilder::new( + "bad_eval", + PolyFuncType::new( + [TypeParam::new_list(TypeBound::Copyable)], + FunctionType::new( + Type::new_function(FunctionType::new(USIZE_T, tv.clone())), + if connect { vec![tv.clone()] } else { vec![] }, + ), + ), + ).unwrap(); + let [func_arg] = fb.input_wires_arr(); + let i = fb.add_load_value(crate::extension::prelude::ConstUsize::new(5)); + let ev = + e.instantiate_extension_op("eval", [seq1ty(USIZE_T), seq1ty(tv)], &PRELUDE_REGISTRY).unwrap(); + let ev = fb.add_dataflow_op(ev, [func_arg, i]).unwrap(); + let reg = ExtensionRegistry::try_new([PRELUDE.to_owned(), e]).unwrap(); + if connect { + fb.set_outputs(ev.outputs()).unwrap(); + } + assert_eq!( + fb.finish_hugr(®).unwrap_err(), + ValidationError::SignatureError { + node: ev.node(), + cause: SignatureError::RowTypeVarOutsideRow { idx: 0 } + } + ); +} + #[test] fn test_polymorphic_call() -> Result<(), Box> { let mut e = Extension::new(EXT_ID); @@ -562,7 +704,7 @@ fn test_polymorphic_call() -> Result<(), Box> { ) .with_extension_delta(ExtensionSet::type_var(1)), ); - // The higher-order "eval" operation - takes a function and its argument. + // Single-input/output version of the higher-order "eval" operation, with extension param. // Note the extension-delta of the eval node includes that of the input function. e.add_op( "eval".into(), diff --git a/hugr/src/ops/module.rs b/hugr/src/ops/module.rs index 5557ac802..5123b9b0b 100644 --- a/hugr/src/ops/module.rs +++ b/hugr/src/ops/module.rs @@ -47,7 +47,8 @@ impl StaticTag for FuncDefn { impl DataflowParent for FuncDefn { fn inner_signature(&self) -> FunctionType { - self.signature.body().clone() + // ok by validation + self.signature.body_norowvars().unwrap().clone() } } diff --git a/hugr/src/types.rs b/hugr/src/types.rs index 4862d7552..22b645a2d 100644 --- a/hugr/src/types.rs +++ b/hugr/src/types.rs @@ -14,7 +14,7 @@ use crate::utils::display_list_with_separator; pub use check::SumTypeError; pub use custom::CustomType; pub use poly_func::PolyFuncType; -pub use signature::FunctionType; +pub use signature::{FunctionType, FunctionTypeVarArgs}; use smol_str::SmolStr; pub use type_param::TypeArg; pub use type_row::TypeRow; @@ -205,11 +205,15 @@ pub enum TypeEnum { Alias(AliasDecl), #[allow(missing_docs)] #[display(fmt = "Function({})", "_0")] - Function(Box), + Function(Box), // Index into TypeParams, and cache of TypeBound (checked in validation) #[allow(missing_docs)] #[display(fmt = "Variable({})", _0)] Variable(usize, TypeBound), + /// Variable index, and cache of inner TypeBound - matches a [TypeParam::List] of [TypeParam::Type] + /// of this bound (checked in validation) + #[display(fmt = "RowVar({})", _0)] + RowVariable(usize, TypeBound), #[allow(missing_docs)] #[display(fmt = "{}", "_0")] Sum(SumType), @@ -221,7 +225,7 @@ impl TypeEnum { TypeEnum::Extension(c) => c.bound(), TypeEnum::Alias(a) => a.bound, TypeEnum::Function(_) => TypeBound::Copyable, - TypeEnum::Variable(_, b) => *b, + TypeEnum::Variable(_, b) | TypeEnum::RowVariable(_, b) => *b, TypeEnum::Sum(SumType::Unit { size: _ }) => TypeBound::Eq, TypeEnum::Sum(SumType::General { rows }) => least_upper_bound( rows.iter() @@ -259,14 +263,6 @@ impl TypeEnum { /// ``` pub struct Type(TypeEnum, TypeBound); -fn validate_each<'a>( - extension_registry: &ExtensionRegistry, - var_decls: &[TypeParam], - mut iter: impl Iterator, -) -> Result<(), SignatureError> { - iter.try_for_each(|t| t.validate(extension_registry, var_decls)) -} - impl Type { /// An empty `TypeRow`. Provided here for convenience pub const EMPTY_TYPEROW: TypeRow = type_row![]; @@ -276,7 +272,7 @@ impl Type { const EMPTY_TYPEROW_REF: &'static TypeRow = &Self::EMPTY_TYPEROW; /// Initialize a new function type. - pub fn new_function(fun_ty: impl Into) -> Self { + pub fn new_function(fun_ty: impl Into) -> Self { Self::new(TypeEnum::Function(Box::new(fun_ty.into()))) } @@ -326,6 +322,17 @@ impl Type { Self(TypeEnum::Variable(idx, bound), bound) } + /// New use (occurrence) of the row variable with specified index. + /// `bound` must match that with which the variable was declared + /// (i.e. as a [TypeParam::List]` of a `[TypeParam::Type]` of that bound). + /// For use in [OpDef], not [FuncDefn], type schemes only. + /// + /// [OpDef]: crate::extension::OpDef + /// [FuncDefn]: crate::ops::FuncDefn + pub const fn new_row_var_use(idx: usize, bound: TypeBound) -> Self { + Self(TypeEnum::RowVariable(idx, bound), bound) + } + /// Report the least upper TypeBound, if there is one. #[inline(always)] pub const fn least_upper_bound(&self) -> TypeBound { @@ -344,14 +351,28 @@ impl Type { TypeBound::Copyable.contains(self.least_upper_bound()) } - /// Checks all variables used in the type are in the provided list - /// of bound variables, and that for each [CustomType] the corresponding + /// If this Type is a row variable, return its bound, otherwise None + pub fn row_var_bound(&self) -> Option { + match self.0 { + TypeEnum::RowVariable(_, b) => Some(b), + _ => None, + } + } + + /// TODO docs + pub fn is_row_var(&self) -> bool { + matches!(self.0, TypeEnum::RowVariable(_, _)) + } + + /// Checks that this [Type] represents a single Type, not a row variable, + /// that all variables used within are in the provided list of bound variables, + /// and that for each [CustomType], the corresponding /// [TypeDef] is in the [ExtensionRegistry] and the type arguments /// [validate] and fit into the def's declared parameters. /// /// [validate]: crate::types::type_param::TypeArg::validate /// [TypeDef]: crate::extension::TypeDef - pub(crate) fn validate( + pub(crate) fn validate_1type( &self, extension_registry: &ExtensionRegistry, var_decls: &[TypeParam], @@ -359,32 +380,58 @@ impl Type { // There is no need to check the components against the bound, // that is guaranteed by construction (even for deserialization) match &self.0 { - TypeEnum::Sum(SumType::General { rows }) => validate_each( - extension_registry, - var_decls, - rows.iter().flat_map(|x| x.iter()), - ), + TypeEnum::Sum(SumType::General { rows }) => rows + .iter() + .try_for_each(|row| row.validate_var_len(extension_registry, var_decls)), TypeEnum::Sum(SumType::Unit { .. }) => Ok(()), // No leaves there TypeEnum::Alias(_) => Ok(()), TypeEnum::Extension(custy) => custy.validate(extension_registry, var_decls), - TypeEnum::Function(ft) => ft.validate(extension_registry, var_decls), + // FunctionTypes used as types of values may have unknown arity (e.g. if they are not called) + TypeEnum::Function(ft) => ft.validate_varargs(extension_registry, var_decls), TypeEnum::Variable(idx, bound) => check_typevar_decl(var_decls, *idx, &(*bound).into()), + TypeEnum::RowVariable(idx, _) => { + Err(SignatureError::RowTypeVarOutsideRow { idx: *idx }) + } + } + } + + /// Checks that this [Type] is valid (as per [Type::validate_1type]), but also + /// allows row variables that may become multiple types + fn validate_in_row( + &self, + extension_registry: &ExtensionRegistry, + var_decls: &[TypeParam], + ) -> Result<(), SignatureError> { + if let TypeEnum::RowVariable(idx, bound) = self.0 { + let t = TypeParam::List { + param: Box::new(bound.into()), + }; + check_typevar_decl(var_decls, idx, &t) + } else { + self.validate_1type(extension_registry, var_decls) } } - pub(crate) fn substitute(&self, t: &Substitution) -> Self { + /// Applies a substitution to a type. + /// This may result in a row of types, if this [Type] is not really a single type but actually a row variable + /// Invariants may be confirmed by validation: + /// * If [Type::validate] returns successfully, this method will return a Vec containing exactly one type + /// * If [Type::validate] fails, but [Type::validate_in_row] succeeds, this method may (depending on structure of self) + /// return a Vec containing any number of [Type]s. These may (or not) pass [Type::validate] + fn substitute(&self, t: &Substitution) -> Vec { match &self.0 { - TypeEnum::Alias(_) | TypeEnum::Sum(SumType::Unit { .. }) => self.clone(), + TypeEnum::RowVariable(idx, bound) => t.apply_rowvar(*idx, *bound), + TypeEnum::Alias(_) | TypeEnum::Sum(SumType::Unit { .. }) => vec![self.clone()], TypeEnum::Variable(idx, bound) => { let TypeArg::Type { ty } = t.apply_var(*idx, &((*bound).into())) else { panic!("Variable was not a type - try validate() first") }; - ty + vec![ty] } - TypeEnum::Extension(cty) => Type::new_extension(cty.substitute(t)), - TypeEnum::Function(bf) => Type::new_function(bf.substitute(t)), + TypeEnum::Extension(cty) => vec![Type::new_extension(cty.substitute(t))], + TypeEnum::Function(bf) => vec![Type::new_function(bf.substitute(t))], TypeEnum::Sum(SumType::General { rows }) => { - Type::new_sum(rows.iter().map(|x| subst_row(x, t))) + vec![Type::new_sum(rows.iter().map(|r| r.substitute(t)))] } } } @@ -392,7 +439,7 @@ impl Type { /// Details a replacement of type variables with a finite list of known values. /// (Variables out of the range of the list will result in a panic) -pub(crate) struct Substitution<'a>(&'a [TypeArg], &'a ExtensionRegistry); +pub(crate) struct Substitution<'a>(pub(crate) &'a [TypeArg], pub(crate) &'a ExtensionRegistry); impl<'a> Substitution<'a> { pub(crate) fn apply_var(&self, idx: usize, decl: &TypeParam) -> TypeArg { @@ -404,20 +451,34 @@ impl<'a> Substitution<'a> { arg.clone() } + fn apply_rowvar(&self, idx: usize, bound: TypeBound) -> Vec { + let arg = self + .0 + .get(idx) + .expect("Undeclared type variable - call validate() ?"); + debug_assert_eq!(check_type_arg(arg, &TypeParam::new_list(bound)), Ok(())); + match arg { + // Row variables are represented as 'TypeArg::Type's (see TypeArg::new_row_var_use) + TypeArg::Sequence { elems } => elems + .iter() + .map(|ta| match ta { + TypeArg::Type { ty } => ty.clone(), + _ => panic!("Not a list of types - call validate() ?"), + }) + .collect(), + TypeArg::Type { ty } if matches!(ty.0, TypeEnum::RowVariable(_, _)) => { + // Standalone "Type" can be used iff its actually a Row Variable not an actual (single) Type + vec![ty.clone()] + } + _ => panic!("Not a type or list of types - did validate() ?"), + } + } + fn extension_registry(&self) -> &ExtensionRegistry { self.1 } } -fn subst_row(row: &TypeRow, tr: &Substitution) -> TypeRow { - let res = row - .iter() - .map(|ty| ty.substitute(tr)) - .collect::>() - .into(); - res -} - pub(crate) fn check_typevar_decl( decls: &[TypeParam], idx: usize, diff --git a/hugr/src/types/poly_func.rs b/hugr/src/types/poly_func.rs index 9e90dac11..e4eccab44 100644 --- a/hugr/src/types/poly_func.rs +++ b/hugr/src/types/poly_func.rs @@ -4,7 +4,7 @@ use crate::extension::{ExtensionRegistry, SignatureError}; use itertools::Itertools; use super::type_param::{check_type_args, TypeArg, TypeParam}; -use super::{FunctionType, Substitution}; +use super::{FunctionType, FunctionTypeVarArgs, Substitution}; /// A polymorphic type scheme, i.e. of a [FuncDecl], [FuncDefn] or [OpDef]. /// (Nodes/operations in the Hugr are not polymorphic.) @@ -20,17 +20,18 @@ use super::{FunctionType, Substitution}; "params.iter().map(ToString::to_string).join(\" \")", "body" )] + pub struct PolyFuncType { /// The declared type parameters, i.e., these must be instantiated with /// the same number of [TypeArg]s before the function can be called. This /// defines the indices used by variables inside the body. params: Vec, /// Template for the function. May contain variables up to length of [Self::params] - body: FunctionType, + body: FunctionTypeVarArgs, } -impl From for PolyFuncType { - fn from(body: FunctionType) -> Self { +impl From for PolyFuncType { + fn from(body: FunctionTypeVarArgs) -> Self { Self { params: vec![], body, @@ -38,7 +39,13 @@ impl From for PolyFuncType { } } -impl TryFrom for FunctionType { +impl From for PolyFuncType { + fn from(body: FunctionType) -> Self { + Into::::into(body).into() + } +} + +impl TryFrom for FunctionTypeVarArgs { /// If the PolyFuncType is not a monomorphic FunctionType, fail with the binders type Error = Vec; @@ -51,6 +58,19 @@ impl TryFrom for FunctionType { } } +impl TryFrom for FunctionType { + /// If the PolyFuncType is not a monomorphic FunctionType, fail with the binders + type Error = PolyFuncType; + + fn try_from(value: PolyFuncType) -> Result { + if let Ok(ftva) = TryInto::::try_into(value.clone()){ + ftva.try_into().map_err(|_| value) + } else { + Err(value) + } + } +} + impl PolyFuncType { /// The type parameters, aka binders, over which this type is polymorphic pub fn params(&self) -> &[TypeParam] { @@ -58,24 +78,32 @@ impl PolyFuncType { } /// The body of the type, a function type. - pub fn body(&self) -> &FunctionType { + pub fn body(&self) -> &FunctionTypeVarArgs { &self.body } + /// The body of the type, a function type. + pub fn body_norowvars(&self) -> Result<&FunctionType,SignatureError> { + self.body.try_as_ref() + } + /// Create a new PolyFuncType given the kinds of the variables it declares /// and the underlying [FunctionType]. - pub fn new(params: impl Into>, body: FunctionType) -> Self { + pub fn new(params: impl Into>, body: impl Into) -> Self { Self { params: params.into(), - body, + body: body.into(), } } /// Validates this instance, checking that the types in the body are /// wellformed with respect to the registry, and the type variables declared. - pub fn validate(&self, reg: &ExtensionRegistry) -> Result<(), SignatureError> { + /// Allows both inputs and outputs to contain [RowVariable]s + /// + /// [RowVariable]: [crate::types::TypeEnum::RowVariable] + pub fn validate_varargs(&self, reg: &ExtensionRegistry) -> Result<(), SignatureError> { // TODO https://github.com/CQCL/hugr/issues/624 validate TypeParams declared here, too - self.body.validate(reg, &self.params) + self.body.validate_varargs(reg, &self.params) } /// Instantiates an outer [PolyFuncType], i.e. with no free variables @@ -89,6 +117,14 @@ impl PolyFuncType { args: &[TypeArg], ext_reg: &ExtensionRegistry, ) -> Result { + self.instantiate_varargs(args,ext_reg).and_then(TryInto::try_into) + } + + pub(crate) fn instantiate_varargs( + &self, + args: &[TypeArg], + ext_reg: &ExtensionRegistry, + ) -> Result { // Check that args are applicable, and that we have a value for each binder, // i.e. each possible free variable within the body. check_type_args(args, &self.params)?; @@ -100,11 +136,13 @@ impl PolyFuncType { pub(crate) mod test { use std::num::NonZeroU64; + use cool_asserts::assert_matches; use lazy_static::lazy_static; - use crate::extension::prelude::{PRELUDE_ID, USIZE_CUSTOM_T, USIZE_T}; + use crate::extension::prelude::{BOOL_T, PRELUDE_ID, USIZE_CUSTOM_T, USIZE_T}; use crate::extension::{ - ExtensionId, ExtensionRegistry, SignatureError, TypeDefBound, PRELUDE, PRELUDE_REGISTRY, + ExtensionId, ExtensionRegistry, SignatureError, TypeDefBound, EMPTY_REG, PRELUDE, + PRELUDE_REGISTRY, }; use crate::std_extensions::collections::{EXTENSION, LIST_TYPENAME}; use crate::types::type_param::{TypeArg, TypeArgError, TypeParam}; @@ -125,7 +163,7 @@ pub(crate) mod test { extension_registry: &ExtensionRegistry, ) -> Result { let res = Self::new(params, body); - res.validate(extension_registry)?; + res.validate_varargs(extension_registry)?; Ok(res) } } @@ -332,4 +370,104 @@ pub(crate) mod test { )?; Ok(()) } + + const TP_ANY: TypeParam = TypeParam::Type { b: TypeBound::Any }; + #[test] + fn row_variables_bad_schema() { + // Mismatched TypeBound (Copyable vs Any) + let decl = TypeParam::List { + param: Box::new(TP_ANY), + }; + let e = PolyFuncType::new_validated( + [decl.clone()], + FunctionType::new( + vec![USIZE_T], + vec![Type::new_row_var_use(0, TypeBound::Copyable)], + ), + &PRELUDE_REGISTRY, + ) + .unwrap_err(); + assert_matches!(e, SignatureError::TypeVarDoesNotMatchDeclaration { actual, cached } => { + assert_eq!(actual, decl); + assert_eq!(cached, TypeParam::List {param: Box::new(TypeParam::Type {b: TypeBound::Copyable})}); + }); + // Declared as row variable, used as type variable + let e = PolyFuncType::new_validated( + [decl.clone()], + FunctionType::new_endo(vec![Type::new_var_use(0, TypeBound::Any)]), + &EMPTY_REG, + ) + .unwrap_err(); + assert_matches!(e, SignatureError::TypeVarDoesNotMatchDeclaration { actual, cached } => { + assert_eq!(actual, decl); + assert_eq!(cached, TP_ANY); + }); + } + + #[test] + fn row_variables() { + let rty = Type::new_row_var_use(0, TypeBound::Any); + let pf = PolyFuncType::new_validated( + [TypeParam::new_list(TP_ANY)], + FunctionType::new(vec![USIZE_T, rty.clone()], vec![Type::new_tuple(rty)]), + &PRELUDE_REGISTRY, + ) + .unwrap(); + + fn seq2() -> Vec { + vec![USIZE_T.into(), BOOL_T.into()] + } + pf.instantiate(&[TypeArg::Type { ty: USIZE_T }], &PRELUDE_REGISTRY) + .unwrap_err(); + pf.instantiate( + &[TypeArg::Sequence { + elems: vec![USIZE_T.into(), TypeArg::Sequence { elems: seq2() }], + }], + &PRELUDE_REGISTRY, + ) + .unwrap_err(); + + let t2 = pf + .instantiate(&[TypeArg::Sequence { elems: seq2() }], &PRELUDE_REGISTRY) + .unwrap(); + assert_eq!( + t2, + FunctionType::new( + vec![USIZE_T, USIZE_T, BOOL_T], + vec![Type::new_tuple(vec![USIZE_T, BOOL_T])] + ) + ); + } + + #[test] + fn row_variables_inner() { + let inner_fty = Type::new_function(FunctionType::new_endo(vec![Type::new_row_var_use( + 0, + TypeBound::Copyable, + )])); + let pf = PolyFuncType::new_validated( + [TypeParam::List { + param: Box::new(TypeParam::Type { + b: TypeBound::Copyable, + }), + }], + FunctionType::new(vec![USIZE_T, inner_fty.clone()], vec![inner_fty]), + &PRELUDE_REGISTRY, + ) + .unwrap(); + + let inner3 = Type::new_function(FunctionType::new_endo(vec![USIZE_T, BOOL_T, USIZE_T])); + let t3 = pf + .instantiate( + &[TypeArg::Sequence { + elems: vec![USIZE_T.into(), BOOL_T.into(), USIZE_T.into()], + }], + &PRELUDE_REGISTRY, + ) + .unwrap(); + assert_eq!( + t3, + FunctionType::new(vec![USIZE_T, inner3.clone()], vec![inner3]) + ); + } } diff --git a/hugr/src/types/serialize.rs b/hugr/src/types/serialize.rs index 4a263af14..093b7ac16 100644 --- a/hugr/src/types/serialize.rs +++ b/hugr/src/types/serialize.rs @@ -1,4 +1,4 @@ -use super::{FunctionType, SumType, Type, TypeArg, TypeBound, TypeEnum}; +use super::{FunctionType, FunctionTypeVarArgs, SumType, Type, TypeArg, TypeBound, TypeEnum}; use super::custom::CustomType; @@ -10,12 +10,13 @@ use crate::ops::AliasDecl; pub(super) enum SerSimpleType { Q, I, - G(Box), + G(Box), Sum(SumType), Array { inner: Box, len: u64 }, Opaque(CustomType), Alias(AliasDecl), V { i: usize, b: TypeBound }, + R { i: usize, b: TypeBound }, } impl From for SerSimpleType { @@ -33,6 +34,7 @@ impl From for SerSimpleType { TypeEnum::Alias(a) => SerSimpleType::Alias(a), TypeEnum::Function(sig) => SerSimpleType::G(sig), TypeEnum::Variable(i, b) => SerSimpleType::V { i, b }, + TypeEnum::RowVariable(i, b) => SerSimpleType::R { i, b }, TypeEnum::Sum(st) => SerSimpleType::Sum(st), } } @@ -51,6 +53,7 @@ impl From for Type { SerSimpleType::Opaque(o) => Type::new_extension(o), SerSimpleType::Alias(a) => Type::new_alias(a), SerSimpleType::V { i, b } => Type::new_var_use(i, b), + SerSimpleType::R { i, b } => Type::new_row_var_use(i, b), } } } diff --git a/hugr/src/types/signature.rs b/hugr/src/types/signature.rs index d7520c773..b4710cac2 100644 --- a/hugr/src/types/signature.rs +++ b/hugr/src/types/signature.rs @@ -5,7 +5,7 @@ use itertools::Either; use std::fmt::{self, Display, Write}; use super::type_param::TypeParam; -use super::{subst_row, Substitution, Type, TypeRow}; +use super::{Substitution, Type, TypeEnum, TypeRow}; use crate::extension::{ExtensionRegistry, ExtensionSet, SignatureError}; use crate::{Direction, IncomingPort, OutgoingPort, Port}; @@ -25,29 +25,98 @@ pub struct FunctionType { pub extension_reqs: ExtensionSet, } -impl FunctionType { - /// Builder method, add extension_reqs to an FunctionType - pub fn with_extension_delta(mut self, rs: impl Into) -> Self { - self.extension_reqs = self.extension_reqs.union(rs.into()); - self +#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct FunctionTypeVarArgs(FunctionType); + +impl FunctionTypeVarArgs { + pub fn any_rowvars(&self) -> bool { + self.rowvar().is_some() + } + + pub fn rowvar(&self) -> Option { + self.0.input.iter().chain(self.0.output.iter()).filter_map(|x| match x.as_type_enum() { + TypeEnum::RowVariable(i,_) => Some(*i), + _ => None + }).next() } - pub(crate) fn validate( + pub fn try_as_ref(&self) -> Result<&FunctionType,SignatureError> { + if let Some(idx) = self.rowvar() { + Err(SignatureError::RowTypeVarOutsideRow { idx }) + } else { + Ok(&self.0) + } + } + + pub(super) fn validate_varargs( &self, extension_registry: &ExtensionRegistry, var_decls: &[TypeParam], ) -> Result<(), SignatureError> { - self.input - .iter() - .chain(self.output.iter()) - .try_for_each(|t| t.validate(extension_registry, var_decls))?; - self.extension_reqs.validate(var_decls) + self.0.input.validate_var_len(extension_registry, var_decls)?; + self.0.output + .validate_var_len(extension_registry, var_decls)?; + self.0.extension_reqs.validate(var_decls) + } + + pub(crate) fn substitute(&self, tr: &Substitution) -> Self { + Self(FunctionType { + input: self.0.input.substitute(tr), + output: self.0.output.substitute(tr), + extension_reqs: self.0.extension_reqs.substitute(tr), + }) + } +} + +impl PartialEq for FunctionTypeVarArgs { + fn eq(&self, other: &FunctionType) -> bool { + &self.0 == other + } +} + +impl PartialEq for FunctionType { + fn eq(&self, other: &FunctionTypeVarArgs) -> bool { + self == &other.0 } +} + +impl Display for FunctionTypeVarArgs { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl TryFrom for FunctionType { + type Error = SignatureError; + fn try_from(value: FunctionTypeVarArgs) -> Result { + if let Some(idx) = value.rowvar() { + Err(SignatureError::RowTypeVarOutsideRow { idx }) + } else { + Ok(value.0) + } + } +} + +impl From for FunctionTypeVarArgs { + fn from(value: FunctionType) -> Self { + Self(value) + } +} + + +impl FunctionType { + /// Builder method, add extension_reqs to an FunctionType + pub fn with_extension_delta(mut self, rs: impl Into) -> Self { + self.extension_reqs = self.extension_reqs.union(rs.into()); + self + } + pub(crate) fn substitute(&self, tr: &Substitution) -> Self { + // TODO assert no row vars in result FunctionType { - input: subst_row(&self.input, tr), - output: subst_row(&self.output, tr), + input: self.input.substitute(tr), + output: self.output.substitute(tr), extension_reqs: self.extension_reqs.substitute(tr), } } diff --git a/hugr/src/types/type_param.rs b/hugr/src/types/type_param.rs index 3e57fcab7..843b1b5a1 100644 --- a/hugr/src/types/type_param.rs +++ b/hugr/src/types/type_param.rs @@ -69,9 +69,10 @@ pub enum TypeParam { /// The [CustomType] defining the parameter. ty: CustomType, }, - /// Argument is a [TypeArg::Sequence]. A list of indeterminate size containing parameters. + /// Argument is a [TypeArg::Sequence]. A list of indeterminate size containing + /// parameters all of the (same) specified element type. List { - /// The [TypeParam] contained in the list. + /// The [TypeParam] describing each element of the list. param: Box, }, /// Argument is a [TypeArg::Sequence]. A tuple of parameters. @@ -101,6 +102,13 @@ impl TypeParam { } } + /// Make a new `TypeParam::List` (an arbitrary-length homogenous list) + pub fn new_list(elem: impl Into) -> Self { + Self::List { + param: Box::new(elem.into()), + } + } + fn contains(&self, other: &TypeParam) -> bool { match (self, other) { (TypeParam::Type { b: b1 }, TypeParam::Type { b: b2 }) => b1.contains(*b2), @@ -162,8 +170,9 @@ pub enum TypeArg { #[allow(missing_docs)] es: ExtensionSet, }, - /// Variable (used in type schemes only), that is not a [TypeArg::Type] - /// or [TypeArg::Extensions] - see [TypeArg::new_var_use] + /// Variable (used in type schemes or inside polymorphic functions), + /// but not a [TypeArg::Type] (not even a row variable i.e. [TypeParam::List] of type) + /// nor [TypeArg::Extensions] - see [TypeArg::new_var_use] Variable { #[allow(missing_docs)] #[serde(flatten)] @@ -214,9 +223,16 @@ impl TypeArg { /// `bound` must match that with which the variable was declared. pub fn new_var_use(idx: usize, decl: TypeParam) -> Self { match decl { - TypeParam::Type { b } => TypeArg::Type { - ty: Type::new_var_use(idx, b), - }, + TypeParam::Type { b } => Type::new_var_use(idx, b).into(), + TypeParam::List { param: bx } if matches!(*bx, TypeParam::Type { .. }) => { + // There are two reasonable schemes for representing row variables: + // 1. TypeArg::Variable(idx, TypeParam::List(TypeParam::Type(typebound))) + // 2. TypeArg::Type(Type::new_row_var_use(idx, typebound)) + // Here we prefer the latter for canonicalization, although we cannot really + // prevent both if users construct the TypeArg variants directly (doing so will break Eq) + let TypeParam::Type { b } = *bx else { panic!() }; + Type::new_row_var_use(idx, b).into() + } TypeParam::Extensions => TypeArg::Extensions { es: ExtensionSet::type_var(idx), }, @@ -237,7 +253,8 @@ impl TypeArg { var_decls: &[TypeParam], ) -> Result<(), SignatureError> { match self { - TypeArg::Type { ty } => ty.validate(extension_registry, var_decls), + // Row variables are represented as 'TypeArg::Type's (see TypeArg::new_row_var_use) + TypeArg::Type { ty } => ty.validate_in_row(extension_registry, var_decls), TypeArg::BoundedNat { .. } => Ok(()), TypeArg::Opaque { arg: custarg } => { // We could also add a facility to Extension to validate that the constant *value* @@ -258,9 +275,17 @@ impl TypeArg { pub(crate) fn substitute(&self, t: &Substitution) -> Self { match self { - TypeArg::Type { ty } => TypeArg::Type { - ty: ty.substitute(t), - }, + TypeArg::Type { ty } => { + // A row variable standing for many types is represented as a single type + // TODO: this case can't happen until we start substituting across Hugrs + // (rather than just their types) - e.g. instantiating the *body* (not just type) + // of a FuncDefn, polymorphic over a row variable, with multiple types + let tys = ty.substitute(t).into_iter().map_into().collect::>(); + match as TryInto<[TypeArg; 1]>>::try_into(tys) { + Ok([ty]) => ty, + Err(elems) => TypeArg::Sequence { elems }, + } + } TypeArg::BoundedNat { .. } => self.clone(), // We do not allow variables as bounds on BoundedNat's TypeArg::Opaque { arg: CustomTypeArg { typ, .. }, @@ -323,13 +348,31 @@ pub fn check_type_arg(arg: &TypeArg, param: &TypeParam) -> Result<(), TypeArgErr _, ) if param.contains(cached_decl) => Ok(()), (TypeArg::Type { ty }, TypeParam::Type { b: bound }) - if bound.contains(ty.least_upper_bound()) => + if bound.contains(ty.least_upper_bound()) && ty.row_var_bound().is_none() => { Ok(()) } (TypeArg::Sequence { elems }, TypeParam::List { param }) => { - elems.iter().try_for_each(|arg| check_type_arg(arg, param)) + elems.iter().try_for_each(|arg| { + // Also allow elements that are RowVars if fitting into a List of Types + if let (TypeArg::Type { ty }, TypeParam::Type { b }) = (arg, &**param) { + if ty.row_var_bound().is_some_and(|arg_b| b.contains(arg_b)) { + return Ok(()); + } + } + check_type_arg(arg, param) + }) } + // Also allow a single "Type" to be used for a List *only* if the Type is a row variable + // (i.e., it's not really a Type, it's multiple Types) + (TypeArg::Type { ty }, TypeParam::List { param }) + if ty + .row_var_bound() + .is_some_and(|b| param.contains(&b.into())) => + { + Ok(()) + } + (TypeArg::Sequence { elems: items }, TypeParam::Tuple { params: types }) => { if items.len() != types.len() { Err(TypeArgError::WrongNumberTuple(items.len(), types.len())) @@ -396,3 +439,85 @@ pub enum TypeArgError { #[error("Invalid value of type argument")] InvalidValue(TypeArg), } + +#[cfg(test)] +mod test { + use itertools::Itertools; + + use super::{check_type_arg, TypeArg, TypeParam}; + use crate::extension::prelude::USIZE_T; + use crate::types::{type_param::TypeArgError, Type, TypeBound}; + + #[test] + fn type_arg_fits_param() { + fn check(arg: impl Into, parm: &TypeParam) -> Result<(), TypeArgError> { + check_type_arg(&arg.into(), parm) + } + fn check_seq>( + args: &[T], + parm: &TypeParam, + ) -> Result<(), TypeArgError> { + let arg = args.iter().cloned().map_into().collect_vec().into(); + check_type_arg(&arg, parm) + } + // Simple cases: a TypeArg::Type is a TypeParam::Type but singleton sequences are lists + check(USIZE_T, &TypeBound::Eq.into()).unwrap(); + let p = TypeParam::new_list(TypeBound::Eq); + check(USIZE_T, &p).unwrap_err(); + check_seq(&[USIZE_T], &TypeBound::Any.into()).unwrap_err(); + + // Into a list of type, we can fit a single row var + check(Type::new_row_var_use(0, TypeBound::Eq), &p).unwrap(); + // or a list of (types or row vars) + check(vec![], &p).unwrap(); + check_seq(&[Type::new_row_var_use(0, TypeBound::Eq)], &p).unwrap(); + check_seq( + &[ + Type::new_row_var_use(1, TypeBound::Any), + USIZE_T, + Type::new_row_var_use(0, TypeBound::Eq), + ], + &TypeParam::new_list(TypeBound::Any), + ) + .unwrap(); + // Next one fails because a list of Eq is required + check_seq( + &[ + Type::new_row_var_use(1, TypeBound::Any), + USIZE_T, + Type::new_row_var_use(0, TypeBound::Eq), + ], + &p, + ) + .unwrap_err(); + // seq of seq of types is not allowed + check(vec![USIZE_T.into(), vec![USIZE_T.into()].into()], &p).unwrap_err(); + + // Similar for nats (but no equivalent of fancy row vars) + check(5, &TypeParam::max_nat()).unwrap(); + check_seq(&[5], &TypeParam::max_nat()).unwrap_err(); + let list_of_nat = TypeParam::new_list(TypeParam::max_nat()); + check(5, &list_of_nat).unwrap_err(); + check_seq(&[5], &list_of_nat).unwrap(); + check(TypeArg::new_var_use(0, list_of_nat.clone()), &list_of_nat).unwrap(); + // But no equivalent of row vars - can't append a nat onto a list-in-a-var: + check( + vec![5.into(), TypeArg::new_var_use(0, list_of_nat.clone())], + &list_of_nat, + ) + .unwrap_err(); + + // TypeParam::Tuples require a TypeArg::Seq of the same number of elems + let usize_and_ty = TypeParam::Tuple { + params: vec![TypeParam::max_nat(), TypeBound::Eq.into()], + }; + check(vec![5.into(), USIZE_T.into()], &usize_and_ty).unwrap(); + check(vec![USIZE_T.into(), 5.into()], &usize_and_ty).unwrap_err(); // Wrong way around + let two_types = TypeParam::Tuple { + params: vec![TypeBound::Any.into(), TypeBound::Any.into()], + }; + check(TypeArg::new_var_use(0, two_types.clone()), &two_types).unwrap(); + // not a Row Var which could have any number of elems + check(TypeArg::new_var_use(0, p), &two_types).unwrap_err(); + } +} diff --git a/hugr/src/types/type_row.rs b/hugr/src/types/type_row.rs index 1f0d379cb..bcec336b5 100644 --- a/hugr/src/types/type_row.rs +++ b/hugr/src/types/type_row.rs @@ -7,9 +7,12 @@ use std::{ ops::{Deref, DerefMut}, }; -use super::Type; -use crate::utils::display_list; +use super::{type_param::TypeParam, Substitution, Type}; use crate::PortIndex; +use crate::{ + extension::{ExtensionRegistry, SignatureError}, + utils::display_list, +}; use delegate::delegate; use itertools::Itertools; @@ -40,6 +43,8 @@ impl TypeRow { #[inline(always)] /// Returns the port type given an offset. Returns `None` if the offset is out of bounds. + // Note/TODO: it might be good to disable this if we are indexing over (past) a Row Variable, + // as substitution could change where in the row the offset refers. pub fn get(&self, offset: impl PortIndex) -> Option<&Type> { self.types.get(offset.index()) } @@ -78,6 +83,28 @@ impl TypeRow { pub fn is_empty(&self) -> bool ; } } + + /// Applies a substitution to the row. Note this may change the length + /// if-and-only-if the row contains any [RowVariable]s. + /// + /// [RowVariable]: [crate::types::TypeEnum::RowVariable] + pub(super) fn substitute(&self, tr: &Substitution) -> TypeRow { + let res = self + .iter() + .flat_map(|ty| ty.substitute(tr)) + .collect::>() + .into(); + res + } + + pub(super) fn validate_var_len( + &self, + exts: &ExtensionRegistry, + var_decls: &[TypeParam], + ) -> Result<(), SignatureError> { + self.iter() + .try_for_each(|t| t.validate_in_row(exts, var_decls)) + } } impl Default for TypeRow { diff --git a/specification/schema/hugr_schema_strict_v1.json b/specification/schema/hugr_schema_strict_v1.json index 056ceeaf2..bcb59e4c6 100644 --- a/specification/schema/hugr_schema_strict_v1.json +++ b/specification/schema/hugr_schema_strict_v1.json @@ -1652,6 +1652,34 @@ "title": "Qubit", "type": "object" }, + "RowVar": { + "additionalProperties": false, + "description": "A variable standing for a row of some (unknown) number of types.\nMay occur only within a row; not a node input/output.", + "properties": { + "t": { + "const": "R", + "default": "R", + "enum": [ + "R" + ], + "title": "T", + "type": "string" + }, + "i": { + "title": "I", + "type": "integer" + }, + "b": { + "$ref": "#/$defs/TypeBound" + } + }, + "required": [ + "i", + "b" + ], + "title": "RowVar", + "type": "object" + }, "SequenceArg": { "additionalProperties": false, "properties": { @@ -1913,6 +1941,7 @@ "I": "#/$defs/USize", "Opaque": "#/$defs/Opaque", "Q": "#/$defs/Qubit", + "R": "#/$defs/RowVar", "Sum": "#/$defs/SumType", "V": "#/$defs/Variable" }, @@ -1925,6 +1954,9 @@ { "$ref": "#/$defs/Variable" }, + { + "$ref": "#/$defs/RowVar" + }, { "$ref": "#/$defs/USize" }, diff --git a/specification/schema/hugr_schema_v1.json b/specification/schema/hugr_schema_v1.json index 27d58db73..188e4121f 100644 --- a/specification/schema/hugr_schema_v1.json +++ b/specification/schema/hugr_schema_v1.json @@ -1652,6 +1652,34 @@ "title": "Qubit", "type": "object" }, + "RowVar": { + "additionalProperties": true, + "description": "A variable standing for a row of some (unknown) number of types.\nMay occur only within a row; not a node input/output.", + "properties": { + "t": { + "const": "R", + "default": "R", + "enum": [ + "R" + ], + "title": "T", + "type": "string" + }, + "i": { + "title": "I", + "type": "integer" + }, + "b": { + "$ref": "#/$defs/TypeBound" + } + }, + "required": [ + "i", + "b" + ], + "title": "RowVar", + "type": "object" + }, "SequenceArg": { "additionalProperties": true, "properties": { @@ -1913,6 +1941,7 @@ "I": "#/$defs/USize", "Opaque": "#/$defs/Opaque", "Q": "#/$defs/Qubit", + "R": "#/$defs/RowVar", "Sum": "#/$defs/SumType", "V": "#/$defs/Variable" }, @@ -1925,6 +1954,9 @@ { "$ref": "#/$defs/Variable" }, + { + "$ref": "#/$defs/RowVar" + }, { "$ref": "#/$defs/USize" }, diff --git a/specification/schema/testing_hugr_schema_strict_v1.json b/specification/schema/testing_hugr_schema_strict_v1.json index 39687363c..9c272a944 100644 --- a/specification/schema/testing_hugr_schema_strict_v1.json +++ b/specification/schema/testing_hugr_schema_strict_v1.json @@ -1729,6 +1729,34 @@ "title": "Qubit", "type": "object" }, + "RowVar": { + "additionalProperties": false, + "description": "A variable standing for a row of some (unknown) number of types.\nMay occur only within a row; not a node input/output.", + "properties": { + "t": { + "const": "R", + "default": "R", + "enum": [ + "R" + ], + "title": "T", + "type": "string" + }, + "i": { + "title": "I", + "type": "integer" + }, + "b": { + "$ref": "#/$defs/TypeBound" + } + }, + "required": [ + "i", + "b" + ], + "title": "RowVar", + "type": "object" + }, "SequenceArg": { "additionalProperties": false, "properties": { @@ -1990,6 +2018,7 @@ "I": "#/$defs/USize", "Opaque": "#/$defs/Opaque", "Q": "#/$defs/Qubit", + "R": "#/$defs/RowVar", "Sum": "#/$defs/SumType", "V": "#/$defs/Variable" }, @@ -2002,6 +2031,9 @@ { "$ref": "#/$defs/Variable" }, + { + "$ref": "#/$defs/RowVar" + }, { "$ref": "#/$defs/USize" }, diff --git a/specification/schema/testing_hugr_schema_v1.json b/specification/schema/testing_hugr_schema_v1.json index 7b87683af..01c3d6bb8 100644 --- a/specification/schema/testing_hugr_schema_v1.json +++ b/specification/schema/testing_hugr_schema_v1.json @@ -1729,6 +1729,34 @@ "title": "Qubit", "type": "object" }, + "RowVar": { + "additionalProperties": true, + "description": "A variable standing for a row of some (unknown) number of types.\nMay occur only within a row; not a node input/output.", + "properties": { + "t": { + "const": "R", + "default": "R", + "enum": [ + "R" + ], + "title": "T", + "type": "string" + }, + "i": { + "title": "I", + "type": "integer" + }, + "b": { + "$ref": "#/$defs/TypeBound" + } + }, + "required": [ + "i", + "b" + ], + "title": "RowVar", + "type": "object" + }, "SequenceArg": { "additionalProperties": true, "properties": { @@ -1990,6 +2018,7 @@ "I": "#/$defs/USize", "Opaque": "#/$defs/Opaque", "Q": "#/$defs/Qubit", + "R": "#/$defs/RowVar", "Sum": "#/$defs/SumType", "V": "#/$defs/Variable" }, @@ -2002,6 +2031,9 @@ { "$ref": "#/$defs/Variable" }, + { + "$ref": "#/$defs/RowVar" + }, { "$ref": "#/$defs/USize" },