@@ -33,11 +33,6 @@ const errors = require('internal/errors');
3333
3434const assert = module . exports = ok ;
3535
36- // At present only the three keys mentioned above are used and
37- // understood by the spec. Implementations or sub modules can pass
38- // other keys to the AssertionError's constructor - they will be
39- // ignored.
40-
4136// All of the following functions must throw an AssertionError
4237// when a corresponding condition is not met, with a message that
4338// may be undefined if not provided. All assertion methods provide
@@ -112,9 +107,9 @@ function areSimilarRegExps(a, b) {
112107 return a . source === b . source && a . flags === b . flags ;
113108}
114109
115- // For small buffers it's faster to compare the buffer in a loop.
116- // The c++ barrier takes the advantage of the faster compare otherwise.
117- // 300 was the number after which compare became faster.
110+ // For small buffers it's faster to compare the buffer in a loop. The c++
111+ // barrier including the Buffer.from operation takes the advantage of the faster
112+ // compare otherwise. 300 was the number after which compare became faster.
118113function areSimilarTypedArrays ( a , b ) {
119114 const len = a . byteLength ;
120115 if ( len !== b . byteLength ) {
@@ -221,21 +216,15 @@ function looseDeepEqual(actual, expected) {
221216 return false ;
222217 }
223218 if ( util . isDate ( actual ) && util . isDate ( expected ) ) {
224- if ( actual . getTime ( ) !== expected . getTime ( ) ) {
225- return false ;
226- }
227- return true ;
219+ return actual . getTime ( ) === expected . getTime ( ) ;
228220 }
229221 if ( util . isRegExp ( actual ) && util . isRegExp ( expected ) ) {
230- if ( ! areSimilarRegExps ( actual , expected ) ) {
231- return false ;
232- }
233- return true ;
222+ return areSimilarRegExps ( actual , expected ) ;
234223 }
235224 const actualTag = objectToString ( actual ) ;
236225 const expectedTag = objectToString ( expected ) ;
237226 if ( actualTag === expectedTag ) {
238- if ( ! isFloatTypedArrayTag ( actualTag ) && ! isObjectOrArrayTag ( actualTag ) &&
227+ if ( ! isObjectOrArrayTag ( actualTag ) && ! isFloatTypedArrayTag ( actualTag ) &&
239228 ArrayBuffer . isView ( actual ) ) {
240229 return areSimilarTypedArrays ( actual , expected ) ;
241230 }
@@ -314,29 +303,38 @@ function innerDeepEqual(actual, expected, strict, memos) {
314303 return areEq ;
315304}
316305
317- function setHasSimilarElement ( set , val1 , usedEntries , strict , memo ) {
318- if ( set . has ( val1 ) ) {
319- if ( usedEntries !== null )
320- usedEntries . add ( val1 ) ;
321- return true ;
322- }
323-
324- // In strict mode the only things which can match a primitive or a function
325- // will already be detected by set.has(val1).
326- if ( strict && ( typeof val1 !== 'object' || val1 === null ) )
327- return false ;
328-
329- // Otherwise go looking.
306+ function setHasEqualElement ( set , val1 , strict , memo ) {
307+ // Go looking.
330308 for ( const val2 of set ) {
331- if ( ! usedEntries . has ( val2 ) && innerDeepEqual ( val1 , val2 , strict , memo ) ) {
332- usedEntries . add ( val2 ) ;
309+ if ( innerDeepEqual ( val1 , val2 , strict , memo ) ) {
310+ // Remove the matching element to make sure we do not check that again.
311+ set . delete ( val2 ) ;
333312 return true ;
334313 }
335314 }
336315
337316 return false ;
338317}
339318
319+ // Note: we actually run this multiple times for each loose key!
320+ // This is done to prevent slowing down the average case.
321+ function setHasLoosePrim ( a , b , val ) {
322+ const altValues = findLooseMatchingPrimitives ( val ) ;
323+ if ( altValues === undefined )
324+ return false ;
325+
326+ var matches = 1 ;
327+ for ( var i = 0 ; i < altValues . length ; i ++ ) {
328+ if ( b . has ( altValues [ i ] ) ) {
329+ matches -- ;
330+ }
331+ if ( a . has ( altValues [ i ] ) ) {
332+ matches ++ ;
333+ }
334+ }
335+ return matches === 0 ;
336+ }
337+
340338function setEquiv ( a , b , strict , memo ) {
341339 // This code currently returns false for this pair of sets:
342340 // assert.deepEqual(new Set(['1', 1]), new Set([1]))
@@ -348,59 +346,124 @@ function setEquiv(a, b, strict, memo) {
348346 if ( a . size !== b . size )
349347 return false ;
350348
351- // This is a set of the entries in b which have been consumed in our pairwise
352- // comparison .
353- //
349+ // This is a lazily initiated Set of entries which have to be compared
350+ // pairwise .
351+ var set = null ;
354352 // When the sets contain only value types (eg, lots of numbers), and we're in
355- // strict mode, we don't need to match off the entries in a pairwise way. In
356- // that case this initialization is done lazily to avoid the allocation &
357- // bookkeeping cost. Unfortunately, we can't get away with that in non-strict
358- // mode.
359- let usedEntries = strict === true ? null : new Set ( ) ;
360-
361- for ( const val1 of a ) {
362- if ( usedEntries === null && typeof val1 === 'object' )
363- usedEntries = new Set ( ) ;
364-
365- // If the value doesn't exist in the second set by reference, and its an
366- // object or an array we'll need to go hunting for something thats
367- // deep-equal to it. Note that this is O(n^2) complexity, and will get
368- // slower if large, very similar sets / maps are nested inside.
369- // Unfortunately there's no real way around this.
370- if ( ! setHasSimilarElement ( b , val1 , usedEntries , strict , memo ) )
353+ // strict mode or if all entries strictly match, we don't need to match the
354+ // entries in a pairwise way. In that case this initialization is done lazily
355+ // to avoid the allocation & bookkeeping cost.
356+ for ( const val of a ) {
357+ // Note: Checking for the objects first improves the performance for object
358+ // heavy sets but it is a minor slow down for primitives. As they are fast
359+ // to check this improves the worst case scenario instead.
360+ if ( typeof val === 'object' && val !== null ) {
361+ if ( set === null ) {
362+ set = new Set ( ) ;
363+ }
364+ // If the specified value doesn't exist in the second set its an not null
365+ // object (or non strict only: a not matching primitive) we'll need to go
366+ // hunting for something thats deep-(strict-)equal to it. To make this
367+ // O(n log n) complexity we have to copy these values in a new set first.
368+ set . add ( val ) ;
369+ } else if ( ! b . has ( val ) && ( strict || ! setHasLoosePrim ( a , b , val ) ) ) {
371370 return false ;
371+ }
372+ }
373+
374+ if ( set !== null ) {
375+ for ( const val of b ) {
376+ // In non-strict-mode we have to check if a primitive value is already
377+ // matching and only if it's not, go hunting for it.
378+ if ( typeof val === 'object' && val !== null ) {
379+ if ( ! setHasEqualElement ( set , val , strict , memo ) )
380+ return false ;
381+ } else if ( ! a . has ( val ) && ( strict || ! setHasLoosePrim ( b , a , val ) ) ) {
382+ return false ;
383+ }
384+ }
372385 }
373386
374387 return true ;
375388}
376389
377- function mapHasSimilarEntry ( map , key1 , item1 , usedEntries , strict , memo ) {
378- // To be able to handle cases like:
379- // Map([[1, 'a'], ['1', 'b']]) vs Map([['1', 'a'], [1, 'b']])
380- // or:
381- // Map([[{}, 'a'], [{}, 'b']]) vs Map([[{}, 'b'], [{}, 'a']])
382- // ... we need to consider *all* matching keys, not just the first we find.
390+ function findLooseMatchingPrimitives ( prim ) {
391+ var values , number ;
392+ switch ( typeof prim ) {
393+ case 'number' :
394+ values = [ '' + prim ] ;
395+ if ( prim === 1 || prim === 0 )
396+ values . push ( Boolean ( prim ) ) ;
397+ return values ;
398+ case 'string' :
399+ number = + prim ;
400+ if ( '' + number === prim ) {
401+ values = [ number ] ;
402+ if ( number === 1 || number === 0 )
403+ values . push ( Boolean ( number ) ) ;
404+ }
405+ return values ;
406+ case 'undefined' :
407+ return [ null ] ;
408+ case 'object' : // Only pass in null as object!
409+ return [ undefined ] ;
410+ case 'boolean' :
411+ number = + prim ;
412+ return [ number , '' + number ] ;
413+ }
414+ }
383415
384- // This check is not strictly necessary. The loop performs this check, but
385- // doing it here improves performance of the common case when reference-equal
386- // keys exist (which includes all primitive-valued keys).
387- if ( map . has ( key1 ) && innerDeepEqual ( item1 , map . get ( key1 ) , strict , memo ) ) {
388- if ( usedEntries !== null )
389- usedEntries . add ( key1 ) ;
390- return true ;
416+ // This is a ugly but relatively fast way to determine if a loose equal entry
417+ // actually has a correspondent matching entry. Otherwise checking for such
418+ // values would be way more expensive (O(n^2)).
419+ // Note: we actually run this multiple times for each loose key!
420+ // This is done to prevent slowing down the average case.
421+ function mapHasLoosePrim ( a , b , key1 , memo , item1 , item2 ) {
422+ const altKeys = findLooseMatchingPrimitives ( key1 ) ;
423+ if ( altKeys === undefined )
424+ return false ;
425+
426+ const setA = new Set ( ) ;
427+ const setB = new Set ( ) ;
428+
429+ var keyCount = 1 ;
430+
431+ setA . add ( item1 ) ;
432+ if ( b . has ( key1 ) ) {
433+ keyCount -- ;
434+ setB . add ( item2 ) ;
391435 }
392436
393- if ( strict && ( typeof key1 !== 'object' || key1 === null ) )
437+ for ( var i = 0 ; i < altKeys . length ; i ++ ) {
438+ const key2 = altKeys [ i ] ;
439+ if ( a . has ( key2 ) ) {
440+ keyCount ++ ;
441+ setA . add ( a . get ( key2 ) ) ;
442+ }
443+ if ( b . has ( key2 ) ) {
444+ keyCount -- ;
445+ setB . add ( b . get ( key2 ) ) ;
446+ }
447+ }
448+ if ( keyCount !== 0 || setA . size !== setB . size )
394449 return false ;
395450
396- for ( const [ key2 , item2 ] of map ) {
397- // The first part is checked above.
398- if ( key2 === key1 || usedEntries . has ( key2 ) )
399- continue ;
451+ for ( const val of setA ) {
452+ if ( ! setHasEqualElement ( setB , val , false , memo ) )
453+ return false ;
454+ }
455+
456+ return true ;
457+ }
400458
459+ function mapHasEqualEntry ( set , map , key1 , item1 , strict , memo ) {
460+ // To be able to handle cases like:
461+ // Map([[{}, 'a'], [{}, 'b']]) vs Map([[{}, 'b'], [{}, 'a']])
462+ // ... we need to consider *all* matching keys, not just the first we find.
463+ for ( const key2 of set ) {
401464 if ( innerDeepEqual ( key1 , key2 , strict , memo ) &&
402- innerDeepEqual ( item1 , item2 , strict , memo ) ) {
403- usedEntries . add ( key2 ) ;
465+ innerDeepEqual ( item1 , map . get ( key2 ) , strict , memo ) ) {
466+ set . delete ( key2 ) ;
404467 return true ;
405468 }
406469 }
@@ -411,21 +474,45 @@ function mapHasSimilarEntry(map, key1, item1, usedEntries, strict, memo) {
411474function mapEquiv ( a , b , strict , memo ) {
412475 // Caveat: In non-strict mode, this implementation does not handle cases
413476 // where maps contain two equivalent-but-not-reference-equal keys.
414- //
415- // For example, maps like this are currently considered not equivalent:
416477 if ( a . size !== b . size )
417478 return false ;
418479
419- let usedEntries = strict === true ? null : new Set ( ) ;
420-
421- for ( const [ key , item ] of a ) {
422- if ( usedEntries === null && typeof key === 'object' )
423- usedEntries = new Set ( ) ;
424-
425- // Just like setEquiv above, this hunt makes this function O(n^2) when
426- // using objects and lists as keys
427- if ( ! mapHasSimilarEntry ( b , key , item , usedEntries , strict , memo ) )
480+ var set = null ;
481+
482+ for ( const [ key , item1 ] of a ) {
483+ // By directly retrieving the value we prevent another b.has(key) check in
484+ // almost all possible cases.
485+ const item2 = b . get ( key ) ;
486+ if ( item2 === undefined ) {
487+ // Just like setEquiv above but in addition we have to make sure the
488+ // values are also equal.
489+ if ( typeof key === 'object' && key !== null ) {
490+ if ( set === null ) {
491+ set = new Set ( ) ;
492+ }
493+ set . add ( key ) ;
494+ // Note: we do not have to pass memo in this case as at least one item
495+ // is undefined.
496+ } else if ( ( ! innerDeepEqual ( item1 , item2 , strict ) || ! b . has ( key ) ) &&
497+ ( strict || ! mapHasLoosePrim ( a , b , key , memo , item1 ) ) ) {
498+ return false ;
499+ }
500+ } else if ( ! innerDeepEqual ( item1 , item2 , strict , memo ) &&
501+ ( strict || ! mapHasLoosePrim ( a , b , key , memo , item1 , item2 ) ) ) {
428502 return false ;
503+ }
504+ }
505+
506+ if ( set !== null ) {
507+ for ( const [ key , item ] of b ) {
508+ if ( typeof key === 'object' && key !== null ) {
509+ if ( ! mapHasEqualEntry ( set , a , key , item , strict , memo ) )
510+ return false ;
511+ } else if ( ! a . has ( key ) &&
512+ ( strict || ! mapHasLoosePrim ( b , a , key , memo , item ) ) ) {
513+ return false ;
514+ }
515+ }
429516 }
430517
431518 return true ;
@@ -437,12 +524,10 @@ function objEquiv(a, b, strict, keys, memos) {
437524 if ( isSet ( a ) ) {
438525 if ( ! isSet ( b ) || ! setEquiv ( a , b , strict , memos ) )
439526 return false ;
440- } else if ( isSet ( b ) ) {
441- return false ;
442527 } else if ( isMap ( a ) ) {
443528 if ( ! isMap ( b ) || ! mapEquiv ( a , b , strict , memos ) )
444529 return false ;
445- } else if ( isMap ( b ) ) {
530+ } else if ( isSet ( b ) || isMap ( b ) ) {
446531 return false ;
447532 }
448533
0 commit comments