-
Notifications
You must be signed in to change notification settings - Fork 14
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.
/// Describes ways to link a "Source" Hugr being inserted into a target Hugr. | ||
#[derive(Clone, Debug, PartialEq, Eq)] | ||
pub struct NameLinkingPolicy { | ||
// TODO: consider pub-funcs-to-add? (With others, optionally filtered by callgraph, made private) |
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 can remove these various (non-doc) comments about future ideas...
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 these comments would be better suited as an issue than as comments here.
It's more searchable and you can be as verbose as you want without bloating the source code.
/// [Visibility::Public]: crate::Visibility::Public | ||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] | ||
#[non_exhaustive] // could consider e.g. disconnections | ||
pub enum MultipleImplHandling { |
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.
A possibility is to call this just NewImplHandling
with the idea of adding in the future an option ::ConvertToDecls
, this would avoid adding new code (except for the entrypoint subtree, when the next PR adds the feature to insert that alongside linking). Existing enum variants would then be PreferExisting
, PreferNew
, and a wrapper around NewFuncHandling
(which allows to e.g. error upon any new impl)
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.
ImplMergeOptions
?
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.
You can probably tell by now I dont find "Handling" particularly descriptive 😂
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.
Yeah, "Handling" is not too good. I mean, "Options" only tells us the thing is an enum, but I guess this could be..."ImplMerging"? (probably DefnMerging
)?
MergeBehaviour, MergePolicy, HowToMerge(Defns), ...
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.
Looking at the other suggestions...ImplAdd
Something? DefnAddOutcome
? (These options are appropriate for impls/defns only not other things as that's the only situation where you have two things that can be merged)
DefnMerging::ConvertToDecls
(later PR) is not great because that's about adding Defns not merging them...
BlahAddPolicy
is not ridiculous - a NameLinkingPolicy is then largely composed of other/sub policies. But Policy
may make this simple thing sound more complex than it is...
/// multiple [FuncDefn]s according to `multi_impls`. | ||
/// | ||
/// [FuncDefn]: crate::ops::FuncDefn | ||
pub fn err_on_conflict(multi_impls: impl Into<MultipleImplHandling>) -> Self { |
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.
Names for constructors, given that there'll be significant extension to this NameLinkingPolicy
class in the future, are tricky and these are a bit dubious....
/// [Public] function with the same name but different signatures. | ||
/// | ||
/// [Public]: crate::Visibility::Public | ||
pub fn on_signature_conflict(mut self, sc: NewFuncHandling) -> Self { |
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 felt that the short expression style NameLinkingPolicy::foo().on_xyz(bar)
was preferable for configuration rather than having to {let pol = NameLinkingPolicy::foo(); *pol.xyz() = bar; pol }
over several lines. Any counter-opinions?
/// [Visibility::Public]: crate::Visibility::Public | ||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] | ||
#[non_exhaustive] // could consider e.g. disconnections | ||
pub enum NewFuncHandling { |
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 "New" is perhaps a misnomer. It can be applied in the situation where the signature is "new" (for the same name, so a conflict); or when the impl (function body in a FuncDefn) is new (and there is an existing impl too). In the next PR one can apply it whenever the source Hugr adds a new name. Does that merit the New prefix here??
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.
Maybe FuncCreationOptions
?
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.
Ah but you have further enums with the same naming pattern below...
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 is basically "how to add a function to a Hugr". So your FuncCreation(Option) is not bad. FuncAddOptions
, hmmm (I have a mild dislike of "Options" as you can probably also tell 😁). HowToAddFunc (!), FuncAdd(Policy/Method/Technique/How). Resolver, Resolution (it's not an algorithm, it is pretty much a decision at the end of an algorithm). Hmmmm. This seems harder to name than the merge one above....
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.
Maybe something like FuncAddResult? I know it's not a rust enum Result {Ok, Err}
but it's actually not that far off - those are basically the two options available now (::Add
and ::RaiseError
), but I'll be adding ::AddIfReached
and ::ErrorIfReached
in the next PR #2555, and there'll be an ::MakePrivate
(really ::AddIfReachedButMakePrivate
) after that....)
FuncAddOutcome
??
Oh, one thing I can say is it only applies to public functions. So could be PubAddOutcome
or PubFunc....
or something. Not sure that helps.
|
||
/// Details a concrete action to link a specific node from source Hugr into a specific target Hugr. | ||
/// | ||
/// A separate enum from [NodeLinkingDirective] to allow [NameLinkingPolicy::to_node_linking] |
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 may not be appropriate for a doc comment, maybe a non-doc comment or maybe just this review comment i.e. remove it...
/// 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: (WIP) Add
insert_link_hugr
to add entrypoint subtree AND link #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.
#[error("Source ({_1}) and target ({_2}) both contained FuncDefn with same public name {_0}")] | ||
MultipleImpls(String, SN, TN), | ||
/// Source and target containing public functions with conflicting signatures | ||
// TODO ALAN Should we indicate which were decls or defns? via an extra enum? |
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.
Opinions here?
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 the error is clear enough 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.
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
andlink_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.
name: name.clone(), | ||
src_node: n, | ||
src_sig: Box::new(sig.clone()), | ||
tgt_node: *ex_ns.as_ref().left_or_else(|(n, _)| n), |
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 expression *ex_ns.as_ref().left_or_else(|(n, _)| n)
is complex and hard to read. Consider extracting this logic into a helper method or using a clearer pattern match to get the target node from the Either type.
Copilot uses AI. Check for mistakes.
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}"); |
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.
Using assert_eq!
with a custom message that includes {name}
won't interpolate the variable. Use panic!
with format string instead: panic!(\"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); |
Copilot uses AI. Check for mistakes.
#[derive(Clone, Debug, Hash, PartialEq, Eq, derive_more::From)] | ||
#[non_exhaustive] | ||
pub enum LinkAction<TN> { | ||
/// Just apply the specified [NodeLinkingDirective]. | ||
LinkNode(#[from] NodeLinkingDirective<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.
[nitpick] The LinkAction
enum currently has only one variant LinkNode
. If this is intended for future extensibility, consider adding a comment explaining the planned additional variants. Otherwise, this abstraction layer may be unnecessary.
Copilot uses AI. Check for mistakes.
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
/// 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
Adds new methods
link_module
andlink_module_view
.closes #2356