8000 Merge pull request #52 from isidentical/last-fstring-status · isidentical/cpython@a0edfb1 · GitHub
[go: up one dir, main page]

Skip to content

Commit a0edfb1

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

File tree

3 files changed

+44
-15
lines changed

3 files changed

+44
-15
lines changed

Lib/test/test_fstring.py

Lines 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

Lines 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

Lines 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 F438 _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