[Python-checkins] cpython: Closes issue 27921: Disallow backslashes anywhere in f-strings. This is a

eric.smith python-checkins at python.org
Sat Sep 3 09:18:38 EDT 2016


https://hg.python.org/cpython/rev/d7ce127b5c0f
changeset: 103014:d7ce127b5c0f
user: Eric V. Smith <eric at trueblade.com>
date: Sat Sep 03 09:18:34 2016 -0400
summary:
 Closes issue 27921: Disallow backslashes anywhere in f-strings. This is a temporary restriction. In 3.6 beta 2, the plan is to again allow backslashes in the string parts of f-strings, but disallow them in the expression parts.
files:
 Lib/test/libregrtest/save_env.py | 2 +-
 Lib/test/test_fstring.py | 142 +++++----------
 Lib/test/test_tools/test_unparse.py | 4 -
 Lib/traceback.py | 4 +-
 Misc/NEWS | 5 +
 Python/ast.c | 10 +
 6 files changed, 69 insertions(+), 98 deletions(-)
diff --git a/Lib/test/libregrtest/save_env.py b/Lib/test/libregrtest/save_env.py
--- a/Lib/test/libregrtest/save_env.py
+++ b/Lib/test/libregrtest/save_env.py
@@ -280,6 +280,6 @@
 print(f"Warning -- {name} was modified by {self.testname}",
 file=sys.stderr, flush=True)
 if self.verbose > 1:
- print(f" Before: {original}\n After: {current} ",
+ print(f" Before: {original}""\n"f" After: {current} ",
 file=sys.stderr, flush=True)
 return False
diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py
--- a/Lib/test/test_fstring.py
+++ b/Lib/test/test_fstring.py
@@ -96,30 +96,6 @@
 self.assertEqual(f'', '')
 self.assertEqual(f'a', 'a')
 self.assertEqual(f' ', ' ')
- self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}',
- '\N{GREEK CAPITAL LETTER DELTA}')
- self.assertEqual(f'\N{GREEK CAPITAL LETTER DELTA}',
- '\u0394')
- self.assertEqual(f'\N{True}', '\u22a8')
- self.assertEqual(rf'\N{True}', r'\NTrue')
-
- def test_escape_order(self):
- # note that hex(ord('{')) == 0x7b, so this
- # string becomes f'a{4*10}b'
- self.assertEqual(f'a\u007b4*10}b', 'a40b')
- self.assertEqual(f'a\x7b4*10}b', 'a40b')
- self.assertEqual(f'a\x7b4*10\N{RIGHT CURLY BRACKET}b', 'a40b')
- self.assertEqual(f'{"a"!\N{LATIN SMALL LETTER R}}', "'a'")
- self.assertEqual(f'{10\x3a02X}', '0A')
- self.assertEqual(f'{10:02\N{LATIN CAPITAL LETTER X}}', '0A')
-
- self.assertAllRaise(SyntaxError, "f-string: single '}' is not allowed",
- [r"""f'a{\u007b4*10}b'""", # mis-matched brackets
- ])
- self.assertAllRaise(SyntaxError, 'unexpected character after line continuation character',
- [r"""f'{"a"\!r}'""",
- r"""f'{a\!r}'""",
- ])
 
 def test_unterminated_string(self):
 self.assertAllRaise(SyntaxError, 'f-string: unterminated string',
@@ -285,8 +261,6 @@
 "f'{ !r}'",
 "f'{10:{ }}'",
 "f' { } '",
- r"f'{\n}'",
- r"f'{\n \n}'",
 
 # Catch the empty expression before the
 # invalid conversion.
@@ -328,24 +302,61 @@
 ["f'{\n}'",
 ])
 
+ def test_no_backslashes(self):
+ # See issue 27921
+
+ # These should work, but currently don't
+ self.assertAllRaise(SyntaxError, 'backslashes not allowed',
+ [r"f'\t'",
+ r"f'{2}\t'",
+ r"f'{2}\t{3}'",
+ r"f'\t{3}'",
+
+ r"f'\N{GREEK CAPITAL LETTER DELTA}'",
+ r"f'{2}\N{GREEK CAPITAL LETTER DELTA}'",
+ r"f'{2}\N{GREEK CAPITAL LETTER DELTA}{3}'",
+ r"f'\N{GREEK CAPITAL LETTER DELTA}{3}'",
+
+ r"f'\u0394'",
+ r"f'{2}\u0394'",
+ r"f'{2}\u0394{3}'",
+ r"f'\u0394{3}'",
+
+ r"f'\U00000394'",
+ r"f'{2}\U00000394'",
+ r"f'{2}\U00000394{3}'",
+ r"f'\U00000394{3}'",
+
+ r"f'\x20'",
+ r"f'{2}\x20'",
+ r"f'{2}\x20{3}'",
+ r"f'\x20{3}'",
+
+ r"f'2\x20'",
+ r"f'2\x203'",
+ r"f'2\x203'",
+ ])
+
+ # And these don't work now, and shouldn't work in the future.
+ self.assertAllRaise(SyntaxError, 'backslashes not allowed',
+ [r"f'{\'a\'}'",
+ r"f'{\t3}'",
+ ])
+
+ # add this when backslashes are allowed again. see issue 27921
+ # these test will be needed because unicode names will be parsed
+ # differently once backslashes are allowed inside expressions
+ ## def test_misformed_unicode_character_name(self):
+ ## self.assertAllRaise(SyntaxError, 'xx',
+ ## [r"f'\N'",
+ ## [r"f'\N{'",
+ ## [r"f'\N{GREEK CAPITAL LETTER DELTA'",
+ ## ])
+
 def test_newlines_in_expressions(self):
 self.assertEqual(f'{0}', '0')
