Skip to content

Commit 5ffeaec

Browse files
izelnakriKrinkle
authored andcommitted
Core: Make equiv internal pairs state passed in
Ref qunitjs#1701
1 parent 3b00e36 commit 5ffeaec

File tree

1 file changed

+21
-35
lines changed

1 file changed

+21
-35
lines changed

Diff for: src/equiv.js

+21-35
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@ import { StringSet } from './globals';
33

44
const BOXABLE_TYPES = new StringSet(['boolean', 'number', 'string']);
55

6-
// Memory for previously seen containers (object, array, map, set).
7-
// Used for recursion detection, and to avoid repeated comparison.
8-
//
9-
// Elements are { a: val, b: val }.
10-
let memory = [];
11-
126
function useStrictEquality (a, b) {
137
return a === b;
148
}
@@ -69,14 +63,14 @@ const objTypeCallbacks = {
6963
// identical reference only
7064
function: useStrictEquality,
7165

72-
array (a, b) {
66+
array (a, b, memory) {
7367
if (a.length !== b.length) {
7468
// Safe and faster
7569
return false;
7670
}
7771

7872
for (let i = 0; i < a.length; i++) {
79-
if (!typeEquiv(a[i], b[i])) {
73+
if (!typeEquiv(a[i], b[i], memory)) {
8074
return false;
8175
}
8276
}
@@ -88,7 +82,7 @@ const objTypeCallbacks = {
8882
// repetitions are not counted, so these are equivalent:
8983
// a = new Set( [ X={}, Y=[], Y ] );
9084
// b = new Set( [ Y, X, X ] );
91-
set (a, b) {
85+
set (a, b, memory) {
9286
if (a.size !== b.size) {
9387
// This optimization has certain quirks because of the lack of
9488
// repetition counting. For instance, adding the same
@@ -116,14 +110,11 @@ const objTypeCallbacks = {
116110
return;
117111
}
118112

119-
// Swap out the global memory, as nested typeEquiv() would clobber it
120-
const originalMemory = memory;
121-
memory = [];
113+
// Don't pass memory, as this nested typeEquiv() must operate
114+
// independently and would otherwise clobber it
122115
if (typeEquiv(bVal, aVal)) {
123116
innerEq = true;
124117
}
125-
// Restore
126-
memory = originalMemory;
127118
});
128119

129120
if (!innerEq) {
@@ -168,14 +159,11 @@ const objTypeCallbacks = {
168159
return;
169160
}
170161

171-
// Swap out the global memory, as nested typeEquiv() would clobber it
172-
const originalMemory = memory;
173-
memory = [];
174-
if (objTypeCallbacks.array([bVal, bKey], [aVal, aKey])) {
162+
// Don't memory main memory, as nested typeEquiv() would clobber it
163+
const localMemory = [];
164+
if (objTypeCallbacks.array([bVal, bKey], [aVal, aKey], localMemory)) {
175165
innerEq = true;
176166
}
177-
// Restore
178-
memory = originalMemory;
179167
});
180168

181169
if (!innerEq) {
@@ -200,7 +188,7 @@ const entryTypeCallbacks = {
200188
symbol: useStrictEquality,
201189

202190
function: useStrictEquality,
203-
object (a, b) {
191+
object (a, b, memory) {
204192
// Handle memory (skip recursion)
205193
if (memory.some((pair) => pair.a === a && pair.b === b)) {
206194
return true;
@@ -212,7 +200,7 @@ const entryTypeCallbacks = {
212200
if (aObjType !== 'object' || bObjType !== 'object') {
213201
// Handle literal `null`
214202
// Handle: Array, Map/Set, Date, Regxp/Function, boxed primitives
215-
return aObjType === bObjType && objTypeCallbacks[aObjType](a, b);
203+
return aObjType === bObjType && objTypeCallbacks[aObjType](a, b, memory);
216204
}
217205

218206
// NOTE: Literal null must not make it here as it would throw
@@ -238,7 +226,7 @@ const entryTypeCallbacks = {
238226
) {
239227
continue;
240228
}
241-
if (!typeEquiv(a[i], b[i])) {
229+
if (!typeEquiv(a[i], b[i], memory)) {
242230
return false;
243231
}
244232
}
@@ -248,11 +236,16 @@ const entryTypeCallbacks = {
248236
bProperties.push(i);
249237
}
250238

251-
return objTypeCallbacks.array(aProperties.sort(), bProperties.sort());
239+
return objTypeCallbacks.array(aProperties.sort(), bProperties.sort(), memory);
252240
}
253241
};
254242

255-
function typeEquiv (a, b) {
243+
244+
// Memory for previously seen containers (object, array, map, set).
245+
// Used for recursion detection, and to avoid repeated comparison.
246+
//
247+
// Elements are { a: val, b: val }.
248+
function typeEquiv (a, b, memory = []) {
256249
// Optimization: Only perform type-specific comparison when pairs are not strictly equal.
257250
if (a === b) {
258251
return true;
@@ -267,14 +260,7 @@ function typeEquiv (a, b) {
267260
(bType === 'object' && BOXABLE_TYPES.has(objectType(b)) ? b.valueOf() : b);
268261
}
269262

270-
return entryTypeCallbacks[aType](a, b);
271-
}
272-
273-
function innerEquiv (a, b) {
274-
const res = typeEquiv(a, b);
275-
// Release any retained objects and reset recursion detection for next call
276-
memory = [];
277-
return res;
263+
return entryTypeCallbacks[aType](a, b, memory);
278264
}
279265

280266
/**
@@ -285,14 +271,14 @@ function innerEquiv (a, b) {
285271
*/
286272
export default function equiv (a, b) {
287273
if (arguments.length === 2) {
288-
return (a === b) || innerEquiv(a, b);
274+
return (a === b) || typeEquiv(a, b);
289275
}
290276

291277
// Given 0 or 1 arguments, just return true (nothing to compare).
292278
// Given (A,B,C,D) compare C,D then B,C then A,B.
293279
let i = arguments.length - 1;
294280
while (i > 0) {
295-
if (!innerEquiv(arguments[i - 1], arguments[i])) {
281+
if (!typeEquiv(arguments[i - 1], arguments[i])) {
296282
return false;
297283
}
298284
i--;

0 commit comments

Comments
 (0)