Skip to content

refactor(hugrv2)!: combine TypeEnum with Term, no RV parametrization#2895

Merged
acl-cqc merged 95 commits intomainfrom
acl/type_wraps_term
May 5, 2026
Merged

refactor(hugrv2)!: combine TypeEnum with Term, no RV parametrization#2895
acl-cqc merged 95 commits intomainfrom
acl/type_wraps_term

Conversation

@acl-cqc
Copy link
Copy Markdown
Contributor

@acl-cqc acl-cqc commented Feb 25, 2026

  • Merge TypeEnum into Term: remove Term::Runtime(Type), add Term::RuntimeFunction(FuncValueType), Term::RuntimeSum(SumType) and Term::RuntimeExtension(CustomType) and remove TypeEnum. That is, runtime types are now just variants of Term.
  • Type now just wraps a Term, but guarantees by construction that the Term inside it represents a single runtime type. (There is no way to bypass this; there is TryFrom<Term> for Type; there are utility constructors on Type that take Types thus preserving the invariant - the same new_extension, new_tuple, new_sum, new_function as before).
  • Remove TypeRV. It never had a consistent meaning: it was sometimes a type, and sometimes a list. Also remove trait MaybeRV, enum NoRV and struct RowVariable.
  • TypeRowRV (much like Type) wraps a Term, but guarantees it represents a list of types (of perhaps unknown length). That is, it could be a Term::List (whose elements are single types), or a Term::Variable (a "row variable" i.e. ranging over lists of types, of unknown length), or a Term::ListConcat (whose elements are one of these three). Again there are utility constructors preserving the invariant, tho there is also a TypeRowRV::new_unchecked.
    • Note prior to 0ac2b53 (see diff LHS) TypeRowRV was merely an alias to Term (with GeneralSum and FuncValueType providing panicking-new + try_new + new_unchecked constructors, removed in that commit). However I think with a few nifty TypeRowRV conversions and methods (just_row_var and concat) the wrapper-struct is easier to use as well as giving more static checking.
  • TypeRow remains as a list of Types, i.e. whose length/number-of-types is known
  • A new trait TypeRowLike allows parametrizing FuncTypeBase so it can still do both Signature and FuncValueType. I have kept this trait crate-hidden, so we keep the substitute and validate methods hidden (as before), which does mean external code cannot parametrize in this way.

Thus, although the new system offers less Rust-compile-time checking, there is still a reasonable amount, and it's more principled now ;)

Along the way,

  • Replace Type::as_type_enum with impl Deref<Target=Term> for Type, similarly for TypeRowRV
  • AliasDecl/Defn and Type::Alias dropped for now, pending Overhaul or scrap aliases #2558
  • Since TermTypeError is now used more (it's a subclass of SignatureError but captures all the errors that might happen from e.g. turning a Term into a Type), I've added impl From<TermTypeError> for OpLoadError (also for ImportErrorInner) - because Rust won't let me impl <T: From<SignatureError>> From<TermTypeError> for T :-(.
  • Generalize Term::new_list to take items that are impl Into<Term> rather than just Term

Proposed follow-ups....

  • Either remove some redundant SignatureError variants (that also appear in TermTypeError) or perhaps combine the two errors altogether. (Less urgent?)
  • Could add a utility method TypeRowRV::new_spliced(IntoIterator<Item=Term>) that checks each Term is either a type or a list of types (and panics if not....ok could also have try_new_spliced), and then assembles the appropriate lists/concats. commit 68fba45 shows this done for FuncValueType whereas here/now it would be for TypeRowRV, and I'm not sure that it's much better than the TypeRowRV::just_row_var, concat and from([Type]) that this PR has now (in particular, just_row_var/concat have better Rust-compile-time checking than this "splicing" approach). So, probably less urgent / only if TypeRowRV is too awkward in practice.

BREAKING CHANGE: TypeEnum and TypeRV are no more - use Term. RowVariable, MaybeRV etc. are gone - use appropriate term types and variables. Type and TypeRowRV merely wrap Term with invariants.

@acl-cqc acl-cqc changed the title refactor!: combine TypeEnum with Term, remove RV stuff refactor(hugrv2)!: combine TypeEnum with Term, remove RV stuff Feb 25, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Feb 25, 2026

Merging this PR will degrade performance by 43.47%

❌ 3 regressed benchmarks
✅ 26 untouched benchmarks
⏩ 6 skipped benchmarks1

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

Performance Changes

Benchmark BASE HEAD Efficiency
circuit_roundtrip/capnp[100] 8.4 ms 9.4 ms -10.87%
construction 11.4 µs 20.2 µs -43.47%
circuit_roundtrip/capnp[1000] 75 ms 84.7 ms -11.41%

Comparing acl/type_wraps_term (07490ea) with main (d6e11c8)

Open in CodSpeed

Footnotes

  1. 6 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 25, 2026

Codecov Report

❌ Patch coverage is 88.33333% with 105 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.09%. Comparing base (d6e11c8) to head (07490ea).

Files with missing lines Patch % Lines
hugr-core/src/import.rs 61.90% 4 Missing and 20 partials ⚠️
hugr-core/src/types/type_param.rs 95.18% 5 Missing and 4 partials ⚠️
hugr-core/src/types/check.rs 0.00% 8 Missing ⚠️
hugr-core/src/types/serialize.rs 88.40% 6 Missing and 2 partials ⚠️
hugr-core/src/extension/resolution/types_mut.rs 83.72% 0 Missing and 7 partials ⚠️
hugr-core/src/types.rs 93.87% 5 Missing and 1 partial ⚠️
hugr-core/src/types/signature.rs 78.57% 6 Missing ⚠️
hugr-core/src/types/type_row.rs 95.04% 4 Missing and 2 partials ⚠️
hugr-core/src/extension/prelude.rs 86.20% 0 Missing and 4 partials ⚠️
hugr-core/src/extension/resolution.rs 33.33% 4 Missing ⚠️
... and 15 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2895      +/-   ##
==========================================
+ Coverage   81.07%   81.09%   +0.01%     
==========================================
  Files         242      241       -1     
  Lines       45183    45054     -129     
  Branches    38951    38822     -129     
==========================================
- Hits        36633    36536      -97     
+ Misses       6585     6542      -43     
- Partials     1965     1976      +11     
Flag Coverage Δ
python 88.89% <ø> (ø)
rust 79.84% <88.33%> (+0.01%) ⬆️

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 changed the title refactor(hugrv2)!: combine TypeEnum with Term, remove RV stuff refactor(hugrv2)!: combine TypeEnum with Term, no RV parametrization Feb 25, 2026
@acl-cqc acl-cqc force-pushed the acl/type_wraps_term branch from 25c7fdd to 3a41dda Compare April 10, 2026 14:48
@acl-cqc
Copy link
Copy Markdown
Contributor Author

acl-cqc commented Apr 10, 2026

@aborgna-q some improvements on the conversions, thanks, including generalization of Term::new_list which was a big win, some questions remaining at #2895 (comment).

Also unsure about aliases. Searching for commented-out code mentioning alias might be useful when we revamp....is it any good to include Term::RuntimeAlias(Void, TypeBound) i.e. for uninstantiable Void? (Maybe not, I don't think we've even figured out the details of that typebound yet, and will we want alias terms or only types?)

@acl-cqc
Copy link
Copy Markdown
Contributor Author

acl-cqc commented Apr 20, 2026

  • Tried FromIterator for both TypeRow and TypeRowRV, see if you think it's worth it? 3a41dda
  • Replaced impl <T: IntoIterator<Item=Type>> From<T> for TypeRow(and ...RV) with from-Vec and from-Array, because this is consistent with Term and so on. Could go for the generic from-T for all three?
  • Moving bound cache into (opaque struct inside) SumType::General in perf!: remove bound-caching from Type, do in SumType::General instead #3022 - needs a bit more work but you can see the idea and can have this ready to merge immediately

I think that's all the serious concerns (in particular, all those marked ALAN), plenty of smaller ones which I can have a look over but ATM it looks like they haven't raised any strong opinions ;)

@acl-cqc acl-cqc requested a review from aborgna-q April 20, 2026 07:36
Copy link
Copy Markdown
Collaborator

@aborgna-q aborgna-q left a comment

Choose a reason for hiding this comment

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

LGTM! Just some additional comments.

Comment thread hugr-core/src/types/type_param.rs Outdated
Comment thread hugr-core/src/types/serialize.rs Outdated
SerSimpleType::G(sig) => Type::new_function(*sig).into(),
SerSimpleType::Sum(st) => Type::from(st).into(),
SerSimpleType::Opaque(o) => Type::new_extension(o).into(),
SerSimpleType::Alias(_) => todo!("alias?"),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What are we doing with the alias stubs? If this stays here, it should at least give a better error.

Comment thread hugr-core/src/types/type_row.rs Outdated
Comment on lines +272 to +275
/// Wraps the given Term, without checking its type.
pub fn new_unchecked(t: impl Into<Term>) -> Self {
Self(t.into())
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Are we OK with this being public? The rest of the code assumes these are valid by construction so this feels like a footgun.

We could have a public try_new if needed, an keep the no-checks version internal-only.

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.

I first made TypeRowRV::new_unchecked crate-private, then thought - do we really need it? Can't we just use try_into().expect()? The latter works fine, and doesn't change anything on CodSpeed (although -10% on capnp roundtrip either way is not great, hmmm)

@acl-cqc acl-cqc force-pushed the acl/type_wraps_term branch 2 times, most recently from 62e058d to 9fe64c5 Compare April 28, 2026 09:29
@acl-cqc
Copy link
Copy Markdown
Contributor Author

acl-cqc commented Apr 28, 2026

  • Removed the commented out alias code. Have kept the commit on my own private branch
  • Dropped TypeRowRV::new_unchecked.
  • ....but are we happy with 10% perf drop in capnp::roundtrip or should I investigate before merging?
  • Also do you think we should change the impl From<Vec<...>> and impl <const N: usize> From<[...; N]> to blanket impl <T: IntoIterator<Item=....> From<T>s for Type/Term?

@acl-cqc acl-cqc requested a review from aborgna-q April 28, 2026 10:11
Copy link
Copy Markdown
Collaborator

@aborgna-q aborgna-q left a comment

Choose a reason for hiding this comment

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

Another suggestion to remove the vestigial commented-out alias code, but otherwise :shipit:

Comment thread hugr-core/src/builder/module.rs Outdated
Comment on lines 334 to 356
@@ -352,7 +352,7 @@ mod test {
};
assert_matches!(build_result, Ok(_));
Ok(())
}
}*/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change

#[inline]
pub fn new_list_concat(lists: impl IntoIterator<Item = Self>) -> Self {
Self::ListConcat(lists.into_iter().collect())
pub fn concat_lists(lists: impl IntoIterator<Item = Self>) -> Self {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ok 👍
Should we still update the comment with the current behaviour?

@acl-cqc
Copy link
Copy Markdown
Contributor Author

acl-cqc commented May 5, 2026

Another suggestion to remove the vestigial commented-out alias code

I had actually tried to but had missed a couple! Thanks :)

@acl-cqc acl-cqc added this pull request to the merge queue May 5, 2026
Merged via the queue into main with commit d372743 May 5, 2026
29 of 30 checks passed
@acl-cqc acl-cqc deleted the acl/type_wraps_term branch May 5, 2026 12:24
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.

5 participants