Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a0edfb1

Browse filesBrowse files
authored
Merge pull request python#52 from isidentical/last-fstring-status
2 parents 0762910 + 00f8bda commit a0edfb1
Copy full SHA for a0edfb1

File tree

Expand file treeCollapse file tree

3 files changed

+44
-15
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+44
-15
lines changed

‎Lib/test/test_fstring.py

Copy file name to clipboardExpand all lines: Lib/test/test_fstring.py
+21-10Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -481,31 +481,31 @@ def test_literal(self):
481481
self.assertEqual(f' ', ' ')
482482

483483
def test_unterminated_string(self):
484-
self.assertAllRaise(SyntaxError, 'f-string: unterminated string',
484+
self.assertAllRaise(SyntaxError, 'unterminated string',
485485
[r"""f'{"x'""",
486486
r"""f'{"x}'""",
487487
r"""f'{("x'""",
488488
r"""f'{("x}'""",
489489
])
490490

491491
def test_mismatched_parens(self):
492-
self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' "
492+
self.assertAllRaise(SyntaxError, r"closing parenthesis '\}' "
493493
r"does not match opening parenthesis '\('",
494494
["f'{((}'",
495495
])
496-
self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\)' "
496+
self.assertAllRaise(SyntaxError, r"closing parenthesis '\)' "
497497
r"does not match opening parenthesis '\['",
498498
["f'{a[4)}'",
499499
])
500-
self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\]' "
500+
self.assertAllRaise(SyntaxError, r"closing parenthesis '\]' "
501501
r"does not match opening parenthesis '\('",
502502
["f'{a(4]}'",
503503
])
504-
self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' "
504+
self.assertAllRaise(SyntaxError, r"closing parenthesis '\}' "
505505
r"does not match opening parenthesis '\['",
506506
["f'{a[4}'",
507507
])
508-
self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' "
508+
self.assertAllRaise(SyntaxError, r"closing parenthesis '\}' "
509509
r"does not match opening parenthesis '\('",
510510
["f'{a(4}'",
511511
])
@@ -573,7 +573,7 @@ def test_compile_time_concat(self):
573573
self.assertEqual(f'' '' f'', '')
574574
self.assertEqual(f'' '' f'' '', '')
575575

576-
self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
576+
self.assertAllRaise(SyntaxError, "expecting '}'",
577577
["f'{3' f'}'", # can't concat to get a valid f-string
578578
])
579579

@@ -729,7 +729,7 @@ def test_parens_in_expressions(self):
729729
# are added around it. But we shouldn't go from an invalid
730730
# expression to a valid one. The added parens are just
731731
# supposed to allow whitespace (including newlines).
732-
self.assertAllRaise(SyntaxError, 'f-string: invalid syntax',
732+
self.assertAllRaise(SyntaxError, 'invalid syntax',
733733
["f'{,}'",
734734
"f'{,}'", # this is (,), which is an error
735735
])
@@ -786,7 +786,7 @@ def test_backslashes_in_string_part(self):
786786
self.assertEqual(f'2\x203', '2 3')
787787
self.assertEqual(f'\x203', ' 3')
788788

789-
with self.assertWarns(SyntaxWarning): # invalid escape sequence
789+
with self.assertWarns(DeprecationWarning): # invalid escape sequence
790790
value = eval(r"f'\{6*7}'")
791791
self.assertEqual(value, '\\42')
792792
self.assertEqual(f'\\{6*7}', '\\42')
@@ -1091,6 +1091,11 @@ def test_conversions(self):
10911091
self.assertEqual(f'{"a"!r}', "'a'")
10921092
self.assertEqual(f'{"a"!a}', "'a'")
10931093

1094+
# Conversions can have trailing whitespace after them since it
1095+
# does not provide any significance
1096+
self.assertEqual(f"{3!s }", "3")
1097+
self.assertEqual(f'{3.14!s :10.10}', '3.14 ')
1098+
10941099
# Not a conversion.
10951100
self.assertEqual(f'{"a!r"}', "a!r")
10961101

@@ -1109,12 +1114,18 @@ def test_conversions(self):
11091114
"f'{3!:}'",
11101115
])
11111116

1112-
for conv in 'g', 'A', '3', 'G', '!', ' s', 's ', ' s ', 'ä', 'ɐ', 'ª':
1117+
for conv in 'g', 'A', '3', 'G', '!', 'ä', 'ɐ', 'ª':
11131118
self.assertAllRaise(SyntaxError,
11141119
"f-string: invalid conversion character %r: "
11151120
"expected 's', 'r', or 'a'" % conv,
11161121
["f'{3!" + conv + "}'"])
11171122

