]> granicus.if.org Git - python/commitdiff
closes bpo-27494: Fix 2to3 handling of trailing comma after a generator expression...
authorJakub Stasiak <jakub@stasiak.at>
Thu, 5 Oct 2017 07:10:09 +0000 (09:10 +0200)
committerBenjamin Peterson <benjamin@python.org>
Thu, 5 Oct 2017 07:10:09 +0000 (00:10 -0700)
Lib/lib2to3/Grammar.txt
Lib/lib2to3/fixer_util.py
Lib/lib2to3/fixes/fix_dict.py
Lib/lib2to3/fixes/fix_paren.py
Lib/lib2to3/fixes/fix_xrange.py
Lib/lib2to3/tests/test_parser.py
Misc/NEWS.d/next/Library/2017-09-26-01-43-17.bpo-27494.37QnaT.rst [new file with mode: 0644]

index 2abd5ee65b5bbd7f1573182d8eaf95adb407e8ea..ded032522bada4f2af99c17bb570d327834c493f 100644 (file)
@@ -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
index babe6cb3f662a961b2f89d3a9023344327afa036..2f9b1c2e766e78833b732cfdecd83096aa2fde05 100644 (file)
@@ -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<
index d3655c9f1b2d9bf82d8ce3a2140cfeff9ba97323..55be553baac81d1389c45bae46070502ef6fd453 100644 (file)
@@ -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)
 
index b205aa7e1e93fbe15d13bb259729f2b45fe0b322..de49eef157d5860677b592ee2181b33b14a2755a 100644 (file)
@@ -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)+ [',']
                      >
index 1e491e166a3f1c4223d306b9ad5817bc31fab2ee..f5f06f3543502fddc3017ecc1e902f4b837994d5 100644 (file)
@@ -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)
index 3f7ab9714e38f9822a3b061e2631281275a01d1e..2efcb80c2f94a95b2646400bc67f25adf5442891 100644 (file)
@@ -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:
diff --git a/Misc/NEWS.d/next/Library/2017-09-26-01-43-17.bpo-27494.37QnaT.rst b/Misc/NEWS.d/next/Library/2017-09-26-01-43-17.bpo-27494.37QnaT.rst
new file mode 100644 (file)
index 0000000..5b5362a
--- /dev/null
@@ -0,0 +1,2 @@
+Make 2to3 accept a trailing comma in generator expressions. For example, ``set(x
+for x in [],)`` is now allowed.