Skip to content

Conversation

@natsukagami
Copy link
Contributor

Fixes #24749.

The PR contains two parts, one tiny and the other more involved.

KeySet performance issues

Previously we use a List[K] to hold keys inside the strict KeySet, in order to
keep iteration order stable. However this degrades performance of KeySet as a Set.
The fix is to simply use something that has both. Currently it means mutable.LinkedHashSet,
so we use it, even though we don't intend to mutate the set at all.

Missing LazyKeySet implementations for some strict Maps

As part of #23769 we categorised most data structures to be strict/pure (as in "not hiding any
computation as part of its structure"), which includes Set.

This however clashes with how .keySet method was implemented: it returns a Set[K], but
provides no guarantee on whether it is a lazy view over the underlying data structure
(which can be lazy, e.g. when you call .keySet from a MapView) or a strict set.
With API compatibility in mind, we now create strict key sets (with a copy of the keys)
in the generic case, while trying to detect whether the .keySet method was called from an
already strict data-structure (which is the majority case) - returning a thin-wrapper LazyKeySet
in that case. As the LazyKeySet works like a view with no aggregation, applying it over a strict
data structure doesn't violate strictness.

This left a few holes however, which was overlooked: some data structures such as mutable.HashSet
overrides .keySet with their own (protected!) classes, extending from the previously-ambiguous
KeySet classes (that is now made strict), and accidentally ending up always using the strict version.

Unfortunately we cannot modify these extended classes (for TASTy compatibility), so similar versions
extending the thin wrapper are added instead, and the old classes deprecated.
To not shoot ourselves in the foot in the future, these classes are also made private[collection]:
We don't expect classes like HashMap to be extended with a custom KeySet, and in the cases that
it does, it is not a problem to reimplement the missing 4-5 methods in the most efficient way for your
extended data structure.

We need a data structure that both acts like a Set (queries membership efficiently), and has a stable iteration order.
LinkedHashSet seems to fit this criteria.
LinkedHashMap's `keySet` has always been returning a LinkedKeySet, a private data structure that was extended from the
generic MapOps.KeySet. Recently MapOps.KeySet was changed to a strict data structure, so we change the `keySet` method to
use the lazy wrapper instead. Unfortunately we cannot change the hierarchy of `LinkedKeySet`, but the API never said we have to use it.
@natsukagami natsukagami requested a review from a team as a code owner December 16, 2025 17:59
@Gedochao Gedochao added the backport:nominated If we agree to backport this PR, replace this tag with "backport:accepted", otherwise delete it. label Dec 17, 2025
@Gedochao Gedochao added this to the 3.8.0 milestone Dec 17, 2025
@natsukagami natsukagami marked this pull request as draft December 17, 2025 17:37
@natsukagami
Copy link
Contributor Author

It turns out that our change to MapOps.GenKeySet in #23769 was never backwards-compatible -- it creates abstract getters and setters for the allKeys val.

I will make a commit that rolls back this change, add some unsafeDiscardUses (which requires #24273 to be backported to the next RC) to make GenKeySet capture-check, and then completely deprecate this trait.

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

Labels

backport:nominated If we agree to backport this PR, replace this tag with "backport:accepted", otherwise delete it.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Map#keySet is slow in 3.8.0-RC3

2 participants