Skip to content

Commit 3b9fa19

Browse files
authored
[3.15] gh-157778: Include opening brace line in multiline f-string errors (GH-157779) (#158047)
gh-157778: Include opening brace line in multiline f-string errors (#157779) (cherry picked from commit 1cdd590)
1 parent ffbbdad commit 3b9fa19

3 files changed

Lines changed: 41 additions & 0 deletions

File tree

‎Lib/test/test_fstring.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,34 @@ def test_unterminated_string(self):
619619
r"""f'{("x}'""",
620620
])
621621

622+
def test_unclosed_multiline_replacement_field(self):
623+
for prefix in ('f', 't', 'rf', 'rt'):
624+
for quote in ('"', "'"):
625+
triple = quote * 3
626+
cases = (
627+
# The apparent closing quotes open a string in the field.
628+
('var = "abc"\na = PREFIXQUOTE {var} extern "C" { QUOTE\n'
629+
'b = QUOTE string QUOTE', 2),
630+
# Parentheses and dictionaries inside the field must not
631+
# change which opening brace the diagnostic identifies.
632+
('a = PREFIXQUOTE{\n(QUOTE', 1),
633+
('a = PREFIXQUOTE{\n{0: QUOTE', 1),
634+
# Use the innermost format field or formatted string.
635+
('a = PREFIXQUOTE{0:\n{1\nQUOTE', 2),
636+
('a = f"{\nPREFIXQUOTE{1\nQUOTE', 2),
637+
# Account for parentheses outside the formatted string.
638+
('a = (PREFIXQUOTE{1\nQUOTE', 1),
639+
)
640+
for source, lineno in cases:
641+
source = source.replace('PREFIX', prefix).replace('QUOTE', triple)
642+
with self.subTest(source=source):
643+
with self.assertRaises(SyntaxError) as cm:
644+
compile(source, '<test>', 'exec')
645+
self.assertEqual(
646+
cm.exception.msg,
647+
f"{prefix[-1]}-string: expecting '}}' to close '{{' "
648+
f"on line {lineno}")
649+
622650
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
623651
def test_mismatched_parens(self):
624652
self.assertAllRaise(SyntaxError, r"closing parenthesis '\}' "
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Include the line number of the opening brace in the :exc:`SyntaxError` for
2+
an unclosed f-string or t-string replacement field when a matching quote
3+
on a later line is interpreted as the start of an unterminated string.

‎Parser/lexer/lexer.c‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,16 @@ tok_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, struct t
11441144
tokenizer_mode *the_current_tok = TOK_GET_MODE(tok);
11451145
if (the_current_tok->quote == quote &&
11461146
the_current_tok->quote_size == quote_size) {
1147+
int level = tok->level - the_current_tok->curly_bracket_depth
1148+
+ the_current_tok->curly_bracket_expr_start_depth;
1149+
assert(level >= 0 && level < tok->level);
1150+
assert(tok->parenstack[level] == '{');
1151+
int lineno = tok->parenlinenostack[level];
1152+
if (lineno != tok->lineno) {
1153+
return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok,
1154+
"%c-string: expecting '}' to close '{' on line %d",
1155+
TOK_GET_STRING_PREFIX(tok), lineno));
1156+
}
11471157
return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok,
11481158
"%c-string: expecting '}'", TOK_GET_STRING_PREFIX(tok)));
11491159
}

0 commit comments

Comments
 (0)