- self.assertEqual(f'{0\n}', '0')
- self.assertEqual(f'{0\r}', '0')
- self.assertEqual(f'{\n0\n}', '0')
- self.assertEqual(f'{\r0\r}', '0')
- self.assertEqual(f'{\n0\r}', '0')
- self.assertEqual(f'{\n0}', '0')
- self.assertEqual(f'{3+\n4}', '7')
- self.assertEqual(f'{3+\\\n4}', '7')
 self.assertEqual(rf'''{3+
 4}''', '7')
- self.assertEqual(f'''{3+\
-4}''', '7')
-
- self.assertAllRaise(SyntaxError, 'f-string: empty expression not allowed',
- [r"f'{\n}'",
- ])
 
 def test_lambda(self):
 x = 5
@@ -380,9 +391,6 @@
 def test_expressions_with_triple_quoted_strings(self):
 self.assertEqual(f"{'''x'''}", 'x')
 self.assertEqual(f"{'''eric's'''}", "eric's")
- self.assertEqual(f'{"""eric\'s"""}', "eric's")
- self.assertEqual(f"{'''eric\"s'''}", 'eric"s')
- self.assertEqual(f'{"""eric"s"""}', 'eric"s')
 
 # Test concatenation within an expression
 self.assertEqual(f'{"x" """eric"s""" "y"}', 'xeric"sy')
@@ -484,10 +492,6 @@
 y = 5
 self.assertEqual(f'{f"{0}"*3}', '000')
 self.assertEqual(f'{f"{y}"*3}', '555')
- self.assertEqual(f'{f"{\'x\'}"*3}', 'xxx')
-
- self.assertEqual(f"{r'x' f'{\"s\"}'}", 'xs')
- self.assertEqual(f"{r'x'rf'{\"s\"}'}", 'xs')
 
 def test_invalid_string_prefixes(self):
 self.assertAllRaise(SyntaxError, 'unexpected EOF while parsing',
@@ -510,24 +514,14 @@
 def test_leading_trailing_spaces(self):
 self.assertEqual(f'{ 3}', '3')
 self.assertEqual(f'{ 3}', '3')
- self.assertEqual(f'{\t3}', '3')
- self.assertEqual(f'{\t\t3}', '3')
 self.assertEqual(f'{3 }', '3')
 self.assertEqual(f'{3 }', '3')
- self.assertEqual(f'{3\t}', '3')
- self.assertEqual(f'{3\t\t}', '3')
 
 self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]}}',
 'expr={1: 2}')
 self.assertEqual(f'expr={ {x: y for x, y in [(1, 2), ]} }',
 'expr={1: 2}')
 
- def test_character_name(self):
- self.assertEqual(f'{4}\N{GREEK CAPITAL LETTER DELTA}{3}',
- '4\N{GREEK CAPITAL LETTER DELTA}3')
- self.assertEqual(f'{{}}\N{GREEK CAPITAL LETTER DELTA}{3}',
- '{}\N{GREEK CAPITAL LETTER DELTA}3')
-
 def test_not_equal(self):
 # There's a special test for this because there's a special
 # case in the f-string parser to look for != as not ending an
@@ -554,10 +548,6 @@
 # Not a conversion, but show that ! is allowed in a format spec.
 self.assertEqual(f'{3.14:!<10.10}', '3.14!!!!!!')
 
- self.assertEqual(f'{"\N{GREEK CAPITAL LETTER DELTA}"}', '\u0394')
- self.assertEqual(f'{"\N{GREEK CAPITAL LETTER DELTA}"!r}', "'\u0394'")
- self.assertEqual(f'{"\N{GREEK CAPITAL LETTER DELTA}"!a}', "'\\u0394'")
-
 self.assertAllRaise(SyntaxError, 'f-string: invalid conversion character',
 ["f'{3!g}'",
 "f'{3!A}'",
@@ -565,9 +555,7 @@
 "f'{3!A}'",
 "f'{3!!}'",
 "f'{3!:}'",
- "f'{3!\N{GREEK CAPITAL LETTER DELTA}}'",
 "f'{3! s}'", # no space before conversion char
- "f'{x!\\x00:.<10}'",
 ])
 
 self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
