[Python-checkins] gh-105938: Emit a SyntaxWarning for escaped braces in an f-string (#105939)

pablogsal webhook-mailer at python.org
Tue Jun 20 08:38:50 EDT 2023


https://github.com/python/cpython/commit/6586cee27f32f0354fe4e77c7b8c6e399329b5e2
commit: 6586cee27f32f0354fe4e77c7b8c6e399329b5e2
branch: main
author: Lysandros Nikolaou <lisandrosnik at gmail.com>
committer: pablogsal <Pablogsal at gmail.com>
date: 2023-06-20T12:38:46Z
summary:

gh-105938: Emit a SyntaxWarning for escaped braces in an f-string (#105939)

files:
M Lib/test/test_fstring.py
M Parser/string_parser.c
M Parser/tokenizer.c

diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py
index 8f6b576b5f785..ad5ac6a2f0432 100644
--- a/Lib/test/test_fstring.py
+++ b/Lib/test/test_fstring.py
@@ -13,6 +13,7 @@
 import types
 import decimal
 import unittest
+import warnings
 from test import support
 from test.support.os_helper import temp_cwd
 from test.support.script_helper import assert_python_failure, assert_python_ok
@@ -904,7 +905,7 @@ def test_backslashes_in_string_part(self):
         self.assertEqual(f'2\x203', '2 3')
         self.assertEqual(f'\x203', ' 3')
 
-        with self.assertWarns(DeprecationWarning):  # invalid escape sequence
+        with self.assertWarns(SyntaxWarning):  # invalid escape sequence
             value = eval(r"f'\{6*7}'")
         self.assertEqual(value, '\\42')
         with self.assertWarns(SyntaxWarning):  # invalid escape sequence
@@ -1037,7 +1038,7 @@ def test_fstring_backslash_before_double_bracket(self):
         ]
         for case, expected_result in deprecated_cases:
             with self.subTest(case=case, expected_result=expected_result):
-                with self.assertWarns(DeprecationWarning):
+                with self.assertWarns(SyntaxWarning):
                     result = eval(case)
                 self.assertEqual(result, expected_result)
         self.assertEqual(fr'\{{\}}', '\\{\\}')
@@ -1046,6 +1047,12 @@ def test_fstring_backslash_before_double_bracket(self):
         self.assertEqual(fr'\}}{1+1}', '\\}2')
         self.assertEqual(fr'{1+1}\}}', '2\\}')
 
+    def test_fstring_backslash_before_double_bracket_warns_once(self):
+        with warnings.catch_warnings(record=True) as w:
+            eval(r"f'\{{'")
+        self.assertEqual(len(w), 1)
+        self.assertEqual(w[0].category, SyntaxWarning)
+
     def test_fstring_backslash_prefix_raw(self):
         self.assertEqual(f'\\', '\\')
         self.assertEqual(f'\\\\', '\\\\')
diff --git a/Parser/string_parser.c b/Parser/string_parser.c
index d4ce33850f7c5..20459e8946349 100644
--- a/Parser/string_parser.c
+++ b/Parser/string_parser.c
@@ -12,6 +12,11 @@ static int
 warn_invalid_escape_sequence(Parser *p, const char *first_invalid_escape, Token *t)
 {
     unsigned char c = *first_invalid_escape;
+    if ((t->type == FSTRING_MIDDLE || t->type == FSTRING_END) && (c == '{' || c == '}')) {  // in this case the tokenizer has already emitted a warning,
+                                                                                            // see tokenizer.c:warn_invalid_escape_sequence
+        return 0;
+    }
+
     int octal = ('4' <= c && c <= '7');
     PyObject *msg =
         octal
@@ -31,7 +36,7 @@ warn_invalid_escape_sequence(Parser *p, const char *first_invalid_escape, Token
     if (PyErr_WarnExplicitObject(category, msg, p->tok->filename,
                                  t->lineno, NULL, NULL) < 0) {
         if (PyErr_ExceptionMatches(category)) {
-            /* Replace the DeprecationWarning exception with a SyntaxError
+            /* Replace the Syntax/DeprecationWarning exception with a SyntaxError
                to get a more accurate error report */
             PyErr_Clear();
 
diff --git a/Parser/tokenizer.c b/Parser/tokenizer.c
index 4f7b1f8114e96..6bdf371a7adf0 100644
--- a/Parser/tokenizer.c
+++ b/Parser/tokenizer.c
@@ -1559,12 +1559,12 @@ warn_invalid_escape_sequence(struct tok_state *tok, int first_invalid_escape_cha
         return -1;
     }
 
-    if (PyErr_WarnExplicitObject(PyExc_DeprecationWarning, msg, tok->filename,
+    if (PyErr_WarnExplicitObject(PyExc_SyntaxWarning, msg, tok->filename,
                                  tok->lineno, NULL, NULL) < 0) {
         Py_DECREF(msg);
 
-        if (PyErr_ExceptionMatches(PyExc_DeprecationWarning)) {
-            /* Replace the DeprecationWarning exception with a SyntaxError
+        if (PyErr_ExceptionMatches(PyExc_SyntaxWarning)) {
+            /* Replace the SyntaxWarning exception with a SyntaxError
                to get a more accurate error report */
             PyErr_Clear();
             return syntaxerror(tok, "invalid escape sequence '\\%c'", (char) first_invalid_escape_char);



More information about the Python-checkins mailing list