]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Closes issue 27921: Disallow backslashes anywhere in f-strings. This is a temporary...
authorEric V. Smith <eric@trueblade.com>
Sat, 3 Sep 2016 13:18:34 +0000 (09:18 -0400)
committerEric V. Smith <eric@trueblade.com>
Sat, 3 Sep 2016 13:18:34 +0000 (09:18 -0400)
Lib/test/libregrtest/save_env.py
Lib/test/test_fstring.py
Lib/test/test_tools/test_unparse.py
Lib/traceback.py
Misc/NEWS
Python/ast.c

index 96ad3af8df4cf79c4f91cd3d4dfb72b84844bcfe..eefbc14ad2d18aed6841cdc60741ef9f53a8c7d3 100644 (file)
@@ -280,6 +280,6 @@ class saved_test_environment:
                     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
index 905ae631267a8794664f8726502c05bf8fda2f94..2ba1b2169fea1ae8f5ce5c980b302b5eca1bbe96 100644 (file)
@@ -96,30 +96,6 @@ f'{a * x()}'"""
         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'{a * x()}'"""
                              "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'{a * x()}'"""
                             ["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 @@ f'{a * x()}'"""
     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 @@ f'{a * x()}'"""
         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 @@ f'{a * x()}'"""
     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 @@ f'{a * x()}'"""
         # 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'{a * x()}'"""
                              "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 @@ f'{a * x()}'"""
 
                              # Can't have { or } in a format spec.
                              "f'{3:}>10}'",
-                             r"f'{3:\\}>10}'",
                              "f'{3:}}>10}'",
                              ])
 
@@ -620,10 +607,6 @@ f'{a * x()}'"""
                              "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 @@ f'{a * x()}'"""
              "'": '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()
index d91ade9228d76cdd3ce181c06e05bbb8c6a80412..4a903b6c6884a95bfc1702c916f09fe1562b79e0 100644 (file)
@@ -138,10 +138,6 @@ class UnparseTestCase(ASTTestCase):
         # 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")
index a1cb5fb1ef15822a3248798bc0303a0e89d27de6..6fc643628e4609e590b92d3d379d9713c67854b3 100644 (file)
@@ -402,7 +402,7 @@ class StackSummary(list):
                 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 @@ class StackSummary(list):
                     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
 
 
index 5c077914b70b6a4f7343da38cb981d8b83ec35c6..e8f1421d6d88093f24b1787786551722d6ba32f8 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,11 @@ What's New in Python 3.6.0 beta 1
 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.
 
index b56faddddaa1719df9bc3bdff5c0f6c1a57c444f..0f9c19333d7c22ef8bb473bffa2902a5fde05bc7 100644 (file)
@@ -4958,6 +4958,16 @@ parsestr(struct compiling *c, const node *n, int *bytesmode, int *fmode)
             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) {