-
Notifications
You must be signed in to change notification settings - Fork 13
feat: Add method to link Hugr modules (linking pt3 of >=4) #2529
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
base: main
Are you sure you want to change the base?
Conversation
This reverts commit caecc43.
hugr-core/src/hugr/linking.rs
Outdated
| /// All [Visibility::Public] module-children are inserted, or linked, according to the | ||
| /// specified policy; private children will also be inserted, at least including all those | ||
| /// used by the copied public children. | ||
| // Yes at present we copy all private children, i.e. a safe over-approximation! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm debating whether to
- specify a vague "at least" bound on behaviour in the docs; and then quietly change the actual behaviour (from the maximum set within that bound, to the minimum) in the next PR
- Add an extra flag in the next PR to preserve the exact behaviour introduced in this PR (i.e. add all the private functions, even unused ones, unless overridden). feat:
insert_link_hugradds entrypoint subtree and links, with reachability #2555 currently takes this latter approach and it's not super-difficult but mildly annoying. (And do people really want those private funcs to be added?)
Thoughts welcome! :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not adding unused private functions sounds like the right thing to do. Let's not bloat the Hugr unless we have a very good reason to.
If we do come up with a use case in the future for adding those private functions, it's an easy change.
Regarding docs: it's up to you. Behaviour change is not breaking, so even if there's a release in between it would not matter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Behaviour change is breaking in my book, but I reckon the best plan is to avoid releasing inbetween this PR and #2555 being merged after it ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds functionality to link Hugr modules by introducing link_module and link_module_view methods that operate on module children based on a configurable linking policy. The implementation focuses on public functions and provides flexible conflict resolution strategies for handling naming and signature conflicts between modules.
- Adds
NameLinkingPolicywith configurable behavior for signature conflicts and multiple implementations - Implements
link_moduleandlink_module_viewmethods that apply the policy to merge module children - Provides comprehensive error handling and validation for various linking scenarios
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
lmondada
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll finish my review later. Here's the first half
hugr-core/src/hugr/linking.rs
Outdated
| /// Computes how this policy will act on a specified source (inserted) and target | ||
| /// (host) Hugr. | ||
| #[allow(clippy::type_complexity)] | ||
| pub fn to_node_linking<T: HugrView + ?Sized, S: HugrView>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: presumably S does not need to be Sized either
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My view here is that having to specify + ?Sized is the annoying non-default thing that should be minimized....unfortunately trait LinkHugrs default impls don't compile without the first one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
hugr-core/src/hugr/linking.rs
Outdated
| let Some((mut acc, sig1)) = acc else { | ||
| return Some((new.map_right(|n| (n, vec![])), sig2)); | ||
| }; | ||
| assert_eq!(sig1, sig2, "Invalid Hugr: different signatures for {name}"); |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message 'Invalid Hugr: different signatures for {name}' should use :? formatting for the name variable to ensure proper display. Change to \"Invalid Hugr: different signatures for {name:?}\".
| assert_eq!(sig1, sig2, "Invalid Hugr: different signatures for {name}"); | |
| assert_eq!(sig1, sig2, "Invalid Hugr: different signatures for {name:?}"); |
hugr-core/src/hugr/linking.rs
Outdated
| true => "Multiple FuncDefns", | ||
| false => "FuncDefn and FuncDecl(s)", | ||
| }; | ||
| panic!("Invalid Hugr: {err} for {name}"); |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The panic message should use :? formatting for the name variable. Change to panic!(\"Invalid Hugr: {err} for {name:?}\");.
| panic!("Invalid Hugr: {err} for {name}"); | |
| panic!("Invalid Hugr: {err} for {name:?}"); |
|
|
||
| #[rstest] | ||
| fn combines_decls_defn( | ||
| #[values(OnNewFunc::RaiseError, OnNewFunc::Add)] sig_conflict: OnNewFunc, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This mass of #[values....] is because none of these actually matter, the test is the same in all cases...
I'd like to define a pytest-like "parametrized fixture" #[fixture] fn any_policy(#[values....] ....) -> NameLinkingPolicy and then have every test using any_policy get run multiple times, but rstest doesn't offer such a facility. There are crates that do: https://crates.io/crates/rstest_reuse (a small addition) and https://crates.io/crates/rustest (a complete replacement of rstest)....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks Great!
Just nits.
hugr-core/src/hugr/linking.rs
Outdated
| } | ||
| } | ||
|
|
||
| /// Describes ways to link a "Source" Hugr being inserted into a target Hugr. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT this seems inconsistent, i would choose from source target and "Source" "Target"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Took me a moment to grok that one....lol, love it, will do
hugr-core/src/hugr/linking.rs
Outdated
| /// | ||
| /// [FuncDefn]: crate::ops::FuncDefn | ||
| /// [Visibility::Public]: crate::Visibility::Public |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// | |
| /// [FuncDefn]: crate::ops::FuncDefn | |
| /// [Visibility::Public]: crate::Visibility::Public |
| /// [Visibility::Public]: crate::Visibility::Public | ||
| #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, derive_more::From)] | ||
| #[non_exhaustive] // could consider e.g. disconnections | ||
| pub enum OnMultiDefn { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this could also apply to FuncDecls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?? If their names and sigs are the same, we can just merge them, they are identical (aliases) - of course we cannot merge impls (ok, we could check same nodes in same order, build a map between the indices, and then check the edges matched...even that is quite a bit of work and would miss reorderings of nodes, which brings us to graph isomorphism).
Or do you mean, so you can control whether aliases are combined vs kept separate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about controlling when they are combined vs kept separate.
But this is fine as is
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other way to look at it is that switching between impls of the same-name-and-signature FuncDefn doesn't invalidate the Hugr, whereas other changes invalidate....?
hugr-core/src/hugr/linking.rs
Outdated
| /// Computes how this policy will act on a specified source (inserted) and target | ||
| /// (host) Hugr. | ||
| #[allow(clippy::type_complexity)] | ||
| pub fn to_node_linking<T: HugrView + ?Sized, S: HugrView>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a big fan of to_node_linking. It makes me think that we are converting into a NodeLinking, which isn't a thing. What about link_actions
| /// | ||
| /// [Visibility::Public]: crate::Visibility::Public | ||
| /// [FuncDefn]: crate::ops::FuncDefn | ||
| fn link_module( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should be explicit that entrypoint is ignored
hugr-core/src/hugr/linking.rs
Outdated
| /// Details the concrete actions to link a specific source Hugr into a specific target Hugr. | ||
| /// | ||
| /// Computed from a [NameLinkingPolicy] and contains all actions required to implement | ||
| /// that policy (for those specific Hugrs). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// that policy (for those specific Hugrs). | |
| /// that policy for those specific Hugrs. |
hugr-core/src/hugr/linking.rs
Outdated
| /// | ||
| /// Computed from a [NameLinkingPolicy] and contains all actions required to implement | ||
| /// that policy (for those specific Hugrs). | ||
| pub type LinkActions<SN, TN> = HashMap<SN, LinkAction<TN>>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use BTreeMap so that determinism is never an issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've switched this one to BTreeMap because it's externally visible and so you might print it. The ordering should not affect the result of linking (unless you hand-construct a map of actions which modify each other or something), so I've not switched any of the other HashMaps....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to use BTreeMap everywhere.
When we have determinism bugs we will grep for Hash everywhere.
closes #2356, adding new methods
link_moduleandlink_module_view.Does not use callgraph/reachability; this is coming in #2555. WAIT TO MERGE at the same time as #2555 to avoid the behaviour change of not inserting unreachable private functions.
There are a few other suggestions for even further into the future in #2687.
Questions for review now:
insert_link_hugradds entrypoint subtree and links, with reachability #2555), so an explicitnewdoesn't work well. Is there a good default? Or a better set of factory methods? (They should at least be callednew_, yes.)