Skip to content

Commit fe48e7e

Browse files
committed
requested changes
1 parent 766f2bc commit fe48e7e

File tree

3 files changed

+132
-37
lines changed

3 files changed

+132
-37
lines changed

Lib/ast.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -708,10 +708,10 @@ def maybe_semicolon(self):
708708
if self._source:
709709
self.write("; ")
710710

711-
def fill(self, text="", allow_semi=True):
711+
def fill(self, text="", *, allow_semicolon=True):
712712
"""Indent a piece of text and append it, according to the current
713713
indentation level, or only delineate with semicolon if applicable"""
714-
if self._in_interactive and not self._indent and allow_semi:
714+
if self._in_interactive and not self._indent and allow_semicolon:
715715
self.maybe_semicolon()
716716
self.write(text)
717717
else:
@@ -960,17 +960,17 @@ def visit_Raise(self, node):
960960
self.traverse(node.cause)
961961

962962
def do_visit_try(self, node):
963-
self.fill("try", allow_semi=False)
963+
self.fill("try", allow_semicolon=False)
964964
with self.block():
965965
self.traverse(node.body)
966966
for ex in node.handlers:
967967
self.traverse(ex)
968968
if node.orelse:
969-
self.fill("else", allow_semi=False)
969+
self.fill("else", allow_semicolon=False)
970970
with self.block():
971971
self.traverse(node.orelse)
972972
if node.finalbody:
973-
self.fill("finally", allow_semi=False)
973+
self.fill("finally", allow_semicolon=False)
974974
with self.block():
975975
self.traverse(node.finalbody)
976976

@@ -991,7 +991,7 @@ def visit_TryStar(self, node):
991991
self._in_try_star = prev_in_try_star
992992

993993
def visit_ExceptHandler(self, node):
994-
self.fill("except*" if self._in_try_star else "except", allow_semi=False)
994+
self.fill("except*" if self._in_try_star else "except", allow_semicolon=False)
995995
if node.type:
996996
self.write(" ")
997997
self.traverse(node.type)
@@ -1004,9 +1004,9 @@ def visit_ExceptHandler(self, node):
10041004
def visit_ClassDef(self, node):
10051005
self.maybe_newline()
10061006
for deco in node.decorator_list:
1007-
self.fill("@", allow_semi=False)
1007+
self.fill("@", allow_semicolon=False)
10081008
self.traverse(deco)
1009-
self.fill("class " + node.name, allow_semi=False)
1009+
self.fill("class " + node.name, allow_semicolon=False)
10101010
if hasattr(node, "type_params"):
10111011
self._type_params_helper(node.type_params)
10121012
with self.delimit_if("(", ")", condition = node.bases or node.keywords):
@@ -1036,10 +1036,10 @@ def visit_AsyncFunctionDef(self, node):
10361036
def _function_helper(self, node, fill_suffix):
10371037
self.maybe_newline()
10381038
for deco in node.decorator_list:
1039-
self.fill("@", allow_semi=False)
1039+
self.fill("@", allow_semicolon=False)
10401040
self.traverse(deco)
10411041
def_str = fill_suffix + " " + node.name
1042-
self.fill(def_str, allow_semi=False)
1042+
self.fill(def_str, allow_semicolon=False)
10431043
if hasattr(node, "type_params"):
10441044
self._type_params_helper(node.type_params)
10451045
with self.delimit("(", ")"):
@@ -1090,54 +1090,54 @@ def visit_AsyncFor(self, node):
10901090
self._for_helper("async for ", node)
10911091

10921092
def _for_helper(self, fill, node):
1093-
self.fill(fill, allow_semi=False)
1093+
self.fill(fill, allow_semicolon=False)
10941094
self.set_precedence(_Precedence.TUPLE, node.target)
10951095
self.traverse(node.target)
10961096
self.write(" in ")
10971097
self.traverse(node.iter)
10981098
with self.block(extra=self.get_type_comment(node)):
10991099
self.traverse(node.body)
11001100
if node.orelse:
1101-
self.fill("else", allow_semi=False)
1101+
self.fill("else", allow_semicolon=False)
11021102
with self.block():
11031103
self.traverse(node.orelse)
11041104

11051105
def visit_If(self, node):
1106-
self.fill("if ", allow_semi=False)
1106+
self.fill("if ", allow_semicolon=False)
11071107
self.traverse(node.test)
11081108
with self.block():
11091109
self.traverse(node.body)
11101110
# collapse nested ifs into equivalent elifs.
11111111
while node.orelse and len(node.orelse) == 1 and isinstance(node.orelse[0], If):
11121112
node = node.orelse[0]
1113-
self.fill("elif ", allow_semi=False)
1113+
self.fill("elif ", allow_semicolon=False)
11141114
self.traverse(node.test)
11151115
with self.block():
11161116
self.traverse(node.body)
11171117
# final else
11181118
if node.orelse:
1119-
self.fill("else", allow_semi=False)
1119+
self.fill("else", allow_semicolon=False)
11201120
with self.block():
11211121
self.traverse(node.orelse)
11221122

