-
Notifications
You must be signed in to change notification settings - Fork 15
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
Refactor @wry/equality
to support custom [deepEquals]
methods
#230
base: main
Are you sure you want to change the base?
Conversation
packages/equality/src/equality.ts
Outdated
export function equal(a: any, b: any): boolean { | ||
try { | ||
return check(a, b); | ||
} finally { | ||
previousComparisons.clear(); | ||
} | ||
return new DeepChecker().check(a, b); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For better or worse, since equality checking now potentially calls into user-provided deepEquals
methods, we cannot be sure the check(a, b)
function will return before other code calls equal(...)
, so the trick of using a single previousComparisons
map and clearing it after each check no longer works, and we need to allocate a separate DeepChecker
(with its own comparisons
map) for each call to equal(a, b)
.
The `@wry/*` packages are all believed to work in Node 10, but that Node.js version has been end-of-life'd (even for security updates) since April 2021: https://endoflife.date/nodejs Removing v10 should fix the tests in PR #230, which are broken because Node 10 does not understand class property syntax in the `tests.cjs.js` bundle. The main `@wry/equality` library continues to be compiled to es2015, eliminating class syntax, but the tests need to be compiled to esnext to test generator and async function equality. There may be a way to satisfy both of these constraints, but the easiest solution right now is to avoid testing in Node 10.
We can't get away with having only one previousComparisons Map any more, now that we're allowing user-provided code to run during the recursive comparison, because that user-provided code could call the top-level equal(a, b) function reentrantly.
Since array equality checking no longer falls through to the object case, we can preserve the `definedKeys` behavior for objects (introduced in #21) for arrays, by treating any array holes as undefined elements, using an ordinary `for` loop. Using `a.every` doesn't work because `Array` iteration methods like `Array.prototyp.every` skip over holes.
The `@wry/*` packages are all believed to work in Node 10, but that Node.js version has been end-of-life'd (even for security updates) since April 2021: https://endoflife.date/nodejs Removing v10 should fix the tests in PR #230, which are broken because Node 10 does not understand class property syntax in the `tests.cjs.js` bundle. The main `@wry/equality` library continues to be compiled to es2015, eliminating class syntax, but the tests need to be compiled to esnext to test generator and async function equality. There may be a way to satisfy both of these constraints, but the easiest solution right now is to avoid testing in Node 10.
Using a Symbol should remove any uncertainty about whether the object in question truly intended to implement the Equatable interface, or just happens to define a method called "deepEquals", which might or might not have the same signature.
93d405a
to
e87f5e3
Compare
@wry/equality
to support custom deepEquals
methods@wry/equality
to support custom [deepEquals]
methods
The
@wry/equality
package currently uses only===
equality to compare objects whoseObject.prototype.toString
tag is something other than[object Object]
, but it can mistakenly use deep equality for custom classes/objects that use the default[object Object]
tag. This is a mistake because there's no guarantee iterating over the public enumerable properties of an arbitrary object is a good way to check for equality, so it's better to stay safe and use===
.This PR fixes that mistake by comparing objects that have custom prototypes (other than
Object.prototype
ornull
) using only===
by default. Since this restriction potentially leaves custom objects with no way to participate in deep equality checking, this PR allows objects to implement a[deepEquals]
method if they want to participate, wheredeepEquals
is aSymbol
that must be imported from the@wry/equality
package:This optional
[deepEquals]
method must be defined by both objects, anda[deepEquals](b)
must agree withb[deepEquals](a)
(ifa[deepEquals] !== b[deepEquals]
). If you need to perform nested comparisons, you should use the predicate function passed as the second parameter (equal
in thePoint2D
example above), since it knows which objects have been compared previously, so cycles in the graph will not cause infinite loops.In the process of supporting
[deepEquals]
, I realized the bigswitch (aTag) {...}
list within@wry/equality
had gotten long enough to be slower than using aMap
to look up checker functions (which should take constant time, regardless of how many types are supported by@wry/equality
), so I refactored that system to use aMap
instead of aswitch
.