1123+
for conv in ' s', ' s ':
1124+
self.assertAllRaise(SyntaxError,
1125+
"f-string: conversion type must come right after the"
1126+
" exclamanation mark",
1127+
["f'{3!" + conv + "}'"])
1128+
11181129
self.assertAllRaise(SyntaxError,
11191130
"f-string: invalid conversion character 'ss': "
11201131
"expected 's', 'r', or 'a'",

‎Parser/action_helpers.c

Copy file name to clipboardExpand all lines: Parser/action_helpers.c
+3-2Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -970,7 +970,7 @@ _PyPegen_check_fstring_conversion(Parser *p, Token* symbol, expr_ty conv) {
970970
if (symbol->lineno != conv->lineno || symbol->end_col_offset != conv->col_offset) {
971971
RAISE_SYNTAX_ERROR_KNOWN_RANGE(
972972
symbol, conv,
973-
"conversion type must come right after the exclamanation mark"
973+
"f-string: conversion type must come right after the exclamanation mark"
974974
);
975975
return 1;
976976
}
@@ -1391,7 +1391,8 @@ expr_ty _PyPegen_formatted_value(Parser *p, expr_ty expression, Token *debug, ex
13911391
if (PyUnicode_GET_LENGTH(conversion->v.Name.id) > 1 ||
13921392
!(first == 's' || first == 'r' || first == 'a')) {
13931393
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(conversion,
1394-
"f-string: invalid conversion character: expected 's', 'r', or 'a'");
1394+
"f-string: invalid conversion character %R: expected 's', 'r', or 'a'",
1395+
conversion->v.Name.id);
13951396
return NULL;
13961397
}
13971398

‎Parser/tokenizer.c

Copy file name to clipboardExpand all lines: Parser/tokenizer.c
+20-3Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2378,6 +2378,9 @@ tok_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, struct t
23782378
case ']':
23792379
case '}':
23802380
if (!tok->level) {
2381+
if (tok->tok_mode_stack_index > 0 && !current_tok->bracket_stack && c == '}') {
2382+
return MAKE_TOKEN(syntaxerror(tok, "f-string: single '}' is not allowed"));
2383+
}
23812384
return MAKE_TOKEN(syntaxerror(tok, "unmatched '%c'", c));
23822385
}
23832386
tok->level--;
@@ -2386,6 +2389,18 @@ tok_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, struct t
23862389
(opening == '[' && c == ']') ||
23872390
(opening == '{' && c == '}')))
23882391
{
2392+
/* If the opening bracket belongs to an f-string's expression
2393+
part (e.g. f"{)}") and the closing bracket is an arbitrary
2394+
nested expression, then instead of matching a different
2395+
syntactical construct with it; we'll throw an unmatched
2396+
parentheses error. */
2397+
if (tok->tok_mode_stack_index > 0 && opening == '{') {
2398+
assert(current_tok->bracket_stack >= 0);
2399+
int previous_bracket = current_tok->bracket_stack - 1;
2400+
if (previous_bracket == current_tok->bracket_mark[current_tok->bracket_mark_index]) {
2401+
return MAKE_TOKEN(syntaxerror(tok, "f-string: unmatched '%c'", c));
2402+
}
2403+
}
23892404
if (tok->parenlinenostack[tok->level] != tok->lineno) {
23902405
return MAKE_TOKEN(syntaxerror(tok,
23912406
"closing parenthesis '%c' does not match "
@@ -2427,6 +2442,9 @@ tok_get_fstring_mode(struct tok_state *tok, tokenizer_mode* current_tok, struct
24272442
{
24282443
const char *p_start = NULL;
24292444
const char *p_end = NULL;
2445+
int end_quote_size = 0;
2446+
int unicode_escape = 0;
2447+
24302448
tok->start = tok->cur;
24312449
tok->first_lineno = tok->lineno;
24322450
tok->starting_col_offset = tok->col_offset;
@@ -2467,9 +2485,8 @@ tok_get_fstring_mode(struct tok_state *tok, tokenizer_mode* current_tok, struct
24672485
tok->tok_mode_stack_index--;
24682486
return MAKE_TOKEN(FSTRING_END);
24692487

2470-
f_string_middle:
2471-
int end_quote_size = 0;
2472-
int unicode_escape = 0;
2488+
f_string_middle:
2489+
24732490
while (end_quote_size != current_tok->f_string_quote_size) {
24742491
int c = tok_nextc(tok);
24752492
if (c == EOF || (current_tok->f_string_quote_size == 1 && c == '\n')) {

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.