Skip to content

feat: traits for transforming Types/TypeArgs/etc. #1991

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 19, 2025
Merged

Conversation

acl-cqc
Copy link
Contributor

@acl-cqc acl-cqc commented Mar 17, 2025

Allows fallible transformation (whereas Substitution is infallible), by implementing new trait TypeTransformer. At present this only allows replacing Extension types (with arbitrary types), but we could easily add new trait methods (with default {Ok(None)} to make no change) in the future.

Trait Transformable is more of an implementation detail, it allows commoning up lots of list traversal, I don't really foresee anyone external implementing it, but it doesn't seem worth going to any effort to stop them if they want to.

The (&mut self, ...) -> Result<bool, ...> interface means you may want to clone first in order to recover from an error, but avoids an extra traversal for change-checking (i.e. it's easier to build an (&self, ...) -> Result<Self, ...> interface on top of this, than the other way around).

Copy link

codecov bot commented Mar 17, 2025

Codecov Report

Attention: Patch coverage is 95.40230% with 8 lines in your changes missing coverage. Please review.

Project coverage is 83.72%. Comparing base (53b9505) to head (044ff32).
Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
hugr-core/src/types.rs 95.52% 1 Missing and 5 partials ⚠️
hugr-core/src/types/signature.rs 92.30% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1991      +/-   ##
==========================================
+ Coverage   83.65%   83.72%   +0.06%     
==========================================
  Files         209      209              
  Lines       38993    39167     +174     
  Branches    35666    35838     +172     
==========================================
+ Hits        32620    32791     +171     
+ Misses       4533     4530       -3     
- Partials     1840     1846       +6     
Flag Coverage Δ
python 92.03% <ø> (∅)
rust 82.94% <95.40%> (∅)

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 marked this pull request as ready for review March 17, 2025 19:34
@acl-cqc acl-cqc requested a review from a team as a code owner March 17, 2025 19:34
@acl-cqc acl-cqc requested a review from doug-q March 17, 2025 19:34
@acl-cqc
Copy link
Contributor Author

acl-cqc commented Mar 17, 2025

@doug-q I promise you were randomly picked, and it has nothing to do with my having mentioned you in the description!

Copy link
Collaborator

@doug-q doug-q left a comment

Choose a reason for hiding this comment

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

I like this a lot. I need to do another more careful pass before approving

Copy link
Collaborator

@doug-q doug-q left a comment

Choose a reason for hiding this comment

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

Very nice Alan.

I don't prefer &mut self over self. I don't have a strong opinion either way. Ideally we would not clone if we didn't have to, and ideally we would report whether there was any change. I expect that both of these optimisations are in practise irrelevant. If you would prefer the "functional" interface(or both interfaces, this is easy because of the trait-style interface), go for it.

TypeArg::BoundedNat { .. }
| TypeArg::String { .. }
| TypeArg::Extensions { .. }
| TypeArg::Variable { .. } => Ok(false),
Copy link
Collaborator

Choose a reason for hiding this comment

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

This case should be tested

if args_changed {
*self = Self::new_extension(
custom_type
.get_type_def(&custom_type.get_extension()?)?
Copy link
Collaborator

@doug-q doug-q Mar 19, 2025

Choose a reason for hiding this comment

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

I haven't looked at the other callsites of get_type_def, but I don't like it. I would prefer to inline it here, and just "pub(crate)"-icise get_extension. If you disagree I'm fine with this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd look at it the other way - how about removing get_extension by inlining into get_type_def and "pub(crate)"-icise only get_type_def?

Copy link
Contributor Author

@acl-cqc acl-cqc Mar 19, 2025

Choose a reason for hiding this comment

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

There are three callers of get_type_def and they all call get_extension first with only a slight change in how they handle errors from get_extension (i.e. one caller panics....on errors from either, so that's fine to combine both errors)

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree that makes sense, but I do think get_extension is a perfectly reasonable thing to want to do. Up to you.

Copy link
Contributor Author

@acl-cqc acl-cqc Mar 19, 2025

Choose a reason for hiding this comment

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

OIC. The thing is that the caller has to hang onto the result Arc from get_extension for the lifetime of the &TypeDef. Ugh. I guess if Extension::get_type returned an Option<Arc<TypeDef>> rather than an Option<&TypeDef> then this would go away but otherwise we're a bit stuck.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I don't think the implementation of get_type_def is great either, but it seems mad to make the caller do that work (looking up the CustomType's type-id in the extension). Could change it to an Option rather than Result if that's preferable but the SignatureError is appropriate in at least a couple of cases, so I'm inclined to leave it.

#2001 covers what I think is the way forward but no need to do that in this PR.

@acl-cqc
Copy link
Contributor Author

acl-cqc commented Mar 19, 2025

I don't prefer &mut self over self. I don't have a strong opinion either way. Ideally we would not clone if we didn't have to, and ideally we would report whether there was any change. I expect that both of these optimisations are in practise irrelevant. If you would prefer the "functional" interface(or both interfaces, this is easy because of the trait-style interface), go for it.

Hmmm, I guess a lot depends on how important it is no "not clone if we didn't have to".

  • (self, T) -> Result<Self, T::Error> is no better, in the event of an error you've lost the original
  • (self, T) -> Result<Self, (Self, T::Error)> likewise, you've no guarantee that the one you get back is unchanged
  • (&self, T) -> Result<Self, T::Error> is the only way you know you've still got the original (/in a consistent state), but clones - unnecessary if you have no intention of recovering from failure

The other advantage is that determining whether anything is changed requires an additional traversal (as we are not sharing with Arcs - otherwise a "pointer-eq" check might be an acceptable safe approximation).

And, the &mut self requires much less code for OpType, where there are 20 things that would need to be recreated after transforming the constituent elements, but implementing this over the top of functional-style substitution would require equality checking everything :-(

So the best alternative seems to be, clone even when not needed, and drop change-checking. Hmmm, boo, ok, maybe I'll drop this objection then...;)

@acl-cqc acl-cqc added this pull request to the merge queue Mar 19, 2025
Merged via the queue into main with commit 6ed4ecf Mar 19, 2025
27 checks passed
@acl-cqc acl-cqc deleted the acl/type_transform branch March 19, 2025 12:04
@hugrbot hugrbot mentioned this pull request Mar 19, 2025
github-merge-queue bot pushed a commit that referenced this pull request Mar 21, 2025
## 🤖 New release

* `hugr-model`: 0.18.0 -> 0.18.1 (✓ API compatible changes)
* `hugr-core`: 0.15.0 -> 0.16.0 (✓ API compatible changes)
* `hugr-llvm`: 0.15.0 -> 0.16.0 (✓ API compatible changes)
* `hugr-passes`: 0.15.0 -> 0.16.0 (✓ API compatible changes)
* `hugr`: 0.15.0 -> 0.16.0 (✓ API compatible changes)
* `hugr-cli`: 0.15.0 -> 0.16.0 (✓ API compatible changes)

<details><summary><i><b>Changelog</b></i></summary><p>

## `hugr-model`

<blockquote>

##
[0.18.0](hugr-model-v0.17.1...hugr-model-v0.18.0)
- 2025-03-14

### Bug Fixes

- Hugr-model using undeclared derive_more features
([#1940](#1940))

### New Features

- *(hugr-model)* [**breaking**] Add `read_from_reader` and
`write_to_writer` for streaming reads and writes.
([#1871](#1871))
- `hugr-model` AST ([#1953](#1953))

### Refactor

- *(hugr-model)* Reexport `bumpalo` from `hugr-model`
([#1870](#1870))
</blockquote>

## `hugr-core`

<blockquote>

##
[0.16.0](hugr-core-v0.15.0...hugr-core-v0.16.0)
- 2025-03-21

### Bug Fixes

- correct `CallIndirect` tag from `FnCall` to `DataflowChild`
([#2006](#2006))
- StaticArrayValue serialisation
([#2009](#2009))

### New Features

- traits for transforming Types/TypeArgs/etc.
([#1991](#1991))
- add exit operation to prelude
([#2008](#2008))
- Add llvm codegen for collections.static_array
([#2003](#2003))
</blockquote>

## `hugr-llvm`

<blockquote>

##
[0.16.0](hugr-llvm-v0.15.0...hugr-llvm-v0.16.0)
- 2025-03-21

### Bug Fixes

- Remove return from val_or_panic
([#1999](#1999))

### New Features

- add exit operation to prelude
([#2008](#2008))
- Add llvm codegen for collections.static_array
([#2003](#2003))
</blockquote>

## `hugr-passes`

<blockquote>

##
[0.16.0](hugr-passes-v0.15.0...hugr-passes-v0.16.0)
- 2025-03-21

### Bug Fixes

- correct `CallIndirect` tag from `FnCall` to `DataflowChild`
([#2006](#2006))
</blockquote>

## `hugr`

<blockquote>

##
[0.16.0](hugr-v0.15.0...hugr-v0.16.0)
- 2025-03-21

### Bug Fixes

- correct `CallIndirect` tag from `FnCall` to `DataflowChild`
([#2006](#2006))
- StaticArrayValue serialisation
([#2009](#2009))

### New Features

- traits for transforming Types/TypeArgs/etc.
([#1991](#1991))
- add exit operation to prelude
([#2008](#2008))
- Add llvm codegen for collections.static_array
([#2003](#2003))
- *(hugr-py)* Support envelope compression
([#1994](#1994))
</blockquote>

## `hugr-cli`

<blockquote>

##
[0.16.0](hugr-cli-v0.15.0...hugr-cli-v0.16.0)
- 2025-03-21

### New Features

- *(hugr-cli)* Nicer error when passing a non-envelope file
([#2007](#2007))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).

---------

Co-authored-by: Douglas Wilson <[email protected]>
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