[OrderedCollections] Share OrderedSet's replace primitive with OrderedDictionary.replaceElement - #688
Open
inju2403 wants to merge 2 commits into
Open
Conversation
…dDictionary.replaceElement apple#669 left `OrderedDictionary.replaceElement` untouched to keep that PR purely additive, noting it could later be reimplemented on top of `replace(at:with:)`. Both it and `OrderedSet.replace(at:with:)` performed the same "append the new member, swap it into position, then remove the old one from the end" step inline. Factor that step into an internal `OrderedSet._replaceNew(at:with:in:)` primitive and call it from both, matching the existing `_appendNew` / `_removeExistingMember` primitives that `OrderedDictionary` already delegates its key-side mutations to. In `replaceElement` only the keys go through the primitive; the value at `index` is overwritten in place. Sharing the mechanical step this way — rather than having `replaceElement` call `replace` directly — keeps each type's diagnostics intact: `OrderedDictionary` still traps with "Duplicate key: '\(key)'" and "Index out of range", not `OrderedSet`'s "Duplicate element" / "Index out of bounds". The shared step is already verified by the caller, so there is no behavior change.
lorentey
approved these changes
Jul 20, 2026
…alue Co-authored-by: Karoy Lorentey <klorentey@apple.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
OrderedDictionary.replaceElement(at:withKey:value:)andOrderedSet.replace(at:with:)each performed the same "append the new member, swap it into position, then remove the old one from the end" step inline. This factors that step into an internalOrderedSet._replaceNew(at:with:in:)primitive shared by both. Behavior-preserving cleanup; no public API change.Motivation
This is the follow-up flagged in #669. Adding
OrderedSet.replace(at:with:)there, I noted thatOrderedDictionary.replaceElement(added in #616) already performed this very operation by hand on its keys —_keys._appendNew,_keys.swapAt,_keys.removeLast— and could be reimplemented on top ofreplace. #669 left that out to keep it purely additive; this is that cleanup.Rather than having
replaceElementcallreplace(at:with:)directly, it shares only the mechanical step through an internal primitive. Callingreplacedirectly is simpler, but it would route the dictionary's key mutation throughOrderedSet's checks and replace the dictionary's own diagnostics:replacecall_replaceNewprimitive (this PR)Duplicate elementDuplicate key: '\(key)'Index out of boundsIndex out of range_findinsidereplacebucketthe caller already resolvedThe primitive mirrors the existing
_appendNew/_removeExistingMemberinternal primitives thatOrderedDictionaryalready delegates its key-side mutations to.Detailed design
Adds an internal
OrderedSet._replaceNew(at:with:in:):bucketalready resolved by the caller's_find, appendsitem, swaps it intoindex, then removes the old member from the end — each step O(1). It performs no duplicate/bounds checks; the caller is responsible for those, matching_appendNew(_:in:)and_removeExistingMember(at:in:).OrderedSet.replace(at:with:)now calls it after its own_find+ precondition, instead of inlining the append/swap/remove.OrderedDictionary.replaceElement's general case routes the key through_keys._replaceNewand overwrites the value atindexin place — rather than moving the value through the same append/swap/remove step. Its equal-key in-place path and its own preconditions (Index out of range,Duplicate key: '\(key)') are unchanged.Testing
Behavior-preserving refactor, so no new tests are added. The existing
OrderedCollectionsTestssuite passes, and the affectedreplace(at:with:)/replaceElementtests — which exercise every index across small element counts with both shared and unshared storage — pass with and without-Xswiftc -DCOLLECTIONS_INTERNAL_CHECKS.Checklist