@@ -374,23 +374,52 @@ function setHasEqualElement(set, val1, strict, memo) {
374374 return false ;
375375}
376376
377- // Note: we currently run this multiple times for each loose key!
378- // This is done to prevent slowing down the average case.
379- function setHasLoosePrim ( a , b , val ) {
380- const altValues = findLooseMatchingPrimitives ( val ) ;
381- if ( altValues === undefined )
382- return false ;
377+ // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness#Loose_equality_using
378+ // Sadly it is not possible to detect corresponding values properly in case the
379+ // type is a string, number, bigint or boolean. The reason is that those values
380+ // can match lots of different string values (e.g., 1n == '+00001').
381+ function findLooseMatchingPrimitives ( prim ) {
382+ switch ( typeof prim ) {
383+ case 'undefined' :
384+ return null ;
385+ case 'object' : // Only pass in null as object!
386+ return undefined ;
387+ case 'symbol' :
388+ return false ;
389+ case 'string' :
390+ prim = + prim ;
391+ // Loose equal entries exist only if the string is possible to convert to
392+ // a regular number and not NaN.
393+ // Fall through
394+ case 'number' :
395+ if ( Number . isNaN ( prim ) ) {
396+ return false ;
397+ }
398+ }
399+ return true ;
400+ }
383401
384- let matches = 1 ;
385- for ( var i = 0 ; i < altValues . length ; i ++ ) {
386- if ( b . has ( altValues [ i ] ) ) {
387- matches -- ;
388- }
389- if ( a . has ( altValues [ i ] ) ) {
390- matches ++ ;
391- }
402+ function setMightHaveLoosePrim ( a , b , prim ) {
403+ const altValue = findLooseMatchingPrimitives ( prim ) ;
404+ if ( altValue != null )
405+ return altValue ;
406+
407+ return b . has ( altValue ) && ! a . has ( altValue ) ;
408+ }
409+
410+ function mapMightHaveLoosePrim ( a , b , prim , item , memo ) {
411+ const altValue = findLooseMatchingPrimitives ( prim ) ;
412+ if ( altValue != null ) {
413+ return altValue ;
414+ }
415+ const curB = b . get ( altValue ) ;
416+ if ( curB === undefined && ! b . has ( altValue ) ||
417+ ! innerDeepEqual ( item , curB , false , memo ) ) {
418+ return false ;
392419 }
393- return matches === 0 ;
420+ const curA = a . get ( altValue ) ;
421+ return curA === undefined && a . has ( altValue ) ||
422+ innerDeepEqual ( item , curA , false , memo ) ;
394423}
395424
396425function setEquiv ( a , b , strict , memo ) {
@@ -410,8 +439,19 @@ function setEquiv(a, b, strict, memo) {
410439 // hunting for something thats deep-(strict-)equal to it. To make this
411440 // O(n log n) complexity we have to copy these values in a new set first.
412441 set . add ( val ) ;
413- } else if ( ! b . has ( val ) && ( strict || ! setHasLoosePrim ( a , b , val ) ) ) {
414- return false ;
442+ } else if ( ! b . has ( val ) ) {
443+ if ( strict )
444+ return false ;
445+
446+ // Fast path to detect missing string, symbol, undefined and null values.
447+ if ( ! setMightHaveLoosePrim ( a , b , val ) ) {
448+ return false ;
449+ }
450+
451+ if ( set === null ) {
452+ set = new Set ( ) ;
453+ }
454+ set . add ( val ) ;
415455 }
416456 }
417457
@@ -422,96 +462,18 @@ function setEquiv(a, b, strict, memo) {
422462 if ( typeof val === 'object' && val !== null ) {
423463 if ( ! setHasEqualElement ( set , val , strict , memo ) )
424464 return false ;
425- } else if ( ! a . has ( val ) && ( strict || ! setHasLoosePrim ( b , a , val ) ) ) {
465+ } else if ( ! strict &&
466+ ! a . has ( val ) &&
467+ ! setHasEqualElement ( set , val , strict , memo ) ) {
426468 return false ;
427469 }
428470 }
471+ return set . size === 0 ;
429472 }
430473
431474 return true ;
432475}
433476
434- // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness#Loose_equality_using
435- function findLooseMatchingPrimitives ( prim ) {
436- switch ( typeof prim ) {
437- case 'number' :
438- if ( prim === 0 ) {
439- return [ '' , '0' , false ] ;
440- }
441- if ( prim === 1 ) {
442- return [ '1' , true ] ;
443- }
444- return [ '' + prim ] ;
445- case 'string' :
446- if ( prim === '' || prim === '0' ) {
447- return [ 0 , false ] ;
448- }
449- if ( prim === '1' ) {
450- return [ 1 , true ] ;
451- }
452- const number = + prim ;
453- if ( '' + number === prim ) {
454- return [ number ] ;
455- }
456- return ;
457- case 'undefined' :
458- return [ null ] ;
459- case 'object' : // Only pass in null as object!
460- return [ undefined ] ;
461- case 'boolean' :
462- if ( prim === false ) {
463- return [ '' , '0' , 0 ] ;
464- }
465- return [ '1' , 1 ] ;
466- }
467- }
468-
469- // This is a ugly but relatively fast way to determine if a loose equal entry
470- // currently has a correspondent matching entry. Otherwise checking for such
471- // values would be way more expensive (O(n^2)).
472- // Note: we currently run this multiple times for each loose key!
473- // This is done to prevent slowing down the average case.
474- function mapHasLoosePrim ( a , b , key1 , memo , item1 , item2 ) {
475- const altKeys = findLooseMatchingPrimitives ( key1 ) ;
476- if ( altKeys === undefined )
477- return false ;
478-
479- const setA = new Set ( ) ;
480- const setB = new Set ( ) ;
481-
482- let keyCount = 1 ;
483-
484- setA . add ( item1 ) ;
485- if ( b . has ( key1 ) ) {
486- keyCount -- ;
487- setB . add ( item2 ) ;
488- }
489-
490- for ( var i = 0 ; i < altKeys . length ; i ++ ) {
491- const key2 = altKeys [ i ] ;
492- if ( a . has ( key2 ) ) {
493- keyCount ++ ;
494- setA . add ( a . get ( key2 ) ) ;
495- }
496- if ( b . has ( key2 ) ) {
497- keyCount -- ;
498- setB . add ( b . get ( key2 ) ) ;
499- }
500- }
501- if ( keyCount !== 0 || setA . size !== setB . size )
502- return false ;
503-
504- for ( const val of setA ) {
505- if ( typeof val === 'object' && val !== null ) {
506- if ( ! setHasEqualElement ( setB , val , false , memo ) )
507- return false ;
508- } else if ( ! setB . has ( val ) && ! setHasLoosePrim ( setA , setB , val ) ) {
509- return false ;
510- }
511- }
512- return true ;
513- }
514-
515477function mapHasEqualEntry ( set , map , key1 , item1 , strict , memo ) {
516478 // To be able to handle cases like:
517479 // Map([[{}, 'a'], [{}, 'b']]) vs Map([[{}, 'b'], [{}, 'a']])
@@ -541,9 +503,17 @@ function mapEquiv(a, b, strict, memo) {
541503 // almost all possible cases.
542504 const item2 = b . get ( key ) ;
543505 if ( ( item2 === undefined && ! b . has ( key ) ||
544- ! innerDeepEqual ( item1 , item2 , strict , memo ) ) &&
545- ( strict || ! mapHasLoosePrim ( a , b , key , memo , item1 , item2 ) ) ) {
546- return false ;
506+ ! innerDeepEqual ( item1 , item2 , strict , memo ) ) ) {
507+ if ( strict )
508+ return false ;
509+ // Fast path to detect missing string, symbol, undefined and null
510+ // keys.
511+ if ( ! mapMightHaveLoosePrim ( a , b , key , item1 , memo ) )
512+ return false ;
513+ if ( set === null ) {
514+ set = new Set ( ) ;
515+ }
516+ set . add ( key ) ;
547517 }
548518 }
549519 }
@@ -553,11 +523,14 @@ function mapEquiv(a, b, strict, memo) {
553523 if ( typeof key === 'object' && key !== null ) {
554524 if ( ! mapHasEqualEntry ( set , a , key , item , strict , memo ) )
555525 return false ;
556- } else if ( ! a . has ( key ) &&
557- ( strict || ! mapHasLoosePrim ( b , a , key , memo , item ) ) ) {
526+ } else if ( ! strict &&
527+ ( ! a . has ( key ) ||
528+ ! innerDeepEqual ( a . get ( key ) , item , false , memo ) ) &&
529+ ! mapHasEqualEntry ( set , a , key , item , false , memo ) ) {
558530 return false ;
559531 }
560532 }
533+ return set . size === 0 ;
561534 }
562535
563536 return true ;
0 commit comments