Skip to content

Commit

Permalink
Merge pull request python#62 from isidentical/opinioted-fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
pablogsal authored Apr 5, 2023
2 parents 4790764 + 51f80f4 commit 45903ad
Show file tree
Hide file tree
Showing 3 changed files with 836 additions and 528 deletions.
12 changes: 8 additions & 4 deletions Grammar/python.gram
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ yield_stmt[stmt_ty]: y=yield_expr { _PyAST_Expr(y, EXTRA) }

assert_stmt[stmt_ty]: 'assert' a=expression b=[',' z=expression { z }] { _PyAST_Assert(a, b, EXTRA) }

import_stmt[stmt_ty]:
import_stmt[stmt_ty]:
| invalid_import
| import_name
| import_from
Expand Down Expand Up @@ -415,8 +415,8 @@ try_stmt[stmt_ty]:
| invalid_try_stmt
| 'try' &&':' b=block f=finally_block { _PyAST_Try(b, NULL, NULL, f, EXTRA) }
| 'try' &&':' b=block ex[asdl_excepthandler_seq*]=except_block+ el=[else_block] f=[finally_block] { _PyAST_Try(b, ex, el, f, EXTRA) }
| 'try' &&':' b=block ex[asdl_excepthandler_seq*]=except_star_block+ el=[else_block] f=[finally_block] {
CHECK_VERSION(stmt_ty, 11, "Exception groups are",
| 'try' &&':' b=block ex[asdl_excepthandler_seq*]=except_star_block+ el=[else_block] f=[finally_block] {
CHECK_VERSION(stmt_ty, 11, "Exception groups are",
_PyAST_TryStar(b, ex, el, f, EXTRA)) }


Expand Down Expand Up @@ -1263,7 +1263,7 @@ invalid_group:
invalid_import:
| a='import' dotted_name 'from' dotted_name {
RAISE_SYNTAX_ERROR_STARTING_FROM(a, "Did you mean to use 'from ... import ...' instead?") }

invalid_import_from_targets:
| import_from_as_names ',' NEWLINE {
RAISE_SYNTAX_ERROR("trailing comma not allowed without surrounding parentheses") }
Expand Down Expand Up @@ -1362,3 +1362,7 @@ invalid_replacement_field:
| '{' a=':' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "f-string: expression required before ':'") }
| '{' a='!' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "f-string: expression required before '!'") }
| '{' a='}' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "f-string: empty expression not allowed") }
| '{' (yield_expr | star_expressions) "="? invalid_conversion_character
invalid_conversion_character:
| a="!" &(':'|'}') { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "f-string: missed conversion character") }
| a="!" !NAME { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "f-string: invalid conversion character") }
114 changes: 95 additions & 19 deletions Lib/test/test_fstring.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,50 @@ def test_ast_line_numbers_with_parentheses(self):

expr = """
x = (
u'wat',
u"wat",
b'wat',
b"wat",
f'wat',
f"wat",
)
y = (
u'''wat''',
u\"\"\"wat\"\"\",
b'''wat''',
b\"\"\"wat\"\"\",
f'''wat''',
f\"\"\"wat\"\"\",
)
"""
t = ast.parse(expr)
self.assertEqual(type(t), ast.Module)
self.assertEqual(len(t.body), 2)
x, y = t.body

# Check the single quoted string offsets first.
offsets = [
(elt.col_offset, elt.end_col_offset)
for elt in x.value.elts
]
self.assertTrue(all(
offset == (4, 10)
for offset in offsets
))

# Check the triple quoted string offsets.
offsets = [
(elt.col_offset, elt.end_col_offset)
for elt in y.value.elts
]
self.assertTrue(all(
offset == (4, 14)
for offset in offsets
))

expr = """
x = (
'PERL_MM_OPT', (
f'wat'
f'some_string={f(x)} '
Expand Down Expand Up @@ -444,7 +488,11 @@ def test_ast_line_numbers_with_parentheses(self):
self.assertEqual(wat2.lineno, 5)
self.assertEqual(wat2.end_lineno, 6)
self.assertEqual(wat2.col_offset, 32)
self.assertEqual(wat2.end_col_offset, 18)
# wat ends at the offset 17, but the whole f-string
# ends at the offset 18 (since the quote is part of the
# f-string but not the wat string)
self.assertEqual(wat2.end_col_offset, 17)
self.assertEqual(fstring.end_col_offset, 18)

def test_docstring(self):
def f():
Expand Down Expand Up @@ -578,8 +626,14 @@ def test_compile_time_concat(self):
self.assertEqual(f'' '' f'', '')
self.assertEqual(f'' '' f'' '', '')

# This is not really [f'{'] + [f'}'] since we treat the inside
# of braces as a purely new context, so it is actually f'{ and
# then eval(' f') (a valid expression) and then }' which would
# constitute a valid f-string.
self.assertEqual(f'{' f'}', ' f')

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

def test_comments(self):
Expand Down Expand Up @@ -743,10 +797,6 @@ def test_parens_in_expressions(self):
["f'{3)+(4}'",
])

self.assertAllRaise(SyntaxError, 'unterminated string literal',
["f'{\n}'",
])

def test_newlines_before_syntax_error(self):
self.assertAllRaise(SyntaxError, "invalid syntax",
["f'{.}'", "\nf'{.}'", "\n\nf'{.}'"])
Expand Down Expand Up @@ -825,18 +875,39 @@ def test_misformed_unicode_character_name(self):
r"'\N{GREEK CAPITAL LETTER DELTA'",
])

def test_no_backslashes_in_expression_part(self):
self.assertAllRaise(SyntaxError, 'f-string expression part cannot include a backslash',
[r"f'{\'a\'}'",
r"f'{\t3}'",
r"f'{\}'",
r"rf'{\'a\'}'",
r"rf'{\t3}'",
r"rf'{\}'",
r"""rf'{"\N{LEFT CURLY BRACKET}"}'""",
r"f'{\n}'",
def test_backslashes_in_expression_part(self):
self.assertEqual(f"{(
1 +
2
)}", "3")

self.assertEqual("\N{LEFT CURLY BRACKET}", '{')
self.assertEqual(f'{"\N{LEFT CURLY BRACKET}"}', '{')
self.assertEqual(rf'{"\N{LEFT CURLY BRACKET}"}', '{')

self.assertAllRaise(SyntaxError, 'empty expression not allowed',
["f'{\n}'",
])

def test_invalid_backslashes_inside_fstring_context(self):
# All of these variations are invalid python syntax,
# so they are also invalid in f-strings as well.
cases = [
formatting.format(expr=expr)
for formatting in [
"{expr}",
"f'{{{expr}}}'",
"rf'{{{expr}}}'",
]
for expr in [
r"\'a\'",
r"\t3",
r"\\"[0],
]
]
self.assertAllRaise(SyntaxError, 'unexpected character after line continuation',
cases)

def test_no_escapes_for_braces(self):
"""
Only literal curly braces begin an expression.
Expand Down Expand Up @@ -1120,11 +1191,16 @@ def test_conversions(self):
"f'{3!:}'",
])

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

for conv_non_identifier in '3', '!':
self.assertAllRaise(SyntaxError,
"f-string: invalid conversion character",
["f'{3!" + conv_non_identifier + "}'"])

for conv in ' s', ' s ':
self.assertAllRaise(SyntaxError,
Expand Down
Loading

0 comments on commit 45903ad

Please sign in to comment.