@@ -600,7 +588,6 @@
 
 # Can't have { or } in a format spec.
 "f'{3:}>10}'",
- r"f'{3:\\}>10}'",
 "f'{3:}}>10}'",
 ])
 
@@ -620,10 +607,6 @@
 "f'{'",
 ])
 
- self.assertAllRaise(SyntaxError, 'invalid syntax',
- [r"f'{3:\\{>10}'",
- ])
-
 # But these are just normal strings.
 self.assertEqual(f'{"{"}', '{')
 self.assertEqual(f'{"}"}', '}')
@@ -712,34 +695,11 @@
 "'": 'squote',
 'foo': 'bar',
 }
- self.assertEqual(f'{d["\'"]}', 'squote')
- self.assertEqual(f"{d['\"']}", 'dquote')
-
 self.assertEqual(f'''{d["'"]}''', 'squote')
 self.assertEqual(f"""{d['"']}""", 'dquote')
 
 self.assertEqual(f'{d["foo"]}', 'bar')
 self.assertEqual(f"{d['foo']}", 'bar')
- self.assertEqual(f'{d[\'foo\']}', 'bar')
- self.assertEqual(f"{d[\"foo\"]}", 'bar')
-
- def test_escaped_quotes(self):
- d = {'"': 'a',
- "'": 'b'}
-
- self.assertEqual(fr"{d['\"']}", 'a')
- self.assertEqual(fr'{d["\'"]}', 'b')
- self.assertEqual(fr"{'\"'}", '"')
- self.assertEqual(fr'{"\'"}', "'")
- self.assertEqual(f'{"\\"3"}', '"3')
-
- self.assertAllRaise(SyntaxError, 'f-string: unterminated string',
- [r'''f'{"""\\}' ''', # Backslash at end of expression
- ])
- self.assertAllRaise(SyntaxError, 'unexpected character after line continuation',
- [r"rf'{3\}'",
- ])
-
 
 if __name__ == '__main__':
 unittest.main()
diff --git a/Lib/test/test_tools/test_unparse.py b/Lib/test/test_tools/test_unparse.py
--- a/Lib/test/test_tools/test_unparse.py
+++ b/Lib/test/test_tools/test_unparse.py
@@ -138,10 +138,6 @@
 # See issue 25180
 self.check_roundtrip(r"""f'{f"{0}"*3}'""")
 self.check_roundtrip(r"""f'{f"{y}"*3}'""")
- self.check_roundtrip(r"""f'{f"{\'x\'}"*3}'""")
-
- self.check_roundtrip(r'''f"{r'x' f'{\"s\"}'}"''')
- self.check_roundtrip(r'''f"{r'x'rf'{\"s\"}'}"''')
 
 def test_del_statement(self):
 self.check_roundtrip("del x, y, z")
diff --git a/Lib/traceback.py b/Lib/traceback.py
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -402,7 +402,7 @@
 count += 1
 else:
 if count > 3:
- result.append(f' [Previous line repeated {count-3} more times]\n')
+ result.append(f' [Previous line repeated {count-3} more times]''\n')
 last_file = frame.filename
 last_line = frame.lineno
 last_name = frame.name
@@ -419,7 +419,7 @@
 row.append(' {name} = {value}\n'.format(name=name, value=value))
 result.append(''.join(row))
 if count > 3:
- result.append(f' [Previous line repeated {count-3} more times]\n')
+ result.append(f' [Previous line repeated {count-3} more times]''\n')
 return result
 
 
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,11 @@
 Core and Builtins
 -----------------
 
+- Issue #27921: Disallow backslashes in f-strings. This is a temporary
+ restriction: in beta 2, backslashes will only be disallowed inside
+ the braces (where the expressions are). This is a breaking change
+ from the 3.6 alpha releases.
+ 
 - Issue #27870: A left shift of zero by a large integer no longer attempts
 to allocate large amounts of memory.
 
diff --git a/Python/ast.c b/Python/ast.c
--- a/Python/ast.c
+++ b/Python/ast.c
@@ -4958,6 +4958,16 @@
 return NULL;
 }
 }
+
+ /* Temporary hack: if this is an f-string, no backslashes are allowed. */
+ /* See issue 27921. */
+ if (*fmode && strchr(s, '\\') != NULL) {
+ /* Syntax error. At a later date fix this so it only checks for
+ backslashes within the braces. */
+ ast_error(c, n, "backslashes not allowed in f-strings");
+ return NULL;
+ }
+
 /* Avoid invoking escape decoding routines if possible. */
 rawmode = rawmode || strchr(s, '\\') == NULL;
 if (*bytesmode) {
-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list

AltStyle によって変換されたページ (->オリジナル) /