@@ -615,3 +615,75 @@ def __eq__(self, other):
615615lookup = LookupOrderSubclass ("lookup" , True )
616616assert {stored : 1 }[lookup ] == 1
617617assert LookupOrderKey .calls == ["lookup" ]
618+
619+
620+ class MergeHashKey :
621+ hash_disabled = False
622+
623+ def __init__ (self , value ):
624+ self .value = value
625+
626+ def __hash__ (self ):
627+ assert not self .hash_disabled , "dictionary merge rehashed a stored key"
628+ return 42
629+
630+ def __eq__ (self , other ):
631+ if not isinstance (other , MergeHashKey ):
632+ return NotImplemented
633+ return self .value == other .value
634+
635+
636+ def merge_with_update (source ):
637+ result = {}
638+ result .update (source )
639+ return result
640+
641+
642+ def merge_with_ior (source ):
643+ result = {}
644+ result |= source
645+ return result
646+
647+
648+ # Exact dictionary merges reuse hashes, including colliding keys and holes.
649+ merge_keys = [MergeHashKey (i ) for i in range (3 )]
650+ merge_source = dict (zip (merge_keys , ("first" , "removed" , "last" )))
651+ del merge_source [merge_keys [1 ]]
652+ existing_merge_key = MergeHashKey (2 )
653+ merge_target = {existing_merge_key : "old" }
654+ MergeHashKey .hash_disabled = True
655+ for merge in (
656+ dict ,
657+ merge_with_update ,
658+ merge_with_ior ,
659+ lambda source : {} | source ,
660+ lambda source : dict .__ror__ (source , {}),
661+ lambda source : {** source },
662+ ):
663+ assert list (merge (merge_source ).items ()) == list (merge_source .items ())
664+
665+ # Overwriting a matching key keeps its identity and position.
666+ merge_target .update (merge_source )
667+ assert next (iter (merge_target )) is existing_merge_key
668+ assert list (merge_target .values ()) == ["last" , "first" ]
669+ merge_source .update (merge_source )
670+ merge_source |= merge_source
671+ assert list (merge_source .values ()) == ["first" , "last" ]
672+ MergeHashKey .hash_disabled = False
673+
674+
675+ class MergeMapping (dict ):
676+ def __iter__ (self ):
677+ return iter (("virtual" ,))
678+
679+ def keys (self ):
680+ return ["virtual" ]
681+
682+ def __getitem__ (self , key ):
683+ assert key == "virtual"
684+ return 42
685+
686+
687+ # Generic mappings retain their lookup hooks instead of exposing dict storage.
688+ for merge in (dict , merge_with_update , merge_with_ior ):
689+ assert merge (MergeMapping (stored = 0 )) == {"virtual" : 42 }
0 commit comments