Skip to content

refactor(hugrv2)!: WIP+RFC: Unify Type and Term#2785

Closed
acl-cqc wants to merge 97 commits intoacl/no_1type_rowfrom
acl/unify_type_term
Closed

refactor(hugrv2)!: WIP+RFC: Unify Type and Term#2785
acl-cqc wants to merge 97 commits intoacl/no_1type_rowfrom
acl/unify_type_term

Conversation

@acl-cqc
Copy link
Copy Markdown
Contributor

@acl-cqc acl-cqc commented Jan 2, 2026

Follows #2784; closes #2341.

  • Add Term::RuntimeFunction, Term::RuntimeSum and Term::RuntimeExtension, removing Term::Runtime(Type) as well as enum Type (now an alias for Term) and TypeEnum (RIP).
    • These are named so that what were previously Types are now RuntimeXXX's. However this new convention is not consistent with (does not apply to) Term::RuntimeType :-(... so the existing convention, that the name XXXType (e.g. Term::RuntimeType, Term::StringType) means a type of Terms i.e. in the static system, takes priority.
    • I think this nomenclature is confusing and might be better to (a) rename everything XXXType to XXXKind (e.g. Term::StringKind, Term::StaticKind) and (b) then rename everything RuntimeXXX to XXXType (e.g. Term::FunctionType, Term::ExtensionType). Thus, Term::RuntimeType(TypeBound) -> Term::TypeKind(TypeBound)). This will be even more breaking as Rust doesn't have good support for renaming enum variants, note....
    • Note also Term::RuntimeExtension (which would be Term::ExtensionType under renaming proposed above) is planned to become Term::Extensionin the next PR, i.e. able to produce things other than runtime types
  • SumType caches its bound; this is the only case where TypeEnum's cache was useful (as Extensions already cache inside CustomType and Functions are always Copyable) so then we can drop the Type/TypeEnum wrapping
  • Remove the RV hack. This is the big win for now. In some sense it is a step backwards (we lose some Rust static type checking). But RowVars were always a hack, in that they looked like one entry in a list, but were any number of entries. Instead now variables that range over lists are not members of lists, but there is (already) a Term::ListConcat all of whose members are lists; this is a much nicer/more-principled solution.
    • The key here is that TypeRow remains as a Vec<Term> (see below) but TypeRowRV is now a Term: either a Term::List (of single types, including variables), a Term::Variable (a variable ranging over lists of types), or a Term::ListConcat (of sublists each being one of these three, and canonicalized to a mix of the previous two only).
    • See 68fba45 for a way of bringing back some of the ease-of-use/compactness of the RV system using the checking we have implemented in Rust; however, the "improvement" in the user code (/the pain of having to explicitly Term::new_list_concat) was not that great so I have reverted...thoughts?
  • I removed Aliases; I mean I could add them but they are vestigial and not really working anyway (Overhaul or scrap aliases #2558). Or perhaps we should add them in and support properly now we have linking?
  • Significant work has gone into preserving backwards-compatibility for serde/json serialization of types, as IIUC there is no plan for deprecating this (it's required for extension declarations as well as Hugrs). In particular I made much use of serde_as with some new markers (SerType, SerTypeRowRV). This has had some impact, see next two points.
  • Abandon the parametrized FuncTypeBase / PolyFuncTypeBase (instead adding macros for code common to Signature/FuncValueType and PolyFuncType / PolyFuncTypeRV), necessary because FuncValueType needs a #[serde_as(as=SerTypeRowRV)] declaration. However, see next point.
  • NOTE the current state of the PR abuses TypeRow significantly. In general it can store arbitrary Terms (of course), but it serializes into json in the legacy format that only supports Types. Meanwhile TypeRowRV is just Term but places using it tend to use a #[serde_as(as=SerTypeRowRV)] which serializes into json in the legacy format that supports Types and RowVariables.
    • I've given Signature, FuncValueType and SumType checking constructors: ::try_new checks the contents are actually types/lists of types; ::new does the same check and unwraps; ::new_unchecked avoids the check (but will break at JSON-serialization time, and shouldn't really happen anyways).
    • A perhaps-better solution is to make TypeRowRV be a wrapper-struct around Term and instead put these checking constructors on both TypeRow (i.e. requiring that the elements are single types) and TypeRowRV. However this has problems because of our use of impl Into<TypeRow>(/RV) everywhere: either
      • impl From would call new_unchecked (meaning the checks are bypassed in many cases)
      • impl From would call new (i.e. .into() panics)
      • We change all the x: impl Into<TypeRow> into x: impl TryInto<TypeRow> and add Results with that error type, but this would be very disruptive.
    • Also impl TryFrom<Term> for TypeRow would become Term::as_list(self) -> Option<Vec<Term>> i.e. a utility for a single-variant match - we have plenty of these so that's fine.
    • We could even make Type be a wrapper-struct around Term that checks it's a runtime type (a RuntimeExtension / RuntimeFunction / RuntimeSum / Variable-of-appropriate-type), again via checking constructors, but I think this would be tooooo painful
      Summary This PR is substantially painful, although I hope that removing the RV hack can be a significant win in itself right now, if we can do it right. The bigger gains are expected to come in later PRs (model-style Custom constructors in hugr-core, i.e. TypeDef -> TermDef #2296 and Unify Value with Term #2756, eventually allowing "inspectable" custom constants with efficient serialization).

TODOs/unresolved questions:

  • Is it time to retire old aliases TypeArg, TypeParam and the new Type? (Perhaps also TypeRowRV but the above suggests making it a non-alias struct with guarantees; I guess we could do the same for TypeRV too)
  • If we don't strengthen TypeRow to guarantee its elements are runtime-types, then
    • rename to TermRow....or just remove it
    • add checking contsructors to all OpTypes, etc., too
  • Update hugr-py
  • Update hugr-llvm (perhaps only test snapshots)

BREAKING CHANGE: Term gains RuntimeFunction, RuntimeSum and RuntimeExtension variants; Type is now an alias for Term. Use check_term_type(tm, &TypeBound::Linear.into()) to check that tm: &Term represents a runtime type. No more aliases.

acl-cqc added 30 commits January 1, 2026 12:14
This reverts commit 3aad2180f710237a31fc67e5995a2f607d9f0168.
We are no longer parametrizing over it: Signature andFuncValueType
are two separate structs. So it serves no purpose.

The alternative would be to reintroduce FuncTypeBase. Signature
(the <TypeRow> instantiation) would serialize correctly, but we
could not mark FuncValueType (the <Term> instantation) with the
necessary serde directives. We could work round this by introducing
a `struct TypeRowRV(Term)` - with constructors `new`, `try_new` and
`new_unchecked` and perhaps validation - and parametrize over that.
Might not be sooo bad...??
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Jan 30, 2026

Merging this PR will degrade performance by 32.53%

❌ 2 regressed benchmarks
✅ 23 untouched benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
circuit_roundtrip/capnp[1000] 74.5 ms 84.2 ms -11.56%
construction 11.7 µs 17.4 µs -32.53%

Comparing acl/unify_type_term (f226d19) with acl/no_1type_row (7e65a1b)

Open in CodSpeed

@acl-cqc acl-cqc changed the title refactor!(hugrv2): WIP+RFC: Unify Type and Term refactor(hugrv2)!: WIP+RFC: Unify Type and Term Jan 30, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Jan 30, 2026

Codecov Report

❌ Patch coverage is 84.21053% with 192 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.64%. Comparing base (7e65a1b) to head (f226d19).

Files with missing lines Patch % Lines
hugr-core/src/types/signature.rs 76.47% 17 Missing and 11 partials ⚠️
hugr-core/src/types/type_param.rs 86.13% 23 Missing and 5 partials ⚠️
hugr-core/src/import.rs 63.79% 2 Missing and 19 partials ⚠️
hugr-core/src/types.rs 91.44% 9 Missing and 4 partials ⚠️
hugr-core/src/types/serialize.rs 85.71% 8 Missing and 3 partials ⚠️
hugr-core/src/extension/resolution/types_mut.rs 61.53% 1 Missing and 9 partials ⚠️
...ore/src/std_extensions/collections/borrow_array.rs 33.33% 1 Missing and 7 partials ⚠️
hugr-core/src/types/check.rs 0.00% 8 Missing ⚠️
hugr-core/src/types/poly_func.rs 91.83% 5 Missing and 3 partials ⚠️
...src/std_extensions/collections/array/op_builder.rs 14.28% 0 Missing and 6 partials ⚠️
... and 22 more
Additional details and impacted files
@@                 Coverage Diff                  @@
##           acl/no_1type_row    #2785      +/-   ##
====================================================
- Coverage             83.70%   83.64%   -0.06%     
====================================================
  Files                   260      259       -1     
  Lines                 52559    52416     -143     
  Branches              47397    47254     -143     
====================================================
- Hits                  43995    43845     -150     
+ Misses                 6175     6163      -12     
- Partials               2389     2408      +19     
Flag Coverage Δ
rust 83.08% <84.21%> (-0.07%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@acl-cqc acl-cqc force-pushed the acl/unify_type_term branch from 318385b to f5047be Compare January 30, 2026 12:14
[Type::new_function(FuncValueType::new(
[usize_t()],
[tv.clone()],
tv.clone(),
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice example of where we go back on #2784: this "row variable" previously had to be written in square brackets so it could be placed as an element of a TypeRow, despite the variable standing for a list itself

///
/// let expected_json = json!({
/// "typ": usize_t(),
/// "typ": {"t": "I"}, // No public way to serialize a Term as a (SerSimple)Type...
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be #[serde_as(as="crate::types::serialize::SerType")] on a rust decl, but we don't have a way to do that here. We could do "type": SerSimpleType::from(usize_t()) but would have to make SerSimpleType public

@hugrbot
Copy link
Copy Markdown
Collaborator

hugrbot commented Feb 16, 2026

This PR contains breaking changes to the public Rust API.

cargo-semver-checks summary
    Building hugr v0.25.4 (current)
     Built [  42.033s] (current)
   Parsing hugr v0.25.4 (current)
    Parsed [   0.004s] (current)
  Building hugr v0.25.4 (baseline)
     Built [  32.699s] (baseline)
   Parsing hugr v0.25.4 (baseline)
    Parsed [   0.004s] (baseline)
  Checking hugr v0.25.4 -> v0.25.4 (assume minor change)
   Checked [   0.012s] 196 checks: 196 pass, 49 skip
   Summary no semver update required
  Finished [  77.494s] hugr
  Building hugr-cli v0.25.4 (current)
     Built [  33.857s] (current)
   Parsing hugr-cli v0.25.4 (current)
    Parsed [   0.009s] (current)
  Building hugr-cli v0.25.4 (baseline)
     Built [  29.087s] (baseline)
   Parsing hugr-cli v0.25.4 (baseline)
    Parsed [   0.009s] (baseline)
  Checking hugr-cli v0.25.4 -> v0.25.4 (assume minor change)
   Checked [   0.017s] 196 checks: 196 pass, 49 skip
   Summary no semver update required
  Finished [  65.316s] hugr-cli
  Building hugr-core v0.25.4 (current)
     Built [  20.154s] (current)
   Parsing hugr-core v0.25.4 (current)
    Parsed [   0.081s] (current)
  Building hugr-core v0.25.4 (baseline)
     Built [  20.058s] (baseline)
   Parsing hugr-core v0.25.4 (baseline)
    Parsed [   0.079s] (baseline)
  Checking hugr-core v0.25.4 -> v0.25.4 (assume minor change)
   Checked [   0.210s] 196 checks: 189 pass, 7 fail, 0 warn, 49 skip

--- failure enum_missing: pub enum removed or renamed ---

Description:
A publicly-visible enum cannot be imported by its prior path. A `pub use` may have been removed, or the enum itself may have been renamed or removed entirely.
      ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
     impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/enum_missing.ron

Failed in:
enum hugr_core::types::NoRV, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/row_var.rs:55
enum hugr_core::types::TypeEnum, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types.rs:332

--- failure enum_struct_variant_changed_kind: An enum struct variant changed kind ---

Description:
A pub enum's struct variant with at least one pub field has changed to a different kind of enum variant, breaking access to its pub fields.
      ref: https://doc.rust-lang.org/reference/items/enumerations.html
     impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/enum_struct_variant_changed_kind.ron

Failed in:
variant SumType::General in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/types.rs:176

--- failure enum_variant_missing: pub enum variant removed or renamed ---

Description:
A publicly-visible enum has at least one variant that is no longer available under its prior name. It may have been renamed or removed entirely.
      ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
     impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/enum_variant_missing.ron

Failed in:
variant SignatureError::RowVarWhereTypeExpected, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/extension.rs:419
variant Term::Runtime, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_param.rs:100
variant Term::Runtime, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_param.rs:100
variant Term::Runtime, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_param.rs:100
variant Term::Runtime, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_param.rs:100
variant Term::Runtime, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_param.rs:100

--- failure function_missing: pub fn removed or renamed ---

Description:
A publicly-visible function cannot be imported by its prior path. A `pub use` may have been removed, or the function itself may have been renamed or removed entirely.
      ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
     impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/function_missing.ron

Failed in:
function hugr_core::extension::resolution::resolve_type_extensions, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/extension/resolution.rs:45

--- failure inherent_method_missing: pub method removed or renamed ---

Description:
A publicly-visible method or associated fn is no longer available under its prior name. It may have been renamed or removed entirely.
      ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
     impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/inherent_method_missing.ron

Failed in:
ModuleBuilder::add_alias_def, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/builder/module.rs:187
ModuleBuilder::add_alias_declare, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/builder/module.rs:211
SumType::as_unary_option, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types.rs:295
Term::new_list_concat, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_param.rs:311
Term::as_runtime, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_param.rs:338
Term::new_list_concat, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_param.rs:311
Term::as_runtime, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_param.rs:338
Term::new_list_concat, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_param.rs:311
Term::as_runtime, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_param.rs:338
Term::new_list_concat, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_param.rs:311
Term::as_runtime, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_param.rs:338
Term::new_list_concat, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_param.rs:311
Term::as_runtime, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_param.rs:338

--- failure method_requires_different_generic_type_params: method now requires a different number of generic type parameters ---

Description:
A method now requires a different number of generic type parameters than it used to. Uses of this method that supplied the previous number of generic types will be broken.
      ref: https://doc.rust-lang.org/reference/items/generics.html
     impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/method_requires_different_generic_type_params.ron

Failed in:
hugr_core::extension::resolution::ExtensionCollectionError::dropped_signature takes 0 generic types instead of 1, in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/extension/resolution.rs:243
hugr_core::extension::resolution::ExtensionCollectionError::dropped_type takes 0 generic types instead of 1, in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/extension/resolution.rs:254

--- failure struct_missing: pub struct removed or renamed ---

Description:
A publicly-visible struct cannot be imported by its prior path. A `pub use` may have been removed, or the struct itself may have been renamed or removed entirely.
      ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
     impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/struct_missing.ron

Failed in:
struct hugr_core::types::TypeBase, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types.rs:403
struct hugr_core::types::type_row::TypeRowBase, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_row.rs:23
struct hugr_core::types::type_param::ListPartIter, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_param.rs:765
struct hugr_core::types::FuncTypeBase, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/signature.rs:36
struct hugr_core::types::RowVariable, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/row_var.rs:17
struct hugr_core::ops::handle::AliasID, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/ops/handle.rs:80
struct hugr_core::types::type_param::TuplePartIter, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/types/type_param.rs:800

   Summary semver requires new major version: 7 major and 0 minor checks failed
  Finished [  42.173s] hugr-core
  Building hugr-llvm v0.25.4 (current)
     Built [  23.316s] (current)
   Parsing hugr-llvm v0.25.4 (current)
    Parsed [   0.012s] (current)
  Building hugr-llvm v0.25.4 (baseline)
     Built [  24.201s] (baseline)
   Parsing hugr-llvm v0.25.4 (baseline)
    Parsed [   0.011s] (baseline)
  Checking hugr-llvm v0.25.4 -> v0.25.4 (assume minor change)
   Checked [   0.040s] 196 checks: 196 pass, 49 skip
   Summary no semver update required
  Finished [  51.433s] hugr-llvm
  Building hugr-model v0.25.4 (current)
     Built [   9.691s] (current)
   Parsing hugr-model v0.25.4 (current)
    Parsed [   0.015s] (current)
  Building hugr-model v0.25.4 (baseline)
     Built [  10.007s] (baseline)
   Parsing hugr-model v0.25.4 (baseline)
    Parsed [   0.015s] (baseline)
  Checking hugr-model v0.25.4 -> v0.25.4 (assume minor change)
   Checked [   0.030s] 196 checks: 196 pass, 49 skip
   Summary no semver update required
  Finished [  20.923s] hugr-model
  Building hugr-passes v0.25.4 (current)
     Built [  24.109s] (current)
   Parsing hugr-passes v0.25.4 (current)
    Parsed [   0.022s] (current)
  Building hugr-passes v0.25.4 (baseline)
     Built [  23.852s] (baseline)
   Parsing hugr-passes v0.25.4 (baseline)
    Parsed [   0.021s] (baseline)
  Checking hugr-passes v0.25.4 -> v0.25.4 (assume minor change)
   Checked [   0.034s] 196 checks: 196 pass, 49 skip
   Summary no semver update required
  Finished [  49.781s] hugr-passes
  Building hugr-persistent v0.4.4 (current)
     Built [  20.674s] (current)
   Parsing hugr-persistent v0.4.4 (current)
    Parsed [   0.008s] (current)
  Building hugr-persistent v0.4.4 (baseline)
     Built [  21.193s] (baseline)
   Parsing hugr-persistent v0.4.4 (baseline)
    Parsed [   0.007s] (baseline)
  Checking hugr-persistent v0.4.4 -> v0.4.4 (assume minor change)
   Checked [   0.014s] 196 checks: 196 pass, 49 skip
   Summary no semver update required
  Finished [  43.692s] hugr-persistent

@acl-cqc
Copy link
Copy Markdown
Contributor Author

acl-cqc commented Feb 25, 2026

Gonna do this a different way, Type can remain but wrap a (checked) Term, significantly less change but still achieves the required effect

@acl-cqc acl-cqc closed this Feb 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants