refactor(hugrv2)!: combine TypeEnum with Term, no RV parametrization#2895
refactor(hugrv2)!: combine TypeEnum with Term, no RV parametrization#2895
Conversation
…erm::List, mangle_name, skip simple_alias
Merging this PR will degrade performance by 41.37%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ | construction |
12 µs | 20.4 µs | -41.37% |
Comparing acl/type_wraps_term (80f1934) with main (7881c99)
Footnotes
-
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 Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2895 +/- ##
==========================================
+ Coverage 81.30% 81.33% +0.02%
==========================================
Files 240 239 -1
Lines 45400 45271 -129
Branches 39168 39039 -129
==========================================
- Hits 36913 36821 -92
+ Misses 6497 6449 -48
- Partials 1990 2001 +11
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| /// - `used_extensions`: A The registry where to store the used extensions. | ||
| /// - `missing_extensions`: A set of `ExtensionId`s of which the | ||
| /// `Weak<Extension>` pointer has been invalidated. | ||
| pub(crate) fn collect_func_type_exts( |
There was a problem hiding this comment.
I think we could combine these by making trait TypeRowLike (crate-private) include gathering extensions, but that would move stuff out of extension/resolution that (has) lives(/lived) in there. Thoughts?
There was a problem hiding this comment.
I like keeping the logic here rather than in an unrelated trait, esp. if we end up making that trait public at some point.
But it's not a strong preference.
| const EMPTY: Type = Type::new_unit_sum(0); // as no Type::default() | ||
| let mut tm = std::mem::replace(typ, EMPTY).into(); | ||
| let r = resolve_term_exts(node, &mut tm, extensions, used_extensions); | ||
| *typ = tm.try_into().unwrap(); |
There was a problem hiding this comment.
Because we don't have a Type::new_unchecked. I guess we could add one (e.g. crate-private), or indeed an as_mut (but assuming we keep that private, not an impl DerefMut). Footguns again...but potentially resolve_type_exts(t, ...) would become resolve_term_exts(t.as_mut_term_unchecked(), ...)
There was a problem hiding this comment.
I feel this one is fine as-is. Type is 96B, so all is done on the stack.
We don't need to fumble with pointers as with TypeRowRV.
| /// Adds the extensions used in the type to the `used_extensions` registry. | ||
| pub(super) fn resolve_type_exts<RV: MaybeRV>( | ||
| /// Adds the extensions used in the row to the `used_extensions` registry. | ||
| fn resolve_typerow_rv_exts( |
There was a problem hiding this comment.
This is what we get for not having a TypeRowRV::as_mut(&mut self) -> &mut Term. I mean we could; it is a footgun, but so is new_unchecked. At least the latter rings an alarm bell; perhaps we could have as_mut_term_unchecked...and keep it crate-private, but at least resolve_typerow_rv_exts(t, ...) would be resolve_term_exts(t.as_mut_term_unchecked(), ...)
There was a problem hiding this comment.
I don't think TypeRowRV::as_mut_term_unchecked would really be unchecked since there's nothing to check on the call site, right?
But I agree we should try to avoid the footgun.
An option would be to mark the function as unsafe to ensure we are careful on the call point.
| } | ||
| } | ||
|
|
||
| /*impl FromIterator<Type> for TypeRowRV { |
There was a problem hiding this comment.
This would allow fn foo(t: impl Iterator<Item=Type>) -> TypeRowRV { t.collect() } and could equally (perhaps primarily) be done for TypeRow as well as TypeRowRV but I wasn't sure it added much. (If you are in that situation, currently you would have to t.collect_vec().into())
There was a problem hiding this comment.
Perhaps we should implement this instead of <T: IntoIterator<Item = Type>> From<T> for TypeRowRV ?
Having to collect_vec seems quite annoying.
There was a problem hiding this comment.
For TypeRow and TypeRowRV, I think we want From<Vec<Type>> and similarly for From<[Type;N]>, it'll be too painful otherwise. ATM we write lots of parameters as impl Into<TypeRow> so that allows e.g. someTypeRowRV.concat([type1,type2]).
There was a problem hiding this comment.
However the place for FromIter might be Term...will continue in thread #2895 (comment) EDIT, no totally not
There was a problem hiding this comment.
Ok, so I've replaced the impl <T: IntoIterator<Item=Type>> From<T> for TypeRowRV with impl From<Vec<Type>> and impl <const N:usize> From<[Type; N]>, because that's consistent with what we do for TypeRow and also (from array/vec of Term) for Term.
However, could also make all three use <T: IntoIterator<Item....>> ??
(I think it's good to avoid changing a list of Types into a Term without Term::new_list)
So FromIterator could still be done as a separate thing. Should be done for both TypeRow and TypeRowRV if it is for either, so done in 3a41dda - looks like an improvement??
| TypeEnum::RowVar(rv) => rv.validate(var_decls), | ||
| } | ||
| self.0.validate(var_decls)?; | ||
| // ALAN even this should be only a debug-assert really: |
There was a problem hiding this comment.
I mean, how paranoid are we feeling about the benefit of sanity checks? :)
There was a problem hiding this comment.
Move caching of TypeBound from Type into SumType::General (via a checked struct - prior to 0ac2b53 there was such, called GeneralSum, so we could reintroduce that and add the bound there). This because of the other Type variants, CustomType stores the bound itself already, and FunctionType is always Copyable. This can come soon after I think.
If we do the move of the bound then we wouldn't need the check here, right?
There was a problem hiding this comment.
It'll move into either SumType::validate or, since that doesn't currently exist ATM, into Term::validate (which currently doesn't check the elements are types at all...so that would be a better place)
| ) -> Result<()> { | ||
| let hugr_elem_ty = match args.node().args() { | ||
| [TypeArg::Runtime(ty)] => ty.clone(), | ||
| [ty] => ty.clone().try_into().expect("List elements not a type"), |
There was a problem hiding this comment.
There are some new expects now in the lowering code that used to return an error instead (in stack_array.rs too).
Could we bail! instead of panicking?
There was a problem hiding this comment.
Ah yes, hadn't seen what bail did. Nice. Done (*2, think that's all of them?)
| } | ||
| } | ||
| } | ||
| pub struct Type(Term, TypeBound); |
There was a problem hiding this comment.
nit: This may be an opportunity to change the tuple struct to have named fields.
type.0 doesn't tell much; type.term and type.bound would be clearer.
There was a problem hiding this comment.
I'm hoping to do the SumType change soon after I think, it's fairly straightforward structural stuff so hopefully can be done via some ahem magic (cf. Carl Sagan)...then we'll just have pub struct Type(Term). Happy to get that ready before I merge this...
| TypeEnum::RowVar(rv) => rv.validate(var_decls), | ||
| } | ||
| self.0.validate(var_decls)?; | ||
| // ALAN even this should be only a debug-assert really: |
There was a problem hiding this comment.
Move caching of TypeBound from Type into SumType::General (via a checked struct - prior to 0ac2b53 there was such, called GeneralSum, so we could reintroduce that and add the bound there). This because of the other Type variants, CustomType stores the bound itself already, and FunctionType is always Copyable. This can come soon after I think.
If we do the move of the bound then we wouldn't need the check here, right?
| &Weak::default(), | ||
| )), | ||
| Type::new_alias(AliasDecl::new("my_alias", TypeBound::Copyable)), | ||
| //Type::new_alias(AliasDecl::new("my_alias", TypeBound::Copyable)), |
There was a problem hiding this comment.
| //Type::new_alias(AliasDecl::new("my_alias", TypeBound::Copyable)), |
| #[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 { |
There was a problem hiding this comment.
Yes, I would mention that behaviour
| /// Returns a [`Type`] if the [`Term`] is a runtime type. | ||
| #[must_use] | ||
| pub fn as_runtime(&self) -> Option<TypeBase<NoRV>> { | ||
| pub(crate) fn least_upper_bound(&self) -> Option<TypeBound> { |
There was a problem hiding this comment.
I wouldn't drop the others even if we make this public, they are just helpers with clear names.
Maybe add a comment in their docs referencing least_upper_bound (if public).
| /// Fallibly convert a [Term] to a [TypeRow]. | ||
| /// | ||
| /// This will fail if `arg` is not a [Term::List] or any of the elements are not [Type]s | ||
| impl TryFrom<Term> for TypeRow { |
There was a problem hiding this comment.
If the first conversion is also a TryFrom then calling the chain may get too boilerplate-y (since it requires explicit types).
| } | ||
| } | ||
|
|
||
| /*impl FromIterator<Type> for TypeRowRV { |
There was a problem hiding this comment.
Perhaps we should implement this instead of <T: IntoIterator<Item = Type>> From<T> for TypeRowRV ?
Having to collect_vec seems quite annoying.
25c7fdd to
3a41dda
Compare
|
@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 |
85eb0fc to
80f1934
Compare
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 ;) |
Term::Runtime(Type), addTerm::RuntimeFunction(FuncValueType),Term::RuntimeSum(SumType)andTerm::RuntimeExtension(CustomType)and removeTypeEnum. That is, runtime types are now just variants ofTerm.Typenow just wraps aTerm, but guarantees by construction that the Term inside it represents a single runtime type. (There is no way to bypass this; there isTryFrom<Term> for Type; there are utility constructors onTypethat takeTypes thus preserving the invariant - the samenew_extension,new_tuple,new_sum,new_functionas before).TypeRV. It never had a consistent meaning: it was sometimes a type, and sometimes a list. Also removetrait MaybeRV,enum NoRVandstruct RowVariable.TypeRowRV(much like Type) wraps aTerm, but guarantees it represents a list of types (of perhaps unknown length). That is, it could be aTerm::List(whose elements are single types), or aTerm::Variable(a "row variable" i.e. ranging over lists of types, of unknown length), or aTerm::ListConcat(whose elements are one of these three). Again there are utility constructors preserving the invariant, tho there is also aTypeRowRV::new_unchecked.new+try_new+new_uncheckedconstructors, removed in that commit). However I think with a few nifty TypeRowRV conversions and methods (just_row_varandconcat) the wrapper-struct is easier to use as well as giving more static checking.TypeRowremains as a list ofTypes, i.e. whose length/number-of-types is knowntrait TypeRowLikeallows parametrizingFuncTypeBaseso it can still do bothSignatureandFuncValueType. I have kept this trait crate-hidden, so we keep thesubstituteandvalidatemethods 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,
Type::as_type_enumwithimpl Deref<Target=Term> for Type, similarly for TypeRowRVTermTypeErroris 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 addedimpl From<TermTypeError> for OpLoadError(alsofor ImportErrorInner) - because Rust won't let meimpl <T: From<SignatureError>> From<TermTypeError> for T:-(.Term::new_listto take items that areimpl Into<Term>rather than justTermProposed follow-ups....
TypeintoSumType::General, in perf!: remove bound-caching from Type, do in SumType::General instead #3022TypeRowRV::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 havetry_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 theTypeRowRV::just_row_var,concatandfrom([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:
TypeEnumandTypeRVare no more - useTerm.RowVariable,MaybeRVetc. are gone - use appropriate term types and variables.TypeandTypeRowRVmerely wrapTermwith invariants.