aboutsummaryrefslogtreecommitdiff
blob: 7a53a76c1b7a7070a04a4ca9131926da38176c8a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
## ----------------------------------------------------------------------------
## dict strategy (see dictmultiobject.py)

from rpython.rlib import rerased
from rpython.rlib.debug import mark_dict_non_null
from pypy.objspace.std.dictmultiobject import (AbstractTypedStrategy,
                                               DictStrategy,
                                               create_iterator_classes)


# this strategy is selected by EmptyDictStrategy.switch_to_correct_strategy
class IdentityDictStrategy(AbstractTypedStrategy, DictStrategy):
    """
    Strategy for custom instances which compares by identity (i.e., the
    default unless you override __hash__, __eq__ or __cmp__).  The storage is
    just a normal RPython dict, which has already the correct by-identity
    semantics.

    Note that at a first sight, you might have problems if you mutate the
    class of an object which is already inside an identitydict.  Consider this
    example::

    class X(object):
        pass
    d = {x(): 1}
    X.__eq__ = ...
    d[y] # might trigger a call to __eq__?

    We want to be sure that x.__eq__ is called in the same cases as in
    CPython.  However, as long as the strategy is IdentityDictStrategy, the
    __eq__ will never be called.

    It turns out that it's not a problem.  In CPython (and in PyPy without
    this strategy), the __eq__ is called if ``hash(y) == hash(x)`` and ``x is
    not y``.  Note that hash(x) is computed at the time when we insert x in
    the dict, not at the time we lookup y.

    Now, how can hash(y) == hash(x)?  There are two possibilities:

      1. we write a custom __hash__ for the class of y, thus making it a not
        "compares by reference" type

      2. the class of y is "compares by reference" type, and by chance the
         hash is the same as x

    In the first case, the getitem immediately notice that y is not of the
    right type, and switches the strategy to ObjectDictStrategy, then the
    lookup works as usual.

    The second case is completely non-deterministic, even in CPython.
    Depending on the phase of the moon, you might call the __eq__ or not, so
    it is perfectly fine to *never* call it.  Morever, in practice with the
    minimark GC we never have two live objects with the same hash, so it would
    never happen anyway.
    """

    erase, unerase = rerased.new_erasing_pair("identitydict")
    erase = staticmethod(erase)
    unerase = staticmethod(unerase)

    def wrap(self, unwrapped):
        return unwrapped

    def unwrap(self, wrapped):
        return wrapped

    def get_empty_storage(self):
        d = {}
        mark_dict_non_null(d)
        return self.erase(d)

    def is_correct_type(self, w_obj):
        w_type = self.space.type(w_obj)
        return w_type.compares_by_identity()

    def _never_equal_to(self, w_lookup_type):
        return False

    def w_keys(self, w_dict):
        return self.space.newlist(self.unerase(w_dict.dstorage).keys())

create_iterator_classes(IdentityDictStrategy)