@@ -354,23 +354,52 @@ function setHasEqualElement(set, val1, strict, memo) {
354
354
return false ;
355
355
}
356
356
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
+ }
363
381
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 ;
372
399
}
373
- return matches === 0 ;
400
+ const curA = a . get ( altValue ) ;
401
+ return curA === undefined && a . has ( altValue ) ||
402
+ innerDeepEqual ( item , curA , false , memo ) ;
374
403
}
375
404
376
405
function setEquiv ( a , b , strict , memo ) {
@@ -390,8 +419,19 @@ function setEquiv(a, b, strict, memo) {
390
419
// hunting for something thats deep-(strict-)equal to it. To make this
391
420
// O(n log n) complexity we have to copy these values in a new set first.
392
421
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 ) ;
395
435
}
396
436
}
397
437
@@ -402,96 +442,18 @@ function setEquiv(a, b, strict, memo) {
402
442
if ( typeof val === 'object' && val !== null ) {
403
443
if ( ! setHasEqualElement ( set , val , strict , memo ) )
404
444
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 ) ) {
406
448
return false ;
407
449
}
408
450
}
451
+ return set . size === 0 ;
409
452
}
410
453
411
454
return true ;
412
455
}
413
456
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
-
495
457
function mapHasEqualEntry ( set , map , key1 , item1 , strict , memo ) {
496
458
// To be able to handle cases like:
497
459
// Map([[{}, 'a'], [{}, 'b']]) vs Map([[{}, 'b'], [{}, 'a']])
@@ -521,9 +483,17 @@ function mapEquiv(a, b, strict, memo) {
521
483
// almost all possible cases.
522
484
const item2 = b . get ( key ) ;
523
485
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 ) ;
527
497
}
528
498
}
529
499
}
@@ -533,11 +503,14 @@ function mapEquiv(a, b, strict, memo) {
533
503
if ( typeof key === 'object' && key !== null ) {
534
504
if ( ! mapHasEqualEntry ( set , a , key , item , strict , memo ) )
535
505
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 ) ) {
538
510
return false ;
539
511
}
540
512
}
513
+ return set . size === 0 ;
541
514
}
542
515
543
516
return true ;
0 commit comments