11231123
def visit_While(self, node):
1124-
self.fill("while ", allow_semi=False)
1124+
self.fill("while ", allow_semicolon=False)
11251125
self.traverse(node.test)
11261126
with self.block():
11271127
self.traverse(node.body)
11281128
if node.orelse:
1129-
self.fill("else", allow_semi=False)
1129+
self.fill("else", allow_semicolon=False)
11301130
with self.block():
11311131
self.traverse(node.orelse)
11321132

11331133
def visit_With(self, node):
1134-
self.fill("with ", allow_semi=False)
1134+
self.fill("with ", allow_semicolon=False)
11351135
self.interleave(lambda: self.write(", "), self.traverse, node.items)
11361136
with self.block(extra=self.get_type_comment(node)):
11371137
self.traverse(node.body)
11381138

11391139
def visit_AsyncWith(self, node):
1140-
self.fill("async with ", allow_semi=False)
1140+
self.fill("async with ", allow_semicolon=False)
11411141
self.interleave(lambda: self.write(", "), self.traverse, node.items)
11421142
with self.block(extra=self.get_type_comment(node)):
11431143
self.traverse(node.body)
@@ -1279,7 +1279,7 @@ def visit_Name(self, node):
12791279
self.write(node.id)
12801280

12811281
def _write_docstring(self, node):
1282-
self.fill(allow_semi=False)
1282+
self.fill(allow_semicolon=False)
12831283
if node.kind == "u":
12841284
self.write("u")
12851285
self._write_str_avoiding_backslashes(node.value, quote_types=_MULTI_QUOTES)
@@ -1573,7 +1573,7 @@ def visit_Slice(self, node):
15731573
self.traverse(node.step)
15741574

15751575
def visit_Match(self, node):
1576-
self.fill("match ", allow_semi=False)
1576+
self.fill("match ", allow_semicolon=False)
15771577
self.traverse(node.subject)
15781578
with self.block():
15791579
for case in node.cases:
@@ -1667,7 +1667,7 @@ def visit_withitem(self, node):
16671667
self.traverse(node.optional_vars)
16681668

16691669
def visit_match_case(self, node):
1670-
self.fill("case ", allow_semi=False)
1670+
self.fill("case ", allow_semicolon=False)
16711671
self.traverse(node.pattern)
16721672
if node.guard:
16731673
self.write(" if ")

Lib/test/test_ast/test_ast.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -814,17 +814,6 @@ def test_repr_large_input_crash(self):
814814
r"Exceeds the limit \(\d+ digits\)"):
815815
repr(ast.Constant(value=eval(source)))
816816

817-
def test_unparse_interactive(self):
818-
# gh-129598: Fix of ast.unparse() when ast.Interactive contains multiple statements
819-
source = "i = 1; 'expr'; raise Exception"
820-
self.assertEqual(source, ast.unparse(ast.parse(source, mode='single')))
821-
source = "if i:\n 'expr'\nelse:\n raise Exception"
822-
unparsed = "if i:\n 'expr'\nelse:\n raise Exception"
823-
self.assertEqual(unparsed, ast.unparse(ast.parse(source, mode='single')))
824-
source = "@decorator\ndef func():\n 'docstring'\n i = 1; 'expr'; raise Exception"
825-
unparsed = '''@decorator\ndef func():\n """docstring"""\n i = 1\n 'expr'\n raise Exception'''
826-
self.assertEqual(unparsed, ast.unparse(ast.parse(source, mode='single')))
827-
828817

829818
class CopyTests(unittest.TestCase):
830819
"""Test copying and pickling AST nodes."""

Lib/test/test_unparse.py

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,13 @@ def check_invalid(self, node, raises=ValueError):
141141
with self.subTest(node=node):
142142
self.assertRaises(raises, ast.unparse, node)
143143

144-
def get_source(self, code1, code2=None):
144+
def get_source(self, code1, code2=None, **kwargs):
145145
code2 = code2 or code1
146-
code1 = ast.unparse(ast.parse(code1))
146+
code1 = ast.unparse(ast.parse(code1, **kwargs))
147147
return code1, code2
148148

149-
def check_src_roundtrip(self, code1, code2=None):
150-
code1, code2 = self.get_source(code1, code2)
149+
def check_src_roundtrip(self, code1, code2=None, **kwargs):
150+
code1, code2 = self.get_source(code1, code2, **kwargs)
151151
with self.subTest(code1=code1, code2=code2):
152152
self.assertEqual(code2, code1)
153153

@@ -466,6 +466,112 @@ def test_type_ignore(self):
466466
):
467467
self.check_ast_roundtrip(statement, type_comments=True)
468468

