Skip to content

Make semantics of asset comparisons consistent#1263

Open
alexdewar wants to merge 3 commits intomainfrom
fix-comparing-assets
Open

Make semantics of asset comparisons consistent#1263
alexdewar wants to merge 3 commits intomainfrom
fix-comparing-assets

Conversation

@alexdewar
Copy link
Copy Markdown
Member

Description

AssetRef needs to implement Ord, Eq and Hash and we have to do this manually as opposed to deriving the traits. There are a couple of problems with the current implementations, however:

  1. Under some circumstances, they panic (e.g. because the AssetState is one that we don't need to hash)
  2. They are not consistent with one another (e.g. you can only sort commissioned assets, but you can test for equality of more types)

While this might not cause problems with the code as it is, it is a fragile and bugprone setup and we may end up violating some invariant in the standard library somewhere at some point (e.g. see documentation for HashMap).

Let's make these implementations all consistent. The way I've done this is to define a separate AssetCmp enum, which contains just the properties we use for comparison and which derives Ord, Eq, Hash etc. The implementations of these traits for AssetRef can then just create AssetCmp objects as needed and then provide a thin wrapper around the derived implementation for AssetCmp. Another upside of this approach is it makes it easy to change the properties that are compared by just changing AssetCmp, rather than having to change the code in a bunch of different places.

One difference from the old code is that we are now comparing agent IDs and parent IDs (if present) for Ord and Eq, whereas before we didn't bother, but in practice, this won't change anything. Similarly, there were places where we compared assets' processes with Rc::ptr_eq but now use the process ID, but it shouldn't be a functional change in our code base.

Closes #1215.

Type of change

  • Bug fix (non-breaking change to fix an issue)
  • New feature (non-breaking change to add functionality)
  • Refactoring (non-breaking, non-functional change to improve maintainability)
  • Optimization (non-breaking change to speed up the code)
  • Breaking change (whatever its nature)
  • Documentation (improve or add documentation)

Key checklist

  • All tests pass: $ cargo test
  • The documentation builds and looks OK: $ cargo doc
  • Update release notes for the latest release if this PR adds a new feature or fixes a bug
    present in the previous release

Further checks

  • Code is commented, particularly in hard-to-understand areas
  • Tests added that prove fix is effective or that feature works

Copilot AI review requested due to automatic review settings April 27, 2026 14:32
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.76%. Comparing base (a10a1f4) to head (2950ee2).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1263      +/-   ##
==========================================
+ Coverage   89.74%   89.76%   +0.01%     
==========================================
  Files          57       57              
  Lines        8195     8189       -6     
  Branches     8195     8189       -6     
==========================================
- Hits         7355     7351       -4     
+ Misses        544      542       -2     
  Partials      296      296              

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

Copy link
Copy Markdown
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 refactors AssetRef comparison behavior to make Eq/Ord/Hash consistent and non-panicking by introducing an internal AssetCmp key type that derives the comparison traits, addressing the inconsistency described in #1215.

Changes:

  • Remove derived PartialEq from Asset.
  • Implement AssetRef’s PartialEq/Eq/Ord/PartialOrd/Hash via a new derived AssetCmp enum.
  • Update/replace AssetRef documentation and comparison logic to avoid panics and align semantics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/asset.rs
Comment on lines +123 to 124
#[derive(Clone)]
pub struct Asset {
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

Removing PartialEq from the public Asset type is an API-breaking change for downstream crates (they can no longer write asset1 == asset2 or use Asset in APIs requiring PartialEq). If the intent is to avoid deep comparisons for performance, consider implementing a manual PartialEq with the desired semantics (e.g., ID-based when present / a shallow key otherwise), or gate the removal behind a major version bump / explicit deprecation plan.

Copilot uses AI. Check for mistakes.
Comment thread src/asset.rs Outdated
Comment thread src/asset.rs
Comment on lines +1048 to +1061
/// Get a representation of this [`AssetRef`] that can be used for comparisons
fn get_asset_cmp(&self) -> AssetCmp<'_> {
if let Some(id) = self.id() {
AssetCmp::WithID(id)
} else {
AssetCmp::WithoutID((
self.process_id(),
self.region_id(),
self.commission_year,
self.agent_id(),
self.group_id(),
))
}
}
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

This PR changes the fundamental Eq/Ord/Hash semantics for AssetRef (and removes prior panics). There are existing tests in this module, but none that assert key invariants like: (1) a == b implies equal hashes, (2) hashing/ordering does not panic for all AssetState variants, and (3) commissioned (WithID) assets compare/hash purely by ID. Adding focused unit tests around get_asset_cmp/AssetCmp would help prevent regressions.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
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 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/asset.rs
Comment on lines 1185 to 1206
@@ -1212,12 +1200,31 @@ impl PartialOrd for AssetRef {
}
}

impl Ord for AssetRef {
fn cmp(&self, other: &Self) -> Ordering {
self.id().unwrap().cmp(&other.id().unwrap())
impl Hash for AssetRef {
fn hash<H: Hasher>(&self, state: &mut H) {
self.get_asset_cmp().hash(state);
}
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

This change is specifically about making Eq/Ord/Hash for AssetRef consistent and non-panicking, but there don’t appear to be any unit tests covering the new comparison/hashing semantics (e.g., hashing/ordering Future/Selected/Decommissioned assets and asserting Eq/Ord/Hash consistency). Adding targeted tests here would help prevent regressions in key semantics used by HashMap/sorting.

Copilot uses AI. Check for mistakes.
Comment thread src/asset.rs Outdated
Comment on lines +1040 to +1041
/// [`AssetRef`] implements equality, ordering, and hashing using the comparison key represented by
/// `AssetCmp`. When the underlying asset has an ID, that ID is used for `Eq`/`Ord`/`Hash`.
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

The doc comment references the comparison key as AssetCmp, but AssetCmp is an internal/private type and won’t be visible in generated docs. Consider rewording to describe it as an internal comparison key (or link it as [AssetCmp] and make it pub(crate) if you want it referenced in docs), to avoid confusing readers of the public API docs.

Suggested change
/// [`AssetRef`] implements equality, ordering, and hashing using the comparison key represented by
/// `AssetCmp`. When the underlying asset has an ID, that ID is used for `Eq`/`Ord`/`Hash`.
/// [`AssetRef`] implements equality, ordering, and hashing using an internal comparison key.
/// When the underlying asset has an ID, that ID is used for `Eq`/`Ord`/`Hash`.

Copilot uses AI. Check for mistakes.
@alexdewar alexdewar force-pushed the fix-comparing-assets branch from 4148f8c to 2950ee2 Compare April 27, 2026 15:09
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.

Inconsistent semantics (and bad performance?) when comparing asset types

2 participants