Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Allow "Row Variables" declared as List<Type> #804

Merged
merged 70 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
7b242ac
Refactor: break out fn valid_row for validating a TypeRow
acl-cqc Jan 10, 2024
ba05010
Type::{validate,substitute}_in_row, {Substitution,SubstValues}::apply…
acl-cqc Jan 10, 2024
59c5854
[test] types.rs::construct - also validate
acl-cqc Jan 12, 2024
72b4fb2
[tests] poly_func.rs: test row variables
acl-cqc Jan 12, 2024
2cf1e7f
[test] validate Hugr passing around Function of unknown arity
acl-cqc Jan 12, 2024
8899806
[tests]Validation prevents row variables being used in node signatures
acl-cqc Jan 12, 2024
e1f1825
Simplify default apply_typevar_in_row
acl-cqc Jan 12, 2024
1074093
Separate TypeEnum::RowVariable
acl-cqc Jan 12, 2024
4291848
Merge remote-tracking branch 'origin/main' into feat/row_typevar
acl-cqc Apr 15, 2024
98c75a2
Doc updates - no DeBruijn, ref OpDef vs FuncDefn
acl-cqc Apr 15, 2024
b918bee
Remove unused test setup changes
acl-cqc Apr 15, 2024
a096f1a
Merge remote-tracking branch 'origin/main' into feat/row_typevar
acl-cqc May 9, 2024
4b0e23f
Combine Type::substitute(_in_row), doc, add TODOs
acl-cqc May 9, 2024
a0aba20
FunctionType/PolyFuncType validate_varargs (fixed-len not reqd)
acl-cqc May 9, 2024
bd0689f
Type::validate(=>_1type), fix TypeParam to call validate_in_row
acl-cqc May 10, 2024
72f4b98
{subst,valid}_row => TypeRow::{substitute,validate_var_len}
acl-cqc May 10, 2024
d9653aa
Normalize representation of row variable as TypeArg; extend check_typ…
acl-cqc May 10, 2024
15bf177
Test bad schema, test can't fit row variable into type hole
acl-cqc May 10, 2024
bc11c68
Extend validation tests using eval+parallel
acl-cqc May 10, 2024
f39373b
And extend no_outer_row_variables to include unconnected out'port's o…
acl-cqc May 10, 2024
bd39303
comments re. canonicalization, tests
acl-cqc May 10, 2024
ed64d12
clippy
acl-cqc May 11, 2024
d02c9c0
single_type_seq -> seq1ty
acl-cqc May 11, 2024
50b9c85
Merge remote-tracking branch 'origin/main' into feat/row_typevar
acl-cqc May 11, 2024
742ab55
Combine helpers giving extension_with_eval_parallel
acl-cqc May 12, 2024
ce72762
Add RowVar to serialization/tys.py
acl-cqc May 12, 2024
c556a8a
Regenerate schema
acl-cqc May 12, 2024
7214cbc
Really regenerate schema (after poetry install)
acl-cqc May 12, 2024
ce28154
Use Vec<TypeArg> .into() -> TypeArg
acl-cqc May 14, 2024
0e4b232
Add TypeParam::new_list
acl-cqc May 14, 2024
5b39b9c
rowparam() -> rowp.clone()
acl-cqc May 14, 2024
9ae2255
new_row_var => new_row_var_use, some cleanups using .into()
acl-cqc May 14, 2024
412ec08
squash! Add TypeParam::new_list
acl-cqc May 14, 2024
34ed4c9
Fix apply_row_var...TODO cover this
acl-cqc May 14, 2024
85d1969
check_type_arg_rv: improve comment
acl-cqc May 14, 2024
3cf333d
Remove rowvar_in_list using TypeParam::contains
acl-cqc May 14, 2024
2929135
Add Type::row_var_bound
acl-cqc May 14, 2024
0afdaf9
Remove check_type_arg_rv
acl-cqc May 14, 2024
5a8bf88
Use Type .into() -> TypeArg more
acl-cqc May 14, 2024
2e53529
Extend comment that TypeArg::Variable's are not row-vars
acl-cqc May 14, 2024
ed09ae6
type_row.rs: comment ...Variable]*s*
acl-cqc May 14, 2024
8c93c9d
Add check_type_arg tests as rstest cases
acl-cqc May 14, 2024
8811e1a
More tests! (giving up on rstest)
acl-cqc May 14, 2024
d728714
Add a couple of PolyFuncType-serialization tests
acl-cqc May 14, 2024
9af9d01
In validation, panic upon malformed TypeArgVariable
acl-cqc May 15, 2024
ef86cd7
Correct comment in TypeArg::new_var_use
acl-cqc May 15, 2024
0ff4a05
Add anti-row-var checks in TypeRow::get/get_mut
acl-cqc May 15, 2024
b9fc1cf
Make TypeRow::get(_mut) pub only to crate::types
acl-cqc May 15, 2024
bcffd57
Revert "Make TypeRow::get(_mut) pub only to crate::types"
acl-cqc May 15, 2024
98cf6af
row_var_bound -> is_row_var
acl-cqc May 15, 2024
4a41f96
Clarify bound in Type::new_(,row_)var_use
acl-cqc May 15, 2024
5359441
Thank you Doug!
acl-cqc May 15, 2024
fdb19ae
Comments (Function values, "must match" => "must be exactly")
acl-cqc May 15, 2024
d949cc9
Another TypeParam::new_list
acl-cqc May 15, 2024
571d025
Test: p -> seq_param
acl-cqc May 15, 2024
a353026
Test: rowvar = Type::new_row_var_use
acl-cqc May 15, 2024
93536b3
Capture TypeArg::Type substituting-to-multiple in a unit test, fix
acl-cqc May 15, 2024
d339e2c
Another test with a list-of-list-of-type
acl-cqc May 20, 2024
71b3ae2
Use vec![].into() somewhat selectively
acl-cqc May 20, 2024
b77e3c8
Merge 'origin/main' into feat/row_typevar. Proptest failing...
acl-cqc May 20, 2024
91317ae
Fix proptest by local reject
acl-cqc May 20, 2024
3cbccc7
Temp remove TypeRow checks
acl-cqc May 20, 2024
a43e0a1
Merge remote-tracking branch 'origin/main' into feat/row_typevar
acl-cqc May 20, 2024
e0c7162
Add non-debug asserts in FunctionType
acl-cqc May 20, 2024
7ebb596
Combine Type::validate_1type / validate_in_row to validate(bool,...)
acl-cqc May 20, 2024
bc2d358
validate_varargs => validate_var_len
acl-cqc May 20, 2024
5b63d0c
RowVarOutsideRow => RowVarWhereTypeExpected
acl-cqc May 21, 2024
2a28052
check_no_rowvars - ignore index, check whole row - breaks no_outer_ro…
acl-cqc May 21, 2024
c559abc
FunctionType::find_row_var, check in builder and validation (before p…
acl-cqc May 21, 2024
98c1fc9
Tests (rewrite w/out builder; add builder test, using now-pub-crate e…
acl-cqc May 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,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 row
#[error("Row variable {idx} was found outside of a type row")]
RowTypeVarOutsideRow { idx: usize },
/// The type stored in a [LeafOp::TypeApply] is not what we compute from the
/// [ExtensionRegistry].
///
Expand Down
48 changes: 48 additions & 0 deletions src/hugr/validate/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use super::*;
use crate::builder::test::closed_dfg_root_hugr;
use crate::builder::{
BuildError, Container, Dataflow, DataflowHugr, DataflowSubContainer, FunctionBuilder,
HugrBuilder,
};
use crate::extension::prelude::{BOOL_T, PRELUDE, USIZE_T};
use crate::extension::{
Expand Down Expand Up @@ -559,6 +560,53 @@ fn no_polymorphic_consts() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}

#[test]
fn inner_row_variables() -> Result<(), Box<dyn std::error::Error>> {
let tv = Type::new_row_var(0, TypeBound::Any);
let inner_ft = Type::new_function(FunctionType::new_endo(vec![tv]));
let mut fb = FunctionBuilder::new(
"id",
PolyFuncType::new(
[TypeParam::List {
param: Box::new(TypeParam::Type { b: TypeBound::Any }),
}],
FunctionType::new_endo(vec![inner_ft.clone()]),
),
)?;
// All the wires here are carrying higher-order Function values
// (the Functions themselves have variable arity, but that's fine as we are not calling them)
let id = fb.add_dataflow_op(LeafOp::Noop { ty: inner_ft }, fb.input_wires())?;
fb.finish_hugr_with_outputs(id.outputs(), &EMPTY_REG)?;
Ok(())
}

#[test]
fn no_outer_row_variables() -> Result<(), Box<dyn std::error::Error>> {
let tv = Type::new_row_var(0, TypeBound::Any);
let fb = FunctionBuilder::new(
"impossible_id_of_unknown_arity",
PolyFuncType::new(
[TypeParam::List {
param: Box::new(TypeParam::Type { b: TypeBound::Any }),
}],
FunctionType::new_endo(vec![tv]),
),
)?;
// Input and Output nodes have a Port whose type is List(Type(Any))
// - so this is illegal because the wire between them is not a type:
let err = fb
.clone()
.finish_hugr_with_outputs(fb.input_wires(), &EMPTY_REG)
.unwrap_err();
assert_matches!(
err,
BuildError::InvalidHUGR(ValidationError::SignatureError { .. })
);
// Also try leaving no inputs/outputs - so this should be illegal because the ports are unconnected
fb.finish_hugr(&EMPTY_REG).unwrap_err();
Ok(())
}

#[cfg(feature = "extension_inference")]
mod extension_tests {
use super::*;
Expand Down
85 changes: 77 additions & 8 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,14 @@ pub enum TypeEnum {
#[allow(missing_docs)]
#[display(fmt = "Function({})", "_0")]
Function(Box<PolyFuncType>),
// DeBruijn index, and cache of TypeBound (checked in validation)
/// DeBruijn index, and cache of TypeBound of [TypeParam::Type] (checked in validation)
#[allow(missing_docs)]
#[display(fmt = "Variable({})", _0)]
Variable(usize, TypeBound),
/// DeBruijn 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 = "Tuple({})", "_0")]
Tuple(TypeRow),
Expand All @@ -171,7 +175,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 { row }) => {
least_upper_bound(row.iter().map(Type::least_upper_bound))
Expand Down Expand Up @@ -268,10 +272,17 @@ impl Type {
/// New use (occurrence) of the type variable with specified DeBruijn index.
/// For use in type schemes only: `bound` must match that with which the
/// variable was declared (i.e. as a [TypeParam::Type]`(bound)`).
pub fn new_var_use(idx: usize, bound: TypeBound) -> Self {
pub const fn new_var_use(idx: usize, bound: TypeBound) -> Self {
Self(TypeEnum::Variable(idx, bound), bound)
}

/// New use (occurrence) of the row variable with specified DeBruijn index.
/// For use in type schemes only: `bound` must match that with which the
/// variable was declared (i.e. as a [TypeParam::List]`(`[TypeParam::Type]`(bound))`).
pub const fn new_row_var(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 {
Expand Down Expand Up @@ -305,27 +316,55 @@ 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::Tuple(row) | TypeEnum::Sum(SumType::General { row }) => row
.iter()
.try_for_each(|t| t.validate(extension_registry, var_decls)),
TypeEnum::Tuple(row) | TypeEnum::Sum(SumType::General { row }) => {
valid_row(row, 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),
TypeEnum::Variable(idx, bound) => check_typevar_decl(var_decls, *idx, &(*bound).into()),
TypeEnum::RowVariable(idx, _) => {
Err(SignatureError::RowTypeVarOutsideRow { idx: *idx })
}
}
}

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(extension_registry, var_decls)
}
}

pub(crate) fn substitute(&self, t: &impl Substitution) -> Self {
match &self.0 {
TypeEnum::Alias(_) | TypeEnum::Sum(SumType::Unit { .. }) => self.clone(),
TypeEnum::Variable(idx, bound) => t.apply_typevar(*idx, *bound),
TypeEnum::RowVariable(_, _) => {
panic!("Row Variable found outside of row - should have been detected in validate")
}
TypeEnum::Extension(cty) => Type::new_extension(cty.substitute(t)),
TypeEnum::Function(bf) => Type::new_function(bf.substitute(t)),
TypeEnum::Tuple(elems) => Type::new_tuple(subst_row(elems, t)),
TypeEnum::Sum(SumType::General { row }) => Type::new_sum(subst_row(row, t)),
}
}

fn substitute_in_row(&self, t: &impl Substitution) -> Vec<Self> {
match &self.0 {
TypeEnum::RowVariable(idx, bound) => t.apply_rowvar(*idx, *bound),
_ => vec![self.substitute(t)],
}
}
}

/// A function that replaces type variables with values.
Expand All @@ -344,13 +383,26 @@ pub(crate) trait Substitution {
/// Apply to a variable whose kind is any given [TypeParam]
fn apply_var(&self, idx: usize, decl: &TypeParam) -> TypeArg;

fn apply_rowvar(&self, idx: usize, bound: TypeBound) -> Vec<Type> {
vec![self.apply_typevar(idx, bound)]
}

fn extension_registry(&self) -> &ExtensionRegistry;
}

fn valid_row(
row: &TypeRow,
exts: &ExtensionRegistry,
var_decls: &[TypeParam],
) -> Result<(), SignatureError> {
row.iter()
.try_for_each(|t| t.validate_in_row(exts, var_decls))
}

fn subst_row(row: &TypeRow, tr: &impl Substitution) -> TypeRow {
let res = row
.iter()
.map(|ty| ty.substitute(tr))
.flat_map(|ty| ty.substitute_in_row(tr))
.collect::<Vec<_>>()
.into();
res
Expand Down Expand Up @@ -399,10 +451,16 @@ pub(crate) mod test {
pub(crate) use poly_func::test::nested_func;

use super::*;
use crate::extension::PRELUDE;
use crate::{const_extension_ids, Extension};
use crate::{extension::prelude::USIZE_T, ops::AliasDecl};

use crate::types::TypeBound;

const_extension_ids! {
const MY_EXT: ExtensionId = "my_extension";
}

#[test]
fn construct() {
let t: Type = Type::new_tuple(vec![
Expand All @@ -411,7 +469,7 @@ pub(crate) mod test {
Type::new_extension(CustomType::new(
"my_custom",
[],
"my_extension".try_into().unwrap(),
MY_EXT,
TypeBound::Copyable,
)),
Type::new_alias(AliasDecl::new("my_alias", TypeBound::Eq)),
Expand All @@ -421,6 +479,17 @@ pub(crate) mod test {
"Tuple([usize([]), Function(forall . [[]][]), my_custom([]), Alias(my_alias)])"
.to_string()
);

let mut ext = Extension::new(MY_EXT);
ext.add_type(
"my_custom".into(),
vec![],
"".into(),
TypeBound::Copyable.into(),
)
.unwrap();
let reg = ExtensionRegistry::try_new([PRELUDE.to_owned(), ext]).unwrap();
t.validate(&reg, &[]).unwrap()
acl-cqc marked this conversation as resolved.
Show resolved Hide resolved
}

#[test]
Expand Down
112 changes: 110 additions & 2 deletions src/types/poly_func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
use itertools::Itertools;

use super::type_param::{check_type_args, TypeArg, TypeParam};
use super::{FunctionType, Substitution};
use super::{FunctionType, Substitution, Type, TypeBound};

/// A polymorphic function type, e.g. of a [Graph], or perhaps an [OpDef].
/// (Nodes/operations in the Hugr are not polymorphic.)
Expand Down Expand Up @@ -160,6 +160,27 @@ impl<'a> Substitution for SubstValues<'a> {
arg.clone()
}

fn apply_rowvar(&self, idx: usize, bound: TypeBound) -> Vec<Type> {
let arg = self
.0
.get(idx)
.expect("Undeclared type variable - call validate() ?");
match arg {
TypeArg::Sequence { elems } => elems
.iter()
.map(|ta| match ta {
TypeArg::Type { ty } => ty.clone(),
_ => panic!("Not a list of types - did validate() ?"),
})
.collect(),
TypeArg::Type { ty } => {
debug_assert_eq!(check_type_arg(arg, &TypeParam::Type { b: bound }), Ok(()));
vec![ty.clone()]
}
_ => panic!("Not a type or list of types - did validate() ?"),
}
}

fn extension_registry(&self) -> &ExtensionRegistry {
self.1
}
Expand Down Expand Up @@ -221,7 +242,7 @@ pub(crate) mod test {
use lazy_static::lazy_static;
use smol_str::SmolStr;

use crate::extension::prelude::{array_type, PRELUDE_ID, USIZE_CUSTOM_T, USIZE_T};
use crate::extension::prelude::{array_type, BOOL_T, PRELUDE_ID, USIZE_CUSTOM_T, USIZE_T};
use crate::extension::{
ExtensionId, ExtensionRegistry, SignatureError, TypeDefBound, PRELUDE, PRELUDE_REGISTRY,
};
Expand Down Expand Up @@ -618,4 +639,91 @@ pub(crate) mod test {
)
)
}

#[test]
fn row_variables() {
const TP: TypeParam = TypeParam::Type { b: TypeBound::Any };
// Mismatched TypeBound (Copyable vs Any)
PolyFuncType::new_validated(
[TypeParam::List {
param: Box::new(TypeParam::Type {
b: TypeBound::Copyable,
}),
}],
FunctionType::new(
vec![USIZE_T, Type::new_var_use(0, TypeBound::Any)],
vec![Type::new_sum(vec![Type::new_row_var(0, TypeBound::Any)])],
),
&PRELUDE_REGISTRY,
)
.unwrap_err();

let pf = PolyFuncType::new_validated(
[TypeParam::List {
param: Box::new(TP),
}],
FunctionType::new(
vec![USIZE_T, Type::new_row_var(0, TypeBound::Any)],
vec![Type::new_sum(vec![Type::new_row_var(0, TypeBound::Any)])],
),
&PRELUDE_REGISTRY,
)
.unwrap();

fn seq2() -> Vec<TypeArg> {
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_sum(vec![USIZE_T, BOOL_T])]
)
);
}

#[test]
fn row_variables_inner() {
let inner_fty = Type::new_function(FunctionType::new_endo(vec![Type::new_row_var(
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])
);
}
}
Loading