
import cython

class Unhashable(object):
    def __hash__(self):
        raise TypeError('I am not hashable')

class Hashable(object):
    def __hash__(self):
        return 1
    def __eq__(self, other):
        return isinstance(other, Hashable)

class CountedHashable(object):
    def __init__(self):
        self.hash_count = 0
        self.eq_count = 0
    def __hash__(self):
        self.hash_count += 1
        return 42
    def __eq__(self, other):
        self.eq_count += 1
        return id(self) == id(other)

@cython.test_fail_if_path_exists('//AttributeNode')
@cython.test_assert_path_exists('//PythonCapiCallNode')
@cython.locals(d=dict)
def setdefault1(d, key):
    """
    >>> d = {}
    >>> setdefault1(d, 1)
    >>> len(d)
    1
    >>> setdefault1(d, 1)
    >>> len(d)
    1
    >>> d[1]
    >>> setdefault1(d, Unhashable())  # doctest: +ELLIPSIS
    Traceback (most recent call last):
    TypeError: ...I am not hashable...
    >>> len(d)
    1
    >>> h1 = setdefault1(d, Hashable())
    >>> len(d)
    2
    >>> h2 = setdefault1(d, Hashable())
    >>> len(d)
    2
    >>> d[Hashable()]

    # CPython's behaviour depends on version and py_debug setting, so just compare to it
    >>> py_hashed1 = CountedHashable()
    >>> y = {py_hashed1: 5}
    >>> py_hashed2 = CountedHashable()
    >>> y.setdefault(py_hashed2)

    >>> cy_hashed1 = CountedHashable()
    >>> y = {cy_hashed1: 5}
    >>> cy_hashed2 = CountedHashable()
    >>> setdefault1(y, cy_hashed2)
    >>> py_hashed1.hash_count - cy_hashed1.hash_count
    0
    >>> py_hashed2.hash_count - cy_hashed2.hash_count
    0
    >>> (py_hashed1.eq_count + py_hashed2.eq_count) - (cy_hashed1.eq_count + cy_hashed2.eq_count)
    0
    """
    return d.setdefault(key)

@cython.test_fail_if_path_exists('//AttributeNode')
@cython.test_assert_path_exists('//PythonCapiCallNode')
@cython.locals(d=dict)
def setdefault2(d, key, value):
    """
    >>> d = {}
    >>> setdefault2(d, 1, 2)
    2
    >>> len(d)
    1
    >>> setdefault2(d, 1, 2)
    2
    >>> len(d)
    1
    >>> l = setdefault2(d, 2, [])
    >>> len(d)
    2
    >>> l.append(1)
    >>> setdefault2(d, 2, [])
    [1]
    >>> len(d)
    2
    >>> setdefault2(d, Unhashable(), 1)  # doctest: +ELLIPSIS
    Traceback (most recent call last):
    TypeError: ...I am not hashable...
    >>> h1 = setdefault2(d, Hashable(), 55)
    >>> len(d)
    3
    >>> h2 = setdefault2(d, Hashable(), 66)
    >>> len(d)
    3
    >>> d[Hashable()]
    55

    # CPython's behaviour depends on version and py_debug setting, so just compare to it
    >>> py_hashed1 = CountedHashable()
    >>> y = {py_hashed1: 5}
    >>> py_hashed2 = CountedHashable()
    >>> y.setdefault(py_hashed2, [])
    []

    >>> cy_hashed1 = CountedHashable()
    >>> y = {cy_hashed1: 5}
    >>> cy_hashed2 = CountedHashable()
    >>> setdefault2(y, cy_hashed2, [])
    []
    >>> py_hashed1.hash_count - cy_hashed1.hash_count
    0
    >>> py_hashed2.hash_count - cy_hashed2.hash_count
    0
    >>> (py_hashed1.eq_count + py_hashed2.eq_count) - (cy_hashed1.eq_count + cy_hashed2.eq_count)
    0
    """
    return d.setdefault(key, value)
