import doctest
import unittest

doctests = """

Setup

    >>> class AClass:
    ...    def __init__(self):
    ...        self._setitem_name = None
    ...        self._setitem_val = None
    ...        self._delitem_name = None
    ...    def __setitem__(self, name, val):
    ...        self._delitem_name = None
    ...        self._setitem_name = name
    ...        self._setitem_val = val
    ...    def __repr__(self):
    ...        if self._setitem_name is not None:
    ...            return f"A[{self._setitem_name}]={self._setitem_val}"
    ...        elif self._delitem_name is not None:
    ...            return f"delA[{self._delitem_name}]"
    ...    def __getitem__(self, name):
    ...        return ParameterisedA(name)
    ...    def __delitem__(self, name):
    ...        self._setitem_name = None
    ...        self._delitem_name = name
    ...
    >>> class ParameterisedA:
    ...    def __init__(self, name):
    ...        self._name = name
    ...    def __repr__(self):
    ...        return f"A[{self._name}]"
    ...    def __iter__(self):
    ...        for p in self._name:
    ...            yield p
    >>> class B:
    ...    def __iter__(self):
    ...        yield StarredB()
    ...    def __repr__(self):
    ...        return "B"
    >>> class StarredB:
    ...    def __repr__(self):
    ...        return "StarredB"
    >>> A = AClass()
    >>> b = B()

Slices that are supposed to work, starring our custom B class

    >>> A[*b]
    A[(StarredB,)]
    >>> A[*b] = 1; A
    A[(StarredB,)]=1
    >>> del A[*b]; A
    delA[(StarredB,)]

    >>> A[*b, *b]
    A[(StarredB, StarredB)]
    >>> A[*b, *b] = 1; A
    A[(StarredB, StarredB)]=1
    >>> del A[*b, *b]; A
    delA[(StarredB, StarredB)]

    >>> A[b, *b]
    A[(B, StarredB)]
    >>> A[b, *b] = 1; A
    A[(B, StarredB)]=1
    >>> del A[b, *b]; A
    delA[(B, StarredB)]

    >>> A[*b, b]
    A[(StarredB, B)]
    >>> A[*b, b] = 1; A
    A[(StarredB, B)]=1
    >>> del A[*b, b]; A
    delA[(StarredB, B)]

    >>> A[b, b, *b]
    A[(B, B, StarredB)]
    >>> A[b, b, *b] = 1; A
    A[(B, B, StarredB)]=1
    >>> del A[b, b, *b]; A
    delA[(B, B, StarredB)]

    >>> A[*b, b, b]
    A[(StarredB, B, B)]
    >>> A[*b, b, b] = 1; A
    A[(StarredB, B, B)]=1
    >>> del A[*b, b, b]; A
    delA[(StarredB, B, B)]

    >>> A[b, *b, b]
    A[(B, StarredB, B)]
    >>> A[b, *b, b] = 1; A
    A[(B, StarredB, B)]=1
    >>> del A[b, *b, b]; A
    delA[(B, StarredB, B)]

    >>> A[b, b, *b, b]
    A[(B, B, StarredB, B)]
    >>> A[b, b, *b, b] = 1; A
    A[(B, B, StarredB, B)]=1
    >>> del A[b, b, *b, b]; A
    delA[(B, B, StarredB, B)]

    >>> A[b, *b, b, b]
    A[(B, StarredB, B, B)]
    >>> A[b, *b, b, b] = 1; A
    A[(B, StarredB, B, B)]=1
    >>> del A[b, *b, b, b]; A
    delA[(B, StarredB, B, B)]

    >>> A[A[b, *b, b]]
    A[A[(B, StarredB, B)]]
    >>> A[A[b, *b, b]] = 1; A
    A[A[(B, StarredB, B)]]=1
    >>> del A[A[b, *b, b]]; A
    delA[A[(B, StarredB, B)]]

    >>> A[*A[b, *b, b]]
    A[(B, StarredB, B)]
    >>> A[*A[b, *b, b]] = 1; A
    A[(B, StarredB, B)]=1
    >>> del A[*A[b, *b, b]]; A
    delA[(B, StarredB, B)]

    >>> A[b, ...]
    A[(B, Ellipsis)]
    >>> A[b, ...] = 1; A
    A[(B, Ellipsis)]=1
    >>> del A[b, ...]; A
    delA[(B, Ellipsis)]

    >>> A[*A[b, ...]]
    A[(B, Ellipsis)]
    >>> A[*A[b, ...]] = 1; A
    A[(B, Ellipsis)]=1
    >>> del A[*A[b, ...]]; A
    delA[(B, Ellipsis)]

Slices that are supposed to work, starring a list

    >>> l = [1, 2, 3]

    >>> A[*l]
    A[(1, 2, 3)]
    >>> A[*l] = 1; A
    A[(1, 2, 3)]=1
    >>> del A[*l]; A
    delA[(1, 2, 3)]

    >>> A[*l, 4]
    A[(1, 2, 3, 4)]
    >>> A[*l, 4] = 1; A
    A[(1, 2, 3, 4)]=1
    >>> del A[*l, 4]; A
    delA[(1, 2, 3, 4)]

    >>> A[0, *l]
    A[(0, 1, 2, 3)]
    >>> A[0, *l] = 1; A
    A[(0, 1, 2, 3)]=1
    >>> del A[0, *l]; A
    delA[(0, 1, 2, 3)]

    >>> A[1:2, *l]
    A[(slice(1, 2, None), 1, 2, 3)]
    >>> A[1:2, *l] = 1; A
    A[(slice(1, 2, None), 1, 2, 3)]=1
    >>> del A[1:2, *l]; A
    delA[(slice(1, 2, None), 1, 2, 3)]

    >>> repr(A[1:2, *l]) == repr(A[1:2, 1, 2, 3])
    True

Slices that are supposed to work, starring a tuple

    >>> t = (1, 2, 3)

    >>> A[*t]
    A[(1, 2, 3)]
    >>> A[*t] = 1; A
    A[(1, 2, 3)]=1
    >>> del A[*t]; A
    delA[(1, 2, 3)]

    >>> A[*t, 4]
    A[(1, 2, 3, 4)]
    >>> A[*t, 4] = 1; A
    A[(1, 2, 3, 4)]=1
    >>> del A[*t, 4]; A
    delA[(1, 2, 3, 4)]

    >>> A[0, *t]
    A[(0, 1, 2, 3)]
    >>> A[0, *t] = 1; A
    A[(0, 1, 2, 3)]=1
    >>> del A[0, *t]; A
    delA[(0, 1, 2, 3)]

    >>> A[1:2, *t]
    A[(slice(1, 2, None), 1, 2, 3)]
    >>> A[1:2, *t] = 1; A
    A[(slice(1, 2, None), 1, 2, 3)]=1
    >>> del A[1:2, *t]; A
    delA[(slice(1, 2, None), 1, 2, 3)]

    >>> repr(A[1:2, *t]) == repr(A[1:2, 1, 2, 3])
    True

Starring an expression (rather than a name) in a slice

    >>> def returns_list():
    ...     return [1, 2, 3]

    >>> A[returns_list()]
    A[[1, 2, 3]]
    >>> A[returns_list()] = 1; A
    A[[1, 2, 3]]=1
    >>> del A[returns_list()]; A
    delA[[1, 2, 3]]

    >>> A[returns_list(), 4]
    A[([1, 2, 3], 4)]
    >>> A[returns_list(), 4] = 1; A
    A[([1, 2, 3], 4)]=1
    >>> del A[returns_list(), 4]; A
    delA[([1, 2, 3], 4)]

    >>> A[*returns_list()]
    A[(1, 2, 3)]
    >>> A[*returns_list()] = 1; A
    A[(1, 2, 3)]=1
    >>> del A[*returns_list()]; A
    delA[(1, 2, 3)]

    >>> A[*returns_list(), 4]
    A[(1, 2, 3, 4)]
    >>> A[*returns_list(), 4] = 1; A
    A[(1, 2, 3, 4)]=1
    >>> del A[*returns_list(), 4]; A
    delA[(1, 2, 3, 4)]

    >>> A[0, *returns_list()]
    A[(0, 1, 2, 3)]
    >>> A[0, *returns_list()] = 1; A
    A[(0, 1, 2, 3)]=1
    >>> del A[0, *returns_list()]; A
    delA[(0, 1, 2, 3)]

    >>> A[*returns_list(), *returns_list()]
    A[(1, 2, 3, 1, 2, 3)]
    >>> A[*returns_list(), *returns_list()] = 1; A
    A[(1, 2, 3, 1, 2, 3)]=1
    >>> del A[*returns_list(), *returns_list()]; A
    delA[(1, 2, 3, 1, 2, 3)]

Using both a starred object and a start:stop in a slice
(See also tests in test_syntax confirming that starring *inside* a start:stop
is *not* valid syntax.)

    >>> A[1:2, *b]
    A[(slice(1, 2, None), StarredB)]
    >>> A[*b, 1:2]
    A[(StarredB, slice(1, 2, None))]
    >>> A[1:2, *b, 1:2]
    A[(slice(1, 2, None), StarredB, slice(1, 2, None))]
    >>> A[*b, 1:2, *b]
    A[(StarredB, slice(1, 2, None), StarredB)]

    >>> A[1:, *b]
    A[(slice(1, None, None), StarredB)]
    >>> A[*b, 1:]
    A[(StarredB, slice(1, None, None))]
    >>> A[1:, *b, 1:]
    A[(slice(1, None, None), StarredB, slice(1, None, None))]
    >>> A[*b, 1:, *b]
    A[(StarredB, slice(1, None, None), StarredB)]

    >>> A[:1, *b]
    A[(slice(None, 1, None), StarredB)]
    >>> A[*b, :1]
    A[(StarredB, slice(None, 1, None))]
    >>> A[:1, *b, :1]
    A[(slice(None, 1, None), StarredB, slice(None, 1, None))]
    >>> A[*b, :1, *b]
    A[(StarredB, slice(None, 1, None), StarredB)]

    >>> A[:, *b]
    A[(slice(None, None, None), StarredB)]
    >>> A[*b, :]
    A[(StarredB, slice(None, None, None))]
    >>> A[:, *b, :]
    A[(slice(None, None, None), StarredB, slice(None, None, None))]
    >>> A[*b, :, *b]
    A[(StarredB, slice(None, None, None), StarredB)]

*args annotated as starred expression

    >>> def f1(*args: *b): pass
    >>> f1.__annotations__
    {'args': StarredB}

    >>> def f2(*args: *b, arg1): pass
    >>> f2.__annotations__
    {'args': StarredB}

    >>> def f3(*args: *b, arg1: int): pass
    >>> f3.__annotations__
    {'args': StarredB, 'arg1': <class 'int'>}

    >>> def f4(*args: *b, arg1: int = 2): pass
    >>> f4.__annotations__
    {'args': StarredB, 'arg1': <class 'int'>}

    >>> def f5(*args: *b = (1,)): pass
    Traceback (most recent call last):
        ...
    SyntaxError: invalid syntax
"""

__test__ = {'doctests' : doctests}

def load_tests(loader, tests, pattern):
    tests.addTest(doctest.DocTestSuite())
    return tests


if __name__ == "__main__":
    unittest.main()
