[pypy-commit] pypy py3.7: pure python HAMT immutable dict implementation from the immutables package

cfbolz pypy.commits at gmail.com
Mon Dec 23 10:29:26 EST 2019


Author: Carl Friedrich Bolz-Tereick <cfbolz at gmx.de>
Branch: py3.7
Changeset: r98359:4f0d7c92cfdd
Date: 2019-12-23 16:14 +0100
http://bitbucket.org/pypy/pypy/changeset/4f0d7c92cfdd/

Log:	pure python HAMT immutable dict implementation from the immutables
	package

	taken from rev 11863b29e3fbcd7d25335befce706e21a785f5e0 from
	https://github.com/MagicStack/immutables/

	discussed on Twitter here:
	https://twitter.com/1st1/status/1208819455507218437

	tests converted to pytest using unittest2pytest

diff too long, truncating to 2000 out of 2125 lines

diff --git a/extra_tests/test_immutables_map.py b/extra_tests/test_immutables_map.py
new file mode 100644
--- /dev/null
+++ b/extra_tests/test_immutables_map.py
@@ -0,0 +1,1298 @@
+import collections.abc
+import gc
+import pickle
+import random
+import sys
+import weakref
+import pytest
+
+from _immutables_map import Map
+
+
+class HashKey:
+    _crasher = None
+
+    def __init__(self, hash, name, *, error_on_eq_to=None):
+        assert hash != -1
+        self.name = name
+        self.hash = hash
+        self.error_on_eq_to = error_on_eq_to
+
+    def __repr__(self):
+        if self._crasher is not None and self._crasher.error_on_repr:
+            raise ReprError
+        return '<Key name:{} hash:{}>'.format(self.name, self.hash)
+
+    def __hash__(self):
+        if self._crasher is not None and self._crasher.error_on_hash:
+            raise HashingError
+
+        return self.hash
+
+    def __eq__(self, other):
+        if not isinstance(other, HashKey):
+            return NotImplemented
+
+        if self._crasher is not None and self._crasher.error_on_eq:
+            raise EqError
+
+        if self.error_on_eq_to is not None and self.error_on_eq_to is other:
+            raise ValueError('cannot compare {!r} to {!r}'.format(self, other))
+        if other.error_on_eq_to is not None and other.error_on_eq_to is self:
+            raise ValueError('cannot compare {!r} to {!r}'.format(other, self))
+
+        return (self.name, self.hash) == (other.name, other.hash)
+
+
+class KeyStr(str):
+
+    def __hash__(self):
+        if HashKey._crasher is not None and HashKey._crasher.error_on_hash:
+            raise HashingError
+        return super().__hash__()
+
+    def __eq__(self, other):
+        if HashKey._crasher is not None and HashKey._crasher.error_on_eq:
+            raise EqError
+        return super().__eq__(other)
+
+    def __repr__(self, other):
+        if HashKey._crasher is not None and HashKey._crasher.error_on_repr:
+            raise ReprError
+        return super().__eq__(other)
+
+
+class HashKeyCrasher:
+
+    def __init__(self, *, error_on_hash=False, error_on_eq=False,
+                 error_on_repr=False):
+        self.error_on_hash = error_on_hash
+        self.error_on_eq = error_on_eq
+        self.error_on_repr = error_on_repr
+
+    def __enter__(self):
+        if HashKey._crasher is not None:
+            raise RuntimeError('cannot nest crashers')
+        HashKey._crasher = self
+
+    def __exit__(self, *exc):
+        HashKey._crasher = None
+
+
+class HashingError(Exception):
+    pass
+
+
+class EqError(Exception):
+    pass
+
+
+class ReprError(Exception):
+    pass
+
+
+class BaseMapTest:
+
+    def test_hashkey_helper_1(self):
+        k1 = HashKey(10, 'aaa')
+        k2 = HashKey(10, 'bbb')
+
+        assert k1 != k2
+        assert hash(k1) == hash(k2)
+
+        d = dict()
+        d[k1] = 'a'
+        d[k2] = 'b'
+
+        assert d[k1] == 'a'
+        assert d[k2] == 'b'
+
+    def test_map_basics_1(self):
+        h = self.Map()
+        h = None  # NoQA
+
+    def test_map_basics_2(self):
+        h = self.Map()
+        assert len(h) == 0
+
+        h2 = h.set('a', 'b')
+        assert h is not h2
+        assert len(h) == 0
+        assert len(h2) == 1
+
+        assert h.get('a') is None
+        assert h.get('a', 42) == 42
+
+        assert h2.get('a') == 'b'
+
+        h3 = h2.set('b', 10)
+        assert h2 is not h3
+        assert len(h) == 0
+        assert len(h2) == 1
+        assert len(h3) == 2
+        assert h3.get('a') == 'b'
+        assert h3.get('b') == 10
+
+        assert h.get('b') is None
+        assert h2.get('b') is None
+
+        assert h.get('a') is None
+        assert h2.get('a') == 'b'
+
+        h = h2 = h3 = None
+
+    def test_map_basics_3(self):
+        h = self.Map()
+        o = object()
+        h1 = h.set('1', o)
+        h2 = h1.set('1', o)
+        assert h1 is h2
+
+    def test_map_basics_4(self):
+        h = self.Map()
+        h1 = h.set('key', [])
+        h2 = h1.set('key', [])
+        assert h1 is not h2
+        assert len(h1) == 1
+        assert len(h2) == 1
+        assert h1.get('key') is not h2.get('key')
+
+    def test_map_collision_1(self):
+        k1 = HashKey(10, 'aaa')
+        k2 = HashKey(10, 'bbb')
+        k3 = HashKey(10, 'ccc')
+
+        h = self.Map()
+        h2 = h.set(k1, 'a')
+        h3 = h2.set(k2, 'b')
+
+        assert h.get(k1) == None
+        assert h.get(k2) == None
+
+        assert h2.get(k1) == 'a'
+        assert h2.get(k2) == None
+
+        assert h3.get(k1) == 'a'
+        assert h3.get(k2) == 'b'
+
+        h4 = h3.set(k2, 'cc')
+        h5 = h4.set(k3, 'aa')
+
+        assert h3.get(k1) == 'a'
+        assert h3.get(k2) == 'b'
+        assert h4.get(k1) == 'a'
+        assert h4.get(k2) == 'cc'
+        assert h4.get(k3) == None
+        assert h5.get(k1) == 'a'
+        assert h5.get(k2) == 'cc'
+        assert h5.get(k2) == 'cc'
+        assert h5.get(k3) == 'aa'
+
+        assert len(h) == 0
+        assert len(h2) == 1
+        assert len(h3) == 2
+        assert len(h4) == 2
+        assert len(h5) == 3
+
+    def test_map_collision_2(self):
+        A = HashKey(100, 'A')
+        B = HashKey(101, 'B')
+        C = HashKey(0b011000011100000100, 'C')
+        D = HashKey(0b011000011100000100, 'D')
+        E = HashKey(0b1011000011100000100, 'E')
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+
+        # BitmapNode(size=6 bitmap=0b100110000):
+        #     NULL:
+        #         BitmapNode(size=4 bitmap=0b1000000000000000000001000):
+        #             <Key name:A hash:100>: 'a'
+        #             NULL:
+        #                 CollisionNode(size=4 id=0x108572410):
+        #                     <Key name:C hash:100100>: 'c'
+        #                     <Key name:D hash:100100>: 'd'
+        #     <Key name:B hash:101>: 'b'
+
+        h = h.set(E, 'e')
+
+        # BitmapNode(size=4 count=2.0 bitmap=0b110000 id=10b8ea5c0):
+        #     None:
+        #         BitmapNode(size=4 count=2.0
+        #                    bitmap=0b1000000000000000000001000 id=10b8ea518):
+        #             <Key name:A hash:100>: 'a'
+        #             None:
+        #                 BitmapNode(size=2 count=1.0 bitmap=0b10
+        #                            id=10b8ea4a8):
+        #                     None:
+        #                         BitmapNode(size=4 count=2.0
+        #                                    bitmap=0b100000001000
+        #                                    id=10b8ea4e0):
+        #                             None:
+        #                                 CollisionNode(size=4 id=10b8ea470):
+        #                                     <Key name:C hash:100100>: 'c'
+        #                                     <Key name:D hash:100100>: 'd'
+        #                             <Key name:E hash:362244>: 'e'
+        #     <Key name:B hash:101>: 'b'
+
+    def test_map_stress(self):
+        COLLECTION_SIZE = 7000
+        TEST_ITERS_EVERY = 647
+        CRASH_HASH_EVERY = 97
+        CRASH_EQ_EVERY = 11
+        RUN_XTIMES = 3
+
+        for _ in range(RUN_XTIMES):
+            h = self.Map()
+            d = dict()
+
+            for i in range(COLLECTION_SIZE):
+                key = KeyStr(i)
+
+                if not (i % CRASH_HASH_EVERY):
+                    with HashKeyCrasher(error_on_hash=True):
+                        with pytest.raises(HashingError):
+                            h.set(key, i)
+
+                h = h.set(key, i)
+
+                if not (i % CRASH_EQ_EVERY):
+                    with HashKeyCrasher(error_on_eq=True):
+                        with pytest.raises(EqError):
+                            h.get(KeyStr(i))  # really trigger __eq__
+
+                d[key] = i
+                assert len(d) == len(h)
+
+                if not (i % TEST_ITERS_EVERY):
+                    assert set(h.items()) == set(d.items())
+                    assert len(h.items()) == len(d.items())
+
+            assert len(h) == COLLECTION_SIZE
+
+            for key in range(COLLECTION_SIZE):
+                assert h.get(KeyStr(key), 'not found') == key
+
+            keys_to_delete = list(range(COLLECTION_SIZE))
+            random.shuffle(keys_to_delete)
+            for iter_i, i in enumerate(keys_to_delete):
+                key = KeyStr(i)
+
+                if not (iter_i % CRASH_HASH_EVERY):
+                    with HashKeyCrasher(error_on_hash=True):
+                        with pytest.raises(HashingError):
+                            h.delete(key)
+
+                if not (iter_i % CRASH_EQ_EVERY):
+                    with HashKeyCrasher(error_on_eq=True):
+                        with pytest.raises(EqError):
+                            h.delete(KeyStr(i))
+
+                h = h.delete(key)
+                assert h.get(key, 'not found') == 'not found'
+                del d[key]
+                assert len(d) == len(h)
+
+                if iter_i == COLLECTION_SIZE // 2:
+                    hm = h
+                    dm = d.copy()
+
+                if not (iter_i % TEST_ITERS_EVERY):
+                    assert set(h.keys()) == set(d.keys())
+                    assert len(h.keys()) == len(d.keys())
+
+            assert len(d) == 0
+            assert len(h) == 0
+
+            # ============
+
+            for key in dm:
+                assert hm.get(str(key)) == dm[key]
+            assert len(dm) == len(hm)
+
+            for i, key in enumerate(keys_to_delete):
+                if str(key) in dm:
+                    hm = hm.delete(str(key))
+                    dm.pop(str(key))
+                assert hm.get(str(key), 'not found') == 'not found'
+                assert len(d) == len(h)
+
+                if not (i % TEST_ITERS_EVERY):
+                    assert set(h.values()) == set(d.values())
+                    assert len(h.values()) == len(d.values())
+
+            assert len(d) == 0
+            assert len(h) == 0
+            assert list(h.items()) == []
+
+    def test_map_delete_1(self):
+        A = HashKey(100, 'A')
+        B = HashKey(101, 'B')
+        C = HashKey(102, 'C')
+        D = HashKey(103, 'D')
+        E = HashKey(104, 'E')
+        Z = HashKey(-100, 'Z')
+
+        Er = HashKey(103, 'Er', error_on_eq_to=D)
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+        h = h.set(E, 'e')
+
+        orig_len = len(h)
+
+        # BitmapNode(size=10 bitmap=0b111110000 id=0x10eadc618):
+        #     <Key name:A hash:100>: 'a'
+        #     <Key name:B hash:101>: 'b'
+        #     <Key name:C hash:102>: 'c'
+        #     <Key name:D hash:103>: 'd'
+        #     <Key name:E hash:104>: 'e'
+
+        h = h.delete(C)
+        assert len(h) == orig_len - 1
+
+        with pytest.raises(ValueError, match='cannot compare'):
+            h.delete(Er)
+
+        h = h.delete(D)
+        assert len(h) == orig_len - 2
+
+        with pytest.raises(KeyError) as ex:
+            h.delete(Z)
+        assert ex.value.args[0] is Z
+
+        h = h.delete(A)
+        assert len(h) == orig_len - 3
+
+        assert h.get(A, 42) == 42
+        assert h.get(B) == 'b'
+        assert h.get(E) == 'e'
+
+    def test_map_delete_2(self):
+        A = HashKey(100, 'A')
+        B = HashKey(201001, 'B')
+        C = HashKey(101001, 'C')
+        BLike = HashKey(201001, 'B-like')
+        D = HashKey(103, 'D')
+        E = HashKey(104, 'E')
+        Z = HashKey(-100, 'Z')
+
+        Er = HashKey(201001, 'Er', error_on_eq_to=B)
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+        h = h.set(E, 'e')
+
+        h = h.set(B, 'b')  # trigger branch in BitmapNode.assoc
+
+        with pytest.raises(KeyError):
+            h.delete(BLike)    # trigger branch in BitmapNode.without
+
+        orig_len = len(h)
+
+        # BitmapNode(size=8 bitmap=0b1110010000):
+        #     <Key name:A hash:100>: 'a'
+        #     <Key name:D hash:103>: 'd'
+        #     <Key name:E hash:104>: 'e'
+        #     NULL:
+        #         BitmapNode(size=4 bitmap=0b100000000001000000000):
+        #             <Key name:B hash:201001>: 'b'
+        #             <Key name:C hash:101001>: 'c'
+
+        with pytest.raises(ValueError, match='cannot compare'):
+            h.delete(Er)
+
+        with pytest.raises(KeyError) as ex:
+            h.delete(Z)
+        assert ex.value.args[0] is Z
+        assert len(h) == orig_len
+
+        h = h.delete(C)
+        assert len(h) == orig_len - 1
+
+        h = h.delete(B)
+        assert len(h) == orig_len - 2
+
+        h = h.delete(A)
+        assert len(h) == orig_len - 3
+
+        assert h.get(D) == 'd'
+        assert h.get(E) == 'e'
+
+        with pytest.raises(KeyError):
+            h = h.delete(A)
+        with pytest.raises(KeyError):
+            h = h.delete(B)
+        h = h.delete(D)
+        h = h.delete(E)
+        assert len(h) == 0
+
+    def test_map_delete_3(self):
+        A = HashKey(0b00000000001100100, 'A')
+        B = HashKey(0b00000000001100101, 'B')
+
+        C = HashKey(0b11000011100000100, 'C')
+        D = HashKey(0b11000011100000100, 'D')
+        X = HashKey(0b01000011100000100, 'Z')
+        Y = HashKey(0b11000011100000100, 'Y')
+
+        E = HashKey(0b00000000001101000, 'E')
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+        h = h.set(E, 'e')
+
+        assert len(h) == 5
+        h = h.set(C, 'c')  # trigger branch in CollisionNode.assoc
+        assert len(h) == 5
+
+        orig_len = len(h)
+
+        with pytest.raises(KeyError):
+            h.delete(X)
+        with pytest.raises(KeyError):
+            h.delete(Y)
+
+        # BitmapNode(size=6 bitmap=0b100110000):
+        #     NULL:
+        #         BitmapNode(size=4 bitmap=0b1000000000000000000001000):
+        #             <Key name:A hash:100>: 'a'
+        #             NULL:
+        #                 CollisionNode(size=4 id=0x108572410):
+        #                     <Key name:C hash:100100>: 'c'
+        #                     <Key name:D hash:100100>: 'd'
+        #     <Key name:B hash:101>: 'b'
+        #     <Key name:E hash:104>: 'e'
+
+        h = h.delete(A)
+        assert len(h) == orig_len - 1
+
+        h = h.delete(E)
+        assert len(h) == orig_len - 2
+
+        assert h.get(C) == 'c'
+        assert h.get(B) == 'b'
+
+        h2 = h.delete(C)
+        assert len(h2) == orig_len - 3
+
+        h2 = h.delete(D)
+        assert len(h2) == orig_len - 3
+
+        assert len(h) == orig_len - 2
+
+    def test_map_delete_4(self):
+        A = HashKey(100, 'A')
+        B = HashKey(101, 'B')
+        C = HashKey(100100, 'C')
+        D = HashKey(100100, 'D')
+        E = HashKey(100100, 'E')
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+        h = h.set(E, 'e')
+
+        orig_len = len(h)
+
+        # BitmapNode(size=4 bitmap=0b110000):
+        #     NULL:
+        #         BitmapNode(size=4 bitmap=0b1000000000000000000001000):
+        #             <Key name:A hash:100>: 'a'
+        #             NULL:
+        #                 CollisionNode(size=6 id=0x10515ef30):
+        #                     <Key name:C hash:100100>: 'c'
+        #                     <Key name:D hash:100100>: 'd'
+        #                     <Key name:E hash:100100>: 'e'
+        #     <Key name:B hash:101>: 'b'
+
+        h = h.delete(D)
+        assert len(h) == orig_len - 1
+
+        h = h.delete(E)
+        assert len(h) == orig_len - 2
+
+        h = h.delete(C)
+        assert len(h) == orig_len - 3
+
+        h = h.delete(A)
+        assert len(h) == orig_len - 4
+
+        h = h.delete(B)
+        assert len(h) == 0
+
+    def test_map_delete_5(self):
+        h = self.Map()
+
+        keys = []
+        for i in range(17):
+            key = HashKey(i, str(i))
+            keys.append(key)
+            h = h.set(key, 'val-{}'.format(i))
+
+        collision_key16 = HashKey(16, '18')
+        h = h.set(collision_key16, 'collision')
+
+        # ArrayNode(id=0x10f8b9318):
+        #     0::
+        #     BitmapNode(size=2 count=1 bitmap=0b1):
+        #         <Key name:0 hash:0>: 'val-0'
+        #
+        # ... 14 more BitmapNodes ...
+        #
+        #     15::
+        #     BitmapNode(size=2 count=1 bitmap=0b1):
+        #         <Key name:15 hash:15>: 'val-15'
+        #
+        #     16::
+        #     BitmapNode(size=2 count=1 bitmap=0b1):
+        #         NULL:
+        #             CollisionNode(size=4 id=0x10f2f5af8):
+        #                 <Key name:16 hash:16>: 'val-16'
+        #                 <Key name:18 hash:16>: 'collision'
+
+        assert len(h) == 18
+
+        h = h.delete(keys[2])
+        assert len(h) == 17
+
+        h = h.delete(collision_key16)
+        assert len(h) == 16
+        h = h.delete(keys[16])
+        assert len(h) == 15
+
+        h = h.delete(keys[1])
+        assert len(h) == 14
+        with pytest.raises(KeyError) as ex:
+            h.delete(keys[1])
+        assert ex.value.args[0] is keys[1]
+        assert len(h) == 14
+
+        for key in keys:
+            if key in h:
+                h = h.delete(key)
+        assert len(h) == 0
+
+    def test_map_delete_6(self):
+        h = self.Map()
+        h = h.set(1, 1)
+        h = h.delete(1)
+        assert len(h) == 0
+        assert h == self.Map()
+
+    def test_map_items_1(self):
+        A = HashKey(100, 'A')
+        B = HashKey(201001, 'B')
+        C = HashKey(101001, 'C')
+        D = HashKey(103, 'D')
+        E = HashKey(104, 'E')
+        F = HashKey(110, 'F')
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+        h = h.set(E, 'e')
+        h = h.set(F, 'f')
+
+        it = h.items()
+        assert set(list(it)) == \
+            {(A, 'a'), (B, 'b'), (C, 'c'), (D, 'd'), (E, 'e'), (F, 'f')}
+
+    def test_map_items_2(self):
+        A = HashKey(100, 'A')
+        B = HashKey(101, 'B')
+        C = HashKey(100100, 'C')
+        D = HashKey(100100, 'D')
+        E = HashKey(100100, 'E')
+        F = HashKey(110, 'F')
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+        h = h.set(E, 'e')
+        h = h.set(F, 'f')
+
+        it = h.items()
+        assert set(list(it)) == \
+            {(A, 'a'), (B, 'b'), (C, 'c'), (D, 'd'), (E, 'e'), (F, 'f')}
+
+    def test_map_items_3(self):
+        h = self.Map()
+        assert len(h.items()) == 0
+        assert list(h.items()) == []
+
+    def test_map_items_4(self):
+        h = self.Map(a=1, b=2, c=3)
+        k = h.items()
+        assert set(k) == {('a', 1), ('b', 2), ('c', 3)}
+        assert set(k) == {('a', 1), ('b', 2), ('c', 3)}
+
+    def test_map_keys_1(self):
+        A = HashKey(100, 'A')
+        B = HashKey(101, 'B')
+        C = HashKey(100100, 'C')
+        D = HashKey(100100, 'D')
+        E = HashKey(100100, 'E')
+        F = HashKey(110, 'F')
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+        h = h.set(E, 'e')
+        h = h.set(F, 'f')
+
+        assert set(list(h.keys())) == {A, B, C, D, E, F}
+        assert set(list(h)) == {A, B, C, D, E, F}
+
+    def test_map_keys_2(self):
+        h = self.Map(a=1, b=2, c=3)
+        k = h.keys()
+        assert set(k) == {'a', 'b', 'c'}
+        assert set(k) == {'a', 'b', 'c'}
+
+    def test_map_values_1(self):
+        A = HashKey(100, 'A')
+        B = HashKey(101, 'B')
+        C = HashKey(100100, 'C')
+        D = HashKey(100100, 'D')
+        E = HashKey(100100, 'E')
+        F = HashKey(110, 'F')
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+        h = h.set(E, 'e')
+        h = h.set(F, 'f')
+
+        assert set(list(h.values())) == {'a', 'b', 'c', 'd', 'e', 'f'}
+
+    def test_map_values_2(self):
+        h = self.Map(a=1, b=2, c=3)
+        k = h.values()
+        assert set(k) == {1, 2, 3}
+        assert set(k) == {1, 2, 3}
+
+    def test_map_eq_1(self):
+        A = HashKey(100, 'A')
+        B = HashKey(101, 'B')
+        C = HashKey(100100, 'C')
+        D = HashKey(100100, 'D')
+        E = HashKey(120, 'E')
+
+        h1 = self.Map()
+        h1 = h1.set(A, 'a')
+        h1 = h1.set(B, 'b')
+        h1 = h1.set(C, 'c')
+        h1 = h1.set(D, 'd')
+
+        h2 = self.Map()
+        h2 = h2.set(A, 'a')
+
+        assert not (h1 == h2)
+        assert h1 != h2
+
+        h2 = h2.set(B, 'b')
+        assert not (h1 == h2)
+        assert h1 != h2
+
+        h2 = h2.set(C, 'c')
+        assert not (h1 == h2)
+        assert h1 != h2
+
+        h2 = h2.set(D, 'd2')
+        assert not (h1 == h2)
+        assert h1 != h2
+
+        h2 = h2.set(D, 'd')
+        assert h1 == h2
+        assert not (h1 != h2)
+
+        h2 = h2.set(E, 'e')
+        assert not (h1 == h2)
+        assert h1 != h2
+
+        h2 = h2.delete(D)
+        assert not (h1 == h2)
+        assert h1 != h2
+
+        h2 = h2.set(E, 'd')
+        assert not (h1 == h2)
+        assert h1 != h2
+
+    def test_map_eq_2(self):
+        A = HashKey(100, 'A')
+        Er = HashKey(100, 'Er', error_on_eq_to=A)
+
+        h1 = self.Map()
+        h1 = h1.set(A, 'a')
+
+        h2 = self.Map()
+        h2 = h2.set(Er, 'a')
+
+        with pytest.raises(ValueError, match='cannot compare'):
+            h1 == h2
+
+        with pytest.raises(ValueError, match='cannot compare'):
+            h1 != h2
+
+    def test_map_eq_3(self):
+        assert self.Map() != 1
+
+    def test_map_gc_1(self):
+        A = HashKey(100, 'A')
+
+        h = self.Map()
+        h = h.set(0, 0)  # empty Map node is memoized in _map.c
+        ref = weakref.ref(h)
+
+        a = []
+        a.append(a)
+        a.append(h)
+        b = []
+        a.append(b)
+        b.append(a)
+        h = h.set(A, b)
+
+        del h, a, b
+
+        gc.collect()
+        gc.collect()
+        gc.collect()
+
+        assert ref() is None
+
+    def test_map_gc_2(self):
+        A = HashKey(100, 'A')
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(A, h)
+
+        ref = weakref.ref(h)
+        hi = iter(h.items())
+        next(hi)
+
+        del h, hi
+
+        gc.collect()
+        gc.collect()
+        gc.collect()
+
+        assert ref() is None
+
+    def test_map_in_1(self):
+        A = HashKey(100, 'A')
+        AA = HashKey(100, 'A')
+
+        B = HashKey(101, 'B')
+
+        h = self.Map()
+        h = h.set(A, 1)
+
+        assert A in h
+        assert not (B in h)
+
+        with pytest.raises(EqError):
+            with HashKeyCrasher(error_on_eq=True):
+                AA in h
+
+        with pytest.raises(HashingError):
+            with HashKeyCrasher(error_on_hash=True):
+                AA in h
+
+    def test_map_getitem_1(self):
+        A = HashKey(100, 'A')
+        AA = HashKey(100, 'A')
+
+        B = HashKey(101, 'B')
+
+        h = self.Map()
+        h = h.set(A, 1)
+
+        assert h[A] == 1
+        assert h[AA] == 1
+
+        with pytest.raises(KeyError):
+            h[B]
+
+        with pytest.raises(EqError):
+            with HashKeyCrasher(error_on_eq=True):
+                h[AA]
+
+        with pytest.raises(HashingError):
+            with HashKeyCrasher(error_on_hash=True):
+                h[AA]
+
+    def test_repr_1(self):
+        h = self.Map()
+        assert repr(h).startswith('<immutables.Map({}) at 0x')
+
+        h = h.set(1, 2).set(2, 3).set(3, 4)
+        assert repr(h).startswith(
+            '<immutables.Map({1: 2, 2: 3, 3: 4}) at 0x')
+
+    def test_repr_2(self):
+        h = self.Map()
+        A = HashKey(100, 'A')
+
+        with pytest.raises(ReprError):
+            with HashKeyCrasher(error_on_repr=True):
+                repr(h.set(1, 2).set(A, 3).set(3, 4))
+
+        with pytest.raises(ReprError):
+            with HashKeyCrasher(error_on_repr=True):
+                repr(h.set(1, 2).set(2, A).set(3, 4))
+
+    def test_repr_3(self):
+        class Key:
+            def __init__(self):
+                self.val = None
+
+            def __hash__(self):
+                return 123
+
+            def __repr__(self):
+                return repr(self.val)
+
+        h = self.Map()
+        k = Key()
+        h = h.set(k, 1)
+        k.val = h
+
+        assert repr(h).startswith(
+            '<immutables.Map({{...}: 1}) at 0x')
+
+    def test_hash_1(self):
+        h = self.Map()
+        assert hash(h) != -1
+        assert hash(h) == hash(h)
+
+        h = h.set(1, 2).set('a', 'b')
+        assert hash(h) != -1
+        assert hash(h) == hash(h)
+
+        assert hash(h.set(1, 2).set('a', 'b')) == \
+            hash(h.set('a', 'b').set(1, 2))
+
+    def test_hash_2(self):
+        h = self.Map()
+        A = HashKey(100, 'A')
+
+        m = h.set(1, 2).set(A, 3).set(3, 4)
+        with pytest.raises(HashingError):
+            with HashKeyCrasher(error_on_hash=True):
+                hash(m)
+
+        m = h.set(1, 2).set(2, A).set(3, 4)
+        with pytest.raises(HashingError):
+            with HashKeyCrasher(error_on_hash=True):
+                hash(m)
+
+    def test_abc_1(self):
+        assert issubclass(self.Map, collections.abc.Mapping)
+
+    def test_map_mut_1(self):
+        h = self.Map()
+        h = h.set('a', 1)
+
+        hm1 = h.mutate()
+        hm2 = h.mutate()
+
+        assert not isinstance(hm1, self.Map)
+
+        assert hm1 is not hm2
+        assert hm1['a'] == 1
+        assert hm2['a'] == 1
+
+        hm1.set('b', 2)
+        hm1.set('c', 3)
+
+        hm2.set('x', 100)
+        hm2.set('a', 1000)
+
+        assert hm1['a'] == 1
+        assert hm1.get('x', -1) == -1
+
+        assert hm2['a'] == 1000
+        assert 'x' in hm2
+
+        h1 = hm1.finish()
+        h2 = hm2.finish()
+
+        assert isinstance(h1, self.Map)
+
+        assert dict(h.items()) == {'a': 1}
+        assert dict(h1.items()) == {'a': 1, 'b': 2, 'c': 3}
+        assert dict(h2.items()) == {'a': 1000, 'x': 100}
+
+    def test_map_mut_2(self):
+        h = self.Map()
+        h = h.set('a', 1)
+
+        hm1 = h.mutate()
+        hm1.set('a', 2)
+        hm1.set('a', 3)
+        hm1.set('a', 4)
+        h2 = hm1.finish()
+
+        assert dict(h.items()) == {'a': 1}
+        assert dict(h2.items()) == {'a': 4}
+
+    def test_map_mut_3(self):
+        h = self.Map()
+        h = h.set('a', 1)
+        hm1 = h.mutate()
+
+        assert repr(hm1).startswith(
+            "<immutables.MapMutation({'a': 1})")
+
+        with pytest.raises(TypeError, match='unhashable type'):
+            hash(hm1)
+
+    def test_map_mut_4(self):
+        h = self.Map()
+        h = h.set('a', 1)
+        h = h.set('b', 2)
+
+        hm1 = h.mutate()
+        hm2 = h.mutate()
+
+        assert hm1 == hm2
+
+        hm1.set('a', 10)
+        assert hm1 != hm2
+
+        hm2.set('a', 10)
+        assert hm1 == hm2
+
+        assert hm2.pop('a') == 10
+        assert hm1 != hm2
+
+    def test_map_mut_5(self):
+        h = self.Map({'a': 1, 'b': 2}, z=100)
+        assert isinstance(h, self.Map)
+        assert dict(h.items()) == {'a': 1, 'b': 2, 'z': 100}
+
+        h2 = h.update(z=200, y=-1)
+        assert dict(h.items()) == {'a': 1, 'b': 2, 'z': 100}
+        assert dict(h2.items()) == {'a': 1, 'b': 2, 'z': 200, 'y': -1}
+
+        h3 = h2.update([(1, 2), (3, 4)])
+        assert dict(h.items()) == {'a': 1, 'b': 2, 'z': 100}
+        assert dict(h2.items()) == {'a': 1, 'b': 2, 'z': 200, 'y': -1}
+        assert dict(h3.items()) == \
+                         {'a': 1, 'b': 2, 'z': 200, 'y': -1, 1: 2, 3: 4}
+
+        h4 = h3.update()
+        assert h4 is h3
+
+        h5 = h4.update(self.Map({'zzz': 'yyz'}))
+
+        assert dict(h5.items()) == \
+                         {'a': 1, 'b': 2, 'z': 200, 'y': -1, 1: 2, 3: 4,
+                          'zzz': 'yyz'}
+
+    def test_map_mut_6(self):
+        h = self.Map({'a': 1, 'b': 2}, z=100)
+        assert dict(h.items()) == {'a': 1, 'b': 2, 'z': 100}
+
+        with pytest.raises(TypeError, match='not iterable'):
+            h.update(1)
+
+        with pytest.raises(ValueError, match='map update sequence element'):
+            h.update([(1, 2), (3, 4, 5)])
+
+        with pytest.raises(TypeError, match='cannot convert map update'):
+            h.update([(1, 2), 1])
+
+        assert dict(h.items()) == {'a': 1, 'b': 2, 'z': 100}
+
+    def test_map_mut_7(self):
+        key = HashKey(123, 'aaa')
+
+        h = self.Map({'a': 1, 'b': 2}, z=100)
+        assert dict(h.items()) == {'a': 1, 'b': 2, 'z': 100}
+
+        upd = {key: 1}
+        with HashKeyCrasher(error_on_hash=True):
+            with pytest.raises(HashingError):
+                h.update(upd)
+
+        upd = self.Map({key: 'zzz'})
+        with HashKeyCrasher(error_on_hash=True):
+            with pytest.raises(HashingError):
+                h.update(upd)
+
+        upd = [(1, 2), (key, 'zzz')]
+        with HashKeyCrasher(error_on_hash=True):
+            with pytest.raises(HashingError):
+                h.update(upd)
+
+        assert dict(h.items()) == {'a': 1, 'b': 2, 'z': 100}
+
+    def test_map_mut_8(self):
+        key1 = HashKey(123, 'aaa')
+        key2 = HashKey(123, 'bbb')
+
+        h = self.Map({key1: 123})
+        assert dict(h.items()) == {key1: 123}
+
+        upd = {key2: 1}
+        with HashKeyCrasher(error_on_eq=True):
+            with pytest.raises(EqError):
+                h.update(upd)
+
+        upd = self.Map({key2: 'zzz'})
+        with HashKeyCrasher(error_on_eq=True):
+            with pytest.raises(EqError):
+                h.update(upd)
+
+        upd = [(1, 2), (key2, 'zzz')]
+        with HashKeyCrasher(error_on_eq=True):
+            with pytest.raises(EqError):
+                h.update(upd)
+
+        assert dict(h.items()) == {key1: 123}
+
+    def test_map_mut_9(self):
+        key1 = HashKey(123, 'aaa')
+
+        src = {key1: 123}
+        with HashKeyCrasher(error_on_hash=True):
+            with pytest.raises(HashingError):
+                self.Map(src)
+
+        src = [(1, 2), (key1, 123)]
+        with HashKeyCrasher(error_on_hash=True):
+            with pytest.raises(HashingError):
+                self.Map(src)
+
+    def test_map_mut_10(self):
+        key1 = HashKey(123, 'aaa')
+
+        m = self.Map({key1: 123})
+
+        mm = m.mutate()
+        with HashKeyCrasher(error_on_hash=True):
+            with pytest.raises(HashingError):
+                del mm[key1]
+
+        mm = m.mutate()
+        with HashKeyCrasher(error_on_hash=True):
+            with pytest.raises(HashingError):
+                mm.pop(key1, None)
+
+        mm = m.mutate()
+        with HashKeyCrasher(error_on_hash=True):
+            with pytest.raises(HashingError):
+                mm.set(key1, 123)
+
+    def test_map_mut_11(self):
+        m = self.Map({'a': 1, 'b': 2})
+
+        mm = m.mutate()
+        assert mm.pop('a', 1) == 1
+        assert mm.finish() == self.Map({'b': 2})
+
+        mm = m.mutate()
+        assert mm.pop('b', 1) == 2
+        assert mm.finish() == self.Map({'a': 1})
+
+        mm = m.mutate()
+        assert mm.pop('b', 1) == 2
+        del mm['a']
+        assert mm.finish() == self.Map()
+
+    def test_map_mut_12(self):
+        m = self.Map({'a': 1, 'b': 2})
+
+        mm = m.mutate()
+        mm.finish()
+
+        with pytest.raises(ValueError, match='has been finished'):
+            mm.pop('a')
+
+        with pytest.raises(ValueError, match='has been finished'):
+            del mm['a']
+
+        with pytest.raises(ValueError, match='has been finished'):
+            mm.set('a', 'b')
+
+        with pytest.raises(ValueError, match='has been finished'):
+            mm['a'] = 'b'
+
+        with pytest.raises(ValueError, match='has been finished'):
+            mm.update(a='b')
+
+    def test_map_mut_13(self):
+        key1 = HashKey(123, 'aaa')
+        key2 = HashKey(123, 'aaa')
+
+        m = self.Map({key1: 123})
+
+        mm = m.mutate()
+        with HashKeyCrasher(error_on_eq=True):
+            with pytest.raises(EqError):
+                del mm[key2]
+
+        mm = m.mutate()
+        with HashKeyCrasher(error_on_eq=True):
+            with pytest.raises(EqError):
+                mm.pop(key2, None)
+
+        mm = m.mutate()
+        with HashKeyCrasher(error_on_eq=True):
+            with pytest.raises(EqError):
+                mm.set(key2, 123)
+
+    def test_map_mut_14(self):
+        m = self.Map(a=1, b=2)
+
+        with m.mutate() as mm:
+            mm['z'] = 100
+            del mm['a']
+
+        assert mm.finish() == self.Map(z=100, b=2)
+
+    def test_map_mut_15(self):
+        m = self.Map(a=1, b=2)
+
+        with pytest.raises(ZeroDivisionError):
+            with m.mutate() as mm:
+                mm['z'] = 100
+                del mm['a']
+                1 / 0
+
+        assert mm.finish() == self.Map(z=100, b=2)
+        assert m == self.Map(a=1, b=2)
+
+    def test_map_mut_16(self):
+        m = self.Map(a=1, b=2)
+        hash(m)
+
+        m2 = self.Map(m)
+        m3 = self.Map(m, c=3)
+
+        assert m == m2
+        assert len(m) == len(m2)
+        assert hash(m) == hash(m2)
+
+        assert m is not m2
+        assert m3 == self.Map(a=1, b=2, c=3)
+
+    def test_map_mut_17(self):
+        m = self.Map(a=1)
+        with m.mutate() as mm:
+            with pytest.raises(TypeError, match='cannot create Maps from MapMutations'):
+                self.Map(mm)
+
+    def test_map_mut_18(self):
+        m = self.Map(a=1, b=2)
+        with m.mutate() as mm:
+            mm.update(self.Map(x=1), z=2)
+            mm.update(c=3)
+            mm.update({'n': 100, 'a': 20})
+            m2 = mm.finish()
+
+        expected = self.Map(
+            {'b': 2, 'c': 3, 'n': 100, 'z': 2, 'x': 1, 'a': 20})
+
+        assert len(m2) == 6
+        assert m2 == expected
+        assert m == self.Map(a=1, b=2)
+
+    def test_map_mut_19(self):
+        m = self.Map(a=1, b=2)
+        m2 = m.update({'a': 20})
+        assert len(m2) == 2
+
+    def test_map_mut_stress(self):
+        COLLECTION_SIZE = 7000
+        TEST_ITERS_EVERY = 647
+        RUN_XTIMES = 3
+
+        for _ in range(RUN_XTIMES):
+            h = self.Map()
+            d = dict()
+
+            for i in range(COLLECTION_SIZE // TEST_ITERS_EVERY):
+
+                hm = h.mutate()
+                for j in range(TEST_ITERS_EVERY):
+                    key = random.randint(1, 100000)
+                    key = HashKey(key % 271, str(key))
+
+                    hm.set(key, key)
+                    d[key] = key
+
+                    assert len(hm) == len(d)
+
+                h2 = hm.finish()
+                assert dict(h2.items()) == d
+                h = h2
+
+            assert dict(h.items()) == d
+            assert len(h) == len(d)
+
+            it = iter(tuple(d.keys()))
+            for i in range(COLLECTION_SIZE // TEST_ITERS_EVERY):
+
+                hm = h.mutate()
+                for j in range(TEST_ITERS_EVERY):
+                    try:
+                        key = next(it)
+                    except StopIteration:
+                        break
+
+                    del d[key]
+                    del hm[key]
+
+                    assert len(hm) == len(d)
+
+                h2 = hm.finish()
+                assert dict(h2.items()) == d
+                h = h2
+
+            assert dict(h.items()) == d
+            assert len(h) == len(d)
+
+    def test_map_pickle(self):
+        h = self.Map(a=1, b=2)
+        for proto in range(pickle.HIGHEST_PROTOCOL):
+            p = pickle.dumps(h, proto)
+            uh = pickle.loads(p)
+
+            assert isinstance(uh, self.Map)
+            assert h == uh
+
+        with pytest.raises(TypeError, match="can('t|not) pickle"):
+            pickle.dumps(h.mutate())
+
+    def test_map_is_subscriptable(self):
+        assert self.Map[int, str] is self.Map
+
+class TestPyMap(BaseMapTest):
+    Map = Map
diff --git a/lib_pypy/_immutables_map.py b/lib_pypy/_immutables_map.py
new file mode 100644
--- /dev/null
+++ b/lib_pypy/_immutables_map.py
@@ -0,0 +1,817 @@
+import collections.abc
+import itertools
+import reprlib
+import sys
+
+
+__all__ = ('Map',)
+
+
+# Thread-safe counter.
+_mut_id = itertools.count(1).__next__
+
+
+# Python version of _map.c.  The topmost comment there explains
+# all datastructures and algorithms.
+# The code here follows C code closely on purpose to make
+# debugging and testing easier.
+
+
+def map_hash(o):
+    x = hash(o)
+    return (x & 0xffffffff) ^ ((x >> 32) & 0xffffffff)
+
+
+def map_mask(hash, shift):
+    return (hash >> shift) & 0x01f
+
+
+def map_bitpos(hash, shift):
+    return 1 << map_mask(hash, shift)
+
+
+def map_bitcount(v):
+    v = v - ((v >> 1) & 0x55555555)
+    v = (v & 0x33333333) + ((v >> 2) & 0x33333333)
+    v = (v & 0x0F0F0F0F) + ((v >> 4) & 0x0F0F0F0F)
+    v = v + (v >> 8)
+    v = (v + (v >> 16)) & 0x3F
+    return v
+
+
+def map_bitindex(bitmap, bit):
+    return map_bitcount(bitmap & (bit - 1))
+
+
+W_EMPTY, W_NEWNODE, W_NOT_FOUND = range(3)
+void = object()
+
+
+class BitmapNode:
+
+    def __init__(self, size, bitmap, array, mutid):
+        self.size = size
+        self.bitmap = bitmap
+        assert isinstance(array, list) and len(array) == size
+        self.array = array
+        self.mutid = mutid
+
+    def clone(self, mutid):
+        return BitmapNode(self.size, self.bitmap, self.array.copy(), mutid)
+
+    def assoc(self, shift, hash, key, val, mutid):
+        bit = map_bitpos(hash, shift)
+        idx = map_bitindex(self.bitmap, bit)
+
+        if self.bitmap & bit:
+            key_idx = 2 * idx
+            val_idx = key_idx + 1
+
+            key_or_null = self.array[key_idx]
+            val_or_node = self.array[val_idx]
+
+            if key_or_null is None:
+                sub_node, added = val_or_node.assoc(
+                    shift + 5, hash, key, val, mutid)
+                if val_or_node is sub_node:
+                    return self, added
+
+                if mutid and mutid == self.mutid:
+                    self.array[val_idx] = sub_node
+                    return self, added
+                else:
+                    ret = self.clone(mutid)
+                    ret.array[val_idx] = sub_node
+                    return ret, added
+
+            if key == key_or_null:
+                if val is val_or_node:
+                    return self, False
+
+                if mutid and mutid == self.mutid:
+                    self.array[val_idx] = val
+                    return self, False
+                else:
+                    ret = self.clone(mutid)
+                    ret.array[val_idx] = val
+                    return ret, False
+
+            existing_key_hash = map_hash(key_or_null)
+            if existing_key_hash == hash:
+                sub_node = CollisionNode(
+                    4, hash, [key_or_null, val_or_node, key, val], mutid)
+            else:
+                sub_node = BitmapNode(0, 0, [], mutid)
+                sub_node, _ = sub_node.assoc(
+                    shift + 5, existing_key_hash,
+                    key_or_null, val_or_node,
+                    mutid)
+                sub_node, _ = sub_node.assoc(
+                    shift + 5, hash, key, val,
+                    mutid)
+
+            if mutid and mutid == self.mutid:
+                self.array[key_idx] = None
+                self.array[val_idx] = sub_node
+                return self, True
+            else:
+                ret = self.clone(mutid)
+                ret.array[key_idx] = None
+                ret.array[val_idx] = sub_node
+                return ret, True
+
+        else:
+            key_idx = 2 * idx
+            val_idx = key_idx + 1
+
+            n = map_bitcount(self.bitmap)
+
+            new_array = self.array[:key_idx]
+            new_array.append(key)
+            new_array.append(val)
+            new_array.extend(self.array[key_idx:])
+
+            if mutid and mutid == self.mutid:
+                self.size = 2 * (n + 1)
+                self.bitmap |= bit
+                self.array = new_array
+                return self, True
+            else:
+                return BitmapNode(
+                    2 * (n + 1), self.bitmap | bit, new_array, mutid), True
+
+    def find(self, shift, hash, key):
+        bit = map_bitpos(hash, shift)
+
+        if not (self.bitmap & bit):
+            raise KeyError
+
+        idx = map_bitindex(self.bitmap, bit)
+        key_idx = idx * 2
+        val_idx = key_idx + 1
+
+        key_or_null = self.array[key_idx]
+        val_or_node = self.array[val_idx]
+
+        if key_or_null is None:
+            return val_or_node.find(shift + 5, hash, key)
+
+        if key == key_or_null:
+            return val_or_node
+
+        raise KeyError(key)
+
+    def without(self, shift, hash, key, mutid):
+        bit = map_bitpos(hash, shift)
+        if not (self.bitmap & bit):
+            return W_NOT_FOUND, None
+
+        idx = map_bitindex(self.bitmap, bit)
+        key_idx = 2 * idx
+        val_idx = key_idx + 1
+
+        key_or_null = self.array[key_idx]
+        val_or_node = self.array[val_idx]
+
+        if key_or_null is None:
+            res, sub_node = val_or_node.without(shift + 5, hash, key, mutid)
+
+            if res is W_EMPTY:
+                raise RuntimeError('unreachable code')  # pragma: no cover
+
+            elif res is W_NEWNODE:
+                if (type(sub_node) is BitmapNode and
+                        sub_node.size == 2 and
+                        sub_node.array[0] is not None):
+
+                    if mutid and mutid == self.mutid:
+                        self.array[key_idx] = sub_node.array[0]
+                        self.array[val_idx] = sub_node.array[1]
+                        return W_NEWNODE, self
+                    else:
+                        clone = self.clone(mutid)
+                        clone.array[key_idx] = sub_node.array[0]
+                        clone.array[val_idx] = sub_node.array[1]
+                        return W_NEWNODE, clone
+
+                if mutid and mutid == self.mutid:
+                    self.array[val_idx] = sub_node
+                    return W_NEWNODE, self
+                else:
+                    clone = self.clone(mutid)
+                    clone.array[val_idx] = sub_node
+                    return W_NEWNODE, clone
+
+            else:
+                assert sub_node is None
+                return res, None
+
+        else:
+            if key == key_or_null:
+                if self.size == 2:
+                    return W_EMPTY, None
+
+                new_array = self.array[:key_idx]
+                new_array.extend(self.array[val_idx + 1:])
+
+                if mutid and mutid == self.mutid:
+                    self.size -= 2
+                    self.bitmap &= ~bit
+                    self.array = new_array
+                    return W_NEWNODE, self
+                else:
+                    new_node = BitmapNode(
+                        self.size - 2, self.bitmap & ~bit, new_array, mutid)
+                    return W_NEWNODE, new_node
+
+            else:
+                return W_NOT_FOUND, None
+
+    def keys(self):
+        for i in range(0, self.size, 2):
+            key_or_null = self.array[i]
+
+            if key_or_null is None:
+                val_or_node = self.array[i + 1]
+                yield from val_or_node.keys()
+            else:
+                yield key_or_null
+
+    def values(self):
+        for i in range(0, self.size, 2):
+            key_or_null = self.array[i]
+            val_or_node = self.array[i + 1]
+
+            if key_or_null is None:
+                yield from val_or_node.values()
+            else:
+                yield val_or_node
+
+    def items(self):
+        for i in range(0, self.size, 2):
+            key_or_null = self.array[i]
+            val_or_node = self.array[i + 1]
+
+            if key_or_null is None:
+                yield from val_or_node.items()
+            else:
+                yield key_or_null, val_or_node
+
+    def dump(self, buf, level):  # pragma: no cover
+        buf.append(
+            '    ' * (level + 1) +
+            'BitmapNode(size={} count={} bitmap={} id={:0x}):'.format(
+                self.size, self.size / 2, bin(self.bitmap), id(self)))
+
+        for i in range(0, self.size, 2):
+            key_or_null = self.array[i]
+            val_or_node = self.array[i + 1]
+
+            pad = '    ' * (level + 2)
+
+            if key_or_null is None:
+                buf.append(pad + 'None:')
+                val_or_node.dump(buf, level + 2)
+            else:
+                buf.append(pad + '{!r}: {!r}'.format(key_or_null, val_or_node))
+
+
+class CollisionNode:
+
+    def __init__(self, size, hash, array, mutid):
+        self.size = size
+        self.hash = hash
+        self.array = array
+        self.mutid = mutid
+
+    def find_index(self, key):
+        for i in range(0, self.size, 2):
+            if self.array[i] == key:
+                return i
+        return -1
+
+    def find(self, shift, hash, key):
+        for i in range(0, self.size, 2):
+            if self.array[i] == key:
+                return self.array[i + 1]
+        raise KeyError(key)
+
+    def assoc(self, shift, hash, key, val, mutid):
+        if hash == self.hash:
+            key_idx = self.find_index(key)
+
+            if key_idx == -1:
+                new_array = self.array.copy()
+                new_array.append(key)
+                new_array.append(val)
+
+                if mutid and mutid == self.mutid:
+                    self.size += 2
+                    self.array = new_array
+                    return self, True
+                else:
+                    new_node = CollisionNode(
+                        self.size + 2, hash, new_array, mutid)
+                    return new_node, True
+
+            val_idx = key_idx + 1
+            if self.array[val_idx] is val:
+                return self, False
+
+            if mutid and mutid == self.mutid:
+                self.array[val_idx] = val
+                return self, False
+            else:
+                new_array = self.array.copy()
+                new_array[val_idx] = val
+                return CollisionNode(self.size, hash, new_array, mutid), False
+
+        else:
+            new_node = BitmapNode(
+                2, map_bitpos(self.hash, shift), [None, self], mutid)
+            return new_node.assoc(shift, hash, key, val, mutid)
+
+    def without(self, shift, hash, key, mutid):
+        if hash != self.hash:
+            return W_NOT_FOUND, None
+
+        key_idx = self.find_index(key)
+        if key_idx == -1:
+            return W_NOT_FOUND, None
+
+        new_size = self.size - 2
+        if new_size == 0:
+            # Shouldn't be ever reachable
+            return W_EMPTY, None  # pragma: no cover
+
+        if new_size == 2:
+            if key_idx == 0:
+                new_array = [self.array[2], self.array[3]]
+            else:
+                assert key_idx == 2
+                new_array = [self.array[0], self.array[1]]
+
+            new_node = BitmapNode(
+                2, map_bitpos(hash, shift), new_array, mutid)
+            return W_NEWNODE, new_node
+
+        new_array = self.array[:key_idx]
+        new_array.extend(self.array[key_idx + 2:])
+        if mutid and mutid == self.mutid:
+            self.array = new_array
+            self.size -= 2
+            return W_NEWNODE, self
+        else:
+            new_node = CollisionNode(
+                self.size - 2, self.hash, new_array, mutid)
+            return W_NEWNODE, new_node
+
+    def keys(self):
+        for i in range(0, self.size, 2):
+            yield self.array[i]
+
+    def values(self):
+        for i in range(1, self.size, 2):
+            yield self.array[i]
+
+    def items(self):
+        for i in range(0, self.size, 2):
+            yield self.array[i], self.array[i + 1]
+
+    def dump(self, buf, level):  # pragma: no cover
+        pad = '    ' * (level + 1)
+        buf.append(
+            pad + 'CollisionNode(size={} id={:0x}):'.format(
+                self.size, id(self)))
+
+        pad = '    ' * (level + 2)
+        for i in range(0, self.size, 2):
+            key = self.array[i]
+            val = self.array[i + 1]
+
+            buf.append('{}{!r}: {!r}'.format(pad, key, val))
+
+
+class MapKeys:
+
+    def __init__(self, c, m):
+        self.__count = c
+        self.__root = m
+
+    def __len__(self):
+        return self.__count
+
+    def __iter__(self):
+        return iter(self.__root.keys())
+
+
+class MapValues:
+
+    def __init__(self, c, m):
+        self.__count = c
+        self.__root = m
+
+    def __len__(self):
+        return self.__count
+
+    def __iter__(self):
+        return iter(self.__root.values())
+
+
+class MapItems:
+
+    def __init__(self, c, m):
+        self.__count = c
+        self.__root = m
+
+    def __len__(self):
+        return self.__count
+
+    def __iter__(self):
+        return iter(self.__root.items())
+
+
+class Map:
+
+    def __init__(self, col=None, **kw):
+        self.__count = 0
+        self.__root = BitmapNode(0, 0, [], 0)
+        self.__hash = -1
+
+        if isinstance(col, Map):
+            self.__count = col.__count
+            self.__root = col.__root
+            self.__hash = col.__hash
+            col = None
+        elif isinstance(col, MapMutation):
+            raise TypeError('cannot create Maps from MapMutations')
+
+        if col or kw:
+            init = self.update(col, **kw)
+            self.__count = init.__count
+            self.__root = init.__root
+
+    @classmethod
+    def _new(cls, count, root):
+        m = Map.__new__(Map)
+        m.__count = count
+        m.__root = root
+        m.__hash = -1
+        return m
+
+    def __reduce__(self):
+        return (type(self), (dict(self.items()),))
+
+    def __len__(self):
+        return self.__count
+
+    def __eq__(self, other):
+        if not isinstance(other, Map):
+            return NotImplemented
+
+        if len(self) != len(other):
+            return False
+
+        for key, val in self.__root.items():
+            try:
+                oval = other.__root.find(0, map_hash(key), key)
+            except KeyError:
+                return False
+            else:
+                if oval != val:
+                    return False
+
+        return True
+
+    def update(self, col=None, **kw):
+        it = None
+        if col is not None:
+            if hasattr(col, 'items'):
+                it = iter(col.items())
+            else:
+                it = iter(col)
+
+        if it is not None:
+            if kw:
+                it = iter(itertools.chain(it, kw.items()))
+        else:
+            if kw:
+                it = iter(kw.items())
+
+        if it is None:
+
+            return self
+
+        mutid = _mut_id()
+        root = self.__root
+        count = self.__count
+
+        i = 0
+        while True:
+            try:
+                tup = next(it)
+            except StopIteration:
+                break
+
+            try:
+                tup = tuple(tup)
+            except TypeError:
+                raise TypeError(
+                    'cannot convert map update '
+                    'sequence element #{} to a sequence'.format(i)) from None
+            key, val, *r = tup
+            if r:
+                raise ValueError(
+                    'map update sequence element #{} has length '
+                    '{}; 2 is required'.format(i, len(r) + 2))
+
+            root, added = root.assoc(0, map_hash(key), key, val, mutid)
+            if added:
+                count += 1
+
+            i += 1
+
+        return Map._new(count, root)
+
+    def mutate(self):
+        return MapMutation(self.__count, self.__root)
+
+    def set(self, key, val):
+        new_count = self.__count
+        new_root, added = self.__root.assoc(0, map_hash(key), key, val, 0)
+
+        if new_root is self.__root:
+            assert not added
+            return self
+
+        if added:
+            new_count += 1
+
+        return Map._new(new_count, new_root)
+
+    def delete(self, key):
+        res, node = self.__root.without(0, map_hash(key), key, 0)
+        if res is W_EMPTY:
+            return Map()
+        elif res is W_NOT_FOUND:
+            raise KeyError(key)
+        else:
+            return Map._new(self.__count - 1, node)
+
+    def get(self, key, default=None):
+        try:
+            return self.__root.find(0, map_hash(key), key)
+        except KeyError:
+            return default
+
+    def __getitem__(self, key):
+        return self.__root.find(0, map_hash(key), key)
+
+    def __contains__(self, key):
+        try:
+            self.__root.find(0, map_hash(key), key)
+        except KeyError:
+            return False
+        else:
+            return True
+
+    def __iter__(self):
+        yield from self.__root.keys()
+
+    def keys(self):
+        return MapKeys(self.__count, self.__root)
+
+    def values(self):
+        return MapValues(self.__count, self.__root)
+
+    def items(self):
+        return MapItems(self.__count, self.__root)
+
+    def __hash__(self):
+        if self.__hash != -1:
+            return self.__hash
+
+        MAX = sys.maxsize
+        MASK = 2 * MAX + 1
+
+        h = 1927868237 * (self.__count * 2 + 1)
+        h &= MASK
+
+        for key, value in self.__root.items():
+            hx = hash(key)
+            h ^= (hx ^ (hx << 16) ^ 89869747) * 3644798167
+            h &= MASK
+
+            hx = hash(value)
+            h ^= (hx ^ (hx << 16) ^ 89869747) * 3644798167
+            h &= MASK
+
+        h = h * 69069 + 907133923
+        h &= MASK
+
+        if h > MAX:
+            h -= MASK + 1  # pragma: no cover
+        if h == -1:
+            h = 590923713  # pragma: no cover
+
+        self.__hash = h
+        return h
+
+    @reprlib.recursive_repr("{...}")
+    def __repr__(self):
+        items = []
+        for key, val in self.items():
+            items.append("{!r}: {!r}".format(key, val))
+        return '<immutables.Map({{{}}}) at 0x{:0x}>'.format(
+            ', '.join(items), id(self))
+
+    def __dump__(self):  # pragma: no cover
+        buf = []
+        self.__root.dump(buf, 0)
+        return '\n'.join(buf)
+
+    def __class_getitem__(cls, item):
+        return cls
+
+
+class MapMutation:
+
+    def __init__(self, count, root):
+        self.__count = count
+        self.__root = root
+        self.__mutid = _mut_id()
+
+    def set(self, key, val):
+        self[key] = val
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *exc):
+        self.finish()
+        return False
+
+    def __iter__(self):
+        raise TypeError('{} is not iterable'.format(type(self)))
+
+    def __delitem__(self, key):
+        if self.__mutid == 0:
+            raise ValueError('mutation {!r} has been finished'.format(self))
+
+        res, new_root = self.__root.without(
+            0, map_hash(key), key, self.__mutid)
+        if res is W_EMPTY:
+            self.__count = 0
+            self.__root = BitmapNode(0, 0, [], self.__mutid)
+        elif res is W_NOT_FOUND:
+            raise KeyError(key)
+        else:
+            self.__root = new_root
+            self.__count -= 1
+
+    def __setitem__(self, key, val):
+        if self.__mutid == 0:
+            raise ValueError('mutation {!r} has been finished'.format(self))
+
+        self.__root, added = self.__root.assoc(
+            0, map_hash(key), key, val, self.__mutid)
+
+        if added:
+            self.__count += 1
+
+    def pop(self, key, *args):
+        if self.__mutid == 0:
+            raise ValueError('mutation {!r} has been finished'.format(self))
+
+        if len(args) > 1:
+            raise TypeError(
+                'pop() accepts 1 to 2 positional arguments, '
+                'got {}'.format(len(args) + 1))
+        elif len(args) == 1:
+            default = args[0]
+        else:


More information about the pypy-commit mailing list