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