Skip to content

Commit 64fddc4

Browse files
serhiy-storchakaambv
authored andcommitted
bpo-33475: Fix and improve converting annotations to strings. (pythonGH-6774)
1 parent d852142 commit 64fddc4

File tree

5 files changed

+358
-603
lines changed

5 files changed

+358
-603
lines changed

Include/ast.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ PyAPI_FUNC(mod_ty) PyAST_FromNodeObject(
1919
#ifndef Py_LIMITED_API
2020

2121
/* _PyAST_ExprAsUnicode is defined in ast_unparse.c */
22-
PyAPI_FUNC(PyObject *) _PyAST_ExprAsUnicode(
23-
expr_ty e,
24-
int omit_parens);
22+
PyAPI_FUNC(PyObject *) _PyAST_ExprAsUnicode(expr_ty);
2523

2624
#endif /* !Py_LIMITED_API */
2725

Lib/test/test_future.py

Lines changed: 64 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -157,62 +157,84 @@ def test_annotations(self):
157157
eq('True or False or None')
158158
eq('True and False')
159159
eq('True and False and None')
160-
eq('(Name1 and Name2) or Name3')
161-
eq('Name1 or (Name2 and Name3)')
162-
eq('(Name1 and Name2) or (Name3 and Name4)')
163-
eq('Name1 or (Name2 and Name3) or Name4')
160+
eq('Name1 and Name2 or Name3')
161+
eq('Name1 and (Name2 or Name3)')
162+
eq('Name1 or Name2 and Name3')
163+
eq('(Name1 or Name2) and Name3')
164+
eq('Name1 and Name2 or Name3 and Name4')
165+
eq('Name1 or Name2 and Name3 or Name4')
166+
eq('a + b + (c + d)')
167+
eq('a * b * (c * d)')
168+
eq('(a ** b) ** c ** d')
164169
eq('v1 << 2')
165170
eq('1 >> v2')
166-
eq(r'1 % finished')
167-
eq('((1 + v2) - (v3 * 4)) ^ (((5 ** v6) / 7) // 8)')
171+
eq('1 % finished')
172+
eq('1 + v2 - v3 * 4 ^ 5 ** v6 / 7 // 8')
168173
eq('not great')
174+
eq('not not great')
169175
eq('~great')
170176
eq('+value')
177+
eq('++value')
171178
eq('-1')
172-
eq('(~int) and (not ((v1 ^ (123 + v2)) | True))')
179+
eq('~int and not v1 ^ 123 + v2 | True')
180+
eq('a + (not b)')
173181
eq('lambda arg: None')
174182
eq('lambda a=True: a')
175183
eq('lambda a, b, c=True: a')
176-
eq("lambda a, b, c=True, *, d=(1 << v2), e='str': a")
177-
eq("lambda a, b, c=True, *vararg, d=(v1 << 2), e='str', **kwargs: a + b")
184+
eq("lambda a, b, c=True, *, d=1 << v2, e='str': a")
185+
eq("lambda a, b, c=True, *vararg, d=v1 << 2, e='str', **kwargs: a + b")
186+
eq('lambda x: lambda y: x + y')
178187
eq('1 if True else 2')
179-
eq('(str or None) if True else (str or bytes or None)')
180-
eq('(str or None) if (1 if True else 2) else (str or bytes or None)')
181-
eq("{'2.7': dead, '3.7': (long_live or die_hard)}")
182-
eq("{'2.7': dead, '3.7': (long_live or die_hard), **{'3.6': verygood}}")
188+
eq('str or None if int or True else str or bytes or None')
189+
eq('str or None if (1 if True else 2) else str or bytes or None')
190+
eq("0 if not x else 1 if x > 0 else -1")
191+
eq("(1 if x > 0 else -1) if x else 0")
192+
eq("{'2.7': dead, '3.7': long_live or die_hard}")
193+
eq("{'2.7': dead, '3.7': long_live or die_hard, **{'3.6': verygood}}")
183194
eq("{**a, **b, **c}")
184-
eq("{'2.7', '3.6', '3.7', '3.8', '3.9', ('4.0' if gilectomy else '3.10')}")
185-
eq("({'a': 'b'}, (True or False), (+value), 'string', b'bytes') or None")
195+
eq("{'2.7', '3.6', '3.7', '3.8', '3.9', '4.0' if gilectomy else '3.10'}")
196+
eq("{*a, *b, *c}")
197+
eq("({'a': 'b'}, True or False, +value, 'string', b'bytes') or None")
186198
eq("()")
187-
eq("(1,)")
188-
eq("(1, 2)")
189-
eq("(1, 2, 3)")
199+
eq("(a,)")
200+
eq("(a, b)")
201+
eq("(a, b, c)")
202+
eq("(*a, *b, *c)")
190203
eq("[]")
191-
eq("[1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)]")
204+
eq("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C]")
205+
eq("[*a, *b, *c]")
192206
eq("{i for i in (1, 2, 3)}")
193-
eq("{(i ** 2) for i in (1, 2, 3)}")
194-
eq("{(i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))}")
195-
eq("{((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)}")
207+
eq("{i ** 2 for i in (1, 2, 3)}")
208+
eq("{i ** 2 for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))}")
209+
eq("{i ** 2 + j for i in (1, 2, 3) for j in (1, 2, 3)}")
196210
eq("[i for i in (1, 2, 3)]")
197-
eq("[(i ** 2) for i in (1, 2, 3)]")
198-
eq("[(i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))]")
199-
eq("[((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)]")
200-
eq(r"{i: 0 for i in (1, 2, 3)}")
211+
eq("[i ** 2 for i in (1, 2, 3)]")
212+
eq("[i ** 2 for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))]")
213+
eq("[i ** 2 + j for i in (1, 2, 3) for j in (1, 2, 3)]")
214+
eq("(i for i in (1, 2, 3))")
215+
eq("(i ** 2 for i in (1, 2, 3))")
216+
eq("(i ** 2 for i, _ in ((1, 'a'), (2, 'b'), (3, 'c')))")
217+
eq("(i ** 2 + j for i in (1, 2, 3) for j in (1, 2, 3))")
218+
eq("{i: 0 for i in (1, 2, 3)}")
201219
eq("{i: j for i, j in ((1, 'a'), (2, 'b'), (3, 'c'))}")
220+
eq("[(x, y) for x, y in (a, b)]")
221+
eq("[(x,) for x, in (a,)]")
202222
eq("Python3 > Python2 > COBOL")
203223
eq("Life is Life")
204224
eq("call()")
205225
eq("call(arg)")
206226
eq("call(kwarg='hey')")
207227
eq("call(arg, kwarg='hey')")
208-
eq("call(arg, another, kwarg='hey', **kwargs)")
228+
eq("call(arg, *args, another, kwarg='hey')")
229+
eq("call(arg, another, kwarg='hey', **kwargs, kwarg2='ho')")
209230
eq("lukasz.langa.pl")
210231
eq("call.me(maybe)")
211232
eq("1 .real")
212233
eq("1.0 .real")
213234
eq("....__class__")
214235
eq("list[str]")
215236
eq("dict[str, int]")
237+
eq("set[str,]")
216238
eq("tuple[str, ...]")
217239
eq("tuple[str, int, float, dict[str, int]]")
218240
eq("slice[0]")
@@ -222,49 +244,28 @@ def test_annotations(self):
222244
eq("slice[:-1]")
223245
eq("slice[1:]")
224246
eq("slice[::-1]")
225-
eq('(str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None)')
247+
eq("slice[()]")
248+
eq("slice[a, b:c, d:e:f]")
249+
eq("slice[(x for x in a)]")
250+
eq('str or None if sys.version_info[0] > (3,) else str or bytes or None')
226251
eq("f'f-string without formatted values is just a string'")
227252
eq("f'{{NOT a formatted value}}'")
228253
eq("f'some f-string with {a} {few():.2f} {formatted.values!r}'")
229254
eq('''f"{f'{nested} inner'} outer"''')
230255
eq("f'space between opening braces: { {a for a in (1, 2, 3)}}'")
231-
232-
def test_annotations_inexact(self):
233-
"""Source formatting is not always preserved
234-
235-
This is due to reconstruction from AST. We *need to* put the parens
236-
in nested expressions because we don't know if the source code
237-
had them in the first place or not.
238-
"""
239-
eq = partial(self.assertAnnotationEqual, drop_parens=True)
240-
eq('Name1 and Name2 or Name3')
241-
eq('Name1 or Name2 and Name3')
242-
eq('Name1 and Name2 or Name3 and Name4')
243-
eq('Name1 or Name2 and Name3 or Name4')
244-
eq('1 + v2 - v3 * 4 ^ v5 ** 6 / 7 // 8')
245-
eq('~int and not v1 ^ 123 + v2 | True')
246-
eq('str or None if True else str or bytes or None')
247-
eq("{'2.7': dead, '3.7': long_live or die_hard}")
248-
eq("{'2.7', '3.6', '3.7', '3.8', '3.9', '4.0' if gilectomy else '3.10'}")
249-
eq("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C]")
250-
# Consequently, we always drop unnecessary parens if they were given in
251-
# the outer scope:
252-
some_name = self.getActual("(SomeName)")
253-
self.assertEqual(some_name, 'SomeName')
254-
# Interestingly, in the case of tuples (and generator expressions) the
255-
# parens are *required* by the Python syntax in the annotation context.
256-
# But there's no point storing that detail in __annotations__ so we're
257-
# fine with the parens-less form.
258-
eq = partial(self.assertAnnotationEqual, is_tuple=True)
259-
eq("(Good, Bad, Ugly)")
260-
eq("(i for i in (1, 2, 3))")
261-
eq("((i ** 2) for i in (1, 2, 3))")
262-
eq("((i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c')))")
263-
eq("(((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3))")
264-
eq("(*starred)")
256+
eq("f'{(lambda x: x)}'")
257+
eq("f'{(None if a else lambda x: x)}'")
265258
eq('(yield from outside_of_generator)')
266259
eq('(yield)')
267-
eq('(await some.complicated[0].call(with_args=(True or (1 is not 1))))')
260+
eq('(yield a + b)')
261+
eq('await some.complicated[0].call(with_args=True or 1 is not 1)')
262+
eq('[x for x in (a if b else c)]')
263+
eq('[x for x in a if (b if c else d)]')
264+
eq('f(x for x in a)')
265+
eq('f(1, (x for x in a))')
266+
eq('f((x for x in a), 2)')
267+
eq('(((a)))', 'a')
268+
eq('(((a, b)))', '(a, b)')
268269

269270

270271
if __name__ == "__main__":
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed miscellaneous bugs in converting annotations to strings and optimized
2+
parentheses in the string representation.

0 commit comments

Comments
 (0)