Skip to content

Commit

Permalink
pythongh-105938: Emit a SyntaxWarning for escaped braces in an f-string
Browse files Browse the repository at this point in the history
  • Loading branch information
lysnikolaou committed Jun 20, 2023
1 parent 155577d commit e25dbb3
Show file tree
Hide file tree
Showing 3 changed files with 18 additions and 6 deletions.
11 changes: 9 additions & 2 deletions Lib/test/test_fstring.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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'\{{\}}', '\\{\\}')
Expand All @@ -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'\\\\', '\\\\')
Expand Down
7 changes: 6 additions & 1 deletion Parser/string_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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();

Expand Down
6 changes: 3 additions & 3 deletions Parser/tokenizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit e25dbb3

Please sign in to comment.