|
4 | 4 |
|
5 | 5 | import unittest |
6 | 6 |
|
7 | | -from iptest import run_test |
| 7 | +from iptest import run_test, is_cli |
8 | 8 |
|
9 | 9 | class ListCompTest(unittest.TestCase): |
10 | 10 | def test_positive(self): |
@@ -41,4 +41,69 @@ def test_negative(self): |
41 | 41 | self.assertRaises(NameError, lambda: [(x, z) for x in "iron" if z > x for z in "python" ]) |
42 | 42 | self.assertRaises(NameError, lambda: [(i, j) for i in range(10) if j < 'c' for j in ['a', 'b', 'c'] if i % 3 == 0]) |
43 | 43 |
|
| 44 | + def test_ipy3_gh809(self): |
| 45 | + """https://github.com/IronLanguages/ironpython3/issues/809""" |
| 46 | + |
| 47 | + # iterable is evaluated in the outer scope |
| 48 | + self.assertIn('self', [x for x in dir()]) |
| 49 | + |
| 50 | + # this rule applies recursively to nested comprehensions |
| 51 | + self.assertIn('self', [x for x in [y for y in dir()]]) |
| 52 | + self.assertIn('self', [x for x in [y for y in [z for z in dir()]]]) |
| 53 | + |
| 54 | + # this only applies to the first iterable |
| 55 | + # subsequent iterables are evaluated within the comprehension scope |
| 56 | + self.assertEqual([(0, 'x')], [(x, y) for x in range(1) for y in dir() if not y.startswith('.')]) # (filtering out auxiliary variable staring with a dot, used by CPython) |
| 57 | + |
| 58 | + # also subsequent conditions are evaluated within the comprehension scope |
| 59 | + a, b, c, d = range(4) |
| 60 | + self.assertTrue(len(dir()) >= 4) |
| 61 | + self.assertEqual([], [dir() for x in range(1) if len(dir()) >= 4]) |
| 62 | + |
| 63 | + # subsequent iterables introduce local variables after the first iteration |
| 64 | + self.assertEqual([(0, 'x'), (1, 'x'), (1, 'y'), (2, 'x'), (2, 'y')], |
| 65 | + [(x, y) for x in range(3) |
| 66 | + for y in dir() if not y.startswith('.')]) |
| 67 | + self.assertEqual([(0, 'x', 'x'), (0, 'x', 'y'), |
| 68 | + (1, 'x', 'x'), (1, 'x', 'y'), (1, 'x', 'z'), |
| 69 | + (1, 'y', 'x'), (1, 'y', 'y'), (1, 'y', 'z'), |
| 70 | + (1, 'z', 'x'), (1, 'z', 'y'), (1, 'z', 'z')], |
| 71 | + [(x, y, z) for x in range(2) |
| 72 | + for y in dir() if not y.startswith('.') |
| 73 | + for z in dir() if not z.startswith('.')]) |
| 74 | + |
| 75 | + # lambdas create a new scope |
| 76 | + self.assertEqual([], [x for x in (lambda: dir())()]) |
| 77 | + self.assertEqual([[]], [x() for x in [lambda: dir()]]) |
| 78 | + |
| 79 | + # first iterable is captured and subsequent assignments do not change it |
| 80 | + self.maxDiff = None |
| 81 | + x, y, z = range(1), range(2), range(3) |
| 82 | + if is_cli: |
| 83 | + _dir = ['x', 'y'] |
| 84 | + # TODO: should be: _dir = ['x', 'y', 'z'] |
| 85 | + # See: https://github.com/IronLanguages/ironpython3/issues/1132 |
| 86 | + else: |
| 87 | + _dir = ['.0', 'x', 'y', 'z'] # adds implementation-level variable '.0' |
| 88 | + # below, x and first/third y is local, second y and z is from the outer scope |
| 89 | + self.assertEqual([(x, y, z, dir()) for x in y for y in z], |
| 90 | + [(0, 0, range(3), _dir), |
| 91 | + (0, 1, range(3), _dir), |
| 92 | + (0, 2, range(3), _dir), |
| 93 | + (1, 0, range(3), _dir), |
| 94 | + (1, 1, range(3), _dir), |
| 95 | + (1, 2, range(3), _dir)]) |
| 96 | + |
| 97 | + # mixing scopes |
| 98 | + def apply(f, i): return f(i) |
| 99 | + x = 2 |
| 100 | + res = [x for x in apply(lambda i: range(i+x), x)] |
| 101 | + self.assertEqual(res, [0, 1, 2, 3]) |
| 102 | + |
| 103 | + res = [(x, y) for x in apply(lambda i: range(i+x), x) for y in apply(lambda i: range(i+x//2), x)] |
| 104 | + self.assertEqual(res, [(1, 0), (2, 0), (2, 1), (2, 2), (3, 0), (3, 1), (3, 2), (3, 3)]) |
| 105 | + |
| 106 | + res = [x for x in [y for y in apply(lambda i: range(i+x), x)]] |
| 107 | + self.assertEqual(res, [0, 1, 2, 3]) |
| 108 | + |
44 | 109 | run_test(__name__) |
0 commit comments