@@ -1471,6 +1471,106 @@ def test_dict_items_result_gc(self):
1471
1471
gc .collect ()
1472
1472
self .assertTrue (gc .is_tracked (next (it )))
1473
1473
1474
+ def test_str_nonstr (self ):
1475
+ # cpython uses a different lookup function if the dict only contains
1476
+ # `str` keys. Make sure the unoptimized path is used when a non-`str`
1477
+ # key appears.
1478
+
1479
+ class StrSub (str ):
1480
+ pass
1481
+
1482
+ eq_count = 0
1483
+ # This class compares equal to the string 'key3'
1484
+ class Key3 :
1485
+ def __hash__ (self ):
1486
+ return hash ('key3' )
1487
+
1488
+ def __eq__ (self , other ):
1489
+ nonlocal eq_count
1490
+ if isinstance (other , Key3 ) or isinstance (other , str ) and other == 'key3' :
1491
+ eq_count += 1
1492
+ return True
1493
+ return False
1494
+
1495
+ key3_1 = StrSub ('key3' )
1496
+ key3_2 = Key3 ()
1497
+ key3_3 = Key3 ()
1498
+
1499
+ dicts = []
1500
+
1501
+ # Create dicts of the form `{'key1': 42, 'key2': 43, key3: 44}` in a
1502
+ # bunch of different ways. In all cases, `key3` is not of type `str`.
1503
+ # `key3_1` is a `str` subclass and `key3_2` is a completely unrelated
1504
+ # type.
1505
+ for key3 in (key3_1 , key3_2 ):
1506
+ # A literal
1507
+ dicts .append ({'key1' : 42 , 'key2' : 43 , key3 : 44 })
1508
+
1509
+ # key3 inserted via `dict.__setitem__`
1510
+ d = {'key1' : 42 , 'key2' : 43 }
1511
+ d [key3 ] = 44
1512
+ dicts .append (d )
1513
+
1514
+ # key3 inserted via `dict.setdefault`
1515
+ d = {'key1' : 42 , 'key2' : 43 }
1516
+ self .assertEqual (d .setdefault (key3 , 44 ), 44 )
1517
+ dicts .append (d )
1518
+
1519
+ # key3 inserted via `dict.update`
1520
+ d = {'key1' : 42 , 'key2' : 43 }
1521
+ d .update ({key3 : 44 })
1522
+ dicts .append (d )
1523
+
1524
+ # key3 inserted via `dict.__ior__`
1525
+ d = {'key1' : 42 , 'key2' : 43 }
1526
+ d |= {key3 : 44 }
1527
+ dicts .append (d )
1528
+
1529
+ # `dict(iterable)`
1530
+ def make_pairs ():
1531
+ yield ('key1' , 42 )
1532
+ yield ('key2' , 43 )
1533
+ yield (key3 , 44 )
1534
+ d = dict (make_pairs ())
1535
+ dicts .append (d )
1536
+
1537
+ # `dict.copy`
1538
+ d = d .copy ()
1539
+ dicts .append (d )
1540
+
1541
+ # dict comprehension
1542
+ d = {key : 42 + i for i ,key in enumerate (['key1' , 'key2' , key3 ])}
1543
+ dicts .append (d )
1544
+
1545
+ for d in dicts :
1546
+ with self .subTest (d = d ):
1547
+ self .assertEqual (d .get ('key1' ), 42 )
1548
+
1549
+ # Try to make an object that is of type `str` and is equal to
1550
+ # `'key1'`, but (at least on cpython) is a different object.
1551
+ noninterned_key1 = 'ke'
1552
+ noninterned_key1 += 'y1'
1553
+ if support .check_impl_detail (cpython = True ):
1554
+ # suppress a SyntaxWarning
1555
+ interned_key1 = 'key1'
1556
+ self .assertFalse (noninterned_key1 is interned_key1 )
1557
+ self .assertEqual (d .get (noninterned_key1 ), 42 )
1558
+
1559
+ self .assertEqual (d .get ('key3' ), 44 )
1560
+ self .assertEqual (d .get (key3_1 ), 44 )
1561
+ self .assertEqual (d .get (key3_2 ), 44 )
1562
+
1563
+ # `key3_3` itself is definitely not a dict key, so make sure
1564
+ # that `__eq__` gets called.
1565
+ #
1566
+ # Note that this might not hold for `key3_1` and `key3_2`
1567
+ # because they might be the same object as one of the dict keys,
1568
+ # in which case implementations are allowed to skip the call to
1569
+ # `__eq__`.
1570
+ eq_count = 0
1571
+ self .assertEqual (d .get (key3_3 ), 44 )
1572
+ self .assertGreaterEqual (eq_count , 1 )
1573
+
1474
1574
1475
1575
class CAPITest (unittest .TestCase ):
1476
1576
0 commit comments