Author: Armin Rigo <ar...@tunes.org>
Branch: py3.5
Changeset: r90070:012eb9df87bd
Date: 2017-02-12 18:08 +0100
http://bitbucket.org/pypy/pypy/changeset/012eb9df87bd/

Log:    f-strings should never show up as docstrings (cpython 3.6.1 bugfix)

diff --git a/lib-python/3/test/test_fstring.py 
b/lib-python/3/test/test_fstring.py
--- a/lib-python/3/test/test_fstring.py
+++ b/lib-python/3/test/test_fstring.py
@@ -1,4 +1,5 @@
-# This test file is from CPython 3.6.0
+# This test file is from CPython 3.6 rev. 30341d5c1423, with a few
+# changes
 
 import ast
 import types
@@ -72,18 +73,18 @@
         # Make sure x was called.
         self.assertTrue(x.called)
 
+    def test_docstring(self):
+        def f():
+            f'''Not a docstring'''
+        self.assertIsNone(f.__doc__)
+        def g():
+            '''Not a docstring''' \
+            f''
+        self.assertIsNone(g.__doc__)
+
     def test_literal_eval(self):
-        # With no expressions, an f-string is okay.
-        self.assertEqual(ast.literal_eval("f'x'"), 'x')
-        self.assertEqual(ast.literal_eval("f'x' 'y'"), 'xy')
-
-        # But this should raise an error.
         with self.assertRaisesRegex(ValueError, 'malformed node or string'):
-            ast.literal_eval("f'x{3}'")
-
-        # As should this, which uses a different ast node
-        with self.assertRaisesRegex(ValueError, 'malformed node or string'):
-            ast.literal_eval("f'{3}'")
+            ast.literal_eval("f'x'")
 
     def test_ast_compile_time_concat(self):
         x = ['']
diff --git a/pypy/interpreter/astcompiler/codegen.py 
b/pypy/interpreter/astcompiler/codegen.py
--- a/pypy/interpreter/astcompiler/codegen.py
+++ b/pypy/interpreter/astcompiler/codegen.py
@@ -1506,7 +1506,8 @@
         self.update_position(joinedstr.lineno)
         for node in joinedstr.values:
             node.walkabout(self)
-        self.emit_op_arg(ops.BUILD_STRING, len(joinedstr.values))
+        if len(joinedstr.values) != 1:
+            self.emit_op_arg(ops.BUILD_STRING, len(joinedstr.values))
 
     def visit_FormattedValue(self, fmt):
         fmt.value.walkabout(self)
diff --git a/pypy/interpreter/astcompiler/fstring.py 
b/pypy/interpreter/astcompiler/fstring.py
--- a/pypy/interpreter/astcompiler/fstring.py
+++ b/pypy/interpreter/astcompiler/fstring.py
@@ -325,25 +325,23 @@
 
 
 def f_string_to_ast_node(astbuilder, joined_pieces, atom_node):
-    # remove empty Strs
+    # Remove empty Strs, but always return an ast.JoinedStr object.
+    # In this way it cannot be grabbed later for being used as a
+    # docstring.  In codegen.py we still special-case length-1 lists
+    # and avoid calling "BUILD_STRING 1" in this case.
     space = astbuilder.space
     values = [node for node in joined_pieces
                    if not isinstance(node, ast.Str)
                       or space.is_true(node.s)]
-    if len(values) > 1:
-        return ast.JoinedStr(values, atom_node.get_lineno(),
-                                     atom_node.get_column())
-    elif len(values) == 1:
-        return values[0]
-    else:
-        assert len(joined_pieces) > 0    # they are all empty strings
-        return joined_pieces[0]
+    return ast.JoinedStr(values, atom_node.get_lineno(),
+                                 atom_node.get_column())
 
 
 def string_parse_literal(astbuilder, atom_node):
     space = astbuilder.space
     encoding = astbuilder.compile_info.encoding
     joined_pieces = []
+    fmode = False
     try:
         for i in range(atom_node.num_children()):
             w_next = parsestring.parsestr(
@@ -353,6 +351,7 @@
                                     atom_node)
             else:
                 parse_f_string(astbuilder, joined_pieces, w_next, atom_node)
+                fmode = True
 
     except error.OperationError as e:
         if e.match(space, space.w_UnicodeError):
@@ -366,7 +365,7 @@
         errmsg = space.str_w(space.str(e.get_w_value(space)))
         raise astbuilder.error('(%s) %s' % (kind, errmsg), atom_node)
 
-    if len(joined_pieces) == 1:   # <= the common path
+    if not fmode and len(joined_pieces) == 1:   # <= the common path
         return joined_pieces[0]   # ast.Str, Bytes or FormattedValue
 
     # with more than one piece, it is a combination of Str and
@@ -376,4 +375,5 @@
         if isinstance(node, ast.Bytes):
             astbuilder.error("cannot mix bytes and nonbytes literals",
                              atom_node)
+    assert fmode
     return f_string_to_ast_node(astbuilder, joined_pieces, atom_node)
diff --git a/pypy/interpreter/astcompiler/test/test_compiler.py 
b/pypy/interpreter/astcompiler/test/test_compiler.py
--- a/pypy/interpreter/astcompiler/test/test_compiler.py
+++ b/pypy/interpreter/astcompiler/test/test_compiler.py
@@ -417,6 +417,7 @@
                 foo = Foo()
                 exec("'moduledoc'", foo.__dict__)
              ''',                            "moduledoc"),
+            ('''def foo(): f"abc"''',        None),
             ]:
             yield self.simple_test, source, "foo.__doc__", expected
 
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to