You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fixes three defects in the internal equal deep comparison. Two came from porting IgniteUI/igniteui-react#193, and the third came up in review.
Set/Map one-to-one matching. An entry could match an entry on the other side that was already matched, so new Set([{x:1},{x:1}]) equaled new Set([{x:1},{x:2}]). Each entry now takes a distinct partner.
Symmetric conversions. A custom valueOf/toString was checked on the first argument only, so equal({}, Object.create({ valueOf: () => 1 })) was true and the reverse was false. This also made greedy Set/Map pairing miss valid matches. Objects now compare by conversion only when both sides override it, and are unequal when only one side does. equal is now an equivalence relation, so greedy pairing is correct.
Null-prototype objects. Comparing Object.create(null) objects threw valueOf is not a function. They now compare by their keys.
…jects
A Set or Map entry could match an entry on the other side that was
already matched, so `new Set([{x:1},{x:1}])` equaled
`new Set([{x:1},{x:2}])`. Each entry now takes a distinct match.
Null-prototype objects threw, since `valueOf` and `toString` were
called without an existence check.
Ported from IgniteUI/igniteui-react#193.
Greedy matching fails with asymmetric custom comparisons
src/internals/utils/objects.ts:80
This greedy choice is not safe for all values supported by equal: custom valueOf/toString dispatch makes comparisons directional (for example, equal({}, Object.create({ valueOf: () => 1 })) is true, while the reverse is false). With a plain object before that custom object on the left, the first candidate is consumed and the otherwise valid one-to-one pairing is missed, so two Sets that contain the same two objects can compare false. Use a complete bipartite matching/backtracking strategy, or make the underlying comparison symmetric before relying on greedy matching.
Comparing a custom conversion with the default one made `equal`
directional and non-transitive, so the greedy Set/Map pairing could
miss a valid match. Objects now compare by conversion only when both
override it, and are unequal when only one does. `equal` is then an
equivalence relation, and greedy pairing is safe.
rkaraivanov
changed the title
fix(equal): match Set/Map entries one to one, allow null-prototype objects
fix(equal): one-to-one Set/Map matching, symmetric conversions, null-prototype objects
Sep 28, 2026
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
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.
Description
Fixes three defects in the internal
equaldeep comparison. Two came from porting IgniteUI/igniteui-react#193, and the third came up in review.new Set([{x:1},{x:1}])equalednew Set([{x:1},{x:2}]). Each entry now takes a distinct partner.valueOf/toStringwas checked on the first argument only, soequal({}, Object.create({ valueOf: () => 1 }))wastrueand the reverse wasfalse. This also made greedy Set/Map pairing miss valid matches. Objects now compare by conversion only when both sides override it, and are unequal when only one side does.equalis now an equivalence relation, so greedy pairing is correct.Object.create(null)objects threwvalueOf is not a function. They now compare by their keys.Ported from IgniteUI/igniteui-react#193.
Type of Change
Checklist