summaryrefslogtreecommitdiffstats
path: root/Lib/lib2to3
diff options
context:
space:
mode:
authorJakub Stasiak <jakub@stasiak.at>2017-10-05 07:10:09 (GMT)
committerBenjamin Peterson <benjamin@python.org>2017-10-05 07:10:09 (GMT)
commitaf810b35b494ef1d255d4bf340b92a9dad446995 (patch)
tree78df02ecf9d934a174aa2ad1202d583b29faeba7 /Lib/lib2to3
parenta8ed11742b4c2115597977ce04fa8e043d9e0792 (diff)
downloadcpython-af810b35b494ef1d255d4bf340b92a9dad446995.zip
cpython-af810b35b494ef1d255d4bf340b92a9dad446995.tar.gz
cpython-af810b35b494ef1d255d4bf340b92a9dad446995.tar.bz2
closes bpo-27494: Fix 2to3 handling of trailing comma after a generator expression (#3771)
Diffstat (limited to 'Lib/lib2to3')
-rw-r--r--Lib/lib2to3/Grammar.txt25
-rw-r--r--Lib/lib2to3/fixer_util.py6
-rw-r--r--Lib/lib2to3/fixes/fix_dict.py2
-rw-r--r--Lib/lib2to3/fixes/fix_paren.py4
-rw-r--r--Lib/lib2to3/fixes/fix_xrange.py2
-rw-r--r--Lib/lib2to3/tests/test_parser.py7
6 files changed, 36 insertions, 10 deletions
diff --git a/Lib/lib2to3/Grammar.txt b/Lib/lib2to3/Grammar.txt
index 2abd5ee..ded0325 100644
--- a/Lib/lib2to3/Grammar.txt
+++ b/Lib/lib2to3/Grammar.txt
@@ -130,8 +130,8 @@ atom: ('(' [yield_expr|testlist_gexp] ')' |
'{' [dictsetmaker] '}' |
'`' testlist1 '`' |
NAME | NUMBER | STRING+ | '.' '.' '.')
-listmaker: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] )
-testlist_gexp: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] )
+listmaker: (test|star_expr) ( old_comp_for | (',' (test|star_expr))* [','] )
+testlist_gexp: (test|star_expr) ( old_comp_for | (',' (test|star_expr))* [','] )
lambdef: 'lambda' [varargslist] ':' test
trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
subscriptlist: subscript (',' subscript)* [',']
@@ -161,9 +161,28 @@ argument: ( test [comp_for] |
star_expr )
comp_iter: comp_for | comp_if
-comp_for: [ASYNC] 'for' exprlist 'in' testlist_safe [comp_iter]
+comp_for: [ASYNC] 'for' exprlist 'in' or_test [comp_iter]
comp_if: 'if' old_test [comp_iter]
+# As noted above, testlist_safe extends the syntax allowed in list
+# comprehensions and generators. We can't use it indiscriminately in all
+# derivations using a comp_for-like pattern because the testlist_safe derivation
+# contains comma which clashes with trailing comma in arglist.
+#
+# This was an issue because the parser would not follow the correct derivation
+# when parsing syntactically valid Python code. Since testlist_safe was created
+# specifically to handle list comprehensions and generator expressions enclosed
+# with parentheses, it's safe to only use it in those. That avoids the issue; we
+# can parse code like set(x for x in [],).
+#
+# The syntax supported by this set of rules is not a valid Python 3 syntax,
+# hence the prefix "old".
+#
+# See https://bugs.python.org/issue27494
+old_comp_iter: old_comp_for | old_comp_if
+old_comp_for: [ASYNC] 'for' exprlist 'in' testlist_safe [old_comp_iter]
+old_comp_if: 'if' old_test [old_comp_iter]
+
testlist1: test (',' test)*
# not used in grammar, but may appear in "node" passed from Parser to Compiler
diff --git a/Lib/lib2to3/fixer_util.py b/Lib/lib2to3/fixer_util.py
index babe6cb..2f9b1c2 100644
--- a/Lib/lib2to3/fixer_util.py
+++ b/Lib/lib2to3/fixer_util.py
@@ -101,8 +101,8 @@ def ListComp(xp, fp, it, test=None):
test.prefix = " "
if_leaf = Leaf(token.NAME, "if")
if_leaf.prefix = " "
- inner_args.append(Node(syms.comp_if, [if_leaf, test]))
- inner = Node(syms.listmaker, [xp, Node(syms.comp_for, inner_args)])
+ inner_args.append(Node(syms.old_comp_if, [if_leaf, test]))
+ inner = Node(syms.listmaker, [xp, Node(syms.old_comp_for, inner_args)])
return Node(syms.atom,
[Leaf(token.LBRACE, "["),
inner,
@@ -208,7 +208,7 @@ def attr_chain(obj, attr):
next = getattr(next, attr)
p0 = """for_stmt< 'for' any 'in' node=any ':' any* >
- | comp_for< 'for' any 'in' node=any any* >
+ | old_comp_for< 'for' any 'in' node=any any* >
"""
p1 = """
power<
diff --git a/Lib/lib2to3/fixes/fix_dict.py b/Lib/lib2to3/fixes/fix_dict.py
index d3655c9..55be553 100644
--- a/Lib/lib2to3/fixes/fix_dict.py
+++ b/Lib/lib2to3/fixes/fix_dict.py
@@ -83,7 +83,7 @@ class FixDict(fixer_base.BaseFix):
p1 = patcomp.compile_pattern(P1)
P2 = """for_stmt< 'for' any 'in' node=any ':' any* >
- | comp_for< 'for' any 'in' node=any any* >
+ | old_comp_for< 'for' any 'in' node=any any* >
"""
p2 = patcomp.compile_pattern(P2)
diff --git a/Lib/lib2to3/fixes/fix_paren.py b/Lib/lib2to3/fixes/fix_paren.py
index b205aa7..de49eef 100644
--- a/Lib/lib2to3/fixes/fix_paren.py
+++ b/Lib/lib2to3/fixes/fix_paren.py
@@ -15,7 +15,7 @@ class FixParen(fixer_base.BaseFix):
PATTERN = """
atom< ('[' | '(')
(listmaker< any
- comp_for<
+ old_comp_for<
'for' NAME 'in'
target=testlist_safe< any (',' any)+ [',']
>
@@ -24,7 +24,7 @@ class FixParen(fixer_base.BaseFix):
>
|
testlist_gexp< any
- comp_for<
+ old_comp_for<
'for' NAME 'in'
target=testlist_safe< any (',' any)+ [',']
>
diff --git a/Lib/lib2to3/fixes/fix_xrange.py b/Lib/lib2to3/fixes/fix_xrange.py
index 1e491e1..f5f06f3 100644
--- a/Lib/lib2to3/fixes/fix_xrange.py
+++ b/Lib/lib2to3/fixes/fix_xrange.py
@@ -55,7 +55,7 @@ class FixXrange(fixer_base.BaseFix):
p1 = patcomp.compile_pattern(P1)
P2 = """for_stmt< 'for' any 'in' node=any ':' any* >
- | comp_for< 'for' any 'in' node=any any* >
+ | old_comp_for< 'for' any 'in' node=any any* >
| comparison< any 'in' node=any any*>
"""
p2 = patcomp.compile_pattern(P2)
diff --git a/Lib/lib2to3/tests/test_parser.py b/Lib/lib2to3/tests/test_parser.py
index 3f7ab97..2efcb80 100644
--- a/Lib/lib2to3/tests/test_parser.py
+++ b/Lib/lib2to3/tests/test_parser.py
@@ -459,6 +459,13 @@ class TestLiterals(GrammarTest):
self.validate(s)
+class TestGeneratorExpressions(GrammarTest):
+
+ def test_trailing_comma_after_generator_expression_argument_works(self):
+ # BPO issue 27494
+ self.validate("set(x for x in [],)")
+
+
def diff(fn, result):
try:
with open('@', 'w') as f: