@@ -354,23 +354,52 @@ function setHasEqualElement(set, val1, strict, memo) {
354354 return false ;
355355}
356356
357- // Note: we currently run this multiple times for each loose key!
358- // This is done to prevent slowing down the average case.
359- function setHasLoosePrim ( a , b , val ) {
360- const altValues = findLooseMatchingPrimitives ( val ) ;
361- if ( altValues === undefined )
362- return false ;
357+ // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness#Loose_equality_using
358+ // Sadly it is not possible to detect corresponding values properly in case the
359+ // type is a string, number, bigint or boolean. The reason is that those values
360+ // can match lots of different string values (e.g., 1n == '+00001').
361+ function findLooseMatchingPrimitives ( prim ) {
362+ switch ( typeof prim ) {
363+ case 'undefined' :
364+ return null ;
365+ case 'object' : // Only pass in null as object!
366+ return undefined ;
367+ case 'symbol' :
368+ return false ;
369+ case 'string' :
370+ prim = + prim ;
371+ // Loose equal entries exist only if the string is possible to convert to
372+ // a regular number and not NaN.
373+ // Fall through
374+ case 'number' :
375+ if ( Number . isNaN ( prim ) ) {
376+ return false ;
377+ }
378+ }
379+ return true ;
380+ }
363381
364- let matches = 1 ;
365- for ( var i = 0 ; i < altValues . length ; i ++ ) {
366- if ( b . has ( altValues [ i ] ) ) {
367- matches -- ;
368- }
369- if ( a . has ( altValues [ i ] ) ) {
370- matches ++ ;
371- }
382+ function setMightHaveLoosePrim ( a , b , prim ) {
383+ const altValue = findLooseMatchingPrimitives ( prim ) ;
384+ if ( altValue != null )
385+ return altValue ;
386+
387+ return b . has ( altValue ) && ! a . has ( altValue ) ;
388+ }
389+
390+ function mapMightHaveLoosePrim ( a , b , prim , item , memo ) {
391+ const altValue = findLooseMatchingPrimitives ( prim ) ;
392+ if ( altValue != null ) {
393+ return altValue ;
394+ }
395+ const curB = b . get ( altValue ) ;
396+ if ( curB === undefined && ! b . has ( altValue ) ||
397+ ! innerDeepEqual ( item , curB , false , memo ) ) {
398+ return false ;
372399 }
373- return matches === 0 ;
400+ const curA = a . get ( altValue ) ;
401+ return curA === undefined && a . has ( altValue ) ||
402+ innerDeepEqual ( item , curA , false , memo ) ;
374403}
375404
376405function setEquiv ( a , b , strict , memo ) {
@@ -390,8 +419,19 @@ function setEquiv(a, b, strict, memo) {
390419 // hunting for something thats deep-(strict-)equal to it. To make this
391420 // O(n log n) complexity we have to copy these values in a new set first.
392421 set . add ( val ) ;
393- } else if ( ! b . has ( val ) && ( strict || ! setHasLoosePrim ( a , b , val ) ) ) {
394- return false ;
422+ } else if ( ! b . has ( val ) ) {
423+ if ( strict )
424+ return false ;
425+
426+ // Fast path to detect missing string, symbol, undefined and null values.
427+ if ( ! setMightHaveLoosePrim ( a , b , val ) ) {
428+ return false ;
429+ }
430+
431+ if ( set === null ) {
432+ set = new Set ( ) ;
433+ }
434+ set . add ( val ) ;
395435 }
396436 }
397437
@@ -402,96 +442,18 @@ function setEquiv(a, b, strict, memo) {
402442 if ( typeof val === 'object' && val !== null ) {
403443 if ( ! setHasEqualElement ( set , val , strict , memo ) )
404444 return false ;
405- } else if ( ! a . has ( val ) && ( strict || ! setHasLoosePrim ( b , a , val ) ) ) {
445+ } else if ( ! strict &&
446+ ! a . has ( val ) &&
447+ ! setHasEqualElement ( set , val , strict , memo ) ) {
406448 return false ;
407449 }
408450 }
451+ return set . size === 0 ;
409452 }
410453
411454 return true ;
412455}
413456
414- // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness#Loose_equality_using
415- function findLooseMatchingPrimitives ( prim ) {
416- switch ( typeof prim ) {
417- case 'number' :
418- if ( prim === 0 ) {
419- return [ '' , '0' , false ] ;
420- }
421- if ( prim === 1 ) {
422- return [ '1' , true ] ;
423- }
424- return [ '' + prim ] ;
425- case 'string' :
426- if ( prim === '' || prim === '0' ) {
427- return [ 0 , false ] ;
428- }
429- if ( prim === '1' ) {
430- return [ 1 , true ] ;
431- }
432- const number = + prim ;
433- if ( '' + number === prim ) {
434- return [ number ] ;
435- }
436- return ;
437- case 'undefined' :
438- return [ null ] ;
439- case 'object' : // Only pass in null as object!
440- return [ undefined ] ;
441- case 'boolean' :
442- if ( prim === false ) {
443- return [ '' , '0' , 0 ] ;
444- }
445- return [ '1' , 1 ] ;
446- }
447- }
448-
449- // This is a ugly but relatively fast way to determine if a loose equal entry
450- // currently has a correspondent matching entry. Otherwise checking for such
451- // values would be way more expensive (O(n^2)).
452- // Note: we currently run this multiple times for each loose key!
453- // This is done to prevent slowing down the average case.
454- function mapHasLoosePrim ( a , b , key1 , memo , item1 , item2 ) {
455- const altKeys = findLooseMatchingPrimitives ( key1 ) ;
456- if ( altKeys === undefined )
457- return false ;
458-
459- const setA = new Set ( ) ;
460- const setB = new Set ( ) ;
461-
462- let keyCount = 1 ;
463-
464- setA . add ( item1 ) ;
465- if ( b . has ( key1 ) ) {
466- keyCount -- ;
467- setB . add ( item2 ) ;
468- }
469-
470- for ( var i = 0 ; i < altKeys . length ; i ++ ) {
471- const key2 = altKeys [ i ] ;
472- if ( a . has ( key2 ) ) {
473- keyCount ++ ;
474- setA . add ( a . get ( key2 ) ) ;
475- }
476- if ( b . has ( key2 ) ) {
477- keyCount -- ;
478- setB . add ( b . get ( key2 ) ) ;
479- }
480- }
481- if ( keyCount !== 0 || setA . size !== setB . size )
482- return false ;
483-
484- for ( const val of setA ) {
485- if ( typeof val === 'object' && val !== null ) {
486- if ( ! setHasEqualElement ( setB , val , false , memo ) )
487- return false ;
488- } else if ( ! setB . has ( val ) && ! setHasLoosePrim ( setA , setB , val ) ) {
489- return false ;
490- }
491- }
492- return true ;
493- }
494-
495457function mapHasEqualEntry ( set , map , key1 , item1 , strict , memo ) {
496458 // To be able to handle cases like:
497459 // Map([[{}, 'a'], [{}, 'b']]) vs Map([[{}, 'b'], [{}, 'a']])
@@ -521,9 +483,17 @@ function mapEquiv(a, b, strict, memo) {
521483 // almost all possible cases.
522484 const item2 = b . get ( key ) ;
523485 if ( ( item2 === undefined && ! b . has ( key ) ||
524- ! innerDeepEqual ( item1 , item2 , strict , memo ) ) &&
525- ( strict || ! mapHasLoosePrim ( a , b , key , memo , item1 , item2 ) ) ) {
526- return false ;
486+ ! innerDeepEqual ( item1 , item2 , strict , memo ) ) ) {
487+ if ( strict )
488+ return false ;
489+ // Fast path to detect missing string, symbol, undefined and null
490+ // keys.
491+ if ( ! mapMightHaveLoosePrim ( a , b , key , item1 , memo ) )
492+ return false ;
493+ if ( set === null ) {
494+ set = new Set ( ) ;
495+ }
496+ set . add ( key ) ;
527497 }
528498 }
529499 }
@@ -533,11 +503,14 @@ function mapEquiv(a, b, strict, memo) {
533503 if ( typeof key === 'object' && key !== null ) {
534504 if ( ! mapHasEqualEntry ( set , a , key , item , strict , memo ) )
535505 return false ;
536- } else if ( ! a . has ( key ) &&
537- ( strict || ! mapHasLoosePrim ( b , a , key , memo , item ) ) ) {
506+ } else if ( ! strict &&
507+ ( ! a . has ( key ) ||
508+ ! innerDeepEqual ( a . get ( key ) , item , false , memo ) ) &&
509+ ! mapHasEqualEntry ( set , a , key , item , false , memo ) ) {
538510 return false ;
539511 }
540512 }
513+ return set . size === 0 ;
541514 }
542515
543516 return true ;
0 commit comments