469+
def test_unparse_interactive(self):
470+
# gh-129598: Fix of ast.unparse() when ast.Interactive contains multiple statements
471+
self.check_src_roundtrip("i = 1; 'expr'; raise Exception", mode='single')
472+
self.check_src_roundtrip("i: int = 1; j: float = 0; k += l", mode='single')
473+
combinable = (
474+
"'expr'",
475+
"(i := 1)",
476+
"import foo",
477+
"from foo import bar",
478+
"i = 1",
479+
"i += 1",
480+
"i: int = 1",
481+
"return i",
482+
"pass",
483+
"break",
484+
"continue",
485+
"del i",
486+
"assert i",
487+
"global i",
488+
"nonlocal j",
489+
"await i",
490+
"yield i",
491+
"yield from i",
492+
"raise i",
493+
"type t[T] = ...",
494+
"i",
495+
)
496+
for a in combinable:
497+
for b in combinable:
498+
self.check_src_roundtrip(f"{a}; {b}", mode='single')
499+
500+
# rest of the tests just make sure mode='single' parse and unparse didn't break
501+
self.check_src_roundtrip(
502+
"if i:\n 'expr'\nelse:\n raise Exception",
503+
"if i:\n 'expr'\nelse:\n raise Exception",
504+
mode='single'
505+
)
506+
self.check_src_roundtrip(
507+
"@decorator1\n@decorator2\ndef func():\n 'docstring'\n i = 1; 'expr'; raise Exception",
508+
'''@decorator1\n@decorator2\ndef func():\n """docstring"""\n i = 1\n 'expr'\n raise Exception''',
509+
mode='single'
510+
)
511+
self.check_src_roundtrip(
512+
"@decorator1\n@decorator2\nclass cls:\n 'docstring'\n i = 1; 'expr'; raise Exception",
513+
'''@decorator1\n@decorator2\nclass cls:\n """docstring"""\n i = 1\n 'expr'\n raise Exception''',
514+
mode='single'
515+
)
516+
for statement in (
517+
"def x():\n pass",
518+
"def x(y):\n pass",
519+
"async def x():\n pass",
520+
"async def x(y):\n pass",
521+
"for x in y:\n pass",
522+
"async for x in y:\n pass",
523+
"with x():\n pass",
524+
"async with x():\n pass",
525+
"def f():\n pass",
526+
"def f(a):\n pass",
527+
"def f(b=2):\n pass",
528+
"def f(a, b):\n pass",
529+
"def f(a, b=2):\n pass",
530+
"def f(a=5, b=2):\n pass",
531+
"def f(*, a=1, b=2):\n pass",
532+
"def f(*, a=1, b):\n pass",
533+
"def f(*, a, b=2):\n pass",
534+
"def f(a, b=None, *, c, **kwds):\n pass",
535+
"def f(a=2, *args, c=5, d, **kwds):\n pass",
536+
"def f(*args, **kwargs):\n pass",
537+
"class cls:\n\n def f(self):\n pass",
538+
"class cls:\n\n def f(self, a):\n pass",
539+
"class cls:\n\n def f(self, b=2):\n pass",
540+
"class cls:\n\n def f(self, a, b):\n pass",
541+
"class cls:\n\n def f(self, a, b=2):\n pass",
542+
"class cls:\n\n def f(self, a=5, b=2):\n pass",
543+
"class cls:\n\n def f(self, *, a=1, b=2):\n pass",
544+
"class cls:\n\n def f(self, *, a=1, b):\n pass",
545+
"class cls:\n\n def f(self, *, a, b=2):\n pass",
546+
"class cls:\n\n def f(self, a, b=None, *, c, **kwds):\n pass",
547+
"class cls:\n\n def f(self, a=2, *args, c=5, d, **kwds):\n pass",
548+
"class cls:\n\n def f(self, *args, **kwargs):\n pass",
549+
):
550+
self.check_src_roundtrip(statement, mode='single')
551+
for statement in (
552+
"def x():",
553+
"def x(y):",
554+
"async def x():",
555+
"async def x(y):",
556+
"for x in y:",
557+
"async for x in y:",
558+
"with x():",
559+
"async with x():",
560+
"def f():",
561+
"def f(a):",
562+
"def f(b=2):",
563+
"def f(a, b):",
564+
"def f(a, b=2):",
565+
"def f(a=5, b=2):",
566+
"def f(*, a=1, b=2):",
567+
"def f(*, a=1, b):",
568+
"def f(*, a, b=2):",
569+
"def f(a, b=None, *, c, **kwds):",
570+
"def f(a=2, *args, c=5, d, **kwds):",
571+
"def f(*args, **kwargs):",
572+
):
573+
self.check_src_roundtrip(statement + '\n i=1;j=2', statement + '\n i = 1\n j = 2', mode='single')
574+
469575

470576
class CosmeticTestCase(ASTTestCase):
471577
"""Test if there are cosmetic issues caused by unnecessary additions"""

0 commit comments

Comments
 (0)