@@ -374,23 +374,52 @@ function setHasEqualElement(set, val1, strict, memo) {
374
374
return false ;
375
375
}
376
376
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
+ }
383
401
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 ;
392
419
}
393
- return matches === 0 ;
420
+ const curA = a . get ( altValue ) ;
421
+ return curA === undefined && a . has ( altValue ) ||
422
+ innerDeepEqual ( item , curA , false , memo ) ;
394
423
}
395
424
396
425
function setEquiv ( a , b , strict , memo ) {
@@ -410,8 +439,19 @@ function setEquiv(a, b, strict, memo) {
410
439
// hunting for something thats deep-(strict-)equal to it. To make this
411
440
// O(n log n) complexity we have to copy these values in a new set first.
412
441
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 ) ;
415
455
}
416
456
}
417
457
@@ -422,96 +462,18 @@ function setEquiv(a, b, strict, memo) {
422
462
if ( typeof val === 'object' && val !== null ) {
423
463
if ( ! setHasEqualElement ( set , val , strict , memo ) )
424
464
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 ) ) {
426
468
return false ;
427
469
}
428
470
}
471
+ return set . size === 0 ;
429
472
}
430
473
431
474
return true ;
432
475
}
433
476
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
-
515
477
function mapHasEqualEntry ( set , map , key1 , item1 , strict , memo ) {
516
478
// To be able to handle cases like:
517
479
// Map([[{}, 'a'], [{}, 'b']]) vs Map([[{}, 'b'], [{}, 'a']])
@@ -541,9 +503,17 @@ function mapEquiv(a, b, strict, memo) {
541
503
// almost all possible cases.
542
504
const item2 = b . get ( key ) ;
543
505
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 ) ;
547
517
}
548
518
}
549
519
}
@@ -553,11 +523,14 @@ function mapEquiv(a, b, strict, memo) {
553
523
if ( typeof key === 'object' && key !== null ) {
554
524
if ( ! mapHasEqualEntry ( set , a , key , item , strict , memo ) )
555
525
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 ) ) {
558
530
return false ;
559
531
}
560
532
}
533
+ return set . size === 0 ;
561
534
}
562
535
563
536
return true ;
0 commit comments