From 078da5de31284b2965bd9dbd02b986737636c418 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Fri, 26 Jul 2024 12:21:01 +0100 Subject: [PATCH 01/19] refactor!: Use internal tagging for TypeDefBound for pydantic compatibility. BREAKING CHANGE: exernally tagged TypedDefBound serialisation will no longer work. --- hugr-core/src/extension/declarative/types.rs | 8 ++++++-- hugr-core/src/extension/prelude.rs | 10 +++++----- hugr-core/src/extension/type_def.rs | 14 ++++++++------ hugr-core/src/hugr/validate/test.rs | 4 +++- hugr-core/src/std_extensions/collections.rs | 2 +- hugr-core/src/std_extensions/ptr.rs | 5 +++-- hugr-core/src/types/poly_func.rs | 4 +++- 7 files changed, 29 insertions(+), 18 deletions(-) diff --git a/hugr-core/src/extension/declarative/types.rs b/hugr-core/src/extension/declarative/types.rs index a0e5cef5a..10b6e41a0 100644 --- a/hugr-core/src/extension/declarative/types.rs +++ b/hugr-core/src/extension/declarative/types.rs @@ -89,8 +89,12 @@ enum TypeDefBoundDeclaration { impl From for TypeDefBound { fn from(bound: TypeDefBoundDeclaration) -> Self { match bound { - TypeDefBoundDeclaration::Copyable => Self::Explicit(TypeBound::Copyable), - TypeDefBoundDeclaration::Any => Self::Explicit(TypeBound::Any), + TypeDefBoundDeclaration::Copyable => Self::Explicit { + bound: TypeBound::Copyable, + }, + TypeDefBoundDeclaration::Any => Self::Explicit { + bound: TypeBound::Any, + }, } } } diff --git a/hugr-core/src/extension/prelude.rs b/hugr-core/src/extension/prelude.rs index 739caad16..31b6431fe 100644 --- a/hugr-core/src/extension/prelude.rs +++ b/hugr-core/src/extension/prelude.rs @@ -95,14 +95,14 @@ lazy_static! { TypeName::new_inline("usize"), vec![], "usize".into(), - TypeDefBound::Explicit(crate::types::TypeBound::Copyable), + TypeDefBound::Explicit { bound: crate::types::TypeBound::Copyable }, ) .unwrap(); prelude.add_type( STRING_TYPE_NAME, vec![], "string".into(), - TypeDefBound::Explicit(crate::types::TypeBound::Copyable), + TypeDefBound::Explicit { bound: crate::types::TypeBound::Copyable }, ) .unwrap(); prelude.add_op( @@ -115,7 +115,7 @@ lazy_static! { TypeName::new_inline("array"), vec![ TypeParam::max_nat(), TypeBound::Any.into()], "array".into(), - TypeDefBound::FromParams(vec![1]), + TypeDefBound::FromParams { indices: vec![1] }, ) .unwrap(); prelude @@ -131,7 +131,7 @@ lazy_static! { TypeName::new_inline("qubit"), vec![], "qubit".into(), - TypeDefBound::Explicit(TypeBound::Any), + TypeDefBound::Explicit { bound: TypeBound::Any }, ) .unwrap(); prelude @@ -139,7 +139,7 @@ lazy_static! { ERROR_TYPE_NAME, vec![], "Simple opaque error type.".into(), - TypeDefBound::Explicit(TypeBound::Copyable), + TypeDefBound::Explicit { bound: TypeBound::Copyable }, ) .unwrap(); prelude diff --git a/hugr-core/src/extension/type_def.rs b/hugr-core/src/extension/type_def.rs index bd1e64ffc..5e79538a3 100644 --- a/hugr-core/src/extension/type_def.rs +++ b/hugr-core/src/extension/type_def.rs @@ -13,16 +13,18 @@ use crate::types::TypeBound; /// The type bound of a [`TypeDef`] #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[serde(tag = "b")] +#[allow(missing_docs)] pub enum TypeDefBound { /// Defined by an explicit bound. - Explicit(TypeBound), + Explicit { bound: TypeBound }, /// Derived as the least upper bound of the marked parameters. - FromParams(Vec), + FromParams { indices: Vec }, } impl From for TypeDefBound { fn from(bound: TypeBound) -> Self { - Self::Explicit(bound) + Self::Explicit { bound } } } @@ -105,8 +107,8 @@ impl TypeDef { /// The [`TypeBound`] of the definition. pub fn bound(&self, args: &[TypeArg]) -> TypeBound { match &self.bound { - TypeDefBound::Explicit(bound) => *bound, - TypeDefBound::FromParams(indices) => { + TypeDefBound::Explicit { bound } => *bound, + TypeDefBound::FromParams { indices } => { let args: Vec<_> = args.iter().collect(); if indices.is_empty() { // Assume most general case @@ -180,7 +182,7 @@ mod test { }], extension: "MyRsrc".try_into().unwrap(), description: "Some parametrised type".into(), - bound: TypeDefBound::FromParams(vec![0]), + bound: TypeDefBound::FromParams { indices: vec![0] }, }; let typ = Type::new_extension( def.instantiate(vec![TypeArg::Type { diff --git a/hugr-core/src/hugr/validate/test.rs b/hugr-core/src/hugr/validate/test.rs index 76ccec8b4..6cf56039d 100644 --- a/hugr-core/src/hugr/validate/test.rs +++ b/hugr-core/src/hugr/validate/test.rs @@ -370,7 +370,9 @@ fn invalid_types() { "MyContainer".into(), vec![TypeBound::Copyable.into()], "".into(), - TypeDefBound::Explicit(TypeBound::Any), + TypeDefBound::Explicit { + bound: TypeBound::Any, + }, ) .unwrap(); let reg = ExtensionRegistry::try_new([e, PRELUDE.to_owned()]).unwrap(); diff --git a/hugr-core/src/std_extensions/collections.rs b/hugr-core/src/std_extensions/collections.rs index c182543ca..77234e5df 100644 --- a/hugr-core/src/std_extensions/collections.rs +++ b/hugr-core/src/std_extensions/collections.rs @@ -147,7 +147,7 @@ fn extension() -> Extension { LIST_TYPENAME, vec![TP], "Generic dynamically sized list of type T.".into(), - TypeDefBound::FromParams(vec![0]), + TypeDefBound::FromParams { indices: vec![0] }, ) .unwrap(); let list_type_def = extension.get_type(&LIST_TYPENAME).unwrap(); diff --git a/hugr-core/src/std_extensions/ptr.rs b/hugr-core/src/std_extensions/ptr.rs index 72c53a97f..cd3472b3f 100644 --- a/hugr-core/src/std_extensions/ptr.rs +++ b/hugr-core/src/std_extensions/ptr.rs @@ -91,11 +91,12 @@ fn extension() -> Extension { PTR_TYPE_ID, TYPE_PARAMS.into(), "Standard extension pointer type.".into(), - TypeDefBound::Explicit(TypeBound::Copyable), + TypeDefBound::Explicit { + bound: TypeBound::Copyable, + }, ) .unwrap(); PtrOpDef::load_all_ops(&mut extension).unwrap(); - extension } diff --git a/hugr-core/src/types/poly_func.rs b/hugr-core/src/types/poly_func.rs index 40a92b2ea..a8d672364 100644 --- a/hugr-core/src/types/poly_func.rs +++ b/hugr-core/src/types/poly_func.rs @@ -318,7 +318,9 @@ pub(crate) mod test { TYPE_NAME, vec![bound.clone()], "".into(), - TypeDefBound::Explicit(TypeBound::Any), + TypeDefBound::Explicit { + bound: TypeBound::Any, + }, ) .unwrap(); From 066dcdedfa8954be2bab575b1f6b625be86b8bc2 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Fri, 26 Jul 2024 12:21:51 +0100 Subject: [PATCH 02/19] feat: pydantic models for extension definition --- hugr-py/poetry.lock | 37 +++++- hugr-py/pyproject.toml | 2 + hugr-py/src/hugr/serialization/extension.py | 66 +++++++++++ hugr-py/src/hugr/serialization/ops.py | 21 ---- .../src/hugr/serialization/testing_hugr.py | 3 +- hugr-py/tests/serialization/test_extension.py | 108 ++++++++++++++++++ 6 files changed, 213 insertions(+), 24 deletions(-) create mode 100644 hugr-py/src/hugr/serialization/extension.py create mode 100644 hugr-py/tests/serialization/test_extension.py diff --git a/hugr-py/poetry.lock b/hugr-py/poetry.lock index af969251a..0fb4cce8f 100644 --- a/hugr-py/poetry.lock +++ b/hugr-py/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "accessible-pygments" @@ -449,6 +449,28 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pydantic-extra-types" +version = "2.9.0" +description = "Extra Pydantic types." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_extra_types-2.9.0-py3-none-any.whl", hash = "sha256:f0bb975508572ba7bf3390b7337807588463b7248587e69f43b1ad7c797530d0"}, + {file = "pydantic_extra_types-2.9.0.tar.gz", hash = "sha256:e061c01636188743bb69f368dcd391f327b8cfbfede2fe1cbb1211b06601ba3b"}, +] + +[package.dependencies] +pydantic = ">=2.5.2" + +[package.extras] +all = ["pendulum (>=3.0.0,<4.0.0)", "phonenumbers (>=8,<9)", "pycountry (>=23)", "python-ulid (>=1,<2)", "python-ulid (>=1,<3)", "pytz (>=2024.1)", "semver (>=3.0.2)", "tzdata (>=2024.1)"] +pendulum = ["pendulum (>=3.0.0,<4.0.0)"] +phonenumbers = ["phonenumbers (>=8,<9)"] +pycountry = ["pycountry (>=23)"] +python-ulid = ["python-ulid (>=1,<2)", "python-ulid (>=1,<3)"] +semver = ["semver (>=3.0.2)"] + [[package]] name = "pydata-sphinx-theme" version = "0.15.4" @@ -512,6 +534,17 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "semver" +version = "3.0.2" +description = "Python helper for Semantic Versioning (https://semver.org)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4"}, + {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, +] + [[package]] name = "snowballstemmer" version = "2.2.0" @@ -739,4 +772,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = ">=3.10" -content-hash = "4775b9f50c8ed74e8e0bc54d4e303b53fd2661d0937fac18115d0c6102ac8bb3" +content-hash = "db5c61ff58a73254e04e3c84a403b039670f5d83c3b9072c5d60a50927bd1e22" diff --git a/hugr-py/pyproject.toml b/hugr-py/pyproject.toml index 8f84b32ba..8a9100851 100644 --- a/hugr-py/pyproject.toml +++ b/hugr-py/pyproject.toml @@ -26,6 +26,8 @@ repository = "https://github.com/CQCL/hugr" [tool.poetry.dependencies] python = ">=3.10" pydantic = ">=2.7,<2.9" +pydantic-extra-types = "^2.9.0" +semver = "^3.0.2" [tool.poetry.group.docs] optional = true diff --git a/hugr-py/src/hugr/serialization/extension.py b/hugr-py/src/hugr/serialization/extension.py new file mode 100644 index 000000000..b5a668803 --- /dev/null +++ b/hugr-py/src/hugr/serialization/extension.py @@ -0,0 +1,66 @@ +from typing import Annotated, Any, Literal + +import pydantic as pd +from pydantic_extra_types.semantic_version import SemanticVersion + +from .ops import Value +from .tys import ExtensionId, ExtensionSet, PolyFuncType, TypeBound, TypeParam + + +class ExplicitBound(pd.BaseModel): + b: Literal["Explicit"] = "Explicit" + bound: TypeBound + + +class FromParamsBound(pd.BaseModel): + b: Literal["FromParams"] = "FromParams" + indices: list[int] + + +class TypeDefBound(pd.RootModel): + root: Annotated[ExplicitBound | FromParamsBound, pd.Field(discriminator="b")] + + +class TypeDef(pd.BaseModel): + extension: ExtensionId + name: str + description: str + params: list[TypeParam] + bound: TypeDefBound + + +class ExtensionValue(pd.BaseModel): + extension: ExtensionId + name: str + typed_value: Value + + +# -------------------------------------- +# --------------- OpDef ---------------- +# -------------------------------------- + + +class FixedHugr(pd.BaseModel): + extensions: ExtensionSet + hugr: Any + + +class OpDef(pd.BaseModel, populate_by_name=True): + """Serializable definition for dynamically loaded operations.""" + + extension: ExtensionId + name: str # Unique identifier of the operation. + description: str # Human readable description of the operation. + misc: dict[str, Any] | None = None + signature: PolyFuncType | None = None + lower_funcs: list[FixedHugr] + + +class Extension(pd.BaseModel): + # TODO schema version + version: SemanticVersion + name: ExtensionId + extension_reqs: set[ExtensionId] + types: dict[str, TypeDef] + values: dict[str, ExtensionValue] + operations: dict[str, OpDef] diff --git a/hugr-py/src/hugr/serialization/ops.py b/hugr-py/src/hugr/serialization/ops.py index 2ee08edc0..9e1e0cde7 100644 --- a/hugr-py/src/hugr/serialization/ops.py +++ b/hugr-py/src/hugr/serialization/ops.py @@ -658,27 +658,6 @@ class OpType(RootModel): model_config = ConfigDict(json_schema_extra={"required": ["parent", "op"]}) -# -------------------------------------- -# --------------- OpDef ---------------- -# -------------------------------------- - - -class FixedHugr(ConfiguredBaseModel): - extensions: ExtensionSet - hugr: Any - - -class OpDef(ConfiguredBaseModel, populate_by_name=True): - """Serializable definition for dynamically loaded operations.""" - - extension: ExtensionId - name: str # Unique identifier of the operation. - description: str # Human readable description of the operation. - misc: dict[str, Any] | None = None - signature: PolyFuncType | None = None - lower_funcs: list[FixedHugr] - - # Now that all classes are defined, we need to update the ForwardRefs in all type # annotations. We use some inspect magic to find all classes defined in this file. classes = ( diff --git a/hugr-py/src/hugr/serialization/testing_hugr.py b/hugr-py/src/hugr/serialization/testing_hugr.py index 6dcaed156..fa57ece7a 100644 --- a/hugr-py/src/hugr/serialization/testing_hugr.py +++ b/hugr-py/src/hugr/serialization/testing_hugr.py @@ -1,6 +1,7 @@ from pydantic import ConfigDict -from .ops import OpDef, OpType, Value +from .extension import OpDef +from .ops import OpType, Value from .ops import classes as ops_classes from .serial_hugr import VersionField from .tys import ConfiguredBaseModel, PolyFuncType, SumType, Type, model_rebuild diff --git a/hugr-py/tests/serialization/test_extension.py b/hugr-py/tests/serialization/test_extension.py new file mode 100644 index 000000000..03ab6cbc9 --- /dev/null +++ b/hugr-py/tests/serialization/test_extension.py @@ -0,0 +1,108 @@ +from semver import Version + +from hugr.serialization.extension import ( + ExplicitBound, + Extension, + OpDef, + TypeDef, + TypeDefBound, +) +from hugr.serialization.tys import ( + FunctionType, + PolyFuncType, + Type, + TypeBound, + TypeParam, + TypeTypeParam, + Variable, +) + +EXAMPLE = r""" +{ + "version": "0.1.0", + "name": "ext", + "extension_reqs": [], + "types": { + "foo": { + "extension": "ext", + "name": "foo", + "params": [ + { + "tp": "Type", + "b": "C" + } + ], + "description": "foo", + "bound": { + "b": "Explicit", + "bound": "C" + } + } + }, + "values": {}, + "operations": { + "New": { + "extension": "ext", + "name": "New", + "description": "new", + "signature": { + "params": [ + { + "tp": "Type", + "b": "C" + } + ], + "body": { + "input": [ + { + "t": "V", + "i": 0, + "b": "C" + } + ], + "output": [], + "extension_reqs": [] + } + }, + "lower_funcs": [] + } + } +} +""" + + +def test_deserialize(): + param = TypeParam(root=TypeTypeParam(b=TypeBound.Copyable)) + + bound = TypeDefBound(root=ExplicitBound(bound=TypeBound.Copyable)) + type_def = TypeDef( + extension="ext", name="foo", description="foo", params=[param], bound=bound + ) + body = FunctionType( + input=[Type(root=Variable(b=TypeBound.Copyable, i=0))], output=[] + ) + op_def = OpDef( + extension="ext", + name="New", + description="new", + signature=PolyFuncType( + params=[param], + body=body, + ), + lower_funcs=[], + ) + ext = Extension( + version=Version(0, 1, 0), + name="ext", + extension_reqs=set(), + types={"foo": type_def}, + values={}, + operations={"New": op_def}, + ) + + ext_load = Extension.model_validate_json(EXAMPLE) + assert ext == ext_load + + dumped_json = ext.model_dump_json() + + assert Extension.model_validate_json(dumped_json) == ext From b9ae43ab5be88ffc45f1c8c1493b84060ceb61b9 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Fri, 26 Jul 2024 14:47:34 +0100 Subject: [PATCH 03/19] feat: generate top level schemas including extensions --- hugr-py/src/hugr/serialization/extension.py | 34 +- poetry.lock | 35 ++ scripts/generate_schema.py | 13 +- specification/schema/hugr_schema_live.json | 490 +++++++++++++----- .../schema/hugr_schema_strict_live.json | 490 +++++++++++++----- .../schema/testing_hugr_schema_live.json | 351 +++++++++---- .../testing_hugr_schema_strict_live.json | 351 +++++++++---- 7 files changed, 1334 insertions(+), 430 deletions(-) diff --git a/hugr-py/src/hugr/serialization/extension.py b/hugr-py/src/hugr/serialization/extension.py index b5a668803..7da77a01b 100644 --- a/hugr-py/src/hugr/serialization/extension.py +++ b/hugr-py/src/hugr/serialization/extension.py @@ -3,16 +3,25 @@ import pydantic as pd from pydantic_extra_types.semantic_version import SemanticVersion +from hugr import get_serialisation_version + from .ops import Value -from .tys import ExtensionId, ExtensionSet, PolyFuncType, TypeBound, TypeParam +from .tys import ( + ConfiguredBaseModel, + ExtensionId, + ExtensionSet, + PolyFuncType, + TypeBound, + TypeParam, +) -class ExplicitBound(pd.BaseModel): +class ExplicitBound(ConfiguredBaseModel): b: Literal["Explicit"] = "Explicit" bound: TypeBound -class FromParamsBound(pd.BaseModel): +class FromParamsBound(ConfiguredBaseModel): b: Literal["FromParams"] = "FromParams" indices: list[int] @@ -21,7 +30,7 @@ class TypeDefBound(pd.RootModel): root: Annotated[ExplicitBound | FromParamsBound, pd.Field(discriminator="b")] -class TypeDef(pd.BaseModel): +class TypeDef(ConfiguredBaseModel): extension: ExtensionId name: str description: str @@ -29,7 +38,7 @@ class TypeDef(pd.BaseModel): bound: TypeDefBound -class ExtensionValue(pd.BaseModel): +class ExtensionValue(ConfiguredBaseModel): extension: ExtensionId name: str typed_value: Value @@ -40,12 +49,12 @@ class ExtensionValue(pd.BaseModel): # -------------------------------------- -class FixedHugr(pd.BaseModel): +class FixedHugr(ConfiguredBaseModel): extensions: ExtensionSet hugr: Any -class OpDef(pd.BaseModel, populate_by_name=True): +class OpDef(ConfiguredBaseModel, populate_by_name=True): """Serializable definition for dynamically loaded operations.""" extension: ExtensionId @@ -56,11 +65,18 @@ class OpDef(pd.BaseModel, populate_by_name=True): lower_funcs: list[FixedHugr] -class Extension(pd.BaseModel): - # TODO schema version +class Extension(ConfiguredBaseModel): version: SemanticVersion name: ExtensionId extension_reqs: set[ExtensionId] types: dict[str, TypeDef] values: dict[str, ExtensionValue] operations: dict[str, OpDef] + + @classmethod + def get_version(cls) -> str: + return get_serialisation_version() + + @classmethod + def _pydantic_rebuild(cls, config: pd.ConfigDict | None = None, **kwargs): + pass diff --git a/poetry.lock b/poetry.lock index 3980c34a9..b0700e73c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -152,6 +152,8 @@ develop = true [package.dependencies] pydantic = ">=2.7,<2.9" +pydantic-extra-types = "^2.9.0" +semver = "^3.0.2" [package.source] type = "directory" @@ -434,6 +436,28 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pydantic-extra-types" +version = "2.9.0" +description = "Extra Pydantic types." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_extra_types-2.9.0-py3-none-any.whl", hash = "sha256:f0bb975508572ba7bf3390b7337807588463b7248587e69f43b1ad7c797530d0"}, + {file = "pydantic_extra_types-2.9.0.tar.gz", hash = "sha256:e061c01636188743bb69f368dcd391f327b8cfbfede2fe1cbb1211b06601ba3b"}, +] + +[package.dependencies] +pydantic = ">=2.5.2" + +[package.extras] +all = ["pendulum (>=3.0.0,<4.0.0)", "phonenumbers (>=8,<9)", "pycountry (>=23)", "python-ulid (>=1,<2)", "python-ulid (>=1,<3)", "pytz (>=2024.1)", "semver (>=3.0.2)", "tzdata (>=2024.1)"] +pendulum = ["pendulum (>=3.0.0,<4.0.0)"] +phonenumbers = ["phonenumbers (>=8,<9)"] +pycountry = ["pycountry (>=23)"] +python-ulid = ["python-ulid (>=1,<2)", "python-ulid (>=1,<3)"] +semver = ["semver (>=3.0.2)"] + [[package]] name = "pytest" version = "8.3.2" @@ -560,6 +584,17 @@ files = [ {file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"}, ] +[[package]] +name = "semver" +version = "3.0.2" +description = "Python helper for Semantic Versioning (https://semver.org)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4"}, + {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, +] + [[package]] name = "toml" version = "0.10.2" diff --git a/scripts/generate_schema.py b/scripts/generate_schema.py index 78d66fb19..8a40930c7 100644 --- a/scripts/generate_schema.py +++ b/scripts/generate_schema.py @@ -14,7 +14,9 @@ from pathlib import Path from pydantic import ConfigDict +from pydantic.json_schema import models_json_schema +from hugr.serialization.extension import Extension from hugr.serialization.serial_hugr import SerialHugr from hugr.serialization.testing_hugr import TestingHugr @@ -22,7 +24,7 @@ def write_schema( out_dir: Path, name_prefix: str, - schema: type[SerialHugr] | type[TestingHugr], + schema: type[SerialHugr] | type[TestingHugr] | type[Extension], config: ConfigDict | None = None, **kwargs, ): @@ -30,10 +32,15 @@ def write_schema( filename = f"{name_prefix}_{version}.json" path = out_dir / filename print(f"Rebuilding model with config: {config}") - schema._pydantic_rebuild(config or ConfigDict(), force=True, **kwargs) + schemas = [schema, Extension] + for s in schemas: + s._pydantic_rebuild(config or ConfigDict(), force=True, **kwargs) print(f"Writing schema to {path}") + _, top_level_schema = models_json_schema( + [(s, "validation") for s in schemas], title="HUGR schema" + ) with path.open("w") as f: - json.dump(schema.model_json_schema(), f, indent=4) + json.dump(top_level_schema, f, indent=4) if __name__ == "__main__": diff --git a/specification/schema/hugr_schema_live.json b/specification/schema/hugr_schema_live.json index f3db83a57..95a6b6b8c 100644 --- a/specification/schema/hugr_schema_live.json +++ b/specification/schema/hugr_schema_live.json @@ -560,39 +560,76 @@ "title": "ExitBlock", "type": "object" }, - "ExtensionValue": { - "additionalProperties": true, - "description": "An extension constant value, that can check it is of a given [CustomType].", + "ExplicitBound": { "properties": { - "v": { - "const": "Extension", - "default": "Extension", + "b": { + "const": "Explicit", + "default": "Explicit", "enum": [ - "Extension" + "Explicit" ], - "title": "ValueTag", + "title": "B", "type": "string" }, - "extensions": { + "bound": { + "$ref": "#/$defs/TypeBound" + } + }, + "required": [ + "bound" + ], + "title": "ExplicitBound", + "type": "object" + }, + "Extension": { + "properties": { + "version": { + "title": "Version", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "extension_reqs": { "items": { "type": "string" }, - "title": "Extensions", - "type": "array" + "title": "Extension Reqs", + "type": "array", + "uniqueItems": true }, - "typ": { - "$ref": "#/$defs/Type" + "types": { + "additionalProperties": { + "$ref": "#/$defs/TypeDef" + }, + "title": "Types", + "type": "object" }, - "value": { - "$ref": "#/$defs/CustomConst" + "values": { + "additionalProperties": { + "$ref": "#/$defs/hugr__serialization__extension__ExtensionValue" + }, + "title": "Values", + "type": "object" + }, + "operations": { + "additionalProperties": { + "$ref": "#/$defs/OpDef" + }, + "title": "Operations", + "type": "object" } }, "required": [ - "extensions", - "typ", - "value" + "version", + "name", + "extension_reqs", + "types", + "values", + "operations" ], - "title": "ExtensionValue", + "title": "Extension", "type": "object" }, "ExtensionsArg": { @@ -637,6 +674,51 @@ "title": "ExtensionsParam", "type": "object" }, + "FixedHugr": { + "properties": { + "extensions": { + "items": { + "type": "string" + }, + "title": "Extensions", + "type": "array" + }, + "hugr": { + "title": "Hugr" + } + }, + "required": [ + "extensions", + "hugr" + ], + "title": "FixedHugr", + "type": "object" + }, + "FromParamsBound": { + "properties": { + "b": { + "const": "FromParams", + "default": "FromParams", + "enum": [ + "FromParams" + ], + "title": "B", + "type": "string" + }, + "indices": { + "items": { + "type": "integer" + }, + "title": "Indices", + "type": "array" + } + }, + "required": [ + "indices" + ], + "title": "FromParamsBound", + "type": "object" + }, "FuncDecl": { "additionalProperties": true, "description": "External function declaration, linked at runtime.", @@ -1048,6 +1130,61 @@ "title": "Noop", "type": "object" }, + "OpDef": { + "description": "Serializable definition for dynamically loaded operations.", + "properties": { + "extension": { + "title": "Extension", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + }, + "misc": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Misc" + }, + "signature": { + "anyOf": [ + { + "$ref": "#/$defs/PolyFuncType" + }, + { + "type": "null" + } + ], + "default": null + }, + "lower_funcs": { + "items": { + "$ref": "#/$defs/FixedHugr" + }, + "title": "Lower Funcs", + "type": "array" + } + }, + "required": [ + "extension", + "name", + "description", + "lower_funcs" + ], + "title": "OpDef", + "type": "object" + }, "OpType": { "description": "A constant operation.", "discriminator": { @@ -1328,6 +1465,117 @@ "title": "SequenceArg", "type": "object" }, + "SerialHugr": { + "additionalProperties": true, + "description": "A serializable representation of a Hugr.", + "properties": { + "version": { + "description": "Serialisation Schema Version", + "title": "Version", + "type": "string" + }, + "nodes": { + "items": { + "$ref": "#/$defs/OpType" + }, + "title": "Nodes", + "type": "array" + }, + "edges": { + "items": { + "maxItems": 2, + "minItems": 2, + "prefixItems": [ + { + "maxItems": 2, + "minItems": 2, + "prefixItems": [ + { + "type": "integer" + }, + { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + } + ], + "type": "array" + }, + { + "maxItems": 2, + "minItems": 2, + "prefixItems": [ + { + "type": "integer" + }, + { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + } + ], + "type": "array" + } + ], + "type": "array" + }, + "title": "Edges", + "type": "array" + }, + "metadata": { + "anyOf": [ + { + "items": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Metadata" + }, + "encoder": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "The name of the encoder used to generate the Hugr.", + "title": "Encoder" + } + }, + "required": [ + "version", + "nodes", + "edges" + ], + "title": "Hugr", + "type": "object" + }, "StringArg": { "additionalProperties": true, "properties": { @@ -1665,6 +1913,59 @@ "title": "TypeBound", "type": "string" }, + "TypeDef": { + "properties": { + "extension": { + "title": "Extension", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + }, + "params": { + "items": { + "$ref": "#/$defs/TypeParam" + }, + "title": "Params", + "type": "array" + }, + "bound": { + "$ref": "#/$defs/TypeDefBound" + } + }, + "required": [ + "extension", + "name", + "description", + "params", + "bound" + ], + "title": "TypeDef", + "type": "object" + }, + "TypeDefBound": { + "discriminator": { + "mapping": { + "Explicit": "#/$defs/ExplicitBound", + "FromParams": "#/$defs/FromParamsBound" + }, + "propertyName": "b" + }, + "oneOf": [ + { + "$ref": "#/$defs/ExplicitBound" + }, + { + "$ref": "#/$defs/FromParamsBound" + } + ], + "title": "TypeDefBound" + }, "TypeParam": { "description": "A type parameter.", "discriminator": { @@ -1832,7 +2133,7 @@ "description": "A constant Value.", "discriminator": { "mapping": { - "Extension": "#/$defs/ExtensionValue", + "Extension": "#/$defs/hugr__serialization__ops__ExtensionValue", "Function": "#/$defs/FunctionValue", "Sum": "#/$defs/SumValue", "Tuple": "#/$defs/TupleValue" @@ -1841,7 +2142,7 @@ }, "oneOf": [ { - "$ref": "#/$defs/ExtensionValue" + "$ref": "#/$defs/hugr__serialization__ops__ExtensionValue" }, { "$ref": "#/$defs/FunctionValue" @@ -1912,115 +2213,64 @@ ], "title": "VariableArg", "type": "object" - } - }, - "additionalProperties": true, - "description": "A serializable representation of a Hugr.", - "properties": { - "version": { - "description": "Serialisation Schema Version", - "title": "Version", - "type": "string" }, - "nodes": { - "items": { - "$ref": "#/$defs/OpType" - }, - "title": "Nodes", - "type": "array" - }, - "edges": { - "items": { - "maxItems": 2, - "minItems": 2, - "prefixItems": [ - { - "maxItems": 2, - "minItems": 2, - "prefixItems": [ - { - "type": "integer" - }, - { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ] - } - ], - "type": "array" - }, - { - "maxItems": 2, - "minItems": 2, - "prefixItems": [ - { - "type": "integer" - }, - { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ] - } - ], - "type": "array" - } - ], - "type": "array" + "hugr__serialization__extension__ExtensionValue": { + "properties": { + "extension": { + "title": "Extension", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "typed_value": { + "$ref": "#/$defs/Value" + } }, - "title": "Edges", - "type": "array" + "required": [ + "extension", + "name", + "typed_value" + ], + "title": "ExtensionValue", + "type": "object" }, - "metadata": { - "anyOf": [ - { + "hugr__serialization__ops__ExtensionValue": { + "additionalProperties": true, + "description": "An extension constant value, that can check it is of a given [CustomType].", + "properties": { + "v": { + "const": "Extension", + "default": "Extension", + "enum": [ + "Extension" + ], + "title": "ValueTag", + "type": "string" + }, + "extensions": { "items": { - "anyOf": [ - { - "type": "object" - }, - { - "type": "null" - } - ] + "type": "string" }, + "title": "Extensions", "type": "array" }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - }, - "encoder": { - "anyOf": [ - { - "type": "string" + "typ": { + "$ref": "#/$defs/Type" }, - { - "type": "null" + "value": { + "$ref": "#/$defs/CustomConst" } + }, + "required": [ + "extensions", + "typ", + "value" ], - "default": null, - "description": "The name of the encoder used to generate the Hugr.", - "title": "Encoder" + "title": "ExtensionValue", + "type": "object" } }, - "required": [ - "version", - "nodes", - "edges" - ], - "title": "Hugr", - "type": "object" + "title": "HUGR schema" } \ No newline at end of file diff --git a/specification/schema/hugr_schema_strict_live.json b/specification/schema/hugr_schema_strict_live.json index 1a9de7e23..b95171d95 100644 --- a/specification/schema/hugr_schema_strict_live.json +++ b/specification/schema/hugr_schema_strict_live.json @@ -560,39 +560,76 @@ "title": "ExitBlock", "type": "object" }, - "ExtensionValue": { - "additionalProperties": false, - "description": "An extension constant value, that can check it is of a given [CustomType].", + "ExplicitBound": { "properties": { - "v": { - "const": "Extension", - "default": "Extension", + "b": { + "const": "Explicit", + "default": "Explicit", "enum": [ - "Extension" + "Explicit" ], - "title": "ValueTag", + "title": "B", "type": "string" }, - "extensions": { + "bound": { + "$ref": "#/$defs/TypeBound" + } + }, + "required": [ + "bound" + ], + "title": "ExplicitBound", + "type": "object" + }, + "Extension": { + "properties": { + "version": { + "title": "Version", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "extension_reqs": { "items": { "type": "string" }, - "title": "Extensions", - "type": "array" + "title": "Extension Reqs", + "type": "array", + "uniqueItems": true }, - "typ": { - "$ref": "#/$defs/Type" + "types": { + "additionalProperties": { + "$ref": "#/$defs/TypeDef" + }, + "title": "Types", + "type": "object" }, - "value": { - "$ref": "#/$defs/CustomConst" + "values": { + "additionalProperties": { + "$ref": "#/$defs/hugr__serialization__extension__ExtensionValue" + }, + "title": "Values", + "type": "object" + }, + "operations": { + "additionalProperties": { + "$ref": "#/$defs/OpDef" + }, + "title": "Operations", + "type": "object" } }, "required": [ - "extensions", - "typ", - "value" + "version", + "name", + "extension_reqs", + "types", + "values", + "operations" ], - "title": "ExtensionValue", + "title": "Extension", "type": "object" }, "ExtensionsArg": { @@ -637,6 +674,51 @@ "title": "ExtensionsParam", "type": "object" }, + "FixedHugr": { + "properties": { + "extensions": { + "items": { + "type": "string" + }, + "title": "Extensions", + "type": "array" + }, + "hugr": { + "title": "Hugr" + } + }, + "required": [ + "extensions", + "hugr" + ], + "title": "FixedHugr", + "type": "object" + }, + "FromParamsBound": { + "properties": { + "b": { + "const": "FromParams", + "default": "FromParams", + "enum": [ + "FromParams" + ], + "title": "B", + "type": "string" + }, + "indices": { + "items": { + "type": "integer" + }, + "title": "Indices", + "type": "array" + } + }, + "required": [ + "indices" + ], + "title": "FromParamsBound", + "type": "object" + }, "FuncDecl": { "additionalProperties": false, "description": "External function declaration, linked at runtime.", @@ -1048,6 +1130,61 @@ "title": "Noop", "type": "object" }, + "OpDef": { + "description": "Serializable definition for dynamically loaded operations.", + "properties": { + "extension": { + "title": "Extension", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + }, + "misc": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Misc" + }, + "signature": { + "anyOf": [ + { + "$ref": "#/$defs/PolyFuncType" + }, + { + "type": "null" + } + ], + "default": null + }, + "lower_funcs": { + "items": { + "$ref": "#/$defs/FixedHugr" + }, + "title": "Lower Funcs", + "type": "array" + } + }, + "required": [ + "extension", + "name", + "description", + "lower_funcs" + ], + "title": "OpDef", + "type": "object" + }, "OpType": { "description": "A constant operation.", "discriminator": { @@ -1328,6 +1465,117 @@ "title": "SequenceArg", "type": "object" }, + "SerialHugr": { + "additionalProperties": false, + "description": "A serializable representation of a Hugr.", + "properties": { + "version": { + "description": "Serialisation Schema Version", + "title": "Version", + "type": "string" + }, + "nodes": { + "items": { + "$ref": "#/$defs/OpType" + }, + "title": "Nodes", + "type": "array" + }, + "edges": { + "items": { + "maxItems": 2, + "minItems": 2, + "prefixItems": [ + { + "maxItems": 2, + "minItems": 2, + "prefixItems": [ + { + "type": "integer" + }, + { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + } + ], + "type": "array" + }, + { + "maxItems": 2, + "minItems": 2, + "prefixItems": [ + { + "type": "integer" + }, + { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + } + ], + "type": "array" + } + ], + "type": "array" + }, + "title": "Edges", + "type": "array" + }, + "metadata": { + "anyOf": [ + { + "items": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Metadata" + }, + "encoder": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "The name of the encoder used to generate the Hugr.", + "title": "Encoder" + } + }, + "required": [ + "version", + "nodes", + "edges" + ], + "title": "Hugr", + "type": "object" + }, "StringArg": { "additionalProperties": false, "properties": { @@ -1665,6 +1913,59 @@ "title": "TypeBound", "type": "string" }, + "TypeDef": { + "properties": { + "extension": { + "title": "Extension", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + }, + "params": { + "items": { + "$ref": "#/$defs/TypeParam" + }, + "title": "Params", + "type": "array" + }, + "bound": { + "$ref": "#/$defs/TypeDefBound" + } + }, + "required": [ + "extension", + "name", + "description", + "params", + "bound" + ], + "title": "TypeDef", + "type": "object" + }, + "TypeDefBound": { + "discriminator": { + "mapping": { + "Explicit": "#/$defs/ExplicitBound", + "FromParams": "#/$defs/FromParamsBound" + }, + "propertyName": "b" + }, + "oneOf": [ + { + "$ref": "#/$defs/ExplicitBound" + }, + { + "$ref": "#/$defs/FromParamsBound" + } + ], + "title": "TypeDefBound" + }, "TypeParam": { "description": "A type parameter.", "discriminator": { @@ -1832,7 +2133,7 @@ "description": "A constant Value.", "discriminator": { "mapping": { - "Extension": "#/$defs/ExtensionValue", + "Extension": "#/$defs/hugr__serialization__ops__ExtensionValue", "Function": "#/$defs/FunctionValue", "Sum": "#/$defs/SumValue", "Tuple": "#/$defs/TupleValue" @@ -1841,7 +2142,7 @@ }, "oneOf": [ { - "$ref": "#/$defs/ExtensionValue" + "$ref": "#/$defs/hugr__serialization__ops__ExtensionValue" }, { "$ref": "#/$defs/FunctionValue" @@ -1912,115 +2213,64 @@ ], "title": "VariableArg", "type": "object" - } - }, - "additionalProperties": false, - "description": "A serializable representation of a Hugr.", - "properties": { - "version": { - "description": "Serialisation Schema Version", - "title": "Version", - "type": "string" }, - "nodes": { - "items": { - "$ref": "#/$defs/OpType" - }, - "title": "Nodes", - "type": "array" - }, - "edges": { - "items": { - "maxItems": 2, - "minItems": 2, - "prefixItems": [ - { - "maxItems": 2, - "minItems": 2, - "prefixItems": [ - { - "type": "integer" - }, - { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ] - } - ], - "type": "array" - }, - { - "maxItems": 2, - "minItems": 2, - "prefixItems": [ - { - "type": "integer" - }, - { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ] - } - ], - "type": "array" - } - ], - "type": "array" + "hugr__serialization__extension__ExtensionValue": { + "properties": { + "extension": { + "title": "Extension", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "typed_value": { + "$ref": "#/$defs/Value" + } }, - "title": "Edges", - "type": "array" + "required": [ + "extension", + "name", + "typed_value" + ], + "title": "ExtensionValue", + "type": "object" }, - "metadata": { - "anyOf": [ - { + "hugr__serialization__ops__ExtensionValue": { + "additionalProperties": false, + "description": "An extension constant value, that can check it is of a given [CustomType].", + "properties": { + "v": { + "const": "Extension", + "default": "Extension", + "enum": [ + "Extension" + ], + "title": "ValueTag", + "type": "string" + }, + "extensions": { "items": { - "anyOf": [ - { - "type": "object" - }, - { - "type": "null" - } - ] + "type": "string" }, + "title": "Extensions", "type": "array" }, - { - "type": "null" - } - ], - "default": null, - "title": "Metadata" - }, - "encoder": { - "anyOf": [ - { - "type": "string" + "typ": { + "$ref": "#/$defs/Type" }, - { - "type": "null" + "value": { + "$ref": "#/$defs/CustomConst" } + }, + "required": [ + "extensions", + "typ", + "value" ], - "default": null, - "description": "The name of the encoder used to generate the Hugr.", - "title": "Encoder" + "title": "ExtensionValue", + "type": "object" } }, - "required": [ - "version", - "nodes", - "edges" - ], - "title": "Hugr", - "type": "object" + "title": "HUGR schema" } \ No newline at end of file diff --git a/specification/schema/testing_hugr_schema_live.json b/specification/schema/testing_hugr_schema_live.json index b64d79048..632a2c083 100644 --- a/specification/schema/testing_hugr_schema_live.json +++ b/specification/schema/testing_hugr_schema_live.json @@ -560,39 +560,76 @@ "title": "ExitBlock", "type": "object" }, - "ExtensionValue": { - "additionalProperties": true, - "description": "An extension constant value, that can check it is of a given [CustomType].", + "ExplicitBound": { "properties": { - "v": { - "const": "Extension", - "default": "Extension", + "b": { + "const": "Explicit", + "default": "Explicit", "enum": [ - "Extension" + "Explicit" ], - "title": "ValueTag", + "title": "B", "type": "string" }, - "extensions": { + "bound": { + "$ref": "#/$defs/TypeBound" + } + }, + "required": [ + "bound" + ], + "title": "ExplicitBound", + "type": "object" + }, + "Extension": { + "properties": { + "version": { + "title": "Version", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "extension_reqs": { "items": { "type": "string" }, - "title": "Extensions", - "type": "array" + "title": "Extension Reqs", + "type": "array", + "uniqueItems": true }, - "typ": { - "$ref": "#/$defs/Type" + "types": { + "additionalProperties": { + "$ref": "#/$defs/TypeDef" + }, + "title": "Types", + "type": "object" }, - "value": { - "$ref": "#/$defs/CustomConst" + "values": { + "additionalProperties": { + "$ref": "#/$defs/hugr__serialization__extension__ExtensionValue" + }, + "title": "Values", + "type": "object" + }, + "operations": { + "additionalProperties": { + "$ref": "#/$defs/OpDef" + }, + "title": "Operations", + "type": "object" } }, "required": [ - "extensions", - "typ", - "value" + "version", + "name", + "extension_reqs", + "types", + "values", + "operations" ], - "title": "ExtensionValue", + "title": "Extension", "type": "object" }, "ExtensionsArg": { @@ -638,7 +675,6 @@ "type": "object" }, "FixedHugr": { - "additionalProperties": true, "properties": { "extensions": { "items": { @@ -658,6 +694,31 @@ "title": "FixedHugr", "type": "object" }, + "FromParamsBound": { + "properties": { + "b": { + "const": "FromParams", + "default": "FromParams", + "enum": [ + "FromParams" + ], + "title": "B", + "type": "string" + }, + "indices": { + "items": { + "type": "integer" + }, + "title": "Indices", + "type": "array" + } + }, + "required": [ + "indices" + ], + "title": "FromParamsBound", + "type": "object" + }, "FuncDecl": { "additionalProperties": true, "description": "External function declaration, linked at runtime.", @@ -1070,7 +1131,6 @@ "type": "object" }, "OpDef": { - "additionalProperties": true, "description": "Serializable definition for dynamically loaded operations.", "properties": { "extension": { @@ -1593,6 +1653,85 @@ "title": "TailLoop", "type": "object" }, + "TestingHugr": { + "additionalProperties": true, + "description": "A serializable representation of a Hugr Type, SumType, PolyFuncType,\nValue, OpType. Intended for testing only.", + "properties": { + "version": { + "description": "Serialisation Schema Version", + "title": "Version", + "type": "string" + }, + "typ": { + "anyOf": [ + { + "$ref": "#/$defs/Type" + }, + { + "type": "null" + } + ], + "default": null + }, + "sum_type": { + "anyOf": [ + { + "$ref": "#/$defs/SumType" + }, + { + "type": "null" + } + ], + "default": null + }, + "poly_func_type": { + "anyOf": [ + { + "$ref": "#/$defs/PolyFuncType" + }, + { + "type": "null" + } + ], + "default": null + }, + "value": { + "anyOf": [ + { + "$ref": "#/$defs/Value" + }, + { + "type": "null" + } + ], + "default": null + }, + "optype": { + "anyOf": [ + { + "$ref": "#/$defs/OpType" + }, + { + "type": "null" + } + ], + "default": null + }, + "op_def": { + "anyOf": [ + { + "$ref": "#/$defs/OpDef" + }, + { + "type": "null" + } + ], + "default": null + } + }, + "title": "TestingHugr", + "type": "object" + }, "TupleParam": { "additionalProperties": true, "properties": { @@ -1742,6 +1881,59 @@ "title": "TypeBound", "type": "string" }, + "TypeDef": { + "properties": { + "extension": { + "title": "Extension", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + }, + "params": { + "items": { + "$ref": "#/$defs/TypeParam" + }, + "title": "Params", + "type": "array" + }, + "bound": { + "$ref": "#/$defs/TypeDefBound" + } + }, + "required": [ + "extension", + "name", + "description", + "params", + "bound" + ], + "title": "TypeDef", + "type": "object" + }, + "TypeDefBound": { + "discriminator": { + "mapping": { + "Explicit": "#/$defs/ExplicitBound", + "FromParams": "#/$defs/FromParamsBound" + }, + "propertyName": "b" + }, + "oneOf": [ + { + "$ref": "#/$defs/ExplicitBound" + }, + { + "$ref": "#/$defs/FromParamsBound" + } + ], + "title": "TypeDefBound" + }, "TypeParam": { "description": "A type parameter.", "discriminator": { @@ -1909,7 +2101,7 @@ "description": "A constant Value.", "discriminator": { "mapping": { - "Extension": "#/$defs/ExtensionValue", + "Extension": "#/$defs/hugr__serialization__ops__ExtensionValue", "Function": "#/$defs/FunctionValue", "Sum": "#/$defs/SumValue", "Tuple": "#/$defs/TupleValue" @@ -1918,7 +2110,7 @@ }, "oneOf": [ { - "$ref": "#/$defs/ExtensionValue" + "$ref": "#/$defs/hugr__serialization__ops__ExtensionValue" }, { "$ref": "#/$defs/FunctionValue" @@ -1989,83 +2181,64 @@ ], "title": "VariableArg", "type": "object" - } - }, - "additionalProperties": true, - "description": "A serializable representation of a Hugr Type, SumType, PolyFuncType,\nValue, OpType. Intended for testing only.", - "properties": { - "version": { - "description": "Serialisation Schema Version", - "title": "Version", - "type": "string" }, - "typ": { - "anyOf": [ - { - "$ref": "#/$defs/Type" - }, - { - "type": "null" - } - ], - "default": null - }, - "sum_type": { - "anyOf": [ - { - "$ref": "#/$defs/SumType" + "hugr__serialization__extension__ExtensionValue": { + "properties": { + "extension": { + "title": "Extension", + "type": "string" }, - { - "type": "null" - } - ], - "default": null - }, - "poly_func_type": { - "anyOf": [ - { - "$ref": "#/$defs/PolyFuncType" + "name": { + "title": "Name", + "type": "string" }, - { - "type": "null" - } - ], - "default": null - }, - "value": { - "anyOf": [ - { + "typed_value": { "$ref": "#/$defs/Value" - }, - { - "type": "null" } + }, + "required": [ + "extension", + "name", + "typed_value" ], - "default": null + "title": "ExtensionValue", + "type": "object" }, - "optype": { - "anyOf": [ - { - "$ref": "#/$defs/OpType" + "hugr__serialization__ops__ExtensionValue": { + "additionalProperties": true, + "description": "An extension constant value, that can check it is of a given [CustomType].", + "properties": { + "v": { + "const": "Extension", + "default": "Extension", + "enum": [ + "Extension" + ], + "title": "ValueTag", + "type": "string" }, - { - "type": "null" - } - ], - "default": null - }, - "op_def": { - "anyOf": [ - { - "$ref": "#/$defs/OpDef" + "extensions": { + "items": { + "type": "string" + }, + "title": "Extensions", + "type": "array" }, - { - "type": "null" + "typ": { + "$ref": "#/$defs/Type" + }, + "value": { + "$ref": "#/$defs/CustomConst" } + }, + "required": [ + "extensions", + "typ", + "value" ], - "default": null + "title": "ExtensionValue", + "type": "object" } }, - "title": "TestingHugr", - "type": "object" + "title": "HUGR schema" } \ No newline at end of file diff --git a/specification/schema/testing_hugr_schema_strict_live.json b/specification/schema/testing_hugr_schema_strict_live.json index 0d07bf374..9fa206355 100644 --- a/specification/schema/testing_hugr_schema_strict_live.json +++ b/specification/schema/testing_hugr_schema_strict_live.json @@ -560,39 +560,76 @@ "title": "ExitBlock", "type": "object" }, - "ExtensionValue": { - "additionalProperties": false, - "description": "An extension constant value, that can check it is of a given [CustomType].", + "ExplicitBound": { "properties": { - "v": { - "const": "Extension", - "default": "Extension", + "b": { + "const": "Explicit", + "default": "Explicit", "enum": [ - "Extension" + "Explicit" ], - "title": "ValueTag", + "title": "B", "type": "string" }, - "extensions": { + "bound": { + "$ref": "#/$defs/TypeBound" + } + }, + "required": [ + "bound" + ], + "title": "ExplicitBound", + "type": "object" + }, + "Extension": { + "properties": { + "version": { + "title": "Version", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "extension_reqs": { "items": { "type": "string" }, - "title": "Extensions", - "type": "array" + "title": "Extension Reqs", + "type": "array", + "uniqueItems": true }, - "typ": { - "$ref": "#/$defs/Type" + "types": { + "additionalProperties": { + "$ref": "#/$defs/TypeDef" + }, + "title": "Types", + "type": "object" }, - "value": { - "$ref": "#/$defs/CustomConst" + "values": { + "additionalProperties": { + "$ref": "#/$defs/hugr__serialization__extension__ExtensionValue" + }, + "title": "Values", + "type": "object" + }, + "operations": { + "additionalProperties": { + "$ref": "#/$defs/OpDef" + }, + "title": "Operations", + "type": "object" } }, "required": [ - "extensions", - "typ", - "value" + "version", + "name", + "extension_reqs", + "types", + "values", + "operations" ], - "title": "ExtensionValue", + "title": "Extension", "type": "object" }, "ExtensionsArg": { @@ -638,7 +675,6 @@ "type": "object" }, "FixedHugr": { - "additionalProperties": false, "properties": { "extensions": { "items": { @@ -658,6 +694,31 @@ "title": "FixedHugr", "type": "object" }, + "FromParamsBound": { + "properties": { + "b": { + "const": "FromParams", + "default": "FromParams", + "enum": [ + "FromParams" + ], + "title": "B", + "type": "string" + }, + "indices": { + "items": { + "type": "integer" + }, + "title": "Indices", + "type": "array" + } + }, + "required": [ + "indices" + ], + "title": "FromParamsBound", + "type": "object" + }, "FuncDecl": { "additionalProperties": false, "description": "External function declaration, linked at runtime.", @@ -1070,7 +1131,6 @@ "type": "object" }, "OpDef": { - "additionalProperties": false, "description": "Serializable definition for dynamically loaded operations.", "properties": { "extension": { @@ -1593,6 +1653,85 @@ "title": "TailLoop", "type": "object" }, + "TestingHugr": { + "additionalProperties": false, + "description": "A serializable representation of a Hugr Type, SumType, PolyFuncType,\nValue, OpType. Intended for testing only.", + "properties": { + "version": { + "description": "Serialisation Schema Version", + "title": "Version", + "type": "string" + }, + "typ": { + "anyOf": [ + { + "$ref": "#/$defs/Type" + }, + { + "type": "null" + } + ], + "default": null + }, + "sum_type": { + "anyOf": [ + { + "$ref": "#/$defs/SumType" + }, + { + "type": "null" + } + ], + "default": null + }, + "poly_func_type": { + "anyOf": [ + { + "$ref": "#/$defs/PolyFuncType" + }, + { + "type": "null" + } + ], + "default": null + }, + "value": { + "anyOf": [ + { + "$ref": "#/$defs/Value" + }, + { + "type": "null" + } + ], + "default": null + }, + "optype": { + "anyOf": [ + { + "$ref": "#/$defs/OpType" + }, + { + "type": "null" + } + ], + "default": null + }, + "op_def": { + "anyOf": [ + { + "$ref": "#/$defs/OpDef" + }, + { + "type": "null" + } + ], + "default": null + } + }, + "title": "TestingHugr", + "type": "object" + }, "TupleParam": { "additionalProperties": false, "properties": { @@ -1742,6 +1881,59 @@ "title": "TypeBound", "type": "string" }, + "TypeDef": { + "properties": { + "extension": { + "title": "Extension", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + }, + "params": { + "items": { + "$ref": "#/$defs/TypeParam" + }, + "title": "Params", + "type": "array" + }, + "bound": { + "$ref": "#/$defs/TypeDefBound" + } + }, + "required": [ + "extension", + "name", + "description", + "params", + "bound" + ], + "title": "TypeDef", + "type": "object" + }, + "TypeDefBound": { + "discriminator": { + "mapping": { + "Explicit": "#/$defs/ExplicitBound", + "FromParams": "#/$defs/FromParamsBound" + }, + "propertyName": "b" + }, + "oneOf": [ + { + "$ref": "#/$defs/ExplicitBound" + }, + { + "$ref": "#/$defs/FromParamsBound" + } + ], + "title": "TypeDefBound" + }, "TypeParam": { "description": "A type parameter.", "discriminator": { @@ -1909,7 +2101,7 @@ "description": "A constant Value.", "discriminator": { "mapping": { - "Extension": "#/$defs/ExtensionValue", + "Extension": "#/$defs/hugr__serialization__ops__ExtensionValue", "Function": "#/$defs/FunctionValue", "Sum": "#/$defs/SumValue", "Tuple": "#/$defs/TupleValue" @@ -1918,7 +2110,7 @@ }, "oneOf": [ { - "$ref": "#/$defs/ExtensionValue" + "$ref": "#/$defs/hugr__serialization__ops__ExtensionValue" }, { "$ref": "#/$defs/FunctionValue" @@ -1989,83 +2181,64 @@ ], "title": "VariableArg", "type": "object" - } - }, - "additionalProperties": false, - "description": "A serializable representation of a Hugr Type, SumType, PolyFuncType,\nValue, OpType. Intended for testing only.", - "properties": { - "version": { - "description": "Serialisation Schema Version", - "title": "Version", - "type": "string" }, - "typ": { - "anyOf": [ - { - "$ref": "#/$defs/Type" - }, - { - "type": "null" - } - ], - "default": null - }, - "sum_type": { - "anyOf": [ - { - "$ref": "#/$defs/SumType" + "hugr__serialization__extension__ExtensionValue": { + "properties": { + "extension": { + "title": "Extension", + "type": "string" }, - { - "type": "null" - } - ], - "default": null - }, - "poly_func_type": { - "anyOf": [ - { - "$ref": "#/$defs/PolyFuncType" + "name": { + "title": "Name", + "type": "string" }, - { - "type": "null" - } - ], - "default": null - }, - "value": { - "anyOf": [ - { + "typed_value": { "$ref": "#/$defs/Value" - }, - { - "type": "null" } + }, + "required": [ + "extension", + "name", + "typed_value" ], - "default": null + "title": "ExtensionValue", + "type": "object" }, - "optype": { - "anyOf": [ - { - "$ref": "#/$defs/OpType" + "hugr__serialization__ops__ExtensionValue": { + "additionalProperties": false, + "description": "An extension constant value, that can check it is of a given [CustomType].", + "properties": { + "v": { + "const": "Extension", + "default": "Extension", + "enum": [ + "Extension" + ], + "title": "ValueTag", + "type": "string" }, - { - "type": "null" - } - ], - "default": null - }, - "op_def": { - "anyOf": [ - { - "$ref": "#/$defs/OpDef" + "extensions": { + "items": { + "type": "string" + }, + "title": "Extensions", + "type": "array" }, - { - "type": "null" + "typ": { + "$ref": "#/$defs/Type" + }, + "value": { + "$ref": "#/$defs/CustomConst" } + }, + "required": [ + "extensions", + "typ", + "value" ], - "default": null + "title": "ExtensionValue", + "type": "object" } }, - "title": "TestingHugr", - "type": "object" + "title": "HUGR schema" } \ No newline at end of file From f175105c9f8883d6422ebe1bca241a60522b349b Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Fri, 26 Jul 2024 17:55:34 +0100 Subject: [PATCH 04/19] feat!: Use custom serialization for SignatureFunc. New "binary" field in serialisation may indicate that binary validation/computation functions are expected. --- hugr-core/src/extension/op_def.rs | 75 ++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/hugr-core/src/extension/op_def.rs b/hugr-core/src/extension/op_def.rs index 2b628d820..5092f8669 100644 --- a/hugr-core/src/extension/op_def.rs +++ b/hugr-core/src/extension/op_def.rs @@ -117,12 +117,9 @@ pub trait CustomLowerFunc: Send + Sync { /// Encode a signature as `PolyFuncTypeRV` but optionally allow validating type /// arguments via a custom binary. The binary cannot be serialized so will be /// lost over a serialization round-trip. -#[derive(serde::Deserialize, serde::Serialize)] pub struct CustomValidator { - #[serde(flatten)] poly_func: PolyFuncTypeRV, - #[serde(skip)] - pub(crate) validate: Box, + pub(crate) validate: Option>, } impl CustomValidator { @@ -142,26 +139,74 @@ impl CustomValidator { ) -> Self { Self { poly_func: poly_func.into(), - validate: Box::new(validate), + validate: Some(Box::new(validate)), } } } /// The two ways in which an OpDef may compute the Signature of each operation node. -#[derive(serde::Deserialize, serde::Serialize)] pub enum SignatureFunc { // Note: except for serialization, we could have type schemes just implement the same // CustomSignatureFunc trait too, and replace this enum with Box. // However instead we treat all CustomFunc's as non-serializable. /// A PolyFuncType (polymorphic function type), with optional custom /// validation for provided type arguments, - #[serde(rename = "signature")] PolyFuncType(CustomValidator), - #[serde(skip)] /// A custom binary which computes a polymorphic function type given values /// for its static type parameters. CustomFunc(Box), + /// Declaration specified a custom binary but it was not provided. + MissingCustomFunc, +} + +mod serialize_signature_func { + use serde::{Deserialize, Serialize}; + + use super::{PolyFuncTypeRV, SignatureFunc}; + #[derive(serde::Deserialize, serde::Serialize)] + struct SerSignatureFunc { + signature: Option, + binary: bool, + } + + pub(super) fn serialize( + value: &super::SignatureFunc, + serializer: S, + ) -> Result + where + S: serde::Serializer, + { + match value { + SignatureFunc::PolyFuncType(custom) => SerSignatureFunc { + signature: Some(custom.poly_func.clone()), + binary: custom.validate.is_some(), + }, + SignatureFunc::CustomFunc(_) => SerSignatureFunc { + signature: None, + binary: true, + }, + SignatureFunc::MissingCustomFunc => SerSignatureFunc { + signature: None, + binary: false, + }, + } + .serialize(serializer) + } + + pub(super) fn deserialize<'de, D>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let SerSignatureFunc { signature, .. } = SerSignatureFunc::deserialize(deserializer)?; + Ok(if let Some(sig) = signature { + sig.into() + } else { + SignatureFunc::MissingCustomFunc + }) + } } + +#[derive(PartialEq, Eq, Debug)] struct NoValidate; impl ValidateTypeArgs for NoValidate { fn validate<'o, 'a: 'o>( @@ -221,6 +266,7 @@ impl SignatureFunc { match self { SignatureFunc::PolyFuncType(ts) => ts.poly_func.params(), SignatureFunc::CustomFunc(func) => func.static_params(), + SignatureFunc::MissingCustomFunc => panic!("Missing signature function."), } } @@ -244,7 +290,11 @@ impl SignatureFunc { let temp: PolyFuncTypeRV; // to keep alive let (pf, args) = match &self { SignatureFunc::PolyFuncType(custom) => { - custom.validate.validate(args, def, exts)?; + custom + .validate + .as_ref() + .unwrap_or(&Default::default()) + .validate(args, def, exts)?; (&custom.poly_func, args) } SignatureFunc::CustomFunc(func) => { @@ -255,6 +305,7 @@ impl SignatureFunc { temp = func.compute_signature(static_args, def, exts)?; (&temp, other_args) } + SignatureFunc::MissingCustomFunc => panic!("Missing signature function."), }; let mut res = pf.instantiate(args, exts)?; @@ -270,6 +321,7 @@ impl Debug for SignatureFunc { match self { Self::PolyFuncType(ts) => ts.poly_func.fmt(f), Self::CustomFunc { .. } => f.write_str(""), + Self::MissingCustomFunc => f.write_str(""), } } } @@ -321,7 +373,7 @@ pub struct OpDef { #[serde(default, skip_serializing_if = "HashMap::is_empty")] misc: HashMap, - #[serde(flatten)] + #[serde(with = "serialize_signature_func", flatten)] signature_func: SignatureFunc, // Some operations cannot lower themselves and tools that do not understand them // can only treat them as opaque/black-box ops. @@ -355,6 +407,7 @@ impl OpDef { temp = custom.compute_signature(static_args, self, exts)?; (&temp, other_args) } + SignatureFunc::MissingCustomFunc => panic!("Missing signature function."), }; args.iter() .try_for_each(|ta| ta.validate(exts, var_decls))?; @@ -562,7 +615,7 @@ pub(super) mod test { validate: _, }) => Some(poly_func.clone()), // This is ruled out by `new()` but leave it here for later. - SignatureFunc::CustomFunc(_) => None, + SignatureFunc::CustomFunc(_) | SignatureFunc::MissingCustomFunc => None, }; let get_lower_funcs = |lfs: &Vec| { From c5ab7d0f84b4fd51a0a5e38fb6f4a6e986988fd8 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Mon, 29 Jul 2024 10:02:30 +0100 Subject: [PATCH 05/19] skip lower_funcs if empty --- hugr-core/src/extension/op_def.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/hugr-core/src/extension/op_def.rs b/hugr-core/src/extension/op_def.rs index 5092f8669..9f50c8b6a 100644 --- a/hugr-core/src/extension/op_def.rs +++ b/hugr-core/src/extension/op_def.rs @@ -377,6 +377,7 @@ pub struct OpDef { signature_func: SignatureFunc, // Some operations cannot lower themselves and tools that do not understand them // can only treat them as opaque/black-box ops. + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub(crate) lower_funcs: Vec, /// Operations can optionally implement [`ConstFold`] to implement constant folding. From 0f3ec3ed85bc86a52fb743426139c5e0953fda58 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Mon, 29 Jul 2024 11:00:05 +0100 Subject: [PATCH 06/19] error rather than panic on missing function --- hugr-core/src/extension.rs | 5 +++++ hugr-core/src/extension/op_def.rs | 24 ++++++++++++------------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/hugr-core/src/extension.rs b/hugr-core/src/extension.rs index 9d8dd0570..344cd926e 100644 --- a/hugr-core/src/extension.rs +++ b/hugr-core/src/extension.rs @@ -205,6 +205,11 @@ pub enum SignatureError { cached: Signature, expected: Signature, }, + + /// Extension declaration specifies a binary compute signature function, but none + /// was loaded. + #[error("Binary compute signature function not loaded.")] + MissingComputeFunc, } /// Concrete instantiations of types and operations defined in extensions. diff --git a/hugr-core/src/extension/op_def.rs b/hugr-core/src/extension/op_def.rs index 9f50c8b6a..950064db2 100644 --- a/hugr-core/src/extension/op_def.rs +++ b/hugr-core/src/extension/op_def.rs @@ -156,7 +156,7 @@ pub enum SignatureFunc { /// for its static type parameters. CustomFunc(Box), /// Declaration specified a custom binary but it was not provided. - MissingCustomFunc, + MissingComputeFunc, } mod serialize_signature_func { @@ -185,7 +185,7 @@ mod serialize_signature_func { signature: None, binary: true, }, - SignatureFunc::MissingCustomFunc => SerSignatureFunc { + SignatureFunc::MissingComputeFunc => SerSignatureFunc { signature: None, binary: false, }, @@ -201,7 +201,7 @@ mod serialize_signature_func { Ok(if let Some(sig) = signature { sig.into() } else { - SignatureFunc::MissingCustomFunc + SignatureFunc::MissingComputeFunc }) } } @@ -262,12 +262,12 @@ impl From for SignatureFunc { } impl SignatureFunc { - fn static_params(&self) -> &[TypeParam] { - match self { + fn static_params(&self) -> Result<&[TypeParam], SignatureError> { + Ok(match self { SignatureFunc::PolyFuncType(ts) => ts.poly_func.params(), SignatureFunc::CustomFunc(func) => func.static_params(), - SignatureFunc::MissingCustomFunc => panic!("Missing signature function."), - } + SignatureFunc::MissingComputeFunc => return Err(SignatureError::MissingComputeFunc), + }) } /// Compute the concrete signature ([FuncValueType]). @@ -305,7 +305,7 @@ impl SignatureFunc { temp = func.compute_signature(static_args, def, exts)?; (&temp, other_args) } - SignatureFunc::MissingCustomFunc => panic!("Missing signature function."), + SignatureFunc::MissingComputeFunc => return Err(SignatureError::MissingComputeFunc), }; let mut res = pf.instantiate(args, exts)?; @@ -321,7 +321,7 @@ impl Debug for SignatureFunc { match self { Self::PolyFuncType(ts) => ts.poly_func.fmt(f), Self::CustomFunc { .. } => f.write_str(""), - Self::MissingCustomFunc => f.write_str(""), + Self::MissingComputeFunc => f.write_str(""), } } } @@ -408,7 +408,7 @@ impl OpDef { temp = custom.compute_signature(static_args, self, exts)?; (&temp, other_args) } - SignatureFunc::MissingCustomFunc => panic!("Missing signature function."), + SignatureFunc::MissingComputeFunc => return Err(SignatureError::MissingComputeFunc), }; args.iter() .try_for_each(|ta| ta.validate(exts, var_decls))?; @@ -463,7 +463,7 @@ impl OpDef { } /// Returns a reference to the params of this [`OpDef`]. - pub fn params(&self) -> &[TypeParam] { + pub fn params(&self) -> Result<&[TypeParam], SignatureError> { self.signature_func.static_params() } @@ -616,7 +616,7 @@ pub(super) mod test { validate: _, }) => Some(poly_func.clone()), // This is ruled out by `new()` but leave it here for later. - SignatureFunc::CustomFunc(_) | SignatureFunc::MissingCustomFunc => None, + SignatureFunc::CustomFunc(_) | SignatureFunc::MissingComputeFunc => None, }; let get_lower_funcs = |lfs: &Vec| { From 6b923d8f21e01b4203669d7215d36ff5930ff82a Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Mon, 29 Jul 2024 11:06:31 +0100 Subject: [PATCH 07/19] add some explanation comments --- hugr-core/src/extension/op_def.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hugr-core/src/extension/op_def.rs b/hugr-core/src/extension/op_def.rs index 950064db2..e22ea5db1 100644 --- a/hugr-core/src/extension/op_def.rs +++ b/hugr-core/src/extension/op_def.rs @@ -165,7 +165,11 @@ mod serialize_signature_func { use super::{PolyFuncTypeRV, SignatureFunc}; #[derive(serde::Deserialize, serde::Serialize)] struct SerSignatureFunc { + /// If the type scheme is available explicitly, store it. signature: Option, + /// Whether an associated binary function is expected. + /// If `signature` is `None`, a true value here indicates a custom compute function. + /// If `signature` is not `None`, a true value here indicates a custom validation function. binary: bool, } @@ -198,6 +202,8 @@ mod serialize_signature_func { D: serde::Deserializer<'de>, { let SerSignatureFunc { signature, .. } = SerSignatureFunc::deserialize(deserializer)?; + + // TODO for now, an indication of expected custom validation function is ignored. Ok(if let Some(sig) = signature { sig.into() } else { From 6b7c7880eb47b83233d82e17794427ba958e0296 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Mon, 29 Jul 2024 13:51:52 +0100 Subject: [PATCH 08/19] CustomValidator docstrings --- hugr-core/src/extension/op_def.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hugr-core/src/extension/op_def.rs b/hugr-core/src/extension/op_def.rs index e22ea5db1..bd9507580 100644 --- a/hugr-core/src/extension/op_def.rs +++ b/hugr-core/src/extension/op_def.rs @@ -119,6 +119,8 @@ pub trait CustomLowerFunc: Send + Sync { /// lost over a serialization round-trip. pub struct CustomValidator { poly_func: PolyFuncTypeRV, + /// Optional custom function for validating type arguments before returning the signature. + /// If None, no custom validation is performed. pub(crate) validate: Option>, } From bf071dc6beda956cd66054a113387334a1b1bb76 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Mon, 29 Jul 2024 14:09:59 +0100 Subject: [PATCH 09/19] feat: be explicit about missing validation --- hugr-core/src/extension.rs | 5 ++++ hugr-core/src/extension/op_def.rs | 48 ++++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/hugr-core/src/extension.rs b/hugr-core/src/extension.rs index 344cd926e..434e357f6 100644 --- a/hugr-core/src/extension.rs +++ b/hugr-core/src/extension.rs @@ -210,6 +210,11 @@ pub enum SignatureError { /// was loaded. #[error("Binary compute signature function not loaded.")] MissingComputeFunc, + + /// Extension declaration specifies a binary compute signature function, but none + /// was loaded. + #[error("Binary validate signature function not loaded.")] + MissingValidateFunc, } /// Concrete instantiations of types and operations defined in extensions. diff --git a/hugr-core/src/extension/op_def.rs b/hugr-core/src/extension/op_def.rs index bd9507580..91a1c2363 100644 --- a/hugr-core/src/extension/op_def.rs +++ b/hugr-core/src/extension/op_def.rs @@ -154,10 +154,12 @@ pub enum SignatureFunc { /// A PolyFuncType (polymorphic function type), with optional custom /// validation for provided type arguments, PolyFuncType(CustomValidator), + /// Declaration specified a custom validate binary but it was not provided. + MissingValidateFunc(PolyFuncTypeRV), /// A custom binary which computes a polymorphic function type given values /// for its static type parameters. CustomFunc(Box), - /// Declaration specified a custom binary but it was not provided. + /// Declaration specified a custom compute binary but it was not provided. MissingComputeFunc, } @@ -187,6 +189,10 @@ mod serialize_signature_func { signature: Some(custom.poly_func.clone()), binary: custom.validate.is_some(), }, + SignatureFunc::MissingValidateFunc(poly_func) => SerSignatureFunc { + signature: Some(poly_func.clone()), + binary: true, + }, SignatureFunc::CustomFunc(_) => SerSignatureFunc { signature: None, binary: true, @@ -203,14 +209,16 @@ mod serialize_signature_func { where D: serde::Deserializer<'de>, { - let SerSignatureFunc { signature, .. } = SerSignatureFunc::deserialize(deserializer)?; - - // TODO for now, an indication of expected custom validation function is ignored. - Ok(if let Some(sig) = signature { - sig.into() - } else { - SignatureFunc::MissingComputeFunc - }) + let SerSignatureFunc { signature, binary } = SerSignatureFunc::deserialize(deserializer)?; + + match (signature, binary) { + (Some(sig), false) => Ok(sig.into()), + (Some(sig), true) => Ok(SignatureFunc::MissingValidateFunc(sig)), + (None, true) => Ok(SignatureFunc::MissingComputeFunc), + (None, false) => Err(serde::de::Error::custom( + "No signature provided and custom computation not expected.", + )), + } } } @@ -272,12 +280,22 @@ impl From for SignatureFunc { impl SignatureFunc { fn static_params(&self) -> Result<&[TypeParam], SignatureError> { Ok(match self { - SignatureFunc::PolyFuncType(ts) => ts.poly_func.params(), + SignatureFunc::PolyFuncType(CustomValidator { poly_func: ts, .. }) + | SignatureFunc::MissingValidateFunc(ts) => ts.params(), SignatureFunc::CustomFunc(func) => func.static_params(), SignatureFunc::MissingComputeFunc => return Err(SignatureError::MissingComputeFunc), }) } + /// If the signature is missing a custom validation function, ignore and treat as + /// self-contained type scheme (with no custom validation). + pub fn ignore_missing_validation(self) -> Self { + match self { + SignatureFunc::MissingValidateFunc(ts) => CustomValidator::from_polyfunc(ts).into(), + _ => self, + } + } + /// Compute the concrete signature ([FuncValueType]). /// /// # Panics @@ -314,6 +332,9 @@ impl SignatureFunc { (&temp, other_args) } SignatureFunc::MissingComputeFunc => return Err(SignatureError::MissingComputeFunc), + SignatureFunc::MissingValidateFunc(_) => { + return Err(SignatureError::MissingValidateFunc) + } }; let mut res = pf.instantiate(args, exts)?; @@ -330,6 +351,7 @@ impl Debug for SignatureFunc { Self::PolyFuncType(ts) => ts.poly_func.fmt(f), Self::CustomFunc { .. } => f.write_str(""), Self::MissingComputeFunc => f.write_str(""), + Self::MissingValidateFunc(_) => f.write_str(""), } } } @@ -417,6 +439,9 @@ impl OpDef { (&temp, other_args) } SignatureFunc::MissingComputeFunc => return Err(SignatureError::MissingComputeFunc), + SignatureFunc::MissingValidateFunc(_) => { + return Err(SignatureError::MissingValidateFunc) + } }; args.iter() .try_for_each(|ta| ta.validate(exts, var_decls))?; @@ -622,7 +647,8 @@ pub(super) mod test { SignatureFunc::PolyFuncType(CustomValidator { poly_func, validate: _, - }) => Some(poly_func.clone()), + }) + | SignatureFunc::MissingValidateFunc(poly_func) => Some(poly_func.clone()), // This is ruled out by `new()` but leave it here for later. SignatureFunc::CustomFunc(_) | SignatureFunc::MissingComputeFunc => None, }; From 0e3b160a4d9d2d37adc5995ef9c6934612cffe1c Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Mon, 29 Jul 2024 15:52:29 +0100 Subject: [PATCH 10/19] feat: extract serialisation in to module and add test --- hugr-core/src/extension/op_def.rs | 60 +----- .../op_def/serialize_signature_func.rs | 178 ++++++++++++++++++ hugr-core/src/hugr/serialize/test.rs | 12 ++ hugr-core/src/std_extensions.rs | 17 ++ 4 files changed, 208 insertions(+), 59 deletions(-) create mode 100644 hugr-core/src/extension/op_def/serialize_signature_func.rs diff --git a/hugr-core/src/extension/op_def.rs b/hugr-core/src/extension/op_def.rs index 91a1c2363..bf0c07be0 100644 --- a/hugr-core/src/extension/op_def.rs +++ b/hugr-core/src/extension/op_def.rs @@ -13,6 +13,7 @@ use crate::ops::{OpName, OpNameRef}; use crate::types::type_param::{check_type_args, TypeArg, TypeParam}; use crate::types::{FuncValueType, PolyFuncType, PolyFuncTypeRV, Signature}; use crate::Hugr; +mod serialize_signature_func; /// Trait necessary for binary computations of OpDef signature pub trait CustomSignatureFunc: Send + Sync { @@ -163,65 +164,6 @@ pub enum SignatureFunc { MissingComputeFunc, } -mod serialize_signature_func { - use serde::{Deserialize, Serialize}; - - use super::{PolyFuncTypeRV, SignatureFunc}; - #[derive(serde::Deserialize, serde::Serialize)] - struct SerSignatureFunc { - /// If the type scheme is available explicitly, store it. - signature: Option, - /// Whether an associated binary function is expected. - /// If `signature` is `None`, a true value here indicates a custom compute function. - /// If `signature` is not `None`, a true value here indicates a custom validation function. - binary: bool, - } - - pub(super) fn serialize( - value: &super::SignatureFunc, - serializer: S, - ) -> Result - where - S: serde::Serializer, - { - match value { - SignatureFunc::PolyFuncType(custom) => SerSignatureFunc { - signature: Some(custom.poly_func.clone()), - binary: custom.validate.is_some(), - }, - SignatureFunc::MissingValidateFunc(poly_func) => SerSignatureFunc { - signature: Some(poly_func.clone()), - binary: true, - }, - SignatureFunc::CustomFunc(_) => SerSignatureFunc { - signature: None, - binary: true, - }, - SignatureFunc::MissingComputeFunc => SerSignatureFunc { - signature: None, - binary: false, - }, - } - .serialize(serializer) - } - - pub(super) fn deserialize<'de, D>(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let SerSignatureFunc { signature, binary } = SerSignatureFunc::deserialize(deserializer)?; - - match (signature, binary) { - (Some(sig), false) => Ok(sig.into()), - (Some(sig), true) => Ok(SignatureFunc::MissingValidateFunc(sig)), - (None, true) => Ok(SignatureFunc::MissingComputeFunc), - (None, false) => Err(serde::de::Error::custom( - "No signature provided and custom computation not expected.", - )), - } - } -} - #[derive(PartialEq, Eq, Debug)] struct NoValidate; impl ValidateTypeArgs for NoValidate { diff --git a/hugr-core/src/extension/op_def/serialize_signature_func.rs b/hugr-core/src/extension/op_def/serialize_signature_func.rs new file mode 100644 index 000000000..ea8905abf --- /dev/null +++ b/hugr-core/src/extension/op_def/serialize_signature_func.rs @@ -0,0 +1,178 @@ +use serde::{Deserialize, Serialize}; + +use super::{PolyFuncTypeRV, SignatureFunc}; +#[derive(serde::Deserialize, serde::Serialize, PartialEq, Debug, Clone)] +struct SerSignatureFunc { + /// If the type scheme is available explicitly, store it. + signature: Option, + /// Whether an associated binary function is expected. + /// If `signature` is `None`, a true value here indicates a custom compute function. + /// If `signature` is not `None`, a true value here indicates a custom validation function. + binary: bool, +} + +pub(super) fn serialize(value: &super::SignatureFunc, serializer: S) -> Result +where + S: serde::Serializer, +{ + match value { + SignatureFunc::PolyFuncType(custom) => SerSignatureFunc { + signature: Some(custom.poly_func.clone()), + binary: custom.validate.is_some(), + }, + SignatureFunc::MissingValidateFunc(poly_func) => SerSignatureFunc { + signature: Some(poly_func.clone()), + binary: true, + }, + SignatureFunc::CustomFunc(_) | SignatureFunc::MissingComputeFunc => SerSignatureFunc { + signature: None, + binary: true, + }, + } + .serialize(serializer) +} + +pub(super) fn deserialize<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let SerSignatureFunc { signature, binary } = SerSignatureFunc::deserialize(deserializer)?; + + match (signature, binary) { + (Some(sig), false) => Ok(sig.into()), + (Some(sig), true) => Ok(SignatureFunc::MissingValidateFunc(sig)), + (None, true) => Ok(SignatureFunc::MissingComputeFunc), + (None, false) => Err(serde::de::Error::custom( + "No signature provided and custom computation not expected.", + )), + } +} +#[derive(serde::Deserialize, serde::Serialize, Debug)] +/// Wrapper we can derive serde for, to allow round-trip serialization +struct Wrapper { + #[serde( + serialize_with = "serialize", + deserialize_with = "deserialize", + flatten + )] + inner: SignatureFunc, +} + +#[cfg(test)] +mod test { + use cool_asserts::assert_matches; + use serde::de::Error; + + use super::*; + use crate::{ + extension::{op_def::NoValidate, prelude::USIZE_T, CustomSignatureFunc, CustomValidator}, + types::{FuncValueType, Signature, TypeArg}, + }; + // Define test-only conversions via serialization roundtrip + impl TryFrom for SignatureFunc { + type Error = serde_json::Error; + fn try_from(value: SerSignatureFunc) -> Result { + let ser = serde_json::to_value(value).unwrap(); + let w: Wrapper = serde_json::from_value(ser)?; + Ok(w.inner) + } + } + + impl From for SerSignatureFunc { + fn from(value: SignatureFunc) -> Self { + let ser = serde_json::to_value(Wrapper { inner: value }).unwrap(); + serde_json::from_value(ser).unwrap() + } + } + struct CustomSig; + + impl CustomSignatureFunc for CustomSig { + fn compute_signature<'o, 'a: 'o>( + &'a self, + _arg_values: &[TypeArg], + _def: &'o crate::extension::op_def::OpDef, + _extension_registry: &crate::extension::ExtensionRegistry, + ) -> Result { + Ok(Default::default()) + } + + fn static_params(&self) -> &[crate::types::type_param::TypeParam] { + &[] + } + } + #[test] + fn test_serial_sig_func() { + // test round-trip + let sig: FuncValueType = Signature::new_endo(USIZE_T.clone()).into(); + let simple: SignatureFunc = sig.clone().into(); + let ser: SerSignatureFunc = simple.into(); + let expected_ser = SerSignatureFunc { + signature: Some(sig.clone().into()), + binary: false, + }; + + assert_eq!(ser, expected_ser); + let deser = SignatureFunc::try_from(ser).unwrap(); + assert_matches!( deser, + SignatureFunc::PolyFuncType(CustomValidator { + poly_func, + validate, + }) => { + assert_eq!(poly_func, sig.clone().into()); + assert!(validate.is_none()); + }); + + let with_custom: SignatureFunc = + CustomValidator::new_with_validator(sig.clone(), NoValidate).into(); + let ser: SerSignatureFunc = with_custom.into(); + let expected_ser = SerSignatureFunc { + signature: Some(sig.clone().into()), + binary: true, + }; + assert_eq!(ser, expected_ser); + let deser = SignatureFunc::try_from(ser.clone()).unwrap(); + assert_matches!(&deser, + SignatureFunc::MissingValidateFunc(poly_func) => { + assert_eq!(poly_func, &PolyFuncTypeRV::from(sig.clone())); + } + ); + + // re-serializing should give the same result + assert_eq!( + SerSignatureFunc::from(SignatureFunc::try_from(ser).unwrap()), + expected_ser + ); + + let deser_ignored = deser.ignore_missing_validation(); + assert_matches!( + &deser_ignored, + &SignatureFunc::PolyFuncType(CustomValidator { validate: None, .. }) + ); + + let custom: SignatureFunc = CustomSig.into(); + let ser: SerSignatureFunc = custom.into(); + let expected_ser = SerSignatureFunc { + signature: None, + binary: true, + }; + assert_eq!(ser, expected_ser); + + let deser = SignatureFunc::try_from(ser).unwrap(); + assert_matches!(&deser, &SignatureFunc::MissingComputeFunc); + + assert_eq!(SerSignatureFunc::from(deser), expected_ser); + + let bad_ser = SerSignatureFunc { + signature: None, + binary: false, + }; + + let err = SignatureFunc::try_from(bad_ser).unwrap_err(); + + assert_eq!( + err.to_string(), + serde_json::Error::custom("No signature provided and custom computation not expected.") + .to_string() + ); + } +} diff --git a/hugr-core/src/hugr/serialize/test.rs b/hugr-core/src/hugr/serialize/test.rs index e77167951..65b48ce46 100644 --- a/hugr-core/src/hugr/serialize/test.rs +++ b/hugr-core/src/hugr/serialize/test.rs @@ -517,6 +517,18 @@ fn roundtrip_optype(#[case] optype: impl Into + std::fmt::Debug) { }); } +#[test] +// test all standard extension serialisations are valid against scheme +fn std_extensions_valid() { + let std_reg = crate::std_extensions::std_reg(); + for (_, ext) in std_reg.into_iter() { + let val = serde_json::to_value(ext).unwrap(); + NamedSchema::check_schemas(&val, get_schemas(true)); + // check deserialises correctly, can't check equality because of custom binaries. + let _: crate::extension::Extension = serde_json::from_value(val).unwrap(); + } +} + mod proptest { use super::check_testing_roundtrip; use super::{NodeSer, SimpleOpDef}; diff --git a/hugr-core/src/std_extensions.rs b/hugr-core/src/std_extensions.rs index b93534df4..8ce690674 100644 --- a/hugr-core/src/std_extensions.rs +++ b/hugr-core/src/std_extensions.rs @@ -2,7 +2,24 @@ //! //! These may be moved to other crates in the future, or dropped altogether. +use crate::extension::ExtensionRegistry; + pub mod arithmetic; pub mod collections; pub mod logic; pub mod ptr; + +/// Extension registry with all standard extensions and prelude. +pub fn std_reg() -> ExtensionRegistry { + ExtensionRegistry::try_new([ + crate::extension::prelude::PRELUDE.to_owned(), + arithmetic::int_ops::EXTENSION.to_owned(), + arithmetic::int_types::EXTENSION.to_owned(), + arithmetic::conversions::EXTENSION.to_owned(), + arithmetic::float_ops::EXTENSION.to_owned(), + arithmetic::float_types::EXTENSION.to_owned(), + logic::EXTENSION.to_owned(), + ptr::EXTENSION.to_owned(), + ]) + .unwrap() +} From e31839c9689e1213b3649a985be4d087ecc47d4c Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Tue, 30 Jul 2024 09:42:58 +0100 Subject: [PATCH 11/19] simplify `ignore_missing_validation` --- hugr-core/src/extension/op_def.rs | 7 +++---- hugr-core/src/extension/op_def/serialize_signature_func.rs | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/hugr-core/src/extension/op_def.rs b/hugr-core/src/extension/op_def.rs index bf0c07be0..29152c690 100644 --- a/hugr-core/src/extension/op_def.rs +++ b/hugr-core/src/extension/op_def.rs @@ -231,10 +231,9 @@ impl SignatureFunc { /// If the signature is missing a custom validation function, ignore and treat as /// self-contained type scheme (with no custom validation). - pub fn ignore_missing_validation(self) -> Self { - match self { - SignatureFunc::MissingValidateFunc(ts) => CustomValidator::from_polyfunc(ts).into(), - _ => self, + pub fn ignore_missing_validation(&mut self) { + if let SignatureFunc::MissingValidateFunc(ts) = self { + *self = SignatureFunc::PolyFuncType(CustomValidator::from_polyfunc(ts.clone())); } } diff --git a/hugr-core/src/extension/op_def/serialize_signature_func.rs b/hugr-core/src/extension/op_def/serialize_signature_func.rs index ea8905abf..2e87e639f 100644 --- a/hugr-core/src/extension/op_def/serialize_signature_func.rs +++ b/hugr-core/src/extension/op_def/serialize_signature_func.rs @@ -130,7 +130,7 @@ mod test { binary: true, }; assert_eq!(ser, expected_ser); - let deser = SignatureFunc::try_from(ser.clone()).unwrap(); + let mut deser = SignatureFunc::try_from(ser.clone()).unwrap(); assert_matches!(&deser, SignatureFunc::MissingValidateFunc(poly_func) => { assert_eq!(poly_func, &PolyFuncTypeRV::from(sig.clone())); @@ -143,9 +143,9 @@ mod test { expected_ser ); - let deser_ignored = deser.ignore_missing_validation(); + deser.ignore_missing_validation(); assert_matches!( - &deser_ignored, + &deser, &SignatureFunc::PolyFuncType(CustomValidator { validate: None, .. }) ); From a47252a0e65408cdcd43c20c290e3ee2c8fac92f Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Tue, 30 Jul 2024 10:11:36 +0100 Subject: [PATCH 12/19] separate explicit and custom validate variants --- hugr-core/src/extension/op_def.rs | 75 ++++++++----------- .../op_def/serialize_signature_func.rs | 25 +++---- .../src/std_extensions/arithmetic/int_ops.rs | 4 +- 3 files changed, 43 insertions(+), 61 deletions(-) diff --git a/hugr-core/src/extension/op_def.rs b/hugr-core/src/extension/op_def.rs index 29152c690..6e0afdc4a 100644 --- a/hugr-core/src/extension/op_def.rs +++ b/hugr-core/src/extension/op_def.rs @@ -120,47 +120,37 @@ pub trait CustomLowerFunc: Send + Sync { /// lost over a serialization round-trip. pub struct CustomValidator { poly_func: PolyFuncTypeRV, - /// Optional custom function for validating type arguments before returning the signature. - /// If None, no custom validation is performed. - pub(crate) validate: Option>, + //Optional custom function for validating type arguments before returning the signature. + //If None, no custom validation is performed. + pub(crate) validate: Box, } impl CustomValidator { - /// Encode a signature using a `PolyFuncTypeRV` - pub fn from_polyfunc(poly_func: impl Into) -> Self { - Self { - poly_func: poly_func.into(), - validate: Default::default(), - } - } - /// Encode a signature using a `PolyFuncTypeRV`, with a custom function for /// validating type arguments before returning the signature. - pub fn new_with_validator( + pub fn new( poly_func: impl Into, validate: impl ValidateTypeArgs + 'static, ) -> Self { Self { poly_func: poly_func.into(), - validate: Some(Box::new(validate)), + validate: Box::new(validate), } } } -/// The two ways in which an OpDef may compute the Signature of each operation node. +/// The ways in which an OpDef may compute the Signature of each operation node. pub enum SignatureFunc { - // Note: except for serialization, we could have type schemes just implement the same - // CustomSignatureFunc trait too, and replace this enum with Box. - // However instead we treat all CustomFunc's as non-serializable. - /// A PolyFuncType (polymorphic function type), with optional custom - /// validation for provided type arguments, - PolyFuncType(CustomValidator), - /// Declaration specified a custom validate binary but it was not provided. + /// An explicit polymorphic function type. + PolyFuncType(PolyFuncTypeRV), + /// A polymorphic function type with a custom binary for validating type arguments. + CustomValidator(CustomValidator), + /// Serialized declaration specified a custom validate binary but it was not provided. MissingValidateFunc(PolyFuncTypeRV), /// A custom binary which computes a polymorphic function type given values /// for its static type parameters. CustomFunc(Box), - /// Declaration specified a custom compute binary but it was not provided. + /// Serialized declaration specified a custom compute binary but it was not provided. MissingComputeFunc, } @@ -191,38 +181,39 @@ impl From for SignatureFunc { impl From for SignatureFunc { fn from(value: PolyFuncType) -> Self { - Self::PolyFuncType(CustomValidator::from_polyfunc(value)) + Self::PolyFuncType(value.into()) } } impl From for SignatureFunc { fn from(v: PolyFuncTypeRV) -> Self { - Self::PolyFuncType(CustomValidator::from_polyfunc(v)) + Self::PolyFuncType(v) } } impl From for SignatureFunc { fn from(v: FuncValueType) -> Self { - Self::PolyFuncType(CustomValidator::from_polyfunc(v)) + Self::PolyFuncType(v.into()) } } impl From for SignatureFunc { fn from(v: Signature) -> Self { - Self::PolyFuncType(CustomValidator::from_polyfunc(FuncValueType::from(v))) + Self::PolyFuncType(FuncValueType::from(v).into()) } } impl From for SignatureFunc { fn from(v: CustomValidator) -> Self { - Self::PolyFuncType(v) + Self::CustomValidator(v) } } impl SignatureFunc { fn static_params(&self) -> Result<&[TypeParam], SignatureError> { Ok(match self { - SignatureFunc::PolyFuncType(CustomValidator { poly_func: ts, .. }) + SignatureFunc::PolyFuncType(ts) + | SignatureFunc::CustomValidator(CustomValidator { poly_func: ts, .. }) | SignatureFunc::MissingValidateFunc(ts) => ts.params(), SignatureFunc::CustomFunc(func) => func.static_params(), SignatureFunc::MissingComputeFunc => return Err(SignatureError::MissingComputeFunc), @@ -233,7 +224,7 @@ impl SignatureFunc { /// self-contained type scheme (with no custom validation). pub fn ignore_missing_validation(&mut self) { if let SignatureFunc::MissingValidateFunc(ts) = self { - *self = SignatureFunc::PolyFuncType(CustomValidator::from_polyfunc(ts.clone())); + *self = SignatureFunc::PolyFuncType(ts.clone()); } } @@ -256,14 +247,11 @@ impl SignatureFunc { ) -> Result { let temp: PolyFuncTypeRV; // to keep alive let (pf, args) = match &self { - SignatureFunc::PolyFuncType(custom) => { - custom - .validate - .as_ref() - .unwrap_or(&Default::default()) - .validate(args, def, exts)?; + SignatureFunc::CustomValidator(custom) => { + custom.validate.as_ref().validate(args, def, exts)?; (&custom.poly_func, args) } + SignatureFunc::PolyFuncType(ts) => (ts, args), SignatureFunc::CustomFunc(func) => { let static_params = func.static_params(); let (static_args, other_args) = args.split_at(min(static_params.len(), args.len())); @@ -289,7 +277,8 @@ impl SignatureFunc { impl Debug for SignatureFunc { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - Self::PolyFuncType(ts) => ts.poly_func.fmt(f), + Self::CustomValidator(ts) => ts.poly_func.fmt(f), + Self::PolyFuncType(ts) => ts.fmt(f), Self::CustomFunc { .. } => f.write_str(""), Self::MissingComputeFunc => f.write_str(""), Self::MissingValidateFunc(_) => f.write_str(""), @@ -368,7 +357,8 @@ impl OpDef { ) -> Result<(), SignatureError> { let temp: PolyFuncTypeRV; // to keep alive let (pf, args) = match &self.signature_func { - SignatureFunc::PolyFuncType(ts) => (&ts.poly_func, args), + SignatureFunc::CustomValidator(ts) => (&ts.poly_func, args), + SignatureFunc::PolyFuncType(ts) => (ts, args), SignatureFunc::CustomFunc(custom) => { let (static_args, other_args) = args.split_at(min(custom.static_params().len(), args.len())); @@ -444,7 +434,7 @@ impl OpDef { pub(super) fn validate(&self, exts: &ExtensionRegistry) -> Result<(), SignatureError> { // TODO https://github.com/CQCL/hugr/issues/624 validate declared TypeParams // for both type scheme and custom binary - if let SignatureFunc::PolyFuncType(ts) = &self.signature_func { + if let SignatureFunc::CustomValidator(ts) = &self.signature_func { // 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. @@ -585,10 +575,11 @@ pub(super) mod test { // a compile error here. To fix: modify the fields matched on here, // maintaining the lack of `..` and, for each part that is // serializable, ensure we are checking it for equality below. - SignatureFunc::PolyFuncType(CustomValidator { + SignatureFunc::CustomValidator(CustomValidator { poly_func, validate: _, }) + | SignatureFunc::PolyFuncType(poly_func) | SignatureFunc::MissingValidateFunc(poly_func) => Some(poly_func.clone()), // This is ruled out by `new()` but leave it here for later. SignatureFunc::CustomFunc(_) | SignatureFunc::MissingComputeFunc => None, @@ -816,9 +807,7 @@ pub(super) mod test { use crate::{ builder::test::simple_dfg_hugr, - extension::{ - op_def::LowerFunc, CustomValidator, ExtensionId, ExtensionSet, OpDef, SignatureFunc, - }, + extension::{op_def::LowerFunc, ExtensionId, ExtensionSet, OpDef, SignatureFunc}, types::PolyFuncTypeRV, }; @@ -830,7 +819,7 @@ pub(super) mod test { // this is not serialized. When it is, we should generate // examples here . any::() - .prop_map(|x| SignatureFunc::PolyFuncType(CustomValidator::from_polyfunc(x))) + .prop_map(SignatureFunc::PolyFuncType) .boxed() } } diff --git a/hugr-core/src/extension/op_def/serialize_signature_func.rs b/hugr-core/src/extension/op_def/serialize_signature_func.rs index 2e87e639f..03c427de7 100644 --- a/hugr-core/src/extension/op_def/serialize_signature_func.rs +++ b/hugr-core/src/extension/op_def/serialize_signature_func.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use super::{PolyFuncTypeRV, SignatureFunc}; +use super::{CustomValidator, PolyFuncTypeRV, SignatureFunc}; #[derive(serde::Deserialize, serde::Serialize, PartialEq, Debug, Clone)] struct SerSignatureFunc { /// If the type scheme is available explicitly, store it. @@ -16,11 +16,12 @@ where S: serde::Serializer, { match value { - SignatureFunc::PolyFuncType(custom) => SerSignatureFunc { - signature: Some(custom.poly_func.clone()), - binary: custom.validate.is_some(), + SignatureFunc::PolyFuncType(poly) => SerSignatureFunc { + signature: Some(poly.clone()), + binary: false, }, - SignatureFunc::MissingValidateFunc(poly_func) => SerSignatureFunc { + SignatureFunc::CustomValidator(CustomValidator { poly_func, .. }) + | SignatureFunc::MissingValidateFunc(poly_func) => SerSignatureFunc { signature: Some(poly_func.clone()), binary: true, }, @@ -114,16 +115,11 @@ mod test { assert_eq!(ser, expected_ser); let deser = SignatureFunc::try_from(ser).unwrap(); assert_matches!( deser, - SignatureFunc::PolyFuncType(CustomValidator { - poly_func, - validate, - }) => { + SignatureFunc::PolyFuncType(poly_func) => { assert_eq!(poly_func, sig.clone().into()); - assert!(validate.is_none()); }); - let with_custom: SignatureFunc = - CustomValidator::new_with_validator(sig.clone(), NoValidate).into(); + let with_custom: SignatureFunc = CustomValidator::new(sig.clone(), NoValidate).into(); let ser: SerSignatureFunc = with_custom.into(); let expected_ser = SerSignatureFunc { signature: Some(sig.clone().into()), @@ -144,10 +140,7 @@ mod test { ); deser.ignore_missing_validation(); - assert_matches!( - &deser, - &SignatureFunc::PolyFuncType(CustomValidator { validate: None, .. }) - ); + assert_matches!(&deser, &SignatureFunc::PolyFuncType(_)); let custom: SignatureFunc = CustomSig.into(); let ser: SerSignatureFunc = custom.into(); diff --git a/hugr-core/src/std_extensions/arithmetic/int_ops.rs b/hugr-core/src/std_extensions/arithmetic/int_ops.rs index e9bef39b6..0f5584f6b 100644 --- a/hugr-core/src/std_extensions/arithmetic/int_ops.rs +++ b/hugr-core/src/std_extensions/arithmetic/int_ops.rs @@ -114,12 +114,12 @@ impl MakeOpDef for IntOpDef { fn signature(&self) -> SignatureFunc { use IntOpDef::*; match self { - iwiden_s | iwiden_u => CustomValidator::new_with_validator( + iwiden_s | iwiden_u => CustomValidator::new( int_polytype(2, vec![int_tv(0)], vec![int_tv(1)]), IOValidator { f_ge_s: false }, ) .into(), - inarrow_s | inarrow_u => CustomValidator::new_with_validator( + inarrow_s | inarrow_u => CustomValidator::new( int_polytype(2, int_tv(0), sum_ty_with_err(int_tv(1))), IOValidator { f_ge_s: true }, ) From 4cd9af66a9890f11c1eca90a11d431f143d0fac0 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Tue, 30 Jul 2024 10:33:04 +0100 Subject: [PATCH 13/19] fix `CustomValidator` docstrings --- hugr-core/src/extension/op_def.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hugr-core/src/extension/op_def.rs b/hugr-core/src/extension/op_def.rs index 6e0afdc4a..83e629691 100644 --- a/hugr-core/src/extension/op_def.rs +++ b/hugr-core/src/extension/op_def.rs @@ -115,13 +115,12 @@ pub trait CustomLowerFunc: Send + Sync { ) -> Option; } -/// Encode a signature as `PolyFuncTypeRV` but optionally allow validating type +/// Encode a signature as `PolyFuncTypeRV` but allow validating type /// arguments via a custom binary. The binary cannot be serialized so will be /// lost over a serialization round-trip. pub struct CustomValidator { poly_func: PolyFuncTypeRV, - //Optional custom function for validating type arguments before returning the signature. - //If None, no custom validation is performed. + /// Custom function for validating type arguments before returning the signature. pub(crate) validate: Box, } From 21642eaf77ea8f666348a6ff17ff4ef2ff5e43fc Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Tue, 30 Jul 2024 10:55:06 +0100 Subject: [PATCH 14/19] minor review suggestions --- hugr-core/src/extension/op_def.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/hugr-core/src/extension/op_def.rs b/hugr-core/src/extension/op_def.rs index 83e629691..bc429d7ce 100644 --- a/hugr-core/src/extension/op_def.rs +++ b/hugr-core/src/extension/op_def.rs @@ -115,7 +115,7 @@ pub trait CustomLowerFunc: Send + Sync { ) -> Option; } -/// Encode a signature as `PolyFuncTypeRV` but allow validating type +/// Encode a signature as [PolyFuncTypeRV] but with additional validation of type /// arguments via a custom binary. The binary cannot be serialized so will be /// lost over a serialization round-trip. pub struct CustomValidator { @@ -142,7 +142,7 @@ impl CustomValidator { pub enum SignatureFunc { /// An explicit polymorphic function type. PolyFuncType(PolyFuncTypeRV), - /// A polymorphic function type with a custom binary for validating type arguments. + /// A polymorphic function type (like [Self::PolyFuncType] but also with a custom binary for validating type arguments. CustomValidator(CustomValidator), /// Serialized declaration specified a custom validate binary but it was not provided. MissingValidateFunc(PolyFuncTypeRV), @@ -247,7 +247,7 @@ impl SignatureFunc { let temp: PolyFuncTypeRV; // to keep alive let (pf, args) = match &self { SignatureFunc::CustomValidator(custom) => { - custom.validate.as_ref().validate(args, def, exts)?; + custom.validate.validate(args, def, exts)?; (&custom.poly_func, args) } SignatureFunc::PolyFuncType(ts) => (ts, args), @@ -580,7 +580,6 @@ pub(super) mod test { }) | SignatureFunc::PolyFuncType(poly_func) | SignatureFunc::MissingValidateFunc(poly_func) => Some(poly_func.clone()), - // This is ruled out by `new()` but leave it here for later. SignatureFunc::CustomFunc(_) | SignatureFunc::MissingComputeFunc => None, }; From 8bcb85c3bc10431e9bcc92d9ad9b36e2de8634a0 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Tue, 30 Jul 2024 10:58:43 +0100 Subject: [PATCH 15/19] improve tests --- .../op_def/serialize_signature_func.rs | 21 ++++++++++--------- hugr-core/src/hugr/serialize/test.rs | 3 ++- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/hugr-core/src/extension/op_def/serialize_signature_func.rs b/hugr-core/src/extension/op_def/serialize_signature_func.rs index 03c427de7..9b412662a 100644 --- a/hugr-core/src/extension/op_def/serialize_signature_func.rs +++ b/hugr-core/src/extension/op_def/serialize_signature_func.rs @@ -48,16 +48,6 @@ where )), } } -#[derive(serde::Deserialize, serde::Serialize, Debug)] -/// Wrapper we can derive serde for, to allow round-trip serialization -struct Wrapper { - #[serde( - serialize_with = "serialize", - deserialize_with = "deserialize", - flatten - )] - inner: SignatureFunc, -} #[cfg(test)] mod test { @@ -69,6 +59,17 @@ mod test { extension::{op_def::NoValidate, prelude::USIZE_T, CustomSignatureFunc, CustomValidator}, types::{FuncValueType, Signature, TypeArg}, }; + + #[derive(serde::Deserialize, serde::Serialize, Debug)] + /// Wrapper we can derive serde for, to allow round-trip serialization + struct Wrapper { + #[serde( + serialize_with = "serialize", + deserialize_with = "deserialize", + flatten + )] + inner: SignatureFunc, + } // Define test-only conversions via serialization roundtrip impl TryFrom for SignatureFunc { type Error = serde_json::Error; diff --git a/hugr-core/src/hugr/serialize/test.rs b/hugr-core/src/hugr/serialize/test.rs index 65b48ce46..48eeae6ef 100644 --- a/hugr-core/src/hugr/serialize/test.rs +++ b/hugr-core/src/hugr/serialize/test.rs @@ -525,7 +525,8 @@ fn std_extensions_valid() { let val = serde_json::to_value(ext).unwrap(); NamedSchema::check_schemas(&val, get_schemas(true)); // check deserialises correctly, can't check equality because of custom binaries. - let _: crate::extension::Extension = serde_json::from_value(val).unwrap(); + let deser: crate::extension::Extension = serde_json::from_value(val.clone()).unwrap(); + assert_eq!(serde_json::to_value(deser).unwrap(), val); } } From b243b1755177cb398797ed718576c3c0a68b043c Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Tue, 30 Jul 2024 11:02:47 +0100 Subject: [PATCH 16/19] move `NoValidate` to test only --- hugr-core/src/extension/op_def.rs | 19 ------------------- .../op_def/serialize_signature_func.rs | 18 +++++++++++++++++- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/hugr-core/src/extension/op_def.rs b/hugr-core/src/extension/op_def.rs index bc429d7ce..b79822614 100644 --- a/hugr-core/src/extension/op_def.rs +++ b/hugr-core/src/extension/op_def.rs @@ -153,25 +153,6 @@ pub enum SignatureFunc { MissingComputeFunc, } -#[derive(PartialEq, Eq, Debug)] -struct NoValidate; -impl ValidateTypeArgs for NoValidate { - fn validate<'o, 'a: 'o>( - &self, - _arg_values: &[TypeArg], - _def: &'o OpDef, - _extension_registry: &ExtensionRegistry, - ) -> Result<(), SignatureError> { - Ok(()) - } -} - -impl Default for Box { - fn default() -> Self { - Box::new(NoValidate) - } -} - impl From for SignatureFunc { fn from(v: T) -> Self { Self::CustomFunc(Box::new(v)) diff --git a/hugr-core/src/extension/op_def/serialize_signature_func.rs b/hugr-core/src/extension/op_def/serialize_signature_func.rs index 9b412662a..88c8c30de 100644 --- a/hugr-core/src/extension/op_def/serialize_signature_func.rs +++ b/hugr-core/src/extension/op_def/serialize_signature_func.rs @@ -56,7 +56,10 @@ mod test { use super::*; use crate::{ - extension::{op_def::NoValidate, prelude::USIZE_T, CustomSignatureFunc, CustomValidator}, + extension::{ + prelude::USIZE_T, CustomSignatureFunc, CustomValidator, ExtensionRegistry, OpDef, + SignatureError, ValidateTypeArgs, + }, types::{FuncValueType, Signature, TypeArg}, }; @@ -102,6 +105,19 @@ mod test { &[] } } + + struct NoValidate; + impl ValidateTypeArgs for NoValidate { + fn validate<'o, 'a: 'o>( + &self, + _arg_values: &[TypeArg], + _def: &'o OpDef, + _extension_registry: &ExtensionRegistry, + ) -> Result<(), SignatureError> { + Ok(()) + } + } + #[test] fn test_serial_sig_func() { // test round-trip From 7010d0e60b37fe804387fab4f82ac994b569f2d3 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Tue, 30 Jul 2024 11:04:06 +0100 Subject: [PATCH 17/19] remove unused method --- hugr-py/src/hugr/serialization/extension.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/hugr-py/src/hugr/serialization/extension.py b/hugr-py/src/hugr/serialization/extension.py index 7da77a01b..faa750cce 100644 --- a/hugr-py/src/hugr/serialization/extension.py +++ b/hugr-py/src/hugr/serialization/extension.py @@ -76,7 +76,3 @@ class Extension(ConfiguredBaseModel): @classmethod def get_version(cls) -> str: return get_serialisation_version() - - @classmethod - def _pydantic_rebuild(cls, config: pd.ConfigDict | None = None, **kwargs): - pass From ac834de7c5640f22f670d2503ddf3125f2f2c27e Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Tue, 30 Jul 2024 11:08:53 +0100 Subject: [PATCH 18/19] convenience constructors for `TypeDefBound` --- hugr-core/src/extension/prelude.rs | 10 +++++----- hugr-core/src/extension/type_def.rs | 21 +++++++++++++++++++++ hugr-core/src/hugr/validate/test.rs | 4 +--- hugr-core/src/std_extensions/collections.rs | 2 +- hugr-core/src/std_extensions/ptr.rs | 4 +--- hugr-core/src/types/poly_func.rs | 4 +--- 6 files changed, 30 insertions(+), 15 deletions(-) diff --git a/hugr-core/src/extension/prelude.rs b/hugr-core/src/extension/prelude.rs index 31b6431fe..9e827a6e8 100644 --- a/hugr-core/src/extension/prelude.rs +++ b/hugr-core/src/extension/prelude.rs @@ -95,14 +95,14 @@ lazy_static! { TypeName::new_inline("usize"), vec![], "usize".into(), - TypeDefBound::Explicit { bound: crate::types::TypeBound::Copyable }, + TypeDefBound::copyable(), ) .unwrap(); prelude.add_type( STRING_TYPE_NAME, vec![], "string".into(), - TypeDefBound::Explicit { bound: crate::types::TypeBound::Copyable }, + TypeDefBound::copyable(), ) .unwrap(); prelude.add_op( @@ -115,7 +115,7 @@ lazy_static! { TypeName::new_inline("array"), vec![ TypeParam::max_nat(), TypeBound::Any.into()], "array".into(), - TypeDefBound::FromParams { indices: vec![1] }, + TypeDefBound::from_params(vec![1] ), ) .unwrap(); prelude @@ -131,7 +131,7 @@ lazy_static! { TypeName::new_inline("qubit"), vec![], "qubit".into(), - TypeDefBound::Explicit { bound: TypeBound::Any }, + TypeDefBound::any(), ) .unwrap(); prelude @@ -139,7 +139,7 @@ lazy_static! { ERROR_TYPE_NAME, vec![], "Simple opaque error type.".into(), - TypeDefBound::Explicit { bound: TypeBound::Copyable }, + TypeDefBound::copyable(), ) .unwrap(); prelude diff --git a/hugr-core/src/extension/type_def.rs b/hugr-core/src/extension/type_def.rs index 5e79538a3..dca4a630b 100644 --- a/hugr-core/src/extension/type_def.rs +++ b/hugr-core/src/extension/type_def.rs @@ -28,6 +28,27 @@ impl From for TypeDefBound { } } +impl TypeDefBound { + /// Create a new [`TypeDefBound::Explicit`] with the `Any` bound. + pub fn any() -> Self { + TypeDefBound::Explicit { + bound: TypeBound::Any, + } + } + + /// Create a new [`TypeDefBound::Explicit`] with the `Copyable` bound. + pub fn copyable() -> Self { + TypeDefBound::Explicit { + bound: TypeBound::Copyable, + } + } + + /// Create a new [`TypeDefBound::FromParams`] with the given indices. + pub fn from_params(indices: Vec) -> Self { + TypeDefBound::FromParams { indices } + } +} + /// A declaration of an opaque type. /// Note this does not provide any way to create instances /// - typically these are operations also provided by the Extension. diff --git a/hugr-core/src/hugr/validate/test.rs b/hugr-core/src/hugr/validate/test.rs index 6cf56039d..4252b8cd9 100644 --- a/hugr-core/src/hugr/validate/test.rs +++ b/hugr-core/src/hugr/validate/test.rs @@ -370,9 +370,7 @@ fn invalid_types() { "MyContainer".into(), vec![TypeBound::Copyable.into()], "".into(), - TypeDefBound::Explicit { - bound: TypeBound::Any, - }, + TypeDefBound::any(), ) .unwrap(); let reg = ExtensionRegistry::try_new([e, PRELUDE.to_owned()]).unwrap(); diff --git a/hugr-core/src/std_extensions/collections.rs b/hugr-core/src/std_extensions/collections.rs index 77234e5df..3c4677f5c 100644 --- a/hugr-core/src/std_extensions/collections.rs +++ b/hugr-core/src/std_extensions/collections.rs @@ -147,7 +147,7 @@ fn extension() -> Extension { LIST_TYPENAME, vec![TP], "Generic dynamically sized list of type T.".into(), - TypeDefBound::FromParams { indices: vec![0] }, + TypeDefBound::from_params(vec![0]), ) .unwrap(); let list_type_def = extension.get_type(&LIST_TYPENAME).unwrap(); diff --git a/hugr-core/src/std_extensions/ptr.rs b/hugr-core/src/std_extensions/ptr.rs index cd3472b3f..fb5b82d34 100644 --- a/hugr-core/src/std_extensions/ptr.rs +++ b/hugr-core/src/std_extensions/ptr.rs @@ -91,9 +91,7 @@ fn extension() -> Extension { PTR_TYPE_ID, TYPE_PARAMS.into(), "Standard extension pointer type.".into(), - TypeDefBound::Explicit { - bound: TypeBound::Copyable, - }, + TypeDefBound::copyable(), ) .unwrap(); PtrOpDef::load_all_ops(&mut extension).unwrap(); diff --git a/hugr-core/src/types/poly_func.rs b/hugr-core/src/types/poly_func.rs index a8d672364..9581ebc4c 100644 --- a/hugr-core/src/types/poly_func.rs +++ b/hugr-core/src/types/poly_func.rs @@ -318,9 +318,7 @@ pub(crate) mod test { TYPE_NAME, vec![bound.clone()], "".into(), - TypeDefBound::Explicit { - bound: TypeBound::Any, - }, + TypeDefBound::any(), ) .unwrap(); From 9990e782eac2c83c69dd99e8b10cc257ac4962f8 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Tue, 30 Jul 2024 11:14:30 +0100 Subject: [PATCH 19/19] fix schema gen script --- scripts/generate_schema.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/generate_schema.py b/scripts/generate_schema.py index 8a40930c7..2d66cf837 100644 --- a/scripts/generate_schema.py +++ b/scripts/generate_schema.py @@ -24,7 +24,7 @@ def write_schema( out_dir: Path, name_prefix: str, - schema: type[SerialHugr] | type[TestingHugr] | type[Extension], + schema: type[SerialHugr] | type[TestingHugr], config: ConfigDict | None = None, **kwargs, ): @@ -32,9 +32,8 @@ def write_schema( filename = f"{name_prefix}_{version}.json" path = out_dir / filename print(f"Rebuilding model with config: {config}") + schema._pydantic_rebuild(config or ConfigDict(), force=True, **kwargs) schemas = [schema, Extension] - for s in schemas: - s._pydantic_rebuild(config or ConfigDict(), force=True, **kwargs) print(f"Writing schema to {path}") _, top_level_schema = models_json_schema( [(s, "validation") for s in schemas], title="HUGR schema"