Skip to content
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

Fix: Relax Interner trait bounds for borrowed elements #14039

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

raynelfss
Copy link
Contributor

Summary

Fix trait bounds in certain methods of Interner to allow for the use of elements that satisfy the trait bounds of Equivalent<T> in IndexMap.

Details and comments

In a very special edge case for the Interner a user was not able to check if it contained an instance of String from an &str. The following commits relax the trait bounds on the contains, try_key, and insert methods, to allow for more flexibility by behaving similarly to the trait Equivalent<T> from IndexMap.

- Fix trait bounds in certain methods of `Interner` to allow use of elements that satisfy the trait bounds of `Equivalent<T>` in `IndexMap`.
@raynelfss raynelfss added the Changelog: None Do not include in changelog label Mar 17, 2025
@raynelfss raynelfss requested a review from a team as a code owner March 17, 2025 17:46
@qiskit-bot
Copy link
Collaborator

One or more of the following people are relevant to this code:

  • @Qiskit/terra-core

@coveralls
Copy link

coveralls commented Mar 17, 2025

Pull Request Test Coverage Report for Build 13931421405

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 5 of 15 (33.33%) changed or added relevant lines in 1 file are covered.
  • 50 unchanged lines in 5 files lost coverage.
  • Overall coverage decreased (-0.02%) to 88.07%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/circuit/src/interner.rs 5 15 33.33%
Files with Coverage Reduction New Missed Lines %
crates/accelerate/src/unitary_synthesis.rs 1 94.69%
crates/qasm2/src/lex.rs 4 93.23%
qiskit/transpiler/passes/scheduling/time_unit_conversion.py 9 92.31%
crates/qasm2/src/parse.rs 12 97.15%
crates/circuit/src/bit.rs 24 87.14%
Totals Coverage Status
Change from base Build 13905217637: -0.02%
Covered Lines: 72612
Relevant Lines: 82448

💛 - Coveralls

pub fn try_key<Q>(&self, value: &Q) -> Option<Interned<T>>
where
Q: ?Sized + Hash + Eq + ToOwned,
T: Borrow<Q> + ToOwned<Owned = <Q as ToOwned>::Owned>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we can instead reexport indexmap's Equivalent with pub use indexmap::Equivalent in this file? And then just use it in these method bounds so it's easier to understand what's happening.

Copy link
Contributor Author

@raynelfss raynelfss Mar 17, 2025

Choose a reason for hiding this comment

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

I tried putting in Q: Equivalent<T> alone, but then the compiler will complain because, to satisfy the trait bounds of Q: Equivalent<T>, Q needs to be: Q: ?Sized + ToOwned, and T needs to satisfy T: Borrow<Q> as a minimum. The rest is more specificity to mention that the owned versions of both T and Q need to be of the same base type.

Bringing those trait bounds in already makes using Q: Equivalent<T> redundant, but it is implicitly satisfied. Is there an easier wa of conveying this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this would work?

pub fn try_key<Q>(&self, value: &Q) -> Option<Interned<T>>
    where
        Q: ?Sized + Hash + Equivalent<T::Owned>,
    {
        todo!()
    }

Copy link
Contributor Author

@raynelfss raynelfss Mar 18, 2025

Choose a reason for hiding this comment

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

This works as long as we also specify that T: Borrow<Q>. Unfortunately, though, it's still not enough for the insert method. Fixed in d5161e3!

Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

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

In a very special edge case for the Interner a user was not able to check if it contained an instance of String from an &str.

Can you share exactly where this was happening? The original trait bounds were written specifically to permit this, and there's even a test that involves try_key with a String interner, (can_merge_two_interners), so it definitely should work. Are you sure that you're not accidentally passing a &&str instead of a &str somewhere?

That said: I'm not opposed to expanding the traits to make it easier, if there's no harm.

Comment on lines +414 to +418
pub fn insert<Q>(&mut self, value: &Q) -> Interned<T>
where
Q: ?Sized + Hash + Eq + ToOwned,
T: Borrow<Q> + ToOwned<Owned = <Q as ToOwned>::Owned>,
{
Copy link
Member

Choose a reason for hiding this comment

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

Minor: you can possibly put the Owned = bound into the trait bound on Q and avoid needing to respecify it on T, which is already bound in the impl prelude.

@raynelfss
Copy link
Contributor Author

In a very special edge case for the Interner a user was not able to check if it contained an instance of String from an &str.

Can you share exactly where this was happening? The original trait bounds were written specifically to permit this, and there's even a test that involves try_key with a String interner, (can_merge_two_interners), so it definitely should work. Are you sure that you're not accidentally passing a &&str instead of a &str somewhere?

Sure, @jakelishman! This happened in the wake of me creating #14040. When you try creating an Interner<String> it will not let you use try_key or contains with just an instance of &str. Since the functions were previously defined with &T in mind, it would reject the instance of &str since it didn't match the type &String.

@jakelishman
Copy link
Member

Ok right, but that's because your interner type is defined wrong. It should be Interner<str>, not Interner<String>.

@jakelishman
Copy link
Member

jakelishman commented Mar 19, 2025

To expand slightly: we don't use Interner<Vec<T>>, we use Interner<[T]>, because the "inner" object we're actually concerned with interning is the [T]. The owned sized version of that is Vec<T> and the reference type is &[T], while the base underlying unsized type that the interner strictly stores is [T], of which Interned<[T]> is kind of like a smart-pointer to (of course it's not a proper pointer because actually it's just an offset into an Interner's internal data structure), but you're meant to be able to think of it like that's what it represents.

str is to [T] as String is to Vec<T>.

@raynelfss
Copy link
Contributor Author

@jakelishman that makes sense, I'll make sure to change the label accordingly. That being said, do you think that would completely invalidate the additions of this PR? It seems unnecessary, going off by what you just described.

@jakelishman
Copy link
Member

I'm fine with expanding the interface of the Interner if it's useful. I suppose there's a minor risk that expanding the interface may make it easier to accidentally use the wrong type (since that seems to have been the underlying problem here), but that's not a great reason to avoid something if there's real benefit to expanding what we can accept.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: None Do not include in changelog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants