# Tests for extended unpacking, starred expressions.

import doctest
import unittest


doctests = """

Unpack tuple

    >>> t = (1, 2, 3)
    >>> a, *b, c = t
    >>> a == 1 and b == [2] and c == 3
    True

Unpack list

    >>> l = [4, 5, 6]
    >>> a, *b = l
    >>> a == 4 and b == [5, 6]
    True

Unpack implied tuple

    >>> *a, = 7, 8, 9
    >>> a == [7, 8, 9]
    True

Unpack nested implied tuple

    >>> [*[*a]] = [[7,8,9]]
    >>> a == [[7,8,9]]
    True

Unpack string... fun!

    >>> a, *b = 'one'
    >>> a == 'o' and b == ['n', 'e']
    True

Unpack long sequence

    >>> a, b, c, *d, e, f, g = range(10)
    >>> (a, b, c, d, e, f, g) == (0, 1, 2, [3, 4, 5, 6], 7, 8, 9)
    True

Unpack short sequence

    >>> a, *b, c = (1, 2)
    >>> a == 1 and c == 2 and b == []
    True

Unpack generic sequence

    >>> class Seq:
    ...     def __getitem__(self, i):
    ...         if i >= 0 and i < 3: return i
    ...         raise IndexError
    ...
    >>> a, *b = Seq()
    >>> a == 0 and b == [1, 2]
    True

Unpack in for statement

    >>> for a, *b, c in [(1,2,3), (4,5,6,7)]:
    ...     print(a, b, c)
    ...
    1 [2] 3
    4 [5, 6] 7

Unpack in list

    >>> [a, *b, c] = range(5)
    >>> a == 0 and b == [1, 2, 3] and c == 4
    True

Multiple targets

    >>> a, *b, c = *d, e = range(5)
    >>> a == 0 and b == [1, 2, 3] and c == 4 and d == [0, 1, 2, 3] and e == 4
    True

Assignment unpacking

    >>> a, b, *c = range(5)
    >>> a, b, c
    (0, 1, [2, 3, 4])
    >>> *a, b, c = a, b, *c
    >>> a, b, c
    ([0, 1, 2], 3, 4)

Set display element unpacking

    >>> a = [1, 2, 3]
    >>> sorted({1, *a, 0, 4})
    [0, 1, 2, 3, 4]

    >>> {1, *1, 0, 4}
    Traceback (most recent call last):
      ...
    TypeError: 'int' object is not iterable

Dict display element unpacking

    >>> kwds = {'z': 0, 'w': 12}
    >>> sorted({'x': 1, 'y': 2, **kwds}.items())
    [('w', 12), ('x', 1), ('y', 2), ('z', 0)]

    >>> sorted({**{'x': 1}, 'y': 2, **{'z': 3}}.items())
    [('x', 1), ('y', 2), ('z', 3)]

    >>> sorted({**{'x': 1}, 'y': 2, **{'x': 3}}.items())
    [('x', 3), ('y', 2)]

    >>> sorted({**{'x': 1}, **{'x': 3}, 'x': 4}.items())
    [('x', 4)]

    >>> {**{}}
    {}

    >>> a = {}
    >>> {**a}[0] = 1
    >>> a
    {}

    >>> {**1}
    Traceback (most recent call last):
    ...
    TypeError: 'int' object is not a mapping

    >>> {**[]}
    Traceback (most recent call last):
    ...
    TypeError: 'list' object is not a mapping

    >>> class OnlyKeys:
    ...     def keys(self):
    ...         return ['key']
    >>> {**OnlyKeys()}
    Traceback (most recent call last):
      ...
    TypeError: 'OnlyKeys' object is not subscriptable

    >>> class BrokenKeys:
    ...     def keys(self):
    ...         return 1
    >>> {**BrokenKeys()}
    Traceback (most recent call last):
      ...
    TypeError: test.test_unpack_ex.BrokenKeys.keys() must return an iterable, not int

    >>> len(eval("{" + ", ".join("**{{{}: {}}}".format(i, i)
    ...                          for i in range(1000)) + "}"))
    1000

    >>> {0:1, **{0:2}, 0:3, 0:4}
    {0: 4}

Comprehension element unpacking

    >>> a, b, c = [0, 1, 2], 3, 4
    >>> [*a, b, c]
    [0, 1, 2, 3, 4]

    >>> l = [a, (3, 4), {5}, {6: None}, (i for i in range(7, 10))]
    >>> [*item for item in l]
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

    >>> [*[0, 1] for i in range(5)]
    [0, 1, 0, 1, 0, 1, 0, 1, 0, 1]

    >>> [*'a' for i in range(5)]
    ['a', 'a', 'a', 'a', 'a']

    >>> [*[] for i in range(10)]
    []

    >>> [*(x*2) for x in [[1, 2, 3], [], 'cat']]
    [1, 2, 3, 1, 2, 3, 'c', 'a', 't', 'c', 'a', 't']

    >>> {**{} for a in [1]}
    {}

    >>> {**{7: i} for i in range(10)}
    {7: 9}

    >>> dicts = [{1: 2}, {3: 4}, {5: 6, 7: 8}, {}, {9: 10}, {1: 0}]
    >>> {**d for d in dicts}
    {1: 0, 3: 4, 5: 6, 7: 8, 9: 10}

    >>> gen = (*(0, 1) for i in range(5))
    >>> next(gen)
    0
    >>> list(gen)
    [1, 0, 1, 0, 1, 0, 1, 0, 1]

Comprehension unpacking with conditionals and double loops

    >>> [*[i, i+1] for i in range(5) if i % 2 == 0]
    [0, 1, 2, 3, 4, 5]

    >>> [*y for x in [[[0], [1, 2, 3], [], [4, 5]], [[6, 7]]] for y in x]
    [0, 1, 2, 3, 4, 5, 6, 7]

    >>> [*y for x in [[[0], [1, 2, 3], [], [4, 5]], [[6, 7]]] for y in x if y and y[0]>0]
    [1, 2, 3, 4, 5, 6, 7]

    >>> dicts = [{1: 2}, {3: 4}, {5: 6, 7: 8}, {}, {9: 10}, {1: 0}]
    >>> {**d for d in dicts if len(d) != 2}
    {1: 0, 3: 4, 9: 10}

Scoping of assignment expressions in comprehensions

    >>> [*((y := i**2), 2*y) for i in range(4)]
    [0, 0, 1, 2, 4, 8, 9, 18]
    >>> y
    9

    >>> [*(y := [i, i+1, i+2]) for i in range(4)]
    [0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5]
    >>> y
    [3, 4, 5]

    >>> g = (*(z := [i, i+1, i+2]) for i in range(4))
    >>> z
    Traceback (most recent call last):
    ...
    NameError: name 'z' is not defined
    >>> next(g)
    0
    >>> z
    [0, 1, 2]
    >>> next(g)
    1
    >>> z
    [0, 1, 2]
    >>> next(g)
    2
    >>> z
    [0, 1, 2]
    >>> next(g)
    1
    >>> z
    [1, 2, 3]

    >>> x = [1, 2, 3]
    >>> y = [4, 5, 6]
    >>> def f(*args):
    ...     print(args)

    >>> f(*x if x else y)
    (1, 2, 3)


Malformed comperehension element unpacking

    >>> [*x for x in [1, 2, 3]]
    Traceback (most recent call last):
    ...
    [*x for x in [1, 2, 3]]
     ^^
    TypeError: Value after * must be an iterable, not int


Error messages for specific failure modes of unpacking

    >>> [*x if x else y for x in z]
    Traceback (most recent call last):
    ...
    [*x if x else y for x in z]
     ^^^^^^^^^^^^^^
    SyntaxError: invalid starred expression. Did you forget to wrap the conditional expression in parentheses?

    >>> [*x if x else y]
    Traceback (most recent call last):
    ...
    [*x if x else y]
     ^^^^^^^^^^^^^^
    SyntaxError: invalid starred expression. Did you forget to wrap the conditional expression in parentheses?

    >>> [x if x else *y for x in z]
    Traceback (most recent call last):
    ...
    [x if x else *y for x in z]
                 ^
    SyntaxError: cannot unpack only part of a conditional expression

    >>> [x if x else *y]
    Traceback (most recent call last):
    ...
    [x if x else *y]
                 ^
    SyntaxError: cannot unpack only part of a conditional expression

    >>> {**x if x else y}
    Traceback (most recent call last):
    ...
    {**x if x else y}
     ^^^^^^^^^^^^^^^^
    SyntaxError: invalid double starred expression. Did you forget to wrap the conditional expression in parentheses?
    >>> {x if x else **y}
    Traceback (most recent call last):
    ...
    {x if x else **y}
                 ^^
    SyntaxError: cannot use dict unpacking on only part of a conditional expression

    >>> [**x for x in [{1: 2}]]
    Traceback (most recent call last):
    ...
    [**x for x in [{1: 2}]]
     ^^^
    SyntaxError: cannot use dict unpacking in list comprehension

    >>> (**x for x in [{1:2}])
    Traceback (most recent call last):
    ...
        (**x for x in [{1:2}])
         ^^^
    SyntaxError: cannot use dict unpacking in generator expression

    >>> dict(**x for x in [{1:2}])
    Traceback (most recent call last):
    ...
        dict(**x for x in [{1:2}])
             ^^^
    SyntaxError: cannot use dict unpacking in generator expression

    >>> {*a: b for a, b in {1: 2}.items()}
    Traceback (most recent call last):
    ...
    {*a: b for a, b in {1: 2}.items()}
     ^^
    SyntaxError: cannot use a starred expression in a dictionary key

    >>> {**a: b for a, b in {1: 2}.items()}
    Traceback (most recent call last):
    ...
    {**a: b for a, b in {1: 2}.items()}
     ^^^
    SyntaxError: cannot use dict unpacking in a dictionary key

    >>> {a: *b for a, b in {1: 2}.items()}
    Traceback (most recent call last):
    ...
    {a: *b for a, b in {1: 2}.items()}
        ^^
    SyntaxError: cannot use a starred expression in a dictionary value

    >>> {a: **b for a, b in {1: 2}.items()}
    Traceback (most recent call last):
    ...
    {a: **b for a, b in {1: 2}.items()}
        ^^^
    SyntaxError: cannot use dict unpacking in a dictionary value


# Generator expression in function arguments

    >>> list(*x for x in (range(5) for i in range(3)))
    [0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4]

    >>> def f(arg):
    ...     print(type(arg), list(arg), list(arg))
    >>> f(*x for x in [[1,2,3]])
    <class 'generator'> [1, 2, 3] []

Iterable argument unpacking

    >>> print(*[1], *[2], 3)
    1 2 3

Make sure that they don't corrupt the passed-in dicts.

    >>> def f(x, y):
    ...     print(x, y)
    ...
    >>> original_dict = {'x': 1}
    >>> f(**original_dict, y=2)
    1 2
    >>> original_dict
    {'x': 1}

Now for some failures

Make sure the raised errors are right for keyword argument unpackings

    >>> from collections.abc import MutableMapping
    >>> class CrazyDict(MutableMapping):
    ...     def __init__(self):
    ...         self.d = {}
    ...
    ...     def __iter__(self):
    ...         for x in self.d.__iter__():
    ...             if x == 'c':
    ...                 self.d['z'] = 10
    ...             yield x
    ...
    ...     def __getitem__(self, k):
    ...         return self.d[k]
    ...
    ...     def __len__(self):
    ...         return len(self.d)
    ...
    ...     def __setitem__(self, k, v):
    ...         self.d[k] = v
    ...
    ...     def __delitem__(self, k):
    ...         del self.d[k]
    ...
    >>> d = CrazyDict()
    >>> d.d = {chr(ord('a') + x): x for x in range(5)}
    >>> e = {**d}
    Traceback (most recent call last):
    ...
    RuntimeError: dictionary changed size during iteration

    >>> d.d = {chr(ord('a') + x): x for x in range(5)}
    >>> def f(**kwargs): print(kwargs)
    >>> f(**d)
    Traceback (most recent call last):
    ...
    RuntimeError: dictionary changed size during iteration

Overridden parameters

    >>> f(x=5, **{'x': 3}, y=2)
    Traceback (most recent call last):
      ...
    TypeError: test.test_unpack_ex.f() got multiple values for keyword argument 'x'

    >>> f(**{'x': 3}, x=5, y=2)
    Traceback (most recent call last):
      ...
    TypeError: test.test_unpack_ex.f() got multiple values for keyword argument 'x'

    >>> f(**{'x': 3}, **{'x': 5}, y=2)
    Traceback (most recent call last):
      ...
    TypeError: test.test_unpack_ex.f() got multiple values for keyword argument 'x'

    >>> f(x=5, **{'x': 3}, **{'x': 2})
    Traceback (most recent call last):
      ...
    TypeError: test.test_unpack_ex.f() got multiple values for keyword argument 'x'

    >>> f(**{1: 3}, **{1: 5})
    Traceback (most recent call last):
      ...
    TypeError: test.test_unpack_ex.f() got multiple values for keyword argument '1'

Unpacking non-sequence

    >>> a, *b = 7
    Traceback (most recent call last):
      ...
    TypeError: cannot unpack non-iterable int object

Unpacking sequence too short

    >>> a, *b, c, d, e = Seq()
    Traceback (most recent call last):
      ...
    ValueError: not enough values to unpack (expected at least 4, got 3)

Unpacking sequence too short and target appears last

    >>> a, b, c, d, *e = Seq()
    Traceback (most recent call last):
      ...
    ValueError: not enough values to unpack (expected at least 4, got 3)

Unpacking a sequence where the test for too long raises a different kind of
error

    >>> class BozoError(Exception):
    ...     pass
    ...
    >>> class BadSeq:
    ...     def __getitem__(self, i):
    ...         if i >= 0 and i < 3:
    ...             return i
    ...         elif i == 3:
    ...             raise BozoError
    ...         else:
    ...             raise IndexError
    ...

Trigger code while not expecting an IndexError (unpack sequence too long, wrong
error)

    >>> a, *b, c, d, e = BadSeq()
    Traceback (most recent call last):
      ...
    test.test_unpack_ex.BozoError

Now some general starred expressions (all fail).

    >>> a, *b, c, *d, e = range(10) # doctest:+ELLIPSIS
    Traceback (most recent call last):
      ...
    SyntaxError: multiple starred expressions in assignment

    >>> [*b, *c] = range(10) # doctest:+ELLIPSIS
    Traceback (most recent call last):
      ...
    SyntaxError: multiple starred expressions in assignment

    >>> a,*b,*c,*d = range(4) # doctest:+ELLIPSIS
    Traceback (most recent call last):
      ...
    SyntaxError: multiple starred expressions in assignment

    >>> *a = range(10) # doctest:+ELLIPSIS
    Traceback (most recent call last):
      ...
    SyntaxError: starred assignment target must be in a list or tuple

    >>> *a # doctest:+ELLIPSIS
    Traceback (most recent call last):
      ...
    SyntaxError: can't use starred expression here

    >>> *1 # doctest:+ELLIPSIS
    Traceback (most recent call last):
      ...
    SyntaxError: can't use starred expression here

    >>> x = *a # doctest:+ELLIPSIS
    Traceback (most recent call last):
      ...
    SyntaxError: can't use starred expression here

    >>> (*x),y = 1, 2 # doctest:+ELLIPSIS
    Traceback (most recent call last):
      ...
    SyntaxError: cannot use starred expression here

    >>> (((*x))),y = 1, 2 # doctest:+ELLIPSIS
    Traceback (most recent call last):
      ...
    SyntaxError: cannot use starred expression here

    >>> z,(*x),y = 1, 2, 4 # doctest:+ELLIPSIS
    Traceback (most recent call last):
      ...
    SyntaxError: cannot use starred expression here

    >>> z,(*x) = 1, 2 # doctest:+ELLIPSIS
    Traceback (most recent call last):
      ...
    SyntaxError: cannot use starred expression here

    >>> ((*x),y) = 1, 2 # doctest:+ELLIPSIS
    Traceback (most recent call last):
      ...
    SyntaxError: cannot use starred expression here

Some size constraints (all fail.)

    >>> s = ", ".join("a%d" % i for i in range(1<<8)) + ", *rest = range((1<<8) + 1)"
    >>> compile(s, 'test', 'exec') # doctest:+ELLIPSIS
    Traceback (most recent call last):
     ...
    SyntaxError: too many expressions in star-unpacking assignment

    >>> s = ", ".join("a%d" % i for i in range((1<<8) + 1)) + ", *rest = range((1<<8) + 2)"
    >>> compile(s, 'test', 'exec') # doctest:+ELLIPSIS
    Traceback (most recent call last):
     ...
    SyntaxError: too many expressions in star-unpacking assignment

(there is an additional limit, on the number of expressions after the
'*rest', but it's 1<<24 and testing it takes too much memory.)

"""

def test_errors_in_iter():
    """
    >>> class A:
    ...     def __iter__(self):
    ...         raise exc
    ...
    >>> exc = ZeroDivisionError('some error')
    >>> [*A()]
    Traceback (most recent call last):
      ...
    ZeroDivisionError: some error

    >>> {*A()}
    Traceback (most recent call last):
      ...
    ZeroDivisionError: some error

    >>> exc = AttributeError('some error')
    >>> [*A()]
    Traceback (most recent call last):
      ...
    AttributeError: some error

    >>> {*A()}
    Traceback (most recent call last):
      ...
    AttributeError: some error

    >>> exc = TypeError('some error')
    >>> [*A()]
    Traceback (most recent call last):
      ...
    TypeError: some error

    >>> {*A()}
    Traceback (most recent call last):
      ...
    TypeError: some error
    """

def test_errors_in_next():
    """
    >>> class I:
    ...     def __iter__(self):
    ...         return self
    ...     def __next__(self):
    ...         raise exc
    ...
    >>> class A:
    ...     def __iter__(self):
    ...         return I()
    ...

    >>> exc = ZeroDivisionError('some error')
    >>> [*A()]
    Traceback (most recent call last):
      ...
    ZeroDivisionError: some error

    >>> {*A()}
    Traceback (most recent call last):
      ...
    ZeroDivisionError: some error

    >>> exc = AttributeError('some error')
    >>> [*A()]
    Traceback (most recent call last):
      ...
    AttributeError: some error

    >>> {*A()}
    Traceback (most recent call last):
      ...
    AttributeError: some error

    >>> exc = TypeError('some error')
    >>> [*A()]
    Traceback (most recent call last):
      ...
    TypeError: some error

    >>> {*A()}
    Traceback (most recent call last):
      ...
    TypeError: some error
    """

def test_errors_in_keys():
    """
    >>> class D:
    ...     def keys(self):
    ...         raise exc
    ...
    >>> exc = ZeroDivisionError('some error')
    >>> {**D()}
    Traceback (most recent call last):
      ...
    ZeroDivisionError: some error

    >>> exc = AttributeError('some error')
    >>> {**D()}
    Traceback (most recent call last):
      ...
    AttributeError: some error

    >>> exc = KeyError('some error')
    >>> {**D()}
    Traceback (most recent call last):
      ...
    KeyError: 'some error'
    """

def test_errors_in_keys_next():
    """
    >>> class I:
    ...     def __iter__(self):
    ...         return self
    ...     def __next__(self):
    ...         raise exc
    ...
    >>> class D:
    ...     def keys(self):
    ...         return I()
    ...
    >>> exc = ZeroDivisionError('some error')
    >>> {**D()}
    Traceback (most recent call last):
      ...
    ZeroDivisionError: some error

    >>> exc = AttributeError('some error')
    >>> {**D()}
    Traceback (most recent call last):
      ...
    AttributeError: some error

    >>> exc = KeyError('some error')
    >>> {**D()}
    Traceback (most recent call last):
      ...
    KeyError: 'some error'
    """

def test_errors_in_getitem():
    """
    >>> class D:
    ...     def keys(self):
    ...         return ['key']
    ...     def __getitem__(self, key):
    ...         raise exc
    ...
    >>> exc = ZeroDivisionError('some error')
    >>> {**D()}
    Traceback (most recent call last):
      ...
    ZeroDivisionError: some error

    >>> exc = AttributeError('some error')
    >>> {**D()}
    Traceback (most recent call last):
      ...
    AttributeError: some error

    >>> exc = KeyError('some error')
    >>> {**D()}
    Traceback (most recent call last):
      ...
    KeyError: 'some error'
    """

__test__ = {'doctests' : doctests}

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


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