Skip to content

Commit

Permalink
feat!: remove the Eq type bound. (#1364)
Browse files Browse the repository at this point in the history
Closes #1351 

BREAKING CHANGE: Eq type bound removed. References to `Eq` in serialized
HUGRs will be treated as `Copyable`.

---------

Co-authored-by: Agustín Borgna <[email protected]>
Co-authored-by: Douglas Wilson <[email protected]>
  • Loading branch information
3 people authored Jul 26, 2024
1 parent 657cbb0 commit 1218d21
Show file tree
Hide file tree
Showing 20 changed files with 65 additions and 71 deletions.
2 changes: 1 addition & 1 deletion hugr-core/examples/extension/declarative.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ extensions:
# Parameters are not currently supported.
name: CopyableType
description: A simple type with no parameters
# Types may have a "Eq", "Copyable", or "Any" bound.
# Types may have a "Copyable", or "Any" bound.
# This field is optional and defaults to "Any".
bound: Copyable
operations:
Expand Down
3 changes: 0 additions & 3 deletions hugr-core/src/extension/declarative/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,6 @@ impl TypeDeclaration {
Debug, Copy, Clone, Serialize, Deserialize, Hash, PartialEq, Eq, Default, derive_more::Display,
)]
enum TypeDefBoundDeclaration {
/// The equality operation is valid on this type.
Eq,
/// The type can be copied in the program.
Copyable,
/// No bound on the type.
Expand All @@ -91,7 +89,6 @@ enum TypeDefBoundDeclaration {
impl From<TypeDefBoundDeclaration> for TypeDefBound {
fn from(bound: TypeDefBoundDeclaration) -> Self {
match bound {
TypeDefBoundDeclaration::Eq => Self::Explicit(TypeBound::Eq),
TypeDefBoundDeclaration::Copyable => Self::Explicit(TypeBound::Copyable),
TypeDefBoundDeclaration::Any => Self::Explicit(TypeBound::Any),
}
Expand Down
12 changes: 6 additions & 6 deletions hugr-core/src/extension/op_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ pub(super) mod test {
assert_eq!(def.validate_args(&args, &PRELUDE_REGISTRY, &[]), Ok(()));

// Second arg may be a variable (substitutable)
let tyvar = Type::new_var_use(0, TypeBound::Eq);
let tyvar = Type::new_var_use(0, TypeBound::Copyable);
let tyvars: Vec<Type> = vec![tyvar.clone(); 3];
let args = [TypeArg::BoundedNat { n: 3 }, tyvar.clone().into()];
assert_eq!(
Expand All @@ -684,15 +684,15 @@ pub(super) mod test {
.with_extension_delta(EXT_ID)
)
);
def.validate_args(&args, &PRELUDE_REGISTRY, &[TypeBound::Eq.into()])
def.validate_args(&args, &PRELUDE_REGISTRY, &[TypeBound::Copyable.into()])
.unwrap();

// quick sanity check that we are validating the args - note changed bound:
assert_eq!(
def.validate_args(&args, &PRELUDE_REGISTRY, &[TypeBound::Any.into()]),
Err(SignatureError::TypeVarDoesNotMatchDeclaration {
actual: TypeBound::Any.into(),
cached: TypeBound::Eq.into()
cached: TypeBound::Copyable.into()
})
);

Expand Down Expand Up @@ -729,16 +729,16 @@ pub(super) mod test {
Signature::new_endo(vec![Type::new_var_use(0, TypeBound::Any)]),
),
)?;
let tv = Type::new_var_use(1, TypeBound::Eq);
let tv = Type::new_var_use(1, TypeBound::Copyable);
let args = [TypeArg::Type { ty: tv.clone() }];
let decls = [TypeParam::Extensions, TypeBound::Eq.into()];
let decls = [TypeParam::Extensions, TypeBound::Copyable.into()];
def.validate_args(&args, &EMPTY_REG, &decls).unwrap();
assert_eq!(
def.compute_signature(&args, &EMPTY_REG),
Ok(Signature::new_endo(tv).with_extension_delta(EXT_ID))
);
// But not with an external row variable
let arg: TypeArg = TypeRV::new_row_var_use(0, TypeBound::Eq).into();
let arg: TypeArg = TypeRV::new_row_var_use(0, TypeBound::Copyable).into();
assert_eq!(
def.compute_signature(&[arg.clone()], &EMPTY_REG),
Err(SignatureError::TypeArgMismatch(
Expand Down
17 changes: 10 additions & 7 deletions hugr-core/src/extension/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,14 @@ lazy_static! {
TypeName::new_inline("usize"),
vec![],
"usize".into(),
TypeDefBound::Explicit(crate::types::TypeBound::Eq),
TypeDefBound::Explicit(crate::types::TypeBound::Copyable),
)
.unwrap();
prelude.add_type(
STRING_TYPE_NAME,
vec![],
"string".into(),
TypeDefBound::Explicit(crate::types::TypeBound::Eq),
TypeDefBound::Explicit(crate::types::TypeBound::Copyable),
)
.unwrap();
prelude.add_op(
Expand Down Expand Up @@ -137,7 +137,7 @@ lazy_static! {
ERROR_TYPE_NAME,
vec![],
"Simple opaque error type.".into(),
TypeDefBound::Explicit(TypeBound::Eq),
TypeDefBound::Explicit(TypeBound::Copyable),
)
.unwrap();
prelude
Expand All @@ -158,8 +158,11 @@ lazy_static! {

}

pub(crate) const USIZE_CUSTOM_T: CustomType =
CustomType::new_simple(TypeName::new_inline("usize"), PRELUDE_ID, TypeBound::Eq);
pub(crate) const USIZE_CUSTOM_T: CustomType = CustomType::new_simple(
TypeName::new_inline("usize"),
PRELUDE_ID,
TypeBound::Copyable,
);

pub(crate) const QB_CUSTOM_T: CustomType =
CustomType::new_simple(TypeName::new_inline("qubit"), PRELUDE_ID, TypeBound::Any);
Expand Down Expand Up @@ -214,7 +217,7 @@ pub const STRING_TYPE_NAME: TypeName = TypeName::new_inline("string");

/// Custom type for strings.
pub const STRING_CUSTOM_TYPE: CustomType =
CustomType::new_simple(STRING_TYPE_NAME, PRELUDE_ID, TypeBound::Eq);
CustomType::new_simple(STRING_TYPE_NAME, PRELUDE_ID, TypeBound::Copyable);

/// String type.
pub const STRING_TYPE: Type = Type::new_extension(STRING_CUSTOM_TYPE);
Expand Down Expand Up @@ -259,7 +262,7 @@ pub const PRINT_OP_ID: OpName = OpName::new_inline("print");

/// The custom type for Errors.
pub const ERROR_CUSTOM_TYPE: CustomType =
CustomType::new_simple(ERROR_TYPE_NAME, PRELUDE_ID, TypeBound::Eq);
CustomType::new_simple(ERROR_TYPE_NAME, PRELUDE_ID, TypeBound::Copyable);
/// Unspecified opaque error type.
pub const ERROR_TYPE: Type = Type::new_extension(ERROR_CUSTOM_TYPE);

Expand Down
2 changes: 1 addition & 1 deletion hugr-core/src/extension/type_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ mod test {
);
assert_eq!(typ.least_upper_bound(), TypeBound::Copyable);
let typ2 = Type::new_extension(def.instantiate([USIZE_T.into()]).unwrap());
assert_eq!(typ2.least_upper_bound(), TypeBound::Eq);
assert_eq!(typ2.least_upper_bound(), TypeBound::Copyable);

// And some bad arguments...firstly, wrong kind of TypeArg:
assert_eq!(
Expand Down
10 changes: 5 additions & 5 deletions hugr-core/src/hugr/serialize/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,8 +457,8 @@ fn polyfunctype1() -> PolyFuncType {

fn polyfunctype2() -> PolyFuncTypeRV {
let tv0 = TypeRV::new_row_var_use(0, TypeBound::Any);
let tv1 = TypeRV::new_row_var_use(1, TypeBound::Eq);
let params = [TypeBound::Any, TypeBound::Eq].map(TypeParam::new_list);
let tv1 = TypeRV::new_row_var_use(1, TypeBound::Copyable);
let params = [TypeBound::Any, TypeBound::Copyable].map(TypeParam::new_list);
let inputs = vec![
TypeRV::new_function(FuncValueType::new(tv0.clone(), tv1.clone())),
tv0,
Expand All @@ -474,7 +474,7 @@ fn polyfunctype2() -> PolyFuncTypeRV {
#[case(Signature::new_endo(type_row![]).into())]
#[case(polyfunctype1())]
#[case(PolyFuncType::new([TypeParam::String], Signature::new_endo(type_row![Type::new_var_use(0, TypeBound::Copyable)])))]
#[case(PolyFuncType::new([TypeBound::Eq.into()], Signature::new_endo(type_row![Type::new_var_use(0, TypeBound::Eq)])))]
#[case(PolyFuncType::new([TypeBound::Copyable.into()], Signature::new_endo(type_row![Type::new_var_use(0, TypeBound::Copyable)])))]
#[case(PolyFuncType::new([TypeParam::new_list(TypeBound::Any)], Signature::new_endo(type_row![])))]
#[case(PolyFuncType::new([TypeParam::Tuple { params: [TypeBound::Any.into(), TypeParam::bounded_nat(2.try_into().unwrap())].into() }], Signature::new_endo(type_row![])))]
#[case(PolyFuncType::new(
Expand All @@ -487,7 +487,7 @@ fn roundtrip_polyfunctype_fixedlen(#[case] poly_func_type: PolyFuncType) {
#[rstest]
#[case(FuncValueType::new_endo(type_row![]).into())]
#[case(PolyFuncTypeRV::new([TypeParam::String], FuncValueType::new_endo(type_row![Type::new_var_use(0, TypeBound::Copyable)])))]
#[case(PolyFuncTypeRV::new([TypeBound::Eq.into()], FuncValueType::new_endo(type_row![Type::new_var_use(0, TypeBound::Eq)])))]
#[case(PolyFuncTypeRV::new([TypeBound::Copyable.into()], FuncValueType::new_endo(type_row![Type::new_var_use(0, TypeBound::Copyable)])))]
#[case(PolyFuncTypeRV::new([TypeParam::new_list(TypeBound::Any)], FuncValueType::new_endo(type_row![])))]
#[case(PolyFuncTypeRV::new([TypeParam::Tuple { params: [TypeBound::Any.into(), TypeParam::bounded_nat(2.try_into().unwrap())].into() }], FuncValueType::new_endo(type_row![])))]
#[case(PolyFuncTypeRV::new(
Expand All @@ -506,7 +506,7 @@ fn roundtrip_polyfunctype_varlen(#[case] poly_func_type: PolyFuncTypeRV) {
#[case(ops::AliasDecl { name: "aliasdecl".into(), bound: TypeBound::Any})]
#[case(ops::Const::new(Value::false_val()))]
#[case(ops::Const::new(Value::function(crate::builder::test::simple_dfg_hugr()).unwrap()))]
#[case(ops::Input::new(type_row![Type::new_var_use(3,TypeBound::Eq)]))]
#[case(ops::Input::new(type_row![Type::new_var_use(3,TypeBound::Copyable)]))]
#[case(ops::Output::new(vec![Type::new_function(FuncValueType::new_endo(type_row![]))]))]
#[case(ops::Call::try_new(polyfunctype1(), [TypeArg::BoundedNat{n: 1}, TypeArg::Extensions{ es: ExtensionSet::singleton(&PRELUDE_ID)} ], &EMPTY_REG).unwrap())]
#[case(ops::CallIndirect { signature : Signature::new_endo(type_row![BOOL_T]) })]
Expand Down
6 changes: 3 additions & 3 deletions hugr-core/src/ops/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -714,15 +714,15 @@ mod test {
"my_type",
vec![TypeArg::BoundedNat { n: 8 }],
ex_id.clone(),
TypeBound::Eq,
TypeBound::Copyable,
);
let json_const: Value =
CustomSerialized::new(typ_int.clone(), 6.into(), ex_id.clone()).into();
let classic_t = Type::new_extension(typ_int.clone());
assert_matches!(classic_t.least_upper_bound(), TypeBound::Eq);
assert_matches!(classic_t.least_upper_bound(), TypeBound::Copyable);
assert_eq!(json_const.get_type(), classic_t);

let typ_qb = CustomType::new("my_type", vec![], ex_id, TypeBound::Eq);
let typ_qb = CustomType::new("my_type", vec![], ex_id, TypeBound::Copyable);
let t = Type::new_extension(typ_qb.clone());
assert_ne!(json_const.get_type(), t);
}
Expand Down
9 changes: 7 additions & 2 deletions hugr-core/src/std_extensions/arithmetic/int_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ pub const INT_TYPE_ID: TypeName = TypeName::new_inline("int");
/// the operation, the semantic interpretation may be unsigned integer, signed
/// integer or bit string.
pub fn int_custom_type(width_arg: impl Into<TypeArg>) -> CustomType {
CustomType::new(INT_TYPE_ID, [width_arg.into()], EXTENSION_ID, TypeBound::Eq)
CustomType::new(
INT_TYPE_ID,
[width_arg.into()],
EXTENSION_ID,
TypeBound::Copyable,
)
}

/// Integer type of a given bit width (specified by the TypeArg).
Expand Down Expand Up @@ -187,7 +192,7 @@ pub fn extension() -> Extension {
INT_TYPE_ID,
vec![LOG_WIDTH_TYPE_PARAM],
"integral value of a given bit width".to_owned(),
TypeBound::Eq.into(),
TypeBound::Copyable.into(),
)
.unwrap();

Expand Down
4 changes: 2 additions & 2 deletions hugr-core/src/std_extensions/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ fn extension() -> Extension {
PTR_TYPE_ID,
TYPE_PARAMS.into(),
"Standard extension pointer type.".into(),
TypeDefBound::Explicit(TypeBound::Eq),
TypeDefBound::Explicit(TypeBound::Copyable),
)
.unwrap();
PtrOpDef::load_all_ops(&mut extension).unwrap();
Expand All @@ -109,7 +109,7 @@ lazy_static! {
/// integer or bit string.
pub fn ptr_custom_type(ty: impl Into<Type>) -> CustomType {
let ty = ty.into();
CustomType::new(PTR_TYPE_ID, [ty.into()], EXTENSION_ID, TypeBound::Eq)
CustomType::new(PTR_TYPE_ID, [ty.into()], EXTENSION_ID, TypeBound::Copyable)
}

/// Integer type of a given bit width (specified by the TypeArg).
Expand Down
22 changes: 11 additions & 11 deletions hugr-core/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,8 @@ impl EdgeKind {
#[cfg_attr(test, derive(Arbitrary))]
/// Bounds on the valid operations on a type in a HUGR program.
pub enum TypeBound {
/// The equality operation is valid on this type.
#[serde(rename = "E")]
Eq,
/// The type can be copied in the program.
#[serde(rename = "C")]
#[serde(rename = "C", alias = "E")] // alias to read in legacy Eq variants
Copyable,
/// No bound on the type.
#[serde(rename = "A")]
Expand All @@ -105,13 +102,13 @@ impl TypeBound {
/// Report if this bound contains another.
pub const fn contains(&self, other: TypeBound) -> bool {
use TypeBound::*;
matches!((self, other), (Any, _) | (_, Eq) | (Copyable, Copyable))
matches!((self, other), (Any, _) | (_, Copyable))
}
}

/// Calculate the least upper bound for an iterator of bounds
pub(crate) fn least_upper_bound(mut tags: impl Iterator<Item = TypeBound>) -> TypeBound {
tags.fold_while(TypeBound::Eq, |acc, new| {
tags.fold_while(TypeBound::Copyable, |acc, new| {
if acc == TypeBound::Any || new == TypeBound::Any {
Done(TypeBound::Any)
} else {
Expand Down Expand Up @@ -247,7 +244,7 @@ impl<RV: MaybeRV> TypeEnum<RV> {
TypeEnum::Function(_) => TypeBound::Copyable,
TypeEnum::Variable(_, b) => *b,
TypeEnum::RowVar(b) => b.bound(),
TypeEnum::Sum(SumType::Unit { size: _ }) => TypeBound::Eq,
TypeEnum::Sum(SumType::Unit { size: _ }) => TypeBound::Copyable,
TypeEnum::Sum(SumType::General { rows }) => least_upper_bound(
rows.iter()
.flat_map(TypeRowRV::iter)
Expand All @@ -274,7 +271,7 @@ impl<RV: MaybeRV> TypeEnum<RV> {
/// # use hugr::type_row;
///
/// let sum = Type::new_sum([type_row![], type_row![]]);
/// assert_eq!(sum.least_upper_bound(), TypeBound::Eq);
/// assert_eq!(sum.least_upper_bound(), TypeBound::Copyable);
/// ```
///
/// ```
Expand Down Expand Up @@ -316,7 +313,10 @@ impl<RV: MaybeRV> TypeBase<RV> {
/// An empty `TypeRow` or `TypeRowRV`. Provided here for convenience
pub const EMPTY_TYPEROW: TypeRowBase<RV> = TypeRowBase::<RV>::new();
/// Unit type (empty tuple).
pub const UNIT: Self = Self(TypeEnum::Sum(SumType::Unit { size: 1 }), TypeBound::Eq);
pub const UNIT: Self = Self(
TypeEnum::Sum(SumType::Unit { size: 1 }),
TypeBound::Copyable,
);

const EMPTY_TYPEROW_REF: &'static TypeRowBase<RV> = &Self::EMPTY_TYPEROW;

Expand Down Expand Up @@ -364,7 +364,7 @@ impl<RV: MaybeRV> TypeBase<RV> {
/// New UnitSum with empty Tuple variants
pub const fn new_unit_sum(size: u8) -> Self {
// should be the only way to avoid going through SumType::new
Self(TypeEnum::Sum(SumType::new_unary(size)), TypeBound::Eq)
Self(TypeEnum::Sum(SumType::new_unary(size)), TypeBound::Copyable)
}

/// New use (occurrence) of the type variable with specified index.
Expand Down Expand Up @@ -620,7 +620,7 @@ pub(crate) mod test {
"my_extension".try_into().unwrap(),
TypeBound::Copyable,
)),
Type::new_alias(AliasDecl::new("my_alias", TypeBound::Eq)),
Type::new_alias(AliasDecl::new("my_alias", TypeBound::Copyable)),
]);
assert_eq!(
&t.to_string(),
Expand Down
4 changes: 2 additions & 2 deletions hugr-core/src/types/poly_func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ pub(crate) mod test {
fn test_bound_covariance() -> Result<(), SignatureError> {
decl_accepts_rejects_var(
TypeBound::Copyable.into(),
&[TypeBound::Copyable.into(), TypeBound::Eq.into()],
&[TypeBound::Copyable.into()],
&[TypeBound::Any.into()],
)?;

Expand All @@ -366,7 +366,7 @@ pub(crate) mod test {
};
decl_accepts_rejects_var(
list_of_tys(TypeBound::Copyable),
&[list_of_tys(TypeBound::Copyable), list_of_tys(TypeBound::Eq)],
&[list_of_tys(TypeBound::Copyable)],
&[list_of_tys(TypeBound::Any)],
)?;

Expand Down
14 changes: 7 additions & 7 deletions hugr-core/src/types/type_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,21 +450,21 @@ mod test {
check_type_arg(&arg, param)
}
// Simple cases: a TypeArg::Type is a TypeParam::Type but singleton sequences are lists
check(USIZE_T, &TypeBound::Eq.into()).unwrap();
let seq_param = TypeParam::new_list(TypeBound::Eq);
check(USIZE_T, &TypeBound::Copyable.into()).unwrap();
let seq_param = TypeParam::new_list(TypeBound::Copyable);
check(USIZE_T, &seq_param).unwrap_err();
check_seq(&[USIZE_T], &TypeBound::Any.into()).unwrap_err();

// Into a list of type, we can fit a single row var
check(rowvar(0, TypeBound::Eq), &seq_param).unwrap();
check(rowvar(0, TypeBound::Copyable), &seq_param).unwrap();
// or a list of (types or row vars)
check(vec![], &seq_param).unwrap();
check_seq(&[rowvar(0, TypeBound::Eq)], &seq_param).unwrap();
check_seq(&[rowvar(0, TypeBound::Copyable)], &seq_param).unwrap();
check_seq(
&[
rowvar(1, TypeBound::Any),
USIZE_T.into(),
rowvar(0, TypeBound::Eq),
rowvar(0, TypeBound::Copyable),
],
&TypeParam::new_list(TypeBound::Any),
)
Expand All @@ -474,7 +474,7 @@ mod test {
&[
rowvar(1, TypeBound::Any),
USIZE_T.into(),
rowvar(0, TypeBound::Eq),
rowvar(0, TypeBound::Copyable),
],
&seq_param,
)
Expand Down Expand Up @@ -502,7 +502,7 @@ mod test {

// 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()],
params: vec![TypeParam::max_nat(), TypeBound::Copyable.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
Expand Down
5 changes: 2 additions & 3 deletions hugr-py/src/hugr/serialization/tys.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,18 +381,17 @@ def deserialize(self) -> tys.PolyFuncType:


class TypeBound(Enum):
Eq = "E"
Copyable = "C"
Any = "A"

@staticmethod
def join(*bs: TypeBound) -> TypeBound:
"""Computes the least upper bound for a sequence of bounds."""
res = TypeBound.Eq
res = TypeBound.Copyable
for b in bs:
if b == TypeBound.Any:
return TypeBound.Any
if res == TypeBound.Eq:
if res == TypeBound.Copyable:
res = b
return res

Expand Down
Loading

0 comments on commit 1218d21

Please sign in to comment.