Skip to content

Conversation

@acl-cqc
Copy link
Contributor

@acl-cqc acl-cqc commented Aug 25, 2025

closes #2356, adding new methods link_module and link_module_view.

  • These ignore the entrypoint.
  • But take a 'policy' that acts on names of public functions to determine how to link the two Hugrs.

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:

  • factory methods for NameLinkingPolicy? There are 8 possible policies now and more fields will be added (e.g. in feat: insert_link_hugr adds entrypoint subtree and links, with reachability #2555), so an explicit new doesn't work well. Is there a good default? Or a better set of factory methods? (They should at least be called new_, yes.)
  • Do tests need to be more systematic about covering the possible policy variants?

acl-cqc added 30 commits August 11, 2025 13:28
/// 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!
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'm debating whether to

  1. 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
  2. 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_hugr adds 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! :-)

Copy link
Contributor

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.

Copy link
Contributor Author

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 ;)

@lmondada lmondada requested a review from Copilot September 17, 2025 05:20
Copy link
Contributor

Copilot AI left a 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 NameLinkingPolicy with configurable behavior for signature conflicts and multiple implementations
  • Implements link_module and link_module_view methods 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.

Copy link
Contributor

@lmondada lmondada left a 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

/// 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>(
Copy link
Contributor

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

Copy link
Contributor Author

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.

@acl-cqc acl-cqc requested a review from Copilot November 17, 2025 13:27
Copy link
Contributor

Copilot AI left a 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.

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}");
Copy link

Copilot AI Nov 17, 2025

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:?}\".

Suggested change
assert_eq!(sig1, sig2, "Invalid Hugr: different signatures for {name}");
assert_eq!(sig1, sig2, "Invalid Hugr: different signatures for {name:?}");

Copilot uses AI. Check for mistakes.
true => "Multiple FuncDefns",
false => "FuncDefn and FuncDecl(s)",
};
panic!("Invalid Hugr: {err} for {name}");
Copy link

Copilot AI Nov 17, 2025

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:?}\");.

Suggested change
panic!("Invalid Hugr: {err} for {name}");
panic!("Invalid Hugr: {err} for {name:?}");

Copilot uses AI. Check for mistakes.

#[rstest]
fn combines_decls_defn(
#[values(OnNewFunc::RaiseError, OnNewFunc::Add)] sig_conflict: OnNewFunc,
Copy link
Contributor Author

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)....

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.

Looks Great!

Just nits.

}
}

/// Describes ways to link a "Source" Hugr being inserted into a target Hugr.
Copy link
Collaborator

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"

Copy link
Contributor Author

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

Comment on lines 261 to 263
///
/// [FuncDefn]: crate::ops::FuncDefn
/// [Visibility::Public]: crate::Visibility::Public
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
///
/// [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 {
Copy link
Collaborator

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.

Copy link
Contributor Author

@acl-cqc acl-cqc Nov 24, 2025

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?

Copy link
Collaborator

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

Copy link
Contributor Author

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....?

/// 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>(
Copy link
Collaborator

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(
Copy link
Collaborator

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

/// 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).
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// that policy (for those specific Hugrs).
/// that policy for those specific Hugrs.

///
/// 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>>;
Copy link
Collaborator

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?

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'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....

Copy link
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Link Module-rooted Hugrs (w/ configurable policies)

4 participants